Subject: [PATCH] sha1_name: allow to add @{...} alias via config

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Wed, 8 Sep 2010 14:04:01 +1000

This allows users to add new @{..} alias via ref-at.* config
variables. The rewrite rule is printf-alike.

My itch is I usually work on a topic and only want to see commits in
that topic. So I make a tag to the topic's base, then do

git log base/my-topic..

That is a lot of keystrokes, and my mind is small enough sometimes I
don't even remember the topic name, stucking at "base/  what?"

Now I have "ref-at.base = base/%(tip)" in my gitconfig and I only need
to do "git log @{base}..".

This is probably not the best way to do though. Any advice?

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 cache.h     |    3 ++
 config.c    |    3 ++
 sha1_name.c |  100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 3 files changed, 101 insertions(+), 5 deletions(-)

diff --git a/cache.h b/cache.h
index be02a42..bad577d 100644
--- a/cache.h
+++ b/cache.h
@@ -1112,4 +1112,7 @@ extern struct startup_info *startup_info;
 /* builtin/merge.c */
 int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
 
+/* sha1_name.c */
+extern int git_ref_at_config(const char *var, const char *value);
+
 #endif /* CACHE_H */
diff --git a/config.c b/config.c
index cdcf583..b560f51 100644
--- a/config.c
+++ b/config.c
@@ -720,6 +720,9 @@ int git_default_config(const char *var, const char *value, void *dummy)
 	if (!prefixcmp(var, "advice."))
 		return git_default_advice_config(var, value);
 
+	if (!strncmp(var, "ref-at.", 7))
+		return git_ref_at_config(var, value);
+
 	if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
 		pager_use_color = git_config_bool(var,value);
 		return 0;
diff --git a/sha1_name.c b/sha1_name.c
index 7b7e617..c9987f4 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -7,6 +7,84 @@
 #include "refs.h"
 #include "remote.h"
 
+struct ref_at {
+	const char *mark;
+	const char *format;
+};
+
+static struct ref_at *ref_at;
+static int ref_at_nr;
+
+int git_ref_at_config(const char *var, const char *value)
+{
+	ref_at_nr++;
+	ref_at = xrealloc(ref_at, sizeof(*ref_at)*ref_at_nr);
+	ref_at[ref_at_nr-1].mark = xstrdup(var+7); /* ref-at. */
+	ref_at[ref_at_nr-1].format = xstrdup(value);
+	return 0;
+}
+
+static int substitute_ref_at(struct strbuf *sb, const char *name)
+{
+	int i;
+	const char *at = strchr(name, '@');
+	const char *p, *p2;
+
+	if (!at || at[1] != '{')
+		return -1;
+	for (i = 0; i < ref_at_nr; i++) {
+		int len = strlen(ref_at[i].mark);
+		if (!(at[len+2] == '}' && !strncmp(at+2, ref_at[i].mark, len)))
+			continue;
+
+		p = ref_at[i].format;
+		while (*p) {
+			if (*p != '%') {
+				strbuf_addch(sb, *p);
+				p++;
+				continue;
+			}
+			if (p[1] == '%') {
+				strbuf_addch(sb, '%');
+				p += 2;
+				continue;
+			}
+			if (p[1] != '(') {
+				error("ref-at.%s: '%%%c' not supported", ref_at[i].mark, p[1]);
+				return -1;
+			}
+			p += 2;
+			p2 = strchr(p, ')');
+			if (!p2)
+				return -1;
+			if (!strncmp(p, "branch", p2-p)) {
+				strbuf_add(sb, name, at-name);
+				p = p2+1;
+				continue;
+			}
+			if (!strncmp(p, "tip", p2-p)) {
+				if (at == name || !strncmp(name, "HEAD", at-name)) {
+					unsigned char sha1[20];
+					int flag;
+					const char *real_ref = resolve_ref("HEAD", sha1, 0, &flag);
+					if (!strncmp(real_ref, "refs/heads/", 11))
+						strbuf_addstr(sb, real_ref+11);
+					else
+						strbuf_addstr(sb, real_ref);
+				}
+				else
+					strbuf_add(sb, name, p2-p);
+				p = p2+1;
+				continue;
+			}
+			error("ref-at.%s: '%%(%.*s)' is not supported", ref_at[i].mark, p2-p, p);
+			return -1;
+		}
+		return 1;
+	}
+	return 0;
+}
+
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
 	struct alternate_object_database *alt;
@@ -346,6 +424,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	char *real_ref = NULL;
 	int refs_found = 0;
 	int at, reflog_len;
+	struct strbuf new_str = STRBUF_INIT;
 
 	if (len == 40 && !get_sha1_hex(str, sha1))
 		return 0;
@@ -355,6 +434,11 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	if (len && str[len-1] == '}') {
 		for (at = len-2; at >= 0; at--) {
 			if (str[at] == '@' && str[at+1] == '{') {
+				if (substitute_ref_at(&new_str, str) >= 0) {
+					str = new_str.buf;
+					len = new_str.len;
+					break;
+				}
 				if (!upstream_mark(str + at, len - at)) {
 					reflog_len = (len-1) - (at+2);
 					len = at;
@@ -366,7 +450,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 
 	/* Accept only unambiguous ref paths. */
 	if (len && ambiguous_path(str, len))
-		return -1;
+		goto failed;
 
 	if (!len && reflog_len) {
 		struct strbuf buf = STRBUF_INIT;
@@ -374,10 +458,11 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 		/* try the @{-N} syntax for n-th checkout */
 		ret = interpret_branch_name(str+at, &buf);
 		if (ret > 0) {
+			strbuf_release(&new_str);
 			/* substitute this branch name and restart */
 			return get_sha1_1(buf.buf, buf.len, sha1);
 		} else if (ret == 0) {
-			return -1;
+			goto failed;
 		}
 		/* allow "@{...}" to mean the current branch reflog */
 		refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
@@ -387,7 +472,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 		refs_found = dwim_ref(str, len, sha1, &real_ref);
 
 	if (!refs_found)
-		return -1;
+		goto failed;
 
 	if (warn_ambiguous_refs && refs_found > 1)
 		warning(warn_msg, len, str);
@@ -400,7 +485,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 
 		/* a @{-N} placed anywhere except the start is an error */
 		if (str[at+2] == '-')
-			return -1;
+			goto failed;
 
 		/* Is it asking for N-th entry, or approxidate? */
 		for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
@@ -421,7 +506,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 			at_time = approxidate_careful(tmp, &errors);
 			free(tmp);
 			if (errors)
-				return -1;
+				goto failed;
 		}
 		if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
 				&co_time, &co_tz, &co_cnt)) {
@@ -438,7 +523,12 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 	}
 
 	free(real_ref);
+	strbuf_release(&new_str);
 	return 0;
+
+failed:
+	strbuf_release(&new_str);
+	return -1;
 }
 
 static int get_parent(const char *name, int len,
-- 
1.7.1.rc1.69.g24c2f7