3

git log --first-parent omits all but the first parent of merge commits.

Example:

$ git log --oneline --graph

* 087f5ed Master C
*   36c50a2 Merge branch 'feature'
|\  
| * 98c89df Feature B
| * 89b3a7b Feature A
* | 9a95133 Master B
|/  
* 766c9b0 Master A

$ git log --oneline --graph --first-parent

* 087f5ed Master C
* 36c50a2 Merge branch 'feature'
* 9a95133 Master B
* 766c9b0 Master A

Is there a Mercurial equivalent?

Max Nanasy
  • 5,871
  • 7
  • 33
  • 38
  • That's an interesting use-case. Can you tell me what you're trying to accomplish by doing this? I do not believe there is a single command that does this in Mercurial. But I'm puzzled as to why you would want to. – Omnifarious Mar 15 '13 at 05:41
  • @Omnifarious I'm mostly curious whether Mercurial has such functionality. – Max Nanasy Mar 15 '13 at 15:32
  • Well, it could be cobbled together. But it also wouldn't be terribly useful for anything. Which parent Mercurial considers 'first' is arbitrary and not related to anything in particular (I think it sorts the hashes and the 'lowest hash' is the first parent). – Omnifarious Mar 15 '13 at 15:55
  • @Omnifarious In git, the first parent is whatever commit was checked out when `git merge` was run. So if I merge branch feature into master (i.e. on master, `git merge feature`), the current master commit is the first parent of the merge commit, whereas if I merge branch master into feature (i.e. on feature, `git merge master`), the current feature commit is the first parent of the merge commit. – Max Nanasy Mar 15 '13 at 19:02
  • I figured that was the case. You know, somewhere Mercurial must be doing something similar. Because while it always lists parent 0 as the smallest hash, it lists parent 1 and parent 0 in different orders sometimes, seemingly related to which was the 'merged in' parent. Hmmm... – Omnifarious Mar 15 '13 at 19:12
  • Yeah, the order the changesets are in in the revlog reflects which was 'merged', but at inner layers just above that it tends to be obscured by sorting them according to which has the lower hash. And this seems to be maintained when doing pulls over a network and similar things. Interesting. Let me check one more thing.... – Omnifarious Mar 15 '13 at 19:17
  • Yeah, this ordering seems to be preserved when you do pulls and things over a network. Hmmm.... So, yes, it looks like it would be possible to write a Mercurial command that did this. The information isn't lost. It's just also not readily available to any existing command (outside the debug* commands). Of course, I have only done a few various tests. I would have to look at the code in detail to figure out if this information is preserved on purpose, or accidentally on purpose (by always following a certain convention or something). – Omnifarious Mar 15 '13 at 19:21
  • Well, **revset** will be longer (in chars and time spent), but it's possible, so - "Mercurial equivalent will be log with revset" – Lazy Badger Mar 17 '13 at 17:02

4 Answers4

4

Direct equivalent: hg log -r '_firstancestors(...)'

There is a direct equivalent: the hidden revset _firstancestors follows the first-parent (p1) side of each merge. In Mercurial, like in Git, the first parent is the commit that was checked-out when hg merge [second parent] was called.

hg log -r '_firstancestors(myfeature)'

'hidden' means that it starts with an underscore, and is not listed in the help etc., but can still be used if you know it exists. I don't know why it is hidden.

Examples

This revset alias lists all commits on a feature branch while ignoring all the ancestry whenever master is merged into the branch to bring it up-to-date. (The shop I work prefers merging over rebasing).

[revsetalias]
feature($1) = _firstancestors($1) and not _firstancestors(master)

[alias]
f = log -r "feature($(echo $HG_ARGS| sed 's/^f //'))"

Example usage (uses my own logging template):

$ hg f myfeature
o  423 myfeature default/myfeature -- Esteis -- 2016-07-12 -- 123abc
|  this
o    422 -- Esteis -- 2016-07-12 -- 123def
|\   merge master into myfeature
o ~  421 -- Esteis -- 2016-07-12 -- 456abc
|    that
o  420 -- Esteis -- 2016-07-12 -- 789def
|  and the other
Esteis
  • 4,669
  • 2
  • 29
  • 45
1

You can use revsets for this:

