Jump To …

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_free(&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_lighweight_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(&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, 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_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(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;
}