All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
* [RFC] t7410: 210 tests for various 'git submodule update' scenarios
@ 2014-04-16  0:54 Johan Herland
  2014-04-16 17:21 ` W. Trevor King
  2014-04-17 16:04 ` Jens Lehmann
  0 siblings, 2 replies; 5+ messages in thread
From: Johan Herland @ 2014-04-16  0:54 UTC (permalink / raw
  To: git
  Cc: Johan Herland, W. Trevor King, Jens Lehmann, Heiko Voigt,
	Junio C Hamano

This is a work-in-progress to flesh out (and promote discussion about)
the expected behaviors for all possible scenarios in which
'git submodule update' might be run.

More details in the top of t/t7410-submodule-update-scenarios.sh

Cc: W. Trevor King <wking@tremily.us>
Cc: Jens Lehmann <Jens.Lehmann@web.de>
Cc: Heiko Voigt <hvoigt@hvoigt.net>
Cc: Junio C Hamano <gitster@pobox.com>

---

Hi,

This started as an exercise for myself to figure out all the various
corner cases under which "git submodule update" may be run. Then, as I
found some cases where the current behavior was not immediately obvious
to me, and some other cases where the current behaviour is likely buggy
and/or Wrong(tm), I figured these tests might serve as a backdrop for a
closer discussion of those cases.

I've attempted to include all variables/axes that combine to directly
affect the behavior of 'submodule update'. I've excluded some variables
whose effect on 'submodule update' is completely orthogonal/independent
(i.e. how 'submodule update' is affected by unstaged/uncommitted
changes in the submodule). That said, there might be more variables
that I have missed in my analysis...

I've attempted to split the areas to be discussed into 3 todo items and
7 discussion items (labeled T1 - T3. and D1 - D7, respectively), and
I've cross-referenced them to the specific test cases that are affected
by each todo/discussion item.

I'll leave it up to Jens and Junio to determine whether this patch
is worth applying or not. There is certainly considerable overlap
between these tests and existing tests for 'submodule update'. There's
also plenty of room for refactoring and deduplication in this file,
itself, although I hope the current format of the tests makes it fairly
easy to see what is being tested, and what is the expected behavior.

Have fun! :)

...Johan


 t/t7410-submodule-update-scenarios.sh | 2859 +++++++++++++++++++++++++++++++++
 1 file changed, 2859 insertions(+)
 create mode 100755 t/t7410-submodule-update-scenarios.sh

