{"thread":{"id":"26134","subject":"[PATCH 1/5] alias: add functions to do param substitution and alias running","startedAt":"2010-12-24T14:07:44Z","lastAt":"2010-12-24T14:07:49Z","messageCount":6,"participants":["Nguyễn Thái Ngọc Duy"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"158554","messageId":"1293199669-19016-1-git-send-email-pclouds@gmail.com","threadId":"26134","inReplyTo":null,"subject":"[PATCH 0/5] Custom extended SHA-1 syntax","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2010-12-24T14:07:44Z","receivedAt":"2010-12-24T14:07:44Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Merry Christmas!\n\nThis series introduces two new syntax: @{~foo} and ^{~foo}.\nBoth syntax allow an external program to run and return the result. So\nusers can extend the ref/sha1 syntax as they want. External commands are\ndefined similar to aliases in config.\n\nThe former does ref->ref mapping. The latter does SHA-1->SHA-1\nmapping. Both allow @{} and ^{} in the result respectively, so you can\nmake a chain of mapping (*). This should allow Kevin to add \"search\nthe n-th commit that matches 'foo'\" syntax.\n\nI think this is something nice to have. For one thing, commonly used\nmappings can be collected in config file and distributed. Shell requirements\ncan be loosened because you don't need shell backquotes\n(OK I'm always on a shell so this is moot, but an IDE, maybe).\n\nOK time for movies..\n\n(*) They can also make infinite chain. I don't think I can stop them\nfrom doing that.\n\nNguyễn Thái Ngọc Duy (5):\n  alias: add functions to do param substitution and alias running\n  get_sha1: allow custom SHA-1 mapping with $SHA1^{~alias} syntax\n  sha1_name: move interpret_nth_prior_checkout closer to\n    interpret_branch_name\n  interpret_branch_name: takes @{u} code out and reorder the function\n  get_sha1: allow custom ref mapping with $ref@{~alias} syntax\n\n Documentation/config.txt    |   18 +++\n Documentation/revisions.txt |   12 ++\n alias.c                     |  167 ++++++++++++++++++++++-\n cache.h                     |   13 ++\n sha1_name.c                 |  321 ++++++++++++++++++++++++++++++++----------\n t/t1511-rev-parse-caret.sh  |   45 ++++++\n t/t1512-rev-parse-at.sh     |   63 +++++++++\n 7 files changed, 557 insertions(+), 82 deletions(-)\n create mode 100755 t/t1512-rev-parse-at.sh\n\n-- \n1.7.3.3.476.g10a82\n"},{"id":"158553","messageId":"1293199669-19016-2-git-send-email-pclouds@gmail.com","threadId":"26134","inReplyTo":"1293199669-19016-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 1/5] alias: add functions to do param substitution and alias running","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2010-12-24T14:07:45Z","receivedAt":"2010-12-24T14:07:45Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"These functions help substitute %foo% in an alias command to real\nvalues, then run the alias and return the first line from stdout.\n\nThe normal use case is:\n\n    /* extract all %xxx% from cmd to params */\n    extract_alias_params(cmd, params);\n    param = lookup_alias_param(params, \"%foo%\");\n    if (param)\n        param->value = \"value for %foo%\";\n    param = lookup_alias_param(params, \"%bar%\");\n    if (param)\n        param->value = \"value for %bar%\";\n    /* substitute %foo% and %bar% */\n    expand_alias_params(cmd, params);\n    free_alias_params(params);\n    if (!get_alias_oneline(alias, cmd, output))\n        /* do something with output->buf here */\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n alias.c |  139 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n cache.h |   11 +++++\n 2 files changed, 150 insertions(+), 0 deletions(-)\n\ndiff --git a/alias.c b/alias.c\nindex eb9f08b..6626bb0 100644\n--- a/alias.c\n+++ b/alias.c\n@@ -1,4 +1,5 @@\n #include \"cache.h\"\n+#include \"run-command.h\"\n \n static const char *alias_key;\n static char *alias_val;\n@@ -85,3 +86,141 @@ int split_cmdline(char *cmdline, const char ***argv)\n const char *split_cmdline_strerror(int split_cmdline_errno) {\n \treturn split_cmdline_errors[-split_cmdline_errno-1];\n }\n+\n+int extract_alias_params(const char *cmd, struct alias_param **params)\n+{\n+\tconst char *s = cmd;\n+\tint nr_param = 0;\n+\n+\t*params = NULL;\n+\twhile (s && (s = strchr(s, '%')) != NULL) {\n+\t\tint len = strcspn(s+1, \"% \")+2;\n+\t\tif (len < 2 || s[len-1] != '%')\n+\t\t\treturn error(\"malformed parameter at %s\", s);\n+\t\tnr_param++;\n+\t\t*params = xrealloc(*params, sizeof(struct alias_param)*nr_param);\n+\t\t(*params)[nr_param-1].param = xstrndup(s, len);\n+\t\t(*params)[nr_param-1].pos = s - cmd;\n+\t\t(*params)[nr_param-1].value = NULL;\n+\t\ts += len;\n+\t}\n+\n+\tnr_param++;\n+\t*params = xrealloc(*params, sizeof(struct alias_param)*nr_param);\n+\t(*params)[nr_param-1].param = NULL;\n+\t(*params)[nr_param-1].value = NULL;\n+\treturn 0;\n+}\n+\n+struct alias_param *lookup_alias_param(struct alias_param *params, const char *param)\n+{\n+\tint i;\n+\tfor (i = 0; params[i].param; i++)\n+\t\tif (!strcmp(params[i].param, param))\n+\t\t\treturn params+i;\n+\treturn NULL;\n+}\n+\n+int expand_alias_params(struct strbuf *cmd, const struct alias_param *params)\n+{\n+\tint i, offset = 0;\n+\n+\t/* TODO: quote for '!' commands */\n+\tfor (i = 0; params[i].param; i++) {\n+\t\tif (!params[i].value)\n+\t\t\treturn error(\"param %s not substituted\", params[i].param);\n+\t\tstrbuf_splice(cmd,\n+\t\t\t      params[i].pos + offset, strlen(params[i].param),\n+\t\t\t      params[i].value, strlen(params[i].value));\n+\t\toffset += strlen(params[i].value) - strlen(params[i].param);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void free_alias_params(struct alias_param *params)\n+{\n+\tint i;\n+\tfor (i = 0; params[i].param; i++) {\n+\t\tfree(params[i].param);\n+\t\tfree(params[i].value);\n+\t}\n+\tfree(params);\n+}\n+\n+static void *wait_and_finish(void *arg)\n+{\n+\tstruct child_process *cp = arg;\n+\tchar buf[1024];\n+\twhile (xread(cp->out, buf, 1024) > 0)\n+\t\t;\n+\tclose(cp->out);\n+\tfinish_command(cp);\n+\tfree(cp->argv);\n+\tfree(cp);\n+\treturn (void *) (intptr_t) 0;\n+}\n+\n+static int start_support_alias(const char *alias, char *cmd, struct child_process **cpp)\n+{\n+\tstruct child_process *cp;\n+\tconst char **argv;\n+\tint count;\n+\n+\tcp = xmalloc(sizeof(struct child_process));\n+\tmemset(cp, 0, sizeof(struct child_process));\n+\tcp->in = 0;\n+\tcp->out = -1;\n+\n+\tif (cmd[0] == '!') {\n+\t\targv = xmalloc(sizeof(*argv)*4);\n+\t\targv[0] = \"/bin/sh\";\n+\t\targv[1] = \"-c\";\n+\t\targv[2] = cmd+1;\n+\t\targv[3] = NULL;\n+\t}\n+\telse {\n+\t\tcount = split_cmdline(cmd, &argv);\n+\t\tif (count < 0) {\n+\t\t\tfree(cp);\n+\t\t\treturn error(\"Bad alias %s: %s\", cmd, split_cmdline_strerror(count));\n+\t\t}\n+\t\tcp->git_cmd = 1;\n+\t}\n+\n+\tcp->argv = argv;\n+\tif (start_command(cp)) {\n+\t\terror(\"Failed to run %s\", cmd);\n+\t\tfree(cp);\n+\t\tfree(argv);\n+\t\treturn -1;\n+\t}\n+\t*cpp = cp;\n+\treturn 0;\n+}\n+\n+int get_alias_oneline(const char *alias, char *cmd, struct strbuf *ref)\n+{\n+\tstruct child_process *cp;\n+\tFILE *fp;\n+\tint ret;\n+\n+\tret = start_support_alias(alias, cmd, &cp);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tfp = fdopen(cp->out, \"r\");\n+\tret = strbuf_getline(ref, fp, '\\n');\n+\tif (!ret) {\n+\t\t/* let it finish, if there's error, users should know */\n+#ifdef NO_PTHREADS\n+\t\twait_and_finish(cp);\n+#else\n+\t\tpthread_t thread;\n+\t\tpthread_create(&thread, NULL, wait_and_finish, cp);\n+#endif\n+\t\treturn 0;\n+\t}\n+\twait_and_finish(cp);\n+\treturn -1;\n+}\ndiff --git a/cache.h b/cache.h\nindex e83bc2d..20a37ff 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1113,10 +1113,21 @@ extern int ws_blank_line(const char *line, int len, unsigned ws_rule);\n int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);\n void overlay_tree_on_cache(const char *tree_name, const char *prefix);\n \n+struct alias_param {\n+\tchar *param;\n+\tint pos;\n+\tchar *value;\n+};\n+\n char *alias_lookup(const char *alias);\n int split_cmdline(char *cmdline, const char ***argv);\n /* Takes a negative value returned by split_cmdline */\n const char *split_cmdline_strerror(int cmdline_errno);\n+int extract_alias_params(const char *cmd, struct alias_param **params);\n+struct alias_param *lookup_alias_param(struct alias_param *params, const char *param);\n+int expand_alias_params(struct strbuf *cmd, const struct alias_param *params);\n+void free_alias_params(struct alias_param *params);\n+int get_alias_oneline(const char *alias, char *cmd, struct strbuf *result);\n \n /* git.c */\n struct startup_info {\n-- \n1.7.3.3.476.g10a82\n"},{"id":"158556","messageId":"1293199669-19016-3-git-send-email-pclouds@gmail.com","threadId":"26134","inReplyTo":"1293199669-19016-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 2/5] get_sha1: allow custom SHA-1 mapping with $SHA1^{~alias} syntax","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2010-12-24T14:07:46Z","receivedAt":"2010-12-24T14:07:46Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"\"alias\" is defined in config as sha1.alias. %sha1% and %arg% in the\nalias command will be substituted.\n\nThe alias command is supposed to return a piece of text that\nget_sha1() can consume, preferably straight SHA-1.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/config.txt    |    9 ++++\n Documentation/revisions.txt |    7 +++\n alias.c                     |   23 +++++++++--\n cache.h                     |    1 +\n sha1_name.c                 |   86 +++++++++++++++++++++++++++++++++++++++++++\n t/t1511-rev-parse-caret.sh  |   45 ++++++++++++++++++++++\n 6 files changed, 166 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0f85793..0c4fb66 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -588,6 +588,15 @@ it will be treated as a shell command.  For example, defining\n executed from the top-level directory of a repository, which may\n not necessarily be the current directory.\n \n+sha1.*::\n+\tExtended SHA-1 syntax aliases. These aliases are similar to alias.*\n+\tbut each will correspond to a '{caret}\\{{tilde}alias\\}' syntax (see\n+\tlinkgit:gitrevisions[7]). '%sha1%' in the alias command will be\n+\tsubstituted with resolved SHA-1 before the caret. If\n+\t'{caret}\\{{tilde}alias:extra\\}' syntax is used, '%arg%' in the\n+\talias command will be substituted with 'extra'. The command is\n+\tsupposed to return an extended SHA-1 syntax.\n+\n am.keepcr::\n \tIf true, git-am will call git-mailsplit for patches in mbox format\n \twith parameter '--keep-cr'. In this case git-mailsplit will\ndiff --git a/Documentation/revisions.txt b/Documentation/revisions.txt\nindex 174fa8e..413f91c 100644\n--- a/Documentation/revisions.txt\n+++ b/Documentation/revisions.txt\n@@ -106,6 +106,13 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.\n   and dereference the tag recursively until a non-tag object is\n   found.\n \n+* A suffix '{caret}' followed by an SHA-1 alias with a leading\n+  '{tilde}', all enclosed in brace pair (e.g.\n+  `v0.998{caret}\\{{tilde}alias\\}`) will invoke the corresponding\n+  alias command, specified in config (see linkgit:git-config[1],\n+  key 'sha1.*'). The command is supposed to return an extended\n+  SHA-1.\n+\n * A suffix '{caret}' to a revision parameter followed by a brace\n   pair that contains a text led by a slash (e.g. `HEAD^{/fix nasty bug}`):\n   this is the same as `:/fix nasty bug` syntax below except that\ndiff --git a/alias.c b/alias.c\nindex 6626bb0..29a9903 100644\n--- a/alias.c\n+++ b/alias.c\n@@ -1,12 +1,12 @@\n #include \"cache.h\"\n #include \"run-command.h\"\n \n-static const char *alias_key;\n static char *alias_val;\n \n static int alias_lookup_cb(const char *k, const char *v, void *cb)\n {\n-\tif (!prefixcmp(k, \"alias.\") && !strcmp(k+6, alias_key)) {\n+\tconst char *key = cb;\n+\tif (!strcmp(k, key)) {\n \t\tif (!v)\n \t\t\treturn config_error_nonbool(k);\n \t\talias_val = xstrdup(v);\n@@ -15,14 +15,27 @@ static int alias_lookup_cb(const char *k, const char *v, void *cb)\n \treturn 0;\n }\n \n-char *alias_lookup(const char *alias)\n+static char *generic_alias_lookup(const char *alias, const char *namespase)\n {\n-\talias_key = alias;\n+\tstruct strbuf key = STRBUF_INIT;\n+\tstrbuf_addstr(&key, namespase);\n+\tstrbuf_addstr(&key, alias);\n \talias_val = NULL;\n-\tgit_config(alias_lookup_cb, NULL);\n+\tgit_config(alias_lookup_cb, key.buf);\n+\tstrbuf_release(&key);\n \treturn alias_val;\n }\n \n+char *alias_lookup(const char *alias)\n+{\n+\treturn generic_alias_lookup(alias, \"alias.\");\n+}\n+\n+char *sha1_alias_lookup(const char *alias)\n+{\n+\treturn generic_alias_lookup(alias, \"sha1.\");\n+}\n+\n #define SPLIT_CMDLINE_BAD_ENDING 1\n #define SPLIT_CMDLINE_UNCLOSED_QUOTE 2\n static const char *split_cmdline_errors[] = {\ndiff --git a/cache.h b/cache.h\nindex 20a37ff..56a1b18 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1120,6 +1120,7 @@ struct alias_param {\n };\n \n char *alias_lookup(const char *alias);\n+char *sha1_alias_lookup(const char *alias);\n int split_cmdline(char *cmdline, const char ***argv);\n /* Takes a negative value returned by split_cmdline */\n const char *split_cmdline_strerror(int cmdline_errno);\ndiff --git a/sha1_name.c b/sha1_name.c\nindex c5c59ce..3a98a50 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -529,6 +529,76 @@ struct object *peel_to_type(const char *name, int namelen,\n \t}\n }\n \n+/*\n+ * Handle $SHA1^{~alias} syntax. Config key sha1.alias will be used.\n+ * The following parameters are substituted:\n+ *\n+ *  - %sha1%  SHA-1 before ^{~alias}, required\n+ *  - %arg%   extra arguments after colon in SHA1^{~alias:args}\n+ *\n+ * The alias returns a string, preferabbly SHA-1.\n+ */\n+static int peel_alias(const char *name, int len, const char *sp,\n+\t\t      const unsigned char *input_sha1,\n+\t\t      struct strbuf *result)\n+{\n+\tstruct alias_param *params, *param;\n+\tstruct strbuf alias = STRBUF_INIT;\n+\tstruct strbuf cmd = STRBUF_INIT;\n+\tchar *s;\n+\tint ret, arg_len = 0;\n+\n+\tif (name[len-1] != '}')\n+\t\treturn -1;\n+\tlen -= sp - name + 1; /* remove $SHA1^{~ and } */\n+\tif (!len)\n+\t\treturn error(\"$SHA1^{~alias} syntax with no alias\");\n+\n+\ts = strchr(sp, ':');\n+\tif (s) {\n+\t\targ_len = (sp + len) - (s + 1);\n+\t\tlen = s - sp;\n+\t}\n+\tstrbuf_add(&alias, sp, len);\n+\ts = sha1_alias_lookup(alias.buf);\n+\tif (!s) {\n+\t\tret = error(\"unable to find SHA-1 alias '%s'\", alias.buf);\n+\t\tgoto done;\n+\t}\n+\tstrbuf_attach(&cmd, s, strlen(s), strlen(s)+1);\n+\n+\textract_alias_params(cmd.buf, &params);\n+\tparam = lookup_alias_param(params, \"%sha1%\");\n+\tif (!param) {\t\t/* %sha1% is mandatory */\n+\t\tret = error(\"%%sha1%% not found in alias '%s'\", alias.buf);\n+\t\tgoto done;\n+\t}\n+\tparam->value = xstrdup(sha1_to_hex(input_sha1));\n+\n+\tparam = lookup_alias_param(params, \"%arg%\");\n+\tif (param && arg_len)\n+\t\tparam->value = xstrndup(sp + len + 1,arg_len);\n+\telse if (param) {\n+\t\tret = error(\"Alias '%s' needs arguments\", alias.buf);\n+\t\tgoto done;\n+\t}\n+\telse if (arg_len) {\n+\t\tret = error(\"Arguments are given but alias '%s' does not need them\", alias.buf);\n+\t\tgoto done;\n+\t}\n+\n+\texpand_alias_params(&cmd, params);\n+\tfree_alias_params(params);\n+\n+\ttrace_printf(\"trace: sha1 alias expansion: %s => %s\\n\",\n+\t\t     alias.buf, cmd.buf);\n+\tret = get_alias_oneline(alias.buf, cmd.buf, result);\n+done:\n+\tstrbuf_release(&alias);\n+\tstrbuf_release(&cmd);\n+\treturn ret;\n+}\n+\n static int peel_onion(const char *name, int len, unsigned char *sha1)\n {\n \tunsigned char outer[20];\n@@ -566,6 +636,22 @@ static int peel_onion(const char *name, int len, unsigned char *sha1)\n \t\texpected_type = OBJ_NONE;\n \telse if (sp[0] == '/')\n \t\texpected_type = OBJ_COMMIT;\n+\telse if (sp[0] == '~') {\n+\t\tstruct strbuf new_name = STRBUF_INIT;\n+\n+\t\tif (get_sha1_1(name, sp - name - 2, outer))\n+\t\t\treturn -1;\n+\n+\t\tif (peel_alias(name, len, sp + 1, outer, &new_name))\n+\t\t\treturn -1;\n+\t\tif (get_sha1_1(new_name.buf, new_name.len, sha1)) {\n+\t\t\tstrbuf_release(&new_name);\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\tstrbuf_release(&new_name);\n+\t\treturn 0;\n+\t}\n \telse\n \t\treturn -1;\n \ndiff --git a/t/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh\nindex e043cb7..b99bfa2 100755\n--- a/t/t1511-rev-parse-caret.sh\n+++ b/t/t1511-rev-parse-caret.sh\n@@ -70,4 +70,49 @@ test_expect_success 'ref^{/Initial}' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'ref^{~}' '\n+\ttest_must_fail git rev-parse HEAD^{~}\n+'\n+\n+test_expect_success 'ref^{~non-existent}' '\n+\ttest_must_fail git rev-parse HEAD^{~non-existent}\n+'\n+\n+test_expect_success 'ref^{~simple}' '\n+\tgit config sha1.simple \"rev-parse %sha1%\" &&\n+\ttest_must_fail git rev-parse master^{~simple:something} &&\n+\tgit rev-parse master^{~simple} >actual &&\n+\tgit rev-parse master >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'ref^{~external}' '\n+\tgit config sha1.external \"!echo %sha1%\" &&\n+\ttest_must_fail git rev-parse master^{~external:something} &&\n+\tgit rev-parse master^{~external} >actual &&\n+\tgit rev-parse master >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'ref^{~witharg:args}' '\n+\tgit config sha1.witharg \"!echo %sha1%^{%arg%}\" &&\n+\ttest_must_fail git rev-parse master^{~witharg} &&\n+\tgit rev-parse master^{~witharg:tree} >actual &&\n+\tgit rev-parse master^{tree} >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'ref^{~simple}^{~external}' '\n+\tgit rev-parse master^{~simple}^{~external} >actual &&\n+\tgit rev-parse master >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'ref^{~mapping}' '\n+\tgit config sha1.mapping \"!echo %sha1%^{~simple}\" &&\n+\tgit rev-parse master^{~mapping} >actual &&\n+\tgit rev-parse master >expected &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\n-- \n1.7.3.3.476.g10a82\n"},{"id":"158555","messageId":"1293199669-19016-4-git-send-email-pclouds@gmail.com","threadId":"26134","inReplyTo":"1293199669-19016-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 3/5] sha1_name: move interpret_nth_prior_checkout closer to interpret_branch_name","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2010-12-24T14:07:47Z","receivedAt":"2010-12-24T14:07:47Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"interpret_nth_prior_checkout() is a subfunction of\ninterpret_branch_name(). So it's better to let them be close together\n(i.e. not letting get_sha1_mb() stays in between).\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n sha1_name.c |   84 +++++++++++++++++++++++++++++-----------------------------\n 1 files changed, 42 insertions(+), 42 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex 3a98a50..dcdf1e6 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -883,6 +883,48 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,\n \treturn 0;\n }\n \n+int get_sha1_mb(const char *name, unsigned char *sha1)\n+{\n+\tstruct commit *one, *two;\n+\tstruct commit_list *mbs;\n+\tunsigned char sha1_tmp[20];\n+\tconst char *dots;\n+\tint st;\n+\n+\tdots = strstr(name, \"...\");\n+\tif (!dots)\n+\t\treturn get_sha1(name, sha1);\n+\tif (dots == name)\n+\t\tst = get_sha1(\"HEAD\", sha1_tmp);\n+\telse {\n+\t\tstruct strbuf sb;\n+\t\tstrbuf_init(&sb, dots - name);\n+\t\tstrbuf_add(&sb, name, dots - name);\n+\t\tst = get_sha1(sb.buf, sha1_tmp);\n+\t\tstrbuf_release(&sb);\n+\t}\n+\tif (st)\n+\t\treturn st;\n+\tone = lookup_commit_reference_gently(sha1_tmp, 0);\n+\tif (!one)\n+\t\treturn -1;\n+\n+\tif (get_sha1(dots[3] ? (dots + 3) : \"HEAD\", sha1_tmp))\n+\t\treturn -1;\n+\ttwo = lookup_commit_reference_gently(sha1_tmp, 0);\n+\tif (!two)\n+\t\treturn -1;\n+\tmbs = get_merge_bases(one, two, 1);\n+\tif (!mbs || mbs->next)\n+\t\tst = -1;\n+\telse {\n+\t\tst = 0;\n+\t\thashcpy(sha1, mbs->item->object.sha1);\n+\t}\n+\tfree_commit_list(mbs);\n+\treturn st;\n+}\n+\n /*\n  * Parse @{-N} syntax, return the number of characters parsed\n  * if successful; otherwise signal an error with negative value.\n@@ -931,48 +973,6 @@ release_return:\n \treturn retval;\n }\n \n-int get_sha1_mb(const char *name, unsigned char *sha1)\n-{\n-\tstruct commit *one, *two;\n-\tstruct commit_list *mbs;\n-\tunsigned char sha1_tmp[20];\n-\tconst char *dots;\n-\tint st;\n-\n-\tdots = strstr(name, \"...\");\n-\tif (!dots)\n-\t\treturn get_sha1(name, sha1);\n-\tif (dots == name)\n-\t\tst = get_sha1(\"HEAD\", sha1_tmp);\n-\telse {\n-\t\tstruct strbuf sb;\n-\t\tstrbuf_init(&sb, dots - name);\n-\t\tstrbuf_add(&sb, name, dots - name);\n-\t\tst = get_sha1(sb.buf, sha1_tmp);\n-\t\tstrbuf_release(&sb);\n-\t}\n-\tif (st)\n-\t\treturn st;\n-\tone = lookup_commit_reference_gently(sha1_tmp, 0);\n-\tif (!one)\n-\t\treturn -1;\n-\n-\tif (get_sha1(dots[3] ? (dots + 3) : \"HEAD\", sha1_tmp))\n-\t\treturn -1;\n-\ttwo = lookup_commit_reference_gently(sha1_tmp, 0);\n-\tif (!two)\n-\t\treturn -1;\n-\tmbs = get_merge_bases(one, two, 1);\n-\tif (!mbs || mbs->next)\n-\t\tst = -1;\n-\telse {\n-\t\tst = 0;\n-\t\thashcpy(sha1, mbs->item->object.sha1);\n-\t}\n-\tfree_commit_list(mbs);\n-\treturn st;\n-}\n-\n /*\n  * This reads short-hand syntax that not only evaluates to a commit\n  * object name, but also can act as if the end user spelled the name\n-- \n1.7.3.3.476.g10a82\n"},{"id":"158557","messageId":"1293199669-19016-5-git-send-email-pclouds@gmail.com","threadId":"26134","inReplyTo":"1293199669-19016-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 4/5] interpret_branch_name: takes @{u} code out and reorder the function","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2010-12-24T14:07:48Z","receivedAt":"2010-12-24T14:07:48Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Previously interpret_branch_name() is structured as:\n\n - check for @{-N}\n - if there's still some more chars left, recurse to try again\n - check for @{upstream}\n\nNow it looks like\n\n - check for @{-N}\n - check for @{upstream}\n - still more? recurse\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n sha1_name.c |   99 ++++++++++++++++++++++++++++++++++-------------------------\n 1 files changed, 57 insertions(+), 42 deletions(-)\n\ndiff --git a/sha1_name.c b/sha1_name.c\nindex dcdf1e6..cdf14c7 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -973,6 +973,27 @@ release_return:\n \treturn retval;\n }\n \n+static int interpret_at_upstream(const char *at, struct strbuf *real_ref)\n+{\n+\tstruct branch *upstream;\n+\tint cplen = strlen(at);\n+\tint len;\n+\tchar *upstream_ref;\n+\n+\tlen = upstream_mark(at, cplen);\n+\tif (!len)\n+\t\treturn -1;\n+\tupstream = branch_get(real_ref->buf);\n+\tif (!upstream\n+\t    || !upstream->merge\n+\t    || !upstream->merge[0]->dst)\n+\t\treturn error(\"No upstream branch found for '%s'\", real_ref->buf);\n+\tupstream_ref = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);\n+\tcplen = strlen(upstream_ref);\n+\tstrbuf_attach(real_ref, upstream_ref, cplen, cplen+1);\n+\treturn len;\n+}\n+\n /*\n  * This reads short-hand syntax that not only evaluates to a commit\n  * object name, but also can act as if the end user spelled the name\n@@ -996,55 +1017,49 @@ release_return:\n  */\n int interpret_branch_name(const char *name, struct strbuf *buf)\n {\n-\tchar *cp;\n-\tstruct branch *upstream;\n \tint namelen = strlen(name);\n-\tint len = interpret_nth_prior_checkout(name, buf);\n-\tint tmp_len;\n-\n-\tif (!len)\n-\t\treturn len; /* syntax Ok, not enough switches */\n-\tif (0 < len && len == namelen)\n-\t\treturn len; /* consumed all */\n-\telse if (0 < len) {\n-\t\t/* we have extra data, which might need further processing */\n-\t\tstruct strbuf tmp = STRBUF_INIT;\n-\t\tint used = buf->len;\n-\t\tint ret;\n-\n-\t\tstrbuf_add(buf, name + len, namelen - len);\n-\t\tret = interpret_branch_name(buf->buf, &tmp);\n-\t\t/* that data was not interpreted, remove our cruft */\n-\t\tif (ret < 0) {\n-\t\t\tstrbuf_setlen(buf, used);\n-\t\t\treturn len;\n-\t\t}\n-\t\tstrbuf_reset(buf);\n-\t\tstrbuf_addbuf(buf, &tmp);\n-\t\tstrbuf_release(&tmp);\n-\t\t/* tweak for size of {-N} versus expanded ref name */\n-\t\treturn ret - used + len;\n-\t}\n+\tstruct strbuf tmp = STRBUF_INIT;\n+\tint ret, len = -1, used = 0;\n+\tconst char *cp;\n \n \tcp = strchr(name, '@');\n \tif (!cp)\n \t\treturn -1;\n-\ttmp_len = upstream_mark(cp, namelen - (cp - name));\n-\tif (!tmp_len)\n+\tif (cp == name)\n+\t\tlen = interpret_nth_prior_checkout(cp, buf);\n+\telse {\n+\t\tstrbuf_reset(buf);\n+\t\tstrbuf_add(buf, name, cp - name);\n+\t\tused += cp - name;\n+\t}\n+\n+\tif (len == -1)\n+\t\tlen = interpret_at_upstream(cp, buf);\n+\tif (len == -1) {\n+\t\tstrbuf_reset(buf);\n \t\treturn -1;\n-\tlen = cp + tmp_len - name;\n-\tcp = xstrndup(name, cp - name);\n-\tupstream = branch_get(*cp ? cp : NULL);\n-\tif (!upstream\n-\t    || !upstream->merge\n-\t    || !upstream->merge[0]->dst)\n-\t\treturn error(\"No upstream branch found for '%s'\", cp);\n-\tfree(cp);\n-\tcp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);\n+\t}\n+\n+\tif (!len)\n+\t\treturn len; /* syntax Ok, not enough switches */\n+\n+\tused += len;\n+\tif (used == namelen)\n+\t\treturn used; /* consumed all */\n+\n+\t/* we have extra data, which might need further processing */\n+\tlen = buf->len;\n+\tstrbuf_add(buf, name + used, namelen - used);\n+\tret = interpret_branch_name(buf->buf, &tmp);\n+\t/* that data was not interpreted, remove our cruft */\n+\tif (ret < 0) {\n+\t\tstrbuf_setlen(buf, len);\n+\t\treturn used;\n+\t}\n \tstrbuf_reset(buf);\n-\tstrbuf_addstr(buf, cp);\n-\tfree(cp);\n-\treturn len;\n+\tstrbuf_addbuf(buf, &tmp);\n+\tstrbuf_release(&tmp);\n+\treturn used + (ret - len);\n }\n \n int strbuf_branchname(struct strbuf *sb, const char *name)\n-- \n1.7.3.3.476.g10a82\n"},{"id":"158558","messageId":"1293199669-19016-6-git-send-email-pclouds@gmail.com","threadId":"26134","inReplyTo":"1293199669-19016-1-git-send-email-pclouds@gmail.com","subject":"[PATCH 5/5] get_sha1: allow custom ref mapping with $ref@{~alias} syntax","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2010-12-24T14:07:49Z","receivedAt":"2010-12-24T14:07:49Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"Similar to $SHA1^{~alias}, the alias is defined as ref.alias. It is\nsupposed to return something that can resolve to a ref.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Documentation/config.txt    |    9 +++++\n Documentation/revisions.txt |    5 +++\n alias.c                     |    5 +++\n cache.h                     |    1 +\n sha1_name.c                 |   70 +++++++++++++++++++++++++++++++++++++++++-\n t/t1512-rev-parse-at.sh     |   63 ++++++++++++++++++++++++++++++++++++++\n 6 files changed, 151 insertions(+), 2 deletions(-)\n create mode 100755 t/t1512-rev-parse-at.sh\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 0c4fb66..a20b467 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -597,6 +597,15 @@ sha1.*::\n \talias command will be substituted with 'extra'. The command is\n \tsupposed to return an extended SHA-1 syntax.\n \n+ref.*::\n+\tRef translation aliases. These aliases are similar to alias.*\n+\tbut each will correspond to a '@\\{{tilde}alias\\}' syntax (see\n+\tlinkgit:gitrevisions[7]). '%ref%' in the alias command will be\n+\tsubstituted with the ref before the '@'. If\n+\t'@\\{{tilde}alias:extra\\}' syntax is used, '%arg%' in the alias\n+\tcommand will be substituted with 'extra'. The command is supposed\n+\tto return a ref.\n+\n am.keepcr::\n \tIf true, git-am will call git-mailsplit for patches in mbox format\n \twith parameter '--keep-cr'. In this case git-mailsplit will\ndiff --git a/Documentation/revisions.txt b/Documentation/revisions.txt\nindex 413f91c..86b0b63 100644\n--- a/Documentation/revisions.txt\n+++ b/Documentation/revisions.txt\n@@ -80,6 +80,11 @@ the `$GIT_DIR/refs` directory or from the `$GIT_DIR/packed-refs` file.\n   the branch the ref is set to build on top of.  Missing ref defaults\n   to the current branch.\n \n+* A ref followed by '@\\{{tilde}alias\\}' will invoke the corresponding\n+  alias command, specified in config (see linkgit:git-config[1],\n+  key 'ref.*'). The command is supposed to return a ref. The\n+  result ref can contain '@\\{..\\}' syntax.\n+\n * A suffix '{caret}' to a revision parameter (e.g. 'HEAD{caret}') means the first parent of\n   that commit object.  '{caret}<n>' means the <n>th parent (i.e.\n   'rev{caret}'\ndiff --git a/alias.c b/alias.c\nindex 29a9903..242401e 100644\n--- a/alias.c\n+++ b/alias.c\n@@ -36,6 +36,11 @@ char *sha1_alias_lookup(const char *alias)\n \treturn generic_alias_lookup(alias, \"sha1.\");\n }\n \n+char *ref_alias_lookup(const char *alias)\n+{\n+\treturn generic_alias_lookup(alias, \"ref.\");\n+}\n+\n #define SPLIT_CMDLINE_BAD_ENDING 1\n #define SPLIT_CMDLINE_UNCLOSED_QUOTE 2\n static const char *split_cmdline_errors[] = {\ndiff --git a/cache.h b/cache.h\nindex 56a1b18..1d4ce65 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -1121,6 +1121,7 @@ struct alias_param {\n \n char *alias_lookup(const char *alias);\n char *sha1_alias_lookup(const char *alias);\n+char *ref_alias_lookup(const char *alias);\n int split_cmdline(char *cmdline, const char ***argv);\n /* Takes a negative value returned by split_cmdline */\n const char *split_cmdline_strerror(int cmdline_errno);\ndiff --git a/sha1_name.c b/sha1_name.c\nindex cdf14c7..2734f24 100644\n--- a/sha1_name.c\n+++ b/sha1_name.c\n@@ -359,7 +359,8 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)\n \tif (len && str[len-1] == '}') {\n \t\tfor (at = len-2; at >= 0; at--) {\n \t\t\tif (str[at] == '@' && str[at+1] == '{') {\n-\t\t\t\tif (!upstream_mark(str + at, len - at)) {\n+\t\t\t\tif (!upstream_mark(str + at, len - at) &&\n+\t\t\t\t    str[at+2] != '~') {\n \t\t\t\t\treflog_len = (len-1) - (at+2);\n \t\t\t\t\tlen = at;\n \t\t\t\t}\n@@ -994,6 +995,69 @@ static int interpret_at_upstream(const char *at, struct strbuf *real_ref)\n \treturn len;\n }\n \n+static int interpret_at_alias(const char *at, struct strbuf *real_ref)\n+{\n+\tstruct alias_param *params, *param;\n+\tstruct strbuf alias = STRBUF_INIT;\n+\tstruct strbuf cmd = STRBUF_INIT;\n+\tchar *s;\n+\tint ret, len, arg_len = 0;\n+\n+\tif (at[1] != '{' || at[2] != '~')\n+\t\treturn -1;\n+\tlen = strcspn(at + 3, \":}\");\n+\tif (!len)\n+\t\treturn error(\"$ref@{~alias} syntax with no alias\");\n+\tif (at[3 + len] == ':') {\n+\t\ts = strchr(at + 3 + len + 1, '}');\n+\t\tif (!s)\n+\t\t\treturn error(\"$ref@{~alias} does not end with }\");\n+\t\targ_len = s - at - (3 + len + 1);\n+\t}\n+\tstrbuf_add(&alias, at + 3, len);\n+\ts = ref_alias_lookup(alias.buf);\n+\tif (!s) {\n+\t\tret = error(\"unable to find ref alias '%s'\", alias.buf);\n+\t\tgoto done;\n+\t}\n+\n+\tstrbuf_attach(&cmd, s, strlen(s), strlen(s)+1);\n+\textract_alias_params(cmd.buf, &params);\n+\tparam = lookup_alias_param(params, \"%ref%\");\n+\tif (param && real_ref->len)\n+\t\tparam->value = xstrdup(real_ref->buf);\n+\telse if (param) {\n+\t\tret = error(\"%%ref%% not found in alias '%s'\", cmd.buf);\n+\t\tgoto done;\n+\t}\n+\telse if (real_ref->len) {\n+\t\tret = error(\"%%ref%% is required but alias '%s' is not followed by a ref\", cmd.buf);\n+\t\tgoto done;\n+\t}\n+\tparam = lookup_alias_param(params, \"%arg%\");\n+\tif (param && arg_len) {\n+\t\tparam->value = xstrndup(at + len + 4, arg_len);\n+\t\tlen += arg_len + 1;\n+\t}\n+\telse if (param) {\n+\t\tret = error(\"Alias '%s' needs arguments\", alias.buf);\n+\t\tgoto done;\n+\t}\n+\telse if (arg_len) {\n+\t\tret = error(\"Arguments are given but alias '%s' does not need them\", alias.buf);\n+\t\tgoto done;\n+\t}\n+\texpand_alias_params(&cmd, params);\n+\tfree_alias_params(params);\n+\n+\ttrace_printf(\"trace: ref alias expansion: %s => %s\\n\", alias.buf, cmd.buf);\n+\tret = get_alias_oneline(alias.buf, cmd.buf, real_ref) ? -1 : len + 4;\n+done:\n+\tstrbuf_release(&alias);\n+\tstrbuf_release(&cmd);\n+\treturn ret;\n+}\n+\n /*\n  * This reads short-hand syntax that not only evaluates to a commit\n  * object name, but also can act as if the end user spelled the name\n@@ -1035,6 +1099,8 @@ int interpret_branch_name(const char *name, struct strbuf *buf)\n \n \tif (len == -1)\n \t\tlen = interpret_at_upstream(cp, buf);\n+\tif (len == -1)\n+\t\tlen = interpret_at_alias(cp, buf);\n \tif (len == -1) {\n \t\tstrbuf_reset(buf);\n \t\treturn -1;\n@@ -1044,7 +1110,7 @@ int interpret_branch_name(const char *name, struct strbuf *buf)\n \t\treturn len; /* syntax Ok, not enough switches */\n \n \tused += len;\n-\tif (used == namelen)\n+\tif (used == namelen && !strstr(buf->buf, \"@{\"))\n \t\treturn used; /* consumed all */\n \n \t/* we have extra data, which might need further processing */\ndiff --git a/t/t1512-rev-parse-at.sh b/t/t1512-rev-parse-at.sh\nnew file mode 100755\nindex 0000000..6594146\n--- /dev/null\n+++ b/t/t1512-rev-parse-at.sh\n@@ -0,0 +1,63 @@\n+#!/bin/sh\n+\n+test_description='tests for $ref@{something}'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit foo &&\n+\tgit branch foo &&\n+\ttest_commit bar &&\n+\tgit branch bar &&\n+\ttest_commit zoo-bar &&\n+\tgit branch zoo/bar &&\n+\ttest_commit zoo-zoo-bar &&\n+\tgit branch zoo/zoo/bar\n+'\n+\n+test_expect_success 'ref@{~}' '\n+\ttest_must_fail git rev-parse HEAD@{~}\n+'\n+\n+test_expect_success 'ref@{~non-existent}' '\n+\ttest_must_fail git rev-parse HEAD@{~non-existent}\n+'\n+\n+test_expect_success 'ref@{~simple}' '\n+\tgit config ref.simple \"symbolic-ref %ref%\" &&\n+\ttest_must_fail git rev-parse HEAD@{~simple:something} &&\n+\tgit rev-parse HEAD@{~simple} >actual &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'ref@{~external}' '\n+\tgit config ref.external \"!echo zoo/%ref%\" &&\n+\ttest_must_fail git rev-parse bar@{~external:something} &&\n+\tgit rev-parse bar@{~external} >actual &&\n+\tgit rev-parse zoo/bar >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'ref@{~witharg:args}' '\n+\tgit config ref.witharg \"!echo %ref%/%arg%\" &&\n+\ttest_must_fail git rev-parse zoo@{~witharg} &&\n+\tgit rev-parse zoo@{~witharg:bar} >actual &&\n+\tgit rev-parse zoo/bar >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'ref@{~external}@{~external}' '\n+\tgit rev-parse bar@{~external}@{~external} >actual &&\n+\tgit rev-parse zoo/zoo/bar >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'ref@{~mapping}' '\n+\tgit config ref.mapping \"!echo %ref%@{~external}@{~external}\" &&\n+\tgit rev-parse bar@{~mapping} >actual &&\n+\tgit rev-parse zoo/zoo/bar >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_done\n-- \n1.7.3.3.476.g10a82\n"}]}