50

I have accidentally made a commit to my local repository. To be more specific, I committed changes to lots of files all at once, when I meant to commit them one at a time.

How do I undo this commit and get my working copy back to the state it was before the commit so I can do what I should have done in the first place?

I have not pushed or pulled or anything else since the commit.


You may suspect this is a duplicate, so I will explain why the following questions are different, or do not answer my question:

Mercurial undo last commit

An answer to this one states that you can do this with hg commit --amend, but does not explain how or give an example of doing this. The mercurail help does not spell it out for me either.

How do you "rollback" last commit on Mercurial?

States to use hg rollback. This command is apparently deprecated, I tried using it anyway, but I got the messge: no rollback information available. A shame this doesn't work as this would be a really intuitive way to achieve what I want.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
crobar
  • 2,810
  • 4
  • 28
  • 46
  • Possible duplicate of [Mercurial undo last commit](https://stackoverflow.com/questions/4760684/mercurial-undo-last-commit) – StayOnTarget Jul 18 '18 at 18:16
  • In the present state I can't see why this is not a duplicate of https://stackoverflow.com/questions/4760684/mercurial-undo-last-commit. Maybe they were once less similar, or maybe the other question has been improved, but they are asking the same thing as far as I can tell. – StayOnTarget Jul 18 '18 at 18:16
  • I mention that question in this question above. That question doesn't have the right answer. No one has minded the similarity for three years and this question and the answer has many up votes. – crobar Jul 19 '18 at 05:06
  • I did see your note in the question which is why I thought I should leave a specific comment. Noting that a question may be a duplicate is not saying there is anything wrong with it - just that it would be easier for readers if they can find their information in a single place. This question has more than one way to solve it, so I'm not surprised that different answers exist. But IMO the fact that the other one doesn't have the same answer does not mean that the *question* itself is not a duplicate. Also, I don't think there is any time limit. its always worth trying to improve SO. – StayOnTarget Jul 19 '18 at 11:27
  • Well, another way to put it is that the selected answer on that question is wrong/out of date but since it was marked as answered, no-one would ever have answered it. – crobar Jul 19 '18 at 14:39
  • Actually I see someone has edited it in 2016 to hint at the right answer, but not the exact command. – crobar Jul 19 '18 at 14:41

4 Answers4

62

Based on information from this question:

hg strip --keep --rev .
--keep: do not modify working directory during strip
--rev . (the dot denotes the last commit. read sid0's answer regarding descendants)


For people more familiar with git language, you are looking for git reset --mixed HEAD^

hard which would discard your changes, making your work "disappear" (I assume that is not an option)

soft would undo the commit, but keep previously committed files indexed (that is, tracked)

mixed keeps your changed files in place, but tells the index to undo the commit. in git-speak: git st would say Changes not staged for commit


See also git-reset docs , Difference git reset soft/mixed, git/hg Command equivalence table

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
maosmurf
  • 1,858
  • 17
  • 16
32

Ok, I think I've found the answer, it's to enable the strip extension, and use the following command:

hg strip -r -1 --keep

This strips the last revision from the repostiory (the -r -1 bit), and the --keep option means no changes are made to the working copy. So you end up with a working copy exactly as it was just before the commit, and no last commit in the repository.

I'm no mercurial expert, so use at your own risk.

crobar
  • 2,810
  • 4
  • 28
  • 46
  • 4
    1. JFYI `--keep` needed only if you have modified Working Dir and want to preserve these changes. If you want to "...get my working copy back to the state it was before the commit", you can skip --keep 2. "The strip command removes the specified changesets and all their descendants" - it your case (removing tip) it's not relevant, but in common case individual removing of commits in the middle of history may be better with `histedit` extension – Lazy Badger Jul 07 '15 at 10:50
  • 3
    Well to get my working copy back into the state it was just before I committed, it's essential that you use `--keep`, otherwise you are going back to the previous commit, and not back to the state it was in just before that commit. – crobar Jul 31 '15 at 08:36
  • The documentation about `--keep` says "do[es] not modify working directory during strip", but it does just the opposite. With --keep the working directory is brought into the state before the commit, without it is not changed. In both cases a backup is stored in `.hg/strip-backup`. – Harald Oct 09 '19 at 09:51
  • @Harald, I don't think what you said is right, with `--keep` the working directory is not changed, only the commit history. – crobar Oct 10 '19 at 21:14
2

commenting and elaborating on @crobar's post cause # of characters is not enough.

when doing a local commit (no push) then run hg strip -r -1 --keep, it deletes your previous commit AND preserves your list of files waiting to be committed. basically this is a full undo.

if i use hg strip -r -1 without the --keep, it still deletes your previous commit BUT when i try to list the files to be committed it can't find a change so I wouldn't recommend doing this.

if i do a commit then do a push (to remote) then do hg strip -r -1 --keep, it does exactly what its supposed to do BUT when you do another commit then push, it creates another branch.

I.E. 

o----o----Branch (local)
      \
       \
        --o----o----o----Branch (with previous push)

my source/reference: testing these scenarios

mb.
  • 1,293
  • 1
  • 12
  • 20
1

What I'd do in this situation is make a new branch that has the changes I want before I ever thought about removing the bad change-set. So in this case I'll need to go back to the revision before I made the bad commit, re-do my changes and then make multiple commits, one for each file.

Step by step:

1) Go back to before the bad commit.

% cd <top of repo>
% hg log -l 5
<identify the last good revision>
% hg update -r <last good revision number>

2) Remake the changes: I'd get a patch which describes the changes I want, and apply it to the tree. (I'm assuming tip currently points to where we started)

% hg diff -r .:tip | patch -p1
patching file a/b/c/d
patching file a/b/e/f

3) Make new commits: We're now back at the state before you made the commit you wanted to split up. Do hg status and look at the modified files, make sure everything is as you expect. At this point you can either commit the files one by one by naming them on the command line, or use an extension like record or crecord to interactively select them.

% hg commit a/b/c/d
% hg commit a/b/e/f

...or...

% hg crecord
<select files in UI>
% hg crecord
<select files in UI>

You'll end up with a repo that looks like this:

o----o----B
      \
       \
        --o----o----o----T

Where B is the old bad commit, and T is the new tip of the repo. If you want to then make that branch as closed, so it doesn't show up in logs / etc, you can...

% hg update -r <revision B>
% hg commit --close_branch
% hg update -r tip

If you want to remove it completely, you can strip it.

% hg strip -r <revision B>

Either way, your log will look like nothing ever happened.

Paul S
  • 7,645
  • 2
  • 24
  • 36