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

From:
Mark Jamsek <mark@jamsek.com>
Subject:
add keyword support to tog
To:
gameoftrees@openbsd.org
Date:
Wed, 19 Jul 2023 21:19:48 +1000

Download raw body.

Thread
As per the subject, this adds support for keywords to the tog blame,
diff, log, and tree commands, including regress.

The change to tog.c is quite minimal, although there's also a seemingly
unrelated change where we now return 1 when exiting with error rather
than 0, but this is so we can check for invalid keyword results
otherwise regress thinks tog succeeded unexpectedly. I'm unsure,
however, if we are exiting 0 here despite error for some reason?


-----------------------------------------------
commit 4ae2198e7e133a396266894ede59f6202b7d4647 (main)
from: Mark Jamsek <mark@jamsek.dev>
date: Wed Jul 19 11:07:12 2023 UTC
 
 tog: add support for commit keywords
 
 Allow keywords as arguments to options and operands for the blame, diff, log,
 and tree commands. Also, return 1 when exiting with error rather than 0.
 
 M  regress/tog/blame.sh  |  229+  0-
 M  regress/tog/diff.sh   |  171+  0-
 M  regress/tog/log.sh    |  146+  0-
 M  regress/tog/tree.sh   |  165+  0-
 M  tog/Makefile          |    1+  1-
 M  tog/tog.1             |  153+  7-
 M  tog/tog.c             |   52+  5-

7 files changed, 917 insertions(+), 13 deletions(-)

diff df6221c7df42758252c508006201c3f66e6ae831 4ae2198e7e133a396266894ede59f6202b7d4647
commit - df6221c7df42758252c508006201c3f66e6ae831
commit + 4ae2198e7e133a396266894ede59f6202b7d4647
blob - 93c8f6f920ce4b2013710cddd1e94d1b83b0a7ad
blob + a329dca28bf36978b73b04fe71d37578bfbc712f
--- regress/tog/blame.sh
+++ regress/tog/blame.sh
@@ -76,5 +76,234 @@ test_parseargs "$@"
 	test_done "$testroot" "$ret"
 }
 
