git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH v2 00/21] git bisect improvements
@ 2016-04-10 13:18 Stephan Beyer
  2016-04-10 13:18 ` [PATCH v2 01/21] bisect: write about `bisect next` in documentation Stephan Beyer
                   ` (19 more replies)
  0 siblings, 20 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:18 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Hi,

a long time ago[1] I sent the first version of this patchset
to the list. Since then I wrote different variants of the algorithm,
fixed some bugs, made the tests work ;), and finally performed some
performance tests to pick the best version of the different variants.

For the performance tests I used the Git repositories of the Linux
kernel and of Git itself and performed whole-history bisections
with a bisect script that decided "good" or "bad" based on the hash
of a commit. And another bisect script that did the opposite.

I omit the details. The best variant uses a DFS for the traversal
without any further "smart" tricks: These tricks led to more
"administrative expense" than gain of performance.

I'm sorry that it took so long to prepare the 2nd patchset (mainly
vacation and other work in between). So I hope it's sufficiently
good for inclusion. :)

Cheers

 1. https://www.mail-archive.com/git@vger.kernel.org/msg86353.html


Stephan Beyer (21):
  bisect: write about `bisect next` in documentation
  bisect: allow 'bisect run' if no good commit is known
  t/test-lib-functions.sh: generalize test_cmp_rev
  t: use test_cmp_rev() where appropriate
  t6030: generalize test to not rely on current implementation
  bisect: add test for the bisect algorithm
  bisect: plug the biggest memory leak
  bisect: make bisect compile if DEBUG_BISECT is set
  bisect: make algorithm behavior independent of DEBUG_BISECT
  bisect: get rid of recursion in count_distance()
  bisect: use struct node_data array instead of int array
  bisect: replace clear_distance() by unique markers
  bisect: use commit instead of commit list as arguments when
    appropriate
  bisect: extract get_distance() function from code duplication
  bisect: introduce distance_direction()
  bisect: make total number of commits global
  bisect: rename count_distance() to compute_weight()
  bisect: prepare for different algorithms based on find_all
  bisect: use a bottom-up traversal to find relevant weights
  bisect: compute best bisection in compute_relevant_weights()
  bisect: get back halfway shortcut

 Documentation/git-bisect.txt              |  24 ++
 bisect.c                                  | 481 ++++++++++++++++++++----------
 git-bisect.sh                             |  32 +-
 t/t2012-checkout-last.sh                  |   8 +-
 t/t3308-notes-merge.sh                    |   8 +-
 t/t3310-notes-merge-manual-resolve.sh     |   8 +-
 t/t3311-notes-merge-fanout.sh             |   6 +-
 t/t3404-rebase-interactive.sh             |  38 +--
 t/t3407-rebase-abort.sh                   |   8 +-
 t/t3410-rebase-preserve-dropped-merges.sh |   4 +-
 t/t3411-rebase-preserve-around-merges.sh  |  10 +-
 t/t3414-rebase-preserve-onto.sh           |  12 +-
 t/t3501-revert-cherry-pick.sh             |   4 +-
 t/t3506-cherry-pick-ff.sh                 |   6 +-
 t/t3903-stash.sh                          |   6 +-
 t/t4150-am.sh                             |  18 +-
 t/t5404-tracking-branches.sh              |   2 +-
 t/t5505-remote.sh                         |   4 +-
 t/t5520-pull.sh                           |  36 +--
 t/t6022-merge-rename.sh                   |   2 +-
 t/t6030-bisect-porcelain.sh               | 228 +++++++-------
 t/t6036-recursive-corner-cases.sh         |  58 ++--
 t/t6042-merge-rename-corner-cases.sh      |  50 ++--
 t/t7003-filter-branch.sh                  |   8 +-
 t/t7004-tag.sh                            |   2 +-
 t/t7110-reset-merge.sh                    |  24 +-
 t/t7201-co.sh                             |  12 +-
 t/t7601-merge-pull-config.sh              |  17 +-
 t/t7603-merge-reduce-heads.sh             |  30 +-
 t/t7605-merge-resolve.sh                  |   5 +-
 t/t8010-bisect-algorithm.sh               | 155 ++++++++++
 t/t9162-git-svn-dcommit-interactive.sh    |   8 +-
 t/t9300-fast-import.sh                    |  12 +-
 t/test-lib-functions.sh                   |  14 +-
 34 files changed, 832 insertions(+), 508 deletions(-)
 create mode 100755 t/t8010-bisect-algorithm.sh

-- 
2.8.1.137.g522756c

^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCH v2 01/21] bisect: write about `bisect next` in documentation
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
@ 2016-04-10 13:18 ` Stephan Beyer
  2016-04-10 13:18 ` [PATCH v2 02/21] bisect: allow 'bisect run' if no good commit is known Stephan Beyer
                   ` (18 subsequent siblings)
  19 siblings, 0 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:18 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Mention `bisect next` in the documentation of bisect.
`bisect next` is only useful in rare cases and the result
can also be accomplished using other utilities (like reflog).
However, it is available as a bisect command and should hence be
documented.

Also mention the use case when no good commit is known.
Some user message in git-bisect.sh is changed to reflect that
use case. It is also simplified: there is no need to mention
running `bisect start` explicitly, because it can be done
indirectly using `bisect bad`.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---

Notes:
    I rephrased the "Bisect next" section: no encouragement of checking
    out another branch (or commit) but mention that it recomputes the next
    commit, if one accidentally checked out another commit.

 Documentation/git-bisect.txt | 24 ++++++++++++++++++++++++
 git-bisect.sh                | 15 ++++-----------
 2 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index 7e79aae..c76765f 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -27,6 +27,7 @@ on the subcommand:
  git bisect replay <logfile>
  git bisect log
  git bisect run <cmd>...
+ git bisect next
  git bisect help
 
 This command uses a binary search algorithm to find which commit in
@@ -66,6 +67,15 @@ checks it out, and outputs something similar to the following:
 Bisecting: 675 revisions left to test after this (roughly 10 steps)
 ------------------------------------------------
 
+Note that in cases you do not know a good commit,
+you can also start with:
+
+------------------------------------------------
+$ git bisect start
+$ git bisect bad                 # current version is bad
+$ git bisect next                # check out another commit
+------------------------------------------------
+
 You should now compile the checked-out version and test it. If that
 version works correctly, type
 
@@ -353,6 +363,20 @@ rewind the tree to the pristine state.  Finally the script should exit
 with the status of the real test to let the `git bisect run` command loop
 determine the eventual outcome of the bisect session.
 
+Bisect next
+~~~~~~~~~~~
+
+In case you have marked a commit as bad but you do not know a good
+commit, you do not have to crawl through the commit history yourself to
+find a good commit. Simply issue the command:
+
+------------
+$ git bisect next
+------------
+
+In general, the command computes the next commit for the bisection and
+checks it out.
+
 OPTIONS
 -------
 --no-checkout::
diff --git a/git-bisect.sh b/git-bisect.sh
index 5d1cb00..5c93a27 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -334,16 +334,10 @@ bisect_next_check() {
 	*)
 		bad_syn=$(bisect_voc bad)
 		good_syn=$(bisect_voc good)
-		if test -s "$GIT_DIR/BISECT_START"
-		then
-
-			eval_gettextln "You need to give me at least one \$bad_syn and one \$good_syn revision.
-(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
-		else
-			eval_gettextln "You need to start by \"git bisect start\".
-You then need to give me at least one \$good_syn and one \$bad_syn revision.
-(You can use \"git bisect \$bad_syn\" and \"git bisect \$good_syn\" for that.)" >&2
-		fi
+		eval_gettextln "You need to give me at least one \$bad_syn revision.
+Use \"git bisect \$bad_syn\" for that. One \$good_syn revision is also helpful
+for bisecting (use \"git bisect \$good_syn\"). If you do not know one \$good_syn
+revision, you can use \"git bisect next\" to find one." >&2
 		exit 1 ;;
 	esac
 }
@@ -677,7 +671,6 @@ case "$#" in
 	skip)
 		bisect_skip "$@" ;;
 	next)
-		# Not sure we want "next" at the UI level anymore.
 		bisect_next "$@" ;;
 	visualize|view)
 		bisect_visualize "$@" ;;
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 02/21] bisect: allow 'bisect run' if no good commit is known
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
  2016-04-10 13:18 ` [PATCH v2 01/21] bisect: write about `bisect next` in documentation Stephan Beyer
@ 2016-04-10 13:18 ` Stephan Beyer
  2016-04-10 13:18 ` [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev Stephan Beyer
                   ` (17 subsequent siblings)
  19 siblings, 0 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:18 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Now that the documentation talks about bisecting without a good commit
being known, this should also be allowed for "git bisect run".

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---

Notes:
    This is a new patch in the patchset.

 git-bisect.sh | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/git-bisect.sh b/git-bisect.sh
index 5c93a27..f44ce1e 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -39,6 +39,8 @@ _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
 _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
 TERM_BAD=bad
 TERM_GOOD=good
+IF_NO_GOOD=ask
+AUTONEXT=false
 
 bisect_head()
 {
@@ -312,11 +314,11 @@ bisect_next_check() {
 	,,*)
 		: have both $TERM_GOOD and $TERM_BAD - ok
 		;;
-	*,)
+	*,false)
 		# do not have both but not asked to fail - just report.
 		false
 		;;
-	t,,"$TERM_GOOD")
+	t,,ask)
 		# have bad (or new) but not good (or old).  we could bisect although
 		# this is less optimum.
 		eval_gettextln "Warning: bisecting only with a \$TERM_BAD commit." >&2
@@ -331,6 +333,9 @@ bisect_next_check() {
 		fi
 		: bisect without $TERM_GOOD...
 		;;
+	t,,true)
+		:
+		;;
 	*)
 		bad_syn=$(bisect_voc bad)
 		good_syn=$(bisect_voc good)
@@ -343,13 +348,13 @@ revision, you can use \"git bisect next\" to find one." >&2
 }
 
 bisect_auto_next() {
-	bisect_next_check && bisect_next || :
+	bisect_next_check $AUTONEXT && bisect_next || :
 }
 
 bisect_next() {
 	case "$#" in 0) ;; *) usage ;; esac
 	bisect_autostart
-	bisect_next_check $TERM_GOOD
+	bisect_next_check $IF_NO_GOOD
 
 	# Perform all bisection computation, display and checkout
 	git bisect--helper --next-all $(test -f "$GIT_DIR/BISECT_HEAD" && echo --no-checkout)
@@ -478,7 +483,9 @@ bisect_replay () {
 }
 
 bisect_run () {
-	bisect_next_check fail
+	bisect_next_check $IF_NO_GOOD
+	AUTONEXT=true
+	IF_NO_GOOD=true
 
 	while true
 	do
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
  2016-04-10 13:18 ` [PATCH v2 01/21] bisect: write about `bisect next` in documentation Stephan Beyer
  2016-04-10 13:18 ` [PATCH v2 02/21] bisect: allow 'bisect run' if no good commit is known Stephan Beyer
@ 2016-04-10 13:18 ` Stephan Beyer
  2016-04-11  0:07   ` Eric Sunshine
  2016-04-15 20:00   ` Junio C Hamano
  2016-04-10 13:18 ` [PATCH v2 04/21] t: use test_cmp_rev() where appropriate Stephan Beyer
                   ` (16 subsequent siblings)
  19 siblings, 2 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:18 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

test_cmp_rev() took exactly two parameters, the expected revision
and the revision to test. This commit generalizes this function
such that it takes any number of at least two revisions: the
expected one and a list of actual ones. The function returns true
if and only if at least one actual revision coincides with the
expected revision.

While at it, the side effect of generating two (temporary) files
is removed.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 t/test-lib-functions.sh | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 8d99eb3..8caf59c 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -711,11 +711,17 @@ test_must_be_empty () {
 	fi
 }
 
-# Tests that its two parameters refer to the same revision
+# Tests that the first parameter refers to the same revision
+# of at least one other parameter
 test_cmp_rev () {
-	git rev-parse --verify "$1" >expect.rev &&
-	git rev-parse --verify "$2" >actual.rev &&
-	test_cmp expect.rev actual.rev
+	hash1="$(git rev-parse --verify "$1")" || return
+	shift
+	for rev
+	do
+		hash2="$(git rev-parse --verify "$rev")" || return
+		test "$hash1" = "$hash2" && return 0
+	done
+	return 1
 }
 
 # Print a sequence of numbers or letters in increasing order.  This is
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 04/21] t: use test_cmp_rev() where appropriate
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (2 preceding siblings ...)
  2016-04-10 13:18 ` [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev Stephan Beyer
@ 2016-04-10 13:18 ` Stephan Beyer
  2016-04-11  0:07   ` Eric Sunshine
  2016-04-15 20:48   ` Junio C Hamano
  2016-04-10 13:18 ` [PATCH v2 05/21] t6030: generalize test to not rely on current implementation Stephan Beyer
                   ` (15 subsequent siblings)
  19 siblings, 2 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:18 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

test_cmp_rev() from t/test-lib-functions.sh is used to make many
tests clearer.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---

Notes:
    This change is in some way independent of the bisect topic but
    the next patch is based on this (for t6030).

 t/t2012-checkout-last.sh                  |  8 ++--
 t/t3308-notes-merge.sh                    |  8 ++--
 t/t3310-notes-merge-manual-resolve.sh     |  8 ++--
 t/t3311-notes-merge-fanout.sh             |  6 +--
 t/t3404-rebase-interactive.sh             | 38 +++++++--------
 t/t3407-rebase-abort.sh                   |  8 ++--
 t/t3410-rebase-preserve-dropped-merges.sh |  4 +-
 t/t3411-rebase-preserve-around-merges.sh  | 10 ++--
 t/t3414-rebase-preserve-onto.sh           | 12 ++---
 t/t3501-revert-cherry-pick.sh             |  4 +-
 t/t3506-cherry-pick-ff.sh                 |  6 +--
 t/t3903-stash.sh                          |  6 +--
 t/t4150-am.sh                             | 18 +++----
 t/t5404-tracking-branches.sh              |  2 +-
 t/t5505-remote.sh                         |  4 +-
 t/t5520-pull.sh                           | 36 +++++++-------
 t/t6022-merge-rename.sh                   |  2 +-
 t/t6030-bisect-porcelain.sh               | 79 ++++++++++++-------------------
 t/t6036-recursive-corner-cases.sh         | 58 +++++++++++------------
 t/t6042-merge-rename-corner-cases.sh      | 50 +++++++++----------
 t/t7003-filter-branch.sh                  |  8 ++--
 t/t7004-tag.sh                            |  2 +-
 t/t7110-reset-merge.sh                    | 24 +++++-----
 t/t7201-co.sh                             | 12 ++---
 t/t7601-merge-pull-config.sh              | 17 ++++---
 t/t7603-merge-reduce-heads.sh             | 30 ++++++------
 t/t7605-merge-resolve.sh                  |  5 +-
 t/t9162-git-svn-dcommit-interactive.sh    |  8 ++--
 t/t9300-fast-import.sh                    | 12 ++---
 29 files changed, 232 insertions(+), 253 deletions(-)

diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
index e7ba8c5..64cb449 100755
--- a/t/t2012-checkout-last.sh
+++ b/t/t2012-checkout-last.sh
@@ -43,7 +43,7 @@ test_expect_success '"checkout -" attaches again' '
 
 test_expect_success '"checkout -" detaches again' '
 	git checkout - &&
-	test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
+	test_cmp_rev HEAD other &&
 	test_must_fail git symbolic-ref HEAD
 '
 
@@ -101,19 +101,19 @@ test_expect_success 'merge base test setup' '
 test_expect_success 'another...master' '
 	git checkout another &&
 	git checkout another...master &&
-	test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+	test_cmp_rev HEAD master^
 '
 
 test_expect_success '...master' '
 	git checkout another &&
 	git checkout ...master &&
-	test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+	test_cmp_rev HEAD master^
 '
 
 test_expect_success 'master...' '
 	git checkout another &&
 	git checkout master... &&
-	test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
+	test_cmp_rev HEAD master^
 '
 
 test_expect_success '"checkout -" works after a rebase A' '
diff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh
index 19aed7e..1f72e9e 100755
--- a/t/t3308-notes-merge.sh
+++ b/t/t3308-notes-merge.sh
@@ -93,7 +93,7 @@ test_expect_success 'merge non-notes ref into empty notes ref (remote-notes/orig
 	git notes merge refs/remote-notes/origin/x &&
 	verify_notes v &&
 	# refs/remote-notes/origin/x and v should point to the same notes commit
-	test "$(git rev-parse refs/remote-notes/origin/x)" = "$(git rev-parse refs/notes/v)"
+	test_cmp_rev refs/remote-notes/origin/x refs/notes/v
 '
 
 test_expect_success 'merge notes into empty notes ref (x => y)' '
@@ -101,13 +101,13 @@ test_expect_success 'merge notes into empty notes ref (x => y)' '
 	git notes merge x &&
 	verify_notes y &&
 	# x and y should point to the same notes commit
-	test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+	test_cmp_rev refs/notes/x refs/notes/y
 '
 
 test_expect_success 'merge empty notes ref (z => y)' '
 	git notes merge z &&
 	# y should not change (still == x)
-	test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+	test_cmp_rev refs/notes/x refs/notes/y
 '
 
 test_expect_success 'change notes on other notes ref (y)' '
@@ -174,7 +174,7 @@ test_expect_success 'merge changed (y) into original (x) => Fast-forward' '
 	verify_notes x &&
 	verify_notes y &&
 	# x and y should point to same the notes commit
-	test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
+	test_cmp_rev refs/notes/x refs/notes/y
 '
 
 test_expect_success 'merge empty notes ref (z => y)' '
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
index d557212..5e46fcf 100755
--- a/t/t3310-notes-merge-manual-resolve.sh
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -516,7 +516,7 @@ cp expect_log_w expect_log_m
 test_expect_success 'reset notes ref m to somewhere else (w)' '
 	git update-ref refs/notes/m refs/notes/w &&
 	verify_notes m &&
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+	test_cmp_rev refs/notes/m refs/notes/w
 '
 
 test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' '
@@ -537,8 +537,8 @@ EOF
 	test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
 	test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
 	# Refs are unchanged
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
-	test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
+	test_cmp_rev refs/notes/m refs/notes/w &&
+	test_cmp_rev refs/notes/y NOTES_MERGE_PARTIAL^1 &&
 	test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
 	# Mention refs/notes/m, and its current and expected value in output
 	grep -q "refs/notes/m" output &&
@@ -557,7 +557,7 @@ test_expect_success 'resolve situation by aborting the notes merge' '
 	test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
 	test_cmp /dev/null output &&
 	# m has not moved (still == w)
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
+	test_cmp_rev refs/notes/m refs/notes/w &&
 	# Verify that other notes refs has not changed (w, x, y and z)
 	verify_notes w &&
 	verify_notes x &&
diff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh
index 93516ef..3fb4d11 100755
--- a/t/t3311-notes-merge-fanout.sh
+++ b/t/t3311-notes-merge-fanout.sh
@@ -135,13 +135,13 @@ test_expect_success 'No-op merge (already included) (x => y)' '
 	git update-ref refs/notes/m refs/notes/y &&
 	git config core.notesRef refs/notes/m &&
 	git notes merge x &&
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
+	test_cmp_rev refs/notes/m refs/notes/y
 '
 
 test_expect_success 'Fast-forward merge (y => x)' '
 	git update-ref refs/notes/m refs/notes/x &&
 	git notes merge y &&
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
+	test_cmp_rev refs/notes/m refs/notes/y
 '
 
 cat <<EOF | sort >expect_notes_z
@@ -394,7 +394,7 @@ test_expect_success 'verify conflict entries (with no fanout)' '
 		exit 1
 	done ) &&
 	# Verify that current notes tree (pre-merge) has not changed (m == w)
-	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
+	test_cmp_rev refs/notes/m refs/notes/w
 '
 
 cat >expect_log_m <<EOF
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index b79f442..9b15995 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -131,7 +131,7 @@ test_expect_success 'no changes are a nop' '
 	set_fake_editor &&
 	git rebase -i F &&
 	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
-	test $(git rev-parse I) = $(git rev-parse HEAD)
+	test_cmp_rev I HEAD
 '
 
 test_expect_success 'test the [branch] option' '
@@ -141,8 +141,8 @@ test_expect_success 'test the [branch] option' '
 	set_fake_editor &&
 	git rebase -i F branch2 &&
 	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
-	test $(git rev-parse I) = $(git rev-parse branch2) &&
-	test $(git rev-parse I) = $(git rev-parse HEAD)
+	test_cmp_rev I branch2 &&
+	test_cmp_rev I HEAD
 '
 
 test_expect_success 'test --onto <branch>' '
@@ -150,8 +150,8 @@ test_expect_success 'test --onto <branch>' '
 	set_fake_editor &&
 	git rebase -i --onto branch1 F &&
 	test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-onto" &&
-	test $(git rev-parse HEAD^) = $(git rev-parse branch1) &&
-	test $(git rev-parse I) = $(git rev-parse branch2)
+	test_cmp_rev HEAD^ branch1 &&
+	test_cmp_rev I branch2
 '
 
 test_expect_success 'rebase on top of a non-conflicting commit' '
@@ -161,12 +161,12 @@ test_expect_success 'rebase on top of a non-conflicting commit' '
 	git rebase -i branch2 &&
 	test file6 = $(git diff --name-only original-branch1) &&
 	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
-	test $(git rev-parse I) = $(git rev-parse branch2) &&
-	test $(git rev-parse I) = $(git rev-parse HEAD~2)
+	test_cmp_rev I branch2 &&
+	test_cmp_rev I HEAD~2
 '
 
 test_expect_success 'reflog for the branch shows state before rebase' '
-	test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
+	test_cmp_rev branch1@{1} original-branch1
 '
 
 test_expect_success 'exchange two commits' '
@@ -198,7 +198,7 @@ test_expect_success 'stop on conflicting pick' '
 	git tag new-branch1 &&
 	set_fake_editor &&
 	test_must_fail git rebase -i master &&
-	test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
+	test_cmp_rev HEAD~3 master &&
 	test_cmp expect .git/rebase-merge/patch &&
 	test_cmp expect2 file1 &&
 	test "$(git diff --name-status |
@@ -209,7 +209,7 @@ test_expect_success 'stop on conflicting pick' '
 
 test_expect_success 'abort' '
 	git rebase --abort &&
-	test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
+	test_cmp_rev new-branch1 HEAD &&
 	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
 	test_path_is_missing .git/rebase-merge
 '
@@ -247,7 +247,7 @@ test_expect_success 'squash' '
 	FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \
 		git rebase -i --onto master HEAD~2 &&
 	test B = $(cat file7) &&
-	test $(git rev-parse HEAD^) = $(git rev-parse master)
+	test_cmp_rev HEAD^ master
 '
 
 test_expect_success 'retain authorship when squashing' '
@@ -306,9 +306,9 @@ test_expect_success 'preserve merges with -p' '
 	git update-index --refresh &&
 	git diff-files --quiet &&
 	git diff-index --quiet --cached HEAD -- &&
-	test $(git rev-parse HEAD~6) = $(git rev-parse branch1) &&
-	test $(git rev-parse HEAD~4^2) = $(git rev-parse to-be-preserved) &&
-	test $(git rev-parse HEAD^^2^) = $(git rev-parse HEAD^^^) &&
+	test_cmp_rev HEAD~6 branch1 &&
+	test_cmp_rev HEAD~4^2 to-be-preserved &&
+	test_cmp_rev HEAD^^2^ HEAD^^^ &&
 	test $(git show HEAD~5:file1) = B &&
 	test $(git show HEAD~3:file1) = C &&
 	test $(git show HEAD:file1) = E &&
@@ -335,7 +335,7 @@ test_expect_success '--continue tries to commit' '
 	echo resolved > file1 &&
 	git add file1 &&
 	FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
-	test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) &&
+	test_cmp_rev HEAD^ new-branch1 &&
 	git show HEAD | grep chouette
 '
 
@@ -600,7 +600,7 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' '
 	test $(git rev-parse branch3) != $(git rev-parse branch4) &&
 	set_fake_editor &&
 	git rebase -i branch3 &&
-	test $(git rev-parse branch3) = $(git rev-parse branch4)
+	test_cmp_rev branch3 branch4
 
 '
 
@@ -660,7 +660,7 @@ test_expect_success 'rebase -i continue with unstaged submodule' '
 	test_must_fail git rebase -i submodule-base &&
 	git reset &&
 	git rebase --continue &&
-	test $(git rev-parse submodule-base) = $(git rev-parse HEAD)
+	test_cmp_rev submodule-base HEAD
 '
 
 test_expect_success 'avoid unnecessary reset' '
@@ -682,7 +682,7 @@ test_expect_success 'reword' '
 	FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" git rebase -i A &&
 	git show HEAD | grep "E changed" &&
 	test $(git rev-parse master) != $(git rev-parse HEAD) &&
-	test $(git rev-parse master^) = $(git rev-parse HEAD^) &&
+	test_cmp_rev master^ HEAD^ &&
 	FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" git rebase -i A &&
 	git show HEAD^ | grep "D changed" &&
 	FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" git rebase -i A &&
@@ -741,7 +741,7 @@ test_expect_success 'always cherry-pick with --no-ff' '
 		git diff HEAD~$p original-no-ff-branch~$p > out &&
 		test_cmp empty out
 	done &&
-	test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
+	test_cmp_rev HEAD~3 original-no-ff-branch~3 &&
 	git diff HEAD~3 original-no-ff-branch~3 > out &&
 	test_cmp empty out
 '
diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
index a6a6c40..b5bafe8 100755
--- a/t/t3407-rebase-abort.sh
+++ b/t/t3407-rebase-abort.sh
@@ -40,7 +40,7 @@ testrebase() {
 		test_must_fail git rebase$type master &&
 		test_path_is_dir "$dotest" &&
 		git rebase --abort &&
-		test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
+		test_cmp_rev to-rebase pre-rebase &&
 		test ! -d "$dotest"
 	'
 
@@ -51,9 +51,9 @@ testrebase() {
 		test_must_fail git rebase$type master &&
 		test_path_is_dir "$dotest" &&
 		test_must_fail git rebase --skip &&
-		test $(git rev-parse HEAD) = $(git rev-parse master) &&
+		test_cmp_rev HEAD master &&
 		git rebase --abort &&
-		test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
+		test_cmp_rev to-rebase pre-rebase &&
 		test ! -d "$dotest"
 	'
 
@@ -69,7 +69,7 @@ testrebase() {
 		test_must_fail git rebase --continue &&
 		test $(git rev-parse HEAD) != $(git rev-parse master) &&
 		git rebase --abort &&
-		test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
+		test_cmp_rev to-rebase pre-rebase &&
 		test ! -d "$dotest"
 	'
 
diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh
index 6f73b95..99518ad 100755
--- a/t/t3410-rebase-preserve-dropped-merges.sh
+++ b/t/t3410-rebase-preserve-dropped-merges.sh
@@ -51,7 +51,7 @@ test_expect_success 'skip same-resolution merges with -p' '
 	test_commit J file1 23 &&
 	test_commit K file7 file7 &&
 	git rebase -i -p L &&
-	test $(git rev-parse HEAD^^) = $(git rev-parse L) &&
+	test_cmp_rev HEAD^^ L &&
 	test "23" = "$(cat file1)" &&
 	test "I" = "$(cat file6)" &&
 	test "file7" = "$(cat file7)"
@@ -76,7 +76,7 @@ test_expect_success 'keep different-resolution merges with -p' '
 	echo 234 > file1 &&
 	git add file1 &&
 	git rebase --continue &&
-	test $(git rev-parse HEAD^^^) = $(git rev-parse L2) &&
+	test_cmp_rev HEAD^^^ L2 &&
 	test "234" = "$(cat file1)" &&
 	test "I" = "$(cat file6)" &&
 	test "file7" = "$(cat file7)"
diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh
index dc81bf2..d2dc55a 100755
--- a/t/t3411-rebase-preserve-around-merges.sh
+++ b/t/t3411-rebase-preserve-around-merges.sh
@@ -38,8 +38,8 @@ test_expect_success 'setup' '
 #
 test_expect_success 'squash F1 into D1' '
 	FAKE_LINES="1 squash 4 2 3" git rebase -i -p B1 &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
-	test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
+	test_cmp_rev HEAD^2 C1 &&
+	test_cmp_rev HEAD~2 B1 &&
 	git tag E2
 '
 
@@ -67,9 +67,9 @@ test_expect_success 'rebase two levels of merge' '
 	test_commit L1 &&
 	test_merge M1 K1 &&
 	GIT_EDITOR=: git rebase -i -p E2 &&
-	test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
-	test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
-	test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse HEAD^2^2^1)"
+	test_cmp_rev HEAD~3 E2 &&
+	test_cmp_rev HEAD~2 HEAD^2^2~2 &&
+	test_cmp_rev HEAD^2^1^1 HEAD^2^2^1
 '
 
 test_done
diff --git a/t/t3414-rebase-preserve-onto.sh b/t/t3414-rebase-preserve-onto.sh
index ee0a6cc..5389b1a 100755
--- a/t/t3414-rebase-preserve-onto.sh
+++ b/t/t3414-rebase-preserve-onto.sh
@@ -43,8 +43,8 @@ test_expect_success 'setup' '
 test_expect_success 'rebase from B1 onto H1' '
 	git checkout G1 &&
 	git rebase -p --onto H1 B1 &&
-	test "$(git rev-parse HEAD^1^1^1)" = "$(git rev-parse H1)" &&
-	test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse H1)"
+	test_cmp_rev HEAD^1^1^1 H1 &&
+	test_cmp_rev HEAD^2^1^1 H1
 '
 
 # On the other hand if rebase from E1 which is within one branch,
@@ -58,8 +58,8 @@ test_expect_success 'rebase from B1 onto H1' '
 test_expect_success 'rebase from E1 onto H1' '
 	git checkout G1 &&
 	git rebase -p --onto H1 E1 &&
-	test "$(git rev-parse HEAD^1^1)" = "$(git rev-parse H1)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse D1)"
+	test_cmp_rev HEAD^1^1 H1 &&
+	test_cmp_rev HEAD^2 D1
 '
 
 # And the same if we rebase from a commit in the second-parent branch.
@@ -73,8 +73,8 @@ test_expect_success 'rebase from C1 onto H1' '
 	git checkout G1 &&
 	git rev-list --first-parent --pretty=oneline C1..G1 &&
 	git rebase -p --onto H1 C1 &&
-	test "$(git rev-parse HEAD^2^1)" = "$(git rev-parse H1)" &&
-	test "$(git rev-parse HEAD^1)" = "$(git rev-parse F1)"
+	test_cmp_rev HEAD^2^1 H1 &&
+	test_cmp_rev HEAD^1 F1
 '
 
 test_done
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 51f3bbb..53922d9 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -63,7 +63,7 @@ test_expect_success 'cherry-pick after renaming branch' '
 
 	git checkout rename2 &&
 	git cherry-pick added &&
-	test $(git rev-parse HEAD^) = $(git rev-parse rename2) &&
+	test_cmp_rev HEAD^ rename2 &&
 	test -f opos &&
 	grep "Add extra line at the end" opos &&
 	git reflog -1 | grep cherry-pick
@@ -74,7 +74,7 @@ test_expect_success 'revert after renaming branch' '
 
 	git checkout rename1 &&
 	git revert added &&
-	test $(git rev-parse HEAD^) = $(git rev-parse rename1) &&
+	test_cmp_rev HEAD^ rename1 &&
 	test -f spoo &&
 	! grep "Add extra line at the end" spoo &&
 	git reflog -1 | grep revert
diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh
index fb889ac..5f7f964 100755
--- a/t/t3506-cherry-pick-ff.sh
+++ b/t/t3506-cherry-pick-ff.sh
@@ -24,7 +24,7 @@ test_expect_success 'cherry-pick using --ff fast forwards' '
 	git reset --hard first &&
 	test_tick &&
 	git cherry-pick --ff second &&
-	test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify second)"
+	test_cmp_rev HEAD second
 '
 
 test_expect_success 'cherry-pick not using --ff does not fast forwards' '
@@ -80,14 +80,14 @@ test_expect_success 'cherry pick with --ff a merge (1)' '
 	git reset --hard A -- &&
 	git cherry-pick --ff -m 1 C &&
 	git diff --exit-code C &&
-	test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)"
+	test_cmp_rev HEAD C
 '
 
 test_expect_success 'cherry pick with --ff a merge (2)' '
 	git reset --hard B -- &&
 	git cherry-pick --ff -m 2 C &&
 	git diff --exit-code C &&
-	test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)"
+	test_cmp_rev HEAD C
 '
 
 test_expect_success 'cherry pick a merge relative to nonexistent parent with --ff should fail' '
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 2142c1f..3517543 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -34,7 +34,7 @@ index 0cfbf08..00750ed 100644
 EOF
 
 test_expect_success 'parents of stash' '
-	test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
+	test_cmp_rev stash^ HEAD &&
 	git diff stash^2..stash > output &&
 	test_cmp output expect
 '
@@ -188,7 +188,7 @@ test_expect_success 'stash branch' '
 	git commit file -m second &&
 	git stash branch stashbranch &&
 	test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
-	test $(git rev-parse HEAD) = $(git rev-parse master^) &&
+	test_cmp_rev HEAD master^ &&
 	git diff --cached > output &&
 	test_cmp output expect &&
 	git diff > output &&
@@ -653,7 +653,7 @@ test_expect_success 'stash where working directory contains "HEAD" file' '
 	git stash &&
 	git diff-files --quiet &&
 	git diff-index --cached --quiet HEAD &&
-	test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
+	test_cmp_rev stash^ HEAD &&
 	git diff stash^..stash > output &&
 	test_cmp output expect
 '
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index b41bd17..c6b0a0f 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -209,8 +209,8 @@ test_expect_success 'am applies patch correctly' '
 	git am <patch1 &&
 	test_path_is_missing .git/rebase-apply &&
 	git diff --exit-code second &&
-	test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
-	test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+	test_cmp_rev second HEAD &&
+	test_cmp_rev second^ HEAD^
 '
 
 test_expect_success 'am fails if index is dirty' '
@@ -232,8 +232,8 @@ test_expect_success 'am applies patch e-mail not in a mbox' '
 	git am patch1.eml &&
 	test_path_is_missing .git/rebase-apply &&
 	git diff --exit-code second &&
-	test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
-	test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+	test_cmp_rev second HEAD &&
+	test_cmp_rev second^ HEAD^
 '
 
 test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
@@ -243,8 +243,8 @@ test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
 	git am patch1-crlf.eml &&
 	test_path_is_missing .git/rebase-apply &&
 	git diff --exit-code second &&
-	test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
-	test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+	test_cmp_rev second HEAD &&
+	test_cmp_rev second^ HEAD^
 '
 
 test_expect_success 'am applies patch e-mail with preceding whitespace' '
@@ -254,8 +254,8 @@ test_expect_success 'am applies patch e-mail with preceding whitespace' '
 	git am patch1-ws.eml &&
 	test_path_is_missing .git/rebase-apply &&
 	git diff --exit-code second &&
-	test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
-	test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+	test_cmp_rev second HEAD &&
+	test_cmp_rev second^ HEAD^
 '
 
 test_expect_success 'am applies stgit patch' '
@@ -456,7 +456,7 @@ test_expect_success 'am changes committer and keeps author' '
 	git checkout first &&
 	git am patch2 &&
 	test_path_is_missing .git/rebase-apply &&
-	test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
+	test_cmp_rev master^^ HEAD^^ &&
 	git diff --exit-code master..HEAD &&
 	git diff --exit-code master^..HEAD^ &&
 	compare author master HEAD &&
diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh
index 2b8c0ba..7ce40c5 100755
--- a/t/t5404-tracking-branches.sh
+++ b/t/t5404-tracking-branches.sh
@@ -40,7 +40,7 @@ test_expect_success 'mixed-success push returns error' '
 '
 
 test_expect_success 'check tracking branches updated correctly after push' '
-	test "$(git rev-parse origin/master)" = "$(git rev-parse master)"
+	test_cmp_rev origin/master master
 '
 
 test_expect_success 'check tracking branches not updated for failed refs' '
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
index dd2e6ce..0300344 100755
--- a/t/t5505-remote.sh
+++ b/t/t5505-remote.sh
@@ -727,7 +727,7 @@ test_expect_success 'rename a remote' '
 		git remote rename origin upstream &&
 		rmdir .git/refs/remotes/origin &&
 		test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" &&
-		test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
+		test_cmp_rev upstream/master master &&
 		test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
 		test "$(git config branch.master.remote)" = "upstream"
 	)
@@ -760,7 +760,7 @@ test_expect_success 'rename a remote with name prefix of other remote' '
 		cd four.three &&
 		git remote add o git://example.com/repo.git &&
 		git remote rename o upstream &&
-		test "$(git rev-parse origin/master)" = "$(git rev-parse master)"
+		test_cmp_rev origin/master master
 	)
 '
 
diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
index c952d5e..b8c0f46 100755
--- a/t/t5520-pull.sh
+++ b/t/t5520-pull.sh
@@ -209,7 +209,7 @@ test_expect_success 'fast-forwards working tree if branch head is updated' '
 	git pull . second:third 2>err &&
 	test_i18ngrep "fetch updated the current branch head" err &&
 	test "$(cat file)" = modified &&
-	test "$(git rev-parse third)" = "$(git rev-parse second)"
+	test_cmp_rev third second
 '
 
 test_expect_success 'fast-forward fails with conflicting work tree' '
@@ -220,7 +220,7 @@ test_expect_success 'fast-forward fails with conflicting work tree' '
 	test_must_fail git pull . second:third 2>err &&
 	test_i18ngrep "Cannot fast-forward your working tree" err &&
 	test "$(cat file)" = conflict &&
-	test "$(git rev-parse third)" = "$(git rev-parse second)"
+	test_cmp_rev third second
 '
 
 test_expect_success '--rebase' '
@@ -233,14 +233,14 @@ test_expect_success '--rebase' '
 	git commit -m "new file" &&
 	git tag before-rebase &&
 	git pull --rebase . copy &&
-	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^ copy &&
 	test new = "$(git show HEAD:file2)"
 '
 
 test_expect_success '--rebase fails with multiple branches' '
 	git reset --hard before-rebase &&
 	test_must_fail git pull --rebase . copy master 2>err &&
-	test "$(git rev-parse HEAD)" = "$(git rev-parse before-rebase)" &&
+	test_cmp_rev HEAD before-rebase &&
 	test_i18ngrep "Cannot rebase onto multiple branches" err &&
 	test modified = "$(git show HEAD:file)"
 '
@@ -260,7 +260,7 @@ test_expect_success 'pull.rebase' '
 	git reset --hard before-rebase &&
 	test_config pull.rebase true &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^ copy &&
 	test new = "$(git show HEAD:file2)"
 '
 
@@ -268,7 +268,7 @@ test_expect_success 'branch.to-rebase.rebase' '
 	git reset --hard before-rebase &&
 	test_config branch.to-rebase.rebase true &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^ copy &&
 	test new = "$(git show HEAD:file2)"
 '
 
@@ -297,8 +297,8 @@ test_expect_success 'pull.rebase=false create a new merge commit' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase false &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^1 before-preserve-rebase &&
+	test_cmp_rev HEAD^2 copy &&
 	test file3 = "$(git show HEAD:file3.t)"
 '
 
@@ -306,7 +306,7 @@ test_expect_success 'pull.rebase=true flattens keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase true &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^^ copy &&
 	test file3 = "$(git show HEAD:file3.t)"
 '
 
