"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Mark Jamsek <mark@jamsek.com>
Subject:
Re: expand keyword support to more commands
To:
Mark Jamsek <mark@jamsek.com>
Cc:
gameoftrees@openbsd.org
Date:
Tue, 18 Jul 2023 00:38:14 +1000

Download raw body.

Thread
Mark Jamsek <mark@jamsek.com> wrote:
> This adds commit keywords to backout, branch, checkout, cherrypick, and
> patch. I've added some basic tests for the new commands, but also added
> some less trivial test cases to log regress.
> 
> The special case here is 'got checkout' as it creates a work tree based
> on either the repository's HEAD or the head of the branch specified with
> -b; therefore, irrespective of whether -b is used, the :base and :head
> keywords are equivalent as the work tree's base commit will always be
> the head of the checked-out branch.
> 
> I don't think this is a problem, but worth pointing out. Please see how
> I've noted this in the manual.
> 
> Of these new additions, this is especially useful for both the backout
> and cherrypick commands, but I think they will also get good use with
> the branch command too. I still want to add support to the blame, cat,
> tag, and tree commands but I've run out of time tonight so will do it
> tomorrow. It's trivial to make the change in got.c, but I want to have
> tests for them. And I would also like to support keywords in tog too;
> it will be nice to have that consistency across both UIs.
> 

I had a case of using HEAD to refer to both the repository and
work tree branch head; revised diff below:


-----------------------------------------------
commit 9e5708113dc8540b8491d9959309bb51823b2255 (main)
from: Mark Jamsek <mark@jamsek.dev>
date: Mon Jul 17 14:34:14 2023 UTC
 
 expand support for commit keywords to more got commands
 
 Add the ability to use keywords in the backout, branch, checkout, cherrypick,
 and patch commands. Includes some basic regress tests for the new commands,
 and also some more contrived test cases for 'got log -c[:]keyword[:(+|-)[N]]'.
 
 M  got/got.1                      |  194+  0-
 M  got/got.c                      |   46+  8-
 M  regress/cmdline/backout.sh     |   96+  0-
 M  regress/cmdline/branch.sh      |   58+  0-
 M  regress/cmdline/checkout.sh    |   76+  0-
 M  regress/cmdline/cherrypick.sh  |   63+  0-
 M  regress/cmdline/log.sh         |  103+  0-
 M  regress/cmdline/patch.sh       |   85+  0-

8 files changed, 721 insertions(+), 8 deletions(-)

diff e764eb5bc6671e7ecb9af487ced338743d239686 9e5708113dc8540b8491d9959309bb51823b2255
commit - e764eb5bc6671e7ecb9af487ced338743d239686
commit + 9e5708113dc8540b8491d9959309bb51823b2255
blob - 88a4286fff9c196d6fcc50a06ce49f886eb81d91
blob + 6850047d13f8afebd1c2166d9b5f9d3e689eab3b
--- got/got.1
+++ got/got.1
@@ -570,6 +570,43 @@ If this option is not specified, the most recent commi
 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 special
+.Ar commit
+keywords
+.Qq :base
+and
+.Qq :head
+can also be used, with both resolving to the
+repository's HEAD reference, or, if the
+.Fl b
+option is used, the head of the checked-out
+.Ar branch .
+Keywords and reference names may be appended with
+.Qq :+
+or
+.Qq :-
+modifiers and an optional integer N to denote the
+Nth descendant or antecedent by first parent traversal, respectively;
+for example,
+.Sy :head:-2
+denotes the work tree branch head's 2nd generation ancestor, and
+.Sy foo:-3
+will denote the 3rd generation ancestor of the commit resolved by the
+.Qq foo
+reference.
+If an integer does not follow the
+.Qq :+
+or
+.Qq :-
+modifier, a
+.Qq 1
+is implicitly appended
+.Po e.g.,
+.Sy :head:-
+is equivalent to
+.Sy :head:-1
+.Pc .
 If this option is not specified, the most recent commit on the selected
 branch will be used.
 .Pp
@@ -1364,6 +1401,46 @@ or tag name which will be resolved to a commit ID.
 .Ar commit
 argument is a commit ID SHA1 hash or an existing reference
 or tag name which will be resolved to a commit ID.