+test_blame_commit_keywords()
+{
+	test_init blame_commit_keywords 80 10
+	local repo="$testroot/repo"
+	local wt="$testroot/wt"
+	local id=$(git_show_head "$repo")
+	local author_time=$(git_show_author_time "$repo")
+	local ymd=$(date -u -r $author_time +"%G-%m-%d")
+
+	set -A ids "$id"
+	set -A short_ids "$(trim_obj_id 32 $id)"
+
+	cat <<-EOF >$TOG_TEST_SCRIPT
+	WAIT_FOR_UI	wait for blame to finish
+	SCREENDUMP
+	EOF
+
+	# :base requires work tree
+	echo "tog: '-c :base' requires work tree" > "$testroot/stderr.expected"
+	tog blame -r "$repo" -c:base alpha 2> "$testroot/stderr"
+	ret=$?
+	if [ $ret -eq 0 ]; then
+		echo "blame command succeeded unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cmp -s "$testroot/stderr.expected" "$testroot/stderr"
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u "$testroot/stderr.expected" "$testroot/stderr"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# :head keyword in repo
+	cat <<-EOF >$testroot/view.expected
+	commit $id
+	[1/1] /alpha
+	$(pop_id 1 $short_ids) alpha
+
+
+
+
+
+
+
+	EOF
+
+	tog blame -r "$repo" -c:head alpha
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "blame command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	got checkout "$repo" "$wt" > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "got checkout failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# move into the work tree (test is run in a subshell)
+	cd "$wt"
+	echo -n > alpha
+
+	for i in $(seq 8); do
+		echo "alpha $i" >> alpha
+
+		got ci -m "commit $i" > /dev/null
+		ret=$?
+		if [ $ret -ne 0 ]; then
+			echo "commit failed unexpectedly" >&2
+			test_done "$testroot" "$ret"
+			return 1
+		fi
+
+		id=$(git_show_head "$repo")
+		set -- "$ids" "$id"
+		ids=$*
+		set -- "$short_ids" "$(trim_obj_id 32 $id)"
+		short_ids=$*
+	done
+
+	author_time=$(git_show_author_time "$repo")
+	ymd=$(date -u -r $author_time +"%G-%m-%d")
+
+	# :base:- keyword in work tree
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 8 $ids)
+	[1/7] /alpha
+	$(pop_id 2 $short_ids) alpha 1
+	$(pop_id 3 $short_ids) alpha 2
+	$(pop_id 4 $short_ids) alpha 3
+	$(pop_id 5 $short_ids) alpha 4
+	$(pop_id 6 $short_ids) alpha 5
+	$(pop_id 7 $short_ids) alpha 6
+	$(pop_id 8 $short_ids) alpha 7
+
+	EOF
+
+	tog blame -c:base:- alpha
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "blame command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# :head:-4 keyword in work tree
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 5 $ids)
+	[1/4] /alpha
+	$(pop_id 2 $short_ids) alpha 1
+	$(pop_id 3 $short_ids) alpha 2
+	$(pop_id 4 $short_ids) alpha 3
+	$(pop_id 5 $short_ids) alpha 4
+
+
+
+
+	EOF
+
+	tog blame -c:head:-4 alpha
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "blame command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# :base:+2 keyword in work tree
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 5 $ids)
+	[1/4] /alpha
+	$(pop_id 2 $short_ids) alpha 1
+	$(pop_id 3 $short_ids) alpha 2
+	$(pop_id 4 $short_ids) alpha 3
+	$(pop_id 5 $short_ids) alpha 4
+
+
+
+
+	EOF
+
+	got up -c:head:-6 > /dev/null
+	if [ $ret -ne 0 ]; then
+		echo "update command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	tog blame -c:base:+2 alpha
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "blame command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# master:-99 keyword in work tree
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 1 $ids)
+	[1/1] /alpha
+	$(pop_id 1 $short_ids) alpha
+
+
+
+
+
+
+
+	EOF
+
+	tog blame -cmaster:-99 alpha
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "blame command failed unexpectedly" >&2
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_blame_basic
+run_test test_blame_commit_keywords
blob - 53edc5c3c79e0951bfdaf8315a353c3430a7b579
blob + 6ab39dc440fb2afb20ecaabc0425b545530d6c6d
--- regress/tog/diff.sh
+++ regress/tog/diff.sh
@@ -205,7 +205,178 @@ test_parseargs "$@"
 	test_done "$testroot" "$ret"
 }
 
