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") ?
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") ?
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
.)
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.
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.
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.
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.
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
}
git log --topo-order
Then look for the first merge before the commit.
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
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:
~/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.
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
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"
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.
A graphical solution is to find it in gitk (using the “SHA1 ID” box) and follow the line of commits up to the merge commit.
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