hg log -r "p1(merge())"

merge() gets all the merge commits and p1() gets the first parent of those commits.

Use hg help revsets to get more information on revsets.

Steve Kaye
  • 6,262
  • 2
  • 23
  • 27
  • 1
    I'm not sure that's what the OP wants. It sounds like he might want the straight line of history resulting from always following only the left side of merges. – djc Mar 13 '13 at 09:37
  • After looking at the git documentation, I think that you're right. That looks like a tough one. – Steve Kaye Mar 13 '13 at 12:42
1
>hg glog -r 410:426 --template "{rev} - {desc|firstline|fill68}\n"
o    426 - Merge test fixes for dulwich changes and output changes.
|\
| o    425 - Merge incoming fix.
| |\
| | o  424 - getremotechanges: fix incoming support
| | |
o | |  423 - overlay: stop using deprecated tree.entries() method
| | |
| o |  422 - Fix all-version-tests.
| | |
o | |  421 - Test output format tweaks for test-outgoing.
| | |
o | |  420 - test-incoming: fixes for hg 1.7
| | |
| o |    419 - Merge fix for `hg out` failing on empty repo.
| |\ \
| | o |  418 - In some situations where a reference is being used but does not
| | | |  exist in _map_git or _map_hg, silently skip the reference rather
| | | |  than throwing an error. This allows hg outgoing to work on
| | | |  repositories which do not contain any revisions at all.
| o | |  417 - only want heads and tags
| |/ /
| o |  416 - test-url-parsing: update expecations missed by edaadbd99074
| | |
| o |  415 - to be recognized port number in path to repository
| | |
| o |  414 - Unbreak outgoing to non-git repos with hg pre-1.9
| | |
| o |  413 - test fixes for progress cleanup
| |/
| o  412 - Fix mercurial issue2855
| |
| o  411 - Convert dulwich progress into mercurial ui.progress
|/
o  410 - test-incoming: only run on hg 1.7.x and newer

Instead of extremely degenerated case from gist, I use part of real repository DAG (hg-git repository). I hope, the selection is sufficiently indicative for the problem being solved.

Needed revset (in plain English) will be

"Range A:B without second parent and it's ancestors for mergesets"

In revset functional language (TBT!)

-r "410::426 - (p2(merge()) or ancestors(p2(merge())))"

In case of the full range of changesets as source more readable form will be something like

hg log -r "!(p2(merge()) or ancestors(p2(merge())))"

Edit 1

