submodule: add absorb-git-dir function
[gitweb.git] / submodule.c
index 2de06a33516d40ab169a9a031df4fb547f9094fd..45ccfb7ab4d512a317b950c99ae50bf491cc5c95 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;
@@ -123,9 +124,7 @@ void stage_updated_gitmodules(void)
 static int add_submodule_odb(const char *path)
 {
        struct strbuf objects_directory = STRBUF_INIT;
-       struct alternate_object_database *alt_odb;
        int ret = 0;
-       size_t alloc;
 
        ret = strbuf_git_path_submodule(&objects_directory, path, "objects/");
        if (ret)
@@ -134,26 +133,7 @@ static int add_submodule_odb(const char *path)
                ret = -1;
                goto done;
        }
-       /* avoid adding it twice */
-       prepare_alt_odb();
-       for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next)
-               if (alt_odb->name - alt_odb->base == objects_directory.len &&
-                               !strncmp(alt_odb->base, objects_directory.buf,
-                                       objects_directory.len))
-                       goto done;
-
-       alloc = st_add(objects_directory.len, 42); /* for "12/345..." sha1 */
-       alt_odb = xmalloc(st_add(sizeof(*alt_odb), alloc));
-       alt_odb->next = alt_odb_list;
-       xsnprintf(alt_odb->base, alloc, "%s", objects_directory.buf);
-       alt_odb->name = alt_odb->base + objects_directory.len;
-       alt_odb->name[2] = '/';
-       alt_odb->name[40] = '\0';
-       alt_odb->name[41] = '\0';
-       alt_odb_list = alt_odb;
-
-       /* add possible alternates from the submodule */
-       read_info_alternates(objects_directory.buf, 0);
+       add_to_alternates_memory(objects_directory.buf);
 done:
        strbuf_release(&objects_directory);
        return ret;
@@ -392,10 +372,9 @@ static void show_submodule_header(FILE *f, const char *path,
        }
 
 output_header:
-       strbuf_addf(&sb, "%s%sSubmodule %s %s..", line_prefix, meta, path,
-                       find_unique_abbrev(one->hash, DEFAULT_ABBREV));
-       if (!fast_backward && !fast_forward)
-               strbuf_addch(&sb, '.');
+       strbuf_addf(&sb, "%s%sSubmodule %s ", line_prefix, meta, path);
+       strbuf_add_unique_abbrev(&sb, one->hash, DEFAULT_ABBREV);
+       strbuf_addstr(&sb, (fast_backward || fast_forward) ? ".." : "...");
        strbuf_add_unique_abbrev(&sb, two->hash, DEFAULT_ABBREV);
        if (message)
                strbuf_addf(&sb, " %s%s\n", message, reset);
@@ -1244,30 +1223,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 = xstrdup(real_path(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;
@@ -1283,3 +1238,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 = xstrdup(real_path(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 = xstrdup(real_path(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 = xstrdup(real_path(sub_git_dir));
+       real_common_git_dir = xstrdup(real_path(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);
+}