Mercurial undo merge that has more commits following it? - mercurial

Have a scenario where we un-intentionally merged a named branch (ABC) into our default branch.
hg rollback is not an option because there have been a couple commits since.
Is there a way to undo this?

You're going to need the Mq extension. If you don't have it turned on, do so by adding this to your Mercurial.ini or .hgrc file.
[extensions]
hgext.mq=
If you're not familiar with it, the Mq extension let's you manipulate history. The good news is, this will allow us to fix your repo. The bad news is that anyone who has a clone of the messed up repo will have to clone it again, because we'll be changing history.
First, go make another clone of your repo to work in, so we don't mess anything up.
Now, find the revision id of the merge changeset (that merged default and your named branch). Write it down. We'll refer to it as changesetM. Now find the revision id of the next changeset. Write it down. We'll refer to it as changesetN.
Once you have those two revision ids, head over to your command prompt and cd into your repo. Then type out the following, replacing changeset[M|N] with the appropriate revision id.:
$ hg qimport -r changesetN:tip
# This will add all of your changes since the merge to the queue
$ hg qpop -a
# This pops them all out of your history.
$ hg strip changesetM
# This removes the merge changeset.
$ hg update -C default
# Make sure we're on the default branch
$ hg qpush -a
# Take the changesets in the queue and push them back onto your history.
$ hg qfinish -a
# Remove changesets from the queue and finalize them as normal changesets.
Essentially, you are rebasing the new changesets on top of the default branch, removing the merge changeset in the process. Once you're done, you'll need to push the changes to a new repository on the server, and have your colleagues clone fresh copies.
Lastly, if you have any other Mercurial questions, also check out kiln.stackexchange.com.
UPDATE
I forgot to mention: If someone has based changes on something that was only in the other branch, it is possible that hg qpush -a will fail. You'll see a foo.txt.rej and foo.txt.orig file laying around. Unfortunately, you'll have to fix this yourself. To fix it, open the original file, the .orig file, and the .rej file and choose the right changes to merge in, saving them in the original file. Once you've merged it in, use hg qrefresh to update that patch to the new, merged patch. From their, you should be able to run hg qpush -a again and continue. If you run into the same error again on another patch, just follow the same process.

If you haven't publish the repo publicly you can do this
hg clone -r (parent1 of bad merge) -r (parent2 of bad merge) old new
and delete the old repo.

I came across the following scenario today:
# changeset: 1728:5d703e1051d3
|\ parent: 1727:1a5f73b5edb4
| | parent: 1720:65ddd0bde225
| | user: nn
| | date: Wed Feb 27 10:35:00 2013 +0100
| | summary: Merge with SomeBranch
| |
| o changeset: 1727:1a5f73b5edb4
| | user: nn
| | date: Wed Feb 27 10:34:30 2013 +0100
| | summary: lorem ipsum
| |
[some more changesets]
| |
o | changeset: 1720:65ddd0bde225
| | branch: SomeBranch
| | user: nn
| | date: Wed Feb 27 07:44:46 2013 +0100
| | summary: lorem ipsum
Where SomeBranch should not have been merged into default. What we did to solve this, was to use the backout command with the parent option like so:
hg backout --rev=1728 --parent=1727
By this you don't undo the merge itself: Looking at a branch graph (either with graph log or in TortoiseHg) you'll still see SomeBranch going into default at r1728. The result of the merge is however undone, meaning that the changeset containing the backout (r1729 in my case) is identical to r1727.

Related

Undoing accidental hg amend on top of Mercurial queue

