Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect a branch point in git?

Tags:

git

I'm trying to determine the first commit in a branch. I've read various other SO posts (including Finding a branch point with Git?), but they don't give me what I'm after.

From looking at a branch graph I can see that it is possible to determine when a branch diverged from master (on the included image, the purple line coming from "2nd commit on master"), however I can't replicate this in text using command-line tools (git log --graph works but is not what I'm after).

The commit I'm after is 0124fc8, i.e, the first commit to feature/new-branch.

All merges back to master are done with --no-ff.

I've tried git merge-base feature/new-branch master, however this gives me 0f5e36f (--all does the same)

I've also tried git rev-list --boundary feature/new-branch...master, which gives me several commits, but none are the one that I want.

SourceTree showing a branch graph

like image 707
Dave Avatar asked Oct 19 '12 09:10

Dave


1 Answers

In the absence of merges back onto master, the last entry of git rev-list master..feature/new-branch would be the one you want. a..b in a rev-list gives you all the commits in b's history that are not in a's history. But since you've merged, all the commits in feature/new-branch's history are also in master's history, so we have to be a bit cleverer.

The question you linked (rather, this answer to it) gives a very good start. The trick is to use --first-parent to avoid considering the right-hand-side of all the merges into master as being part of master's history. git rev-list --first-parent master gives you all the commits that were committed directly into master, and doesn't show any commits that were merged. Alas, --first-parent doesn't combine with .. in the way we want, so we have to make our own .. equivalent, using diff. We can use diff to take our list of all the commits in feature/new-branch's history, and remove all the commits that's are in master's "direct" history. I've used backslash escaping to split the one command across several lines:-

diff --new-line-format=%L --old-line-format= --unchanged-line-format= \
    <(git rev-list --first-parent master) \
    <(git rev-list --first-parent feature/new-branch)

The options to diff make it only print out the lines that are in the second input but not the first input, with no + or - stuff and no headers. I'm not well up on POSIX, so it may be that only GNU diff has those options. Similarly, for the <( ) operator on the other two lines, you need bash or zsh. If you have to use some other shell, you'll probably need to use temporary files.

The first input (the one of lines to ignore) is the "direct" history to master, as we found out above; the second input is the "direct" history of feature/new-branch. The output of the command is all the commits that were committed to feature/new-branch "directly", but not to master "directly", where "directly" means "not through a merge". Including --first-parent on the second input is necessary to avoid including any commits that were merged into master before branching feature/new-branch, but it'll have the side-effect of excluding commits from any other branches that were merged into feature/new-branch (which doesn't make a difference if you only want the first commit).

The output is in reverse order, as usual, so the last line of the output is the commit you're interested in.

I'm really curious to know what you're trying to achieve with this information, as it seems like it doesn't fit with Git's model of the world at all. Git users know and expect that the history of a branch includes the history from the branch's ancestor, and the history of any merges into it.

like image 184
Dan Hulme Avatar answered Sep 23 '22 00:09

Dan Hulme