Reputation: 5085
Given a branch and commit, I want to find the first tag that brought in the given commit.
I want to write a script that would do this.
I do not want to print all tags (which git log --tags --simplify-by-decoration
does) but only the tags that appear between the tip of branch and the commit. I can use the --merged
and --contains
options of git tag
command for that but it prints tag sorted by names. I need them sorted in the way they appear in the graph (so I can just do | tail -1
) and I cannot do that by sorting them by fieldnames as authordate
, committerdate
, creatordate
, taggerdate
.
To give more context, the script is the counterpart to this SO thread of git-find-merge
just that using merge commits to denote PRD code is an anti-pattern.
Update:
Below is the script I came up with eventually from torek's answer (github link: git-find-tag script).
~/bin/git-find-tag:
#!/bin/sh
commit=$1
if [ -z $commit ]; then
echo 1>&2 "fatal: commit is required"
exit 1
fi
commit=$(git rev-parse $commit)
branch=${2-@}
pattern=${3-.}
# tags between branch and commit sorted as in graph following a pattern
tags=$(
git log --decorate --simplify-by-decoration --ancestry-path \
--pretty="format:%D" $commit..$branch \
| sed 's/, /\n/g' \
| grep '^tag: ' \
| sed 's/tag: //' \
| egrep "$pattern"
)
if [ ! -z "$tags" ]; then
echo "tags:"
for tag in $tags; do
echo " $tag"
done
echo ""
fi
tag=$(echo "$tags" | tail -1)
if [ -z "$tag" ]; then
# tag not found
echo 1>&2 "fatal: no tag found"
exit 1
fi
git show -s "$tag"
Upvotes: 2
Views: 1530
Reputation: 488213
It seems that git log
already does what you want:
git log --oneline --decorate --simplify-by-decoration --ancestry-path start..end
For instance, I ran this on the Git repository for Git:
$ git log --oneline --decorate --simplify-by-decoration --ancestry-path \
HEAD~20..HEAD
b06d36431 (HEAD -> master, tag: v2.13.0, origin/master, origin/HEAD) Git 2.13
4fa66c85f (tag: v2.13.0-rc2) Git 2.13-rc2
027a3b943 (tag: v2.13.0-rc1) Git 2.13-rc1
(note that there is no --tags
here!). See the details below for a caveat.
The problem is not completely solveable in general as there's no strictly defined order in which the graph will be emitted. We know (from the documentation) that --graph
turns on --topo-sort
; but there are graphs in which two different topological sorts are both valid, such as a classic diamond:
... newer / child commits
|
D
/ \
B C
\ /
A older / parent commits
|
...
The two valid orders here are (D, B, C, A) and (D, C, B, A) and we don't know (unless we cheat1) which order git log
will use. If all but A
are tagged, the last one will be one of (B, C), but we don't know which one.
If you can be sure there are no ambiguous sorts, VonC's suggestion of --contains
, or a more direct git merge-base --is-ancestor
test, can determine whether any given tagged commit is an ancestor of any other tagged commit. The definition of "topological order" is "never show children after showing parent", i.e., whatever is "most parent-y" is the one coming out last.
If not, and you want to match what git log
will do, you will need to use git log
. Since it already does what you want, though, just use it. :-) You would like it to:
--simplify-by-decoration
does the job, more or less, though it will also include branch names (so beware of just using | tail -1
: you need a bit more sophistication so as to take the last tag—but you can just pipe through grep tag:
along the way).end
) and after some starting point (^start
). Note that if you want to include the starting point itself, add the ^@
suffix, which means "all parents of the commit, but not the commit itself".start
commit: --ancestry-path
does the trick, by excluding commits reachable from end
that are not already excluded by ^start
.We spell ^start end
as the more familiar start..end
here, although either way works.
1The source to git log
will (with a lot of difficulty) tell you which sort actually occurs in these cases.
Upvotes: 4