Editing history in your repository

First of all, a disclaimer. You can't do this. At the most basic level, a Mercurial repository contains a list of changesets, each changeset being represented by an id, which is the hash of all of the content (data and metadata) of the change, plus the ids of its parents. Change the data and the id changes, and so it's a different changeset and no longer part of the original tree of changes.

Also, from a workflow point of view, if someone has pulled from your repository, and you try to remove a file from history, then when they push back to you, they push the file you tried to delete and it reappears. You can't put the genie back into the bottle. So even if it's technically possible, it's not possible in a practical sense.

Having said all of this, there are good reasons why people might want to change history. Here are some examples:

As long as you understand the implications, it is possible to do this.

The key implication is that the changeset IDs will change from the point at which the revision occurs. This means that developers with clones will need to rebase their changes, and care must be taken to manage the effects of the revision. This document does not attempt to cover this process. It assumes that if you need to do this, you will ensure that you know what to do, and you make sure it happens.

OK, so you're going to revise history. There are lots of possible revisions:

A similar process can be used for all of these. The simplest approach (as with many things :-) ) is to use the MqExtension.

1. Using mercurial Queues

To start using Mercurial Queues, you need to have run

hg qinit

in the repository.

Let's pretend you comitted the file in revision BAD. Then doing

hg qimport -r BAD:tip

will import the changesets into MQ. If you want to undo the entire changeset BAD (obliterate it!), then do this:

 hg qpop -a
 hg qdelete BAD.diff

If you only want to edit the changeset, then do this:

 hg qpop BAD.diff
 # edit files, remove passwords, etc.
 hg qrefresh

To go back to standard Mercurial changesets you do

 hg qpush -a
 hg qdelete -r qbase:qtip

One thing this process over-simplifies is that the hg qpush -a step may fail, if later changes depend on the obliterated data. In that case, you have to fix the problem manually - there's no easy answer here, after you edit history, you need to manage the consequences, in your own repository as well as elsewhere.

Here's a real example. I add a file I shouldn't in revision 1, edit it in revision 2, then try to obliterate revision 1. After doing so, I need to resolve the issue with revision 2 (editing what is now a nonexistent file). In this case it's easy, we just drop revision 2 as well. In other cases, this could be much harder to deal with, possibly even so much harder that you decide it's not worth it. It depends on your data (and possibly your lawyers!).

>hg init
>echo Line 1 >a
>hg commit --addremove -m "Added a"
adding a

>rem Add the launch codes for the nuclear arsenal here...
>echo Super secret >b
>hg commit --addremove -m "Added b"
adding b

>hg log
changeset:   1:65bcb0d3f953
tag:         tip
user:        "Paul Moore <user@example.com>"
date:        Sat Mar 22 16:43:00 2008 +0000
summary:     Added b

changeset:   0:5dd6949828e1
user:        "Paul Moore <user@example.com>"
date:        Sat Mar 22 16:42:40 2008 +0000
summary:     Added a

>rem Here we compound the error...
>echo More secret stuff >>b
>hg commit -m "Edited b"

>echo More safe stuff >>a
>hg commit -m "Edited a"

>hg log
changeset:   3:ea4f8ad48048
tag:         tip
user:        "Paul Moore <user@example.com>"
date:        Sat Mar 22 16:43:46 2008 +0000
summary:     Edited a

changeset:   2:6bb0d654a0a6
user:        "Paul Moore <user@example.com>"
date:        Sat Mar 22 16:43:32 2008 +0000
summary:     Edited b

changeset:   1:65bcb0d3f953
user:        "Paul Moore <user@example.com>"
date:        Sat Mar 22 16:43:00 2008 +0000
summary:     Added b

changeset:   0:5dd6949828e1
user:        "Paul Moore <user@example.com>"
date:        Sat Mar 22 16:42:40 2008 +0000
summary:     Added a

>rem We realise our mistake. We need to get rid of changeset 1,
>rem so that file b is no longer in out repository!
>hg qinit
>hg qimport -r 1:tip
>hg qpop -a
Patch queue now empty

>rem Delete changeset 1
>hg qdelete 1.diff

>rem Now start to put everything back
>hg qpush -a
applying 2.diff
unable to find 'b' for patching
1 out of 1 hunk FAILED -- saving rejects to file b.rej
patch failed, unable to continue (try -v)
b: No such file or directory
b not tracked!
patch failed, rejects left in working dir
Errors during apply, please fix and refresh 2.diff

>rem Hmm, change 2 depends on file b. Fix things up. Luckily, this is easy, just delete change 2 as well.
>rem In reality, change 2 may contain other edits, and we'd need to do some further fixing.
>hg qdelete 2.diff
abort: cannot delete applied patch 2.diff

>rem Even this isn't as simple as all that. Back out change 2 so we can delete it.
>hg qpop -a
Patch queue now empty

>hg qdelete 2.diff

>rem And now we're good to go.
>hg qpush -a
applying 3.diff
Now at: 3.diff

>hg qdelete -r qbase:qtip

>rem No sign of file b, and the world is safe again.
>rem Except, of course, that evil Doctor Death pulled from us 5 minutes ago.
>rem But at least as we all get blown up, we can be glad that it's not a technical problem :-)
>hg log
changeset:   1:a50e33884959
tag:         tip
user:        "Paul Moore <user@example.com>"
date:        Sat Mar 22 16:43:46 2008 +0000
summary:     Edited a

changeset:   0:5dd6949828e1
user:        "Paul Moore <user@example.com>"
date:        Sat Mar 22 16:42:40 2008 +0000
summary:     Added a

The process of going up through the patch stack, tidying up the debris (as in our example, where change 2 wouldn't apply as it depended on the obliterated file "b"), is what is generally referred to as "rebasing" the changes. It can be simple, in the case of a localised change, but it can be arbitrarily complex. Before you start editing history, you need to be sure that you know what to do to rebase.

2. Other options

There are other options that may be more appropriate in particular circumstances.