The following sequence of commands requires that the Mercurial Queues exension be enabled, and also the Evolve extension (for hg amend, alias hg refresh).
This combination of commands messes up the existing Mercurial queue, making the commit corresponding to the pushed patch obsolete, and creating a new commit with exactly the same contents.
This error is in theory quite easy to make: just type hg ref instead of hg qref, but I just made it recently after using Mercurial Queues for years. Regardless, I haven't figured out a clean way to fix this and get back the original state.
Note that a simple hg rollback does work in my example, but I'm not sure it will work in my case, because I've tried other things trying to fix this. In any case, hg rollback isn't something one should rely on.
In summary, how do I undo the hg amend and get back my applied MQ patch?
#!/bin/sh
hg init test
cd test
echo "This is foo" >> foo
hg add
hg ci -m "Add foo"
hg init --mq
echo "Line 2 of foo" >> foo
hg qnew p
hg ci --mq -m "Add patch p"
hg ref
hg log -vG --hidden
# changeset: 2:f7f038d3aab5
| tag: tip
| parent: 0:9d3a95922194
| user: Faheem Mitha <faheem#faheem.info>
| date: Sun Mar 11 16:38:51 2018 +0530
| files: foo
| description:
| [mq]: p
|
|
| x changeset: 1:e467a2433c7f
|/ tag: p
| tag: qbase
| tag: qtip
| user: Faheem Mitha <faheem#faheem.info>
| date: Sun Mar 11 16:38:50 2018 +0530
| obsolete: rewritten using amend as 2:f7f038d3aab5 by Faheem Mitha <faheem#faheem.info> (at 2018-03-11 16:38 +0530)
| obsolete: rewritten by Faheem Mitha <faheem#faheem.info> as f7f038d3aab5 (at 2018-03-11 16:38 +0530)
| files: foo
| description:
| [mq]: p
|
|
o changeset: 0:9d3a95922194
tag: qparent
user: Faheem Mitha <faheem#faheem.info>
date: Sun Mar 11 16:38:50 2018 +0530
files: foo
description:
Add foo
That's not what I get with your test case. I need an additional
echo "Line 3" >> foo
before the hg ref. Also, versioning your mq seems irrelevant here; I think you can remove the hg init --mq and hg ci --mq lines.
hg amend really ought to block that. But if you wanted to manually fix things up, edit .hg/patches/series and replace the obsolete hash with the successor's hash. (Just make sure to use the 20-byte hash, as is given by eg hg log -T '{node}\n').
Warning: when I tried that with your test case and qpopped, something mysteriously un-obsoleted the old head, and it gives a strange warning about the current directory not being a head (despite hg log -r 'head()' listing it.) But at least you have your queue back in a working state.

Creating diff patch for all changes in a branch?

I created some branch (call it brnch) from default and made some commits there. Now there is a need to cherry-pick all diffs from the branch to some release branch for hotfix. Is something like that possible in mercurial?
^ ^
| |<--- brnch
| - commit3
| |
| - commit2
- |
| |
| - commit1
- |
| |
| |
|--
|
-
default
Is it possible to create a patch from all commmits in the branch brnch (commit1, commit2, commit3) and apply to some another branch (release hot-fix in my case)?
See hg help export and hg help import.
Examples:
hg export branch(brnch) -o brnch.patch # export everything on brnch
hg export -r commit1 -r commit2 -o cherrypick.patch # cherrypick two changesets
hg update <somewhere_else>
hg import brnch.patch # import the revisions in the patch
hg import --no-commit brnch.patch # import (but don't commit) revisions.
hg commit -m hotfix # commit changes as one commit with "hotfix" description.
You can simply achieve that by means of hg rebase.
For simplicity I will assume that you want to see everything from commit1 onward in your brnch see put into your release branch. Thus checkout the release branch and then rebase your brnch commits:
hg update release
hg rebase --source commit1 --collapse --keep --dest . --message "bla bla something fixed"
Alternatively you can give a revision range. If the commits you want to see collapsed are non-linear in the brnch branch, then you will need to resort to graft (which is cherry-picking individual commits) - and you can collapse (hg fold) all grafted changesets subsequently in your release branch, provided you keep them initially in phase draft.

Continue mercurial branch at new location

