changeset 124:c9aad709bd3a

Document the backout command.
author Bryan O'Sullivan <bos@serpentine.com>
date Tue, 26 Dec 2006 13:08:20 -0800
parents f954c6f6eaa1
children 8f8a1ad9627a
files en/00book.tex en/Makefile en/examples/backout en/examples/run-example en/undo-manual-merge.dot en/undo-manual.dot en/undo-non-tip.dot en/undo-simple.dot en/undo.tex
diffstat 9 files changed, 351 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/en/00book.tex	Tue Dec 26 09:59:12 2006 -0800
+++ b/en/00book.tex	Tue Dec 26 13:08:20 2006 -0800
@@ -41,6 +41,7 @@
 \include{tour-merge}
 \include{concepts}
 \include{daily}
+\include{undo}
 \include{hook}
 \include{template}
 \include{mq}
--- a/en/Makefile	Tue Dec 26 09:59:12 2006 -0800
+++ b/en/Makefile	Tue Dec 26 13:08:20 2006 -0800
@@ -33,15 +33,22 @@
 	tour-merge-merge.svg \
 	tour-merge-pull.svg \
 	tour-merge-sep-repos.svg \
+	undo-manual.dot \
+	undo-manual-merge.dot \
+	undo-non-tip.dot \
+	undo-simple.dot \
 	wdir.svg \
 	wdir-after-commit.svg \
 	wdir-branch.svg \
 	wdir-merge.svg \
 	wdir-pre-branch.svg
 
+image-dot := $(filter %.dot,$(image-sources))
 image-svg := $(filter %.svg,$(image-sources))
+image-png := $(filter %.png,$(image-sources))
 
 example-sources := \
+	backout \
 	daily.copy \
 	daily.files \
 	daily.rename \
@@ -82,7 +89,9 @@
 	if grep 'Reference.*undefined' $(@:.pdf=.log); then exit 1; fi
 endef
 
-pdf/hgbook.pdf: $(sources) $(image-sources:%.svg=%.pdf) examples
+image-pdf := $(image-dot:%.dot=%.pdf) $(image-svg:%.svg=%.pdf) $(image-png)
+
+pdf/hgbook.pdf: $(sources) $(image-pdf) examples
 	$(call pdf)
 
 html: html/onepage/hgbook.html html/split/hgbook.html