diff --git a/t/t7410-submodule-update-scenarios.sh b/t/t7410-submodule-update-scenarios.sh
new file mode 100755
index 0000000..22056cc
--- /dev/null
+++ b/t/t7410-submodule-update-scenarios.sh
@@ -0,0 +1,2859 @@
+#!/bin/sh
+#
+# Copyright (c) 2014 Johan Herland
+#
+
+test_description='Test "git submodule update" in a variety of scenarios
+
+This test attempts to map out the various scenarios in which submodule update
+is run, and verify their expected behaviour (according to the git submodule
+documentation).
+'
+
+. ./test-lib.sh
+
+# First a textual representation of the various cases we are testing:
+#
+# The factors/variables being explored are:
+#  - submodule.sub.branch
+#     1.* - unset
+#     2.* - == foo (exists upstream)
+#     3.* - == bar (MISSING upstream)
+#  - submodule.sub.update
+#     ?.1.* - none
+#     ?.2.* - checkout
+#     ?.3.* - merge
+#     ?.4.* - rebase
+#     ?.5.* - !command
+#  - remote mode (whether or not --remote is given on command-line)
+#     ?.?.1.* - disabled
+#     ?.?.2.* - enabled
+#  - current state of submodule:
+#     ?.?.?.1 - not yet cloned
+#     ?.?.?.2 - cloned, detached, HEAD == gitlink
+#     ?.?.?.3 - cloned, detached, HEAD != gitlink
+#     ?.?.?.4 - cloned, on branch foo (exists upstream), HEAD == gitlink
+#     ?.?.?.5 - cloned, on branch foo (exists upstream), HEAD != gitlink
+#     ?.?.?.6 - cloned, on branch bar (MISSING upstream), HEAD == gitlink
+#     ?.?.?.7 - cloned, on branch bar (MISSING upstream), HEAD != gitlink
+#
+# The above variables are combined to enumerate all possible cases, from
+# 1.1.1.1 to 3.5.2.7 (yes, that's 210 separate test cases). Each test case is
+# named with its number followed by a short description of its expected outcome.
+
+# TODO ITEMS:
+#
+# T1: Rename "master" branch to "default" to test correct behaviour when
+#     submodule's upstream has no master branch.
+#     Affects: pre, 1.3.1.1, 1.3.2.1, 1.4.1.1, 1.4.2.1
+#
+# T2: Test with submodule.<name>.url != submodule's remote.origin.url. Does
+#     "submodule update --remote" sync with submodule.<name>.url, or with the
+#     submodule's origin? (or with the submodule's current branch's upstream)?
+#
+# T3: Fix uninitialized submodule state after attempting to clone from upstream
+#     where .branch is missing.
+#     Affects: pre, 3.2.2.1, 3.3.2.1, 3.4.2.1, 3.5.2.1
+
+# DISCUSSION ITEMS:
+#
+# D1: When submodule is already at right commit, checkout-mode currently does
+#     nothing. Should it instead detach, even when no update is needed?
+#     Affects: 1.2.1.4, 1.2.1.6, 2.2.1.4, 2.2.1.6, 3.2.1.4, 3.2.1.6
+#
+# D2: Should all/some of 1.3.*/1.4.* abort/error because we don't know what to
+#     merge/rebase with (because .branch is unset)? Or is the current default
+#     to origin/HEAD OK?
+#     Affects: 1.3.*, 1.4.*
+#
+# D3: When submodule is already at right commit, merge/rebase-mode currently
+#     does nothing. Should it do something else (e.g. not leave submodule
+#     detached, or checked out on the "wrong" branch (i.e. != .branch))?
+#     (This discussion point is related to D1, D5 and D6)
+#     Affects: 1.3.1.2, 1.3.1.4, 1.3.1.6,
+#              1.4.1.2, 1.4.1.4, 1.4.1.6,
+#              2.3.1.2, 2.3.1.4, 2.3.1.6, 2.3.2.3, 2.3.2.5, 2.3.2.7,
+#              2.4.1.2, 2.4.1.4, 2.4.1.6, 2.4.2.3, 2.4.2.5, 2.4.2.7,
+#              3.3.1.2, 3.3.1.4, 3.3.1.6
+#              3.4.1.2, 3.4.1.4, 3.4.1.6
+#
+# D4: When 'submodule update' performs a clone to populate a submodule, it
+#     currently also creates a default branch (named after origin/HEAD) in
+#     that submodule, EVEN WHEN THAT BRANCH WILL NEVER BE USED (e.g. because
+#     we're in checkout-mode and submodule will always be detached). Is this
+#     good, or should the clone performed by 'submodule update' skip the
+#     automatic local branch creation?
+#     Affects: 1.2.*.1, 1.3.*.1, 1.4.*.1, 1.5.*.1,
+#              2.2.*.1, 2.3.*.1, 2.4.*.1, 2.5.*.1,
+#              3.2.1.1, 3.3.1.1, 3.4.1.1, 3.5.1.1
+#
+# D5: When in merge/rebase-mode, and 'submodule update' actually ends up doing
+#     a merge/rebase, that will happen on the current branch (or detached HEAD)
+#     and NOT on the configured (or default) .branch. Is this OK? Should we
+#     abort (or at least warn) instead? (In general, .branch seems only to
+#     affect the submodule's HEAD when the submodule is first cloned.)
+#     (This discussion point is related to D3 and D6)
+#     Affects: 1.3.1.3, 1.3.1.5, 1.3.1.7, 1.3.2.>=2,
+#              1.4.1.3, 1.4.1.5, 1.4.1.7, 1.4.2.>=2,
+#              2.3.1.3, 2.3.1.5, 2.3.1.7, 2.3.2.2, 2.3.2.4, 2.3.2.6,
+#              2.4.1.3, 2.4.1.5, 2.4.1.7, 2.4.2.2, 2.4.2.4, 2.4.2.6
+#              3.3.1.3, 3.3.1.5, 3.3.1.7
+#              3.4.1.3, 3.4.1.5, 3.4.1.7
+#
+# D6: The meaning of submodule.<name>.branch is initially confusing, as it does
+#     not really concern the submodule's local branch (except as a naming hint
+#     when the submodule is first cloned). Instead, submodule.<name>.branch is
+#     really about which branch in the _upstream_ submodule (as defined by
+#     submodule.<name>.url, or by the submodule's remote.origin.url?) that we
+#     want to integrate with. This is probably the more useful setting, and it
+#     becomes obviously correct after (re-)reading gitmodules(5) and
+#     git-config(1). However, from just reading the "update" section in
+#     git-submodule(1) (or not even that), things are not so clear-cut. Would
+#     submodule.<name>.upstream (or .remote-branch, or similar) be a better
+#     name for this?
+#
+# D7: What to do when .branch refers to a branch that is missing from upstream?
+#     Currently, when trying to clone, the clone fails (which causes 'git
+#     submodule update --remote' to fail), but leaves the submodule in an
+#     uninitialized state (there is a .git, but the work tree is missing).
+#     This is probably not the behavior we want...
+#     Affects: pre, 3.2.2.1, 3.3.2.1, 3.4.2.1, 3.5.2.1
+
+# First, we need to setup the environment in which the test cases are run.
+# This consists of an upstream super-repo containing a single submodule.
+# The submodule commit graph looks like this:
+#
+#   $root_commit --->  $old_commit ---> $new_commit  <- master (HEAD) # T1
+#                \          ^- (superproject's gitlink points here)
+#                 --> $foo_commit  <- foo
+#
+# Each test case then starts by cloning upstream_super -> super and setting up
+# its submodule + configuration according to the current test case. It then
+# runs "git submodule update" (with or without --remote) and verifies the
+# expected results.
+
+test_expect_success 'preliminaries: setup upstream super and sub' '
+	git init upstream_sub &&
+	(cd upstream_sub &&
+	 test_commit root_commit &&
+	 test_commit foo_commit &&
+	 git branch foo &&
+	 git reset --hard HEAD^ &&
+#	 git branch -m default &&  # T1
+	 test_commit old_commit
+	) &&
+	git init upstream_super &&
+	(cd upstream_super &&
+	 git submodule add ../upstream_sub sub &&
+	 test_tick &&
+	 git commit -m "Add sub"
+	) &&
+	(cd upstream_sub &&
+	 test_commit new_commit
+	)
+'
+
+# The following commit IDs are used in various test cases
+root_commit=$(git -C upstream_sub rev-parse --verify -q root_commit)
+foo_commit=$(git -C upstream_sub rev-parse --verify -q foo_commit)
+old_commit=$(git -C upstream_sub rev-parse --verify -q old_commit)
+new_commit=$(git -C upstream_sub rev-parse --verify -q new_commit)
+
+fail()
+{
+	echo "$@"
+	false
+}
+
+# Verify that the path in $1 contains a cloned git repo
+verify_is_cloned()
+{
+	test -e "$1/.git" ||
+		fail "Failed to find cloned repo at '$1'"
+}
+
+# Verify that commit $2 in repo $1 has parents $3, $4, ... (in the given order).
+verify_parents()
+{
+	repo=$1; shift
+	commit=$1; shift
+	expect_p=$@
+	actual_p=$(git --git-dir="$repo/.git" log --format=%P -1 "$commit")
+	test "$expect_p" = "$actual_p" ||
+		fail "Expected $commit parents '$expect_p' != '$actual_p'"
+}
+
+# Verify that HEAD in repo $1 points to commit $2.
+# If $2 is of the form 'parents.<SHA1>[.<SHA1>...]', then check that HEAD
+# currently points to a commit whose parents are the given SHA1s (in order).
+verify_head()
+{
+	head=$(git --git-dir="$1/.git" rev-parse -q HEAD)
+	case "$2" in
+	parents.*)
+		parents=$(echo "$2" | sed -e 's/^parents\.//' -e 's/\./ /g')
+		verify_parents "$1" "$head" $parents ||
+			fail "Failed to verify parents of HEAD in '$1'"
+		;;
+	*)
+		test "$2" = "$head" ||
+			fail "Expected HEAD in '$1' to be '$2', but is '$head'"
+	esac
+}
+
+# Verify that the repo in $1 is currently checked out on branch $2.
+# If $2 is "detached", verify that $1'a HEAD is in fact detached.
+verify_branch()
+{
+	expect=$2
+	if test "$expect" = "detached"
+	then
+		expect=
+	fi
+	actual=$(git --git-dir="$1/.git" symbolic-ref --short -q HEAD)
+	test "$expect" = "$actual" ||
+		fail "Expected branch in '$1' to be '$expect', but is '$actual'"
+}
+
+# Clone upstream_super to super, and run "git submodule init"
+setup_super()
+{
+	rm -rf super &&
+	git clone upstream_super super &&
+	(cd super &&
+	 git submodule init &&
+	 test_must_fail verify_is_cloned sub &&
+	 test "$(git rev-parse -q HEAD:sub)" = "$old_commit"
+	)
+}
+
+# Setup submodule.sub.branch == $1
+# If $1 == "unset", ensure submodule.sub.branch is unset
+setup_sub_branch()
+{
+	if test "$1" = "unset"
+	then
+		git config --unset-all "submodule.sub.branch"
+		test_must_fail git config "submodule.sub.branch" ||
+			error "Failed to unset submodule.sub.branch"
+	else
+		git config "submodule.sub.branch" "$1"
+		actual=$(git config "submodule.sub.branch")
+		test "$actual" = "$1" ||
+			error "Failed to set submodule.sub.branch to '$1'"
+	fi
+}
+
+# Setup submodule.sub.update == $1
+setup_sub_update()
+{
+	git config "submodule.sub.update" "$1"
+	actual=$(git config "submodule.sub.update")
+	test "$actual" = "$1" ||
+		error "Failed to set submodule.sub.update to '$1'"
+}
+
+# Setup the state of the sub submodule according to $1, which is one of:
+#  - "uncloned" - sub is an empty dir.
+#  - "detach" - sub is detached at the commit in $2.
+#  - * - sub is checked out on branch named in $1, at the commit in $2.
+setup_sub_state()
+{
+	case "$1" in
+	uncloned)
+		test_must_fail verify_is_cloned sub ||
+			error "Failed to verify uncloned sub"
+		;;
+	detach)
+		git submodule update --checkout sub &&
+		(cd sub &&
+		 git checkout "$2"
+		) &&
+		verify_is_cloned sub &&
+		verify_branch sub detached &&
+		verify_head sub "$2" ||
+			error "Failed to detach sub at '$2'"
+		;;
+	*)
+		git submodule update --checkout sub &&
+		(cd sub &&
+		 git checkout -b "$1" "$2"
+		) &&
+		verify_is_cloned sub &&
+		verify_branch sub "$1" &&
+		verify_head sub "$2" ||
+			error "Failed to attach sub to '$1' at '$2'"
+	esac
+}
+
+# Verify the state of the sub submodule according to $1, which is one of:
+#  - "uncloned" - sub is an empty dir.
+#  - "detach" - sub is detached at the commit in $2.
+#  - * - sub is checked out on branch named in $1, at the commit in $2.
+# If $2 is of the form 'parents.<SHA1>[.<SHA1>...]', then check that HEAD
+# currently points to a commit whose parents are the given SHA1s (in order).
+verify_sub_state()
+{
+	case "$1" in
+	uncloned)
+		test_must_fail verify_is_cloned sub ||
+			fail "Failed to verify uncloned sub"
+		;;
+	detach)
+		verify_is_cloned sub &&
+		verify_branch sub detached &&
+		verify_head sub "$2" ||
+			fail "Failed to verify detached sub at '$2'"
+		;;
+	*)
+		verify_is_cloned sub &&
+		verify_branch sub "$1" &&
+		verify_head sub "$2" ||
+			fail "Failed to verify attached sub at '$1' -> '$2'"
+	esac
+}
+
+verify_sub_patch_id()
+{
+	p1=$(git --git-dir=sub/.git show $1 | git patch-id | cut -d' ' -f1)
+	p2=$(git --git-dir=sub/.git show $2 | git patch-id | cut -d' ' -f1)
+	test "$p1" = "$p2" ||
+		fail "'$1' and '$2' has different patch-ids ('$p1' != '$p2')"
+}
+
+# 1.1.* - .branch is unset, .update == none
+
+test_expect_success '1.1.1.1 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '1.1.1.2 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '1.1.1.3 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '1.1.1.4 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '1.1.1.5 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '1.1.1.6 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '1.1.1.7 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+test_expect_success '1.1.2.1 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '1.1.2.2 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '1.1.2.3 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '1.1.2.4 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '1.1.2.5 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '1.1.2.6 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '1.1.2.7 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 1.2.* - .branch is unset, .update == checkout
+
+test_expect_success '1.2.1.1 - clone, detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # D4
+	)
+'
+
+test_expect_success '1.2.1.2 - already @ $old_commit -> keep detached' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '1.2.1.3 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '1.2.1.4 - already @ $old_commit -> keep attached' ' # D1
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '1.2.1.5 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '1.2.1.6 - already @ $old_commit -> keep attached' ' # D1
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '1.2.1.7 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '1.2.2.1 - clone, detach @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit # D4
+	)
+'
+
+test_expect_success '1.2.2.2 - detach @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit
+	)
+'
+
+test_expect_success '1.2.2.3 - detach @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit
+	)
+'
+
+test_expect_success '1.2.2.4 - detach @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit
+	)
+'
+
+test_expect_success '1.2.2.5 - detach @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit
+	)
+'
+
+test_expect_success '1.2.2.6 - detach @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit
+	)
+'
+
+test_expect_success '1.2.2.7 - detach @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit
+	)
+'
+
+# 1.3.* - .branch is unset, .update == merge # D2
+
+test_expect_success '1.3.1.1 - clone, create branch named from origin/HEAD @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # T1, D4, FIXME: s/detach/master/?
+	)
+'
+
+test_expect_success '1.3.1.2 - already at $old_commit, remain detached' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '1.3.1.3 - merge $old_commit -> $foo_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_success '1.3.1.4 - already at $old_commit, remain attached to foo' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '1.3.1.5 - merge $old_commit -> $foo_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state foo parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_success '1.3.1.6 - already at $old_commit, remain attached to bar' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '1.3.1.7 - merge $old_commit -> $foo_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state bar parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_success '1.3.2.1 - clone, create branch from origin/HEAD @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit # T1, D4, FIXME: s/detach/master/?
+	)
+'
+
+test_expect_success '1.3.2.2 - ffwd $old_commit -> $new_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit
+	)
+'
+
+test_expect_success '1.3.2.3 - merge $new_commit -> $foo_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach parents.$foo_commit.$new_commit
+	)
+'
+
+test_expect_success '1.3.2.4 - ffwd $old_commit -> $new_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $new_commit
+	)
+'
+
+test_expect_success '1.3.2.5 - merge $new_commit -> $foo_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state foo parents.$foo_commit.$new_commit
+	)
+'
+
+test_expect_success '1.3.2.6 - ffwd $old_commit -> $new_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $new_commit
+	)
+'
+
+test_expect_success '1.3.2.7 - merge $new_commit -> $foo_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state bar parents.$foo_commit.$new_commit
+	)
+'
+
+# 1.4.* - .branch is unset, .update == rebase # D2
+
+test_expect_success '1.4.1.1 - clone, create branch named from origin/HEAD @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # T1, D4, FIXME: s/detach/master/?
+	)
+'
+
+test_expect_success '1.4.1.2 - already at $old_commit, remain detached' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '1.4.1.3 - rebase $foo_commit onto $old_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state detach parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '1.4.1.4 - already at $old_commit, remain attached to foo' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '1.4.1.5 - rebase $foo_commit onto $old_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state foo parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '1.4.1.6 - already at $old_commit, remain attached to bar' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '1.4.1.7 - rebase $foo_commit onto $old_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state bar parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '1.4.2.1 - clone, create branch from origin/HEAD @ $new_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit # T1, D4, FIXME: s/detach/master/?
+	)
+'
+
+test_expect_success '1.4.2.2 - ffwd $old_commit -> $new_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit
+	)
+'
+
+test_expect_success '1.4.2.3 - rebase $foo_commit onto $new_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach parents.$new_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '1.4.2.4 - ffwd $old_commit -> $new_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $new_commit
+	)
+'
+
+test_expect_success '1.4.2.5 - rebase $foo_commit onto $new_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state foo parents.$new_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '1.4.2.6 - ffwd $old_commit -> $new_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $new_commit
+	)
+'
+
+test_expect_success '1.4.2.7 - rebase $foo_commit onto $new_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state bar parents.$new_commit
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+# 1.5.* - .branch is unset, .update == !command
+
+test_expect_success '1.5.1.1 - clone, create branch named from origin/HEAD @ $old_commit. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update !false &&
+	 setup_sub_branch unset &&
+	 git submodule update && # should not invoke "false"
+	 verify_sub_state detach $old_commit # T1, D4, FIXME: s/detach/master/?
+	)
+'
+
+test_expect_success '1.5.1.2 - already at $old_commit, remain detached. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch unset &&
+	 git submodule update && # should not invoke "false"
+	 verify_sub_state detach $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: detached @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.1.3 - !command invoked to integrate $old_commit -> $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '1.5.1.4 - already at $old_commit, remain attached to foo. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to foo @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.1.5 - !command invoked to integrate $old_commit -> foo/$foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '1.5.1.6 - already at $old_commit, remain attached to bar. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to bar @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.1.7 - !command invoked to integrate $old_commit -> bar/$foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '1.5.2.1 - clone, create branch from origin/HEAD @ $new_commit. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update !false &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $new_commit # T1, D4, FIXME: s/detach/master/?
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: detached @ $old_commit, and asked to integrate $new_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
+test "\$1" = "$new_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.2.2 - !command invoked to integrate $new_commit -> $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: detached @ $foo_commit, and asked to integrate $new_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$new_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.2.3 - !command invoked to integrate $new_commit -> $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to foo @ $old_commit, and asked to integrate $new_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
+test "\$1" = "$new_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.2.4 - !command invoked to integrate $new_commit -> foo/$old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to foo @ $foo_commit, and asked to integrate $new_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$new_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.2.5 - !command invoked to integrate $new_commit -> foo/$foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to bar @ $old_commit, and asked to integrate $new_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
+test "\$1" = "$new_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.2.6 - !command invoked to integrate $new_commit -> bar/$old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to bar @ $foo_commit, and asked to integrate $new_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$new_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '1.5.2.7 - !command invoked to integrate $new_commit -> bar/$foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch unset &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+# 2.1.* - .branch == foo (exists upstream), .update == none
+
+test_expect_success '2.1.1.1 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '2.1.1.2 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '2.1.1.3 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '2.1.1.4 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '2.1.1.5 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '2.1.1.6 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '2.1.1.7 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+test_expect_success '2.1.2.1 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '2.1.2.2 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '2.1.2.3 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '2.1.2.4 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '2.1.2.5 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '2.1.2.6 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '2.1.2.7 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 2.2.* - .branch == foo (exists upstream), .update == checkout
+
+test_expect_success '2.2.1.1 - clone, detach @ $old_commit (.branch is ignored)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # D4
+	)
+'
+
+test_expect_success '2.2.1.2 - already @ $old_commit -> keep detached' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '2.2.1.3 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '2.2.1.4 - already @ $old_commit -> keep attached' ' # D1
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '2.2.1.5 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '2.2.1.6 - already @ $old_commit -> keep attached' ' # D1
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '2.2.1.7 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '2.2.2.1 - clone, detach @ $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit # D4
+	)
+'
+
+test_expect_success '2.2.2.2 - detach @ $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '2.2.2.3 - already @ $foo_commit, remain detached' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '2.2.2.4 - detach @ $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '2.2.2.5 - already @ $foo_commit, remain attached to foo' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '2.2.2.6 - detach @ $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '2.2.2.7 - already @ $foo_commit, remain attached to bar' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 2.3.* - .branch == foo (exists upstream), .update == merge
+
+test_expect_success '2.3.1.1 - clone, create branch called foo @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/foo/?
+	)
+'
+
+test_expect_success '2.3.1.2 - already at $old_commit, remain detached' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '2.3.1.3 - merge $old_commit -> $foo_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_success '2.3.1.4 - already at $old_commit, remain attached to foo' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '2.3.1.5 - merge $old_commit -> $foo_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state foo parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_success '2.3.1.6 - already at $old_commit, remain attached to bar' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '2.3.1.7 - merge $old_commit -> $foo_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state bar parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_success '2.3.2.1 - clone, create branch from origin/foo @ $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit # D4, FIXME: s/detach/foo/?
+	)
+'
+
+test_expect_success '2.3.2.2 - merge $foo_commit -> $old_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach parents.$old_commit.$foo_commit
+	)
+'
+
+test_expect_success '2.3.2.3 - already @ $foo_commit, remain detached' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '2.3.2.4 - merge $foo_commit -> $old_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state foo parents.$old_commit.$foo_commit
+	)
+'
+
+test_expect_success '2.3.2.5 - already @ $foo_commit, remain attached to foo' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '2.3.2.6 - merge $foo_commit -> $old_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state bar parents.$old_commit.$foo_commit
+	)
+'
+
+test_expect_success '2.3.2.7 - already @ $foo_commit, remain attached to bar' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 2.4.* - .branch == foo (exists upstream), .update == rebase
+
+test_expect_success '2.4.1.1 - clone, create branch called foo @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/foo/?
+	)
+'
+
+test_expect_success '2.4.1.2 - already at $old_commit, remain detached' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '2.4.1.3 - rebase $foo_commit onto $old_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state detach parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '2.4.1.4 - already at $old_commit, remain attached to foo' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '2.4.1.5 - rebase $foo_commit onto $old_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state foo parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '2.4.1.6 - already at $old_commit, remain attached to bar' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '2.4.1.7 - rebase $foo_commit onto $old_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state bar parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '2.4.2.1 - clone, create branch from origin/foo @ $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit # D4, FIXME: s/detach/foo/?
+	)
+'
+
+test_expect_success '2.4.2.2 - rebase $old_commit onto $foo_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach parents.$foo_commit &&
+	 verify_sub_patch_id HEAD $old_commit
+	)
+'
+
+test_expect_success '2.4.2.3 - already @ $foo_commit, remain detached' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '2.4.2.4 - rebase $old_commit onto $foo_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state foo parents.$foo_commit &&
+	 verify_sub_patch_id HEAD $old_commit
+	)
+'
+
+test_expect_success '2.4.2.5 - already @ $foo_commit, remain attached to foo' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '2.4.2.6 - rebase $old_commit onto $foo_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state bar parents.$foo_commit &&
+	 verify_sub_patch_id HEAD $old_commit
+	)
+'
+
+test_expect_success '2.4.2.7 - already @ $foo_commit, remain attached to bar' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 2.5.* - .branch == foo (exists upstream), .update == !command
+
+test_expect_success '2.5.1.1 - clone, create branch called foo @ $old_commit. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update !false &&
+	 setup_sub_branch foo &&
+	 git submodule update && # should not invoke "false"
+	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/foo/?
+	)
+'
+
+test_expect_success '2.5.1.2 - already at $old_commit, remain detached. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch foo &&
+	 git submodule update && # should not invoke "false"
+	 verify_sub_state detach $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: detached @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '2.5.1.3 - !command invoked to integrate $old_commit -> $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '2.5.1.4 - already at $old_commit, remain attached to foo. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to foo @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '2.5.1.5 - !command invoked to integrate $old_commit -> foo/$foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '2.5.1.6 - already at $old_commit, remain attached to bar. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to bar @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '2.5.1.7 - !command invoked to integrate $old_commit -> bar/$foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch foo &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '2.5.2.1 - clone, create branch from origin/foo @ $foo_commit. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update !false &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit # D4, FIXME: s/detach/foo/?
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: detached @ $old_commit, and asked to integrate $foo_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
+test "\$1" = "$foo_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '2.5.2.2 - !command invoked to integrate $foo_commit -> $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '2.5.2.3 - already at $foo_commit, remain detached. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to foo @ $old_commit, and asked to integrate $foo_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
+test "\$1" = "$foo_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '2.5.2.4 - !command invoked to integrate $foo_commit -> foo/$old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '2.5.2.5 - already at $foo_commit, remain attached to foo. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to bar @ $old_commit, and asked to integrate $foo_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
+test "\$1" = "$foo_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '2.5.2.6 - !command invoked to integrate $foo_commit -> bar/$old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '2.5.2.7 - already at $foo_commit, remain attached to bar. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch foo &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 3.1.* - .branch == bar (MISSING upstream), .update == none
+
+test_expect_success '3.1.1.1 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '3.1.1.2 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.1.1.3 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '3.1.1.4 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.1.1.5 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '3.1.1.6 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.1.1.7 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+test_expect_success '3.1.2.1 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update --remote &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '3.1.2.2 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.1.2.3 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '3.1.2.4 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.1.2.5 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '3.1.2.6 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.1.2.7 - do nothing (.update == none)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update none &&
+	 setup_sub_branch bar &&
+	 git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 3.2.* - .branch == bar (MISSING upstream), .update == checkout
+
+test_expect_success '3.2.1.1 - clone, detach @ $old_commit (.branch is ignored)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # D4
+	)
+'
+
+test_expect_success '3.2.1.2 - already @ $old_commit -> keep detached' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.2.1.3 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.2.1.4 - already @ $old_commit -> keep attached' ' # D1
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.2.1.5 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.2.1.6 - already @ $old_commit -> keep attached' ' # D1
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.2.1.7 - detach @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_failure '3.2.2.1 - fail to clone (remote branch bar missing), leave uncloned' ' # T3, D7
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '3.2.2.2 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.2.2.3 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '3.2.2.4 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.2.2.5 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '3.2.2.6 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.2.2.7 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update checkout &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 3.3.* - .branch == bar (MISSING upstream), .update == merge
+
+test_expect_success '3.3.1.1 - clone, create branch called bar @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/bar/?
+	)
+'
+
+test_expect_success '3.3.1.2 - already at $old_commit, remain detached' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.3.1.3 - merge $old_commit -> $foo_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_success '3.3.1.4 - already at $old_commit, remain attached to foo' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.3.1.5 - merge $old_commit -> $foo_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state foo parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_success '3.3.1.6 - already at $old_commit, remain attached to bar' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.3.1.7 - merge $old_commit -> $foo_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state bar parents.$foo_commit.$old_commit
+	)
+'
+
+test_expect_failure '3.3.2.1 - fail to clone (remote branch bar missing), leave uncloned' ' # T3, D7
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '3.3.2.2 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.3.2.3 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '3.3.2.4 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.3.2.5 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '3.3.2.6 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.3.2.7 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update merge &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 3.4.* - .branch == bar (MISSING upstream), .update == rebase
+
+test_expect_success '3.4.1.1 - clone, create branch called bar @ $old_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/bar/?
+	)
+'
+
+test_expect_success '3.4.1.2 - already at $old_commit, remain detached' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.4.1.3 - rebase $foo_commit onto $old_commit, remain detached' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state detach parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '3.4.1.4 - already at $old_commit, remain attached to foo' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.4.1.5 - rebase $foo_commit onto $old_commit, remain attached to foo' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state foo parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_success '3.4.1.6 - already at $old_commit, remain attached to bar' ' # D3
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.4.1.7 - rebase $foo_commit onto $old_commit, remain attached to bar' ' # D5
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state bar parents.$old_commit &&
+	 verify_sub_patch_id HEAD $foo_commit
+	)
+'
+
+test_expect_failure '3.4.2.1 - fail to clone (remote branch bar missing), leave uncloned' ' # T3, D7
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '3.4.2.2 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.4.2.3 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '3.4.2.4 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.4.2.5 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '3.4.2.6 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.4.2.7 - fail (remote branch bar missing)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update rebase &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+# 3.5.* - .branch == bar (MISSING upstream), .update == !command
+
+test_expect_success '3.5.1.1 - clone, create branch called bat @ $old_commit. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 git submodule update && # should not invoke "false"
+	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/bar/?
+	)
+'
+
+test_expect_success '3.5.1.2 - already at $old_commit, remain detached. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 git submodule update && # should not invoke "false"
+	 verify_sub_state detach $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: detached @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '3.5.1.3 - !command invoked to integrate $old_commit -> $foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '3.5.1.4 - already at $old_commit, remain attached to foo. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to foo @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '3.5.1.5 - !command invoked to integrate $old_commit -> foo/$foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_success '3.5.1.6 - already at $old_commit, remain attached to bar. !command is NOT invoked' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+cat > test_command.sh <<EOF
+#!/bin/sh
+# Verify: attached to bar @ $foo_commit, and asked to integrate $old_commit
+test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
+test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
+test "\$1" = "$old_commit" &&
+git checkout -b success $root_commit
+EOF
+chmod +x test_command.sh
+
+test_expect_success '3.5.1.7 - !command invoked to integrate $old_commit -> bar/$foo_commit' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update !../../test_command.sh &&
+	 setup_sub_branch bar &&
+	 git submodule update &&
+	 verify_sub_state success $root_commit # test_command.sh is happy
+	)
+'
+
+test_expect_failure '3.5.2.1 - fail to clone (remote branch bar missing), leave uncloned' ' # T3, D7
+	setup_super &&
+	(cd super &&
+	 setup_sub_state uncloned &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state uncloned
+	)
+'
+
+test_expect_success '3.5.2.2 - fail (remote branch bar missing, !command not invoked)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state detach $old_commit
+	)
+'
+
+test_expect_success '3.5.2.3 - fail (remote branch bar missing, !command not invoked)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state detach $foo_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state detach $foo_commit
+	)
+'
+
+test_expect_success '3.5.2.4 - fail (remote branch bar missing, !command not invoked)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state foo $old_commit
+	)
+'
+
+test_expect_success '3.5.2.5 - fail (remote branch bar missing, !command not invoked)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state foo $foo_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state foo $foo_commit
+	)
+'
+
+test_expect_success '3.5.2.6 - fail (remote branch bar missing, !command not invoked)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $old_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state bar $old_commit
+	)
+'
+
+test_expect_success '3.5.2.7 - fail (remote branch bar missing, !command not invoked)' '
+	setup_super &&
+	(cd super &&
+	 setup_sub_state bar $foo_commit &&
+	 setup_sub_update !false &&
+	 setup_sub_branch bar &&
+	 test_must_fail git submodule update --remote &&
+	 verify_sub_state bar $foo_commit
+	)
+'
+
+test_done
-- 
1.9.1.587.g6ba9303

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

