Merge branch 'ls/p4-retry-thrice'
[gitweb.git] / submodule.c
index 260090898acf4701a96c51d13cf4834d107f5c55..11ff11ed893879601cf0643fb345afe80942734f 100644 (file)
@@ -14,6 +14,7 @@
 #include "blob.h"
 #include "thread-utils.h"
 #include "quote.h"
+#include "worktree.h"
 
 static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND;
 static int parallel_jobs = 1;
@@ -550,27 +551,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);
@@ -582,19 +623,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);
        }
 }
 
@@ -610,46 +666,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;
@@ -662,18 +735,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;
                }
@@ -1068,45 +1143,64 @@ int submodule_uses_gitfile(const char *path)
        return 1;
 }
 
-int ok_to_remove_submodule(const char *path)
+/*
+ * Check if it is a bad idea to remove a submodule, i.e. if we'd lose data
+ * when doing so.
+ *
+ * Return 1 if we'd lose data, return 0 if the removal is fine,
+ * and negative values for errors.
+ */
+int bad_to_remove_submodule(const char *path, unsigned flags)
 {
        ssize_t len;
        struct child_process cp = CHILD_PROCESS_INIT;
-       const char *argv[] = {
-               "status",
-               "--porcelain",
-               "-u",
-               "--ignore-submodules=none",
-               NULL,
-       };
        struct strbuf buf = STRBUF_INIT;
-       int ok_to_remove = 1;
+       int ret = 0;
 
        if (!file_exists(path) || is_empty_dir(path))
-               return 1;
+               return 0;
 
        if (!submodule_uses_gitfile(path))
-               return 0;
+               return 1;
+
+       argv_array_pushl(&cp.args, "status", "--porcelain",
+                                  "--ignore-submodules=none", NULL);
+
+       if (flags & SUBMODULE_REMOVAL_IGNORE_UNTRACKED)
+               argv_array_push(&cp.args, "-uno");
+       else
+               argv_array_push(&cp.args, "-uall");
+
+       if (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))
+               argv_array_push(&cp.args, "--ignored");
 
-       cp.argv = argv;
        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 status --porcelain -uall --ignore-submodules=none' in submodule %s", path);
+       if (start_command(&cp)) {
+               if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)
+                       die(_("could not start 'git status in submodule '%s'"),
+                               path);
+               ret = -1;
+               goto out;
+       }
 
        len = strbuf_read(&buf, cp.out, 1024);
        if (len > 2)
-               ok_to_remove = 0;
+               ret = 1;
        close(cp.out);
 
-       if (finish_command(&cp))
-               die("'git status --porcelain -uall --ignore-submodules=none' failed in submodule %s", path);
-
+       if (finish_command(&cp)) {
+               if (flags & SUBMODULE_REMOVAL_DIE_ON_ERROR)
+                       die(_("could not run 'git status in submodule '%s'"),
+                               path);
+               ret = -1;
+       }
+out:
        strbuf_release(&buf);
-       return ok_to_remove;
+       return ret;
 }
 
 static int find_first_merges(struct object_array *result, const char *path,
@@ -1272,30 +1366,6 @@ int merge_submodule(unsigned char result[20], const char *path,
        return 0;
 }
 
-/* Update gitfile and core.worktree setting to connect work tree and git dir */
-void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir)
-{
-       struct strbuf file_name = STRBUF_INIT;
-       struct strbuf rel_path = STRBUF_INIT;
-       const char *real_work_tree = real_pathdup(work_tree);
-
-       /* Update gitfile */
-       strbuf_addf(&file_name, "%s/.git", work_tree);
-       write_file(file_name.buf, "gitdir: %s",
-                  relative_path(git_dir, real_work_tree, &rel_path));
-
-       /* Update core.worktree setting */
-       strbuf_reset(&file_name);
-       strbuf_addf(&file_name, "%s/config", git_dir);
-       git_config_set_in_file(file_name.buf, "core.worktree",
-                              relative_path(real_work_tree, git_dir,
-                                            &rel_path));
-
-       strbuf_release(&file_name);
-       strbuf_release(&rel_path);
-       free((void *)real_work_tree);
-}
-
 int parallel_submodules(void)
 {
        return parallel_jobs;
@@ -1311,3 +1381,105 @@ void prepare_submodule_repo_env(struct argv_array *out)
        }
        argv_array_push(out, "GIT_DIR=.git");
 }
