Let\'s say I have the following local repository with a commit tree like this:
master --> a
\\
\\
develop c --> d
Here is a PowerShell implementation of Mark Reed's solution:
git show-branch -a | where-object { $_.Contains('*') -eq $true} | Where-object {$_.Contains($branchName) -ne $true } | select -first 1 | % {$_ -replace('.*\[(.*)\].*','$1')} | % { $_ -replace('[\^~].*','') }
This did not work for me when I had done something like develop > release-v1.0.0 > feature-foo
, it would go all the way back to develop, note there was a rebase involved, not sure if that is compounding my issue...
The following did give the correct commit hash for me
git log --decorate \
| grep 'commit' \
| grep 'origin/' \
| head -n 2 \
| tail -n 1 \
| awk '{ print $2 }' \
| tr -d "\n"
Remember that, as described in "Git: Finding what branch a commit came from", you cannot easily pinpoint the branch where that commit has been made (branches can be renamed, moved, deleted...), even though git branch --contains <commit>
is a start.
git branch --contains <commit>
doesn't list the feature
branch and list develop
branch,/refs/heads/develop
If the two commits id match, you are good to go (that would mean the feature
branch has its origin at the HEAD of develop
).
The solution based on git show-branch did not quite work for me (see below), so I've combined it with the one based on git log and ended up with this:
git log --decorate --simplify-by-decoration --oneline \ # selects only commits with a branch or tag
| grep -v "(HEAD" \ # removes current head (and branch)
| head -n1 \ # selects only the closest decoration
| sed 's/.* (\(.*\)) .*/\1/' \ # filters out everything but decorations
| sed 's/\(.*\), .*/\1/' \ # picks only the first decoration
| sed 's/origin\///' # strips "origin/" from the decoration
log
commandmaster
and develop
results (mostly) in <SHA> Initial commit
A---B---D---E---F <-origin/master, master
\ \
\ \
\ G---H---I <- origin/hotfix, hotfix
\
\
J---K---L <-origin/develop, develop
\
\
M---N---O <-origin/feature/a, feature/a
\ \
\ \
\ P---Q---R <-origin/feature/b, feature/b
\
\
S---T---U <-origin/feature/c, feature/c
Despite local branch existence (e.g. only origin/topic
is present since the commit O
was checked-out by directly by its SHA), the script should print as follows:
G
, H
, I
(branch hotfix
) → master
M
, N
, O
(branch feature/a
) → develop
S
, T
, U
(branch feature/c
) → develop
P
, Q
, R
(branch feature/b
) → feature/a
J
, K
, L
(branch develop
) → <sha> Initial commit
*B
, D
, E
, F
(branch master
) → <sha> Initial commit
* - or master
if develop
's commits were on top of master's HEAD (~ the master would be fast-forwardable to develop)
The solution based on git show-branch proved unreliable for me in the following situations:
grep '\*' \
for `grep '!' \ – and that is just the beginning of all the troublesmaster
and develop
results in develop
and `` respectivelymaster
branch (hotfix/
branches) end up with the develop
as a parent since their closest master
branch parent was marked with !
instead of *
for a reason.I have a solution to your overall problem (determine if feature
is descended from the tip of develop
), but it doesn't work using the method you outlined.
You can use git branch --contains
to list all the branches descended from the tip of develop
, then use grep
to make sure feature
is among them.
git branch --contains develop | grep "^ *feature$"
If it is among them, it will print " feature"
to standard output and have a return code of 0. Otherwise, it will print nothing and have a return code of 1.
@Mark Reed: You should add that the commit line should not only contain an asterisk, but begin with an asterisk! Otherwise commit messages that contain an asterisk are also included in the matched lines. So it should be:
git show-branch -a | awk -F'[]^~[]' '/^\*/ && !/'"$current_branch"'/ {print $2;exit}'
or the long version:
git show-branch -a |
awk '^\*' | # we want only lines that contain an asterisk
awk -v "$current_branch" | # but also don't contain the current branch
head -n1 | # and only the first such line
sed 's/.*\[\(.*\)\].*/\1/' | # really, just the part of the line between []
sed 's/[\^~].*//' # and with any relative refs (^, ~n) removed`