Some help with merging legacy branch in Mercurial - mercurial

We're currently working on the new version (version 2.0) of an application.
We have a customer running version 1.0 of the app who found a bug. We updated to the tagged changeset for version 1.0 and located and fixed the bug.
We committed the change which created a new head in our source tree.
The question is how best to merge this? Ideally I would want to merge it into the changeset that followed version 1.0. I don't want to merge it into the tip because the code where the bug was found doesn't actually exist anymore.
I realise I perhaps should have created a separate branch for "v1.0 bug fix".
Thanks,
Ben

When moving a change within a repository using merge it's all about the most recent common ancestor of the place you have the change and the place you want it. If before making this fix your repo looked like this:
[a]-[b]-[c]-[d]
with the 1.0 tagged changeset being [b] then you now have this:
[a]-[b]-[c]-[d]
\
-[e]
where the fix is in [e]. If that's the case then you just need to do this:
hg update d
hg merge e
hg commit
Then you'll have this:
[a]-[b]-[c]-[d]-[f]
\ /
-[e]-----
If on the other hand before making the changes your repo looked like this:
[a]-[b]-[c]-[d]
\
-[e]-[f]
where the 1.0 rag pointed to [f] then you now have this:
[a]-[b]-[c]-[d]
\
-[e]-[f]-[g]
with the fix in [g]. If you want to move the changeset [g] into [d] without bringing [e] and [f] along with it, there's no good way to do it. The not-so-good way available to you (called cherrypicking) is to use the hg export and hg import commands.
No workflow requires cherry picking, but avoiding it requires a little forethought. In that second case you would avoid it by making the fix not on the 1.0 series (as a child of [f]) but instead as a child of the most recent common ancestor of the two places you want that change. Since you want that change in both [d] and [f] you look for their most recent common ancestor and see it's [b] and make the change as a child of that using these commands:
hg update b
..edit..
hg commit
leaving you with this graph:
[a]-[b]-[c]-[d]
\
\--[g]
\
-[e]-[f]
this fix, [g] is a new head, and you can merge it into both [d] (2.0) and [f] (1.0) without any cherry picking at all. The commands for that would be:
hg update d
hg merge g
hg commit
hg update f
hg merge g
hg commit
and the resulting graph would be:
[a]-[b]-[c]-[d]--[h]
\ /
\--[g]----
\ \
-[e]-[f]-[i]
where [h] is your new 2.0 with the fix, and [i] is your new 1.0 with the fix.
Summary: you can always avoid cherry picking with forethought, but it's not the end of the world if you didn't

It sounds like you have this:
[a]-[b]-[v1]-[c]-[d]-[v2]
\
-[fix]
and want to get rid of the extra head without merging any of the changes in [fix].
Option #1
The following commands will simple mark the branch closed, and it won't count as an extra head and be hidden from hg heads and hg branches commands:
hg update fix
hg commit --close-branch -m "closed branch"
Option #2
The following commands will "dummy merge" the extra head, throwing away any changes.
hg update v2
hg --config ui.merge=internal:fail merge
hg revert --all --rev .
hg commit -m "dummy merge"
the --config ui.merge=internal:fail flag prevents a merge tool from trying to merge any conflicts, but doesn't prevent files added to the other head from appearing, since there would be no merge conflicts with a newly added file. The revert will simply update all the files back to the first parent's state. You'll end up with:
[a]-[b]-[v1]-[c]-[d]-[v2]-[merge]
\ /
-[fix]-----------
but the content of [merge] will be the same as [v2].
Option #3
Yet another way to do a dummy merge:
hg update v2
hg debugsetparents v2 fix
hg commit -m "dummy merge"
This sets the working directory to v2, but then "fakes out" Mercurial that both v2 and fix are the parents and commits it as a merge.

Related

Mercurial: How can you have commits that have the same parent and are merged leaving one head?