+test_diff_commit_keywords()
+{
+	test_init diff_commit_keywords 120 24
+	local repo="$testroot/repo"
+	local wt="$testroot/wt"
+	local id=$(git_show_head "$repo")
+	local author_time=$(git_show_author_time "$repo")
+	local date=$(date -u -r $author_time +"%a %b %e %X %Y UTC")
+
+	set -A ids "$id"
+	set -A alpha_ids $(get_blob_id "$repo" "" alpha)
+
+	got checkout "$repo" "$wt" > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "got checkout failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# move into the work tree (test is run in a subshell)
+	cd "$wt"
+
+	for i in $(seq 8); do
+		echo "alpha $i" > alpha
+
+		got ci -m "commit $i" > /dev/null
+		ret=$?
+		if [ $ret -ne 0 ]; then
+			echo "commit failed unexpectedly" >&2
+			test_done "$testroot" "$ret"
+			return 1
+		fi
+
+		id=$(git_show_head "$repo")
+		set -- "$ids" "$id"
+		ids=$*
+		set -- "$alpha_ids" "$(get_blob_id "$repo" "" alpha)"
+		alpha_ids=$*
+	done
+
+	cat <<-EOF >$TOG_TEST_SCRIPT
+	SCREENDUMP
+	EOF
+
+	# diff consecutive commits with keywords
+	local lhs_id=$(pop_id 1 $ids)
+	local rhs_id=$(pop_id 2 $ids)
+
+	cat <<-EOF >$testroot/view.expected
+	[1/20] diff $lhs_id $rhs_id
+	commit $rhs_id
+	from: Flan Hacker <flan_hacker@openbsd.org>
+	date: $date
+
+	commit 1
+
+	M  alpha  |  1+  1-
+
+	1 file changed, 1 insertion(+), 1 deletion(-)
+
+	commit - $lhs_id
+	commit + $rhs_id
+	blob - $(pop_id 1 $alpha_ids)
+	blob + $(pop_id 2 $alpha_ids)
+	--- alpha
+	+++ alpha
+	@@ -1 +1 @@
+	-alpha
+	+alpha 1
+
+
+
+	(END)
+	EOF
+
+	tog diff :base:-99 :head:-7
+	cmp -s "$testroot/view.expected" "$testroot/view"
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u "$testroot/view.expected" "$testroot/view"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# diff arbitrary commits with keywords
+	lhs_id=$(pop_id 5 $ids)
+	rhs_id=$(pop_id 8 $ids)
+
+	cat <<-EOF >$testroot/view.expected
+	[1/10] diff $lhs_id $rhs_id
+	commit - $lhs_id
+	commit + $rhs_id
+	blob - $(pop_id 5 $alpha_ids)
+	blob + $(pop_id 8 $alpha_ids)
+	--- alpha
+	+++ alpha
+	@@ -1 +1 @@
+	-alpha 4
+	+alpha 7
+
+
+
+
+
+
+
+
+
+
+
+
+
+	(END)
+	EOF
+
+	tog diff master:-4 :head:-
+	cmp -s "$testroot/view.expected" "$testroot/view"
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u "$testroot/view.expected" "$testroot/view"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# diff consecutive commits using keywords with -r repository
+	lhs_id=$(pop_id 8 $ids)
+	rhs_id=$(pop_id 9 $ids)
+	author_time=$(git_show_author_time "$repo")
+	date=$(date -u -r $author_time +"%a %b %e %X %Y UTC")
+
+	cat <<-EOF >$testroot/view.expected
+	[1/20] diff $lhs_id refs/heads/master
+	commit $rhs_id (master)
+	from: Flan Hacker <flan_hacker@openbsd.org>
+	date: $date
+
+	commit 8
+
+	M  alpha  |  1+  1-
+
+	1 file changed, 1 insertion(+), 1 deletion(-)
+
+	commit - $lhs_id
+	commit + $rhs_id
+	blob - $(pop_id 8 $alpha_ids)
+	blob + $(pop_id 9 $alpha_ids)
+	--- alpha
+	+++ alpha
+	@@ -1 +1 @@
+	-alpha 7
+	+alpha 8
+
+
+
+	(END)
+	EOF
+
+	tog diff -r "$repo" :head:- master
+	cmp -s "$testroot/view.expected" "$testroot/view"
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u "$testroot/view.expected" "$testroot/view"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_diff_contiguous_commits
 run_test test_diff_arbitrary_commits
 run_test test_diff_J_keymap_on_last_loaded_commit
+run_test test_diff_commit_keywords
blob - 67469c1eae523eaf0c2b46922e300c93c42a323f
blob + 2a28c65c9bf9e227cba943ce259438976ac67c02
--- regress/tog/log.sh
+++ regress/tog/log.sh
@@ -354,6 +354,151 @@ test_parseargs "$@"
 	test_done "$testroot" "$ret"
 }
 
