Mercurial: how can I see only the changes introduced by a merge? - mercurial

I'm trying to get in the habit of doing code reviews, but merges have been making the process difficult because I don't know how to ask Mercurial to "show only changes introduced by the merge which were not present in either of its parents."
Or, slightly more formally (thanks to Steve Losh):
Show me every hunk in the merge that wasn't present in either of its parents, and show me every hunk present in either of its parents that isn't also present in 3.
For example, assume I have a repository with two files, a and b. If "a" is changed in revision 1, "b" is changed in revision 2 (which is on a separate branch) and these two changes are merged in revision 3, I'll get a history which looks like this:
# changeset: 3
|\ summary: Merged.
| |
| o changeset: 2
| | summary: Changing b
| |
o | changeset: 1
|/ summary: Changing a
|
o changeset: 0
summary: Adding a and b
But if I ask to see the changes introduced by revision 3, hg di -c 3, Mercurial will show me the same thing as if I asked to see the changes introduced in revision 1, hg di -c 1:
$ hg di -c 3
--- a/a
+++ b/a
## -1,1 +1,1 ##
-a
+Change to a
$ hg di -c 1
--- a/a
+++ b/a
## -1,1 +1,1 ##
-a
+Change to a
But, obviously, this isn't very helpful - instead, I would like to be told that no new changes were introduced by revision 3 (or, if there was a conflict during the merge, I would like to see only the resolution to that conflict). Something like:
$ hg di -c 3
$
So, how can I do this?
ps: I know that I can reduce the number of merges in my repository using rebase… But that's not my problem - my problem is figuring out what was changed with a merge.

The short answer: you can't do this with any stock Mercurial command.
Running hg diff -c 3 will show you the changes between 3 and its first parent -- i.e. the changeset you were at when you ran hg merge.
This makes sense when you think of branches as more than just simple changesets. When you run hg up 1 && hg merge 2 you're telling Mercurial: "Merge changeset 2 into changeset 1".
It's more obvious if you're using named branches. Say changeset 2 in your example was on a named branch called rewrite-ui. When you run hg update 1 && hg merge rewrite-ui you're effectively saying: "Merge all the changes in the rewrite-ui branch into the current branch." When you later run hg diff -c on this changeset it's showing you everything that was introduced to the default branch (or whatever branch 1 happens to be on) by the merge, which makes sense.
From your question, though, it looks like you're looking for a way to say:
Show me every hunk in this changeset that wasn't present in either of its parents, and show me every hunk present in either of its parents that isn't also present in 3.
This isn't a simple thing to calculate (I'm not even sure I got the description right just now). I can definitely see how it would be useful, though, so if you can define it unambiguously you might convince one of us Mercurial contributors that read SO to implement it.

In order to do code reviews you really want to see just the changes in the project that you are reviewing. To that end we use a new branch for each story and use pull requests to spotlight the changes, having merged all changes into the story branch before creating the pull request. We host our code on bitbucket and the review / pull request tools are really very good, offering a side by side diff.
Pull requests with side-by-side diffs

Related

How do I merge two heads on Mercurial?

Please, help me! I'm not familiar with Mercurial.
I have a message in my bitbucket telling me that my "default branch has multiple [two] heads", which is not what I want. The branches are both labeled "Default." I've tried hg merge and I get "abort: nothing to merge."
When I do hg heads, only one gets listed.
changeset: 4:fb6f0d961015
tag: tip
user: Name <my_email>
date: Fri Feb 07 03:39:23 2014 -0800
summary: Folder
Have you pulled changes from the server? It sounds like you have one head locally, but when your changes are combined with what is on the server it produces two heads...
If your changes 'C' are based on change 'B', but someone else has also made change 'D' based on 'B' ...
C D
| |
B /
|
A
You see A-B-C on your machine (only one head), but when it is pushed to the server it would create two.
If that is the case, you need to pull the latest changes from the server and then do the merge.
If you just performed a hg pull you probably just need to do
hg update -r <revision>
where is the one that resulted from pulling. In general you may find it convinient to combine the two operation with
hg pull -u <repository>

TortoiseHg files shown in merge