In the picture below I found Mozilla's mercurial repository graph with a situation that I thought was not possible. They have two commits that have the same parent where one is a merge. I have wanted to do this, but cannot find an answer.
# merge pull request from foo polish text
|\
| o bug 1 - add text ...
|/
o merge pull request from bar bug 2
How did they get this to occur? Usually when you pull changes in that add onto the head, if you attempt to merge you get "nothing to merge".
One method for achieving this is to use Mercurials built-in "debug" commands. They're effectively commands you should never need to use unless you're, you know, debugging Mercurial. Using them could in theory destroy your repository, so work on a clone if you really need to.
$ hg help --debug
...
debugsetparents
manually set the parents of the current working directory
What this command does is allow us to, as you can see, manually set the parents of the working directory - using this we can fake a merge.
So, with this knowledge, and with a degree of caution, we can do:
$ hg log --graph
# 1[tip] Change by A.N.Other
|
o 0 Local Change
$ hg debugsetparents 0 1
$ hg log --graph
# 1[tip] Change by A.N.Other
|
# 0 Local Change
$ hg commit -m "Merged"
$ hg log --graph
# 2[tip] Merged
|\
| o 1 Change by A.N.Other
|/
o 0 Local Change
It's not something that I would normally advise, but it would allow you to illustrate that you're pulling in some changes from "another branch", when that branch is actually at the tip of your changes, which I guess is the aim.
Also, note shambulator's comment below - for this to include the changes in 1, you need to have that as your working directory when performing the debugsetparents, otherwise you'll lose those changes. Unless of course your plan is to discard those changes, in which case you should update to changeset 0, and then fake the merge.

Finding root changeset of tip for pull, graft, strip workflow in Mercurial

If I have a repo that looks like this:
A--B--C--D
\
E--F--G
How can I specify revision E? I can do something like -r 'ancestors(D,G)' to get B, but I want a reasonable way to specify E.
Also, let me explain why I'm doing this in case there is a better workflow.
In the above picture, A--B--C is in a main repository. A--B--E--F--G is in my own private forked repository, and I need to merge F (and only F) into the main repo. To do this, I pull my private repo into main, then graft F (to create D), and then use the hg strip extension to remove E--F--G and then push back up to the main repo.
Currently I do this by opening a gui tool and looking at the picture of the branches and manually copy-pasting the revision E from that tool. I don't like to use GUI tools to actually manage the repo, so I just paste that revision into a terminal for hg strip. This is the only thing I still need to do in the gui tool and I'd rather not have to launch it each time I do this. What I want, is to figure out how to specify the revision and then make an alais that does hg strip on that revision
I think that min(descendants(max(ancestor(D,G))) and not ancestors(D)) will get what you want.
It finds the newest common ancestor of D and G (max(ancestor(D,G))), gets all of its descendants and then excludes the ancestors of D. Finally, it uses min() to get the oldest of that set which is E.
The max() isn't really required in your example but covers the chance that you could have merged the main branch into yours at some point. You could remove that to make the query shorter. You could also shorten it by using :: as a shortcut for ancestors() and descendants() and ! as a shortcut for not which gives a crazy looking min(ancestor(D,G)::) and !::D)
I really wouldn't just pass this revset to hg strip though. I'd make sure that it gives the revision that you want before stripping it.
I'd consider using the Mercurial Queues extension for this workflow. You'd basically keep E, F and G in a patch queue, unapply all of them, pull, update to C, apply F and discard E and G. This would be something like this:
> hg qpop all
> hg pull -u
> hg qpush --move F
> hg qdelete E
> hg qdelete G
Where F, E and G are the names of the patch files that you created using hg qnew. It seems like a nicer workflow to me and doesn't involve deleting commits using complicated revset queries.
I actually found a better solution to my workflow, since I'm working with remote repositories.
I have a remote1 that containts A--B--C and a remote2 that has A--B--E--F--G and I want to graft F onto remote1.
Here'es the workflow I came up with, starting in a local clone of remote1 (using outgoing to solve the root problem)
$ hg pull remote2
$ hg graft F
$ hg push -r tip
$ hg strip -r 'roots(outgoing())'

Can a Mercurial graft cope with renamed files

