Edit

kc3-lang/libgit2/examples/tag.c

Branch :

  • Show log

    Commit

  • Author : Edward Thomson
    Date : 2024-06-14 12:50:40
    Hash : 649ef1cc
    Message : signature: add `git_signature_default_from_env` People who are doing a commit expect a unified timestamp between author and committer information when we're using the current timestamp. Provide a single function that returns both author and committer information so that they can have an identical timestamp when none is specified in the environment.

  • examples/tag.c
  • /*
     * 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 */
    struct tag_options {
    	const char *message;
    	const char *pattern;
    	const char *tag_name;
    	const char *target;
    	int num_lines;
    	int force;
    };
    
    /** tag_state represents the current program state for dragging around */
    typedef struct {
    	git_repository *repo;
    	struct 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_OBJECT_TAG:
    			print_tag((git_tag *) obj, state);
    			break;
    		case GIT_OBJECT_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_dispose(&tag_names);
    }
    
    static void action_delete_tag(tag_state *state)
    {
    	struct 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_dispose(&abbrev_oid);
    	git_object_free(obj);
    }
    
    static void action_create_lightweight_tag(tag_state *state)
    {
    	git_repository *repo = state->repo;
    	struct 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;
    	struct 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_from_env(&tagger, NULL, 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, struct 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_lightweight_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(struct 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 lg2_tag(git_repository *repo, int argc, char **argv)
    {
    	struct tag_options opts;
    	tag_action action;
    	tag_state state;
    
    	tag_options_init(&opts);
    	parse_options(&action, &opts, argc, argv);
    
    	state.repo = repo;
    	state.opts = &opts;
    	action(&state);
    
    	return 0;
    }