I am writing code to do analysis of our commits to Hg, and am comparing my results to TortoiseHg. I am having trouble understanding the behavior of TortoiseHg in the case of a merge.
When I select a merge changeset in TortoiseHg, the list of affected files only shows those files that had conflicts, unless I press the "Show All" button. At least that appears to be the intent, based on what I can glean from the web, and from observation that the files shown in the list have a double headed arrow if I press the Show All button.
I am attempting to emulate that by diffing each file in the changeset against both parents, and only including the file in my analysis if it differs from both parents. However, I am encountering files that TortoiseHg shows in the description of a merge, but that only differ from one parent. I see that in TortoiseHg as well - diffing against parent 1 or 2 shows a change, but the other parent doesn't.
I have also tried diffing with the --git option, to make sure it is not a metadata change I am missing, but that doesn't change the results at all.
To get the information about a changeset I am using:
hg log -v -r <rev> --removed --style xml
I pick up the parents of the merge changeset, and for each file in the merge, do
hg diff -r <parent1> -r <rev> filename
hg diff -r <parent2> -r <rev< filename
And I find that files TortoiseHg shows in its summary of the merge I report as having merged with no conflicts.
Can anyone shed light on the discrepancy?
Update:
I was able to reproduce this with the source code for TortoiseHg itself.
Clone from https://hg01.codeplex.com/tortoisehg
Open the repo in tortoiseHg and select rev 12602 (58eb7c70). This is a merge with parents of 12599 (6c716caa) and 12601 (39c95a81).
TortoiseHg shows the file tortoisehg/hgqt/repowidget.py as the only conflicted file in the merge, yet
hg diff -r 12599 -r 12602 tortoisehg/hgqt/repowidget.py
returns nothing, while
hg diff -r 12601 -r 12602 tortoisehg/hgqt/repowidget.py
shows two lines changing.
I think I've figured out what tortoisehg's logic is here (though I haven't checked the source to be sure).
As you've guessed, tortoise shows files changed on both sides of a merge with a double arrow. However, it does not look simply at the diff of the merge to each of its parents (e.g. p1(58eb7c70)::58eb7c70 and p2(58eb7c70)::58eb7c70). Instead, tortoise finds all changes introduced in the merge, compared the last common ancestor of the two parents.
Let's take the tortoise repo as an example. The graph view of the ancestry of 58eb7c70 is:
Jonathan:tortoisehg $ hg log --graph -r ::58eb7c70 -l 5 --template "{node|short}\n{desc|firstline}\n\n"
o 58eb7c70d501
|\ Merge with stable (noop)
| |
| o 39c95a813105
| | repowidget: show all errors on infobar
| |
| o da7ff15b4b96
| | repowidget: limit infobar error messages to 2 lines of up to 140 chars by default
| |
o | 6c716caa11fd
|\| Merge with stable
| |
| o 48c055ad634f
| | sync: show non-ascii command-line arguments correctly
| |
As you can see, merge 58eb7c70d501 merged two branches of development, with one changeset (p1, 6c716caa11fd) on one side, but two on the other (p2, 39c95a813105, and its parent, da7ff15b4b96). The point where these branches diverged is the last common ancestor of p1 and p2 -- 48c055ad634f.
(The last common ancestor can be found directly with hg log -r "last(ancestor(p1(58eb7c70), p2(58eb7c70)))")
Let's look at the changes that were made on those two branches. We'll compare each parent of the merge with the common ancestor:
Jonathan:tortoisehg $ hg status --rev "48c055ad634f::6c716caa11fd"
M .hgtags
M tortoisehg/hgqt/commit.py
M tortoisehg/hgqt/compress.py
M tortoisehg/hgqt/hgemail.py
M tortoisehg/hgqt/postreview.py
M tortoisehg/hgqt/purge.py
M tortoisehg/hgqt/rename.py
M tortoisehg/hgqt/repowidget.py
M tortoisehg/hgqt/revset.py
M tortoisehg/hgqt/run.py
M tortoisehg/hgqt/settings.py
M tortoisehg/hgqt/status.py
M tortoisehg/hgqt/sync.py
M tortoisehg/hgqt/visdiff.py
M tortoisehg/util/cachethg.py
M tortoisehg/util/hglib.py
Jonathan:tortoisehg $ hg status --rev "48c055ad634f::39c95a813105"
M tortoisehg/hgqt/repowidget.py
These are the changes that were actually merged by 58eb7c70d501 -- everything changed on the two branches since they diverged. As you can see, the only file in common between the lists -- the only file that was changed on both branches -- is tortoisehg/hgqt/repowidget.py, just as you expected. You'll see that this file was changed in da7ff15b4b96, the one changeset that's not a parent of the merge but is still included in the changes merged from the two branches.
tortoisehg/hgqt/repowidget.py was modified in 6c716caa11fd, which would explain why your second call to hg diff gives you results; the first call compares two revisions across which no changes were registered to tortoisehg/hgqt/repowidget.py; this seems sound to me, unless I am missing something else about how hg diff behaves.

mercurial - see changes on the branch ignoring all the merge commits

I have a branch that was developed for a long period of time. During the development default branch was merged into that branch several times. I would like now to review all the changes done on that branch ignoring merges, to decide whether it is safe to merge it to default.
I tried
hg diff -r "branch('myBranch') - merge()"
but it still shows changes introduced by merges. Also tried following this How to show the diff specific to a named branch in mercurial but
hg diff -r "branch('myBranch') - branch('default')"
still bring changes introduced by merges.
You have to read about revsets syntax
Your case
hg log -r "branch('myBranch') and ! merge()"
The problem with your commands is that when you perform a hg diff and pass it several changesets, you actually perform a diff between those changesets, hence you will see the merge result.
If you want to see just the changes made by the changesets then you could use export:
$ hg export -r "branch('mybranch') and not merge()"
// lists the changes made by each changeset
For easier reviewing, you can output these to files with names based on the revision/changeset id:
$ hg export -r "branch('mybranch') and not merge()" -o "%H.patch"
... creates a file for each non-merge changeset in mybranch, and outputs it to a file with the name "40-digit changeset id.patch". If you'd prefer the revision number (only useful for your local repository as revision id's are local), use "%R.patch".
The following uses the log command but with the --patch parameter it can show the modified lines as well:
hg log --branch my-branch --no-merges --patch
Short form:
hg log -Mpb my-branch
That is a very good question, which I am trying to find good answer for a long time, and yet not found a good one. OK, one thing which 100% works is this:
hg status # make sure that you don't have local changes
hg up <target_branch>
hg merge <your branch>
hg diff > merge.diff
hg up -C # this one cleans the merge
I use this workflow all the time, but it does not satisfy me fully because it requires to switch branches (when I actually might not want to do actual merge right at this exact moment, I am just checking whats there)
I've been searching for ages for a good solution here, but so far there are none found. Tried those:
hg diff -r "branch('.') and ! merge()" # this page
hg diff -r "default:branch('.') and not merge()"
hg diff -r "parents(branch(.)):branch('.') and not merge()"
This problem also discussed in:
Mercurial: how can I see only the changes introduced by a merge?
which has good answer as: "so if you can define it unambiguously you might convince one of us Mercurial contributors that read SO to implement it."
Try:
hg diff -r"ancestor(default,my_branch)" -rmy_branch
If you haven't done any merges to the branch, then "ancestor" will pick up the original branch point. If you've done merges, then "ancestor" will pick up the latest merge point.
For example, on the graph below you'll get the diff between 520 and 519:
# 521 (default)
|
| o 520 (my_branch)
|/|
o | 519
| |
o | 518
| |
o | 517
| |
| o 516
| |
| o 515
| |
| o 514
|/
o 513
On the simpler graph below you'll get a diff between 516 and 513:
# 517 (default)
|
| o 516 (my_branch)
| |
| o 515
| |
| o 514
|/
o 513
Per Matt Mackall what you probably want is:
hg diff -r mainbranchrev -r mywork
He writes:
You may be under the impression that "a changeset is a delta" or
similar, and that this means that Mercurial can magically chain together
a bunch of non-contiguous deltas to construct a bigger delta that's just
your changes.
In fact, Mercurial is much simpler than that. A changeset is actually a
snapshot: a complete set of complete files representing the state of the
project at the time of commit. And a diff is simply the difference
between these two snapshots. So when you do:
hg stat --rev '3408::3575 and user(mdiamond) and not merge()'
..status simply uses the first and last changesets of the resulting set
to do its calculation.
When you do incremental merges on your development branch, you're
inextricably tangling your work with mainline development, and Mercurial
is not nearly smart enough to untangle it in the way you're asking. Your
best bet is to compare the head of your work with the head of the main
branch:
hg diff -r mainbranchrev -r mywork
..which will show all your work plus whatever merge fixups you had to
do.