I have a branch dev that has been merged into a release branch to form a release commit.
I am hoping to reopen dev from after that merge commit so that my repository has a visual "fan-in fan-out" style. By that I mean that all commits and branches converge into my release commit and then work for the next release diverges out of it. This visually makes it very easy to see what is contained in each release as it keeps them isolated between our tagged release commits.
The effect is that dev is no longer a contiguous branch and I am wondering if this is possible in mercurial.
When I try to hg branch dev again after the release, mercurial complains that it already exists. Even if I commit with say --close-branch after I merge it.
So currently my workaround is to:
(1) either use a different dev-VERSION for each release which is more fiddly and harder to script or
(2) run dev in parallel to release and keep merging from dev into release which isn't so bad, just a little messier for our particular release process.
I know that this might seem like a non-standard mercurial release process. It would suit our particular project and team structure really well though.
Thanks for any help!
EDIT:
To make what I mean a bit clearer, imagine I had merged a feature-01 branch into dev and then merged dev into release in order to create release-1.1. Then I want to make a new feature-02 branch off that merges into dev with a visualisation like:
# changeset: 6:4bcc59fe6ded
|\ branch: dev
| | summary: Merge feature-02
| |
| o changeset: 5:8e117ffe64d0
| | branch: feature-02
| | summary: Add d
\ |
o changeset: 4:c39dae3ff2fa <---- all commits converge to here then diverge back out
/| branch: release
| | summary: Release v1.1
| |
o | changeset: 3:c89af2b3e8db
| | branch: dev
summary: Merge feature-01
This is an example bash script where I'm trying to get something like this:
# Create test
rm -rf test
mkdir test
cd test
# Set up v1.0
hg init
hg branch release
touch a
hg commit -m 'Release v1.0'
# Set up dev branch
hg branch dev
touch b
hg add b
hg commit -m 'Created dev'
# Feature branch
hg branch feature-01
touch c
hg add c
hg commit -m 'Add c'
hg update dev
hg merge feature-01
hg commit -m 'Merge feature-01'
# Release all the things
hg update release
hg merge dev
hg commit -m 'Release v1.1'
# Start a new feature branch
hg update dev # <------ crucial line
hg branch feature-02
touch d
hg add d
hg commit -m 'Add d'
hg update dev
hg merge feature-02
hg commit -m 'Merge feature-02'
If I try to continue feature-02 on the dev branch, it ends up having commit 3:e7ba9d1d9bfe as it's parent when I would like it to have the release commit 4:bbc951a4b339 as its parent but still be on the dev branch.
hg log -G gives roughly this output:
# changeset: 6:46539f99dadd
|\ branch: dev
| | tag: tip
| | summary: Merge feature-02
| |
| o changeset: 5:885b0b930fed
|/ branch: feature-02
| summary: Add d
|
| o changeset: 4:bbc951a4b339 <--- not the parent of the new changes
|/| branch: release
| | summary: Release v1.1
| |
...
I could change my process so that the first feature branch after a new release is branched directly off release, but that makes the process harder to script and just delays the problem until I have to merge it into dev:
So if I use the same script as above but change it slightly:
...
# Start a new feature branch
#hg update dev
hg update release # <--- changed to release
hg branch feature-02
...
Then my graph ends up being:
# changeset: 6:4bcc59fe6ded <---- has the previous `dev` as a parent
|\ branch: dev
| | summary: Merge feature-02
| |
| o changeset: 5:8e117ffe64d0
| | branch: feature-02
| | summary: Add d
| |
| o changeset: 4:c39dae3ff2fa
|/| branch: release
| | summary: Release v1.1
| |
o | changeset: 3:c89af2b3e8db
|\ \ branch: dev
|| | summary: Merge feature-01
...
If I try to rebranch dev:
...
# Start a new feature branch
#hg update dev
#hg update release
hg branch dev # <--- changed
hg branch feature-02
...
I get an error: abort: a branch of the same name already exists.
I hope that helps make it clearer! Thanks for any help!
After doing your merge, just do hg update dev and start making commits. You don't need to close and reopen the branch. You don't need to create a new branch.

How to push, without creating new heads, after creating multi-head branch, rebase and several merges

