commit 8e7bd50a820730b5f8356254f7a44fa922fab3bc from: Stefan Sperling date: Thu Aug 22 20:20:56 2019 UTC initial implementation of 'got tag' command commit - a009df92847bdf738778c064cc50e9c204b6e1d6 commit + 8e7bd50a820730b5f8356254f7a44fa922fab3bc blob - 5cc02975e9734606b79d513463bbce1e7f4e8fd6 blob + c570e9380338cb7e97047870f80835c0834d134c --- got/got.1 +++ got/got.1 @@ -520,6 +520,59 @@ Git's garbage collector. .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. @@ -1124,7 +1177,10 @@ attempts to reject 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 @@ -88,6 +88,7 @@ __dead static void usage_tree(void); __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); @@ -111,6 +112,7 @@ static const struct got_error* cmd_tree(int, char *[] 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 *[]); @@ -135,6 +137,7 @@ static struct got_cmd got_commands[] = { { "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" }, @@ -3293,7 +3296,373 @@ done: 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 @@ -122,6 +122,7 @@ #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; @@ -250,6 +251,7 @@ static const struct got_error { { 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 @@ -275,3 +275,8 @@ const char *got_object_tag_get_message(struct got_tag_ 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 @@ -593,6 +593,184 @@ done: 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 @@ -1,5 +1,6 @@ -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: @@ -29,6 +30,9 @@ blame: branch: ./branch.sh +tag: + ./tag.sh + ref: ./ref.sh blob - 346b80d2069558d1f86a09e0cce41f0e2d7f7a94 blob + 1b91f45a44501291c93f9f5e151ef448f9e31e77 --- regress/cmdline/common.sh +++ regress/cmdline/common.sh @@ -56,6 +56,14 @@ function git_show_author_time (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 @@ -0,0 +1,172 @@ +#!/bin/sh +# +# Copyright (c) 2019 Stefan Sperling +# +# 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