+test_log_commit_keywords()
+{
+	test_init log_commit_keywords 120 10
+	local repo="$testroot/repo"
+	local wt="$testroot/wt"
+	local id=$(git_show_head "$repo")
+	local author_time=$(git_show_author_time "$repo")
+	local ymd=$(date -u -r $author_time +"%G-%m-%d")
+
+	set -A ids "$id"
+	set -A short_ids "$(trim_obj_id 32 $id)"
+
+	got checkout "$repo" "$wt" > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "got checkout failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# move into the work tree (test is run in a subshell)
+	cd "$wt"
+	echo -n > alpha
+
+	for i in $(seq 8); do
+		echo "alpha $i" >> alpha
+
+		got ci -m "commit $i" > /dev/null
+		ret=$?
+		if [ $ret -ne 0 ]; then
+			echo "commit failed unexpectedly" >&2
+			test_done "$testroot" "$ret"
+			return 1
+		fi
+
+		id=$(git_show_head "$repo")
+		set -- "$ids" "$id"
+		ids=$*
+		set -- "$short_ids" "$(trim_obj_id 32 $id)"
+		short_ids=$*
+	done
+
+	cat <<-EOF >$TOG_TEST_SCRIPT
+	SCREENDUMP
+	EOF
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 5 $ids) [1/5]
+	$ymd $(pop_id 5 $short_ids) flan_hacker  commit 4
+	$ymd $(pop_id 4 $short_ids) flan_hacker  commit 3
+	$ymd $(pop_id 3 $short_ids) flan_hacker  commit 2
+	$ymd $(pop_id 2 $short_ids) flan_hacker  commit 1
+	$ymd $(pop_id 1 $short_ids) flan_hacker  adding the test tree
+
+
+
+
+	EOF
+
+	tog log -c:base:-4
+	cmp -s "$testroot/view.expected" "$testroot/view"
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u "$testroot/view.expected" "$testroot/view"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 7 $ids) [1/7]
+	$ymd $(pop_id 7 $short_ids) flan_hacker  commit 6
+	$ymd $(pop_id 6 $short_ids) flan_hacker  commit 5
+	$ymd $(pop_id 5 $short_ids) flan_hacker  commit 4
+	$ymd $(pop_id 4 $short_ids) flan_hacker  commit 3
+	$ymd $(pop_id 3 $short_ids) flan_hacker  commit 2
+	$ymd $(pop_id 2 $short_ids) flan_hacker  commit 1
+	$ymd $(pop_id 1 $short_ids) flan_hacker  adding the test tree
+
+
+	EOF
+
+	tog log -r "$repo" -c:head:-2
+	cmp -s "$testroot/view.expected" "$testroot/view"
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u "$testroot/view.expected" "$testroot/view"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 5 $ids) [1/5]
+	$ymd $(pop_id 5 $short_ids) flan_hacker  commit 4
+	$ymd $(pop_id 4 $short_ids) flan_hacker  commit 3
+	$ymd $(pop_id 3 $short_ids) flan_hacker  commit 2
+	$ymd $(pop_id 2 $short_ids) flan_hacker  commit 1
+	$ymd $(pop_id 1 $short_ids) flan_hacker  adding the test tree
+
+
+
+
+	EOF
+
+	got up -c:base:-6 > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "got update failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	tog log -c:base:+2
+	cmp -s "$testroot/view.expected" "$testroot/view"
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u "$testroot/view.expected" "$testroot/view"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 1 $ids) [1/1]
+	$ymd $(pop_id 1 $short_ids) flan_hacker  adding the test tree
+
+
+
+
+
+
+
+
+	EOF
+
+	tog log -c:base:-99
+	cmp -s "$testroot/view.expected" "$testroot/view"
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u "$testroot/view.expected" "$testroot/view"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_log_hsplit_diff
 run_test test_log_vsplit_diff
@@ -362,3 +507,4 @@ run_test test_log_logmsg_widechar
 run_test test_log_hsplit_ref
 run_test test_log_hsplit_tree
 run_test test_log_logmsg_widechar
+run_test test_log_commit_keywords
blob - c26b425e144352ef6828dcd4df36836b60aee6c9
blob + ab5481d9ca9da400e86ac14e7511cf21b1e24863
--- regress/tog/tree.sh
+++ regress/tog/tree.sh
@@ -178,8 +178,173 @@ test_parseargs "$@"
 	test_done "$testroot" "$ret"
 }
 
