changeset 117:6b0f4498569e

Beef up daily routine material. Focus on merge-across-copy.
author Bryan O'Sullivan <bos@serpentine.com>
date Tue, 14 Nov 2006 15:11:22 -0800
parents ca99f247899e
children 1ee53cb37a99
files en/Makefile en/daily.tex en/examples/daily.files
diffstat 3 files changed, 261 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/en/Makefile	Tue Nov 14 11:42:40 2006 -0800
+++ b/en/Makefile	Tue Nov 14 15:11:22 2006 -0800
@@ -41,6 +41,7 @@
 image-svg := $(filter %.svg,$(image-sources))
 
 example-sources := \
+	daily.copy \
 	daily.files \
 	hook.msglen \
 	hook.simple \
--- a/en/daily.tex	Tue Nov 14 11:42:40 2006 -0800
+++ b/en/daily.tex	Tue Nov 14 15:11:22 2006 -0800
@@ -1,9 +1,7 @@
 \chapter{Mercurial in daily use}
 \label{chap:daily}
 
-\section{Routine file management tasks}
-
-\subsection{Telling Mercurial which files to track}
+\section{Telling Mercurial which files to track}
 
 Mercurial does not work with files in your repository unless you tell
 it to manage them.  The \hgcmd{status} command will tell you which
@@ -13,29 +11,52 @@
 To tell Mercurial to track a file, use the \hgcmd{add} command.  Once
 you have added a file, the entry in the output of \hgcmd{status} for
 that file changes from ``\texttt{?}'' to ``\texttt{A}''.
+\interaction{daily.files.add}
 
 After you run a \hgcmd{commit}, the files that you added before the
 commit will no longer be listed in the output of \hgcmd{status}.  The
 reason for this is that \hgcmd{status} only tells you about
