[PATCH] describe: Break annotated tag ties by tagger date

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]