What's the best way to back out multiple changesets in mercurial?

Is the most reliable method to go one-by-one, using the backout command for each of many changesets, or is there a way to create one big reversal changeset to cover a whole bunch of [edit: non-contiguous] changesets.
If one-by-one, does order matter? (Should one go last-to-first?)
Does the best method differ if there are merges among different sub-projects along the way?
Does this tend to go smoothly in your experience? :-)
If you have no merges along the way, you can either back out every individual change (in reverse order), or, if there are many of them, do it with one big inverse patch.
If you have good changesets atop the ones you need to back out, better commit the inverse patch on top of the most recent bad changeset, then rebasing them onto the tip of the branch.
1 -- 2 -- A -- B -- C -- 3 -- 4
\
C'B'A'
$ hg up C
$ hg diff -r C:2 > backout.diff
$ hg import --no-commit backout.diff
$ hg ci -m "Backout A, B, C"
$ hg up 4
$ hg rebase -s C'B'A -d .
There will be problems if you want to back out merge changesets, see this wiki page for more information.
In such a case, if possible, consider re-doing the branch and stripping the old lineage. Otherwise, you might have to abandon the branch altogether, salvaging the good changesets via graft or transplant.
There is --collapse option for rebase.
Helgi's answer can be upgraded into:
1 -- A -- 2 -- B -- 3 -- C -- 4 -- 5
\
C' -- B' -- A'
$ hg update --clean C
$ hg backout --rev C --message "Backed out changeset: C"
$ hg backout --rev B
$ hg commit --message "Backed out changeset: B"
$ hg backout --rev A
$ hg commit --message "Backed out changeset: A"
$ hg rebase --collapse --source C' --dest 5
$ hg commit --message "Backed out C, B, A"
which will result in the following
1 -- A -- 2 -- B -- 3 -- C -- 4 -- 5 -- C'B'A'
However, backing out in separate branch may result in [logical] conflict in the subsequent merge.
1 -- A -- 2 -- B -- 3 -- X -- 4
\ \
B' -- A' -- M
if X depends on A or B, then M will have conflict (at least logical conflict).
What I came up with is inelegant, but got the job done, despite that the changes I needed to back out were interspersed with other work and had some internal branching. Here's what I did. (Comments and improvements are welcome.)
Got a list of all of the changesets (which I then used to generate the commands below):
hg log -r 'keyword(xyz)' --template '{rev}\n'
Generated a patch for each changeset:
hg diff -p -U 8 --reverse -c 15094 > 15094.rev.patch
hg diff -p -U 8 --reverse -c 15095 > 15095.rev.patch
...
Then, applied each reverse patch. Here the order matters, last-to-first:
hg import -m "reversing changeset 15302" 15302.rev.patch
hg import -m "reversing changeset 15292" 15292.rev.patch
...
This process was interrupted several times for merges that didn't go through automatically, where I had to manually apply changes to a file from its .rej file and then manually commit, before picking up the imports where it had left off.
Finally (in another clone... did I mention I did this all in a clone?) I compressed the whole set of reverse changesets into one changeset using hg histedit -o and its fold command.
Now I've got a single changeset that I should be able to reverse and apply if I decide to put the work back in at a later date (Although if I cross that bridge, I might apply the "forward" patches piecemeal again in order to get better blame/annotate information)
This is how you can do it with TortoiseHg.
Of course you can do the same with the command line.
Given this history, where you wan't to get rid of changeset A, B and C:
1 -- 2 -- A -- B -- C -- 3 -- 4
First update to revision 2.
Then rebase the first of any later revisions you wan't to keep - in this case revision 3.
Your history now looks like this:
1 -- 2 -- A -- B -- C
\
3 -- 4
Now update to revison 4.
And finally use "Merge with local" to merge revision C onto revision 4.
At this point it is crucial that you select the option "Discard all changes from merge target (other) revision".
The description may not be the most logical, but it means that you merge the old tip C back to the default branch - but without the changesets A, B and C.
The result is:
1 -- 2 -- A -- B -- C --
\ /
3 -- 4
Commit and you're done.
If you don't want the "backout" changesets in your history, you could also do something else:
Make a clone of your repository, but only up to the last changeset that you don't want to get rid of.
See Mercurial: Fix a borked history for an example how to do this.
If your repository was a local one, that's all you have to do.
But if the bad changesets were already pushed to a central repository, you'd need server access to delete the repository there and replace it by your clone.
Plus, if someone else already pulled from the repo with the bad changesets, they need to delete and re-clone (otherwise the bad changesets are in the central repo again as soon as one of the other people pushes again).
So it depends on the circumstances whether this solution is a good one for you...

