commit - a009df92847bdf738778c064cc50e9c204b6e1d6
commit + 8e7bd50a820730b5f8356254f7a44fa922fab3bc
blob - 5cc02975e9734606b79d513463bbce1e7f4e8fd6
blob + c570e9380338cb7e97047870f80835c0834d134c
--- got/got.1
+++ got/got.1
.It Cm br
Short alias for
.Cm branch .
+.It Cm tag Oo Fl m Ar message Oc Oo Fl r Ar repository-path Oc Oo Fl l Oc Ar name Op Ar commit Oc
+Manage tags in a repository.
+.Pp
+Tags are managed via references which live in the
+.Dq refs/tags/
+reference namespace.
+The
+.Cm got tag
+command operates on references in this namespace only.
+.Pp
+Expect one or two arguments and attempt to create a tag with the given
+.Ar name ,
+and make this tag point at the given
+.Ar commit .
+If no commit is specified, default to the latest commit on the work tree's
+current branch if invoked in a work tree, and to a commit resolved via
+the repository's HEAD reference otherwise.
+Otherwise, the expected argument is a commit ID SHA1 hash or an existing
+reference or tag name which will be resolved to a commit ID.
+An abbreviated hash argument will be expanded to a full SHA1 hash
+automatically, provided the abbreviation is unique.
+.Pp
+The options for
+.Cm got tag
+are as follows:
+.Bl -tag -width Ds
+.It Fl m Ar message
+Use the specified tag message when creating the new tag
+Without the
+.Fl m
+option,
+.Cm got import
+opens a temporary file in an editor where a tag message can be written.
+.It Fl r Ar repository-path
+Use the repository at the specified path.
+If not specified, assume the repository is located at or above the current
+working directory.
+If this directory is a
+.Nm
+work tree, use the repository path associated with this work tree.
+.It Fl l
+List all existing tags in the repository instead of creating a new tag.
+If this option is used, no other command-line arguments are allowed.
+.El
+.Pp
+By design, the
+.Cm got tag
+command will not delete tags or change existing tags.
+If a tag must be deleted, the
+.Cm got ref
+command may be used to delete a tag's reference.
+This should only be done if the tag has not already been copied to
+another repository.
.It Cm add Ar file-path ...
Schedule unversioned files in a work tree for addition to the
repository in the next commit.
environment variables with a missing email address.
.It Ev VISUAL , EDITOR
The editor spawned by
-.Cm got commit .
+.Cm got commit ,
+.Cm got import ,
+or
+.Cm got tag .
.It Ev GOT_LOG_DEFAULT_LIMIT
The default limit on the number of commits traversed by
.Cm got log .
blob - 215ac4b28ec9af15c08d48b2d902dee4ca65fc6a
blob + 792a87a9761babecdde9f39b750d73416cc13418
--- got/got.c
+++ got/got.c
__dead static void usage_status(void);
__dead static void usage_ref(void);
__dead static void usage_branch(void);
+__dead static void usage_tag(void);
__dead static void usage_add(void);
__dead static void usage_remove(void);
__dead static void usage_revert(void);
static const struct got_error* cmd_status(int, char *[]);
static const struct got_error* cmd_ref(int, char *[]);
static const struct got_error* cmd_branch(int, char *[]);
+static const struct got_error* cmd_tag(int, char *[]);
static const struct got_error* cmd_add(int, char *[]);
static const struct got_error* cmd_remove(int, char *[]);
static const struct got_error* cmd_revert(int, char *[]);
{ "status", cmd_status, usage_status, "st" },
{ "ref", cmd_ref, usage_ref, "" },
{ "branch", cmd_branch, usage_branch, "br" },
+ { "tag", cmd_tag, usage_tag, "" },
{ "add", cmd_add, usage_add, "" },
{ "remove", cmd_remove, usage_remove, "rm" },
{ "revert", cmd_revert, usage_revert, "rv" },
return error;
}
+
__dead static void
+usage_tag(void)
+{
+ fprintf(stderr,
+ "usage: %s tag [-r repository] -l | -d name | "
+ "[-m message] name [commit]\n", getprogname());
+ exit(1);
+}
+
+static const struct got_error *
+list_tags(struct got_repository *repo, struct got_worktree *worktree)
+{
+ static const struct got_error *err = NULL;
+ struct got_reflist_head refs;
+ struct got_reflist_entry *re;
+
+ SIMPLEQ_INIT(&refs);
+
+ err = got_ref_list(&refs, repo);
+ if (err)
+ return err;
+
+ SIMPLEQ_FOREACH(re, &refs, entry) {
+ const char *refname;
+ char *refstr, *tagmsg0, *tagmsg, *line, *id_str, *datestr;
+ char datebuf[26];
+ time_t tagger_time;
+ struct got_object_id *id;
+ struct got_tag_object *tag;
+
+ refname = got_ref_get_name(re->ref);
+ if (strncmp(refname, "refs/tags/", 10) != 0)
+ continue;
+ refname += 10;
+ refstr = got_ref_to_str(re->ref);
+ if (refstr == NULL) {
+ err = got_error_from_errno("got_ref_to_str");
+ break;
+ }
+ printf("%stag %s %s\n", GOT_COMMIT_SEP_STR, refname, refstr);
+ free(refstr);
+
+ err = got_ref_resolve(&id, repo, re->ref);
+ if (err)
+ break;
+ err = got_object_open_as_tag(&tag, repo, id);
+ free(id);
+ if (err)
+ break;
+ err = got_object_id_str(&id_str,
+ got_object_tag_get_object_id(tag));
+ if (err)
+ break;
+ switch (got_object_tag_get_object_type(tag)) {
+ case GOT_OBJ_TYPE_BLOB:
+ printf("%s %s\n", GOT_OBJ_LABEL_BLOB, id_str);
+ break;
+ case GOT_OBJ_TYPE_TREE:
+ printf("%s %s\n", GOT_OBJ_LABEL_TREE, id_str);
+ break;
+ case GOT_OBJ_TYPE_COMMIT:
+ printf("%s %s\n", GOT_OBJ_LABEL_COMMIT, id_str);
+ break;
+ case GOT_OBJ_TYPE_TAG:
+ printf("%s %s\n", GOT_OBJ_LABEL_TAG, id_str);
+ break;
+ default:
+ break;
+ }
+ free(id_str);
+ printf("from: %s\n", got_object_tag_get_tagger(tag));
+ tagger_time = got_object_tag_get_tagger_time(tag);
+ datestr = get_datestr(&tagger_time, datebuf);
+ if (datestr)
+ printf("date: %s UTC\n", datestr);
+ tagmsg0 = strdup(got_object_tag_get_message(tag));
+ got_object_tag_close(tag);
+ if (tagmsg0 == NULL) {
+ err = got_error_from_errno("strdup");
+ break;
+ }
+
+ tagmsg = tagmsg0;
+ do {
+ line = strsep(&tagmsg, "\n");
+ if (line)
+ printf(" %s\n", line);
+ } while (line);
+ free(tagmsg0);
+ }
+
+ got_ref_list_free(&refs);
+ return NULL;
+}
+
+static const struct got_error *
+get_tag_message(char **tagmsg, const char *commit_id_str, const char *repo_path)
+{
+ const struct got_error *err = NULL;
+ char *template = NULL, *initial_content = NULL;
+ char *tagmsg_path = NULL, *editor = NULL;
+ int fd = -1;
+
+ if (asprintf(&template, "/tmp/got-tagmsg") == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ if (asprintf(&initial_content, "\n# tagging commit %s\n",
+ commit_id_str) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ err = got_opentemp_named_fd(&tagmsg_path, &fd, template);
+ if (err)
+ goto done;
+
+ dprintf(fd, initial_content);
+ close(fd);
+
+ err = get_editor(&editor);
+ if (err)
+ goto done;
+ err = edit_logmsg(tagmsg, editor, tagmsg_path, initial_content);
+done:
+ if (err == NULL || err->code == GOT_ERR_COMMIT_MSG_EMPTY) {
+ unlink(tagmsg_path);
+ free(tagmsg_path);
+ tagmsg_path = NULL;
+ }
+ free(initial_content);
+ free(template);
+ free(editor);
+
+ /* Editor is done; we can now apply unveil(2) */
+ if (err == NULL) {
+ err = apply_unveil(repo_path, 0, NULL);
+ if (err) {
+ free(*tagmsg);
+ *tagmsg = NULL;
+ }
+ }
+ return err;
+}
+
+static const struct got_error *
+add_tag(struct got_repository *repo, const char *tag_name,
+ const char *commit_arg, const char *tagmsg_arg)
+{
+ const struct got_error *err = NULL;
+ struct got_object_id *commit_id = NULL, *tag_id = NULL;
+ char *label = NULL, *commit_id_str = NULL;
+ struct got_reference *ref = NULL;
+ char *refname = NULL, *tagmsg = NULL;
+ const char *tagger;
+
+ /*
+ * Don't let the user create a tag named '-'.
+ * While technically a valid reference name, this case is usually
+ * an unintended typo.
+ */
+ if (tag_name[0] == '-' && tag_name[1] == '\0')
+ return got_error(GOT_ERR_BAD_REF_NAME);
+
+ err = get_author(&tagger);
+ if (err)
+ return err;
+
+ err = match_object_id(&commit_id, &label, commit_arg,
+ GOT_OBJ_TYPE_COMMIT, 1, repo);
+ if (err)
+ goto done;
+
+ err = got_object_id_str(&commit_id_str, commit_id);
+ if (err)
+ goto done;
+
+ if (strncmp("refs/tags/", tag_name, 10) == 0) {
+ refname = strdup(tag_name);
+ if (refname == NULL) {
+ err = got_error_from_errno("strdup");
+ goto done;
+ }
+ tag_name += 10;
+ } else if (asprintf(&refname, "refs/tags/%s", tag_name) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ err = got_ref_open(&ref, repo, refname, 0);
+ if (err == NULL) {
+ err = got_error(GOT_ERR_TAG_EXISTS);
+ goto done;
+ } else if (err->code != GOT_ERR_NOT_REF)
+ goto done;
+
+ if (tagmsg_arg == NULL) {
+ err = get_tag_message(&tagmsg, commit_id_str,
+ got_repo_get_path(repo));
+ if (err)
+ goto done;
+ }
+
+ err = got_object_tag_create(&tag_id, tag_name, commit_id,
+ tagger, time(NULL), tagmsg ? tagmsg : tagmsg_arg, repo);
+ if (err)
+ goto done;
+
+ err = got_ref_alloc(&ref, refname, tag_id);
+ if (err)
+ goto done;
+
+ err = got_ref_write(ref, repo);
+
+ if (err == NULL) {
+ char *tag_id_str;
+ err = got_object_id_str(&tag_id_str, tag_id);
+ printf("Created tag %s\n", tag_id_str);
+ free(tag_id_str);
+ }
+done:
+ if (ref)
+ got_ref_close(ref);
+ free(commit_id);
+ free(commit_id_str);
+ free(refname);
+ free(tagmsg);
+ return err;
+}
+
+static const struct got_error *
+cmd_tag(int argc, char *argv[])
+{
+ const struct got_error *error = NULL;
+ struct got_repository *repo = NULL;
+ struct got_worktree *worktree = NULL;
+ char *cwd = NULL, *repo_path = NULL, *commit_id_str = NULL;
+ const char *tag_name, *commit_id_arg = NULL, *tagmsg = NULL;
+ int ch, do_list = 0;
+
+ while ((ch = getopt(argc, argv, "m:r:l")) != -1) {
+ switch (ch) {
+ case 'm':
+ tagmsg = optarg;
+ break;
+ case 'r':
+ repo_path = realpath(optarg, NULL);
+ if (repo_path == NULL)
+ err(1, "-r option");
+ got_path_strip_trailing_slashes(repo_path);
+ break;
+ case 'l':
+ do_list = 1;
+ break;
+ default:
+ usage_tag();
+ /* NOTREACHED */
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (do_list) {
+ if (tagmsg)
+ errx(1, "-l and -m options are mutually exclusive\n");
+ if (argc > 0)
+ usage_tag();
+ } else if (argc < 1 || argc > 2)
+ usage_tag();
+ else {
+ tag_name = argv[0];
+ if (argc > 1)
+ commit_id_arg = argv[1];
+ }
+
+#ifndef PROFILE
+ if (do_list) {
+ if (pledge("stdio rpath wpath flock proc exec sendfd unveil",
+ NULL) == -1)
+ err(1, "pledge");
+ } else {
+ if (pledge("stdio rpath wpath cpath fattr flock proc exec "
+ "sendfd unveil", NULL) == -1)
+ err(1, "pledge");
+ }
+#endif
+ cwd = getcwd(NULL, 0);
+ if (cwd == NULL) {
+ error = got_error_from_errno("getcwd");
+ goto done;
+ }
+
+ if (repo_path == NULL) {
+ error = got_worktree_open(&worktree, cwd);
+ if (error && error->code != GOT_ERR_NOT_WORKTREE)
+ goto done;
+ else
+ error = NULL;
+ if (worktree) {
+ repo_path =
+ strdup(got_worktree_get_repo_path(worktree));
+ if (repo_path == NULL)
+ error = got_error_from_errno("strdup");
+ if (error)
+ goto done;
+ } else {
+ repo_path = strdup(cwd);
+ if (repo_path == NULL) {
+ error = got_error_from_errno("strdup");
+ goto done;
+ }
+ }
+ }
+
+ error = got_repo_open(&repo, repo_path);
+ if (error != NULL)
+ goto done;
+
+
+ if (do_list) {
+ error = apply_unveil(got_repo_get_path(repo), 1, NULL);
+ if (error)
+ goto done;
+ error = list_tags(repo, worktree);
+ } else {
+ if (tagmsg) {
+ error = apply_unveil(got_repo_get_path(repo), 1, NULL);
+ if (error)
+ goto done;
+ }
+
+ if (commit_id_arg == NULL) {
+ struct got_reference *head_ref;
+ struct got_object_id *commit_id;
+ error = got_ref_open(&head_ref, repo,
+ worktree ? got_worktree_get_head_ref_name(worktree)
+ : GOT_REF_HEAD, 0);
+ if (error)
+ goto done;
+ error = got_ref_resolve(&commit_id, repo, head_ref);
+ got_ref_close(head_ref);
+ if (error)
+ goto done;
+ error = got_object_id_str(&commit_id_str, commit_id);
+ free(commit_id);
+ if (error)
+ goto done;
+ }
+
+ error = add_tag(repo, tag_name,
+ commit_id_str ? commit_id_str : commit_id_arg, tagmsg);
+ }
+done:
+ if (repo)
+ got_repo_close(repo);
+ if (worktree)
+ got_worktree_close(worktree);
+ free(cwd);
+ free(repo_path);
+ free(commit_id_str);
+ return error;
+}
+
+__dead static void
usage_add(void)
{
fprintf(stderr, "usage: %s add file-path ...\n", getprogname());
blob - 884a46a8f9776653a7f73ea355dafc30f1f7ff20
blob + b156fbf00a432c54a36fa14acf4c334307dcdd17
--- include/got_error.h
+++ include/got_error.h
#define GOT_ERR_STAGED_PATHS 106
#define GOT_ERR_PATCH_CHOICE 107
#define GOT_ERR_COMMIT_NO_EMAIL 108
+#define GOT_ERR_TAG_EXISTS 109
static const struct got_error {
int code;
{ GOT_ERR_COMMIT_NO_EMAIL,"GOT_AUTHOR environment variable contains "
"no email address; an email address is required for compatibility "
"with Git" },
+ { GOT_ERR_TAG_EXISTS,"specified tag already exists" },
};
/*
blob - b0bf900c64d3a893263960c80bff6c6e0e3ac6cb
blob + 93db9af0036c3dc58da8d9f1c76664ddf7fde238
--- include/got_object.h
+++ include/got_object.h
const struct got_error *got_object_commit_add_parent(struct got_commit_object *,
const char *);
+
+/* Create a new tag object in the repository. */
+const struct got_error *got_object_tag_create(struct got_object_id **,
+ const char *, struct got_object_id *, const char *,
+ time_t, const char *, struct got_repository *);
blob - ac485f2d0f28c395fbed5974f9a5a3bd0bfa5e74
blob + e790d9a523cf97c0b9589729659bd763e331c45e
--- lib/object_create.c
+++ lib/object_create.c
free(author_str);
free(committer_str);
if (commitfile && fclose(commitfile) != 0 && err == NULL)
+ err = got_error_from_errno("fclose");
+ if (err) {
+ free(*id);
+ *id = NULL;
+ }
+ return err;
+}
+
+const struct got_error *
+got_object_tag_create(struct got_object_id **id,
+ const char *tag_name, struct got_object_id *object_id, const char *tagger,
+ time_t tagger_time, const char *tagmsg, struct got_repository *repo)
+{
+ const struct got_error *err = NULL;
+ SHA1_CTX sha1_ctx;
+ char *header = NULL;
+ char *tag_str = NULL, *tagger_str = NULL;
+ char *id_str = NULL, *obj_str = NULL, *type_str = NULL;
+ size_t headerlen, len = 0, n;
+ FILE *tagfile = NULL;
+ char *msg0 = NULL, *msg;
+ const char *obj_type_str;
+ int obj_type;
+
+ *id = NULL;
+
+ SHA1Init(&sha1_ctx);
+
+ err = got_object_id_str(&id_str, object_id);
+ if (err)
+ goto done;
+ if (asprintf(&obj_str, "%s%s\n", GOT_TAG_LABEL_OBJECT, id_str) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ err = got_object_get_type(&obj_type, repo, object_id);
+ if (err)
+ goto done;
+
+ switch (obj_type) {
+ case GOT_OBJ_TYPE_BLOB:
+ obj_type_str = GOT_OBJ_LABEL_BLOB;
+ break;
+ case GOT_OBJ_TYPE_TREE:
+ obj_type_str = GOT_OBJ_LABEL_TREE;
+ break;
+ case GOT_OBJ_TYPE_COMMIT:
+ obj_type_str = GOT_OBJ_LABEL_COMMIT;
+ break;
+ case GOT_OBJ_TYPE_TAG:
+ obj_type_str = GOT_OBJ_LABEL_TAG;
+ break;
+ default:
+ err = got_error(GOT_ERR_OBJ_TYPE);
+ goto done;
+ }
+
+ if (asprintf(&type_str, "%s%s\n", GOT_TAG_LABEL_TYPE,
+ obj_type_str) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ if (asprintf(&tag_str, "%s%s\n", GOT_TAG_LABEL_TAG, tag_name) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ if (asprintf(&tagger_str, "%s%s %lld +0000\n",
+ GOT_COMMIT_LABEL_AUTHOR, tagger, tagger_time) == -1)
+ return got_error_from_errno("asprintf");
+
+ msg0 = strdup(tagmsg);
+ if (msg0 == NULL) {
+ err = got_error_from_errno("strdup");
+ goto done;
+ }
+ msg = msg0;
+
+ while (isspace((unsigned char)msg[0]))
+ msg++;
+
+ len = strlen(obj_str) + strlen(type_str) + strlen(tag_str) +
+ strlen(tagger_str) + 1 + strlen(msg) + 1;
+
+ if (asprintf(&header, "%s %zd", GOT_OBJ_LABEL_TAG, len) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ headerlen = strlen(header) + 1;
+ SHA1Update(&sha1_ctx, header, headerlen);
+
+ tagfile = got_opentemp();
+ if (tagfile == NULL) {
+ err = got_error_from_errno("got_opentemp");
+ goto done;
+ }
+
+ n = fwrite(header, 1, headerlen, tagfile);
+ if (n != headerlen) {
+ err = got_ferror(tagfile, GOT_ERR_IO);
+ goto done;
+ }
+ len = strlen(obj_str);
+ SHA1Update(&sha1_ctx, obj_str, len);
+ n = fwrite(obj_str, 1, len, tagfile);
+ if (n != len) {
+ err = got_ferror(tagfile, GOT_ERR_IO);
+ goto done;
+ }
+ len = strlen(type_str);
+ SHA1Update(&sha1_ctx, type_str, len);
+ n = fwrite(type_str, 1, len, tagfile);
+ if (n != len) {
+ err = got_ferror(tagfile, GOT_ERR_IO);
+ goto done;
+ }
+
+ len = strlen(tag_str);
+ SHA1Update(&sha1_ctx, tag_str, len);
+ n = fwrite(tag_str, 1, len, tagfile);
+ if (n != len) {
+ err = got_ferror(tagfile, GOT_ERR_IO);
+ goto done;
+ }
+
+ len = strlen(tagger_str);
+ SHA1Update(&sha1_ctx, tagger_str, len);
+ n = fwrite(tagger_str, 1, len, tagfile);
+ if (n != len) {
+ err = got_ferror(tagfile, GOT_ERR_IO);
+ goto done;
+ }
+
+ SHA1Update(&sha1_ctx, "\n", 1);
+ n = fwrite("\n", 1, 1, tagfile);
+ if (n != 1) {
+ err = got_ferror(tagfile, GOT_ERR_IO);
+ goto done;
+ }
+
+ len = strlen(msg);
+ SHA1Update(&sha1_ctx, msg, len);
+ n = fwrite(msg, 1, len, tagfile);
+ if (n != len) {
+ err = got_ferror(tagfile, GOT_ERR_IO);
+ goto done;
+ }
+
+ SHA1Update(&sha1_ctx, "\n", 1);
+ n = fwrite("\n", 1, 1, tagfile);
+ if (n != 1) {
+ err = got_ferror(tagfile, GOT_ERR_IO);
+ goto done;
+ }
+
+ *id = malloc(sizeof(**id));
+ if (*id == NULL) {
+ err = got_error_from_errno("malloc");
+ goto done;
+ }
+ SHA1Final((*id)->sha1, &sha1_ctx);
+
+ if (fflush(tagfile) != 0) {
+ err = got_error_from_errno("fflush");
+ goto done;
+ }
+ rewind(tagfile);
+
+ err = create_object_file(*id, tagfile, repo);
+done:
+ free(msg0);
+ free(header);
+ free(obj_str);
+ free(tagger_str);
+ if (tagfile && fclose(tagfile) != 0 && err == NULL)
err = got_error_from_errno("fclose");
if (err) {
free(*id);
blob - 010a6bbd9da121a641c0c2f1321b9919f736476f
blob + 535e0b8450f2e076c694aaae464ec319797b783f
--- regress/cmdline/Makefile
+++ regress/cmdline/Makefile
-REGRESS_TARGETS=checkout update status log add rm diff blame branch ref commit \
- revert cherrypick backout rebase import histedit stage unstage cat
+REGRESS_TARGETS=checkout update status log add rm diff blame branch tag \
+ ref commit revert cherrypick backout rebase import histedit stage \
+ unstage cat
NOOBJ=Yes
checkout:
branch:
./branch.sh
+tag:
+ ./tag.sh
+
ref:
./ref.sh
blob - 346b80d2069558d1f86a09e0cce41f0e2d7f7a94
blob + 1b91f45a44501291c93f9f5e151ef448f9e31e77
--- regress/cmdline/common.sh
+++ regress/cmdline/common.sh
(cd $repo && git show --no-patch --pretty='format:%at' $object)
}
+function git_show_tagger_time
+{
+ local repo="$1"
+ local tag="$2"
+ (cd $repo && git cat-file tag $tag | grep ^tagger | \
+ sed -e "s/^tagger $GOT_AUTHOR//" | cut -d' ' -f2)
+}
+
function git_show_parent_commit
{
local repo="$1"
blob - /dev/null
blob + d76ab174ed2d704f7ea8afe42e9e6b7e6736a52e (mode 755)
--- /dev/null
+++ regress/cmdline/tag.sh
+#!/bin/sh
+#
+# Copyright (c) 2019 Stefan Sperling <stsp@openbsd.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+. ./common.sh
+
+function test_tag_create {
+ local testroot=`test_init tag_create`
+ local commit_id=`git_show_head $testroot/repo`
+ local tag=1.0.0
+ local tag2=2.0.0
+
+ # Create a tag based on repository's HEAD reference
+ got tag -m 'test' -r $testroot/repo $tag HEAD > $testroot/stdout
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ echo "got ref command failed unexpectedly"
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ tag_id=`got ref -r $testroot/repo -l \
+ | grep "^refs/tags/$tag" | tr -d ' ' | cut -d: -f2`
+ echo "Created tag $tag_id" > $testroot/stdout.expected
+ cmp -s $testroot/stdout $testroot/stdout.expected
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # Ensure that Git recognizes the tag Got has created
+ (cd $testroot/repo && git checkout -q $tag)
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ echo "git checkout command failed unexpectedly"
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # Ensure Got recognizes the new tag
+ got checkout -c $tag $testroot/repo $testroot/wt >/dev/null
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ echo "got checkout command failed unexpectedly"
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ # Create a tag based on implied worktree HEAD ref
+ (cd $testroot/wt && got tag -m 'test' $tag2 > $testroot/stdout)
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ tag_id2=`got ref -r $testroot/repo -l \
+ | grep "^refs/tags/$tag2" | tr -d ' ' | cut -d: -f2`
+ echo "Created tag $tag_id2" > $testroot/stdout.expected
+ cmp -s $testroot/stdout $testroot/stdout.expected
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ (cd $testroot/repo && git checkout -q $tag2)
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ echo "git checkout command failed unexpectedly"
+ test_done "$testroot" "$ret"
+ fi
+
+ # Attempt to create a tag pointing at a non-commit
+ local tree_id=`git_show_tree $testroot/repo`
+ (cd $testroot/wt && got tag -m 'test' foobar $tree_id \
+ 2> $testroot/stderr)
+ ret="$?"
+ if [ "$ret" == "0" ]; then
+ echo "git tag command succeeded unexpectedly"
+ test_done "$testroot" "1"
+ return 1
+ fi
+
+ echo "got: object not found" > $testroot/stderr.expected
+ cmp -s $testroot/stderr $testroot/stderr.expected
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stderr.expected $testroot/stderr
+ test_done "$testroot" "$ret"
+ return 1
+ fi
+
+ got ref -r $testroot/repo -l > $testroot/stdout
+ echo "HEAD: $commit_id" > $testroot/stdout.expected
+ echo -n "refs/got/worktree/base-" >> $testroot/stdout.expected
+ cat $testroot/wt/.got/uuid | tr -d '\n' >> $testroot/stdout.expected
+ echo ": $commit_id" >> $testroot/stdout.expected
+ echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+ echo "refs/tags/$tag: $tag_id" >> $testroot/stdout.expected
+ echo "refs/tags/$tag2: $tag_id2" >> $testroot/stdout.expected
+ cmp -s $testroot/stdout $testroot/stdout.expected
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+function test_tag_list {
+ local testroot=`test_init tag_list`
+ local commit_id=`git_show_head $testroot/repo`
+ local tag=1.0.0
+ local tag2=2.0.0
+
+ (cd $testroot/repo && git tag -a -m 'test' $tag)
+ (cd $testroot/repo && git tag -a -m 'test' $tag2)
+
+ tag_id=`got ref -r $testroot/repo -l \
+ | grep "^refs/tags/$tag" | tr -d ' ' | cut -d: -f2`
+ local tagger_time=`git_show_tagger_time $testroot/repo $tag`
+ d1=`env TZ=UTC date -r $tagger_time +"%a %b %d %X %Y UTC"`
+ tag_id2=`got ref -r $testroot/repo -l \
+ | grep "^refs/tags/$tag2" | tr -d ' ' | cut -d: -f2`
+ local tagger_time2=`git_show_tagger_time $testroot/repo $tag2`
+ d2=`env TZ=UTC date -r $tagger_time2 +"%a %b %d %X %Y UTC"`
+
+ got tag -r $testroot/repo -l > $testroot/stdout
+
+ echo "-----------------------------------------------" \
+ > $testroot/stdout.expected
+ echo "tag $tag $tag_id" >> $testroot/stdout.expected
+ echo "commit $commit_id" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $d1" >> $testroot/stdout.expected
+ echo " " >> $testroot/stdout.expected
+ echo " test" >> $testroot/stdout.expected
+ echo " " >> $testroot/stdout.expected
+ echo "-----------------------------------------------" \
+ >> $testroot/stdout.expected
+ echo "tag $tag2 $tag_id2" >> $testroot/stdout.expected
+ echo "commit $commit_id" >> $testroot/stdout.expected
+ echo "from: $GOT_AUTHOR" >> $testroot/stdout.expected
+ echo "date: $d2" >> $testroot/stdout.expected
+ echo " " >> $testroot/stdout.expected
+ echo " test" >> $testroot/stdout.expected
+ echo " " >> $testroot/stdout.expected
+ cmp -s $testroot/stdout $testroot/stdout.expected
+ ret="$?"
+ if [ "$ret" != "0" ]; then
+ diff -u $testroot/stdout.expected $testroot/stdout
+ fi
+ test_done "$testroot" "$ret"
+}
+
+run_test test_tag_create
+run_test test_tag_list