+
+/*
+ * Embeds a single submodules git directory into the superprojects git dir,
+ * non recursively.
+ */
+static void relocate_single_git_dir_into_superproject(const char *prefix,
+                                                     const char *path)
+{
+       char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL;
+       const char *new_git_dir;
+       const struct submodule *sub;
+
+       if (submodule_uses_worktrees(path))
+               die(_("relocate_gitdir for submodule '%s' with "
+                     "more than one worktree not supported"), path);
+
+       old_git_dir = xstrfmt("%s/.git", path);
+       if (read_gitfile(old_git_dir))
+               /* If it is an actual gitfile, it doesn't need migration. */
+               return;
+
+       real_old_git_dir = real_pathdup(old_git_dir);
+
+       sub = submodule_from_path(null_sha1, path);
+       if (!sub)
+               die(_("could not lookup name for submodule '%s'"), path);
+
+       new_git_dir = git_path("modules/%s", sub->name);
+       if (safe_create_leading_directories_const(new_git_dir) < 0)
+               die(_("could not create directory '%s'"), new_git_dir);
+       real_new_git_dir = real_pathdup(new_git_dir);
+
+       if (!prefix)
+               prefix = get_super_prefix();
+
+       fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
+               prefix ? prefix : "", path,
+               real_old_git_dir, real_new_git_dir);
+
+       relocate_gitdir(path, real_old_git_dir, real_new_git_dir);
+
+       free(old_git_dir);
+       free(real_old_git_dir);
+       free(real_new_git_dir);
+}
+
+/*
+ * Migrate the git directory of the submodule given by path from
+ * having its git directory within the working tree to the git dir nested
+ * in its superprojects git dir under modules/.
+ */
+void absorb_git_dir_into_superproject(const char *prefix,
+                                     const char *path,
+                                     unsigned flags)
+{
+       const char *sub_git_dir, *v;
+       char *real_sub_git_dir = NULL, *real_common_git_dir = NULL;
+       struct strbuf gitdir = STRBUF_INIT;
+
+       strbuf_addf(&gitdir, "%s/.git", path);
+       sub_git_dir = resolve_gitdir(gitdir.buf);
+
+       /* Not populated? */
+       if (!sub_git_dir)
+               goto out;
+
+       /* Is it already absorbed into the superprojects git dir? */
+       real_sub_git_dir = real_pathdup(sub_git_dir);
+       real_common_git_dir = real_pathdup(get_git_common_dir());
+       if (!skip_prefix(real_sub_git_dir, real_common_git_dir, &v))
+               relocate_single_git_dir_into_superproject(prefix, path);
+
+       if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) {
+               struct child_process cp = CHILD_PROCESS_INIT;
+               struct strbuf sb = STRBUF_INIT;
+
+               if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES)
+                       die("BUG: we don't know how to pass the flags down?");
+
+               if (get_super_prefix())
+                       strbuf_addstr(&sb, get_super_prefix());
+               strbuf_addstr(&sb, path);
+               strbuf_addch(&sb, '/');
+
+               cp.dir = path;
+               cp.git_cmd = 1;
+               cp.no_stdin = 1;
+               argv_array_pushl(&cp.args, "--super-prefix", sb.buf,
+                                          "submodule--helper",
+                                          "absorb-git-dirs", NULL);
+               prepare_submodule_repo_env(&cp.env_array);
+               if (run_command(&cp))
+                       die(_("could not recurse into submodule '%s'"), path);
+
+               strbuf_release(&sb);
+       }
+
+out:
+       strbuf_release(&gitdir);
+       free(real_sub_git_dir);
+       free(real_common_git_dir);
+}