summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builtin/submodule--helper.c37
-rwxr-xr-xt/t5617-clone-submodules.sh38
-rwxr-xr-xt/t7406-submodule-update.sh156
3 files changed, 227 insertions, 4 deletions
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index ef76a111c7..767a0c81cd 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1917,6 +1917,7 @@ static void submodule_update_clone_release(struct submodule_update_clone *suc)
struct update_data {
const char *prefix;
char *displaypath;
+ const char *super_branch;
enum submodule_update_type update_default;
struct string_list references;
struct submodule_update_strategy update_strategy;
@@ -2091,6 +2092,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
strvec_push(&child->args, suc->update_data->single_branch ?
"--single-branch" :
"--no-single-branch");
+ if (ud->super_branch) {
+ strvec_pushf(&child->args, "--branch=%s", ud->super_branch);
+ strvec_pushf(&child->args, "--branch-oid=%s",
+ oid_to_hex(&ce->oid));
+ }
cleanup:
free(displaypath);
@@ -2254,9 +2260,14 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet,
static int run_update_command(const struct update_data *ud, int subforce)
{
struct child_process cp = CHILD_PROCESS_INIT;
- const char *update_target = oid_to_hex(&ud->oid);;
+ const char *update_target;
int ret;
+ if (ud->update_strategy.type == SM_UPDATE_CHECKOUT && ud->super_branch)
+ update_target = ud->super_branch;
+ else
+ update_target = oid_to_hex(&ud->oid);
+
switch (ud->update_strategy.type) {
case SM_UPDATE_CHECKOUT:
cp.git_cmd = 1;
@@ -2523,6 +2534,7 @@ static int update_submodule(struct update_data *update_data)
int submodule_up_to_date;
int ret;
struct object_id suboid;
+ const char *submodule_head = NULL;
ret = determine_submodule_update_strategy(the_repository,
update_data->just_cloned,
@@ -2533,7 +2545,8 @@ static int update_submodule(struct update_data *update_data)
return ret;
if (!update_data->just_cloned &&
- resolve_gitlink_ref(update_data->sm_path, "HEAD", &suboid, NULL))
+ resolve_gitlink_ref(update_data->sm_path, "HEAD", &suboid,
+ &submodule_head))
return die_message(_("Unable to find current revision in submodule path '%s'"),
update_data->displaypath);
@@ -2568,8 +2581,17 @@ static int update_submodule(struct update_data *update_data)
free(remote_ref);
}
- submodule_up_to_date = !update_data->just_cloned &&
- oideq(&update_data->oid, &suboid);
+ if (update_data->just_cloned)
+ submodule_up_to_date = 0;
+ else if (update_data->super_branch)
+ /* Check that the submodule's HEAD points to super_branch. */
+ submodule_up_to_date =
+ skip_prefix(submodule_head, "refs/heads/",
+ &submodule_head) &&
+ !strcmp(update_data->super_branch, submodule_head);
+ else
+ submodule_up_to_date = oideq(&update_data->oid, &suboid);
+
if (!submodule_up_to_date || update_data->force) {
ret = run_update_procedure(update_data);
if (ret)
@@ -2603,6 +2625,12 @@ static int update_submodules(struct update_data *update_data)
int i, ret = 0;
struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;
+ if (the_repository->settings.submodule_propagate_branches) {
+ struct branch *current_branch = branch_get(NULL);
+ if (current_branch)
+ update_data->super_branch = current_branch->name;
+ }
+
suc.update_data = update_data;
run_processes_parallel_tr2(suc.update_data->max_jobs, update_clone_get_next_task,
update_clone_start_failure,
@@ -2718,6 +2746,7 @@ static int module_update(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, module_update_options,
git_submodule_helper_usage, 0);
+ prepare_repo_settings(the_repository);
if (opt.require_init)
opt.init = 1;
diff --git a/t/t5617-clone-submodules.sh b/t/t5617-clone-submodules.sh
index c43a5b26fa..43f9b52bd4 100755
--- a/t/t5617-clone-submodules.sh
+++ b/t/t5617-clone-submodules.sh
@@ -13,10 +13,17 @@ test_expect_success 'setup' '
git config --global protocol.file.allow always &&
git checkout -b main &&
test_commit commit1 &&
+ mkdir subsub &&
+ (
+ cd subsub &&
+ git init &&
+ test_commit subsubcommit1
+ ) &&
mkdir sub &&
(
cd sub &&
git init &&
+ git submodule add "file://$pwd/subsub" subsub &&
test_commit subcommit1 &&
git tag sub_when_added_to_super &&
git branch other
@@ -107,4 +114,35 @@ test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodule
test_cmp_config -C super_clone3/sub false --default false remote.origin.promisor
'
+test_expect_success 'submodule.propagateBranches checks out branches at correct commits' '
+ test_when_finished "git checkout main" &&
+
+ git checkout -b checked-out &&
+ git -C sub checkout -b not-in-clone &&
+ git -C subsub checkout -b not-in-clone &&
+ git clone --recurse-submodules \
+ --branch checked-out \
+ -c submodule.propagateBranches=true \
+ "file://$pwd/." super_clone4 &&
+
+ # Assert that each repo is pointing to "checked-out"
+ for REPO in "super_clone4" "super_clone4/sub" "super_clone4/sub/subsub"
+ do
+ HEAD_BRANCH=$(git -C $REPO symbolic-ref HEAD) &&
+ test $HEAD_BRANCH = "refs/heads/checked-out" || return 1
+ done &&
+
+ # Assert that the submodule branches are pointing to the right revs
+ EXPECT_SUB_OID="$(git -C super_clone4 rev-parse :sub)" &&
+ ACTUAL_SUB_OID="$(git -C super_clone4/sub rev-parse refs/heads/checked-out)" &&
+ test $EXPECT_SUB_OID = $ACTUAL_SUB_OID &&
+ EXPECT_SUBSUB_OID="$(git -C super_clone4/sub rev-parse :subsub)" &&
+ ACTUAL_SUBSUB_OID="$(git -C super_clone4/sub/subsub rev-parse refs/heads/checked-out)" &&
+ test $EXPECT_SUBSUB_OID = $ACTUAL_SUBSUB_OID &&
+
+ # Assert that the submodules do not have branches from their upstream
+ test_must_fail git -C super_clone4/sub rev-parse not-in-clone &&
+ test_must_fail git -C super_clone4/sub/subsub rev-parse not-in-clone
+'
+
test_done
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index f094e3d7f3..b749d35f78 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -1179,4 +1179,160 @@ test_expect_success 'submodule update --recursive skip submodules with strategy=
test_cmp expect.err actual.err
'
+test_expect_success 'setup superproject with submodule.propagateBranches' '
+ git init sub1 &&
+ test_commit -C sub1 "sub1" &&
+ git init branch-super &&
+ git -C branch-super submodule add ../sub1 sub1 &&
+ git -C branch-super commit -m "super" &&
+
+ # Clone into a clean repo that we can cp around
+ git clone --recurse-submodules \
+ -c submodule.propagateBranches=true \
+ branch-super branch-super-clean &&
+ git -C branch-super-clean config submodule.propagateBranches true &&
+
+ # sub2 will not be in the clone. We will fetch the containing
+ # superproject commit and clone sub2 with "git submodule update".
+ git init sub2 &&
+ test_commit -C sub2 "sub2" &&
+ git -C branch-super submodule add ../sub2 sub2 &&
+ git -C branch-super commit -m "add sub2"
+'
+
+test_clean_submodule ()
+{
+ local negate super_dir sub_dir expect_oid actual_oid &&
+ if test "$1" = "!"
+ then
+ negate=t
+ shift
+ fi
+ super_dir="$1" &&
+ sub_dir="$2" &&
+ expect_oid="$(git -C "$super_dir" rev-parse ":$sub_dir")" &&
+ actual_oid="$(git -C "$super_dir/$sub_dir" rev-parse HEAD)" &&
+ if test -n "$negate"
+ then
+ ! test "$expect_oid" = "$actual_oid"
+ else
+ test "$expect_oid" = "$actual_oid"
+ fi
+}
+
+# Test the behavior of a newly cloned submodule
+test_expect_success 'branches - newly-cloned submodule, detached HEAD' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned fetch origin main &&
+ git -C branch-super-cloned checkout FETCH_HEAD &&
+ git -C branch-super-cloned/sub1 checkout --detach &&
+ git -C branch-super-cloned submodule update &&
+
+ # sub1 and sub2 should be in detached HEAD
+ git -C branch-super-cloned/sub1 rev-parse --verify HEAD &&
+ test_must_fail git -C branch-super-cloned/sub1 symbolic-ref HEAD &&
+ test_clean_submodule branch-super-cloned sub1 &&
+ git -C branch-super-cloned/sub2 rev-parse --verify HEAD &&
+ test_must_fail git -C branch-super-cloned/sub2 symbolic-ref HEAD &&
+ test_clean_submodule branch-super-cloned sub2
+'
+
+test_expect_success 'branches - newly-cloned submodule, branch checked out' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned fetch origin main &&
+ git -C branch-super-cloned checkout FETCH_HEAD &&
+ git -C branch-super-cloned branch new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 branch new-branch &&
+ git -C branch-super-cloned submodule update &&
+
+ # Ignore sub1, we will test it later.
+ # sub2 should check out the branch
+ HEAD_BRANCH2=$(git -C branch-super-cloned/sub2 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH2 = "refs/heads/new-branch" &&
+ test_clean_submodule branch-super-cloned sub2
+'
+
+# Test the behavior of an already-cloned submodule.
+# NEEDSWORK When updating with branches, we always use the branch instead of the
+# gitlink's OID. This results in some imperfect behavior:
+#
+# - If the gitlink's OID disagrees with the branch OID, updating with branches
+# may result in a dirty worktree
+# - If the branch does not exist, the update fails.
+#
+# We will reevaluate when "git checkout --recurse-submodules" supports branches
+# For now, just test for this imperfect behavior.
+test_expect_success 'branches - correct branch checked out, OIDs agree' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch --recurse-submodules new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 checkout new-branch &&
+ git -C branch-super-cloned submodule update &&
+
+ HEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH1 = "refs/heads/new-branch" &&
+ test_clean_submodule branch-super-cloned sub1
+'
+
+test_expect_success 'branches - correct branch checked out, OIDs disagree' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch --recurse-submodules new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 checkout new-branch &&
+ test_commit -C branch-super-cloned/sub1 new-commit &&
+ git -C branch-super-cloned submodule update &&
+
+ HEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH1 = "refs/heads/new-branch" &&
+ test_clean_submodule ! branch-super-cloned sub1
+'
+
+test_expect_success 'branches - other branch checked out, correct branch exists, OIDs agree' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch --recurse-submodules new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 checkout main &&
+ git -C branch-super-cloned submodule update &&
+
+ HEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH1 = "refs/heads/new-branch" &&
+ test_clean_submodule branch-super-cloned sub1
+'
+
+test_expect_success 'branches - other branch checked out, correct branch exists, OIDs disagree' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch --recurse-submodules new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ git -C branch-super-cloned/sub1 checkout new-branch &&
+ test_commit -C branch-super-cloned/sub1 new-commit &&
+ git -C branch-super-cloned/sub1 checkout main &&
+ git -C branch-super-cloned submodule update &&
+
+ HEAD_BRANCH1=$(git -C branch-super-cloned/sub1 symbolic-ref HEAD) &&
+ test $HEAD_BRANCH1 = "refs/heads/new-branch" &&
+ test_clean_submodule ! branch-super-cloned sub1
+'
+
+test_expect_success 'branches - other branch checked out, correct branch does not exist' '
+ test_when_finished "rm -fr branch-super-cloned" &&
+ cp -r branch-super-clean branch-super-cloned &&
+
+ git -C branch-super-cloned branch new-branch &&
+ git -C branch-super-cloned checkout new-branch &&
+ test_must_fail git -C branch-super-cloned submodule update
+'
+
test_done