Mercurial 3-way Diff Without Merging - mercurial

I've got WinMerge set up to compare two arbitrary revisions like so:
hg windiff -r 23 -r 37
I've got kdiff3 set up to be used for 3-way comparisons during merge resolution.
How does one configure mercurial to use kdiff3 to compare 3 arbitrary revisions like this:
hg 3diff -r 23 -r 36 -r 37
I'd like to be able to get a handle on things before starting to merge or rebase.
(Is this even possible?)

Related

Some hg changesets not merging after graft

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"

mercurial - first revision a string appeared

Say the string "abc123" first appeared in revision 12, and the current revision is 200. How can I know that the string "abc123" first appeared in revision 12? Plus, how do I get verision 12's hash?
You should be able to use
hg grep abc123 -r 0:tip
to search revisions in order for the first revision that the string abc123 appears in. Keep in mind that this can be a very slow process for a large repository with a long history, as each revision will have to be checked individually.
To speed up the process for such a repository, you can use hg bisect. E.g.:
hg bisect -r # reset bisect if needed
hg bisect -g 0
hg bisect -b tip
hg bisect -c '! hg grep -r . abc123'
Note that marking revisions that contain the string as "bad" and those that don't contain it as "good" seems counterintuitive, because bisect is normally used to hunt for the first revision that contains a bug. Thus the "bad" revisions are the ones that contain the string.
Note also that bisect may not work if there are revision ranges that do not contain the string you are looking for.
Finally, the ! operator to negate a shell exit code may not be supported by some shells. In those cases, use
hg bisect -c 'if hg grep -r . abc123; then false; else true; fi'
instead.
To get the hash of (say) revision 12, use:
hg id -i -r 12
or:
hg id --debug -i -r 12
The latter version will print the full hash, the former will give you the abbreviated hash.

How to see changes in subrepos between commits

I have a mercurial repo with subrepos (also mercurial). Imagine the situation where I have changed the subrepos and the main repo. Now I want to see the changes between several commits including the changes in the subrepos.
Is it possible?
I use TortoiseHG and diffmerge. In diffmerege calling for visual diff from TortoiseHg, I can't see the changes in the subrepos between several commits.
In the command line you can do the following. Let us say you want to see all the changes of a subrepo named example between the changesets (in the main repo) c608f6017bd7 and 72d284a44170.
In the main repo
hg diff -rc608f6017bd7:72d284a44170 .hgsubstate | grep example
will return the changesets of the subrepo example, something like:
-001fc0acef220bcd42898ef3932dee8330ea64c0 example
+77f9db4d51c4b483607178aba91c872b0adedf1e example
Now you can see the logs and the diffs of the subrepo changes with:
cd example
hg log -r001fc0acef220bcd42898ef3932dee8330ea64c0:77f9db4d51c4b483607178aba91c872b0adedf1e
hg diff -r001fc0acef220bcd42898ef3932dee8330ea64c0:77f9db4d51c4b483607178aba91c872b0adedf1e
If you need it often, you can create a bash script named sublog like:
#!/bin/bash
r=$(hg diff -r$1:$2 .hgsubstate | grep $3 | cut -c 2-41 | tr '\n' ':' | sed 's/:$//'; echo '')
cd $3
hg log -r:$r
and use it like:
sublog c608f6017bd7 72d284a44170 example
I can only tell you how to achieve it on the command line - but that is readily available with tortoiseHG, too:
Most commands can be made aware of subrepositories by using the -S or --subrepos flag. As such, in order to see the diff between two changesets X and Y, including those on all subrepositories, do at the main repository:
hg diff -S -rX:Y
Mind, of course, that it will not show a diff in the subrepositories if there was no change of the sub-repository version(s) committed to the main one.
With the versions of TortoiseHg I've used (which doesn't include the last few releases), I haven't seen a way of doing what you're asking about. There are a few options though:
you can type commands directly in the output log window in TortoiseHg, so you can do hg diff -S -rX:Y there.
Archive the versions of the parent repo which you want to diff to some directories (hg archive --repository <path-to-repo> -r <rev> -S -t files -- <outputfolderpath>, or in TortoiseHg, right-click the changeset, select Export -> Archive). Then use diffmerge on the archive directories. This is a bit tedious (especially if you want to diff many changesets), but you will get a "deep" visual diff.

