Reputation: 12018
I wish to do what git log --no-walk --tags --decorate=short
does, but I also want to check for tags in a given range soley (commit range), not just all the tags.
Upvotes: 1
Views: 1706
Reputation: 488163
Edit: as jthill noted in a comment, git rev-list
(and hence git log
) has --simplify-by-decoration
, which discards commits that do not have a (branch or) tag-name pointing to them:
git log --simplify-by-decoration --decorate=short X..Y
may do just what you want. Unfortunately, testing shows that it also keeps commits that have a branch-name pointing to them. The documentation is not 100% clear on this:
--simplify-by-decoration
Commits that are referred by some branch or tag are selected.
but then later:
The
--simplify-by-decoration
option allows you to view only the big picture of the topology of the history, by omitting commits that are not referenced by tags. ...
Original answer below line (it should work).
OK, it sounds like you want to combine the effects of something like X..Y
with "select only commits that have a tag pointing to them".
There are two main Git commands for doing this kind of operation:
git for-each-ref
, which lets you work with reference names—any name from anywhere within the refs/
name-space; tags are in refs/tags/
.
git rev-list
, which is actually kind of the same command as git log
except that instead of showing commit messages by default, it shows hashes by default. (There are other minor differences, but they are so similar that both git log
and git rev-list
are built from one source file.)
The main problem you have here is that while git log
/ git rev-list
easily let you restrict which commit(s) are selected, with Y ^X
or X..Y
for one example, or with --no-walk --tags[=pattern]
for another, they're not very good at set intersection operations. They can directly do limited union style operations, e.g., git rev-list --no-walk --tags=foo\* --tags=bar\* --branches=baz\*
would print the ID of every commit tagged with a name that matches foo*
or bar*
, or is pointed-to by a branch name matching baz*
.
(The rev-list
command can do full, arbitrary set union operations using pipes and --stdin
, but not intersections. This is one area where Mercurial's syntax beats Git's. Of course Mercurial has the advantage of having all of Python to fall back onto, internally.)
What you want, though, is the intersection of two sets:
set 1: your range selection, probably something like X..Y
or --since=date1 --until=date2
set 2: every commit pointed-to by (some or all) tags
Computing this set intersection requires some code. On a Unix-like system we have all the tools we need: Git itself, and comm
, which can do the set intersection. The comm
utility needs its input to be sorted so we'll pipe each set through sort
here. So we just need a short (7 line) script.
We start with the usual temp file boilerplate, e.g.:
tf1=$(mktemp) || die "can't make first temp file"
trap "rm -f $tf1" 0 1 2 3 15
tf2=$(mktemp) || die "can't make second temp file"
trap "rm -f $tf1 $tf2" 0 1 2 3 15
Now we get set 1 using git rev-list
:
git rev-list <specifiers> | sort > $tf1
Then we get set 2, using git for-each-ref
. Assuming we want annotated tags to resolve to their objects (usually a commit, but maybe another annotated tag, but if it's another annotated tag we'll let that tag resolve on its own), we get set 2 with:
git for-each-ref --format='%(object)' refs/tags | sort > $tf2
Note that this is %(object)
, not %(objectname)
; the latter would only get us the ID of the annotated tag object.
Finally, we want only commit IDs that are in both temp files, i.e., we want comm -12
which suppresses all but column 3 (lines that are in both files):
comm -12 $tf1 $tf2
The output of this script is the set of commits to show (in an order determined by sorting their commit hashes, so we'll rely on git log
to fix the sort order). Now we just use your original git log
command, but run it on the selected commits:
git log --no-walk --decorate=short --date-order $(script)
(this is all untested of course).
If git rev-list
had a --stdin-intersect
option we could do all this within Git and the shell:
git log --no-walk --decorate=short $( \
git for-each-ref --format='%(object)' refs/tags | \
git rev-list --stdin-intersect X..Y \
)
but it doesn't, hence the need for the little script.
Upvotes: 2