Subject: [PATCH/RFC] get_sha1: allow users to extend ..^{%..} syntax

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Wed, 22 Dec 2010 22:33:46 +0700

This allows users to add abitrary sth^{%foo} syntax that translates
from an SHA-1 to another SHA-1, where "foo" refers to an alias
"sha1-foo" or "foo". If either is found, %sha1% in the alias will be
replaced with SHA-1 of "sth". The alias command is supposed to print
translated SHA-1 to stdout.

Another syntax is sth^{%foo:args} where %arg% in the alias will be
replaced with "args" from the syntax. This gives users much more
flexibilty in extending SHA-1 syntax. I'm not sure if I should support
multiple arguments though.

My mind is still with the :/ syntax. So as an example,

git config alias.sha1-topic 'rev-list --merges --grep=%arg% --max-count=1 %sha1%'
git rev-parse origin/pu^{%topic:nd/struct-pathspec}

would give me sha-1 of my topic. This is supposed to be faster than
standard :/ syntax because it only searches merges. I can also make it
more accurate to my liking by adjust --grep= freely. Unfortunately
git-rev-list takes ~1 sec to run. Maybe I can detect rev-list command
and run it internally without run_command().

The intention is that a similar syntax can be used for branch
translation too, i.e ref@{%...}.

One of the rising issues is that every time this syntax is evaluated,
external process will be run again. Some cache might help. I should
also take alias code out of git.c for reuse here.

So comments? (This patch looks bad, I know)

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 sha1_name.c |   87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 87 insertions(+), 0 deletions(-)

diff --git a/sha1_name.c b/sha1_name.c
index c5c59ce..6b40cec 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -6,6 +6,7 @@
 #include "tree-walk.h"
 #include "refs.h"
 #include "remote.h"
+#include "run-command.h"
 
 static int get_sha1_oneline(const char *, unsigned char *, struct commit_list *);
 
@@ -529,6 +530,86 @@ struct object *peel_to_type(const char *name, int namelen,
 	}
 }
 
+static int peel_alias_onion(const char *name, int len, const char *sp,
+			    unsigned char *sha1)
+{
+	int alias_len;
+	struct strbuf sb = STRBUF_INIT;
+	struct strbuf cmd = STRBUF_INIT;
+	struct child_process cp;
+	const char **argv;
+	int count, hex_len;
+	char *s, *arg;
+	char hex[40];
+
+	len--;			/* remove the last '}' */
+	arg = strchr(sp, ':');
+	if (arg)
+		alias_len = arg - sp;
+	else
+		alias_len = len - (sp - name);
+	strbuf_add(&sb, "sha1-", 5);
+	strbuf_add(&sb, sp, alias_len);
+	s = alias_lookup(sb.buf);
+	if (!s) {
+		strbuf_reset(&sb);
+		strbuf_add(&sb, sp, alias_len);
+		s = alias_lookup(sb.buf);
+	}
+	strbuf_release(&sb);
+	if (!s)
+		return error("unable to find alias for %s", name);
+
+	strbuf_attach(&cmd, s, strlen(s), strlen(s)+1);
+	s = strstr(cmd.buf, "%sha1%");
+	if (!s) {
+		error("%%sha1%% not found, not an sha1 alias:\n%s", cmd.buf);
+		strbuf_release(&cmd);
+		return -1;
+	}
+	strbuf_splice(&cmd, s - cmd.buf, 6, sha1_to_hex(sha1), 40);
+	if (arg) {
+		int arg_len = len - (++arg - name);
+		s = strstr(cmd.buf, "%arg%");
+		if (!s) {
+			error("%%arg%% not found, not an sha1 alias:\n%s", cmd.buf);
+			strbuf_release(&cmd);
+			return -1;
+		}
+		strbuf_splice(&cmd, s - cmd.buf, 5, arg, arg_len);
+	}
+
+	count = split_cmdline(cmd.buf, &argv);
+	if (count < 0) {
+		error("Bad alias.%s string: %s", cmd.buf, split_cmdline_strerror(count));
+		strbuf_release(&cmd);
+		return -1;
+	}
+
+	memset(&cp, 0, sizeof(cp));
+	cp.git_cmd = 1;
+	cp.in = 0;
+	cp.out = -1;
+	cp.argv = argv;
+	trace_argv_printf(argv, "trace: sha1 alias expansion: %s =>", cmd.buf);
+	if (start_command(&cp)) {
+		error("Failed to run %s", cmd.buf);
+		strbuf_release(&cmd);
+		return -1;
+	}
+	hex_len = xread(cp.out, hex, 40);
+	close(cp.out);
+	if (finish_command(&cp)) {
+		error("Failed to finish %s", cmd.buf);
+		strbuf_release(&cmd);
+		return -1;
+	}
+	strbuf_release(&cmd);
+	if (hex_len != 40 || get_sha1_hex(hex, sha1))
+		return error("failed to get result SHA-1 from ...%s", sp-3);
+	return 0;
+}
+
 static int peel_onion(const char *name, int len, unsigned char *sha1)
 {
 	unsigned char outer[20];
@@ -566,6 +647,12 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)
 		expected_type = OBJ_NONE;
 	else if (sp[0] == '/')
 		expected_type = OBJ_COMMIT;
+	else if (sp[0] == '%') {
+		if (get_sha1_1(name, sp - name - 2, outer))
+			return -1;
+		hashcpy(sha1, outer);
+		return peel_alias_onion(name, len, sp + 1, sha1);
+	}
 	else
 		return -1;
 
-- 
1.7.3.3.476.g10a82