Merge branch 'hv/submodule-recurse-push'
authorJunio C Hamano <gitster@pobox.com>
Tue, 24 Apr 2012 21:40:20 +0000 (14:40 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 24 Apr 2012 21:40:20 +0000 (14:40 -0700)
"git push --recurse-submodules" learns to optionally look into the
histories of submodules bound to the superproject and push them out.

By Heiko Voigt
* hv/submodule-recurse-push:
push: teach --recurse-submodules the on-demand option
Refactor submodule push check to use string list instead of integer
Teach revision walking machinery to walk multiple times sequencially

1  2 
.gitignore
Documentation/git-push.txt
Makefile
builtin/push.c
object.c
revision.c
submodule.h
transport.c
transport.h
diff --combined .gitignore
index 83a5c9df1b2c21e21be9375ac01be94c8dcf941a,382481cd4df04e43efca6c5533550c3239981898..1dbeb668dbdcf13ccdbb532e4314c901ae6f7d1c
@@@ -92,7 -92,6 +92,7 @@@
  /git-name-rev
  /git-mv
  /git-notes
 +/git-p4
  /git-pack-redundant
  /git-pack-objects
  /git-pack-refs
  /test-index-version
  /test-line-buffer
  /test-match-trees
 +/test-mergesort
  /test-mktemp
  /test-parse-options
  /test-path-utils
+ /test-revision-walking
  /test-run-command
  /test-sha1
  /test-sigchain
index 48760db3371ef762fe6e0f099045c208206742f1,d653f00f6fbaab94bdfaadcd84146ddce50f453b..a52b7b1a1985ae84d1de8f0cfd107928c648d469
@@@ -10,7 -10,7 +10,7 @@@ SYNOPSI
  --------
  [verse]
  'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
 -         [--repo=<repository>] [-f | --force] [-v | --verbose] [-u | --set-upstream]
 +         [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
           [<repository> [<refspec>...]]
  
  DESCRIPTION
@@@ -71,14 -71,6 +71,14 @@@ nor in any Push line of the correspondi
        Instead of naming each ref to push, specifies that all
        refs under `refs/heads/` be pushed.
  
 +--prune::
 +      Remove remote branches that don't have a local counterpart. For example
 +      a remote branch `tmp` will be removed if a local branch with the same
 +      name doesn't exist any more. This also respects refspecs, e.g.
 +      `git push --prune remote refs/heads/{asterisk}:refs/tmp/{asterisk}` would
 +      make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo`
 +      doesn't exist.
 +
  --mirror::
        Instead of naming each ref to push, specifies that all
        refs under `refs/` (which includes but is not
@@@ -170,10 -162,16 +170,16 @@@ useful if you write an alias or script 
        is specified. This flag forces progress status even if the
        standard error stream is not directed to a terminal.
  
- --recurse-submodules=check::
-       Check whether all submodule commits used by the revisions to be
-       pushed are available on a remote tracking branch. Otherwise the
-       push will be aborted and the command will exit with non-zero status.
+ --recurse-submodules=check|on-demand::
+       Make sure all submodule commits used by the revisions to be
+       pushed are available on a remote tracking branch. If 'check' is
+       used git will verify that all submodule commits that changed in
+       the revisions to be pushed are available on at least one remote
+       of the submodule. If any commits are missing the push will be
+       aborted and exit with non-zero status. If 'on-demand' is used
+       all submodules that changed in the revisions to be pushed will
+       be pushed. If on-demand was not able to push all necessary
+       revisions it will also be aborted and exit with non-zero status.
  
  
  include::urls-remotes.txt[]
diff --combined Makefile
index f1caae8a827e7381df8226b36485409dfca71642,681134a75c3bf5081882f2b5ddd456b17852bf2f..d6748e075434e355180fba7ccfaf72299a332fb9
+++ b/Makefile
@@@ -56,10 -56,6 +56,10 @@@ all:
  # FreeBSD can use either, but MinGW and some others need to use
  # libcharset.h's locale_charset() instead.
  #
 +# Define CHARSET_LIB to you need to link with library other than -liconv to
 +# use locale_charset() function.  On some platforms this needs to set to
 +# -lcharset
 +#
  # Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't
  # need -lintl when linking.
  #
@@@ -440,7 -436,6 +440,7 @@@ SCRIPT_PERL += git-send-email.per
  SCRIPT_PERL += git-svn.perl
  
  SCRIPT_PYTHON += git-remote-testgit.py
 +SCRIPT_PYTHON += git-p4.py
  
  SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
@@@ -481,10 -476,10 +481,11 @@@ TEST_PROGRAMS_NEED_X += test-genrando
  TEST_PROGRAMS_NEED_X += test-index-version
  TEST_PROGRAMS_NEED_X += test-line-buffer
  TEST_PROGRAMS_NEED_X += test-match-trees
 +TEST_PROGRAMS_NEED_X += test-mergesort
  TEST_PROGRAMS_NEED_X += test-mktemp
  TEST_PROGRAMS_NEED_X += test-parse-options
  TEST_PROGRAMS_NEED_X += test-path-utils
+ TEST_PROGRAMS_NEED_X += test-revision-walking
  TEST_PROGRAMS_NEED_X += test-run-command
  TEST_PROGRAMS_NEED_X += test-sha1
  TEST_PROGRAMS_NEED_X += test-sigchain
@@@ -592,7 -587,6 +593,7 @@@ LIB_H += log-tree.
  LIB_H += mailmap.h
  LIB_H += merge-file.h
  LIB_H += merge-recursive.h
 +LIB_H += mergesort.h
  LIB_H += notes.h
  LIB_H += notes-cache.h
  LIB_H += notes-merge.h
@@@ -623,7 -617,6 +624,7 @@@ LIB_H += streaming.
  LIB_H += string-list.h
  LIB_H += submodule.h
  LIB_H += tag.h
 +LIB_H += thread-utils.h
  LIB_H += transport.h
  LIB_H += tree.h
  LIB_H += tree-walk.h
@@@ -697,7 -690,6 +698,7 @@@ LIB_OBJS += mailmap.
  LIB_OBJS += match-trees.o
  LIB_OBJS += merge-file.o
  LIB_OBJS += merge-recursive.o
 +LIB_OBJS += mergesort.o
  LIB_OBJS += name-hash.o
  LIB_OBJS += notes.o
  LIB_OBJS += notes-cache.o
@@@ -1710,7 -1702,6 +1711,7 @@@ endi
  
  ifdef HAVE_LIBCHARSET_H
        BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
 +      EXTLIBS += $(CHARSET_LIB)
  endif
  
  ifdef HAVE_DEV_TTY
@@@ -1853,13 -1844,6 +1854,13 @@@ DEFAULT_PAGER_CQ_SQ = $(subst ','\'',$(
  BASIC_CFLAGS += -DDEFAULT_PAGER='$(DEFAULT_PAGER_CQ_SQ)'
  endif
  
 +ifdef SHELL_PATH
 +SHELL_PATH_CQ = "$(subst ",\",$(subst \,\\,$(SHELL_PATH)))"
 +SHELL_PATH_CQ_SQ = $(subst ','\'',$(SHELL_PATH_CQ))
 +
 +BASIC_CFLAGS += -DSHELL_PATH='$(SHELL_PATH_CQ_SQ)'
 +endif
 +
  ALL_CFLAGS += $(BASIC_CFLAGS)
  ALL_LDFLAGS += $(BASIC_LDFLAGS)
  
@@@ -2269,8 -2253,6 +2270,8 @@@ $(XDIFF_LIB): $(XDIFF_OBJS
  $(VCSSVN_LIB): $(VCSSVN_OBJS)
        $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS)
  
 +export DEFAULT_EDITOR DEFAULT_PAGER
 +
  doc:
        $(MAKE) -C Documentation all
  
@@@ -2375,10 -2357,6 +2376,10 @@@ GIT-BUILD-OPTIONS: FORC
        @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
        @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
 +      @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
 +ifdef GIT_TEST_OPTS
 +      @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
 +endif
  ifdef GIT_TEST_CMP
        @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@
  endif
@@@ -2387,18 -2365,7 +2388,18 @@@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEX
  endif
        @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
        @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
 -      @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
 +ifdef GIT_PERF_REPEAT_COUNT
 +      @echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@
 +endif
 +ifdef GIT_PERF_REPO
 +      @echo GIT_PERF_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPO)))'\' >>$@
 +endif
 +ifdef GIT_PERF_LARGE_REPO
 +      @echo GIT_PERF_LARGE_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_LARGE_REPO)))'\' >>$@
 +endif
 +ifdef GIT_PERF_MAKE_OPTS
 +      @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@
 +endif
  
  ### Detect Tck/Tk interpreter path changes
  ifndef NO_TCLTK
@@@ -2434,11 -2401,6 +2435,11 @@@ export NO_SVN_TEST
  test: all
        $(MAKE) -C t/ all
  
 +perf: all
 +      $(MAKE) -C t/perf/ all
 +
 +.PHONY: test perf
 +
  test-ctype$X: ctype.o
  
  test-date$X: date.o ctype.o
@@@ -2648,6 -2610,7 +2649,6 @@@ dist-doc
  
  distclean: clean
        $(RM) configure
 -      $(RM) po/git.pot
  
  profile-clean:
        $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs)))
diff --combined builtin/push.c
index 693671315ee11313ca98209ebf28fdeffb13f636,f9d21921ba2226b00f2ca376aacade281179b436..19c40d7a55199984d7cdc1efbacfca7b628a4d20
@@@ -19,12 -19,11 +19,12 @@@ static int thin
  static int deleterefs;
  static const char *receivepack;
  static int verbosity;
 -static int progress;
 +static int progress = -1;
  
  static const char **refspec;
  static int refspec_nr;
  static int refspec_alloc;
 +static int default_matching_used;
  
  static void add_refspec(const char *ref)
  {
@@@ -66,16 -65,6 +66,16 @@@ static void set_refspecs(const char **r
        }
  }
  
 +static int push_url_of_remote(struct remote *remote, const char ***url_p)
 +{
 +      if (remote->pushurl_nr) {
 +              *url_p = remote->pushurl;
 +              return remote->pushurl_nr;
 +      }
 +      *url_p = remote->url;
 +      return remote->url_nr;
 +}
 +
  static void setup_push_upstream(struct remote *remote)
  {
        struct strbuf refspec = STRBUF_INIT;
@@@ -87,7 -76,7 +87,7 @@@
                    "\n"
                    "    git push %s HEAD:<name-of-remote-branch>\n"),
                    remote->name);
 -      if (!branch->merge_nr || !branch->merge)
 +      if (!branch->merge_nr || !branch->merge || !branch->remote_name)
                die(_("The current branch %s has no upstream branch.\n"
                    "To push the current branch and set the remote as upstream, use\n"
                    "\n"
        if (branch->merge_nr != 1)
                die(_("The current branch %s has multiple upstream branches, "
                    "refusing to push."), branch->name);
 +      if (strcmp(branch->remote_name, remote->name))
 +              die(_("You are pushing to remote '%s', which is not the upstream of\n"
 +                    "your current branch '%s', without telling me what to push\n"
 +                    "to update which remote branch."),
 +                  remote->name, branch->name);
 +
        strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
        add_refspec(refspec.buf);
  }
@@@ -112,9 -95,6 +112,9 @@@ static void setup_default_push_refspecs
  {
        switch (push_default) {
        default:
 +      case PUSH_DEFAULT_UNSPECIFIED:
 +              default_matching_used = 1;
 +              /* fallthru */
        case PUSH_DEFAULT_MATCHING:
                add_refspec(":");
                break;
        }
  }
  
 +static const char message_advice_pull_before_push[] =
 +      N_("Updates were rejected because the tip of your current branch is behind\n"
 +         "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
 +         "before pushing again.\n"
 +         "See the 'Note about fast-forwards' in 'git push --help' for details.");
 +
 +static const char message_advice_use_upstream[] =
 +      N_("Updates were rejected because a pushed branch tip is behind its remote\n"
 +         "counterpart. If you did not intend to push that branch, you may want to\n"
 +         "specify branches to push or set the 'push.default' configuration\n"
 +         "variable to 'current' or 'upstream' to push only the current branch.");
 +
 +static const char message_advice_checkout_pull_push[] =
 +      N_("Updates were rejected because a pushed branch tip is behind its remote\n"
 +         "counterpart. Check out this branch and merge the remote changes\n"
 +         "(e.g. 'git pull') before pushing again.\n"
 +         "See the 'Note about fast-forwards' in 'git push --help' for details.");
 +
 +static void advise_pull_before_push(void)
 +{
 +      if (!advice_push_non_ff_current || !advice_push_nonfastforward)
 +              return;
 +      advise(_(message_advice_pull_before_push));
 +}
 +
 +static void advise_use_upstream(void)
 +{
 +      if (!advice_push_non_ff_default || !advice_push_nonfastforward)
 +              return;
 +      advise(_(message_advice_use_upstream));
 +}
 +
 +static void advise_checkout_pull_push(void)
 +{
 +      if (!advice_push_non_ff_matching || !advice_push_nonfastforward)
 +              return;
 +      advise(_(message_advice_checkout_pull_push));
 +}
 +
  static int push_with_options(struct transport *transport, int flags)
  {
        int err;
                error(_("failed to push some refs to '%s'"), transport->url);
  
        err |= transport_disconnect(transport);
 -
        if (!err)
                return 0;
  
 -      if (nonfastforward && advice_push_nonfastforward) {
 -              fprintf(stderr, _("To prevent you from losing history, non-fast-forward updates were rejected\n"
 -                              "Merge the remote changes (e.g. 'git pull') before pushing again.  See the\n"
 -                              "'Note about fast-forwards' section of 'git push --help' for details.\n"));
 +      switch (nonfastforward) {
 +      default:
 +              break;
 +      case NON_FF_HEAD:
 +              advise_pull_before_push();
 +              break;
 +      case NON_FF_OTHER:
 +              if (default_matching_used)
 +                      advise_use_upstream();
 +              else
 +                      advise_checkout_pull_push();
 +              break;
        }
  
        return 1;
@@@ -262,7 -196,13 +262,7 @@@ static int do_push(const char *repo, in
                        setup_default_push_refspecs(remote);
        }
        errs = 0;
 -      if (remote->pushurl_nr) {
 -              url = remote->pushurl;
 -              url_nr = remote->pushurl_nr;
 -      } else {
 -              url = remote->url;
 -              url_nr = remote->url_nr;
 -      }
 +      url_nr = push_url_of_remote(remote, &url);
        if (url_nr) {
                for (i = 0; i < url_nr; i++) {
                        struct transport *transport =
@@@ -284,13 -224,21 +284,21 @@@ static int option_parse_recurse_submodu
                                   const char *arg, int unset)
  {
        int *flags = opt->value;
+       if (*flags & (TRANSPORT_RECURSE_SUBMODULES_CHECK |
+                     TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND))
+               die("%s can only be used once.", opt->long_name);
        if (arg) {
                if (!strcmp(arg, "check"))
                        *flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK;
+               else if (!strcmp(arg, "on-demand"))
+                       *flags |= TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND;
                else
                        die("bad %s argument: %s", opt->long_name, arg);
        } else
-               die("option %s needs an argument (check)", opt->long_name);
+               die("option %s needs an argument (check|on-demand)",
+                               opt->long_name);
  
        return 0;
  }
@@@ -320,9 -268,7 +328,9 @@@ int cmd_push(int argc, const char **arg
                OPT_STRING( 0 , "exec", &receivepack, "receive-pack", "receive pack program"),
                OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
                        TRANSPORT_PUSH_SET_UPSTREAM),
 -              OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
 +              OPT_BOOL(0, "progress", &progress, "force progress reporting"),
 +              OPT_BIT(0, "prune", &flags, "prune locally removed refs",
 +                      TRANSPORT_PUSH_PRUNE),
                OPT_END()
        };
  
diff --combined object.c
index 0498b18d451b2335d21e2db3edc0ce7838aaa50e,f84e80a955bd83a4a3de6971d106a8ac430806bc..49a864ce54e6ca0f21ad86aab27a422570d1bcec
+++ b/object.c
@@@ -198,17 -198,6 +198,17 @@@ struct object *parse_object(const unsig
        if (obj && obj->parsed)
                return obj;
  
 +      if ((obj && obj->type == OBJ_BLOB) ||
 +          (!obj && has_sha1_file(sha1) &&
 +           sha1_object_info(sha1, NULL) == OBJ_BLOB)) {
 +              if (check_sha1_signature(repl, NULL, 0, NULL) < 0) {
 +                      error("sha1 mismatch %s\n", sha1_to_hex(repl));
 +                      return NULL;
 +              }
 +              parse_blob_buffer(lookup_blob(sha1), NULL, 0);
 +              return lookup_object(sha1);
 +      }
 +
        buffer = read_sha1_file(sha1, &type, &size);
        if (buffer) {
                if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) {
@@@ -286,3 -275,14 +286,14 @@@ void object_array_remove_duplicates(str
                array->nr = dst;
        }
  }
+ void clear_object_flags(unsigned flags)
+ {
+       int i;
+       for (i=0; i < obj_hash_size; i++) {
+               struct object *obj = obj_hash[i];
+               if (obj)
+                       obj->flags &= ~flags;
+       }
+ }
diff --combined revision.c
index 92095f5fcbb4454b3d605d2cdd6c9a1567498562,4fbc14b6b4f181ce88675ff406cec5c5af10607a..edb225d1fa2e572cacf467f1706f203ee02ddda7
@@@ -1582,7 -1582,6 +1582,7 @@@ static int handle_revision_opt(struct r
                revs->grep_filter.regflags |= REG_EXTENDED;
        } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
                revs->grep_filter.regflags |= REG_ICASE;
 +              DIFF_OPT_SET(&revs->diffopt, PICKAXE_IGNORE_CASE);
        } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
                revs->grep_filter.fixed = 1;
        } else if (!strcmp(arg, "--all-match")) {
@@@ -2062,6 -2061,11 +2062,11 @@@ static void set_children(struct rev_inf
        }
  }
  
+ void reset_revision_walk(void)
+ {
+       clear_object_flags(SEEN | ADDED | SHOWN);
+ }
  int prepare_revision_walk(struct rev_info *revs)
  {
        int nr = revs->pending.nr;
                if (commit) {
                        if (!(commit->object.flags & SEEN)) {
                                commit->object.flags |= SEEN;
 -                              commit_list_insert_by_date(commit, &revs->commits);
 +                              commit_list_insert(commit, &revs->commits);
                        }
                }
                e++;
        }
 +      commit_list_reverse(&revs->commits);
 +      commit_list_sort_by_date(&revs->commits);
        if (!revs->leak_pending)
                free(list);
  
@@@ -2152,6 -2154,7 +2157,6 @@@ static int commit_match(struct commit *
        if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list)
                return 1;
        return grep_buffer(&opt->grep_filter,
 -                         NULL, /* we say nothing, not even filename */
                           commit->buffer, strlen(commit->buffer));
  }
  
diff --combined submodule.h
index 9c5e5c0c30676d5dc8be685c97b7d7f303dd7842,6633b53dfac455ce3f85ff218a24dec709fe6365..e105b0ebe6c06a03af7f82bdfbc9beb66377544f
@@@ -13,7 -13,7 +13,7 @@@ enum 
  void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
                const char *path);
  int submodule_config(const char *var, const char *value, void *cb);
 -void gitmodules_config();
 +void gitmodules_config(void);
  int parse_submodule_config_option(const char *var, const char *value);
  void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
  int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);
@@@ -29,6 -29,8 +29,8 @@@ int fetch_populated_submodules(int num_
  unsigned is_submodule_modified(const char *path, int ignore_untracked);
  int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
                    const unsigned char a[20], const unsigned char b[20], int search);
- int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);
+ int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name,
+               struct string_list *needs_pushing);
+ int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name);
  
  #endif
diff --combined transport.c
index 2dfac700b630aabb2d1014c51a08889b3a3bae82,4177b7d6091c0b11491d77815df69c6da8ed23fb..1811b500d92b1120a01d0ac09f86c0218f3d163b
@@@ -11,6 -11,7 +11,7 @@@
  #include "branch.h"
  #include "url.h"
  #include "submodule.h"
+ #include "string-list.h"
  
  /* rsync support */
  
@@@ -721,10 -722,6 +722,10 @@@ void transport_print_push_status(const 
  {
        struct ref *ref;
        int n = 0;
 +      unsigned char head_sha1[20];
 +      char *head;
 +
 +      head = resolve_refdup("HEAD", head_sha1, 1, NULL);
  
        if (verbose) {
                for (ref = refs; ref; ref = ref->next)
                    ref->status != REF_STATUS_UPTODATE &&
                    ref->status != REF_STATUS_OK)
                        n += print_one_push_status(ref, dest, n, porcelain);
 -              if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD)
 -                      *nonfastforward = 1;
 +              if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD &&
 +                  *nonfastforward != NON_FF_HEAD) {
 +                      if (!strcmp(head, ref->name))
 +                              *nonfastforward = NON_FF_HEAD;
 +                      else
 +                              *nonfastforward = NON_FF_OTHER;
 +              }
        }
  }
  
@@@ -1002,17 -994,32 +1003,36 @@@ void transport_set_verbosity(struct tra
         * Rules used to determine whether to report progress (processing aborts
         * when a rule is satisfied):
         *
 -       *   1. Report progress, if force_progress is 1 (ie. --progress).
 -       *   2. Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
 -       *   3. Report progress if isatty(2) is 1.
 +       *   . Report progress, if force_progress is 1 (ie. --progress).
 +       *   . Don't report progress, if force_progress is 0 (ie. --no-progress).
 +       *   . Don't report progress, if verbosity < 0 (ie. -q/--quiet ).
 +       *   . Report progress if isatty(2) is 1.
         **/
 -      transport->progress = force_progress || (verbosity >= 0 && isatty(2));
 +      if (force_progress >= 0)
 +              transport->progress = !!force_progress;
 +      else
 +              transport->progress = verbosity >= 0 && isatty(2);
  }
  
+ static void die_with_unpushed_submodules(struct string_list *needs_pushing)
+ {
+       int i;
+       fprintf(stderr, "The following submodule paths contain changes that can\n"
+                       "not be found on any remote:\n");
+       for (i = 0; i < needs_pushing->nr; i++)
+               printf("  %s\n", needs_pushing->items[i].string);
+       fprintf(stderr, "\nPlease try\n\n"
+                       "       git push --recurse-submodules=on-demand\n\n"
+                       "or cd to the path and use\n\n"
+                       "       git push\n\n"
+                       "to push them to a remote.\n\n");
+       string_list_clear(needs_pushing, 0);
+       die("Aborting.");
+ }
  int transport_push(struct transport *transport,
                   int refspec_nr, const char **refspec, int flags,
                   int *nonfastforward)
                        match_flags |= MATCH_REFS_ALL;
                if (flags & TRANSPORT_PUSH_MIRROR)
                        match_flags |= MATCH_REFS_MIRROR;
 +              if (flags & TRANSPORT_PUSH_PRUNE)
 +                      match_flags |= MATCH_REFS_PRUNE;
  
                if (match_push_refs(local_refs, &remote_refs,
                                    refspec_nr, refspec, match_flags)) {
                        flags & TRANSPORT_PUSH_MIRROR,
                        flags & TRANSPORT_PUSH_FORCE);
  
-               if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !is_bare_repository()) {
+               if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
                        struct ref *ref = remote_refs;
                        for (; ref; ref = ref->next)
                                if (!is_null_sha1(ref->new_sha1) &&
-                                   check_submodule_needs_pushing(ref->new_sha1,transport->remote->name))
-                                       die("There are unpushed submodules, aborting.");
+                                   !push_unpushed_submodules(ref->new_sha1,
+                                           transport->remote->name))
+                                   die ("Failed to push all needed submodules!");
+               }
+               if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
+                             TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) {
+                       struct ref *ref = remote_refs;
+                       struct string_list needs_pushing;
+                       memset(&needs_pushing, 0, sizeof(struct string_list));
+                       needs_pushing.strdup_strings = 1;
+                       for (; ref; ref = ref->next)
+                               if (!is_null_sha1(ref->new_sha1) &&
+                                   find_unpushed_submodules(ref->new_sha1,
+                                           transport->remote->name, &needs_pushing))
+                                       die_with_unpushed_submodules(&needs_pushing);
                }
  
                push_ret = transport->push_refs(transport, remote_refs, flags);
@@@ -1163,7 -1183,7 +1198,7 @@@ int transport_disconnect(struct transpo
  }
  
  /*
 - * Strip username (and password) from an url and return
 + * Strip username (and password) from a URL and return
   * it in a newly allocated string.
   */
  char *transport_anonymize_url(const char *url)
diff --combined transport.h
index 1631a35ea6332fb07e993ce42042ba9c8d037a21,c6bee7061310d5d4ec9503f5dee0b4627c8b9576..b866c126e695810131cdab537b8b994c0c32e14e
@@@ -102,7 -102,7 +102,8 @@@ struct transport 
  #define TRANSPORT_PUSH_PORCELAIN 16
  #define TRANSPORT_PUSH_SET_UPSTREAM 32
  #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
 +#define TRANSPORT_PUSH_PRUNE 128
+ #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
  
  #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
  
@@@ -138,8 -138,6 +139,8 @@@ int transport_set_option(struct transpo
  void transport_set_verbosity(struct transport *transport, int verbosity,
        int force_progress);
  
 +#define NON_FF_HEAD 1
 +#define NON_FF_OTHER 2
  int transport_push(struct transport *connection,
                   int refspec_nr, const char **refspec, int flags,
                   int * nonfastforward);