Re: [PATCH] for-each-ref: `:short` format for `refname`

5 messages, 3 authors, 2016-06-15 · open the first message on its own page

Re: [PATCH] for-each-ref: `:short` format for `refname`

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:45:16

"Bert Wesarg" [off-list ref] writes:
On Mon, Sep 1, 2008 at 21:10, Shawn O. Pearce [off-list ref] wrote:
...
quoted
You can still get ambiguous names.  Avoiding them requires going
through all refs and building their short forms, then using the
full ref name for any ref which had more than one name shorten to
the same string.  Ugly, but implementable, and probably something
that should be considered.
What about: try the list backwards until the first match, than try the
matched part (this what %.*s matched) with the forward list, if both
give the same pattern, its not disambiguous. If not try the next
pattern backwards.
How does it catch the case where you have both 'xyzzy' branch and 'xyzzy'
tag, which is the point of disambiguation issue Shawn raised?

Re: [PATCH] for-each-ref: `:short` format for `refname`

From: Bert Wesarg <hidden>
Date: 2016-06-15 22:45:16

On Mon, Sep 1, 2008 at 23:28, Junio C Hamano [off-list ref] wrote:
"Bert Wesarg" [off-list ref] writes:
quoted
On Mon, Sep 1, 2008 at 21:10, Shawn O. Pearce [off-list ref] wrote:
...
quoted
You can still get ambiguous names.  Avoiding them requires going
through all refs and building their short forms, then using the
full ref name for any ref which had more than one name shorten to
the same string.  Ugly, but implementable, and probably something
that should be considered.
What about: try the list backwards until the first match, than try the
matched part (this what %.*s matched) with the forward list, if both
give the same pattern, its not disambiguous. If not try the next
pattern backwards.
How does it catch the case where you have both 'xyzzy' branch and 'xyzzy'
tag, which is the point of disambiguation issue Shawn raised?
Right.

Re: [PATCH] for-each-ref: `:short` format for `refname`

From: Bert Wesarg <hidden>
Date: 2016-06-15 22:45:16

On Mon, Sep 1, 2008 at 23:44, Bert Wesarg [off-list ref] wrote:
On Mon, Sep 1, 2008 at 23:28, Junio C Hamano [off-list ref] wrote:
quoted
"Bert Wesarg" [off-list ref] writes:
quoted
On Mon, Sep 1, 2008 at 21:10, Shawn O. Pearce [off-list ref] wrote:
...
quoted
You can still get ambiguous names.  Avoiding them requires going
through all refs and building their short forms, then using the
full ref name for any ref which had more than one name shorten to
the same string.  Ugly, but implementable, and probably something
that should be considered.
What about: try the list backwards until the first match, than try the
matched part (this what %.*s matched) with the forward list, if both
give the same pattern, its not disambiguous. If not try the next
pattern backwards.
How does it catch the case where you have both 'xyzzy' branch and 'xyzzy'
tag, which is the point of disambiguation issue Shawn raised?
Right.
I was wrong:

given these two refs:

  refs/heads/xyzzy
  refs/tags/xyzzy

first try to shorten "refs/heads/xyzzy":

  first (from the end) matched pattern is "refs/heads/%.*s" with
"xyzzy" as result

  but resolved ref for "xyzzy" is "refs/tags/xyzzy" => continue

  next matched pattern is "%.*s" with "refs/heads/xyzzy" as result

  end result is therefore: "refs/heads/xyzzy"

second try to shorten "refs/tags/xyzzy":

  first (from the end) matched pattern is "refs/tags/%.*s" with
"xyzzy" as result

  resolved ref for "xyzzy" is "refs/tags/xyzzy" => end

  end result is therefore: "xyzzy"

the output would be:

  refs/heads/xyzzy
  xyzzy

The question is now, if this is usable for bash completion? Current
bash completion would handle this case wrong, because you get two
xyzzy.

Bert

Re: [PATCH] for-each-ref: `:short` format for `refname`

