/* * 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 * . */ #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(void) { 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_libgit2_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_libgit2_shutdown(); return 0; }