12
o---1---o---o---o---o     master
     \         /
      o---o---2           feature

Is it possible to get commit 1 without using reflog ? Using git 2.4.2

I tried:

git merge-base master feature
git merge-base feature master
returns: commit 2 
git merge-base --fork-point master feature
git merge-base --fork-point feature master
returns nothing ( exit code 1 )

Not duplicates:

Community
  • 1
  • 1
Willem D'Haeseleer
  • 19,661
  • 9
  • 66
  • 99

3 Answers3

9

Found my answer as a subset from this question: ( backporting was in fact also my usecase ! ) https://stackoverflow.com/a/16718891/867294

[alias]
    oldest-ancestor = !bash -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

I am somewhat baffled that this is not part of git as default. You would at least expect that this would be the behavior of the --fork-point option for merge-base. If anyone knows a better/default alternative. Please put it in the comments or as a separate answer !

Community
  • 1
  • 1
Willem D'Haeseleer
  • 19,661
  • 9
  • 66
  • 99
  • 1
    Yeah, I don't understand why `--fork-point` would rely on the reflog when you can get the same information from the commit graph. – Daniel Lubarov Jun 18 '15 at 17:46
  • As with the other answer, if we needed code that works with all edge cases, we'd need a special case for $1 == $2. Looks correct otherwise. Could be made slightly shorter with `diff -U 1`. – Daniel Lubarov Jun 19 '15 at 18:14
  • @Daniel Hey Daniel, You seem to grok this command really well, would you mind providing a breakdown as to how it works ? I can't say I fully understand the bash and git magic going on there... – Willem D'Haeseleer Jun 19 '15 at 21:30
  • 1
    Yeah it's clever but hard to decipher! It lists the full history (newest to oldest) of both commits, then uses diff to find all commits which aren't shared. So the last line would be the first non-shared commit, except that `-U 1` causes diff to append an extra line of "context" at the end, which will be the last shared commit before the branches diverged. The `<(...)` syntax is [process substitution](https://en.wikipedia.org/wiki/Process_substitution). – Daniel Lubarov Jun 19 '15 at 22:47
  • 1
    Note that by default `rev-list` orders commits based on when they were authored, whereas we want commits ordered based on when they became reachable. The two dates can differ for commits which were merged in from another branch -- I might have just merged an old commit. Using `--first-parent` is one way to take care of that -- we'll just see the merge commit, but not look at any of the commits that were merged in. It also speeds things up a bit. – Daniel Lubarov Jun 19 '15 at 22:56
  • 1
    Hello. I've found that your solution couldn't bypass intermediate merge. I will create a separate question at stackoverflow. – puchu Mar 15 '18 at 19:42
  • https://stackoverflow.com/questions/49310139/get-commit-where-merged-branch-forked-from-with-intermediate-merge – puchu Mar 15 '18 at 22:20
3

As you noticed, git merge-base will not help you. The reason for this is that feature is part of master’s history and it could be fast-forwarded to master.

If this is your specific setup though, that you have merged feature into master, and the feature branch still points to the commit before the merge, then you can just get all the commits that are included in master but not included in feature and take the last one; the merge base you are looking for is its parent. You can do that by specifying the range feature..master:

git show $(git rev-list feature..master | tail -1)~1

This will show you the merge base commit “1” in your graph.

poke
  • 369,085
  • 72
  • 557
  • 602
  • This seems like a good approach, although if you need 100% correctness, watch out for the edge case where the feature branch was just forked and master hasn't had any new commits since. – Daniel Lubarov Jun 18 '15 at 17:43
  • Another minor issue is that rev-list is chronological by default. The oldest commit could have been merged into master much later than it was authored. I would use `--first-parent` so that we only see commits directly on master. – Daniel Lubarov Jun 18 '15 at 20:34
  • One final caveat is that if master is merged into the feature branch, rev-list will consider those master commits reachable from the feature branch and this won't find the original fork point. I don't think there's a way to tell rev-list to exclude just the commits on the first-parent path of `feature`. – Daniel Lubarov Jun 18 '15 at 20:40
2

I think the following does what you are looking for:

git merge-base --fork-point master