Mercurial undo a series of commits - mercurial

I have a mercurial repo with the following history (most recent commit at the top) on a feature branch:
mergeDefaultA
|
mergeDefaultB
|
C
|
mergeDefaultD
mergeDefaultXXXX are merge commits that came as the result of merging the default branch into the feature branch.
What has happened is commit C is screwed, but this was not noticed until after I had pushed mergeDefaultA to Bitbucket. What I want is the following picture:
exactlyWhatIsInMergeDefaultD
|
mergeDefaultA
|
mergeDefaultB
|
C
|
mergeDefaultD
Where exactlyWhatIsInMergeDefaultD is literally exactly what was the state of the code in mergeDefaultD. However, everything I'm reading seems to indicate either you can't undo a series of commits like this (only a single commit back) and even then many of the options aren't available once you've pushed "into the wild".
How do I achieve this?
If this was git, I'd do:
git revert mergeDefaultD
How do I do the same in Mercurial?

Here's what I think you want:
hg revert -r GOOD_REVISION_NUMBER --all
hg commit -A -m "reverting back to revision GOOD_REVISION_NUMBER"
Once that is committed, as soon as someone pulls from you (or you push to them) they will get the most recent revision containing only the good stuff. If they ever do want to go back to the bad stuff, you could always update to that revision:
hg update -r BAD_REVISION_NUMBER

