Mercurial: How to merge with manual control over changes & files? - mercurial

Note: The scenario I describe here is not answered in Stack Overflow: Completely manual Mercurial merge.
I will explain my query with an example. Assume I start off a Mercurial repository to design a car:
C:\Car$ dir
Car.cpp
Car.h
I work on the design of Car for quite a while and the repository looks like:
r0-r1-...-r100-(default)
At some point in time I branch default to SolarCarBranch to work on a solar powered car in parallel:
C:\SolarCar$ dir
Car.cpp
Car.h
Solar.cpp
Solar.h
After some more time, the repository looks like:
r0-r1-...-r100-...-r200-(default)
\--r101-...-r201-(SolarCarBranch)
How do I merge SolarCarBranch back to default?
Take note of the following complications in the merge I want:
I should be able to continue work on both default and SolarCarBranch after the merge.
There might be fuel efficiency fixes in Car.cpp and Car.h in SolarCarBranch that I want pulled into default, however I do not want all the changes in those files. So, I want to cherry pick the changes I want to be included in default during the merge (aka manual merge).
I do not want Solar.cpp and Solar.h appearing in default. The world may not yet be ready for a solar powered car. ;-)
What I have learned:
This is possible by a hg merge SolarCarBranch
This can be achieved by setting kdiff3.premerge=False in Mercurial.ini
I do not know how to achieve this since premerge=False still merges/copies Solar.cpp and Solar.h into default without asking me for permission.

You're really close and you don't need to resort to Transplant (yuck) or cherry picking in general. Turning off premerge is the first step, and then just remove the files you don't want in the main branch after merging but before committing. You'll only have to do it once.
Here's a setup:
o changeset: 3:343d531512a3
| branch: solar
| tag: tip
| parent: 1:cb26642f8db5
| user: Ry4an Brase <ry4an#msi.umn.edu>
| date: Wed Mar 10 11:16:48 2010 -0600
| files: afile
| description:
| solar-change
|
|
| # changeset: 2:c5d14e34db07
| | parent: 0:56465175b2fc
| | user: Ry4an Brase <ry4an#msi.umn.edu>
| | date: Wed Mar 10 11:05:44 2010 -0600
| | files: other-main-file
| | description:
| | moremain
| |
| |
o | changeset: 1:cb26642f8db5
|/ branch: solar
| user: Ry4an Brase <ry4an#msi.umn.edu>
| date: Wed Mar 10 11:04:32 2010 -0600
| files: solar-only
| description:
| solar-initial
|
|
o changeset: 0:56465175b2fc
user: Ry4an Brase <ry4an#msi.umn.edu>
date: Wed Mar 10 11:04:14 2010 -0600
files: afile
description:
initial
You can see that changeset 1 adds a file to the solar branch -- a file we don't want in default. While changeset 3 tweaks a file that also exists in main, afile, and we want to manually control whether or not that change happens.
So do hg update default ; hg merge -r solar. The merge tool will pop up for afile and we decide line-by-line or chunk-by-chunk if we want those changes. After saving do a hg stat:
% hg stat
M afile
M solar-only
And we see that solar-only is queued up for commiting in default. Just remove it (with if).
% hg rm -f solar-only
Now hg stat shows it as removed:
% hg stat
M afile
R solar-only
and when we commit we'll have what we want in the new changeset.

Your first condition, to be able to continue work on both branches, is satisfied by simply not closing the branch (which you do with the --close-branch option to hg commit).
You can cherry pick changesets to merge using the Transplant extension. This will let you choose only specific changesets to merge into default.
The third condition is satisfied by the the idea that provided you don't mix changes to both your regular car and your solar powered car in the same commit, you can pull changesets back from the solar branch with impunity, and not have to worry about mixing your car types.
You might want to look at the internal option flags in the merge configuration docs too.

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.

Generating merge logs with mercurial

