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);