diff options
Diffstat (limited to 'examples/tag.c')
-rw-r--r-- | examples/tag.c | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/examples/tag.c b/examples/tag.c new file mode 100644 index 000000000..ebb8e37b2 --- /dev/null +++ b/examples/tag.c @@ -0,0 +1,319 @@ +/* + * libgit2 "tag" example - shows how to list, create and delete tags + * + * 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 + * <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#include "common.h" + +/** + * The following example partially reimplements the `git tag` command + * and some of its options. + * + * These commands should work: + + * - Tag name listing (`tag`) + * - Filtered tag listing with messages (`tag -n3 -l "v0.1*"`) + * - Lightweight tag creation (`tag test v0.18.0`) + * - Tag creation (`tag -a -m "Test message" test v0.18.0`) + * - Tag deletion (`tag -d test`) + * + * The command line parsing logic is simplified and doesn't handle + * all of the use cases. + */ + +/** tag_options represents the parsed command line options */ +typedef struct { + const char *message; + const char *pattern; + const char *tag_name; + const char *target; + int num_lines; + int force; +} tag_options; + +/** tag_state represents the current program state for dragging around */ +typedef struct { + git_repository *repo; + tag_options *opts; +} tag_state; + +/** An action to execute based on the command line arguments */ +typedef void (*tag_action)(tag_state *state); +typedef struct args_info args_info; + +static void check(int result, const char *message) +{ + if (result) fatal(message, NULL); +} + +/** Tag listing: Print individual message lines */ +static void print_list_lines(const char *message, const tag_state *state) +{ + const char *msg = message; + int num = state->opts->num_lines - 1; + + if (!msg) return; + + /** first line - headline */ + while(*msg && *msg != '\n') printf("%c", *msg++); + + /** skip over new lines */ + while(*msg && *msg == '\n') msg++; + + printf("\n"); + + /** print just headline? */ + if (num == 0) return; + if (*msg && msg[1]) printf("\n"); + + /** print individual commit/tag lines */ + while (*msg && num-- >= 2) { + printf(" "); + + while (*msg && *msg != '\n') printf("%c", *msg++); + + /** handle consecutive new lines */ + if (*msg && *msg == '\n' && msg[1] == '\n') { + num--; + printf("\n"); + } + while(*msg && *msg == '\n') msg++; + + printf("\n"); + } +} + +/** Tag listing: Print an actual tag object */ +static void print_tag(git_tag *tag, const tag_state *state) +{ + printf("%-16s", git_tag_name(tag)); + + if (state->opts->num_lines) { + const char *msg = git_tag_message(tag); + print_list_lines(msg, state); + } else { + printf("\n"); + } +} + +/** Tag listing: Print a commit (target of a lightweight tag) */ +static void print_commit(git_commit *commit, const char *name, + const tag_state *state) +{ + printf("%-16s", name); + + if (state->opts->num_lines) { + const char *msg = git_commit_message(commit); + print_list_lines(msg, state); + } else { + printf("\n"); + } +} + +/** Tag listing: Fallback, should not happen */ +static void print_name(const char *name) +{ + printf("%s\n", name); +} + +/** Tag listing: Lookup tags based on ref name and dispatch to print */ +static int each_tag(const char *name, tag_state *state) +{ + git_repository *repo = state->repo; + git_object *obj; + + check_lg2(git_revparse_single(&obj, repo, name), + "Failed to lookup rev", name); + + switch (git_object_type(obj)) { + case GIT_OBJ_TAG: + print_tag((git_tag *) obj, state); + break; + case GIT_OBJ_COMMIT: + print_commit((git_commit *) obj, name, state); + break; + default: + print_name(name); + } + + git_object_free(obj); + return 0; +} + +static void action_list_tags(tag_state *state) +{ + const char *pattern = state->opts->pattern; + git_strarray tag_names = {0}; + size_t i; + + check_lg2(git_tag_list_match(&tag_names, pattern ? pattern : "*", state->repo), + "Unable to get list of tags", NULL); + + for(i = 0; i < tag_names.count; i++) { + each_tag(tag_names.strings[i], state); + } + + git_strarray_free(&tag_names); +} + +static void action_delete_tag(tag_state *state) +{ + tag_options *opts = state->opts; + git_object *obj; + git_buf abbrev_oid = {0}; + + check(!opts->tag_name, "Name required"); + + check_lg2(git_revparse_single(&obj, state->repo, opts->tag_name), + "Failed to lookup rev", opts->tag_name); + + check_lg2(git_object_short_id(&abbrev_oid, obj), + "Unable to get abbreviated OID", opts->tag_name); + + check_lg2(git_tag_delete(state->repo, opts->tag_name), + "Unable to delete tag", opts->tag_name); + + printf("Deleted tag '%s' (was %s)\n", opts->tag_name, abbrev_oid.ptr); + + git_buf_free(&abbrev_oid); + git_object_free(obj); +} + +static void action_create_lighweight_tag(tag_state *state) +{ + git_repository *repo = state->repo; + tag_options *opts = state->opts; + git_oid oid; + git_object *target; + + check(!opts->tag_name, "Name required"); + + if (!opts->target) opts->target = "HEAD"; + + check(!opts->target, "Target required"); + + check_lg2(git_revparse_single(&target, repo, opts->target), + "Unable to resolve spec", opts->target); + + check_lg2(git_tag_create_lightweight(&oid, repo, opts->tag_name, + target, opts->force), "Unable to create tag", NULL); + + git_object_free(target); +} + +static void action_create_tag(tag_state *state) +{ + git_repository *repo = state->repo; + tag_options *opts = state->opts; + git_signature *tagger; + git_oid oid; + git_object *target; + + check(!opts->tag_name, "Name required"); + check(!opts->message, "Message required"); + + if (!opts->target) opts->target = "HEAD"; + + check_lg2(git_revparse_single(&target, repo, opts->target), + "Unable to resolve spec", opts->target); + + check_lg2(git_signature_default(&tagger, repo), + "Unable to create signature", NULL); + + check_lg2(git_tag_create(&oid, repo, opts->tag_name, + target, tagger, opts->message, opts->force), "Unable to create tag", NULL); + + git_object_free(target); + git_signature_free(tagger); +} + +static void print_usage() +{ + fprintf(stderr, "usage: see `git help tag`\n"); + exit(1); +} + +/** Parse command line arguments and choose action to run when done */ +static void parse_options(tag_action *action, tag_options *opts, int argc, char **argv) +{ + args_info args = ARGS_INFO_INIT; + *action = &action_list_tags; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *curr = argv[args.pos]; + + if (curr[0] != '-') { + if (!opts->tag_name) + opts->tag_name = curr; + else if (!opts->target) + opts->target = curr; + else + print_usage(); + + if (*action != &action_create_tag) + *action = &action_create_lighweight_tag; + } else if (!strcmp(curr, "-n")) { + opts->num_lines = 1; + *action = &action_list_tags; + } else if (!strcmp(curr, "-a")) { + *action = &action_create_tag; + } else if (!strcmp(curr, "-f")) { + opts->force = 1; + } else if (match_int_arg(&opts->num_lines, &args, "-n", 0)) { + *action = &action_list_tags; + } else if (match_str_arg(&opts->pattern, &args, "-l")) { + *action = &action_list_tags; + } else if (match_str_arg(&opts->tag_name, &args, "-d")) { + *action = &action_delete_tag; + } else if (match_str_arg(&opts->message, &args, "-m")) { + *action = &action_create_tag; + } + } +} + +/** Initialize tag_options struct */ +static void tag_options_init(tag_options *opts) +{ + memset(opts, 0, sizeof(*opts)); + + opts->message = NULL; + opts->pattern = NULL; + opts->tag_name = NULL; + opts->target = NULL; + opts->num_lines = 0; + opts->force = 0; +} + +int main(int argc, char **argv) +{ + git_repository *repo; + tag_options opts; + tag_action action; + tag_state state; + + git_threads_init(); + + check_lg2(git_repository_open_ext(&repo, ".", 0, NULL), + "Could not open repository", NULL); + + tag_options_init(&opts); + parse_options(&action, &opts, argc, argv); + + state.repo = repo; + state.opts = &opts; + action(&state); + + git_repository_free(repo); + git_threads_shutdown(); + + return 0; +} |