We're using Mercurial for source control and would like to introduce automatic merging and changelog for our QA process.
For the purpose of this discussion, let's just assume that we use a simple 3-branch flow like the below:
\ = forward merge
| = back merge of fix
Default --O--A--*--B--*---
\ ^ \
\ | \
QA -----M--X--C--*--N---
^ \
| \
Master --------H--------*---
So the question is:
Is it possible to construct a revset query that, at merge N, preferably before but otherwise after actually performing it, will result in a log of all commits on Default branch, which aren't yet in QA branch (On the above drawing that would be A and B).
Alternatively formulation:
Is it possible to construct a revset query that will return all the changesets, which would get merged if we merged Default into QA.
I'm only interested in commits on Default branch, not individual commits in feature branches already merged into Default (Feature branches not included on the drawing). I am also not interested in commits on the QA branch.
The repository is pretty well maintained, but a couple of years old and contains about 13000 nodes.
I've been experimenting with ancestor and ancestors, but either I get X as the latest common ancestor, or some very very old node. I understand the logic of getting X, but I'm really after O.
The most luck I've had is with variations of this:
hg log --rev "((heads(branch('Default')) and !closed()) % heads(branch('QA'))) and branch('Default') and p2(!branch('QA'))"
hg log --rev "((heads(branch('Default')) and !closed()) % heads(branch('QA'))) and branch('Default') and !children(branch('QA'))"
Update
Using "hg merge --preview --rev XYZ", as suggested by Tom, generates output like this:
changeset: 13070:7e59fc16aa4e
branch: Default
parent: 13068:5b9409ad504f
parent: 13069:849bd43d2023
user: *******
date: Mon Dec 18 18:40:46 2017 +0200
summary: Merged Feature branch A
changeset: 13071:07470ff919ff
branch: Feature branch B
parent: 13061:540eda2c959b
parent: 13068:5b9409ad504f
user: *****
date: Mon Dec 18 18:49:42 2017 +0200
summary: Merge with Default
changeset: 13072:a53260ffabca
branch: Feature branch B
user: *****
date: Mon Dec 18 18:58:05 2017 +0200
summary: Some text
changeset: 13073:37c895f2abf0
branch: Default
parent: 13070:7e59fc16aa4e
parent: 13072:a53260ffabca
user: *******
date: Mon Dec 18 18:58:05 2017 +0200
summary: Merged Feature branch B
Not too bad, but not super fun either. At least it requires more logic than simple grep/sls to parse.
Thanks in advance,
John
I found a solution that mostly does what I want:
hg log --rev "branch('Default') & !ancestors(branch('QA')) & ancestors(heads(branch('Default')) & !closed()) & !children(branch('QA'))"
The only thing now missing is to exclude multiple head merges, since they're just noise in a changelog.
Update:
So I picked this project up again and found some great answers elsewhere on SO.
The following produces output similar to hg merge --preview, but can be templated:
hg log -r "ancestors('$sourceBranch') - ancestors(.)" --template $template
This filters out all commits not on source branch:
hg log -r "(ancestors('$sourceBranch') - ancestors(.)) and (branch('$sourceBranch'))" --template $template

Mercurial built-in template style description

I am trying to tune the output of hg log by defining my own style. In fact, just adding information to the default style would be perfect. Is there a place where I can get the description of the default styles (default, compact, ...) to write my own?
After some research, I finally found the location of the default styles for hg log, located on my ubuntu 14.04 at
/usr/share/mercurial/templates/
You can find there the following files:
map-cmdline.bisect
map-cmdline.changelog
map-cmdline.compact
map-cmdline.default
map-cmdline.phases
map-cmdline.xml
They do not contain any color scheme, but they are allowed me to build my 'patched' version of default.
From other comments elsewhere you can find out where the styles location is by:
hg debuginstall --config ui.editor=cat
Or using a bug if you choose a directory under the template it complains and tells you where they are (probably should for non matching styles), well it does for 1.9.2:
hg in --style paper
If you want to colour the output you could use the color extension now.
add to .hgrc:
[extensions]
color=
If you know how to use the various keywords that Mercurial templating feature understands (see hg help templating for a very complete usage doc), then you can either:
specify your choices by giving a template on the command line, e.g.,
$ hg tip --template "{rev} at {date|isodate}"\n
882 at 2014-12-17 08:50 +0100
or edit an ascii file with the same format and use it with the style option, e.g.,
$ cat ~/mystyle.txt
changeset = "{rev} at {date|isodate}\n"
$ hg tip --style ~/mystyle.txt
882 at 2014-12-17 08:50 +0100
It's strange, but the default templates are hardcoded in mercurial code.
The file map-cmdline.default is never read at runtime, but can be used to exactly replicate mercurial output (and thus to extend it).
Example of how to extend hg log output to include the git commit hash of a git repository cloned via hg-git.
Look for the location of map-cmdline.default:
$ hg debuginstall --config ui.editor=cat | grep "default template"
checking default template (/usr/lib/python2.7/dist-packages/mercurial/templates/map-cmdline.default)
Edit /etc/mercurial/hgrc, pasting a slightly modified version of map-cmdline.default:
...first part of your hgrc...
[templates]
# support "git-commit" line in hg log
changeset_git = '{cset}{cset_git}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{summary}\n'
cset_git = '{if(gitnode, "git-commit: {label("status.unknown", gitnode|short)}\n")}'
...the rest of 'templates' section of map-cmdline.default...
[templatealias]
...the whole 'templatealias' section of map-cmdline.default...
Example output:
~/git$ hg log --graph
# changeset: 54881:d420062ce3d6
|\ git-commit: 6326d2e53311 <-- new line, only appears in git clones
| | tag: default/pu
| | tag: tip
| | parent: 54879:31a4c8ccc461
| | parent: 54880:691b9814f119
| | user: Junio C Hamano <gitster#pobox.com>
| | date: Sun Mar 25 10:52:08 2018 -0700
| | summary: Merge branch 'jh/partial-clone' into pu

Mercurial undo merge that has more commits following it?

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.

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? :)