How to show list of unapplied changesets in Mercurial

After pushing changesets to a repository called 'A' how can I see the list of changesets waiting to be applied when I am in 'A'?
Expanding on that,
In repo B I push changesets to repo B
I change to repo B
How can I list the changesets pushed in step 1?
Not sure what you mean by "unapplied" changesets, however here's a couple thoughts.
You can easily see what changesets will be pushed to a repository by doing hg outgoing prior to doing the hg push. This will list all of the changesets that will be pushed using default options.
Similarly you can use hg incoming in the destination repository to show what changesets would be pulled from another repo.
As for "unapplied" changesets, if I assume you mean changesets that are newer than the working directory, you could use hg log -r .:tip, which should (I've not had a chance to test it) show all newer revisions, but not actually all recently-pushed ones.
Edit: I've updated the revision set in the -r option to something that should work. Have a look at revsets on the Mercurial manpage for more possibilities.
$ hg summary
parent: 0:9f47fcf4811f
.
branch: default
commit: (clean)
update: 2 new changesets (update) <<<<<
The update bit tells you what (I think) you want.
I had written a different answer, but I ended up with a better way of doing what is needed here (an even better and definitive –for me– solution is at the end of this post, in the [EDIT] section).
Use hg log.
Specifically, issue an hg sum command first. This will give me:
parent: 189:77e9fd7e4554
<some commit message>
branch: default
commit: (clean)
update: 2 new changesets (update)
To see what those 2 new changesets are made of, I use
hg log -r tip -r 2 -v
Obviously, 2 is to be replaced with the number of changesets that hg sum reports.
This works because tip will refer to the most recent (or "unapplied") changeset. By limiting the output to the 2 latest changes (-l 2), the information is shown only for those changesets that I'm interested in. With -v, the list of files affected by the changeset is also shown.
To make things simpler, I have defined a user command in my .bashrc file:
alias hglog="hg log -r tip -l $1"
This allows me to type hg sum (to get the number of pending/unapplied changesets) and then to type hglog x where x is the number of changesets revealed by hg sum.
There is probably a more complete way of doing this, for instance using custom templates, but I guess it's pushing things too far in terms of sophistication.
[EDIT] (Third iteration)
I have reached the most satisfying answer to this question by expanding on the alias idea so that I no longer have to type hg sum. My .bashrc file now contains this:
show_pending_changesets() {
nb=$(hg sum | grep "update:" | sed 's/update: \([0-9]*\) .*/\1/');
if [ `expr $nb + 1 2> /dev/null` ] ; then
hg log -r tip -v -l $nb
else
echo "Nothing new to report"
fi ;
}
...
alias hgwhatsnew=show_pending_changesets
Explanation: I'm using sed to extract the number of changesets from the last line (which is the one that starts with update:) of the output of hg sum. That number is then fed to hg log. All I have to do then is to type hgw and tab-complete it. HTH