@@ -314,7 +314,7 @@ test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase 1 &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^^ copy &&
 	test file3 = "$(git show HEAD:file3.t)"
 '
 
@@ -322,8 +322,8 @@ test_expect_success 'pull.rebase=preserve rebases and merges keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase preserve &&
 	git pull . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)"
+	test_cmp_rev HEAD^^ copy &&
+	test_cmp_rev HEAD^2 keep-merge
 '
 
 test_expect_success 'pull.rebase=interactive' '
@@ -346,8 +346,8 @@ test_expect_success '--rebase=false create a new merge commit' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase true &&
 	git pull --rebase=false . copy &&
-	test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^1 before-preserve-rebase &&
+	test_cmp_rev HEAD^2 copy &&
 	test file3 = "$(git show HEAD:file3.t)"
 '
 
@@ -355,7 +355,7 @@ test_expect_success '--rebase=true rebases and flattens keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase preserve &&
 	git pull --rebase=true . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^^ copy &&
 	test file3 = "$(git show HEAD:file3.t)"
 '
 
@@ -363,8 +363,8 @@ test_expect_success '--rebase=preserve rebases and merges keep-merge' '
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase true &&
 	git pull --rebase=preserve . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)"
+	test_cmp_rev HEAD^^ copy &&
+	test_cmp_rev HEAD^2 keep-merge
 '
 
 test_expect_success '--rebase=invalid fails' '
@@ -376,7 +376,7 @@ test_expect_success '--rebase overrides pull.rebase=preserve and flattens keep-m
 	git reset --hard before-preserve-rebase &&
 	test_config pull.rebase preserve &&
 	git pull --rebase . copy &&
-	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
+	test_cmp_rev HEAD^^ copy &&
 	test file3 = "$(git show HEAD:file3.t)"
 '
 
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index 05ebba7..4e31f6a 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -788,7 +788,7 @@ test_expect_success 'merge rename + small change' '
 
 	test 1 -eq $(git ls-files -s | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
-	test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file)
+	test_cmp_rev HEAD:renamed_file HEAD~1:file
 '
 
 test_expect_success 'setup for use of extended merge markers' '
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index e74662b..05bc639 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -335,16 +335,16 @@ test_expect_success 'bisect skip only one range' '
 	git bisect reset &&
 	git bisect start $HASH7 $HASH1 &&
 	git bisect skip $HASH1..$HASH5 &&
-	test "$HASH6" = "$(git rev-parse --verify HEAD)" &&
+	test_cmp_rev HEAD $HASH6 &&
 	test_must_fail git bisect bad > my_bisect_log.txt &&
 	grep "first bad commit could be any of" my_bisect_log.txt
 '
 
 test_expect_success 'bisect skip many ranges' '
 	git bisect start $HASH7 $HASH1 &&
-	test "$HASH4" = "$(git rev-parse --verify HEAD)" &&
+	test_cmp_rev HEAD $HASH4 &&
 	git bisect skip $HASH2 $HASH2.. ..$HASH5 &&
-	test "$HASH6" = "$(git rev-parse --verify HEAD)" &&
+	test_cmp_rev HEAD $HASH6 &&
 	test_must_fail git bisect bad > my_bisect_log.txt &&
 	grep "first bad commit could be any of" my_bisect_log.txt
 '
@@ -356,7 +356,7 @@ test_expect_success 'bisect starting with a detached HEAD' '
 	git bisect start &&
 	test $HEAD = $(cat .git/BISECT_START) &&
 	git bisect reset &&
-	test $HEAD = $(git rev-parse --verify HEAD)
+	test_cmp_rev "$HEAD" HEAD
 '
 
 test_expect_success 'bisect errors out if bad and good are mistaken' '
@@ -370,18 +370,15 @@ test_expect_success 'bisect does not create a "bisect" branch' '
 	git bisect reset &&
 	git bisect start $HASH7 $HASH1 &&
 	git branch bisect &&
-	rev_hash4=$(git rev-parse --verify HEAD) &&
-	test "$rev_hash4" = "$HASH4" &&
+	test_cmp_rev HEAD $HASH4 &&
 	git branch -D bisect &&
 	git bisect good &&
 	git branch bisect &&
-	rev_hash6=$(git rev-parse --verify HEAD) &&
-	test "$rev_hash6" = "$HASH6" &&
+	test_cmp_rev HEAD $HASH6 &&
 	git bisect good > my_bisect_log.txt &&
 	grep "$HASH7 is the first bad commit" my_bisect_log.txt &&
 	git bisect reset &&
-	rev_hash6=$(git rev-parse --verify bisect) &&
-	test "$rev_hash6" = "$HASH6" &&
+	test_cmp_rev bisect $HASH6 &&
 	git branch -D bisect
 '
 
@@ -481,7 +478,7 @@ test_expect_success 'optimized merge base checks' '
 	grep "$HASH4" my_bisect_log.txt &&
 	git bisect good > my_bisect_log2.txt &&
 	test -f ".git/BISECT_ANCESTORS_OK" &&
-	test "$HASH6" = $(git rev-parse --verify HEAD) &&
+	test_cmp_rev HEAD $HASH6 &&
 	git bisect bad > my_bisect_log3.txt &&
 	git bisect good "$A_HASH" > my_bisect_log4.txt &&
 	grep "merge base must be tested" my_bisect_log4.txt &&
@@ -524,8 +521,7 @@ test_expect_success '"parallel" side branch creation' '
 test_expect_success 'restricting bisection on one dir' '
 	git bisect reset &&
 	git bisect start HEAD $HASH1 -- dir1 &&
-	para1=$(git rev-parse --verify HEAD) &&
-	test "$para1" = "$PARA_HASH1" &&
+	test_cmp_rev HEAD "$PARA_HASH1" &&
 	git bisect bad > my_bisect_log.txt &&
 	grep "$PARA_HASH1 is the first bad commit" my_bisect_log.txt
 '
@@ -533,31 +529,24 @@ test_expect_success 'restricting bisection on one dir' '
 test_expect_success 'restricting bisection on one dir and a file' '
 	git bisect reset &&
 	git bisect start HEAD $HASH1 -- dir1 hello &&
-	para4=$(git rev-parse --verify HEAD) &&
-	test "$para4" = "$PARA_HASH4" &&
+	test_cmp_rev HEAD "$PARA_HASH4" &&
 	git bisect bad &&
-	hash3=$(git rev-parse --verify HEAD) &&
-	test "$hash3" = "$HASH3" &&
+	test_cmp_rev HEAD $HASH3 &&
 	git bisect good &&
-	hash4=$(git rev-parse --verify HEAD) &&
-	test "$hash4" = "$HASH4" &&
+	test_cmp_rev HEAD $HASH4 &&
 	git bisect good &&
-	para1=$(git rev-parse --verify HEAD) &&
-	test "$para1" = "$PARA_HASH1" &&
+	test_cmp_rev HEAD "$PARA_HASH1" &&
 	git bisect good > my_bisect_log.txt &&
 	grep "$PARA_HASH4 is the first bad commit" my_bisect_log.txt
 '
 
 test_expect_success 'skipping away from skipped commit' '
 	git bisect start $PARA_HASH7 $HASH1 &&
-	para4=$(git rev-parse --verify HEAD) &&
-	test "$para4" = "$PARA_HASH4" &&
-        git bisect skip &&
-	hash7=$(git rev-parse --verify HEAD) &&
-	test "$hash7" = "$HASH7" &&
-        git bisect skip &&
-	para3=$(git rev-parse --verify HEAD) &&
-	test "$para3" = "$PARA_HASH3"
+	test_cmp_rev HEAD "$PARA_HASH4" &&
+	git bisect skip &&
+	test_cmp_rev HEAD $HASH7 &&
+	git bisect skip &&
+	test_cmp_rev HEAD "$PARA_HASH3"
 '
 
 test_expect_success 'erroring out when using bad path parameters' '
@@ -644,56 +633,50 @@ test_expect_success 'bisect fails if tree is broken on trial commit' '
 	test_cmp expected.missing-tree.default error.txt
 '
 
-check_same()
-{
-	echo "Checking $1 is the same as $2" &&
-	test_cmp_rev "$1" "$2"
-}
-
 test_expect_success 'bisect: --no-checkout - start commit bad' '
 	git bisect reset &&
 	git bisect start BROKEN_HASH7 BROKEN_HASH4 --no-checkout &&
-	check_same BROKEN_HASH6 BISECT_HEAD &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
 	git bisect reset
 '
 
 test_expect_success 'bisect: --no-checkout - trial commit bad' '
 	git bisect reset &&
 	git bisect start broken BROKEN_HASH4 --no-checkout &&
-	check_same BROKEN_HASH6 BISECT_HEAD &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
 	git bisect reset
 '
 
 test_expect_success 'bisect: --no-checkout - target before breakage' '
 	git bisect reset &&
 	git bisect start broken BROKEN_HASH4 --no-checkout &&
-	check_same BROKEN_HASH6 BISECT_HEAD &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
 	git bisect bad BISECT_HEAD &&
-	check_same BROKEN_HASH5 BISECT_HEAD &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH5 &&
 	git bisect bad BISECT_HEAD &&
-	check_same BROKEN_HASH5 bisect/bad &&
+	test_cmp_rev bisect/bad BROKEN_HASH5 &&
 	git bisect reset
 '
 
 test_expect_success 'bisect: --no-checkout - target in breakage' '
 	git bisect reset &&
 	git bisect start broken BROKEN_HASH4 --no-checkout &&
-	check_same BROKEN_HASH6 BISECT_HEAD &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
 	git bisect bad BISECT_HEAD &&
-	check_same BROKEN_HASH5 BISECT_HEAD &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH5 &&
 	git bisect good BISECT_HEAD &&
-	check_same BROKEN_HASH6 bisect/bad &&
+	test_cmp_rev bisect/bad BROKEN_HASH6 &&
 	git bisect reset
 '
 
 test_expect_success 'bisect: --no-checkout - target after breakage' '
 	git bisect reset &&
 	git bisect start broken BROKEN_HASH4 --no-checkout &&
-	check_same BROKEN_HASH6 BISECT_HEAD &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
 	git bisect good BISECT_HEAD &&
-	check_same BROKEN_HASH8 BISECT_HEAD &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH8 &&
 	git bisect good BISECT_HEAD &&
-	check_same BROKEN_HASH9 bisect/bad &&
+	test_cmp_rev bisect/bad BROKEN_HASH9 &&
 	git bisect reset
 '
 
@@ -708,7 +691,7 @@ test_expect_success 'bisect: demonstrate identification of damage boundary' "
 		rc=\$?
 		rm -f tmp.\$\$
 		test \$rc = 0' &&
-	check_same BROKEN_HASH6 bisect/bad &&
+	test_cmp_rev bisect/bad BROKEN_HASH6 &&
 	git bisect reset
 "
 
@@ -755,7 +738,7 @@ test_expect_success '"git bisect bad HEAD" behaves as "git bisect bad"' '
 	git bisect start HEAD $HASH1 &&
 	git bisect good HEAD &&
 	git bisect bad HEAD &&
-	test "$HASH6" = $(git rev-parse --verify HEAD) &&
+	test_cmp_rev HEAD $HASH6 &&
 	git bisect reset
 '
 
diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh
index 9d6621c..dd1be5d 100755
--- a/t/t6036-recursive-corner-cases.sh
+++ b/t/t6036-recursive-corner-cases.sh
@@ -61,8 +61,8 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
 	test 2 = $(git ls-files -u | wc -l) &&
 	test 2 = $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
-	test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
+	test_cmp_rev :2:three L2:three &&
+	test_cmp_rev :3:three R2:three &&
 
 	test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
 	test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
@@ -128,8 +128,8 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
 	test 2 = $(git ls-files -u | wc -l) &&
 	test 2 = $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
-	test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
+	test_cmp_rev :2:three L2:three &&
+	test_cmp_rev :3:three R2:three &&
 
 	test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
 	test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
@@ -201,8 +201,8 @@ test_expect_success 'git detects differently handled merges conflict' '
 	test 3 = $(git ls-files -u | wc -l) &&
 	test 0 = $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) &&
-	test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) &&
+	test_cmp_rev :2:new_a D:new_a &&
+	test_cmp_rev :3:new_a E:new_a &&
 
 	git cat-file -p B:new_a >>merged &&
 	git cat-file -p C:new_a >>merge-me &&
@@ -281,8 +281,8 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete' '
 	test 2 -eq $(git ls-files -s | wc -l) &&
 	test 2 -eq $(git ls-files -u | wc -l) &&
 
-	test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
-	test $(git rev-parse :2:file) = $(git rev-parse B:file)
+	test_cmp_rev :1:file master:file &&
+	test_cmp_rev :2:file B:file
 '
 
 test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '
@@ -294,8 +294,8 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete, rev
 	test 2 -eq $(git ls-files -s | wc -l) &&
 	test 2 -eq $(git ls-files -u | wc -l) &&
 
-	test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
-	test $(git rev-parse :3:file) = $(git rev-parse B:file)
+	test_cmp_rev :1:file master:file &&
+	test_cmp_rev :3:file B:file
 '
 
 #
@@ -377,8 +377,8 @@ test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '
 	test 3 -eq $(git ls-files -u | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :2:file) = $(git rev-parse D:file) &&
-	test $(git rev-parse :3:file) = $(git rev-parse E:file)
+	test_cmp_rev :2:file D:file &&
+	test_cmp_rev :3:file E:file
 '
 
 #
@@ -483,8 +483,8 @@ test_expect_success 'merge of D & E1 fails but has appropriate contents' '
 	test 1 -eq $(git ls-files -u | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
-	test $(git rev-parse :2:a) = $(git rev-parse B:a)
+	test_cmp_rev :0:ignore-me A:ignore-me &&
+	test_cmp_rev :2:a B:a
 '
 
 test_expect_success 'merge of E1 & D fails but has appropriate contents' '
@@ -496,8 +496,8 @@ test_expect_success 'merge of E1 & D fails but has appropriate contents' '
 	test 1 -eq $(git ls-files -u | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
-	test $(git rev-parse :3:a) = $(git rev-parse B:a)
+	test_cmp_rev :0:ignore-me A:ignore-me &&
+	test_cmp_rev :3:a B:a
 '
 
 test_expect_success 'merge of D & E2 fails but has appropriate contents' '
@@ -509,10 +509,10 @@ test_expect_success 'merge of D & E2 fails but has appropriate contents' '
 	test 3 -eq $(git ls-files -u | wc -l) &&
 	test 1 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :2:a) = $(git rev-parse B:a) &&
-	test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) &&
-	test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
-	test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+	test_cmp_rev :2:a B:a &&
+	test_cmp_rev :3:a/file E2:a/file &&
+	test_cmp_rev :1:a/file C:a/file &&
+	test_cmp_rev :0:ignore-me A:ignore-me &&
 
 	test -f a~HEAD
 '
@@ -526,10 +526,10 @@ test_expect_success 'merge of E2 & D fails but has appropriate contents' '
 	test 3 -eq $(git ls-files -u | wc -l) &&
 	test 1 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :3:a) = $(git rev-parse B:a) &&
-	test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) &&
-	test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
-	test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
+	test_cmp_rev :3:a B:a &&
+	test_cmp_rev :2:a/file E2:a/file &&
+	test_cmp_rev :1:a/file C:a/file &&
+	test_cmp_rev :0:ignore-me A:ignore-me &&
 
 	test -f a~D^0
 '
@@ -619,7 +619,7 @@ test_expect_success 'handle rename/rename(1to2)/modify followed by what looks li
 	test 0 -eq $(git ls-files -u | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)
+	test_cmp_rev HEAD:newname E:newname
 '
 
 #
@@ -694,8 +694,8 @@ test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '
 	test 0 -eq $(git ls-files -u | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
-	test $(git rev-parse HEAD:c) = $(git rev-parse A:a) &&
+	test_cmp_rev HEAD:b A:a &&
+	test_cmp_rev HEAD:c A:a &&
 	test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")"
 '
 
@@ -764,8 +764,8 @@ test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '
 	test 0 -eq $(git ls-files -u | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse HEAD:a) = $(git rev-parse A:a) &&
-	test $(git rev-parse HEAD:c) = $(git rev-parse E:c)
+	test_cmp_rev HEAD:a A:a &&
+	test_cmp_rev HEAD:c E:c
 '
 
 test_done
diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh
index 411550d..9aac880 100755
--- a/t/t6042-merge-rename-corner-cases.sh
+++ b/t/t6042-merge-rename-corner-cases.sh
@@ -68,8 +68,8 @@ test_expect_failure 'rename/modify/add-source conflict resolvable' '
 
 	git merge -s recursive C^0 &&
 
-	test $(git rev-parse B:a) = $(git rev-parse b) &&
-	test $(git rev-parse C:a) = $(git rev-parse a)
+	test_cmp_rev B:a b &&
+	test_cmp_rev C:a a
 '
 
 test_expect_success 'setup resolvable conflict missed if rename missed' '
@@ -105,8 +105,8 @@ test_expect_failure 'conflict caused if rename not detected' '
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
 	test_line_count = 6 c &&
-	test $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&
-	test $(git rev-parse HEAD:b) = $(git rev-parse A:b)
+	test_cmp_rev HEAD:a B:a &&
+	test_cmp_rev HEAD:b A:b
 '
 
 test_expect_success 'setup conflict resolved wrong if rename missed' '
@@ -178,8 +178,8 @@ test_expect_failure 'detect rename/add-source and preserve all data' '
 	test -f a &&
 	test -f b &&
 
-	test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
-	test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
+	test_cmp_rev HEAD:b A:a &&
+	test_cmp_rev HEAD:a C:a
 '
 
 test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
@@ -194,8 +194,8 @@ test_expect_failure 'detect rename/add-source and preserve all data, merge other
 	test -f a &&
 	test -f b &&
 
-	test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
-	test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
+	test_cmp_rev HEAD:b A:a &&
+	test_cmp_rev HEAD:a C:a
 '
 
 test_expect_success 'setup content merge + rename/directory conflict' '
@@ -281,9 +281,9 @@ test_expect_success 'rename/directory conflict + content merge conflict' '
 		left base right &&
 	test_cmp left newfile~HEAD &&
 
-	test $(git rev-parse :1:newfile) = $(git rev-parse base:file) &&
-	test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) &&
-	test $(git rev-parse :3:newfile) = $(git rev-parse right:file) &&
+	test_cmp_rev :1:newfile base:file &&
+	test_cmp_rev :2:newfile left-conflict:newfile &&
+	test_cmp_rev :3:newfile right:file &&
 
 	test -f newfile/realfile &&
 	test -f newfile~HEAD
@@ -432,9 +432,9 @@ test_expect_success 'merge has correct working tree contents' '
 	test 3 -eq $(git ls-files -u | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
-	test $(git rev-parse :3:b) = $(git rev-parse A:a) &&
-	test $(git rev-parse :2:c) = $(git rev-parse A:a) &&
+	test_cmp_rev :1:a A:a &&
+	test_cmp_rev :3:b A:a &&
+	test_cmp_rev :2:c A:a &&
 
 	test ! -f a &&
 	test $(git hash-object b) = $(git rev-parse A:a) &&
@@ -478,10 +478,10 @@ test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge'
 	test 4 -eq $(git ls-files -s | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse 3:a) = $(git rev-parse C:a) &&
-	test $(git rev-parse 1:a) = $(git rev-parse A:a) &&
-	test $(git rev-parse 2:b) = $(git rev-parse B:b) &&
-	test $(git rev-parse 3:c) = $(git rev-parse C:c) &&
+	test_cmp_rev 3:a C:a &&
+	test_cmp_rev 1:a A:a &&
+	test_cmp_rev 2:b B:b &&
+	test_cmp_rev 3:c C:c &&
 
 	test -f a &&
 	test -f b &&
@@ -520,8 +520,8 @@ test_expect_failure 'rename/rename/add-source still tracks new a file' '
 	test 2 -eq $(git ls-files -s | wc -l) &&
 	test 0 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse HEAD:a) = $(git rev-parse C:a) &&
-	test $(git rev-parse HEAD:b) = $(git rev-parse A:a)
+	test_cmp_rev HEAD:a C:a &&
+	test_cmp_rev HEAD:b A:a
 '
 
 test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
@@ -560,11 +560,11 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting
 	test 2 -eq $(git ls-files -u c | wc -l) &&
 	test 4 -eq $(git ls-files -o | wc -l) &&
 
-	test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
-	test $(git rev-parse :2:b) = $(git rev-parse C:b) &&
-	test $(git rev-parse :3:b) = $(git rev-parse B:b) &&
-	test $(git rev-parse :2:c) = $(git rev-parse C:c) &&
-	test $(git rev-parse :3:c) = $(git rev-parse B:c) &&
+	test_cmp_rev :1:a A:a &&
+	test_cmp_rev :2:b C:b &&
+	test_cmp_rev :3:b B:b &&
+	test_cmp_rev :2:c C:c &&
+	test_cmp_rev :3:c B:c &&
 
 	test $(git hash-object c~HEAD) = $(git rev-parse C:c) &&
 	test $(git hash-object c~B\^0) = $(git rev-parse B:c) &&
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index cb8fbd8..72511de 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -354,8 +354,8 @@ test_expect_success '--remap-to-ancestor with filename filters' '
 	git filter-branch -f --remap-to-ancestor \
 		moved-foo moved-bar A..master \
 		-- -- foo &&
-	test $(git rev-parse moved-foo) = $(git rev-parse moved-bar) &&
-	test $(git rev-parse moved-foo) = $(git rev-parse master^) &&
+	test_cmp_rev moved-foo moved-bar &&
+	test_cmp_rev moved-foo master^ &&
 	test $orig_invariant = $(git rev-parse invariant)
 '
 
@@ -372,8 +372,8 @@ test_expect_success 'automatic remapping to ancestor with filename filters' '
 	git filter-branch -f \
 		moved-foo2 moved-bar2 A..master \
 		-- -- foo &&
-	test $(git rev-parse moved-foo2) = $(git rev-parse moved-bar2) &&
-	test $(git rev-parse moved-foo2) = $(git rev-parse master^) &&
+	test_cmp_rev moved-foo2 moved-bar2 &&
+	test_cmp_rev moved-foo2 master^ &&
 	test $orig_invariant = $(git rev-parse invariant2)
 '
 
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index f9b7d79..cd65715 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -337,7 +337,7 @@ test_expect_success \
 	'a non-annotated tag created without parameters should point to HEAD' '
 	git tag non-annotated-tag &&
 	test $(git cat-file -t non-annotated-tag) = commit &&
-	test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD)
+	test_cmp_rev non-annotated-tag HEAD
 '
 
 test_expect_success 'trying to verify an unknown tag should fail' \
diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh
index a82a07a..2373684 100755
--- a/t/t7110-reset-merge.sh
+++ b/t/t7110-reset-merge.sh
@@ -31,7 +31,7 @@ test_expect_success 'reset --merge is ok with changes in file it does not touch'
     git reset --merge HEAD^ &&
     ! grep 4 file1 &&
     grep 4 file2 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test_cmp_rev HEAD initial &&
     test -z "$(git diff --cached)"
 '
 
@@ -39,7 +39,7 @@ test_expect_success 'reset --merge is ok when switching back' '
     git reset --merge second &&
     grep 4 file1 &&
     grep 4 file2 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test_cmp_rev HEAD second &&
     test -z "$(git diff --cached)"
 '
 
@@ -55,7 +55,7 @@ test_expect_success 'reset --keep is ok with changes in file it does not touch'
     git reset --keep HEAD^ &&
     ! grep 4 file1 &&
     grep 4 file2 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test_cmp_rev HEAD initial &&
     test -z "$(git diff --cached)"
 '
 
@@ -63,7 +63,7 @@ test_expect_success 'reset --keep is ok when switching back' '
     git reset --keep second &&
     grep 4 file1 &&
     grep 4 file2 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test_cmp_rev HEAD second &&
     test -z "$(git diff --cached)"
 '
 
@@ -82,7 +82,7 @@ test_expect_success 'reset --merge discards changes added to index (1)' '
     ! grep 4 file1 &&
     ! grep 5 file1 &&
     grep 4 file2 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test_cmp_rev HEAD initial &&
     test -z "$(git diff --cached)"
 '
 
@@ -94,7 +94,7 @@ test_expect_success 'reset --merge is ok again when switching back (1)' '
     ! grep 4 file2 &&
     ! grep 5 file1 &&
     grep 4 file1 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test_cmp_rev HEAD second &&
     test -z "$(git diff --cached)"
 '
 
@@ -122,7 +122,7 @@ test_expect_success 'reset --merge discards changes added to index (2)' '
     git add file2 &&
     git reset --merge HEAD^ &&
     ! grep 4 file2 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test_cmp_rev HEAD initial &&
     test -z "$(git diff)" &&
     test -z "$(git diff --cached)"
 '
@@ -132,7 +132,7 @@ test_expect_success 'reset --merge is ok again when switching back (2)' '
     git reset --merge second &&
     ! grep 4 file2 &&
     grep 4 file1 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test_cmp_rev HEAD second &&
     test -z "$(git diff --cached)"
 '
 
@@ -148,7 +148,7 @@ test_expect_success 'reset --keep keeps changes it does not touch' '
     git add file2 &&
     git reset --keep HEAD^ &&
     grep 4 file2 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
+    test_cmp_rev HEAD initial &&
     test -z "$(git diff --cached)"
 '
 
@@ -156,7 +156,7 @@ test_expect_success 'reset --keep keeps changes when switching back' '
     git reset --keep second &&
     grep 4 file2 &&
     grep 4 file1 &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test_cmp_rev HEAD second &&
     test -z "$(git diff --cached)"
 '
 
@@ -223,7 +223,7 @@ test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
     git checkout third &&
     test_must_fail git merge branch1 &&
     git reset --merge HEAD^ &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
+    test_cmp_rev HEAD second &&
     test -z "$(git diff --cached)" &&
     test -z "$(git diff)"
 '
@@ -249,7 +249,7 @@ test_expect_success '"reset --merge HEAD" is ok with pending merge' '
     git reset --hard third &&
     test_must_fail git merge branch1 &&
     git reset --merge HEAD &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
+    test_cmp_rev HEAD third &&
     test -z "$(git diff --cached)" &&
     test -z "$(git diff)"
 '
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 8859236..3ff3126 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -415,7 +415,7 @@ test_expect_success 'checkout w/--track from non-branch HEAD fails' '
     test_must_fail git checkout --track -b track &&
     test_must_fail git rev-parse --verify track &&
     test_must_fail git symbolic-ref HEAD &&
-    test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
+    test_cmp_rev master^0 HEAD
 '
 
 test_expect_success 'checkout w/--track from tag fails' '
@@ -424,7 +424,7 @@ test_expect_success 'checkout w/--track from tag fails' '
     test_must_fail git checkout --track -b track frotz &&
     test_must_fail git rev-parse --verify track &&
     test_must_fail git symbolic-ref HEAD &&
-    test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
+    test_cmp_rev master^0 HEAD
 '
 
 test_expect_success 'detach a symbolic link HEAD' '
@@ -436,7 +436,7 @@ test_expect_success 'detach a symbolic link HEAD' '
     test "z$it" = zrefs/heads/master &&
     here=$(git rev-parse --verify refs/heads/master) &&
     git checkout side^ &&
-    test "z$(git rev-parse --verify refs/heads/master)" = "z$here"
+    test_cmp_rev refs/heads/master "$here"
 '
 
 test_expect_success \
@@ -446,19 +446,19 @@ test_expect_success \
 
     git checkout --track origin/koala/bear &&
     test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
+    test_cmp_rev HEAD renamer &&
 
     git checkout master && git branch -D koala/bear &&
 
     git checkout --track refs/remotes/origin/koala/bear &&
     test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
+    test_cmp_rev HEAD renamer &&
 
     git checkout master && git branch -D koala/bear &&
 
     git checkout --track remotes/origin/koala/bear &&
     test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
-    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"
+    test_cmp_rev HEAD renamer
 '
 
 test_expect_success \
diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
index c6c44ec..cf50055 100755
--- a/t/t7601-merge-pull-config.sh
+++ b/t/t7601-merge-pull-config.sh
@@ -42,7 +42,7 @@ test_expect_success 'fast-forward pull succeeds with "true" in pull.ff' '
 	git reset --hard c0 &&
 	test_config pull.ff true &&
 	git pull . c1 &&
-	test "$(git rev-parse HEAD)" = "$(git rev-parse c1)"
+	test_cmp_rev HEAD c1
 '
 
 test_expect_success 'pull.ff=true overrides merge.ff=false' '
@@ -50,15 +50,15 @@ test_expect_success 'pull.ff=true overrides merge.ff=false' '
 	test_config merge.ff false &&
 	test_config pull.ff true &&
 	git pull . c1 &&
-	test "$(git rev-parse HEAD)" = "$(git rev-parse c1)"
+	test_cmp_rev HEAD c1
 '
 
 test_expect_success 'fast-forward pull creates merge with "false" in pull.ff' '
 	git reset --hard c0 &&
 	test_config pull.ff false &&
 	git pull . c1 &&
-	test "$(git rev-parse HEAD^1)" = "$(git rev-parse c0)" &&
-	test "$(git rev-parse HEAD^2)" = "$(git rev-parse c1)"
+	test_cmp_rev HEAD^1 c0 &&
+	test_cmp_rev HEAD^2 c1
 '
 
 test_expect_success 'pull prevents non-fast-forward with "only" in pull.ff' '
@@ -79,17 +79,16 @@ test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
 	git reset --hard c1 &&
 	git config pull.octopus "recursive" &&
 	test_must_fail git merge c2 c3 &&
-	test "$(git rev-parse c1)" = "$(git rev-parse HEAD)"
+	test_cmp_rev c1 HEAD
 '
 
 test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
 	git reset --hard c1 &&
 	git config pull.octopus "recursive octopus" &&
 	git merge c2 c3 &&
-	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
-	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
-	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
-	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
+	test_cmp_rev c1 HEAD^1 &&
+	test_cmp_rev c2 HEAD^2 &&
+	test_cmp_rev c3 HEAD^3 &&
 	git diff --exit-code &&
 	test -f c0.c &&
 	test -f c1.c &&
diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
index 9894895..4e69fe7 100755
--- a/t/t7603-merge-reduce-heads.sh
+++ b/t/t7603-merge-reduce-heads.sh
@@ -46,11 +46,10 @@ test_expect_success 'setup' '
 test_expect_success 'merge c1 with c2, c3, c4, c5' '
 	git reset --hard c1 &&
 	git merge c2 c3 c4 c5 &&
-	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
-	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
-	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
-	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
-	test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
+	test_cmp_rev c1 HEAD^1 &&
+	test_cmp_rev c2 HEAD^2 &&
+	test_cmp_rev c3 HEAD^3 &&
+	test_cmp_rev c5 HEAD^4 &&
 	git diff --exit-code &&
 	test -f c0.c &&
 	test -f c1.c &&
@@ -69,11 +68,10 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '
 test_expect_success 'pull c2, c3, c4, c5 into c1' '
 	git reset --hard c1 &&
 	git pull . c2 c3 c4 c5 &&
-	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
-	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
-	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
-	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
-	test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
+	test_cmp_rev c1 HEAD^1 &&
+	test_cmp_rev c2 HEAD^2 &&
+	test_cmp_rev c3 HEAD^3 &&
+	test_cmp_rev c5 HEAD^4 &&
 	git diff --exit-code &&
 	test -f c0.c &&
 	test -f c1.c &&
@@ -113,8 +111,8 @@ test_expect_success 'merge E and I' '
 '
 
 test_expect_success 'verify merge result' '
-	test $(git rev-parse HEAD^1) = $(git rev-parse E) &&
-	test $(git rev-parse HEAD^2) = $(git rev-parse I)
+	test_cmp_rev HEAD^1 E &&
+	test_cmp_rev HEAD^2 I
 '
 
 test_expect_success 'add conflicts' '
@@ -139,8 +137,8 @@ test_expect_success 'merge E2 and I2, causing a conflict and resolve it' '
 '
 
 test_expect_success 'verify merge result' '
-	test $(git rev-parse HEAD^1) = $(git rev-parse E2) &&
-	test $(git rev-parse HEAD^2) = $(git rev-parse I2)
+	test_cmp_rev HEAD^1 E2 &&
+	test_cmp_rev HEAD^2 I2
 '
 
 test_expect_success 'fast-forward to redundant refs' '
@@ -149,7 +147,7 @@ test_expect_success 'fast-forward to redundant refs' '
 '
 
 test_expect_success 'verify merge result' '
-	test $(git rev-parse HEAD) = $(git rev-parse c5)
+	test_cmp_rev HEAD c5
 '
 
 test_expect_success 'merge up-to-date redundant refs' '
@@ -158,7 +156,7 @@ test_expect_success 'merge up-to-date redundant refs' '
 '
 
 test_expect_success 'verify merge result' '
-	test $(git rev-parse HEAD) = $(git rev-parse c5)
+	test_cmp_rev HEAD c5
 '
 
 test_done
diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh
index 0cb9d11..5be44f1 100755
--- a/t/t7605-merge-resolve.sh
+++ b/t/t7605-merge-resolve.sh
@@ -30,9 +30,8 @@ test_expect_success 'setup' '
 test_expect_success 'merge c1 to c2' '
 	git reset --hard c1 &&
 	git merge -s resolve c2 &&
-	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
-	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
-	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
+	test_cmp_rev c1 HEAD^1 &&
+	test_cmp_rev c2 HEAD^2 &&
 	git diff --exit-code &&
 	test -f c0.c &&
 	test -f c1.c &&
diff --git a/t/t9162-git-svn-dcommit-interactive.sh b/t/t9162-git-svn-dcommit-interactive.sh
index e38d9fa..ec77a98 100755
--- a/t/t9162-git-svn-dcommit-interactive.sh
+++ b/t/t9162-git-svn-dcommit-interactive.sh
@@ -21,7 +21,7 @@ test_expect_success 'answers: y [\n] yes' '
 		( echo "y
 
 y" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
-		test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn)
+		test_cmp_rev HEAD remotes/git-svn
 	)
 	'
 
@@ -33,7 +33,7 @@ test_expect_success 'answers: yes yes no' '
 		( echo "yes
 yes
 no" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
-		test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
+		test_cmp_rev HEAD^^^ remotes/git-svn &&
 		git reset --hard remotes/git-svn
 	)
 	'
@@ -45,7 +45,7 @@ test_expect_success 'answers: yes quit' '
 		echo "change #3" >> foo && git commit -a -m"change #3" &&
 		( echo "yes
 quit" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
-		test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
+		test_cmp_rev HEAD^^^ remotes/git-svn &&
 		git reset --hard remotes/git-svn
 	)
 	'
@@ -56,7 +56,7 @@ test_expect_success 'answers: all' '
 		echo "change #2" >> foo && git commit -a -m"change #2" &&
 		echo "change #3" >> foo && git commit -a -m"change #3" &&
 		( echo "all" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
-		test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn) &&
+		test_cmp_rev HEAD remotes/git-svn &&
 		git reset --hard remotes/git-svn
 	)
 	'
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
index 25bb60b..c284d75 100755
--- a/t/t9300-fast-import.sh
+++ b/t/t9300-fast-import.sh
@@ -368,7 +368,7 @@ test_expect_success 'B: accept branch name "TEMP_TAG"' '
 		git prune" &&
 	git fast-import <input &&
 	test -f .git/TEMP_TAG &&
-	test $(git rev-parse master) = $(git rev-parse TEMP_TAG^)
+	test_cmp_rev master TEMP_TAG^
 '
 
 test_expect_success 'B: accept empty committer' '
@@ -1110,7 +1110,7 @@ test_expect_success 'N: copy dirty subdirectory' '
 	INPUT_END
 
 	git fast-import <input &&
-	test $(git rev-parse N2^{tree}) = $(git rev-parse N3^{tree})
+	test_cmp_rev N2^{tree} N3^{tree}
 '
 
 test_expect_success 'N: copy directory by id' '
@@ -1507,7 +1507,7 @@ test_expect_success 'O: comments are all skipped' '
 	INPUT_END
 
 	git fast-import <input &&
-	test $(git rev-parse N3) = $(git rev-parse O1)
+	test_cmp_rev N3 O1
 '
 
 test_expect_success 'O: blank lines not necessary after data commands' '
@@ -1528,7 +1528,7 @@ test_expect_success 'O: blank lines not necessary after data commands' '
 	INPUT_END
 
 	git fast-import <input &&
-	test $(git rev-parse N3) = $(git rev-parse O2)
+	test_cmp_rev N3 O2
 '
 
 test_expect_success 'O: repack before next test' '
@@ -1575,7 +1575,7 @@ test_expect_success 'O: blank lines not necessary after other commands' '
 
 	git fast-import <input &&
 	test 8 = $(find .git/objects/pack -type f | wc -l) &&
-	test $(git rev-parse refs/tags/O3-2nd) = $(git rev-parse O3^) &&
+	test_cmp_rev refs/tags/O3-2nd O3^ &&
 	git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual &&
 	test_cmp expect actual
 '
@@ -1721,7 +1721,7 @@ test_expect_success 'P: verbatim SHA gitlinks' '
 	git gc &&
 	git prune &&
 	git fast-import <input &&
-	test $(git rev-parse --verify subuse2) = $(git rev-parse --verify subuse1)
+	test_cmp_rev subuse2 subuse1
 '
 
 test_expect_success 'P: fail on inline gitlink' '
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 05/21] t6030: generalize test to not rely on current implementation
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (3 preceding siblings ...)
  2016-04-10 13:18 ` [PATCH v2 04/21] t: use test_cmp_rev() where appropriate Stephan Beyer
@ 2016-04-10 13:18 ` Stephan Beyer
  2016-04-10 13:47   ` Torsten Bögershausen
  2016-04-15 21:07   ` Junio C Hamano
  2016-04-10 13:18 ` [PATCH v2 06/21] bisect: add test for the bisect algorithm Stephan Beyer
                   ` (14 subsequent siblings)
  19 siblings, 2 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:18 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

The bisect algorithm allows different outcomes if, for example,
the number of commits between a good and a bad commit is even.
The current test relies on a specific behavior (for example,
the behavior of the halfway() implementation). By disabling
halfway(), some skip tests fail although the algorithm works.

This commit generalizes the test t6030 such that it works
even if the bisect algorithm uses its degree of freedom to
choose another commit.

While at it, fix some indentation issues: use tabs instead of
4 spaces.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 t/t6030-bisect-porcelain.sh | 167 ++++++++++++++++++++++----------------------
 1 file changed, 85 insertions(+), 82 deletions(-)

diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 05bc639..645ccd9 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -10,36 +10,34 @@ exec </dev/null
 
 add_line_into_file()
 {
-    _line=$1
-    _file=$2
+	_line=$1
+	_file=$2
 
-    if [ -f "$_file" ]; then
-        echo "$_line" >> $_file || return $?
-        MSG="Add <$_line> into <$_file>."
-    else
-        echo "$_line" > $_file || return $?
-        git add $_file || return $?
-        MSG="Create file <$_file> with <$_line> inside."
-    fi
+	if [ -f "$_file" ]; then
+		echo "$_line" >> $_file || return $?
+		MSG="Add <$_line> into <$_file>."
+	else
+		echo "$_line" > $_file || return $?
+		git add $_file || return $?
+		MSG="Create file <$_file> with <$_line> inside."
+	fi
 
-    test_tick
-    git commit --quiet -m "$MSG" $_file
+	test_tick
+	git commit --quiet -m "$MSG" $_file
 }
 
-HASH1=
-HASH2=
-HASH3=
-HASH4=
-
 test_expect_success 'set up basic repo with 1 file (hello) and 4 commits' '
