Commit 303e2782e0a61a1cf76da52a945ab8645a8a78ca

Stefan Sperling 2019-08-09T13:37:56

add support for tags to -c options of some got commands

diff --git a/got/got.1 b/got/got.1
index 967828d..7f75a48 100644
--- a/got/got.1
+++ b/got/got.1
@@ -153,7 +153,7 @@ Check out files from the specified
 .Ar commit
 on the selected branch.
 The expected argument is a commit ID SHA1 hash or an existing reference
-which will be resolved to a commit ID.
+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.
 If this option is not specified, the most recent commit on the selected
@@ -223,7 +223,7 @@ This option requires that all paths in the work tree are updated.
 Update the work tree to the specified
 .Ar commit .
 The expected argument is a commit ID SHA1 hash or an existing reference
-which will be resolved to a commit ID.
+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.
 If this option is not specified, the most recent commit on the work tree's
@@ -299,7 +299,7 @@ are as follows:
 Start traversing history at the specified
 .Ar commit .
 The expected argument is a commit ID SHA1 hash or an existing reference
-which will be resolved to a commit ID.
+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.
 If this option is not specified, default to the work tree's current branch
@@ -374,7 +374,7 @@ are as follows:
 Start traversing history at the specified
 .Ar commit .
 The expected argument is a commit ID SHA1 hash or an existing reference
-which will be resolved to a commit ID.
+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.
 .It Fl r Ar repository-path
@@ -412,7 +412,7 @@ are as follows:
 List files and directories as they appear in the specified
 .Ar commit .
 The expected argument is a commit ID SHA1 hash or an existing reference
-which will be resolved to a commit ID.
+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.
 .It Fl r Ar repository-path