+.Pp
+The special
+.Ar commit
+keywords
+.Qq :base
+and
+.Qq :head
+can also be used to represent the work tree's base commit
+and branch head, respectively.
+The former is only valid if invoked in a work tree, while the latter will
+resolve to the tip of the work tree's current branch if invoked in a
+work tree, otherwise it will resolve to the repository's HEAD reference.
+Keywords and references may be appended with
+.Qq :+
+or
+.Qq :-
+modifiers and an optional integer N to denote the
+Nth descendant or antecedent by first parent traversal, respectively;
+for example,
+.Sy :head:-2
+denotes the work tree branch head's 2nd generation ancestor, and
+.Sy :base:+4
+denotes the 4th generation descendant of the work tree's base commit.
+Similarly,
+.Sy foobar:+3
+will denote the 3rd generation descendant of the commit resolved by the
+.Qq foobar
+reference.
+A
+.Qq :+
+or
+.Qq :-
+modifier without a trailing integer has an implicit
+.Qq 1
+appended
+.Po e.g.,
+.Sy :base:+
+is equivalent to
+.Sy :base:+1
+.Pc .
 .It Fl d Ar name
 Delete the branch with the specified
 .Ar name
@@ -1687,6 +1764,49 @@ Ideally, the specified
 Attempt to locate files within the specified
 .Ar commit
 for use as a merge-base for 3-way merges.
+The expected
+.Ar commit
+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 special
+.Ar commit
+keywords
+.Qq :base
+and
+.Qq :head
+can also be used to represent the work tree's base commit
+and branch head, respectively.
+Keywords and references may be appended with
+.Qq :+
+or
+.Qq :-
+modifiers and an optional integer N to denote the
+Nth descendant or antecedent by first parent traversal, respectively;
+for example,
+.Sy :head:-2
+denotes the work tree branch head's 2nd generation ancestor, and
+.Sy :base:+4
+denotes the 4th generation descendant of the work tree's base commit.
+Similarly,
+.Sy flan:+3
+will denote the 3rd generation descendant of the commit resolved by the
+.Qq flan
+reference.
+A
+.Qq :+
+or
+.Qq :-
+modifier without a trailing integer has an implicit
+.Qq 1
+appended
+.Po e.g.,
+.Sy :base:+
+is equivalent to
+.Sy :base:+1
+.Pc .
 Ideally, the specified
 .Ar commit
 should contain versions of files which the changes contained in the
@@ -2166,6 +2286,43 @@ Show the status of each affected file, using the follo
 An abbreviated hash argument will be expanded to a full SHA1 hash
 automatically, provided the abbreviation is unique.
 .Pp
+The special
+.Ar commit
+keywords
+.Qq :base
+and
+.Qq :head
+can also be used to represent the work tree's base commit
+and branch head, respectively.
+Keywords and references may be appended with
+.Qq :+
+or
+.Qq :-
+modifiers and an optional integer N to denote the
+Nth descendant or antecedent by first parent traversal, respectively;
+for example,
+.Sy :head:-2
+denotes the work tree branch head's 2nd generation ancestor, and
+.Sy :base:+4
+denotes the 4th generation descendant of the work tree's base commit.
+Similarly,
+.Sy barbaz:+3
+will denote the 3rd generation descendant of the commit resolved by the
+.Qq barbaz
+reference.
+A
+.Qq :+
+or
+.Qq :-
+modifier without a trailing integer has an implicit
+.Qq 1
+appended
+.Po e.g.,
+.Sy :base:+
+is equivalent to
+.Sy :base:+1
+.Pc .
+.Pp
 Show the status of each affected file, using the following status codes:
 .Bl -column YXZ description
 .It G Ta file was merged
@@ -2274,6 +2431,43 @@ Show the status of each affected file, using the follo
 An abbreviated hash argument will be expanded to a full SHA1 hash
 automatically, provided the abbreviation is unique.
 .Pp
+The special
+.Ar commit
+keywords
+.Qq :base
+and
+.Qq :head
+can also be used to represent the work tree's base commit
+and branch head, respectively.
+Keywords and references may be appended with
+.Qq :+
+or
+.Qq :-
+modifiers and an optional integer N to denote the
+Nth descendant or antecedent by first parent traversal, respectively;
+for example,
+.Sy :head:-2
+denotes the work tree branch head's 2nd generation ancestor, and
+.Sy :base:+4
+denotes the 4th generation descendant of the work tree's base commit.
+Similarly,
+.Sy wip:+5
+will denote the 5th generation descendant of the commit resolved by the
+.Qq wip
+reference.
+A
+.Qq :+
+or
+.Qq :-
+modifier without a trailing integer has an implicit
+.Qq 1
+appended
+.Po e.g.,
+.Sy :base:+
+is equivalent to
+.Sy :base:+1
+.Pc .
+.Pp
 Show the status of each affected file, using the following status codes:
 .Bl -column YXZ description
 .It G Ta file was merged