-     add_line_into_file "1: Hello World" hello &&
-     HASH1=$(git rev-parse --verify HEAD) &&
-     add_line_into_file "2: A new day for git" hello &&
-     HASH2=$(git rev-parse --verify HEAD) &&
-     add_line_into_file "3: Another new day for git" hello &&
-     HASH3=$(git rev-parse --verify HEAD) &&
-     add_line_into_file "4: Ciao for now" hello &&
-     HASH4=$(git rev-parse --verify HEAD)
+	add_line_into_file "1: Hello World" hello &&
+	HASH1=$(git rev-parse --verify HEAD) &&
+	add_line_into_file "2: A new day for git" hello &&
+	HASH2=$(git rev-parse --verify HEAD) &&
+	add_line_into_file "3: Another new day for git" hello &&
+	HASH3=$(git rev-parse --verify HEAD) &&
+	add_line_into_file "4: Ciao for now" hello &&
+	HASH4=$(git rev-parse --verify HEAD) &&
+	git checkout -b monday &&
+	add_line_into_file "5: Ok Monday, let us do it" hello &&
+	git checkout master
 '
 
 test_expect_success 'bisect starts with only one bad' '
@@ -84,9 +82,8 @@ test_expect_success 'bisect fails if given any junk instead of revs' '
 
 test_expect_success 'bisect reset: back in the master branch' '
 	git bisect reset &&
-	echo "* master" > branch.expect &&
 	git branch > branch.output &&
-	cmp branch.expect branch.output
+	grep "^* master" branch.output
 '
 
 test_expect_success 'bisect reset: back in another branch' '
@@ -95,16 +92,14 @@ test_expect_success 'bisect reset: back in another branch' '
 	git bisect good $HASH1 &&
 	git bisect bad $HASH3 &&
 	git bisect reset &&
-	echo "  master" > branch.expect &&
-	echo "* other" >> branch.expect &&
 	git branch > branch.output &&
-	cmp branch.expect branch.output
+	grep "^* other" branch.output
 '
 
 test_expect_success 'bisect reset when not bisecting' '
 	git bisect reset &&
 	git branch > branch.output &&
-	cmp branch.expect branch.output
+	grep "^* other" branch.output
 '
 
 test_expect_success 'bisect reset removes packed refs' '
@@ -180,14 +175,15 @@ test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
 	git checkout HEAD hello
 '
 
-# $HASH1 is good, $HASH4 is bad, we skip $HASH3
+# $HASH1 is good, monday is bad, we skip $HASH3
 # but $HASH2 is bad,
 # so we should find $HASH2 as the first bad commit
 test_expect_success 'bisect skip: successful result' '
 	test_when_finished git bisect reset &&
 	git bisect reset &&
-	git bisect start $HASH4 $HASH1 &&
+	git bisect start monday $HASH1 &&
 	git bisect skip &&
+	( test_cmp_rev HEAD $HASH2 || git bisect bad ) &&
 	git bisect bad > my_bisect_log.txt &&
 	grep "$HASH2 is the first bad commit" my_bisect_log.txt
 '
@@ -207,18 +203,22 @@ test_expect_success 'bisect skip: cannot tell between 3 commits' '
 	grep $HASH4 my_bisect_log.txt
 '
 
-# $HASH1 is good, $HASH4 is bad, we skip $HASH3
-# but $HASH2 is good,
+# $HASH1 is good, monday is bad, we skip $HASH3
+# but $HASH2 is good and $HASH4 is bad,
 # so we should not be able to tell the first bad commit
 # among $HASH3 and $HASH4
 test_expect_success 'bisect skip: cannot tell between 2 commits' '
 	test_when_finished git bisect reset &&
-	git bisect start $HASH4 $HASH1 &&
+	git bisect start monday $HASH1 &&
 	git bisect skip &&
-	test_expect_code 2 git bisect good >my_bisect_log.txt &&
+	next="$(test_cmp_rev HEAD $HASH4 && echo bad || echo good)" &&
+	git bisect $next &&
+	next2="$(test "$next" = "good" && echo bad || echo good)" &&
+	test_expect_code 2 git bisect $next2 >my_bisect_log.txt &&
 	grep "first bad commit could be any of" my_bisect_log.txt &&
 	! grep $HASH1 my_bisect_log.txt &&
 	! grep $HASH2 my_bisect_log.txt &&
+	! grep "$(git rev-parse monday)" my_bisect_log.txt &&
 	grep $HASH3 my_bisect_log.txt &&
 	grep $HASH4 my_bisect_log.txt
 '
@@ -244,36 +244,35 @@ test_expect_success 'bisect skip: with commit both bad and skipped' '
 
 # We want to automatically find the commit that
 # introduced "Another" into hello.
-test_expect_success \
-    '"git bisect run" simple case' \
-    'echo "#"\!"/bin/sh" > test_script.sh &&
-     echo "grep Another hello > /dev/null" >> test_script.sh &&
-     echo "test \$? -ne 0" >> test_script.sh &&
-     chmod +x test_script.sh &&
-     git bisect start &&
-     git bisect good $HASH1 &&
-     git bisect bad $HASH4 &&
-     git bisect run ./test_script.sh > my_bisect_log.txt &&
-     grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
-     git bisect reset'
+test_expect_success '"git bisect run" simple case' '
+	echo "#"\!"/bin/sh" > test_script.sh &&
+	echo "grep Another hello > /dev/null" >> test_script.sh &&
+	echo "test \$? -ne 0" >> test_script.sh &&
+	chmod +x test_script.sh &&
+	git bisect start &&
+	git bisect good $HASH1 &&
+	git bisect bad $HASH4 &&
+	git bisect run ./test_script.sh > my_bisect_log.txt &&
+	grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
+	git bisect reset
+'
 
 # We want to automatically find the commit that
 # introduced "Ciao" into hello.
-test_expect_success \
-    '"git bisect run" with more complex "git bisect start"' \
-    'echo "#"\!"/bin/sh" > test_script.sh &&
-     echo "grep Ciao hello > /dev/null" >> test_script.sh &&
-     echo "test \$? -ne 0" >> test_script.sh &&
-     chmod +x test_script.sh &&
-     git bisect start $HASH4 $HASH1 &&
-     git bisect run ./test_script.sh > my_bisect_log.txt &&
-     grep "$HASH4 is the first bad commit" my_bisect_log.txt &&
-     git bisect reset'
+test_expect_success '"git bisect run" with more complex "git bisect start"' '
+	echo "#"\!"/bin/sh" > test_script.sh &&
+	echo "grep Ciao hello > /dev/null" >> test_script.sh &&
+	echo "test \$? -ne 0" >> test_script.sh &&
+	chmod +x test_script.sh &&
+	git bisect start $HASH4 $HASH1 &&
+	git bisect run ./test_script.sh > my_bisect_log.txt &&
+	grep "$HASH4 is the first bad commit" my_bisect_log.txt &&
+	git bisect reset
+'
 
 # $HASH1 is good, $HASH5 is bad, we skip $HASH3
 # but $HASH4 is good,
 # so we should find $HASH5 as the first bad commit
-HASH5=
 test_expect_success 'bisect skip: add line and then a new test' '
 	add_line_into_file "5: Another new line." hello &&
 	HASH5=$(git rev-parse --verify HEAD) &&
@@ -291,7 +290,6 @@ test_expect_success 'bisect skip and bisect replay' '
 	git bisect reset
 '
 
-HASH6=
 test_expect_success 'bisect run & skip: cannot tell between 2' '
 	add_line_into_file "6: Yet a line." hello &&
 	HASH6=$(git rev-parse --verify HEAD) &&
@@ -315,7 +313,6 @@ test_expect_success 'bisect run & skip: cannot tell between 2' '
 	fi
 '
 
-HASH7=
 test_expect_success 'bisect run & skip: find first bad' '
 	git bisect reset &&
 	add_line_into_file "7: Should be the last line." hello &&
@@ -368,17 +365,19 @@ test_expect_success 'bisect errors out if bad and good are mistaken' '
 
 test_expect_success 'bisect does not create a "bisect" branch' '
 	git bisect reset &&
-	git bisect start $HASH7 $HASH1 &&
+	git bisect start $HASH6 $HASH1 &&
 	git branch bisect &&
-	test_cmp_rev HEAD $HASH4 &&
+	next="$(test_cmp_rev HEAD $HASH4 && echo good || echo bad)" &&
+	hash1="$(test "$next" = "good" && echo $HASH5 || echo $HASH2)" &&
+	hash2="$(test "$next" = "good" && echo $HASH6 || echo $HASH2)" &&
 	git branch -D bisect &&
-	git bisect good &&
+	git bisect $next &&
 	git branch bisect &&
-	test_cmp_rev HEAD $HASH6 &&
-	git bisect good > my_bisect_log.txt &&
-	grep "$HASH7 is the first bad commit" my_bisect_log.txt &&
+	test_cmp_rev HEAD $hash1 &&
+	git bisect $next > my_bisect_log.txt &&
+	grep "$hash2 is the first bad commit" my_bisect_log.txt &&
 	git bisect reset &&
-	test_cmp_rev bisect $HASH6 &&
+	test_cmp_rev bisect $hash1 &&
 	git branch -D bisect
 '
 
@@ -400,14 +399,15 @@ test_expect_success 'side branch creation' '
 '
 
 test_expect_success 'good merge base when good and bad are siblings' '
-	git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt &&
+	git bisect start "$HASH6" "$SIDE_HASH7" > my_bisect_log.txt &&
 	grep "merge base must be tested" my_bisect_log.txt &&
 	grep $HASH4 my_bisect_log.txt &&
 	git bisect good > my_bisect_log.txt &&
 	test_must_fail grep "merge base must be tested" my_bisect_log.txt &&
-	grep $HASH6 my_bisect_log.txt &&
+	grep $HASH5 my_bisect_log.txt &&
 	git bisect reset
 '
+
 test_expect_success 'skipped merge base when good and bad are siblings' '
 	git bisect start "$SIDE_HASH7" "$HASH7" > my_bisect_log.txt &&
 	grep "merge base must be tested" my_bisect_log.txt &&
@@ -446,6 +446,9 @@ test_expect_success 'many merge bases creation' '
 	git checkout "$SIDE_HASH5" &&
 	git merge -m "merge HASH5 and SIDE_HASH5" "$HASH5" &&
 	A_HASH=$(git rev-parse --verify HEAD) &&
+	git checkout "$SIDE_HASH5" &&
+	git merge -m "merge HASH6 and SIDE_HASH5" "$HASH6" &&
+	A6_HASH=$(git rev-parse --verify HEAD) &&
 	git checkout side &&
 	git merge -m "merge HASH7 and SIDE_HASH7" "$HASH7" &&
 	B_HASH=$(git rev-parse --verify HEAD) &&
@@ -478,9 +481,8 @@ test_expect_success 'optimized merge base checks' '
 	grep "$HASH4" my_bisect_log.txt &&
 	git bisect good > my_bisect_log2.txt &&
 	test -f ".git/BISECT_ANCESTORS_OK" &&
-	test_cmp_rev HEAD $HASH6 &&
-	git bisect bad > my_bisect_log3.txt &&
-	git bisect good "$A_HASH" > my_bisect_log4.txt &&
+	next="$(test_cmp_rev HEAD $HASH5 && echo $A_HASH || echo $A6_HASH)" &&
+	git bisect good "$next" > my_bisect_log4.txt &&
 	grep "merge base must be tested" my_bisect_log4.txt &&
 	test_must_fail test -f ".git/BISECT_ANCESTORS_OK"
 '
@@ -529,10 +531,10 @@ test_expect_success 'restricting bisection on one dir' '
 test_expect_success 'restricting bisection on one dir and a file' '
 	git bisect reset &&
 	git bisect start HEAD $HASH1 -- dir1 hello &&
+	( test_cmp_rev HEAD $HASH6 && git bisect skip || : ) &&
 	test_cmp_rev HEAD "$PARA_HASH4" &&
 	git bisect bad &&
-	test_cmp_rev HEAD $HASH3 &&
-	git bisect good &&
+	( test_cmp_rev HEAD $HASH3 && git bisect good || : ) &&
 	test_cmp_rev HEAD $HASH4 &&
 	git bisect good &&
 	test_cmp_rev HEAD "$PARA_HASH1" &&
@@ -542,9 +544,9 @@ test_expect_success 'restricting bisection on one dir and a file' '
 
 test_expect_success 'skipping away from skipped commit' '
 	git bisect start $PARA_HASH7 $HASH1 &&
-	test_cmp_rev HEAD "$PARA_HASH4" &&
+	test_cmp_rev HEAD $PARA_HASH4 $HASH7 &&
 	git bisect skip &&
-	test_cmp_rev HEAD $HASH7 &&
+	test_cmp_rev HEAD $HASH7 $PARA_HASH4 &&
 	git bisect skip &&
 	test_cmp_rev HEAD "$PARA_HASH3"
 '
@@ -621,7 +623,7 @@ EOF
 
 test_expect_success 'bisect fails if tree is broken on start commit' '
 	git bisect reset &&
-	test_must_fail git bisect start BROKEN_HASH7 BROKEN_HASH4 2>error.txt &&
+	test_must_fail git bisect start BROKEN_HASH8 BROKEN_HASH4 2>error.txt &&
 	test_cmp expected.missing-tree.default error.txt
 '
 
@@ -635,7 +637,7 @@ test_expect_success 'bisect fails if tree is broken on trial commit' '
 
 test_expect_success 'bisect: --no-checkout - start commit bad' '
 	git bisect reset &&
-	git bisect start BROKEN_HASH7 BROKEN_HASH4 --no-checkout &&
+	git bisect start BROKEN_HASH8 BROKEN_HASH4 --no-checkout &&
 	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
 	git bisect reset
 '
@@ -672,9 +674,10 @@ test_expect_success 'bisect: --no-checkout - target in breakage' '
 test_expect_success 'bisect: --no-checkout - target after breakage' '
 	git bisect reset &&
 	git bisect start broken BROKEN_HASH4 --no-checkout &&
+	( test_cmp_rev BISECT_HEAD BROKEN_HASH6 || git bisect good BISECT_HEAD ) &&
 	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
 	git bisect good BISECT_HEAD &&
-	test_cmp_rev BISECT_HEAD BROKEN_HASH8 &&
+	test_cmp_rev BISECT_HEAD BROKEN_HASH8 BROKEN_HASH7 &&
 	git bisect good BISECT_HEAD &&
 	test_cmp_rev bisect/bad BROKEN_HASH9 &&
 	git bisect reset
@@ -738,7 +741,7 @@ test_expect_success '"git bisect bad HEAD" behaves as "git bisect bad"' '
 	git bisect start HEAD $HASH1 &&
 	git bisect good HEAD &&
 	git bisect bad HEAD &&
-	test_cmp_rev HEAD $HASH6 &&
+	test_cmp_rev HEAD $HASH6 $PARA_HASH1 &&
 	git bisect reset
 '
 
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 06/21] bisect: add test for the bisect algorithm
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (4 preceding siblings ...)
  2016-04-10 13:18 ` [PATCH v2 05/21] t6030: generalize test to not rely on current implementation Stephan Beyer
@ 2016-04-10 13:18 ` Stephan Beyer
  2016-04-15 21:13   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 07/21] bisect: plug the biggest memory leak Stephan Beyer
                   ` (13 subsequent siblings)
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:18 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---

Notes:
    Based on the review by Christian Couder, I use test_cmp_rev()
    instead of non-standard test ... -o ...

 t/t8010-bisect-algorithm.sh | 155 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 155 insertions(+)
 create mode 100755 t/t8010-bisect-algorithm.sh

diff --git a/t/t8010-bisect-algorithm.sh b/t/t8010-bisect-algorithm.sh
new file mode 100755
index 0000000..b06053a
--- /dev/null
+++ b/t/t8010-bisect-algorithm.sh
@@ -0,0 +1,155 @@
+#!/bin/sh
+#
+# Copyright (c) 2016 Stephan Beyer
+#
+test_description='Tests git bisect algorithm'
+
+. ./test-lib.sh
+
+test_expect_success 'set up a history for the test' '
+	test_commit A1 A 1 &&
+	test_commit A2 A 2 &&
+	test_commit A3 A 3 &&
+	test_commit A4 A 4 &&
+	test_commit A5 A 5 &&
+	test_commit A6 A 6 &&
+	git checkout -b b A5 &&
+	test_commit B1 B 1 &&
+	git checkout master &&
+	test_commit A7 A 7 &&
+	git checkout b &&
+	test_commit B2 B 2 &&
+	git checkout master &&
+	test_commit A8 A 8 &&
+	test_merge Bmerge b &&
+	git checkout b &&
+	test_commit B3 B 3 &&
+	git checkout -b c A7 &&
+	test_commit C1 C 1 &&
+	git checkout -b d A3 &&
+	test_commit D1 D 1 &&
+	git checkout c &&
+	test_commit C2 C 2 &&
+	git checkout d &&
+	test_commit D2 D 2 &&
+	git checkout c &&
+	test_commit C3 C 3 &&
+	git checkout master &&
+	git merge -m BCDmerge b c d &&
+	git tag BCDmerge &&
+	test_commit A9 A 9 &&
+	git checkout d &&
+	test_commit D3 &&
+	git checkout master
+'
+
+test_expect_success 'bisect algorithm works in linear history with an odd number of commits' '
+	git bisect start A7 &&
+	git bisect next &&
+	test_cmp_rev HEAD A3 A4
+'
+
+test_expect_success 'bisect algorithm works in linear history with an even number of commits' '
+	git bisect reset &&
+	git bisect start A8 &&
+	git bisect next &&
+	test_cmp_rev HEAD A4
+'
+
+test_expect_success 'bisect algorithm works with a merge' '
+	git bisect reset &&
+	git bisect start Bmerge &&
+	git bisect next &&
+	test_cmp_rev HEAD A5 &&
+	git bisect good &&
+	test_cmp_rev HEAD A8 &&
+	git bisect good &&
+	test_cmp_rev HEAD B1 B2
+'
+
+#                   | w  min | w  min | w  min | w  min |
+# B---.    BCDmerge | 18  0  | 9    0 | 5    0 | 3    0 |
+# |\ \ \            |        |        |        |        |
+# | | | *  D2       | 5   5  | 2    2 | 2    2*| good   |
+# | | | *  D1       | 4   4  | 1    1 | 1    1 | good   |
+# | | * |  C3       | 10  8  | 1    1 | 1    1 | 1    1*|
+# | | * |  C2       | 9   9 *| good   | good   | good   |
+# | | * |  C1       | 8   8  | good   | good   | good   |
+# | * | |  B3       | 8   8  | 3    3 | 1    1 | 1    1*|
+# * | | |  Bmerge   | 11  7  | 4    4*| good   | good   |
+# |\ \ \ \          |        |        |        |        |
+# | |/ / /          |        |        |        |        |
+# | * | |  B2       | 7   7  | 2    2 | good   | good   |
+# | * | |  B1       | 6   6  | 1    1 | good   | good   |
+# * | | |  A8       | 8   8  | 1    1 | good   | good   |
+# | |/ /            |        |        |        |        |
+# |/| |             |        |        |        |        |
+# * | |   A7        | 7   7  | good   | good   | good   |
+# * | |   A6        | 6   6  | good   | good   | good   |
+# |/ /              |        |        |        |        |
+# * |     A5        | 5   5  | good   | good   | good   |
+# * |     A4        | 4   4  | good   | good   | good   |
+# |/                |        |        |        |        |
+# *       A3        | 3   3  | good   | good   | good   |
+# *       A2        | 2   2  | good   | good   | good   |
+# *       A1        | 1   1  | good   | good   | good   |
+
+test_expect_success 'bisect algorithm works with octopus merge' '
+	git bisect reset &&
+	git bisect start BCDmerge &&
+	git bisect next &&
+	test_cmp_rev HEAD C2 &&
+	git bisect good &&
+	test_cmp_rev HEAD Bmerge &&
+	git bisect good &&
+	test_cmp_rev HEAD D2 &&
+	git bisect good &&
+	test_cmp_rev HEAD B3 C3 &&
+	git bisect good &&
+	test_cmp_rev HEAD C3 B3 &&
+	git bisect good > output &&
+	grep "$(git rev-parse BCDmerge) is the first bad commit" output
+'
+
+# G 5a6bcdf        D3       | w  min | w  min |
+# | B 02f2eed      A9       | 14  0  | 7   0  |
+# | *---. 6174c5c  BCDmerge | 13  1  | 6   1  |
+# | |\ \ \                  |        |        |
+# | |_|_|/                  |        |        |
+# |/| | |                   |        |        |
+# G | | | a6d6dab  D2       | good   | good   |
+# * | | | 86414e4  D1       | good   | good   |
+# | | | * c672402  C3       | 7   7 *| good   |
+# | | | * 0555272  C2       | 6   6  | good   |
+# | | | * 28c2b2a  C1       | 5   5  | good   |
+# | | * | 4b5a7d9  B3       | 5   5  | 3   3 *|
+# | * | | a419ab7  Bmerge   | 8   6  | 4   3 *|
+# | |\ \ \                  |        |        |
+# | | |/ /                  |        |        |
+# | | * | 4fa1e39  B2       | 4   4  | 2   2  |
+# | | * | 92a014d  B1       | 3   3  | 1   1  |
+# | * | | 79158c7  A8       | 5   5  | 1   1  |
+# | | |/                    |        |        |
+# | |/|                     |        |        |
+# | * | 237eb73    A7       | 4   4  | good   |
+# | * | 3b2f811    A6       | 3   3  | good   |
+# | |/                      |        |        |
+# | * 0f2b6d2      A5       | 2   2  | good   |
+# | * 1fcdaf0      A4       | 1   1  | good   |
+# |/                        |        |        |
+# * 096648b        A3       | good   | good   |
+# * 1cf01b8        A2       | good   | good   |
+# * 6623165        A1       | good   | good   |
+
+test_expect_success 'bisect algorithm works with good commit on unrelated branch' '
+	git bisect reset &&
+	git bisect start A9 D3 &&
+	test_cmp_rev HEAD "$(git merge-base A9 D3)" &&
+	test_cmp_rev HEAD D2 &&
+	git bisect good &&
+	test_cmp_rev HEAD C3 &&
+	git bisect good &&
+	test_cmp_rev HEAD B3 Bmerge
+'
+
+test_done
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 07/21] bisect: plug the biggest memory leak
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (5 preceding siblings ...)
  2016-04-10 13:18 ` [PATCH v2 06/21] bisect: add test for the bisect algorithm Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-15 21:18   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 08/21] bisect: make bisect compile if DEBUG_BISECT is set Stephan Beyer
                   ` (12 subsequent siblings)
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/bisect.c b/bisect.c
index 7996c29..901e4d3 100644
--- a/bisect.c
+++ b/bisect.c
@@ -984,6 +984,8 @@ int bisect_next_all(const char *prefix, int no_checkout)
 		exit(10);
 	}
 
+	free_commit_list(revs.commits);
+
 	nr = all - reaches - 1;
 	steps = estimate_bisect_steps(all);
 	printf("Bisecting: %d revision%s left to test after this "
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 08/21] bisect: make bisect compile if DEBUG_BISECT is set
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (6 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 07/21] bisect: plug the biggest memory leak Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-15 21:22   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 09/21] bisect: make algorithm behavior independent of DEBUG_BISECT Stephan Beyer
                   ` (11 subsequent siblings)
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Setting the macro DEBUG_BISECT to 1 enables debugging information
for the bisect algorithm. The code did not compile due to struct
changes.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/bisect.c b/bisect.c
index 901e4d3..2f54d96 100644
--- a/bisect.c
+++ b/bisect.c
@@ -131,7 +131,7 @@ static void show_list(const char *debug, int counted, int nr,
 		unsigned flags = commit->object.flags;
 		enum object_type type;
 		unsigned long size;
-		char *buf = read_sha1_file(commit->object.sha1, &type, &size);
+		char *buf = read_sha1_file(commit->object.oid.hash, &type, &size);
 		const char *subject_start;
 		int subject_len;
 
@@ -143,10 +143,10 @@ static void show_list(const char *debug, int counted, int nr,
 			fprintf(stderr, "%3d", weight(p));
 		else
 			fprintf(stderr, "---");
-		fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1));
+		fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.oid.hash));
 		for (pp = commit->parents; pp; pp = pp->next)
 			fprintf(stderr, " %.*s", 8,
-				sha1_to_hex(pp->item->object.sha1));
+				sha1_to_hex(pp->item->object.oid.hash));
 
 		subject_len = find_commit_subject(buf, &subject_start);
 		if (subject_len)
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 09/21] bisect: make algorithm behavior independent of DEBUG_BISECT
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (7 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 08/21] bisect: make bisect compile if DEBUG_BISECT is set Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-15 21:25   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 10/21] bisect: get rid of recursion in count_distance() Stephan Beyer
                   ` (10 subsequent siblings)
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

If DEBUG_BISECT is set to 1, bisect does not only show debug
information but also changes the algorithm behavior: halfway()
is always false.

This commit makes the algorithm independent of DEBUG_BISECT.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/bisect.c b/bisect.c
index 2f54d96..1a13f35 100644
--- a/bisect.c
+++ b/bisect.c
@@ -101,8 +101,6 @@ static inline int halfway(struct commit_list *p, int nr)
 	 */
 	if (p->item->object.flags & TREESAME)
 		return 0;
-	if (DEBUG_BISECT)
-		return 0;
 	/*
 	 * 2 and 3 are halfway of 5.
 	 * 3 is halfway of 6 but 2 and 4 are not.
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 10/21] bisect: get rid of recursion in count_distance()
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (8 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 09/21] bisect: make algorithm behavior independent of DEBUG_BISECT Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-15 21:31   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 11/21] bisect: use struct node_data array instead of int array Stephan Beyer
                   ` (9 subsequent siblings)
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Large repositories with a huge amount of merge commits in the
bisection process could lead to stack overflows in git bisect.
In order to prevent this, this commit uses an *iterative* version
for counting the number of ancestors of a commit.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 55 ++++++++++++++++++++++---------------------------------
 1 file changed, 22 insertions(+), 33 deletions(-)

diff --git a/bisect.c b/bisect.c
index 1a13f35..16bbfa6 100644
--- a/bisect.c
+++ b/bisect.c
@@ -26,33 +26,23 @@ static const char *term_good;
 /* Remember to update object flag allocation in object.h */
 #define COUNTED		(1u<<16)
 
