diff options
-rw-r--r-- | builtin/submodule--helper.c | 37 | ||||
-rwxr-xr-x | t/t5617-clone-submodules.sh | 38 | ||||
-rwxr-xr-x | t/t7406-submodule-update.sh | 156 |
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 |