diff --git a/got/got.c b/got/got.c
index c05635e..e30bb0b 100644
--- a/got/got.c
+++ b/got/got.c
@@ -825,6 +825,19 @@ resolve_commit_arg(struct got_object_id **commit_id,
 {
 	const struct got_error *err;
 	struct got_reference *ref;
+	struct got_tag_object *tag;
+
+	err = got_repo_object_match_tag(&tag, commit_id_arg,
+	    GOT_OBJ_TYPE_COMMIT, repo);
+	if (err == NULL) {
+		*commit_id = got_object_id_dup(
+		    got_object_tag_get_object_id(tag));
+		if (*commit_id == NULL)
+			err = got_error_from_errno("got_object_id_dup");
+		got_object_tag_close(tag);
+		return err;
+	} else if (err->code != GOT_ERR_NO_OBJ)
+		return err;
 
 	err = got_ref_open(&ref, repo, commit_id_arg, 0);
 	if (err == NULL) {
diff --git a/include/got_repository.h b/include/got_repository.h
index 6b8435a..16cdca5 100644
--- a/include/got_repository.h
+++ b/include/got_repository.h
@@ -16,6 +16,7 @@
 
 struct got_repository;
 struct got_pathlist_head;
+struct got_tag_object;
 
 /* Open and close repositories. */
 const struct got_error *got_repo_open(struct got_repository**, const char *);
@@ -63,6 +64,13 @@ const struct got_error *got_repo_init(const char *);
 const struct got_error *got_repo_match_object_id_prefix(struct got_object_id **,
     const char *, int, struct got_repository *);
 
+/*
+ * Attempt to find a tag object with a given name and target object type.
+ * Return GOT_ERR_NO_OBJ if no matching tag can be found.
+ */
+const struct got_error *got_repo_object_match_tag(struct got_tag_object **,
+    const char *, int, struct got_repository *);
+
 /* A callback function which is invoked when a path is imported. */
 typedef const struct got_error *(*got_repo_import_cb)(void *, const char *);
 
diff --git a/lib/repository.c b/lib/repository.c
index 8f6a042..60e8e27 100644
--- a/lib/repository.c
+++ b/lib/repository.c
@@ -1115,6 +1115,50 @@ done:
 	return err;
 }
 
+const struct got_error *
+got_repo_object_match_tag(struct got_tag_object **tag, const char *name,
+    int obj_type, struct got_repository *repo)
+{
+	const struct got_error *err;
+	struct got_reflist_head refs;
+	struct got_reflist_entry *re;
+	struct got_object_id *tag_id;
+
+	SIMPLEQ_INIT(&refs);
+	*tag = NULL;
+
+	err = got_ref_list(&refs, repo);
+	if (err)
+		return err;
+
+	SIMPLEQ_FOREACH(re, &refs, entry) {
+		const char *refname;
+		refname = got_ref_get_name(re->ref);
+		if (got_ref_is_symbolic(re->ref) ||
+		    strncmp("refs/tags/", refname, 10) != 0)
+			continue;
+		refname += 10;
+		if (strcmp(refname, name) != 0)
+			continue;
+		err = got_ref_resolve(&tag_id, repo, re->ref);
+		if (err)
+			break;
+		err = got_object_open_as_tag(tag, repo, tag_id);
+		free(tag_id);
+		if (err)
+			break;
+		if (got_object_tag_get_object_type(*tag) == obj_type)
+			break;
+		got_object_tag_close(*tag);
+		*tag = NULL;
+	}
+
+	got_ref_list_free(&refs);
+	if (err == NULL && *tag == NULL)
+		err = got_error(GOT_ERR_NO_OBJ);
+	return err;
+}
+
 static const struct got_error *
 alloc_added_blob_tree_entry(struct got_tree_entry **new_te,
     const char *name, mode_t mode, struct got_object_id *blob_id)
diff --git a/regress/cmdline/blame.sh b/regress/cmdline/blame.sh
index e1dbec4..ccd7980 100755
--- a/regress/cmdline/blame.sh
+++ b/regress/cmdline/blame.sh
@@ -57,4 +57,45 @@ function test_blame_basic {
 	test_done "$testroot" "$ret"
 }
 
+function test_blame_tag {
+	local testroot=`test_init blame_tag`
+	local tag=1.0.0
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	echo 1 > $testroot/wt/alpha
+	(cd $testroot/wt && got commit -m "change 1" > /dev/null)
+	local commit1=`git_show_head $testroot/repo`
+
+	echo 2 >> $testroot/wt/alpha
+	(cd $testroot/wt && got commit -m "change 2" > /dev/null)
+	local commit2=`git_show_head $testroot/repo`
+
+	(cd $testroot/repo && git tag -a -m "test" $tag)
+
+	echo 3 >> $testroot/wt/alpha
+	(cd $testroot/wt && got commit -m "change 3" > /dev/null)
+	local commit3=`git_show_head $testroot/repo`
+
+	(cd $testroot/wt && got blame -c $tag alpha > $testroot/stdout)
+
+	local short_commit1=`trim_obj_id 32 $commit1`
+	local short_commit2=`trim_obj_id 32 $commit2`
+
+	echo "$short_commit1 1" > $testroot/stdout.expected
+	echo "$short_commit2 2" >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_blame_basic
+run_test test_blame_tag
diff --git a/regress/cmdline/checkout.sh b/regress/cmdline/checkout.sh
index 2f03e3f..b967040 100755
--- a/regress/cmdline/checkout.sh
+++ b/regress/cmdline/checkout.sh
@@ -210,8 +210,51 @@ function test_checkout_commit_from_wrong_branch {
 	test_done "$testroot" "$ret"
 }
 
+function test_checkout_tag {
+	local testroot=`test_init checkout_tag`
+	local tag="1.0.0"
+
+	(cd $testroot/repo && git tag -a -m "test" $tag)
+
+	echo "A  $testroot/wt/alpha" > $testroot/stdout.expected
+	echo "A  $testroot/wt/beta" >> $testroot/stdout.expected
+	echo "A  $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
+	echo "A  $testroot/wt/gamma/delta" >> $testroot/stdout.expected
+	echo "Now shut up and hack" >> $testroot/stdout.expected
+
+	got checkout -c $tag $testroot/repo $testroot/wt > $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "alpha" > $testroot/content.expected
+	echo "beta" >> $testroot/content.expected
+	echo "zeta" >> $testroot/content.expected
+	echo "delta" >> $testroot/content.expected
+	cat $testroot/wt/alpha $testroot/wt/beta $testroot/wt/epsilon/zeta \
+	    $testroot/wt/gamma/delta > $testroot/content
+
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_checkout_basic
 run_test test_checkout_dir_exists
 run_test test_checkout_dir_not_empty
 run_test test_checkout_sets_xbit
 run_test test_checkout_commit_from_wrong_branch
+run_test test_checkout_tag
diff --git a/regress/cmdline/log.sh b/regress/cmdline/log.sh
index 11f0724..fa21749 100755
--- a/regress/cmdline/log.sh
+++ b/regress/cmdline/log.sh
@@ -110,6 +110,33 @@ function test_log_in_worktree {
 	test_done "$testroot" "0"
 }
 
+function test_log_tag {
+	local testroot=`test_init log_tag`
+	local commit_id=`git_show_head $testroot/repo`
+	local tag="1.0.0"
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/repo && git tag -a -m "test" $tag)
+
+	echo "commit $commit_id (master)" > $testroot/stdout.expected
+	(cd $testroot/wt && got log -l1 -c $tag | grep ^commit \
+		> $testroot/stdout)
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
+
 run_test test_log_in_repo
 run_test test_log_in_bare_repo
 run_test test_log_in_worktree
+run_test test_log_tag
diff --git a/regress/cmdline/update.sh b/regress/cmdline/update.sh
index e4c5367..ec4a92d 100755
--- a/regress/cmdline/update.sh
+++ b/regress/cmdline/update.sh
@@ -1511,6 +1511,47 @@ function test_update_bumps_base_commit_id {
 	test_done "$testroot" "$ret"
 }
 
+function test_update_tag {
+	local testroot=`test_init update_tag`
+	local tag="1.0.0"
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified alpha" > $testroot/repo/alpha
+	git_commit $testroot/repo -m "modified alpha"
+	(cd $testroot/repo && git tag -m "test" -a $tag)
+
+	echo "U  alpha" > $testroot/stdout.expected
+	echo -n "Updated to commit " >> $testroot/stdout.expected
+	git_show_head $testroot/repo >> $testroot/stdout.expected
+	echo >> $testroot/stdout.expected
+
+	(cd $testroot/wt && got update -c $tag > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "modified alpha" > $testroot/content.expected
+	cat $testroot/wt/alpha > $testroot/content
+
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_update_basic
 run_test test_update_adds_file
 run_test test_update_deletes_file
@@ -1540,3 +1581,4 @@ run_test test_update_moved_branch_ref
 run_test test_update_to_another_branch
 run_test test_update_to_commit_on_wrong_branch
 run_test test_update_bumps_base_commit_id
+run_test test_update_tag