Find merge commit which include a specific commit

Git

Git Problem Overview


Imagine the following history:

       c---e---g--- feature
      /         \
-a---b---d---f---h--- master

How can I find when commit "c" has been merged into master (ie, find merge commit "h") ?

Git Solutions


Solution 1 - Git

Add this to your ~/.gitconfig:

[alias]
    find-merge = "!sh -c 'commit=$0 && branch=${1:-HEAD} && (git rev-list $commit..$branch --ancestry-path | cat -n; git rev-list $commit..$branch --first-parent | cat -n) | sort -k2 -s | uniq -f1 -d | sort -n | tail -1 | cut -f2'"
    show-merge = "!sh -c 'merge=$(git find-merge $0 $1) && [ -n \"$merge\" ] && git show $merge'"

Then you can use the aliases like this:

# current branch
git find-merge <SHA-1>
# specify master
git find-merge <SHA-1> master

To see the merge commit's message and other details, use git show-merge with the same arguments.

(Based on Gauthier's answer. Thanks to Rosen Matev and javabrett for correcting a problem with sort.)

Solution 2 - Git

Your example shows that the branch feature is still available.

In that case h is the last result of:

git log master ^feature --ancestry-path

If the branch feature is not available anymore, you can show the merge commits in the history line between c and master:

git log <SHA-1_for_c>..master --ancestry-path --merges

This will however also show all the merges that happened after h, and between e and g on feature.


Comparing the result of the following commands:

git rev-list <SHA-1_for_c>..master --ancestry-path

git rev-list <SHA-1_for_c>..master --first-parent

will give you the SHA-1 of h as the last row in common.

If you have it available, you can use comm -1 -2 on these results. If you are on msysgit, you can use the following perl code to compare:

perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

(perl code from http://www.cyberciti.biz/faq/command-to-display-lines-common-in-files/ , which took it from "someone at comp.unix.shell news group").

See process substitution if you want to make it a one-liner.

Solution 3 - Git

git-get-merge will locate and show the merge commit you're looking for:

pip install git-get-merge
git get-merge <SHA-1>

The command follows the children of the given commit until a merge into another branch (presumably master) is found.

Solution 4 - Git

That is, to summarize Gauthier's post:

perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' <(git rev-list --ancestry-path <SHA-1_for_c>..master) <(git rev-list --first-parent <SHA-1_for_c>..master) | tail -n 1

EDIT: because this uses process substitution "<()", it is not POSIX compatible, and it may not work with your shell. It works with bash or zsh though.

Solution 5 - Git

I needed to do this, and somehow found git-when-merged (which actually references this SO question, but Michael Haggerty never added a reference to his very nice Python script here). So now I have.

Solution 6 - Git

Building on Gauthier's great answer, we don't need to use comm to compare the lists. Since we're looking for the last result in --ancestry-path which is also in --first-parent, we can simply grep for the latter in the output of the former:

git rev-list <SHA>..master --ancestry-path | grep -f <(git rev-list <SHA>..master --first-parent) | tail -1

Or for something snappy and reusable, here's a function to pop into .bashrc:

function git-find-merge() {
  git rev-list $1..master --ancestry-path | grep -f <(git rev-list $1..master --first-parent) | tail -1
}

Solution 7 - Git

I use below bash script which I place at path ~/bin/git-find-merge. It's based on Gauthier's answer and evilstreak's answer with few tweaks to handle corner cases. comm throws when the inputs are not sorted. grep -f works perfectly.

Corner cases:

  • If commit is a merge commit on first-parent path of branch, then return commit.
  • If commit is a NON-merge commit on first-parent path of branch, then return branch. It's either a ff merge or commit is only on branch and there is not a good way to figure out the right commit.
  • If commit and branch are same, then return commit.

~/bin/git-find-merge script:

#!/bin/bash

commit=$1
if [ -z $commit ]; then
	echo 1>&2 "fatal: commit is required"
	exit 1
fi
commit=$(git rev-parse $commit)
branch=${2-@}

# if branch points to commit (both are same), then return commit
if [ $commit == $(git rev-parse $branch) ]; then
	git log -1 $commit
	exit
fi

# if commit is a merge commit on first-parent path of branch,
# then return commit
# if commit is a NON-merge commit on first-parent path of branch,
# then return branch as it's either a ff merge or commit is only on branch
# and there is not a good way to figure out the right commit
if [[ $(git log --first-parent --pretty='%P' $commit..$branch | \
    cut -d' ' -f1 | \
	grep $commit | wc -l) -eq 1 ]]; then
	if [ $(git show -s --format="%P" $commit | wc -w) -gt 1 ]; then
		# if commit is a merge commit
		git log -1 $commit
	else
		# if commit is a NON-merge commit
		echo 1>&2 ""
		echo 1>&2 "error: returning the branch commit (ff merge or commit only on branch)"
		echo 1>&2 ""
		git log -1 $branch
	fi
	exit
fi

# 1st common commit from bottom of first-parent and ancestry-path
merge=$(grep -f \
	<(git rev-list --first-parent  $commit..$branch) \
	<(git rev-list --ancestry-path $commit..$branch) \
		| tail -1)
if [ ! -z $merge ]; then
	git log -1 $merge
	exit
fi

# merge commit not found
echo 1>&2 "fatal: no merge commit found"
exit 1

Which lets me do this:

(master)
$ git find-merge <commit>    # to find when commit merged to current branch
$ git find-merge <branch>    # to find when branch merged to current branch
$ git find-merge <commit> pu # to find when commit merged to pu branch

This script is also available on my github.

Solution 8 - Git

For the Ruby crowd, there's git-whence. Very easy.

$ gem install git-whence
$ git whence 1234567
234557 Merge pull request #203 from branch/pathway

Solution 9 - Git

git log --topo-order

Then look for the first merge before the commit.

Solution 10 - Git

My ruby version of @robinst's idea, works twice faster (which is important when searching for very old commit).

find-commit.rb

commit = ARGV[0]
master = ARGV[1] || 'origin/master'

unless commit
  puts "Usage: find-commit.rb commit [master-branch]"
  puts "Will show commit that merged <commit> into <master-branch>"
  exit 1
end

parents = `git rev-list #{commit}..#{master} --reverse --first-parent --merges`.split("\n")
ancestry = `git rev-list #{commit}..#{master} --reverse --ancestry-path --merges`.split("\n")
merge = (parents & ancestry)[0]

if merge
  system "git show #{merge}"
else
  puts "#{master} doesn't include #{commit}"
  exit 2
end

You can just use it like this:

ruby find-commit.rb SHA master

Solution 11 - Git

This is the best way to find the commit in master:

[alias]
    find-merge = !"f() { git rev-list $1..master --ancestry-path --merges --reverse | head -1; }; f"

Solution 12 - Git

You can try something like this. The idea is to iterate through all merge commit and see if the commit "c" is reachable from one of them:

$ git log --merges --format='%h' master | while read mergecommit; do
  if git log --format='%h' $mergecommit|grep -q $c; then
    echo $mergecommit;
    break
  fi
done

Solution 13 - Git

I've had to do this several times (thanks to everyone that answered this question!), and ended up writing a script (using Gauthier's method) that I could add to my little collection of git utilities. You can find it here: https://github.com/mwoehlke/git-utils/blob/master/bin/git-merge-point.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGuillaume MorinView Question on Stackoverflow
Solution 1 - GitrobinstView Answer on Stackoverflow
Solution 2 - GitGauthierView Answer on Stackoverflow
Solution 3 - GitJianView Answer on Stackoverflow
Solution 4 - GitTotorView Answer on Stackoverflow
Solution 5 - GitAlex DupuyView Answer on Stackoverflow
Solution 6 - GitevilstreakView Answer on Stackoverflow
Solution 7 - GithIpPyView Answer on Stackoverflow
Solution 8 - GitsteelView Answer on Stackoverflow
Solution 9 - GitRomanView Answer on Stackoverflow
Solution 10 - GitKaplan IlyaView Answer on Stackoverflow
Solution 11 - GitTrubluxView Answer on Stackoverflow
Solution 12 - GitholygeekView Answer on Stackoverflow
Solution 13 - GitMatthewView Answer on Stackoverflow