+test_tree_commit_keywords()
+{
+	test_init tree_commit_keywords 48 11
+	local repo="$testroot/repo"
+	local wt="$testroot/wt"
+	local id=$(git_show_head "$repo")
+
+	set -A ids "$id"
+
+	got checkout "$repo" "$wt" > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "got checkout failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# move into the work tree (test is run in a subshell)
+	cd "$wt"
+
+	for i in $(seq 8); do
+		if [ $(( i % 2 )) -eq 0 ]; then
+			echo "file${i}" > "file${i}"
+			got add "file${i}" > /dev/null
+		else
+			echo "alpha $i" > alpha
+		fi
+
+		got ci -m "commit $i" > /dev/null
+		ret=$?
+		if [ $ret -ne 0 ]; then
+			echo "commit failed unexpectedly" >&2
+			test_done "$testroot" "$ret"
+			return 1
+		fi
+
+		id=$(git_show_head "$repo")
+		set -- "$ids" "$id"
+		ids=$*
+	done
+
+
+	cat <<-EOF >$TOG_TEST_SCRIPT
+	SCREENDUMP
+	EOF
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 8 $ids)
+	[1/7] /
+
+	  alpha
+	  beta
+	  epsilon/
+	  file2
+	  file4
+	  file6
+	  gamma/
+
+	EOF
+
+	tog tree -c:base:-
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 6 $ids)
+	[1/6] /
+
+	  alpha
+	  beta
+	  epsilon/
+	  file2
+	  file4
+	  gamma/
+
+
+	EOF
+
+	tog tree -cmaster:-3
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 9 $ids)
+	[1/8] /
+
+	  alpha
+	  beta
+	  epsilon/
+	  file2
+	  file4
+	  file6
+	  file8
+	  gamma/
+	EOF
+
+	got up -c:head:-99 > /dev/null
+	tog tree -c:base:+99
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 4 $ids)
+	[1/5] /
+
+	  alpha
+	  beta
+	  epsilon/
+	  file2
+	  gamma/
+
+
+
+	EOF
+
+	tog tree -c:head:-5
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	cat <<-EOF >$testroot/view.expected
+	commit $(pop_id 1 $ids)
+	[1/4] /
+
+	  alpha
+	  beta
+	  epsilon/
+	  gamma/
+
+
+
+
+	EOF
+
+	tog tree -r "$repo" -cmaster:-99
+	cmp -s $testroot/view.expected $testroot/view
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/view.expected $testroot/view
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	test_done "$testroot" "$ret"
+}
+
 test_parseargs "$@"
 run_test test_tree_basic
 run_test test_tree_vsplit_blame
 run_test test_tree_hsplit_blame
 run_test test_tree_symlink
+run_test test_tree_commit_keywords
blob - e809641a54d987c934b5802c084bde5ad86dfc09
blob + f6118fb46d796b824e8f3299f780ac15cbedc117
--- tog/Makefile
+++ tog/Makefile
@@ -15,7 +15,7 @@ SRCS=		tog.c blame.c commit_graph.c delta.c diff.c \
 		diff_patience.c bloom.c murmurhash2.c sigs.c date.c \
 		object_open_privsep.c read_gitconfig_privsep.c \
 		read_gotconfig_privsep.c pollfd.c reference_parse.c \
-		object_qid.c
+		object_qid.c keyword.c
 MAN =		${PROG}.1
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
blob - 667bfe283076f37b6cb5690da6b2a98d9aae516a
blob + 816fab05d74cd0ab51f96eb3cad17d26d95b56e3
--- tog/tog.1
+++ tog/tog.1
@@ -274,11 +274,46 @@ The expected argument is the name of a branch or a com
 .It Fl c Ar commit
 Start traversing history at the specified
 .Ar commit .
-The expected argument is the name of a branch or a commit ID SHA1 hash.
+The expected argument is a commit ID SHA1 hash, or a reference name or keyword
+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
-if invoked in a work tree, or to the repository's HEAD reference.
+The keywords
+.Qq :base
+and
+.Qq :head
+resolve to 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 r Ar repository-path
 Use the repository at the specified path.
 If not specified, assume the repository is located at or above the current
@@ -296,11 +331,48 @@ Treat each of the two arguments as a reference, a tag 
 .Ar object2
 .Xc
 Display the differences between two objects in the repository.
