Mercurial commit disappeared - mercurial

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.

Related

What are all of Mercurial's built in commit identifiers?

I'm looking for easy ways to move around to different commits, sometimes within a branch (and not necessarily from the latest commit). For example, I'd want a way to always get to the previous commit:
# move to commit before current commit
hg checkout -r ~.1
or move to the top of the branch
hg checkout tip
But I can't figure out things like how to move to the next commit (i.e. the one above the current commit, the negation of ~.1). hg seems to have built in ways of referencing these things (e.g. tip (latest commit), . (current commit), and .~N (N-th previous commit)), but are there any others?
You have to re-read hg help revsets carefully and a) build (if needed) b) use these revsets in hg commands
If you want to use "~" notation, you have to use proper format of revset hg log -r ".~1" for immediate parent and remember "only 1-st parent is evaluated" (mergesets, f.e, have two parents)
"x~n"
The nth first ancestor of x
Top of named branch (branch head) isn't tip (tip - ltest commit in repo, can be in another branch), but branchname per se for hg up
With "x~n" revsets you can use negative numbers also: for n < 0, the nth unambiguous descendant of x.

Mercurial - files modified in current branch

Can you help me to create a proper revset for mercurial hg status ?
I would like to list all the files that were changed in the current branch since it was created.
I tried
hg status --rev "branch(foo)"
where foo is the name of my branch, but this also lists files that were changed in the branch from which my branch was created (?).
I don't get how to create a proper revset for this.
I created my branch and made several changes in multiple files. Now I want to reload these files in my application, but only them.
This seems pretty straightforward (see hg help revsets and hg help revisions for where this comes from).
We might start with the set of all commits in a branch, e.g., for branch foo:
-r 'branch(foo)'
Obviously this can produce a dozen, or even a million, revisions; but you want to see what happened between "branch creation"—which needs to examine the parent of the first such revision—and "current status of branch", which needs to examine the last such revision.
The first1 revision of a large set is obtained by first() and the last by last(). However, when various commands are given a revision specifier, they look it up as a single revision, and here a branch name suffices to name the last commit on the branch anyway.
To get the (first) parent of a revision, we use the p1() function (the suffix ^ is only allowed on a literal revision, not a function that returns a revision). Hence the parent of the first revision on branch foo is:
-r 'p1(first(branch(foo)))'
To get a full diff of this against the last commit in the branch:
hg diff -r 'p1(first(branch(foo)))' -r 'foo'
But you don't want a full diff, you want the file names. The command that produces this is hg status, and it has a slightly different syntax:
hg status --rev 'p1(first(branch(foo)))' --rev 'foo'
The status includes the letters at the front, as well as the names: A for newly added files, M for modified files, and so on. Note the use of --rev rather than just -r (in fact, you can use --rev with hg diff as well).
Note that there's a much shorter syntax, ::foo, that gets all the ancestors of the given branch up to and including the last revision in the named branch. However, this gets too many ancestors. You can, however, use p1(first(branch(foo)))::foo as the (entire) argument to --rev, and this also works. so:
hg status --rev 'p1(first(branch(foo)))::foo`
is a slightly shorter way to express this.
Last, note that comparing the first commit in the branch to the last (as would happen with hg status --rev 'first(branch(foo))' --rev foo, for instance) can miss changes made in that first commit on the branch. That's why we use p1 here.
1The first function selects the first in the set, which may not be the same as the numerically-first revision. For instance, suppose the set is made from x | y and a revision in y is numerically lower than a revision in x, or you use reverse(branch(foo)) to put the commits in high-to-low order. In this case, min instead of first would be the function to use.

Repeating mercurial graft steps by hand

Recently one of my coworkers grafted a commit that changed one (out of three) subrepositories on .hgsubstate. The graft instead changed all three to match the original changeset(even if they were changes from previous one):
changeset main:
f60c22f43d335e95a95301aee58a092b63800e4b external/a
26e615cf033cffa9fba77d2369e3802cc2b9a95e external/b
ca46ca7e5243439de09a2d14ffa60432c6c56d74 external/c
diff of changeset grafted:
7fe8fcdd7648e14bee5889a4b3155fde49f09de4 external/a
5b90cf021b0006c8681247a73e9940f835a959f4 external/b
-8479ff0a18dc684db7f0771ace700915c51e92e6 external/c
+69e97bdab56155fee6ab4d0d21bbf36b34b040f8 external/c
final graft:
7fe8fcdd7648e14bee5889a4b3155fde49f09de4 external/a
5b90cf021b0006c8681247a73e9940f835a959f4 external/b
69e97bdab56155fee6ab4d0d21bbf36b34b040f8 external/c
It seems that graft is replacing the file wholesale, not just it's specific changes. This is counter to my intuition of what graft should do. Worse, it's doing it even if i use -t internal:fail it doesn't let me merge anything.
I would at least like to know why that unfortunate merge is happening so we can avoid it in the future. I tried to reproduce the problem on a toy example, but i failed; since there are approximately 350 commits/merges separating both changesets, it's very likely i missed the problematic part.
My question is: Can i make mercurial output the steps it's taking to do this graft? Or somehow identify the source of the problematic merge?
A graft operation is a 3-way merge that uses the graft parent revision as the base. Nothing else in the graph should matter.
Source: https://groups.google.com/forum/#!topic/mercurial_general/3pHTx8gT208
It seems that my problem lies somewhere else, but I can close this question now.

