If more than one annotated tag points at the same commit, use the tag whose tagger field has a more recent date stamp. This resolves non-deterministic cases where the maintainer has done: $ git tag -a -m "2.1-rc1" v2.1-rc1 deadbeef $ git tag -a -m "2.1" v2.1 deadbeef If the tag is an older-style annotated tag with no tagger date, we assume a date stamp of 1 second after the UNIX epoch. This will cause us to prefer an annotated tag that has a valid date, or to simply avoid scanning the tag object again if a 3rd tag was found for the same commit. We could also try to consider the tag object chain, favoring a tag that "includes" another one: $ git tag -a -m "2.1-rc0" v2.1-rc1 deadbeef $ git tag -a -m "2.1" v2.1 v2.1-rc1 However traversing the tag's object chain looking for inclusion is much more complicated. Its already very likely that even in these cases the v2.1 tag will have a more recent tagger date than v2.1-rc1, so with this change describe should still resolve this by selecting the more recent v2.1. Signed-off-by: Shawn O. Pearce <spearce@xxxxxxxxxxx> --- builtin/describe.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++- t/t6120-describe.sh | 8 ++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/builtin/describe.c b/builtin/describe.c index 71be2a9..cf98664 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -36,6 +36,7 @@ static const char *diff_index_args[] = { struct commit_name { struct tag *tag; int prio; /* annotated tag = 2, tag = 1, head = 0 */ + unsigned long date; unsigned char sha1[20]; char path[FLEX_ARRAY]; /* more */ }; @@ -43,18 +44,74 @@ static const char *prio_names[] = { "head", "lightweight", "annotated", }; +static unsigned long tag_date(const unsigned char *sha1) +{ + enum object_type type; + unsigned long size; + char *buf; + int offset = 0; + unsigned long time = 1; + + buf = read_sha1_file(sha1, &type, &size); + if (buf && type == OBJ_TAG) { + while (offset < size && buf[offset] != '\n') { + int new_offset = offset + 1; + while (new_offset < size && buf[new_offset++] != '\n') + ; /* do nothing */ + if (!prefixcmp(buf + offset, "tagger ")) { + char *line = buf + offset + 7; + char *date; + + buf[new_offset] = '\0'; + date = strchr(line, '>'); + if (date) + time = strtoul(date + 1, NULL, 10); + break; + } + offset = new_offset; + } + } else + error("missing tag %s", sha1_to_hex(sha1)); + free(buf); + return time; +} + +static int replace_name(struct commit_name *e, + int prio, + const unsigned char *sha1, + unsigned long *date) +{ + if (!e || e->prio < prio) + return 1; + + if (e->prio == 2 && prio == 2) { + /* Multiple annotated tags point to the same commit. + * Select one to keep based upon their tagger date. + */ + if (!e->date) + e->date = tag_date(e->sha1); + *date = tag_date(sha1); + if (e->date < *date) + return 1; + } + + return 0; +} + static void add_to_known_names(const char *path, struct commit *commit, int prio, const unsigned char *sha1) { struct commit_name *e = commit->util; - if (!e || e->prio < prio) { + unsigned long date = 0; + if (replace_name(e, prio, sha1, &date)) { size_t len = strlen(path)+1; free(e); e = xmalloc(sizeof(struct commit_name) + len); e->tag = NULL; e->prio = prio; + e->date = date; hashcpy(e->sha1, sha1); memcpy(e->path, path, len); commit->util = e; diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 065dead..876d1ab 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -8,7 +8,7 @@ test_description='test describe o----o----o----o----o----. / \ A c / .------------o---o---o - D e + D,R e ' . ./test-lib.sh @@ -68,6 +68,8 @@ test_expect_success setup ' echo D >another && git add another && git commit -m D && test_tick && git tag -a -m D D && + test_tick && + git tag -a -m R R && test_tick && echo DD >another && git commit -a -m another && @@ -89,10 +91,10 @@ test_expect_success setup ' check_describe A-* HEAD check_describe A-* HEAD^ -check_describe D-* HEAD^^ +check_describe R-* HEAD^^ check_describe A-* HEAD^^2 check_describe B HEAD^^2^ -check_describe D-* HEAD^^^ +check_describe R-* HEAD^^^ check_describe c-* --tags HEAD check_describe c-* --tags HEAD^ -- 1.7.1.rc1.232.gc76b8 -- Shawn. -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html