clear_delta_base_cache(): don't modify hashmap while iterating
[gitweb.git] / submodule.c
index 6f7d883de950af8ba6427537d09e021a4bed36bd..ece17315d671cf182f21c261d879c58f193cde09 100644 (file)
@@ -500,27 +500,67 @@ static int has_remote(const char *refname, const struct object_id *oid,
        return 1;
 }
 
-static int submodule_needs_pushing(const char *path, const unsigned char sha1[20])
+static int append_sha1_to_argv(const unsigned char sha1[20], void *data)
 {
-       if (add_submodule_odb(path) || !lookup_commit_reference(sha1))
+       struct argv_array *argv = data;
+       argv_array_push(argv, sha1_to_hex(sha1));
+       return 0;
+}
+
+static int check_has_commit(const unsigned char sha1[20], void *data)
+{
+       int *has_commit = data;
+
+       if (!lookup_commit_reference(sha1))
+               *has_commit = 0;
+
+       return 0;
+}
+
+static int submodule_has_commits(const char *path, struct sha1_array *commits)
+{
+       int has_commit = 1;
+
+       if (add_submodule_odb(path))
+               return 0;
+
+       sha1_array_for_each_unique(commits, check_has_commit, &has_commit);
+       return has_commit;
+}
+
+static int submodule_needs_pushing(const char *path, struct sha1_array *commits)
+{
+       if (!submodule_has_commits(path, commits))
+               /*
+                * NOTE: We do consider it safe to return "no" here. The
+                * correct answer would be "We do not know" instead of
+                * "No push needed", but it is quite hard to change
+                * the submodule pointer without having the submodule
+                * around. If a user did however change the submodules
+                * without having the submodule around, this indicates
+                * an expert who knows what they are doing or a
+                * maintainer integrating work from other people. In
+                * both cases it should be safe to skip this check.
+                */
                return 0;
 
        if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
                struct child_process cp = CHILD_PROCESS_INIT;
-               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);
-               cp.argv = argv;
+               argv_array_push(&cp.args, "rev-list");
+               sha1_array_for_each_unique(commits, append_sha1_to_argv, &cp.args);
+               argv_array_pushl(&cp.args, "--not", "--remotes", "-n", "1" , NULL);
+
                prepare_submodule_repo_env(&cp.env_array);
                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);
+                       die("Could not run 'git rev-list <commits> --not --remotes -n 1' command in submodule %s",
+                                       path);
                if (strbuf_read(&buf, cp.out, 41))
                        needs_pushing = 1;
                finish_command(&cp);
@@ -532,19 +572,34 @@ static int submodule_needs_pushing(const char *path, const unsigned char sha1[20
        return 0;
 }
 
+static struct sha1_array *submodule_commits(struct string_list *submodules,
+                                           const char *path)
+{
+       struct string_list_item *item;
+
+       item = string_list_insert(submodules, path);
+       if (item->util)
+               return (struct sha1_array *) item->util;
+
+       /* NEEDSWORK: should we have sha1_array_init()? */
+       item->util = xcalloc(1, sizeof(struct sha1_array));
+       return (struct sha1_array *) item->util;
+}
+
 static void collect_submodules_from_diff(struct diff_queue_struct *q,
                                         struct diff_options *options,
                                         void *data)
 {
        int i;
-       struct string_list *needs_pushing = data;
+       struct string_list *submodules = data;
 
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
+               struct sha1_array *commits;
                if (!S_ISGITLINK(p->two->mode))
                        continue;
-               if (submodule_needs_pushing(p->two->path, p->two->oid.hash))
-                       string_list_insert(needs_pushing, p->two->path);
+               commits = submodule_commits(submodules, p->two->path);
+               sha1_array_append(commits, p->two->oid.hash);
        }
 }
 
@@ -560,46 +615,63 @@ static void find_unpushed_submodule_commits(struct commit *commit,
        diff_tree_combined_merge(commit, 1, &rev);
 }
 
-int find_unpushed_submodules(unsigned char new_sha1[20],
+static void free_submodules_sha1s(struct string_list *submodules)
+{
+       struct string_list_item *item;
+       for_each_string_list_item(item, submodules)
+               sha1_array_clear((struct sha1_array *) item->util);
+       string_list_clear(submodules, 1);
+}
+
+int find_unpushed_submodules(struct sha1_array *commits,
                const char *remotes_name, struct string_list *needs_pushing)
 {
        struct rev_info rev;
        struct commit *commit;
-       const char *argv[] = {NULL, NULL, "--not", "NULL", NULL};
-       int argc = ARRAY_SIZE(argv) - 1;
-       char *sha1_copy;
-
-       struct strbuf remotes_arg = STRBUF_INIT;
+       struct string_list submodules = STRING_LIST_INIT_DUP;
+       struct string_list_item *submodule;
+       struct argv_array argv = ARGV_ARRAY_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);
+
+       /* argv.argv[0] will be ignored by setup_revisions */
+       argv_array_push(&argv, "find_unpushed_submodules");
+       sha1_array_for_each_unique(commits, append_sha1_to_argv, &argv);
+       argv_array_push(&argv, "--not");
+       argv_array_pushf(&argv, "--remotes=%s", remotes_name);
+
+       setup_revisions(argv.argc, argv.argv, &rev, NULL);
        if (prepare_revision_walk(&rev))
                die("revision walk setup failed");
 
        while ((commit = get_revision(&rev)) != NULL)
-               find_unpushed_submodule_commits(commit, needs_pushing);
+               find_unpushed_submodule_commits(commit, &submodules);
 
        reset_revision_walk();
-       free(sha1_copy);
-       strbuf_release(&remotes_arg);
+       argv_array_clear(&argv);
+
+       for_each_string_list_item(submodule, &submodules) {
+               struct sha1_array *commits = (struct sha1_array *) submodule->util;
+
+               if (submodule_needs_pushing(submodule->string, commits))
+                       string_list_insert(needs_pushing, submodule->string);
+       }
+       free_submodules_sha1s(&submodules);
 
        return needs_pushing->nr;
 }
 
-static int push_submodule(const char *path)
+static int push_submodule(const char *path, int dry_run)
 {
        if (add_submodule_odb(path))
                return 1;
 
        if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
                struct child_process cp = CHILD_PROCESS_INIT;
-               const char *argv[] = {"push", NULL};
+               argv_array_push(&cp.args, "push");
+               if (dry_run)
+                       argv_array_push(&cp.args, "--dry-run");
 
-               cp.argv = argv;
                prepare_submodule_repo_env(&cp.env_array);
                cp.git_cmd = 1;
                cp.no_stdin = 1;
@@ -612,18 +684,20 @@ static int push_submodule(const char *path)
        return 1;
 }
 
-int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name)
+int push_unpushed_submodules(struct sha1_array *commits,
+                            const char *remotes_name,
+                            int dry_run)
 {
        int i, ret = 1;
        struct string_list needs_pushing = STRING_LIST_INIT_DUP;
 
-       if (!find_unpushed_submodules(new_sha1, remotes_name, &needs_pushing))
+       if (!find_unpushed_submodules(commits, remotes_name, &needs_pushing))
                return 1;
 
        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)) {
+               if (!push_submodule(path, dry_run)) {
                        fprintf(stderr, "Unable to push submodule '%s'\n", path);
                        ret = 0;
                }