view en/ch02-tour-merge.xml @ 752:6b1577ef5135

Update Chinese translation
author Dongsheng Song <dongsheng.song@gmail.com>
date Fri, 20 Mar 2009 17:17:55 +0800
parents 7e7c47481e4f
children 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>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:
-->