/* * libgit2 "describe" example - shows how to describe commits * * Written by the libgit2 contributors * * To the extent possible under law, the author(s) have dedicated all copyright * and related and neighboring rights to this software to the public domain * worldwide. This software is distributed without any warranty. * * You should have received a copy of the CC0 Public Domain Dedication along * with this software. If not, see * . */ #include "common.h" #include /** * The following example partially reimplements the `git describe` command * and some of its options. * * These commands should work: * - Describe HEAD with default options (`describe`) * - Describe specified revision (`describe master~2`) * - Describe specified revisions (`describe master~2 HEAD~3`) * - Describe HEAD with dirty state suffix (`describe --dirty=*`) * - Describe consider all refs (`describe --all master`) * - Describe consider lightweight tags (`describe --tags temp-tag`) * - Describe show non-default abbreviated size (`describe --abbrev=10`) * - Describe always output the long format if matches a tag (`describe --long v1.0`) * - Describe consider only tags of specified pattern (`describe --match v*-release`) * - Describe show the fallback result (`describe --always`) * - Describe follow only the first parent commit (`describe --first-parent`) * * The command line parsing logic is simplified and doesn't handle * all of the use cases. */ /** describe_options represents the parsed command line options */ typedef struct { const char **commits; size_t commit_count; git_describe_options describe_options; git_describe_format_options format_options; } describe_options; typedef struct args_info args_info; static void opts_add_commit(describe_options *opts, const char *commit) { size_t sz; assert(opts != NULL); sz = ++opts->commit_count * sizeof(opts->commits[0]); opts->commits = xrealloc(opts->commits, sz); opts->commits[opts->commit_count - 1] = commit; } static void do_describe_single(git_repository *repo, describe_options *opts, const char *rev) { git_object *commit; git_describe_result *describe_result; git_buf buf = { 0 }; if (rev) { check_lg2(git_revparse_single(&commit, repo, rev), "Failed to lookup rev", rev); check_lg2(git_describe_commit(&describe_result, commit, &opts->describe_options), "Failed to describe rev", rev); } else check_lg2(git_describe_workdir(&describe_result, repo, &opts->describe_options), "Failed to describe workdir", NULL); check_lg2(git_describe_format(&buf, describe_result, &opts->format_options), "Failed to format describe rev", rev); printf("%s\n", buf.ptr); } static void do_describe(git_repository *repo, describe_options *opts) { if (opts->commit_count == 0) do_describe_single(repo, opts, NULL); else { size_t i; for (i = 0; i < opts->commit_count; i++) do_describe_single(repo, opts, opts->commits[i]); } } static void print_usage(void) { fprintf(stderr, "usage: see `git help describe`\n"); exit(1); } /** Parse command line arguments */ static void parse_options(describe_options *opts, int argc, char **argv) { args_info args = ARGS_INFO_INIT; for (args.pos = 1; args.pos < argc; ++args.pos) { const char *curr = argv[args.pos]; if (curr[0] != '-') { opts_add_commit(opts, curr); } else if (!strcmp(curr, "--all")) { opts->describe_options.describe_strategy = GIT_DESCRIBE_ALL; } else if (!strcmp(curr, "--tags")) { opts->describe_options.describe_strategy = GIT_DESCRIBE_TAGS; } else if (!strcmp(curr, "--exact-match")) { opts->describe_options.max_candidates_tags = 0; } else if (!strcmp(curr, "--long")) { opts->format_options.always_use_long_format = 1; } else if (!strcmp(curr, "--always")) { opts->describe_options.show_commit_oid_as_fallback = 1; } else if (!strcmp(curr, "--first-parent")) { opts->describe_options.only_follow_first_parent = 1; } else if (optional_str_arg(&opts->format_options.dirty_suffix, &args, "--dirty", "-dirty")) { } else if (match_int_arg((int *)&opts->format_options.abbreviated_size, &args, "--abbrev", 0)) { } else if (match_int_arg((int *)&opts->describe_options.max_candidates_tags, &args, "--candidates", 0)) { } else if (match_str_arg(&opts->describe_options.pattern, &args, "--match")) { } else { print_usage(); } } if (opts->commit_count > 0) { if (opts->format_options.dirty_suffix) fatal("--dirty is incompatible with commit-ishes", NULL); } else { if (!opts->format_options.dirty_suffix || !opts->format_options.dirty_suffix[0]) { opts_add_commit(opts, "HEAD"); } } } /** Initialize describe_options struct */ static void describe_options_init(describe_options *opts) { memset(opts, 0, sizeof(*opts)); opts->commits = NULL; opts->commit_count = 0; git_describe_init_options(&opts->describe_options, GIT_DESCRIBE_OPTIONS_VERSION); git_describe_init_format_options(&opts->format_options, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION); } int lg2_describe(git_repository *repo, int argc, char **argv) { describe_options opts; describe_options_init(&opts); parse_options(&opts, argc, argv); do_describe(repo, &opts); return 0; }