From 9608a190c0344a55b1d106c1cca17b17fe276c86 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 7 Jul 2013 14:13:41 -0700 Subject: name-ref: factor out name shortening logic from name_ref() The logic will be used in a new codepath for showing exact matches. Signed-off-by: Junio C Hamano diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 87d4854..1234ebb 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -96,6 +96,17 @@ static int subpath_matches(const char *path, const char *filter) return -1; } +static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) +{ + if (shorten_unambiguous) + refname = shorten_unambiguous_ref(refname, 0); + else if (!prefixcmp(refname, "refs/heads/")) + refname = refname + 11; + else if (!prefixcmp(refname, "refs/")) + refname = refname + 5; + return refname; +} + struct name_ref_data { int tags_only; int name_only; @@ -134,13 +145,7 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; - if (can_abbreviate_output) - path = shorten_unambiguous_ref(path, 0); - else if (!prefixcmp(path, "refs/heads/")) - path = path + 11; - else if (!prefixcmp(path, "refs/")) - path = path + 5; - + path = name_ref_abbrev(path, can_abbreviate_output); name_rev(commit, xstrdup(path), 0, 0, deref); } return 0; -- cgit v0.10.2-6-g49f6 From b23e0b9353e38149e791bde0feb1220aa1c8bac1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 7 Jul 2013 14:14:22 -0700 Subject: name-rev: allow converting the exact object name at the tip of a ref "git name-rev" is supposed to convert given object names into strings that name the same objects based on refs, that can be fed to "git rev-parse" to get the same object names back, so the output for the commit object v1.8.3^0 (i.e. the commit tagged as v1.8.3) $ git rev-parse v1.8.3 v1.8.3^0 | git name-rev --stdin 8af06057d0c31a24e8737ae846ac2e116e8bafb9 edca4152560522a431a51fc0a06147fc680b5b18 (tags/v1.8.3^0) has to have "^0" at the end, as "edca41" is a commit, not the tag that references it. But we do not get anything for the tag object (8af0605) itself. This is because the command however did not bother to see if the object is at the tip of some ref, and failed to convert a tag object. Teach it to show this instead: $ git rev-parse v1.8.3 v1.8.3^0 | git name-rev --stdin 8af06057d0c31a24e8737ae846ac2e116e8bafb9 (tags/v1.8.3) edca4152560522a431a51fc0a06147fc680b5b18 (tags/v1.8.3^0) Signed-off-by: Junio C Hamano diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 1234ebb..29a6f56 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -4,6 +4,7 @@ #include "tag.h" #include "refs.h" #include "parse-options.h" +#include "sha1-lookup.h" #define CUTOFF_DATE_SLOP 86400 /* one day */ @@ -113,6 +114,34 @@ struct name_ref_data { const char *ref_filter; }; +static struct tip_table { + struct tip_table_entry { + unsigned char sha1[20]; + const char *refname; + } *table; + int nr; + int alloc; + int sorted; +} tip_table; + +static void add_to_tip_table(const unsigned char *sha1, const char *refname, + int shorten_unambiguous) +{ + refname = name_ref_abbrev(refname, shorten_unambiguous); + + ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); + hashcpy(tip_table.table[tip_table.nr].sha1, sha1); + tip_table.table[tip_table.nr].refname = xstrdup(refname); + tip_table.nr++; + tip_table.sorted = 0; +} + +static int tipcmp(const void *a_, const void *b_) +{ + const struct tip_table_entry *a = a_, *b = b_; + return hashcmp(a->sha1, b->sha1); +} + static int name_ref(const char *path, const unsigned char *sha1, int flags, void *cb_data) { struct object *o = parse_object(sha1); @@ -135,6 +164,8 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void } } + add_to_tip_table(sha1, path, can_abbreviate_output); + while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; if (!t->tagged) @@ -151,6 +182,32 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void return 0; } +static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) +{ + struct tip_table_entry *table = table_; + return table[ix].sha1; +} + +static const char *get_exact_ref_match(const struct object *o) +{ + int found; + + if (!tip_table.table || !tip_table.nr) + return NULL; + + if (!tip_table.sorted) { + qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table), + tipcmp); + tip_table.sorted = 1; + } + + found = sha1_pos(o->sha1, tip_table.table, tip_table.nr, + nth_tip_table_ent); + if (0 <= found) + return tip_table.table[found].refname; + return NULL; +} + /* returns a static buffer */ static const char *get_rev_name(const struct object *o) { @@ -159,7 +216,7 @@ static const char *get_rev_name(const struct object *o) struct commit *c; if (o->type != OBJ_COMMIT) - return NULL; + return get_exact_ref_match(o); c = (struct commit *) o; n = c->util; if (!n) -- cgit v0.10.2-6-g49f6 From 45bc950b4330ab675724b5079aa1528acde9f404 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 7 Jul 2013 14:42:23 -0700 Subject: describe: use argv-array Instead of using a hand allocated args[] array, use argv-array API to manage the dynamically created list of arguments when invoking name-rev. Signed-off-by: Junio C Hamano diff --git a/builtin/describe.c b/builtin/describe.c index 4e675c3..db41cd7 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -7,6 +7,7 @@ #include "parse-options.h" #include "diff.h" #include "hash.h" +#include "argv-array.h" #define SEEN (1u<<0) #define MAX_TAGS (FLAG_BITS - 1) @@ -442,24 +443,23 @@ int cmd_describe(int argc, const char **argv, const char *prefix) die(_("--long is incompatible with --abbrev=0")); if (contains) { - const char **args = xmalloc((7 + argc) * sizeof(char *)); - int i = 0; - args[i++] = "name-rev"; - args[i++] = "--name-only"; - args[i++] = "--no-undefined"; + struct argv_array args; + + argv_array_init(&args); + argv_array_pushl(&args, "name-rev", "--name-only", "--no-undefined", + NULL); if (always) - args[i++] = "--always"; + argv_array_push(&args, "--always"); if (!all) { - args[i++] = "--tags"; - if (pattern) { - char *s = xmalloc(strlen("--refs=refs/tags/") + strlen(pattern) + 1); - sprintf(s, "--refs=refs/tags/%s", pattern); - args[i++] = s; - } + argv_array_push(&args, "--tags"); + if (pattern) + argv_array_pushf(&args, "--refs=refs/tags/%s", pattern); + } + while (*argv) { + argv_array_push(&args, *argv); + argv++; } - memcpy(args + i, argv, argc * sizeof(char *)); - args[i + argc] = NULL; - return cmd_name_rev(i + argc, args, prefix); + return cmd_name_rev(args.argc, args.argv, prefix); } init_hash(&names); -- cgit v0.10.2-6-g49f6 From 118aa4acff6afae733c206df7049ea54f7e0b282 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 18 Jul 2013 14:11:35 -0700 Subject: name-rev: differentiate between tags and commits they point at "git name-rev --stdin" has been fixed to convert an object name that points at a tag to a refname of the tag. The codepath to handle its command line arguments, however, fed the commit that the tag points at to the underlying naming machinery. With this fix, you will get this: $ git name-rev --refs=tags/\* --name-only $(git rev-parse v1.8.3 v1.8.3^0) v1.8.3 v1.8.3^0 which is the same as what you would get from the fixed "--stdin" variant: $ git rev-parse v1.8.3 v1.8.3^0 | git name-rev --refs=tags/\* --name-only v1.8.3 v1.8.3^0 Signed-off-by: Junio C Hamano diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 29a6f56..4c7cc62 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -334,7 +334,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) for (; argc; argc--, argv++) { unsigned char sha1[20]; - struct object *o; + struct object *object; struct commit *commit; if (get_sha1(*argv, sha1)) { @@ -343,17 +343,25 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) continue; } - o = deref_tag(parse_object(sha1), *argv, 0); - if (!o || o->type != OBJ_COMMIT) { - fprintf(stderr, "Could not get commit for %s. Skipping.\n", + commit = NULL; + object = parse_object(sha1); + if (object) { + struct object *peeled = deref_tag(object, *argv, 0); + if (peeled && peeled->type == OBJ_COMMIT) + commit = (struct commit *)peeled; + } + + if (!object) { + fprintf(stderr, "Could not get object for %s. Skipping.\n", *argv); continue; } - commit = (struct commit *)o; - if (cutoff > commit->date) - cutoff = commit->date; - add_object_array((struct object *)commit, *argv, &revs); + if (commit) { + if (cutoff > commit->date) + cutoff = commit->date; + } + add_object_array(object, *argv, &revs); } if (cutoff) diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index a25729f..1d20854 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -174,4 +174,16 @@ check_describe "test2-lightweight-*" --tags --match="test2-*" check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^ +test_expect_success 'name-rev with exact tags' ' + echo A >expect && + tag_object=$(git rev-parse refs/tags/A) && + git name-rev --tags --name-only $tag_object >actual && + test_cmp expect actual && + + echo "A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + git name-rev --tags --name-only $tagged_commit >actual && + test_cmp expect actual +' + test_done -- cgit v0.10.2-6-g49f6 From adfc1857bdb090786fd9d22c1acec39371c76048 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 18 Jul 2013 14:46:51 -0700 Subject: describe: fix --contains when a tag is given as input "git describe" takes a commit and gives it a name based on tags in its neighbourhood. The command does take a commit-ish but when given a tag that points at a commit, it should dereference the tag before computing the name for the commit. As the whole processing is internally delegated to name-rev, if we unwrap tags down to the underlying commit when invoking name-rev, it will make the name-rev issue an error message based on the unwrapped object name (i.e. either 40-hex object name, or "$tag^0") that is different from what the end-user gave to the command when the commit cannot be described. Introduce an internal option --peel-tag to the name-rev to tell it to unwrap a tag in its input from the command line. Signed-off-by: Junio C Hamano diff --git a/builtin/describe.c b/builtin/describe.c index db41cd7..7d73722 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -446,7 +446,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix) struct argv_array args; argv_array_init(&args); - argv_array_pushl(&args, "name-rev", "--name-only", "--no-undefined", + argv_array_pushl(&args, "name-rev", + "--peel-tag", "--name-only", "--no-undefined", NULL); if (always) argv_array_push(&args, "--always"); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 4c7cc62..0aaa19e 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -307,7 +307,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0; + int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, NULL }; struct option opts[] = { OPT_BOOLEAN(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")), @@ -320,6 +320,12 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "undefined", &allow_undefined, N_("allow to print `undefined` names")), OPT_BOOLEAN(0, "always", &always, N_("show abbreviated commit object as fallback")), + { + /* A Hidden OPT_BOOL */ + OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL, + N_("dereference tags in the input (internal use)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1, + }, OPT_END(), }; @@ -361,6 +367,15 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) if (cutoff > commit->date) cutoff = commit->date; } + + if (peel_tag) { + if (!commit) { + fprintf(stderr, "Could not get commit for %s. Skipping.\n", + *argv); + continue; + } + object = (struct object *)commit; + } add_object_array(object, *argv, &revs); } diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 1d20854..c0e5b2a 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -186,4 +186,16 @@ test_expect_success 'name-rev with exact tags' ' test_cmp expect actual ' +test_expect_success 'describe --contains with the exact tags' ' + echo "A^0" >expect && + tag_object=$(git rev-parse refs/tags/A) && + git describe --contains $tag_object >actual && + test_cmp expect actual && + + echo "A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + git describe --contains $tagged_commit >actual && + test_cmp expect actual +' + test_done -- cgit v0.10.2-6-g49f6