diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | describe.c | 118 |
2 files changed, 120 insertions, 1 deletions
@@ -135,7 +135,8 @@ PROGRAMS = \ git-unpack-objects$X git-update-index$X git-update-server-info$X \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \ - git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X + git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \ + git-describe$X # what 'all' will build and 'install' will install. ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) git$X diff --git a/describe.c b/describe.c new file mode 100644 index 0000000000..ebfa4290e9 --- /dev/null +++ b/describe.c @@ -0,0 +1,118 @@ +#include "cache.h" +#include "commit.h" +#include "refs.h" + +#define SEEN (1u << 0) + +static const char describe_usage[] = "git-describe [--all] <committish>*"; + +static int all = 0; /* Default to tags only */ + +static int names = 0, allocs = 0; +static struct commit_name { + const struct commit *commit; + char path[]; +} **name_array = NULL; + +static struct commit_name *match(struct commit *cmit) +{ + int i = names; + struct commit_name **p = name_array; + + while (i-- > 0) { + struct commit_name *n = *p++; + if (n->commit == cmit) + return n; + } + return NULL; +} + +static void add_to_known_names(const char *path, const struct commit *commit) +{ + int idx; + int len = strlen(path)+1; + struct commit_name *name = xmalloc(sizeof(struct commit_name) + len); + + name->commit = commit; + memcpy(name->path, path, len); + idx = names; + if (idx >= allocs) { + allocs = (idx + 50) * 3 / 2; + name_array = xrealloc(name_array, allocs*sizeof(*name_array)); + } + name_array[idx] = name; + names = ++idx; +} + +static int get_name(const char *path, const unsigned char *sha1) +{ + struct commit *commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + return 0; + if (!all && strncmp(path, "refs/tags/", 10)) + return 0; + add_to_known_names(path, commit); + return 0; +} + +static int compare_names(const void *_a, const void *_b) +{ + struct commit_name *a = *(struct commit_name **)_a; + struct commit_name *b = *(struct commit_name **)_b; + unsigned long a_date = a->commit->date; + unsigned long b_date = b->commit->date; + return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1; +} + +static void describe(struct commit *cmit) +{ + struct commit_list *list; + static int initialized = 0; + struct commit_name *n; + + if (!initialized) { + initialized = 1; + for_each_ref(get_name); + qsort(name_array, names, sizeof(*name_array), compare_names); + } + + n = match(cmit); + if (n) { + printf("%s\n", n->path); + return; + } + + list = NULL; + commit_list_insert(cmit, &list); + while (list) { + struct commit *c = pop_most_recent_commit(&list, SEEN); + n = match(c); + if (n) { + printf("%s-g%.8s\n", n->path, sha1_to_hex(cmit->object.sha1)); + return; + } + } +} + +int main(int argc, char **argv) +{ + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + unsigned char sha1[20]; + struct commit *cmit; + + if (!strcmp(arg, "--all")) { + all = 1; + continue; + } + if (get_sha1(arg, sha1) < 0) + usage(describe_usage); + cmit = lookup_commit_reference(sha1); + if (!cmit) + usage(describe_usage); + describe(cmit); + } + return 0; +} |