-Treat each of the two arguments as a reference, a tag name, or an object
-ID SHA1 hash, and display differences between the corresponding objects.
+Treat each of the two arguments as a reference, a tag name, an object
+ID SHA1 hash, or a keyword and display differences between the corresponding
+objects.
 Both objects must be of the same type (blobs, trees, or commits).
 An abbreviated hash argument will be expanded to a full SHA1 hash
 automatically, provided the abbreviation is unique.
+The keywords
+.Qq :base
+and
+.Qq :head
+resolve to 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 .
 .Pp
 The key bindings for
 .Cm tog diff
@@ -484,9 +556,46 @@ The expected argument is the name of a branch or a com
 .It Fl c Ar commit
 Start traversing history at the specified
 .Ar commit .
-The expected argument is the name of a branch or a commit ID SHA1 hash.
+The expected argument is a commit ID SHA1 hash, or a reference name or keyword
+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.
+The keywords
+.Qq :base
+and
+.Qq :head
+resolve to 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 r Ar repository-path
 Use the repository at the specified path.
 If not specified, assume the repository is located at or above the current
@@ -584,9 +693,46 @@ The expected argument is the name of a branch or a com
 .It Fl c Ar commit
 Start traversing history at the specified
 .Ar commit .
-The expected argument is the name of a branch or a commit ID SHA1 hash.
+The expected argument is a commit ID SHA1 hash, or a reference name or keyword
+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.
+The keywords
+.Qq :base
+and
+.Qq :head
+resolve to 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 r Ar repository-path
 Use the repository at the specified path.
 If not specified, assume the repository is located at or above the current
blob - 9479ad1b9a79971165d3eaf5e113e8d1b6ff0ff7
blob + 55201d879df128d62d2a76ec797059af85649fa8
--- tog/tog.c
+++ tog/tog.c
@@ -56,6 +56,7 @@
 #include "got_privsep.h"
 #include "got_path.h"
 #include "got_worktree.h"
+#include "got_keyword.h"
 
 #ifndef MIN
 #define	MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
@@ -4306,7 +4307,7 @@ init_mock_term(const char *test_script_path)
 	screen_dump_path = getenv("TOG_SCR_DUMP");
 	if (screen_dump_path == NULL || *screen_dump_path == '\0')
 		return got_error_msg(GOT_ERR_IO, "TOG_SCR_DUMP not defined");
-	tog_io.sdump = fopen(screen_dump_path, "wex");
+	tog_io.sdump = fopen(screen_dump_path, "we");
 	if (tog_io.sdump == NULL) {
 		err = got_error_from_errno2("fopen", screen_dump_path);
 		goto done;
@@ -4404,7 +4405,7 @@ cmd_log(int argc, char *argv[])
 	struct got_worktree *worktree = NULL;
 	struct got_object_id *start_id = NULL;
 	char *in_repo_path = NULL, *repo_path = NULL, *cwd = NULL;
-	char *start_commit = NULL, *label = NULL;
+	char *keyword_idstr = NULL, *start_commit = NULL, *label = NULL;
 	struct got_reference *ref = NULL;
 	const char *head_ref_name = NULL;
 	int ch, log_branches = 0;
@@ -4490,6 +4491,13 @@ cmd_log(int argc, char *argv[])
 			goto done;
 		head_ref_name = label;
 	} else {
+		error = got_keyword_to_idstr(&keyword_idstr, start_commit,
+		    repo, worktree);
+		if (error != NULL)
+			goto done;
+		if (keyword_idstr != NULL)
+			start_commit = keyword_idstr;
+
 		error = got_ref_open(&ref, repo, start_commit, 0);
 		if (error == NULL)
 			head_ref_name = got_ref_get_name(ref);
@@ -4517,6 +4525,7 @@ done:
 	}
 	error = view_loop(view);
 done:
+	free(keyword_idstr);
 	free(in_repo_path);
 	free(repo_path);
 	free(cwd);
@@ -5918,6 +5927,7 @@ cmd_diff(int argc, char *argv[])
 	struct got_object_id *id1 = NULL, *id2 = NULL;
 	char *repo_path = NULL, *cwd = NULL;
 	char *id_str1 = NULL, *id_str2 = NULL;
