Skip to content

Commit 872e2cf

Browse files
committed
Merge branch 'bw/push-options-recursively-to-submodules'
"git push --recurse-submodules --push-option=<string>" learned to propagate the push option recursively down to pushes in submodules. * bw/push-options-recursively-to-submodules: push: propagate remote and refspec with --recurse-submodules submodule--helper: add push-check subcommand remote: expose parse_push_refspec function push: propagate push-options with --recurse-submodules push: unmark a local variable as static
2 parents b1081e4 + 06bf4ad commit 872e2cf

File tree

9 files changed

+219
-9
lines changed

9 files changed

+219
-9
lines changed

builtin/push.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
510510
int push_cert = -1;
511511
int rc;
512512
const char *repo = NULL; /* default repository */
513-
static struct string_list push_options = STRING_LIST_INIT_DUP;
514-
static struct string_list_item *item;
513+
struct string_list push_options = STRING_LIST_INIT_DUP;
514+
const struct string_list_item *item;
515515

516516
struct option options[] = {
517517
OPT__VERBOSITY(&verbosity),
@@ -584,6 +584,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
584584
die(_("push options must not have new line characters"));
585585

586586
rc = do_push(repo, flags, &push_options);
587+
string_list_clear(&push_options, 0);
587588
if (rc == -1)
588589
usage_with_options(push_usage, options);
589590
else

builtin/submodule--helper.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,50 @@ static int resolve_remote_submodule_branch(int argc, const char **argv,
11051105
return 0;
11061106
}
11071107

1108+
static int push_check(int argc, const char **argv, const char *prefix)
1109+
{
1110+
struct remote *remote;
1111+
1112+
if (argc < 2)
1113+
die("submodule--helper push-check requires at least 1 argument");
1114+
1115+
/*
1116+
* The remote must be configured.
1117+
* This is to avoid pushing to the exact same URL as the parent.
1118+
*/
1119+
remote = pushremote_get(argv[1]);
1120+
if (!remote || remote->origin == REMOTE_UNCONFIGURED)
1121+
die("remote '%s' not configured", argv[1]);
1122+
1123+
/* Check the refspec */
1124+
if (argc > 2) {
1125+
int i, refspec_nr = argc - 2;
1126+
struct ref *local_refs = get_local_heads();
1127+
struct refspec *refspec = parse_push_refspec(refspec_nr,
1128+
argv + 2);
1129+
1130+
for (i = 0; i < refspec_nr; i++) {
1131+
struct refspec *rs = refspec + i;
1132+
1133+
if (rs->pattern || rs->matching)
1134+
continue;
1135+
1136+
/*
1137+
* LHS must match a single ref
1138+
* NEEDSWORK: add logic to special case 'HEAD' once
1139+
* working with submodules in a detached head state
1140+
* ceases to be the norm.
1141+
*/
1142+
if (count_refspec_match(rs->src, local_refs, NULL) != 1)
1143+
die("src refspec '%s' must name a ref",
1144+
rs->src);
1145+
}
1146+
free_refspec(refspec_nr, refspec);
1147+
}
1148+
1149+
return 0;
1150+
}
1151+
11081152
static int absorb_git_dirs(int argc, const char **argv, const char *prefix)
11091153
{
11101154
int i;
@@ -1170,6 +1214,7 @@ static struct cmd_struct commands[] = {
11701214
{"resolve-relative-url-test", resolve_relative_url_test, 0},
11711215
{"init", module_init, SUPPORT_SUPER_PREFIX},
11721216
{"remote-branch", resolve_remote_submodule_branch, 0},
1217+
{"push-check", push_check, 0},
11731218
{"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
11741219
{"is-active", is_active, 0},
11751220
};

remote.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec)
630630
return parse_refspec_internal(nr_refspec, refspec, 1, 0);
631631
}
632632

633-
static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
633+
struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
634634
{
635635
return parse_refspec_internal(nr_refspec, refspec, 0, 0);
636636
}

remote.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ struct ref *ref_remove_duplicates(struct ref *ref_map);
169169

170170
int valid_fetch_refspec(const char *refspec);
171171
struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
172+
extern struct refspec *parse_push_refspec(int nr_refspec, const char **refspec);
172173

173174
void free_refspec(int nr_refspec, struct refspec *refspec);
174175

submodule.c

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "blob.h"
1515
#include "thread-utils.h"
1616
#include "quote.h"
17+
#include "remote.h"
1718
#include "worktree.h"
1819

1920
static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
@@ -783,7 +784,11 @@ int find_unpushed_submodules(struct oid_array *commits,
783784
return needs_pushing->nr;
784785
}
785786

786-
static int push_submodule(const char *path, int dry_run)
787+
static int push_submodule(const char *path,
788+
const struct remote *remote,
789+
const char **refspec, int refspec_nr,
790+
const struct string_list *push_options,
791+
int dry_run)
787792
{
788793
if (add_submodule_odb(path))
789794
return 1;
@@ -794,6 +799,20 @@ static int push_submodule(const char *path, int dry_run)
794799
if (dry_run)
795800
argv_array_push(&cp.args, "--dry-run");
796801

802+
if (push_options && push_options->nr) {
803+
const struct string_list_item *item;
804+
for_each_string_list_item(item, push_options)
805+
argv_array_pushf(&cp.args, "--push-option=%s",
806+
item->string);
807+
}
808+
809+
if (remote->origin != REMOTE_UNCONFIGURED) {
810+
int i;
811+
argv_array_push(&cp.args, remote->name);
812+
for (i = 0; i < refspec_nr; i++)
813+
argv_array_push(&cp.args, refspec[i]);
814+
}
815+
797816
prepare_submodule_repo_env(&cp.env_array);
798817
cp.git_cmd = 1;
799818
cp.no_stdin = 1;
@@ -806,20 +825,67 @@ static int push_submodule(const char *path, int dry_run)
806825
return 1;
807826
}
808827

828+
/*
829+
* Perform a check in the submodule to see if the remote and refspec work.
830+
* Die if the submodule can't be pushed.
831+
*/
832+
static void submodule_push_check(const char *path, const struct remote *remote,
833+
const char **refspec, int refspec_nr)
834+
{
835+
struct child_process cp = CHILD_PROCESS_INIT;
836+
int i;
837+
838+
argv_array_push(&cp.args, "submodule--helper");
839+
argv_array_push(&cp.args, "push-check");
840+
argv_array_push(&cp.args, remote->name);
841+
842+
for (i = 0; i < refspec_nr; i++)
843+
argv_array_push(&cp.args, refspec[i]);
844+
845+
prepare_submodule_repo_env(&cp.env_array);
846+
cp.git_cmd = 1;
847+
cp.no_stdin = 1;
848+
cp.no_stdout = 1;
849+
cp.dir = path;
850+
851+
/*
852+
* Simply indicate if 'submodule--helper push-check' failed.
853+
* More detailed error information will be provided by the
854+
* child process.
855+
*/
856+
if (run_command(&cp))
857+
die("process for submodule '%s' failed", path);
858+
}
859+
809860
int push_unpushed_submodules(struct oid_array *commits,
810-
const char *remotes_name,
861+
const struct remote *remote,
862+
const char **refspec, int refspec_nr,
863+
const struct string_list *push_options,
811864
int dry_run)
812865
{
813866
int i, ret = 1;
814867
struct string_list needs_pushing = STRING_LIST_INIT_DUP;
815868

816-
if (!find_unpushed_submodules(commits, remotes_name, &needs_pushing))
869+
if (!find_unpushed_submodules(commits, remote->name, &needs_pushing))
817870
return 1;
818871

872+
/*
873+
* Verify that the remote and refspec can be propagated to all
874+
* submodules. This check can be skipped if the remote and refspec
875+
* won't be propagated due to the remote being unconfigured (e.g. a URL
876+
* instead of a remote name).
877+
*/
878+
if (remote->origin != REMOTE_UNCONFIGURED)
879+
for (i = 0; i < needs_pushing.nr; i++)
880+
submodule_push_check(needs_pushing.items[i].string,
881+
remote, refspec, refspec_nr);
882+
883+
/* Actually push the submodules */
819884
for (i = 0; i < needs_pushing.nr; i++) {
820885
const char *path = needs_pushing.items[i].string;
821886
fprintf(stderr, "Pushing submodule '%s'\n", path);
822-
if (!push_submodule(path, dry_run)) {
887+
if (!push_submodule(path, remote, refspec, refspec_nr,
888+
push_options, dry_run)) {
823889
fprintf(stderr, "Unable to push submodule '%s'\n", path);
824890
ret = 0;
825891
}

submodule.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
struct diff_options;
55
struct argv_array;
66
struct oid_array;
7+
struct remote;
78

89
enum {
910
RECURSE_SUBMODULES_ONLY = -5,
@@ -91,7 +92,9 @@ extern int find_unpushed_submodules(struct oid_array *commits,
9192
const char *remotes_name,
9293
struct string_list *needs_pushing);
9394
extern int push_unpushed_submodules(struct oid_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,
9598
int dry_run);
9699
extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
97100
extern int parallel_submodules(void);

t/t5531-deep-submodule-push.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,4 +475,56 @@ test_expect_success 'push only unpushed submodules recursively' '
475475
test_cmp expected_pub actual_pub
476476
'
477477

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+
478530
test_done

t/t5545-push-options.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,46 @@ test_expect_success 'push options work properly across http' '
142142
test_cmp expect actual
143143
'
144144

145+
test_expect_success 'push options and submodules' '
146+
test_when_finished "rm -rf parent" &&
147+
test_when_finished "rm -rf parent_upstream" &&
148+
mk_repo_pair &&
149+
git -C upstream config receive.advertisePushOptions true &&
150+
cp -r upstream parent_upstream &&
151+
test_commit -C upstream one &&
152+
153+
test_create_repo parent &&
154+
git -C parent remote add up ../parent_upstream &&
155+
test_commit -C parent one &&
156+
git -C parent push --mirror up &&
157+
158+
git -C parent submodule add ../upstream workbench &&
159+
git -C parent/workbench remote add up ../../upstream &&
160+
git -C parent commit -m "add submoule" &&
161+
162+
test_commit -C parent/workbench two &&
163+
git -C parent add workbench &&
164+
git -C parent commit -m "update workbench" &&
165+
166+
git -C parent push \
167+
--push-option=asdf --push-option="more structured text" \
168+
--recurse-submodules=on-demand up master &&
169+
170+
git -C upstream rev-parse --verify master >expect &&
171+
git -C parent/workbench rev-parse --verify master >actual &&
172+
test_cmp expect actual &&
173+
174+
git -C parent_upstream rev-parse --verify master >expect &&
175+
git -C parent rev-parse --verify master >actual &&
176+
test_cmp expect actual &&
177+
178+
printf "asdf\nmore structured text\n" >expect &&
179+
test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
180+
test_cmp expect upstream/.git/hooks/post-receive.push_options &&
181+
test_cmp expect parent_upstream/.git/hooks/pre-receive.push_options &&
182+
test_cmp expect parent_upstream/.git/hooks/post-receive.push_options
183+
'
184+
145185
stop_httpd
146186

147187
test_done

transport.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,9 @@ int transport_push(struct transport *transport,
10311031
&ref->new_oid);
10321032

10331033
if (!push_unpushed_submodules(&commits,
1034-
transport->remote->name,
1034+
transport->remote,
1035+
refspec, refspec_nr,
1036+
transport->push_options,
10351037
pretend)) {
10361038
oid_array_clear(&commits);
10371039
die("Failed to push all needed submodules!");

0 commit comments

Comments
 (0)