blob - 23959f9ae67808be718c67e326bfd66e1358aeb2
blob + f0cea264f130ba792c3c19b55d2632c8a81f9735
--- got/got.c
+++ got/got.c
@@ -2981,7 +2981,7 @@ cmd_checkout(int argc, char *argv[])
 	char *worktree_path = NULL;
 	const char *path_prefix = "";
 	const char *branch_name = GOT_REF_HEAD, *refname = NULL;
-	char *commit_id_str = NULL;
+	char *commit_id_str = NULL, *keyword_idstr = NULL;
 	struct got_object_id *commit_id = NULL;
 	char *cwd = NULL;
 	int ch, same_path_prefix, allow_nonempty = 0, verbosity = 0;
@@ -3131,6 +3131,16 @@ cmd_checkout(int argc, char *argv[])
 		    NULL);
 		if (error)
 			goto done;
+
+		error = got_keyword_to_idstr(&keyword_idstr, commit_id_str,
+		    repo, worktree);
+		if (error != NULL)
+			goto done;
+		if (keyword_idstr != NULL) {
+			free(commit_id_str);
+			commit_id_str = keyword_idstr;
+		}
+
 		error = got_repo_match_object_id(&commit_id, NULL,
 		    commit_id_str, GOT_OBJ_TYPE_COMMIT, &refs, repo);
 		got_ref_list_free(&refs);
@@ -6974,7 +6984,7 @@ cmd_branch(int argc, char *argv[])
 	struct got_reference *ref = NULL;
 	struct got_pathlist_head paths;
 	struct got_object_id *commit_id = NULL;
-	char *commit_id_str = NULL;
+	char *commit_id_str = NULL, *keyword_idstr = NULL;;
 	int *pack_fds = NULL;
 
 	TAILQ_INIT(&paths);
@@ -7102,6 +7112,14 @@ cmd_branch(int argc, char *argv[])
 			commit_id_arg = worktree ?
 			    got_worktree_get_head_ref_name(worktree) :
 			    GOT_REF_HEAD;
+		else {
+			error = got_keyword_to_idstr(&keyword_idstr,
+			    commit_id_arg, repo, worktree);
+			if (error != NULL)
+				goto done;
+			if (keyword_idstr != NULL)
+				commit_id_arg = keyword_idstr;
+		}
 		error = got_repo_match_object_id(&commit_id, NULL,
 		    commit_id_arg, GOT_OBJ_TYPE_COMMIT, &refs, repo);
 		got_ref_list_free(&refs);
@@ -7153,6 +7171,7 @@ done:
 		}
 	}
 done:
+	free(keyword_idstr);
 	if (ref)
 		got_ref_close(ref);
 	if (repo) {
@@ -8262,7 +8281,7 @@ cmd_patch(int argc, char *argv[])
 	const char *commit_id_str = NULL;
 	struct stat sb;
 	const char *errstr;
-	char *cwd = NULL;
+	char *cwd = NULL, *keyword_idstr = NULL;
 	int ch, nop = 0, strip = -1, reverse = 0;
 	int patchfd;
 	int *pack_fds = NULL;
@@ -8351,8 +8370,14 @@ cmd_patch(int argc, char *argv[])
 		goto done;
 
 	if (commit_id_str != NULL) {
+		error = got_keyword_to_idstr(&keyword_idstr, commit_id_str,
+		    repo, worktree);
+		if (error != NULL)
+			goto done;
+
 		error = got_repo_match_object_id(&commit_id, NULL,
-		    commit_id_str, GOT_OBJ_TYPE_COMMIT, &refs, repo);
+		    keyword_idstr != NULL ? keyword_idstr : commit_id_str,
+		    GOT_OBJ_TYPE_COMMIT, &refs, repo);
 		if (error)
 			goto done;
 	}