I did a series of dumb steps with my local copy of our shared repository, and I'm looking for a way how to fix it. The steps are:
I used bookmarks to have multiple heads of a development branch, that other people use:
-o---o---o-----o--- <- dev branch
\----1----1------ <- another head of the dev branch,
where I committed stuff
I created a new branch, still local, some time later
/---------------x <- new branch
-o---o---o-----o--- <- dev branch
\----1----1------ <- another head of the dev branch,
where I committed stuff
for one head, that contains only my code, I did a rebase on another branch
/-1'--1'-- <- rebase
/---------------x <- new branch
-o---o---o-----o--- <- dev branch
\----1----1------ <- another head of the dev branch,
where I committed stuff
then, I merged the rebase, and then, a couple of commits later, I merged default
----------d-\ <-default
\
/-1'--1'\ \
/---------------x--------x--x-x-x-- <- new branch
-o---o---o-----o---
\----1----1------
Now, I wanted to push my new branch to the server (hg push --new-branch -b newBranch), but I get abort: push creates new remote head, since commits 1' belong to dev branch.
What is the right thing to do? I would like to avoid creating this additional head.
Update:
Per request, this is the output of hg heads:
changeset: 839:f2033d695fcd <- want to push this
branch: newBranch
tag: tip
user: me
date: Wed Oct 31 13:05:51 2012 +0100
changeset: 826:7fde19d7f467
branch: devBranch
user: my-collegue
date: Tue Oct 23 14:59:42 2012 +0200
changeset: 820:23853bbf68df <- the part after rebase that got merged
branch: devBranch
user: me
date: Mon Oct 22 15:36:26 2012 +0200
changeset: 807:899344cfb145 <- obsolete (branch with 1's)
branch: devBranch
parent: 711:454f29c03fb1
user: me
date: Mon Oct 22 15:36:26 2012 +0200
changeset: 712:d5e8a62a7f5f <- default, needs to stay
parent: 648:2bbcc01aa191
user: me
date: Wed Aug 22 16:21:09 2012 +0200
You can push only the one head you're interested in mercurial. For you that would mean doing:
hg push -r f2033d695fcd
If the destination repo has been updated you need to pull, merge, and re-push:
hg pull
hg up -r <remote head>
hg merge -r f2033d695fcd
hg ci
hg push
I solved the problem, without pushing another head to the repo, and without merging 23853bbf68df onto newBranch. This is probably not the cleanest way to do it, but I will leave it as a reference. In a nutshell, I reconstructed the whole branch by taking all commits and re-applying them.
First, I killed the newBranch'es head 899344cfb145, by using strip on the first diverging revision I did:
hg strip -r 646
Then, I produced email patches (couldn't play with mq) for all commits that are in newBranch, since it's inception:
hg export -g -r 797:808 -r 810 -r 815:822 -r 824:830 -o "%n-%m.patch"
797:808 are the patches in newBranch that were in the rebased part of devBranch (1' commits from the original figure).
810 and 815:822 are other patchs in newBranch. 811:814 belong to a different branch, so I had to exclude those.
823 is a merge commit with default, so I skipped this one.
824:830 are all the commits after the merge with default.
Now, I imported these patches on a new head of newBranch:
hg up -r 796
# there are 29 patches, applying till merge
hg import --bypass {01..21}*.patch
hg up tip
hg merge default
hg ci -m 'merging in default'
hg import --bypass {22..28}*.patch
Finally, I just stripped of the original head of newBranch.
hg strip -r 797
This may not work in every situation. During the merge, I had to resolve some conflicts, too, but pretty benign. Hope this helps someone.

How to remove accidental branch in TortoiseHg?

(I am a relative newcomer to TortoiseHg, so bear with me :-) I use TortoiseHg on two machines to talk to my remote source repository. I made changes on one machine, committed them, and attempted to push them to the remote repository BUT I forgot to first do a pull to get the latest code first. The push gave me a few lines of output, suggesting I may have forgotten to pull first (true!) and mentioned something like "abort: push creates new remote branches...".
So I did a pull, which added several nodes to the head of my graph in the repository explorer. The problem is that the push I tried to do is now showing as a branch in the repository explorer. Looking from the server side (codeplex), it shows no sign of my attempted push, indicating this accidental branch is still local on my machine.
How could I remove this accidental branch? I tried selecting that node in the graph then doing "revert" but it did not seem to do anything. I am wondering if it would be simplest to just discard my directory tree on my local machine and do a completely new, clean pull from the server...?
First make sure you have committed all your local changes. Then merge the branches by calling hg merge and commit the result.
This should bring you back to a single branch, reuniting the two heads.
I had a branch I didn't want, but found I could not merge the branch (or it was very difficult for me to figure out how to merge) because it contained a conflict based on a file name case change.
Ultimately, I decided to hg commit --close-branch. Since the branch had existed only on my local repo, and not the repo I had cloned from, the subsequent hg push didn't even bother to push the closed branch out to the master repo! Very convenient. At that point, all I had to do to eliminate it entirely was delete my local repo and re-clone from the "master".
Do a "Merge With" and check "Discard all changes from merge target (other) revision". Of course you should make sure that the target shown as the merge target is really the one you want to throw away before you click the Merge button.
In the Repository explorer, choose the first rev of your local changes, then right click on the tip of the branch you just pulled and choose "Rebase on top of selected" or "Modify history->Rebase on top of selected" depending upon your client version. This will "re-base" your revs on the pulled ones.
Additionally, to help avoid it in the future...
In Repository Explorer, choose Tools->Settings. In the top left drop-down, choose "User global settings", so this applies to all repositories. Then choose Synchronize on the left. In "After Pull Operation" choose "rebase". This will cause your local revisions to be "rebased" upon the revisions you just pulled, rather than leaving them in a different branch.
This is how I do it and is probably what you typically want.
For more info, see the rebase project and the rebase extension.
This is how to do it with the command-line tool. I guess it can be easily mapped to TortoiseHg (although I'm not sure, since I never use it...) Anyway, since it should be done just once in a while, I think there is no problem on using the terminal here.
An example setup
Suppose your remote repository is something like this:
# changeset: 3:a4c18a1fba12
| tag: tip
| summary: commit 4
|
o changeset: 2:91c5dbfba15c
| summary: commit 3
|
o changeset: 1:4c77cb7952ea
| summary: commit 2
|
o changeset: 0:085dae46f27e
summary: commit 1
Locally, you did not have commit 4, so you committed something directly over commit 3:
# changeset: 3:39526003350f
| tag: tip
| summary: commit 4 made locally
|
o changeset: 2:91c5dbfba15c
| summary: commit 3
|
o changeset: 1:4c77cb7952ea
| summary: commit 2
|
o changeset: 0:085dae46f27e
summary: commit 1
So you try to push it, and get this message:
$ hg push
pushing to ssh://hg#bitbucket.org/brandizzi/mercurial-test-repo
searching for changes
remote has heads on branch 'default' that are not known locally: a4c18a1fba12
abort: push creates new remote head 39526003350f!
(pull and merge or see "hg help push" for details about pushing new heads)
As requested, you pull it:
$ hg pull
pulling from ssh://hg#bitbucket.org/brandizzi/mercurial-test-repo
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
You have it now...
o changeset: 4:a4c18a1fba12
| summary: commit 4
|
| # changeset: 3:39526003350f
|/ summary: commit 4 made locally
|
o changeset: 2:91c5dbfba15c
| summary: commit 3
|
o changeset: 1:4c77cb7952ea
| summary: commit 2
|
o changeset: 0:085dae46f27e
summary: commit 1
... but you would rather not merge as requested. You want to have this instead:
o changeset: 4:a4c18a1fba12
| summary: commit 4 made locally
|
o changeset: 3:a4c18a1fba12
| summary: commit 4
|
o changeset: 2:91c5dbfba15c
| summary: commit 3
|
o changeset: 1:4c77cb7952ea
| summary: commit 2
|
o changeset: 0:085dae46f27e
summary: commit 1
And then you want to push it to the remote repo.
How do you get that?
Solving it
To get that, you cannot have pushed the "commit 4 made locally". Also, there is no way to put it after the new remote commits. Said that, we can get what we asked.
Said that, you just need to rebase your local commit onto the new remote commit:
$ hg rebase --source 3 --dest 4
If you are lucky, that will be enough.
Handling conflicts
If you are unlucky, you may have some conflicts:
$ hg rebase --source 3 --dest 4
rebasing 3:39526003350f "commit 4 made locally"
merging test.txt
warning: conflicts while merging test.txt! (edit, then use 'hg resolve --mark')
unresolved conflicts (see hg resolve, then hg rebase --continue)
Then, just resolve the conflicts (by manually editing them):
$ hg st
M test.txt
$ nano test.txt # Edit and save
...mark the file as resolved...
$ hg resolve --mark
(no more unresolved files)
continue: hg rebase --continue
...and proceed with the rebase:
$ hg rebase --continue
rebasing 3:39526003350f "commit 4 made locally"
saved backup bundle to /home/adam/software/mercurial-test-repo/.hg/strip-backup/39526003350f-64863882-backup.hg
Here is your new history:
# changeset: 4:ca31fe8a15f0
| summary: commit 4 made locally
|
o changeset: 3:a4c18a1fba12
| summary: commit 4
|
o changeset: 2:91c5dbfba15c
| summary: commit 3
|
o changeset: 1:4c77cb7952ea
| summary: commit 2
|
o changeset: 0:085dae46f27e
summary: commit 1
Now, push it:
$ hg push
pushing to ssh://hg#bitbucket.org/brandizzi/mercurial-test-repo
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
Those days, it is not as complex as it used to be, right? :)