3

Given a commit X in the history of the master branch, I'd like to identify the merge where that commit got included into master. In other words, I want to find the maximal i such that X is an ancestor of master~i, i.e. the i-th ancestor of master along the first-parent chain. (This assumes that merges into master will have the previous master as first parent, which is usually the case afaik.) How can this be done efficiently?

Example

To give an example, suppose I have the following history:

A - B - C - D - E - F - G - H
     \       \     /   /
      I -[J]- K - L - M
       \         /
        N - O - P

When I want to find out when J git merged into the master (upper line), I want the command to return F. Because from H the first-parent-only history is H - G - F - E - … and J is an ancestor of F but not an ancestor of E. Just looking for the nearest merge in the range J..H would be no good enough, since I don't want to find K or L. Both of these are not part of the first-parent-only history of H since L is the second parent of F.

MvG
  • 57,380
  • 22
  • 148
  • 276
  • A commit is a snapshot in time, so the commit X only occurs once, and cannot have been introduced by a previous commit. Likewise the `diff` from the previous commit (the particular changeset) is unlikely to have ever occurred before in its totality. Do you simply mean - find the first merge commit prior to X, while following the --first-parent mainline, so that you can see what was brought in at that point? – Philip Oakley Aug 25 '16 at 17:34
  • @PhilipOakley: No, that's not what I meant. I've edited my question, hope this clarifies what I'm after. Sure, a commit only occurs once, but it often enters master at a later time, namely not when it was committed but when it was merged. I want to find that merge. Which need not be the nearest merge, since that nearest merge may not be on mainline. So a simple `log --first-parent J..` tells me `J` diverged from master before `C`, and a simple `log --merges J..` (as you suggested in your answer) tells me `K` was the first merge after `J`, so neither of these answers my actual question. – MvG Aug 25 '16 at 21:23
  • added extra notes to my answer - if I had more time I'd try some code, but hopefully the suggestions will get you there. – Philip Oakley Aug 26 '16 at 12:03

1 Answers1

2

How's about:

git log --oneline --merges -1  [<rev range>]

This will list the first merge commit along the current branch, or from the commit X if you give that as the rev range. Review git help log to sweeten and serve.


Update to follow clarified question

The extra technique is the use of

git merge-base --is-ancestor <commit> <commit>

which will return true/false based on the ancestry (e.g https://stackoverflow.com/a/13526591/717355).

so first generate your list of first parent merges on master, either excluding commits reachable by the one in question, or using dates and the --since=<commiter_date> option. Then for each one in order (typically oldest first), check if your commit is an ancestor of the given merge commit. You could use the following bash script:

for merge in $(git rev-list --reverse --first-parent --merges "${commit}"..master)
do if git merge-base --is-ancestor "${commit}" "${merge}"
   then git show -s "${merge}"
        break
   fi
done
Community
  • 1
  • 1
Philip Oakley
  • 13,333
  • 9
  • 48
  • 71
  • Thanks, this ancestry check is useful, even though I'd still need to loop over candidate commits, which I'd rather avoid. Timestamps may be misleading in some setups, I think, since they can be tinkered with or broken due to incorrect clock settings. So I'd suggest a reachability-based criterion there. I've included some code into your post, hope you don't mind. Personally I'd drop the `log --oneline` part (since it doesn't answer the question) and the `--since` part (since I think the alternative is better), but it's your post, and I didn't want to interfere too much. Thanks a lot! – MvG Aug 26 '16 at 13:17
  • I had a few RTFM moments of delving and you may find `git rev-list --first-parent --merges -- reverse ..master` to your liking. Just take the first commit given ( ` | head -1` ) (note that `--max-count=` does not appear to work as `--reverse` is applied last). – Philip Oakley Aug 26 '16 at 14:28
  • Isn't that essentially the comment I suggested to generate candidates? Anyway, `--max-count` wouldn't be appropriate since there may be merges to master before the one I want but which are not reachable by the commit I input. In my example above, `C` might have been a merge of some other feature branch, and would be printed first. So I can't use this as the sole command to find my solution. – MvG Aug 26 '16 at 14:35
  • @MvG, Now I look, yes it is similar. I think it is that more revision limiters were needed. The rev-list order (including the double dot notation J..H) should guarantee that the first in the list (reversed) is the oldest that it could be that is both a merge and on the first parent chain (of master !), which I think was the request. Even if `C` is a merge its not in the J..H range. The max count can be confused as it could depend on reverse, or not, but it looks like it is applied *before* the reverse. Still, its a useful exercise in building Git understanding! – Philip Oakley Aug 26 '16 at 15:34
  • No, `C` *is* in the `J..H` range. That range is equivalent to `^J H`, i.e. the set of all commits reachable from `H` which are not reachable from `J`. Which is the case for `C`. I tried it (with the above example and without the `--merges` switch). – MvG Aug 26 '16 at 15:59
  • @MvG Oops. Yes you are right. Total brain fade. Especially as I've just submitted some documenation patches to update the git revisions pages! Do you have a public repo with that example in it? – Philip Oakley Aug 26 '16 at 18:10
  • No public repo, no real scenario with that exact history. But [these commands](https://gist.github.com/gagern/5b7b47b1103b0b32a7f6559cd3229f27) can reproduce the toy example. Nice set of patches, though I hate viewing them through mail archive. I miss Gmane… – MvG Aug 26 '16 at 18:46
  • 1
    @MvG, Try adding `--ancestry-path` (boy those manuals can be hard work) and https://public-inbox.org/git/ for the gmane replacement (can search by old gmane id numbers) – Philip Oakley Aug 26 '16 at 20:22
  • Sounds promising, but alas … `--first-parent` and `--ancestry-path` together give me an empty list of revisions. This might even be considered a bug, since I see no obvious semantic reasons they would have to be mutually exclusive. – MvG Aug 26 '16 at 21:58
  • See my https://gist.github.com/PhilipOakley/58f344f910e50b72f5a8a2bd55b6c175 which extends your example - looks like there is an option conflict between first parent and ancestry - I've posted it to the git list. – Philip Oakley Aug 26 '16 at 23:17
  • Links to your post for future reference: [mail archive](https://www.mail-archive.com/git@vger.kernel.org/msg101884.html) or [public inbox](https://public-inbox.org/git/2FA1998250474E76A386B82AD635E56A@PhilipOakley/). Thanks for asking. From the first answer I begin to understand the technical difficulty, but I think there should be a way around that. If I can put that into words, I'll likely post about it. – MvG Aug 27 '16 at 10:54