bos@559: bos@559: bos@559: bos@572: bos@572: Finding and fixing mistakes bos@559: bos@584: To err might be human, but to really handle the consequences bos@559: well takes a top-notch revision control system. In this chapter, bos@559: we'll discuss some of the techniques you can use when you find bos@559: that a problem has crept into your project. Mercurial has some bos@559: highly capable features that will help you to isolate the sources bos@559: of problems, and to handle them appropriately. bos@559: bos@559: bos@559: Erasing local history bos@559: bos@559: bos@559: The accidental commit bos@559: bos@584: I have the occasional but persistent problem of typing bos@559: rather more quickly than I can think, which sometimes results bos@559: in me committing a changeset that is either incomplete or bos@559: plain wrong. In my case, the usual kind of incomplete bos@559: changeset is one in which I've created a new source file, but bos@559: forgotten to hg add it. A bos@559: plain wrong changeset is not as common, but no bos@559: less annoying. bos@559: bos@559: bos@559: bos@559: Rolling back a transaction bos@559: bos@592: In , I bos@592: mentioned that Mercurial treats each modification of a bos@592: repository as a transaction. Every time bos@592: you commit a changeset or pull changes from another bos@592: repository, Mercurial remembers what you did. You can undo, bos@592: or roll back, exactly one of these bos@592: actions using the hg rollback bos@592: command. (See bos@592: for an important caveat about the use of this command.) bos@559: bos@584: Here's a mistake that I often find myself making: bos@559: committing a change in which I've created a new file, but bos@567: forgotten to hg add bos@567: it. bos@567: bos@567: &interaction.rollback.commit; bos@567: bos@584: Looking at the output of hg bos@567: status after the commit immediately confirms the bos@567: error. bos@567: bos@567: &interaction.rollback.status; bos@567: bos@584: The commit captured the changes to the file bos@567: a, but not the new file bos@567: b. If I were to push this changeset to a bos@567: repository that I shared with a colleague, the chances are bos@567: high that something in a would refer to bos@567: b, which would not be present in their bos@559: repository when they pulled my changes. I would thus become bos@559: the object of some indignation. bos@559: bos@584: However, luck is with me&emdash;I've caught my error bos@559: before I pushed the changeset. I use the hg rollback command, and Mercurial bos@567: makes that last changeset vanish. bos@567: bos@567: &interaction.rollback.rollback; bos@567: bos@584: Notice that the changeset is no longer present in the bos@567: repository's history, and the working directory once again bos@567: thinks that the file a is modified. The bos@567: commit and rollback have left the working directory exactly as bos@567: it was prior to the commit; the changeset has been completely bos@567: erased. I can now safely hg bos@567: add the file b, and rerun my bos@567: commit. bos@567: bos@567: &interaction.rollback.add; bos@559: bos@559: bos@559: bos@559: The erroneous pull bos@559: bos@584: It's common practice with Mercurial to maintain separate bos@559: development branches of a project in different repositories. bos@559: Your development team might have one shared repository for bos@559: your project's 0.9 release, and another, bos@559: containing different changes, for the 1.0 bos@559: release. bos@559: bos@584: Given this, you can imagine that the consequences could be bos@559: messy if you had a local 0.9 repository, and bos@559: accidentally pulled changes from the shared 1.0 bos@559: repository into it. At worst, you could be paying bos@559: insufficient attention, and push those changes into the shared bos@559: 0.9 tree, confusing your entire team (but don't bos@559: worry, we'll return to this horror scenario later). However, bos@559: it's more likely that you'll notice immediately, because bos@559: Mercurial will display the URL it's pulling from, or you will bos@559: see it pull a suspiciously large number of changes into the bos@559: repository. bos@559: bos@584: The hg rollback command bos@559: will work nicely to expunge all of the changesets that you bos@559: just pulled. Mercurial groups all changes from one hg pull into a single transaction, bos@559: so one hg rollback is all you bos@559: need to undo this mistake. bos@559: bos@559: bos@559: bos@559: Rolling back is useless once you've pushed bos@559: bos@584: The value of the hg bos@559: rollback command drops to zero once you've pushed bos@559: your changes to another repository. Rolling back a change bos@559: makes it disappear entirely, but only in bos@559: the repository in which you perform the hg rollback. Because a rollback bos@559: eliminates history, there's no way for the disappearance of a bos@559: change to propagate between repositories. bos@559: bos@584: If you've pushed a change to another bos@559: repository&emdash;particularly if it's a shared bos@559: repository&emdash;it has essentially escaped into the bos@559: wild, and you'll have to recover from your mistake bos@559: in a different way. What will happen if you push a changeset bos@559: somewhere, then roll it back, then pull from the repository bos@559: you pushed to, is that the changeset will reappear in your bos@559: repository. bos@559: bos@584: (If you absolutely know for sure that the change you want bos@559: to roll back is the most recent change in the repository that bos@559: you pushed to, and you know that nobody bos@559: else could have pulled it from that repository, you can roll bos@559: back the changeset there, too, but you really should really bos@559: not rely on this working reliably. If you do this, sooner or bos@559: later a change really will make it into a repository that you bos@559: don't directly control (or have forgotten about), and come bos@559: back to bite you.) bos@559: bos@559: bos@559: bos@559: You can only roll back once bos@559: bos@584: Mercurial stores exactly one transaction in its bos@559: transaction log; that transaction is the most recent one that bos@559: occurred in the repository. This means that you can only roll bos@559: back one transaction. If you expect to be able to roll back bos@559: one transaction, then its predecessor, this is not the bos@567: behaviour you will get. bos@567: bos@567: &interaction.rollback.twice; bos@567: bos@584: Once you've rolled back one transaction in a repository, bos@567: you can't roll back again in that repository until you perform bos@559: another commit or pull. bos@559: bos@559: bos@559: bos@559: bos@559: Reverting the mistaken change bos@559: bos@584: If you make a modification to a file, and decide that you bos@559: really didn't want to change the file at all, and you haven't bos@559: yet committed your changes, the hg bos@559: revert command is the one you'll need. It looks at bos@559: the changeset that's the parent of the working directory, and bos@559: restores the contents of the file to their state as of that bos@559: changeset. (That's a long-winded way of saying that, in the bos@559: normal case, it undoes your modifications.) bos@559: bos@584: Let's illustrate how the hg bos@559: revert command works with yet another small example. bos@559: We'll begin by modifying a file that Mercurial is already bos@567: tracking. bos@567: bos@567: &interaction.daily.revert.modify; bos@567: bos@584: If we don't bos@559: want that change, we can simply hg bos@567: revert the file. bos@567: bos@567: &interaction.daily.revert.unmodify; bos@567: bos@584: The hg revert command bos@567: provides us with an extra degree of safety by saving our bos@567: modified file with a .orig bos@567: extension. bos@567: bos@567: &interaction.daily.revert.status; bos@559: bos@584: Here is a summary of the cases that the hg revert command can deal with. We bos@559: will describe each of these in more detail in the section that bos@559: follows. bos@559: bos@584: If you modify a file, it will restore the file bos@559: to its unmodified state. bos@559: bos@584: If you hg add a bos@559: file, it will undo the added state of the bos@559: file, but leave the file itself untouched. bos@559: bos@584: If you delete a file without telling Mercurial, bos@559: it will restore the file to its unmodified contents. bos@559: bos@584: If you use the hg bos@559: remove command to remove a file, it will undo bos@559: the removed state of the file, and restore bos@559: the file to its unmodified contents. bos@559: bos@559: bos@559: bos@559: File management errors bos@559: bos@584: The hg revert command is bos@559: useful for more than just modified files. It lets you reverse bos@559: the results of all of Mercurial's file management bos@559: commands&emdash;hg add, bos@559: hg remove, and so on. bos@559: bos@584: If you hg add a file, bos@559: then decide that in fact you don't want Mercurial to track it, bos@559: use hg revert to undo the bos@559: add. Don't worry; Mercurial will not modify the file in any bos@567: way. It will just unmark the file. bos@567: bos@567: &interaction.daily.revert.add; bos@559: bos@584: Similarly, if you ask Mercurial to hg remove a file, you can use bos@559: hg revert to restore it to bos@559: the contents it had as of the parent of the working directory. bos@567: &interaction.daily.revert.remove; This works just as bos@559: well for a file that you deleted by hand, without telling bos@559: Mercurial (recall that in Mercurial terminology, this kind of bos@567: file is called missing). bos@567: bos@567: &interaction.daily.revert.missing; bos@559: bos@584: If you revert a hg copy, bos@559: the copied-to file remains in your working directory bos@559: afterwards, untracked. Since a copy doesn't affect the bos@559: copied-from file in any way, Mercurial doesn't do anything bos@567: with the copied-from file. bos@567: bos@567: &interaction.daily.revert.copy; bos@559: bos@559: bos@559: A slightly special case: reverting a rename bos@559: bos@584: If you hg rename a bos@559: file, there is one small detail that you should remember. bos@559: When you hg revert a bos@559: rename, it's not enough to provide the name of the bos@567: renamed-to file, as you can see here. bos@567: bos@567: &interaction.daily.revert.rename; bos@567: bos@584: As you can see from the output of hg status, the renamed-to file is bos@567: no longer identified as added, but the bos@567: renamed-from file is still removed! bos@559: This is counter-intuitive (at least to me), but at least bos@567: it's easy to deal with. bos@567: bos@567: &interaction.daily.revert.rename-orig; bos@567: bos@584: So remember, to revert a hg bos@567: rename, you must provide bos@567: both the source and destination bos@567: names. bos@559: bos@584: % TODO: the output doesn't look like it will be bos@559: removed! bos@559: bos@584: (By the way, if you rename a file, then modify the bos@559: renamed-to file, then revert both components of the rename, bos@559: when Mercurial restores the file that was removed as part of bos@559: the rename, it will be unmodified. If you need the bos@559: modifications in the renamed-to file to show up in the bos@559: renamed-from file, don't forget to copy them over.) bos@559: bos@584: These fiddly aspects of reverting a rename arguably bos@559: constitute a small bug in Mercurial. bos@559: bos@559: bos@559: bos@559: bos@559: bos@559: Dealing with committed changes bos@559: bos@584: Consider a case where you have committed a change $a$, and bos@559: another change $b$ on top of it; you then realise that change bos@559: $a$ was incorrect. Mercurial lets you back out bos@559: an entire changeset automatically, and building blocks that let bos@559: you reverse part of a changeset by hand. bos@559: bos@592: Before you read this section, here's something to bos@592: keep in mind: the hg backout bos@592: command undoes changes by adding history, bos@592: not by modifying or erasing it. It's the right tool to use if bos@592: you're fixing bugs, but not if you're trying to undo some change bos@592: that has catastrophic consequences. To deal with those, see bos@559: . bos@559: bos@559: bos@559: Backing out a changeset bos@559: bos@584: The hg backout command bos@559: lets you undo the effects of an entire bos@559: changeset in an automated fashion. Because Mercurial's bos@559: history is immutable, this command does bos@559: not get rid of the changeset you want to undo. bos@559: Instead, it creates a new changeset that bos@559: reverses the effect of the to-be-undone bos@559: changeset. bos@559: bos@584: The operation of the hg bos@559: backout command is a little intricate, so let's bos@559: illustrate it with some examples. First, we'll create a bos@567: repository with some simple changes. bos@567: bos@567: &interaction.backout.init; bos@559: bos@584: The hg backout command bos@559: takes a single changeset ID as its argument; this is the bos@559: changeset to back out. Normally, hg bos@559: backout will drop you into a text editor to write bos@559: a commit message, so you can record why you're backing the bos@559: change out. In this example, we provide a commit message on bos@559: the command line using the option. bos@559: bos@559: bos@559: bos@559: Backing out the tip changeset bos@559: bos@584: We're going to start by backing out the last changeset we bos@567: committed. bos@567: bos@567: &interaction.backout.simple; bos@567: bos@584: You can see that the second line from bos@567: myfile is no longer present. Taking a bos@567: look at the output of hg log bos@567: gives us an idea of what the hg bos@567: backout command has done. bos@567: &interaction.backout.simple.log; Notice that the new changeset bos@567: that hg backout has created bos@567: is a child of the changeset we backed out. It's easier to see bos@592: this in , which presents a bos@592: graphical view of the change history. As you can see, the bos@592: history is nice and linear. bos@559: bos@591:
bos@591: Backing out a change using the <command bos@591: role="hg-cmd">hg backout</command> command bos@591: bos@594: bos@591: XXX add text bos@591: bos@591:
bos@559: bos@559:
bos@559: bos@559: Backing out a non-tip change bos@559: bos@584: If you want to back out a change other than the last one bos@559: you committed, pass the option to the bos@567: hg backout command. bos@567: bos@567: &interaction.backout.non-tip.clone; bos@567: bos@584: This makes backing out any changeset a bos@567: one-shot operation that's usually simple and bos@567: fast. bos@567: bos@567: &interaction.backout.non-tip.backout; bos@559: bos@584: If you take a look at the contents of bos@559: myfile after the backout finishes, you'll bos@559: see that the first and third changes are present, but not the bos@567: second. bos@567: bos@567: &interaction.backout.non-tip.cat; bos@559: bos@592: As the graphical history in illustrates, Mercurial bos@559: actually commits two changes in this kind bos@559: of situation (the box-shaped nodes are the ones that Mercurial bos@559: commits automatically). Before Mercurial begins the backout bos@559: process, it first remembers what the current parent of the bos@559: working directory is. It then backs out the target changeset, bos@559: and commits that as a changeset. Finally, it merges back to bos@559: the previous parent of the working directory, and commits the bos@559: result of the merge. bos@559: bos@584: % TODO: to me it looks like mercurial doesn't commit the bos@559: second merge automatically! bos@559: bos@591:
bos@591: Automated backout of a non-tip change using the bos@591: <command role="hg-cmd">hg backout</command> command bos@591: bos@594: bos@591: XXX add text bos@591: bos@591:
bos@559: bos@584: The result is that you end up back where you bos@559: were, only with some extra history that undoes the bos@559: effect of the changeset you wanted to back out. bos@559: bos@559: bos@559: Always use the <option bos@559: role="hg-opt-backout">--merge</option> option bos@559: bos@584: In fact, since the option will do the bos@559: right thing whether or not the changeset bos@559: you're backing out is the tip (i.e. it won't try to merge if bos@559: it's backing out the tip, since there's no need), you should bos@559: always use this option when you run the bos@559: hg backout command. bos@559: bos@559: bos@559:
bos@559: bos@559: Gaining more control of the backout process bos@559: bos@584: While I've recommended that you always use the option when backing bos@559: out a change, the hg backout bos@559: command lets you decide how to merge a backout changeset. bos@559: Taking control of the backout process by hand is something you bos@559: will rarely need to do, but it can be useful to understand bos@559: what the hg backout command bos@559: is doing for you automatically. To illustrate this, let's bos@559: clone our first repository, but omit the backout change that bos@559: it contains. bos@559: bos@567: &interaction.backout.manual.clone; bos@567: bos@584: As with our bos@559: earlier example, We'll commit a third changeset, then back out bos@567: its parent, and see what happens. bos@567: bos@567: &interaction.backout.manual.backout; bos@567: bos@584: Our new changeset is again a descendant of the changeset bos@567: we backout out; it's thus a new head, not bos@567: a descendant of the changeset that was the tip. The hg backout command was quite bos@567: explicit in telling us this. bos@567: bos@567: &interaction.backout.manual.log; bos@559: bos@584: Again, it's easier to see what has happened by looking at bos@592: a graph of the revision history, in . This makes it clear bos@559: that when we use hg backout bos@559: to back out a change other than the tip, Mercurial adds a new bos@559: head to the repository (the change it committed is bos@559: box-shaped). bos@559: bos@591:
bos@591: Backing out a change using the <command bos@591: role="hg-cmd">hg backout</command> command bos@591: bos@594: bos@591: XXX add text bos@591: bos@591:
bos@559: bos@584: After the hg backout bos@559: command has completed, it leaves the new bos@559: backout changeset as the parent of the working bos@567: directory. bos@567: bos@567: &interaction.backout.manual.parents; bos@567: bos@584: Now we have two isolated sets of changes. bos@567: bos@567: &interaction.backout.manual.heads; bos@559: bos@584: Let's think about what we expect to see as the contents of bos@559: myfile now. The first change should be bos@559: present, because we've never backed it out. The second change bos@559: should be missing, as that's the change we backed out. Since bos@559: the history graph shows the third change as a separate head, bos@559: we don't expect to see the third change bos@567: present in myfile. bos@567: bos@567: &interaction.backout.manual.cat; bos@567: bos@584: To get the third change back into the file, we just do a bos@567: normal merge of our two heads. bos@567: bos@567: &interaction.backout.manual.merge; bos@567: bos@592: Afterwards, the graphical history of our bos@592: repository looks like bos@559: . bos@559: bos@591:
bos@591: Manually merging a backout change bos@591: bos@594: bos@591: XXX add text bos@591: bos@591:
bos@559: bos@559:
bos@559: bos@559: Why <command role="hg-cmd">hg backout</command> works as bos@559: it does bos@559: bos@584: Here's a brief description of how the hg backout command works. bos@559: bos@584: It ensures that the working directory is bos@559: clean, i.e. that the output of hg status would be empty. bos@559: bos@584: It remembers the current parent of the working bos@559: directory. Let's call this changeset bos@559: orig bos@559: bos@584: It does the equivalent of a hg update to sync the working bos@559: directory to the changeset you want to back out. Let's bos@559: call this changeset backout bos@559: bos@584: It finds the parent of that changeset. Let's bos@559: call that changeset parent. bos@559: bos@584: For each file that the bos@559: backout changeset affected, it does the bos@559: equivalent of a hg revert -r bos@559: parent on that file, to restore it to the bos@559: contents it had before that changeset was bos@559: committed. bos@559: bos@584: It commits the result as a new changeset. bos@559: This changeset has backout as its bos@559: parent. bos@559: bos@584: If you specify on the command bos@559: line, it merges with orig, and commits bos@559: the result of the merge. bos@559: bos@559: bos@584: An alternative way to implement the hg backout command would be to bos@559: hg export the bos@559: to-be-backed-out changeset as a diff, then use the option to the bos@559: patch command to reverse the effect of the bos@559: change without fiddling with the working directory. This bos@559: sounds much simpler, but it would not work nearly as bos@559: well. bos@559: bos@584: The reason that hg bos@559: backout does an update, a commit, a merge, and bos@559: another commit is to give the merge machinery the best chance bos@559: to do a good job when dealing with all the changes bos@559: between the change you're backing out and bos@559: the current tip. bos@559: bos@584: If you're backing out a changeset that's 100 revisions bos@559: back in your project's history, the chances that the bos@559: patch command will be able to apply a bos@559: reverse diff cleanly are not good, because intervening changes bos@559: are likely to have broken the context that bos@559: patch uses to determine whether it can bos@559: apply a patch (if this sounds like gibberish, see for a bos@559: discussion of the patch command). Also, bos@559: Mercurial's merge machinery will handle files and directories bos@559: being renamed, permission changes, and modifications to binary bos@559: files, none of which patch can deal bos@559: with. bos@559: bos@559: bos@559:
bos@559: bos@559: Changes that should never have been bos@559: bos@584: Most of the time, the hg bos@559: backout command is exactly what you need if you want bos@559: to undo the effects of a change. It leaves a permanent record bos@559: of exactly what you did, both when committing the original bos@559: changeset and when you cleaned up after it. bos@559: bos@584: On rare occasions, though, you may find that you've bos@559: committed a change that really should not be present in the bos@559: repository at all. For example, it would be very unusual, and bos@559: usually considered a mistake, to commit a software project's bos@559: object files as well as its source files. Object files have bos@559: almost no intrinsic value, and they're big, bos@559: so they increase the size of the repository and the amount of bos@559: time it takes to clone or pull changes. bos@559: bos@584: Before I discuss the options that you have if you commit a bos@559: brown paper bag change (the kind that's so bad bos@559: that you want to pull a brown paper bag over your head), let me bos@559: first discuss some approaches that probably won't work. bos@559: bos@592: Since Mercurial treats history as bos@592: accumulative&emdash;every change builds on top of all changes bos@592: that preceded it&emdash;you generally can't just make disastrous bos@592: changes disappear. The one exception is when you've just bos@592: committed a change, and it hasn't been pushed or pulled into bos@592: another repository. That's when you can safely use the hg rollback command, as I detailed in bos@592: . bos@559: bos@584: After you've pushed a bad change to another repository, you bos@559: could still use hg bos@559: rollback to make your local copy of the change bos@559: disappear, but it won't have the consequences you want. The bos@559: change will still be present in the remote repository, so it bos@559: will reappear in your local repository the next time you bos@559: pull. bos@559: bos@584: If a situation like this arises, and you know which bos@559: repositories your bad change has propagated into, you can bos@559: try to get rid of the changeefrom bos@559: every one of those repositories. This is, bos@559: of course, not a satisfactory solution: if you miss even a bos@559: single repository while you're expunging, the change is still bos@559: in the wild, and could propagate further. bos@559: bos@584: If you've committed one or more changes bos@559: after the change that you'd like to see bos@559: disappear, your options are further reduced. Mercurial doesn't bos@559: provide a way to punch a hole in history, leaving bos@559: changesets intact. bos@559: bos@584: XXX This needs filling out. The bos@559: hg-replay script in the bos@559: examples directory works, but doesn't handle bos@559: merge changesets. Kind of an important omission. bos@559: bos@559: bos@559: Protect yourself from <quote>escaped</quote> bos@559: changes bos@559: bos@584: If you've committed some changes to your local repository bos@559: and they've been pushed or pulled somewhere else, this isn't bos@559: necessarily a disaster. You can protect yourself ahead of bos@559: time against some classes of bad changeset. This is bos@559: particularly easy if your team usually pulls changes from a bos@559: central repository. bos@559: bos@584: By configuring some hooks on that repository to validate bos@559: incoming changesets (see chapter ), bos@559: you can bos@559: automatically prevent some kinds of bad changeset from being bos@559: pushed to the central repository at all. With such a bos@559: configuration in place, some kinds of bad changeset will bos@559: naturally tend to die out because they can't bos@559: propagate into the central repository. Better yet, this bos@559: happens without any need for explicit intervention. bos@559: bos@584: For instance, an incoming change hook that verifies that a bos@559: changeset will actually compile can prevent people from bos@559: inadvertantly breaking the build. bos@559: bos@559: bos@559: bos@559: bos@559: Finding the source of a bug bos@559: bos@584: While it's all very well to be able to back out a changeset bos@559: that introduced a bug, this requires that you know which bos@559: changeset to back out. Mercurial provides an invaluable bos@559: command, called hg bisect, that bos@559: helps you to automate this process and accomplish it very bos@559: efficiently. bos@559: bos@584: The idea behind the hg bos@559: bisect command is that a changeset has introduced bos@559: some change of behaviour that you can identify with a simple bos@559: binary test. You don't know which piece of code introduced the bos@559: change, but you know how to test for the presence of the bug. bos@559: The hg bisect command uses your bos@559: test to direct its search for the changeset that introduced the bos@559: code that caused the bug. bos@559: bos@584: Here are a few scenarios to help you understand how you bos@559: might apply this command. bos@559: bos@584: The most recent version of your software has a bos@559: bug that you remember wasn't present a few weeks ago, but bos@559: you don't know when it was introduced. Here, your binary bos@559: test checks for the presence of that bug. bos@559: bos@584: You fixed a bug in a rush, and now it's time to bos@559: close the entry in your team's bug database. The bug bos@559: database requires a changeset ID when you close an entry, bos@559: but you don't remember which changeset you fixed the bug in. bos@559: Once again, your binary test checks for the presence of the bos@559: bug. bos@559: bos@584: Your software works correctly, but runs 15% bos@559: slower than the last time you measured it. You want to know bos@559: which changeset introduced the performance regression. In bos@559: this case, your binary test measures the performance of your bos@559: software, to see whether it's fast or bos@559: slow. bos@559: bos@584: The sizes of the components of your project that bos@559: you ship exploded recently, and you suspect that something bos@559: changed in the way you build your project. bos@559: bos@559: bos@584: From these examples, it should be clear that the hg bisect command is not useful only bos@559: for finding the sources of bugs. You can use it to find any bos@559: emergent property of a repository (anything that bos@559: you can't find from a simple text search of the files in the bos@559: tree) for which you can write a binary test. bos@559: bos@584: We'll introduce a little bit of terminology here, just to bos@559: make it clear which parts of the search process are your bos@559: responsibility, and which are Mercurial's. A bos@559: test is something that bos@559: you run when hg bos@559: bisect chooses a changeset. A bos@559: probe is what hg bos@559: bisect runs to tell whether a revision is good. bos@559: Finally, we'll use the word bisect, as both a bos@559: noun and a verb, to stand in for the phrase search using bos@559: the hg bisect bos@559: command. bos@559: bos@584: One simple way to automate the searching process would be bos@559: simply to probe every changeset. However, this scales poorly. bos@559: If it took ten minutes to test a single changeset, and you had bos@559: 10,000 changesets in your repository, the exhaustive approach bos@559: would take on average 35 days to find the bos@559: changeset that introduced a bug. Even if you knew that the bug bos@559: was introduced by one of the last 500 changesets, and limited bos@559: your search to those, you'd still be looking at over 40 hours to bos@559: find the changeset that introduced your bug. bos@559: bos@584: What the hg bisect command bos@559: does is use its knowledge of the shape of your bos@559: project's revision history to perform a search in time bos@559: proportional to the logarithm of the number bos@559: of changesets to check (the kind of search it performs is called bos@559: a dichotomic search). With this approach, searching through bos@559: 10,000 changesets will take less than three hours, even at ten bos@559: minutes per test (the search will require about 14 tests). bos@559: Limit your search to the last hundred changesets, and it will bos@559: take only about an hour (roughly seven tests). bos@559: bos@584: The hg bisect command is bos@559: aware of the branchy nature of a Mercurial bos@559: project's revision history, so it has no problems dealing with bos@559: branches, merges, or multiple heads in a repository. It can bos@559: prune entire branches of history with a single probe, which is bos@559: how it operates so efficiently. bos@559: bos@559: bos@559: Using the <command role="hg-cmd">hg bisect</command> bos@559: command bos@559: bos@584: Here's an example of hg bos@559: bisect in action. bos@559: bos@559: bos@584: In versions 0.9.5 and earlier of Mercurial, hg bisect was not a core command: bos@559: it was distributed with Mercurial as an extension. This bos@559: section describes the built-in command, not the old bos@559: extension. bos@559: bos@559: bos@584: Now let's create a repository, so that we can try out the bos@559: hg bisect command in bos@567: isolation. bos@567: bos@567: &interaction.bisect.init; bos@567: bos@584: We'll simulate a project that has a bug in it in a bos@567: simple-minded way: create trivial changes in a loop, and bos@567: nominate one specific change that will have the bos@567: bug. This loop creates 35 changesets, each bos@567: adding a single file to the repository. We'll represent our bos@567: bug with a file that contains the text i bos@567: have a gub. bos@567: bos@567: &interaction.bisect.commits; bos@559: bos@584: The next thing that we'd like to do is figure out how to bos@559: use the hg bisect command. bos@559: We can use Mercurial's normal built-in help mechanism for bos@567: this. bos@567: bos@567: &interaction.bisect.help; bos@559: bos@584: The hg bisect command bos@559: works in steps. Each step proceeds as follows. bos@559: bos@584: You run your binary test. bos@559: bos@584: If the test succeeded, you tell hg bisect by running the bos@559: hg bisect good bos@559: command. bos@559: bos@584: If it failed, run the hg bisect bad bos@559: command. bos@559: bos@584: The command uses your information to decide bos@559: which changeset to test next. bos@559: bos@584: It updates the working directory to that bos@559: changeset, and the process begins again. bos@559: bos@584: The process ends when hg bos@559: bisect identifies a unique changeset that marks bos@559: the point where your test transitioned from bos@559: succeeding to failing. bos@559: bos@584: To start the search, we must run the hg bisect --reset command. bos@567: bos@567: &interaction.bisect.search.init; bos@559: bos@584: In our case, the binary test we use is simple: we check to bos@559: see if any file in the repository contains the string i bos@559: have a gub. If it does, this changeset contains the bos@559: change that caused the bug. By convention, a bos@559: changeset that has the property we're searching for is bos@559: bad, while one that doesn't is bos@559: good. bos@559: bos@584: Most of the time, the revision to which the working bos@559: directory is synced (usually the tip) already exhibits the bos@559: problem introduced by the buggy change, so we'll mark it as bos@567: bad. bos@567: bos@567: &interaction.bisect.search.bad-init; bos@559: bos@584: Our next task is to nominate a changeset that we know bos@559: doesn't have the bug; the hg bisect command will bos@559: bracket its search between the first pair of bos@559: good and bad changesets. In our case, we know that revision bos@559: 10 didn't have the bug. (I'll have more words about choosing bos@567: the first good changeset later.) bos@567: bos@567: &interaction.bisect.search.good-init; bos@559: bos@584: Notice that this command printed some output. bos@559: bos@584: It told us how many changesets it must bos@559: consider before it can identify the one that introduced bos@559: the bug, and how many tests that will require. bos@559: bos@584: It updated the working directory to the next bos@559: changeset to test, and told us which changeset it's bos@559: testing. bos@559: bos@559: bos@584: We now run our test in the working directory. We use the bos@559: grep command to see if our bos@559: bad file is present in the working directory. bos@559: If it is, this revision is bad; if not, this revision is good. bos@567: &interaction.bisect.search.step1; bos@559: bos@584: This test looks like a perfect candidate for automation, bos@567: so let's turn it into a shell function. bos@567: &interaction.bisect.search.mytest; bos@567: bos@584: We can now run an entire test step with a single command, bos@567: mytest. bos@567: bos@567: &interaction.bisect.search.step2; bos@567: bos@584: A few more invocations of our canned test step command, bos@567: and we're done. bos@567: bos@567: &interaction.bisect.search.rest; bos@559: bos@584: Even though we had 40 changesets to search through, the bos@559: hg bisect command let us find bos@559: the changeset that introduced our bug with only bos@559: five tests. Because the number of tests that the hg bisect command performs grows bos@559: logarithmically with the number of changesets to search, the bos@559: advantage that it has over the brute force bos@559: search approach increases with every changeset you add. bos@559: bos@559: bos@559: bos@559: Cleaning up after your search bos@559: bos@584: When you're finished using the hg bos@559: bisect command in a repository, you can use the bos@559: hg bisect reset command to bos@559: drop the information it was using to drive your search. The bos@559: command doesn't use much space, so it doesn't matter if you bos@559: forget to run this command. However, hg bisect won't let you start a new bos@559: search in that repository until you do a hg bisect reset. bos@567: bos@567: &interaction.bisect.search.reset; bos@559: bos@559: bos@559: bos@559: bos@559: Tips for finding bugs effectively bos@559: bos@559: bos@559: Give consistent input bos@559: bos@584: The hg bisect command bos@559: requires that you correctly report the result of every test bos@559: you perform. If you tell it that a test failed when it really bos@559: succeeded, it might be able to detect the bos@559: inconsistency. If it can identify an inconsistency in your bos@559: reports, it will tell you that a particular changeset is both bos@559: good and bad. However, it can't do this perfectly; it's about bos@559: as likely to report the wrong changeset as the source of the bos@559: bug. bos@559: bos@559: bos@559: bos@559: Automate as much as possible bos@559: bos@584: When I started using the hg bos@559: bisect command, I tried a few times to run my bos@559: tests by hand, on the command line. This is an approach that bos@559: I, at least, am not suited to. After a few tries, I found bos@559: that I was making enough mistakes that I was having to restart bos@559: my searches several times before finally getting correct bos@559: results. bos@559: bos@584: My initial problems with driving the hg bisect command by hand occurred bos@559: even with simple searches on small repositories; if the bos@559: problem you're looking for is more subtle, or the number of bos@559: tests that hg bisect must bos@559: perform increases, the likelihood of operator error ruining bos@559: the search is much higher. Once I started automating my bos@559: tests, I had much better results. bos@559: bos@584: The key to automated testing is twofold: bos@559: bos@584: always test for the same symptom, and bos@584: bos@584: always feed consistent input to the hg bisect command. bos@559: bos@584: In my tutorial example above, the grep bos@559: command tests for the symptom, and the if bos@559: statement takes the result of this check and ensures that we bos@559: always feed the same input to the hg bos@559: bisect command. The mytest bos@559: function marries these together in a reproducible way, so that bos@559: every test is uniform and consistent. bos@559: bos@559: bos@559: bos@559: Check your results bos@559: bos@584: Because the output of a hg bos@559: bisect search is only as good as the input you bos@559: give it, don't take the changeset it reports as the absolute bos@559: truth. A simple way to cross-check its report is to manually bos@559: run your test at each of the following changesets: bos@559: bos@584: The changeset that it reports as the first bad bos@559: revision. Your test should still report this as bos@559: bad. bos@559: bos@584: The parent of that changeset (either parent, bos@559: if it's a merge). Your test should report this changeset bos@559: as good. bos@559: bos@584: A child of that changeset. Your test should bos@559: report this changeset as bad. bos@559: bos@559: bos@559: bos@559: bos@559: Beware interference between bugs bos@559: bos@584: It's possible that your search for one bug could be bos@559: disrupted by the presence of another. For example, let's say bos@559: your software crashes at revision 100, and worked correctly at bos@559: revision 50. Unknown to you, someone else introduced a bos@559: different crashing bug at revision 60, and fixed it at bos@559: revision 80. This could distort your results in one of bos@559: several ways. bos@559: bos@584: It is possible that this other bug completely bos@559: masks yours, which is to say that it occurs bos@559: before your bug has a chance to manifest itself. If you can't bos@559: avoid that other bug (for example, it prevents your project bos@559: from building), and so can't tell whether your bug is present bos@559: in a particular changeset, the hg bos@559: bisect command cannot help you directly. Instead, bos@559: you can mark a changeset as untested by running hg bisect --skip. bos@559: bos@584: A different problem could arise if your test for a bug's bos@559: presence is not specific enough. If you check for my bos@559: program crashes, then both your crashing bug and an bos@559: unrelated crashing bug that masks it will look like the same bos@559: thing, and mislead hg bos@559: bisect. bos@559: bos@584: Another useful situation in which to use hg bisect --skip is if you can't bos@559: test a revision because your project was in a broken and hence bos@559: untestable state at that revision, perhaps because someone bos@559: checked in a change that prevented the project from bos@559: building. bos@559: bos@559: bos@559: bos@559: Bracket your search lazily bos@559: bos@584: Choosing the first good and bos@559: bad changesets that will mark the end points of bos@559: your search is often easy, but it bears a little discussion bos@559: nevertheless. From the perspective of hg bisect, the newest bos@559: changeset is conventionally bad, and the older bos@559: changeset is good. bos@559: bos@584: If you're having trouble remembering when a suitable bos@559: good change was, so that you can tell hg bisect, you could do worse than bos@559: testing changesets at random. Just remember to eliminate bos@559: contenders that can't possibly exhibit the bug (perhaps bos@559: because the feature with the bug isn't present yet) and those bos@559: where another problem masks the bug (as I discussed bos@559: above). bos@559: bos@584: Even if you end up early by thousands of bos@559: changesets or months of history, you will only add a handful bos@559: of tests to the total number that hg bos@559: bisect must perform, thanks to its logarithmic bos@559: behaviour. bos@559: bos@559: bos@559: bos@559:
bos@559: bos@559: