1

Suppose I have a Git history that looks like (credit to this answer for the diagram, and see here for a clonable git repo with this structure):

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (note: deleted!)

And suppose I've deleted topic so that I no longer have it in my local or remote repositories (and the history has long since moved on, to the point where my reflog hasn't contained it for many months).

I'd like to find commit F, i.e., the newest commit in the master branch that does not contain any commits from the topic branch.

If it helps, I've located commit C, the commit where I started working on the topic branch.

Important: I can't do this by looking at the visual output of Git. In the real code I have, topic was merged into master tens or perhaps hundreds of times, and then master was subsequently developed (with many unrelated merges) thereafter. Therefore the proposed solutions for this question aren't usable for me.

Elliott Slaughter
  • 1,455
  • 19
  • 27

2 Answers2

3

I'd like to find … the newest commit in the master branch that does not contain any commits from the topic branch. If it helps, I've located commit C, the commit where I started working on the topic branch.

Commits on an ancestry path to that first topic-branch commit:

git rev-list --ancestry-path C..master

Commits in the master branch's first-parent history that aren't also in C's history (i.e. set a reasonable cutoff):

git rev-list --first-parent C..master

The first commit in the master branch's first-parent history that isn't on any master ancestry path to C:

git rev-list --first-parent C..master \
| grep -vm1 -f <(git rev-list --ancestry-path C..master)

Doing that with your sample repo:

$ git rev-list --first-parent :/first..| grep -vm1 -f <(git rev-list --ancestry-path :/first..)
e7c863d79e8918ca947fc602ebb06266374c10e6
$ lgdo
*   a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\  
| *   648ca35 (origin/topic) merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master
jthill
  • 55,082
  • 5
  • 77
  • 137
1

I found a script called git-when-merged which gets me most of the way there. In my original example, doing

git when-merged C

from the master branch locates G, the merge commit where C first made it into master. After that it's a simple looking through the immediate parents to find the one that's not on the path to C.

Elliott Slaughter
  • 1,455
  • 19
  • 27
  • If you have `G`, `F` is just the first parent of `G` : `G^`. `git rev-parse G^` if you want its sha. – LeGEC Sep 01 '21 at 07:05
  • Here is a bash one liner to produce `F`'s sha : `git rev-parse $(git when-merged C)^` – LeGEC Sep 02 '21 at 07:07
  • Your second command seems to produce two lines, F and also the most recent commit on master. But I guess if you `tail -n1` on it, that would give you F. – Elliott Slaughter Sep 03 '21 at 16:56
  • Ah, I haven't looked at the output of `git when-merged`, I assumed it produced only the sha of the merge commit. If it outputs two words, `git rev-parse` will take this as a request to compute two hashes. – LeGEC Sep 03 '21 at 19:55
  • Right, the output looks like `refs/heads/master `. – Elliott Slaughter Sep 03 '21 at 19:56
  • 1
    Ok, so to entirely script it you need a way to select only that second field. `... | cut -d' ' -f2` or `... | awk '{ print $2 }'` for example. – LeGEC Sep 03 '21 at 20:00