Merge branch 'fg/submodule-ff-check-before-push'
authorJunio C Hamano <gitster@pobox.com>
Fri, 2 Sep 2011 20:07:58 +0000 (13:07 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 2 Sep 2011 20:07:58 +0000 (13:07 -0700)
* fg/submodule-ff-check-before-push:
push: Don't push a repository with unpushed submodules

1  2 
Documentation/git-push.txt
submodule.c
transport.c
index 49c6e9fa51daea75272ea7eec8a360bfd324a467,6ae6ba3c2e497cb4bbc75a7c625aec85c03317f7..aede48877fb080bd12c346c74cf7453860d7de21
@@@ -162,6 -162,12 +162,12 @@@ 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.
  include::urls-remotes.txt[]
  
  OUTPUT
@@@ -327,12 -333,12 +333,12 @@@ a case where you do mean to lose histor
  Examples
  --------
  
 -git push::
 +`git push`::
        Works like `git push <remote>`, where <remote> is the
        current branch's remote (or `origin`, if no remote is
        configured for the current branch).
  
 -git push origin::
 +`git push origin`::
        Without additional configuration, works like
        `git push origin :`.
  +
@@@ -344,45 -350,45 +350,45 @@@ use `git config remote.origin.push HEAD
  the ones in the examples below) can be configured as the default for
  `git push origin`.
  
 -git push origin :::
 +`git push origin :`::
        Push "matching" branches to `origin`. See
        <refspec> in the <<OPTIONS,OPTIONS>> section above for a
        description of "matching" branches.
  
 -git push origin master::
 +`git push origin master`::
        Find a ref that matches `master` in the source repository
        (most likely, it would find `refs/heads/master`), and update
        the same ref (e.g. `refs/heads/master`) in `origin` repository
        with it.  If `master` did not exist remotely, it would be
        created.
  
 -git push origin HEAD::
 +`git push origin HEAD`::
        A handy way to push the current branch to the same name on the
        remote.
  
 -git push origin master:satellite/master dev:satellite/dev::
 +`git push origin master:satellite/master dev:satellite/dev`::
        Use the source ref that matches `master` (e.g. `refs/heads/master`)
        to update the ref that matches `satellite/master` (most probably
        `refs/remotes/satellite/master`) in the `origin` repository, then
        do the same for `dev` and `satellite/dev`.
  
 -git push origin HEAD:master::
 +`git push origin HEAD:master`::
        Push the current branch to the remote ref matching `master` in the
        `origin` repository. This form is convenient to push the current
        branch without thinking about its local name.
  
 -git push origin master:refs/heads/experimental::
 +`git push origin master:refs/heads/experimental`::
        Create the branch `experimental` in the `origin` repository
        by copying the current `master` branch.  This form is only
        needed to create a new branch or tag in the remote repository when
        the local name and the remote name are different; otherwise,
        the ref name on its own will work.
  
 -git push origin :experimental::
 +`git push origin :experimental`::
        Find a ref that matches `experimental` in the `origin` repository
        (e.g. `refs/heads/experimental`), and delete it.
  
 -git push origin {plus}dev:master::
 +`git push origin {plus}dev:master`::
        Update the origin repository's master branch with the dev branch,
        allowing non-fast-forward updates.  *This can leave unreferenced
        commits dangling in the origin repository.*  Consider the
diff --combined submodule.c
index f71d5ed8b0e0f202084ecc6b10636196f2763b16,45f508c6fd5b64d423d1e3722c3def0cfb94fa92..7a76edf911d941722586abb4f38284fc4e6909ec
@@@ -32,7 -32,7 +32,7 @@@ static int add_submodule_odb(const cha
        const char *git_dir;
  
        strbuf_addf(&objects_directory, "%s/.git", path);
 -      git_dir = read_gitfile_gently(objects_directory.buf);
 +      git_dir = read_gitfile(objects_directory.buf);
        if (git_dir) {
                strbuf_reset(&objects_directory);
                strbuf_addstr(&objects_directory, git_dir);
@@@ -308,6 -308,114 +308,114 @@@ void set_config_fetch_recurse_submodule
        config_fetch_recurse_submodules = value;
  }
  
+ static int has_remote(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
+ {
+       return 1;
+ }
+ static int submodule_needs_pushing(const char *path, const unsigned char sha1[20])
+ {
+       if (add_submodule_odb(path) || !lookup_commit_reference(sha1))
+               return 0;
+       if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
+               struct child_process cp;
+               const char *argv[] = {"rev-list", NULL, "--not", "--remotes", "-n", "1" , NULL};
+               struct strbuf buf = STRBUF_INIT;
+               int needs_pushing = 0;
+               argv[1] = sha1_to_hex(sha1);
+               memset(&cp, 0, sizeof(cp));
+               cp.argv = argv;
+               cp.env = local_repo_env;
+               cp.git_cmd = 1;
+               cp.no_stdin = 1;
+               cp.out = -1;
+               cp.dir = path;
+               if (start_command(&cp))
+                       die("Could not run 'git rev-list %s --not --remotes -n 1' command in submodule %s",
+                               sha1_to_hex(sha1), path);
+               if (strbuf_read(&buf, cp.out, 41))
+                       needs_pushing = 1;
+               finish_command(&cp);
+               close(cp.out);
+               strbuf_release(&buf);
+               return needs_pushing;
+       }
+       return 0;
+ }
+ static void collect_submodules_from_diff(struct diff_queue_struct *q,
+                                        struct diff_options *options,
+                                        void *data)
+ {
+       int i;
+       int *needs_pushing = data;
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               if (!S_ISGITLINK(p->two->mode))
+                       continue;
+               if (submodule_needs_pushing(p->two->path, p->two->sha1)) {
+                       *needs_pushing = 1;
+                       break;
+               }
+       }
+ }
+ static void commit_need_pushing(struct commit *commit, struct commit_list *parent, int *needs_pushing)
+ {
+       const unsigned char (*parents)[20];
+       unsigned int i, n;
+       struct rev_info rev;
+       n = commit_list_count(parent);
+       parents = xmalloc(n * sizeof(*parents));
+       for (i = 0; i < n; i++) {
+               hashcpy((unsigned char *)(parents + i), parent->item->object.sha1);
+               parent = parent->next;
+       }
+       init_revisions(&rev, NULL);
+       rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = collect_submodules_from_diff;
+       rev.diffopt.format_callback_data = needs_pushing;
+       diff_tree_combined(commit->object.sha1, parents, n, 1, &rev);
+       free(parents);
+ }
+ int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
+ {
+       struct rev_info rev;
+       struct commit *commit;
+       const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
+       int argc = ARRAY_SIZE(argv) - 1;
+       char *sha1_copy;
+       int needs_pushing = 0;
+       struct strbuf remotes_arg = STRBUF_INIT;
+       strbuf_addf(&remotes_arg, "--remotes=%s", remotes_name);
+       init_revisions(&rev, NULL);
+       sha1_copy = xstrdup(sha1_to_hex(new_sha1));
+       argv[1] = sha1_copy;
+       argv[3] = remotes_arg.buf;
+       setup_revisions(argc, argv, &rev, NULL);
+       if (prepare_revision_walk(&rev))
+               die("revision walk setup failed");
+       while ((commit = get_revision(&rev)) && !needs_pushing)
+               commit_need_pushing(commit, commit->parents, &needs_pushing);
+       free(sha1_copy);
+       strbuf_release(&remotes_arg);
+       return needs_pushing;
+ }
  static int is_submodule_commit_present(const char *path, unsigned char sha1[20])
  {
        int is_present = 0;
@@@ -479,7 -587,7 +587,7 @@@ int fetch_populated_submodules(int num_
                strbuf_addf(&submodule_path, "%s/%s", work_tree, ce->name);
                strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf);
                strbuf_addf(&submodule_prefix, "%s%s/", prefix, ce->name);
 -              git_dir = read_gitfile_gently(submodule_git_dir.buf);
 +              git_dir = read_gitfile(submodule_git_dir.buf);
                if (!git_dir)
                        git_dir = submodule_git_dir.buf;
                if (is_directory(git_dir)) {
@@@ -517,7 -625,7 +625,7 @@@ unsigned is_submodule_modified(const ch
        const char *git_dir;
  
        strbuf_addf(&buf, "%s/.git", path);
 -      git_dir = read_gitfile_gently(buf.buf);
 +      git_dir = read_gitfile(buf.buf);
        if (!git_dir)
                git_dir = buf.buf;
        if (!is_directory(git_dir)) {
diff --combined transport.c
index 98c577804f177b1c6f9df0e34e5fc3a656a81d27,fa279d531fe1b99841424080487e685fbe04e5bb..d2725e57dcee993b6d969f039179406a863c94b2
@@@ -10,6 -10,7 +10,7 @@@
  #include "refs.h"
  #include "branch.h"
  #include "url.h"
+ #include "submodule.h"
  
  /* rsync support */
  
@@@ -482,18 -483,14 +483,18 @@@ static int set_git_option(struct git_tr
  static int connect_setup(struct transport *transport, int for_push, int verbose)
  {
        struct git_transport_data *data = transport->data;
 +      struct strbuf sb = STRBUF_INIT;
  
        if (data->conn)
                return 0;
  
 -      data->conn = git_connect(data->fd, transport->url,
 -                               for_push ? data->options.receivepack :
 -                               data->options.uploadpack,
 +      strbuf_addstr(&sb, for_push ? data->options.receivepack :
 +                               data->options.uploadpack);
 +      if (for_push && transport->verbose < 0)
 +              strbuf_addstr(&sb, " --quiet");
 +      data->conn = git_connect(data->fd, transport->url, sb.buf,
                                 verbose ? CONNECT_VERBOSE : 0);
 +      strbuf_release(&sb);
  
        return 0;
  }
@@@ -1045,6 -1042,14 +1046,14 @@@ int transport_push(struct transport *tr
                        flags & TRANSPORT_PUSH_MIRROR,
                        flags & TRANSPORT_PUSH_FORCE);
  
+               if ((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) && !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_ret = transport->push_refs(transport, remote_refs, flags);
                err = push_had_errors(remote_refs);
                ret = push_ret | err;