threads / rfc / 36417

[RFC] t7410: 210 tests for various 'git submodule update' scenarios

Subject: [RFC] t7410: 210 tests for various 'git submodule update' scenarios

## tl;dr

5 messages between Apr 16, 2014 and Apr 17, 2014.

replies: 4people: 3as markdown or json

Johan Herland· Apr 16, 2014, 00:54 UTC · lore

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
W. Trevor King· Apr 16, 2014, 17:21 UTC · re: Johan Herland · lore

Re: [RFC] t7410: 210 tests for various 'git submodule update' scenarios

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 :).
Show 8 quoted lines
> +#  - 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.

Show 9 quoted lines
> +# 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.

Show 12 quoted lines
> +# 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.

Show 6 quoted lines
> …                               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?

Show 6 quoted lines
> +# 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
Johan Herland· Apr 17, 2014, 11:42 UTC · re: W. Trevor King · lore

Re: [RFC] t7410: 210 tests for various 'git submodule update' scenarios

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:
Show 6 quoted lines
> 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...

Show 13 quoted lines
>> +#  - 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...

Show 12 quoted lines
>> +# 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?
Show 6 quoted lines
>> +# 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.

Show 11 quoted lines
>> +# 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.
Show 9 quoted lines
>> +# 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.

Show 12 quoted lines
>> +# 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).

Show 21 quoted lines
>> +# 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.

Show 11 quoted lines
> 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.

Show 9 quoted lines
>> +# 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...

Show 5 quoted lines
>> +#     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)

Show 12 quoted lines
>> …                               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.

Show 8 quoted lines
>> +# 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

W. Trevor King· Apr 17, 2014, 15:31 UTC · re: Johan Herland · lore

Re: [RFC] t7410: 210 tests for various 'git submodule update' scenarios

On Thu, Apr 17, 2014 at 01:42:42PM +0200, Johan Herland wrote:
Show 22 quoted lines
> >> +# 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:
Show 5 quoted lines
> * 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.

Show 7 quoted lines
>  - 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 ;).

Show 14 quoted lines
> >> +# 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 …'
Show 8 quoted lines
> >> +#     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.

Show 10 quoted lines
> >> +# 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
Jens Lehmann· Apr 17, 2014, 16:04 UTC · re: Johan Herland · lore

Re: [RFC] t7410: 210 tests for various 'git submodule update' scenarios

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.

Show 2912 quoted lines
> 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
> 

← back to recent threads