* Re: [RFC] t7410: 210 tests for various 'git submodule update' scenarios
  2014-04-16  0:54 [RFC] t7410: 210 tests for various 'git submodule update' scenarios Johan Herland
@ 2014-04-16 17:21 ` W. Trevor King
  2014-04-17 11:42   ` Johan Herland
  2014-04-17 16:04 ` Jens Lehmann
  1 sibling, 1 reply; 5+ messages in thread
From: W. Trevor King @ 2014-04-16 17:21 UTC (permalink / raw
  To: Johan Herland; +Cc: git, Jens Lehmann, Heiko Voigt, Junio C Hamano

[-- Attachment #1: Type: text/plain, Size: 8621 bytes --]

On Wed, Apr 16, 2014 at 02:54:48AM +0200, Johan Herland wrote:
> This is a work-in-progress to flesh out (and promote discussion about)
> the expected behaviors for all possible scenarios in which
> 'git submodule update' might be run.

This is lovely :).

> +#  - current state of submodule:
> +#     ?.?.?.1 - not yet cloned
> +#     ?.?.?.2 - cloned, detached, HEAD == gitlink
> +#     ?.?.?.3 - cloned, detached, HEAD != gitlink
> +#     ?.?.?.4 - cloned, on branch foo (exists upstream), HEAD == gitlink
> +#     ?.?.?.5 - cloned, on branch foo (exists upstream), HEAD != gitlink
> +#     ?.?.?.6 - cloned, on branch bar (MISSING upstream), HEAD == gitlink
> +#     ?.?.?.7 - cloned, on branch bar (MISSING upstream), HEAD != gitlink

The remote branches should only matter for the initial clone and
--remote updates.  Also, only the configured submodule.<name>.branch
(your first axis) should be checked; the locally checked-out submodule
branch shouldn't matter.

> +# T2: Test with submodule.<name>.url != submodule's remote.origin.url. Does
> +#     "submodule update --remote" sync with submodule.<name>.url, or with the
> +#     submodule's origin? (or with the submodule's current branch's upstream)?

All fetches should currently use the submodule's remote.origin.url.
submodule.<name>.url is only used for the initial clone (*.*.*.1), and
never referenced again.  This would change using my tightly-bound
submodule proposal [1], where a difference between
submodule.<name>.url and the submodule's @{upstream} URL would be
trigger a dirty-tree condition (for folks with tight-bind syncing
enabled) from which you couldn't update before resolving the
difference.

> +# D1: When submodule is already at right commit, checkout-mode currently does
> +#     nothing. Should it instead detach, even when no update is needed?
> +#     Affects: 1.2.1.4, 1.2.1.6, 2.2.1.4, 2.2.1.6, 3.2.1.4, 3.2.1.6

“Checkout updates always leave a detached HEAD” seems easier to
explain, so I'm leaning that way.

> +# D2: Should all/some of 1.3.*/1.4.* abort/error because we don't know what to
> +#     merge/rebase with (because .branch is unset)? Or is the current default
> +#     to origin/HEAD OK?
> +#     Affects: 1.3.*, 1.4.*

Maybe you mean 1.3.*, 1.4.*, and 1.5.* (merge, rebase, and !command)?
In all of these cases, we're integrating the current HEAD with the
gitlinked (*.*.1.*) or remote-tracking branch (*.*.2.*).  Since
submodule.<name>.branch defaults to master (and may be changed to HEAD
after a long transition period? [2,3]), I don't think we should
abort/error in those cases.

> +# D3: When submodule is already at right commit, merge/rebase-mode currently
> +#     does nothing. Should it do something else (e.g. not leave submodule
> +#     detached, or checked out on the "wrong" branch (i.e. != .branch))?
> +#     (This discussion point is related to D1, D5 and D6)

“Non-checkout updates always leave you on a branch” seems easier to
explain, but I think we'd want to distinguish between the local branch
and the remote submodule.<name>.branch [1].  Lacking that distinction,
I'd prefer to leave the checked-out branch unchanged.

> +# D4: When 'submodule update' performs a clone to populate a submodule, it
> +#     currently also creates a default branch (named after origin/HEAD) in
> +#     that submodule, EVEN WHEN THAT BRANCH WILL NEVER BE USED (e.g. because
> +#     we're in checkout-mode and submodule will always be detached). Is this
> +#     good, or should the clone performed by 'submodule update' skip the
> +#     automatic local branch creation?
> +#     Affects: 1.2.*.1, 1.3.*.1, 1.4.*.1, 1.5.*.1,
> +#              2.2.*.1, 2.3.*.1, 2.4.*.1, 2.5.*.1,
> +#              3.2.1.1, 3.3.1.1, 3.4.1.1, 3.5.1.1

“Checkout updates always leave a detached HEAD” seems easier to
explain, so I'm leaning that way.

> +# D5: When in merge/rebase-mode, and 'submodule update' actually ends up doing
> +#     a merge/rebase, that will happen on the current branch (or detached HEAD)
> +#     and NOT on the configured (or default) .branch. Is this OK? Should we
> +#     abort (or at least warn) instead? (In general, .branch seems only to
> +#     affect the submodule's HEAD when the submodule is first cloned.)
> +#     (This discussion point is related to D3 and D6)
> +#     Affects: 1.3.1.3, 1.3.1.5, 1.3.1.7, 1.3.2.>=2,
> +#              1.4.1.3, 1.4.1.5, 1.4.1.7, 1.4.2.>=2,
> +#              2.3.1.3, 2.3.1.5, 2.3.1.7, 2.3.2.2, 2.3.2.4, 2.3.2.6,
> +#              2.4.1.3, 2.4.1.5, 2.4.1.7, 2.4.2.2, 2.4.2.4, 2.4.2.6
> +#              3.3.1.3, 3.3.1.5, 3.3.1.7
> +#              3.4.1.3, 3.4.1.5, 3.4.1.7

With the --remote option that added submodule.<name>.branch (which
eventually landed with v8 of that series [4]), I initially imagined it
as the name of the local branch [5], but transitioned to imagining it
as the name of the remote-tracking branch in v5 of that series [6].
There were no major logical changes between v5 and v8.  With the v8
version that landed in Git v1.8.2, submodule.<name>.branch was clearly
the name of the remote-tracking branch, and we gave no way to
separately configure the local branch.

Recently, we decided that local branches might be important after all
[7], which lead to the partially landed v5 of my local-branch-creation
series [8], now partially reverted with d851ffb (Revert "submodule:
explicit local branch creation in module_clone", 2014-04-02).  Like v4
of that series [9], I considered the landed-and-now-reverted v5 to be
a stop-gap until we got better local-branch handling.

Anyhow, that's why submodule.<name>.branch is only important when we
interact with the remote repository (during clones and --remote
updates).  We've never landed a patch that explicitly addresses what
the local branch should be.

> +# D6: The meaning of submodule.<name>.branch is initially confusing, as it does
> +#     not really concern the submodule's local branch (except as a naming hint
> +#     when the submodule is first cloned). Instead, submodule.<name>.branch is
> +#     really about which branch in the _upstream_ submodule

Which is how gitmodules(5) explains it:

  submodule.<name>.branch
    A remote branch name for tracking updates…

> +#     submodule.<name>.url, or by the submodule's remote.origin.url?)
> +#     want to integrate with.

The submodule's remote.origin.url for everything except the initial
clone (*.*.*.1).  See my response to T2.

> …                               This is probably the more useful setting, and it
> +#     becomes obviously correct after (re-)reading gitmodules(5) and
> +#     git-config(1). However, from just reading the "update" section in
> +#     git-submodule(1) (or not even that), things are not so clear-cut. Would
> +#     submodule.<name>.upstream (or .remote-branch, or similar) be a better
> +#     name for this?

Are the docs from 23d25e4 (submodule: explicit local branch creation
in module_clone, 2014-01-26; now reverted with d851ffb, Revert
"submodule: explicit local branch creation in module_clone",
2014-04-02) clearer?  Maybe we can salvage some of those docs even
though we've reverted the actual code changes?

> +# D7: What to do when .branch refers to a branch that is missing from upstream?
> +#     Currently, when trying to clone, the clone fails (which causes 'git
> +#     submodule update --remote' to fail), but leaves the submodule in an
> +#     uninitialized state (there is a .git, but the work tree is missing).
> +#     This is probably not the behavior we want...
> +#     Affects: pre, 3.2.2.1, 3.3.2.1, 3.4.2.1, 3.5.2.1

I think we should remove the submodule's .git file after the failed clone.

Cheers,
Trevor

[1]: http://thread.gmane.org/gmane.comp.version-control.git/240336
[2]: http://thread.gmane.org/gmane.comp.version-control.git/245283
[3]: http://thread.gmane.org/gmane.comp.version-control.git/245357
[4]: http://thread.gmane.org/gmane.comp.version-control.git/211830
[5]: http://article.gmane.org/gmane.comp.version-control.git/210730
[6]: http://article.gmane.org/gmane.comp.version-control.git/210764
[7]: http://thread.gmane.org/gmane.comp.version-control.git/239799
[8]: http://thread.gmane.org/gmane.comp.version-control.git/241112
[9]: http://article.gmane.org/gmane.comp.version-control.git/240498

-- 
This email may be signed or encrypted with GnuPG (http://www.gnupg.org).
For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [RFC] t7410: 210 tests for various 'git submodule update' scenarios
  2014-04-16 17:21 ` W. Trevor King
