Mercurial > hgbook
view en/ch04-daily.xml @ 775:29f0f79cf614
Update paragraph IDs
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Thu, 16 Apr 2009 23:46:45 -0700 |
parents | 3b640272a966 |
children | 1a0a78e197c3 |
line wrap: on
line source
<!-- vim: set filetype=docbkxml shiftwidth=2 autoindent expandtab tw=77 : --> <chapter id="chap:daily"> <?dbhtml filename="mercurial-in-daily-use.html"?> <title>Mercurial in daily use</title> <sect1> <title>Telling Mercurial which files to track</title> <para id="x_1a3">Mercurial does not work with files in your repository unless you tell it to manage them. The <command role="hg-cmd">hg status</command> command will tell you which files Mercurial doesn't know about; it uses a <quote><literal>?</literal></quote> to display such files.</para> <para id="x_1a4">To tell Mercurial to track a file, use the <command role="hg-cmd">hg add</command> command. Once you have added a file, the entry in the output of <command role="hg-cmd">hg status</command> for that file changes from <quote><literal>?</literal></quote> to <quote><literal>A</literal></quote>.</para> &interaction.daily.files.add; <para id="x_1a5">After you run a <command role="hg-cmd">hg commit</command>, the files that you added before the commit will no longer be listed in the output of <command role="hg-cmd">hg status</command>. The reason for this is that by default, <command role="hg-cmd">hg status</command> only tells you about <quote>interesting</quote> files&emdash;those that you have (for example) modified, removed, or renamed. 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.)</para> <para id="x_1a6">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.</para> <sect2> <title>Explicit versus implicit file naming</title> <para id="x_1a7">A useful behavior that Mercurial has is that if you pass the name of a directory to a command, every Mercurial command will treat this as <quote>I want to operate on every file in this directory and its subdirectories</quote>.</para> &interaction.daily.files.add-dir; <para id="x_1a8">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>myfile.txt</filename> in the earlier example.</para> <para id="x_1a9">What's going on is that in the former case, we explicitly named the file to add on the command line. The assumption that Mercurial makes in such cases is that we know what we are doing, and it doesn't print any output.</para> <para id="x_1aa">However, when we <emphasis>imply</emphasis> 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 behavior is common to most Mercurial commands.</para> </sect2> <sect2> <title>Mercurial tracks files, not directories</title> <para id="x_1ab">Mercurial does not track directory information. Instead, it tracks 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.</para> <para id="x_1ac">Empty directories are rarely useful, and there are unintrusive workarounds that you can use to achieve an appropriate effect. The developers of Mercurial thus felt that the complexity that would be required to manage empty directories was not worth the limited benefit this feature would bring.</para> <para id="x_1ad">If you need an empty directory in your repository, there are a few ways to achieve this. One is to create a directory, then <command role="hg-cmd">hg add</command> a <quote>hidden</quote> file to that directory. On Unix-like systems, any file name that begins with a period (<quote><literal>.</literal></quote>) is treated as hidden by most commands and GUI tools. This approach is illustrated below.</para> &interaction.daily.files.hidden; <para id="x_1ae">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.</para> </sect2> </sect1> <sect1> <title>How to stop tracking a file</title> <para id="x_1af">Once you decide that a file no longer belongs in your repository, use the <command role="hg-cmd">hg remove</command> command. This deletes the file, and tells Mercurial to stop tracking it. A removed file is represented in the output of <command role="hg-cmd">hg status</command> with a <quote><literal>R</literal></quote>.</para> &interaction.daily.files.remove; <para id="x_1b0">After you <command role="hg-cmd">hg remove</command> a file, Mercurial will no longer track changes to that file, even if you recreate a file with the same name in your working directory. If you do recreate a file with the same name and want Mercurial to track the new file, simply <command role="hg-cmd">hg add</command> it. Mercurial will know that the newly added file is not related to the old file of the same name.</para> <sect2> <title>Removing a file does not affect its history</title> <para id="x_1b1">It is important to understand that removing a file has only two effects.</para> <itemizedlist> <listitem><para id="x_1b2">It removes the current version of the file from the working directory.</para> </listitem> <listitem><para id="x_1b3">It stops Mercurial from tracking changes to the file, from the time of the next commit.</para> </listitem></itemizedlist> <para id="x_1b4">Removing a file <emphasis>does not</emphasis> in any way alter the <emphasis>history</emphasis> of the file.</para> <para id="x_1b5">If you update the working directory to a changeset that was committed when it was still tracking a file that you later removed, the file will reappear in the working directory, with the contents it had when you committed that changeset. If you then update the working directory to a later changeset, in which the file had been removed, Mercurial will once again remove the file from the working directory.</para> </sect2> <sect2> <title>Missing files</title> <para id="x_1b6">Mercurial considers a file that you have deleted, but not used <command role="hg-cmd">hg remove</command> to delete, to be <emphasis>missing</emphasis>. A missing file is represented with <quote><literal>!</literal></quote> in the output of <command role="hg-cmd">hg status</command>. Mercurial commands will not generally do anything with missing files.</para> &interaction.daily.files.missing; <para id="x_1b7">If your repository contains a file that <command role="hg-cmd">hg status</command> reports as missing, and you want the file to stay gone, you can run <command role="hg-cmd">hg remove <option role="hg-opt-remove">--after</option></command> at any time later on, to tell Mercurial that you really did mean to remove the file.</para> &interaction.daily.files.remove-after; <para id="x_1b8">On the other hand, if you deleted the missing file by accident, give <command role="hg-cmd">hg revert</command> the name of the file to recover. It will reappear, in unmodified form.</para> &interaction.daily.files.recover-missing; </sect2> <sect2> <title>Aside: why tell Mercurial explicitly to remove a file?</title> <para id="x_1b9">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 <command role="hg-cmd">hg commit</command>, and stop tracking the file. In practice, this made it too easy to accidentally remove a file without noticing.</para> </sect2> <sect2> <title>Useful shorthand&emdash;adding and removing files in one step</title> <para id="x_1ba">Mercurial offers a combination command, <command role="hg-cmd">hg addremove</command>, that adds untracked files and marks missing files as removed.</para> &interaction.daily.files.addremove; <para id="x_1bb">The <command role="hg-cmd">hg commit</command> command also provides a <option role="hg-opt-commit">-A</option> option that performs this same add-and-remove, immediately followed by a commit.</para> &interaction.daily.files.commit-addremove; </sect2> </sect1> <sect1> <title>Copying files</title> <para id="x_1bc">Mercurial provides a <command role="hg-cmd">hg copy</command> 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.</para> <sect2> <title>The results of copying during a merge</title> <para id="x_1bd">What happens during a merge is that changes <quote>follow</quote> 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.</para> &interaction.daily.copy.init; <para id="x_1be">We need to do some work in parallel, so that we'll have something to merge. So let's clone our repository.</para> &interaction.daily.copy.clone; <para id="x_1bf">Back in our initial repository, let's use the <command role="hg-cmd">hg copy</command> command to make a copy of the first file we created.</para> &interaction.daily.copy.copy; <para id="x_1c0">If we look at the output of the <command role="hg-cmd">hg status</command> command afterwards, the copied file looks just like a normal added file.</para> &interaction.daily.copy.status; <para id="x_1c1">But if we pass the <option role="hg-opt-status">-C</option> option to <command role="hg-cmd">hg status</command>, it prints another line of output: this is the file that our newly-added file was copied <emphasis>from</emphasis>.</para> &interaction.daily.copy.status-copy; <para id="x_1c2">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.</para> &interaction.daily.copy.other; <para id="x_1c3">Now we have a modified <filename>file</filename> 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</filename> into its copy, <filename>new-file</filename>.</para> &interaction.daily.copy.merge; </sect2> <sect2 id="sec:daily:why-copy"> <title>Why should changes follow copies?</title> <para id="x_1c4">This behavior&emdash;of changes to a file propagating out to copies of the file&emdash;might seem esoteric, but in most cases it's highly desirable.</para> <para id="x_1c5">First of all, remember that this propagation <emphasis>only</emphasis> happens when you merge. So if you <command role="hg-cmd">hg copy</command> a file, and subsequently modify the original file during the normal course of your work, nothing will happen.</para> <para id="x_1c6">The second thing to know is that modifications will only propagate across a copy as long as the changeset that you're merging changes from <emphasis>hasn't yet seen</emphasis> the copy.</para> <para id="x_1c7">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 <command role="hg-cmd">hg copy</command> 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.</para> <para id="x_1c8">If you pulled and merged my changes, and Mercurial <emphasis>didn't</emphasis> propagate changes across copies, your new source file would now contain the bug, and unless you knew to propagate the bug fix by hand, the bug would <emphasis>remain</emphasis> in your copy of the file.</para> <para id="x_1c9">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 <emphasis>only</emphasis> revision control system that propagates changes across copies like this.</para> <para id="x_1ca">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 at the first merge, and not afterwards.</para> </sect2> <sect2> <title>How to make changes <emphasis>not</emphasis> follow a copy</title> <para id="x_1cb">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</command>) to make a copy of a file, then <command role="hg-cmd">hg add</command> the new copy by hand. Before you do so, though, please do reread <xref linkend="sec:daily:why-copy"/>, and make an informed decision that this behavior is not appropriate to your specific case.</para> </sect2> <sect2> <title>Behavior of the <command role="hg-cmd">hg copy</command> command</title> <para id="x_1cc">When you use the <command role="hg-cmd">hg copy</command> command, Mercurial makes a copy of each source file as it currently stands in the working directory. This means that if you make some modifications to a file, then <command role="hg-cmd">hg copy</command> it without first having committed those changes, the new copy will also contain the modifications you have made up until that point. (I find this behavior a little counterintuitive, which is why I mention it here.)</para> <para id="x_1cd">The <command role="hg-cmd">hg copy</command> command acts similarly to the Unix <command>cp</command> command (you can use the <command role="hg-cmd">hg cp</command> alias if you prefer). We must supply two or more arguments, of which the last is treated as the <emphasis>destination</emphasis>, and all others are <emphasis>sources</emphasis>.</para> <para id="x_685">If you pass <command role="hg-cmd">hg copy</command> a single file as the source, and the destination does not exist, it creates a new file with that name.</para> &interaction.daily.copy.simple; <para id="x_1ce">If the destination is a directory, Mercurial copies its sources into that directory.</para> &interaction.daily.copy.dir-dest; <para id="x_1cf">Copying a directory is recursive, and preserves the directory structure of the source.</para> &interaction.daily.copy.dir-src; <para id="x_1d0">If the source and destination are both directories, the source tree is recreated in the destination directory.</para> &interaction.daily.copy.dir-src-dest; <para id="x_1d1">As with the <command role="hg-cmd">hg remove</command> command, if you copy a file manually and then want Mercurial to know that you've copied the file, simply use the <option role="hg-opt-copy">--after</option> option to <command role="hg-cmd">hg copy</command>.</para> &interaction.daily.copy.after; </sect2> </sect1> <sect1> <title>Renaming files</title> <para id="x_1d2">It's rather more common to need to rename a file than to make a copy of it. The reason I discussed the <command role="hg-cmd">hg copy</command> command before talking about renaming files is that Mercurial treats a rename in essentially the same way as a copy. Therefore, knowing what Mercurial does when you copy a file tells you what to expect when you rename a file.</para> <para id="x_1d3">When you use the <command role="hg-cmd">hg rename</command> command, Mercurial makes a copy of each source file, then deletes it and marks the file as removed.</para> &interaction.daily.rename.rename; <para id="x_1d4">The <command role="hg-cmd">hg status</command> command shows the newly copied file as added, and the copied-from file as removed.</para> &interaction.daily.rename.status; <para id="x_1d5">As with the results of a <command role="hg-cmd">hg copy</command>, we must use the <option role="hg-opt-status">-C</option> option to <command role="hg-cmd">hg status</command> to see that the added file is really being tracked by Mercurial as a copy of the original, now removed, file.</para> &interaction.daily.rename.status-copy; <para id="x_1d6">As with <command role="hg-cmd">hg remove</command> and <command role="hg-cmd">hg copy</command>, you can tell Mercurial about a rename after the fact using the <option role="hg-opt-rename">--after</option> option. In most other respects, the behavior of the <command role="hg-cmd">hg rename</command> command, and the options it accepts, are similar to the <command role="hg-cmd">hg copy</command> command.</para> <para id="x_686">If you're familiar with the Unix command line, you'll be glad to know that <command role="hg-cmd">hg rename</command> command can be invoked as <command role="hg-cmd">hg mv</command>.</para> <sect2> <title>Renaming files and merging changes</title> <para id="x_1d7">Since Mercurial's rename is implemented as copy-and-remove, the same propagation of changes happens when you merge after a rename as after a copy.</para> <para id="x_1d8">If I modify a file, and you rename it to a new name, and then we merge our respective changes, my modifications to the file under its original name will be propagated into the file under its new name. (This is something you might expect to <quote>simply work,</quote> but not all revision control systems actually do this.)</para> <para id="x_1d9">Whereas having changes follow a copy is a feature where you can perhaps nod and say <quote>yes, that might be useful,</quote> it should be clear that having them follow a rename is definitely important. Without this facility, it would simply be too easy for changes to become orphaned when files are renamed.</para> </sect2> <sect2> <title>Divergent renames and merging</title> <para id="x_1da">The case of diverging names occurs when two developers start with a file&emdash;let's call it <filename>foo</filename>&emdash;in their respective repositories.</para> &interaction.rename.divergent.clone; <para id="x_1db">Anne renames the file to <filename>bar</filename>.</para> &interaction.rename.divergent.rename.anne; <para id="x_1dc">Meanwhile, Bob renames it to <filename>quux</filename>. (Remember that <command role="hg-cmd">hg mv</command> is an alias for <command role="hg-cmd">hg rename</command>.)</para> &interaction.rename.divergent.rename.bob; <para id="x_1dd">I like to think of this as a conflict because each developer has expressed different intentions about what the file ought to be named.</para> <para id="x_1de">What do you think should happen when they merge their work? Mercurial's actual behavior is that it always preserves <emphasis>both</emphasis> names when it merges changesets that contain divergent renames.</para> &interaction.rename.divergent.merge; <para id="x_1df">Notice that while Mercurial warns about the divergent renames, it leaves it up to you to do something about the divergence after the merge.</para> </sect2> <sect2> <title>Convergent renames and merging</title> <para id="x_1e0">Another kind of rename conflict occurs when two people choose to rename different <emphasis>source</emphasis> files to the same <emphasis>destination</emphasis>. In this case, Mercurial runs its normal merge machinery, and lets you guide it to a suitable resolution.</para> </sect2> <sect2> <title>Other name-related corner cases</title> <para id="x_1e1">Mercurial has a longstanding bug in which it fails to handle a merge where one side has a file with a given name, while another has a directory with the same name. This is documented as <ulink role="hg-bug" url="http://www.selenic.com/mercurial/bts/issue29">issue 29</ulink>.</para> &interaction.issue29.go; </sect2> </sect1> <sect1> <title>Recovering from mistakes</title> <para id="x_1e2">Mercurial has some useful commands that will help you to recover from some common mistakes.</para> <para id="x_1e3">The <command role="hg-cmd">hg revert</command> command lets you undo changes that you have made to your working directory. For example, if you <command role="hg-cmd">hg add</command> a file by accident, just run <command role="hg-cmd">hg revert</command> with the name of the file you added, and while the file won't be touched in any way, it won't be tracked for adding by Mercurial any longer, either. You can also use <command role="hg-cmd">hg revert</command> to get rid of erroneous changes to a file.</para> <para id="x_1e4">It's good to remember that the <command role="hg-cmd">hg revert</command> command is useful for changes that you have not yet committed. Once you've committed a change, if you decide it was a mistake, you can still do something about it, though your options may be more limited.</para> <para id="x_1e5">For more information about the <command role="hg-cmd">hg revert</command> command, and details about how to deal with changes you have already committed, see <xref linkend="chap:undo"/>.</para> </sect1> <sect1> <title>Dealing with tricky merges</title> <para id="x_687">In a complicated or large project, it's not unusual for a merge of two changesets to result in some headaches. Suppose there's a big source file that's been extensively edited by each side of a merge: this is almost inevitably going to result in conflicts, some of which can take a few tries to sort out.</para> <para id="x_688">Let's develop a simple case of this and see how to deal with it. We'll start off with a repository containing one file, and clone it twice.</para> &interaction.ch04-resolve.init; <para id="x_689">In one clone, we'll modify the file in one way.</para> &interaction.ch04-resolve.left; <para id="x_68a">In another, we'll modify the file differently.</para> &interaction.ch04-resolve.right; <para id="x_68b">Next, we'll pull each set of changes into our original repo.</para> &interaction.ch04-resolve.pull; <para id="x_68c">We expect our repository to now contain two heads.</para> &interaction.ch04-resolve.heads; <para id="x_68d">Normally, if we run <command role="hg-cmd">hg merge</command> at this point, it will drop us into a GUI that will let us manually resolve the conflicting edits to <filename>myfile.txt</filename>. However, to simplify things for presentation here, we'd like the merge to fail immediately instead. Here's one way we can do so.</para> &interaction.ch04-resolve.export; <para id="x_68e">We've told Mercurial's merge machinery to run the command <command>false</command> (which, as we desire, fails immediately) if it detects a merge that it can't sort out automatically.</para> <para id="x_68f">If we now fire up <command role="hg-cmd">hg merge</command>, it should grind to a halt and report a failure.</para> &interaction.ch04-resolve.merge; <para id="x_690">Even if we don't notice that the merge failed, Mercurial will prevent us from accidentally committing the result of a failed merge.</para> &interaction.ch04-resolve.cifail; <para id="x_691">When <command role="hg-cmd">hg commit</command> fails in this case, it suggests that we use the unfamiliar <command role="hg-cmd">hg resolve</command> command. As usual, <command role="hg-cmd">hg help resolve</command> will print a helpful synopsis.</para> <sect2> <title>File resolution states</title> <para id="x_692">When a merge occurs, most files will usually remain unmodified. For each file where Mercurial has to do something, it tracks the state of the file.</para> <itemizedlist> <listitem> <para id="x_693">A <emphasis>resolved</emphasis> file has been successfully merged, either automatically by Mercurial or manually with human intervention.</para> </listitem> <listitem> <para id="x_694">An <emphasis>unresolved</emphasis> file was not merged successfully, and needs more attention.</para> </listitem> </itemizedlist> <para id="x_695">If Mercurial sees <emphasis>any</emphasis> file in the unresolved state after a merge, it considers the merge to have failed. Fortunately, we do not need to restart the entire merge from scratch.</para> <para id="x_696">The <option role="hg-opt-resolve">--list</option> or <option role="hg-opt-resolve">-l</option> option to <command role="hg-cmd">hg resolve</command> prints out the state of each merged file.</para> &interaction.ch04-resolve.list; <para id="x_697">In the output from <command role="hg-cmd">hg resolve</command>, a resolved file is marked with <literal>R</literal>, while an unresolved file is marked with <literal>U</literal>. If any files are listed with <literal>U</literal>, we know that an attempt to commit the results of the merge will fail.</para> </sect2> <sect2> <title>Resolving a file merge</title> <para id="x_698">We have several options to move a file from the unresolved into the resolved state. By far the most common is to rerun <command role="hg-cmd">hg resolve</command>. If we pass the names of individual files or directories, it will retry the merges of any unresolved files present in those locations. We can also pass the <option role="hg-opt-resolve">--all</option> or <option role="hg-opt-resolve">-a</option> option, which will retry the merges of <emphasis>all</emphasis> unresolved files.</para> <para id="x_699">Mercurial also lets us modify the resolution state of a file directly. We can manually mark a file as resolved using the <option role="hg-opt-resolve">--mark</option> option, or as unresolved using the <option role="hg-opt-resolve">--unmark</option> option. This allows us to clean up a particularly messy merge by hand, and to keep track of our progress with each file as we go.</para> </sect2> </sect1> </chapter> <!-- local variables: sgml-parent-document: ("00book.xml" "book" "chapter") end: -->