Re: [PATCH] name-rev: Fix non-shortest description

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

 



On Mon, Aug 27, 2007 at 12:37:33PM +0100, Johannes Schindelin wrote:

> As an easy way to fix it, use a weighting factor for merge traversals:
> A merge traversal is now made 65535 times more expensive than a
> first-parent traversal.

That's a clever trick, and I think it should work in most cases as long
as there are fewer than 65535 generations (though I haven't really
thought hard about it).

Here's my alternate approach which builds a stack of merge traversals,
and let's you compare two stacks (it uses the same criteria as the
original, but now we have the other generational information accessible
for comparison).

It comes up with the answer Uwe expects for his problem commit, and I
think in general is the "right" way to do this. Certainly the code looks
much clearer to me, but that's probably quite subjective. :) And it
should open up the door to alternative comparison mechanisms (Uwe had
mentioned an "oldest" flag, which should be possible, though there might
be assumptions in the name_rev recursion that need to be tweaked).

-- >8 --
name-rev: keep track of all generations, not just latest

Previously, name-rev found the "best" name for a rev by
choosing the one with the fewest number of merge traversals
and first-parent generations. This led to problems when
comparing a tip_name of "foo~1000" to "bar~500"; the
generational numbers were never compared.

This would manifest itself as choosing a non-optimal path to
a particular rev. For example, in the linux-2.6 repo, commit
0567a0c022d5b343370a343121f38fd89925de55 was named as:

  tags/v2.6.22~1686^2~1^3~5

when it could be:

  tags/v2.6.22-rc1~1009^2~1^3~5

To address this, when we follow a non-first-parent merge,
instead of turning the generation number into a string
(which we cease to compare), we keep a stack of merges, each
with a parent number and a generation.

The two tags above would be represented as (respectively):

tags/v2.6.22, (1/1686, 2/1, 3/5)
tags/v2.6.22-rc1, (1/1009, 2/1, 3/5)

Given these two data structures, we can decide which one is
a better name by comparing the number of merge traversals,
and the generational length of each traversal.
---
 builtin-name-rev.c |  183 +++++++++++++++++++++++++++------------------------
 1 files changed, 97 insertions(+), 86 deletions(-)

diff --git a/builtin-name-rev.c b/builtin-name-rev.c
index 9830595..099d65b 100644
--- a/builtin-name-rev.c
+++ b/builtin-name-rev.c
@@ -9,17 +9,37 @@
 static const char name_rev_usage[] =
 	"git-name-rev [--tags | --refs=<pattern>] ( --all | --stdin | committish [committish...] )\n";
 
+#define MAX_GENERATIONS 32
 typedef struct rev_name {
-	const char *tip_name;
-	int merge_traversals;
-	int generation;
+	const char *ref;
+	struct {
+	       unsigned char parent;
+	       unsigned long n;
+	} generations[MAX_GENERATIONS];
+	int len;
 } rev_name;
 
 static long cutoff = LONG_MAX;
 
-static void name_rev(struct commit *commit,
-		const char *tip_name, int merge_traversals, int generation,
-		int deref)
+static void print_rev_name(struct rev_name *name);
+
+static int rev_name_cmp(rev_name *a, rev_name *b)
+{
+	int i;
+	if (a->len < b->len)
+		return -1;
+	else if (a->len > b->len)
+		return 1;
+	for (i = 0; i < a->len; i++) {
+		if (a->generations[i].n < b->generations[i].n)
+			return -1;
+		else if (a->generations[i].n > b->generations[i].n)
+			return 1;
+	}
+	return 0;
+}
+
+static void name_rev(struct commit *commit, rev_name *cur)
 {
 	struct rev_name *name = (struct rev_name *)commit->util;
 	struct commit_list *parents;
@@ -31,61 +51,39 @@ static void name_rev(struct commit *commit,
 	if (commit->date < cutoff)
 		return;
 
-	if (deref) {
-		char *new_name = xmalloc(strlen(tip_name)+3);
-		strcpy(new_name, tip_name);
-		strcat(new_name, "^0");
-		tip_name = new_name;
-
-		if (generation)
-			die("generation: %d, but deref?", generation);
-	}
-
-	printf("considering %s~%d\n", tip_name, generation);
 	if (name == NULL) {
 		name = xmalloc(sizeof(rev_name));
 		commit->util = name;
-		printf("  better than NULL\n");
-		goto copy_data;
-	} else if (name->merge_traversals > merge_traversals ||
-			(name->merge_traversals == merge_traversals &&
-			 name->generation > generation)) {
-		printf("  better than %s~%d\n",
-				name->tip_name, name->generation);
-copy_data:
-		name->tip_name = tip_name;
-		name->merge_traversals = merge_traversals;
-		name->generation = generation;
-	} else {
-		printf("  not as good as %s~%d\n",
-				name->tip_name, name->generation);
-		return;
+		memcpy(name, cur, sizeof(*name));
 	}
+	else if (rev_name_cmp(cur, name) < 0)
+		memcpy(name, cur, sizeof(*name));
+	else
+		return;
 
 	for (parents = commit->parents;
 			parents;
 			parents = parents->next, parent_number++) {
 		if (parent_number > 1) {
-			int len = strlen(tip_name);
-			char *new_name = xmalloc(len +
-				1 + decimal_length(generation) +  /* ~<n> */
-				1 + 2 +				  /* ^NN */
-				1);
-
-			if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
-				len -= 2;
-			if (generation > 0)
-				sprintf(new_name, "%.*s~%d^%d", len, tip_name,
-						generation, parent_number);
-			else
-				sprintf(new_name, "%.*s^%d", len, tip_name,
-						parent_number);
-
-			name_rev(parents->item, new_name,
-				merge_traversals + 1 , 0, 0);
-		} else {
-			name_rev(parents->item, tip_name, merge_traversals,
-				generation + 1, 0);
+			if (cur->len == MAX_GENERATIONS)
+				return;
+			cur->generations[cur->len].parent = parent_number;
+			cur->generations[cur->len].n = 0;
+			cur->len++;
+			name_rev(parents->item, cur);
+			cur->len--;
+		}
+		else if (cur->len == 0) {
+			cur->generations[0].parent = 1;
+			cur->generations[0].n = 1;
+			cur->len = 1;
+			name_rev(parents->item, cur);
+			cur->len = 0;
+		}
+		else {
+			cur->generations[cur->len-1].n++;
+			name_rev(parents->item, cur);
+			cur->generations[cur->len-1].n--;
 		}
 	}
 }
@@ -101,6 +99,7 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
 	struct object *o = parse_object(sha1);
 	struct name_ref_data *data = cb_data;
 	int deref = 0;
+	struct rev_name name;
 
 	if (data->tags_only && prefixcmp(path, "refs/tags/"))
 		return 0;
@@ -127,38 +126,51 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
 		else if (!prefixcmp(path, "refs/"))
 			path = path + 5;
 
-		name_rev(commit, xstrdup(path), 0, 0, deref);
+		name.ref = xstrdup(path);
+		if (deref) {
+			name.generations[0].parent = 0;
+			name.generations[0].n = 0;
+			name.len = 1;
+		}
+		else
+			name.len = 0;
+		name_rev(commit, &name);
 	}
 	return 0;
 }
 
