hgbook
annotate en/ch09-undo.xml @ 713:f87515a4a3cf
Remove dead symbolic links
for i in `find .`; do if (test -h $i); then file $i|grep broken; fi; done
./es/99book.bib: broken symbolic link to `../en/99book.bib'
./es/bookhtml.cfg: broken symbolic link to `../en/bookhtml.cfg'
./es/fixhtml.py: broken symbolic link to `../en/fixhtml.py'
./es/hgbook.css: broken symbolic link to `../en/hgbook.css'
./es/htlatex.book: broken symbolic link to `../en/htlatex.book'
for i in `find .`; do if (test -h $i); then file $i|grep broken; fi; done
./es/99book.bib: broken symbolic link to `../en/99book.bib'
./es/bookhtml.cfg: broken symbolic link to `../en/bookhtml.cfg'
./es/fixhtml.py: broken symbolic link to `../en/fixhtml.py'
./es/hgbook.css: broken symbolic link to `../en/hgbook.css'
./es/htlatex.book: broken symbolic link to `../en/htlatex.book'
author | Dongsheng Song <dongsheng.song@gmail.com> |
---|---|
date | Thu May 21 14:26:31 2009 +0800 (2009-05-21) |
parents | 1a0a78e197c3 |
children |
rev | line source |
---|---|
bos@559 | 1 <!-- vim: set filetype=docbkxml shiftwidth=2 autoindent expandtab tw=77 : --> |
bos@559 | 2 |
bos@559 | 3 <chapter id="chap:undo"> |
bos@572 | 4 <?dbhtml filename="finding-and-fixing-mistakes.html"?> |
bos@572 | 5 <title>Finding and fixing mistakes</title> |
bos@559 | 6 |
bos@584 | 7 <para id="x_d2">To err might be human, but to really handle the consequences |
bos@559 | 8 well takes a top-notch revision control system. In this chapter, |
bos@559 | 9 we'll discuss some of the techniques you can use when you find |
bos@559 | 10 that a problem has crept into your project. Mercurial has some |
bos@559 | 11 highly capable features that will help you to isolate the sources |
bos@559 | 12 of problems, and to handle them appropriately.</para> |
bos@559 | 13 |
bos@559 | 14 <sect1> |
bos@559 | 15 <title>Erasing local history</title> |
bos@559 | 16 |
bos@559 | 17 <sect2> |
bos@559 | 18 <title>The accidental commit</title> |
bos@559 | 19 |
bos@584 | 20 <para id="x_d3">I have the occasional but persistent problem of typing |
bos@559 | 21 rather more quickly than I can think, which sometimes results |
bos@559 | 22 in me committing a changeset that is either incomplete or |
bos@559 | 23 plain wrong. In my case, the usual kind of incomplete |
bos@559 | 24 changeset is one in which I've created a new source file, but |
bos@559 | 25 forgotten to <command role="hg-cmd">hg add</command> it. A |
bos@559 | 26 <quote>plain wrong</quote> changeset is not as common, but no |
bos@559 | 27 less annoying.</para> |
bos@559 | 28 |
bos@559 | 29 </sect2> |
bos@559 | 30 <sect2 id="sec:undo:rollback"> |
bos@559 | 31 <title>Rolling back a transaction</title> |
bos@559 | 32 |
bos@592 | 33 <para id="x_d4">In <xref linkend="sec:concepts:txn"/>, I |
bos@592 | 34 mentioned that Mercurial treats each modification of a |
bos@592 | 35 repository as a <emphasis>transaction</emphasis>. Every time |
bos@592 | 36 you commit a changeset or pull changes from another |
bos@592 | 37 repository, Mercurial remembers what you did. You can undo, |
bos@592 | 38 or <emphasis>roll back</emphasis>, exactly one of these |
bos@592 | 39 actions using the <command role="hg-cmd">hg rollback</command> |
bos@592 | 40 command. (See <xref linkend="sec:undo:rollback-after-push"/> |
bos@592 | 41 for an important caveat about the use of this command.)</para> |
bos@559 | 42 |
bos@584 | 43 <para id="x_d5">Here's a mistake that I often find myself making: |
bos@559 | 44 committing a change in which I've created a new file, but |
bos@567 | 45 forgotten to <command role="hg-cmd">hg add</command> |
bos@567 | 46 it.</para> |
bos@567 | 47 |
bos@567 | 48 &interaction.rollback.commit; |
bos@567 | 49 |
bos@584 | 50 <para id="x_d6">Looking at the output of <command role="hg-cmd">hg |
bos@567 | 51 status</command> after the commit immediately confirms the |
bos@567 | 52 error.</para> |
bos@567 | 53 |
bos@567 | 54 &interaction.rollback.status; |
bos@567 | 55 |
bos@584 | 56 <para id="x_d7">The commit captured the changes to the file |
bos@567 | 57 <filename>a</filename>, but not the new file |
bos@567 | 58 <filename>b</filename>. If I were to push this changeset to a |
bos@567 | 59 repository that I shared with a colleague, the chances are |
bos@567 | 60 high that something in <filename>a</filename> would refer to |
bos@567 | 61 <filename>b</filename>, which would not be present in their |
bos@559 | 62 repository when they pulled my changes. I would thus become |
bos@559 | 63 the object of some indignation.</para> |
bos@559 | 64 |
bos@584 | 65 <para id="x_d8">However, luck is with me&emdash;I've caught my error |
bos@559 | 66 before I pushed the changeset. I use the <command |
bos@559 | 67 role="hg-cmd">hg rollback</command> command, and Mercurial |
bos@567 | 68 makes that last changeset vanish.</para> |
bos@567 | 69 |
bos@567 | 70 &interaction.rollback.rollback; |
bos@567 | 71 |
bos@584 | 72 <para id="x_d9">Notice that the changeset is no longer present in the |
bos@567 | 73 repository's history, and the working directory once again |
bos@567 | 74 thinks that the file <filename>a</filename> is modified. The |
bos@567 | 75 commit and rollback have left the working directory exactly as |
bos@567 | 76 it was prior to the commit; the changeset has been completely |
bos@567 | 77 erased. I can now safely <command role="hg-cmd">hg |
bos@567 | 78 add</command> the file <filename>b</filename>, and rerun my |
bos@567 | 79 commit.</para> |
bos@567 | 80 |
bos@567 | 81 &interaction.rollback.add; |
bos@559 | 82 |
bos@559 | 83 </sect2> |
bos@559 | 84 <sect2> |
bos@559 | 85 <title>The erroneous pull</title> |
bos@559 | 86 |
bos@584 | 87 <para id="x_da">It's common practice with Mercurial to maintain separate |
bos@559 | 88 development branches of a project in different repositories. |
bos@559 | 89 Your development team might have one shared repository for |
bos@559 | 90 your project's <quote>0.9</quote> release, and another, |
bos@559 | 91 containing different changes, for the <quote>1.0</quote> |
bos@559 | 92 release.</para> |
bos@559 | 93 |
bos@584 | 94 <para id="x_db">Given this, you can imagine that the consequences could be |
bos@559 | 95 messy if you had a local <quote>0.9</quote> repository, and |
bos@559 | 96 accidentally pulled changes from the shared <quote>1.0</quote> |
bos@559 | 97 repository into it. At worst, you could be paying |
bos@559 | 98 insufficient attention, and push those changes into the shared |
bos@559 | 99 <quote>0.9</quote> tree, confusing your entire team (but don't |
bos@559 | 100 worry, we'll return to this horror scenario later). However, |
bos@559 | 101 it's more likely that you'll notice immediately, because |
bos@559 | 102 Mercurial will display the URL it's pulling from, or you will |
bos@559 | 103 see it pull a suspiciously large number of changes into the |
bos@559 | 104 repository.</para> |
bos@559 | 105 |
bos@584 | 106 <para id="x_dc">The <command role="hg-cmd">hg rollback</command> command |
bos@559 | 107 will work nicely to expunge all of the changesets that you |
bos@559 | 108 just pulled. Mercurial groups all changes from one <command |
bos@559 | 109 role="hg-cmd">hg pull</command> into a single transaction, |
bos@559 | 110 so one <command role="hg-cmd">hg rollback</command> is all you |
bos@559 | 111 need to undo this mistake.</para> |
bos@559 | 112 |
bos@559 | 113 </sect2> |
bos@559 | 114 <sect2 id="sec:undo:rollback-after-push"> |
bos@559 | 115 <title>Rolling back is useless once you've pushed</title> |
bos@559 | 116 |
bos@584 | 117 <para id="x_dd">The value of the <command role="hg-cmd">hg |
bos@559 | 118 rollback</command> command drops to zero once you've pushed |
bos@559 | 119 your changes to another repository. Rolling back a change |
bos@559 | 120 makes it disappear entirely, but <emphasis>only</emphasis> in |
bos@559 | 121 the repository in which you perform the <command |
bos@559 | 122 role="hg-cmd">hg rollback</command>. Because a rollback |
bos@559 | 123 eliminates history, there's no way for the disappearance of a |
bos@559 | 124 change to propagate between repositories.</para> |
bos@559 | 125 |
bos@584 | 126 <para id="x_de">If you've pushed a change to another |
bos@559 | 127 repository&emdash;particularly if it's a shared |
bos@559 | 128 repository&emdash;it has essentially <quote>escaped into the |
bos@559 | 129 wild,</quote> and you'll have to recover from your mistake |
bos@680 | 130 in a different way. If you push a changeset somewhere, then |
bos@680 | 131 roll it back, then pull from the repository you pushed to, the |
bos@680 | 132 changeset you thought you'd gotten rid of will simply reappear |
bos@680 | 133 in your repository.</para> |
bos@680 | 134 |
bos@680 | 135 <para id="x_df">(If you absolutely know for sure that the change |
bos@680 | 136 you want to roll back is the most recent change in the |
bos@680 | 137 repository that you pushed to, <emphasis>and</emphasis> you |
bos@680 | 138 know that nobody else could have pulled it from that |
bos@680 | 139 repository, you can roll back the changeset there, too, but |
bos@680 | 140 you really should not expect this to work reliably. Sooner or |
bos@559 | 141 later a change really will make it into a repository that you |
bos@559 | 142 don't directly control (or have forgotten about), and come |
bos@559 | 143 back to bite you.)</para> |
bos@559 | 144 |
bos@559 | 145 </sect2> |
bos@559 | 146 <sect2> |
bos@559 | 147 <title>You can only roll back once</title> |
bos@559 | 148 |
bos@584 | 149 <para id="x_e0">Mercurial stores exactly one transaction in its |
bos@559 | 150 transaction log; that transaction is the most recent one that |
bos@559 | 151 occurred in the repository. This means that you can only roll |
bos@559 | 152 back one transaction. If you expect to be able to roll back |
bos@559 | 153 one transaction, then its predecessor, this is not the |
bos@672 | 154 behavior you will get.</para> |
bos@567 | 155 |
bos@567 | 156 &interaction.rollback.twice; |
bos@567 | 157 |
bos@584 | 158 <para id="x_e1">Once you've rolled back one transaction in a repository, |
bos@567 | 159 you can't roll back again in that repository until you perform |
bos@559 | 160 another commit or pull.</para> |
bos@559 | 161 |
bos@559 | 162 </sect2> |
bos@559 | 163 </sect1> |
bos@559 | 164 <sect1> |
bos@559 | 165 <title>Reverting the mistaken change</title> |
bos@559 | 166 |
bos@584 | 167 <para id="x_e2">If you make a modification to a file, and decide that you |
bos@559 | 168 really didn't want to change the file at all, and you haven't |
bos@559 | 169 yet committed your changes, the <command role="hg-cmd">hg |
bos@559 | 170 revert</command> command is the one you'll need. It looks at |
bos@559 | 171 the changeset that's the parent of the working directory, and |
bos@559 | 172 restores the contents of the file to their state as of that |
bos@559 | 173 changeset. (That's a long-winded way of saying that, in the |
bos@559 | 174 normal case, it undoes your modifications.)</para> |
bos@559 | 175 |
bos@584 | 176 <para id="x_e3">Let's illustrate how the <command role="hg-cmd">hg |
bos@559 | 177 revert</command> command works with yet another small example. |
bos@559 | 178 We'll begin by modifying a file that Mercurial is already |
bos@567 | 179 tracking.</para> |
bos@567 | 180 |
bos@567 | 181 &interaction.daily.revert.modify; |
bos@567 | 182 |
bos@584 | 183 <para id="x_e4">If we don't |
bos@559 | 184 want that change, we can simply <command role="hg-cmd">hg |
bos@567 | 185 revert</command> the file.</para> |
bos@567 | 186 |
bos@567 | 187 &interaction.daily.revert.unmodify; |
bos@567 | 188 |
bos@584 | 189 <para id="x_e5">The <command role="hg-cmd">hg revert</command> command |
bos@567 | 190 provides us with an extra degree of safety by saving our |
bos@567 | 191 modified file with a <filename>.orig</filename> |
bos@567 | 192 extension.</para> |
bos@567 | 193 |
bos@567 | 194 &interaction.daily.revert.status; |
bos@559 | 195 |
bos@680 | 196 <tip> |
bos@680 | 197 <title>Be careful with <filename>.orig</filename> files</title> |
bos@680 | 198 |
bos@681 | 199 <para id="x_6b8">It's extremely unlikely that you are either using |
bos@680 | 200 Mercurial to manage files with <filename>.orig</filename> |
bos@680 | 201 extensions or that you even care about the contents of such |
bos@680 | 202 files. Just in case, though, it's useful to remember that |
bos@680 | 203 <command role="hg-cmd">hg revert</command> will |
bos@680 | 204 unconditionally overwrite an existing file with a |
bos@680 | 205 <filename>.orig</filename> extension. For instance, if you |
bos@680 | 206 already have a file named <filename>foo.orig</filename> when |
bos@680 | 207 you revert <filename>foo</filename>, the contents of |
bos@680 | 208 <filename>foo.orig</filename> will be clobbered.</para> |
bos@680 | 209 </tip> |
bos@680 | 210 |
bos@584 | 211 <para id="x_e6">Here is a summary of the cases that the <command |
bos@559 | 212 role="hg-cmd">hg revert</command> command can deal with. We |
bos@559 | 213 will describe each of these in more detail in the section that |
bos@559 | 214 follows.</para> |
bos@559 | 215 <itemizedlist> |
bos@584 | 216 <listitem><para id="x_e7">If you modify a file, it will restore the file |
bos@559 | 217 to its unmodified state.</para> |
bos@559 | 218 </listitem> |
bos@584 | 219 <listitem><para id="x_e8">If you <command role="hg-cmd">hg add</command> a |
bos@559 | 220 file, it will undo the <quote>added</quote> state of the |
bos@559 | 221 file, but leave the file itself untouched.</para> |
bos@559 | 222 </listitem> |
bos@584 | 223 <listitem><para id="x_e9">If you delete a file without telling Mercurial, |
bos@559 | 224 it will restore the file to its unmodified contents.</para> |
bos@559 | 225 </listitem> |
bos@584 | 226 <listitem><para id="x_ea">If you use the <command role="hg-cmd">hg |
bos@559 | 227 remove</command> command to remove a file, it will undo |
bos@559 | 228 the <quote>removed</quote> state of the file, and restore |
bos@559 | 229 the file to its unmodified contents.</para> |
bos@559 | 230 </listitem></itemizedlist> |
bos@559 | 231 |
bos@559 | 232 <sect2 id="sec:undo:mgmt"> |
bos@559 | 233 <title>File management errors</title> |
bos@559 | 234 |
bos@584 | 235 <para id="x_eb">The <command role="hg-cmd">hg revert</command> command is |
bos@559 | 236 useful for more than just modified files. It lets you reverse |
bos@559 | 237 the results of all of Mercurial's file management |
bos@559 | 238 commands&emdash;<command role="hg-cmd">hg add</command>, |
bos@559 | 239 <command role="hg-cmd">hg remove</command>, and so on.</para> |
bos@559 | 240 |
bos@584 | 241 <para id="x_ec">If you <command role="hg-cmd">hg add</command> a file, |
bos@559 | 242 then decide that in fact you don't want Mercurial to track it, |
bos@559 | 243 use <command role="hg-cmd">hg revert</command> to undo the |
bos@559 | 244 add. Don't worry; Mercurial will not modify the file in any |
bos@567 | 245 way. It will just <quote>unmark</quote> the file.</para> |
bos@567 | 246 |
bos@567 | 247 &interaction.daily.revert.add; |
bos@559 | 248 |
bos@584 | 249 <para id="x_ed">Similarly, if you ask Mercurial to <command |
bos@559 | 250 role="hg-cmd">hg remove</command> a file, you can use |
bos@559 | 251 <command role="hg-cmd">hg revert</command> to restore it to |
bos@559 | 252 the contents it had as of the parent of the working directory. |
bos@567 | 253 &interaction.daily.revert.remove; This works just as |
bos@559 | 254 well for a file that you deleted by hand, without telling |
bos@559 | 255 Mercurial (recall that in Mercurial terminology, this kind of |
bos@567 | 256 file is called <quote>missing</quote>).</para> |
bos@567 | 257 |
bos@567 | 258 &interaction.daily.revert.missing; |
bos@559 | 259 |
bos@584 | 260 <para id="x_ee">If you revert a <command role="hg-cmd">hg copy</command>, |
bos@559 | 261 the copied-to file remains in your working directory |
bos@559 | 262 afterwards, untracked. Since a copy doesn't affect the |
bos@559 | 263 copied-from file in any way, Mercurial doesn't do anything |
bos@567 | 264 with the copied-from file.</para> |
bos@567 | 265 |
bos@567 | 266 &interaction.daily.revert.copy; |
bos@559 | 267 </sect2> |
bos@559 | 268 </sect1> |
bos@680 | 269 |
bos@559 | 270 <sect1> |
bos@559 | 271 <title>Dealing with committed changes</title> |
bos@559 | 272 |
bos@680 | 273 <para id="x_f5">Consider a case where you have committed a change |
bos@680 | 274 <emphasis>a</emphasis>, and another change |
bos@680 | 275 <emphasis>b</emphasis> on top of it; you then realise that |
bos@680 | 276 change <emphasis>a</emphasis> was incorrect. Mercurial lets you |
bos@680 | 277 <quote>back out</quote> an entire changeset automatically, and |
bos@680 | 278 building blocks that let you reverse part of a changeset by |
bos@680 | 279 hand.</para> |
bos@559 | 280 |
bos@592 | 281 <para id="x_f6">Before you read this section, here's something to |
bos@592 | 282 keep in mind: the <command role="hg-cmd">hg backout</command> |
bos@680 | 283 command undoes the effect of a change by |
bos@680 | 284 <emphasis>adding</emphasis> to your repository's history, not by |
bos@680 | 285 modifying or erasing it. It's the right tool to use if you're |
bos@680 | 286 fixing bugs, but not if you're trying to undo some change that |
bos@680 | 287 has catastrophic consequences. To deal with those, see |
bos@559 | 288 <xref linkend="sec:undo:aaaiiieee"/>.</para> |
bos@559 | 289 |
bos@559 | 290 <sect2> |
bos@559 | 291 <title>Backing out a changeset</title> |
bos@559 | 292 |
bos@584 | 293 <para id="x_f7">The <command role="hg-cmd">hg backout</command> command |
bos@559 | 294 lets you <quote>undo</quote> the effects of an entire |
bos@559 | 295 changeset in an automated fashion. Because Mercurial's |
bos@559 | 296 history is immutable, this command <emphasis>does |
bos@559 | 297 not</emphasis> get rid of the changeset you want to undo. |
bos@559 | 298 Instead, it creates a new changeset that |
bos@559 | 299 <emphasis>reverses</emphasis> the effect of the to-be-undone |
bos@559 | 300 changeset.</para> |
bos@559 | 301 |
bos@584 | 302 <para id="x_f8">The operation of the <command role="hg-cmd">hg |
bos@559 | 303 backout</command> command is a little intricate, so let's |
bos@559 | 304 illustrate it with some examples. First, we'll create a |
bos@567 | 305 repository with some simple changes.</para> |
bos@567 | 306 |
bos@567 | 307 &interaction.backout.init; |
bos@559 | 308 |
bos@584 | 309 <para id="x_f9">The <command role="hg-cmd">hg backout</command> command |
bos@559 | 310 takes a single changeset ID as its argument; this is the |
bos@559 | 311 changeset to back out. Normally, <command role="hg-cmd">hg |
bos@559 | 312 backout</command> will drop you into a text editor to write |
bos@559 | 313 a commit message, so you can record why you're backing the |
bos@559 | 314 change out. In this example, we provide a commit message on |
bos@559 | 315 the command line using the <option |
bos@559 | 316 role="hg-opt-backout">-m</option> option.</para> |
bos@559 | 317 |
bos@559 | 318 </sect2> |
bos@559 | 319 <sect2> |
bos@559 | 320 <title>Backing out the tip changeset</title> |
bos@559 | 321 |
bos@584 | 322 <para id="x_fa">We're going to start by backing out the last changeset we |
bos@567 | 323 committed.</para> |
bos@567 | 324 |
bos@567 | 325 &interaction.backout.simple; |
bos@567 | 326 |
bos@584 | 327 <para id="x_fb">You can see that the second line from |
bos@567 | 328 <filename>myfile</filename> is no longer present. Taking a |
bos@567 | 329 look at the output of <command role="hg-cmd">hg log</command> |
bos@567 | 330 gives us an idea of what the <command role="hg-cmd">hg |
bos@567 | 331 backout</command> command has done. |
bos@567 | 332 &interaction.backout.simple.log; Notice that the new changeset |
bos@567 | 333 that <command role="hg-cmd">hg backout</command> has created |
bos@567 | 334 is a child of the changeset we backed out. It's easier to see |
bos@592 | 335 this in <xref linkend="fig:undo:backout"/>, which presents a |
bos@592 | 336 graphical view of the change history. As you can see, the |
bos@592 | 337 history is nice and linear.</para> |
bos@559 | 338 |
bos@591 | 339 <figure id="fig:undo:backout"> |
bos@591 | 340 <title>Backing out a change using the <command |
bos@591 | 341 role="hg-cmd">hg backout</command> command</title> |
bos@591 | 342 <mediaobject> |
bos@594 | 343 <imageobject><imagedata fileref="figs/undo-simple.png"/></imageobject> |
bos@591 | 344 <textobject><phrase>XXX add text</phrase></textobject> |
bos@591 | 345 </mediaobject> |
bos@591 | 346 </figure> |
bos@559 | 347 |
bos@559 | 348 </sect2> |
bos@559 | 349 <sect2> |
bos@559 | 350 <title>Backing out a non-tip change</title> |
bos@559 | 351 |
bos@584 | 352 <para id="x_fd">If you want to back out a change other than the last one |
bos@559 | 353 you committed, pass the <option |
bos@559 | 354 role="hg-opt-backout">--merge</option> option to the |
bos@567 | 355 <command role="hg-cmd">hg backout</command> command.</para> |
bos@567 | 356 |
bos@567 | 357 &interaction.backout.non-tip.clone; |
bos@567 | 358 |
bos@584 | 359 <para id="x_fe">This makes backing out any changeset a |
bos@567 | 360 <quote>one-shot</quote> operation that's usually simple and |
bos@567 | 361 fast.</para> |
bos@567 | 362 |
bos@567 | 363 &interaction.backout.non-tip.backout; |
bos@559 | 364 |
bos@584 | 365 <para id="x_ff">If you take a look at the contents of |
bos@559 | 366 <filename>myfile</filename> after the backout finishes, you'll |
bos@559 | 367 see that the first and third changes are present, but not the |
bos@567 | 368 second.</para> |
bos@567 | 369 |
bos@567 | 370 &interaction.backout.non-tip.cat; |
bos@559 | 371 |
bos@592 | 372 <para id="x_100">As the graphical history in <xref |
bos@559 | 373 linkend="fig:undo:backout-non-tip"/> illustrates, Mercurial |
bos@680 | 374 still commits one change in this kind of situation (the |
bos@680 | 375 box-shaped node is the ones that Mercurial commits |
bos@680 | 376 automatically), but the revision graph now looks different. |
bos@680 | 377 Before Mercurial begins the backout process, it first |
bos@680 | 378 remembers what the current parent of the working directory is. |
bos@680 | 379 It then backs out the target changeset, and commits that as a |
bos@680 | 380 changeset. Finally, it merges back to the previous parent of |
bos@680 | 381 the working directory, but notice that it <emphasis>does not |
bos@680 | 382 commit</emphasis> the result of the merge. The repository |
bos@680 | 383 now contains two heads, and the working directory is in a |
bos@680 | 384 merge state.</para> |
bos@559 | 385 |
bos@591 | 386 <figure id="fig:undo:backout-non-tip"> |
bos@591 | 387 <title>Automated backout of a non-tip change using the |
bos@591 | 388 <command role="hg-cmd">hg backout</command> command</title> |
bos@591 | 389 <mediaobject> |
bos@594 | 390 <imageobject><imagedata fileref="figs/undo-non-tip.png"/></imageobject> |
bos@591 | 391 <textobject><phrase>XXX add text</phrase></textobject> |
bos@591 | 392 </mediaobject> |
bos@591 | 393 </figure> |
bos@559 | 394 |
bos@584 | 395 <para id="x_103">The result is that you end up <quote>back where you |
bos@559 | 396 were</quote>, only with some extra history that undoes the |
bos@559 | 397 effect of the changeset you wanted to back out.</para> |
bos@559 | 398 |
bos@681 | 399 <para id="x_6b9">You might wonder why Mercurial does not commit the result |
bos@680 | 400 of the merge that it performed. The reason lies in Mercurial |
bos@680 | 401 behaving conservatively: a merge naturally has more scope for |
bos@680 | 402 error than simply undoing the effect of the tip changeset, |
bos@680 | 403 so your work will be safest if you first inspect (and test!) |
bos@680 | 404 the result of the merge, <emphasis>then</emphasis> commit |
bos@680 | 405 it.</para> |
bos@680 | 406 |
bos@559 | 407 <sect3> |
bos@559 | 408 <title>Always use the <option |
bos@559 | 409 role="hg-opt-backout">--merge</option> option</title> |
bos@559 | 410 |
bos@584 | 411 <para id="x_104">In fact, since the <option |
bos@559 | 412 role="hg-opt-backout">--merge</option> option will do the |
bos@559 | 413 <quote>right thing</quote> whether or not the changeset |
bos@559 | 414 you're backing out is the tip (i.e. it won't try to merge if |
bos@559 | 415 it's backing out the tip, since there's no need), you should |
bos@559 | 416 <emphasis>always</emphasis> use this option when you run the |
bos@559 | 417 <command role="hg-cmd">hg backout</command> command.</para> |
bos@559 | 418 |
bos@559 | 419 </sect3> |
bos@559 | 420 </sect2> |
bos@559 | 421 <sect2> |
bos@559 | 422 <title>Gaining more control of the backout process</title> |
bos@559 | 423 |
bos@584 | 424 <para id="x_105">While I've recommended that you always use the <option |
bos@559 | 425 role="hg-opt-backout">--merge</option> option when backing |
bos@559 | 426 out a change, the <command role="hg-cmd">hg backout</command> |
bos@559 | 427 command lets you decide how to merge a backout changeset. |
bos@559 | 428 Taking control of the backout process by hand is something you |
bos@559 | 429 will rarely need to do, but it can be useful to understand |
bos@559 | 430 what the <command role="hg-cmd">hg backout</command> command |
bos@559 | 431 is doing for you automatically. To illustrate this, let's |
bos@559 | 432 clone our first repository, but omit the backout change that |
bos@559 | 433 it contains.</para> |
bos@559 | 434 |
bos@567 | 435 &interaction.backout.manual.clone; |
bos@567 | 436 |
bos@584 | 437 <para id="x_106">As with our |
bos@559 | 438 earlier example, We'll commit a third changeset, then back out |
bos@567 | 439 its parent, and see what happens.</para> |
bos@567 | 440 |
bos@567 | 441 &interaction.backout.manual.backout; |
bos@567 | 442 |
bos@584 | 443 <para id="x_107">Our new changeset is again a descendant of the changeset |
bos@567 | 444 we backout out; it's thus a new head, <emphasis>not</emphasis> |
bos@567 | 445 a descendant of the changeset that was the tip. The <command |
bos@567 | 446 role="hg-cmd">hg backout</command> command was quite |
bos@567 | 447 explicit in telling us this.</para> |
bos@567 | 448 |
bos@567 | 449 &interaction.backout.manual.log; |
bos@559 | 450 |
bos@584 | 451 <para id="x_108">Again, it's easier to see what has happened by looking at |
bos@592 | 452 a graph of the revision history, in <xref |
bos@559 | 453 linkend="fig:undo:backout-manual"/>. This makes it clear |
bos@559 | 454 that when we use <command role="hg-cmd">hg backout</command> |
bos@559 | 455 to back out a change other than the tip, Mercurial adds a new |
bos@559 | 456 head to the repository (the change it committed is |
bos@559 | 457 box-shaped).</para> |
bos@559 | 458 |
bos@591 | 459 <figure id="fig:undo:backout-manual"> |
bos@591 | 460 <title>Backing out a change using the <command |
bos@591 | 461 role="hg-cmd">hg backout</command> command</title> |
bos@591 | 462 <mediaobject> |
bos@594 | 463 <imageobject><imagedata fileref="figs/undo-manual.png"/></imageobject> |
bos@591 | 464 <textobject><phrase>XXX add text</phrase></textobject> |
bos@591 | 465 </mediaobject> |
bos@591 | 466 </figure> |
bos@559 | 467 |
bos@584 | 468 <para id="x_10a">After the <command role="hg-cmd">hg backout</command> |
bos@559 | 469 command has completed, it leaves the new |
bos@559 | 470 <quote>backout</quote> changeset as the parent of the working |
bos@567 | 471 directory.</para> |
bos@567 | 472 |
bos@567 | 473 &interaction.backout.manual.parents; |
bos@567 | 474 |
bos@584 | 475 <para id="x_10b">Now we have two isolated sets of changes.</para> |
bos@567 | 476 |
bos@567 | 477 &interaction.backout.manual.heads; |
bos@559 | 478 |
bos@584 | 479 <para id="x_10c">Let's think about what we expect to see as the contents of |
bos@559 | 480 <filename>myfile</filename> now. The first change should be |
bos@559 | 481 present, because we've never backed it out. The second change |
bos@559 | 482 should be missing, as that's the change we backed out. Since |
bos@559 | 483 the history graph shows the third change as a separate head, |
bos@559 | 484 we <emphasis>don't</emphasis> expect to see the third change |
bos@567 | 485 present in <filename>myfile</filename>.</para> |
bos@567 | 486 |
bos@567 | 487 &interaction.backout.manual.cat; |
bos@567 | 488 |
bos@584 | 489 <para id="x_10d">To get the third change back into the file, we just do a |
bos@567 | 490 normal merge of our two heads.</para> |
bos@567 | 491 |
bos@567 | 492 &interaction.backout.manual.merge; |
bos@567 | 493 |
bos@592 | 494 <para id="x_10e">Afterwards, the graphical history of our |
bos@592 | 495 repository looks like |
bos@559 | 496 <xref linkend="fig:undo:backout-manual-merge"/>.</para> |
bos@559 | 497 |
bos@591 | 498 <figure id="fig:undo:backout-manual-merge"> |
bos@591 | 499 <title>Manually merging a backout change</title> |
bos@591 | 500 <mediaobject> |
bos@594 | 501 <imageobject><imagedata fileref="figs/undo-manual-merge.png"/></imageobject> |
bos@591 | 502 <textobject><phrase>XXX add text</phrase></textobject> |
bos@591 | 503 </mediaobject> |
bos@591 | 504 </figure> |
bos@559 | 505 |
bos@559 | 506 </sect2> |
bos@559 | 507 <sect2> |
bos@559 | 508 <title>Why <command role="hg-cmd">hg backout</command> works as |
bos@559 | 509 it does</title> |
bos@559 | 510 |
bos@584 | 511 <para id="x_110">Here's a brief description of how the <command |
bos@559 | 512 role="hg-cmd">hg backout</command> command works.</para> |
bos@559 | 513 <orderedlist> |
bos@584 | 514 <listitem><para id="x_111">It ensures that the working directory is |
bos@559 | 515 <quote>clean</quote>, i.e. that the output of <command |
bos@559 | 516 role="hg-cmd">hg status</command> would be empty.</para> |
bos@559 | 517 </listitem> |
bos@584 | 518 <listitem><para id="x_112">It remembers the current parent of the working |
bos@559 | 519 directory. Let's call this changeset |
bos@680 | 520 <literal>orig</literal>.</para> |
bos@559 | 521 </listitem> |
bos@584 | 522 <listitem><para id="x_113">It does the equivalent of a <command |
bos@559 | 523 role="hg-cmd">hg update</command> to sync the working |
bos@559 | 524 directory to the changeset you want to back out. Let's |
bos@680 | 525 call this changeset <literal>backout</literal>.</para> |
bos@559 | 526 </listitem> |
bos@584 | 527 <listitem><para id="x_114">It finds the parent of that changeset. Let's |
bos@559 | 528 call that changeset <literal>parent</literal>.</para> |
bos@559 | 529 </listitem> |
bos@584 | 530 <listitem><para id="x_115">For each file that the |
bos@559 | 531 <literal>backout</literal> changeset affected, it does the |
bos@559 | 532 equivalent of a <command role="hg-cmd">hg revert -r |
bos@559 | 533 parent</command> on that file, to restore it to the |
bos@559 | 534 contents it had before that changeset was |
bos@559 | 535 committed.</para> |
bos@559 | 536 </listitem> |
bos@584 | 537 <listitem><para id="x_116">It commits the result as a new changeset. |
bos@559 | 538 This changeset has <literal>backout</literal> as its |
bos@559 | 539 parent.</para> |
bos@559 | 540 </listitem> |
bos@584 | 541 <listitem><para id="x_117">If you specify <option |
bos@559 | 542 role="hg-opt-backout">--merge</option> on the command |
bos@559 | 543 line, it merges with <literal>orig</literal>, and commits |
bos@559 | 544 the result of the merge.</para> |
bos@559 | 545 </listitem></orderedlist> |
bos@559 | 546 |
bos@584 | 547 <para id="x_118">An alternative way to implement the <command |
bos@559 | 548 role="hg-cmd">hg backout</command> command would be to |
bos@559 | 549 <command role="hg-cmd">hg export</command> the |
bos@559 | 550 to-be-backed-out changeset as a diff, then use the <option |
bos@559 | 551 role="cmd-opt-patch">--reverse</option> option to the |
bos@559 | 552 <command>patch</command> command to reverse the effect of the |
bos@559 | 553 change without fiddling with the working directory. This |
bos@559 | 554 sounds much simpler, but it would not work nearly as |
bos@559 | 555 well.</para> |
bos@559 | 556 |
bos@584 | 557 <para id="x_119">The reason that <command role="hg-cmd">hg |
bos@559 | 558 backout</command> does an update, a commit, a merge, and |
bos@559 | 559 another commit is to give the merge machinery the best chance |
bos@559 | 560 to do a good job when dealing with all the changes |
bos@559 | 561 <emphasis>between</emphasis> the change you're backing out and |
bos@559 | 562 the current tip.</para> |
bos@559 | 563 |
bos@584 | 564 <para id="x_11a">If you're backing out a changeset that's 100 revisions |
bos@559 | 565 back in your project's history, the chances that the |
bos@559 | 566 <command>patch</command> command will be able to apply a |
bos@559 | 567 reverse diff cleanly are not good, because intervening changes |
bos@559 | 568 are likely to have <quote>broken the context</quote> that |
bos@559 | 569 <command>patch</command> uses to determine whether it can |
bos@559 | 570 apply a patch (if this sounds like gibberish, see <xref |
bos@559 | 571 linkend="sec:mq:patch"/> for a |
bos@559 | 572 discussion of the <command>patch</command> command). Also, |
bos@559 | 573 Mercurial's merge machinery will handle files and directories |
bos@559 | 574 being renamed, permission changes, and modifications to binary |
bos@559 | 575 files, none of which <command>patch</command> can deal |
bos@559 | 576 with.</para> |
bos@559 | 577 |
bos@559 | 578 </sect2> |
bos@559 | 579 </sect1> |
bos@559 | 580 <sect1 id="sec:undo:aaaiiieee"> |
bos@559 | 581 <title>Changes that should never have been</title> |
bos@559 | 582 |
bos@584 | 583 <para id="x_11b">Most of the time, the <command role="hg-cmd">hg |
bos@559 | 584 backout</command> command is exactly what you need if you want |
bos@559 | 585 to undo the effects of a change. It leaves a permanent record |
bos@559 | 586 of exactly what you did, both when committing the original |
bos@559 | 587 changeset and when you cleaned up after it.</para> |
bos@559 | 588 |
bos@584 | 589 <para id="x_11c">On rare occasions, though, you may find that you've |
bos@559 | 590 committed a change that really should not be present in the |
bos@559 | 591 repository at all. For example, it would be very unusual, and |
bos@559 | 592 usually considered a mistake, to commit a software project's |
bos@559 | 593 object files as well as its source files. Object files have |
bos@559 | 594 almost no intrinsic value, and they're <emphasis>big</emphasis>, |
bos@559 | 595 so they increase the size of the repository and the amount of |
bos@559 | 596 time it takes to clone or pull changes.</para> |
bos@559 | 597 |
bos@584 | 598 <para id="x_11d">Before I discuss the options that you have if you commit a |
bos@559 | 599 <quote>brown paper bag</quote> change (the kind that's so bad |
bos@559 | 600 that you want to pull a brown paper bag over your head), let me |
bos@559 | 601 first discuss some approaches that probably won't work.</para> |
bos@559 | 602 |
bos@592 | 603 <para id="x_11e">Since Mercurial treats history as |
bos@592 | 604 accumulative&emdash;every change builds on top of all changes |
bos@592 | 605 that preceded it&emdash;you generally can't just make disastrous |
bos@592 | 606 changes disappear. The one exception is when you've just |
bos@592 | 607 committed a change, and it hasn't been pushed or pulled into |
bos@592 | 608 another repository. That's when you can safely use the <command |
bos@592 | 609 role="hg-cmd">hg rollback</command> command, as I detailed in |
bos@592 | 610 <xref linkend="sec:undo:rollback"/>.</para> |
bos@559 | 611 |
bos@584 | 612 <para id="x_11f">After you've pushed a bad change to another repository, you |
bos@559 | 613 <emphasis>could</emphasis> still use <command role="hg-cmd">hg |
bos@559 | 614 rollback</command> to make your local copy of the change |
bos@559 | 615 disappear, but it won't have the consequences you want. The |
bos@559 | 616 change will still be present in the remote repository, so it |
bos@559 | 617 will reappear in your local repository the next time you |
bos@559 | 618 pull.</para> |
bos@559 | 619 |
bos@584 | 620 <para id="x_120">If a situation like this arises, and you know which |
bos@559 | 621 repositories your bad change has propagated into, you can |
bos@680 | 622 <emphasis>try</emphasis> to get rid of the change from |
bos@559 | 623 <emphasis>every</emphasis> one of those repositories. This is, |
bos@559 | 624 of course, not a satisfactory solution: if you miss even a |
bos@559 | 625 single repository while you're expunging, the change is still |
bos@559 | 626 <quote>in the wild</quote>, and could propagate further.</para> |
bos@559 | 627 |
bos@584 | 628 <para id="x_121">If you've committed one or more changes |
bos@559 | 629 <emphasis>after</emphasis> the change that you'd like to see |
bos@559 | 630 disappear, your options are further reduced. Mercurial doesn't |
bos@559 | 631 provide a way to <quote>punch a hole</quote> in history, leaving |
bos@559 | 632 changesets intact.</para> |
bos@559 | 633 |
bos@680 | 634 <sect2> |
bos@680 | 635 <title>Backing out a merge</title> |
bos@680 | 636 |
bos@681 | 637 <para id="x_6ba">Since merges are often complicated, it is not unheard of |
bos@680 | 638 for a merge to be mangled badly, but committed erroneously. |
bos@680 | 639 Mercurial provides an important safeguard against bad merges |
bos@680 | 640 by refusing to commit unresolved files, but human ingenuity |
bos@680 | 641 guarantees that it is still possible to mess a merge up and |
bos@680 | 642 commit it.</para> |
bos@680 | 643 |
bos@681 | 644 <para id="x_6bb">Given a bad merge that has been committed, usually the |
bos@680 | 645 best way to approach it is to simply try to repair the damage |
bos@680 | 646 by hand. A complete disaster that cannot be easily fixed up |
bos@680 | 647 by hand ought to be very rare, but the <command |
bos@680 | 648 role="hg-cmd">hg backout</command> command may help in |
bos@680 | 649 making the cleanup easier. It offers a <option |
bos@680 | 650 role="hg-opt-backout">--parent</option> option, which lets |
bos@680 | 651 you specify which parent to revert to when backing out a |
bos@680 | 652 merge.</para> |
bos@680 | 653 |
bos@680 | 654 <figure id="fig:undo:bad-merge-1"> |
bos@680 | 655 <title>A bad merge</title> |
bos@680 | 656 <mediaobject> |
bos@680 | 657 <imageobject><imagedata fileref="figs/bad-merge-1.png"/></imageobject> |
bos@680 | 658 <textobject><phrase>XXX add text</phrase></textobject> |
bos@680 | 659 </mediaobject> |
bos@680 | 660 </figure> |
bos@680 | 661 |
bos@681 | 662 <para id="x_6bc">Suppose we have a revision graph like that in <xref |
bos@680 | 663 linkend="fig:undo:bad-merge-1"/>. What we'd like is to |
bos@680 | 664 <emphasis>redo</emphasis> the merge of revisions 2 and |
bos@680 | 665 3.</para> |
bos@680 | 666 |
bos@681 | 667 <para id="x_6bd">One way to do so would be as follows.</para> |
bos@680 | 668 |
bos@680 | 669 <orderedlist> |
bos@680 | 670 <listitem> |
bos@681 | 671 <para id="x_6be">Call <command role="hg-cmd">hg backout --rev=4 |
bos@680 | 672 --parent=2</command>. This tells <command |
bos@680 | 673 role="hg-cmd">hg backout</command> to back out revision |
bos@680 | 674 4, which is the bad merge, and to when deciding which |
bos@680 | 675 revision to prefer, to choose parent 2, one of the parents |
bos@680 | 676 of the merge. The effect can be seen in <xref |
bos@680 | 677 linkend="fig:undo:bad-merge-2"/>.</para> |
bos@680 | 678 <figure id="fig:undo:bad-merge-2"> |
bos@680 | 679 <title>Backing out the merge, favoring one parent</title> |
bos@680 | 680 <mediaobject> |
bos@680 | 681 <imageobject><imagedata fileref="figs/bad-merge-2.png"/></imageobject> |
bos@680 | 682 <textobject><phrase>XXX add text</phrase></textobject> |
bos@680 | 683 </mediaobject> |
bos@680 | 684 </figure> |
bos@680 | 685 </listitem> |
bos@680 | 686 |
bos@680 | 687 <listitem> |
bos@681 | 688 <para id="x_6bf">Call <command role="hg-cmd">hg backout --rev=4 |
bos@680 | 689 --parent=3</command>. This tells <command |
bos@680 | 690 role="hg-cmd">hg backout</command> to back out revision |
bos@680 | 691 4 again, but this time to choose parent 3, the other |
bos@680 | 692 parent of the merge. The result is visible in <xref |
bos@680 | 693 linkend="fig:undo:bad-merge-3"/>, in which the repository |
bos@680 | 694 now contains three heads.</para> |
bos@680 | 695 <figure id="fig:undo:bad-merge-3"> |
bos@680 | 696 <title>Backing out the merge, favoring the other |
bos@680 | 697 parent</title> |
bos@680 | 698 <mediaobject> |
bos@680 | 699 <imageobject><imagedata fileref="figs/bad-merge-3.png"/></imageobject> |
bos@680 | 700 <textobject><phrase>XXX add text</phrase></textobject> |
bos@680 | 701 </mediaobject> |
bos@680 | 702 </figure> |
bos@680 | 703 </listitem> |
bos@680 | 704 |
bos@680 | 705 <listitem> |
bos@681 | 706 <para id="x_6c0">Redo the bad merge by merging the two backout heads, |
bos@680 | 707 which reduces the number of heads in the repository to |
bos@680 | 708 two, as can be seen in <xref |
bos@680 | 709 linkend="fig:undo:bad-merge-4"/>.</para> |
bos@680 | 710 <figure id="fig:undo:bad-merge-4"> |
bos@680 | 711 <title>Merging the backouts</title> |
bos@680 | 712 <mediaobject> |
bos@680 | 713 <imageobject><imagedata fileref="figs/bad-merge-4.png"/></imageobject> |
bos@680 | 714 <textobject><phrase>XXX add text</phrase></textobject> |
bos@680 | 715 </mediaobject> |
bos@680 | 716 </figure> |
bos@680 | 717 </listitem> |
bos@680 | 718 |
bos@680 | 719 <listitem> |
bos@681 | 720 <para id="x_6c1">Merge with the commit that was made after the bad |
bos@680 | 721 merge, as shown in <xref |
bos@680 | 722 linkend="fig:undo:bad-merge-5"/>.</para> |
bos@680 | 723 <figure id="fig:undo:bad-merge-5"> |
bos@680 | 724 <title>Merging the backouts</title> |
bos@680 | 725 <mediaobject> |
bos@680 | 726 <imageobject><imagedata fileref="figs/bad-merge-5.png"/></imageobject> |
bos@680 | 727 <textobject><phrase>XXX add text</phrase></textobject> |
bos@680 | 728 </mediaobject> |
bos@680 | 729 </figure> |
bos@680 | 730 </listitem> |
bos@680 | 731 </orderedlist> |
bos@680 | 732 </sect2> |
bos@559 | 733 |
bos@559 | 734 <sect2> |
bos@559 | 735 <title>Protect yourself from <quote>escaped</quote> |
bos@559 | 736 changes</title> |
bos@559 | 737 |
bos@584 | 738 <para id="x_123">If you've committed some changes to your local repository |
bos@559 | 739 and they've been pushed or pulled somewhere else, this isn't |
bos@559 | 740 necessarily a disaster. You can protect yourself ahead of |
bos@559 | 741 time against some classes of bad changeset. This is |
bos@559 | 742 particularly easy if your team usually pulls changes from a |
bos@559 | 743 central repository.</para> |
bos@559 | 744 |
bos@584 | 745 <para id="x_124">By configuring some hooks on that repository to validate |
bos@559 | 746 incoming changesets (see chapter <xref linkend="chap:hook"/>), |
bos@559 | 747 you can |
bos@559 | 748 automatically prevent some kinds of bad changeset from being |
bos@559 | 749 pushed to the central repository at all. With such a |
bos@559 | 750 configuration in place, some kinds of bad changeset will |
bos@559 | 751 naturally tend to <quote>die out</quote> because they can't |
bos@559 | 752 propagate into the central repository. Better yet, this |
bos@559 | 753 happens without any need for explicit intervention.</para> |
bos@559 | 754 |
bos@680 | 755 <para id="x_125">For instance, an incoming change hook that |
bos@680 | 756 verifies that a changeset will actually compile can prevent |
bos@680 | 757 people from inadvertently <quote>breaking the |
bos@680 | 758 build</quote>.</para> |
bos@680 | 759 </sect2> |
bos@680 | 760 |
bos@680 | 761 <sect2> |
bos@680 | 762 <title>What to do about sensitive changes that escape</title> |
bos@680 | 763 |
bos@681 | 764 <para id="x_6c2">Even a carefully run project can suffer an unfortunate |
bos@680 | 765 event such as the committing and uncontrolled propagation of a |
bos@680 | 766 file that contains important passwords.</para> |
bos@680 | 767 |
bos@681 | 768 <para id="x_6c3">If something like this happens to you, and the information |
bos@680 | 769 that gets accidentally propagated is truly sensitive, your |
bos@680 | 770 first step should be to mitigate the effect of the leak |
bos@680 | 771 without trying to control the leak itself. If you are not 100% |
bos@680 | 772 certain that you know exactly who could have seen the changes, |
bos@680 | 773 you should immediately change passwords, cancel credit cards, |
bos@680 | 774 or find some other way to make sure that the information that |
bos@680 | 775 has leaked is no longer useful. In other words, assume that |
bos@680 | 776 the change has propagated far and wide, and that there's |
bos@680 | 777 nothing more you can do.</para> |
bos@680 | 778 |
bos@681 | 779 <para id="x_6c4">You might hope that there would be mechanisms you could |
bos@680 | 780 use to either figure out who has seen a change or to erase the |
bos@680 | 781 change permanently everywhere, but there are good reasons why |
bos@680 | 782 these are not possible.</para> |
bos@680 | 783 |
bos@681 | 784 <para id="x_6c5">Mercurial does not provide an audit trail of who has |
bos@680 | 785 pulled changes from a repository, because it is usually either |
bos@680 | 786 impossible to record such information or trivial to spoof it. |
bos@680 | 787 In a multi-user or networked environment, you should thus be |
bos@680 | 788 extremely skeptical of yourself if you think that you have |
bos@680 | 789 identified every place that a sensitive changeset has |
bos@680 | 790 propagated to. Don't forget that people can and will send |
bos@680 | 791 bundles by email, have their backup software save data |
bos@680 | 792 offsite, carry repositories on USB sticks, and find other |
bos@680 | 793 completely innocent ways to confound your attempts to track |
bos@680 | 794 down every copy of a problematic change.</para> |
bos@680 | 795 |
bos@681 | 796 <para id="x_6c6">Mercurial also does not provide a way to make a file or |
bos@680 | 797 changeset completely disappear from history, because there is |
bos@680 | 798 no way to enforce its disappearance; someone could easily |
bos@680 | 799 modify their copy of Mercurial to ignore such directives. In |
bos@680 | 800 addition, even if Mercurial provided such a capability, |
bos@680 | 801 someone who simply hadn't pulled a <quote>make this file |
bos@680 | 802 disappear</quote> changeset wouldn't be affected by it, nor |
bos@680 | 803 would web crawlers visiting at the wrong time, disk backups, |
bos@680 | 804 or other mechanisms. Indeed, no distributed revision control |
bos@680 | 805 system can make data reliably vanish. Providing the illusion |
bos@680 | 806 of such control could easily give a false sense of security, |
bos@680 | 807 and be worse than not providing it at all.</para> |
bos@559 | 808 </sect2> |
bos@559 | 809 </sect1> |
bos@680 | 810 |
bos@559 | 811 <sect1 id="sec:undo:bisect"> |
bos@559 | 812 <title>Finding the source of a bug</title> |
bos@559 | 813 |
bos@584 | 814 <para id="x_126">While it's all very well to be able to back out a changeset |
bos@559 | 815 that introduced a bug, this requires that you know which |
bos@559 | 816 changeset to back out. Mercurial provides an invaluable |
bos@559 | 817 command, called <command role="hg-cmd">hg bisect</command>, that |
bos@559 | 818 helps you to automate this process and accomplish it very |
bos@559 | 819 efficiently.</para> |
bos@559 | 820 |
bos@584 | 821 <para id="x_127">The idea behind the <command role="hg-cmd">hg |
bos@559 | 822 bisect</command> command is that a changeset has introduced |
bos@672 | 823 some change of behavior that you can identify with a simple |
bos@683 | 824 pass/fail test. You don't know which piece of code introduced the |
bos@559 | 825 change, but you know how to test for the presence of the bug. |
bos@559 | 826 The <command role="hg-cmd">hg bisect</command> command uses your |
bos@559 | 827 test to direct its search for the changeset that introduced the |
bos@559 | 828 code that caused the bug.</para> |
bos@559 | 829 |
bos@584 | 830 <para id="x_128">Here are a few scenarios to help you understand how you |
bos@559 | 831 might apply this command.</para> |
bos@559 | 832 <itemizedlist> |
bos@584 | 833 <listitem><para id="x_129">The most recent version of your software has a |
bos@559 | 834 bug that you remember wasn't present a few weeks ago, but |
bos@559 | 835 you don't know when it was introduced. Here, your binary |
bos@559 | 836 test checks for the presence of that bug.</para> |
bos@559 | 837 </listitem> |
bos@584 | 838 <listitem><para id="x_12a">You fixed a bug in a rush, and now it's time to |
bos@559 | 839 close the entry in your team's bug database. The bug |
bos@559 | 840 database requires a changeset ID when you close an entry, |
bos@559 | 841 but you don't remember which changeset you fixed the bug in. |
bos@559 | 842 Once again, your binary test checks for the presence of the |
bos@559 | 843 bug.</para> |
bos@559 | 844 </listitem> |
bos@584 | 845 <listitem><para id="x_12b">Your software works correctly, but runs 15% |
bos@559 | 846 slower than the last time you measured it. You want to know |
bos@559 | 847 which changeset introduced the performance regression. In |
bos@559 | 848 this case, your binary test measures the performance of your |
bos@559 | 849 software, to see whether it's <quote>fast</quote> or |
bos@559 | 850 <quote>slow</quote>.</para> |
bos@559 | 851 </listitem> |
bos@584 | 852 <listitem><para id="x_12c">The sizes of the components of your project that |
bos@559 | 853 you ship exploded recently, and you suspect that something |
bos@559 | 854 changed in the way you build your project.</para> |
bos@559 | 855 </listitem></itemizedlist> |
bos@559 | 856 |
bos@584 | 857 <para id="x_12d">From these examples, it should be clear that the <command |
bos@559 | 858 role="hg-cmd">hg bisect</command> command is not useful only |
bos@559 | 859 for finding the sources of bugs. You can use it to find any |
bos@559 | 860 <quote>emergent property</quote> of a repository (anything that |
bos@559 | 861 you can't find from a simple text search of the files in the |
bos@559 | 862 tree) for which you can write a binary test.</para> |
bos@559 | 863 |
bos@584 | 864 <para id="x_12e">We'll introduce a little bit of terminology here, just to |
bos@559 | 865 make it clear which parts of the search process are your |
bos@559 | 866 responsibility, and which are Mercurial's. A |
bos@559 | 867 <emphasis>test</emphasis> is something that |
bos@559 | 868 <emphasis>you</emphasis> run when <command role="hg-cmd">hg |
bos@559 | 869 bisect</command> chooses a changeset. A |
bos@559 | 870 <emphasis>probe</emphasis> is what <command role="hg-cmd">hg |
bos@559 | 871 bisect</command> runs to tell whether a revision is good. |
bos@559 | 872 Finally, we'll use the word <quote>bisect</quote>, as both a |
bos@559 | 873 noun and a verb, to stand in for the phrase <quote>search using |
bos@559 | 874 the <command role="hg-cmd">hg bisect</command> |
bos@559 | 875 command</quote>.</para> |
bos@559 | 876 |
bos@584 | 877 <para id="x_12f">One simple way to automate the searching process would be |
bos@559 | 878 simply to probe every changeset. However, this scales poorly. |
bos@559 | 879 If it took ten minutes to test a single changeset, and you had |
bos@559 | 880 10,000 changesets in your repository, the exhaustive approach |
bos@559 | 881 would take on average 35 <emphasis>days</emphasis> to find the |
bos@559 | 882 changeset that introduced a bug. Even if you knew that the bug |
bos@559 | 883 was introduced by one of the last 500 changesets, and limited |
bos@559 | 884 your search to those, you'd still be looking at over 40 hours to |
bos@559 | 885 find the changeset that introduced your bug.</para> |
bos@559 | 886 |
bos@584 | 887 <para id="x_130">What the <command role="hg-cmd">hg bisect</command> command |
bos@559 | 888 does is use its knowledge of the <quote>shape</quote> of your |
bos@559 | 889 project's revision history to perform a search in time |
bos@559 | 890 proportional to the <emphasis>logarithm</emphasis> of the number |
bos@559 | 891 of changesets to check (the kind of search it performs is called |
bos@559 | 892 a dichotomic search). With this approach, searching through |
bos@559 | 893 10,000 changesets will take less than three hours, even at ten |
bos@559 | 894 minutes per test (the search will require about 14 tests). |
bos@559 | 895 Limit your search to the last hundred changesets, and it will |
bos@559 | 896 take only about an hour (roughly seven tests).</para> |
bos@559 | 897 |
bos@584 | 898 <para id="x_131">The <command role="hg-cmd">hg bisect</command> command is |
bos@559 | 899 aware of the <quote>branchy</quote> nature of a Mercurial |
bos@559 | 900 project's revision history, so it has no problems dealing with |
bos@559 | 901 branches, merges, or multiple heads in a repository. It can |
bos@559 | 902 prune entire branches of history with a single probe, which is |
bos@559 | 903 how it operates so efficiently.</para> |
bos@559 | 904 |
bos@559 | 905 <sect2> |
bos@559 | 906 <title>Using the <command role="hg-cmd">hg bisect</command> |
bos@559 | 907 command</title> |
bos@559 | 908 |
bos@584 | 909 <para id="x_132">Here's an example of <command role="hg-cmd">hg |
bos@559 | 910 bisect</command> in action.</para> |
bos@559 | 911 |
bos@559 | 912 <note> |
bos@584 | 913 <para id="x_133"> In versions 0.9.5 and earlier of Mercurial, <command |
bos@559 | 914 role="hg-cmd">hg bisect</command> was not a core command: |
bos@559 | 915 it was distributed with Mercurial as an extension. This |
bos@559 | 916 section describes the built-in command, not the old |
bos@559 | 917 extension.</para> |
bos@559 | 918 </note> |
bos@559 | 919 |
bos@584 | 920 <para id="x_134">Now let's create a repository, so that we can try out the |
bos@559 | 921 <command role="hg-cmd">hg bisect</command> command in |
bos@567 | 922 isolation.</para> |
bos@567 | 923 |
bos@567 | 924 &interaction.bisect.init; |
bos@567 | 925 |
bos@584 | 926 <para id="x_135">We'll simulate a project that has a bug in it in a |
bos@567 | 927 simple-minded way: create trivial changes in a loop, and |
bos@567 | 928 nominate one specific change that will have the |
bos@567 | 929 <quote>bug</quote>. This loop creates 35 changesets, each |
bos@567 | 930 adding a single file to the repository. We'll represent our |
bos@567 | 931 <quote>bug</quote> with a file that contains the text <quote>i |
bos@567 | 932 have a gub</quote>.</para> |
bos@567 | 933 |
bos@567 | 934 &interaction.bisect.commits; |
bos@559 | 935 |
bos@584 | 936 <para id="x_136">The next thing that we'd like to do is figure out how to |
bos@559 | 937 use the <command role="hg-cmd">hg bisect</command> command. |
bos@559 | 938 We can use Mercurial's normal built-in help mechanism for |
bos@567 | 939 this.</para> |
bos@567 | 940 |
bos@567 | 941 &interaction.bisect.help; |
bos@559 | 942 |
bos@584 | 943 <para id="x_137">The <command role="hg-cmd">hg bisect</command> command |
bos@559 | 944 works in steps. Each step proceeds as follows.</para> |
bos@559 | 945 <orderedlist> |
bos@584 | 946 <listitem><para id="x_138">You run your binary test.</para> |
bos@559 | 947 <itemizedlist> |
bos@584 | 948 <listitem><para id="x_139">If the test succeeded, you tell <command |
bos@559 | 949 role="hg-cmd">hg bisect</command> by running the |
bos@680 | 950 <command role="hg-cmd">hg bisect --good</command> |
bos@559 | 951 command.</para> |
bos@559 | 952 </listitem> |
bos@584 | 953 <listitem><para id="x_13a">If it failed, run the <command |
bos@680 | 954 role="hg-cmd">hg bisect --bad</command> |
bos@559 | 955 command.</para></listitem></itemizedlist> |
bos@559 | 956 </listitem> |
bos@584 | 957 <listitem><para id="x_13b">The command uses your information to decide |
bos@559 | 958 which changeset to test next.</para> |
bos@559 | 959 </listitem> |
bos@584 | 960 <listitem><para id="x_13c">It updates the working directory to that |
bos@559 | 961 changeset, and the process begins again.</para> |
bos@559 | 962 </listitem></orderedlist> |
bos@584 | 963 <para id="x_13d">The process ends when <command role="hg-cmd">hg |
bos@559 | 964 bisect</command> identifies a unique changeset that marks |
bos@559 | 965 the point where your test transitioned from |
bos@559 | 966 <quote>succeeding</quote> to <quote>failing</quote>.</para> |
bos@559 | 967 |
bos@584 | 968 <para id="x_13e">To start the search, we must run the <command |
bos@567 | 969 role="hg-cmd">hg bisect --reset</command> command.</para> |
bos@567 | 970 |
bos@567 | 971 &interaction.bisect.search.init; |
bos@559 | 972 |
bos@584 | 973 <para id="x_13f">In our case, the binary test we use is simple: we check to |
bos@559 | 974 see if any file in the repository contains the string <quote>i |
bos@559 | 975 have a gub</quote>. If it does, this changeset contains the |
bos@559 | 976 change that <quote>caused the bug</quote>. By convention, a |
bos@559 | 977 changeset that has the property we're searching for is |
bos@559 | 978 <quote>bad</quote>, while one that doesn't is |
bos@559 | 979 <quote>good</quote>.</para> |
bos@559 | 980 |
bos@584 | 981 <para id="x_140">Most of the time, the revision to which the working |
bos@559 | 982 directory is synced (usually the tip) already exhibits the |
bos@559 | 983 problem introduced by the buggy change, so we'll mark it as |
bos@567 | 984 <quote>bad</quote>.</para> |
bos@567 | 985 |
bos@567 | 986 &interaction.bisect.search.bad-init; |
bos@559 | 987 |
bos@584 | 988 <para id="x_141">Our next task is to nominate a changeset that we know |
bos@559 | 989 <emphasis>doesn't</emphasis> have the bug; the <command |
bos@559 | 990 role="hg-cmd">hg bisect</command> command will |
bos@559 | 991 <quote>bracket</quote> its search between the first pair of |
bos@559 | 992 good and bad changesets. In our case, we know that revision |
bos@559 | 993 10 didn't have the bug. (I'll have more words about choosing |
bos@567 | 994 the first <quote>good</quote> changeset later.)</para> |
bos@567 | 995 |
bos@567 | 996 &interaction.bisect.search.good-init; |
bos@559 | 997 |
bos@584 | 998 <para id="x_142">Notice that this command printed some output.</para> |
bos@559 | 999 <itemizedlist> |
bos@584 | 1000 <listitem><para id="x_143">It told us how many changesets it must |
bos@559 | 1001 consider before it can identify the one that introduced |
bos@559 | 1002 the bug, and how many tests that will require.</para> |
bos@559 | 1003 </listitem> |
bos@584 | 1004 <listitem><para id="x_144">It updated the working directory to the next |
bos@559 | 1005 changeset to test, and told us which changeset it's |
bos@559 | 1006 testing.</para> |
bos@559 | 1007 </listitem></itemizedlist> |
bos@559 | 1008 |
bos@584 | 1009 <para id="x_145">We now run our test in the working directory. We use the |
bos@559 | 1010 <command>grep</command> command to see if our |
bos@559 | 1011 <quote>bad</quote> file is present in the working directory. |
bos@559 | 1012 If it is, this revision is bad; if not, this revision is good. |
bos@567 | 1013 &interaction.bisect.search.step1;</para> |
bos@559 | 1014 |
bos@584 | 1015 <para id="x_146">This test looks like a perfect candidate for automation, |
bos@567 | 1016 so let's turn it into a shell function.</para> |
bos@567 | 1017 &interaction.bisect.search.mytest; |
bos@567 | 1018 |
bos@584 | 1019 <para id="x_147">We can now run an entire test step with a single command, |
bos@567 | 1020 <literal>mytest</literal>.</para> |
bos@567 | 1021 |
bos@567 | 1022 &interaction.bisect.search.step2; |
bos@567 | 1023 |
bos@584 | 1024 <para id="x_148">A few more invocations of our canned test step command, |
bos@567 | 1025 and we're done.</para> |
bos@567 | 1026 |
bos@567 | 1027 &interaction.bisect.search.rest; |
bos@559 | 1028 |
bos@584 | 1029 <para id="x_149">Even though we had 40 changesets to search through, the |
bos@559 | 1030 <command role="hg-cmd">hg bisect</command> command let us find |
bos@559 | 1031 the changeset that introduced our <quote>bug</quote> with only |
bos@559 | 1032 five tests. Because the number of tests that the <command |
bos@559 | 1033 role="hg-cmd">hg bisect</command> command performs grows |
bos@559 | 1034 logarithmically with the number of changesets to search, the |
bos@559 | 1035 advantage that it has over the <quote>brute force</quote> |
bos@559 | 1036 search approach increases with every changeset you add.</para> |
bos@559 | 1037 |
bos@559 | 1038 </sect2> |
bos@559 | 1039 <sect2> |
bos@559 | 1040 <title>Cleaning up after your search</title> |
bos@559 | 1041 |
bos@584 | 1042 <para id="x_14a">When you're finished using the <command role="hg-cmd">hg |
bos@559 | 1043 bisect</command> command in a repository, you can use the |
bos@680 | 1044 <command role="hg-cmd">hg bisect --reset</command> command to |
bos@559 | 1045 drop the information it was using to drive your search. The |
bos@559 | 1046 command doesn't use much space, so it doesn't matter if you |
bos@559 | 1047 forget to run this command. However, <command |
bos@559 | 1048 role="hg-cmd">hg bisect</command> won't let you start a new |
bos@559 | 1049 search in that repository until you do a <command |
bos@680 | 1050 role="hg-cmd">hg bisect --reset</command>.</para> |
bos@567 | 1051 |
bos@567 | 1052 &interaction.bisect.search.reset; |
bos@559 | 1053 |
bos@559 | 1054 </sect2> |
bos@559 | 1055 </sect1> |
bos@559 | 1056 <sect1> |
bos@559 | 1057 <title>Tips for finding bugs effectively</title> |
bos@559 | 1058 |
bos@559 | 1059 <sect2> |
bos@559 | 1060 <title>Give consistent input</title> |
bos@559 | 1061 |
bos@584 | 1062 <para id="x_14b">The <command role="hg-cmd">hg bisect</command> command |
bos@559 | 1063 requires that you correctly report the result of every test |
bos@559 | 1064 you perform. If you tell it that a test failed when it really |
bos@559 | 1065 succeeded, it <emphasis>might</emphasis> be able to detect the |
bos@559 | 1066 inconsistency. If it can identify an inconsistency in your |
bos@559 | 1067 reports, it will tell you that a particular changeset is both |
bos@559 | 1068 good and bad. However, it can't do this perfectly; it's about |
bos@559 | 1069 as likely to report the wrong changeset as the source of the |
bos@559 | 1070 bug.</para> |
bos@559 | 1071 |
bos@559 | 1072 </sect2> |
bos@559 | 1073 <sect2> |
bos@559 | 1074 <title>Automate as much as possible</title> |
bos@559 | 1075 |
bos@584 | 1076 <para id="x_14c">When I started using the <command role="hg-cmd">hg |
bos@559 | 1077 bisect</command> command, I tried a few times to run my |
bos@559 | 1078 tests by hand, on the command line. This is an approach that |
bos@559 | 1079 I, at least, am not suited to. After a few tries, I found |
bos@559 | 1080 that I was making enough mistakes that I was having to restart |
bos@559 | 1081 my searches several times before finally getting correct |
bos@559 | 1082 results.</para> |
bos@559 | 1083 |
bos@584 | 1084 <para id="x_14d">My initial problems with driving the <command |
bos@559 | 1085 role="hg-cmd">hg bisect</command> command by hand occurred |
bos@559 | 1086 even with simple searches on small repositories; if the |
bos@559 | 1087 problem you're looking for is more subtle, or the number of |
bos@559 | 1088 tests that <command role="hg-cmd">hg bisect</command> must |
bos@559 | 1089 perform increases, the likelihood of operator error ruining |
bos@559 | 1090 the search is much higher. Once I started automating my |
bos@559 | 1091 tests, I had much better results.</para> |
bos@559 | 1092 |
bos@584 | 1093 <para id="x_14e">The key to automated testing is twofold:</para> |
bos@559 | 1094 <itemizedlist> |
bos@584 | 1095 <listitem><para id="x_14f">always test for the same symptom, and</para> |
bos@584 | 1096 </listitem> |
bos@584 | 1097 <listitem><para id="x_150">always feed consistent input to the <command |
bos@559 | 1098 role="hg-cmd">hg bisect</command> command.</para> |
bos@559 | 1099 </listitem></itemizedlist> |
bos@584 | 1100 <para id="x_151">In my tutorial example above, the <command>grep</command> |
bos@559 | 1101 command tests for the symptom, and the <literal>if</literal> |
bos@559 | 1102 statement takes the result of this check and ensures that we |
bos@559 | 1103 always feed the same input to the <command role="hg-cmd">hg |
bos@559 | 1104 bisect</command> command. The <literal>mytest</literal> |
bos@559 | 1105 function marries these together in a reproducible way, so that |
bos@559 | 1106 every test is uniform and consistent.</para> |
bos@559 | 1107 |
bos@559 | 1108 </sect2> |
bos@559 | 1109 <sect2> |
bos@559 | 1110 <title>Check your results</title> |
bos@559 | 1111 |
bos@584 | 1112 <para id="x_152">Because the output of a <command role="hg-cmd">hg |
bos@559 | 1113 bisect</command> search is only as good as the input you |
bos@559 | 1114 give it, don't take the changeset it reports as the absolute |
bos@559 | 1115 truth. A simple way to cross-check its report is to manually |
bos@559 | 1116 run your test at each of the following changesets:</para> |
bos@559 | 1117 <itemizedlist> |
bos@584 | 1118 <listitem><para id="x_153">The changeset that it reports as the first bad |
bos@559 | 1119 revision. Your test should still report this as |
bos@559 | 1120 bad.</para> |
bos@559 | 1121 </listitem> |
bos@584 | 1122 <listitem><para id="x_154">The parent of that changeset (either parent, |
bos@559 | 1123 if it's a merge). Your test should report this changeset |
bos@559 | 1124 as good.</para> |
bos@559 | 1125 </listitem> |
bos@584 | 1126 <listitem><para id="x_155">A child of that changeset. Your test should |
bos@559 | 1127 report this changeset as bad.</para> |
bos@559 | 1128 </listitem></itemizedlist> |
bos@559 | 1129 |
bos@559 | 1130 </sect2> |
bos@559 | 1131 <sect2> |
bos@559 | 1132 <title>Beware interference between bugs</title> |
bos@559 | 1133 |
bos@584 | 1134 <para id="x_156">It's possible that your search for one bug could be |
bos@559 | 1135 disrupted by the presence of another. For example, let's say |
bos@559 | 1136 your software crashes at revision 100, and worked correctly at |
bos@559 | 1137 revision 50. Unknown to you, someone else introduced a |
bos@559 | 1138 different crashing bug at revision 60, and fixed it at |
bos@559 | 1139 revision 80. This could distort your results in one of |
bos@559 | 1140 several ways.</para> |
bos@559 | 1141 |
bos@584 | 1142 <para id="x_157">It is possible that this other bug completely |
bos@559 | 1143 <quote>masks</quote> yours, which is to say that it occurs |
bos@559 | 1144 before your bug has a chance to manifest itself. If you can't |
bos@559 | 1145 avoid that other bug (for example, it prevents your project |
bos@559 | 1146 from building), and so can't tell whether your bug is present |
bos@559 | 1147 in a particular changeset, the <command role="hg-cmd">hg |
bos@559 | 1148 bisect</command> command cannot help you directly. Instead, |
bos@559 | 1149 you can mark a changeset as untested by running <command |
bos@559 | 1150 role="hg-cmd">hg bisect --skip</command>.</para> |
bos@559 | 1151 |
bos@584 | 1152 <para id="x_158">A different problem could arise if your test for a bug's |
bos@559 | 1153 presence is not specific enough. If you check for <quote>my |
bos@559 | 1154 program crashes</quote>, then both your crashing bug and an |
bos@559 | 1155 unrelated crashing bug that masks it will look like the same |
bos@559 | 1156 thing, and mislead <command role="hg-cmd">hg |
bos@559 | 1157 bisect</command>.</para> |
bos@559 | 1158 |
bos@584 | 1159 <para id="x_159">Another useful situation in which to use <command |
bos@559 | 1160 role="hg-cmd">hg bisect --skip</command> is if you can't |
bos@559 | 1161 test a revision because your project was in a broken and hence |
bos@559 | 1162 untestable state at that revision, perhaps because someone |
bos@559 | 1163 checked in a change that prevented the project from |
bos@559 | 1164 building.</para> |
bos@559 | 1165 |
bos@559 | 1166 </sect2> |
bos@559 | 1167 <sect2> |
bos@559 | 1168 <title>Bracket your search lazily</title> |
bos@559 | 1169 |
bos@584 | 1170 <para id="x_15a">Choosing the first <quote>good</quote> and |
bos@559 | 1171 <quote>bad</quote> changesets that will mark the end points of |
bos@559 | 1172 your search is often easy, but it bears a little discussion |
bos@559 | 1173 nevertheless. From the perspective of <command |
bos@559 | 1174 role="hg-cmd">hg bisect</command>, the <quote>newest</quote> |
bos@559 | 1175 changeset is conventionally <quote>bad</quote>, and the older |
bos@559 | 1176 changeset is <quote>good</quote>.</para> |
bos@559 | 1177 |
bos@584 | 1178 <para id="x_15b">If you're having trouble remembering when a suitable |
bos@559 | 1179 <quote>good</quote> change was, so that you can tell <command |
bos@559 | 1180 role="hg-cmd">hg bisect</command>, you could do worse than |
bos@559 | 1181 testing changesets at random. Just remember to eliminate |
bos@559 | 1182 contenders that can't possibly exhibit the bug (perhaps |
bos@559 | 1183 because the feature with the bug isn't present yet) and those |
bos@559 | 1184 where another problem masks the bug (as I discussed |
bos@559 | 1185 above).</para> |
bos@559 | 1186 |
bos@584 | 1187 <para id="x_15c">Even if you end up <quote>early</quote> by thousands of |
bos@559 | 1188 changesets or months of history, you will only add a handful |
bos@559 | 1189 of tests to the total number that <command role="hg-cmd">hg |
bos@559 | 1190 bisect</command> must perform, thanks to its logarithmic |
bos@672 | 1191 behavior.</para> |
bos@559 | 1192 |
bos@559 | 1193 </sect2> |
bos@559 | 1194 </sect1> |
bos@559 | 1195 </chapter> |
bos@559 | 1196 |
bos@559 | 1197 <!-- |
bos@559 | 1198 local variables: |
bos@559 | 1199 sgml-parent-document: ("00book.xml" "book" "chapter") |
bos@559 | 1200 end: |
bos@559 | 1201 --> |