commit c4df265ebabf6993a1c217bb2bb59d5aeb3fe5e5 from: Mark Jamsek date: Wed Jul 19 12:46:31 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 tog with error rather than 0 so regress can discern success from failure. ok stsp@ commit - df6221c7df42758252c508006201c3f66e6ae831 commit + c4df265ebabf6993a1c217bb2bb59d5aeb3fe5e5 blob - 93c8f6f920ce4b2013710cddd1e94d1b83b0a7ad blob + a329dca28bf36978b73b04fe71d37578bfbc712f --- regress/tog/blame.sh +++ regress/tog/blame.sh @@ -76,5 +76,234 @@ EOF 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 @@ EOF 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 + 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 + 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 @@ -347,10 +347,155 @@ EOF 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_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" } @@ -362,3 +507,4 @@ run_test test_log_scroll_right 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 @@ EOF 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 @@ key binding can be used to toggle display of merged co .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 @@ work tree, use the repository path associated with thi .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 @@ are as follows: .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 @@ are as follows: .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 @@ cmd_log(int argc, char *argv[]) } 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 @@ cmd_diff(int argc, char *argv[]) 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 @@ cmd_tree(int argc, char *argv[]) } 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; }