I tested revset, rethought about methodology (instead of excluding I want only add needed to empty set), nearest iteration for my use-case for now (have bugs, can't find solution) is

(p1(ancestors(p1(426))) or p1(426) or 426) and 410::426

which (still) include some unwanted revisions

enter image description here

Lazy Badger
  • 94,711
  • 9
  • 78
  • 110
  • Clearly I do not have a completely mastery of the revset language. – Omnifarious Mar 18 '13 at 15:21
  • @Omnifarious - don't worry, my current revset in answer is also buggy and I try (now) to polish it (got long hardly readable line and even not final version yet) – Lazy Badger Mar 18 '13 at 16:52
  • I still think the best way to solve this problem is to use named branches. Then you can just select the branch you want. You don't always have to remember to merge in the 'right' direction for it to work either. – Omnifarious Mar 18 '13 at 17:36
  • RE: "extremely degenerated case from gist": https://gist.github.com/MaxNanasy/5184202 is from http://selenic.com/hg, the first real repository I tried `hg log -G --follow-first` with. – Max Nanasy Mar 18 '13 at 17:49
  • I believe it doesn't work is because it excludes too many changesets: Not all of merge commits' second parents' ancestors should be excluded; just the ones that aren't also the first parents' ancestors. – Max Nanasy Mar 18 '13 at 18:18
  • @Omnifarious RE: "remember to merge in the 'right' direction": In Git, at least, this would not generally be an issue, since merging in the "wrong" direction would A. not update the right branch pointer and B. have a different semantic interpretation (e.g. merging feature to master is adding a feature, while merging master to feature is updating from upstream). – Max Nanasy Mar 18 '13 at 20:17
  • @MaxNanasy: I routinely have developers here 'merge master to feature' and locally test before they do 'merge feature to master'. The frequently results in the 'merge feature to master' becoming a fast-forward merge, so the merge parents end up in the 'wrong' order. – Omnifarious Mar 18 '13 at 20:52
  • @Omnifarious The problem in that situation is the antipattern (IMO) of using a fast-forward merge for a multi-commit feature branch merge. – Max Nanasy Mar 18 '13 at 20:55
  • @MaxNanasy - Perhaps so. But what guarantee do you have that a random repository will report useful information with `--first-parent` then? How do you keep other people from doing something you consider an anti-pattern? – Omnifarious Mar 18 '13 at 21:13
  • 1
    @Omnifarious I guess you're right. OTOH, the same argument could be made about your proposed solution of named branches, so ultimately, this may just be an instance of the impedance mismatch between git and mercurial branch philosophies. – Max Nanasy Mar 18 '13 at 22:08
0

hg log --follow-first seems to do roughly the same thing, with a few provisions:

  1. It's deprecated (with no explanation that I can find).

  2. I can't figure out how to get it to display anything other than just the straight-line history from the current changeset (e.g. display history following the first parent from two divergent heads (the equivalent of git log --first-parent branch-a branch-b), although this may be just because of my lack of knowledge of Mercurial.

  3. It shows empty branch lines with hg log -G:

    $ hg log -G --template '{node|short} {desc|firstline}'
    
    @  51b90923fc9d Master C
    |
    o    bb51d979fd68 Merge branch 'feature'
    |\
    | o  a9ca2597ebbc Feature B
    | |
    | o  d0a54af09272 Feature A
    | |
    o |  77ceb31100be Master B
    |/
    o  b5a0b2c7468f Master A
    
    $ hg log -G --template '{node|short} {desc|firstline}' --follow-first
    
    @  51b90923fc9d Master C
    |
    o    bb51d979fd68 Merge branch 'feature'
    |\
    o |  77ceb31100be Master B
    |/
    o  b5a0b2c7468f Master A
    

    The above example may not seem too bad, but see https://gist.github.com/MaxNanasy/5184202 for an example in which this behavior renders unwieldy output.

    This issue may not matter much if issue #2 is insurmountable, because then hg log without -G would be the only useful way to use --follow-first AFAICT.

Max Nanasy
  • 5,871
  • 7
  • 33
  • 38
  • If you need behavior like this, I would suggest named branches in Mercurial. It's a much more reliable way of accomplishing something similar. Just make sure the 'special' branch you want all the data is about has a particular name. Then you can use the revset language to select only changes that are a part of it pretty easily. – Omnifarious Mar 18 '13 at 05:21
  • 1. Deprecated, because (I suppose) revset do the job better 2. Read about -r parameter and options (revsets in worst case again) for specifying range or *some revision* – Lazy Badger Mar 18 '13 at 09:18
  • @LazyBadger If revsets do the job better, then what revset expression is equivalent to `--follow-first`? – Max Nanasy Mar 18 '13 at 09:56
  • @Omnifarious Named branches do indeed seem to work in the case of e.g. the HG repository itself (`hg log -G -r 'branch(stable)'` gives the same output as `hg log -G --follow-first` (including the unwieldy merge lines)), but what if e.g. the repository I'm viewing is not under my control and doesn't use named branches in this way? – Max Nanasy Mar 18 '13 at 17:59
  • @Omnifarious I spoke too soon: They actually only give the same output for the first ~1000 lines; `diff -u <(hg log -r 'branch(stable)' --template '{node|short} {desc|firstline}\n' | sort) <(hg log --follow-first --template '{node|short} {desc|firstline}\n' | sort)` reveals that they span different sets of changesets, thus proving my point about what to do when the repository I'm viewing is not under my control. – Max Nanasy Mar 18 '13 at 18:09
  • I would've actually been surprised if the output was the same. I suspect their has been branching and merging within the 'branch' (something of a misnomer) tagged as 'stable'. But I think the `-r 'branch(stable)'` output is actually the more accurate reflection of what's going on. – Omnifarious Mar 18 '13 at 19:08
  • @Omnifarious What do you mean by "the more accurate reflection of what's going on"? – Max Nanasy Mar 18 '13 at 20:25