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/developIf 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) → masterM, N, O (branch feature/a) → developS, T, U (branch feature/c) → developP, Q, R (branch feature/b) → feature/aJ, 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`