How do I cherry-pick a single revision in Mercurial? - mercurial

In Mercurial/TortoiseHg, given the following example, what is the easiest way to merge revision "G" into repo A without taking D,E and F (Assume that G has no dependency on D,E or F).
Repo A: A - B - C
Repo B (Clone of A) A - B - C - D - E - F - G
Is a patch the best bet?

Tonfa is right. What you're describing isn't 'merging' (or 'pushing' or 'pulling'); it's 'cherry-picking'. A push or a pull moves all the changesets from one repo to another that aren't already in that repo. A 'merge' takes two 'heads' and merges them down to a new changeset that's the combination of both.
If you really need to move G over but can't possibly abide having D,E,F there you should 'hg export' G from repo A, and then 'hg import' it in repo A. The Transplant extension is a wrapper around export/import with some niceties to help avoid moving the same changeset over multiple times.
However, the drawback to using import/export, transplant, and cherry-picking in general is that you can't really move over G without its ancestors, because in Mercurial a changeset's name is its 'hashid' which includes the hashids of its parents. Different parents (G's new parent would be C and not F) means a different hashid, so it's not G anymore -- it's the work of G but a new changeset by name.
Moving over G as something new, let's call it G' (Gee prime), isn't a big deal for some uses, but for others it's a big pita. When soon repo B get's a new changeset, H, and you want to move it over its parent will be changing from G to G', which have different hashes. That means H will move over as H' -- 100 changesets down the line and you'll have different hashids for everything all because you couldn't stand having D,E,F in repo A.
Things will get even more out of whack if/when you want to move stuff from Repo A into Repo B (the opposite direction of your earlier move). If you try to do a simple 'hg push' from A to B you'll get G' (and H' and by subsequent descendants) which will be duplicates of the changesets you already have in Repo B.
What then, are your options?
Don't care. Your data is still there you just end up with the same changesets with different names and more work on future exchanges between the two repos. It's not wrong, it's just a little clumsy maybe, and some folks don't care.
Move all of D,E, and F over to Repo A. You can move all the changesets over if they're harmless and avoid all the hassle. If they're not so harmless you can move them over and then do a 'hg backout' to undo the effects of D,E and F in a new changeset H.
Give G better parentage to begin with. It's mean for me to mention this because it's too late to go this route (without editing history). What you should have done before working on changeset G was to hg update C. If G doesn't rely on or require changesets D,E, and F then it shouldn't be their kid.
If instead you update to C first you'll have a graph like this:
A - B - C - D - E - F
\
G
then, the whole answer to this question would just be hg push -r G ../repoA and G would move over cleanly, keeping its same hashid, and D, E, and F wouldn't go with it.
UPDATE:
As pointed out in the comments. With modern Mercurials the hg graft command is the perfect way to do this.

Refering to the title, which addresses cherry picking in general, I give the example of working in one repo, as internet search engines might bring people here for cherry picking in general. Working in one repository, it would be done with hg graft:
hg update C
hg graft G
The result is:
G'
/
A - B - C - D - E - F - G
Extra warning: The two changesets will be treated as independent, parallel commits on the same files and might make you run into merge conflicts, which is why cherry picking should be avoided in general for branch management. For example, if G is a bug fix applied to a stable version branch bookmarked as 1.0.1, you should rather merge the freeze branch with it, and from time to time merge the master branch with the freeze branch's bugfixes.

Here's another approach:
hg import =(hg diff -c 7b44cc577701f956f12b029ad54d32fdce0a002d services/webpack/package.json)
This creates a diff for the changeset you want to patch in, then saves it to a temporary file and imports it. The filename(s) are optional.
<(...) also seems to work if you're not using zsh (creates a named pipe instead). Or you can manually save the patch to a file:
hg diff -c xxx > mypatchfile

Related

How to copy working directory to a new commit

I have the following commit structure:
a->b->c
Now c is an experiment, and it diverges significantly from b (to the point where any changes to b cause conflicts in c). I'd like to keep c, but want to stop maintaing the chain. Because there are so many conflicts between b and c, simple rebasing would be a bunch of work.
So I want to make
a->b
\->c'
where c' is simply a copy of the workspace in c. In git I would use git reset --soft a and just make a new commit for my c', is there something equivalent I can do in mercurial?
Sounds like you want to use revert --all
pseudo commands (letters a,c mean revids of commits ):
update -r a
revert --all -r c
commit -m "new c"
strip -r c
a guide to using revert all using thg GUI can be found here
So I'm going to post what I ended up doing, but hope I get a better answer.
Make a "do nothing" commit on a (in my case, I added a line). We need the fake because rebasing onto a from b will stop saying nothing to rebase.
a->b->c
\->f
hg rebase -s b -d f --keep clones the whole branch:
a->b->c
\->f->b'->c'
hg histedit, fold c' and drop f.
a->b->c
\->c''
hg prune c
a->b
\->c''

'Forgetting' a dead-end branch

I've got a mercurial repository. It was on rev A. I made some changes, committed (to rev B), and pushed. However, later, I realised I didn't want to make those changes. I updated back to rev A, and made some alternative changes, to rev C.
C
| -
| B
|/
A
However, now I can't push rev C, because it complains that it would create a new remote head (which it would). How do I make the remote mercurial simply forget about rev B and all the changes therein, so I can push rev C and carry on from there?
Editing History is hard. Once you push a changeset to a public repository, it can no longer be easily pruned from the history.
The most direct solution to your problem is:
hg update <tip of branch you want to forget>
hg commit --close-branch -m "close unwanted branch"
hg update <tip of branch you want to keep>
Push all your changes. As you noted, you will need to use --force since multiple heads on the branch now exist.
If you really need to prune the branch, then read EditingHistory again. If it still seems feasible, you can use one of the methods described in PruningDeadBranches.
Personally I'd close the branch and force the push (as Tim Henigan describes), as it leaves the DAG in a state which is truthful. There is another option though. Doing a dummy merge. This is a merge, but one where you ignore the incoming changes.
hg update C
hg -y merge --tool=internal:fail B
hg revert --all --rev .
hg resolve -a -m
hg ci
The end result is
M
|\
C |
| |
| B
|/
A
... but M doesn't contain any of B's changes.
Use the backout command to reverse the B change.
hg up B
hg backout B
Now, you're working directory is in the same state it was before B, which I'll call A'.
After you backout, make your real change and commit. This is what your history will look like:
C
|
A'
|
B
|
A
Mercurial branches are permanent and registered in commit objects themselves. There are some (not too easy) methods for closing and/or removing branches, mainly listed here. I have even already used some of them before. Note that those are somewhat desperate solutions for people who did use branches thinking they can be temporary.
However, if you really want temporary branches, use bookmarks.
Other people have some good answers for getting rid of 'B', but just to put it out there you can always do:
hg push --rev C
which will push C but not B and not complain about new heads.

Mercurial commit disappeared

We have switched to Mercurial recently. All had been going well until we had two incidents of committed changes going missing. Examining the logs has not made us any wiser.
Below is an example. The files committed at (1) revert to a previous state at (2) even though those files are not mentioned in the merge.
What can I check to understand why the files reverted?
There are three interesting changesets in this graph that can influence the (2) merge:
Teal changeset: not shown, but looks like it's just below the graph. This is the first parent of (2)
Blue changeset: number five from the bottom, labelled "Fix test". This is the second parent of (2).
Common ancestor of the parents: also not shown, will be further below. Strangely, it looks like the teal changeset could be the common ancestor, but Mercurial will now allow you to make such a degenerate merge under normal circumstances.
When Mercurial does a merge, these are the only three changesets that matter: the two heads you merge and their common ancestor. In a three-way merge the logic is now:
ancestor parent1 parent2 => merge
X X Y Y (clean)
X Y X Y (clean)
X Y Y Y (clean)
X Y Z W (conflict)
Read the table like this: "if the ancestor was X, and the first parent was also X and the second parent was Y, then the merge will contain Y". In other words: a three-way merge favors change and will let a modification win.
You can find the ancestor with
$ hg log -r "ancestor(p1(changeset-2), p2(changeset-2))"
where changeset-2 is the one marked with (2) above. When you say
The files committed at (1) revert to a previous state at (2) even though those files are not mentioned in the merge.
then it's important to understand that "a merge" is just a snapshot that shows how to mix two other changesets. The change made "in" a merge is the difference between this snapshot and its two parent changesets:
$ hg status --rev "p1(changeset-2):changeset-2"
$ hg status --rev "p2(changeset-2):changeset-2"
This shows how the merge changeset is different from its first and second parent, respectively. I'm sure the files are mentioned in one of those lists — unless the merge isn't the culprit after all.
When you examine the three changesets and the differences between them, then you will probably see that someone has to resolve a conflict (the fourth line in the merge table above) and picked the wrong file at some step along the way.
The merge at 2 is between a very old branch (dark blue, forked from the mainline/green branch just after commit 1) and an even older branch (light blue, hasn't been in sync with mainline since before commit 1)
It seems likely that the merge at 2 picked the wrong version of the file - can't tell from here if that was the tool picking the wrong version of the file, or the user manually selecting the wrong version.
Edited to add:
To help track down exactly what changed at 2, you can use hg diff -r REV1 -r REV2, which will show you the line-by-line differences between any two revisions.
When you know that the badness was introduced sometime between point 1 and point 2, hg bisect may help you track down the exact source of the badness:
hg bisect [-gbsr] [-U] [-c CMD] [REV]
subdivision search of changesets
This command helps to find changesets which introduce problems. To use,
mark the earliest changeset you know exhibits the problem as bad, then mark
the latest changeset which is free from the problem as good.
Bisect will update your working directory to a revision for testing
(unless the -U/--noupdate option is specified). Once you have
performed tests, mark the working directory as good or bad, and bisect
will either update to another candidate changeset or announce that it
has found the bad revision.

Mercurial: Picking commits to be included in a release

I have a local mercurial repository (for now) within which I have already made several commits, each commit is a self contained bug fix. Is it possible to pick which of the bug fixes (commits) I want to be included when it is time to build a release version of my application.
To elaborate, assuming A, B, C, D, and E are commits I have already done to my repository and each of them relates to a bug fix like so:
A <- B <- C <- D <- E <- working dir
I need to be able to for example pick which of the bug fixes will go into the release version (this depends on the time allocated for deployment as well as testing outcomes). So for example I might get a report saying the release should only contain bug fixes A, C and D.
Is it possible to construct a release version containing only the A, C and D commits (Keeping in mind that each commit is self contained and does not depend on the other commits to actually be there)?
Probably having a branch for each bug fix and then merging into a release branch is the easiest way to accomplish this (or is it not?), but the current situation at hand is as described above with no branches.
This isn't the normal work mode of Mercurial (or git). A repository can only contain a changeset if it also contains all of that changeset's ancestors. So you can't get D into a repo without also having A, B, and C in there.
So here's:
What you Should have Done
Control the parentage of your changesets. Don't make C the parent of D just because you happen to have fixed D after C. Before you fix a bug hg update to the previous release.
Imagine A was a release and B, C, and D, were all bug fixes. If you do a loop like this:
foreach bug you have:
hg update A
... fix bug ...
hg commit
hg merge # merges with the "other" head
then you'll end up with a graph like this:
---[A]----[B2]--[C2]--[D2]----
| / / /
+-[B] / /
| / /
+-----[C] /
| /
+---------[D]
and now if you want to create a release with only, say, B and D in it you can do:
hg update B
hg merge D
and that creates a new head that has A + B + D but no C.
Tl;Dr: make a change's parent be as early in history as you can, not whatever happens to be tip at the time.
What you can do Now
That's the ideal, but fortunately it's no big thing. You can never bring exactly D across without bringing C (because C's hash is part of the calculation of D's hash), but you can bring the work that's in D into a new head easily enough. Here are some ways, any of which will work:
hg export / hg import
hg transplant
hg graft (new in 2.0)
hg rebase (only possible if you haven't yet pushed)
Any of those will let you bring that patch/delta that's in D over -- it will have a different hash ID and when some day you merge D in for real (using merge) you'll have duplicate work in two different changesets, but merge will figure it all out.
If this was my tree and it hasn't been pushed anywhere, I'd (assuming an empty patch queue and MQ enabled):
hg qimport -g -r B: # import revisions B and later into mq as "git" style patches
hg qpop -a # unapply them all
hg qpush --move C # Apply changes in C (--move rearranges the order)
hg qpush --move D # Apply changes in D
hg qfin -a # Convert C & D back to changesets
hg push <release server> # Push them out to the release branch
Then you can hg qpush -a; hg qfin -a to get B & E back into changesets.
Final Result:
---A---C---D---B---E
Advantages:
Nobody needs know you didn't do things in this order to start with (evil grin)
You could modify any of the change-sets whilst doing this
Alternatively, with graft in 2.0:
hg update -r A # Goto rev A (no need to do anything special for A)
hg graft C # Graft C on to a new anonymous branch
hg graft D # Graft D
This will give you
---A---B---C---D---E
\
--C'--D' <-You are here
An hg push -r D' should just push the new, cherry-picked, head.
You can then hg merge to get one head again with B and E included.
Advantages:
Non destructive, so true history is kept, and no chance of loss if you muck up
hg tags the new changesets with the hash of the original version, so totally trackable
Probably a little simpler.
While it's somehow strange way and release-policy, you can do it in different form. You have to manipulate with two main objects: changesets and branches
Version 1
You use two branches (default + f.e "release 1.0"). Default branch is mainline of your work - all changesets commited to this branch. At release-time, you branch first needed-for-release changeset into (new) branch, transplant or graft rest of needed in release changesets from default to this branch, head of release 1.0 will be prepared for release this way.
Next release will differ only in new branch name
Version 2
One branch used, MQ extension added. Variations:
all changesets are MQ-pathes and only needed for release are applied to repo
changesets are changesets, only unwanted for release converted to mq-pathes, later qfinish'ed and returned to repo

hg convert from svn, need to pull branches from more than one place

I have a few odd issues with a svn repository I'm trying to convert to hg. Our repository looks more or less like this.
proj_root ___ trunk
\ \___ tags
\_____ branches __A
\ \__B
\_____Q __ C
\___ D
Ideally I could specify --config convert.svn.branches=branches;branches/Q or somesuch nonesense. (Q does not contain a branch in itself, just the two "sub branches".
I just finished converting a repository in which Q only had one subdirectory, and the solution there was easy. rename C/ . in a --filemap option, but that fails rather dramatically for this case (the Q branch interleaves C and D).
Ideally, I could simply ignore Q entirely in one pass, and then do a second conversion with the convert.svn.branches option set to branches/Q, but I cannot get the following syntax in the filemap to work:
exclude "Q C D"
nor
exclude "C D"
My hope was that excluding C and D would prevent any files from being imported on that branch, and since I have a specified filemap, the resulting empty commits would be removed. I can't get the exclude directive to exclude any directories, no matter the various syntax options I've tried.
Edit: The end result I'd like to have is a mercurial repo with trunk as the normal tip, and named branches A, B, C and D... that silly Q thing can just go away during the conversion, if I can figure out how.
For some reason when I tried this yesterday, it wasn't working, but today it works...
a filemap of
exclude "C"
exclude "D"
seems to be working properly now... I'm having an odd issue with the first conversion I'm doing crashing, but that's an issue to take up with the devs (unrelated)