{"thread":{"id":"30094","subject":"[PATCH v6 0/4] push: submodule support","startedAt":"2012-03-29T07:21:20Z","lastAt":"2012-03-29T07:21:24Z","messageCount":5,"participants":["Heiko Voigt"],"isPatch":true,"patchVersion":6,"patchTotal":4},"messages":[{"id":"188041","messageId":"cover.1332968667.git.hvoigt@hvoigt.net","threadId":"30094","inReplyTo":null,"subject":"[PATCH v6 0/4] push: submodule support","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2012-03-29T07:21:20Z","receivedAt":"2012-03-29T07:21:20Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"Hi,\n\nthis is the sixth iteration of this series which should contain all\ndiscussed cleanups from the last iteration. The testsuite was hanging\nfor me with make -j5 on Mac OS X 10.6.8. I doubt that it was because\nof any change in series but please verify as well. A non parallel run\npassed. If I find some time I will try to bisect that.\n\nCheers Heiko\n\nThe first iteration of this patch series can be found here:\nhttp://thread.gmane.org/gmane.comp.version-control.git/176328/focus=176327\n\nThe second iteration of this patch series can be found here:\nhttp://thread.gmane.org/gmane.comp.version-control.git/177992\n\nThe third iteration of this patch series can be found here:\nhttp://thread.gmane.org/gmane.comp.version-control.git/179037/focus=179048\n\nThe fourth iteration of this patch series can be found here:\nhttp://thread.gmane.org/gmane.comp.version-control.git/179731\n\nThe fifth iteration of this patch series can be found here:\nhttp://thread.gmane.org/gmane.comp.version-control.git/190618\n\n\nHeiko Voigt (4):\n  Teach revision walking machinery to walk multiple times sequencially\n  string-list: document that string_list_insert() inserts unique\n    strings\n  Refactor submodule push check to use string list instead of integer\n  push: teach --recurse-submodules the on-demand option\n\n .gitignore                                       |    1 +\n Documentation/git-push.txt                       |   14 +++-\n Documentation/technical/api-revision-walking.txt |    5 ++\n Documentation/technical/api-string-list.txt      |    4 +-\n Makefile                                         |    1 +\n builtin/push.c                                   |   10 ++-\n object.c                                         |   11 +++\n object.h                                         |    2 +\n revision.c                                       |    5 ++\n revision.h                                       |    1 +\n submodule.c                                      |   73 ++++++++++++++---\n submodule.h                                      |    4 +-\n t/t0062-revision-walking.sh                      |   33 ++++++++\n t/t5531-deep-submodule-push.sh                   |   94 ++++++++++++++++++++++\n test-revision-walking.c                          |   66 +++++++++++++++\n transport.c                                      |   41 +++++++++-\n transport.h                                      |    1 +\n 17 files changed, 344 insertions(+), 22 deletions(-)\n create mode 100755 t/t0062-revision-walking.sh\n create mode 100644 test-revision-walking.c\n\n-- \n1.7.10.rc2.31.gd8c60\n"},{"id":"188042","messageId":"2f646e5b836b06c77c5ad25eabe8372691c29768.1332968667.git.hvoigt@hvoigt.net","threadId":"30094","inReplyTo":"cover.1332968667.git.hvoigt@hvoigt.net","subject":"[PATCH v6 1/4] Teach revision walking machinery to walk multiple times sequencially","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2012-03-29T07:21:21Z","receivedAt":"2012-03-29T07:21:21Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"Previously it was not possible to iterate revisions twice using the\nrevision walking api. We add a reset_revision_walk() which clears the\nused flags. This allows us to do multiple sequencial revision walks.\n\nWe add the appropriate calls to the existing submodule machinery doing\nrevision walks. This is done to avoid surprises if future code wants to\ncall these functions more than once during the processes lifetime.\n\nSigned-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n---\n .gitignore                                       |    1 +\n Documentation/technical/api-revision-walking.txt |    5 ++\n Makefile                                         |    1 +\n object.c                                         |   11 ++++\n object.h                                         |    2 +\n revision.c                                       |    5 ++\n revision.h                                       |    1 +\n submodule.c                                      |    2 +\n t/t0062-revision-walking.sh                      |   33 +++++++++++\n test-revision-walking.c                          |   66 ++++++++++++++++++++++\n 10 files changed, 127 insertions(+)\n create mode 100755 t/t0062-revision-walking.sh\n create mode 100644 test-revision-walking.c\n\ndiff --git a/.gitignore b/.gitignore\nindex 87fcc5f..382481c 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -183,6 +183,7 @@\n /test-mktemp\n /test-parse-options\n /test-path-utils\n+/test-revision-walking\n /test-run-command\n /test-sha1\n /test-sigchain\ndiff --git a/Documentation/technical/api-revision-walking.txt b/Documentation/technical/api-revision-walking.txt\nindex 996da05..b7d0d9a 100644\n--- a/Documentation/technical/api-revision-walking.txt\n+++ b/Documentation/technical/api-revision-walking.txt\n@@ -56,6 +56,11 @@ function.\n \treturning a `struct commit *` each time you call it. The end of the\n \trevision list is indicated by returning a NULL pointer.\n \n+`reset_revision_walk`::\n+\n+\tReset the flags used by the revision walking api. You can use\n+\tthis to do multiple sequencial revision walks.\n+\n Data structures\n ---------------\n \ndiff --git a/Makefile b/Makefile\nindex be1957a..e771b29 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -483,6 +483,7 @@ TEST_PROGRAMS_NEED_X += test-match-trees\n TEST_PROGRAMS_NEED_X += test-mktemp\n TEST_PROGRAMS_NEED_X += test-parse-options\n TEST_PROGRAMS_NEED_X += test-path-utils\n+TEST_PROGRAMS_NEED_X += test-revision-walking\n TEST_PROGRAMS_NEED_X += test-run-command\n TEST_PROGRAMS_NEED_X += test-sha1\n TEST_PROGRAMS_NEED_X += test-sigchain\ndiff --git a/object.c b/object.c\nindex 6b06297..f84e80a 100644\n--- a/object.c\n+++ b/object.c\n@@ -275,3 +275,14 @@ void object_array_remove_duplicates(struct object_array *array)\n \t\tarray->nr = dst;\n \t}\n }\n+\n+void clear_object_flags(unsigned flags)\n+{\n+\tint i;\n+\n+\tfor (i=0; i < obj_hash_size; i++) {\n+\t\tstruct object *obj = obj_hash[i];\n+\t\tif (obj)\n+\t\t\tobj->flags &= ~flags;\n+\t}\n+}\ndiff --git a/object.h b/object.h\nindex b6618d9..6a97b6b 100644\n--- a/object.h\n+++ b/object.h\n@@ -76,4 +76,6 @@ void add_object_array(struct object *obj, const char *name, struct object_array\n void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode);\n void object_array_remove_duplicates(struct object_array *);\n \n+void clear_object_flags(unsigned flags);\n+\n #endif /* OBJECT_H */\ndiff --git a/revision.c b/revision.c\nindex b3554ed..992facd 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2062,6 +2062,11 @@ static void set_children(struct rev_info *revs)\n \t}\n }\n \n+void reset_revision_walk(void)\n+{\n+\tclear_object_flags(SEEN | ADDED | SHOWN);\n+}\n+\n int prepare_revision_walk(struct rev_info *revs)\n {\n \tint nr = revs->pending.nr;\ndiff --git a/revision.h b/revision.h\nindex b8e9223..eb87fd1 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -192,6 +192,7 @@ extern void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ct\n \t\t\t\t const char * const usagestr[]);\n extern int handle_revision_arg(const char *arg, struct rev_info *revs,int flags,int cant_be_filename);\n \n+extern void reset_revision_walk(void);\n extern int prepare_revision_walk(struct rev_info *revs);\n extern struct commit *get_revision(struct rev_info *revs);\n extern char *get_revision_mark(const struct rev_info *revs, const struct commit *commit);\ndiff --git a/submodule.c b/submodule.c\nindex 9a28060..645ff5d 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -404,6 +404,7 @@ int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remote\n \twhile ((commit = get_revision(&rev)) && !needs_pushing)\n \t\tcommit_need_pushing(commit, &needs_pushing);\n \n+\treset_revision_walk();\n \tfree(sha1_copy);\n \tstrbuf_release(&remotes_arg);\n \n@@ -741,6 +742,7 @@ static int find_first_merges(struct object_array *result, const char *path,\n \t\tif (in_merge_bases(b, &commit, 1))\n \t\t\tadd_object_array(o, NULL, &merges);\n \t}\n+\treset_revision_walk();\n \n \t/* Now we've got all merges that contain a and b. Prune all\n \t * merges that contain another found merge and save them in\ndiff --git a/t/t0062-revision-walking.sh b/t/t0062-revision-walking.sh\nnew file mode 100755\nindex 0000000..3d98eb8\n--- /dev/null\n+++ b/t/t0062-revision-walking.sh\n@@ -0,0 +1,33 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2012 Heiko Voigt\n+#\n+\n+test_description='Test revision walking api'\n+\n+. ./test-lib.sh\n+\n+cat >run_twice_expected <<-EOF\n+1st\n+ > add b\n+ > add a\n+2nd\n+ > add b\n+ > add a\n+EOF\n+\n+test_expect_success 'setup' '\n+\techo a > a &&\n+\tgit add a &&\n+\tgit commit -m \"add a\" &&\n+\techo b > b &&\n+\tgit add b &&\n+\tgit commit -m \"add b\"\n+'\n+\n+test_expect_success 'revision walking can be done twice' '\n+\ttest-revision-walking run-twice > run_twice_actual\n+\ttest_cmp run_twice_expected run_twice_actual\n+'\n+\n+test_done\ndiff --git a/test-revision-walking.c b/test-revision-walking.c\nnew file mode 100644\nindex 0000000..3ade02c\n--- /dev/null\n+++ b/test-revision-walking.c\n@@ -0,0 +1,66 @@\n+/*\n+ * test-revision-walking.c: test revision walking API.\n+ *\n+ * (C) 2012 Heiko Voigt <hvoigt@hvoigt.net>\n+ *\n+ * This code is free software; you can redistribute it and/or modify\n+ * it under the terms of the GNU General Public License version 2 as\n+ * published by the Free Software Foundation.\n+ */\n+\n+#include \"cache.h\"\n+#include \"commit.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+\n+static void print_commit(struct commit *commit)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct pretty_print_context ctx = {0};\n+\tctx.date_mode = DATE_NORMAL;\n+\tformat_commit_message(commit, \" %m %s\", &sb, &ctx);\n+\tprintf(\"%s\\n\", sb.buf);\n+\tstrbuf_release(&sb);\n+}\n+\n+static int run_revision_walk(void)\n+{\n+\tstruct rev_info rev;\n+\tstruct commit *commit;\n+\tconst char *argv[] = {NULL, \"--all\", NULL};\n+\tint argc = ARRAY_SIZE(argv) - 1;\n+\tint got_revision = 0;\n+\n+\tinit_revisions(&rev, NULL);\n+\tsetup_revisions(argc, argv, &rev, NULL);\n+\tif (prepare_revision_walk(&rev))\n+\t\tdie(\"revision walk setup failed\");\n+\n+\twhile ((commit = get_revision(&rev)) != NULL) {\n+\t\tprint_commit(commit);\n+\t\tgot_revision = 1;\n+\t}\n+\n+\treset_revision_walk();\n+\treturn got_revision;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tif (argc < 2)\n+\t\treturn 1;\n+\n+\tif (!strcmp(argv[1], \"run-twice\")) {\n+\t\tprintf(\"1st\\n\");\n+\t\tif (!run_revision_walk())\n+\t\t\treturn 1;\n+\t\tprintf(\"2nd\\n\");\n+\t\tif (!run_revision_walk())\n+\t\t\treturn 1;\n+\n+\t\treturn 0;\n+\t}\n+\n+\tfprintf(stderr, \"check usage\\n\");\n+\treturn 1;\n+}\n-- \n1.7.10.rc2.31.gd8c60\n"},{"id":"188044","messageId":"edd8579ebf2cf3bc91137b9fff1d090ea75bee56.1332968667.git.hvoigt@hvoigt.net","threadId":"30094","inReplyTo":"cover.1332968667.git.hvoigt@hvoigt.net","subject":"[PATCH v6 2/4] string-list: document that string_list_insert() inserts unique strings","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2012-03-29T07:21:22Z","receivedAt":"2012-03-29T07:21:22Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n---\n Documentation/technical/api-string-list.txt |    4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt\nindex ce24eb9..5a0c14f 100644\n--- a/Documentation/technical/api-string-list.txt\n+++ b/Documentation/technical/api-string-list.txt\n@@ -83,7 +83,9 @@ Functions\n \n \tInsert a new element to the string_list. The returned pointer can be\n \thandy if you want to write something to the `util` pointer of the\n-\tstring_list_item containing the just added string.\n+\tstring_list_item containing the just added string. If the given\n+\tstring already exists the insertion will be skipped and the\n+\tpointer to the existing item returned.\n +\n Since this function uses xrealloc() (which die()s if it fails) if the\n list needs to grow, it is safe not to check the pointer. I.e. you may\n-- \n1.7.10.rc2.31.gd8c60\n"},{"id":"188043","messageId":"1762667f5d46192f001cc64d71d96dea142742ae.1332968667.git.hvoigt@hvoigt.net","threadId":"30094","inReplyTo":"cover.1332968667.git.hvoigt@hvoigt.net","subject":"[PATCH v6 3/4] Refactor submodule push check to use string list instead of integer","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2012-03-29T07:21:23Z","receivedAt":"2012-03-29T07:21:23Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"This allows us to tell the user which submodules have not been pushed.\nAdditionally this is helpful when we want to automatically try to push\nsubmodules that have not been pushed.\n\nSigned-off-by: Heiko Voigt <hvoigt@hvoigt.net>\n---\n submodule.c |   23 +++++++++++------------\n submodule.h |    3 ++-\n transport.c |   24 ++++++++++++++++++++++--\n 3 files changed, 35 insertions(+), 15 deletions(-)\n\ndiff --git a/submodule.c b/submodule.c\nindex 645ff5d..fa7d5f4 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -357,21 +357,19 @@ static void collect_submodules_from_diff(struct diff_queue_struct *q,\n \t\t\t\t\t void *data)\n {\n \tint i;\n-\tint *needs_pushing = data;\n+\tstruct string_list *needs_pushing = data;\n \n \tfor (i = 0; i < q->nr; i++) {\n \t\tstruct diff_filepair *p = q->queue[i];\n \t\tif (!S_ISGITLINK(p->two->mode))\n \t\t\tcontinue;\n-\t\tif (submodule_needs_pushing(p->two->path, p->two->sha1)) {\n-\t\t\t*needs_pushing = 1;\n-\t\t\tbreak;\n-\t\t}\n+\t\tif (submodule_needs_pushing(p->two->path, p->two->sha1))\n+\t\t\tstring_list_insert(needs_pushing, p->two->path);\n \t}\n }\n \n-\n-static void commit_need_pushing(struct commit *commit, int *needs_pushing)\n+static void find_unpushed_submodule_commits(struct commit *commit,\n+\t\tstruct string_list *needs_pushing)\n {\n \tstruct rev_info rev;\n \n@@ -382,14 +380,15 @@ static void commit_need_pushing(struct commit *commit, int *needs_pushing)\n \tdiff_tree_combined_merge(commit, 1, &rev);\n }\n \n-int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)\n+int find_unpushed_submodules(unsigned char new_sha1[20],\n+\t\tconst char *remotes_name, struct string_list *needs_pushing)\n {\n \tstruct rev_info rev;\n \tstruct commit *commit;\n \tconst char *argv[] = {NULL, NULL, \"--not\", \"NULL\", NULL};\n \tint argc = ARRAY_SIZE(argv) - 1;\n \tchar *sha1_copy;\n-\tint needs_pushing = 0;\n+\n \tstruct strbuf remotes_arg = STRBUF_INIT;\n \n \tstrbuf_addf(&remotes_arg, \"--remotes=%s\", remotes_name);\n@@ -401,14 +400,14 @@ int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remote\n \tif (prepare_revision_walk(&rev))\n \t\tdie(\"revision walk setup failed\");\n \n-\twhile ((commit = get_revision(&rev)) && !needs_pushing)\n-\t\tcommit_need_pushing(commit, &needs_pushing);\n+\twhile ((commit = get_revision(&rev)) != NULL)\n+\t\tfind_unpushed_submodule_commits(commit, needs_pushing);\n \n \treset_revision_walk();\n \tfree(sha1_copy);\n \tstrbuf_release(&remotes_arg);\n \n-\treturn needs_pushing;\n+\treturn needs_pushing->nr;\n }\n \n static int is_submodule_commit_present(const char *path, unsigned char sha1[20])\ndiff --git a/submodule.h b/submodule.h\nindex 80e04f3..fd58690 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -29,6 +29,7 @@ int fetch_populated_submodules(int num_options, const char **options,\n unsigned is_submodule_modified(const char *path, int ignore_untracked);\n int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],\n \t\t    const unsigned char a[20], const unsigned char b[20], int search);\n-int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);\n+int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,\n+\t\tstruct string_list *needs_pushing);\n \n #endif\ndiff --git a/transport.c b/transport.c\nindex 181f8f2..a095290 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -11,6 +11,7 @@\n #include \"branch.h\"\n #include \"url.h\"\n #include \"submodule.h\"\n+#include \"string-list.h\"\n \n /* rsync support */\n \n@@ -1004,6 +1005,20 @@ void transport_set_verbosity(struct transport *transport, int verbosity,\n \t\ttransport->progress = verbosity >= 0 && isatty(2);\n }\n \n+static void die_with_unpushed_submodules(struct string_list *needs_pushing)\n+{\n+\tint i;\n+\n+\tfprintf(stderr, \"The following submodule paths contain changes that can\\n\"\n+\t\t\t\"not be found on any remote:\\n\");\n+\tfor (i = 0; i < needs_pushing->nr; i++)\n+\t\tprintf(\"  %s\\n\", needs_pushing->items[i].string);\n+\n+\tstring_list_clear(needs_pushing, 0);\n+\n+\tdie(\"Aborting.\");\n+}\n+\n int transport_push(struct transport *transport,\n \t\t   int refspec_nr, const char **refspec, int flags,\n \t\t   int *nonfastforward)\n@@ -1046,10 +1061,15 @@ int transport_push(struct transport *transport,\n \n \t\tif ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) {\n \t\t\tstruct ref *ref = remote_refs;\n+\t\t\tstruct string_list needs_pushing;\n+\n+\t\t\tmemset(&needs_pushing, 0, sizeof(struct string_list));\n+\t\t\tneeds_pushing.strdup_strings = 1;\n \t\t\tfor (; ref; ref = ref->next)\n \t\t\t\tif (!is_null_sha1(ref->new_sha1) &&\n-\t\t\t\t    check_submodule_needs_pushing(ref->new_sha1,transport->remote->name))\n-\t\t\t\t\tdie(\"There are unpushed submodules, aborting.\");\n+\t\t\t\t    find_unpushed_submodules(ref->new_sha1,\n+\t\t\t\t\t    transport->remote->name, &needs_pushing))\n+\t\t\t\t\tdie_with_unpushed_submodules(&needs_pushing);\n \t\t}\n \n \t\tpush_ret = transport->push_refs(transport, remote_refs, flags);\n-- \n1.7.10.rc2.31.gd8c60\n"},{"id":"188045","messageId":"d8c60e8b0b884e62bd445c238633b67b76e5e4a6.1332968667.git.hvoigt@hvoigt.net","threadId":"30094","inReplyTo":"cover.1332968667.git.hvoigt@hvoigt.net","subject":"[PATCH v6 4/4] push: teach --recurse-submodules the on-demand option","fromName":"Heiko Voigt","fromEmail":"hvoigt@hvoigt.net","sentAt":"2012-03-29T07:21:24Z","receivedAt":"2012-03-29T07:21:24Z","isPatch":true,"sender":{"key":"hvoigt@hvoigt.net","avatar":"https://avatars.githubusercontent.com/u/184958?v=4"},"body":"When using this option git will search for all submodules that\nhave changed in the revisions to be send. It will then try to\npush the currently checked out branch of each submodule.\n\nThis helps when a user has finished working on a change which\ninvolves submodules and just wants to push everything in one go.\n\nSigned-off-by: Fredrik Gustafsson <iveqy@iveqy.com>\nMentored-by: Jens Lehmann <Jens.Lehmann@web.de>\nMentored-by: Heiko Voigt <hvoigt@hvoigt.net>\n---\n Documentation/git-push.txt     |   14 ++++--\n builtin/push.c                 |   10 ++++-\n submodule.c                    |   48 ++++++++++++++++++++\n submodule.h                    |    1 +\n t/t5531-deep-submodule-push.sh |   94 ++++++++++++++++++++++++++++++++++++++++\n transport.c                    |   17 +++++++-\n transport.h                    |    1 +\n 7 files changed, 179 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-push.txt b/Documentation/git-push.txt\nindex 48760db..a52b7b1 100644\n--- a/Documentation/git-push.txt\n+++ b/Documentation/git-push.txt\n@@ -170,10 +170,16 @@ useful if you write an alias or script around 'git push'.\n \tis specified. This flag forces progress status even if the\n \tstandard error stream is not directed to a terminal.\n \n---recurse-submodules=check::\n-\tCheck whether all submodule commits used by the revisions to be\n-\tpushed are available on a remote tracking branch. Otherwise the\n-\tpush will be aborted and the command will exit with non-zero status.\n+--recurse-submodules=check|on-demand::\n+\tMake sure all submodule commits used by the revisions to be\n+\tpushed are available on a remote tracking branch. If 'check' is\n+\tused git will verify that all submodule commits that changed in\n+\tthe revisions to be pushed are available on at least one remote\n+\tof the submodule. If any commits are missing the push will be\n+\taborted and exit with non-zero status. If 'on-demand' is used\n+\tall submodules that changed in the revisions to be pushed will\n+\tbe pushed. If on-demand was not able to push all necessary\n+\trevisions it will also be aborted and exit with non-zero status.\n \n \n include::urls-remotes.txt[]\ndiff --git a/builtin/push.c b/builtin/push.c\nindex d315475..f3abec8 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -224,13 +224,21 @@ static int option_parse_recurse_submodules(const struct option *opt,\n \t\t\t\t   const char *arg, int unset)\n {\n \tint *flags = opt->value;\n+\n+\tif (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |\n+\t\t      TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))\n+\t\tdie(\"%s can only be used once.\", opt->long_name);\n+\n \tif (arg) {\n \t\tif (!strcmp(arg, \"check\"))\n \t\t\t*flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;\n+\t\telse if (!strcmp(arg, \"on-demand\"))\n+\t\t\t*flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;\n \t\telse\n \t\t\tdie(\"bad %s argument: %s\", opt->long_name, arg);\n \t} else\n-\t\tdie(\"option %s needs an argument (check)\", opt->long_name);\n+\t\tdie(\"option %s needs an argument (check|on-demand)\",\n+\t\t\t\topt->long_name);\n \n \treturn 0;\n }\ndiff --git a/submodule.c b/submodule.c\nindex fa7d5f4..784b580 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -410,6 +410,54 @@ int find_unpushed_submodules(unsigned char new_sha1[20],\n \treturn needs_pushing->nr;\n }\n \n+static int push_submodule(const char *path)\n+{\n+\tif (add_submodule_odb(path))\n+\t\treturn 1;\n+\n+\tif (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {\n+\t\tstruct child_process cp;\n+\t\tconst char *argv[] = {\"push\", NULL};\n+\n+\t\tmemset(&cp, 0, sizeof(cp));\n+\t\tcp.argv = argv;\n+\t\tcp.env = local_repo_env;\n+\t\tcp.git_cmd = 1;\n+\t\tcp.no_stdin = 1;\n+\t\tcp.dir = path;\n+\t\tif (run_command(&cp))\n+\t\t\treturn 0;\n+\t\tclose(cp.out);\n+\t}\n+\n+\treturn 1;\n+}\n+\n+int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name)\n+{\n+\tint i, ret = 1;\n+\tstruct string_list needs_pushing;\n+\n+\tmemset(&needs_pushing, 0, sizeof(struct string_list));\n+\tneeds_pushing.strdup_strings = 1;\n+\n+\tif (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing))\n+\t\treturn 1;\n+\n+\tfor (i = 0; i < needs_pushing.nr; i++) {\n+\t\tconst char *path = needs_pushing.items[i].string;\n+\t\tfprintf(stderr, \"Pushing submodule '%s'\\n\", path);\n+\t\tif (!push_submodule(path)) {\n+\t\t\tfprintf(stderr, \"Unable to push submodule '%s'\\n\", path);\n+\t\t\tret = 0;\n+\t\t}\n+\t}\n+\n+\tstring_list_clear(&needs_pushing, 0);\n+\n+\treturn ret;\n+}\n+\n static int is_submodule_commit_present(const char *path, unsigned char sha1[20])\n {\n \tint is_present = 0;\ndiff --git a/submodule.h b/submodule.h\nindex fd58690..6633b53 100644\n--- a/submodule.h\n+++ b/submodule.h\n@@ -31,5 +31,6 @@ int merge_submodule(unsigned char result[20], const char *path, const unsigned c\n \t\t    const unsigned char a[20], const unsigned char b[20], int search);\n int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,\n \t\tstruct string_list *needs_pushing);\n+int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);\n \n #endif\ndiff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh\nindex 30bec4b..1947c28 100755\n--- a/t/t5531-deep-submodule-push.sh\n+++ b/t/t5531-deep-submodule-push.sh\n@@ -119,4 +119,98 @@ test_expect_success 'push succeeds if submodule has no remote and is on the firs\n \t)\n '\n \n+test_expect_success 'push unpushed submodules when not needed' '\n+\t(\n+\t\tcd work &&\n+\t\t(\n+\t\t\tcd gar/bage &&\n+\t\t\tgit checkout master &&\n+\t\t\t>junk5 &&\n+\t\t\tgit add junk5 &&\n+\t\t\tgit commit -m \"Fifth junk\" &&\n+\t\t\tgit push &&\n+\t\t\tgit rev-parse origin/master >../../../expected\n+\t\t) &&\n+\t\tgit checkout master &&\n+\t\tgit add gar/bage &&\n+\t\tgit commit -m \"Fifth commit for gar/bage\" &&\n+\t\tgit push --recurse-submodules=on-demand ../pub.git master\n+\t) &&\n+\t(\n+\t\tcd submodule.git &&\n+\t\tgit rev-parse master >../actual\n+\t) &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'push unpushed submodules when not needed 2' '\n+\t(\n+\t\tcd submodule.git &&\n+\t\tgit rev-parse master >../expected\n+\t) &&\n+\t(\n+\t\tcd work &&\n+\t\t(\n+\t\t\tcd gar/bage &&\n+\t\t\t>junk6 &&\n+\t\t\tgit add junk6 &&\n+\t\t\tgit commit -m \"Sixth junk\"\n+\t\t) &&\n+\t\t>junk2 &&\n+\t\tgit add junk2 &&\n+\t\tgit commit -m \"Second junk for work\" &&\n+\t\tgit push --recurse-submodules=on-demand ../pub.git master\n+\t) &&\n+\t(\n+\t\tcd submodule.git &&\n+\t\tgit rev-parse master >../actual\n+\t) &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'push unpushed submodules recursively' '\n+\t(\n+\t\tcd work &&\n+\t\t(\n+\t\t\tcd gar/bage &&\n+\t\t\tgit checkout master &&\n+\t\t\t> junk7 &&\n+\t\t\tgit add junk7 &&\n+\t\t\tgit commit -m \"Seventh junk\" &&\n+\t\t\tgit rev-parse master >../../../expected\n+\t\t) &&\n+\t\tgit checkout master &&\n+\t\tgit add gar/bage &&\n+\t\tgit commit -m \"Seventh commit for gar/bage\" &&\n+\t\tgit push --recurse-submodules=on-demand ../pub.git master\n+\t) &&\n+\t(\n+\t\tcd submodule.git &&\n+\t\tgit rev-parse master >../actual\n+\t) &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'push unpushable submodule recursively fails' '\n+\t(\n+\t\tcd work &&\n+\t\t(\n+\t\t\tcd gar/bage &&\n+\t\t\tgit rev-parse origin/master >../../../expected &&\n+\t\t\tgit checkout master~0 &&\n+\t\t\t> junk8 &&\n+\t\t\tgit add junk8 &&\n+\t\t\tgit commit -m \"Eighth junk\"\n+\t\t) &&\n+\t\tgit add gar/bage &&\n+\t\tgit commit -m \"Eighth commit for gar/bage\" &&\n+\t\ttest_must_fail git push --recurse-submodules=on-demand ../pub.git master\n+\t) &&\n+\t(\n+\t\tcd submodule.git &&\n+\t\tgit rev-parse master >../actual\n+\t) &&\n+\ttest_cmp expected actual\n+'\n+\n test_done\ndiff --git a/transport.c b/transport.c\nindex a095290..7bd4f46 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1013,6 +1013,11 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing)\n \t\t\t\"not be found on any remote:\\n\");\n \tfor (i = 0; i < needs_pushing->nr; i++)\n \t\tprintf(\"  %s\\n\", needs_pushing->items[i].string);\n+\tfprintf(stderr, \"\\nPlease try\\n\\n\"\n+\t\t\t\"\tgit push --recurse-submodules=on-demand\\n\\n\"\n+\t\t\t\"or cd to the path and use\\n\\n\"\n+\t\t\t\"\tgit push\\n\\n\"\n+\t\t\t\"to push them to a remote.\\n\\n\");\n \n \tstring_list_clear(needs_pushing, 0);\n \n@@ -1059,7 +1064,17 @@ int transport_push(struct transport *transport,\n \t\t\tflags & TRANSPORT_PUSH_MIRROR,\n \t\t\tflags & TRANSPORT_PUSH_FORCE);\n \n-\t\tif ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) {\n+\t\tif ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {\n+\t\t\tstruct ref *ref = remote_refs;\n+\t\t\tfor (; ref; ref = ref->next)\n+\t\t\t\tif (!is_null_sha1(ref->new_sha1) &&\n+\t\t\t\t    !push_unpushed_submodules(ref->new_sha1,\n+\t\t\t\t\t    transport->remote->name))\n+\t\t\t\t    die (\"Failed to push all needed submodules!\");\n+\t\t}\n+\n+\t\tif ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |\n+\t\t\t      TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) {\n \t\t\tstruct ref *ref = remote_refs;\n \t\t\tstruct string_list needs_pushing;\n \ndiff --git a/transport.h b/transport.h\nindex ce99ef8..eb6d38d 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -103,6 +103,7 @@ struct transport {\n #define TRANSPORT_PUSH_SET_UPSTREAM 32\n #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64\n #define TRANSPORT_PUSH_PRUNE 128\n+#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256\n \n #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)\n \n-- \n1.7.10.rc2.31.gd8c60\n"}]}