From: Shawn O. Pearce <hidden>
Date: 2016-06-15 22:45:16

Bert Wesarg [off-list ref] wrote:
given these two refs:

  refs/heads/xyzzy
  refs/tags/xyzzy

first try to shorten "refs/heads/xyzzy":

  first (from the end) matched pattern is "refs/heads/%.*s" with
"xyzzy" as result

  but resolved ref for "xyzzy" is "refs/tags/xyzzy" => continue

  next matched pattern is "%.*s" with "refs/heads/xyzzy" as result

  end result is therefore: "refs/heads/xyzzy"

second try to shorten "refs/tags/xyzzy":

  first (from the end) matched pattern is "refs/tags/%.*s" with
"xyzzy" as result

  resolved ref for "xyzzy" is "refs/tags/xyzzy" => end

  end result is therefore: "xyzzy"

the output would be:

  refs/heads/xyzzy
  xyzzy

The question is now, if this is usable for bash completion? Current
bash completion would handle this case wrong, because you get two
xyzzy.
I think this is reasonable.  Its better than what we have today,
which is ambiguous completion.  So this looks reasoanble to me.
Usually people don't have ambiguous names, but it happens.  I've
been known to do something stupid like this:

  git checkout -b v1.0 v1.0
  git reset --hard v1.2
  git log v1.0
  # wtf?!?!!?!

;-)

-- 
Shawn.

[PATCH v2] for-each-ref: `:short` format for `refname`

From: Bert Wesarg <hidden>
Date: 2016-06-15 22:45:17

Tries to shorten the refname to a non-ambiguous name.
I.e. the full and the short refname points to the same object.

Signed-off-by: Bert Wesarg <redacted>
Cc: git@vger.kernel.org
Cc: szeder@ira.uka.de
Cc: Shawn O. Pearce <redacted>
---
 Documentation/git-for-each-ref.txt |    2 +
 builtin-for-each-ref.c             |  133 ++++++++++++++++++++++++++++++++++--
 t/t6300-for-each-ref.sh            |   31 ++++++++
 3 files changed, 159 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index eae6c0e..89158d9 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -74,6 +74,8 @@ For all objects, the following names can be used:
 
 refname::
 	The name of the ref (the part after $GIT_DIR/).
