push: propagate remote and refspec with --recurse-submodules

Teach "push --recurse-submodules" to propagate, if given a name as remote, the provided remote and refspec recursively to the pushes performed in the submodules. The push will therefore only succeed if all submodules have a remote with such a name configured. Note that "push --recurse-submodules" with a path or URL as remote will not propagate the remote or refspec and instead use the default remote and refspec configured in the submodule, preserving the current behavior. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Apr 5, 2017 at 10:47 UTC 06bf4ad1db92c32af38e16d9b7f928edbd647780
4 files changed +117 -5
submodule.c
+60 -3
@@ -14,6 +14,7 @@
14 #include "blob.h"
15 #include "thread-utils.h"
16 #include "quote.h"
17 +#include "remote.h"
18 #include "worktree.h"
19
20 static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
@@ -783,6 +784,8 @@ int find_unpushed_submodules(struct sha1_array *commits,
784 }
785
786 static int push_submodule(const char *path,
787 + const struct remote *remote,
788 + const char **refspec, int refspec_nr,
789 const struct string_list *push_options,
790 int dry_run)
791 {
@@ -801,6 +804,14 @@ static int push_submodule(const char *path,
804 argv_array_pushf(&cp.args, "--push-option=%s",
805 item->string);
806 }
807 +
808 + if (remote->origin != REMOTE_UNCONFIGURED) {
809 + int i;
810 + argv_array_push(&cp.args, remote->name);
811 + for (i = 0; i < refspec_nr; i++)
812 + argv_array_push(&cp.args, refspec[i]);
813 + }
814 +
815 prepare_submodule_repo_env(&cp.env_array);
816 cp.git_cmd = 1;
817 cp.no_stdin = 1;
@@ -813,21 +824,67 @@ static int push_submodule(const char *path,
824 return 1;
825 }
826
827 +/*
828 + * Perform a check in the submodule to see if the remote and refspec work.
829 + * Die if the submodule can't be pushed.
830 + */
831 +static void submodule_push_check(const char *path, const struct remote *remote,
832 + const char **refspec, int refspec_nr)
833 +{
834 + struct child_process cp = CHILD_PROCESS_INIT;
835 + int i;
836 +
837 + argv_array_push(&cp.args, "submodule--helper");
838 + argv_array_push(&cp.args, "push-check");
839 + argv_array_push(&cp.args, remote->name);
840 +
841 + for (i = 0; i < refspec_nr; i++)
842 + argv_array_push(&cp.args, refspec[i]);
843 +
844 + prepare_submodule_repo_env(&cp.env_array);
845 + cp.git_cmd = 1;
846 + cp.no_stdin = 1;
847 + cp.no_stdout = 1;
848 + cp.dir = path;
849 +
850 + /*
851 + * Simply indicate if 'submodule--helper push-check' failed.
852 + * More detailed error information will be provided by the
853 + * child process.
854 + */
855 + if (run_command(&cp))
856 + die("process for submodule '%s' failed", path);
857 +}
858 +
859 int push_unpushed_submodules(struct sha1_array *commits,
817 - const char *remotes_name,
860 + const struct remote *remote,
861 + const char **refspec, int refspec_nr,
862 const struct string_list *push_options,
863 int dry_run)
864 {
865 int i, ret = 1;
866 struct string_list needs_pushing = STRING_LIST_INIT_DUP;
867
824 - if (!find_unpushed_submodules(commits, remotes_name, &needs_pushing))
868 + if (!find_unpushed_submodules(commits, remote->name, &needs_pushing))
869 return 1;
870
871 + /*
872 + * Verify that the remote and refspec can be propagated to all
873 + * submodules. This check can be skipped if the remote and refspec
874 + * won't be propagated due to the remote being unconfigured (e.g. a URL
875 + * instead of a remote name).
876 + */
877 + if (remote->origin != REMOTE_UNCONFIGURED)
878 + for (i = 0; i < needs_pushing.nr; i++)
879 + submodule_push_check(needs_pushing.items[i].string,
880 + remote, refspec, refspec_nr);
881 +
882 + /* Actually push the submodules */
883 for (i = 0; i < needs_pushing.nr; i++) {
884 const char *path = needs_pushing.items[i].string;
885 fprintf(stderr, "Pushing submodule '%s'\n", path);
830 - if (!push_submodule(path, push_options, dry_run)) {
886 + if (!push_submodule(path, remote, refspec, refspec_nr,
887 + push_options, dry_run)) {
888 fprintf(stderr, "Unable to push submodule '%s'\n", path);
889 ret = 0;
890 }
submodule.h
+3 -1
@@ -4,6 +4,7 @@
4 struct diff_options;
5 struct argv_array;
6 struct sha1_array;
7 +struct remote;
8
9 enum {
10 RECURSE_SUBMODULES_ONLY = -5,
@@ -91,7 +92,8 @@ extern int find_unpushed_submodules(struct sha1_array *commits,
92 const char *remotes_name,
93 struct string_list *needs_pushing);
94 extern int push_unpushed_submodules(struct sha1_array *commits,
94 - const char *remotes_name,
95 + const struct remote *remote,
96 + const char **refspec, int refspec_nr,
97 const struct string_list *push_options,
98 int dry_run);
99 extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
t/t5531-deep-submodule-push.sh
+52
@@ -475,4 +475,56 @@ test_expect_success 'push only unpushed submodules recursively' '
475 test_cmp expected_pub actual_pub
476 '
477
478 +test_expect_success 'push propagating the remotes name to a submodule' '
479 + git -C work remote add origin ../pub.git &&
480 + git -C work remote add pub ../pub.git &&
481 +
482 + > work/gar/bage/junk10 &&
483 + git -C work/gar/bage add junk10 &&
484 + git -C work/gar/bage commit -m "Tenth junk" &&
485 + git -C work add gar/bage &&
486 + git -C work commit -m "Tenth junk added to gar/bage" &&
487 +
488 + # Fails when submodule does not have a matching remote
489 + test_must_fail git -C work push --recurse-submodules=on-demand pub master &&
490 + # Succeeds when submodules has matching remote and refspec
491 + git -C work push --recurse-submodules=on-demand origin master &&
492 +
493 + git -C submodule.git rev-parse master >actual_submodule &&
494 + git -C pub.git rev-parse master >actual_pub &&
495 + git -C work/gar/bage rev-parse master >expected_submodule &&
496 + git -C work rev-parse master >expected_pub &&
497 + test_cmp expected_submodule actual_submodule &&
498 + test_cmp expected_pub actual_pub
499 +'
500 +
501 +test_expect_success 'push propagating refspec to a submodule' '
502 + > work/gar/bage/junk11 &&
503 + git -C work/gar/bage add junk11 &&
504 + git -C work/gar/bage commit -m "Eleventh junk" &&
505 +
506 + git -C work checkout branch2 &&
507 + git -C work add gar/bage &&
508 + git -C work commit -m "updating gar/bage in branch2" &&
509 +
510 + # Fails when submodule does not have a matching branch
511 + test_must_fail git -C work push --recurse-submodules=on-demand origin branch2 &&
512 + # Fails when refspec includes an object id
513 + test_must_fail git -C work push --recurse-submodules=on-demand origin \
514 + "$(git -C work rev-parse branch2):refs/heads/branch2" &&
515 + # Fails when refspec includes 'HEAD' as it is unsupported at this time
516 + test_must_fail git -C work push --recurse-submodules=on-demand origin \
517 + HEAD:refs/heads/branch2 &&
518 +
519 + git -C work/gar/bage branch branch2 master &&
520 + git -C work push --recurse-submodules=on-demand origin branch2 &&
521 +
522 + git -C submodule.git rev-parse branch2 >actual_submodule &&
523 + git -C pub.git rev-parse branch2 >actual_pub &&
524 + git -C work/gar/bage rev-parse branch2 >expected_submodule &&
525 + git -C work rev-parse branch2 >expected_pub &&
526 + test_cmp expected_submodule actual_submodule &&
527 + test_cmp expected_pub actual_pub
528 +'
529 +
530 test_done
transport.c
+2 -1
@@ -1030,7 +1030,8 @@ int transport_push(struct transport *transport,
1030 sha1_array_append(&commits, ref->new_oid.hash);
1031
1032 if (!push_unpushed_submodules(&commits,
1033 - transport->remote->name,
1033 + transport->remote,
1034 + refspec, refspec_nr,
1035 transport->push_options,
1036 pretend)) {
1037 sha1_array_clear(&commits);