Why does mercurial's log --follow option miss changesets?

If I use log -k it pulls up two changesets, but when I add --follow or -f it misses one of them. Why would that be?
(I'd thought that --follow could only add changesets to the set returned, i.e., the ones that had modified selected files before their names were changed to the current names.)
The -f option only follows the history through ancestors/descendents of the starting revision.
My guess is that -k (the keyword search) is returning results from two branches in your repo. Therefore, by adding -f / --follow, you are restricting the results to the changesets that are directly related to the starting revision.
I'm back here because I finally get (mostly) what --follow keeps and what it drops. Although Edward was basically right, I was confused after testing out his answer because I'd misunderstood what was meant by "starting revision," and also there are some nuances about the way it follows ancestors/descendants that led to results that didn't fit what I expected.
Here's a repository with a simple branch:
% hg glog --template {rev}
# 2
|
| o 1
|/
o 0
% hg glog --template {rev} -r '0+1+2'
# 2
|
| o 1
|/
o 0
% hg glog --template {rev} -r '1+2+0'
# 2
|
| o 1
|/
o 0
The next examples using --follow illustrate that the "starting revision" that's relevant to --follow is the first one in the specification (not the earliest in the repository or anything like that), and that even the subsequent ordering matters.
Everything is included if we start with the shared ancestor.
% hg glog --template {rev} -r '0+1+2' --follow
# 2
|
| o 1
|/
o 0
If we don't, then the results even depend on the order of later ones. It looks like it can traverse across the graph, but only in one direction (to ancestors or to descendants, but not both in the same call.)
% hg glog --template {rev} -r '1+2+0' --follow
o 1
|
% hg glog --template {rev} -r '1+0+2' --follow
o 1
|
o 0
And it's not just an issue if you're ordering manually. Using the basic primitives, you can wind up with the revisions in different orders, leading to confusing behaviors.
% hg log --template {rev} -r 'all()'
012
% hg log --template {rev} -r 'all()' --follow
012
% hg log --template {rev} -r 'reverse(all())'
210
% hg log --template {rev} -r 'reverse(all())' --follow
20
I still don't have a clear and concise specification in mind for what it does, and I think it's tricky to generate revsets that are "safe" for follow. (Could hg give a warning message when changesets are dropped from the results for this reason?) So when I'm doing tricky processing that depends on --follow, I run without --follow too, comparing to make sure I'm not losing anything.
I think that your expectation is valid, and I have found --follow to be a bit buggy, when combined with other options.
See these confirmed (and somewhat old) bugs:
https://bz.mercurial-scm.org/show_bug.cgi?id=4959 [Marked FIXED as of Jan 2018]
https://bz.mercurial-scm.org/show_bug.cgi?id=5053
For me, a workaround was to use the --rev "follow('my/filename')" revset syntax, rather than the problematic --follow flag.
Maybe give that a shot?

Tag diffing in mercurial

In mercurial is there a way to diff between 2 different tags?
I have tagged my builds and have a couple commits in between builds and want to figure out the differences between the 2 builds.
hg diff -r tag1:tag2
That's all there is to it.
This answer in the Kiln StackExchange seems quite complete (based on hg diff and hg log):
To see all of the changesets that were introduced between, say, the tags v1.0 and v1.1, run:
hg log -r v1.0:v1.1
To see the net sum of differences introduced in those revisions, you'd instead run:
hg diff -r v1.0:v1.1
Mercurial can even format this output in changelog-style, if you want. Simply add the --style changelog parameter:
hg log -r v1.0:v1.1 --style changelog