@@ -8363,6 +8388,7 @@ done:
 	print_patch_progress_stats(&ppa);
 done:
 	got_ref_list_free(&refs);
+	free(keyword_idstr);
 	free(commit_id);
 	if (repo) {
 		close_error = got_repo_close(repo);
@@ -10194,7 +10220,7 @@ cmd_cherrypick(int argc, char *argv[])
 	const struct got_error *error = NULL;
 	struct got_worktree *worktree = NULL;
 	struct got_repository *repo = NULL;
-	char *cwd = NULL, *commit_id_str = NULL;
+	char *cwd = NULL, *commit_id_str = NULL, *keyword_idstr = NULL;
 	struct got_object_id *commit_id = NULL;
 	struct got_commit_object *commit = NULL;
 	struct got_object_qid *pid;
@@ -10274,7 +10300,12 @@ cmd_cherrypick(int argc, char *argv[])
 		goto done;
 	}
 
-	error = got_repo_match_object_id(&commit_id, NULL, argv[0],
+	error = got_keyword_to_idstr(&keyword_idstr, argv[0], repo, worktree);
+	if (error != NULL)
+		goto done;
+
+	error = got_repo_match_object_id(&commit_id, NULL,
+	    keyword_idstr != NULL ? keyword_idstr : argv[0],
 	    GOT_OBJ_TYPE_COMMIT, NULL, repo);
 	if (error)
 		goto done;
@@ -10303,6 +10334,7 @@ done:
 	print_merge_progress_stats(&upa);
 done:
 	free(cwd);
+	free(keyword_idstr);
 	if (commit)
 		got_object_commit_close(commit);
 	free(commit_id_str);
@@ -10336,7 +10368,7 @@ cmd_backout(int argc, char *argv[])
 	const struct got_error *error = NULL;
 	struct got_worktree *worktree = NULL;
 	struct got_repository *repo = NULL;
-	char *cwd = NULL, *commit_id_str = NULL;
+	char *cwd = NULL, *commit_id_str = NULL, *keyword_idstr = NULL;
 	struct got_object_id *commit_id = NULL;
 	struct got_commit_object *commit = NULL;
 	struct got_object_qid *pid;
@@ -10416,7 +10448,12 @@ cmd_backout(int argc, char *argv[])
 		goto done;
 	}
 
-	error = got_repo_match_object_id(&commit_id, NULL, argv[0],
+	error = got_keyword_to_idstr(&keyword_idstr, argv[0], repo, worktree);
+	if (error != NULL)
+		goto done;
+
+	error = got_repo_match_object_id(&commit_id, NULL,
+	    keyword_idstr != NULL ? keyword_idstr : argv[0],
 	    GOT_OBJ_TYPE_COMMIT, NULL, repo);
 	if (error)
 		goto done;
@@ -10449,6 +10486,7 @@ done:
 	print_merge_progress_stats(&upa);
 done:
 	free(cwd);
+	free(keyword_idstr);
 	if (commit)
 		got_object_commit_close(commit);
 	free(commit_id_str);
blob - 86b8cddd9616882e06b859c2bd3e8de87a317150
blob + 76bc77fd0afea94f3494da3f05e3f617d0019853
--- regress/cmdline/backout.sh
+++ regress/cmdline/backout.sh
@@ -543,9 +543,105 @@ test_parseargs "$@"
 	test_done "$testroot" "$ret"
 }
 