@@ -107,11 +116,13 @@
 	perl -pi -e 's/&#x00([0-7][0-9a-f]);/chr(hex($$1))/egi' $(dir $(1))/*.html
 endef
 
-html/onepage/hgbook.html: $(sources) $(image-sources:%.svg=%.png) examples
+image-html := $(image-dot:%.dot=%.png) $(image-svg:%.svg=%.png) $(image-png)
+
+html/onepage/hgbook.html: $(sources) $(image-html) examples
 	$(call htlatex,$@,$<)
 	cp $(image-sources:%.svg=%.png) $(dir $@)
 
-html/split/hgbook.html: $(sources) $(image-sources:%.svg=%.png) examples
+html/split/hgbook.html: $(sources) $(image-html) examples
 	$(call htlatex,$@,$<,2)
 	cp $(image-sources:%.svg=%.png) $(dir $@)
 
@@ -136,6 +147,9 @@
 %.png: %.svg
 	inkscape -D -e $@ $<
 
+%.svg: %.dot
+	dot -Tsvg -o $@ $<
+
 # Produce eps & pdf for the pdf
 
 %.pdf: %.eps
@@ -144,6 +158,9 @@
 %.eps: %.svg
 	inkscape -E $@ $<
 
+%.eps: %.dot
+	dot -Tps -o $@ $<
+
 examples: examples/.run
 
 examples/.run: $(example-sources:%=examples/%.run)
@@ -157,6 +174,8 @@
 
 clean:
 	rm -rf beta html pdf \
+		$(image-dot:%.dot=%.pdf) \
+		$(image-dot:%.dot=%.png) \
 		$(image-svg:%.svg=%.pdf) \
 		$(image-svg:%.svg=%.png) \
 		examples/*.{out,run} examples/.run build_id.tex
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/en/examples/backout	Tue Dec 26 13:08:20 2006 -0800
@@ -0,0 +1,82 @@
+#!/bin/bash
+
+# We have to fake the merges here, because they cause conflicts with
+# three-way command-line merge, and kdiff3 may not be available.
+
+export HGMERGE=$(mktemp)
+echo '#!/bin/sh' >> $HGMERGE
+echo 'echo first change > "$1"' >> $HGMERGE
+echo 'echo third change > "$1"' >> $HGMERGE
+chmod 700 $HGMERGE
+
+#$ name: init
+
+hg init myrepo
+cd myrepo
+echo first change >> myfile
+hg add myfile
+hg commit -m 'first change'
+echo second change >> myfile
+hg commit -m 'second change'
+
+#$ name: simple
+
+hg backout -m 'back out second change' tip
+cat myfile
+
+#$ name: simple.log
+
+hg log --style compact
+
+#$ name: non-tip.clone
+
+cd ..
+hg clone -r1 myrepo non-tip-repo
+cd non-tip-repo
+
+#$ name: non-tip.backout
+
+echo third change >> myfile
+hg commit -m 'third change'
+hg backout --merge -m 'back out second change' 1
+
+#$ name: non-tip.cat
+cat myfile
+
+#$ name: manual.clone
+
+cd ..
+hg clone -r1 myrepo newrepo
+cd newrepo
+
+#$ name: manual.backout
+
+echo third change >> myfile
+hg commit -m 'third change'
+hg backout -m 'back out second change' 1
+
+#$ name: manual.log
+
+hg log --style compact
+
+#$ name: manual.parents
+
+hg parents
+
+#$ name: manual.heads
+
+hg heads
+
+#$ name: manual.cat
+
+cat myfile
+
+#$ name: manual.merge
+
+hg merge
+hg commit -m 'merged backout with previous tip'
+cat myfile
+
+#$ name:
+
+rm $HGMERGE
--- a/en/examples/run-example	Tue Dec 26 09:59:12 2006 -0800
+++ b/en/examples/run-example	Tue Dec 26 13:08:20 2006 -0800
@@ -135,6 +135,7 @@
         print >> rcfp, 'PS2="%s"' % self.ps2
         print >> rcfp, 'unset HISTFILE'
         print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd()
+        print >> rcfp, 'export HGMERGE=merge'
         print >> rcfp, 'export LANG=C'
         print >> rcfp, 'export LC_ALL=C'
         print >> rcfp, 'export TZ=GMT'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/en/undo-manual-merge.dot	Tue Dec 26 13:08:20 2006 -0800
@@ -0,0 +1,8 @@
+digraph undo_manual {
+	"first change" -> "second change";
+	"second change" -> "third change";
+	backout [label="back out\nsecond change", shape=box];
+	"second change" -> backout;
+	"third change" -> "manual\nmerge";
+	backout -> "manual\nmerge";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/en/undo-manual.dot	Tue Dec 26 13:08:20 2006 -0800
@@ -0,0 +1,6 @@
+digraph undo_manual {
+	"first change" -> "second change";
+	"second change" -> "third change";
+	backout [label="back out\nsecond change", shape=box];
+	"second change" -> backout;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/en/undo-non-tip.dot	Tue Dec 26 13:08:20 2006 -0800
@@ -0,0 +1,9 @@
+digraph undo_non_tip {
+	"first change" -> "second change";
+	"second change" -> "third change";
+	backout [label="back out\nsecond change", shape=box];
+	"second change" -> backout;
+	merge [label="automated\nmerge", shape=box];
+	"third change" -> merge;
+	backout -> merge;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/en/undo-simple.dot	Tue Dec 26 13:08:20 2006 -0800
@@ -0,0 +1,4 @@
+digraph undo_simple {
+	"first change" -> "second change";
+	"second change" -> "back out\nsecond change";
+}
--- a/en/undo.tex	Tue Dec 26 09:59:12 2006 -0800
+++ b/en/undo.tex	Tue Dec 26 13:08:20 2006 -0800
@@ -115,11 +115,12 @@
 \section{Reverting the mistaken change}
 
 If you make a modification to a file, and decide that you really
-didn't want to change the file at all, the \hgcmd{revert} command is
-the one you'll need.  It looks at the changeset that's the parent of
-the working directory, and restores the contents of the file to their
-state as of that changeset.  (That's a long-winded way of saying that,
-in the normal case, it undoes your modifications.)
+didn't want to change the file at all, and you haven't yet committed
+your changes, the \hgcmd{revert} command is the one you'll need.  It
+looks at the changeset that's the parent of the working directory, and
+restores the contents of the file to their state as of that changeset.
+(That's a long-winded way of saying that, in the normal case, it
+undoes your modifications.)
 
 Let's illustrate how the \hgcmd{revert} command works with yet another
 small example.  We'll begin by modifying a file that Mercurial is
@@ -131,6 +132,21 @@
 by saving our modified file with a \filename{.orig} extension.
 \interaction{daily.revert.status}
 
+Here is a summary of the cases that the \hgcmd{revert} command can
+deal with.  We will describe each of these in more detail in the
+section that follows.
+\begin{itemize}
+\item If you modify a file, it will restore the file to its unmodified
+  state.
+\item If you \hgcmd{add} a file, it will undo the ``added'' state of
+  the file, but leave the file itself untouched.
+\item If you delete a file without telling Mercurial, it will restore
+  the file to its unmodified contents.
+\item If you use the \hgcmd{remove} command to remove a file, it will
+  undo the ``removed'' state of the file, and restore the file to its
+  unmodified contents.
+\end{itemize}
+
 \subsection{File management errors}
 \label{sec:undo:mgmt}
 
@@ -183,6 +199,203 @@
 These fiddly aspects of reverting a rename arguably constitute a small
 bug in Mercurial.
 
+\section{Dealing with committed changes}
+
+Consider a case where you have committed a change $a$, and another
+change $b$ on top of it; you then realise that change $a$ was
+incorrect.  Mercurial lets you ``back out'' an entire changeset
+automatically, and building blocks that let you reverse part of a
+changeset by hand.
+
+\subsection{Backing out a changeset}
+
+The \hgcmd{backout} command lets you ``undo'' the effects of an entire
+changeset in an automated fashion.  Because Mercurial's history is
+immutable, this command \emph{does not} get rid of the changeset you
+want to undo.  Instead, it creates a new changeset that
+\emph{reverses} the effect of the to-be-undone changeset.
+
+The operation of the \hgcmd{backout} command is a little intricate, so
+let's illustrate it with some examples.  First, we'll create a
+repository with some simple changes.
+\interaction{backout.init}
+
+The \hgcmd{backout} command takes a single changeset ID as its
+argument; this is the changeset to back out.  Normally,
+\hgcmd{backout} will drop you into a text editor to write a commit
+message, so you can record why you're backing the change out.  In this
+example, we provide a commit message on the command line using the
+\hgopt{backout}{-m} option.
+
+\subsection{Backing out the tip changeset}
+
+We're going to start by backing out the last changeset we committed.
+\interaction{backout.simple}
+You can see that the second line from \filename{myfile} is no longer
+present.  Taking a look at the output of \hgcmd{log} gives us an idea
+of what the \hgcmd{backout} command has done.
+\interaction{backout.simple.log}
+Notice that the new changeset that \hgcmd{backout} has created is a
+child of the changeset we backed out.  It's easier to see this in
+figure~\ref{fig:undo:backout}, which presents a graphical view of the
+change history.  As you can see, the history is nice and linear.
+
+\begin{figure}[htb]
+  \centering
+  \grafix{undo-simple}
+  \caption{Backing out a change using the \hgcmd{backout} command}
+  \label{fig:undo:backout}
+\end{figure}
+
+\subsection{Backing out a non-tip change}
+
+If you want to back out a change other than the last one you
+committed, pass the \hgopt{backout}{--merge} option to the
+\hgcmd{backout} command.
+\interaction{backout.non-tip.clone}
+This makes backing out any changeset a ``one-shot'' operation that's
+usually simple and fast.
+\interaction{backout.non-tip.backout}
+
+If you take a look at the contents of \filename{myfile} after the
+backout finishes, you'll see that the first and third changes are
+present, but not the second.
+\interaction{backout.non-tip.cat}
+
+As the graphical history in figure~\ref{fig:undo:backout-non-tip}
+illustrates, Mercurial actually commits \emph{two} changes in this
+kind of situation (the box-shaped nodes are the ones that Mercurial
+commits automatically).  Before Mercurial begins the backout process,
+it first remembers what the current parent of the working directory
+is.  It then backs out the target changeset, and commits that as a
+changeset.  Finally, it merges back to the previous parent of the
+working directory, and commits the result of the merge.
+
+\begin{figure}[htb]
+  \centering
+  \grafix{undo-non-tip}
+  \caption{Automated backout of a non-tip change using the \hgcmd{backout} command}
+  \label{fig:undo:backout-non-tip}
+\end{figure}
+
+The result is that you end up ``back where you were'', only with some
+extra history that undoes the effect of the changeset you wanted to
+back out.
+
+\subsubsection{Always use the \hgopt{backout}{--merge} option}
+
+In fact, since the \hgopt{backout}{--merge} option will do the ``right
+thing'' whether or not the changeset you're backing out is the tip
+(i.e.~it won't try to merge if it's backing out the tip, since there's
+no need), you should \emph{always} use this option when you run the
+\hgcmd{backout} command.
+
+\subsection{Gaining more control of the backout process}
+
+While I've recommended that you always use the
+\hgopt{backout}{--merge} option when backing out a change, the
+\hgcmd{backout} command lets you decide how to merge a backout
+changeset.  Taking control of the backout process by hand is something
+you will rarely need to do, but it can be useful to understand what
+the \hgcmd{backout} command is doing for you automatically.  To
+illustrate this, let's clone our first repository, but omit the
+backout change that it contains.
+
+\interaction{backout.manual.clone}
+As with our earlier example, We'll commit a third changeset, then back
+out its parent, and see what happens.
+\interaction{backout.manual.backout} 
+Our new changeset is again a descendant of the changeset we backout
+out; it's thus a new head, \emph{not} a descendant of the changeset
+that was the tip.  The \hgcmd{backout} command was quite explicit in
+telling us this.
+\interaction{backout.manual.log}
+
+Again, it's easier to see what has happened by looking at a graph of
+the revision history, in figure~\ref{fig:undo:backout-manual}.  This
+makes it clear that when we use \hgcmd{backout} to back out a change
+other than the tip, Mercurial adds a new head to the repository (the
+change it committed is box-shaped).
+
+\begin{figure}[htb]
+  \centering
+  \grafix{undo-manual}
+  \caption{Backing out a change using the \hgcmd{backout} command}
+  \label{fig:undo:backout-manual}
+\end{figure}
+
+After the \hgcmd{backout} command has completed, it leaves the new
+``backout'' changeset as the parent of the working directory.
+\interaction{backout.manual.parents}
+Now we have two isolated sets of changes.
+\interaction{backout.manual.heads}
+
+Let's think about what we expect to see as the contents of
+\filename{myfile} now.  The first change should be present, because
+we've never backed it out.  The second change should be missing, as
+that's the change we backed out.  Since the history graph shows the
+third change as a separate head, we \emph{don't} expect to see the
+third change present in \filename{myfile}.
+\interaction{backout.manual.cat}
+To get the third change back into the file, we just do a normal merge
+of our two heads.
+\interaction{backout.manual.merge}
+Afterwards, the graphical history of our repository looks like
+figure~\ref{fig:undo:backout-manual-merge}.
+
+\begin{figure}[htb]
+  \centering
+  \grafix{undo-manual-merge}
+  \caption{Manually merging a backout change}
+  \label{fig:undo:backout-manual-merge}
+\end{figure}
+
+\subsection{A rationale}
+
+Here's a brief description of how the \hgcmd{backout} command works.
+\begin{enumerate}
+\item It ensures that the working directory is ``clean'', i.e.~that
+  the output of \hgcmd{status} would be empty.
+\item It remembers the current parent of the working directory.  Let's
+  call this changeset \texttt{orig}
+\item It does the equivalent of a \hgcmd{update} to sync the working
+  directory to the changeset you want to back out.  Let's call this
+  changeset \texttt{backout}
+\item It finds the parent of that changeset.  Let's call that
+  changeset \texttt{parent}.
+\item For each file that the \texttt{backout} changeset affected, it
+  does the equivalent of a \hgcmdargs{revert}{-r parent} on that file,
+  to restore it to the contents it had before that changeset was
+  committed.
+\item It commits the result as a new changeset.  This changeset has
+  \texttt{backout} as its parent.
+\item If you specify \hgopt{backout}{--merge} on the command line, it
+  merges with \texttt{orig}, and commits the result of the merge.
+\end{enumerate}
+
+An alternative way to implement the \hgcmd{backout} command would be
+to \hgcmd{export} the to-be-backed-out changeset as a diff, then use
+the \cmdopt{patch}{--reverse} option to the \command{patch} command to
+reverse the effect of the change without fiddling with the working
+directory.  This sounds much simpler, but it would not work nearly as
+well.
+
+The reason that \hgcmd{backout} does an update, a commit, a merge, and
+another commit is to give the merge machinery the best chance to do a
+good job when dealing with all the changes \emph{between} the change
+you're backing out and the current tip.  
+
+If you're backing out a changeset that's~100 revisions back in your
+project's history, the chances that the \command{patch} command will
+be able to apply a reverse diff cleanly are not good, because
+intervening changes are likely to have ``broken the context'' that
+\command{patch} uses to determine whether it can apply a patch (if
+this sounds like gibberish, see \section{sec:mq:patch} for a
+discussion of the \command{patch} command).  Also, Mercurial's merge
+machinery will handle files and directories being renamed, permission
+changes, and modifications to binary files, none of which
+\command{patch} can deal with.
+
 %%% Local Variables: 
 %%% mode: latex
 %%% TeX-master: "00book"