diff options
author | Jeff King <peff@peff.net> | 2009-04-07 03:14:20 -0400 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2009-04-07 23:22:15 -0700 |
commit | 7c2b3029df45a74d0ebd11afcc94259791cfb90d (patch) | |
tree | 5c3888be7cb3dc41dbd6ef7d93a8b58f059a98ad /refs.c | |
parent | 8cae19d987b1bbd43258558f591e39d9d216dcb3 (diff) | |
download | git-7c2b3029df45a74d0ebd11afcc94259791cfb90d.tar.gz git-7c2b3029df45a74d0ebd11afcc94259791cfb90d.tar.xz |
make get_short_ref a public function
Often we want to shorten a full ref name to something "prettier"
to show a user. For example, "refs/heads/master" is often shown
simply as "master", or "refs/remotes/origin/master" is shown as
"origin/master".
Many places in the code use a very simple formula: skip common
prefixes like refs/heads, refs/remotes, etc. This is codified in
the prettify_ref function.
for-each-ref has a more correct (but more expensive) approach:
consider the ref lookup rules, and try shortening as much as
possible while remaining unambiguous.
This patch makes the latter strategy globally available as
shorten_unambiguous_ref.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'refs.c')
-rw-r--r-- | refs.c | 99 |
1 files changed, 99 insertions, 0 deletions
@@ -1652,3 +1652,102 @@ struct ref *find_ref_by_name(const struct ref *list, const char *name) return (struct ref *)list; return NULL; } + +/* + * 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; +} + +char *shorten_unambiguous_ref(const char *ref) +{ + int i; + static char **scanf_fmts; + static int nr_rules; + char *short_name; + + /* 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 xstrdup(ref); + + /* buffer for scanf result, at most ref must fit */ + short_name = xstrdup(ref); + + /* skip first rule, it will always match */ + for (i = nr_rules - 1; i > 0 ; --i) { + int j; + int short_name_len; + + if (1 != sscanf(ref, scanf_fmts[i], short_name)) + continue; + + short_name_len = strlen(short_name); + + /* + * check if the short name resolves to a valid ref, + * but use only rules prior to the matched one + */ + for (j = 0; j < i; j++) { + const char *rule = ref_rev_parse_rules[j]; + unsigned char short_objectname[20]; + char refname[PATH_MAX]; + + /* + * the short name is ambiguous, if it resolves + * (with this previous rule) to a valid ref + * read_ref() returns 0 on success + */ + mksnpath(refname, sizeof(refname), + rule, short_name_len, short_name); + if (!read_ref(refname, short_objectname)) + break; + } + + /* + * short name is non-ambiguous if all previous rules + * haven't resolved to a valid ref + */ + if (j == i) + return short_name; + } + + free(short_name); + return xstrdup(ref); +} |