+test_backout_commit_keywords() {
+	local testroot=$(test_init backout_commit_keywords)
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "new" > $testroot/wt/new
+	(cd $testroot/wt && got add new > /dev/null)
+	echo "modified alpha" > $testroot/wt/alpha
+	(cd $testroot/wt && got rm epsilon/zeta > /dev/null)
+	(cd $testroot/wt && got commit -m "bad changes" > /dev/null)
+
+	local bad_commit=`git_show_head $testroot/repo`
+
+	(cd $testroot/wt && got update > /dev/null)
+
+	echo "modified beta" > $testroot/wt/beta
+	(cd $testroot/wt && got commit -m "changing beta" > /dev/null)
+	echo "modified beta again" > $testroot/wt/beta
+	(cd $testroot/wt && got commit -m "changing beta again" > /dev/null)
+
+	(cd $testroot/wt && got update > /dev/null)
+
+	(cd $testroot/wt && got bo :head:-2 > $testroot/stdout)
+
+	echo "G  alpha" > $testroot/stdout.expected
+	echo "A  epsilon/zeta" >> $testroot/stdout.expected
+	echo "D  new" >> $testroot/stdout.expected
+	echo "Backed out commit $bad_commit" >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "alpha" > $testroot/content.expected
+	cat $testroot/wt/alpha > $testroot/content
+	cmp -s $testroot/content.expected $testroot/content
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/content.expected $testroot/content
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	if [ -e "$testroot/wt/new" ]; then
+		echo "file '$testroot/wt/new' still exists on disk" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	if [ ! -e "$testroot/wt/epsilon/zeta" ]; then
+		echo "file '$testroot/wt/epsilon/zeta' is missing on disk" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo 'M  alpha' > $testroot/stdout.expected
+	echo 'A  epsilon/zeta' >> $testroot/stdout.expected
+	echo 'D  new' >> $testroot/stdout.expected
+	(cd $testroot/wt && got status > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	local next_backout=`git_show_head $testroot/repo`
+
+	(cd "$testroot/wt" && got ci -m "backed-out bad commit" > /dev/null)
+	(cd "$testroot/wt" && got up > /dev/null)
+
+	echo "G  beta" > $testroot/stdout.expected
+	echo "Backed out commit $next_backout" >> $testroot/stdout.expected
+	(cd "$testroot/wt" && got bo :base:- > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_backout_basic
 run_test test_backout_edits_for_file_since_deleted
 run_test test_backout_next_commit
 run_test test_backout_umask
 run_test test_backout_logmsg_ref
+run_test test_backout_commit_keywords
blob - cab7171d5632c0bdf72e14e1d6dda98d3a4ce810
blob + 71213582275291870b3a3f5941f02a76914cc1c1
--- regress/cmdline/branch.sh
+++ regress/cmdline/branch.sh
@@ -537,6 +537,63 @@ test_parseargs "$@"
 	test_done "$testroot" "$ret"
 }
 
+test_branch_commit_keywords() {
+	local testroot=$(test_init branch_commit_keywords)
+
+	set -A ids "$(git_show_head $testroot/repo)"
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "checkout failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	for i in $(seq 4); do
+		echo "beta change $i" > "$testroot/wt/beta"
+
+		(cd "$testroot/wt" && got ci -m "commit number $i" > /dev/null)
+		ret=$?
+		if [ $ret -ne 0 ]; then
+			echo "commit failed unexpectedly" >&2
+			test_done "$testroot" "$ret"
+			return 1
+		fi
+		set -- "$ids" "$(git_show_head $testroot/repo)"
+		ids=$*
+	done
+
+	(cd "$testroot/wt" && got up > /dev/null)
+
+	echo "  kwbranch: $(pop_id 3 $ids)" > $testroot/stdout.expected
+	echo "  master: $(pop_id 5 $ids)" >> $testroot/stdout.expected
+
+	(cd "$testroot/wt" && got br -nc :head:-2 kwbranch > /dev/null)
+	got br -r "$testroot/repo" -l > "$testroot/stdout"
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "  kwbranch2: $(pop_id 4 $ids)" > $testroot/stdout.expected
+
+	got br -r "$testroot/repo" -c master:- kwbranch2 > /dev/null
+	got br -r "$testroot/repo" -l | grep kwbranch2 > "$testroot/stdout"
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_branch_create
 run_test test_branch_list
@@ -545,3 +602,4 @@ run_test test_branch_packed_ref_collision
 run_test test_branch_delete_packed
 run_test test_branch_show
 run_test test_branch_packed_ref_collision
+run_test test_branch_commit_keywords
blob - 9403e4715c6ed077315fb8b1bcb68bccd4247176
blob + 9bdce2b8a07cd2893ca474983afc996ca236d190
--- regress/cmdline/checkout.sh
+++ regress/cmdline/checkout.sh
@@ -932,6 +932,81 @@ test_parseargs "$@"
 	test_done "$testroot" "$ret"
 }
 
+test_checkout_commit_keywords() {
+	local testroot=$(test_init checkout_commit_keywords)
+
+	set -A ids "$(git_show_head $testroot/repo)"
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "checkout failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	for i in $(seq 4); do
+		echo "zeta change $i" > "$testroot/wt/epsilon/zeta"
+
+		(cd "$testroot/wt" && got ci -m "commit number $i" > /dev/null)
+		ret=$?
+		if [ $ret -ne 0 ]; then
+			echo "commit failed unexpectedly" >&2
+			test_done "$testroot" "$ret"
+			return 1
+		fi
+		set -- "$ids" "$(git_show_head $testroot/repo)"
+		ids=$*
+	done
+
+	echo "A  $testroot/wt2/alpha" > $testroot/stdout.expected
+	echo "A  $testroot/wt2/beta" >> $testroot/stdout.expected
+	echo "A  $testroot/wt2/epsilon/zeta" >> $testroot/stdout.expected
+	echo "A  $testroot/wt2/gamma/delta" >> $testroot/stdout.expected
+	echo "Checked out refs/heads/master: $(pop_id 4 $ids)" \
+		>> $testroot/stdout.expected
+	echo "Now shut up and hack" >> $testroot/stdout.expected
+
+	got co -c :head:- $testroot/repo $testroot/wt2 > $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "A  $testroot/wt3/alpha" > $testroot/stdout.expected
+	echo "A  $testroot/wt3/beta" >> $testroot/stdout.expected
+	echo "A  $testroot/wt3/epsilon/zeta" >> $testroot/stdout.expected
+	echo "A  $testroot/wt3/gamma/delta" >> $testroot/stdout.expected
+	echo "Checked out refs/heads/master: $(pop_id 4 $ids)" \
+		>> $testroot/stdout.expected
+	echo "Now shut up and hack" >> $testroot/stdout.expected
+
+	got co -bmaster -c:base:- $testroot/repo $testroot/wt3 > \
+	    $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_checkout_basic
 run_test test_checkout_dir_exists
@@ -948,3 +1023,4 @@ run_test test_checkout_ulimit_n
 run_test test_checkout_quiet
 run_test test_checkout_umask
 run_test test_checkout_ulimit_n
+run_test test_checkout_commit_keywords
blob - 364fe8dd78d0449f3b9718c310a7ead6bddbe3a6
blob + 8de61842e9e87808d61b276b6d639e2f1b7d5e09
--- regress/cmdline/cherrypick.sh
+++ regress/cmdline/cherrypick.sh
@@ -2028,6 +2028,68 @@ test_parseargs "$@"
 	test_done "$testroot" "$ret"
 }
 
+test_cherrypick_commit_keywords() {
+	local testroot=`test_init cherrypick_commit_keywords`
+
+	set -A ids "$(git_show_head $testroot/repo)"
+
+	(cd $testroot/repo && git checkout -q -b branch-1)
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "checkout failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "changed on branch-1" >> "$testroot/repo/alpha"
+	git_commit $testroot/repo -m "alpha changed on branch-1"
+	set -- "$ids" "$(git_show_head $testroot/repo)"
+	ids=$*
+
+	for i in $(seq 4); do
+		echo "branch-1 change $i" >> "$testroot/repo/gamma/delta"
+
+		git_commit $testroot/repo -m "commit number $i"
+		set -- "$ids" "$(git_show_head $testroot/repo)"
+		ids=$*
+	done
+
+	echo "G  alpha" > $testroot/stdout.expected
+	echo "Merged commit $(pop_id 2 $ids)" >> $testroot/stdout.expected
+
+	(cd "$testroot/wt" && got cy master:+ > $testroot/stdout)
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd "$testroot/wt" && got rv alpha > /dev/null)
+
+	echo "C  gamma/delta" > $testroot/stdout.expected
+	echo "Merged commit $(pop_id 5 $ids)" >> $testroot/stdout.expected
+	echo "Files with new merge conflicts: 1" >> $testroot/stdout.expected
+	(cd "$testroot/wt" && got cy branch-1:- > $testroot/stdout)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_cherrypick_basic
 run_test test_cherrypick_root_commit
@@ -2047,3 +2109,4 @@ run_test test_cherrypick_logmsg_ref
 run_test test_cherrypick_binary_file
 run_test test_cherrypick_umask
 run_test test_cherrypick_logmsg_ref
+run_test test_cherrypick_commit_keywords
blob - 96004ce8b8a631507d11bbb3ba545448a1a16429
blob + 58e79bdb23ae3a08aa668ad20b439963c6eda8d1
--- regress/cmdline/log.sh
+++ regress/cmdline/log.sh
@@ -1043,8 +1043,111 @@ test_log_commit_keywords() {
 	ret=$?
 	if [ $ret -ne 0 ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
 	fi
 
+	echo "got: '::base:+': invalid commit keyword" > \
+	    $testroot/stderr.expected
+
+	(cd $testroot/wt && got log -c::base:+ 2> $testroot/stderr)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: ':head:-:': invalid commit keyword" > \
+	    $testroot/stderr.expected
+
+	(cd $testroot/wt && got log -c:head:-: 2> $testroot/stderr)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: 'master::+': invalid commit keyword" > \
+	    $testroot/stderr.expected
+
+	(cd $testroot/wt && got log -cmaster::+ 2> $testroot/stderr)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: 'master:1+': invalid commit keyword" > \
+	    $testroot/stderr.expected
+
+	(cd $testroot/wt && got log -cmaster:1+ 2> $testroot/stderr)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: ':base:-1:base:-1': invalid commit keyword" > \
+	    $testroot/stderr.expected
+
+	(cd $testroot/wt && got log -c:base:-1:base:-1 2> $testroot/stderr)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: 'main:-main:-': invalid commit keyword" > \
+	    $testroot/stderr.expected
+
+	(cd $testroot/wt && got log -cmain:-main:- 2> $testroot/stderr)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: ':base:*1': invalid commit keyword" > \
+	    $testroot/stderr.expected
+
+	(cd $testroot/wt && got log -c:base:*1 2> $testroot/stderr)
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: reference null not found" > $testroot/stderr.expected
+
+	(cd $testroot/wt && got log -cnull:+ 2> $testroot/stderr)
+
+	cmp -s $testroot/stderr.expected $testroot/stderr
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+	fi
+
 	test_done "$testroot" "$ret"
 }
 
blob - 17db471e5c3aa81e0eb9233b406861cdccd08fcd
blob + f95aac7980de13e20863266dcd1ae971cb702364
--- regress/cmdline/patch.sh
+++ regress/cmdline/patch.sh
@@ -1956,6 +1956,90 @@ test_parseargs "$@"
 	test_done $testroot 0
 }
 
+test_patch_commit_keywords() {
+	local testroot=`test_init patch_commit_keywords`
+
+	got checkout $testroot/repo $testroot/wt >/dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	jot 10 > $testroot/wt/numbers
+	(cd $testroot/wt && got add numbers && got commit -m +numbers) \
+		>/dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	jot 10 | sed s/4/four/ > $testroot/wt/numbers
+
+	# get rid of the metadata
+	(cd $testroot/wt && got diff | sed -n '/^---/,$p' > patch) \
+		>/dev/null
+
+	jot 10 | sed s/6/six/ > $testroot/wt/numbers
+	(cd $testroot/wt && got commit -m 'edit numbers') >/dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	(cd $testroot/wt && got patch -c :head:- patch) >$testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo 'G  numbers' > $testroot/stdout.expected
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout $testroot/stdout.expected
+		test_done $testroot $ret
+		return 1
+	fi
+
+	jot 10 | sed -e s/4/four/ -e s/6/six/ > $testroot/wt/numbers.expected
+	cmp -s $testroot/wt/numbers $testroot/wt/numbers.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/wt/numbers $testroot/wt/numbers.expected
+	fi
+
+	(cd "$testroot/wt" && got rv numbers > /dev/null)
+
+	(cd $testroot/wt && got patch -c :base:- patch) >$testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo 'G  numbers' > $testroot/stdout.expected
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout $testroot/stdout.expected
+		test_done $testroot $ret
+		return 1
+	fi
+
+	jot 10 | sed -e s/4/four/ -e s/6/six/ > $testroot/wt/numbers.expected
+	cmp -s $testroot/wt/numbers $testroot/wt/numbers.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/wt/numbers $testroot/wt/numbers.expected
+	fi
+
+	test_done $testroot $ret
+}
+
 test_parseargs "$@"
 run_test test_patch_basic
 run_test test_patch_dont_apply
@@ -1986,3 +2070,4 @@ run_test test_patch_remove_binary_file
 run_test test_patch_newfile_xbit_git_diff
 run_test test_patch_umask
 run_test test_patch_remove_binary_file
+run_test test_patch_commit_keywords



-- 
Mark Jamsek <https://bsdbox.org>
GPG: F2FF 13DE 6A06 C471 CA80  E6E2 2930 DC66 86EE CF68