Mercurial > hgbook
diff en/ch02-tour-merge.xml @ 749:7e7c47481e4f
Oops, this is the real merge for my hg's oddity
author | Dongsheng Song <dongsheng.song@gmail.com> |
---|---|
date | Fri, 20 Mar 2009 16:43:35 +0800 |
parents | en/ch03-tour-merge.xml@d0160b0b1a9e |
children | 1c13ed2130a7 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/ch02-tour-merge.xml Fri Mar 20 16:43:35 2009 +0800 @@ -0,0 +1,401 @@ +<!-- 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>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>Merging is a fundamental part of working with a distributed + revision control tool.</para> + <itemizedlist> + <listitem><para>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>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>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>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 figure <xref endterm="fig.tour-merge.sep-repos.caption" + linkend="fig.tour-merge.sep-repos"/>.</para> + + &interaction.tour.merge.cat; + + <informalfigure id="fig.tour-merge.sep-repos"> + <mediaobject> + <imageobject><imagedata fileref="images/tour-merge-sep-repos.png"/></imageobject> + <textobject><phrase>XXX add text</phrase></textobject> + <caption><para id="fig.tour-merge.sep-repos.caption">Divergent recent + histories of the <filename + class="directory">my-hello</filename> and <filename + class="directory">my-new-hello</filename> + repositories</para></caption> + </mediaobject> + </informalfigure> + + <para>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>However, the <command role="hg-cmd">hg pull</command> + command says something about <quote>heads</quote>.</para> + + <sect2> + <title>Head changesets</title> + + <para>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> + + <informalfigure id="fig.tour-merge.pull"> + <mediaobject> + <imageobject><imagedata fileref="images/tour-merge-pull.png"/></imageobject> + <textobject><phrase>XXX add text</phrase></textobject> + <caption><para id="fig.tour-merge.pull.caption">Repository contents after + pulling from <filename class="directory">my-hello</filename> into + <filename class="directory">my-new-hello</filename></para></caption> + </mediaobject> + </informalfigure> + + <para>In figure <xref endterm="fig.tour-merge.pull.caption" + 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 figure <xref + endterm="fig.tour-merge.sep-repos.caption" + 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>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>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; + + <informalfigure id="fig.tour-merge.merge"> + <mediaobject> + <imageobject><imagedata fileref="images/tour-merge-merge.png"/></imageobject> + <textobject><phrase>XXX add text</phrase></textobject> + <caption><para id="fig.tour-merge.merge.caption">Working directory and + repository during merge, and following commit</para></caption> + </mediaobject> + </informalfigure> + + <para>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>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>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>In figure <xref endterm="fig.tour-merge.merge.caption" + 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>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> + + <informalfigure id="fig.tour-merge.conflict"> + <mediaobject> + <imageobject><imagedata fileref="images/tour-merge-conflict.png"/> + </imageobject> + <textobject><phrase>XXX add text</phrase></textobject> + <caption><para id="fig.tour-merge.conflict.caption">Conflicting + changes to a document</para></caption> + </mediaobject> + </informalfigure> + + <para>Figure <xref endterm="fig.tour-merge.conflict.caption" + 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>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>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>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 + figure <xref endterm="fig.tour-merge.kdiff3.caption" + 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>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>In the middle is <quote>our</quote> version of + the file, with the contents that we modified.</para> + </listitem> + <listitem><para>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>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>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> + + <informalfigure id="fig.tour-merge.kdiff3"> + <mediaobject> + <imageobject><imagedata width="100%" fileref="images/kdiff3.png"/> + </imageobject> + <textobject><phrase>XXX add text</phrase></textobject> + <caption><para id="fig.tour-merge.kdiff3.caption">Using + <command>kdiff3</command> to merge versions of a file</para> + </caption> + </mediaobject> + </informalfigure> + + <para>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>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>In this example, we will reproduce the file modification + history of figure <xref endterm="fig.tour-merge.conflict.caption" + 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>We'll clone the repository and make a change to the + file.</para> + + &interaction.tour-merge-conflict.cousin; + + <para>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>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>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><emphasis role="bold">XXX FIX THIS + EXAMPLE.</emphasis></para> + + &interaction.tour-merge-conflict.merge; + + <para>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>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>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>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>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>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>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>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>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>(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: +-->