-/*
- * This is a truly stupid algorithm, but it's only
- * used for bisection, and we just don't care enough.
- *
- * We care just barely enough to avoid recursing for
- * non-merge entries.
- */
 static int count_distance(struct commit_list *entry)
 {
 	int nr = 0;
+	struct commit_list *todo = NULL;
+	commit_list_append(entry->item, &todo);
 
-	while (entry) {
-		struct commit *commit = entry->item;
-		struct commit_list *p;
+	while (todo) {
+		struct commit *commit = pop_commit(&todo);
 
-		if (commit->object.flags & (UNINTERESTING | COUNTED))
-			break;
-		if (!(commit->object.flags & TREESAME))
-			nr++;
-		commit->object.flags |= COUNTED;
-		p = commit->parents;
-		entry = p;
-		if (p) {
-			p = p->next;
-			while (p) {
-				nr += count_distance(p);
-				p = p->next;
+		if (!(commit->object.flags & (UNINTERESTING | COUNTED))) {
+			struct commit_list *p;
+			if (!(commit->object.flags & TREESAME))
+				nr++;
+			commit->object.flags |= COUNTED;
+
+			for (p = commit->parents; p; p = p->next) {
+				commit_list_insert(p->item, &todo);
 			}
 		}
 	}
@@ -287,7 +277,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 	 * can reach.  So we do not have to run the expensive
 	 * count_distance() for single strand of pearls.
 	 *
-	 * However, if you have more than one parents, you cannot
+	 * However, if you have more than one parent, you cannot
 	 * just add their distance and one for yourself, since
 	 * they usually reach the same ancestor and you would
 	 * end up counting them twice that way.
@@ -296,17 +286,16 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 	 * way, and then fill the blanks using cheaper algorithm.
 	 */
 	for (p = list; p; p = p->next) {
-		if (p->item->object.flags & UNINTERESTING)
-			continue;
-		if (weight(p) != -2)
-			continue;
-		weight_set(p, count_distance(p));
-		clear_distance(list);
+		if (!(p->item->object.flags & UNINTERESTING)
+		 && (weight(p) == -2)) {
+			weight_set(p, count_distance(p));
+			clear_distance(list);
 
-		/* Does it happen to be at exactly half-way? */
-		if (!find_all && halfway(p, nr))
-			return p;
-		counted++;
+			/* Does it happen to be at exactly half-way? */
+			if (!find_all && halfway(p, nr))
+				return p;
+			counted++;
+		}
 	}
 
 	show_list("bisection 2 count_distance", counted, nr, list);
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 11/21] bisect: use struct node_data array instead of int array
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (9 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 10/21] bisect: get rid of recursion in count_distance() Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-12 23:02   ` Christian Couder
  2016-04-15 21:47   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 12/21] bisect: replace clear_distance() by unique markers Stephan Beyer
                   ` (8 subsequent siblings)
  19 siblings, 2 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

This is a preparation for subsequent changes.
During a bisection process, we want to augment commits with
further information to improve speed.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 61 ++++++++++++++++++++++++++++++-------------------------------
 1 file changed, 30 insertions(+), 31 deletions(-)

diff --git a/bisect.c b/bisect.c
index 16bbfa6..bc1bfbd 100644
--- a/bisect.c
+++ b/bisect.c
@@ -23,9 +23,21 @@ static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
 static const char *term_bad;
 static const char *term_good;
 
+struct node_data {
+	int weight;
+};
+
 /* Remember to update object flag allocation in object.h */
 #define COUNTED		(1u<<16)
 
+#define DEBUG_BISECT 0
+
+static inline struct node_data *node_data(struct commit *elem)
+{
+	assert(elem->util);
+	return (struct node_data *)elem->util;
+}
+
 static int count_distance(struct commit_list *entry)
 {
 	int nr = 0;
@@ -59,18 +71,6 @@ static void clear_distance(struct commit_list *list)
 	}
 }
 
-#define DEBUG_BISECT 0
-
-static inline int weight(struct commit_list *elem)
-{
-	return *((int*)(elem->item->util));
-}
-
-static inline void weight_set(struct commit_list *elem, int weight)
-{
-	*((int*)(elem->item->util)) = weight;
-}
-
 static int count_interesting_parents(struct commit *commit)
 {
 	struct commit_list *p;
@@ -95,7 +95,7 @@ static inline int halfway(struct commit_list *p, int nr)
 	 * 2 and 3 are halfway of 5.
 	 * 3 is halfway of 6 but 2 and 4 are not.
 	 */
-	switch (2 * weight(p) - nr) {
+	switch (2 * node_data(p->item)->weight - nr) {
 	case -1: case 0: case 1:
 		return 1;
 	default:
@@ -128,7 +128,7 @@ static void show_list(const char *debug, int counted, int nr,
 			(flags & UNINTERESTING) ? 'U' : ' ',
 			(flags & COUNTED) ? 'C' : ' ');
 		if (commit->util)
-			fprintf(stderr, "%3d", weight(p));
+			fprintf(stderr, "%3d", node_data(commit)->weight);
 		else
 			fprintf(stderr, "---");
 		fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.oid.hash));
@@ -156,7 +156,7 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
 
 		if (flags & TREESAME)
 			continue;
-		distance = weight(p);
+		distance = node_data(p->item)->weight;
 		if (nr - distance < distance)
 			distance = nr - distance;
 		if (distance > best_distance) {
@@ -196,7 +196,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
 
 		if (flags & TREESAME)
 			continue;
-		distance = weight(p);
+		distance = node_data(p->item)->weight;
 		if (nr - distance < distance)
 			distance = nr - distance;
 		array[cnt].commit = p->item;
@@ -234,7 +234,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
  * or positive distance.
  */
 static struct commit_list *do_find_bisection(struct commit_list *list,
-					     int nr, int *weights,
+					     int nr, struct node_data *weights,
 					     int find_all)
 {
 	int n, counted;
@@ -246,11 +246,11 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 		struct commit *commit = p->item;
 		unsigned flags = commit->object.flags;
 
-		p->item->util = &weights[n++];
+		commit->util = &weights[n++];
 		switch (count_interesting_parents(commit)) {
 		case 0:
 			if (!(flags & TREESAME)) {
-				weight_set(p, 1);
+				node_data(commit)->weight = 1;
 				counted++;
 				show_list("bisection 2 count one",
 					  counted, nr, list);
@@ -261,10 +261,10 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 			 */
 			break;
 		case 1:
-			weight_set(p, -1);
+			node_data(commit)->weight = -1;
 			break;
 		default:
-			weight_set(p, -2);
+			node_data(commit)->weight = -2;
 			break;
 		}
 	}
@@ -287,8 +287,8 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 	 */
 	for (p = list; p; p = p->next) {
 		if (!(p->item->object.flags & UNINTERESTING)
-		 && (weight(p) == -2)) {
-			weight_set(p, count_distance(p));
+		 && (node_data(p->item)->weight == -2)) {
+			node_data(p->item)->weight = count_distance(p);
 			clear_distance(list);
 
 			/* Does it happen to be at exactly half-way? */
@@ -305,12 +305,12 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 			struct commit_list *q;
 			unsigned flags = p->item->object.flags;
 
-			if (0 <= weight(p))
+			if (0 <= node_data(p->item)->weight)
 				continue;
 			for (q = p->item->parents; q; q = q->next) {
 				if (q->item->object.flags & UNINTERESTING)
 					continue;
-				if (0 <= weight(q))
+				if (0 <= node_data(q->item)->weight)
 					break;
 			}
 			if (!q)
@@ -321,14 +321,13 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 			 * add one for p itself if p is to be counted,
 			 * otherwise inherit it from q directly.
 			 */
+			node_data(p->item)->weight = node_data(q->item)->weight;
 			if (!(flags & TREESAME)) {
-				weight_set(p, weight(q)+1);
+				node_data(p->item)->weight++;
 				counted++;
 				show_list("bisection 2 count one",
 					  counted, nr, list);
 			}
-			else
-				weight_set(p, weight(q));
 
 			/* Does it happen to be at exactly half-way? */
 			if (!find_all && halfway(p, nr))
@@ -350,7 +349,7 @@ struct commit_list *find_bisection(struct commit_list *list,
 {
 	int nr, on_list;
 	struct commit_list *p, *best, *next, *last;
-	int *weights;
+	struct node_data *weights;
 
 	show_list("bisection 2 entry", 0, 0, list);
 
@@ -376,14 +375,14 @@ struct commit_list *find_bisection(struct commit_list *list,
 	show_list("bisection 2 sorted", 0, nr, list);
 
 	*all = nr;
-	weights = xcalloc(on_list, sizeof(*weights));
+	weights = (struct node_data *)xcalloc(on_list, sizeof(*weights));
 
 	/* Do the real work of finding bisection commit. */
 	best = do_find_bisection(list, nr, weights, find_all);
 	if (best) {
 		if (!find_all)
 			best->next = NULL;
-		*reaches = weight(best);
+		*reaches = node_data(best->item)->weight;
 	}
 	free(weights);
 	return best;
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 12/21] bisect: replace clear_distance() by unique markers
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (10 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 11/21] bisect: use struct node_data array instead of int array Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-12 23:20   ` Christian Couder
  2016-04-15 22:07   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 13/21] bisect: use commit instead of commit list as arguments when appropriate Stephan Beyer
                   ` (7 subsequent siblings)
  19 siblings, 2 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

clear_distance() was a O(#commits)-time function to clear the COUNTED
flag from commits counted in count_distance().
The functions count_distance() and clear_distance() were called for
each merge commit.

This commit gets rid of the clear_distance() function by making
count_distance() use unique marker ids that do not need to be
cleared afterwards. This speeds up the bisecting process on
large repositories with a huge amount of merges.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 29 +++++++++++------------------
 1 file changed, 11 insertions(+), 18 deletions(-)

diff --git a/bisect.c b/bisect.c
index bc1bfbd..4209c75 100644
--- a/bisect.c
+++ b/bisect.c
@@ -23,13 +23,13 @@ static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
 static const char *term_bad;
 static const char *term_good;
 
+static unsigned marker;
+
 struct node_data {
 	int weight;
+	unsigned marked;
 };
 
-/* Remember to update object flag allocation in object.h */
-#define COUNTED		(1u<<16)
-
 #define DEBUG_BISECT 0
 
 static inline struct node_data *node_data(struct commit *elem)
@@ -43,15 +43,17 @@ static int count_distance(struct commit_list *entry)
 	int nr = 0;
 	struct commit_list *todo = NULL;
 	commit_list_append(entry->item, &todo);
+	marker++;
 
 	while (todo) {
 		struct commit *commit = pop_commit(&todo);
 
-		if (!(commit->object.flags & (UNINTERESTING | COUNTED))) {
+		if (!(commit->object.flags & UNINTERESTING)
+		 && node_data(commit)->marked != marker) {
 			struct commit_list *p;
 			if (!(commit->object.flags & TREESAME))
 				nr++;
-			commit->object.flags |= COUNTED;
+			node_data(commit)->marked = marker;
 
 			for (p = commit->parents; p; p = p->next) {
 				commit_list_insert(p->item, &todo);
@@ -62,15 +64,6 @@ static int count_distance(struct commit_list *entry)
 	return nr;
 }
 
-static void clear_distance(struct commit_list *list)
-{
-	while (list) {
-		struct commit *commit = list->item;
-		commit->object.flags &= ~COUNTED;
-		list = list->next;
-	}
-}
-
 static int count_interesting_parents(struct commit *commit)
 {
 	struct commit_list *p;
@@ -123,10 +116,9 @@ static void show_list(const char *debug, int counted, int nr,
 		const char *subject_start;
 		int subject_len;
 
-		fprintf(stderr, "%c%c%c ",
+		fprintf(stderr, "%c%c ",
 			(flags & TREESAME) ? ' ' : 'T',
-			(flags & UNINTERESTING) ? 'U' : ' ',
-			(flags & COUNTED) ? 'C' : ' ');
+			(flags & UNINTERESTING) ? 'U' : ' ');
 		if (commit->util)
 			fprintf(stderr, "%3d", node_data(commit)->weight);
 		else
@@ -289,7 +281,6 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 		if (!(p->item->object.flags & UNINTERESTING)
 		 && (node_data(p->item)->weight == -2)) {
 			node_data(p->item)->weight = count_distance(p);
-			clear_distance(list);
 
 			/* Does it happen to be at exactly half-way? */
 			if (!find_all && halfway(p, nr))
@@ -351,6 +342,8 @@ struct commit_list *find_bisection(struct commit_list *list,
 	struct commit_list *p, *best, *next, *last;
 	struct node_data *weights;
 
+	marker = 0;
+
 	show_list("bisection 2 entry", 0, 0, list);
 
 	/*
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 13/21] bisect: use commit instead of commit list as arguments when appropriate
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (11 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 12/21] bisect: replace clear_distance() by unique markers Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-15 22:08   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 14/21] bisect: extract get_distance() function from code duplication Stephan Beyer
                   ` (6 subsequent siblings)
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

It makes no sense that the argument for count_distance() and
halfway() is a commit list when only its first commit is relevant.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/bisect.c b/bisect.c
index 4209c75..2c1102f 100644
--- a/bisect.c
+++ b/bisect.c
@@ -38,11 +38,11 @@ static inline struct node_data *node_data(struct commit *elem)
 	return (struct node_data *)elem->util;
 }
 
-static int count_distance(struct commit_list *entry)
+static int count_distance(struct commit *elem)
 {
 	int nr = 0;
 	struct commit_list *todo = NULL;
-	commit_list_append(entry->item, &todo);
+	commit_list_append(elem, &todo);
 	marker++;
 
 	while (todo) {
@@ -77,18 +77,18 @@ static int count_interesting_parents(struct commit *commit)
 	return count;
 }
 
-static inline int halfway(struct commit_list *p, int nr)
+static inline int halfway(struct commit *commit, int nr)
 {
 	/*
 	 * Don't short-cut something we are not going to return!
 	 */
-	if (p->item->object.flags & TREESAME)
+	if (commit->object.flags & TREESAME)
 		return 0;
 	/*
 	 * 2 and 3 are halfway of 5.
 	 * 3 is halfway of 6 but 2 and 4 are not.
 	 */
-	switch (2 * node_data(p->item)->weight - nr) {
+	switch (2 * node_data(commit)->weight - nr) {
 	case -1: case 0: case 1:
 		return 1;
 	default:
@@ -280,10 +280,10 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 	for (p = list; p; p = p->next) {
 		if (!(p->item->object.flags & UNINTERESTING)
 		 && (node_data(p->item)->weight == -2)) {
-			node_data(p->item)->weight = count_distance(p);
+			node_data(p->item)->weight = count_distance(p->item);
 
 			/* Does it happen to be at exactly half-way? */
-			if (!find_all && halfway(p, nr))
+			if (!find_all && halfway(p->item, nr))
 				return p;
 			counted++;
 		}
@@ -321,7 +321,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 			}
 
 			/* Does it happen to be at exactly half-way? */
-			if (!find_all && halfway(p, nr))
+			if (!find_all && halfway(p->item, nr))
 				return p;
 		}
 	}
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 14/21] bisect: extract get_distance() function from code duplication
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (12 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 13/21] bisect: use commit instead of commit list as arguments when appropriate Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-15 22:08   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 15/21] bisect: introduce distance_direction() Stephan Beyer
                   ` (5 subsequent siblings)
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/bisect.c b/bisect.c
index 2c1102f..cfd406c 100644
--- a/bisect.c
+++ b/bisect.c
@@ -38,6 +38,14 @@ static inline struct node_data *node_data(struct commit *elem)
 	return (struct node_data *)elem->util;
 }
 
+static inline int get_distance(struct commit *commit, int total)
+{
+	int distance = node_data(commit)->weight;
+	if (total - distance < distance)
+		distance = total - distance;
+	return distance;
+}
+
 static int count_distance(struct commit *elem)
 {
 	int nr = 0;
@@ -148,9 +156,7 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
 
 		if (flags & TREESAME)
 			continue;
-		distance = node_data(p->item)->weight;
-		if (nr - distance < distance)
-			distance = nr - distance;
+		distance = get_distance(p->item, nr);
 		if (distance > best_distance) {
 			best = p;
 			best_distance = distance;
@@ -188,9 +194,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
 
 		if (flags & TREESAME)
 			continue;
-		distance = node_data(p->item)->weight;
-		if (nr - distance < distance)
-			distance = nr - distance;
+		distance = get_distance(p->item, nr);
 		array[cnt].commit = p->item;
 		array[cnt].distance = distance;
 		cnt++;
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 15/21] bisect: introduce distance_direction()
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (13 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 14/21] bisect: extract get_distance() function from code duplication Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-15 22:10   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 16/21] bisect: make total number of commits global Stephan Beyer
                   ` (4 subsequent siblings)
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

We introduce the concept of rising and falling distances
(in addition to a halfway distance).
This will be useful in subsequent commits.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 33 +++++++++++++++++++++++----------
 1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/bisect.c b/bisect.c
index cfd406c..f737ce7 100644
--- a/bisect.c
+++ b/bisect.c
@@ -46,6 +46,28 @@ static inline int get_distance(struct commit *commit, int total)
 	return distance;
 }
 
+/*
+ * Return -1 if the distance is falling.
+ * (A falling distance means that the distance of the
+ *  given commit is larger than the distance of its
+ *  child commits.)
+ * Return 0 if the distance is halfway.
+ * Return 1 if the distance is rising.
+ */
+static inline int distance_direction(struct commit *commit, int total)
+{
+	int doubled_diff = 2 * node_data(commit)->weight - total;
+	if (doubled_diff < -1)
+		return 1;
+	if (doubled_diff > 1)
+		return -1;
+	/*
+	 * 2 and 3 are halfway of 5.
+	 * 3 is halfway of 6 but 2 and 4 are not.
+	 */
+	return 0;
+}
+
 static int count_distance(struct commit *elem)
 {
 	int nr = 0;
@@ -92,16 +114,7 @@ static inline int halfway(struct commit *commit, int nr)
 	 */
 	if (commit->object.flags & TREESAME)
 		return 0;
-	/*
-	 * 2 and 3 are halfway of 5.
-	 * 3 is halfway of 6 but 2 and 4 are not.
-	 */
-	switch (2 * node_data(commit)->weight - nr) {
-	case -1: case 0: case 1:
-		return 1;
-	default:
-		return 0;
-	}
+	return !distance_direction(commit, nr);
 }
 
 #if !DEBUG_BISECT
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 16/21] bisect: make total number of commits global
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (14 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 15/21] bisect: introduce distance_direction() Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-13 13:23   ` Christian Couder
                     ` (2 more replies)
  2016-04-10 13:19 ` [PATCH v2 17/21] bisect: rename count_distance() to compute_weight() Stephan Beyer
                   ` (3 subsequent siblings)
  19 siblings, 3 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

The total number of commits in a bisect process is a property of
the bisect process. Making this property global helps to make the code
clearer.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 74 ++++++++++++++++++++++++++++++++++------------------------------
 1 file changed, 39 insertions(+), 35 deletions(-)

diff --git a/bisect.c b/bisect.c
index f737ce7..2b415ad 100644
--- a/bisect.c
+++ b/bisect.c
@@ -23,6 +23,8 @@ static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
 static const char *term_bad;
 static const char *term_good;
 
+static int total;
+
 static unsigned marker;
 
 struct node_data {
@@ -38,7 +40,7 @@ static inline struct node_data *node_data(struct commit *elem)
 	return (struct node_data *)elem->util;
 }
 
-static inline int get_distance(struct commit *commit, int total)
+static inline int get_distance(struct commit *commit)
 {
 	int distance = node_data(commit)->weight;
 	if (total - distance < distance)
@@ -54,7 +56,7 @@ static inline int get_distance(struct commit *commit, int total)
  * Return 0 if the distance is halfway.
  * Return 1 if the distance is rising.
  */
-static inline int distance_direction(struct commit *commit, int total)
+static inline int distance_direction(struct commit *commit)
 {
 	int doubled_diff = 2 * node_data(commit)->weight - total;
 	if (doubled_diff < -1)
@@ -107,25 +109,25 @@ static int count_interesting_parents(struct commit *commit)
 	return count;
 }
 
-static inline int halfway(struct commit *commit, int nr)
+static inline int halfway(struct commit *commit)
 {
 	/*
 	 * Don't short-cut something we are not going to return!
 	 */
 	if (commit->object.flags & TREESAME)
 		return 0;
-	return !distance_direction(commit, nr);
+	return !distance_direction(commit);
 }
 
 #if !DEBUG_BISECT
-#define show_list(a,b,c,d) do { ; } while (0)
+#define show_list(a,b,c) do { ; } while (0)
 #else
-static void show_list(const char *debug, int counted, int nr,
+static void show_list(const char *debug, int counted,
 		      struct commit_list *list)
 {
 	struct commit_list *p;
 
-	fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
+	fprintf(stderr, "%s (%d/%d)\n", debug, counted, total);
 
 	for (p = list; p; p = p->next) {
 		struct commit_list *pp;
@@ -157,7 +159,7 @@ static void show_list(const char *debug, int counted, int nr,
 }
 #endif /* DEBUG_BISECT */
 
-static struct commit_list *best_bisection(struct commit_list *list, int nr)
+static struct commit_list *best_bisection(struct commit_list *list)
 {
 	struct commit_list *p, *best;
 	int best_distance = -1;
@@ -169,7 +171,7 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
 
 		if (flags & TREESAME)
 			continue;
-		distance = get_distance(p->item, nr);
+		distance = get_distance(p->item);
 		if (distance > best_distance) {
 			best = p;
 			best_distance = distance;
@@ -195,10 +197,10 @@ static int compare_commit_dist(const void *a_, const void *b_)
 	return oidcmp(&a->commit->object.oid, &b->commit->object.oid);
 }
 
-static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
+static struct commit_list *best_bisection_sorted(struct commit_list *list)
 {
 	struct commit_list *p;
-	struct commit_dist *array = xcalloc(nr, sizeof(*array));
+	struct commit_dist *array = xcalloc(total, sizeof(*array));
 	int cnt, i;
 
 	for (p = list, cnt = 0; p; p = p->next) {
@@ -207,7 +209,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
 
 		if (flags & TREESAME)
 			continue;
-		distance = get_distance(p->item, nr);
+		distance = get_distance(p->item);
 		array[cnt].commit = p->item;
 		array[cnt].distance = distance;
 		cnt++;
@@ -243,7 +245,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
  * or positive distance.
  */
 static struct commit_list *do_find_bisection(struct commit_list *list,
-					     int nr, struct node_data *weights,
+					     struct node_data *weights,
 					     int find_all)
 {
 	int n, counted;
@@ -262,7 +264,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 				node_data(commit)->weight = 1;
 				counted++;
 				show_list("bisection 2 count one",
-					  counted, nr, list);
+					  counted, list);
 			}
 			/*
 			 * otherwise, it is known not to reach any
@@ -278,7 +280,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 		}
 	}
 
-	show_list("bisection 2 initialize", counted, nr, list);
+	show_list("bisection 2 initialize", counted, list);
 
 	/*
 	 * If you have only one parent in the resulting set
@@ -300,15 +302,15 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 			node_data(p->item)->weight = count_distance(p->item);
 
 			/* Does it happen to be at exactly half-way? */
-			if (!find_all && halfway(p->item, nr))
+			if (!find_all && halfway(p->item))
 				return p;
 			counted++;
 		}
 	}
 
-	show_list("bisection 2 count_distance", counted, nr, list);
+	show_list("bisection 2 count_distance", counted, list);
 
-	while (counted < nr) {
+	while (counted < total) {
 		for (p = list; p; p = p->next) {
 			struct commit_list *q;
 			unsigned flags = p->item->object.flags;
@@ -334,40 +336,41 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 				node_data(p->item)->weight++;
 				counted++;
 				show_list("bisection 2 count one",
-					  counted, nr, list);
+					  counted, list);
 			}
 
 			/* Does it happen to be at exactly half-way? */
-			if (!find_all && halfway(p->item, nr))
+			if (!find_all && halfway(p->item))
 				return p;
 		}
 	}
 
-	show_list("bisection 2 counted all", counted, nr, list);
+	show_list("bisection 2 counted all", counted, list);
 
 	if (!find_all)
-		return best_bisection(list, nr);
+		return best_bisection(list);
 	else
-		return best_bisection_sorted(list, nr);
+		return best_bisection_sorted(list);
 }
 
 struct commit_list *find_bisection(struct commit_list *list,
 					  int *reaches, int *all,
 					  int find_all)
 {
-	int nr, on_list;
+	int on_list;
 	struct commit_list *p, *best, *next, *last;
 	struct node_data *weights;
 
+	total = 0;
 	marker = 0;
 
-	show_list("bisection 2 entry", 0, 0, list);
+	show_list("bisection 2 entry", 0, list);
 
 	/*
 	 * Count the number of total and tree-changing items on the
 	 * list, while reversing the list.
 	 */
-	for (nr = on_list = 0, last = NULL, p = list;
+	for (on_list = 0, last = NULL, p = list;
 	     p;
 	     p = next) {
 		unsigned flags = p->item->object.flags;
@@ -378,23 +381,24 @@ struct commit_list *find_bisection(struct commit_list *list,
 		p->next = last;
 		last = p;
 		if (!(flags & TREESAME))
-			nr++;
+			total++;
 		on_list++;
 	}
 	list = last;
-	show_list("bisection 2 sorted", 0, nr, list);
+	show_list("bisection 2 sorted", 0, list);
 
-	*all = nr;
+	*all = total;
 	weights = (struct node_data *)xcalloc(on_list, sizeof(*weights));
 
 	/* Do the real work of finding bisection commit. */
-	best = do_find_bisection(list, nr, weights, find_all);
+	best = do_find_bisection(list, weights, find_all);
 	if (best) {
 		if (!find_all)
 			best->next = NULL;
 		*reaches = node_data(best->item)->weight;
 	}
 	free(weights);
+
 	return best;
 }
 
@@ -931,7 +935,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
 {
 	struct rev_info revs;
 	struct commit_list *tried;
-	int reaches = 0, all = 0, nr, steps;
+	int reaches = 0, nr, steps;
 	const unsigned char *bisect_rev;
 
 	read_bisect_terms(&term_bad, &term_good);
@@ -945,7 +949,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
 
 	bisect_common(&revs);
 
-	revs.commits = find_bisection(revs.commits, &reaches, &all,
+	revs.commits = find_bisection(revs.commits, &reaches, &total,
 				       !!skipped_revs.nr);
 	revs.commits = managed_skipped(revs.commits, &tried);
 
@@ -963,7 +967,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
 		exit(1);
 	}
 
-	if (!all) {
+	if (!total) {
 		fprintf(stderr, "No testable commit found.\n"
 			"Maybe you started with bad path parameters?\n");
 		exit(4);
@@ -982,8 +986,8 @@ int bisect_next_all(const char *prefix, int no_checkout)
 
 	free_commit_list(revs.commits);
 
-	nr = all - reaches - 1;
-	steps = estimate_bisect_steps(all);
+	nr = total - reaches - 1;
+	steps = estimate_bisect_steps(total);
 	printf("Bisecting: %d revision%s left to test after this "
 	       "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
 	       steps, (steps == 1 ? "" : "s"));
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 17/21] bisect: rename count_distance() to compute_weight()
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (15 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 16/21] bisect: make total number of commits global Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-13 13:32   ` Christian Couder
  2016-04-15 22:12   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 18/21] bisect: prepare for different algorithms based on find_all Stephan Beyer
                   ` (2 subsequent siblings)
  19 siblings, 2 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

Let us use the term "weight" for the number of ancestors
of each commit, and "distance" for the number
min{weight, #commits - weight}. (Bisect finds the commit
with maximum distance.)

In these terms, "count_distance()" is the wrong name of
the function. So it is renamed to "compute_weight()",
and it also directly sets the computed weight.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/bisect.c b/bisect.c
index 2b415ad..a254f28 100644
--- a/bisect.c
+++ b/bisect.c
@@ -70,7 +70,7 @@ static inline int distance_direction(struct commit *commit)
 	return 0;
 }
 
-static int count_distance(struct commit *elem)
+static int compute_weight(struct commit *elem)
 {
 	int nr = 0;
 	struct commit_list *todo = NULL;
@@ -93,6 +93,7 @@ static int count_distance(struct commit *elem)
 		}
 	}
 
+	node_data(elem)->weight = nr;
 	return nr;
 }
 
@@ -241,7 +242,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list)
  * be computed.
  *
  * weight = -2 means it has more than one parent and its distance is
- * unknown.  After running count_distance() first, they will get zero
+ * unknown.  After running compute_weight() first, they will get zero
  * or positive distance.
  */
 static struct commit_list *do_find_bisection(struct commit_list *list,
@@ -286,7 +287,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 	 * If you have only one parent in the resulting set
 	 * then you can reach one commit more than that parent
 	 * can reach.  So we do not have to run the expensive
-	 * count_distance() for single strand of pearls.
+	 * compute_weight() for single strand of pearls.
 	 *
 	 * However, if you have more than one parent, you cannot
 	 * just add their distance and one for yourself, since
@@ -299,7 +300,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 	for (p = list; p; p = p->next) {
 		if (!(p->item->object.flags & UNINTERESTING)
 		 && (node_data(p->item)->weight == -2)) {
-			node_data(p->item)->weight = count_distance(p->item);
+			compute_weight(p->item);
 
 			/* Does it happen to be at exactly half-way? */
 			if (!find_all && halfway(p->item))
@@ -308,7 +309,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 		}
 	}
 
-	show_list("bisection 2 count_distance", counted, list);
+	show_list("bisection 2 compute_weight", counted, list);
 
 	while (counted < total) {
 		for (p = list; p; p = p->next) {
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 18/21] bisect: prepare for different algorithms based on find_all
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (16 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 17/21] bisect: rename count_distance() to compute_weight() Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-15 22:36   ` Junio C Hamano
  2016-04-10 13:19 ` [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights Stephan Beyer
  2016-04-10 13:24 ` [PATCH v2 20/21] bisect: compute best bisection in compute_relevant_weights() Stephan Beyer
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

This is a preparation commit with copy-and-paste involved.
The function do_find_bisection() is changed and copied to
two almost similar functions compute_all_weights() and
compute_relevant_weights().

The function compute_relevant_weights() stops when a
"halfway" commit is found.

To keep the code clean, the halfway commit is not returned
and has to be found by best_bisection() afterwards.
This results in a singular additional O(#commits)-time
overhead but this will be outweighed by the following
changes to compute_relevant_weights().

It is necessary to keep compute_all_weights() for the
"git rev-list --bisect-all" command. All other bisect-related
commands will use compute_relevant_weights().

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---
 bisect.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 97 insertions(+), 19 deletions(-)

diff --git a/bisect.c b/bisect.c
index a254f28..c6bad43 100644
--- a/bisect.c
+++ b/bisect.c
@@ -179,6 +179,7 @@ static struct commit_list *best_bisection(struct commit_list *list)
 		}
 	}
 
+	best->next = NULL;
 	return best;
 }
 
@@ -245,9 +246,8 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list)
  * unknown.  After running compute_weight() first, they will get zero
  * or positive distance.
  */
-static struct commit_list *do_find_bisection(struct commit_list *list,
-					     struct node_data *weights,
-					     int find_all)
+static void compute_all_weights(struct commit_list *list,
+				struct node_data *weights)
 {
 	int n, counted;
 	struct commit_list *p;
@@ -301,10 +301,88 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 		if (!(p->item->object.flags & UNINTERESTING)
 		 && (node_data(p->item)->weight == -2)) {
 			compute_weight(p->item);
+			counted++;
+		}
+	}
+
+	show_list("bisection 2 compute_weight", counted, list);
+
+	while (counted < total) {
+		for (p = list; p; p = p->next) {
+			struct commit_list *q;
+			unsigned flags = p->item->object.flags;
+
+			if (0 <= node_data(p->item)->weight)
+				continue;
+			for (q = p->item->parents; q; q = q->next) {
+				if (q->item->object.flags & UNINTERESTING)
+					continue;
+				if (0 <= node_data(q->item)->weight)
+					break;
+			}
+			if (!q)
+				continue;
+
+			/*
+			 * weight for p is unknown but q is known.
+			 * add one for p itself if p is to be counted,
+			 * otherwise inherit it from q directly.
+			 */
+			node_data(p->item)->weight = node_data(q->item)->weight;
+			if (!(flags & TREESAME)) {
+				node_data(p->item)->weight++;
+				counted++;
+				show_list("bisection 2 count one",
+					  counted, list);
+			}
+		}
+	}
+	show_list("bisection 2 counted all", counted, list);
+}
+
+/* At the moment this is basically the same as compute_all_weights()
+ * but with a halfway shortcut */
+static void compute_relevant_weights(struct commit_list *list,
+				     struct node_data *weights)
+{
+	int n, counted;
+	struct commit_list *p;
+
+	counted = 0;
+
+	for (n = 0, p = list; p; p = p->next) {
+		struct commit *commit = p->item;
+		unsigned flags = commit->object.flags;
+
+		commit->util = &weights[n++];
+		switch (count_interesting_parents(commit)) {
+		case 0:
+			if (!(flags & TREESAME)) {
+				node_data(commit)->weight = 1;
+				counted++;
+				show_list("bisection 2 count one",
+					  counted, list);
+			}
+			break;
+		case 1:
+			node_data(commit)->weight = -1;
+			break;
+		default:
+			node_data(commit)->weight = -2;
+			break;
+		}
+	}
+
+	show_list("bisection 2 initialize", counted, list);
+
+	for (p = list; p; p = p->next) {
+		if (!(p->item->object.flags & UNINTERESTING)
+		 && (node_data(p->item)->weight == -2)) {
+			compute_weight(p->item);
 
 			/* Does it happen to be at exactly half-way? */
-			if (!find_all && halfway(p->item))
-				return p;
+			if (halfway(p->item))
+				return;
 			counted++;
 		}
 	}
@@ -341,17 +419,11 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
 			}
 
 			/* Does it happen to be at exactly half-way? */
-			if (!find_all && halfway(p->item))
-				return p;
+			if (halfway(p->item))
+				return;
 		}
 	}
-
 	show_list("bisection 2 counted all", counted, list);
-
-	if (!find_all)
-		return best_bisection(list);
-	else
-		return best_bisection_sorted(list);
 }
 
 struct commit_list *find_bisection(struct commit_list *list,
@@ -365,6 +437,9 @@ struct commit_list *find_bisection(struct commit_list *list,
 	total = 0;
 	marker = 0;
 
+	if (!list)
+		return NULL;
+
 	show_list("bisection 2 entry", 0, list);
 
 	/*
@@ -391,13 +466,16 @@ struct commit_list *find_bisection(struct commit_list *list,
 	*all = total;
 	weights = (struct node_data *)xcalloc(on_list, sizeof(*weights));
 
-	/* Do the real work of finding bisection commit. */
-	best = do_find_bisection(list, weights, find_all);
-	if (best) {
-		if (!find_all)
-			best->next = NULL;
-		*reaches = node_data(best->item)->weight;
+	if (find_all) {
+		compute_all_weights(list, weights);
+		best = best_bisection_sorted(list);
+	} else {
+		compute_relevant_weights(list, weights);
+		best = best_bisection(list);
 	}
+	assert(best);
+	*reaches = node_data(best->item)->weight;
+
 	free(weights);
 
 	return best;
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (17 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 18/21] bisect: prepare for different algorithms based on find_all Stephan Beyer
@ 2016-04-10 13:19 ` Stephan Beyer
  2016-04-13 14:11   ` Christian Couder
                     ` (3 more replies)
  2016-04-10 13:24 ` [PATCH v2 20/21] bisect: compute best bisection in compute_relevant_weights() Stephan Beyer
  19 siblings, 4 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:19 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

The idea is to reverse the DAG and perform a traversal
starting on all sources of the reversed DAG.

We walk from the bottom commits, incrementing the weight while
walking on a part of the graph that is single strand of pearls,
or doing the "count the reachable ones the hard way" using
compute_weight() when we hit a merge commit.

A traversal ends when the computed weight is falling or halfway.
This way, commits with too high weight to be relevant are never
visited (and their weights are never computed).

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---

Notes:
    I rephrased the commit message.
    
    I renamed the functions such that they don't talk about "BFS"
    because that is irrelevant. Also use a DFS now because it is
    less code (and a little more efficient).
    
    I plugged some leaks.

 bisect.c | 250 +++++++++++++++++++++++++++++++++++++++++----------------------
 1 file changed, 162 insertions(+), 88 deletions(-)

diff --git a/bisect.c b/bisect.c
index c6bad43..9487ba9 100644
--- a/bisect.c
+++ b/bisect.c
@@ -30,6 +30,9 @@ static unsigned marker;
 struct node_data {
 	int weight;
 	unsigned marked;
+	unsigned parents;
+	unsigned visited : 1;
+	struct commit_list *children;
 };
 
 #define DEBUG_BISECT 0
@@ -110,16 +113,6 @@ static int count_interesting_parents(struct commit *commit)
 	return count;
 }
 
-static inline int halfway(struct commit *commit)
-{
-	/*
-	 * Don't short-cut something we are not going to return!
-	 */
-	if (commit->object.flags & TREESAME)
-		return 0;
-	return !distance_direction(commit);
-}
-
 #if !DEBUG_BISECT
 #define show_list(a,b,c) do { ; } while (0)
 #else
@@ -340,90 +333,168 @@ static void compute_all_weights(struct commit_list *list,
 	show_list("bisection 2 counted all", counted, list);
 }
 
-/* At the moment this is basically the same as compute_all_weights()
- * but with a halfway shortcut */
+static struct commit_list *build_reversed_dag(struct commit_list *list,
+					      struct node_data *nodes)
+{
+	struct commit_list *sources = NULL;
+	struct commit_list *p;
+	int n = 0;
+	for (p = list; p; p = p->next)
+		p->item->util = &nodes[n++];
+	for (p = list; p; p = p->next) {
+		struct commit_list *parent;
+		struct commit *commit = p->item;
+		for (parent = commit->parents; parent; parent = parent->next) {
+			if (!(parent->item->object.flags & UNINTERESTING)) {
+				struct commit_list **next = &node_data(parent->item)->children;
+				commit_list_insert(commit, next);
+				node_data(commit)->parents++;
+			}
+		}
+	}
+
+	/* find all sources */
+	for (p = list; p; p = p->next) {
+		if (node_data(p->item)->parents == 0)
+			commit_list_insert(p->item, &sources);
+	}
+
+	return sources;
+}
+
+static inline void commit_list_insert_unique(struct commit *item,
+				      struct commit_list **list)
+{
+	if (!*list || item < (*list)->item) /* empty list or item will be first */
+		commit_list_insert(item, list);
+	else if (item != (*list)->item) { /* item will not be first or not inserted */
+		struct commit_list *p = *list;
+		for (; p->next && p->next->item < item; p = p->next);
+		if (!p->next || item != p->next->item) /* not already inserted */
+			commit_list_insert(item, &p->next);
+	}
+}
+
+/* do a traversal on the reversed DAG (starting from commits in queue),
+ * but stop at merge commits */
+static void traversal_up_to_merges(struct commit_list *queue,
+				   struct commit_list **merges)
+{
+	assert(queue);
+	while (queue) {
+		struct commit *top = queue->item;
+		struct commit_list *p;
+
+		pop_commit(&queue);
+
+		if (distance_direction(top) > 0) {
+			node_data(top)->visited = 1;
+
+			/* queue children */
+			for (p = node_data(top)->children; p; p = p->next) {
+				if (node_data(p->item)->parents > 1) /* child is a merge */
+					commit_list_insert_unique(p->item, merges);
+				else {
+					node_data(p->item)->weight = node_data(top)->weight;
+					if (!(p->item->object.flags & TREESAME))
+						node_data(p->item)->weight++;
+					commit_list_insert(p->item, &queue);
+				}
+			}
+		}
+	}
+}
+
+static inline int all_parents_are_visited(struct commit *merge)
+{
+	struct commit_list *p;
+	for (p = merge->parents; p; p = p->next) {
+		if (p->item->util && !node_data(p->item)->visited)
+			return 0;
+	}
+	return 1;
+}
+
+static struct commit *extract_merge_to_queue(struct commit_list **merges)
+{
+	assert(merges);
+
+	struct commit_list *p, *q;
+	struct commit *found;
+
+	/* find a merge that is ready, i.e. all parents have been computed */
+	for (q = NULL, p = *merges; p && !all_parents_are_visited(p->item);
+	     q = p, p = p->next);
+	if (!p)
+		return NULL;
+
+	/* remove that commit from the list and return it */
+	if (q) {
+		assert(q->next == p);
+		q->next = p->next;
+	} else /* found first element of list */
+		*merges = p->next;
+	found = p->item;
+	free(p);
+
+	return found;
+}
+
+static inline int find_new_queue_from_merges(struct commit_list **queue,
+				      struct commit_list **merges)
+{
+	if (*merges) {
+		struct commit *merge = extract_merge_to_queue(merges);
+		*queue = NULL;
+		if (merge) {
+			commit_list_insert(merge, queue);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static inline void compute_merge_weights(struct commit_list *merges)
+{
+	struct commit_list *p;
+	for (p = merges; p; p = p->next)
+		compute_weight(p->item);
+}
+
+static void bottom_up_traversal(struct commit_list *queue)
+{
+	struct commit_list *merges = NULL;
+	traversal_up_to_merges(queue, &merges);
+	while (find_new_queue_from_merges(&queue, &merges)) {
+		compute_merge_weights(queue);
+		traversal_up_to_merges(queue, &merges);
+	}
+
+	/* cleanup */
+	free_commit_list(merges);
+}
+
+/* The idea is to reverse the DAG and perform a modified breadth-first search
+ * on it, starting on all sources of the reversed DAG.
+ * Before each visit of a commit, its weight is induced.
+ * This only works for non-merge commits, so the traversal stops prematurely on
+ * merge commits (that are collected in a list).
+ * Merge commits from that collection are considered for further visits
+ * as soon as all parents have been visited.
+ * Their weights are computed using compute_weight().
+ * Each traversal ends when the computed weight is falling or halfway.
+ */
 static void compute_relevant_weights(struct commit_list *list,
 				     struct node_data *weights)
 {
-	int n, counted;
 	struct commit_list *p;
+	struct commit_list *sources = build_reversed_dag(list, weights);
 
-	counted = 0;
+	for (p = sources; p; p = p->next)
+		node_data(p->item)->weight = 1;
+	bottom_up_traversal(sources);
 
-	for (n = 0, p = list; p; p = p->next) {
-		struct commit *commit = p->item;
-		unsigned flags = commit->object.flags;
-
-		commit->util = &weights[n++];
-		switch (count_interesting_parents(commit)) {
-		case 0:
-			if (!(flags & TREESAME)) {
-				node_data(commit)->weight = 1;
-				counted++;
-				show_list("bisection 2 count one",
-					  counted, list);
-			}
-			break;
-		case 1:
-			node_data(commit)->weight = -1;
-			break;
-		default:
-			node_data(commit)->weight = -2;
-			break;
-		}
-	}
-
-	show_list("bisection 2 initialize", counted, list);
-
-	for (p = list; p; p = p->next) {
-		if (!(p->item->object.flags & UNINTERESTING)
-		 && (node_data(p->item)->weight == -2)) {
-			compute_weight(p->item);
-
-			/* Does it happen to be at exactly half-way? */
-			if (halfway(p->item))
-				return;
-			counted++;
-		}
-	}
-
-	show_list("bisection 2 compute_weight", counted, list);
-
-	while (counted < total) {
-		for (p = list; p; p = p->next) {
-			struct commit_list *q;
-			unsigned flags = p->item->object.flags;
-
-			if (0 <= node_data(p->item)->weight)
-				continue;
-			for (q = p->item->parents; q; q = q->next) {
-				if (q->item->object.flags & UNINTERESTING)
-					continue;
-				if (0 <= node_data(q->item)->weight)
-					break;
-			}
-			if (!q)
-				continue;
-
-			/*
-			 * weight for p is unknown but q is known.
-			 * add one for p itself if p is to be counted,
-			 * otherwise inherit it from q directly.
-			 */
-			node_data(p->item)->weight = node_data(q->item)->weight;
-			if (!(flags & TREESAME)) {
-				node_data(p->item)->weight++;
-				counted++;
-				show_list("bisection 2 count one",
-					  counted, list);
-			}
-
-			/* Does it happen to be at exactly half-way? */
-			if (halfway(p->item))
-				return;
-		}
-	}
-	show_list("bisection 2 counted all", counted, list);
+	show_list("bisection 3 result", total, list);
 }
 
 struct commit_list *find_bisection(struct commit_list *list,
@@ -470,8 +541,11 @@ struct commit_list *find_bisection(struct commit_list *list,
 		compute_all_weights(list, weights);
 		best = best_bisection_sorted(list);
 	} else {
+		int i;
 		compute_relevant_weights(list, weights);
 		best = best_bisection(list);
+		for (i = 0; i < on_list; i++) /* cleanup */
+			free_commit_list(weights[i].children);
 	}
 	assert(best);
 	*reaches = node_data(best->item)->weight;
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 20/21] bisect: compute best bisection in compute_relevant_weights()
  2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
                   ` (18 preceding siblings ...)
  2016-04-10 13:19 ` [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights Stephan Beyer
@ 2016-04-10 13:24 ` Stephan Beyer
  2016-04-10 13:24   ` [PATCH v2 21/21] bisect: get back halfway shortcut Stephan Beyer
  19 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:24 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

This commit gets rid of the O(#commits) extra overhead of
the best_bisection() function.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---

Notes:
    I made the best_bisection structure be allocated on the heap
    because it will get free()d when the code is invoked by
    git rev-list --bisect ... The old code crashed in this case.

 bisect.c | 44 +++++++++++++++++++-------------------------
 1 file changed, 19 insertions(+), 25 deletions(-)

diff --git a/bisect.c b/bisect.c
index 9487ba9..9f51d73 100644
--- a/bisect.c
+++ b/bisect.c
@@ -27,6 +27,8 @@ static int total;
 
 static unsigned marker;
 
+static struct commit_list *best_bisection;
+
 struct node_data {
 	int weight;
 	unsigned marked;
@@ -73,6 +75,14 @@ static inline int distance_direction(struct commit *commit)
 	return 0;
 }
 
+static inline void update_best_bisection(struct commit *commit)
+{
+	if (distance_direction(commit) >= 0
+	 && node_data(commit)->weight > node_data(best_bisection->item)->weight) {
+		best_bisection->item = commit;
+	}
+}
+
 static int compute_weight(struct commit *elem)
 {
 	int nr = 0;
@@ -153,29 +163,6 @@ static void show_list(const char *debug, int counted,
 }
 #endif /* DEBUG_BISECT */
 
-static struct commit_list *best_bisection(struct commit_list *list)
-{
-	struct commit_list *p, *best;
-	int best_distance = -1;
-
-	best = list;
-	for (p = list; p; p = p->next) {
-		int distance;
-		unsigned flags = p->item->object.flags;
-
-		if (flags & TREESAME)
-			continue;
-		distance = get_distance(p->item);
-		if (distance > best_distance) {
-			best = p;
-			best_distance = distance;
-		}
-	}
-
-	best->next = NULL;
-	return best;
-}
-
 struct commit_dist {
 	struct commit *commit;
 	int distance;
@@ -402,6 +389,8 @@ static void traversal_up_to_merges(struct commit_list *queue,
 				}
 			}
 		}
+
+		update_best_bisection(top);
 	}
 }
 
@@ -457,8 +446,10 @@ static inline int find_new_queue_from_merges(struct commit_list **queue,
 static inline void compute_merge_weights(struct commit_list *merges)
 {
 	struct commit_list *p;
-	for (p = merges; p; p = p->next)
+	for (p = merges; p; p = p->next) {
 		compute_weight(p->item);
+		update_best_bisection(p->item);
+	}
 }
 
 static void bottom_up_traversal(struct commit_list *queue)
@@ -490,6 +481,9 @@ static void compute_relevant_weights(struct commit_list *list,
 	struct commit_list *p;
 	struct commit_list *sources = build_reversed_dag(list, weights);
 
+	best_bisection = (struct commit_list *)xcalloc(1, sizeof(*best_bisection));
+	best_bisection->item = sources->item;
+
 	for (p = sources; p; p = p->next)
 		node_data(p->item)->weight = 1;
 	bottom_up_traversal(sources);
@@ -543,7 +537,7 @@ struct commit_list *find_bisection(struct commit_list *list,
 	} else {
 		int i;
 		compute_relevant_weights(list, weights);
-		best = best_bisection(list);
+		best = best_bisection;
 		for (i = 0; i < on_list; i++) /* cleanup */
 			free_commit_list(weights[i].children);
 	}
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH v2 21/21] bisect: get back halfway shortcut
  2016-04-10 13:24 ` [PATCH v2 20/21] bisect: compute best bisection in compute_relevant_weights() Stephan Beyer
@ 2016-04-10 13:24   ` Stephan Beyer
  2016-04-15 22:53     ` Junio C Hamano
  0 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 13:24 UTC (permalink / raw)
  To: git; +Cc: Stephan Beyer, Christian Couder, Junio C Hamano

The documentation says that when the maximum possible distance
is found, the algorithm stops immediately. That feature is
reestablished by this commit.

Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
---

Notes:
    I plugged a memory leak here.

 bisect.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/bisect.c b/bisect.c
index 9f51d73..e583852 100644
--- a/bisect.c
+++ b/bisect.c
@@ -364,8 +364,8 @@ static inline void commit_list_insert_unique(struct commit *item,
 
 /* do a traversal on the reversed DAG (starting from commits in queue),
  * but stop at merge commits */
-static void traversal_up_to_merges(struct commit_list *queue,
-				   struct commit_list **merges)
+static int traversal_up_to_merges(struct commit_list *queue,
+				  struct commit_list **merges)
 {
 	assert(queue);
 	while (queue) {
@@ -391,7 +391,13 @@ static void traversal_up_to_merges(struct commit_list *queue,
 		}
 
 		update_best_bisection(top);
+		if (distance_direction(top) == 0) { // halfway
+			assert(!(top->object.flags & TREESAME));
+			free_commit_list(queue);
+			return 1;
+		}
 	}
+	return 0;
 }
 
 static inline int all_parents_are_visited(struct commit *merge)
@@ -455,10 +461,12 @@ static inline void compute_merge_weights(struct commit_list *merges)
 static void bottom_up_traversal(struct commit_list *queue)
 {
 	struct commit_list *merges = NULL;
-	traversal_up_to_merges(queue, &merges);
-	while (find_new_queue_from_merges(&queue, &merges)) {
+	int halfway_found = traversal_up_to_merges(queue, &merges);
+
+	while (!halfway_found
+	    && find_new_queue_from_merges(&queue, &merges)) {
 		compute_merge_weights(queue);
-		traversal_up_to_merges(queue, &merges);
+		halfway_found &= traversal_up_to_merges(queue, &merges);
 	}
 
 	/* cleanup */
-- 
2.8.1.137.g522756c

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 05/21] t6030: generalize test to not rely on current implementation
  2016-04-10 13:18 ` [PATCH v2 05/21] t6030: generalize test to not rely on current implementation Stephan Beyer
@ 2016-04-10 13:47   ` Torsten Bögershausen
  2016-04-10 19:16     ` Junio C Hamano
  2016-04-11  0:23     ` Eric Sunshine
  2016-04-15 21:07   ` Junio C Hamano
  1 sibling, 2 replies; 56+ messages in thread
From: Torsten Bögershausen @ 2016-04-10 13:47 UTC (permalink / raw)
  To: Stephan Beyer, git; +Cc: Christian Couder, Junio C Hamano

On 10.04.16 15:18, Stephan Beyer wrote:
Some nit-comments inline

> ---
>  t/t6030-bisect-porcelain.sh | 167 ++++++++++++++++++++++----------------------
>  1 file changed, 85 insertions(+), 82 deletions(-)
> 
> diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
> index 05bc639..645ccd9 100755
> --- a/t/t6030-bisect-porcelain.sh
> +++ b/t/t6030-bisect-porcelain.sh
> @@ -10,36 +10,34 @@ exec </dev/null
> +	if [ -f "$_file" ]; then
I know that the old code did the same, is there a chance
to adopt to the git-style:
	if test -f "$_file" ; then
> +		echo "$_line" >> $_file || return $?

[]
> +test_expect_success '"git bisect run" simple case' '
> +	echo "#"\!"/bin/sh" > test_script.sh &&
> +	echo "grep Another hello > /dev/null" >> test_script.sh &&
> +	echo "test \$? -ne 0" >> test_script.sh &&
> +	chmod +x test_script.sh &&
> +	git bisect start &&
> +	git bisect good $HASH1 &&
> +	git bisect bad $HASH4 &&
> +	git bisect run ./test_script.sh > my_bisect_log.txt &&
> +	grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
> +	git bisect reset
> +'
Portabily:
Since yesterday/yesterweek the usage of hard-coded
#!/bin/sh had shown to be problematic
Junio posted an update like this:
-	printf "#!/bin/sh\n" >diff &&
-	printf "printf \"\$GIT_PREFIX\"" >>diff &&
-	chmod +x diff &&
+	write_script diff <<-\EOF &&
+	printf "%s" "$GIT_PREFIX"
+	EOF

(Same for the scripts below)

>
> +test_expect_success '"git bisect run" with more complex "git bisect start"' '
> +	echo "#"\!"/bin/sh" > test_script.sh &&
> +	echo "grep Ciao hello > /dev/null" >> test_script.sh &&
> +	echo "test \$? -ne 0" >> test_script.sh &&
Style nit, please no ' ' after ">>":
echo "test \$? -ne 0" >>test_script.sh &&

(and more below)

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 05/21] t6030: generalize test to not rely on current implementation
  2016-04-10 13:47   ` Torsten Bögershausen
@ 2016-04-10 19:16     ` Junio C Hamano
  2016-04-10 19:37       ` Stephan Beyer
  2016-04-11  0:23     ` Eric Sunshine
  1 sibling, 1 reply; 56+ messages in thread
From: Junio C Hamano @ 2016-04-10 19:16 UTC (permalink / raw)
  To: Torsten Bögershausen; +Cc: Stephan Beyer, git, Christian Couder

Torsten Bögershausen <tboegi@web.de> writes:

> Portabily:
> Since yesterday/yesterweek the usage of hard-coded
> #!/bin/sh had shown to be problematic

That is not a new revelation, though ;-) It is just that these are
problematic to those on minority platforms, and by definition they
are noticed only when a very few people on minority platforms
happened to have run tests.

Thanks for keeping an eye on patches in flight to prevent new
instances of this issue from getting added.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 05/21] t6030: generalize test to not rely on current implementation
  2016-04-10 19:16     ` Junio C Hamano
@ 2016-04-10 19:37       ` Stephan Beyer
  0 siblings, 0 replies; 56+ messages in thread
From: Stephan Beyer @ 2016-04-10 19:37 UTC (permalink / raw)
  To: Junio C Hamano, Torsten Bögershausen; +Cc: git, Christian Couder

On 04/10/2016 09:16 PM, Junio C Hamano wrote:
> Torsten Bögershausen <tboegi@web.de> writes:
> 
>> Portabily:
>> Since yesterday/yesterweek the usage of hard-coded
>> #!/bin/sh had shown to be problematic
> 
> That is not a new revelation, though ;-) It is just that these are
> problematic to those on minority platforms, and by definition they
> are noticed only when a very few people on minority platforms
> happened to have run tests.
> 
> Thanks for keeping an eye on patches in flight to prevent new
> instances of this issue from getting added.

Although it's not getting added but only re-indented ;)
[I was not sure if this is a good idea at all to include a re-indent as
a while-at-it in a commit. Maybe it was a good idea so that I am now
obliged to "fix" it ;)]

Cheers,
Stephan

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/21] t: use test_cmp_rev() where appropriate
  2016-04-10 13:18 ` [PATCH v2 04/21] t: use test_cmp_rev() where appropriate Stephan Beyer
@ 2016-04-11  0:07   ` Eric Sunshine
  2016-04-15 20:48   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2016-04-11  0:07 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: Git List, Christian Couder, Junio C Hamano

On Sun, Apr 10, 2016 at 9:18 AM, Stephan Beyer <s-beyer@gmx.net> wrote:
> test_cmp_rev() from t/test-lib-functions.sh is used to make many
> tests clearer.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
> diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
> @@ -537,8 +537,8 @@ EOF
>         test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
>         test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
>         # Refs are unchanged
> -       test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
> -       test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
> +       test_cmp_rev refs/notes/m refs/notes/w &&
> +       test_cmp_rev refs/notes/y NOTES_MERGE_PARTIAL^1 &&
>         test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&

Does this one deserve the same type of treatment (but prefixed with !)?

    ! test_cmp_rev refs/notes/m NOTES_MERGE_PARTIAL^1

I could understand avoiding this conversion if temp_cmp_rev was still
noisy upon failure, however patch 3/21 silenced it.

Aside: Would make sense for test_cmp_rev to accept an optional ! as
its first argument to signify that the revs should not match?

>         # Mention refs/notes/m, and its current and expected value in output
>         grep -q "refs/notes/m" output &&
> diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
> @@ -354,8 +354,8 @@ test_expect_success '--remap-to-ancestor with filename filters' '
> -       test $(git rev-parse moved-foo) = $(git rev-parse moved-bar) &&
> -       test $(git rev-parse moved-foo) = $(git rev-parse master^) &&
> +       test_cmp_rev moved-foo moved-bar &&
> +       test_cmp_rev moved-foo master^ &&
>         test $orig_invariant = $(git rev-parse invariant)

At other places in this patch, you also converted lines such as the
above, so why not here?

    test_cmp_rev invariant $orig_invariant

>  '
>
> @@ -372,8 +372,8 @@ test_expect_success 'automatic remapping to ancestor with filename filters' '
> -       test $(git rev-parse moved-foo2) = $(git rev-parse moved-bar2) &&
> -       test $(git rev-parse moved-foo2) = $(git rev-parse master^) &&
> +       test_cmp_rev moved-foo2 moved-bar2 &&
> +       test_cmp_rev moved-foo2 master^ &&
>         test $orig_invariant = $(git rev-parse invariant2)

Ditto.

>  '
>
> diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
> @@ -79,17 +79,16 @@ test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
>  test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
>         git reset --hard c1 &&
>         git config pull.octopus "recursive octopus" &&
>         git merge c2 c3 &&
> -       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> -       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> -       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> -       test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
> +       test_cmp_rev c1 HEAD^1 &&
> +       test_cmp_rev c2 HEAD^2 &&
> +       test_cmp_rev c3 HEAD^3 &&

This drops the check:

    ! test_cmp_rev c1 HEAD &&

Is that intentional? I suppose the argument is that if c1 == HEAD^1
passes, then c1 is certainly not == HEAD?

> diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
> @@ -46,11 +46,10 @@ test_expect_success 'setup' '
>  test_expect_success 'merge c1 with c2, c3, c4, c5' '
>         git reset --hard c1 &&
>         git merge c2 c3 c4 c5 &&
> -       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> -       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> -       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> -       test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
> -       test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
> +       test_cmp_rev c1 HEAD^1 &&
> +       test_cmp_rev c2 HEAD^2 &&
> +       test_cmp_rev c3 HEAD^3 &&
> +       test_cmp_rev c5 HEAD^4 &&

Ditto.

> @@ -69,11 +68,10 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '
>  test_expect_success 'pull c2, c3, c4, c5 into c1' '
>         git reset --hard c1 &&
>         git pull . c2 c3 c4 c5 &&
> -       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> -       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> -       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> -       test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
> -       test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
> +       test_cmp_rev c1 HEAD^1 &&
> +       test_cmp_rev c2 HEAD^2 &&
> +       test_cmp_rev c3 HEAD^3 &&
> +       test_cmp_rev c5 HEAD^4 &&

Ditto.

> diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh
> @@ -30,9 +30,8 @@ test_expect_success 'setup' '
>  test_expect_success 'merge c1 to c2' '
>         git reset --hard c1 &&
>         git merge -s resolve c2 &&
> -       test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> -       test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> -       test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> +       test_cmp_rev c1 HEAD^1 &&
> +       test_cmp_rev c2 HEAD^2 &&

Ditto.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev
  2016-04-10 13:18 ` [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev Stephan Beyer
@ 2016-04-11  0:07   ` Eric Sunshine
  2016-04-15 20:00   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2016-04-11  0:07 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: Git List, Christian Couder, Junio C Hamano

On Sun, Apr 10, 2016 at 9:18 AM, Stephan Beyer <s-beyer@gmx.net> wrote:
> test_cmp_rev() took exactly two parameters, the expected revision
> and the revision to test. This commit generalizes this function
> such that it takes any number of at least two revisions: the
> expected one and a list of actual ones. The function returns true
> if and only if at least one actual revision coincides with the
> expected revision.
>
> While at it, the side effect of generating two (temporary) files
> is removed.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
> diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
> @@ -711,11 +711,17 @@ test_must_be_empty () {
> -# Tests that its two parameters refer to the same revision
> +# Tests that the first parameter refers to the same revision
> +# of at least one other parameter
>  test_cmp_rev () {
> -       git rev-parse --verify "$1" >expect.rev &&
> -       git rev-parse --verify "$2" >actual.rev &&
> -       test_cmp expect.rev actual.rev
> +       hash1="$(git rev-parse --verify "$1")" || return
> +       shift
> +       for rev
> +       do
> +               hash2="$(git rev-parse --verify "$rev")" || return
> +               test "$hash1" = "$hash2" && return 0
> +       done
> +       return 1
>  }

The original code printed helpful diagnostic information when the
comparison failed, but the new code does not. Is this intentional?

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 05/21] t6030: generalize test to not rely on current implementation
  2016-04-10 13:47   ` Torsten Bögershausen
  2016-04-10 19:16     ` Junio C Hamano
@ 2016-04-11  0:23     ` Eric Sunshine
  1 sibling, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2016-04-11  0:23 UTC (permalink / raw)
  To: Torsten Bögershausen
  Cc: Stephan Beyer, Git List, Christian Couder, Junio C Hamano

On Sun, Apr 10, 2016 at 9:47 AM, Torsten Bögershausen <tboegi@web.de> wrote:
> On 10.04.16 15:18, Stephan Beyer wrote:
>> diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
>> @@ -10,36 +10,34 @@ exec </dev/null
>> +     if [ -f "$_file" ]; then
> I know that the old code did the same, is there a chance
> to adopt to the git-style:
>         if test -f "$_file" ; then

Hmm, isn't the preferred style?

    if test -f "$_file"
    then

>> +test_expect_success '"git bisect run" simple case' '
>> +     echo "#"\!"/bin/sh" > test_script.sh &&
>> +     echo "grep Another hello > /dev/null" >> test_script.sh &&
>> +     echo "test \$? -ne 0" >> test_script.sh &&
>> +     chmod +x test_script.sh &&
>> +     git bisect start &&
>> +     git bisect good $HASH1 &&
>> +     git bisect bad $HASH4 &&
>> +     git bisect run ./test_script.sh > my_bisect_log.txt &&
>> +     grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
>> +     git bisect reset
>> +'
> Portabily:
> Since yesterday/yesterweek the usage of hard-coded
> #!/bin/sh had shown to be problematic
> Junio posted an update like this:
> -       printf "#!/bin/sh\n" >diff &&
> -       printf "printf \"\$GIT_PREFIX\"" >>diff &&
> -       chmod +x diff &&
> +       write_script diff <<-\EOF &&
> +       printf "%s" "$GIT_PREFIX"
> +       EOF

It might be nice to have these style fixes and modernizations as a
preparatory cleanup patch.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 11/21] bisect: use struct node_data array instead of int array
  2016-04-10 13:19 ` [PATCH v2 11/21] bisect: use struct node_data array instead of int array Stephan Beyer
@ 2016-04-12 23:02   ` Christian Couder
  2016-04-15 21:47   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Christian Couder @ 2016-04-12 23:02 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Junio C Hamano

On Sun, Apr 10, 2016 at 3:19 PM, Stephan Beyer <s-beyer@gmx.net> wrote:
>
> @@ -321,14 +321,13 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>                          * add one for p itself if p is to be counted,
>                          * otherwise inherit it from q directly.
>                          */
> +                       node_data(p->item)->weight = node_data(q->item)->weight;
>                         if (!(flags & TREESAME)) {
> -                               weight_set(p, weight(q)+1);
> +                               node_data(p->item)->weight++;

It is not so easy to see that the above does the same thing as before.

Maybe review would be easier if this part of the code was simplified
in a separate patch and/or if the weight() and weight_set() function
were kept, maybe like this:

static inline int weight(struct commit *elem)
{
       return node_data(elem)->weight;
}

static inline void set_weight(struct commit *elem, int weight)
{
       node_data(elem)->weight = weight;
}

>                                 counted++;
>                                 show_list("bisection 2 count one",
>                                           counted, nr, list);
>                         }
> -                       else
> -                               weight_set(p, weight(q));
>
>                         /* Does it happen to be at exactly half-way? */
>                         if (!find_all && halfway(p, nr))

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 12/21] bisect: replace clear_distance() by unique markers
  2016-04-10 13:19 ` [PATCH v2 12/21] bisect: replace clear_distance() by unique markers Stephan Beyer
@ 2016-04-12 23:20   ` Christian Couder
  2016-04-15 22:07   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Christian Couder @ 2016-04-12 23:20 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Junio C Hamano

On Sun, Apr 10, 2016 at 3:19 PM, Stephan Beyer <s-beyer@gmx.net> wrote:
>
> @@ -123,10 +116,9 @@ static void show_list(const char *debug, int counted, int nr,
>                 const char *subject_start;
>                 int subject_len;
>
> -               fprintf(stderr, "%c%c%c ",
> +               fprintf(stderr, "%c%c ",
>                         (flags & TREESAME) ? ' ' : 'T',
> -                       (flags & UNINTERESTING) ? 'U' : ' ',
> -                       (flags & COUNTED) ? 'C' : ' ');
> +                       (flags & UNINTERESTING) ? 'U' : ' ');

Maybe node_data(commit)->marked could be printed instead of  'C' or ' '.

>                 if (commit->util)
>                         fprintf(stderr, "%3d", node_data(commit)->weight);
>                 else

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 16/21] bisect: make total number of commits global
  2016-04-10 13:19 ` [PATCH v2 16/21] bisect: make total number of commits global Stephan Beyer
@ 2016-04-13 13:23   ` Christian Couder
  2016-04-15 22:11   ` Junio C Hamano
  2016-04-16  0:44   ` Junio C Hamano
  2 siblings, 0 replies; 56+ messages in thread
From: Christian Couder @ 2016-04-13 13:23 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Junio C Hamano

On Sun, Apr 10, 2016 at 3:19 PM, Stephan Beyer <s-beyer@gmx.net> wrote:
> The total number of commits in a bisect process is a property of
> the bisect process. Making this property global helps to make the code
> clearer.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  bisect.c | 74 ++++++++++++++++++++++++++++++++++------------------------------
>  1 file changed, 39 insertions(+), 35 deletions(-)
>
> diff --git a/bisect.c b/bisect.c
> index f737ce7..2b415ad 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -23,6 +23,8 @@ static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
>  static const char *term_bad;
>  static const char *term_good;
>
> +static int total;
> +
>  static unsigned marker;
>
>  struct node_data {
> @@ -38,7 +40,7 @@ static inline struct node_data *node_data(struct commit *elem)
>         return (struct node_data *)elem->util;
>  }
>
> -static inline int get_distance(struct commit *commit, int total)
> +static inline int get_distance(struct commit *commit)
>  {
>         int distance = node_data(commit)->weight;
>         if (total - distance < distance)
> @@ -54,7 +56,7 @@ static inline int get_distance(struct commit *commit, int total)
>   * Return 0 if the distance is halfway.
>   * Return 1 if the distance is rising.
>   */
> -static inline int distance_direction(struct commit *commit, int total)
> +static inline int distance_direction(struct commit *commit)
>  {
>         int doubled_diff = 2 * node_data(commit)->weight - total;
>         if (doubled_diff < -1)
> @@ -107,25 +109,25 @@ static int count_interesting_parents(struct commit *commit)
>         return count;
>  }
>
> -static inline int halfway(struct commit *commit, int nr)
> +static inline int halfway(struct commit *commit)
>  {
>         /*
>          * Don't short-cut something we are not going to return!
>          */
>         if (commit->object.flags & TREESAME)
>                 return 0;
> -       return !distance_direction(commit, nr);
> +       return !distance_direction(commit);
>  }
>
>  #if !DEBUG_BISECT
> -#define show_list(a,b,c,d) do { ; } while (0)
> +#define show_list(a,b,c) do { ; } while (0)
>  #else
> -static void show_list(const char *debug, int counted, int nr,
> +static void show_list(const char *debug, int counted,
>                       struct commit_list *list)
>  {
>         struct commit_list *p;
>
> -       fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
> +       fprintf(stderr, "%s (%d/%d)\n", debug, counted, total);
>
>         for (p = list; p; p = p->next) {
>                 struct commit_list *pp;
> @@ -157,7 +159,7 @@ static void show_list(const char *debug, int counted, int nr,
>  }
>  #endif /* DEBUG_BISECT */
>
> -static struct commit_list *best_bisection(struct commit_list *list, int nr)
> +static struct commit_list *best_bisection(struct commit_list *list)
>  {
>         struct commit_list *p, *best;
>         int best_distance = -1;
> @@ -169,7 +171,7 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
>
>                 if (flags & TREESAME)
>                         continue;
> -               distance = get_distance(p->item, nr);
> +               distance = get_distance(p->item);
>                 if (distance > best_distance) {
>                         best = p;
>                         best_distance = distance;
> @@ -195,10 +197,10 @@ static int compare_commit_dist(const void *a_, const void *b_)
>         return oidcmp(&a->commit->object.oid, &b->commit->object.oid);
>  }
>
> -static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
> +static struct commit_list *best_bisection_sorted(struct commit_list *list)
>  {
>         struct commit_list *p;
> -       struct commit_dist *array = xcalloc(nr, sizeof(*array));
> +       struct commit_dist *array = xcalloc(total, sizeof(*array));
>         int cnt, i;
>
>         for (p = list, cnt = 0; p; p = p->next) {
> @@ -207,7 +209,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
>
>                 if (flags & TREESAME)
>                         continue;
> -               distance = get_distance(p->item, nr);
> +               distance = get_distance(p->item);
>                 array[cnt].commit = p->item;
>                 array[cnt].distance = distance;
>                 cnt++;
> @@ -243,7 +245,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
>   * or positive distance.
>   */
>  static struct commit_list *do_find_bisection(struct commit_list *list,
> -                                            int nr, struct node_data *weights,
> +                                            struct node_data *weights,
>                                              int find_all)
>  {
>         int n, counted;
> @@ -262,7 +264,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>                                 node_data(commit)->weight = 1;
>                                 counted++;
>                                 show_list("bisection 2 count one",
> -                                         counted, nr, list);
> +                                         counted, list);
>                         }
>                         /*
>                          * otherwise, it is known not to reach any
> @@ -278,7 +280,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>                 }
>         }
>
> -       show_list("bisection 2 initialize", counted, nr, list);
> +       show_list("bisection 2 initialize", counted, list);
>
>         /*
>          * If you have only one parent in the resulting set
> @@ -300,15 +302,15 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>                         node_data(p->item)->weight = count_distance(p->item);
>
>                         /* Does it happen to be at exactly half-way? */
> -                       if (!find_all && halfway(p->item, nr))
> +                       if (!find_all && halfway(p->item))
>                                 return p;
>                         counted++;
>                 }
>         }
>
> -       show_list("bisection 2 count_distance", counted, nr, list);
> +       show_list("bisection 2 count_distance", counted, list);
>
> -       while (counted < nr) {
> +       while (counted < total) {
>                 for (p = list; p; p = p->next) {
>                         struct commit_list *q;
>                         unsigned flags = p->item->object.flags;
> @@ -334,40 +336,41 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>                                 node_data(p->item)->weight++;
>                                 counted++;
>                                 show_list("bisection 2 count one",
> -                                         counted, nr, list);
> +                                         counted, list);
>                         }
>
>                         /* Does it happen to be at exactly half-way? */
> -                       if (!find_all && halfway(p->item, nr))
> +                       if (!find_all && halfway(p->item))
>                                 return p;
>                 }
>         }
>
> -       show_list("bisection 2 counted all", counted, nr, list);
> +       show_list("bisection 2 counted all", counted, list);
>
>         if (!find_all)
> -               return best_bisection(list, nr);
> +               return best_bisection(list);
>         else
> -               return best_bisection_sorted(list, nr);
> +               return best_bisection_sorted(list);
>  }
>
>  struct commit_list *find_bisection(struct commit_list *list,
>                                           int *reaches, int *all,
>                                           int find_all)

If you really want to use a global variable, then you should probably
have removed the "int *all" argument too...

>  {
> -       int nr, on_list;
> +       int on_list;
>         struct commit_list *p, *best, *next, *last;
>         struct node_data *weights;
>
> +       total = 0;
>         marker = 0;
>
> -       show_list("bisection 2 entry", 0, 0, list);
> +       show_list("bisection 2 entry", 0, list);
>
>         /*
>          * Count the number of total and tree-changing items on the
>          * list, while reversing the list.
>          */
> -       for (nr = on_list = 0, last = NULL, p = list;
> +       for (on_list = 0, last = NULL, p = list;
>              p;
>              p = next) {
>                 unsigned flags = p->item->object.flags;
> @@ -378,23 +381,24 @@ struct commit_list *find_bisection(struct commit_list *list,
>                 p->next = last;
>                 last = p;
>                 if (!(flags & TREESAME))
> -                       nr++;
> +                       total++;
>                 on_list++;
>         }
>         list = last;
> -       show_list("bisection 2 sorted", 0, nr, list);
> +       show_list("bisection 2 sorted", 0, list);
>
> -       *all = nr;
> +       *all = total;

... that would remove the above line...

>         weights = (struct node_data *)xcalloc(on_list, sizeof(*weights));
>
>         /* Do the real work of finding bisection commit. */
> -       best = do_find_bisection(list, nr, weights, find_all);
> +       best = do_find_bisection(list, weights, find_all);
>         if (best) {
>                 if (!find_all)
>                         best->next = NULL;
>                 *reaches = node_data(best->item)->weight;
>         }
>         free(weights);
> +
>         return best;
>  }
>
> @@ -931,7 +935,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
>  {
>         struct rev_info revs;
>         struct commit_list *tried;
> -       int reaches = 0, all = 0, nr, steps;
> +       int reaches = 0, nr, steps;
>         const unsigned char *bisect_rev;
>
>         read_bisect_terms(&term_bad, &term_good);
> @@ -945,7 +949,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
>
>         bisect_common(&revs);
>
> -       revs.commits = find_bisection(revs.commits, &reaches, &all,
> +       revs.commits = find_bisection(revs.commits, &reaches, &total,
>                                        !!skipped_revs.nr);

...and simplify the above call.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 17/21] bisect: rename count_distance() to compute_weight()
  2016-04-10 13:19 ` [PATCH v2 17/21] bisect: rename count_distance() to compute_weight() Stephan Beyer
@ 2016-04-13 13:32   ` Christian Couder
  2016-04-15 22:12   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Christian Couder @ 2016-04-13 13:32 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Junio C Hamano

On Sun, Apr 10, 2016 at 3:19 PM, Stephan Beyer <s-beyer@gmx.net> wrote:
>
> @@ -70,7 +70,7 @@ static inline int distance_direction(struct commit *commit)
>         return 0;
>  }
>
> -static int count_distance(struct commit *elem)
> +static int compute_weight(struct commit *elem)
>  {
>         int nr = 0;
>         struct commit_list *todo = NULL;
> @@ -93,6 +93,7 @@ static int count_distance(struct commit *elem)
>                 }
>         }
>
> +       node_data(elem)->weight = nr;
>         return nr;
>  }

As the return value is not used below, I am not sure it is still worth
it to return the weight.

> @@ -241,7 +242,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list)
>   * be computed.
>   *
>   * weight = -2 means it has more than one parent and its distance is
> - * unknown.  After running count_distance() first, they will get zero
> + * unknown.  After running compute_weight() first, they will get zero
>   * or positive distance.
>   */
>  static struct commit_list *do_find_bisection(struct commit_list *list,
> @@ -286,7 +287,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>          * If you have only one parent in the resulting set
>          * then you can reach one commit more than that parent
>          * can reach.  So we do not have to run the expensive
> -        * count_distance() for single strand of pearls.
> +        * compute_weight() for single strand of pearls.
>          *
>          * However, if you have more than one parent, you cannot
>          * just add their distance and one for yourself, since
> @@ -299,7 +300,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>         for (p = list; p; p = p->next) {
>                 if (!(p->item->object.flags & UNINTERESTING)
>                  && (node_data(p->item)->weight == -2)) {
> -                       node_data(p->item)->weight = count_distance(p->item);
> +                       compute_weight(p->item);
>
>                         /* Does it happen to be at exactly half-way? */
>                         if (!find_all && halfway(p->item))

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights
  2016-04-10 13:19 ` [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights Stephan Beyer
@ 2016-04-13 14:11   ` Christian Couder
  2016-04-15 22:47   ` Junio C Hamano
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 56+ messages in thread
From: Christian Couder @ 2016-04-13 14:11 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Junio C Hamano

On Sun, Apr 10, 2016 at 3:19 PM, Stephan Beyer <s-beyer@gmx.net> wrote:
> The idea is to reverse the DAG and perform a traversal
> starting on all sources of the reversed DAG.
>
> We walk from the bottom commits, incrementing the weight while
> walking on a part of the graph that is single strand of pearls,
> or doing the "count the reachable ones the hard way" using
> compute_weight() when we hit a merge commit.
>
> A traversal ends when the computed weight is falling or halfway.

Yeah, it looks like it could be a good optimization to end a traversal
looking for "relevant" commits when the weight is falling.

> This way, commits with too high weight to be relevant are never
> visited (and their weights are never computed).
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>
> Notes:
>     I rephrased the commit message.
>
>     I renamed the functions such that they don't talk about "BFS"
>     because that is irrelevant. Also use a DFS now because it is
>     less code (and a little more efficient).
>
>     I plugged some leaks.

That's a lot of things in just one commit.

>  bisect.c | 250 +++++++++++++++++++++++++++++++++++++++++----------------------
>  1 file changed, 162 insertions(+), 88 deletions(-)

Also from the diff stats it looks like you add a lot of code in this
commit and the previous one.
I wonder why you are saying that a DFS is less code above then.

The previous patch (18/21) has the following diff stat:

> bisect.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
> 1 file changed, 97 insertions(+), 19 deletions(-)

And the subsequent patches don't reduce code size overall.
Diff stat for 20/21 is:

> bisect.c | 44 +++++++++++++++++++-------------------------
> 1 file changed, 19 insertions(+), 25 deletions(-)

And diff stat for 21/21 is:

> bisect.c | 18 +++++++++++++-----
> 1 file changed, 13 insertions(+), 5 deletions(-)

So after your patches from 18/21 to 21/21 there are around 150 more
lines of code.
Maybe this is worth it, but I wonder if at least some optimizations,
like for example ending a traversal looking for "relevant" commits
when the weight is falling, could be implemented without changing the
code so much and adding so many lines.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev
  2016-04-10 13:18 ` [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev Stephan Beyer
  2016-04-11  0:07   ` Eric Sunshine
@ 2016-04-15 20:00   ` Junio C Hamano
  2016-04-24 19:51     ` Stephan Beyer
  1 sibling, 1 reply; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 20:00 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> test_cmp_rev() took exactly two parameters, the expected revision
> and the revision to test. This commit generalizes this function
> such that it takes any number of at least two revisions: the
> expected one and a list of actual ones. The function returns true
> if and only if at least one actual revision coincides with the
> expected revision.

There may be cases where you want to find the expected one among
various things you actually have (which is what the above talks
about; it is like "list-what-I-actually-got | grep what-i-want"),
but an equally useful use case would be "I would get only one
outcome from test, I anticipate one of these things, all of which is
OK, but I cannot dictate which one of them should come out" (it is
like "list-what-I-can-accept | grep what-I-actually-got").

I am not enthused by the new test that implements the "match one
against multi" check only in one way among these possible two to
squat on a very generic name, test_cmp_rev.

The above _may_ appear a non-issue until you realize one thing that
is there to help those who debug the tests, which is ...

> While at it, the side effect of generating two (temporary) files
> is removed.

That is not strictly a side effect.  test_cmp allows you to see what
was expected and what you actually had when the test failed (we
always compare expect with actual and not the other way around, so
that "diff -u expect actual" would show how the actual behaviour
diverted from our expectation in a natural way).

Something with the semantics of these two:

	test_revs_have_expected () {
        	expect=$1
		shift
		git rev-parse "$@" | grep -e "$expect" >/dev/null && return
		echo >&2 "The expected '$1' is not found in:"
                printf >&2 " '%s'\n", "$@"
                return 1
	}

	test_rev_among_expected () {
		actual=$1
                shift
		git rev-parse "$@" | grep -e "$actual" >/dev/null && return
		echo >&2 "'$1' is not among expected ones:"
                printf >&2 " '%s'\n", "$@"
                return 1
	}

might be more appropriate.

>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  t/test-lib-functions.sh | 14 ++++++++++----
>  1 file changed, 10 insertions(+), 4 deletions(-)
>
> diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
> index 8d99eb3..8caf59c 100644
> --- a/t/test-lib-functions.sh
> +++ b/t/test-lib-functions.sh
> @@ -711,11 +711,17 @@ test_must_be_empty () {
>  	fi
>  }
>  
> -# Tests that its two parameters refer to the same revision
> +# Tests that the first parameter refers to the same revision
> +# of at least one other parameter
>  test_cmp_rev () {
> -	git rev-parse --verify "$1" >expect.rev &&
> -	git rev-parse --verify "$2" >actual.rev &&
> -	test_cmp expect.rev actual.rev
> +	hash1="$(git rev-parse --verify "$1")" || return
> +	shift
> +	for rev
> +	do
> +		hash2="$(git rev-parse --verify "$rev")" || return
> +		test "$hash1" = "$hash2" && return 0
> +	done
> +	return 1
>  }
>  
>  # Print a sequence of numbers or letters in increasing order.  This is

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 04/21] t: use test_cmp_rev() where appropriate
  2016-04-10 13:18 ` [PATCH v2 04/21] t: use test_cmp_rev() where appropriate Stephan Beyer
  2016-04-11  0:07   ` Eric Sunshine
@ 2016-04-15 20:48   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 20:48 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> test_cmp_rev() from t/test-lib-functions.sh is used to make many
> tests clearer.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>
> Notes:
>     This change is in some way independent of the bisect topic but
>     the next patch is based on this (for t6030).

It seems that with this step test_cmp_rev is only used to compare
two revisions, not "I want to see this expected one among many"
introduced by 03/21.

And they all make the resulting code look easier to read.

As I said, these tests would become more cumbersome to debug when
they break with this change, though.

Also, with this mass rewrite, it is not sensible to assume that all
of these tests that start calling test_cmp_rev() do not mind having
expect.rev and actual.rev files in their working trees (and it is
also not sensible to assume that they do not mind having hash1 and
hash2 shell variables clobbered), so not using temporary files may
be good, but perhaps something like

	test_cmp_rev () {
		test "$(git rev-parse --verify "$1")" = \
                     "$(git rev-parse --verify "$2")" && return
		echo >&2 "Revs '$1' and '$2" are different"
		return false
	}

may be necessary.  I dunno.

> diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh
> index e7ba8c5..64cb449 100755
> --- a/t/t2012-checkout-last.sh
> +++ b/t/t2012-checkout-last.sh
> @@ -43,7 +43,7 @@ test_expect_success '"checkout -" attaches again' '
>  
>  test_expect_success '"checkout -" detaches again' '
>  	git checkout - &&
> -	test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" &&
> +	test_cmp_rev HEAD other &&
>  	test_must_fail git symbolic-ref HEAD
>  '
>  
> @@ -101,19 +101,19 @@ test_expect_success 'merge base test setup' '
>  test_expect_success 'another...master' '
>  	git checkout another &&
>  	git checkout another...master &&
> -	test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
> +	test_cmp_rev HEAD master^
>  '
>  
>  test_expect_success '...master' '
>  	git checkout another &&
>  	git checkout ...master &&
> -	test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
> +	test_cmp_rev HEAD master^
>  '
>  
>  test_expect_success 'master...' '
>  	git checkout another &&
>  	git checkout master... &&
> -	test "z$(git rev-parse --verify HEAD)" = "z$(git rev-parse --verify master^)"
> +	test_cmp_rev HEAD master^
>  '
>  
>  test_expect_success '"checkout -" works after a rebase A' '
> diff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh
> index 19aed7e..1f72e9e 100755
> --- a/t/t3308-notes-merge.sh
> +++ b/t/t3308-notes-merge.sh
> @@ -93,7 +93,7 @@ test_expect_success 'merge non-notes ref into empty notes ref (remote-notes/orig
>  	git notes merge refs/remote-notes/origin/x &&
>  	verify_notes v &&
>  	# refs/remote-notes/origin/x and v should point to the same notes commit
> -	test "$(git rev-parse refs/remote-notes/origin/x)" = "$(git rev-parse refs/notes/v)"
> +	test_cmp_rev refs/remote-notes/origin/x refs/notes/v
>  '
>  
>  test_expect_success 'merge notes into empty notes ref (x => y)' '
> @@ -101,13 +101,13 @@ test_expect_success 'merge notes into empty notes ref (x => y)' '
>  	git notes merge x &&
>  	verify_notes y &&
>  	# x and y should point to the same notes commit
> -	test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
> +	test_cmp_rev refs/notes/x refs/notes/y
>  '
>  
>  test_expect_success 'merge empty notes ref (z => y)' '
>  	git notes merge z &&
>  	# y should not change (still == x)
> -	test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
> +	test_cmp_rev refs/notes/x refs/notes/y
>  '
>  
>  test_expect_success 'change notes on other notes ref (y)' '
> @@ -174,7 +174,7 @@ test_expect_success 'merge changed (y) into original (x) => Fast-forward' '
>  	verify_notes x &&
>  	verify_notes y &&
>  	# x and y should point to same the notes commit
> -	test "$(git rev-parse refs/notes/x)" = "$(git rev-parse refs/notes/y)"
> +	test_cmp_rev refs/notes/x refs/notes/y
>  '
>  
>  test_expect_success 'merge empty notes ref (z => y)' '
> diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
> index d557212..5e46fcf 100755
> --- a/t/t3310-notes-merge-manual-resolve.sh
> +++ b/t/t3310-notes-merge-manual-resolve.sh
> @@ -516,7 +516,7 @@ cp expect_log_w expect_log_m
>  test_expect_success 'reset notes ref m to somewhere else (w)' '
>  	git update-ref refs/notes/m refs/notes/w &&
>  	verify_notes m &&
> -	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
> +	test_cmp_rev refs/notes/m refs/notes/w
>  '
>  
>  test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' '
> @@ -537,8 +537,8 @@ EOF
>  	test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 &&
>  	test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 &&
>  	# Refs are unchanged
> -	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
> -	test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
> +	test_cmp_rev refs/notes/m refs/notes/w &&
> +	test_cmp_rev refs/notes/y NOTES_MERGE_PARTIAL^1 &&
>  	test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" &&
>  	# Mention refs/notes/m, and its current and expected value in output
>  	grep -q "refs/notes/m" output &&
> @@ -557,7 +557,7 @@ test_expect_success 'resolve situation by aborting the notes merge' '
>  	test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
>  	test_cmp /dev/null output &&
>  	# m has not moved (still == w)
> -	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
> +	test_cmp_rev refs/notes/m refs/notes/w &&
>  	# Verify that other notes refs has not changed (w, x, y and z)
>  	verify_notes w &&
>  	verify_notes x &&
> diff --git a/t/t3311-notes-merge-fanout.sh b/t/t3311-notes-merge-fanout.sh
> index 93516ef..3fb4d11 100755
> --- a/t/t3311-notes-merge-fanout.sh
> +++ b/t/t3311-notes-merge-fanout.sh
> @@ -135,13 +135,13 @@ test_expect_success 'No-op merge (already included) (x => y)' '
>  	git update-ref refs/notes/m refs/notes/y &&
>  	git config core.notesRef refs/notes/m &&
>  	git notes merge x &&
> -	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
> +	test_cmp_rev refs/notes/m refs/notes/y
>  '
>  
>  test_expect_success 'Fast-forward merge (y => x)' '
>  	git update-ref refs/notes/m refs/notes/x &&
>  	git notes merge y &&
> -	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/y)"
> +	test_cmp_rev refs/notes/m refs/notes/y
>  '
>  
>  cat <<EOF | sort >expect_notes_z
> @@ -394,7 +394,7 @@ test_expect_success 'verify conflict entries (with no fanout)' '
>  		exit 1
>  	done ) &&
>  	# Verify that current notes tree (pre-merge) has not changed (m == w)
> -	test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)"
> +	test_cmp_rev refs/notes/m refs/notes/w
>  '
>  
>  cat >expect_log_m <<EOF
> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
> index b79f442..9b15995 100755
> --- a/t/t3404-rebase-interactive.sh
> +++ b/t/t3404-rebase-interactive.sh
> @@ -131,7 +131,7 @@ test_expect_success 'no changes are a nop' '
>  	set_fake_editor &&
>  	git rebase -i F &&
>  	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
> -	test $(git rev-parse I) = $(git rev-parse HEAD)
> +	test_cmp_rev I HEAD
>  '
>  
>  test_expect_success 'test the [branch] option' '
> @@ -141,8 +141,8 @@ test_expect_success 'test the [branch] option' '
>  	set_fake_editor &&
>  	git rebase -i F branch2 &&
>  	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" &&
> -	test $(git rev-parse I) = $(git rev-parse branch2) &&
> -	test $(git rev-parse I) = $(git rev-parse HEAD)
> +	test_cmp_rev I branch2 &&
> +	test_cmp_rev I HEAD
>  '
>  
>  test_expect_success 'test --onto <branch>' '
> @@ -150,8 +150,8 @@ test_expect_success 'test --onto <branch>' '
>  	set_fake_editor &&
>  	git rebase -i --onto branch1 F &&
>  	test "$(git symbolic-ref -q HEAD)" = "refs/heads/test-onto" &&
> -	test $(git rev-parse HEAD^) = $(git rev-parse branch1) &&
> -	test $(git rev-parse I) = $(git rev-parse branch2)
> +	test_cmp_rev HEAD^ branch1 &&
> +	test_cmp_rev I branch2
>  '
>  
>  test_expect_success 'rebase on top of a non-conflicting commit' '
> @@ -161,12 +161,12 @@ test_expect_success 'rebase on top of a non-conflicting commit' '
>  	git rebase -i branch2 &&
>  	test file6 = $(git diff --name-only original-branch1) &&
>  	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
> -	test $(git rev-parse I) = $(git rev-parse branch2) &&
> -	test $(git rev-parse I) = $(git rev-parse HEAD~2)
> +	test_cmp_rev I branch2 &&
> +	test_cmp_rev I HEAD~2
>  '
>  
>  test_expect_success 'reflog for the branch shows state before rebase' '
> -	test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
> +	test_cmp_rev branch1@{1} original-branch1
>  '
>  
>  test_expect_success 'exchange two commits' '
> @@ -198,7 +198,7 @@ test_expect_success 'stop on conflicting pick' '
>  	git tag new-branch1 &&
>  	set_fake_editor &&
>  	test_must_fail git rebase -i master &&
> -	test "$(git rev-parse HEAD~3)" = "$(git rev-parse master)" &&
> +	test_cmp_rev HEAD~3 master &&
>  	test_cmp expect .git/rebase-merge/patch &&
>  	test_cmp expect2 file1 &&
>  	test "$(git diff --name-status |
> @@ -209,7 +209,7 @@ test_expect_success 'stop on conflicting pick' '
>  
>  test_expect_success 'abort' '
>  	git rebase --abort &&
> -	test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
> +	test_cmp_rev new-branch1 HEAD &&
>  	test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" &&
>  	test_path_is_missing .git/rebase-merge
>  '
> @@ -247,7 +247,7 @@ test_expect_success 'squash' '
>  	FAKE_LINES="1 squash 2" EXPECT_HEADER_COUNT=2 \
>  		git rebase -i --onto master HEAD~2 &&
>  	test B = $(cat file7) &&
> -	test $(git rev-parse HEAD^) = $(git rev-parse master)
> +	test_cmp_rev HEAD^ master
>  '
>  
>  test_expect_success 'retain authorship when squashing' '
> @@ -306,9 +306,9 @@ test_expect_success 'preserve merges with -p' '
>  	git update-index --refresh &&
>  	git diff-files --quiet &&
>  	git diff-index --quiet --cached HEAD -- &&
> -	test $(git rev-parse HEAD~6) = $(git rev-parse branch1) &&
> -	test $(git rev-parse HEAD~4^2) = $(git rev-parse to-be-preserved) &&
> -	test $(git rev-parse HEAD^^2^) = $(git rev-parse HEAD^^^) &&
> +	test_cmp_rev HEAD~6 branch1 &&
> +	test_cmp_rev HEAD~4^2 to-be-preserved &&
> +	test_cmp_rev HEAD^^2^ HEAD^^^ &&
>  	test $(git show HEAD~5:file1) = B &&
>  	test $(git show HEAD~3:file1) = C &&
>  	test $(git show HEAD:file1) = E &&
> @@ -335,7 +335,7 @@ test_expect_success '--continue tries to commit' '
>  	echo resolved > file1 &&
>  	git add file1 &&
>  	FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
> -	test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) &&
> +	test_cmp_rev HEAD^ new-branch1 &&
>  	git show HEAD | grep chouette
>  '
>  
> @@ -600,7 +600,7 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' '
>  	test $(git rev-parse branch3) != $(git rev-parse branch4) &&
>  	set_fake_editor &&
>  	git rebase -i branch3 &&
> -	test $(git rev-parse branch3) = $(git rev-parse branch4)
> +	test_cmp_rev branch3 branch4
>  
>  '
>  
> @@ -660,7 +660,7 @@ test_expect_success 'rebase -i continue with unstaged submodule' '
>  	test_must_fail git rebase -i submodule-base &&
>  	git reset &&
>  	git rebase --continue &&
> -	test $(git rev-parse submodule-base) = $(git rev-parse HEAD)
> +	test_cmp_rev submodule-base HEAD
>  '
>  
>  test_expect_success 'avoid unnecessary reset' '
> @@ -682,7 +682,7 @@ test_expect_success 'reword' '
>  	FAKE_LINES="1 2 3 reword 4" FAKE_COMMIT_MESSAGE="E changed" git rebase -i A &&
>  	git show HEAD | grep "E changed" &&
>  	test $(git rev-parse master) != $(git rev-parse HEAD) &&
> -	test $(git rev-parse master^) = $(git rev-parse HEAD^) &&
> +	test_cmp_rev master^ HEAD^ &&
>  	FAKE_LINES="1 2 reword 3 4" FAKE_COMMIT_MESSAGE="D changed" git rebase -i A &&
>  	git show HEAD^ | grep "D changed" &&
>  	FAKE_LINES="reword 1 2 3 4" FAKE_COMMIT_MESSAGE="B changed" git rebase -i A &&
> @@ -741,7 +741,7 @@ test_expect_success 'always cherry-pick with --no-ff' '
>  		git diff HEAD~$p original-no-ff-branch~$p > out &&
>  		test_cmp empty out
>  	done &&
> -	test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
> +	test_cmp_rev HEAD~3 original-no-ff-branch~3 &&
>  	git diff HEAD~3 original-no-ff-branch~3 > out &&
>  	test_cmp empty out
>  '
> diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh
> index a6a6c40..b5bafe8 100755
> --- a/t/t3407-rebase-abort.sh
> +++ b/t/t3407-rebase-abort.sh
> @@ -40,7 +40,7 @@ testrebase() {
>  		test_must_fail git rebase$type master &&
>  		test_path_is_dir "$dotest" &&
>  		git rebase --abort &&
> -		test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
> +		test_cmp_rev to-rebase pre-rebase &&
>  		test ! -d "$dotest"
>  	'
>  
> @@ -51,9 +51,9 @@ testrebase() {
>  		test_must_fail git rebase$type master &&
>  		test_path_is_dir "$dotest" &&
>  		test_must_fail git rebase --skip &&
> -		test $(git rev-parse HEAD) = $(git rev-parse master) &&
> +		test_cmp_rev HEAD master &&
>  		git rebase --abort &&
> -		test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
> +		test_cmp_rev to-rebase pre-rebase &&
>  		test ! -d "$dotest"
>  	'
>  
> @@ -69,7 +69,7 @@ testrebase() {
>  		test_must_fail git rebase --continue &&
>  		test $(git rev-parse HEAD) != $(git rev-parse master) &&
>  		git rebase --abort &&
> -		test $(git rev-parse to-rebase) = $(git rev-parse pre-rebase) &&
> +		test_cmp_rev to-rebase pre-rebase &&
>  		test ! -d "$dotest"
>  	'
>  
> diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh
> index 6f73b95..99518ad 100755
> --- a/t/t3410-rebase-preserve-dropped-merges.sh
> +++ b/t/t3410-rebase-preserve-dropped-merges.sh
> @@ -51,7 +51,7 @@ test_expect_success 'skip same-resolution merges with -p' '
>  	test_commit J file1 23 &&
>  	test_commit K file7 file7 &&
>  	git rebase -i -p L &&
> -	test $(git rev-parse HEAD^^) = $(git rev-parse L) &&
> +	test_cmp_rev HEAD^^ L &&
>  	test "23" = "$(cat file1)" &&
>  	test "I" = "$(cat file6)" &&
>  	test "file7" = "$(cat file7)"
> @@ -76,7 +76,7 @@ test_expect_success 'keep different-resolution merges with -p' '
>  	echo 234 > file1 &&
>  	git add file1 &&
>  	git rebase --continue &&
> -	test $(git rev-parse HEAD^^^) = $(git rev-parse L2) &&
> +	test_cmp_rev HEAD^^^ L2 &&
>  	test "234" = "$(cat file1)" &&
>  	test "I" = "$(cat file6)" &&
>  	test "file7" = "$(cat file7)"
> diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh
> index dc81bf2..d2dc55a 100755
> --- a/t/t3411-rebase-preserve-around-merges.sh
> +++ b/t/t3411-rebase-preserve-around-merges.sh
> @@ -38,8 +38,8 @@ test_expect_success 'setup' '
>  #
>  test_expect_success 'squash F1 into D1' '
>  	FAKE_LINES="1 squash 4 2 3" git rebase -i -p B1 &&
> -	test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
> -	test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
> +	test_cmp_rev HEAD^2 C1 &&
> +	test_cmp_rev HEAD~2 B1 &&
>  	git tag E2
>  '
>  
> @@ -67,9 +67,9 @@ test_expect_success 'rebase two levels of merge' '
>  	test_commit L1 &&
>  	test_merge M1 K1 &&
>  	GIT_EDITOR=: git rebase -i -p E2 &&
> -	test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
> -	test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
> -	test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse HEAD^2^2^1)"
> +	test_cmp_rev HEAD~3 E2 &&
> +	test_cmp_rev HEAD~2 HEAD^2^2~2 &&
> +	test_cmp_rev HEAD^2^1^1 HEAD^2^2^1
>  '
>  
>  test_done
> diff --git a/t/t3414-rebase-preserve-onto.sh b/t/t3414-rebase-preserve-onto.sh
> index ee0a6cc..5389b1a 100755
> --- a/t/t3414-rebase-preserve-onto.sh
> +++ b/t/t3414-rebase-preserve-onto.sh
> @@ -43,8 +43,8 @@ test_expect_success 'setup' '
>  test_expect_success 'rebase from B1 onto H1' '
>  	git checkout G1 &&
>  	git rebase -p --onto H1 B1 &&
> -	test "$(git rev-parse HEAD^1^1^1)" = "$(git rev-parse H1)" &&
> -	test "$(git rev-parse HEAD^2^1^1)" = "$(git rev-parse H1)"
> +	test_cmp_rev HEAD^1^1^1 H1 &&
> +	test_cmp_rev HEAD^2^1^1 H1
>  '
>  
>  # On the other hand if rebase from E1 which is within one branch,
> @@ -58,8 +58,8 @@ test_expect_success 'rebase from B1 onto H1' '
>  test_expect_success 'rebase from E1 onto H1' '
>  	git checkout G1 &&
>  	git rebase -p --onto H1 E1 &&
> -	test "$(git rev-parse HEAD^1^1)" = "$(git rev-parse H1)" &&
> -	test "$(git rev-parse HEAD^2)" = "$(git rev-parse D1)"
> +	test_cmp_rev HEAD^1^1 H1 &&
> +	test_cmp_rev HEAD^2 D1
>  '
>  
>  # And the same if we rebase from a commit in the second-parent branch.
> @@ -73,8 +73,8 @@ test_expect_success 'rebase from C1 onto H1' '
>  	git checkout G1 &&
>  	git rev-list --first-parent --pretty=oneline C1..G1 &&
>  	git rebase -p --onto H1 C1 &&
> -	test "$(git rev-parse HEAD^2^1)" = "$(git rev-parse H1)" &&
> -	test "$(git rev-parse HEAD^1)" = "$(git rev-parse F1)"
> +	test_cmp_rev HEAD^2^1 H1 &&
> +	test_cmp_rev HEAD^1 F1
>  '
>  
>  test_done
> diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
> index 51f3bbb..53922d9 100755
> --- a/t/t3501-revert-cherry-pick.sh
> +++ b/t/t3501-revert-cherry-pick.sh
> @@ -63,7 +63,7 @@ test_expect_success 'cherry-pick after renaming branch' '
>  
>  	git checkout rename2 &&
>  	git cherry-pick added &&
> -	test $(git rev-parse HEAD^) = $(git rev-parse rename2) &&
> +	test_cmp_rev HEAD^ rename2 &&
>  	test -f opos &&
>  	grep "Add extra line at the end" opos &&
>  	git reflog -1 | grep cherry-pick
> @@ -74,7 +74,7 @@ test_expect_success 'revert after renaming branch' '
>  
>  	git checkout rename1 &&
>  	git revert added &&
> -	test $(git rev-parse HEAD^) = $(git rev-parse rename1) &&
> +	test_cmp_rev HEAD^ rename1 &&
>  	test -f spoo &&
>  	! grep "Add extra line at the end" spoo &&
>  	git reflog -1 | grep revert
> diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh
> index fb889ac..5f7f964 100755
> --- a/t/t3506-cherry-pick-ff.sh
> +++ b/t/t3506-cherry-pick-ff.sh
> @@ -24,7 +24,7 @@ test_expect_success 'cherry-pick using --ff fast forwards' '
>  	git reset --hard first &&
>  	test_tick &&
>  	git cherry-pick --ff second &&
> -	test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify second)"
> +	test_cmp_rev HEAD second
>  '
>  
>  test_expect_success 'cherry-pick not using --ff does not fast forwards' '
> @@ -80,14 +80,14 @@ test_expect_success 'cherry pick with --ff a merge (1)' '
>  	git reset --hard A -- &&
>  	git cherry-pick --ff -m 1 C &&
>  	git diff --exit-code C &&
> -	test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)"
> +	test_cmp_rev HEAD C
>  '
>  
>  test_expect_success 'cherry pick with --ff a merge (2)' '
>  	git reset --hard B -- &&
>  	git cherry-pick --ff -m 2 C &&
>  	git diff --exit-code C &&
> -	test "$(git rev-parse --verify HEAD)" = "$(git rev-parse --verify C)"
> +	test_cmp_rev HEAD C
>  '
>  
>  test_expect_success 'cherry pick a merge relative to nonexistent parent with --ff should fail' '
> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
> index 2142c1f..3517543 100755
> --- a/t/t3903-stash.sh
> +++ b/t/t3903-stash.sh
> @@ -34,7 +34,7 @@ index 0cfbf08..00750ed 100644
>  EOF
>  
>  test_expect_success 'parents of stash' '
> -	test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
> +	test_cmp_rev stash^ HEAD &&
>  	git diff stash^2..stash > output &&
>  	test_cmp output expect
>  '
> @@ -188,7 +188,7 @@ test_expect_success 'stash branch' '
>  	git commit file -m second &&
>  	git stash branch stashbranch &&
>  	test refs/heads/stashbranch = $(git symbolic-ref HEAD) &&
> -	test $(git rev-parse HEAD) = $(git rev-parse master^) &&
> +	test_cmp_rev HEAD master^ &&
>  	git diff --cached > output &&
>  	test_cmp output expect &&
>  	git diff > output &&
> @@ -653,7 +653,7 @@ test_expect_success 'stash where working directory contains "HEAD" file' '
>  	git stash &&
>  	git diff-files --quiet &&
>  	git diff-index --cached --quiet HEAD &&
> -	test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" &&
> +	test_cmp_rev stash^ HEAD &&
>  	git diff stash^..stash > output &&
>  	test_cmp output expect
>  '
> diff --git a/t/t4150-am.sh b/t/t4150-am.sh
> index b41bd17..c6b0a0f 100755
> --- a/t/t4150-am.sh
> +++ b/t/t4150-am.sh
> @@ -209,8 +209,8 @@ test_expect_success 'am applies patch correctly' '
>  	git am <patch1 &&
>  	test_path_is_missing .git/rebase-apply &&
>  	git diff --exit-code second &&
> -	test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
> -	test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
> +	test_cmp_rev second HEAD &&
> +	test_cmp_rev second^ HEAD^
>  '
>  
>  test_expect_success 'am fails if index is dirty' '
> @@ -232,8 +232,8 @@ test_expect_success 'am applies patch e-mail not in a mbox' '
>  	git am patch1.eml &&
>  	test_path_is_missing .git/rebase-apply &&
>  	git diff --exit-code second &&
> -	test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
> -	test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
> +	test_cmp_rev second HEAD &&
> +	test_cmp_rev second^ HEAD^
>  '
>  
>  test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
> @@ -243,8 +243,8 @@ test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
>  	git am patch1-crlf.eml &&
>  	test_path_is_missing .git/rebase-apply &&
>  	git diff --exit-code second &&
> -	test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
> -	test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
> +	test_cmp_rev second HEAD &&
> +	test_cmp_rev second^ HEAD^
>  '
>  
>  test_expect_success 'am applies patch e-mail with preceding whitespace' '
> @@ -254,8 +254,8 @@ test_expect_success 'am applies patch e-mail with preceding whitespace' '
>  	git am patch1-ws.eml &&
>  	test_path_is_missing .git/rebase-apply &&
>  	git diff --exit-code second &&
> -	test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
> -	test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
> +	test_cmp_rev second HEAD &&
> +	test_cmp_rev second^ HEAD^
>  '
>  
>  test_expect_success 'am applies stgit patch' '
> @@ -456,7 +456,7 @@ test_expect_success 'am changes committer and keeps author' '
>  	git checkout first &&
>  	git am patch2 &&
>  	test_path_is_missing .git/rebase-apply &&
> -	test "$(git rev-parse master^^)" = "$(git rev-parse HEAD^^)" &&
> +	test_cmp_rev master^^ HEAD^^ &&
>  	git diff --exit-code master..HEAD &&
>  	git diff --exit-code master^..HEAD^ &&
>  	compare author master HEAD &&
> diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh
> index 2b8c0ba..7ce40c5 100755
> --- a/t/t5404-tracking-branches.sh
> +++ b/t/t5404-tracking-branches.sh
> @@ -40,7 +40,7 @@ test_expect_success 'mixed-success push returns error' '
>  '
>  
>  test_expect_success 'check tracking branches updated correctly after push' '
> -	test "$(git rev-parse origin/master)" = "$(git rev-parse master)"
> +	test_cmp_rev origin/master master
>  '
>  
>  test_expect_success 'check tracking branches not updated for failed refs' '
> diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
> index dd2e6ce..0300344 100755
> --- a/t/t5505-remote.sh
> +++ b/t/t5505-remote.sh
> @@ -727,7 +727,7 @@ test_expect_success 'rename a remote' '
>  		git remote rename origin upstream &&
>  		rmdir .git/refs/remotes/origin &&
>  		test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" &&
> -		test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" &&
> +		test_cmp_rev upstream/master master &&
>  		test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" &&
>  		test "$(git config branch.master.remote)" = "upstream"
>  	)
> @@ -760,7 +760,7 @@ test_expect_success 'rename a remote with name prefix of other remote' '
>  		cd four.three &&
>  		git remote add o git://example.com/repo.git &&
>  		git remote rename o upstream &&
> -		test "$(git rev-parse origin/master)" = "$(git rev-parse master)"
> +		test_cmp_rev origin/master master
>  	)
>  '
>  
> diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh
> index c952d5e..b8c0f46 100755
> --- a/t/t5520-pull.sh
> +++ b/t/t5520-pull.sh
> @@ -209,7 +209,7 @@ test_expect_success 'fast-forwards working tree if branch head is updated' '
>  	git pull . second:third 2>err &&
>  	test_i18ngrep "fetch updated the current branch head" err &&
>  	test "$(cat file)" = modified &&
> -	test "$(git rev-parse third)" = "$(git rev-parse second)"
> +	test_cmp_rev third second
>  '
>  
>  test_expect_success 'fast-forward fails with conflicting work tree' '
> @@ -220,7 +220,7 @@ test_expect_success 'fast-forward fails with conflicting work tree' '
>  	test_must_fail git pull . second:third 2>err &&
>  	test_i18ngrep "Cannot fast-forward your working tree" err &&
>  	test "$(cat file)" = conflict &&
> -	test "$(git rev-parse third)" = "$(git rev-parse second)"
> +	test_cmp_rev third second
>  '
>  
>  test_expect_success '--rebase' '
> @@ -233,14 +233,14 @@ test_expect_success '--rebase' '
>  	git commit -m "new file" &&
>  	git tag before-rebase &&
>  	git pull --rebase . copy &&
> -	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^ copy &&
>  	test new = "$(git show HEAD:file2)"
>  '
>  
>  test_expect_success '--rebase fails with multiple branches' '
>  	git reset --hard before-rebase &&
>  	test_must_fail git pull --rebase . copy master 2>err &&
> -	test "$(git rev-parse HEAD)" = "$(git rev-parse before-rebase)" &&
> +	test_cmp_rev HEAD before-rebase &&
>  	test_i18ngrep "Cannot rebase onto multiple branches" err &&
>  	test modified = "$(git show HEAD:file)"
>  '
> @@ -260,7 +260,7 @@ test_expect_success 'pull.rebase' '
>  	git reset --hard before-rebase &&
>  	test_config pull.rebase true &&
>  	git pull . copy &&
> -	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^ copy &&
>  	test new = "$(git show HEAD:file2)"
>  '
>  
> @@ -268,7 +268,7 @@ test_expect_success 'branch.to-rebase.rebase' '
>  	git reset --hard before-rebase &&
>  	test_config branch.to-rebase.rebase true &&
>  	git pull . copy &&
> -	test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^ copy &&
>  	test new = "$(git show HEAD:file2)"
>  '
>  
> @@ -297,8 +297,8 @@ test_expect_success 'pull.rebase=false create a new merge commit' '
>  	git reset --hard before-preserve-rebase &&
>  	test_config pull.rebase false &&
>  	git pull . copy &&
> -	test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" &&
> -	test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^1 before-preserve-rebase &&
> +	test_cmp_rev HEAD^2 copy &&
>  	test file3 = "$(git show HEAD:file3.t)"
>  '
>  
> @@ -306,7 +306,7 @@ test_expect_success 'pull.rebase=true flattens keep-merge' '
>  	git reset --hard before-preserve-rebase &&
>  	test_config pull.rebase true &&
>  	git pull . copy &&
> -	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^^ copy &&
>  	test file3 = "$(git show HEAD:file3.t)"
>  '
>  
> @@ -314,7 +314,7 @@ test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' '
>  	git reset --hard before-preserve-rebase &&
>  	test_config pull.rebase 1 &&
>  	git pull . copy &&
> -	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^^ copy &&
>  	test file3 = "$(git show HEAD:file3.t)"
>  '
>  
> @@ -322,8 +322,8 @@ test_expect_success 'pull.rebase=preserve rebases and merges keep-merge' '
>  	git reset --hard before-preserve-rebase &&
>  	test_config pull.rebase preserve &&
>  	git pull . copy &&
> -	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
> -	test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)"
> +	test_cmp_rev HEAD^^ copy &&
> +	test_cmp_rev HEAD^2 keep-merge
>  '
>  
>  test_expect_success 'pull.rebase=interactive' '
> @@ -346,8 +346,8 @@ test_expect_success '--rebase=false create a new merge commit' '
>  	git reset --hard before-preserve-rebase &&
>  	test_config pull.rebase true &&
>  	git pull --rebase=false . copy &&
> -	test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" &&
> -	test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^1 before-preserve-rebase &&
> +	test_cmp_rev HEAD^2 copy &&
>  	test file3 = "$(git show HEAD:file3.t)"
>  '
>  
> @@ -355,7 +355,7 @@ test_expect_success '--rebase=true rebases and flattens keep-merge' '
>  	git reset --hard before-preserve-rebase &&
>  	test_config pull.rebase preserve &&
>  	git pull --rebase=true . copy &&
> -	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^^ copy &&
>  	test file3 = "$(git show HEAD:file3.t)"
>  '
>  
> @@ -363,8 +363,8 @@ test_expect_success '--rebase=preserve rebases and merges keep-merge' '
>  	git reset --hard before-preserve-rebase &&
>  	test_config pull.rebase true &&
>  	git pull --rebase=preserve . copy &&
> -	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
> -	test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)"
> +	test_cmp_rev HEAD^^ copy &&
> +	test_cmp_rev HEAD^2 keep-merge
>  '
>  
>  test_expect_success '--rebase=invalid fails' '
> @@ -376,7 +376,7 @@ test_expect_success '--rebase overrides pull.rebase=preserve and flattens keep-m
>  	git reset --hard before-preserve-rebase &&
>  	test_config pull.rebase preserve &&
>  	git pull --rebase . copy &&
> -	test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" &&
> +	test_cmp_rev HEAD^^ copy &&
>  	test file3 = "$(git show HEAD:file3.t)"
>  '
>  
> diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
> index 05ebba7..4e31f6a 100755
> --- a/t/t6022-merge-rename.sh
> +++ b/t/t6022-merge-rename.sh
> @@ -788,7 +788,7 @@ test_expect_success 'merge rename + small change' '
>  
>  	test 1 -eq $(git ls-files -s | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
> -	test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file)
> +	test_cmp_rev HEAD:renamed_file HEAD~1:file
>  '
>  
>  test_expect_success 'setup for use of extended merge markers' '
> diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
> index e74662b..05bc639 100755
> --- a/t/t6030-bisect-porcelain.sh
> +++ b/t/t6030-bisect-porcelain.sh
> @@ -335,16 +335,16 @@ test_expect_success 'bisect skip only one range' '
>  	git bisect reset &&
>  	git bisect start $HASH7 $HASH1 &&
>  	git bisect skip $HASH1..$HASH5 &&
> -	test "$HASH6" = "$(git rev-parse --verify HEAD)" &&
> +	test_cmp_rev HEAD $HASH6 &&
>  	test_must_fail git bisect bad > my_bisect_log.txt &&
>  	grep "first bad commit could be any of" my_bisect_log.txt
>  '
>  
>  test_expect_success 'bisect skip many ranges' '
>  	git bisect start $HASH7 $HASH1 &&
> -	test "$HASH4" = "$(git rev-parse --verify HEAD)" &&
> +	test_cmp_rev HEAD $HASH4 &&
>  	git bisect skip $HASH2 $HASH2.. ..$HASH5 &&
> -	test "$HASH6" = "$(git rev-parse --verify HEAD)" &&
> +	test_cmp_rev HEAD $HASH6 &&
>  	test_must_fail git bisect bad > my_bisect_log.txt &&
>  	grep "first bad commit could be any of" my_bisect_log.txt
>  '
> @@ -356,7 +356,7 @@ test_expect_success 'bisect starting with a detached HEAD' '
>  	git bisect start &&
>  	test $HEAD = $(cat .git/BISECT_START) &&
>  	git bisect reset &&
> -	test $HEAD = $(git rev-parse --verify HEAD)
> +	test_cmp_rev "$HEAD" HEAD
>  '
>  
>  test_expect_success 'bisect errors out if bad and good are mistaken' '
> @@ -370,18 +370,15 @@ test_expect_success 'bisect does not create a "bisect" branch' '
>  	git bisect reset &&
>  	git bisect start $HASH7 $HASH1 &&
>  	git branch bisect &&
> -	rev_hash4=$(git rev-parse --verify HEAD) &&
> -	test "$rev_hash4" = "$HASH4" &&
> +	test_cmp_rev HEAD $HASH4 &&
>  	git branch -D bisect &&
>  	git bisect good &&
>  	git branch bisect &&
> -	rev_hash6=$(git rev-parse --verify HEAD) &&
> -	test "$rev_hash6" = "$HASH6" &&
> +	test_cmp_rev HEAD $HASH6 &&
>  	git bisect good > my_bisect_log.txt &&
>  	grep "$HASH7 is the first bad commit" my_bisect_log.txt &&
>  	git bisect reset &&
> -	rev_hash6=$(git rev-parse --verify bisect) &&
> -	test "$rev_hash6" = "$HASH6" &&
> +	test_cmp_rev bisect $HASH6 &&
>  	git branch -D bisect
>  '
>  
> @@ -481,7 +478,7 @@ test_expect_success 'optimized merge base checks' '
>  	grep "$HASH4" my_bisect_log.txt &&
>  	git bisect good > my_bisect_log2.txt &&
>  	test -f ".git/BISECT_ANCESTORS_OK" &&
> -	test "$HASH6" = $(git rev-parse --verify HEAD) &&
> +	test_cmp_rev HEAD $HASH6 &&
>  	git bisect bad > my_bisect_log3.txt &&
>  	git bisect good "$A_HASH" > my_bisect_log4.txt &&
>  	grep "merge base must be tested" my_bisect_log4.txt &&
> @@ -524,8 +521,7 @@ test_expect_success '"parallel" side branch creation' '
>  test_expect_success 'restricting bisection on one dir' '
>  	git bisect reset &&
>  	git bisect start HEAD $HASH1 -- dir1 &&
> -	para1=$(git rev-parse --verify HEAD) &&
> -	test "$para1" = "$PARA_HASH1" &&
> +	test_cmp_rev HEAD "$PARA_HASH1" &&
>  	git bisect bad > my_bisect_log.txt &&
>  	grep "$PARA_HASH1 is the first bad commit" my_bisect_log.txt
>  '
> @@ -533,31 +529,24 @@ test_expect_success 'restricting bisection on one dir' '
>  test_expect_success 'restricting bisection on one dir and a file' '
>  	git bisect reset &&
>  	git bisect start HEAD $HASH1 -- dir1 hello &&
> -	para4=$(git rev-parse --verify HEAD) &&
> -	test "$para4" = "$PARA_HASH4" &&
> +	test_cmp_rev HEAD "$PARA_HASH4" &&
>  	git bisect bad &&
> -	hash3=$(git rev-parse --verify HEAD) &&
> -	test "$hash3" = "$HASH3" &&
> +	test_cmp_rev HEAD $HASH3 &&
>  	git bisect good &&
> -	hash4=$(git rev-parse --verify HEAD) &&
> -	test "$hash4" = "$HASH4" &&
> +	test_cmp_rev HEAD $HASH4 &&
>  	git bisect good &&
> -	para1=$(git rev-parse --verify HEAD) &&
> -	test "$para1" = "$PARA_HASH1" &&
> +	test_cmp_rev HEAD "$PARA_HASH1" &&
>  	git bisect good > my_bisect_log.txt &&
>  	grep "$PARA_HASH4 is the first bad commit" my_bisect_log.txt
>  '
>  
>  test_expect_success 'skipping away from skipped commit' '
>  	git bisect start $PARA_HASH7 $HASH1 &&
> -	para4=$(git rev-parse --verify HEAD) &&
> -	test "$para4" = "$PARA_HASH4" &&
> -        git bisect skip &&
> -	hash7=$(git rev-parse --verify HEAD) &&
> -	test "$hash7" = "$HASH7" &&
> -        git bisect skip &&
> -	para3=$(git rev-parse --verify HEAD) &&
> -	test "$para3" = "$PARA_HASH3"
> +	test_cmp_rev HEAD "$PARA_HASH4" &&
> +	git bisect skip &&
> +	test_cmp_rev HEAD $HASH7 &&
> +	git bisect skip &&
> +	test_cmp_rev HEAD "$PARA_HASH3"
>  '
>  
>  test_expect_success 'erroring out when using bad path parameters' '
> @@ -644,56 +633,50 @@ test_expect_success 'bisect fails if tree is broken on trial commit' '
>  	test_cmp expected.missing-tree.default error.txt
>  '
>  
> -check_same()
> -{
> -	echo "Checking $1 is the same as $2" &&
> -	test_cmp_rev "$1" "$2"
> -}
> -
>  test_expect_success 'bisect: --no-checkout - start commit bad' '
>  	git bisect reset &&
>  	git bisect start BROKEN_HASH7 BROKEN_HASH4 --no-checkout &&
> -	check_same BROKEN_HASH6 BISECT_HEAD &&
> +	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
>  	git bisect reset
>  '
>  
>  test_expect_success 'bisect: --no-checkout - trial commit bad' '
>  	git bisect reset &&
>  	git bisect start broken BROKEN_HASH4 --no-checkout &&
> -	check_same BROKEN_HASH6 BISECT_HEAD &&
> +	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
>  	git bisect reset
>  '
>  
>  test_expect_success 'bisect: --no-checkout - target before breakage' '
>  	git bisect reset &&
>  	git bisect start broken BROKEN_HASH4 --no-checkout &&
> -	check_same BROKEN_HASH6 BISECT_HEAD &&
> +	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
>  	git bisect bad BISECT_HEAD &&
> -	check_same BROKEN_HASH5 BISECT_HEAD &&
> +	test_cmp_rev BISECT_HEAD BROKEN_HASH5 &&
>  	git bisect bad BISECT_HEAD &&
> -	check_same BROKEN_HASH5 bisect/bad &&
> +	test_cmp_rev bisect/bad BROKEN_HASH5 &&
>  	git bisect reset
>  '
>  
>  test_expect_success 'bisect: --no-checkout - target in breakage' '
>  	git bisect reset &&
>  	git bisect start broken BROKEN_HASH4 --no-checkout &&
> -	check_same BROKEN_HASH6 BISECT_HEAD &&
> +	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
>  	git bisect bad BISECT_HEAD &&
> -	check_same BROKEN_HASH5 BISECT_HEAD &&
> +	test_cmp_rev BISECT_HEAD BROKEN_HASH5 &&
>  	git bisect good BISECT_HEAD &&
> -	check_same BROKEN_HASH6 bisect/bad &&
> +	test_cmp_rev bisect/bad BROKEN_HASH6 &&
>  	git bisect reset
>  '
>  
>  test_expect_success 'bisect: --no-checkout - target after breakage' '
>  	git bisect reset &&
>  	git bisect start broken BROKEN_HASH4 --no-checkout &&
> -	check_same BROKEN_HASH6 BISECT_HEAD &&
> +	test_cmp_rev BISECT_HEAD BROKEN_HASH6 &&
>  	git bisect good BISECT_HEAD &&
> -	check_same BROKEN_HASH8 BISECT_HEAD &&
> +	test_cmp_rev BISECT_HEAD BROKEN_HASH8 &&
>  	git bisect good BISECT_HEAD &&
> -	check_same BROKEN_HASH9 bisect/bad &&
> +	test_cmp_rev bisect/bad BROKEN_HASH9 &&
>  	git bisect reset
>  '
>  
> @@ -708,7 +691,7 @@ test_expect_success 'bisect: demonstrate identification of damage boundary' "
>  		rc=\$?
>  		rm -f tmp.\$\$
>  		test \$rc = 0' &&
> -	check_same BROKEN_HASH6 bisect/bad &&
> +	test_cmp_rev bisect/bad BROKEN_HASH6 &&
>  	git bisect reset
>  "
>  
> @@ -755,7 +738,7 @@ test_expect_success '"git bisect bad HEAD" behaves as "git bisect bad"' '
>  	git bisect start HEAD $HASH1 &&
>  	git bisect good HEAD &&
>  	git bisect bad HEAD &&
> -	test "$HASH6" = $(git rev-parse --verify HEAD) &&
> +	test_cmp_rev HEAD $HASH6 &&
>  	git bisect reset
>  '
>  
> diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh
> index 9d6621c..dd1be5d 100755
> --- a/t/t6036-recursive-corner-cases.sh
> +++ b/t/t6036-recursive-corner-cases.sh
> @@ -61,8 +61,8 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
>  	test 2 = $(git ls-files -u | wc -l) &&
>  	test 2 = $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
> -	test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
> +	test_cmp_rev :2:three L2:three &&
> +	test_cmp_rev :3:three R2:three &&
>  
>  	test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
>  	test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
> @@ -128,8 +128,8 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
>  	test 2 = $(git ls-files -u | wc -l) &&
>  	test 2 = $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
> -	test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
> +	test_cmp_rev :2:three L2:three &&
> +	test_cmp_rev :3:three R2:three &&
>  
>  	test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
>  	test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
> @@ -201,8 +201,8 @@ test_expect_success 'git detects differently handled merges conflict' '
>  	test 3 = $(git ls-files -u | wc -l) &&
>  	test 0 = $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) &&
> -	test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) &&
> +	test_cmp_rev :2:new_a D:new_a &&
> +	test_cmp_rev :3:new_a E:new_a &&
>  
>  	git cat-file -p B:new_a >>merged &&
>  	git cat-file -p C:new_a >>merge-me &&
> @@ -281,8 +281,8 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete' '
>  	test 2 -eq $(git ls-files -s | wc -l) &&
>  	test 2 -eq $(git ls-files -u | wc -l) &&
>  
> -	test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
> -	test $(git rev-parse :2:file) = $(git rev-parse B:file)
> +	test_cmp_rev :1:file master:file &&
> +	test_cmp_rev :2:file B:file
>  '
>  
>  test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '
> @@ -294,8 +294,8 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete, rev
>  	test 2 -eq $(git ls-files -s | wc -l) &&
>  	test 2 -eq $(git ls-files -u | wc -l) &&
>  
> -	test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
> -	test $(git rev-parse :3:file) = $(git rev-parse B:file)
> +	test_cmp_rev :1:file master:file &&
> +	test_cmp_rev :3:file B:file
>  '
>  
>  #
> @@ -377,8 +377,8 @@ test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '
>  	test 3 -eq $(git ls-files -u | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :2:file) = $(git rev-parse D:file) &&
> -	test $(git rev-parse :3:file) = $(git rev-parse E:file)
> +	test_cmp_rev :2:file D:file &&
> +	test_cmp_rev :3:file E:file
>  '
>  
>  #
> @@ -483,8 +483,8 @@ test_expect_success 'merge of D & E1 fails but has appropriate contents' '
>  	test 1 -eq $(git ls-files -u | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
> -	test $(git rev-parse :2:a) = $(git rev-parse B:a)
> +	test_cmp_rev :0:ignore-me A:ignore-me &&
> +	test_cmp_rev :2:a B:a
>  '
>  
>  test_expect_success 'merge of E1 & D fails but has appropriate contents' '
> @@ -496,8 +496,8 @@ test_expect_success 'merge of E1 & D fails but has appropriate contents' '
>  	test 1 -eq $(git ls-files -u | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
> -	test $(git rev-parse :3:a) = $(git rev-parse B:a)
> +	test_cmp_rev :0:ignore-me A:ignore-me &&
> +	test_cmp_rev :3:a B:a
>  '
>  
>  test_expect_success 'merge of D & E2 fails but has appropriate contents' '
> @@ -509,10 +509,10 @@ test_expect_success 'merge of D & E2 fails but has appropriate contents' '
>  	test 3 -eq $(git ls-files -u | wc -l) &&
>  	test 1 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :2:a) = $(git rev-parse B:a) &&
> -	test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) &&
> -	test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
> -	test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
> +	test_cmp_rev :2:a B:a &&
> +	test_cmp_rev :3:a/file E2:a/file &&
> +	test_cmp_rev :1:a/file C:a/file &&
> +	test_cmp_rev :0:ignore-me A:ignore-me &&
>  
>  	test -f a~HEAD
>  '
> @@ -526,10 +526,10 @@ test_expect_success 'merge of E2 & D fails but has appropriate contents' '
>  	test 3 -eq $(git ls-files -u | wc -l) &&
>  	test 1 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :3:a) = $(git rev-parse B:a) &&
> -	test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) &&
> -	test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
> -	test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
> +	test_cmp_rev :3:a B:a &&
> +	test_cmp_rev :2:a/file E2:a/file &&
> +	test_cmp_rev :1:a/file C:a/file &&
> +	test_cmp_rev :0:ignore-me A:ignore-me &&
>  
>  	test -f a~D^0
>  '
> @@ -619,7 +619,7 @@ test_expect_success 'handle rename/rename(1to2)/modify followed by what looks li
>  	test 0 -eq $(git ls-files -u | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)
> +	test_cmp_rev HEAD:newname E:newname
>  '
>  
>  #
> @@ -694,8 +694,8 @@ test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '
>  	test 0 -eq $(git ls-files -u | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
> -	test $(git rev-parse HEAD:c) = $(git rev-parse A:a) &&
> +	test_cmp_rev HEAD:b A:a &&
> +	test_cmp_rev HEAD:c A:a &&
>  	test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")"
>  '
>  
> @@ -764,8 +764,8 @@ test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '
>  	test 0 -eq $(git ls-files -u | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse HEAD:a) = $(git rev-parse A:a) &&
> -	test $(git rev-parse HEAD:c) = $(git rev-parse E:c)
> +	test_cmp_rev HEAD:a A:a &&
> +	test_cmp_rev HEAD:c E:c
>  '
>  
>  test_done
> diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh
> index 411550d..9aac880 100755
> --- a/t/t6042-merge-rename-corner-cases.sh
> +++ b/t/t6042-merge-rename-corner-cases.sh
> @@ -68,8 +68,8 @@ test_expect_failure 'rename/modify/add-source conflict resolvable' '
>  
>  	git merge -s recursive C^0 &&
>  
> -	test $(git rev-parse B:a) = $(git rev-parse b) &&
> -	test $(git rev-parse C:a) = $(git rev-parse a)
> +	test_cmp_rev B:a b &&
> +	test_cmp_rev C:a a
>  '
>  
>  test_expect_success 'setup resolvable conflict missed if rename missed' '
> @@ -105,8 +105,8 @@ test_expect_failure 'conflict caused if rename not detected' '
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
>  	test_line_count = 6 c &&
> -	test $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&
> -	test $(git rev-parse HEAD:b) = $(git rev-parse A:b)
> +	test_cmp_rev HEAD:a B:a &&
> +	test_cmp_rev HEAD:b A:b
>  '
>  
>  test_expect_success 'setup conflict resolved wrong if rename missed' '
> @@ -178,8 +178,8 @@ test_expect_failure 'detect rename/add-source and preserve all data' '
>  	test -f a &&
>  	test -f b &&
>  
> -	test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
> -	test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
> +	test_cmp_rev HEAD:b A:a &&
> +	test_cmp_rev HEAD:a C:a
>  '
>  
>  test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
> @@ -194,8 +194,8 @@ test_expect_failure 'detect rename/add-source and preserve all data, merge other
>  	test -f a &&
>  	test -f b &&
>  
> -	test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
> -	test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
> +	test_cmp_rev HEAD:b A:a &&
> +	test_cmp_rev HEAD:a C:a
>  '
>  
>  test_expect_success 'setup content merge + rename/directory conflict' '
> @@ -281,9 +281,9 @@ test_expect_success 'rename/directory conflict + content merge conflict' '
>  		left base right &&
>  	test_cmp left newfile~HEAD &&
>  
> -	test $(git rev-parse :1:newfile) = $(git rev-parse base:file) &&
> -	test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) &&
> -	test $(git rev-parse :3:newfile) = $(git rev-parse right:file) &&
> +	test_cmp_rev :1:newfile base:file &&
> +	test_cmp_rev :2:newfile left-conflict:newfile &&
> +	test_cmp_rev :3:newfile right:file &&
>  
>  	test -f newfile/realfile &&
>  	test -f newfile~HEAD
> @@ -432,9 +432,9 @@ test_expect_success 'merge has correct working tree contents' '
>  	test 3 -eq $(git ls-files -u | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
> -	test $(git rev-parse :3:b) = $(git rev-parse A:a) &&
> -	test $(git rev-parse :2:c) = $(git rev-parse A:a) &&
> +	test_cmp_rev :1:a A:a &&
> +	test_cmp_rev :3:b A:a &&
> +	test_cmp_rev :2:c A:a &&
>  
>  	test ! -f a &&
>  	test $(git hash-object b) = $(git rev-parse A:a) &&
> @@ -478,10 +478,10 @@ test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge'
>  	test 4 -eq $(git ls-files -s | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse 3:a) = $(git rev-parse C:a) &&
> -	test $(git rev-parse 1:a) = $(git rev-parse A:a) &&
> -	test $(git rev-parse 2:b) = $(git rev-parse B:b) &&
> -	test $(git rev-parse 3:c) = $(git rev-parse C:c) &&
> +	test_cmp_rev 3:a C:a &&
> +	test_cmp_rev 1:a A:a &&
> +	test_cmp_rev 2:b B:b &&
> +	test_cmp_rev 3:c C:c &&
>  
>  	test -f a &&
>  	test -f b &&
> @@ -520,8 +520,8 @@ test_expect_failure 'rename/rename/add-source still tracks new a file' '
>  	test 2 -eq $(git ls-files -s | wc -l) &&
>  	test 0 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse HEAD:a) = $(git rev-parse C:a) &&
> -	test $(git rev-parse HEAD:b) = $(git rev-parse A:a)
> +	test_cmp_rev HEAD:a C:a &&
> +	test_cmp_rev HEAD:b A:a
>  '
>  
>  test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
> @@ -560,11 +560,11 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting
>  	test 2 -eq $(git ls-files -u c | wc -l) &&
>  	test 4 -eq $(git ls-files -o | wc -l) &&
>  
> -	test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
> -	test $(git rev-parse :2:b) = $(git rev-parse C:b) &&
> -	test $(git rev-parse :3:b) = $(git rev-parse B:b) &&
> -	test $(git rev-parse :2:c) = $(git rev-parse C:c) &&
> -	test $(git rev-parse :3:c) = $(git rev-parse B:c) &&
> +	test_cmp_rev :1:a A:a &&
> +	test_cmp_rev :2:b C:b &&
> +	test_cmp_rev :3:b B:b &&
> +	test_cmp_rev :2:c C:c &&
> +	test_cmp_rev :3:c B:c &&
>  
>  	test $(git hash-object c~HEAD) = $(git rev-parse C:c) &&
>  	test $(git hash-object c~B\^0) = $(git rev-parse B:c) &&
> diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
> index cb8fbd8..72511de 100755
> --- a/t/t7003-filter-branch.sh
> +++ b/t/t7003-filter-branch.sh
> @@ -354,8 +354,8 @@ test_expect_success '--remap-to-ancestor with filename filters' '
>  	git filter-branch -f --remap-to-ancestor \
>  		moved-foo moved-bar A..master \
>  		-- -- foo &&
> -	test $(git rev-parse moved-foo) = $(git rev-parse moved-bar) &&
> -	test $(git rev-parse moved-foo) = $(git rev-parse master^) &&
> +	test_cmp_rev moved-foo moved-bar &&
> +	test_cmp_rev moved-foo master^ &&
>  	test $orig_invariant = $(git rev-parse invariant)
>  '
>  
> @@ -372,8 +372,8 @@ test_expect_success 'automatic remapping to ancestor with filename filters' '
>  	git filter-branch -f \
>  		moved-foo2 moved-bar2 A..master \
>  		-- -- foo &&
> -	test $(git rev-parse moved-foo2) = $(git rev-parse moved-bar2) &&
> -	test $(git rev-parse moved-foo2) = $(git rev-parse master^) &&
> +	test_cmp_rev moved-foo2 moved-bar2 &&
> +	test_cmp_rev moved-foo2 master^ &&
>  	test $orig_invariant = $(git rev-parse invariant2)
>  '
>  
> diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
> index f9b7d79..cd65715 100755
> --- a/t/t7004-tag.sh
> +++ b/t/t7004-tag.sh
> @@ -337,7 +337,7 @@ test_expect_success \
>  	'a non-annotated tag created without parameters should point to HEAD' '
>  	git tag non-annotated-tag &&
>  	test $(git cat-file -t non-annotated-tag) = commit &&
> -	test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD)
> +	test_cmp_rev non-annotated-tag HEAD
>  '
>  
>  test_expect_success 'trying to verify an unknown tag should fail' \
> diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh
> index a82a07a..2373684 100755
> --- a/t/t7110-reset-merge.sh
> +++ b/t/t7110-reset-merge.sh
> @@ -31,7 +31,7 @@ test_expect_success 'reset --merge is ok with changes in file it does not touch'
>      git reset --merge HEAD^ &&
>      ! grep 4 file1 &&
>      grep 4 file2 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
> +    test_cmp_rev HEAD initial &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -39,7 +39,7 @@ test_expect_success 'reset --merge is ok when switching back' '
>      git reset --merge second &&
>      grep 4 file1 &&
>      grep 4 file2 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
> +    test_cmp_rev HEAD second &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -55,7 +55,7 @@ test_expect_success 'reset --keep is ok with changes in file it does not touch'
>      git reset --keep HEAD^ &&
>      ! grep 4 file1 &&
>      grep 4 file2 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
> +    test_cmp_rev HEAD initial &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -63,7 +63,7 @@ test_expect_success 'reset --keep is ok when switching back' '
>      git reset --keep second &&
>      grep 4 file1 &&
>      grep 4 file2 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
> +    test_cmp_rev HEAD second &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -82,7 +82,7 @@ test_expect_success 'reset --merge discards changes added to index (1)' '
>      ! grep 4 file1 &&
>      ! grep 5 file1 &&
>      grep 4 file2 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
> +    test_cmp_rev HEAD initial &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -94,7 +94,7 @@ test_expect_success 'reset --merge is ok again when switching back (1)' '
>      ! grep 4 file2 &&
>      ! grep 5 file1 &&
>      grep 4 file1 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
> +    test_cmp_rev HEAD second &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -122,7 +122,7 @@ test_expect_success 'reset --merge discards changes added to index (2)' '
>      git add file2 &&
>      git reset --merge HEAD^ &&
>      ! grep 4 file2 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
> +    test_cmp_rev HEAD initial &&
>      test -z "$(git diff)" &&
>      test -z "$(git diff --cached)"
>  '
> @@ -132,7 +132,7 @@ test_expect_success 'reset --merge is ok again when switching back (2)' '
>      git reset --merge second &&
>      ! grep 4 file2 &&
>      grep 4 file1 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
> +    test_cmp_rev HEAD second &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -148,7 +148,7 @@ test_expect_success 'reset --keep keeps changes it does not touch' '
>      git add file2 &&
>      git reset --keep HEAD^ &&
>      grep 4 file2 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse initial)" &&
> +    test_cmp_rev HEAD initial &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -156,7 +156,7 @@ test_expect_success 'reset --keep keeps changes when switching back' '
>      git reset --keep second &&
>      grep 4 file2 &&
>      grep 4 file1 &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
> +    test_cmp_rev HEAD second &&
>      test -z "$(git diff --cached)"
>  '
>  
> @@ -223,7 +223,7 @@ test_expect_success '"reset --merge HEAD^" is ok with pending merge' '
>      git checkout third &&
>      test_must_fail git merge branch1 &&
>      git reset --merge HEAD^ &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse second)" &&
> +    test_cmp_rev HEAD second &&
>      test -z "$(git diff --cached)" &&
>      test -z "$(git diff)"
>  '
> @@ -249,7 +249,7 @@ test_expect_success '"reset --merge HEAD" is ok with pending merge' '
>      git reset --hard third &&
>      test_must_fail git merge branch1 &&
>      git reset --merge HEAD &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse third)" &&
> +    test_cmp_rev HEAD third &&
>      test -z "$(git diff --cached)" &&
>      test -z "$(git diff)"
>  '
> diff --git a/t/t7201-co.sh b/t/t7201-co.sh
> index 8859236..3ff3126 100755
> --- a/t/t7201-co.sh
> +++ b/t/t7201-co.sh
> @@ -415,7 +415,7 @@ test_expect_success 'checkout w/--track from non-branch HEAD fails' '
>      test_must_fail git checkout --track -b track &&
>      test_must_fail git rev-parse --verify track &&
>      test_must_fail git symbolic-ref HEAD &&
> -    test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
> +    test_cmp_rev master^0 HEAD
>  '
>  
>  test_expect_success 'checkout w/--track from tag fails' '
> @@ -424,7 +424,7 @@ test_expect_success 'checkout w/--track from tag fails' '
>      test_must_fail git checkout --track -b track frotz &&
>      test_must_fail git rev-parse --verify track &&
>      test_must_fail git symbolic-ref HEAD &&
> -    test "z$(git rev-parse master^0)" = "z$(git rev-parse HEAD)"
> +    test_cmp_rev master^0 HEAD
>  '
>  
>  test_expect_success 'detach a symbolic link HEAD' '
> @@ -436,7 +436,7 @@ test_expect_success 'detach a symbolic link HEAD' '
>      test "z$it" = zrefs/heads/master &&
>      here=$(git rev-parse --verify refs/heads/master) &&
>      git checkout side^ &&
> -    test "z$(git rev-parse --verify refs/heads/master)" = "z$here"
> +    test_cmp_rev refs/heads/master "$here"
>  '
>  
>  test_expect_success \
> @@ -446,19 +446,19 @@ test_expect_success \
>  
>      git checkout --track origin/koala/bear &&
>      test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
> +    test_cmp_rev HEAD renamer &&
>  
>      git checkout master && git branch -D koala/bear &&
>  
>      git checkout --track refs/remotes/origin/koala/bear &&
>      test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
> +    test_cmp_rev HEAD renamer &&
>  
>      git checkout master && git branch -D koala/bear &&
>  
>      git checkout --track remotes/origin/koala/bear &&
>      test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
> -    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"
> +    test_cmp_rev HEAD renamer
>  '
>  
>  test_expect_success \
> diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh
> index c6c44ec..cf50055 100755
> --- a/t/t7601-merge-pull-config.sh
> +++ b/t/t7601-merge-pull-config.sh
> @@ -42,7 +42,7 @@ test_expect_success 'fast-forward pull succeeds with "true" in pull.ff' '
>  	git reset --hard c0 &&
>  	test_config pull.ff true &&
>  	git pull . c1 &&
> -	test "$(git rev-parse HEAD)" = "$(git rev-parse c1)"
> +	test_cmp_rev HEAD c1
>  '
>  
>  test_expect_success 'pull.ff=true overrides merge.ff=false' '
> @@ -50,15 +50,15 @@ test_expect_success 'pull.ff=true overrides merge.ff=false' '
>  	test_config merge.ff false &&
>  	test_config pull.ff true &&
>  	git pull . c1 &&
> -	test "$(git rev-parse HEAD)" = "$(git rev-parse c1)"
> +	test_cmp_rev HEAD c1
>  '
>  
>  test_expect_success 'fast-forward pull creates merge with "false" in pull.ff' '
>  	git reset --hard c0 &&
>  	test_config pull.ff false &&
>  	git pull . c1 &&
> -	test "$(git rev-parse HEAD^1)" = "$(git rev-parse c0)" &&
> -	test "$(git rev-parse HEAD^2)" = "$(git rev-parse c1)"
> +	test_cmp_rev HEAD^1 c0 &&
> +	test_cmp_rev HEAD^2 c1
>  '
>  
>  test_expect_success 'pull prevents non-fast-forward with "only" in pull.ff' '
> @@ -79,17 +79,16 @@ test_expect_success 'merge c1 with c2 and c3 (recursive in pull.octopus)' '
>  	git reset --hard c1 &&
>  	git config pull.octopus "recursive" &&
>  	test_must_fail git merge c2 c3 &&
> -	test "$(git rev-parse c1)" = "$(git rev-parse HEAD)"
> +	test_cmp_rev c1 HEAD
>  '
>  
>  test_expect_success 'merge c1 with c2 and c3 (recursive and octopus in pull.octopus)' '
>  	git reset --hard c1 &&
>  	git config pull.octopus "recursive octopus" &&
>  	git merge c2 c3 &&
> -	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> -	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> -	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> -	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
> +	test_cmp_rev c1 HEAD^1 &&
> +	test_cmp_rev c2 HEAD^2 &&
> +	test_cmp_rev c3 HEAD^3 &&
>  	git diff --exit-code &&
>  	test -f c0.c &&
>  	test -f c1.c &&
> diff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh
> index 9894895..4e69fe7 100755
> --- a/t/t7603-merge-reduce-heads.sh
> +++ b/t/t7603-merge-reduce-heads.sh
> @@ -46,11 +46,10 @@ test_expect_success 'setup' '
>  test_expect_success 'merge c1 with c2, c3, c4, c5' '
>  	git reset --hard c1 &&
>  	git merge c2 c3 c4 c5 &&
> -	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> -	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> -	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> -	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
> -	test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
> +	test_cmp_rev c1 HEAD^1 &&
> +	test_cmp_rev c2 HEAD^2 &&
> +	test_cmp_rev c3 HEAD^3 &&
> +	test_cmp_rev c5 HEAD^4 &&
>  	git diff --exit-code &&
>  	test -f c0.c &&
>  	test -f c1.c &&
> @@ -69,11 +68,10 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '
>  test_expect_success 'pull c2, c3, c4, c5 into c1' '
>  	git reset --hard c1 &&
>  	git pull . c2 c3 c4 c5 &&
> -	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> -	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> -	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> -	test "$(git rev-parse c3)" = "$(git rev-parse HEAD^3)" &&
> -	test "$(git rev-parse c5)" = "$(git rev-parse HEAD^4)" &&
> +	test_cmp_rev c1 HEAD^1 &&
> +	test_cmp_rev c2 HEAD^2 &&
> +	test_cmp_rev c3 HEAD^3 &&
> +	test_cmp_rev c5 HEAD^4 &&
>  	git diff --exit-code &&
>  	test -f c0.c &&
>  	test -f c1.c &&
> @@ -113,8 +111,8 @@ test_expect_success 'merge E and I' '
>  '
>  
>  test_expect_success 'verify merge result' '
> -	test $(git rev-parse HEAD^1) = $(git rev-parse E) &&
> -	test $(git rev-parse HEAD^2) = $(git rev-parse I)
> +	test_cmp_rev HEAD^1 E &&
> +	test_cmp_rev HEAD^2 I
>  '
>  
>  test_expect_success 'add conflicts' '
> @@ -139,8 +137,8 @@ test_expect_success 'merge E2 and I2, causing a conflict and resolve it' '
>  '
>  
>  test_expect_success 'verify merge result' '
> -	test $(git rev-parse HEAD^1) = $(git rev-parse E2) &&
> -	test $(git rev-parse HEAD^2) = $(git rev-parse I2)
> +	test_cmp_rev HEAD^1 E2 &&
> +	test_cmp_rev HEAD^2 I2
>  '
>  
>  test_expect_success 'fast-forward to redundant refs' '
> @@ -149,7 +147,7 @@ test_expect_success 'fast-forward to redundant refs' '
>  '
>  
>  test_expect_success 'verify merge result' '
> -	test $(git rev-parse HEAD) = $(git rev-parse c5)
> +	test_cmp_rev HEAD c5
>  '
>  
>  test_expect_success 'merge up-to-date redundant refs' '
> @@ -158,7 +156,7 @@ test_expect_success 'merge up-to-date redundant refs' '
>  '
>  
>  test_expect_success 'verify merge result' '
> -	test $(git rev-parse HEAD) = $(git rev-parse c5)
> +	test_cmp_rev HEAD c5
>  '
>  
>  test_done
> diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh
> index 0cb9d11..5be44f1 100755
> --- a/t/t7605-merge-resolve.sh
> +++ b/t/t7605-merge-resolve.sh
> @@ -30,9 +30,8 @@ test_expect_success 'setup' '
>  test_expect_success 'merge c1 to c2' '
>  	git reset --hard c1 &&
>  	git merge -s resolve c2 &&
> -	test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
> -	test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" &&
> -	test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" &&
> +	test_cmp_rev c1 HEAD^1 &&
> +	test_cmp_rev c2 HEAD^2 &&
>  	git diff --exit-code &&
>  	test -f c0.c &&
>  	test -f c1.c &&
> diff --git a/t/t9162-git-svn-dcommit-interactive.sh b/t/t9162-git-svn-dcommit-interactive.sh
> index e38d9fa..ec77a98 100755
> --- a/t/t9162-git-svn-dcommit-interactive.sh
> +++ b/t/t9162-git-svn-dcommit-interactive.sh
> @@ -21,7 +21,7 @@ test_expect_success 'answers: y [\n] yes' '
>  		( echo "y
>  
>  y" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
> -		test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn)
> +		test_cmp_rev HEAD remotes/git-svn
>  	)
>  	'
>  
> @@ -33,7 +33,7 @@ test_expect_success 'answers: yes yes no' '
>  		( echo "yes
>  yes
>  no" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
> -		test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
> +		test_cmp_rev HEAD^^^ remotes/git-svn &&
>  		git reset --hard remotes/git-svn
>  	)
>  	'
> @@ -45,7 +45,7 @@ test_expect_success 'answers: yes quit' '
>  		echo "change #3" >> foo && git commit -a -m"change #3" &&
>  		( echo "yes
>  quit" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
> -		test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
> +		test_cmp_rev HEAD^^^ remotes/git-svn &&
>  		git reset --hard remotes/git-svn
>  	)
>  	'
> @@ -56,7 +56,7 @@ test_expect_success 'answers: all' '
>  		echo "change #2" >> foo && git commit -a -m"change #2" &&
>  		echo "change #3" >> foo && git commit -a -m"change #3" &&
>  		( echo "all" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
> -		test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn) &&
> +		test_cmp_rev HEAD remotes/git-svn &&
>  		git reset --hard remotes/git-svn
>  	)
>  	'
> diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
> index 25bb60b..c284d75 100755
> --- a/t/t9300-fast-import.sh
> +++ b/t/t9300-fast-import.sh
> @@ -368,7 +368,7 @@ test_expect_success 'B: accept branch name "TEMP_TAG"' '
>  		git prune" &&
>  	git fast-import <input &&
>  	test -f .git/TEMP_TAG &&
> -	test $(git rev-parse master) = $(git rev-parse TEMP_TAG^)
> +	test_cmp_rev master TEMP_TAG^
>  '
>  
>  test_expect_success 'B: accept empty committer' '
> @@ -1110,7 +1110,7 @@ test_expect_success 'N: copy dirty subdirectory' '
>  	INPUT_END
>  
>  	git fast-import <input &&
> -	test $(git rev-parse N2^{tree}) = $(git rev-parse N3^{tree})
> +	test_cmp_rev N2^{tree} N3^{tree}
>  '
>  
>  test_expect_success 'N: copy directory by id' '
> @@ -1507,7 +1507,7 @@ test_expect_success 'O: comments are all skipped' '
>  	INPUT_END
>  
>  	git fast-import <input &&
> -	test $(git rev-parse N3) = $(git rev-parse O1)
> +	test_cmp_rev N3 O1
>  '
>  
>  test_expect_success 'O: blank lines not necessary after data commands' '
> @@ -1528,7 +1528,7 @@ test_expect_success 'O: blank lines not necessary after data commands' '
>  	INPUT_END
>  
>  	git fast-import <input &&
> -	test $(git rev-parse N3) = $(git rev-parse O2)
> +	test_cmp_rev N3 O2
>  '
>  
>  test_expect_success 'O: repack before next test' '
> @@ -1575,7 +1575,7 @@ test_expect_success 'O: blank lines not necessary after other commands' '
>  
>  	git fast-import <input &&
>  	test 8 = $(find .git/objects/pack -type f | wc -l) &&
> -	test $(git rev-parse refs/tags/O3-2nd) = $(git rev-parse O3^) &&
> +	test_cmp_rev refs/tags/O3-2nd O3^ &&
>  	git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual &&
>  	test_cmp expect actual
>  '
> @@ -1721,7 +1721,7 @@ test_expect_success 'P: verbatim SHA gitlinks' '
>  	git gc &&
>  	git prune &&
>  	git fast-import <input &&
> -	test $(git rev-parse --verify subuse2) = $(git rev-parse --verify subuse1)
> +	test_cmp_rev subuse2 subuse1
>  '
>  
>  test_expect_success 'P: fail on inline gitlink' '

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 05/21] t6030: generalize test to not rely on current implementation
  2016-04-10 13:18 ` [PATCH v2 05/21] t6030: generalize test to not rely on current implementation Stephan Beyer
  2016-04-10 13:47   ` Torsten Bögershausen
@ 2016-04-15 21:07   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 21:07 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> The bisect algorithm allows different outcomes if, for example,
> the number of commits between a good and a bad commit is even.
> The current test relies on a specific behavior (for example,
> the behavior of the halfway() implementation). By disabling
> halfway(), some skip tests fail although the algorithm works.
>
> This commit generalizes the test t6030 such that it works
> even if the bisect algorithm uses its degree of freedom to
> choose another commit.
>
> While at it, fix some indentation issues: use tabs instead of
> 4 spaces.

While style fixes are very much welcome, it makes the patch
unnecessary noisy.  We typically do so as a preparatory clean-up.

And if you do style fixes, please fix other style issues, such as

 - use of "if [ ... ]; then", which should be spelled as

	if test ...
        then

 - unnecessasry space between redirection operator and the filename,
   and lack of double-quoting around such a filename in a variable
   to work around certain vintage of bash that gives unnecessary
   warnings, e.g. 'echo foo > $file' must be spelled as

	echo foo >"$file"

etc.

> @@ -84,9 +82,8 @@ test_expect_success 'bisect fails if given any junk instead of revs' '
>  
>  test_expect_success 'bisect reset: back in the master branch' '
>  	git bisect reset &&
> -	echo "* master" > branch.expect &&
>  	git branch > branch.output &&
> -	cmp branch.expect branch.output
> +	grep "^* master" branch.output

This is not a style fix, and it is not a "possibly multiple valid
outcomes", either.

If the purpose of change is "to do the right thing", checking the
output from "git symbolic-ref HEAD" against "refs/heads/master" is
the kosher way to check what test is trying to do.

> @@ -180,14 +175,15 @@ test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' '
>  	git checkout HEAD hello
>  '
>  
> -# $HASH1 is good, $HASH4 is bad, we skip $HASH3
> +# $HASH1 is good, monday is bad, we skip $HASH3

I am not sure this s/$HASH4/monday/ is adding value.  Certainly it
breaks consistency, which you could keep by defining SIDE_HASH5 or
something when you added the "Ok Monday, let's do it" commit.  On
the other hand, you could choose to consistently use branch-relative
names by turning $HASH3 to master~1, etc.

>  # but $HASH2 is bad,
>  # so we should find $HASH2 as the first bad commit
> ...

> +test_expect_success '"git bisect run" simple case' '
> +	echo "#"\!"/bin/sh" > test_script.sh &&
> +	echo "grep Another hello > /dev/null" >> test_script.sh &&
> +	echo "test \$? -ne 0" >> test_script.sh &&
> +	chmod +x test_script.sh &&

Use write_script in the "style fix" preparatory clean-up patch?

> +	git bisect start &&
> +	git bisect good $HASH1 &&
> +	git bisect bad $HASH4 &&
> +	git bisect run ./test_script.sh > my_bisect_log.txt &&
> +	grep "$HASH3 is the first bad commit" my_bisect_log.txt &&
> +	git bisect reset
> +'
> ...
> +test_expect_success '"git bisect run" with more complex "git bisect start"' '
> +	echo "#"\!"/bin/sh" > test_script.sh &&
> +	echo "grep Ciao hello > /dev/null" >> test_script.sh &&
> +	echo "test \$? -ne 0" >> test_script.sh &&
> +	chmod +x test_script.sh &&

Likewise.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 06/21] bisect: add test for the bisect algorithm
  2016-04-10 13:18 ` [PATCH v2 06/21] bisect: add test for the bisect algorithm Stephan Beyer
@ 2016-04-15 21:13   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 21:13 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> +test_expect_success 'bisect algorithm works in linear history with an odd number of commits' '
> +	git bisect start A7 &&
> +	git bisect next &&
> +	test_cmp_rev HEAD A3 A4
> +'
> +
> +test_expect_success 'bisect algorithm works in linear history with an even number of commits' '
> +	git bisect reset &&
> +	git bisect start A8 &&
> +	git bisect next &&
> +	test_cmp_rev HEAD A4
> +'
> +
> +test_expect_success 'bisect algorithm works with a merge' '
> +	git bisect reset &&
> +	git bisect start Bmerge &&
> +	git bisect next &&
> +	test_cmp_rev HEAD A5 &&
> +	git bisect good &&
> +	test_cmp_rev HEAD A8 &&
> +	git bisect good &&
> +	test_cmp_rev HEAD B1 B2
> +'
> +
> +#                   | w  min | w  min | w  min | w  min |
> +# B---.    BCDmerge | 18  0  | 9    0 | 5    0 | 3    0 |
> +# |\ \ \            |        |        |        |        |
> +# | | | *  D2       | 5   5  | 2    2 | 2    2*| good   |
> +# | | | *  D1       | 4   4  | 1    1 | 1    1 | good   |
> +# | | * |  C3       | 10  8  | 1    1 | 1    1 | 1    1*|
> +# | | * |  C2       | 9   9 *| good   | good   | good   |
> +# | | * |  C1       | 8   8  | good   | good   | good   |
> +# | * | |  B3       | 8   8  | 3    3 | 1    1 | 1    1*|
> +# * | | |  Bmerge   | 11  7  | 4    4*| good   | good   |
> +# |\ \ \ \          |        |        |        |        |
> +# | |/ / /          |        |        |        |        |
> +# | * | |  B2       | 7   7  | 2    2 | good   | good   |
> +# | * | |  B1       | 6   6  | 1    1 | good   | good   |
> +# * | | |  A8       | 8   8  | 1    1 | good   | good   |
> +# | |/ /            |        |        |        |        |
> +# |/| |             |        |        |        |        |
> +# * | |   A7        | 7   7  | good   | good   | good   |
> +# * | |   A6        | 6   6  | good   | good   | good   |
> +# |/ /              |        |        |        |        |
> +# * |     A5        | 5   5  | good   | good   | good   |
> +# * |     A4        | 4   4  | good   | good   | good   |
> +# |/                |        |        |        |        |
> +# *       A3        | 3   3  | good   | good   | good   |
> +# *       A2        | 2   2  | good   | good   | good   |
> +# *       A1        | 1   1  | good   | good   | good   |

Nice drawing.  With this, it is easy to see how the first three
examples above are testing the right thing, too.  It is not
immediately clear what these asterisks in the table are trying to
say, though (the same comment applies to the other drawing below).

> +test_expect_success 'bisect algorithm works with octopus merge' '
> +	git bisect reset &&
> +	git bisect start BCDmerge &&
> +	git bisect next &&
> +	test_cmp_rev HEAD C2 &&
> +	git bisect good &&
> +	test_cmp_rev HEAD Bmerge &&
> +	git bisect good &&
> +	test_cmp_rev HEAD D2 &&
> +	git bisect good &&
> +	test_cmp_rev HEAD B3 C3 &&
> +	git bisect good &&
> +	test_cmp_rev HEAD C3 B3 &&
> +	git bisect good > output &&
> +	grep "$(git rev-parse BCDmerge) is the first bad commit" output
> +'
> +
> +# G 5a6bcdf        D3       | w  min | w  min |
> +# | B 02f2eed      A9       | 14  0  | 7   0  |
> +# | *---. 6174c5c  BCDmerge | 13  1  | 6   1  |
> +# | |\ \ \                  |        |        |
> +# | |_|_|/                  |        |        |
> +# |/| | |                   |        |        |
> +# G | | | a6d6dab  D2       | good   | good   |
> +# * | | | 86414e4  D1       | good   | good   |
> +# | | | * c672402  C3       | 7   7 *| good   |
> +# | | | * 0555272  C2       | 6   6  | good   |
> +# | | | * 28c2b2a  C1       | 5   5  | good   |
> +# | | * | 4b5a7d9  B3       | 5   5  | 3   3 *|
> +# | * | | a419ab7  Bmerge   | 8   6  | 4   3 *|
> +# | |\ \ \                  |        |        |
> +# | | |/ /                  |        |        |
> +# | | * | 4fa1e39  B2       | 4   4  | 2   2  |
> +# | | * | 92a014d  B1       | 3   3  | 1   1  |
> +# | * | | 79158c7  A8       | 5   5  | 1   1  |
> +# | | |/                    |        |        |
> +# | |/|                     |        |        |
> +# | * | 237eb73    A7       | 4   4  | good   |
> +# | * | 3b2f811    A6       | 3   3  | good   |
> +# | |/                      |        |        |
> +# | * 0f2b6d2      A5       | 2   2  | good   |
> +# | * 1fcdaf0      A4       | 1   1  | good   |
> +# |/                        |        |        |
> +# * 096648b        A3       | good   | good   |
> +# * 1cf01b8        A2       | good   | good   |
> +# * 6623165        A1       | good   | good   |
> +
> +test_expect_success 'bisect algorithm works with good commit on unrelated branch' '
> +	git bisect reset &&
> +	git bisect start A9 D3 &&
> +	test_cmp_rev HEAD "$(git merge-base A9 D3)" &&
> +	test_cmp_rev HEAD D2 &&
> +	git bisect good &&
> +	test_cmp_rev HEAD C3 &&
> +	git bisect good &&
> +	test_cmp_rev HEAD B3 Bmerge
> +'
> +
> +test_done

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 07/21] bisect: plug the biggest memory leak
  2016-04-10 13:19 ` [PATCH v2 07/21] bisect: plug the biggest memory leak Stephan Beyer
@ 2016-04-15 21:18   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 21:18 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  bisect.c | 2 ++
>  1 file changed, 2 insertions(+)
>
> diff --git a/bisect.c b/bisect.c
> index 7996c29..901e4d3 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -984,6 +984,8 @@ int bisect_next_all(const char *prefix, int no_checkout)
>  		exit(10);
>  	}
>  
> +	free_commit_list(revs.commits);
> +
>  	nr = all - reaches - 1;
>  	steps = estimate_bisect_steps(all);
>  	printf("Bisecting: %d revision%s left to test after this "

While I do not think this is wrong per-se (i.e. it is clear that we
no longer need revs.commits), after this the function will return to
the top-level caller and exit immediately, and I do not see anything
that desperately wants to use as much memory as available (i.e. would
be helped by this piece of memory released early).  "the biggest"
may be an overstatement ;-)

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 08/21] bisect: make bisect compile if DEBUG_BISECT is set
  2016-04-10 13:19 ` [PATCH v2 08/21] bisect: make bisect compile if DEBUG_BISECT is set Stephan Beyer
@ 2016-04-15 21:22   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 21:22 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> Setting the macro DEBUG_BISECT to 1 enables debugging information
> for the bisect algorithm. The code did not compile due to struct
> changes.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---

Thanks.

This is something that we should do as a preparatory clean-up patch
before the series.  The real body of the series is more important
thing for us to spend review cycles on, and striving to slim it down
by having preparatory bits graduate early would help the process.


>  bisect.c | 6 +++---
>  1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/bisect.c b/bisect.c
> index 901e4d3..2f54d96 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -131,7 +131,7 @@ static void show_list(const char *debug, int counted, int nr,
>  		unsigned flags = commit->object.flags;
>  		enum object_type type;
>  		unsigned long size;
> -		char *buf = read_sha1_file(commit->object.sha1, &type, &size);
> +		char *buf = read_sha1_file(commit->object.oid.hash, &type, &size);
>  		const char *subject_start;
>  		int subject_len;
>  
> @@ -143,10 +143,10 @@ static void show_list(const char *debug, int counted, int nr,
>  			fprintf(stderr, "%3d", weight(p));
>  		else
>  			fprintf(stderr, "---");
> -		fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1));
> +		fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.oid.hash));
>  		for (pp = commit->parents; pp; pp = pp->next)
>  			fprintf(stderr, " %.*s", 8,
> -				sha1_to_hex(pp->item->object.sha1));
> +				sha1_to_hex(pp->item->object.oid.hash));
>  
>  		subject_len = find_commit_subject(buf, &subject_start);
>  		if (subject_len)

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 09/21] bisect: make algorithm behavior independent of DEBUG_BISECT
  2016-04-10 13:19 ` [PATCH v2 09/21] bisect: make algorithm behavior independent of DEBUG_BISECT Stephan Beyer
@ 2016-04-15 21:25   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 21:25 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> If DEBUG_BISECT is set to 1, bisect does not only show debug
> information but also changes the algorithm behavior: halfway()
> is always false.
>
> This commit makes the algorithm independent of DEBUG_BISECT.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---

Another good candidate for preliminary clean-up.

I do not remember what the rationale was to do this short-cut when I
wrote it at 1daa09d9 (make the previous optimization work also on
path-limited rev-list --bisect, 2007-03-23).  Thanks for spotting it.

>  bisect.c | 2 --
>  1 file changed, 2 deletions(-)
>
> diff --git a/bisect.c b/bisect.c
> index 2f54d96..1a13f35 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -101,8 +101,6 @@ static inline int halfway(struct commit_list *p, int nr)
>  	 */
>  	if (p->item->object.flags & TREESAME)
>  		return 0;
> -	if (DEBUG_BISECT)
> -		return 0;
>  	/*
>  	 * 2 and 3 are halfway of 5.
>  	 * 3 is halfway of 6 but 2 and 4 are not.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 10/21] bisect: get rid of recursion in count_distance()
  2016-04-10 13:19 ` [PATCH v2 10/21] bisect: get rid of recursion in count_distance() Stephan Beyer
@ 2016-04-15 21:31   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 21:31 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> Large repositories with a huge amount of merge commits in the
> bisection process could lead to stack overflows in git bisect.
> In order to prevent this, this commit uses an *iterative* version
> for counting the number of ancestors of a commit.

Yay!

> -/*
> - * This is a truly stupid algorithm, but it's only
> - * used for bisection, and we just don't care enough.
> - *
> - * We care just barely enough to avoid recursing for
> - * non-merge entries.
> - */
>  static int count_distance(struct commit_list *entry)
>  {
>  	int nr = 0;
> +	struct commit_list *todo = NULL;
> +	commit_list_append(entry->item, &todo);
>  
> -	while (entry) {
> -		struct commit *commit = entry->item;
> -		struct commit_list *p;
> +	while (todo) {
> +		struct commit *commit = pop_commit(&todo);
>  
> -		if (commit->object.flags & (UNINTERESTING | COUNTED))
> -			break;
> -		if (!(commit->object.flags & TREESAME))
> -			nr++;
> -		commit->object.flags |= COUNTED;
> -		p = commit->parents;
> -		entry = p;
> -		if (p) {
> -			p = p->next;
> -			while (p) {
> -				nr += count_distance(p);
> -				p = p->next;
> +		if (!(commit->object.flags & (UNINTERESTING | COUNTED))) {
> +			struct commit_list *p;
> +			if (!(commit->object.flags & TREESAME))
> +				nr++;
> +			commit->object.flags |= COUNTED;
> +
> +			for (p = commit->parents; p; p = p->next) {
> +				commit_list_insert(p->item, &todo);
>  			}
>  		}
>  	}
> @@ -287,7 +277,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>  	 * can reach.  So we do not have to run the expensive
>  	 * count_distance() for single strand of pearls.
>  	 *
> -	 * However, if you have more than one parents, you cannot
> +	 * However, if you have more than one parent, you cannot

Thanks.  This grammo is mine, back in 1c4fea3a (git-rev-list
--bisect: optimization, 2007-03-21)

> @@ -296,17 +286,16 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>  	 * way, and then fill the blanks using cheaper algorithm.
>  	 */
>  	for (p = list; p; p = p->next) {
> -		if (p->item->object.flags & UNINTERESTING)
> -			continue;
> -		if (weight(p) != -2)
> -			continue;
> -		weight_set(p, count_distance(p));
> -		clear_distance(list);
> +		if (!(p->item->object.flags & UNINTERESTING)
> +		 && (weight(p) == -2)) {
> +			weight_set(p, count_distance(p));
> +			clear_distance(list);
>  
> -		/* Does it happen to be at exactly half-way? */
> -		if (!find_all && halfway(p, nr))
> -			return p;
> -		counted++;
> +			/* Does it happen to be at exactly half-way? */
> +			if (!find_all && halfway(p, nr))
> +				return p;
> +			counted++;
> +		}
>  	}

I can buy collapsing two if() statements into one, but I'd prefer to
see us keep the structure:

	loop () {
                if (... || ...)
                        continue;
                quite a
                many
                operations
                here
	}

>  
>  	show_list("bisection 2 count_distance", counted, nr, list);

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 11/21] bisect: use struct node_data array instead of int array
  2016-04-10 13:19 ` [PATCH v2 11/21] bisect: use struct node_data array instead of int array Stephan Beyer
  2016-04-12 23:02   ` Christian Couder
@ 2016-04-15 21:47   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 21:47 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> This is a preparation for subsequent changes.
> During a bisection process, we want to augment commits with
> further information to improve speed.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---

Makes sense.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 12/21] bisect: replace clear_distance() by unique markers
  2016-04-10 13:19 ` [PATCH v2 12/21] bisect: replace clear_distance() by unique markers Stephan Beyer
  2016-04-12 23:20   ` Christian Couder
@ 2016-04-15 22:07   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:07 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> @@ -43,15 +43,17 @@ static int count_distance(struct commit_list *entry)
>  	int nr = 0;
>  	struct commit_list *todo = NULL;
>  	commit_list_append(entry->item, &todo);
> +	marker++;
>  
>  	while (todo) {
>  		struct commit *commit = pop_commit(&todo);
>  
> -		if (!(commit->object.flags & (UNINTERESTING | COUNTED))) {
> +		if (!(commit->object.flags & UNINTERESTING)
> +		 && node_data(commit)->marked != marker) {

Makes sense.

> @@ -123,10 +116,9 @@ static void show_list(const char *debug, int counted, int nr,
>  		const char *subject_start;
>  		int subject_len;
>  
> -		fprintf(stderr, "%c%c%c ",
> +		fprintf(stderr, "%c%c ",
>  			(flags & TREESAME) ? ' ' : 'T',
> -			(flags & UNINTERESTING) ? 'U' : ' ',
> -			(flags & COUNTED) ? 'C' : ' ');
> +			(flags & UNINTERESTING) ? 'U' : ' ');

As this one is for debugging, could we keep the output of 'C'
intact?

It is equivalent to

	commit->util && node_data(commit)->marked == marker ? 'C' : ' '

right?

This makes me wonder if node_data(commit) should return NULL instead
of asserting on commit->util in [11/21], by the way.  That would
make the above

	node_data(commit) && node_data(commit)->marked == marker
        ? 'C' : ' '

which may be easier to read.

Another small thing I overlooked in [11/21] is that the parameter to
node_data() helper should not be called "elem", which is typically
the name used to point at an element on a linked list structure such
as commit_list.  Call it "commit" instead, as that is typically the
way we call a single parameter/variable that appears in a function
that is "struct commit".

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 13/21] bisect: use commit instead of commit list as arguments when appropriate
  2016-04-10 13:19 ` [PATCH v2 13/21] bisect: use commit instead of commit list as arguments when appropriate Stephan Beyer
@ 2016-04-15 22:08   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:08 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> It makes no sense that the argument for count_distance() and
> halfway() is a commit list when only its first commit is relevant.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---

Makes sense (modulo perhaps s/elem/commit/).

>  bisect.c | 16 ++++++++--------
>  1 file changed, 8 insertions(+), 8 deletions(-)
>
> diff --git a/bisect.c b/bisect.c
> index 4209c75..2c1102f 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -38,11 +38,11 @@ static inline struct node_data *node_data(struct commit *elem)
>  	return (struct node_data *)elem->util;
>  }
>  
> -static int count_distance(struct commit_list *entry)
> +static int count_distance(struct commit *elem)
>  {
>  	int nr = 0;
>  	struct commit_list *todo = NULL;
> -	commit_list_append(entry->item, &todo);
> +	commit_list_append(elem, &todo);

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 14/21] bisect: extract get_distance() function from code duplication
  2016-04-10 13:19 ` [PATCH v2 14/21] bisect: extract get_distance() function from code duplication Stephan Beyer
@ 2016-04-15 22:08   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:08 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  bisect.c | 16 ++++++++++------
>  1 file changed, 10 insertions(+), 6 deletions(-)

Nice.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 15/21] bisect: introduce distance_direction()
  2016-04-10 13:19 ` [PATCH v2 15/21] bisect: introduce distance_direction() Stephan Beyer
@ 2016-04-15 22:10   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:10 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> We introduce the concept of rising and falling distances
> (in addition to a halfway distance).
> This will be useful in subsequent commits.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  bisect.c | 33 +++++++++++++++++++++++----------
>  1 file changed, 23 insertions(+), 10 deletions(-)
>
> diff --git a/bisect.c b/bisect.c
> index cfd406c..f737ce7 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -46,6 +46,28 @@ static inline int get_distance(struct commit *commit, int total)
>  	return distance;
>  }
>  
> +/*
> + * Return -1 if the distance is falling.
> + * (A falling distance means that the distance of the
> + *  given commit is larger than the distance of its
> + *  child commits.)
> + * Return 0 if the distance is halfway.
> + * Return 1 if the distance is rising.
> + */
> +static inline int distance_direction(struct commit *commit, int total)
> +{
> +	int doubled_diff = 2 * node_data(commit)->weight - total;
> +	if (doubled_diff < -1)
> +		return 1;
> +	if (doubled_diff > 1)
> +		return -1;
> +	/*
> +	 * 2 and 3 are halfway of 5.
> +	 * 3 is halfway of 6 but 2 and 4 are not.
> +	 */
> +	return 0;
> +}

Nice.  This makes it clear that to arrive at a half-way point, it is
pointless to dig a commit to its parents if the direction says it
will only take us further away from the half-way point.

>  static int count_distance(struct commit *elem)
>  {
>  	int nr = 0;
> @@ -92,16 +114,7 @@ static inline int halfway(struct commit *commit, int nr)
>  	 */
>  	if (commit->object.flags & TREESAME)
>  		return 0;
> -	/*
> -	 * 2 and 3 are halfway of 5.
> -	 * 3 is halfway of 6 but 2 and 4 are not.
> -	 */
> -	switch (2 * node_data(commit)->weight - nr) {
> -	case -1: case 0: case 1:
> -		return 1;
> -	default:
> -		return 0;
> -	}
> +	return !distance_direction(commit, nr);
>  }
>  
>  #if !DEBUG_BISECT

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 16/21] bisect: make total number of commits global
  2016-04-10 13:19 ` [PATCH v2 16/21] bisect: make total number of commits global Stephan Beyer
  2016-04-13 13:23   ` Christian Couder
@ 2016-04-15 22:11   ` Junio C Hamano
  2016-04-16  0:44   ` Junio C Hamano
  2 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:11 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> The total number of commits in a bisect process is a property of
> the bisect process. Making this property global helps to make the code
> clearer.

OK.

>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  bisect.c | 74 ++++++++++++++++++++++++++++++++++------------------------------
>  1 file changed, 39 insertions(+), 35 deletions(-)
>
> diff --git a/bisect.c b/bisect.c
> index f737ce7..2b415ad 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -23,6 +23,8 @@ static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
>  static const char *term_bad;
>  static const char *term_good;
>  
> +static int total;
> +
>  static unsigned marker;
>  
>  struct node_data {
> @@ -38,7 +40,7 @@ static inline struct node_data *node_data(struct commit *elem)
>  	return (struct node_data *)elem->util;
>  }
>  
> -static inline int get_distance(struct commit *commit, int total)
> +static inline int get_distance(struct commit *commit)
>  {
>  	int distance = node_data(commit)->weight;
>  	if (total - distance < distance)
> @@ -54,7 +56,7 @@ static inline int get_distance(struct commit *commit, int total)
>   * Return 0 if the distance is halfway.
>   * Return 1 if the distance is rising.
>   */
> -static inline int distance_direction(struct commit *commit, int total)
> +static inline int distance_direction(struct commit *commit)
>  {
>  	int doubled_diff = 2 * node_data(commit)->weight - total;
>  	if (doubled_diff < -1)
> @@ -107,25 +109,25 @@ static int count_interesting_parents(struct commit *commit)
>  	return count;
>  }
>  
> -static inline int halfway(struct commit *commit, int nr)
> +static inline int halfway(struct commit *commit)
>  {
>  	/*
>  	 * Don't short-cut something we are not going to return!
>  	 */
>  	if (commit->object.flags & TREESAME)
>  		return 0;
> -	return !distance_direction(commit, nr);
> +	return !distance_direction(commit);
>  }
>  
>  #if !DEBUG_BISECT
> -#define show_list(a,b,c,d) do { ; } while (0)
> +#define show_list(a,b,c) do { ; } while (0)
>  #else
> -static void show_list(const char *debug, int counted, int nr,
> +static void show_list(const char *debug, int counted,
>  		      struct commit_list *list)
>  {
>  	struct commit_list *p;
>  
> -	fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
> +	fprintf(stderr, "%s (%d/%d)\n", debug, counted, total);
>  
>  	for (p = list; p; p = p->next) {
>  		struct commit_list *pp;
> @@ -157,7 +159,7 @@ static void show_list(const char *debug, int counted, int nr,
>  }
>  #endif /* DEBUG_BISECT */
>  
> -static struct commit_list *best_bisection(struct commit_list *list, int nr)
> +static struct commit_list *best_bisection(struct commit_list *list)
>  {
>  	struct commit_list *p, *best;
>  	int best_distance = -1;
> @@ -169,7 +171,7 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr)
>  
>  		if (flags & TREESAME)
>  			continue;
> -		distance = get_distance(p->item, nr);
> +		distance = get_distance(p->item);
>  		if (distance > best_distance) {
>  			best = p;
>  			best_distance = distance;
> @@ -195,10 +197,10 @@ static int compare_commit_dist(const void *a_, const void *b_)
>  	return oidcmp(&a->commit->object.oid, &b->commit->object.oid);
>  }
>  
> -static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
> +static struct commit_list *best_bisection_sorted(struct commit_list *list)
>  {
>  	struct commit_list *p;
> -	struct commit_dist *array = xcalloc(nr, sizeof(*array));
> +	struct commit_dist *array = xcalloc(total, sizeof(*array));
>  	int cnt, i;
>  
>  	for (p = list, cnt = 0; p; p = p->next) {
> @@ -207,7 +209,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
>  
>  		if (flags & TREESAME)
>  			continue;
> -		distance = get_distance(p->item, nr);
> +		distance = get_distance(p->item);
>  		array[cnt].commit = p->item;
>  		array[cnt].distance = distance;
>  		cnt++;
> @@ -243,7 +245,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n
>   * or positive distance.
>   */
>  static struct commit_list *do_find_bisection(struct commit_list *list,
> -					     int nr, struct node_data *weights,
> +					     struct node_data *weights,
>  					     int find_all)
>  {
>  	int n, counted;
> @@ -262,7 +264,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>  				node_data(commit)->weight = 1;
>  				counted++;
>  				show_list("bisection 2 count one",
> -					  counted, nr, list);
> +					  counted, list);
>  			}
>  			/*
>  			 * otherwise, it is known not to reach any
> @@ -278,7 +280,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>  		}
>  	}
>  
> -	show_list("bisection 2 initialize", counted, nr, list);
> +	show_list("bisection 2 initialize", counted, list);
>  
>  	/*
>  	 * If you have only one parent in the resulting set
> @@ -300,15 +302,15 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>  			node_data(p->item)->weight = count_distance(p->item);
>  
>  			/* Does it happen to be at exactly half-way? */
> -			if (!find_all && halfway(p->item, nr))
> +			if (!find_all && halfway(p->item))
>  				return p;
>  			counted++;
>  		}
>  	}
>  
> -	show_list("bisection 2 count_distance", counted, nr, list);
> +	show_list("bisection 2 count_distance", counted, list);
>  
> -	while (counted < nr) {
> +	while (counted < total) {
>  		for (p = list; p; p = p->next) {
>  			struct commit_list *q;
>  			unsigned flags = p->item->object.flags;
> @@ -334,40 +336,41 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>  				node_data(p->item)->weight++;
>  				counted++;
>  				show_list("bisection 2 count one",
> -					  counted, nr, list);
> +					  counted, list);
>  			}
>  
>  			/* Does it happen to be at exactly half-way? */
> -			if (!find_all && halfway(p->item, nr))
> +			if (!find_all && halfway(p->item))
>  				return p;
>  		}
>  	}
>  
> -	show_list("bisection 2 counted all", counted, nr, list);
> +	show_list("bisection 2 counted all", counted, list);
>  
>  	if (!find_all)
> -		return best_bisection(list, nr);
> +		return best_bisection(list);
>  	else
> -		return best_bisection_sorted(list, nr);
> +		return best_bisection_sorted(list);
>  }
>  
>  struct commit_list *find_bisection(struct commit_list *list,
>  					  int *reaches, int *all,
>  					  int find_all)
>  {
> -	int nr, on_list;
> +	int on_list;
>  	struct commit_list *p, *best, *next, *last;
>  	struct node_data *weights;
>  
> +	total = 0;
>  	marker = 0;
>  
> -	show_list("bisection 2 entry", 0, 0, list);
> +	show_list("bisection 2 entry", 0, list);
>  
>  	/*
>  	 * Count the number of total and tree-changing items on the
>  	 * list, while reversing the list.
>  	 */
> -	for (nr = on_list = 0, last = NULL, p = list;
> +	for (on_list = 0, last = NULL, p = list;
>  	     p;
>  	     p = next) {
>  		unsigned flags = p->item->object.flags;
> @@ -378,23 +381,24 @@ struct commit_list *find_bisection(struct commit_list *list,
>  		p->next = last;
>  		last = p;
>  		if (!(flags & TREESAME))
> -			nr++;
> +			total++;
>  		on_list++;
>  	}
>  	list = last;
> -	show_list("bisection 2 sorted", 0, nr, list);
> +	show_list("bisection 2 sorted", 0, list);
>  
> -	*all = nr;
> +	*all = total;
>  	weights = (struct node_data *)xcalloc(on_list, sizeof(*weights));
>  
>  	/* Do the real work of finding bisection commit. */
> -	best = do_find_bisection(list, nr, weights, find_all);
> +	best = do_find_bisection(list, weights, find_all);
>  	if (best) {
>  		if (!find_all)
>  			best->next = NULL;
>  		*reaches = node_data(best->item)->weight;
>  	}
>  	free(weights);
> +
>  	return best;
>  }
>  
> @@ -931,7 +935,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
>  {
>  	struct rev_info revs;
>  	struct commit_list *tried;
> -	int reaches = 0, all = 0, nr, steps;
> +	int reaches = 0, nr, steps;
>  	const unsigned char *bisect_rev;
>  
>  	read_bisect_terms(&term_bad, &term_good);
> @@ -945,7 +949,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
>  
>  	bisect_common(&revs);
>  
> -	revs.commits = find_bisection(revs.commits, &reaches, &all,
> +	revs.commits = find_bisection(revs.commits, &reaches, &total,
>  				       !!skipped_revs.nr);
>  	revs.commits = managed_skipped(revs.commits, &tried);
>  
> @@ -963,7 +967,7 @@ int bisect_next_all(const char *prefix, int no_checkout)
>  		exit(1);
>  	}
>  
> -	if (!all) {
> +	if (!total) {
>  		fprintf(stderr, "No testable commit found.\n"
>  			"Maybe you started with bad path parameters?\n");
>  		exit(4);
> @@ -982,8 +986,8 @@ int bisect_next_all(const char *prefix, int no_checkout)
>  
>  	free_commit_list(revs.commits);
>  
> -	nr = all - reaches - 1;
> -	steps = estimate_bisect_steps(all);
> +	nr = total - reaches - 1;
> +	steps = estimate_bisect_steps(total);
>  	printf("Bisecting: %d revision%s left to test after this "
>  	       "(roughly %d step%s)\n", nr, (nr == 1 ? "" : "s"),
>  	       steps, (steps == 1 ? "" : "s"));

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 17/21] bisect: rename count_distance() to compute_weight()
  2016-04-10 13:19 ` [PATCH v2 17/21] bisect: rename count_distance() to compute_weight() Stephan Beyer
  2016-04-13 13:32   ` Christian Couder
@ 2016-04-15 22:12   ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:12 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> Let us use the term "weight" for the number of ancestors
> of each commit, and "distance" for the number
> min{weight, #commits - weight}. (Bisect finds the commit
> with maximum distance.)
>
> In these terms, "count_distance()" is the wrong name of
> the function. So it is renamed to "compute_weight()",
> and it also directly sets the computed weight.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  bisect.c | 11 ++++++-----
>  1 file changed, 6 insertions(+), 5 deletions(-)

Makes sense.  We can think of the "distance" the distance from the
periphery of the graph we are looking at, and "bisection" is to find
a point close to the center of the graph.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 18/21] bisect: prepare for different algorithms based on find_all
  2016-04-10 13:19 ` [PATCH v2 18/21] bisect: prepare for different algorithms based on find_all Stephan Beyer
@ 2016-04-15 22:36   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:36 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> This is a preparation commit with copy-and-paste involved.
> The function do_find_bisection() is changed and copied to
> two almost similar functions compute_all_weights() and
> compute_relevant_weights().
>
> The function compute_relevant_weights() stops when a
> "halfway" commit is found.
>
> To keep the code clean, the halfway commit is not returned
> and has to be found by best_bisection() afterwards.
> This results in a singular additional O(#commits)-time
> overhead but this will be outweighed by the following
> changes to compute_relevant_weights().
>
> It is necessary to keep compute_all_weights() for the
> "git rev-list --bisect-all" command. All other bisect-related
> commands will use compute_relevant_weights().
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>  bisect.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
>  1 file changed, 97 insertions(+), 19 deletions(-)
>
> diff --git a/bisect.c b/bisect.c
> index a254f28..c6bad43 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -179,6 +179,7 @@ static struct commit_list *best_bisection(struct commit_list *list)
>  		}
>  	}
>  
> +	best->next = NULL;
>  	return best;
>  }

At this point of this patch it is unclear how this change is
relevant.  I'll read on without complaining but with puzzlement.

> @@ -245,9 +246,8 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list)
>   * unknown.  After running compute_weight() first, they will get zero
>   * or positive distance.
>   */
> -static struct commit_list *do_find_bisection(struct commit_list *list,
> -					     struct node_data *weights,
> -					     int find_all)
> +static void compute_all_weights(struct commit_list *list,
> +				struct node_data *weights)
>  {
>  	int n, counted;
>  	struct commit_list *p;
> @@ -301,10 +301,88 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>  		if (!(p->item->object.flags & UNINTERESTING)
>  		 && (node_data(p->item)->weight == -2)) {
>  			compute_weight(p->item);
> +			counted++;
> +		}
> +	}
> +
> +	show_list("bisection 2 compute_weight", counted, list);
> +
> +	while (counted < total) {
> +		for (p = list; p; p = p->next) {
> +			struct commit_list *q;
> +			unsigned flags = p->item->object.flags;
> +
> +			if (0 <= node_data(p->item)->weight)
> +				continue;
> +			for (q = p->item->parents; q; q = q->next) {
> +				if (q->item->object.flags & UNINTERESTING)
> +					continue;
> +				if (0 <= node_data(q->item)->weight)
> +					break;
> +			}
> +			if (!q)
> +				continue;
> +
> +			/*
> +			 * weight for p is unknown but q is known.
> +			 * add one for p itself if p is to be counted,
> +			 * otherwise inherit it from q directly.
> +			 */

This is not a new problem, but "q" in the comment should be
"q->item"; my bad.

> +			node_data(p->item)->weight = node_data(q->item)->weight;
> +			if (!(flags & TREESAME)) {
> +				node_data(p->item)->weight++;
> +				counted++;
> +				show_list("bisection 2 count one",
> +					  counted, list);
> +			}

This is not a new problem, and I do admit that I wrote the original
at 1daa09d9 (make the previous optimization work also on
path-limited rev-list --bisect, 2007-03-23), but this makes me
wonder why counted++ is not done for p that is treesame.

 ... goes and looks ...

Ahh, the original while loop was counting upto "nr", which is
different from total.  When the traversal is pathspec limited, I
counted in the original (and probably in 'master' branch before your
series) the number of tree-changing commits in 'nr' which can be
smaller than 'total'.  That is why skipping counted++ on a treesame
commit was the correct thing to do.

Is it possible that you did not test --bisect-all with pathspec?  I
have a suspicion that the above loop would not terminate because of
this change.  If that is the case, either "make total global and get
rid of nr" needs to be fixed, or (probably better) move counted++
out of this if statement.  At this point, counted indicates "how
many commits out of 'total' we have computed weight for?", so the
latter would make more sense to me, even though either is OK.

The "priming the well" step for the "no interesting parent--set
weight to either 0 or 1" also needs to adjust counted++, I suspect.

> +		}
> +	}
> +	show_list("bisection 2 counted all", counted, list);
> +}
> +
> +/* At the moment this is basically the same as compute_all_weights()
> + * but with a halfway shortcut */

/*
 * We write our multi-line comments like this.
 * The first and last lines have only asterisk
 * and slash.
 */

> +static void compute_relevant_weights(struct commit_list *list,
> +				     struct node_data *weights)
> +{
> +	int n, counted;
> +	struct commit_list *p;
> +
> +	counted = 0;
> +
> +	for (n = 0, p = list; p; p = p->next) {
> +		struct commit *commit = p->item;
> +		unsigned flags = commit->object.flags;
> +
> +		commit->util = &weights[n++];
> +		switch (count_interesting_parents(commit)) {
> +		case 0:
> +			if (!(flags & TREESAME)) {
> +				node_data(commit)->weight = 1;
> +				counted++;
> +				show_list("bisection 2 count one",
> +					  counted, list);
> +			}
> +			break;
> +		case 1:
> +			node_data(commit)->weight = -1;
> +			break;
> +		default:
> +			node_data(commit)->weight = -2;
> +			break;
> +		}
> +	}
> +
> +	show_list("bisection 2 initialize", counted, list);
> +
> +	for (p = list; p; p = p->next) {
> +		if (!(p->item->object.flags & UNINTERESTING)
> +		 && (node_data(p->item)->weight == -2)) {
> +			compute_weight(p->item);
>  
>  			/* Does it happen to be at exactly half-way? */
> -			if (!find_all && halfway(p->item))
> -				return p;
> +			if (halfway(p->item))
> +				return;

It is somewhat curious why this, after finding the desired commit as
p->item, does not return it.  If it is already known that we have
half-way one, can't we return it immediately (and when we fall
through without finding exactly the half-way one, we signal the
caller that it needs best_bisection() among the list by returning a
NULL or something?

But probably that is optimizing it prematurely.  I dunno.

>  			counted++;
>  		}
>  	}
> @@ -341,17 +419,11 @@ static struct commit_list *do_find_bisection(struct commit_list *list,
>  			}
>  
>  			/* Does it happen to be at exactly half-way? */
> -			if (!find_all && halfway(p->item))
> -				return p;
> +			if (halfway(p->item))
> +				return;
>  		}
>  	}
> -
>  	show_list("bisection 2 counted all", counted, list);
> -
> -	if (!find_all)
> -		return best_bisection(list);
> -	else
> -		return best_bisection_sorted(list);
>  }
>  
>  struct commit_list *find_bisection(struct commit_list *list,
> @@ -365,6 +437,9 @@ struct commit_list *find_bisection(struct commit_list *list,
>  	total = 0;
>  	marker = 0;
>  
> +	if (!list)
> +		return NULL;
> +
>  	show_list("bisection 2 entry", 0, list);
>  
>  	/*
> @@ -391,13 +466,16 @@ struct commit_list *find_bisection(struct commit_list *list,
>  	*all = total;
>  	weights = (struct node_data *)xcalloc(on_list, sizeof(*weights));
>  
> -	/* Do the real work of finding bisection commit. */
> -	best = do_find_bisection(list, weights, find_all);
> -	if (best) {
> -		if (!find_all)
> -			best->next = NULL;
> -		*reaches = node_data(best->item)->weight;
> +	if (find_all) {
> +		compute_all_weights(list, weights);
> +		best = best_bisection_sorted(list);
> +	} else {
> +		compute_relevant_weights(list, weights);
> +		best = best_bisection(list);
>  	}
> +	assert(best);
> +	*reaches = node_data(best->item)->weight;
> +
>  	free(weights);
>  
>  	return best;

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights
  2016-04-10 13:19 ` [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights Stephan Beyer
  2016-04-13 14:11   ` Christian Couder
@ 2016-04-15 22:47   ` Junio C Hamano
  2016-04-15 22:49   ` Junio C Hamano
  2016-04-26 18:27   ` Junio C Hamano
  3 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:47 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> The idea is to reverse the DAG and perform a traversal
> starting on all sources of the reversed DAG.

Please clarify what you mean by "sources" here.  Those who read log
message in Git context would know that you mean the commit graph by
"DAG", and "reversed DAG" means "having reverse linkage that lets
you find children given a parent", so "DAG" does not need such a
clarification.

> We walk from the bottom commits, incrementing the weight while
> walking on a part of the graph that is single strand of pearls,
> or doing the "count the reachable ones the hard way" using
> compute_weight() when we hit a merge commit.

Makes sense.  So instead of "all sources", you can say "perform a
traversal starting from the bottom commits, going from parent to its
children".

> A traversal ends when the computed weight is falling or halfway.
> This way, commits with too high weight to be relevant are never
> visited (and their weights are never computed).

Yup, beautiful.

> diff --git a/bisect.c b/bisect.c
> index c6bad43..9487ba9 100644
> --- a/bisect.c
> +++ b/bisect.c
> @@ -30,6 +30,9 @@ static unsigned marker;
>  struct node_data {
>  	int weight;
>  	unsigned marked;
> +	unsigned parents;
> +	unsigned visited : 1;
> +	struct commit_list *children;
>  };
>  
>  #define DEBUG_BISECT 0

> +static inline void commit_list_insert_unique(struct commit *item,
> +				      struct commit_list **list)
> +{
> +	if (!*list || item < (*list)->item) /* empty list or item will be first */
> +		commit_list_insert(item, list);
> +	else if (item != (*list)->item) { /* item will not be first or not inserted */
> +		struct commit_list *p = *list;
> +		for (; p->next && p->next->item < item; p = p->next);
> +		if (!p->next || item != p->next->item) /* not already inserted */
> +			commit_list_insert(item, &p->next);
> +	}
> +}

Hmmmmmmmmmmmmmmmmmmmmmmmmmmmm.

When you have two commits, struct commit *one, and struct commit
*two, is it safe to do a pointer comparison for ordering?

I know it would work in practice, but I am worried about language
lawyers (and possibly static analysis tools) barking at this code.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights
  2016-04-10 13:19 ` [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights Stephan Beyer
  2016-04-13 14:11   ` Christian Couder
  2016-04-15 22:47   ` Junio C Hamano
@ 2016-04-15 22:49   ` Junio C Hamano
  2016-04-26 18:27   ` Junio C Hamano
  3 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:49 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> +static struct commit *extract_merge_to_queue(struct commit_list **merges)
> +{
> +	assert(merges);
> +
> +	struct commit_list *p, *q;
> +	struct commit *found;
> +

"gcc -Werror -Wdecl-after-statement" will barf at this.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 21/21] bisect: get back halfway shortcut
  2016-04-10 13:24   ` [PATCH v2 21/21] bisect: get back halfway shortcut Stephan Beyer
@ 2016-04-15 22:53     ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-15 22:53 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> The documentation says that when the maximum possible distance
> is found, the algorithm stops immediately. That feature is
> reestablished by this commit.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---
>
> Notes:
>     I plugged a memory leak here.

... relative to patch series v1, I presume?

> @@ -391,7 +391,13 @@ static void traversal_up_to_merges(struct commit_list *queue,
>  		}
>  
>  		update_best_bisection(top);
> +		if (distance_direction(top) == 0) { // halfway

Say /* halfway */ without // double-slash comment.

The remainder of the patch makes sense to me.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 16/21] bisect: make total number of commits global
  2016-04-10 13:19 ` [PATCH v2 16/21] bisect: make total number of commits global Stephan Beyer
  2016-04-13 13:23   ` Christian Couder
  2016-04-15 22:11   ` Junio C Hamano
@ 2016-04-16  0:44   ` Junio C Hamano
  2 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-16  0:44 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> The total number of commits in a bisect process is a property of
> the bisect process. Making this property global helps to make the code
> clearer.
>
> Signed-off-by: Stephan Beyer <s-beyer@gmx.net>
> ---

After wondring about count++ vs nr, I re-read this one.

This patch is mislabled.

Making it global is a lessor, supposed-to-be-no-op change, but the
bigger change is that the definition of "total" is silently changed.

The definition of mid-point was based on 'nr' in the original code,
which counted only the tree-changing commits, and with this patch,
it is based on 'total', which now only counts the tree-changing
commits, so things are internally consistent, and the loop I was
puzzled with, "while (counted < total)", would properly terminate.

Perhaps things become cleaner and easier to understand if this was
split into two steps.  One that changes the meaning of 'total' (and
removes 'nr'), and the other that makes 'total' a global.

Thanks.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev
  2016-04-15 20:00   ` Junio C Hamano
@ 2016-04-24 19:51     ` Stephan Beyer
  2016-04-25 18:08       ` Junio C Hamano
  0 siblings, 1 reply; 56+ messages in thread
From: Stephan Beyer @ 2016-04-24 19:51 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Christian Couder

Hi,

On 04/15/2016 10:00 PM, Junio C Hamano wrote:
> Stephan Beyer <s-beyer@gmx.net> writes:
> 
>> test_cmp_rev() took exactly two parameters, the expected revision
>> and the revision to test. This commit generalizes this function
>> such that it takes any number of at least two revisions: the
>> expected one and a list of actual ones. The function returns true
>> if and only if at least one actual revision coincides with the
>> expected revision.
> 
> There may be cases where you want to find the expected one among
> various things you actually have (which is what the above talks
> about; it is like "list-what-I-actually-got | grep what-i-want"),
> but an equally useful use case would be "I would get only one
> outcome from test, I anticipate one of these things, all of which is
> OK, but I cannot dictate which one of them should come out" (it is
> like "list-what-I-can-accept | grep what-I-actually-got").

I see that these are strictly speaking (slightly) different semantics
but in the end it boils down to be the same, or am I missing anything?

> I am not enthused by the new test that implements the "match one
> against multi" check only in one way among these possible two to
> squat on a very generic name, test_cmp_rev.
> 
> The above _may_ appear a non-issue until you realize one thing that
> is there to help those who debug the tests, which is ...
> 
>> While at it, the side effect of generating two (temporary) files
>> is removed.
> 
> That is not strictly a side effect.  test_cmp allows you to see what
> was expected and what you actually had when the test failed (we
> always compare expect with actual and not the other way around, so
> that "diff -u expect actual" would show how the actual behaviour
> diverted from our expectation in a natural way).

I was referring to *generating the files* as a side effect. I did not
even think about the fact that "diff" in the original code does not only
return an exit code but that it also generates output that can be used
as "helpful diagnostic information" (referring to Eric Sunshine's mail
here). I was not aware that the Git tests should -- besides testing --
already include "tools" for easier debugging in case of a failure... So
dropping this information was not intentional.

> Something with the semantics of these two:
> 
> 	test_revs_have_expected () {
>         	expect=$1
> 		shift
> 		git rev-parse "$@" | grep -e "$expect" >/dev/null && return
> 		echo >&2 "The expected '$1' is not found in:"
>                 printf >&2 " '%s'\n", "$@"
>                 return 1
> 	}
> 
> 	test_rev_among_expected () {
> 		actual=$1
>                 shift
> 		git rev-parse "$@" | grep -e "$actual" >/dev/null && return
> 		echo >&2 "'$1' is not among expected ones:"
>                 printf >&2 " '%s'\n", "$@"
>                 return 1
> 	}
> 
> might be more appropriate.

Ah! That's what I meant above. The code is copy&paste besides variable
naming and the output "title". Such code duplication for the sake of
"easier debugging" in case of a failure?

Also I wonder if test authors in the future would really know *which*
one is the right one to use. In the end, either one of these two will
just be used arbitrarily (and I wouldn't even think there's anything bad
about it, because it *is* the same logic). I think this distinction is
like having two algorithms doing the same but with a different name.
Something you do NOT really want.

So I'd vote against a distinction of these two "cases", but I have no
problem with re-adding "debug" information (like you did in your code
examples).

Thanks!
Stephan

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev
  2016-04-24 19:51     ` Stephan Beyer
@ 2016-04-25 18:08       ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-25 18:08 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

> Hi,
>
> On 04/15/2016 10:00 PM, Junio C Hamano wrote:
>> Stephan Beyer <s-beyer@gmx.net> writes:
>> 
>>> test_cmp_rev() took exactly two parameters, the expected revision
>>> and the revision to test. This commit generalizes this function
>>> such that it takes any number of at least two revisions: the
>>> expected one and a list of actual ones. The function returns true
>>> if and only if at least one actual revision coincides with the
>>> expected revision.
>> 
>> There may be cases where you want to find the expected one among
>> various things you actually have (which is what the above talks
>> about; it is like "list-what-I-actually-got | grep what-i-want"),
>> but an equally useful use case would be "I would get only one
>> outcome from test, I anticipate one of these things, all of which is
>> OK, but I cannot dictate which one of them should come out" (it is
>> like "list-what-I-can-accept | grep what-I-actually-got").
>
> I see that these are strictly speaking (slightly) different semantics
> but in the end it boils down to be the same, or am I missing anything?
>
>> I am not enthused by the new test that implements the "match one
>> against multi" check only in one way among these possible two to
>> squat on a very generic name, test_cmp_rev.
>> 
>> The above _may_ appear a non-issue until you realize one thing that
>> is there to help those who debug the tests, which is ...
>> 
>>> While at it, the side effect of generating two (temporary) files
>>> is removed.
>> 
>> That is not strictly a side effect.  test_cmp allows you to see what
>> was expected and what you actually had when the test failed (we
>> always compare expect with actual and not the other way around, so
>> that "diff -u expect actual" would show how the actual behaviour
>> diverted from our expectation in a natural way).
>
> I was referring to *generating the files* as a side effect. I did not
> even think about the fact that "diff" in the original code does not only
> return an exit code but that it also generates output that can be used
> as "helpful diagnostic information" (referring to Eric Sunshine's mail
> here). I was not aware that the Git tests should -- besides testing --
> already include "tools" for easier debugging in case of a failure... So
> dropping this information was not intentional.
>
>> Something with the semantics of these two:
>> 
>> 	test_revs_have_expected () {
>>         	expect=$1
>> 		shift
>> 		git rev-parse "$@" | grep -e "$expect" >/dev/null && return
>> 		echo >&2 "The expected '$1' is not found in:"
>>                 printf >&2 " '%s'\n", "$@"
>>                 return 1
>> 	}
>> 
>> 	test_rev_among_expected () {
>> 		actual=$1
>>                 shift
>> 		git rev-parse "$@" | grep -e "$actual" >/dev/null && return
>> 		echo >&2 "'$1' is not among expected ones:"
>>                 printf >&2 " '%s'\n", "$@"
>>                 return 1
>> 	}
>> 
>> might be more appropriate.
>
> Ah! That's what I meant above. The code is copy&paste besides variable
> naming and the output "title". Such code duplication for the sake of
> "easier debugging" in case of a failure?
>
> Also I wonder if test authors in the future would really know *which*
> one is the right one to use.

I saw that even you were originally confused about it ;-).

In your proposed log message, you talk about "the expected, and list
of actual ones", which can only mean "there may be multiple answers
from the command (e.g. "merge-base --all") and we only require that
one of the answers is the expected one", which is why among the two
necessary functions I listed "test_revs_have_expected" above first,
but I think most (if not all) of the invocations of the multi-match
form in your patch actually wanted "test_rev_among_expected"
variant, i.e. "there will be one answer from the command, but there
are multiple acceptable answers, all of them valid".

I do not think test authors who understands the reason why we always
say "test_cmp actual expect" and not the other way around will share
the same confusion (and now you were explained and understood why
"diff" in the original was given the expected and actual result in
that order, you no longer are confused wrt this).

And no, this is not "for the sake of easier debugging in case of a
failure".  It is about knowing what you are doing--are you going to
have multiple answers and making sure one right one appears in it,
or are you going to have one answer and allowing any one of multiple
valid ones?  These two are quite different things and it helps the
readers of the test to know which one is being used.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights
  2016-04-10 13:19 ` [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights Stephan Beyer
                     ` (2 preceding siblings ...)
  2016-04-15 22:49   ` Junio C Hamano
@ 2016-04-26 18:27   ` Junio C Hamano
  3 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2016-04-26 18:27 UTC (permalink / raw)
  To: Stephan Beyer; +Cc: git, Christian Couder

Stephan Beyer <s-beyer@gmx.net> writes:

>  struct commit_list *find_bisection(struct commit_list *list,
> @@ -470,8 +541,11 @@ struct commit_list *find_bisection(struct commit_list *list,
>  		compute_all_weights(list, weights);
>  		best = best_bisection_sorted(list);
>  	} else {
> +		int i;
>  		compute_relevant_weights(list, weights);
>  		best = best_bisection(list);
> +		for (i = 0; i < on_list; i++) /* cleanup */
> +			free_commit_list(weights[i].children);
>  	}
>  	assert(best);
>  	*reaches = node_data(best->item)->weight;

One thing I forgot to mention is that we now may want to reconsider
what the first loop in this function does.  It used to be that the
purpose of the loop is to "count the number of total and
tree-changing items on the list while reversing the list" as the
comment says.  While it is still necessary to count the items (by
the way, with 16/21 you made these two numbers identical, i.e. there
no longer is a separate 'total' but your 'total' now actually means
the number of tree-changing items), I do not know if the "reverse"
would still be a good fit for the performance characteristic of the
new algorithm.

The list-reversal there was done as an optimization to make sure
that older ones are processed early to avoid looping too much just
to follow the list to find a single-parent commit whose parent's
weight is already known, as the only meaningful optimization in the
original algorithm was the "we can increment one without doing the
costly graph re-traversal for single-strand-of-pearls".  That
optimization may no longer relevant (or it could even be harmful)
as you traverse the graph in reverse.

^ permalink raw reply	[flat|nested] 56+ messages in thread

end of thread, other threads:[~2016-04-26 18:27 UTC | newest]

Thread overview: 56+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-04-10 13:18 [PATCH v2 00/21] git bisect improvements Stephan Beyer
2016-04-10 13:18 ` [PATCH v2 01/21] bisect: write about `bisect next` in documentation Stephan Beyer
2016-04-10 13:18 ` [PATCH v2 02/21] bisect: allow 'bisect run' if no good commit is known Stephan Beyer
2016-04-10 13:18 ` [PATCH v2 03/21] t/test-lib-functions.sh: generalize test_cmp_rev Stephan Beyer
2016-04-11  0:07   ` Eric Sunshine
2016-04-15 20:00   ` Junio C Hamano
2016-04-24 19:51     ` Stephan Beyer
2016-04-25 18:08       ` Junio C Hamano
2016-04-10 13:18 ` [PATCH v2 04/21] t: use test_cmp_rev() where appropriate Stephan Beyer
2016-04-11  0:07   ` Eric Sunshine
2016-04-15 20:48   ` Junio C Hamano
2016-04-10 13:18 ` [PATCH v2 05/21] t6030: generalize test to not rely on current implementation Stephan Beyer
2016-04-10 13:47   ` Torsten Bögershausen
2016-04-10 19:16     ` Junio C Hamano
2016-04-10 19:37       ` Stephan Beyer
2016-04-11  0:23     ` Eric Sunshine
2016-04-15 21:07   ` Junio C Hamano
2016-04-10 13:18 ` [PATCH v2 06/21] bisect: add test for the bisect algorithm Stephan Beyer
2016-04-15 21:13   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 07/21] bisect: plug the biggest memory leak Stephan Beyer
2016-04-15 21:18   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 08/21] bisect: make bisect compile if DEBUG_BISECT is set Stephan Beyer
2016-04-15 21:22   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 09/21] bisect: make algorithm behavior independent of DEBUG_BISECT Stephan Beyer
2016-04-15 21:25   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 10/21] bisect: get rid of recursion in count_distance() Stephan Beyer
2016-04-15 21:31   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 11/21] bisect: use struct node_data array instead of int array Stephan Beyer
2016-04-12 23:02   ` Christian Couder
2016-04-15 21:47   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 12/21] bisect: replace clear_distance() by unique markers Stephan Beyer
2016-04-12 23:20   ` Christian Couder
2016-04-15 22:07   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 13/21] bisect: use commit instead of commit list as arguments when appropriate Stephan Beyer
2016-04-15 22:08   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 14/21] bisect: extract get_distance() function from code duplication Stephan Beyer
2016-04-15 22:08   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 15/21] bisect: introduce distance_direction() Stephan Beyer
2016-04-15 22:10   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 16/21] bisect: make total number of commits global Stephan Beyer
2016-04-13 13:23   ` Christian Couder
2016-04-15 22:11   ` Junio C Hamano
2016-04-16  0:44   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 17/21] bisect: rename count_distance() to compute_weight() Stephan Beyer
2016-04-13 13:32   ` Christian Couder
2016-04-15 22:12   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 18/21] bisect: prepare for different algorithms based on find_all Stephan Beyer
2016-04-15 22:36   ` Junio C Hamano
2016-04-10 13:19 ` [PATCH v2 19/21] bisect: use a bottom-up traversal to find relevant weights Stephan Beyer
2016-04-13 14:11   ` Christian Couder
2016-04-15 22:47   ` Junio C Hamano
2016-04-15 22:49   ` Junio C Hamano
2016-04-26 18:27   ` Junio C Hamano
2016-04-10 13:24 ` [PATCH v2 20/21] bisect: compute best bisection in compute_relevant_weights() Stephan Beyer
2016-04-10 13:24   ` [PATCH v2 21/21] bisect: get back halfway shortcut Stephan Beyer
2016-04-15 22:53     ` Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).