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

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.

Related

How to update to a branch by name when there's a hash collision?

So my coworker just won the hash lottery. We create a feature branch for every ticket we resolve, following the convention b##### where ##### is the issue number.
The trouble is that when he tried updating to that branch (before it existed) via hg up branch(b29477), it took him to default instead of saying that the branch doesn't exist.
It turns out that branch(b29477) actually returns the name of branch of the thing inside the parens (instead of forcing Mercurial to evaluate the thing inside the parens as a branch name as I thought!), and there so happened to be a changeset beginning with b29477 which was on default, so instead of saying the branch didn't exist, it took him to the tip of default!
Now we can work around this problem by choosing a different branch name, but I want to know if there's any way to hg update <branch_name_and_dont_interpret_this_as_anything_else>?
BTW, hg log also lies about what it's --branch parameter does. It says:
-b --branch BRANCH [+] show changesets within the given named branch
But that's not true at all. Go ahead and run it with a hash. e.g.,
hg log --branch eea844fb
And it will turn up results. If you dig through the docs, you'll discover that it's actually the same as:
hg log -r 'branch(eea844fb)'
Try this:
hg update -r "branch('literal:b29477')"
From the Mercurial help page:
branch(string or set)
All changesets belonging to the given branch or
the branches of the given changesets.
If string starts with re:, the remainder of the name is treated as a
regular expression. To match a branch that actually starts with re:,
use the prefix literal:.
This means that if you use the literal prefix, you are specifying a string. And a string is not a set.
As the text says, if you specify a changeset, Mercurial will show:
the branches of the given changesets

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.

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.

Mercurial commit disappeared

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.

Refer to a mercurial revision relative to a named revision

In git, you can do refer to revisions by something like master^^, meaning two revisions before master. Can you do the same in Mercurial in some way (or with some extension)? For example, if I want to do "the revision before tip", something like tip^.
edit: Rafa mentions hg parents which works for 1 level deep. How do I do it for arbitrary levels deep.
For commands that have options to specify a revision, use -r -2 for the revision prior to the tip, -r -3 for the revision before that, etc.
From hg help revs:
A plain integer is treated as a revision number. Negative integers are treated as sequential offsets from the tip, with -1 denoting the tip, -2 denoting the revision prior to the tip, and so forth.
There is also the ParentrevspecExtension that allows you use syntax like: tip^ for the parent of tip, tip^^ for its parent, etc.
As mentionned by Niall C., when the revisions are linear, it is possible to use the negative specification to go back starting at tip (only works with linear history, obviously).
Starting with Mercurial 1.6, there is something similar to Parentrevspec, but in core to designate complex revision specification: revsets.
For example, "p1(tip)" is the first parent of "tip", etc.
You can do "hg parents -r revision"