Doc: document push.recurseSubmodules=only

Git learned pushing submodules without pushing the superproject by the user specifying --recurse-submodules=only through 6c656c3fe4 ("submodules: add RECURSE_SUBMODULES_ONLY value", 2016-12-20) and 225e8bf778 ("push: add option to push only submodules", 2016-12-20). For users who use this feature regularly, it is desirable to have an equivalent configuration. It turns out that such a configuration (push.recurseSubmodules=only) is already supported, even though it is neither documented nor mentioned in the commit messages, due to the way the --recurse-submodules=only feature was implemented (a function used to parse --recurse-submodules was updated to support "only", but that same function is used to parse push.recurseSubmodules too). What is left is to document it and test it, which is what this commit does. There is a possible point of confusion when recursing into a submodule that itself has the push.recurseSubmodules=only configuration, because if a repository has only its submodules pushed and not itself, its superproject can never be pushed. Therefore, treat such configurations as being "on-demand", and print a warning message. Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Taylor Blau <me@ttaylorr.com>

Jonathan Tan committed Nov 14, 2022 at 13:37 UTC e62f779ae67ce3babe9e4ee934469993a5e6df49
5 files changed +73 -15
Documentation/config/push.txt
+2 -12
@@ -110,18 +110,8 @@ This will result in only b (a and c are cleared).
110 ----
111
112 push.recurseSubmodules::
113 - Make sure all submodule commits used by the revisions to be pushed
114 - are available on a remote-tracking branch. If the value is 'check'
115 - then Git will verify that all submodule commits that changed in the
116 - revisions to be pushed are available on at least one remote of the
117 - submodule. If any commits are missing, the push will be aborted and
118 - exit with non-zero status. If the value is 'on-demand' then all
119 - submodules that changed in the revisions to be pushed will be
120 - pushed. If on-demand was not able to push all necessary revisions
121 - it will also be aborted and exit with non-zero status. If the value
122 - is 'no' then default behavior of ignoring submodules when pushing
123 - is retained. You may override this configuration at time of push by
124 - specifying '--recurse-submodules=check|on-demand|no'.
113 + May be "check", "on-demand", "only", or "no", with the same behavior
114 + as that of "push --recurse-submodules".
115 If not set, 'no' is used by default, unless 'submodule.recurse' is
116 set (in which case a 'true' value means 'on-demand').
117
Documentation/git-push.txt
+5 -1
@@ -409,10 +409,14 @@ Specifying `--no-force-if-includes` disables this behavior.
409 all submodules that changed in the revisions to be pushed will be
410 pushed. If on-demand was not able to push all necessary revisions it will
411 also be aborted and exit with non-zero status. If 'only' is used all
412 - submodules will be recursively pushed while the superproject is left
412 + submodules will be pushed while the superproject is left
413 unpushed. A value of 'no' or using `--no-recurse-submodules` can be used
414 to override the push.recurseSubmodules configuration variable when no
415 submodule recursion is required.
416 ++
417 +When using 'on-demand' or 'only', if a submodule has a
418 +"push.recurseSubmodules={on-demand,only}" or "submodule.recurse" configuration,
419 +further recursion will occur. In this case, "only" is treated as "on-demand".
420
421 --[no-]verify::
422 Toggle the pre-push hook (see linkgit:githooks[5]). The
builtin/push.c
+10 -2
@@ -466,8 +466,16 @@ static int option_parse_recurse_submodules(const struct option *opt,
466
467 if (unset)
468 *recurse_submodules = RECURSE_SUBMODULES_OFF;
469 - else
470 - *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg);
469 + else {
470 + if (!strcmp(arg, "only-is-on-demand")) {
471 + if (*recurse_submodules == RECURSE_SUBMODULES_ONLY) {
472 + warning(_("recursing into submodule with push.recurseSubmodules=only; using on-demand instead"));
473 + *recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
474 + }
475 + } else {
476 + *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg);
477 + }
478 + }
479
480 return 0;
481 }
submodule.c
+6
@@ -1130,6 +1130,12 @@ static int push_submodule(const char *path,
1130 if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
1131 struct child_process cp = CHILD_PROCESS_INIT;
1132 strvec_push(&cp.args, "push");
1133 + /*
1134 + * When recursing into a submodule, treat any "only" configurations as "on-
1135 + * demand", since "only" would not work (we need all submodules to be pushed
1136 + * in order to be able to push the superproject).
1137 + */
1138 + strvec_push(&cp.args, "--recurse-submodules=only-is-on-demand");
1139 if (dry_run)
1140 strvec_push(&cp.args, "--dry-run");
1141
t/t5531-deep-submodule-push.sh
+50
@@ -512,6 +512,56 @@ test_expect_success 'push only unpushed submodules recursively' '
512 test_cmp expected_pub actual_pub
513 '
514
515 +setup_subsub () {
516 + git init upstream &&
517 + git init upstream/sub &&
518 + git init upstream/sub/deepsub &&
519 + test_commit -C upstream/sub/deepsub innermost &&
520 + git -C upstream/sub submodule add ./deepsub deepsub &&
521 + git -C upstream/sub commit -m middle &&
522 + git -C upstream submodule add ./sub sub &&
523 + git -C upstream commit -m outermost &&
524 +
525 + git -c protocol.file.allow=always clone --recurse-submodules upstream downstream &&
526 + git -C downstream/sub/deepsub checkout -b downstream-branch &&
527 + git -C downstream/sub checkout -b downstream-branch &&
528 + git -C downstream checkout -b downstream-branch
529 +}
530 +
531 +new_downstream_commits () {
532 + test_commit -C downstream/sub/deepsub new-innermost &&
533 + git -C downstream/sub add deepsub &&
534 + git -C downstream/sub commit -m new-middle &&
535 + git -C downstream add sub &&
536 + git -C downstream commit -m new-outermost
537 +}
538 +
539 +test_expect_success 'push with push.recurseSubmodules=only on superproject' '
540 + test_when_finished rm -rf upstream downstream &&
541 + setup_subsub &&
542 + new_downstream_commits &&
543 + git -C downstream config push.recurseSubmodules only &&
544 + git -C downstream push origin downstream-branch &&
545 +
546 + test_must_fail git -C upstream rev-parse refs/heads/downstream-branch &&
547 + git -C upstream/sub rev-parse refs/heads/downstream-branch &&
548 + test_must_fail git -C upstream/sub/deepsub rev-parse refs/heads/downstream-branch
549 +'
550 +
551 +test_expect_success 'push with push.recurseSubmodules=only on superproject and top-level submodule' '
552 + test_when_finished rm -rf upstream downstream &&
553 + setup_subsub &&
554 + new_downstream_commits &&
555 + git -C downstream config push.recurseSubmodules only &&
556 + git -C downstream/sub config push.recurseSubmodules only &&
557 + git -C downstream push origin downstream-branch 2> err &&
558 +
559 + test_must_fail git -C upstream rev-parse refs/heads/downstream-branch &&
560 + git -C upstream/sub rev-parse refs/heads/downstream-branch &&
561 + git -C upstream/sub/deepsub rev-parse refs/heads/downstream-branch &&
562 + grep "recursing into submodule with push.recurseSubmodules=only; using on-demand instead" err
563 +'
564 +
565 test_expect_success 'push propagating the remotes name to a submodule' '
566 git -C work remote add origin ../pub.git &&
567 git -C work remote add pub ../pub.git &&