To expand a bit on Harvtronix' answer (which is fine, by the way):
One simple way is to revert to the old revision number ('GOOD') and commit. Note: reverting means that you set the files to the same content as in revision 'GOOD', you don't go back down the tree to that commit. If you did, you would indeed branch off and have two heads.
hg revert -r GOOD --all
hg commit -m "all is good now"
Another way can be to only throw out revision C (if I read your explanation correctly, it's actually just C that is causing the issue). 'hg backout'will introduce the reverse of C in your working directory, so you can then commit it.
hg backout -r C
hg commit -m "Backed out C"
Finally, one last option is to close the bad branch, update to the last commit that was fine and commit further there:
hg up -r BAD
hg commit --close-branch -m "This head is bad"
hg up -r GOOD
... continue here ...

Related

Mercurial workflow: why do I seem to commit everything twice?

I'm trying to grasp the Mercurial basics so please bear with me. :) My current workflow is as follows:
do some work until I'm ready to commit or need the others' changes
pull
at this point I'd like to merge my work with the latest changesets and commit, however Mercurial insists on me committing before merging
so it goes like "commit, merge, commit" and I'm basically committing everything twice, writing the same notes in both changesets and pushing two changesets at a time
Is it intended to be so? Is it possible to have just one changeset coming from me with every merge? Is it indeed desirable?
I've read a lot of online manuals but still feel I do not have solid understanding of the process. All comments are welcome. Thanks!
EDIT: Turns out I didn't know that update could merge incoming changes with uncommitted edits.
Merging always creates a separate changeset in Mercurial.
Plus, merging is not possible as long as you have uncommitted stuff in your local repository.
So the solution is to commit first, and pull and merge afterwards.
This will always result in two changesets, not one.
(...because merging always creates a separate changeset)
But you don't commit the same stuff twice, and especially you shouldn't write the same commit message twice:
The first commit is what you actually changed ("fixed a bug in the foo bar").
The second commit is just the merge (TortoiseHG actually pre-populates the commit message with "Merge", 99% of the time I just leave it like that).
This workflow will prevent a merge in the history, but you still do a merge as noted below:
Do some work until you are ready to commit or need another's changes.
hg pull
hg update (Note: hg pull -u does this and the previous in one step.
During hg update, your uncommitted changes will be merged with the new tip of your current branch. You will still have to resolve any conflicts.
hg commit when ready.
I still recommend if you have extensive changes to commit first before pulling/merging because it is easier to start over by updating to that changeset if the merge goes badly.
Keeping the hg pull and hg update separate allows you to look at the incoming changesets and predict how the merge will go.
The reason is feels strange is that you delay your commit until you want to integrate with the others.
A big feature of distributed version control is that commits are local. Because they're local you should commit often — commit every time you have a small consistent chunk of work done. Your commits are not inflicted on others immediately so you wont interrupt them by making many small commits.
If you begin making more commits you'll see that your workflow becomes:
$ hg commit -m "Refactoring for Issue123"
$ hg commit -m "Basic functionality for Issue123"
$ hg commit -m "Fixed off-by-one error (Issue123)"
$ hg commit -m "Finished implementing Issue123"
$ hg commit -m "Added more tests for Issue123"
$ hg commit -m "Begin use new function from Issue123"
$ hg pull
$ hg merge
$ hg commit -m "Merge"
Here the ratio of merge commits to "real" commits is much more sensible.
Many people (myself included) like to use the rebase extension to avoid the merge completely. That extension linearizes the commits by faking the history so that it looks like you did your four commits after the changesets you pulled down with hg pull. The only change in workflow is that you hg rebase instead of hg merge above and then skip the final commit.

Mark changes as already merged or deliberately ignored with hg pull/push/merge/graft?

I'm transitioning to Mercurial from Subversion, where I'm used to using svnmerge.py to track changes that have already been merged, or which have been blocked from being merged:
# Mark change 123 as having already been merged; it will not be merged again, even if a range
# that contains it is subsequently specified.
svnmerge.py merge -M -r123
#
# Block change 326 from being considered for merges.
svnmerge.py merge -X -r326
#
# Show changes that are available for merging from the source branch.
svnmerge.py avail
#
# Do a catchall merge of the remaining changes. Neither change 123 nor change 326 will be
# considered for merging.
svnmerge.py merge
I want to be able to do something similar for hg pull/push/merge/graft, so that if I know that I never want to merge a given change, I can just block it from consideration, making subsequent cherry-picking, merging, etc., into a more fire-and-forget affair. I have done a lot of googling, but have not found a way to do this.
There also appears to be no way to view a list of as-yet-ungrafted changes.
As I'm often tidying up after other developers and helping them with their merges, it's immensely helpful to be able to do these kinds of things, which one might well consider "inverse cherry-picking;" i.e., marking changes that you do NOT want to merge, and then doing a bulk merge of the remainder.
DAG-based systems like Mercurial ans Git are all or nothing: when you merge two branches, you do a three-way merge of the common ancestor and the two branches.
The three-way merge is only concerned with the final stage of each branch. For instance, it doesn't matter if you make your changes in 10 it 1000 steps — the merge result will be the same.
This implies that the only way to ignore a changeset is to back it out before the merge:
$ hg backout BAD
That will cancel the changeset on the branch, making it appear that it was never made from the perspective of the three-way merge.
If you have a whole branch that you want to merge, but ignore, then you can do a dummy merge:
$ hg merge --tool internal:local --non-interactive
$ hg revert --all --rev .
That goes through the merge, but reverts back to the old state before committing.
The best advice I can give you is to structure your workflow so that the above backouts aren't necessary. This means committing a bugfix on the oldest applicative branch. If a bug is found while creating feature X, then use hg bisect to figure out when the bug was introduced. Now updated back to the oldest branch where you still want to fix the bug:
$ hg update 2.0
# fix bug
$ hg commit -m "Fixed issue-123"
then merge the bugfix into all later branches:
$ hg update 2.1
$ hg merge 2.0
$ hg commit -m "Merge with 2.0 to get bugfix for issue-123"
$ hg update 2.2
$ hg merge 2.1
$ hg commit -m "Merge with 2.1 to get bugfix for issue-123"
If the bugfix no longer applies, then you should still merge, but throw away the unrelated changes:
$ hg update 3.0
$ hg merge 2.2 --tool internal:local --non-interactive
$ hg revert --all --rev .
$ hg commit -m "Dummy merge with 2.2"
That ensures that you can always use
$ hg log -r "::2.2 - ::3.0"
to see changesets on the 2.2 branch that haven't been merged into 3.0 yet.

Mercurial: Switch working directory to branch without losing changes?

Let's say that I have a named branch 'B1' which I'm doing feature development on.
I am at a good stopping point before a demo though not done with the feature so I:
hg up default
hg merge B1
hg ci -m "merged in feature drop"
hg push
Now I continue working for a half an hour or so and go to commit only to realize that I forgot to update back to B1 and that my current working directory is on default - uhoh. In theory I should be able to just mark my working directory parent as the tip of B1 - is there an easy way to do this?
I could of course commit, update back to B1, and merge my changes back, but then there's an unstable changeset in default and this happens often enough to me that I would like a real solution.
Two ways. First, the obvious way:
hg diff > foo
hg up -C b1
hg import --no-commit foo
rm foo
Second, the magical way:
hg up -r 'ancestor(., b1)' # take working dir back to the fork point
hg up b1 # take it forward to the branch head
This way involves merges. Depending on how much your branches have diverged, this may be painless. Or it may be complicated, and you may make a mess of your changes that you haven't saved anywhere. Which is why even magicians like myself prefer to do it the first way.
I would use the shelve extension. I think it’s distributed along with TortoiseHg, you can also use it from the UI:
hg shelve --all
hg up B1
hg unshelve
Rebase extension allow you to change parent for any commit for wrongly commited changeset.
If you want just change branch for future commit - MQ (as mentioned) or Shelve
Typically for this sort of dynamic approach, I favor mercurial queues.
In your situation, what I would do would be to create a patch on default with the changes, pop the patch off, switch over to B1, and apply the patch.
It goes something like:
hg qnew OOPSPATCH
hg qrefresh
hg qpop
hg up B1
hg qpush
<hack hack>
hg qrefresh
hg qfinish
All you need is simple hg up -m B1
From hg up --help:
options:
…
-m --merge merge uncommitted changes
…

Mercurial Getting out of a bad merge

I just merged branch A into B, and for some reason the merge did not go well. I want to revert B back to where it was before the merge and try again like it never happened before. I was thinking of just doing
hg clone myrepo newrepo -r A -r 12345
where 12345 is the revision number before B's bad merge commit
I think this works, but I have a lot of other branches (most of which are closed using commit --close-branch) and this puts those branches back to an inactive state.
Is there a way to clone everything except revision 123456 or something? (where 123456 is the bad commit on B)
Assuming you have not pushed the merge changeset to any public location, the easiest solution is to use the hg strip command that comes with the Mercurial Queues (i.e. mq) extension.
From the wiki:
hg strip rev removes the rev revision
and all its descendants from a
repository. To remove an unwanted
branch, you would specify the first
revision specific to that branch. By
default, hg strip will place a backup
in the .hg/strip-backup/ directory. If
strip turned out to be a bad idea, you
can restore with hg unbundle
.hg/strip-backup/filename.
It might not be as nice as hg rollback, but usually what I do is update to head A, merge in previous head B, check that I got it right this time, and then dummy-merge away the bad merge.
I hope I'm understanding your situation correctly. If I am you should be able to update to the revision of B before the merge, give that revision a new branch name, merge A in to it, and continue on. You'll probably want to mark the original B branch as closed.
$ hg up 12345 #12345 is the revision of B prior to the merge
$ hg branch B-take2
$ hg merge A
$ hg commit -m 'merge A in to B-take2'
$ hg up B
$ hg commit --close-branch -m 'mark original B branch as closed'
Is it too late to use the hg rollback command? If so, try the hg backout command.

Abandoning changes without deleting from history

There is a commit that just didn't work, so I want to abandon it without deleting it from history.
I have updated from an earlier revision and committed, thus creating a new head.
I don't have branches, I don't want branches, I just want to simply go on with the new head exactly as it is, nothing fancy, no merge, no worries, just go on forgetting the previous one.
I can't seem to find how to do that, and I'm starting to believe it can't be done. All I find is stuff about branches, or stuff about merging.
Update your repository to the head with the revision that you want to forget about, then use hg commit --close-branch to mark that (anonymous) branch as closed. Then update to the head of the branch that you do want, and continue working.
You can still see the closed branch if you use the -c option to hg heads, but it won't show up by default and hg merge will know not try to merge with the closed head.
You will need to use hg push --force the first time you push this closed head to another repository since you are actually create additional heads in the remote repository when you push. So tell Mercurial that this is okay with --force. People who pull the closed head wont be bothered by any warnings.
I know you don't want to work with branches at this stage, but that's exactly what you've done. When you went back to an earlier version and committed something that worked you created a branch - an unnamed branch, but a branch all the same.
There's no problem with just carrying on just as you are and not worrying about having multiple heads, but if you want to tidy things up so you don't accidentally pick the wrong head one time then you can kill off the old branch.
There's a good section in the Mercurial documentation that takes you through a number of options around Pruning Dead Branches.
I think the best option for you is to mark the old branch as "closed". If your old head is revision "123" then:
hg update -r 123
hg commit --close-branch -m 'Closing old branch'
hg update -C default
First of all, type:
hg heads
Imagine, you have three heads listed:
changeset: 223:d1c3deae6297
user: Your name <your#email.com>
date: Mon Jun 09 02:24:23 2014 +0200
summary: commit description #3
changeset: 123:91c5402959z3
user: Your name <your#email.com>
date: Sat Dec 23 16:05:38 2013 +0200
summary: commit description #2
changeset: 59:81b9804156a8
user: Your name <your#email.com>
date: Sat Sep 14 13:14:40 2013 +0200
summary: commit description #1
Let's say, you want to keep the last head active (223) and close the rest.
You would then do as follows:
Close head #59
hg up -r 59
hg ci --close-branch -m "clean up heads; approach abandoned"
Close head #123
hg up -r 123
hg ci --close-branch -m "clean up heads; approach abandoned"
Commit the changes
hg push
Don't forget to switch to the right head at the end
hg up -r 223
And you're done.
You want to use hg backout. This removes the changes made by the changeset from any child changeset.
Check this out for a good explanation.
Mercurial Backout
An alternative to closing or stripping the unwanted branch would be to merge it in a way that totally discards its effects, but leaves it in history. This approach will allow those unwanted changes to propagate in a push - so only use this if that is the intended effect.
Let's say the changeset history looks like this:
1-2-3-4-5-6
\
7-8-*
and it is 5 and 6 which are no longer wanted.
You can do this:
hg up 8
hg merge -r 6 -t :local
hg commit ...
which will create this:
1-2-3-4-5-6
\ \
7-8-9-*
The update to 8 ensures you are working at the desired head in history, which you want to keep.
The -t :local instructs hg to use the merge "tool" called local which tells it to ignore changes from the other branch, i.e., the one NOT represented by the current working folder state. More info.
Thus the unwanted changes in 5 and 6 are preserved in history but do not affect anything more recent.
Both Niall's and Nick's answers are straight on. Because I find myself creating lots of dangling heads, I ended up writing an alias to close heads more easily. By adding this to your .hgrc:
[alias]
behead = !REV=$($HG id -i); $HG update $# -q && $HG ci --close-branch -m "Closing dead head" && $HG update $REV -q
(if you already have an [alias] section, you can append to it instead)
You can now close a head in one single-command (and without having to update to a different changeset manually) like this:
$ hg behead 123
Note: the alias takes advantage of the fact that Mercurial aliases can be shell commands. This means that this will probably only work on UNIX, not on Windows.
This is a use case for the Evolve extension. It's currently not bundled with Mercurial, so it is technically a third party extension. But it's being used quite heavily by a bunch of people, including Mercurial developers, is being very actively developed, and isn't going anywhere.
With the Evolve extension, you simply do
hg prune -r revname
and get on with your life. The cset will still be there, but obsoleted. It won't be visible unless you pass the --hidden option to Mercurial commands, and by default won't be pushed to remote repositories. Though I think you can force it if you really want to.
If the cset you are pruning has ancestors you want to keep, then you'll have to run hg evolve to rebase those changesets. hg evolve will do so automatically. Otherwise, you don't have to do anything.
You may clone your corrupted repo to a new one without cloning that unwanted head. Then remove old repository, move newly created clone to the original place and continue working with it. This will take some time, but you'll get a perfectly clean repository without a sign of that unwanted revision.
hg clone --rev myGoodResition myDirtyRepo myCleanRepo
I have run into this issue many times when I want to behead a head that was created in error. I always want to see it disappear off the face of the Earth.
On your local copy, get the latest and then:
Find the beginning of a head you want to strip (where a new neck starts to branch off), get the revision number
Strip it.
Source: TipsAndTricks.
Source: PruningDeadBranches#Using_strip.
hg --config extensions.hgext.mq= strip -n <rev>
Make a trivial file update (add a whitespace to a file), commit and push.
Your repo should now have the head stripped. The last step is important as stripping doesn't create any changes you can push to your central repository. Without the last step you only have stripped the head locally.