+	For a non-ambiguous short name of the ref append `:short`.
+	I.e. both the full and short name will resolve to the same object.
 
 objecttype::
 	The type of the object (`blob`, `tree`, `commit`, `tag`).
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
index 21e92bb..6359ad7 100644
--- a/builtin-for-each-ref.c
+++ b/builtin-for-each-ref.c
@@ -546,6 +546,105 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v
 }
 
 /*
+ * generate a format suitable for scanf from a ref_rev_parse_rules
+ * rule, that is replace the "%.*s" spec with a "%s" spec
+ */
+static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
+{
+	char *spec;
+
+	spec = strstr(rule, "%.*s");
+	if (!spec || strstr(spec + 4, "%.*s"))
+		die("invalid rule in ref_rev_parse_rules: %s", rule);
+
+	/* copy all until spec */
+	strncpy(scanf_fmt, rule, spec - rule);
+	scanf_fmt[spec - rule] = '\0';
+	/* copy new spec */
+	strcat(scanf_fmt, "%s");
+	/* copy remaining rule */
+	strcat(scanf_fmt, spec + 4);
+
+	return;
+}
+
+/*
+ * Shorten the refname to an non-ambiguous form
+ */
+static char *get_short_ref(struct refinfo *ref)
+{
+	int i;
+	static char **scanf_fmts;
+	static int nr_rules;
+	char *short_name;
+	unsigned char fullref_sha1[20];
+
+	/* pre generate scanf formats from ref_rev_parse_rules[] */
+	if (!nr_rules) {
+		size_t total_len = 0;
+
+		/* the rule list is NULL terminated, count them first */
+		for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
+			/* no +1 because strlen("%s") < strlen("%.*s") */
+			total_len += strlen(ref_rev_parse_rules[nr_rules]);
+
+		scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
+
+		total_len = 0;
+		for (i = 0; i < nr_rules; i++) {
+			scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
+					+ total_len;
+			gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
+			total_len += strlen(ref_rev_parse_rules[i]);
+		}
+	}
+
+	/* bail out if there are no rules */
+	if (!nr_rules)
+		return ref->refname;
+
+	read_ref(ref->refname, fullref_sha1);
+
+	/* buffer for scanf result, at most ref->refname must fit */
+	short_name = strdup(ref->refname);
+
+	/* skip first rule, will always match */
+	for (i = 0; i < nr_rules - 1; i++) {
+		const char **p;
+		int short_name_len;
+
+		if (1 != sscanf(ref->refname, scanf_fmts[nr_rules - 1 - i],
+				short_name))
+			continue;
+
+		short_name_len = strlen(short_name);
+
+		/* check if full and short point to the same object
+		 * by checking all rules in forward direction
+		 */
+		for (p = ref_rev_parse_rules; *p; p++) {
+			unsigned char short_sha1[20];
+
+			/* check for valid ref */
+			if (read_ref(mkpath(*p, short_name_len, short_name),
+				     short_sha1))
+				continue;
+
+			/* if the objects differ the short name is ambiguous */
+			if (hashcmp(fullref_sha1, short_sha1))
+				break;
+
+			/* ok, short and full ref point to the same object */
+			return short_name;
+		}
+	}
+
+	free(short_name);
+	return ref->refname;
+}
+
+
+/*
  * Parse the object referred by ref, and grab needed value.
  */
 static void populate_value(struct refinfo *ref)
@@ -570,13 +669,33 @@ static void populate_value(struct refinfo *ref)
 	for (i = 0; i < used_atom_cnt; i++) {
 		const char *name = used_atom[i];
 		struct atom_value *v = &ref->value[i];
-		if (!strcmp(name, "refname"))
-			v->s = ref->refname;
-		else if (!strcmp(name, "*refname")) {
-			int len = strlen(ref->refname);
-			char *s = xmalloc(len + 4);
-			sprintf(s, "%s^{}", ref->refname);
-			v->s = s;
+		int deref = 0;
+		if (*name == '*') {
+			deref = 1;
+			name++;
+		}
+		if (!prefixcmp(name, "refname")) {
+			const char *formatp = strchr(name, ':');
+			const char *refname = ref->refname;
+
+			/* look for "short" refname format */
+			if (formatp) {
+				formatp++;
+				if (!strcmp(formatp, "short"))
+					refname = get_short_ref(ref);
+				else
+					die("unknown refname format %s",
+					    formatp);
+			}
+
+			if (!deref)
+				v->s = refname;
+			else {
+				int len = strlen(refname);
+				char *s = xmalloc(len + 4);
+				sprintf(s, "%s^{}", refname);
+				v->s = s;
+			}
 		}
 	}
 
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 8ced593..ad8d48e 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -262,6 +262,37 @@ for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
 	"
 done
 
+cat >expected <<\EOF
+master
+testtag
+EOF
+
+test_expect_success 'Check short refname format' '
+	(git for-each-ref --format="%(refname:short)" refs/heads &&
+	git for-each-ref --format="%(refname:short)" refs/tags) >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check for invalid refname format' '
+	test_must_fail git for-each-ref --format="%(refname:INVALID)"
+'
+
+cat >expected <<\EOF
+heads/master
+master
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs' '
+	git checkout -b newtag &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Branch" &&
+	setdate_and_increment &&
+	git tag -m "Tagging at $datestamp" master &&
+	git for-each-ref --format "%(refname:short)" refs/heads/master refs/tags/master >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'an unusual tag with an incomplete line' '
 
 	git tag -m "bogo" bogo &&
-- 
1.6.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help