-``interesting'' files by default.  If you have a repository that
+``interesting'' files---those that you have modified or told Mercurial
+to do something with---by default.  If you have a repository that
 contains thousands of files, you will rarely want to know about files
 that Mercurial is tracking, but that have not changed.  (You can still
 get this information; we'll return to this later.)
 
-\begin{figure}[ht]
-  \interaction{daily.files.add}
-  \caption{Telling Mercurial to track a file}
-  \label{ex:daily:add}
-\end{figure}
+Once you add a file, Mercurial doesn't do anything with it
+immediately.  Instead, it will take a snapshot of the file's state the
+next time you perform a commit.  It will then continue to track the
+changes you make to the file every time you commit, until you remove
+the file.
+
+\subsection{Explicit versus implicit file naming}
 
-Once you add a file, Mercurial will track every change you make to it
-until you either remove or rename the file.
+A useful behaviour that Mercurial has is that if you pass the name of
+a directory to a command, every Mercurial command will treat this as
+``I want to operate on every file in this directory and its
+subdirectories''.
+\interaction{daily.files.add-dir}
+Notice in this example that Mercurial printed the names of the files
+it added, whereas it didn't do so when we added the file named
+\filename{a} in the earlier example.
 
-\subsubsection{Aside: Mercurial tracks files, not directories}
+What's going on is that in the former case, we explicitly named the
+file to add on the command line, so the assumption that Mercurial
+makes in such cases is that we know what you were doing, and it
+doesn't print any output.
+
+However, when we \emph{imply} the names of files by giving the name of
+a directory, Mercurial takes the extra step of printing the name of
+each file that it does something with.  This makes it more clear what
+is happening, and reduces the likelihood of a silent and nasty
+surprise.  This behaviour is common to most Mercurial commands.
+
+\subsection{Aside: Mercurial tracks files, not directories}
 
 Mercurial does not track directory information.  Instead, it tracks
-the path to a file, and creates directories along a path when it needs
-to.  This sounds like a trivial distinction, but it has one minor
+the path to a file.  Before creating a file, it first creates any
+missing directory components of the path.  After it deletes a file, it
+then deletes any empty directories that were in the deleted file's
+path.  This sounds like a trivial distinction, but it has one minor
 practical consequence: it is not possible to represent a completely
 empty directory in Mercurial.
 
@@ -61,68 +82,169 @@
 Another way to tackle a need for an empty directory is to simply
 create one in your automated build scripts before they will need it.
 
-\subsection{How to stop tracking a file}
+\section{How to stop tracking a file}
 
-If you decide that a file no longer belongs in your repository, use
+Once you decide that a file no longer belongs in your repository, use
 the \hgcmd{remove} command; this deletes the file, and tells Mercurial
 to stop tracking it.  A removed file is represented in the output of
 \hgcmd{status} with a ``\texttt{R}''.
+\interaction{daily.files.remove}
 
-You might wonder why Mercurial requires you to explicitly tell it that
-you are deleting a file.  Earlier during the development of Mercurial,
-you could simply delete a file however you pleased; Mercurial would
-notice automatically when you next ran a \hgcmd{commit}, and stop
-tracking the file.  In practice, this made it too easy to accidentally
-stop Mercurial from tracking a file.
+\subsection{Missing files}
 
 Mercurial considers a file that you have deleted, but not used
 \hgcmd{remove} to delete, to be \emph{missing}.  A missing file is
 represented with ``\texttt{!}'' in the output of \hgcmd{status}.
-Other Mercurial commands will not do anything with missing files.
+Mercurial commands will not generally do anything with missing files.
+\interaction{daily.files.missing}
+
+If your repository contains a file that \hgcmd{status} reports as
+missing, and you want the file to stay gone, you can run
+\hgcmdargs{remove}{\hgopt{remove}{--after}} at any time later on, to
+tell Mercurial that you really did mean to remove the file.
+\interaction{daily.files.remove-after}
 
-If you have a missing file in your repository, you can run
-\hgcmdargs{remove}{\hgopt{remove}{--after}} later on, to tell
-Mercurial that you deleted the file.  If you deleted the file by
-accident, use \hgcmdargs{revert}{\emph{filename}} to restore the file
-to its last committed state.
+On the other hand, if you deleted the missing file by accident, use
+\hgcmdargs{revert}{\emph{filename}} to recover the file.  It will
+reappear, in unmodified form.
+\interaction{daily.files.recover-missing}
+
+\subsection{Aside: why tell Mercurial explicitly to 
+  remove a file?}
 
-\subsection{Useful shorthand---adding and removing files in one step}
+You might wonder why Mercurial requires you to explicitly tell it that
+you are deleting a file.  Early during the development of Mercurial,
+it let you delete a file however you pleased; Mercurial would notice
+the absence of the file automatically when you next ran a
+\hgcmd{commit}, and stop tracking the file.  In practice, this made it
+too easy to accidentally remove a file without noticing.
+
+\subsection{Useful shorthand---adding and removing files
+  in one step}
 
 Mercurial offers a combination command, \hgcmd{addremove}, that adds
-untracked files and marks missing files as removed.  The
-\hgcmd{commit} command also provides a \hgopt{commit}{-A} option that
-performs an add-and-remove, immediately followed by a commit.  This
-lets you replace the following command sequence:
-\begin{codesample2}
-  hg add
-  hg remove --after
-  hg commit
-\end{codesample2}
-with a single command, \hgcmdargs{commit}{\hgopt{commit}{-A}}.
+untracked files and marks missing files as removed.  
+\interaction{daily.files.addremove}
+The \hgcmd{commit} command also provides a \hgopt{commit}{-A} option
+that performs this same add-and-remove, immediately followed by a
+commit.
+\interaction{daily.files.commit-addremove}
+
+\section{Copying files}
+
+Mercurial provides a \hgcmd{copy} command that lets you make a new
+copy of a file.  When you copy a file using this command, Mercurial
+makes a record of the fact that the new file is a copy of the original
+file.  It treats these copied files specially when you merge your work
+with someone else's.
+
+What happens during a merge is that changes ``follow'' a copy.  To
+best illustrate what this means, let's create an example.  We'll start
+with the usual tiny repository that contains a single file.
+\interaction{daily.copy.init}
+We need to do some work in parallel, so that we'll have something to
+merge.  So let's clone our repository.
+\interaction{daily.copy.clone}
+Back in our initial repository, let's use the \hgcmd{copy} command to
+make a copy of the first file we created.
+\interaction{daily.copy.copy}
+
+If we look at the output of the \hgcmd{status} command afterwards, the
+copied file looks just like a normal added file.
+\interaction{daily.copy.status}
+But if we pass the \hgopt{status}{-C} option to \hgcmd{status}, it
+prints another line of output: this is the file that our newly-added
+file was copied \emph{from}.
+\interaction{daily.copy.status-copy}
+
+Now, back in the repository we cloned, let's make a change in
+parallel.  We'll add a line of content to the original file that we
+created.
+\interaction{daily.copy.other}
+Now we have a modified \filename{file} in this repository.  When we
+pull the changes from the first repository, and merge the two heads,
+Mercurial will propagate the changes that we made locally to
+\filename{file} into its copy, \filename{new-file}.
+\interaction{daily.copy.merge}
+
+\subsection{Why should changes follow copies?}
+\label{sec:daily:why-copy}
+
+This behaviour, of changes to a file propagating out to copies of the
+file, might seem esoteric, but in most cases it's highly desirable.
+
+First of all, remember that this propagation \emph{only} happens when
+you merge.  So if you \hgcmd{copy} a file, and subsequently modify the
+original file during the normal course of your work, nothing will
+happen.
 
-\subsection{Renaming files}
+The second thing to know is that modifications will only propagate
+across a copy as long as the repository that you're pulling changes
+from \emph{doesn't know} about the copy.
+
+The reason that Mercurial does this is as follows.  Let's say I make
+an important bug fix in a source file, and commit my changes.
+Meanwhile, you've decided to \hgcmd{copy} the file in your repository,
+without knowing about the bug or having seen the fix, and you have
+started hacking on your copy of the file.
+
+If you pulled and merged my changes, and Mercurial \emph{didn't}
+propagate changes across copies, your source file would now contain
+the bug, and unless you remembered to propagate the bug fix by hand,
+the bug would \emph{remain} in your copy of the file.
+
+By automatically propagating the change that fixed the bug from the
+original file to the copy, Mercurial prevents this class of problem.
+To my knowledge, Mercurial is the \emph{only} revision control system
+that propagates changes across copies like this.
+
+Once your change history has a record that the copy and subsequent
+merge occurred, there's usually no further need to propagate changes
+from the original file to the copied file, and that's why Mercurial
+only propagates changes across copies until this point, and no
+further.
+
+\subsection{How to make changes \emph{not} follow a copy}
+
+If, for some reason, you decide that this business of automatically
+propagating changes across copies is not for you, simply use your
+system's normal file copy command (on Unix-like systems, that's
+\command{cp}) to make a copy of a file, then \hgcmd{add} the new copy
+by hand.  Before you do so, though, please do reread
+section~\ref{sec:daily:why-copy}, and make an informed decision that
+this behaviour is not appropriate to your specific case.
+
+\subsection{Behaviour of the \hgcmd{copy} command}
+
+The \hgcmd{copy} command acts similarly to the Unix \command{cp}
+command.  The last argument is the \emph{destination}, and all prior
+arguments are \emph{sources}.
+If you pass it a single file as the source, and the destination
+does not exist, it creates a new file with that name.
+\interaction{daily.copy.simple}
+If the destination is a directory, Mercurial copies its sources into
+that directory.
+\interaction{daily.copy.dir-dest}
+Copying a directory is recursive, and preserves the directory
+structure of the source.
+\interaction{daily.copy.dir-src}
+If the source and destination are both directories, the source tree is
+recreated in the destination directory.
+\interaction{daily.copy.dir-src-dest}
+
+\section{Renaming files}
 
 To rename a file that is tracked by Mercurial, use the \hgcmd{rename}
 command.  This command behaves similarly to the Unix \command{mv}
-command.  If the last argument is a directory, it moves all prior
-arguments into that directory.  Otherwise, it renames a single file or
-directory to the name given in the last argument.
+command (and in fact you can use the alias \hgcmd{mv} if you wish).
+If the last argument is a directory, \hgcmd{rename} moves all files
+identified by earlier arguments into that directory.  Otherwise, it
+renames a single file or directory to the name given in the last
+argument.
 
 As with \hgcmd{remove}, you can tell Mercurial about a rename after
 the fact using the \hgopt{remove}{--after} option.
 
-The na\"{i}ve way to ``rename'' a file is simply to rename the file
-yourself, \hgcmd{remove} the old name, and \hgcmd{add} the new name.
-However, if you do this, Mercurial will not know that there was any
-relationship between the files in question, and it will not be able to
-merge
-
-\subsection{Copying files}
-
-You can copy a file in two ways using mercurial.  If you simply copy a
-file and then \hgcmd{add} the new file, Mercurial will not know that
-there was any relationship between the two files.  However, if you 
-
 %%% Local Variables: 
 %%% mode: latex
 %%% TeX-master: "00book"
--- a/en/examples/daily.files	Tue Nov 14 11:42:40 2006 -0800
+++ b/en/examples/daily.files	Tue Nov 14 15:11:22 2006 -0800
@@ -2,21 +2,96 @@
 
 #$ name: add
 
-hg init a
-cd a
-echo content > filename
-mkdir subdir
-echo something > subdir/otherfile
+hg init add-example
+cd add-example
+echo a > a
+hg status
+hg add a
+hg status
+hg commit -m 'Added one file'
 hg status
 
+#$ name: add-dir
+
+mkdir b
+echo b > b/b
+echo c > b/c
+mkdir b/d
+echo d > b/d/d
+hg add b
+hg commit -m 'Added all files in subdirectory'
+
+#$ name:
+
+cd ..
+
 #$ name: hidden
 
+hg init hidden-example
+cd hidden-example
 mkdir empty
 touch empty/.hidden
 hg add empty/.hidden
 hg commit -m 'Manage an empty-looking directory'
 ls empty
 cd ..
-hg clone a b
-ls b
-ls b/empty
+hg clone hidden-example tmp
+ls tmp
+ls tmp/empty
+
+#$ name:
+
+cd ..
+
+#$ name: remove
+
+hg init remove-example
+cd remove-example
+echo a > a
+mkdir b
+echo b > b/b
+hg add a b
+hg commit -m 'Small example for file removal'
+hg remove a
+hg status
+hg remove b
+
+#$ name:
+
+cd ..
+
+#$ name: missing
+hg init missing-example
+cd missing-example
+echo a > a
+hg add a
+hg commit -m'File about to be missing'
+rm a
+hg status
+
+#$ name: remove-after
+
+hg remove --after a
+hg status
+
+#$ name: recover-missing
+hg revert a
+cat a
+hg status
+
+#$ name:
+
+cd ..
+
+#$ name: addremove
+
+hg init addremove-example
+cd addremove-example
+echo a > a
+echo b > b
+hg addremove
+
+#$ name: commit-addremove
+
+echo c > c
+hg commit -A -m 'Commit with addremove'