Mercurial > hgbook
view en/ch02-tour-merge.xml @ 700:de5b1753e865
Merge
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Fri, 27 Mar 2009 11:55:18 -0500 |
parents | 0b45854f0b7b |
children | 4e23c220d1b0 1c13ed2130a7 |
line wrap: on
line source
<!-- vim: set filetype=docbkxml shiftwidth=2 autoindent expandtab tw=77 : --> <chapter id="chap:tour-merge"> <?dbhtml filename="a-tour-of-mercurial-merging-work.html"?> <title>A tour of Mercurial: merging work</title> <para id="x_338">We've now covered cloning a repository, making changes in a repository, and pulling or pushing changes from one repository into another. Our next step is <emphasis>merging</emphasis> changes from separate repositories.</para> <sect1> <title>Merging streams of work</title> <para id="x_339">Merging is a fundamental part of working with a distributed revision control tool.</para> <itemizedlist> <listitem><para id="x_33a">Alice and Bob each have a personal copy of a repository for a project they're collaborating on. Alice fixes a bug in her repository; Bob adds a new feature in his. They want the shared repository to contain both the bug fix and the new feature.</para> </listitem> <listitem><para id="x_33b">I frequently work on several different tasks for a single project at once, each safely isolated in its own repository. Working this way means that I often need to merge one piece of my own work with another.</para> </listitem></itemizedlist> <para id="x_33c">Because merging is such a common thing to need to do, Mercurial makes it easy. Let's walk through the process. We'll begin by cloning yet another repository (see how often they spring up?) and making a change in it.</para> &interaction.tour.merge.clone; <para id="x_33d">We should now have two copies of <filename>hello.c</filename> with different contents. The histories of the two repositories have also diverged, as illustrated in <xref linkend="fig:tour-merge:sep-repos"/>.</para> &interaction.tour.merge.cat; <figure id="fig:tour-merge:sep-repos"> <title>Divergent recent histories of the <filename class="directory">my-hello</filename> and <filename class="directory">my-new-hello</filename> repositories</title> <mediaobject> <imageobject><imagedata fileref="figs/tour-merge-sep-repos.png"/></imageobject> <textobject><phrase>XXX add text</phrase></textobject> </mediaobject> </figure> <para id="x_33f">We already know that pulling changes from our <filename class="directory">my-hello</filename> repository will have no effect on the working directory.</para> &interaction.tour.merge.pull; <para id="x_340">However, the <command role="hg-cmd">hg pull</command> command says something about <quote>heads</quote>.</para> <sect2> <title>Head changesets</title> <para id="x_341">A head is a change that has no descendants, or children, as they're also known. The tip revision is thus a head, because the newest revision in a repository doesn't have any children, but a repository can contain more than one head.</para> <figure id="fig:tour-merge:pull"> <title>Repository contents after pulling from <filename class="directory">my-hello</filename> into <filename class="directory">my-new-hello</filename></title> <mediaobject> <imageobject> <imagedata fileref="figs/tour-merge-pull.png"/> </imageobject> <textobject><phrase>XXX add text</phrase></textobject> </mediaobject> </figure> <para id="x_343">In <xref linkend="fig:tour-merge:pull"/>, you can see the effect of the pull from <filename class="directory">my-hello</filename> into <filename class="directory">my-new-hello</filename>. The history that was already present in <filename class="directory">my-new-hello</filename> is untouched, but a new revision has been added. By referring to <xref linkend="fig:tour-merge:sep-repos"/>, we can see that the <emphasis>changeset ID</emphasis> remains the same in the new repository, but the <emphasis>revision number</emphasis> has changed. (This, incidentally, is a fine example of why it's not safe to use revision numbers when discussing changesets.) We can view the heads in a repository using the <command role="hg-cmd">hg heads</command> command.</para> &interaction.tour.merge.heads; </sect2> <sect2> <title>Performing the merge</title> <para id="x_344">What happens if we try to use the normal <command role="hg-cmd">hg update</command> command to update to the new tip?</para> &interaction.tour.merge.update; <para id="x_345">Mercurial is telling us that the <command role="hg-cmd">hg update</command> command won't do a merge; it won't update the working directory when it thinks we might be wanting to do a merge, unless we force it to do so. Instead, we use the <command role="hg-cmd">hg merge</command> command to merge the two heads.</para> &interaction.tour.merge.merge; <figure id="fig:tour-merge:merge"> <title>Working directory and repository during merge, and following commit</title> <mediaobject> <imageobject> <imagedata fileref="figs/tour-merge-merge.png"/> </imageobject> <textobject><phrase>XXX add text</phrase></textobject> </mediaobject> </figure> <para id="x_347">This updates the working directory so that it contains changes from <emphasis>both</emphasis> heads, which is reflected in both the output of <command role="hg-cmd">hg parents</command> and the contents of <filename>hello.c</filename>.</para> &interaction.tour.merge.parents; </sect2> <sect2> <title>Committing the results of the merge</title> <para id="x_348">Whenever we've done a merge, <command role="hg-cmd">hg parents</command> will display two parents until we <command role="hg-cmd">hg commit</command> the results of the merge.</para> &interaction.tour.merge.commit; <para id="x_349">We now have a new tip revision; notice that it has <emphasis>both</emphasis> of our former heads as its parents. These are the same revisions that were previously displayed by <command role="hg-cmd">hg parents</command>.</para> &interaction.tour.merge.tip; <para id="x_34a">In <xref linkend="fig:tour-merge:merge"/>, you can see a representation of what happens to the working directory during the merge, and how this affects the repository when the commit happens. During the merge, the working directory has two parent changesets, and these become the parents of the new changeset.</para> </sect2> </sect1> <sect1> <title>Merging conflicting changes</title> <para id="x_34b">Most merges are simple affairs, but sometimes you'll find yourself merging changes where each modifies the same portions of the same files. Unless both modifications are identical, this results in a <emphasis>conflict</emphasis>, where you have to decide how to reconcile the different changes into something coherent.</para> <figure id="fig:tour-merge:conflict"> <title>Conflicting changes to a document</title> <mediaobject> <imageobject><imagedata fileref="figs/tour-merge-conflict.png"/></imageobject> <textobject><phrase>XXX add text</phrase></textobject> </mediaobject> </figure> <para id="x_34d"><xref linkend="fig:tour-merge:conflict"/> illustrates an instance of two conflicting changes to a document. We started with a single version of the file; then we made some changes; while someone else made different changes to the same text. Our task in resolving the conflicting changes is to decide what the file should look like.</para> <para id="x_34e">Mercurial doesn't have a built-in facility for handling conflicts. Instead, it runs an external program called <command>hgmerge</command>. This is a shell script that is bundled with Mercurial; you can change it to behave however you please. What it does by default is try to find one of several different merging tools that are likely to be installed on your system. It first tries a few fully automatic merging tools; if these don't succeed (because the resolution process requires human guidance) or aren't present, the script tries a few different graphical merging tools.</para> <para id="x_34f">It's also possible to get Mercurial to run another program or script instead of <command>hgmerge</command>, by setting the <envar>HGMERGE</envar> environment variable to the name of your preferred program.</para> <sect2> <title>Using a graphical merge tool</title> <para id="x_350">My preferred graphical merge tool is <command>kdiff3</command>, which I'll use to describe the features that are common to graphical file merging tools. You can see a screenshot of <command>kdiff3</command> in action in <xref linkend="fig:tour-merge:kdiff3"/>. The kind of merge it is performing is called a <emphasis>three-way merge</emphasis>, because there are three different versions of the file of interest to us. The tool thus splits the upper portion of the window into three panes:</para> <itemizedlist> <listitem><para id="x_351">At the left is the <emphasis>base</emphasis> version of the file, i.e. the most recent version from which the two versions we're trying to merge are descended.</para> </listitem> <listitem><para id="x_352">In the middle is <quote>our</quote> version of the file, with the contents that we modified.</para> </listitem> <listitem><para id="x_353">On the right is <quote>their</quote> version of the file, the one that from the changeset that we're trying to merge with.</para> </listitem></itemizedlist> <para id="x_354">In the pane below these is the current <emphasis>result</emphasis> of the merge. Our task is to replace all of the red text, which indicates unresolved conflicts, with some sensible merger of the <quote>ours</quote> and <quote>theirs</quote> versions of the file.</para> <para id="x_355">All four of these panes are <emphasis>locked together</emphasis>; if we scroll vertically or horizontally in any of them, the others are updated to display the corresponding sections of their respective files.</para> <figure id="fig:tour-merge:kdiff3"> <title>Using <command>kdiff3</command> to merge versions of a file</title> <mediaobject> <imageobject> <imagedata fileref="figs/kdiff3.png"/></imageobject> <textobject> <phrase>XXX add text</phrase> </textobject> </mediaobject> </figure> <para id="x_357">For each conflicting portion of the file, we can choose to resolve the conflict using some combination of text from the base version, ours, or theirs. We can also manually edit the merged file at any time, in case we need to make further modifications.</para> <para id="x_358">There are <emphasis>many</emphasis> file merging tools available, too many to cover here. They vary in which platforms they are available for, and in their particular strengths and weaknesses. Most are tuned for merging files containing plain text, while a few are aimed at specialised file formats (generally XML).</para> </sect2> <sect2> <title>A worked example</title> <para id="x_359">In this example, we will reproduce the file modification history of <xref linkend="fig:tour-merge:conflict"/> above. Let's begin by creating a repository with a base version of our document.</para> &interaction.tour-merge-conflict.wife; <para id="x_35a">We'll clone the repository and make a change to the file.</para> &interaction.tour-merge-conflict.cousin; <para id="x_35b">And another clone, to simulate someone else making a change to the file. (This hints at the idea that it's not all that unusual to merge with yourself when you isolate tasks in separate repositories, and indeed to find and resolve conflicts while doing so.)</para> &interaction.tour-merge-conflict.son; <para id="x_35c">Having created two different versions of the file, we'll set up an environment suitable for running our merge.</para> &interaction.tour-merge-conflict.pull; <para id="x_35d">In this example, I won't use Mercurial's normal <command>hgmerge</command> program to do the merge, because it would drop my nice automated example-running tool into a graphical user interface. Instead, I'll set <envar>HGMERGE</envar> to tell Mercurial to use the non-interactive <command>merge</command> command. This is bundled with many Unix-like systems. If you're following this example on your computer, don't bother setting <envar>HGMERGE</envar>.</para> <para id="x_35e"><emphasis role="bold">XXX FIX THIS EXAMPLE.</emphasis></para> &interaction.tour-merge-conflict.merge; <para id="x_35f">Because <command>merge</command> can't resolve the conflicting changes, it leaves <emphasis>merge markers</emphasis> inside the file that has conflicts, indicating which lines have conflicts, and whether they came from our version of the file or theirs.</para> <para id="x_360">Mercurial can tell from the way <command>merge</command> exits that it wasn't able to merge successfully, so it tells us what commands we'll need to run if we want to redo the merging operation. This could be useful if, for example, we were running a graphical merge tool and quit because we were confused or realised we had made a mistake.</para> <para id="x_361">If automatic or manual merges fail, there's nothing to prevent us from <quote>fixing up</quote> the affected files ourselves, and committing the results of our merge:</para> &interaction.tour-merge-conflict.commit; </sect2> </sect1> <sect1 id="sec:tour-merge:fetch"> <title>Simplifying the pull-merge-commit sequence</title> <para id="x_362">The process of merging changes as outlined above is straightforward, but requires running three commands in sequence.</para> <programlisting>hg pull hg merge hg commit -m 'Merged remote changes'</programlisting> <para id="x_363">In the case of the final commit, you also need to enter a commit message, which is almost always going to be a piece of uninteresting <quote>boilerplate</quote> text.</para> <para id="x_364">It would be nice to reduce the number of steps needed, if this were possible. Indeed, Mercurial is distributed with an extension called <literal role="hg-ext">fetch</literal> that does just this.</para> <para id="x_365">Mercurial provides a flexible extension mechanism that lets people extend its functionality, while keeping the core of Mercurial small and easy to deal with. Some extensions add new commands that you can use from the command line, while others work <quote>behind the scenes,</quote> for example adding capabilities to the server.</para> <para id="x_366">The <literal role="hg-ext">fetch</literal> extension adds a new command called, not surprisingly, <command role="hg-cmd">hg fetch</command>. This extension acts as a combination of <command role="hg-cmd">hg pull</command>, <command role="hg-cmd">hg update</command> and <command role="hg-cmd">hg merge</command>. It begins by pulling changes from another repository into the current repository. If it finds that the changes added a new head to the repository, it begins a merge, then commits the result of the merge with an automatically-generated commit message. If no new heads were added, it updates the working directory to the new tip changeset.</para> <para id="x_367">Enabling the <literal role="hg-ext">fetch</literal> extension is easy. Edit your <filename role="special">.hgrc</filename>, and either go to the <literal role="rc-extensions">extensions</literal> section or create an <literal role="rc-extensions">extensions</literal> section. Then add a line that simply reads <quote><literal>fetch </literal></quote>.</para> <programlisting>[extensions] fetch =</programlisting> <para id="x_368">(Normally, on the right-hand side of the <quote><literal>=</literal></quote> would appear the location of the extension, but since the <literal role="hg-ext">fetch</literal> extension is in the standard distribution, Mercurial knows where to search for it.)</para> </sect1> </chapter> <!-- local variables: sgml-parent-document: ("00book.xml" "book" "chapter") end: -->