@ 2014-04-17 11:42   ` Johan Herland
  2014-04-17 15:31     ` W. Trevor King
  0 siblings, 1 reply; 5+ messages in thread
From: Johan Herland @ 2014-04-17 11:42 UTC (permalink / raw
  To: W. Trevor King
  Cc: Git mailing list, Jens Lehmann, Heiko Voigt, Junio C Hamano

TL;DR: I believe the update-related submodule.<name>.* options
(.branch, .update, .url, and even the --remote option) are
slowly growing into a separate DSL for specfying how to update
submodules. This adds a confusing and opaque layer in front of
what is essentially git commands run by "git submodule update"
within the submodule. Instead of adding more submodule.<name>.*
options to handle even more use cases, we should scale back on
the config options, and allow the user to specify what should
happen on "git submodule update" in the existing language of
git commands.

On Wed, Apr 16, 2014 at 7:21 PM, W. Trevor King <wking@tremily.us> wrote:
> On Wed, Apr 16, 2014 at 02:54:48AM +0200, Johan Herland wrote:
>> This is a work-in-progress to flesh out (and promote discussion about)
>> the expected behaviors for all possible scenarios in which
>> 'git submodule update' might be run.
>
> This is lovely :).

Thanks!

Having been active in git-submodule development a few years ago,
I thought I had a fairly firm grasp of most details regarding
submodules, but when I recently tried to explain to someone how
"git submodule update" was supposed to work, I quickly found that
I had forgotten a lot, and that git-submodule had also changed.
After (albeit casually) reading the docs, I still didn't feel much
wiser, and this humongous testcase is sort of my ultimate attempt
to regain some understanding.

So far, my impression is that there are too many details/variables
and too much state to be able to sanely comprehend what is the intended
behavior in all cases, much less document in a way that is approachable
to regular users...

>> +#  - current state of submodule:
>> +#     ?.?.?.1 - not yet cloned
>> +#     ?.?.?.2 - cloned, detached, HEAD == gitlink
>> +#     ?.?.?.3 - cloned, detached, HEAD != gitlink
>> +#     ?.?.?.4 - cloned, on branch foo (exists upstream), HEAD == gitlink
>> +#     ?.?.?.5 - cloned, on branch foo (exists upstream), HEAD != gitlink
>> +#     ?.?.?.6 - cloned, on branch bar (MISSING upstream), HEAD == gitlink
>> +#     ?.?.?.7 - cloned, on branch bar (MISSING upstream), HEAD != gitlink
>
> The remote branches should only matter for the initial clone and
> --remote updates.  Also, only the configured submodule.<name>.branch
> (your first axis) should be checked; the locally checked-out submodule
> branch shouldn't matter.

Yes, I realized that while working out the various cases, but it was
not at all obvious from the start, given that the config option is
called submodule.<name>.branch and not submodule.<name>.upstream or
similar (which might prevent a casual user from misinterpreting the
option as having something to do with the current local branch in the
submodule). See D6 for more discussion about the naming of .branch.

In general, I experimented with a few different ways of organizing the
axes to get a "flow" of testcases that more naturally "revealed" the
behavior of "git submodule update", but I haven't found a good
organization, yet. I suspect there is none...

>> +# T2: Test with submodule.<name>.url != submodule's remote.origin.url. Does
>> +#     "submodule update --remote" sync with submodule.<name>.url, or with the
>> +#     submodule's origin? (or with the submodule's current branch's upstream)?
>
> All fetches should currently use the submodule's remote.origin.url.
> submodule.<name>.url is only used for the initial clone (*.*.*.1), and
> never referenced again.  This would change using my tightly-bound
> submodule proposal [1], where a difference between
> submodule.<name>.url and the submodule's @{upstream} URL would be
> trigger a dirty-tree condition (for folks with tight-bind syncing
> enabled) from which you couldn't update before resolving the
> difference.

Ok. As stated above, I am worried about the amount of duplicated
state between the superproject's submodule config (which itself is
split between .gitmodules and .git/config) and the submodule's own
config. And from the above paragraph, I suspect two more dimensions
need to be added to the test matrix:

 - submodule's remote.origin.url ==/!= submodule.<name>.url

 - "tightly-bound submodule" is enabled/disabled

Even if we might successfully deal with the duplicate state (and the
exploding test matrix), it will certainly not be easy to
describe/document the intended behavior in a way that's simple to
grasp and straightforward to use.

We should instead seek ways to minimize the duplication of state.
If it is indeed the case that several submodule.<name>.* values in the
superproject are only consulted _once_ (when cloning the submodule),
we should make it obvious that they will not be used after the clone
is done. Similarly, we should make it obvious that the update-related
options only apply exactly when "git submodule update" is run.

What about something like this:

 - submodule.<name>.create: The command that will be used by
   "git submodule update" to initially populate the contents of
   the submodule. The command is run from within the submodule
   directory, and the following environment variables are available:

    - $SUPER_URL: The URL of the superproject's current upstream
      remote.

    - $SUPER_HEAD: The current value of the superproject's HEAD.
      Typically a refname, but may be commit id (if detached).

    - $GITLINK: The current value of the superproject's gitlink
      for this submodule.

   Example values for submodule.<name>.create:

    - 'git clone -n $SUPER_URL/../sub.git . && git reset --hard $GITLINK'
      This clones from a relative submodule URL (by using $SUPER_URL),
      and then does a detached checkout of $GITLINK. This would be
      equivalent to the current "checkout-mode".

    - 'git clone -n git://url.to/sub.git . && git checkout dev-branch'
      This clones from an absolute submodule URL, and then (assuming
      "dev-branch" exists upstream, which triggers a checkout's DWIM
      behavior) creates a local "dev-branch" with origin/dev-branch
      as its upstream. This would be a suitable start point for
      developing on "dev-branch" in the submodule.

    - 'git clone -n -c branch.autosetuprebase=remote git://url.to/sub.git . \
       && git checkout dev-branch'
      Same as above, but prepares for a rebase-style workflow instead.

 - submodule.<name>.update: The command that will be run within the
   already-existing submodule by "git submodule update". The same
   enviroment variables as above are available. Example values for
   submodule.<name>.update:

    - 'git reset --hard $GITLINK'
      Equivalent to checkout-mode (without --remote).

    - 'git fetch && git reset --hard origin/HEAD'
      Equivalent to checkout-mode with --remote.

    - 'git merge $GITLINK'
      Equivalent to merge-mode (without --remote).

    - 'git pull origin next'
      Equivalent to merge-mode with --remote and .branch == next.

    - 'git rebase $GITLINK'
      Equivalent to rebase-mode (without --remote)

    - 'git pull --rebase origin pu'
      Equivalent to rebase-mode with --remote and .branch == pu.

    - 'git pull'
      Defer completely to the submodule's own config.

Pros/Cons:

 Pro: It is obvious to everybody exactly what will happen when
      "git submodule update" is run.

 Pro: We no longer duplicate state with the submodule's config.
      Instead, it is obvious when and how these options modify
      the submodule, and whether or not they operate independently
      of the submodule's own config.

 Pro: We reuse the current "language" (of git commands and
      command-line options) for describing how to perform updates,
      instead of having to invent a duplicate (but terser) language
      in the form of submodule.<name>.* options.

 Pro: With just a couple of options, we can obsolete most of the
      existing submodule.<name>.* options:

       - .branch == $foo is replaced by "git pull origin $foo" in
         the new .update command.

       - .update == none/checkout/merge/rebase is replaced by an
         appropriate git command in the new .update command.

       - .url becomes a 'clone' argument in the .create command.

       - The --remote option is replaced by updating against remote
         .branch instead of $GITLINK

 Pro: Allows for ultimate flexibility in handling submodule updates
      without increasing the pressure to keep adding
      submodule.<name>.* options.

 Con: Wildly backwards-incompatible with existing submodule.* configs.

 Con: Probably missing proper error handling and/or other handling of
      corner cases in the above example commands.

 Con: A user who has copied/modified these options into his
      superproject's .git/config will manually have to reconcile that
      with any future updates to .gitmodules.

 Con: Too much information in each config option? Maybe consider using
      hooks instead of config options?

>> +# D1: When submodule is already at right commit, checkout-mode currently does
>> +#     nothing. Should it instead detach, even when no update is needed?
>> +#     Affects: 1.2.1.4, 1.2.1.6, 2.2.1.4, 2.2.1.6, 3.2.1.4, 3.2.1.6
>
> “Checkout updates always leave a detached HEAD” seems easier to
> explain, so I'm leaning that way.

Yes, although I suspect different people using different workflows will
have different (but valid) opinions on how this should be handled.
Which is why I'm approaching the conclusion outlined in the TL;DR; i.e.
that adding more submodule.<name>.* options (which often interacts
with other options in complex ways) is probably NOT the way to go.

Instead, we should recognize that people may want to have their
submodules updated in so many different ways, that trying to encode it
in a collection of submodule.<name>.* options is pointless. We can then
provide something more flexible that reuses existing syntax, like the
"free-form" options suggested above, or maybe a "submodule-update" hook
that allows even more customization.

Obviously, we should still have good defaults to deal with the most
common cases, but the current set of options is growing too large to be
understandable by most users.