-/* returns a static buffer */
-static const char* get_rev_name(struct object *o)
+static struct rev_name *get_rev_name(struct object *o)
 {
-	static char buffer[1024];
-	struct rev_name *n;
 	struct commit *c;
-
 	if (o->type != OBJ_COMMIT)
-		return "undefined";
+		return NULL;
 	c = (struct commit *) o;
-	n = c->util;
-	if (!n)
-		return "undefined";
-
-	if (!n->generation)
-		return n->tip_name;
-	else {
-		int len = strlen(n->tip_name);
-		if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
-			len -= 2;
-		snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name,
-				n->generation);
-
-		return buffer;
+	return c->util;
+}
+
+static void print_rev_name(struct rev_name *name)
+{
+	int i;
+
+	if (!name) {
+		fputs("undefined", stdout);
+		return;
+	}
+
+	fputs(name->ref, stdout);
+	for (i = 0; i < name->len; i++) {
+		if (name->generations[i].parent != 1)
+			printf("^%d", name->generations[i].parent);
+		if (name->generations[i].n > 0)
+			printf("~%lu", name->generations[i].n);
 	}
 }
 
+static void print_object_name(struct object *o)
+{
+	print_rev_name(get_rev_name(o));
+}
+
 int cmd_name_rev(int argc, const char **argv, const char *prefix)
 {
 	struct object_array revs = { 0, 0, NULL };
@@ -246,26 +258,23 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
 				else if (++forty == 40 &&
 						!ishex(*(p+1))) {
 					unsigned char sha1[40];
-					const char *name = "undefined";
 					char c = *(p+1);
+					struct object *o;
 
 					forty = 0;
 
 					*(p+1) = 0;
-					if (!get_sha1(p - 39, sha1)) {
-						struct object *o =
-							lookup_object(sha1);
-						if (o)
-							name = get_rev_name(o);
-					}
+					if (!get_sha1(p - 39, sha1))
+						o = lookup_object(sha1);
 					*(p+1) = c;
 
-					if (!strcmp(name, "undefined"))
-						continue;
-
 					fwrite(p_start, p - p_start + 1, 1,
 					       stdout);
-					printf(" (%s)", name);
+					if (get_rev_name(o)) {
+						printf(" (");
+						print_object_name(o);
+						printf(" )");
+					}
 					p_start = p + 1;
 				}
 			}
@@ -284,14 +293,16 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
 				continue;
 			if (!data.name_only)
 				printf("%s ", sha1_to_hex(obj->sha1));
-			printf("%s\n", get_rev_name(obj));
+			print_object_name(obj);
+			printf("\n");
 		}
 	} else {
 		int i;
 		for (i = 0; i < revs.nr; i++) {
 			if (!data.name_only)
 				printf("%s ", revs.objects[i].name);
-			printf("%s\n", get_rev_name(revs.objects[i].item));
+			print_object_name(revs.objects[i].item);
+			printf("\n");
 		}
 	}
 
-- 
1.5.3.rc6.845.g365da-dirty

-
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]

  Powered by Linux