I've done a rebase on my local repository, and it's gone wrong. How do I come back from it?
Before the rebase
# 225:c5eb9b47e19a Task #1 modified due to comment by peers
|
| o 224:7a1964c6b694 WIP on Task #2 depending on Task #1
| |
| o 223:eb5a2ce5ef36 Task #1 sent for peer review
|/
o 222:d18063b01959 Remote tip
The unfortunate rebase
Coming from a git world, I'm used to rebase things. That may have been my mistake.
$ hg rebase -r224
merging some_file
3 files to edit
saved backup bundle to /path/to/repo/.hg/strip-backup/7a1964c6b694-backup.hg
After the rebase
# 224:c5eb9b47e19a Task #1 modified due to comment by peers
|
| o 223:eb5a2ce5ef36 Task #1 sent for peer review
|/
o 222:d18063b01959 Remote tip
$ hg diff
# nothing
$ hg st -amdr
# nothing
$ hg update --clean 7a1964c6b694
abort: unknown revision '7a1964c6b694'!
I'v lost my work commited as WIP on Task #2 depending on Task #1: it isn't in c5eb9b47e19a (I must have messed while merging), and the original commit is gone (is it?).
How do I cancel / revert my rebase?
Get "WIP on Task #2 depending on Task #1" back
hg up 223
hg unbundle /path/to/repo/.hg/strip-backup/7a1964c6b694-backup.hg
Get opposite of "WIP on Task #2 depending on Task #1"
hg revert --all -r 223
hg commit -m opposite of WIP on Task #2 depending on Task #1
Apply opposite of "WIP on Task #2 depending on Task #1" to undo the merge
hg update 224
hg rebase -r 225
Related
I have two hg branches (dev and stable) that aren't merging like I'd expect.
On stable: I grafted in a one-line commit from dev.
On dev: Changed that one line that was grafted, committed change.
On stable: merged dev into stable (no conflicts).
However after this merge stable still has the grafted version of the line (step 1). Not the latest changes to that same line from dev (step 2). Why is this?
The file looks like:
This
file
to
be
merged
Changesets:
Changes "to" to "might" on dev
Grafts changeset 1 to stable
Changes "might" back to "to" on dev
Merges dev into stable. Result is "might" (not "to" like I'd expect to see from changeset 3).
Sorry about the delay here: As soon as you shrank the reproducer to the five commits, I knew what was going on, but I wanted to write my own reproducer before answering, and the priority of this dropped a lot. 😀 The script I used, mktest.hg, to create the commits, the graft, and the merge, appears at the end of this answer.
The key issue here is the way merge actually works in Mercurial. It uses the same algorithm as Git does: that is, it completely ignores any of the branch information, and completely ignores any timing information. It looks only at three specific commits, as found by examining the commit graph, as shown in your image. Here's a text variant via my own reproducer:
$ cd test-hg-graft/
$ cat file.txt
This
file
might
be
merged
$ hg lga
# 4:b027441200d2:draft stable tip Chris Torek
|\ merge dev into stable (9 minutes ago)
| |
| o 3:01c6cc386a08:draft stable Chris Torek
| | back to "to" on stable (9 minutes ago)
| |
| o 2:ad954507e465:draft stable Chris Torek
| | s/to/might/ (9 minutes ago)
| |
o | 1:f7521e4f0941:draft dev Chris Torek
|/ s/to/might/ (9 minutes ago)
|
o 0:a163d2c4874b:draft stable Chris Torek
initial (9 minutes ago)
The lga alias is one I stole borrowed copied from someone else:
lga = log -G --style ~/.hgstuff/map-cmdline.lg
where map-cmdline.lg is in the link above. It's just log -G (aka glog) with a more-compact format.
What's going on
When we run:
hg merge dev
Mercurial locates three specific commits:
The current commit on stable, -r3 in this case (the SHA ID will vary), is one of the two endpoint commits.
The target commit on dev is the result of resolving dev to a revision. We can do this ourselves with hg id -r dev for instance:
$ hg id -r dev
f7521e4f0941 (dev)
$ hg id -n -r dev
1
Note that we can do the same thing with # to identify our current revision, although hg summary spills everything out more conveniently.
Last (or in some sense first, though we need the other two to get here), Mercurial locates a merge base commit from these two commits. The merge base is the first commit in the graph that is reachable from both of the other inputs to the merge. In our particular case, that's rev zero, since we split the branches apart right after -r0.
Technically, the merge base is the output of a Lowest Common Ancestor algorithm as run on the Directed Acyclic Graph. See Wikipedia for some examples. There can be more than one LCA; Mercurial picks one at (apparent) random for this case. In our case there is only one LCA though.
Having found the merge base, Mercurial now runs the equivalent of two diff operations:
hg diff -r 0 -r 3
to see what we changed, and:
hg diff -r 0 -r 1
to see what they changed, since the merge base snapshot.1 If we do this ourselves, we see what Mercurial sees:
$ hg diff -r 0 -r 3
$ hg diff -r 0 -r 1
diff --git a/file.txt b/file.txt
--- a/file.txt
+++ b/file.txt
## -1,5 +1,5 ##
This
file
-to
+might
be
merged
(I have my hg diff configured with git = true so that I get diffs that I can feed to Git—long ago I was doing a lot of conversion work here.)
As far as Mercurial is concerned, then, we did nothing on our branch. So it combines do nothing with make this change to file.txt and comes up with this one change to file.txt. That one change is applied to the files from the merge base commit. The resulting files—well, file, singular, in this case—are the ones that are ready to go into the final merge commit, even though they're not the ones you wanted.
Because Mercurial has more information than Git—in particular, which branch something happened on—it would be possible for Mercurial to behave differently from Git here. But in fact, both do the same thing with this kind of operation. They both find a merge base snapshot, compare the snapshot to the two input commit snapshots, and apply the resulting combined changeset to the files from the merge base. Mercurial can do a better job of catching file renames (since it knows them, vs Git, which just has to guess) and could do a different job of merging here, but doesn't.
1Some might object that Mercurial stores changesets, not snapshots. This is true—or rather, sort of true: every once in a while, Mercurial stores a new copy of a file, instead of a change for it. But as long as we have all the commits needed, storing changes vs storing snapshots is pretty much irrelevant. Given two adjacent snapshots, we can find a changeset, and given one snapshot and a changeset to move forward or backward, we can compute a new snapshot. That's how we can extract a snapshot in Mercurial (which stores changesets), or show a changeset in Git (which stores snapshots).
Script: mktest.hg
#! /bin/sh
d=test-hg-graft
test "$1" = replay && rm -rf $d
if test -e $d; then
echo "fatal: $d already exists" 1>&2
exit 1
fi
set -e
mkdir $d
cd $d
hg init
hg branch stable
cat << END > file.txt
This
file
to
be
merged
END
hg add file.txt
hg commit -m initial
hg branch dev
ed file.txt << END
3s/to/might/
w
q
END
hg commit -m 's/to/might/'
hg checkout stable
hg graft -r 1 # pick up s/to/might/; graft makes its own commit
ed file.txt << END
3s/might/to/
w
q
END
hg commit -m 'back to "to" on stable'
hg merge dev
hg commit -m "merge dev into stable"
As noted in the documentation, the hg backout command can cause problems when used with merge changesets. We have had a couple of cases recently of newer developers backing out merge changesets and causing code that we wanted to keep to be reverted when everything gets merged back together.
To avoid this, I'm trying to think of a good way to prevent this from happening at all. Is there a good general way I could write a hook or just disable the backout command entirely?
(Part of our standard developer setup is to install a custom set of extensions, so I already have a good way to install these types of rules locally for our entire development team -- I just haven't thought of a good way to implement the ruleset.)
What about this:
$ hg --version
Mercurial Distributed SCM (version 2.6.3)
...
$ hg log --graph --template='{rev} {desc}'
# 5 c5
|
o 4 merge
|\
| o 3 c4
| |
o | 2 c3
|/
o 1 c2
|
o 0 c1
$ hg backout 4
abort: cannot backout a merge changeset
So it looks like Mercurial does what you want by default. Maybe you need a more recent Mercurial version.
If you are stuck to an old Mercurial version, here is a hacky hook (for *nix/Bash) which aborts backouts of merges:
[hooks]
pre-backout=REV=`echo $HG_PATS | sed -e "s/[^0-9]//g"`; test `hg log -r "parents($REV)" --template='{node}\n' | wc -l` -eq 1 || { echo 'do not do that'; exit 1; }
It extract the revision number from $HG_PATS and then uses hg log and wc to count the number of parents of the revision in question. If there is more than one parent, it is a merge.
Still, I highly recommend to use a recent Mercurial: check the release notes for exciting features you are missing.
Here's a simple Python-based hook that prevents backout of merges. Credit goes to #Oben for pointing me in the right direction.
def prebackout_prevent_backout_merge( ui, repo, **kwargs ):
'''Don't allow backouts to of merge changesets.'''
# Figure out if a --parent version was given or not.
backout_to_parent = kwargs['opts'].get( 'parent', None )
# If no parent version was given, proceed.
if backout_to_parent is '':
return False
# Otherwise abort the operation.
from mercurial import util
raise util.Abort( 'Backout of a merge changeset is not allowed.' )
You can configure this hook in .hgrc or Mercurial.ini with:
[hooks]
pre-backout.ttd_prevent_backout_merge = python:PATH_TO_HOOK_SCRIPT:prebackout_prevent_backout_merge
Is there a Mercurial command you can use after an hg pull to see a list of all files that will be need to be manually merged (ie: that have conflicts) when doing an hg merge?
hg resolve --list
From the documentation:
Merges with unresolved conflicts are often the result of non-interactive merging using the internal:merge configuration setting, or a command-line merge tool like diff3. The resolve command is used to manage the files involved in a merge, after hg merge has been run, and before hg commit is run (i.e. the working directory must have two parents).
Edit 5 January 2012:
(I received an up vote for this answer today so I revisited it. I discovered that I misunderstood the question.)
The question is "I have performed a pull from a remote repository and have not yet performed a merge. Can I see what conflicts will be created upon performing the merge?"
My answer above is clearly wrong. After reading through the linked documentation, I do not think there is a built-in method for doing this. However, there is a way to do it without ruining your working source tree.
Let's assume you have cloned repository A from some remote source to repository B on your local system, i.e. hg clone http://hg.example.com/A B. After doing so, you make changes to your local repository, B, that involve at least one commit. In the meantime, changes have been made to repository A so that when you do a pull you get a message indicated new changesets have been added and heads have been created.
At this point, you can do hg heads to list the two changesets that will be involved in a merge. From this information, you can issue a status command to list the differences between the heads. Assuming the revision numbers in your repository B, according to the heads list, are "1" and "2", then you can do hg status --rev 1:2 to see a list of the changes.
Of course, this doesn't really tell you if conflicts will occur when you do a merge. Since there isn't a command that will show you this, you will have to "preview" the merge by cloning to a new repository and doing the merge there. So, hg clone B C && cd C && hg merge. If you are satisfied with the result of this merge you can do hg com -m 'Merging complete' && hg push && cd ../ && rm -rf C.
It's a bit of a process, but it keeps your current source tree clean if the merge turns out to be a disaster. You might also find this description of working with public repositories helpful.
Unless I'm misreading it myself, the answers above don't seem to address the question that I think is being asked: I have two branches in my repository that I'd like to merge, and I want to know what conflicts will come up (e.g., before stepping through the conflict resolutions one-by-one.)
To do this, I would merge with the :merge3 tool (which tries to merge automatically, but leaves conflicts unresolved) and then use hg resolve --list — or just look at the output of merge command — to see the conflicts.
hg merge <otherbranch> --tool :merge3
hg resolve -l
If you didn't actually want to merge in the end (if you just want to see what would conflict) you can run hg update -C afterwards to undo the merge.
If you do want to finish the merge, you can run hg resolve <filepath> for each file, or just hg resolve --all to step through all that remain with conflicts, before you hg commit the merge changeset.
You can use the --rev option of hg stat with a pair of revisions to see what file differences exist between the two. See below for a slightly verbose but detailed example:
First we start by making a new repository:
[gkeramidas /tmp]$ hg init foo
[gkeramidas /tmp]$ cd foo
Then add a single file called foo.txt to the new repository:
[gkeramidas /tmp/foo]$ echo foo > foo.txt
[gkeramidas /tmp/foo]$ hg commit -Am 'add foo'
adding foo.txt
[gkeramidas /tmp/foo]$ hg glog
# 0[tip] b7ac7bd864b7 2011-01-30 18:11 -0800 gkeramidas
add foo
Now add a second file, called bar.txt as revision 1:
[gkeramidas /tmp/foo]$ echo bar > bar.txt
[gkeramidas /tmp/foo]$ hg commit -Am 'add bar'
adding bar.txt
Go back to revision 0, and add a third file, on a different head. This is done to simulate a pull from someone else who had cloned the same repository at its starting revision:
[gkeramidas /tmp/foo]$ hg up -C 0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
[gkeramidas /tmp/foo]$ echo koko > koko.txt
[gkeramidas /tmp/foo]$ hg commit -Am 'add koko'
adding koko.txt
created new head
[gkeramidas /tmp/foo]$ hg glog
# 2[tip]:0 e5d80abdcb06 2011-01-30 18:12 -0800 gkeramidas
| add koko
|
| o 1 a2d0d0e66ce4 2011-01-30 18:12 -0800 gkeramidas
|/ add bar
|
o 0 b7ac7bd864b7 2011-01-30 18:11 -0800 gkeramidas
add foo
Now you can use hg stat to see what file differences exist between any pair of revisions, e.g. the changes from rev 0 to rev 1 added 'bar.txt' to the file list:
[gkeramidas /tmp/foo]$ hg stat --rev 0:1
A bar.txt
The changes from rev 0 to rev2 added 'koko.txt' to the file list:
[gkeramidas /tmp/foo]$ hg stat --rev 0:2
A koko.txt
But more interestingly, the changes from rev 1 to rev 2 involve two file manifest changes. (1) 'koko.txt' was added in rev 2, and (2) 'bar.txt' exists in rev 1 but is missing from rev 2, so it shows as a 'removed' file:
[gkeramidas /tmp/foo]$ hg stat --rev 1:2
A koko.txt
R bar.txt
I think hg status is what you are looking for.
You may want to read this chapter from Mercurial: The Definitive Guide
http://hgbook.red-bean.com/read/mercurial-in-daily-use.html
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.
(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? :)