+	char *keyword_idstr1 = NULL, *keyword_idstr2 = NULL;
 	char *label1 = NULL, *label2 = NULL;
 	int diff_context = 3, ignore_whitespace = 0;
 	int ch, force_text_diff = 0;
@@ -6000,6 +6010,23 @@ cmd_diff(int argc, char *argv[])
 	if (error)
 		goto done;
 
+	if (id_str1 != NULL) {
+		error = got_keyword_to_idstr(&keyword_idstr1, id_str1,
+		    repo, worktree);
+		if (error != NULL)
+			goto done;
+		if (keyword_idstr1 != NULL)
+			id_str1 = keyword_idstr1;
+	}
+	if (id_str2 != NULL) {
+		error = got_keyword_to_idstr(&keyword_idstr2, id_str2,
+		    repo, worktree);
+		if (error != NULL)
+			goto done;
+		if (keyword_idstr2 != NULL)
+			id_str2 = keyword_idstr2;
+	}
+
 	error = got_repo_match_object_id(&id1, &label1, id_str1,
 	    GOT_OBJ_TYPE_ANY, &tog_refs, repo);
 	if (error)
@@ -6021,6 +6048,8 @@ done:
 		goto done;
 	error = view_loop(view);
 done:
+	free(keyword_idstr1);
+	free(keyword_idstr2);
 	free(label1);
 	free(label2);
 	free(repo_path);
@@ -7052,7 +7081,7 @@ cmd_blame(int argc, char *argv[])
 	char *link_target = NULL;
 	struct got_object_id *commit_id = NULL;
 	struct got_commit_object *commit = NULL;
-	char *commit_id_str = NULL;
+	char *keyword_idstr = NULL, *commit_id_str = NULL;
 	int ch;
 	struct tog_view *view = NULL;
 	int *pack_fds = NULL;
@@ -7130,6 +7159,13 @@ cmd_blame(int argc, char *argv[])
 		error = got_ref_resolve(&commit_id, repo, head_ref);
 		got_ref_close(head_ref);
 	} else {
+		error = got_keyword_to_idstr(&keyword_idstr, commit_id_str,
+		    repo, worktree);
+		if (error != NULL)
+			goto done;
+		if (keyword_idstr != NULL)
+			commit_id_str = keyword_idstr;
+
 		error = got_repo_match_object_id(&commit_id, NULL,
 		    commit_id_str, GOT_OBJ_TYPE_COMMIT, &tog_refs, repo);
 	}
@@ -7170,6 +7206,7 @@ done:
 	free(link_target);
 	free(cwd);
 	free(commit_id);
+	free(keyword_idstr);
 	if (commit)
 		got_object_commit_close(commit);
 	if (worktree)
@@ -8029,7 +8066,7 @@ cmd_tree(int argc, char *argv[])
 	struct got_object_id *commit_id = NULL;
 	struct got_commit_object *commit = NULL;
 	const char *commit_id_arg = NULL;
-	char *label = NULL;
+	char *keyword_idstr = NULL, *label = NULL;
 	struct got_reference *ref = NULL;
 	const char *head_ref_name = NULL;
 	int ch;
@@ -8108,6 +8145,13 @@ cmd_tree(int argc, char *argv[])
 			goto done;
 		head_ref_name = label;
 	} 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_ref_open(&ref, repo, commit_id_arg, 0);
 		if (error == NULL)
 			head_ref_name = got_ref_get_name(ref);
@@ -8145,6 +8189,7 @@ done:
 	}
 	error = view_loop(view);
 done:
+	free(keyword_idstr);
 	free(repo_path);
 	free(cwd);
 	free(commit_id);
@@ -9986,7 +10031,9 @@ main(int argc, char *argv[])
 	    error->code != GOT_ERR_EOF &&
 	    error->code != GOT_ERR_PRIVSEP_EXIT &&
 	    error->code != GOT_ERR_PRIVSEP_PIPE &&
-	    !(error->code == GOT_ERR_ERRNO && errno == EINTR))
+	    !(error->code == GOT_ERR_ERRNO && errno == EINTR)) {
 		fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
+		return 1;
+	}
 	return 0;
 }



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