>> +# D2: Should all/some of 1.3.*/1.4.* abort/error because we don't know what to
>> +#     merge/rebase with (because .branch is unset)? Or is the current default
>> +#     to origin/HEAD OK?
>> +#     Affects: 1.3.*, 1.4.*
>
> Maybe you mean 1.3.*, 1.4.*, and 1.5.* (merge, rebase, and !command)?
> In all of these cases, we're integrating the current HEAD with the
> gitlinked (*.*.1.*) or remote-tracking branch (*.*.2.*).  Since
> submodule.<name>.branch defaults to master (and may be changed to HEAD
> after a long transition period? [2,3]), I don't think we should
> abort/error in those cases.

Yes, it seems we're settling on origin/HEAD as an acceptable default.

>> +# D3: When submodule is already at right commit, merge/rebase-mode currently
>> +#     does nothing. Should it do something else (e.g. not leave submodule
>> +#     detached, or checked out on the "wrong" branch (i.e. != .branch))?
>> +#     (This discussion point is related to D1, D5 and D6)
>
> “Non-checkout updates always leave you on a branch” seems easier to
> explain, but I think we'd want to distinguish between the local branch
> and the remote submodule.<name>.branch [1].  Lacking that distinction,
> I'd prefer to leave the checked-out branch unchanged.

Yes, although again I feel that we are making policy decision based on
what we _believe_ would be ok in our use cases. Adding a .local-branch
config option to specify what happens to the local branch would maybe
fix things for other use cases, but it would also compound the overall
complexity of "submodule update". See above arguments for replacing
config options with explicit git commands, to make "submodule update"
more transparent.

>> +# D4: When 'submodule update' performs a clone to populate a submodule, it
>> +#     currently also creates a default branch (named after origin/HEAD) in
>> +#     that submodule, EVEN WHEN THAT BRANCH WILL NEVER BE USED (e.g. because
>> +#     we're in checkout-mode and submodule will always be detached). Is this
>> +#     good, or should the clone performed by 'submodule update' skip the
>> +#     automatic local branch creation?
>> +#     Affects: 1.2.*.1, 1.3.*.1, 1.4.*.1, 1.5.*.1,
>> +#              2.2.*.1, 2.3.*.1, 2.4.*.1, 2.5.*.1,
>> +#              3.2.1.1, 3.3.1.1, 3.4.1.1, 3.5.1.1
>
> “Checkout updates always leave a detached HEAD” seems easier to
> explain, so I'm leaning that way.

This is not about detached HEAD. This is about the "extra" local branch
automatically created by "git clone". This branch is unused and
unnecessary in checkout-mode, and only coincidentally useful in other
modes (when .branch == origin/HEAD).

