Mercurial > hgbook
changeset 103:5b80c922ebdd
More merge content.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Thu, 19 Oct 2006 15:18:07 -0700 |
parents | ff9dc8bc2a8b |
children | 32bf9a5f22c0 |
files | en/99defs.tex en/Makefile en/examples/run-example en/examples/tour-merge-conflict en/kdiff3.png en/tour-merge-conflict.svg en/tour-merge.tex |
diffstat | 7 files changed, 440 insertions(+), 26 deletions(-) [+] |
line wrap: on
line diff
--- a/en/99defs.tex Wed Oct 18 15:47:04 2006 -0700 +++ b/en/99defs.tex Thu Oct 19 15:18:07 2006 -0700 @@ -108,7 +108,7 @@ % Graphics inclusion. \ifpdf - \newcommand{\grafix}[1]{\includegraphics{#1.pdf}} + \newcommand{\grafix}[1]{\includegraphics{#1}} \else \newcommand{\grafix}[1]{\includegraphics{#1.png}} \fi
--- a/en/Makefile Wed Oct 18 15:47:04 2006 -0700 +++ b/en/Makefile Thu Oct 19 15:18:07 2006 -0700 @@ -19,11 +19,15 @@ tour-merge.tex image-sources := \ + kdiff3.png \ mq-stack.svg \ tour-history.svg \ - tour-merge-sep-repos.svg \ + tour-merge-conflict.svg \ + tour-merge-merge.svg \ tour-merge-pull.svg \ - tour-merge-merge.svg + tour-merge-sep-repos.svg + +image-svg := $(filter %.svg,$(image-sources)) example-sources := \ daily.files \ @@ -38,7 +42,8 @@ mq.tutorial \ template.simple \ template.svnstyle \ - tour + tour \ + tour-merge-conflict latex-options = \ -interaction batchmode \ @@ -134,5 +139,7 @@ echo -n $(hg_id) > build_id.tex clean: - rm -rf beta html pdf *.eps *.pdf *.png *.aux *.dvi *.log *.out \ + rm -rf beta html pdf \ + $(image-svg:%.svg=%.pdf) \ + $(image-svg:%.svg=%.png) \ examples/*.{out,run} examples/.run build_id.tex
--- a/en/examples/run-example Wed Oct 18 15:47:04 2006 -0700 +++ b/en/examples/run-example Thu Oct 19 15:18:07 2006 -0700 @@ -39,7 +39,8 @@ class example: shell = '/usr/bin/env bash' - prompt = '__run_example_prompt__ ' + ps1 = '__run_example_ps1__ ' + ps2 = '__run_example_ps2__ ' pi_re = re.compile(r'#\$\s*(name):\s*(.*)$') timeout = 5 @@ -99,21 +100,23 @@ s = self.read() except OSError, err: if err.errno == errno.EIO: - return '' + return '', '' raise if self.verbose: print >> sys.stderr, self.debugrepr(s) out.write(s) s = out.getvalue() - if s.endswith(self.prompt): - return s.replace('\r\n', '\n')[:-len(self.prompt)] + if s.endswith(self.ps1): + return self.ps1, s.replace('\r\n', '\n')[:-len(self.ps1)] + if s.endswith(self.ps2): + return self.ps2, s.replace('\r\n', '\n')[:-len(self.ps2)] def sendreceive(self, s): self.send(s) - r = self.receive() + ps, r = self.receive() if r.startswith(s): r = r[len(s):] - return r + return ps, r def run(self): ofp = None @@ -128,8 +131,8 @@ rcfile = os.path.join(tmpdir, '.bashrc') rcfp = open(rcfile, 'w') - print >> rcfp, 'PS1="%s"' % self.prompt - print >> rcfp, 'PS2="%s"' % self.prompt + print >> rcfp, 'PS1="%s"' % self.ps1 + print >> rcfp, 'PS2="%s"' % self.ps2 print >> rcfp, 'unset HISTFILE' print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd() print >> rcfp, 'export LANG=C' @@ -153,12 +156,19 @@ os._exit(0) self.poll.register(self.cfd, select.POLLIN | select.POLLERR | select.POLLHUP) + + prompts = { + '': '', + self.ps1: '$', + self.ps2: '>', + } + try: try: # eat first prompt string from shell self.read() # setup env and prompt - self.sendreceive('source %s\n' % rcfile) + ps, output = self.sendreceive('source %s\n' % rcfile) for hunk in self.parse(): # is this line a processing instruction? m = self.pi_re.match(hunk) @@ -174,18 +184,20 @@ ofp = None elif hunk.strip(): # it's something we should execute - output = self.sendreceive(hunk) + newps, output = self.sendreceive(hunk) if not ofp: continue # first, print the command we ran if not hunk.startswith('#'): nl = hunk.endswith('\n') - hunk = ('$ \\textbf{%s}' % - tex_escape(hunk.rstrip('\n'))) + hunk = ('%s \\textbf{%s}' % + (prompts[ps], + tex_escape(hunk.rstrip('\n')))) if nl: hunk += '\n' ofp.write(hunk) # then its output ofp.write(tex_escape(output)) + ps = newps self.status('\n') open(self.name + '.run', 'w') except: @@ -195,7 +207,7 @@ raise else: try: - output = self.sendreceive('exit\n') + ps, output = self.sendreceive('exit\n') if ofp: ofp.write(output) os.close(self.cfd)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/examples/tour-merge-conflict Thu Oct 19 15:18:07 2006 -0700 @@ -0,0 +1,71 @@ +#!/bin/bash + +hg init scam +cd scam + +#$ name: wife + +cat > letter.txt <<EOF +Greetings! + +I am Mariam Abacha, the wife of former +Nigerian dictator Sani Abacha. +EOF + +hg add letter.txt +hg commit -m '419 scam, first draft' + +#$ name: cousin + +cd .. +hg clone scam scam-cousin +cd scam-cousin + +cat > letter.txt <<EOF +Greetings! + +I am Shehu Musa Abacha, cousin to the former +Nigerian dictator Sani Abacha. +EOF + +hg commit -m '419 scam, with cousin' + +#$ name: son + +cd .. +hg clone scam scam-son +cd scam-son + +cat > letter.txt <<EOF +Greetings! + +I am Alhaji Abba Abacha, son of the former +Nigerian dictator Sani Abacha. +EOF + +hg commit -m '419 scam, with son' + +#$ name: pull + +cd .. +hg clone scam-cousin scam-merge +cd scam-merge +hg pull -u ../scam-son + +#$ name: merge + +export HGMERGE=merge +hg merge +cat letter.txt + +#$ name: commit + +cat > letter.txt <<EOF +Greetings! + +I am Bryan O'Sullivan, no relation of the former +Nigerian dictator Sani Abacha. +EOF + +hg commit -m 'Send me your money' +hg tip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/en/tour-merge-conflict.svg Thu Oct 19 15:18:07 2006 -0700 @@ -0,0 +1,210 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="744.09448819" + height="1052.3622047" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.44.1" + sodipodi:docname="tour-merge-conflict.svg"> + <defs + id="defs4"> + <marker + inkscape:stockid="Arrow1Mend" + orient="auto" + refY="0.0" + refX="0.0" + id="Arrow1Mend" + style="overflow:visible;"> + <path + id="path3053" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none;" + transform="scale(0.4) rotate(180) translate(10,0)" /> + </marker> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.4" + inkscape:cx="164.78349" + inkscape:cy="590.07679" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="906" + inkscape:window-height="620" + inkscape:window-x="5" + inkscape:window-y="49" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g1988" + transform="translate(84.85711,0)"> + <g + id="g1876"> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 118.57143,458.21933 L 118.57143,563.79075 L 191.42857,563.79075 L 204.28571,550.93361 L 203.57142,459.6479 L 118.57143,458.21933 z " + id="path1872" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 191.55484,563.36862 L 191.6923,560.98794 L 192.69126,552.44884 L 203.80416,551.31242" + id="path1874" + sodipodi:nodetypes="cccc" /> + </g> + <flowRoot + style="font-size:8px;font-family:Times New Roman" + id="flowRoot1898" + xml:space="preserve"><flowRegion + id="flowRegion1900"><rect + style="font-size:8px;font-family:Times New Roman" + y="464.50504" + x="122.85714" + height="93.571426" + width="76.428574" + id="rect1902" /></flowRegion><flowPara + id="flowPara1904">Greetings!</flowPara><flowPara + id="flowPara1906" /><flowPara + id="flowPara1908">I am Mariam Abacha, the wife of former Nigerian dictator Sani Abacha. I am contacting you in confidence, and as a means of developing</flowPara></flowRoot> </g> + <g + id="g1966" + transform="translate(82,0.35715)"> + <g + transform="translate(-77.85718,-140.0714)" + id="g1910"> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 118.57143,458.21933 L 118.57143,563.79075 L 191.42857,563.79075 L 204.28571,550.93361 L 203.57142,459.6479 L 118.57143,458.21933 z " + id="path1912" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 191.55484,563.36862 L 191.6923,560.98794 L 192.69126,552.44884 L 203.80416,551.31242" + id="path1914" + sodipodi:nodetypes="cccc" /> + </g> + <flowRoot + transform="translate(-77.85718,-140.0714)" + style="font-size:8px;font-family:Times New Roman" + id="flowRoot1916" + xml:space="preserve"><flowRegion + id="flowRegion1918"><rect + style="font-size:8px;font-family:Times New Roman" + y="464.50504" + x="122.85714" + height="93.571426" + width="76.428574" + id="rect1920" /></flowRegion><flowPara + id="flowPara1922">Greetings!</flowPara><flowPara + id="flowPara1924" /><flowPara + id="flowPara1926">I am <flowSpan + style="font-style:italic;fill:red" + id="flowSpan3094">Shehu Musa Abacha, cousin to</flowSpan> the former Nigerian dictator Sani Abacha. I am contacting you in confidence, and as a means of developing</flowPara></flowRoot> </g> + <g + id="g1977" + transform="translate(81.99999,-0.35715)"> + <g + transform="translate(83.57141,-139.3571)" + id="g1932"> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 118.57143,458.21933 L 118.57143,563.79075 L 191.42857,563.79075 L 204.28571,550.93361 L 203.57142,459.6479 L 118.57143,458.21933 z " + id="path1934" + sodipodi:nodetypes="cccccc" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 191.55484,563.36862 L 191.6923,560.98794 L 192.69126,552.44884 L 203.80416,551.31242" + id="path1936" + sodipodi:nodetypes="cccc" /> + </g> + <flowRoot + transform="translate(83.57141,-139.3571)" + style="font-size:8px;font-family:Times New Roman" + id="flowRoot1938" + xml:space="preserve"><flowRegion + id="flowRegion1940"><rect + style="font-size:8px;font-family:Times New Roman" + y="464.50504" + x="122.85714" + height="93.571426" + width="76.428574" + id="rect1942" /></flowRegion><flowPara + id="flowPara1944">Greetings!</flowPara><flowPara + id="flowPara1946" /><flowPara + id="flowPara1948">I am <flowSpan + style="font-style:italic;fill:red" + id="flowSpan3096">Alhaji Abba Abacha, son of</flowSpan> the former Nigerian dictator Sani Abacha. I am contacting you in confidence, and as a means of developing</flowPara></flowRoot> </g> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 215.502,457.71933 L 196.35507,424.5765" + id="path1999" + inkscape:connector-type="polyline" + inkscape:connection-start="#g1988" + inkscape:connection-end="#g1966" /> + <path + style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:black;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow1Mend);stroke-opacity:1" + d="M 277.06936,457.71933 L 296.21629,424.5765" + id="path2001" + inkscape:connector-type="polyline" + inkscape:connection-start="#g1988" + inkscape:connection-end="#g1977" /> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="302.42859" + y="515.08905" + id="text1905"><tspan + sodipodi:role="line" + id="tspan1907" + x="302.42859" + y="515.08905">Base version</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="45.57143" + y="374.1619" + id="text1917"><tspan + sodipodi:role="line" + id="tspan1919" + x="45.57143" + y="374.1619">Our changes</tspan></text> + <text + xml:space="preserve" + style="font-size:12px;font-style:normal;font-weight:normal;fill:black;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Times New Roman" + x="385.71429" + y="374.1619" + id="text1921"><tspan + sodipodi:role="line" + id="tspan1923" + x="385.71429" + y="374.1619">Their changes</tspan></text> + </g> +</svg>
--- a/en/tour-merge.tex Wed Oct 18 15:47:04 2006 -0700 +++ b/en/tour-merge.tex Thu Oct 19 15:18:07 2006 -0700 @@ -113,12 +113,126 @@ \section{Merging conflicting changes} Most merges are simple affairs, but sometimes you'll find yourself -merging a change that you made with another, where both modify the -same portions of the same files. Unless both modifications are -identical, this results in a \emph{conflict}, where you have to decide -how to reconcile the different changes into something coherent. +merging changes where each modifies the same portions of the same +files. Unless both modifications are identical, this results in a +\emph{conflict}, where you have to decide how to reconcile the +different changes into something coherent. + +\begin{figure}[ht] + \centering + \grafix{tour-merge-conflict} + \caption{Conflicting changes to a document} + \label{fig:tour-merge:conflict} +\end{figure} + +Figure~\ref{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. + +Mercurial doesn't have a built-in facility for handling conflicts. +Instead, it runs an external program called \command{hgmerge}. 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. + +It's also possible to get Mercurial to run another program or script +instead of \command{hgmerge}, by setting the \envar{HGMERGE} +environment variable to the name of your preferred program. + +\subsection{Using a graphical merge tool} + +My preferred graphical merge tool is \command{kdiff3}, which I'll use +to describe the features that are common to graphical file merging +tools. You can see a screenshot of \command{kdiff3} in action in +figure~\ref{fig:tour-merge:kdiff3}. The kind of merge it is +performing is called a \emph{three-way merge}, 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: +\begin{itemize} +\item At the left is the \emph{base} version of the file, i.e.~the + most recent version from which the two versions we're trying to + merge are descended. +\item In the middle is ``our'' version of the file, with the contents + that we modified. +\item On the right is ``their'' version of the file, the one that + from the changeset that we're trying to merge with. +\end{itemize} +In the pane below these is the current \emph{result} of the merge. +Our task is to replace all of the red text, which indicates unresolved +conflicts, with some sensible merger of the ``ours'' and ``theirs'' +versions of the file. + +All four of these panes are \emph{locked together}; if we scroll +vertically or horizontally in any of them, the others are updated to +display the corresponding sections of their respective files. -\section{Using an extension to simplify merging} +\begin{figure}[ht] + \centering + \grafix{kdiff3} + \caption{Using \command{kdiff3} to merge versions of a file} + \label{fig:tour-merge:kdiff3} +\end{figure} + +For each conflicting portion of the file, we can choose to resolve +thhe 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. + +There are \emph{many} 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). + +\subsection{A worked example} + +In this example, we will reproduce the file modification history of +figure~\ref{fig:tour-merge:conflict} above. Let's begin by creating a +repository with a base version of our document. +\interaction{tour-merge-conflict.wife} +We'll clone the repository and make a change to the file. +\interaction{tour-merge-conflict.cousin} +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.) +\interaction{tour-merge-conflict.son} +Having created two different versions of the file, we'll set up an +environment suitable for running our merge. +\interaction{tour-merge-conflict.pull} + +In this example, I won't use Mercurial's normal \command{hgmerge} +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} to tell Mercurial to use the non-interactive +\command{merge} command. This is bundled with many Unix-like systems. +If you're following this example on your computer, don't bother +setting \envar{HGMERGE}. +\interaction{tour-merge-conflict.merge} +Because \command{merge} can't resolve the conflicting changes, it +leaves \emph{merge markers} inside the file that has conflicts, +indicating which lines have conflicts, and whether they came from our +version of the file or theirs. + +Mercurial can tell from the way \command{merge} 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. + +If automatic or manual merges fail, there's nothing to prevent us from +``fixing up'' the affected files ourselves, and committing the results +of our merge: +\interaction{tour-merge-conflict.commit} + +\section{Simplifying the pull-merge-commit + sequence} The process of merging changes as outlined above is straightforward, but requires running three commands in sequence. @@ -127,9 +241,9 @@ hg merge hg commit -m 'Merged remote changes' \end{codesample2} -In the case of the final commit, you also need to come up with a -commit message, which is almost always going to be a piece of -uninteresting ``boilerplate'' text. +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 +``boilerplate'' text. It would be nice to reduce the number of steps needed, if this were possible. Indeed, Mercurial is distributed with an extension called