I want to use hg graft to copy a changeset from one branch to another.
However, a file that is modified in the changeset has been renamed (using hg rename) in the source branch -- before the changeset that I want to graft.
When I try I get:
scanning for duplicate grafts
grafting revision 3
searching for copies back to rev 2
unmatched files in local:
f1.txt
resolving manifests
overwrite: False, partial: False
ancestor: c06936303423, local: cfeaa3003c57+, remote: e3f2a201d1e2
remote changed f1a.txt which local deleted
use (c)hanged version or leave (d)eleted? c
Neither (c) nor (d) seem like the right option.
The file f1a.txt was renamed from f1.txt in the source branch. f1a.txt never even existed in the target branch.
Is this possible?
This is an excellent question. It looks like it is not possible. I replayed your scenario and also tried transplant and rebase -- with the same effect.
I guess the basic reason why this does not work is because a graft explicitly only picks the changes of one revision, i.e. past changes (including file renames) explicitly are not considered (update: could be a bug or missing feature as well, see #Soulman's answer).
You did not ask for, but here's a work around ..
Get a patch of the changeset you want to graft:
$ hg export --git -r <rev-to-graft> > my.patch
In this patch, replace f1a.txt with f1.txt, so you get:
...
diff --git a/f1.txt b/f1.txt
--- a/f1.txt
+++ b/f1.txt
## -1,1 +1,1 ##
...
Sed is your friend here: sed -i my.patch -e "s,\([ab]\)/f1a\.txt,\1/f1.txt,g"
Import the edited patch to the target revision:
$ hg up <target-rev>
$ hg import my.patch
I think this is simply a bug that will hopefully be fixed. There is a ticket: Graft fails when files have been moved in an ancestor revision.
A rename is a delete + add + a note saying to treat history of the old file as the history of the new file. When doing a graft (that is making a new changeset by duplicating the same changes without creating a relation as opposed to a merge), you have to incorporate the changes of the changeset some how. You can only graft an "add" operation in this case or decide not to add the file (leave it deleted). I am sorry to say, that it is fundamentally impossible to graft a rename in this case, because it is meaningless if the other branch did not have that file.
I am sorry, if I didn't understand correctly the situation you are in. If that is the case, then please provide an example. (Few commands to set up a dummy repo, which reproduces your case will do the trick).

hg command to see the changeset prior to an hg fetch

What mercurial command can you use to see the changeset prior to changeset xyz?
If you do hg log -r :xyz you see all the changesets prior to (and including) xyz - listed in ascending order. But I'd like to easily see just the prior changeset.
Update: Really what I'm after is this: If I do an hg fetch, what command can I use to see the changeset PRIOR to the the changesets that were pulled in by the fetch?
hg log -r :xyz where xyz is the first changeset pulled in by the fetch works but it returns the entire list of prior changesets where I just want the most recent.
You can't do it. Once you've pulled (and fetch is just pull + either update or merge) there is no record of what you had before the pull and what you just got from the pull.
You should do hg incoming before you pull to see what you will get.
Also, stop using fetch. The acts of pulling and updating and merging are completely separate and doing them in a single commands provides inadequate error reporting and just confuses things. The command is disabled by default and there's talk of removing it entirely. Merging is coding, and it shouldn't be hidden.
Expanding to Show cases you can't cover
If before fetching your history is this:
[A]-[B]-[C]
and you (against all advice) fetch and get [D] you now have:
[A]-[B]-[C]-[D]
And you can see exactly what's new with:
hg diff -r tip-1
or with:
hg diff -r "parent(tip)"
But if starting again with A,B,C you fetch and get D,E yielding this:
[A]-[B]-[C]-[D]-[E]
there is no command you can run to see "what changed" without having previously written [C] down on a post it note.
If, on the other hand your repo started out looking like this:
[A]-[B]
\
-[C]
and after fetching you have this:
[A]-[B]-[D]
\
-[C]-[E]
there's no single command that will tell you "what changed". Similarly, if before pulling your repo looked like this:
[A]-[B]-[C]
and after fetching you got this:
[A]-[B]-[C]-[E]-[F]
\ /
-[D]-------/
where [F] is the new ill-advised auto-merge changeset fetch created then the command:
hg diff -r C
will tell you what's new, but there's no way to look up 'C' without having previously written it down.
I assume xyz is a changeset in hash form. Try:
hg log -r :xyz-1
That should work to list just the changeset prior to 1
I'm not sure what is meant by prior. Your question could mean you might just want the parents of the changeset. These you would get easily with
hg parent -r xyz
This does not need fancy new versions of mercurial.
hg parents [-r REV] [FILE]
show the parents of the working directory or revision
Print the working directory's parent revisions. If a revision is given via
-r/--rev, the parent of that revision will be printed. If a file argument
is given, the revision in which the file was last changed (before the
working directory revision or the argument to --rev if given) is printed.
options:
-r --rev show parents of the specified revision
--style display using template map file
--template display with template
--mq operate on patch repository
With the parentrevspec[1] extension installed, you can use the git-like syntax below. Depending on your shell, the quotes may not be necessary.
hg log -r 'xyz^'
https://www.mercurial-scm.org/wiki/ParentrevspecExtension

How to get the tag changeset after you clone or pull to a tag using mercurial?

As the definite guide aptly points out (search for "Tags and cloning"):
When you run hg clone -r foo to clone a repository as of tag foo, the new
clone will not contain any revision newer than the one the tag refers to,
including the revision where the tag was created. The result is that you'll
get exactly the right subset of the project's history in the new
repository, but not the tag you might have expected.
It means hg tags in your new clone does NOT show the foo tag. Same thing happens if you had cloned before foo tag was added, and you do hg pull -r foo.
(Digression: tag is about the only thing I don't quite get in hg. I understand there are advantages (e.g. merge) in putting it in a changeset, but it always feels weird to have meta data mixed with source code.)
It should be obvious that I'm asking for an automated way, instead of pulling the tag changeset as a separate manual step.
I know I could check for this scenario in an incoming hook (so it works for both clone and pull), or wrap clone and pull.
But is there a better/easier way?
UPDATE hg bug tracker already has this issue.
You want a giant hack with bash and an embedded Perl script? Well, here it is...
#!/bin/bash
if [[ "$1" == "" || "$2" == "" || "$3" == "" ]]; then
echo 'hgclonetag <src> <tgt> <tag>'
exit 1;
fi
REV=`hg log -R $1 --rev $3: --limit=2 | perl -F: -lane 'if (/:([\dA-Fa-f]+)$/) {print $F[2] if defined($flag);$flag=1;}'`
hg clone --rev $REV $1 $2
This invokes the hg log command to extract the revision number after the first tag-related revision and then clones to this revision.
Currently this does not work on remote repos: -R switch only works on local repos unfortunately.
The more I think about it the more I'm convinced the right answer is to just clone everything and update to the tag, which can be done in a single step:
hg clone http://host/path#tagname
That gets you everything and then does hg update to tagname which sets your working directory to the correct revision. Given delta compression that's not necessarily much larger, and if it is you can automate cloning the bulk of it from a previous local clone.
There is a postclone hook. It's called post-clone (the hgrc manpage shows a post-ANYCOMMAND and pre-ANYCOMMAND exist) though as you pointed out you could also use *changegroup or update hooks too, since clone uses both of those functions (unless you suppress update with -U).
What about just adding a --localtag so you have the name but not the extra changeset if you need it for reference only. Something like
hg clone -r tagname URL
hg tag --local tagname
which you could easily build into a shell alias.
Other than that there's not necessarily guaranteed to be a way to have revision X and the revision where revision X is tagged without also having other revisions you don't want since the tag could have been applied after other work was done. You can, of course, always update to 'X' and to have subsequent changesets in you working dir, but they'll still be in your repo.
Honestly, once I figured out that the tag name doesn't come a long when you clone up to a tag, which I admit confused the heck out of me at first, I didn't find any need to bring along the changeset with the tag in it.
Yes it can be done by post-clone/pull hooks, but there are a couple of crooks.
First, it only works for local repo, since you can't get the list of tags in a remote repo.
Second, dealing with clone/pull arguments and options is not trivial. (For clone I need to get the target repo, -r, -u, -U. For pull I need -r and -u.) I tried to use fancyopts, but it can't deal with global options, which are processed away in dispatch. I managed to hack dispatch to give me only the args and opts of a command, but it feels and looks ugly.
Using command wrapper would eliminate the second problem.
I hope one day hg will add an option to clone and pull to do it cleanly.