However, this is probably a git-clone problem, and not a git-submodule
problem: I am surprised that "git clone --no-checkout" produces the
same local branch (without checking it out). I would instead have
expected the clone to only have the refs/remote/origin/* refs (and
HEAD pointing to an unborn branch - like "git init" does).

>> +# D5: When in merge/rebase-mode, and 'submodule update' actually ends up doing
>> +#     a merge/rebase, that will happen on the current branch (or detached HEAD)
>> +#     and NOT on the configured (or default) .branch. Is this OK? Should we
>> +#     abort (or at least warn) instead? (In general, .branch seems only to
>> +#     affect the submodule's HEAD when the submodule is first cloned.)
>> +#     (This discussion point is related to D3 and D6)
>> +#     Affects: 1.3.1.3, 1.3.1.5, 1.3.1.7, 1.3.2.>=2,
>> +#              1.4.1.3, 1.4.1.5, 1.4.1.7, 1.4.2.>=2,
>> +#              2.3.1.3, 2.3.1.5, 2.3.1.7, 2.3.2.2, 2.3.2.4, 2.3.2.6,
>> +#              2.4.1.3, 2.4.1.5, 2.4.1.7, 2.4.2.2, 2.4.2.4, 2.4.2.6
>> +#              3.3.1.3, 3.3.1.5, 3.3.1.7
>> +#              3.4.1.3, 3.4.1.5, 3.4.1.7
>
> With the --remote option that added submodule.<name>.branch (which
> eventually landed with v8 of that series [4]), I initially imagined it
> as the name of the local branch [5], but transitioned to imagining it
> as the name of the remote-tracking branch in v5 of that series [6].
> There were no major logical changes between v5 and v8.  With the v8
> version that landed in Git v1.8.2, submodule.<name>.branch was clearly
> the name of the remote-tracking branch, and we gave no way to
> separately configure the local branch.

First of all, shame on me for not following the discussion at the time.
I still think that when the meaning changed from local to remote-
tracking branch, the actual option name should have changed as well.
Documenting that it specifies a remote branch is good, but not as good
as choosing a better name for it in the first place.

> Recently, we decided that local branches might be important after all
> [7], which lead to the partially landed v5 of my local-branch-creation
> series [8], now partially reverted with d851ffb (Revert "submodule:
> explicit local branch creation in module_clone", 2014-04-02).  Like v4
> of that series [9], I considered the landed-and-now-reverted v5 to be
> a stop-gap until we got better local-branch handling.
>
> Anyhow, that's why submodule.<name>.branch is only important when we
> interact with the remote repository (during clones and --remote
> updates).  We've never landed a patch that explicitly addresses what
> the local branch should be.

Thanks for the recap. I now realize that my above arguments against
increased complexity in submodule.<name>.* options arrive way too
late, and is probably more like trolling than like constructive input.
I am sorry for having been absent during most of this discussion.
Still, I'm afraid that the current set of options are so complex that
they will only ever be fully understood by the very small set of people
involved with their development.

>> +# D6: The meaning of submodule.<name>.branch is initially confusing, as it does
>> +#     not really concern the submodule's local branch (except as a naming hint
>> +#     when the submodule is first cloned). Instead, submodule.<name>.branch is
>> +#     really about which branch in the _upstream_ submodule
>
> Which is how gitmodules(5) explains it:
>
>   submodule.<name>.branch
>     A remote branch name for tracking updates…

Good, but I fear gitmodules(5) is too hidden for the regular user.
It'd be better to mention this in git-submodule(1), as I expect
gitmodules(5) is largely read by .gitmodules _authors_, and not
regular users. Obviously, the real fix would be a better name...

>> +#     submodule.<name>.url, or by the submodule's remote.origin.url?)
>> +#     want to integrate with.
>
> The submodule's remote.origin.url for everything except the initial
> clone (*.*.*.1).  See my response to T2.

As mentioned above, submodule.<name>.url is then an unnecessary state
duplication. We should make it more obvious that it is only ever used
on the initial clone (see my above argument for moving .url into an
explicit git-clone command)

>> …                               This is probably the more useful setting, and it
>> +#     becomes obviously correct after (re-)reading gitmodules(5) and
>> +#     git-config(1). However, from just reading the "update" section in
>> +#     git-submodule(1) (or not even that), things are not so clear-cut. Would
>> +#     submodule.<name>.upstream (or .remote-branch, or similar) be a better
>> +#     name for this?
>
> Are the docs from 23d25e4 (submodule: explicit local branch creation
> in module_clone, 2014-01-26; now reverted with d851ffb, Revert
> "submodule: explicit local branch creation in module_clone",
> 2014-04-02) clearer?  Maybe we can salvage some of those docs even
> though we've reverted the actual code changes?

As I said above, better docs is good, but no replacement for a better
named option.

>> +# D7: What to do when .branch refers to a branch that is missing from upstream?
>> +#     Currently, when trying to clone, the clone fails (which causes 'git
>> +#     submodule update --remote' to fail), but leaves the submodule in an
>> +#     uninitialized state (there is a .git, but the work tree is missing).
>> +#     This is probably not the behavior we want...
>> +#     Affects: pre, 3.2.2.1, 3.3.2.1, 3.4.2.1, 3.5.2.1
>
> I think we should remove the submodule's .git file after the failed clone.

Agreed, but does that extend to the superproject's .git/modules/<name> too?


Again, sorry for the length and lateness of this discussion. I still
hope it may be of some use, though.

...Johan

--
Johan Herland, <johan@herland.net>
www.herland.net

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

* Re: [RFC] t7410: 210 tests for various 'git submodule update' scenarios
  2014-04-17 11:42   ` Johan Herland
@ 2014-04-17 15:31     ` W. Trevor King
  0 siblings, 0 replies; 5+ messages in thread
From: W. Trevor King @ 2014-04-17 15:31 UTC (permalink / raw
  To: Johan Herland; +Cc: Git mailing list, Jens Lehmann, Heiko Voigt, Junio C Hamano

[-- Attachment #1: Type: text/plain, Size: 8814 bytes --]

On Thu, Apr 17, 2014 at 01:42:42PM +0200, Johan Herland wrote:
> >> +# T2: Test with submodule.<name>.url != submodule's remote.origin.url. Does
> >> +#     "submodule update --remote" sync with submodule.<name>.url, or with the
> >> +#     submodule's origin? (or with the submodule's current branch's upstream)?
> >
> > All fetches should currently use the submodule's remote.origin.url.
> > submodule.<name>.url is only used for the initial clone (*.*.*.1), and
> > never referenced again.  This would change using my tightly-bound
> > submodule proposal [1], where a difference between
> > submodule.<name>.url and the submodule's @{upstream} URL would be
> > trigger a dirty-tree condition (for folks with tight-bind syncing
> > enabled) from which you couldn't update before resolving the
> > difference.
> 
> Ok. As stated above, I am worried about the amount of duplicated
> state between the superproject's submodule config (which itself is
> split between .gitmodules and .git/config) and the submodule's own
> config. And from the above paragraph, I suspect two more dimensions
> need to be added to the test matrix:
> 
>  - submodule's remote.origin.url ==/!= submodule.<name>.url
> 
>  - "tightly-bound submodule" is enabled/disabled

Tight-binding hasn't been implemented yet, or even accumulated much
support from other folks ;).  However, the idea is to unify the state
between the superproject's .gitmodules and .git/config and the
submodule's .git/config (or ../.git/modules/<name>/config, or
whatever).  Then folks with tightly-bound syncing enabled have only
one state space to maintain (and get auto-updates for each
superproject checkout), and folks who opt-out of tightly-bound syncing
are presumably embracing the complexity of our current system, with
it's two, confusingly-aligned configuration spaces.

I'm happy to force syncing (i.e. no opting-out allowed) [1], but I
imagine there are folks who would resist ;).  Maybe a deprecation
period to help ease the transition?  This is all assuming that I get
more folks to buy into the tight-syncing ;).

The end-goal of my tightly-bound approach is to remove 'submodule
update' altogether and end up with a simpler interface [2]:

On Sat, Jan 11, 2014 at 05:08:47PM -0800, W. Trevor King wrote:
> * git submodule [--quiet] add [-b <branch>] [-f|--force] [--name <name>]
>                 [--reference <repository>] [--] <repository> [<path>]
> * git submodule [--quiet] init [--] [<path>...]
> * git submodule [--quiet] deinit [-f|--force] [--] <path>...
> * git submodule [--quiet] foreach [--recursive] <command>

All of this 'submodule update' integration confusion would be resolved
by the developer who updated the gitlink, and superproject checkouts
would just swap the local submodule branch/HEAD without having to
worry about clobbering uncommitted state.

On Thu, Apr 17, 2014 at 01:42:42PM +0200, Johan Herland wrote:
> We should instead seek ways to minimize the duplication of state.

The tightly-bound-submodules I'm proposing try to use the submodule's
config settings (plus submodule.<name>.local-branch) as the familiar
language, while your proposal uses Git commands as the familiar
language.  I think both would work, but config settings are easier to
parse automatically, which helps with automatically syncing between
the superproject and submodule configs.  Syncing, in turn, helps
bridge the gap between the easily shared superproject/.gitmodules and
superproject/.git/modules/<name>/config (enabling familiar-to-use Git
commands in the submodule).

>  - submodule.<name>.create: …

Syncing submodule state back up into this is going to be a manual
operation.  For example, changing the submodule's remote.origin.url is
going to require hand-tweaking to update this setting.

>  - submodule.<name>.update: …
>     …
>     - 'git reset --hard $GITLINK'
>       Equivalent to checkout-mode (without --remote).
> 
>     - 'git fetch && git reset --hard origin/HEAD'
>       Equivalent to checkout-mode with --remote.

Folks who sometimes use --remote updates will still need non-remote
updates.  For example, if Alice and Bob are both developers on the
same superproject:

  alice$ git submodule update --recursive --remote # integrate upstream changes
  alice$ git commit -m 'Bumped submodules to upstream tips'
  alice$ git push
  bob$ git pull
  bob$ git submodule update --recursive # integrate Alice's gitlink changes

so it should be easy to toggle back and forth between the two
integration targets.  However:

  git fetch && git reset --hard origin/HEAD

is easy to run using 'git submodule foreach', or after changing into
the submodule directory, so I'm not particularly concerned here.

With tight-binding and superproject-checkout-time auto-updates, the
above would be:

  alice$ git submodule foreach --recursive 'git pull'
  alice$ git commit -m 'Bumped submodules to upstream tips'
  alice$ git push
  bob$ git pull  # update to Alice's new gitlinks happens automatically

Another problem with a single submodule.<name>.update is if I want to
pull origin/HEAD into my submodule's master branch, but origin/dev
into my dev and feature-x branches (table and further discussion in
[3]).  That's not going to pack down into a single command.  In [3], I
lay out how you could setup per-superproject-branch configs for
.local-branch; you'd need something similar for .update.

> I now realize that my above arguments against increased complexity
> in submodule.<name>.* options arrive way too late, and is probably
> more like trolling than like constructive input.

I'm happy to have more input :).  It's hard to imagine an interface so
polished that it can't be improved, and the current submodule
interface is certainly well short of that hypothetical goal ;).

> >> +# D6: The meaning of submodule.<name>.branch is initially confusing, as it does
> >> +#     not really concern the submodule's local branch (except as a naming hint
> >> +#     when the submodule is first cloned). Instead, submodule.<name>.branch is
> >> +#     really about which branch in the _upstream_ submodule
> >
> > Which is how gitmodules(5) explains it:
> >
> >   submodule.<name>.branch
> >     A remote branch name for tracking updates…
> 
> Good, but I fear gitmodules(5) is too hidden for the regular user.
> It'd be better to mention this in git-submodule(1), as I expect
> gitmodules(5) is largely read by .gitmodules _authors_, and not
> regular users. Obviously, the real fix would be a better name...

I'm fine with a rename to .remote-branch.  Migrating through a config
rename should be pretty easy to do, but it's going to make future
changes more difficult until the migration wraps up $n releases in the
future.  I'd prefer jumping straight to my tight-binding approach,
though; where --remote updates are replaced by the more familiar:

  $ git submodule foreach [--recursive] 'git pull …'

> >> +#     submodule.<name>.url, or by the submodule's remote.origin.url?)
> >> +#     want to integrate with.
> >
> > The submodule's remote.origin.url for everything except the initial
> > clone (*.*.*.1).  See my response to T2.
> 
> As mentioned above, submodule.<name>.url is then an unnecessary state
> duplication.

It's not unnecessary before you've cloned.  And you'll also want to
update it if you change the submodule's remote.origin.url, so other
folks (who haven't cloned yet) aren't stranded with the old URL.

> >> +# D7: What to do when .branch refers to a branch that is missing from upstream?
> >> +#     Currently, when trying to clone, the clone fails (which causes 'git
> >> +#     submodule update --remote' to fail), but leaves the submodule in an
> >> +#     uninitialized state (there is a .git, but the work tree is missing).
> >> +#     This is probably not the behavior we want...
> >> +#     Affects: pre, 3.2.2.1, 3.3.2.1, 3.4.2.1, 3.5.2.1
> >
> > I think we should remove the submodule's .git file after the failed clone.
> 
> Agreed, but does that extend to the superproject's .git/modules/<name> too?

That's what 'git clone' usually does, so yes.  Ideally we'd add a
check to the initial clone that bailed out before fetching tons of
objects in the event that the referenced branch / commit was not
present on the remote.

Cheers,
Trevor

[1]: http://article.gmane.org/gmane.comp.version-control.git/240370
[2]: http://article.gmane.org/gmane.comp.version-control.git/240336
[3]: http://article.gmane.org/gmane.comp.version-control.git/240251

-- 
This email may be signed or encrypted with GnuPG (http://www.gnupg.org).
For more information, see http://en.wikipedia.org/wiki/Pretty_Good_Privacy

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]

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

* Re: [RFC] t7410: 210 tests for various 'git submodule update' scenarios
  2014-04-16  0:54 [RFC] t7410: 210 tests for various 'git submodule update' scenarios Johan Herland
  2014-04-16 17:21 ` W. Trevor King
@ 2014-04-17 16:04 ` Jens Lehmann
  1 sibling, 0 replies; 5+ messages in thread
From: Jens Lehmann @ 2014-04-17 16:04 UTC (permalink / raw
  To: Johan Herland, git; +Cc: W. Trevor King, Heiko Voigt, Junio C Hamano

Am 16.04.2014 02:54, schrieb Johan Herland:
> This is a work-in-progress to flesh out (and promote discussion about)
> the expected behaviors for all possible scenarios in which
> 'git submodule update' might be run.

Thanks a lot, this is a very good idea. After fixing the hidden
staged ignored submodules I'm back working on a very similar topic
I posted an RFC for some time ago. The focus there lies on how git
commands currently handle all possible types of submodule changes
together with "submodule update", which shows some inconsistencies
we currently have:

   http://article.gmane.org/gmane.comp.version-control.git/245046

This is in preparation of the recursive update series, where I
intend to extend these tests to also cover how checkout update
mode handles submodules.

I think it makes lots of sense to nail down our current behavior
first and then talk about what is inconsistent there and how we
should change it. Will try to read this discussion when I find
some time, maybe we can join our efforts to cover all combinations
of configurations and command line options.

> More details in the top of t/t7410-submodule-update-scenarios.sh
> 
> Cc: W. Trevor King <wking@tremily.us>
> Cc: Jens Lehmann <Jens.Lehmann@web.de>
> Cc: Heiko Voigt <hvoigt@hvoigt.net>
> Cc: Junio C Hamano <gitster@pobox.com>
> 
> ---
> 
> Hi,
> 
> This started as an exercise for myself to figure out all the various
> corner cases under which "git submodule update" may be run. Then, as I
> found some cases where the current behavior was not immediately obvious
> to me, and some other cases where the current behaviour is likely buggy
> and/or Wrong(tm), I figured these tests might serve as a backdrop for a
> closer discussion of those cases.
> 
> I've attempted to include all variables/axes that combine to directly
> affect the behavior of 'submodule update'. I've excluded some variables
> whose effect on 'submodule update' is completely orthogonal/independent
> (i.e. how 'submodule update' is affected by unstaged/uncommitted
> changes in the submodule). That said, there might be more variables
> that I have missed in my analysis...
> 
> I've attempted to split the areas to be discussed into 3 todo items and
> 7 discussion items (labeled T1 - T3. and D1 - D7, respectively), and
> I've cross-referenced them to the specific test cases that are affected
> by each todo/discussion item.
> 
> I'll leave it up to Jens and Junio to determine whether this patch
> is worth applying or not. There is certainly considerable overlap
> between these tests and existing tests for 'submodule update'. There's
> also plenty of room for refactoring and deduplication in this file,
> itself, although I hope the current format of the tests makes it fairly
> easy to see what is being tested, and what is the expected behavior.
> 
> Have fun! :)
> 
> ...Johan
> 
> 
>  t/t7410-submodule-update-scenarios.sh | 2859 +++++++++++++++++++++++++++++++++
>  1 file changed, 2859 insertions(+)
>  create mode 100755 t/t7410-submodule-update-scenarios.sh
> 
> diff --git a/t/t7410-submodule-update-scenarios.sh b/t/t7410-submodule-update-scenarios.sh
> new file mode 100755
> index 0000000..22056cc
> --- /dev/null
> +++ b/t/t7410-submodule-update-scenarios.sh
> @@ -0,0 +1,2859 @@
> +#!/bin/sh
> +#
> +# Copyright (c) 2014 Johan Herland
> +#
> +
> +test_description='Test "git submodule update" in a variety of scenarios
> +
> +This test attempts to map out the various scenarios in which submodule update
> +is run, and verify their expected behaviour (according to the git submodule
> +documentation).
> +'
> +
> +. ./test-lib.sh
> +
> +# First a textual representation of the various cases we are testing:
> +#
> +# The factors/variables being explored are:
> +#  - submodule.sub.branch
> +#     1.* - unset
> +#     2.* - == foo (exists upstream)
> +#     3.* - == bar (MISSING upstream)
> +#  - submodule.sub.update
> +#     ?.1.* - none
> +#     ?.2.* - checkout
> +#     ?.3.* - merge
> +#     ?.4.* - rebase
> +#     ?.5.* - !command
> +#  - remote mode (whether or not --remote is given on command-line)
> +#     ?.?.1.* - disabled
> +#     ?.?.2.* - enabled
> +#  - current state of submodule:
> +#     ?.?.?.1 - not yet cloned
> +#     ?.?.?.2 - cloned, detached, HEAD == gitlink
> +#     ?.?.?.3 - cloned, detached, HEAD != gitlink
> +#     ?.?.?.4 - cloned, on branch foo (exists upstream), HEAD == gitlink
> +#     ?.?.?.5 - cloned, on branch foo (exists upstream), HEAD != gitlink
> +#     ?.?.?.6 - cloned, on branch bar (MISSING upstream), HEAD == gitlink
> +#     ?.?.?.7 - cloned, on branch bar (MISSING upstream), HEAD != gitlink
> +#
> +# The above variables are combined to enumerate all possible cases, from
> +# 1.1.1.1 to 3.5.2.7 (yes, that's 210 separate test cases). Each test case is
> +# named with its number followed by a short description of its expected outcome.
> +
> +# TODO ITEMS:
> +#
> +# T1: Rename "master" branch to "default" to test correct behaviour when
> +#     submodule's upstream has no master branch.
> +#     Affects: pre, 1.3.1.1, 1.3.2.1, 1.4.1.1, 1.4.2.1
> +#
> +# T2: Test with submodule.<name>.url != submodule's remote.origin.url. Does
> +#     "submodule update --remote" sync with submodule.<name>.url, or with the
> +#     submodule's origin? (or with the submodule's current branch's upstream)?
> +#
> +# T3: Fix uninitialized submodule state after attempting to clone from upstream
> +#     where .branch is missing.
> +#     Affects: pre, 3.2.2.1, 3.3.2.1, 3.4.2.1, 3.5.2.1
> +
> +# DISCUSSION ITEMS:
> +#
> +# D1: When submodule is already at right commit, checkout-mode currently does
> +#     nothing. Should it instead detach, even when no update is needed?
> +#     Affects: 1.2.1.4, 1.2.1.6, 2.2.1.4, 2.2.1.6, 3.2.1.4, 3.2.1.6
> +#
> +# D2: Should all/some of 1.3.*/1.4.* abort/error because we don't know what to
> +#     merge/rebase with (because .branch is unset)? Or is the current default
> +#     to origin/HEAD OK?
> +#     Affects: 1.3.*, 1.4.*
> +#
> +# D3: When submodule is already at right commit, merge/rebase-mode currently
> +#     does nothing. Should it do something else (e.g. not leave submodule
> +#     detached, or checked out on the "wrong" branch (i.e. != .branch))?
> +#     (This discussion point is related to D1, D5 and D6)
> +#     Affects: 1.3.1.2, 1.3.1.4, 1.3.1.6,
> +#              1.4.1.2, 1.4.1.4, 1.4.1.6,
> +#              2.3.1.2, 2.3.1.4, 2.3.1.6, 2.3.2.3, 2.3.2.5, 2.3.2.7,
> +#              2.4.1.2, 2.4.1.4, 2.4.1.6, 2.4.2.3, 2.4.2.5, 2.4.2.7,
> +#              3.3.1.2, 3.3.1.4, 3.3.1.6
> +#              3.4.1.2, 3.4.1.4, 3.4.1.6
> +#
> +# D4: When 'submodule update' performs a clone to populate a submodule, it
> +#     currently also creates a default branch (named after origin/HEAD) in
> +#     that submodule, EVEN WHEN THAT BRANCH WILL NEVER BE USED (e.g. because
> +#     we're in checkout-mode and submodule will always be detached). Is this
> +#     good, or should the clone performed by 'submodule update' skip the
> +#     automatic local branch creation?
> +#     Affects: 1.2.*.1, 1.3.*.1, 1.4.*.1, 1.5.*.1,
> +#              2.2.*.1, 2.3.*.1, 2.4.*.1, 2.5.*.1,
> +#              3.2.1.1, 3.3.1.1, 3.4.1.1, 3.5.1.1
> +#
> +# D5: When in merge/rebase-mode, and 'submodule update' actually ends up doing
> +#     a merge/rebase, that will happen on the current branch (or detached HEAD)
> +#     and NOT on the configured (or default) .branch. Is this OK? Should we
> +#     abort (or at least warn) instead? (In general, .branch seems only to
> +#     affect the submodule's HEAD when the submodule is first cloned.)
> +#     (This discussion point is related to D3 and D6)
> +#     Affects: 1.3.1.3, 1.3.1.5, 1.3.1.7, 1.3.2.>=2,
> +#              1.4.1.3, 1.4.1.5, 1.4.1.7, 1.4.2.>=2,
> +#              2.3.1.3, 2.3.1.5, 2.3.1.7, 2.3.2.2, 2.3.2.4, 2.3.2.6,
> +#              2.4.1.3, 2.4.1.5, 2.4.1.7, 2.4.2.2, 2.4.2.4, 2.4.2.6
> +#              3.3.1.3, 3.3.1.5, 3.3.1.7
> +#              3.4.1.3, 3.4.1.5, 3.4.1.7
> +#
> +# D6: The meaning of submodule.<name>.branch is initially confusing, as it does
> +#     not really concern the submodule's local branch (except as a naming hint
> +#     when the submodule is first cloned). Instead, submodule.<name>.branch is
> +#     really about which branch in the _upstream_ submodule (as defined by
> +#     submodule.<name>.url, or by the submodule's remote.origin.url?) that we
> +#     want to integrate with. This is probably the more useful setting, and it
> +#     becomes obviously correct after (re-)reading gitmodules(5) and
> +#     git-config(1). However, from just reading the "update" section in
> +#     git-submodule(1) (or not even that), things are not so clear-cut. Would
> +#     submodule.<name>.upstream (or .remote-branch, or similar) be a better
> +#     name for this?
> +#
> +# D7: What to do when .branch refers to a branch that is missing from upstream?
> +#     Currently, when trying to clone, the clone fails (which causes 'git
> +#     submodule update --remote' to fail), but leaves the submodule in an
> +#     uninitialized state (there is a .git, but the work tree is missing).
> +#     This is probably not the behavior we want...
> +#     Affects: pre, 3.2.2.1, 3.3.2.1, 3.4.2.1, 3.5.2.1
> +
> +# First, we need to setup the environment in which the test cases are run.
> +# This consists of an upstream super-repo containing a single submodule.
> +# The submodule commit graph looks like this:
> +#
> +#   $root_commit --->  $old_commit ---> $new_commit  <- master (HEAD) # T1
> +#                \          ^- (superproject's gitlink points here)
> +#                 --> $foo_commit  <- foo
> +#
> +# Each test case then starts by cloning upstream_super -> super and setting up
> +# its submodule + configuration according to the current test case. It then
> +# runs "git submodule update" (with or without --remote) and verifies the
> +# expected results.
> +
> +test_expect_success 'preliminaries: setup upstream super and sub' '
> +	git init upstream_sub &&
> +	(cd upstream_sub &&
> +	 test_commit root_commit &&
> +	 test_commit foo_commit &&
> +	 git branch foo &&
> +	 git reset --hard HEAD^ &&
> +#	 git branch -m default &&  # T1
> +	 test_commit old_commit
> +	) &&
> +	git init upstream_super &&
> +	(cd upstream_super &&
> +	 git submodule add ../upstream_sub sub &&
> +	 test_tick &&
> +	 git commit -m "Add sub"
> +	) &&
> +	(cd upstream_sub &&
> +	 test_commit new_commit
> +	)
> +'
> +
> +# The following commit IDs are used in various test cases
> +root_commit=$(git -C upstream_sub rev-parse --verify -q root_commit)
> +foo_commit=$(git -C upstream_sub rev-parse --verify -q foo_commit)
> +old_commit=$(git -C upstream_sub rev-parse --verify -q old_commit)
> +new_commit=$(git -C upstream_sub rev-parse --verify -q new_commit)
> +
> +fail()
> +{
> +	echo "$@"
> +	false
> +}
> +
> +# Verify that the path in $1 contains a cloned git repo
> +verify_is_cloned()
> +{
> +	test -e "$1/.git" ||
> +		fail "Failed to find cloned repo at '$1'"
> +}
> +
> +# Verify that commit $2 in repo $1 has parents $3, $4, ... (in the given order).
> +verify_parents()
> +{
> +	repo=$1; shift
> +	commit=$1; shift
> +	expect_p=$@
> +	actual_p=$(git --git-dir="$repo/.git" log --format=%P -1 "$commit")
> +	test "$expect_p" = "$actual_p" ||
> +		fail "Expected $commit parents '$expect_p' != '$actual_p'"
> +}
> +
> +# Verify that HEAD in repo $1 points to commit $2.
> +# If $2 is of the form 'parents.<SHA1>[.<SHA1>...]', then check that HEAD
> +# currently points to a commit whose parents are the given SHA1s (in order).
> +verify_head()
> +{
> +	head=$(git --git-dir="$1/.git" rev-parse -q HEAD)
> +	case "$2" in
> +	parents.*)
> +		parents=$(echo "$2" | sed -e 's/^parents\.//' -e 's/\./ /g')
> +		verify_parents "$1" "$head" $parents ||
> +			fail "Failed to verify parents of HEAD in '$1'"
> +		;;
> +	*)
> +		test "$2" = "$head" ||
> +			fail "Expected HEAD in '$1' to be '$2', but is '$head'"
> +	esac
> +}
> +
> +# Verify that the repo in $1 is currently checked out on branch $2.
> +# If $2 is "detached", verify that $1'a HEAD is in fact detached.
> +verify_branch()
> +{
> +	expect=$2
> +	if test "$expect" = "detached"
> +	then
> +		expect=
> +	fi
> +	actual=$(git --git-dir="$1/.git" symbolic-ref --short -q HEAD)
> +	test "$expect" = "$actual" ||
> +		fail "Expected branch in '$1' to be '$expect', but is '$actual'"
> +}
> +
> +# Clone upstream_super to super, and run "git submodule init"
> +setup_super()
> +{
> +	rm -rf super &&
> +	git clone upstream_super super &&
> +	(cd super &&
> +	 git submodule init &&
> +	 test_must_fail verify_is_cloned sub &&
> +	 test "$(git rev-parse -q HEAD:sub)" = "$old_commit"
> +	)
> +}
> +
> +# Setup submodule.sub.branch == $1
> +# If $1 == "unset", ensure submodule.sub.branch is unset
> +setup_sub_branch()
> +{
> +	if test "$1" = "unset"
> +	then
> +		git config --unset-all "submodule.sub.branch"
> +		test_must_fail git config "submodule.sub.branch" ||
> +			error "Failed to unset submodule.sub.branch"
> +	else
> +		git config "submodule.sub.branch" "$1"
> +		actual=$(git config "submodule.sub.branch")
> +		test "$actual" = "$1" ||
> +			error "Failed to set submodule.sub.branch to '$1'"
> +	fi
> +}
> +
> +# Setup submodule.sub.update == $1
> +setup_sub_update()
> +{
> +	git config "submodule.sub.update" "$1"
> +	actual=$(git config "submodule.sub.update")
> +	test "$actual" = "$1" ||
> +		error "Failed to set submodule.sub.update to '$1'"
> +}
> +
> +# Setup the state of the sub submodule according to $1, which is one of:
> +#  - "uncloned" - sub is an empty dir.
> +#  - "detach" - sub is detached at the commit in $2.
> +#  - * - sub is checked out on branch named in $1, at the commit in $2.
> +setup_sub_state()
> +{
> +	case "$1" in
> +	uncloned)
> +		test_must_fail verify_is_cloned sub ||
> +			error "Failed to verify uncloned sub"
> +		;;
> +	detach)
> +		git submodule update --checkout sub &&
> +		(cd sub &&
> +		 git checkout "$2"
> +		) &&
> +		verify_is_cloned sub &&
> +		verify_branch sub detached &&
> +		verify_head sub "$2" ||
> +			error "Failed to detach sub at '$2'"
> +		;;
> +	*)
> +		git submodule update --checkout sub &&
> +		(cd sub &&
> +		 git checkout -b "$1" "$2"
> +		) &&
> +		verify_is_cloned sub &&
> +		verify_branch sub "$1" &&
> +		verify_head sub "$2" ||
> +			error "Failed to attach sub to '$1' at '$2'"
> +	esac
> +}
> +
> +# Verify the state of the sub submodule according to $1, which is one of:
> +#  - "uncloned" - sub is an empty dir.
> +#  - "detach" - sub is detached at the commit in $2.
> +#  - * - sub is checked out on branch named in $1, at the commit in $2.
> +# If $2 is of the form 'parents.<SHA1>[.<SHA1>...]', then check that HEAD
> +# currently points to a commit whose parents are the given SHA1s (in order).
> +verify_sub_state()
> +{
> +	case "$1" in
> +	uncloned)
> +		test_must_fail verify_is_cloned sub ||
> +			fail "Failed to verify uncloned sub"
> +		;;
> +	detach)
> +		verify_is_cloned sub &&
> +		verify_branch sub detached &&
> +		verify_head sub "$2" ||
> +			fail "Failed to verify detached sub at '$2'"
> +		;;
> +	*)
> +		verify_is_cloned sub &&
> +		verify_branch sub "$1" &&
> +		verify_head sub "$2" ||
> +			fail "Failed to verify attached sub at '$1' -> '$2'"
> +	esac
> +}
> +
> +verify_sub_patch_id()
> +{
> +	p1=$(git --git-dir=sub/.git show $1 | git patch-id | cut -d' ' -f1)
> +	p2=$(git --git-dir=sub/.git show $2 | git patch-id | cut -d' ' -f1)
> +	test "$p1" = "$p2" ||
> +		fail "'$1' and '$2' has different patch-ids ('$p1' != '$p2')"
> +}
> +
> +# 1.1.* - .branch is unset, .update == none
> +
> +test_expect_success '1.1.1.1 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '1.1.1.2 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '1.1.1.3 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.1.1.4 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '1.1.1.5 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.1.1.6 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '1.1.1.7 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.1.2.1 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '1.1.2.2 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '1.1.2.3 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.1.2.4 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '1.1.2.5 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.1.2.6 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '1.1.2.7 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 1.2.* - .branch is unset, .update == checkout
> +
> +test_expect_success '1.2.1.1 - clone, detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # D4
> +	)
> +'
> +
> +test_expect_success '1.2.1.2 - already @ $old_commit -> keep detached' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '1.2.1.3 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '1.2.1.4 - already @ $old_commit -> keep attached' ' # D1
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '1.2.1.5 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '1.2.1.6 - already @ $old_commit -> keep attached' ' # D1
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '1.2.1.7 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '1.2.2.1 - clone, detach @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit # D4
> +	)
> +'
> +
> +test_expect_success '1.2.2.2 - detach @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit
> +	)
> +'
> +
> +test_expect_success '1.2.2.3 - detach @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit
> +	)
> +'
> +
> +test_expect_success '1.2.2.4 - detach @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit
> +	)
> +'
> +
> +test_expect_success '1.2.2.5 - detach @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit
> +	)
> +'
> +
> +test_expect_success '1.2.2.6 - detach @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit
> +	)
> +'
> +
> +test_expect_success '1.2.2.7 - detach @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit
> +	)
> +'
> +
> +# 1.3.* - .branch is unset, .update == merge # D2
> +
> +test_expect_success '1.3.1.1 - clone, create branch named from origin/HEAD @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # T1, D4, FIXME: s/detach/master/?
> +	)
> +'
> +
> +test_expect_success '1.3.1.2 - already at $old_commit, remain detached' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '1.3.1.3 - merge $old_commit -> $foo_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_success '1.3.1.4 - already at $old_commit, remain attached to foo' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '1.3.1.5 - merge $old_commit -> $foo_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state foo parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_success '1.3.1.6 - already at $old_commit, remain attached to bar' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '1.3.1.7 - merge $old_commit -> $foo_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state bar parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_success '1.3.2.1 - clone, create branch from origin/HEAD @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit # T1, D4, FIXME: s/detach/master/?
> +	)
> +'
> +
> +test_expect_success '1.3.2.2 - ffwd $old_commit -> $new_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit
> +	)
> +'
> +
> +test_expect_success '1.3.2.3 - merge $new_commit -> $foo_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach parents.$foo_commit.$new_commit
> +	)
> +'
> +
> +test_expect_success '1.3.2.4 - ffwd $old_commit -> $new_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $new_commit
> +	)
> +'
> +
> +test_expect_success '1.3.2.5 - merge $new_commit -> $foo_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo parents.$foo_commit.$new_commit
> +	)
> +'
> +
> +test_expect_success '1.3.2.6 - ffwd $old_commit -> $new_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $new_commit
> +	)
> +'
> +
> +test_expect_success '1.3.2.7 - merge $new_commit -> $foo_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar parents.$foo_commit.$new_commit
> +	)
> +'
> +
> +# 1.4.* - .branch is unset, .update == rebase # D2
> +
> +test_expect_success '1.4.1.1 - clone, create branch named from origin/HEAD @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # T1, D4, FIXME: s/detach/master/?
> +	)
> +'
> +
> +test_expect_success '1.4.1.2 - already at $old_commit, remain detached' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '1.4.1.3 - rebase $foo_commit onto $old_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state detach parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.4.1.4 - already at $old_commit, remain attached to foo' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '1.4.1.5 - rebase $foo_commit onto $old_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state foo parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.4.1.6 - already at $old_commit, remain attached to bar' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '1.4.1.7 - rebase $foo_commit onto $old_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state bar parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.4.2.1 - clone, create branch from origin/HEAD @ $new_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit # T1, D4, FIXME: s/detach/master/?
> +	)
> +'
> +
> +test_expect_success '1.4.2.2 - ffwd $old_commit -> $new_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit
> +	)
> +'
> +
> +test_expect_success '1.4.2.3 - rebase $foo_commit onto $new_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach parents.$new_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.4.2.4 - ffwd $old_commit -> $new_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $new_commit
> +	)
> +'
> +
> +test_expect_success '1.4.2.5 - rebase $foo_commit onto $new_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo parents.$new_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '1.4.2.6 - ffwd $old_commit -> $new_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $new_commit
> +	)
> +'
> +
> +test_expect_success '1.4.2.7 - rebase $foo_commit onto $new_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar parents.$new_commit
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +# 1.5.* - .branch is unset, .update == !command
> +
> +test_expect_success '1.5.1.1 - clone, create branch named from origin/HEAD @ $old_commit. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch unset &&
> +	 git submodule update && # should not invoke "false"
> +	 verify_sub_state detach $old_commit # T1, D4, FIXME: s/detach/master/?
> +	)
> +'
> +
> +test_expect_success '1.5.1.2 - already at $old_commit, remain detached. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch unset &&
> +	 git submodule update && # should not invoke "false"
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: detached @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.1.3 - !command invoked to integrate $old_commit -> $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '1.5.1.4 - already at $old_commit, remain attached to foo. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to foo @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.1.5 - !command invoked to integrate $old_commit -> foo/$foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '1.5.1.6 - already at $old_commit, remain attached to bar. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to bar @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.1.7 - !command invoked to integrate $old_commit -> bar/$foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '1.5.2.1 - clone, create branch from origin/HEAD @ $new_commit. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $new_commit # T1, D4, FIXME: s/detach/master/?
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: detached @ $old_commit, and asked to integrate $new_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
> +test "\$1" = "$new_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.2.2 - !command invoked to integrate $new_commit -> $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: detached @ $foo_commit, and asked to integrate $new_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$new_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.2.3 - !command invoked to integrate $new_commit -> $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to foo @ $old_commit, and asked to integrate $new_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
> +test "\$1" = "$new_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.2.4 - !command invoked to integrate $new_commit -> foo/$old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to foo @ $foo_commit, and asked to integrate $new_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$new_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.2.5 - !command invoked to integrate $new_commit -> foo/$foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to bar @ $old_commit, and asked to integrate $new_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
> +test "\$1" = "$new_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.2.6 - !command invoked to integrate $new_commit -> bar/$old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to bar @ $foo_commit, and asked to integrate $new_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$new_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '1.5.2.7 - !command invoked to integrate $new_commit -> bar/$foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch unset &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +# 2.1.* - .branch == foo (exists upstream), .update == none
> +
> +test_expect_success '2.1.1.1 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '2.1.1.2 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '2.1.1.3 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.1.1.4 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '2.1.1.5 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.1.1.6 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '2.1.1.7 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.1.2.1 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '2.1.2.2 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '2.1.2.3 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.1.2.4 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '2.1.2.5 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.1.2.6 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '2.1.2.7 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 2.2.* - .branch == foo (exists upstream), .update == checkout
> +
> +test_expect_success '2.2.1.1 - clone, detach @ $old_commit (.branch is ignored)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # D4
> +	)
> +'
> +
> +test_expect_success '2.2.1.2 - already @ $old_commit -> keep detached' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '2.2.1.3 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '2.2.1.4 - already @ $old_commit -> keep attached' ' # D1
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '2.2.1.5 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '2.2.1.6 - already @ $old_commit -> keep attached' ' # D1
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '2.2.1.7 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '2.2.2.1 - clone, detach @ $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit # D4
> +	)
> +'
> +
> +test_expect_success '2.2.2.2 - detach @ $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.2.2.3 - already @ $foo_commit, remain detached' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.2.2.4 - detach @ $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.2.2.5 - already @ $foo_commit, remain attached to foo' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.2.2.6 - detach @ $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.2.2.7 - already @ $foo_commit, remain attached to bar' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 2.3.* - .branch == foo (exists upstream), .update == merge
> +
> +test_expect_success '2.3.1.1 - clone, create branch called foo @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/foo/?
> +	)
> +'
> +
> +test_expect_success '2.3.1.2 - already at $old_commit, remain detached' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '2.3.1.3 - merge $old_commit -> $foo_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_success '2.3.1.4 - already at $old_commit, remain attached to foo' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '2.3.1.5 - merge $old_commit -> $foo_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state foo parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_success '2.3.1.6 - already at $old_commit, remain attached to bar' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '2.3.1.7 - merge $old_commit -> $foo_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state bar parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_success '2.3.2.1 - clone, create branch from origin/foo @ $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit # D4, FIXME: s/detach/foo/?
> +	)
> +'
> +
> +test_expect_success '2.3.2.2 - merge $foo_commit -> $old_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach parents.$old_commit.$foo_commit
> +	)
> +'
> +
> +test_expect_success '2.3.2.3 - already @ $foo_commit, remain detached' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.3.2.4 - merge $foo_commit -> $old_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo parents.$old_commit.$foo_commit
> +	)
> +'
> +
> +test_expect_success '2.3.2.5 - already @ $foo_commit, remain attached to foo' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.3.2.6 - merge $foo_commit -> $old_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar parents.$old_commit.$foo_commit
> +	)
> +'
> +
> +test_expect_success '2.3.2.7 - already @ $foo_commit, remain attached to bar' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 2.4.* - .branch == foo (exists upstream), .update == rebase
> +
> +test_expect_success '2.4.1.1 - clone, create branch called foo @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/foo/?
> +	)
> +'
> +
> +test_expect_success '2.4.1.2 - already at $old_commit, remain detached' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '2.4.1.3 - rebase $foo_commit onto $old_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state detach parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.4.1.4 - already at $old_commit, remain attached to foo' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '2.4.1.5 - rebase $foo_commit onto $old_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state foo parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.4.1.6 - already at $old_commit, remain attached to bar' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '2.4.1.7 - rebase $foo_commit onto $old_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state bar parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.4.2.1 - clone, create branch from origin/foo @ $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit # D4, FIXME: s/detach/foo/?
> +	)
> +'
> +
> +test_expect_success '2.4.2.2 - rebase $old_commit onto $foo_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach parents.$foo_commit &&
> +	 verify_sub_patch_id HEAD $old_commit
> +	)
> +'
> +
> +test_expect_success '2.4.2.3 - already @ $foo_commit, remain detached' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.4.2.4 - rebase $old_commit onto $foo_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo parents.$foo_commit &&
> +	 verify_sub_patch_id HEAD $old_commit
> +	)
> +'
> +
> +test_expect_success '2.4.2.5 - already @ $foo_commit, remain attached to foo' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '2.4.2.6 - rebase $old_commit onto $foo_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar parents.$foo_commit &&
> +	 verify_sub_patch_id HEAD $old_commit
> +	)
> +'
> +
> +test_expect_success '2.4.2.7 - already @ $foo_commit, remain attached to bar' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 2.5.* - .branch == foo (exists upstream), .update == !command
> +
> +test_expect_success '2.5.1.1 - clone, create branch called foo @ $old_commit. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch foo &&
> +	 git submodule update && # should not invoke "false"
> +	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/foo/?
> +	)
> +'
> +
> +test_expect_success '2.5.1.2 - already at $old_commit, remain detached. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch foo &&
> +	 git submodule update && # should not invoke "false"
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: detached @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '2.5.1.3 - !command invoked to integrate $old_commit -> $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '2.5.1.4 - already at $old_commit, remain attached to foo. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to foo @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '2.5.1.5 - !command invoked to integrate $old_commit -> foo/$foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '2.5.1.6 - already at $old_commit, remain attached to bar. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to bar @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '2.5.1.7 - !command invoked to integrate $old_commit -> bar/$foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch foo &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '2.5.2.1 - clone, create branch from origin/foo @ $foo_commit. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit # D4, FIXME: s/detach/foo/?
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: detached @ $old_commit, and asked to integrate $foo_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
> +test "\$1" = "$foo_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '2.5.2.2 - !command invoked to integrate $foo_commit -> $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '2.5.2.3 - already at $foo_commit, remain detached. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to foo @ $old_commit, and asked to integrate $foo_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
> +test "\$1" = "$foo_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '2.5.2.4 - !command invoked to integrate $foo_commit -> foo/$old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '2.5.2.5 - already at $foo_commit, remain attached to foo. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to bar @ $old_commit, and asked to integrate $foo_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$old_commit" &&
> +test "\$1" = "$foo_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '2.5.2.6 - !command invoked to integrate $foo_commit -> bar/$old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '2.5.2.7 - already at $foo_commit, remain attached to bar. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch foo &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 3.1.* - .branch == bar (MISSING upstream), .update == none
> +
> +test_expect_success '3.1.1.1 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '3.1.1.2 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.1.1.3 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.1.1.4 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.1.1.5 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.1.1.6 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.1.1.7 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.1.2.1 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update --remote &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '3.1.2.2 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.1.2.3 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.1.2.4 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.1.2.5 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.1.2.6 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.1.2.7 - do nothing (.update == none)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update none &&
> +	 setup_sub_branch bar &&
> +	 git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 3.2.* - .branch == bar (MISSING upstream), .update == checkout
> +
> +test_expect_success '3.2.1.1 - clone, detach @ $old_commit (.branch is ignored)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # D4
> +	)
> +'
> +
> +test_expect_success '3.2.1.2 - already @ $old_commit -> keep detached' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.2.1.3 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.2.1.4 - already @ $old_commit -> keep attached' ' # D1
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.2.1.5 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.2.1.6 - already @ $old_commit -> keep attached' ' # D1
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.2.1.7 - detach @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_failure '3.2.2.1 - fail to clone (remote branch bar missing), leave uncloned' ' # T3, D7
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '3.2.2.2 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.2.2.3 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.2.2.4 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.2.2.5 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.2.2.6 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.2.2.7 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update checkout &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 3.3.* - .branch == bar (MISSING upstream), .update == merge
> +
> +test_expect_success '3.3.1.1 - clone, create branch called bar @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/bar/?
> +	)
> +'
> +
> +test_expect_success '3.3.1.2 - already at $old_commit, remain detached' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.3.1.3 - merge $old_commit -> $foo_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_success '3.3.1.4 - already at $old_commit, remain attached to foo' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.3.1.5 - merge $old_commit -> $foo_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state foo parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_success '3.3.1.6 - already at $old_commit, remain attached to bar' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.3.1.7 - merge $old_commit -> $foo_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state bar parents.$foo_commit.$old_commit
> +	)
> +'
> +
> +test_expect_failure '3.3.2.1 - fail to clone (remote branch bar missing), leave uncloned' ' # T3, D7
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '3.3.2.2 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.3.2.3 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.3.2.4 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.3.2.5 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.3.2.6 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.3.2.7 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update merge &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 3.4.* - .branch == bar (MISSING upstream), .update == rebase
> +
> +test_expect_success '3.4.1.1 - clone, create branch called bar @ $old_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/bar/?
> +	)
> +'
> +
> +test_expect_success '3.4.1.2 - already at $old_commit, remain detached' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.4.1.3 - rebase $foo_commit onto $old_commit, remain detached' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state detach parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.4.1.4 - already at $old_commit, remain attached to foo' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.4.1.5 - rebase $foo_commit onto $old_commit, remain attached to foo' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state foo parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.4.1.6 - already at $old_commit, remain attached to bar' ' # D3
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.4.1.7 - rebase $foo_commit onto $old_commit, remain attached to bar' ' # D5
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state bar parents.$old_commit &&
> +	 verify_sub_patch_id HEAD $foo_commit
> +	)
> +'
> +
> +test_expect_failure '3.4.2.1 - fail to clone (remote branch bar missing), leave uncloned' ' # T3, D7
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '3.4.2.2 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.4.2.3 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.4.2.4 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.4.2.5 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.4.2.6 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.4.2.7 - fail (remote branch bar missing)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update rebase &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +# 3.5.* - .branch == bar (MISSING upstream), .update == !command
> +
> +test_expect_success '3.5.1.1 - clone, create branch called bat @ $old_commit. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 git submodule update && # should not invoke "false"
> +	 verify_sub_state detach $old_commit # D4, FIXME: s/detach/bar/?
> +	)
> +'
> +
> +test_expect_success '3.5.1.2 - already at $old_commit, remain detached. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 git submodule update && # should not invoke "false"
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: detached @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '3.5.1.3 - !command invoked to integrate $old_commit -> $foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '3.5.1.4 - already at $old_commit, remain attached to foo. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to foo @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "foo" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '3.5.1.5 - !command invoked to integrate $old_commit -> foo/$foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_success '3.5.1.6 - already at $old_commit, remain attached to bar. !command is NOT invoked' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +cat > test_command.sh <<EOF
> +#!/bin/sh
> +# Verify: attached to bar @ $foo_commit, and asked to integrate $old_commit
> +test "\$(git symbolic-ref --short -q HEAD)" = "bar" &&
> +test "\$(git rev-parse -q --verify HEAD)" = "$foo_commit" &&
> +test "\$1" = "$old_commit" &&
> +git checkout -b success $root_commit
> +EOF
> +chmod +x test_command.sh
> +
> +test_expect_success '3.5.1.7 - !command invoked to integrate $old_commit -> bar/$foo_commit' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update !../../test_command.sh &&
> +	 setup_sub_branch bar &&
> +	 git submodule update &&
> +	 verify_sub_state success $root_commit # test_command.sh is happy
> +	)
> +'
> +
> +test_expect_failure '3.5.2.1 - fail to clone (remote branch bar missing), leave uncloned' ' # T3, D7
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state uncloned &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state uncloned
> +	)
> +'
> +
> +test_expect_success '3.5.2.2 - fail (remote branch bar missing, !command not invoked)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state detach $old_commit
> +	)
> +'
> +
> +test_expect_success '3.5.2.3 - fail (remote branch bar missing, !command not invoked)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state detach $foo_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state detach $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.5.2.4 - fail (remote branch bar missing, !command not invoked)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state foo $old_commit
> +	)
> +'
> +
> +test_expect_success '3.5.2.5 - fail (remote branch bar missing, !command not invoked)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state foo $foo_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state foo $foo_commit
> +	)
> +'
> +
> +test_expect_success '3.5.2.6 - fail (remote branch bar missing, !command not invoked)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $old_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state bar $old_commit
> +	)
> +'
> +
> +test_expect_success '3.5.2.7 - fail (remote branch bar missing, !command not invoked)' '
> +	setup_super &&
> +	(cd super &&
> +	 setup_sub_state bar $foo_commit &&
> +	 setup_sub_update !false &&
> +	 setup_sub_branch bar &&
> +	 test_must_fail git submodule update --remote &&
> +	 verify_sub_state bar $foo_commit
> +	)
> +'
> +
> +test_done
> 

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

end of thread, other threads:[~2014-04-17 16:06 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-04-16  0:54 [RFC] t7410: 210 tests for various 'git submodule update' scenarios Johan Herland
2014-04-16 17:21 ` W. Trevor King
2014-04-17 11:42   ` Johan Herland
2014-04-17 15:31     ` W. Trevor King
2014-04-17 16:04 ` Jens Lehmann

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.