Find all changesets in named branch where file "X" was modified (including merges) [duplicate]

This question already has an answer here:
How do I find the merge that moved my changeset to another branch?
(1 answer)
Closed 9 years ago.
I am looking for a way with TortoiseHg (or plain hg if no other possibility exists) to locate all the changes to a particular file.
I've tried using a revision set query:
merge() and file("path/to/filename.cs")
but that didn't get me what I'm looking for. It returns an empty set. I assume this is because merge() only returns merges, and file() only (appears to) returns non-merges, so the intersection is empty.
I've also tried modifies(pattern) but that doesn't appear sufficiently different from file(pattern) (looks to me like file() is the union of adds() and modifies()). contains(pattern) doesn't return any elements at all.
So, is it possible to get a list of changesets in which a particular file has been modified in any way?
It looks like you're running into the "how did my change get into this branch" problem. When you merge a changeset into a branch, Mercurial doesn't record that as a change. Instead, it makes a note that the merged changeset is now part of the destination branch. The only time a merge will show that a file was modified is if there was a merge conflict, which results in a new changeset.
Finding what merge brought a changeset into a branch is a two step process. First, you have to find the changeset where the change you're interested in occurred.
hg log -r "file('<PATTERN>')
Once you have that revision, find the first descendant changeset in the destination branch:
hg log -r "first(branch(<BRANCH>) and descendants(<REVISION>))"
hg log -r "branch(BRANCH) and file('PATTERN')"
Revset also can be used and entered into Filter Tooolbar in THG Workbench
hg log grep name_of_your_file
Does the trick for me in terms of finding all the change sets that have resulted in a change to a given file. Of course if you would like something pretty:
thg log name_of_your_file

List all merged changesets since last merge?

Using Mercurial, how can I list all the changesets applied by merging a branch, since the last merge from that branch?
Revsets are your friend. Or your nemesis, depending on how complex they get :)
The following command will show all associated changesets between the last two merges:
$ hg log -r "first(last(merge(),2)):last(merge()) & ancestors(last(merge()))"
That complex little expression (which I'll look at making simpler later) does the following:
x:y gives you all changesets between x and y inclusive
merge() is a revset that contains all merges.
last(...,n) gives the last n changesets of a set, with n defaulting to 1
first(...) gives you the first changeset of a set
ancestors(last(merge())) is a set containing all ancestors of the last merge
Combining all of those, the expression above becomes (ready?): Give me all changesets between the first of the last two merges, and the last merge, inclusive, which happen to be contributing ancestors of the last merge.
The ancestors(...) bit filters out any changesets that are not related.
You can limit this to be the changes on a specific branch by adding & branch(branchname). For example, if you are merging onto a release branch from default, you could do:
$ hg log -r "first(last(merge(),2)):last(merge()) & ancestors(last(merge())) & branch(default)"
This wouldn't include the actual merges themselves, as they would appear on the release branch.
Hopefully this makes sense - I'll have a look this afternoon to see if I can get a simpler way, but that's the first that springs to mind. In the meantime, if you use this, you can make it easier by creating a revset alias in your user hgrc file:
[revsetalias]
contrib = first(last(merge(),2)):last(merge()) & ancestors(last(merge()))
So you can then use:
$ hg log -r "contrib"
$ hg log -r "contrib & branch(default)"
For more information have a look at hg help revsets.
I'm not sure icabod's solution is quite right. Let me see if I can explain.
Let's take this change graph.
o----A1----A2----M1--------A3---M2
\ / /
---B1----B2--- /
\ /
----C1--------C2----C3
B is a branch taken from o, and C is a branch taken from B1. If we're at M2 and run icabod's command then:
last(merge()) is M2
first(last(merge(),2)) is M1
So the expression becomes:
hg log -r "M1:M2 & ancestors(M2)"
M1:M2 is the changes made than have revision numbers between M1 and M2, which in this case is A3, C2 & C3, which completely ignores C1.
What I think is you're looking for the set of ancestors of M2, that weren't ancestors of M1. i.e.
hg log -r "ancestors(M2) & not ancestors(M1)"
or
hg log -r "ancestors(last(merge())) and not ancestors(first(last(merge(), 2)))"
I think this should be C1, C2, C3 & A3. It also has the advantage that it doesn't care how, or when, change-sets were added to the repo.
The only problem with this is if the second to last merge isn't an ancestor of the latest merge. I'll leave that as an exercise for the reader ;-)
Of course, all of this can be avoided by doing hg merge --preview (or -P) prior to doing a merge, and it lists all the change-sets that will be included when you do a merge.