From 54cc8aca60e474e637e9d71b57ee2c6f1fb197e6 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 31 Mar 2017 16:56:22 -0700 Subject: push: unmark a local variable as static There isn't any obvious reason for the 'struct string_list push_options' and 'struct string_list_item *item' to be marked as static, so unmark them as being static. Also, clear the push_options string_list to prevent memory leaking. Signed-off-by: Brandon Williams Reviewed-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- builtin/push.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin/push.c b/builtin/push.c index 5c22e9f2e5..a597759d8f 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -510,8 +510,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) int push_cert = -1; int rc; const char *repo = NULL; /* default repository */ - static struct string_list push_options = STRING_LIST_INIT_DUP; - static struct string_list_item *item; + struct string_list push_options = STRING_LIST_INIT_DUP; + const struct string_list_item *item; struct option options[] = { OPT__VERBOSITY(&verbosity), @@ -584,6 +584,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) die(_("push options must not have new line characters")); rc = do_push(repo, flags, &push_options); + string_list_clear(&push_options, 0); if (rc == -1) usage_with_options(push_usage, options); else -- cgit v1.2.3 From 2a90556dde47f27e12a3f8adb1397fd05e5b6690 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 5 Apr 2017 10:47:16 -0700 Subject: push: propagate push-options with --recurse-submodules Teach push --recurse-submodules to propagate push-options recursively to the pushes performed in the submodules. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- submodule.c | 13 +++++++++++-- submodule.h | 1 + t/t5545-push-options.sh | 40 ++++++++++++++++++++++++++++++++++++++++ transport.c | 1 + 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/submodule.c b/submodule.c index c52d6634c5..de444be61a 100644 --- a/submodule.c +++ b/submodule.c @@ -782,7 +782,9 @@ int find_unpushed_submodules(struct sha1_array *commits, return needs_pushing->nr; } -static int push_submodule(const char *path, int dry_run) +static int push_submodule(const char *path, + const struct string_list *push_options, + int dry_run) { if (add_submodule_odb(path)) return 1; @@ -793,6 +795,12 @@ static int push_submodule(const char *path, int dry_run) if (dry_run) argv_array_push(&cp.args, "--dry-run"); + if (push_options && push_options->nr) { + const struct string_list_item *item; + for_each_string_list_item(item, push_options) + argv_array_pushf(&cp.args, "--push-option=%s", + item->string); + } prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; @@ -807,6 +815,7 @@ static int push_submodule(const char *path, int dry_run) int push_unpushed_submodules(struct sha1_array *commits, const char *remotes_name, + const struct string_list *push_options, int dry_run) { int i, ret = 1; @@ -818,7 +827,7 @@ int push_unpushed_submodules(struct sha1_array *commits, for (i = 0; i < needs_pushing.nr; i++) { const char *path = needs_pushing.items[i].string; fprintf(stderr, "Pushing submodule '%s'\n", path); - if (!push_submodule(path, dry_run)) { + if (!push_submodule(path, push_options, dry_run)) { fprintf(stderr, "Unable to push submodule '%s'\n", path); ret = 0; } diff --git a/submodule.h b/submodule.h index 8a8bc49dc9..0e26430fd3 100644 --- a/submodule.h +++ b/submodule.h @@ -92,6 +92,7 @@ extern int find_unpushed_submodules(struct sha1_array *commits, struct string_list *needs_pushing); extern int push_unpushed_submodules(struct sha1_array *commits, const char *remotes_name, + const struct string_list *push_options, int dry_run); extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); extern int parallel_submodules(void); diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh index 97065e62b8..f9232f5d0f 100755 --- a/t/t5545-push-options.sh +++ b/t/t5545-push-options.sh @@ -142,6 +142,46 @@ test_expect_success 'push options work properly across http' ' test_cmp expect actual ' +test_expect_success 'push options and submodules' ' + test_when_finished "rm -rf parent" && + test_when_finished "rm -rf parent_upstream" && + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + cp -r upstream parent_upstream && + test_commit -C upstream one && + + test_create_repo parent && + git -C parent remote add up ../parent_upstream && + test_commit -C parent one && + git -C parent push --mirror up && + + git -C parent submodule add ../upstream workbench && + git -C parent/workbench remote add up ../../upstream && + git -C parent commit -m "add submoule" && + + test_commit -C parent/workbench two && + git -C parent add workbench && + git -C parent commit -m "update workbench" && + + git -C parent push \ + --push-option=asdf --push-option="more structured text" \ + --recurse-submodules=on-demand up master && + + git -C upstream rev-parse --verify master >expect && + git -C parent/workbench rev-parse --verify master >actual && + test_cmp expect actual && + + git -C parent_upstream rev-parse --verify master >expect && + git -C parent rev-parse --verify master >actual && + test_cmp expect actual && + + printf "asdf\nmore structured text\n" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options && + test_cmp expect upstream/.git/hooks/post-receive.push_options && + test_cmp expect parent_upstream/.git/hooks/pre-receive.push_options && + test_cmp expect parent_upstream/.git/hooks/post-receive.push_options +' + stop_httpd test_done diff --git a/transport.c b/transport.c index 417ed7f19f..64e60b6353 100644 --- a/transport.c +++ b/transport.c @@ -1031,6 +1031,7 @@ int transport_push(struct transport *transport, if (!push_unpushed_submodules(&commits, transport->remote->name, + transport->push_options, pretend)) { sha1_array_clear(&commits); die("Failed to push all needed submodules!"); -- cgit v1.2.3 From c19ae47a7940428f8f3f1c49ecdb8906f03c43fa Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 5 Apr 2017 10:47:17 -0700 Subject: remote: expose parse_push_refspec function A future patch needs access to the 'parse_push_refspec()' function so let's export the function so other modules can use it. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- remote.c | 2 +- remote.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/remote.c b/remote.c index 9f83fe2c4c..d335a64173 100644 --- a/remote.c +++ b/remote.c @@ -630,7 +630,7 @@ struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec) return parse_refspec_internal(nr_refspec, refspec, 1, 0); } -static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) +struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) { return parse_refspec_internal(nr_refspec, refspec, 0, 0); } diff --git a/remote.h b/remote.h index dd8c517577..42c8f017b7 100644 --- a/remote.h +++ b/remote.h @@ -169,6 +169,7 @@ struct ref *ref_remove_duplicates(struct ref *ref_map); int valid_fetch_refspec(const char *refspec); struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec); +extern struct refspec *parse_push_refspec(int nr_refspec, const char **refspec); void free_refspec(int nr_refspec, struct refspec *refspec); -- cgit v1.2.3 From 93481a6b89be41086997ecaf9454f0c0bf6a961e Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 5 Apr 2017 10:47:18 -0700 Subject: submodule--helper: add push-check subcommand Add the 'push-check' subcommand to submodule--helper which is used to check if the provided remote and refspec can be used as part of a push operation in the submodule. Signed-off-by: Brandon Williams Signed-off-by: Junio C Hamano --- builtin/submodule--helper.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 85aafe46a4..6ee962207c 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1105,6 +1105,50 @@ static int resolve_remote_submodule_branch(int argc, const char **argv, return 0; } +static int push_check(int argc, const char **argv, const char *prefix) +{ + struct remote *remote; + + if (argc < 2) + die("submodule--helper push-check requires at least 1 argument"); + + /* + * The remote must be configured. + * This is to avoid pushing to the exact same URL as the parent. + */ + remote = pushremote_get(argv[1]); + if (!remote || remote->origin == REMOTE_UNCONFIGURED) + die("remote '%s' not configured", argv[1]); + + /* Check the refspec */ + if (argc > 2) { + int i, refspec_nr = argc - 2; + struct ref *local_refs = get_local_heads(); + struct refspec *refspec = parse_push_refspec(refspec_nr, + argv + 2); + + for (i = 0; i < refspec_nr; i++) { + struct refspec *rs = refspec + i; + + if (rs->pattern || rs->matching) + continue; + + /* + * LHS must match a single ref + * NEEDSWORK: add logic to special case 'HEAD' once + * working with submodules in a detached head state + * ceases to be the norm. + */ + if (count_refspec_match(rs->src, local_refs, NULL) != 1) + die("src refspec '%s' must name a ref", + rs->src); + } + free_refspec(refspec_nr, refspec); + } + + return 0; +} + static int absorb_git_dirs(int argc, const char **argv, const char *prefix) { int i; @@ -1170,6 +1214,7 @@ static struct cmd_struct commands[] = { {"resolve-relative-url-test", resolve_relative_url_test, 0}, {"init", module_init, SUPPORT_SUPER_PREFIX}, {"remote-branch", resolve_remote_submodule_branch, 0}, + {"push-check", push_check, 0}, {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, {"is-active", is_active, 0}, }; -- cgit v1.2.3 From 06bf4ad1db92c32af38e16d9b7f928edbd647780 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Wed, 5 Apr 2017 10:47:19 -0700 Subject: 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 Signed-off-by: Junio C Hamano --- submodule.c | 63 ++++++++++++++++++++++++++++++++++++++++-- submodule.h | 4 ++- t/t5531-deep-submodule-push.sh | 52 ++++++++++++++++++++++++++++++++++ transport.c | 3 +- 4 files changed, 117 insertions(+), 5 deletions(-) diff --git a/submodule.c b/submodule.c index de444be61a..49ab132d05 100644 --- a/submodule.c +++ b/submodule.c @@ -14,6 +14,7 @@ #include "blob.h" #include "thread-utils.h" #include "quote.h" +#include "remote.h" #include "worktree.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; @@ -783,6 +784,8 @@ int find_unpushed_submodules(struct sha1_array *commits, } static int push_submodule(const char *path, + const struct remote *remote, + const char **refspec, int refspec_nr, const struct string_list *push_options, int dry_run) { @@ -801,6 +804,14 @@ static int push_submodule(const char *path, argv_array_pushf(&cp.args, "--push-option=%s", item->string); } + + if (remote->origin != REMOTE_UNCONFIGURED) { + int i; + argv_array_push(&cp.args, remote->name); + for (i = 0; i < refspec_nr; i++) + argv_array_push(&cp.args, refspec[i]); + } + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; @@ -813,21 +824,67 @@ static int push_submodule(const char *path, return 1; } +/* + * Perform a check in the submodule to see if the remote and refspec work. + * Die if the submodule can't be pushed. + */ +static void submodule_push_check(const char *path, const struct remote *remote, + const char **refspec, int refspec_nr) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int i; + + argv_array_push(&cp.args, "submodule--helper"); + argv_array_push(&cp.args, "push-check"); + argv_array_push(&cp.args, remote->name); + + for (i = 0; i < refspec_nr; i++) + argv_array_push(&cp.args, refspec[i]); + + prepare_submodule_repo_env(&cp.env_array); + cp.git_cmd = 1; + cp.no_stdin = 1; + cp.no_stdout = 1; + cp.dir = path; + + /* + * Simply indicate if 'submodule--helper push-check' failed. + * More detailed error information will be provided by the + * child process. + */ + if (run_command(&cp)) + die("process for submodule '%s' failed", path); +} + int push_unpushed_submodules(struct sha1_array *commits, - const char *remotes_name, + const struct remote *remote, + const char **refspec, int refspec_nr, const struct string_list *push_options, int dry_run) { int i, ret = 1; struct string_list needs_pushing = STRING_LIST_INIT_DUP; - if (!find_unpushed_submodules(commits, remotes_name, &needs_pushing)) + if (!find_unpushed_submodules(commits, remote->name, &needs_pushing)) return 1; + /* + * Verify that the remote and refspec can be propagated to all + * submodules. This check can be skipped if the remote and refspec + * won't be propagated due to the remote being unconfigured (e.g. a URL + * instead of a remote name). + */ + if (remote->origin != REMOTE_UNCONFIGURED) + for (i = 0; i < needs_pushing.nr; i++) + submodule_push_check(needs_pushing.items[i].string, + remote, refspec, refspec_nr); + + /* Actually push the submodules */ for (i = 0; i < needs_pushing.nr; i++) { const char *path = needs_pushing.items[i].string; fprintf(stderr, "Pushing submodule '%s'\n", path); - if (!push_submodule(path, push_options, dry_run)) { + if (!push_submodule(path, remote, refspec, refspec_nr, + push_options, dry_run)) { fprintf(stderr, "Unable to push submodule '%s'\n", path); ret = 0; } diff --git a/submodule.h b/submodule.h index 0e26430fd3..127ff9be84 100644 --- a/submodule.h +++ b/submodule.h @@ -4,6 +4,7 @@ struct diff_options; struct argv_array; struct sha1_array; +struct remote; enum { RECURSE_SUBMODULES_ONLY = -5, @@ -91,7 +92,8 @@ extern int find_unpushed_submodules(struct sha1_array *commits, const char *remotes_name, struct string_list *needs_pushing); extern int push_unpushed_submodules(struct sha1_array *commits, - const char *remotes_name, + const struct remote *remote, + const char **refspec, int refspec_nr, const struct string_list *push_options, int dry_run); extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index f55137f76f..57ba322628 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -475,4 +475,56 @@ test_expect_success 'push only unpushed submodules recursively' ' test_cmp expected_pub actual_pub ' +test_expect_success 'push propagating the remotes name to a submodule' ' + git -C work remote add origin ../pub.git && + git -C work remote add pub ../pub.git && + + > work/gar/bage/junk10 && + git -C work/gar/bage add junk10 && + git -C work/gar/bage commit -m "Tenth junk" && + git -C work add gar/bage && + git -C work commit -m "Tenth junk added to gar/bage" && + + # Fails when submodule does not have a matching remote + test_must_fail git -C work push --recurse-submodules=on-demand pub master && + # Succeeds when submodules has matching remote and refspec + git -C work push --recurse-submodules=on-demand origin master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + git -C work/gar/bage rev-parse master >expected_submodule && + git -C work rev-parse master >expected_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + +test_expect_success 'push propagating refspec to a submodule' ' + > work/gar/bage/junk11 && + git -C work/gar/bage add junk11 && + git -C work/gar/bage commit -m "Eleventh junk" && + + git -C work checkout branch2 && + git -C work add gar/bage && + git -C work commit -m "updating gar/bage in branch2" && + + # Fails when submodule does not have a matching branch + test_must_fail git -C work push --recurse-submodules=on-demand origin branch2 && + # Fails when refspec includes an object id + test_must_fail git -C work push --recurse-submodules=on-demand origin \ + "$(git -C work rev-parse branch2):refs/heads/branch2" && + # Fails when refspec includes 'HEAD' as it is unsupported at this time + test_must_fail git -C work push --recurse-submodules=on-demand origin \ + HEAD:refs/heads/branch2 && + + git -C work/gar/bage branch branch2 master && + git -C work push --recurse-submodules=on-demand origin branch2 && + + git -C submodule.git rev-parse branch2 >actual_submodule && + git -C pub.git rev-parse branch2 >actual_pub && + git -C work/gar/bage rev-parse branch2 >expected_submodule && + git -C work rev-parse branch2 >expected_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + test_done diff --git a/transport.c b/transport.c index 64e60b6353..a62e5118c0 100644 --- a/transport.c +++ b/transport.c @@ -1030,7 +1030,8 @@ int transport_push(struct transport *transport, sha1_array_append(&commits, ref->new_oid.hash); if (!push_unpushed_submodules(&commits, - transport->remote->name, + transport->remote, + refspec, refspec_nr, transport->push_options, pretend)) { sha1_array_clear(&commits); -- cgit v1.2.3