Merge branch 'cc/tests-without-assuming-ref-files-backend'
[gitweb.git] / builtin / submodule--helper.c
index 9f658a59a2ca9d4f219142870813671ef808ad32..bd250ca2164b31356fb42406351761cd2381d115 100644 (file)
 #include "run-command.h"
 #include "remote.h"
 #include "refs.h"
+#include "refspec.h"
 #include "connect.h"
 #include "revision.h"
 #include "diffcore.h"
 #include "diff.h"
+#include "object-store.h"
 
 #define OPT_QUIET (1 << 0)
 #define OPT_CACHED (1 << 1)
 #define OPT_RECURSIVE (1 << 2)
+#define OPT_FORCE (1 << 3)
 
 typedef void (*each_submodule_fn)(const struct cache_entry *list_item,
                                  void *cb_data);
@@ -50,6 +53,20 @@ static char *get_default_remote(void)
        return ret;
 }
 
+static int print_default_remote(int argc, const char **argv, const char *prefix)
+{
+       const char *remote;
+
+       if (argc != 1)
+               die(_("submodule--helper print-default-remote takes no arguments"));
+
+       remote = get_default_remote();
+       if (remote)
+               printf("%s\n", remote);
+
+       return 0;
+}
+
 static int starts_with_dot_slash(const char *str)
 {
        return str[0] == '.' && is_dir_sep(str[1]);
@@ -358,6 +375,25 @@ static void module_list_active(struct module_list *list)
        *list = active_modules;
 }
 
+static char *get_up_path(const char *path)
+{
+       int i;
+       struct strbuf sb = STRBUF_INIT;
+
+       for (i = count_slashes(path); i; i--)
+               strbuf_addstr(&sb, "../");
+
+       /*
+        * Check if 'path' ends with slash or not
+        * for having the same output for dir/sub_dir
+        * and dir/sub_dir/
+        */
+       if (!is_dir_sep(path[strlen(path) - 1]))
+               strbuf_addstr(&sb, "../");
+
+       return strbuf_detach(&sb, NULL);
+}
+
 static int module_list(int argc, const char **argv, const char *prefix)
 {
        int i;
@@ -420,7 +456,7 @@ static void init_submodule(const char *path, const char *prefix,
 
        displaypath = get_submodule_displaypath(path, prefix);
 
-       sub = submodule_from_path(&null_oid, path);
+       sub = submodule_from_path(the_repository, &null_oid, path);
 
        if (!sub)
                die(_("No url found for submodule path '%s' in .gitmodules"),
@@ -561,8 +597,12 @@ static void print_status(unsigned int flags, char state, const char *path,
 
        printf("%c%s %s", state, oid_to_hex(oid), displaypath);
 
-       if (state == ' ' || state == '+')
-               printf(" (%s)", compute_rev_name(path, oid_to_hex(oid)));
+       if (state == ' ' || state == '+') {
+               const char *name = compute_rev_name(path, oid_to_hex(oid));
+
+               if (name)
+                       printf(" (%s)", name);
+       }
 
        printf("\n");
 }
@@ -587,7 +627,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
        struct rev_info rev;
        int diff_files_result;
 
-       if (!submodule_from_path(&null_oid, path))
+       if (!submodule_from_path(the_repository, &null_oid, path))
                die(_("no submodule mapping found in .gitmodules for path '%s'"),
                      path);
 
@@ -620,9 +660,13 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
                             displaypath);
        } else if (!(flags & OPT_CACHED)) {
                struct object_id oid;
+               struct ref_store *refs = get_submodule_ref_store(path);
 
-               if (refs_head_ref(get_submodule_ref_store(path),
-                                 handle_submodule_head_ref, &oid))
+               if (!refs) {
+                       print_status(flags, '-', path, ce_oid, displaypath);
+                       goto cleanup;
+               }
+               if (refs_head_ref(refs, handle_submodule_head_ref, &oid))
                        die(_("could not resolve HEAD ref inside the "
                              "submodule '%s'"), path);
 
@@ -707,7 +751,7 @@ static int module_name(int argc, const char **argv, const char *prefix)
        if (argc != 2)
                usage(_("git submodule--helper name <path>"));
 
-       sub = submodule_from_path(&null_oid, argv[1]);
+       sub = submodule_from_path(the_repository, &null_oid, argv[1]);
 
        if (!sub)
                die(_("no submodule mapping found in .gitmodules for path '%s'"),
@@ -718,8 +762,311 @@ static int module_name(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
+struct sync_cb {
+       const char *prefix;
+       unsigned int flags;
+};
+
+#define SYNC_CB_INIT { NULL, 0 }
+
+static void sync_submodule(const char *path, const char *prefix,
+                          unsigned int flags)
+{
+       const struct submodule *sub;
+       char *remote_key = NULL;
+       char *sub_origin_url, *super_config_url, *displaypath;
+       struct strbuf sb = STRBUF_INIT;
+       struct child_process cp = CHILD_PROCESS_INIT;
+       char *sub_config_path = NULL;
+
+       if (!is_submodule_active(the_repository, path))
+               return;
+
+       sub = submodule_from_path(the_repository, &null_oid, path);
+
+       if (sub && sub->url) {
+               if (starts_with_dot_dot_slash(sub->url) ||
+                   starts_with_dot_slash(sub->url)) {
+                       char *remote_url, *up_path;
+                       char *remote = get_default_remote();
+                       strbuf_addf(&sb, "remote.%s.url", remote);
+
+                       if (git_config_get_string(sb.buf, &remote_url))
+                               remote_url = xgetcwd();
+
+                       up_path = get_up_path(path);
+                       sub_origin_url = relative_url(remote_url, sub->url, up_path);
+                       super_config_url = relative_url(remote_url, sub->url, NULL);
+
+                       free(remote);
+                       free(up_path);
+                       free(remote_url);
+               } else {
+                       sub_origin_url = xstrdup(sub->url);
+                       super_config_url = xstrdup(sub->url);
+               }
+       } else {
+               sub_origin_url = xstrdup("");
+               super_config_url = xstrdup("");
+       }
+
+       displaypath = get_submodule_displaypath(path, prefix);
+
+       if (!(flags & OPT_QUIET))
+               printf(_("Synchronizing submodule url for '%s'\n"),
+                        displaypath);
+
+       strbuf_reset(&sb);
+       strbuf_addf(&sb, "submodule.%s.url", sub->name);
+       if (git_config_set_gently(sb.buf, super_config_url))
+               die(_("failed to register url for submodule path '%s'"),
+                     displaypath);
+
+       if (!is_submodule_populated_gently(path, NULL))
+               goto cleanup;
+
+       prepare_submodule_repo_env(&cp.env_array);
+       cp.git_cmd = 1;
+       cp.dir = path;
+       argv_array_pushl(&cp.args, "submodule--helper",
+                        "print-default-remote", NULL);
+
+       strbuf_reset(&sb);
+       if (capture_command(&cp, &sb, 0))
+               die(_("failed to get the default remote for submodule '%s'"),
+                     path);
+
+       strbuf_strip_suffix(&sb, "\n");
+       remote_key = xstrfmt("remote.%s.url", sb.buf);
+
+       strbuf_reset(&sb);
+       submodule_to_gitdir(&sb, path);
+       strbuf_addstr(&sb, "/config");
+
+       if (git_config_set_in_file_gently(sb.buf, remote_key, sub_origin_url))
+               die(_("failed to update remote for submodule '%s'"),
+                     path);
+
+       if (flags & OPT_RECURSIVE) {
+               struct child_process cpr = CHILD_PROCESS_INIT;
+
+               cpr.git_cmd = 1;
+               cpr.dir = path;
+               prepare_submodule_repo_env(&cpr.env_array);
+
+               argv_array_push(&cpr.args, "--super-prefix");
+               argv_array_pushf(&cpr.args, "%s/", displaypath);
+               argv_array_pushl(&cpr.args, "submodule--helper", "sync",
+                                "--recursive", NULL);
+
+               if (flags & OPT_QUIET)
+                       argv_array_push(&cpr.args, "--quiet");
+
+               if (run_command(&cpr))
+                       die(_("failed to recurse into submodule '%s'"),
+                             path);
+       }
+
+cleanup:
+       free(super_config_url);
+       free(sub_origin_url);
+       strbuf_release(&sb);
+       free(remote_key);
+       free(displaypath);
+       free(sub_config_path);
+}
+
+static void sync_submodule_cb(const struct cache_entry *list_item, void *cb_data)
+{
+       struct sync_cb *info = cb_data;
+       sync_submodule(list_item->name, info->prefix, info->flags);
+
+}
+
+static int module_sync(int argc, const char **argv, const char *prefix)
+{
+       struct sync_cb info = SYNC_CB_INIT;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+       int quiet = 0;
+       int recursive = 0;
+
+       struct option module_sync_options[] = {
+               OPT__QUIET(&quiet, N_("Suppress output of synchronizing submodule url")),
+               OPT_BOOL(0, "recursive", &recursive,
+                       N_("Recurse into nested submodules")),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper sync [--quiet] [--recursive] [<path>]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_sync_options,
+                            git_submodule_helper_usage, 0);
+
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
+               return 1;
+
+       info.prefix = prefix;
+       if (quiet)
+               info.flags |= OPT_QUIET;
+       if (recursive)
+               info.flags |= OPT_RECURSIVE;
+
+       for_each_listed_submodule(&list, sync_submodule_cb, &info);
+
+       return 0;
+}
+
+struct deinit_cb {
+       const char *prefix;
+       unsigned int flags;
+};
+#define DEINIT_CB_INIT { NULL, 0 }
+
+static void deinit_submodule(const char *path, const char *prefix,
+                            unsigned int flags)
+{
+       const struct submodule *sub;
+       char *displaypath = NULL;
+       struct child_process cp_config = CHILD_PROCESS_INIT;
+       struct strbuf sb_config = STRBUF_INIT;
+       char *sub_git_dir = xstrfmt("%s/.git", path);
+
+       sub = submodule_from_path(the_repository, &null_oid, path);
+
+       if (!sub || !sub->name)
+               goto cleanup;
+
+       displaypath = get_submodule_displaypath(path, prefix);
+
+       /* remove the submodule work tree (unless the user already did it) */
+       if (is_directory(path)) {
+               struct strbuf sb_rm = STRBUF_INIT;
+               const char *format;
+
+               /*
+                * protect submodules containing a .git directory
+                * NEEDSWORK: instead of dying, automatically call
+                * absorbgitdirs and (possibly) warn.
+                */
+               if (is_directory(sub_git_dir))
+                       die(_("Submodule work tree '%s' contains a .git "
+                             "directory (use 'rm -rf' if you really want "
+                             "to remove it including all of its history)"),
+                           displaypath);
+
+               if (!(flags & OPT_FORCE)) {
+                       struct child_process cp_rm = CHILD_PROCESS_INIT;
+                       cp_rm.git_cmd = 1;
+                       argv_array_pushl(&cp_rm.args, "rm", "-qn",
+                                        path, NULL);
+
+                       if (run_command(&cp_rm))
+                               die(_("Submodule work tree '%s' contains local "
+                                     "modifications; use '-f' to discard them"),
+                                     displaypath);
+               }
+
+               strbuf_addstr(&sb_rm, path);
+
+               if (!remove_dir_recursively(&sb_rm, 0))
+                       format = _("Cleared directory '%s'\n");
+               else
+                       format = _("Could not remove submodule work tree '%s'\n");
+
+               if (!(flags & OPT_QUIET))
+                       printf(format, displaypath);
+
+               strbuf_release(&sb_rm);
+       }
+
+       if (mkdir(path, 0777))
+               printf(_("could not create empty submodule directory %s"),
+                     displaypath);
+
+       cp_config.git_cmd = 1;
+       argv_array_pushl(&cp_config.args, "config", "--get-regexp", NULL);
+       argv_array_pushf(&cp_config.args, "submodule.%s\\.", sub->name);
+
+       /* remove the .git/config entries (unless the user already did it) */
+       if (!capture_command(&cp_config, &sb_config, 0) && sb_config.len) {
+               char *sub_key = xstrfmt("submodule.%s", sub->name);
+               /*
+                * remove the whole section so we have a clean state when
+                * the user later decides to init this submodule again
+                */
+               git_config_rename_section_in_file(NULL, sub_key, NULL);
+               if (!(flags & OPT_QUIET))
+                       printf(_("Submodule '%s' (%s) unregistered for path '%s'\n"),
+                                sub->name, sub->url, displaypath);
+               free(sub_key);
+       }
+
+cleanup:
+       free(displaypath);
+       free(sub_git_dir);
+       strbuf_release(&sb_config);
+}
+
+static void deinit_submodule_cb(const struct cache_entry *list_item,
+                               void *cb_data)
+{
+       struct deinit_cb *info = cb_data;
+       deinit_submodule(list_item->name, info->prefix, info->flags);
+}
+
+static int module_deinit(int argc, const char **argv, const char *prefix)
+{
+       struct deinit_cb info = DEINIT_CB_INIT;
+       struct pathspec pathspec;
+       struct module_list list = MODULE_LIST_INIT;
+       int quiet = 0;
+       int force = 0;
+       int all = 0;
+
+       struct option module_deinit_options[] = {
+               OPT__QUIET(&quiet, N_("Suppress submodule status output")),
+               OPT__FORCE(&force, N_("Remove submodule working trees even if they contain local changes"), 0),
+               OPT_BOOL(0, "all", &all, N_("Unregister all submodules")),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule deinit [--quiet] [-f | --force] [--all | [--] [<path>...]]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_deinit_options,
+                            git_submodule_helper_usage, 0);
+
+       if (all && argc) {
+               error("pathspec and --all are incompatible");
+               usage_with_options(git_submodule_helper_usage,
+                                  module_deinit_options);
+       }
+
+       if (!argc && !all)
+               die(_("Use '--all' if you really want to deinitialize all submodules"));
+
+       if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0)
+               return 1;
+
+       info.prefix = prefix;
+       if (quiet)
+               info.flags |= OPT_QUIET;
+       if (force)
+               info.flags |= OPT_FORCE;
+
+       for_each_listed_submodule(&list, deinit_submodule_cb, &info);
+
+       return 0;
+}
+
 static int clone_submodule(const char *path, const char *gitdir, const char *url,
-                          const char *depth, struct string_list *reference,
+                          const char *depth, struct string_list *reference, int dissociate,
                           int quiet, int progress)
 {
        struct child_process cp = CHILD_PROCESS_INIT;
@@ -738,6 +1085,8 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
                        argv_array_pushl(&cp.args, "--reference",
                                         item->string, NULL);
        }
+       if (dissociate)
+               argv_array_push(&cp.args, "--dissociate");
        if (gitdir && *gitdir)
                argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL);
 
@@ -853,6 +1202,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
        char *p, *path = NULL, *sm_gitdir;
        struct strbuf sb = STRBUF_INIT;
        struct string_list reference = STRING_LIST_INIT_NODUP;
+       int dissociate = 0;
        char *sm_alternate = NULL, *error_strategy = NULL;
 
        struct option module_clone_options[] = {
@@ -871,6 +1221,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
                OPT_STRING_LIST(0, "reference", &reference,
                           N_("repo"),
                           N_("reference repository")),
+               OPT_BOOL(0, "dissociate", &dissociate,
+                          N_("use --reference only while cloning")),
                OPT_STRING(0, "depth", &depth,
                           N_("string"),
                           N_("depth for shallow clones")),
@@ -910,7 +1262,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
 
                prepare_possible_alternates(name, &reference);
 
-               if (clone_submodule(path, sm_gitdir, url, depth, &reference,
+               if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate,
                                    quiet, progress))
                        die(_("clone of '%s' into submodule path '%s' failed"),
                            url, path);
@@ -922,8 +1274,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
                strbuf_reset(&sb);
        }
 
-       /* Connect module worktree and git dir */
-       connect_work_tree_and_git_dir(path, sm_gitdir);
+       connect_work_tree_and_git_dir(path, sm_gitdir, 0);
 
        p = git_pathdup_submodule(path, "config");
        if (!p)
@@ -963,6 +1314,7 @@ struct submodule_update_clone {
        int quiet;
        int recommend_shallow;
        struct string_list references;
+       int dissociate;
        const char *depth;
        const char *recursive_prefix;
        const char *prefix;
@@ -978,7 +1330,7 @@ struct submodule_update_clone {
        int failed_clones_nr, failed_clones_alloc;
 };
 #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
-       SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
+       SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
        NULL, NULL, NULL, \
        STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
 
@@ -1030,7 +1382,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
                goto cleanup;
        }
 
-       sub = submodule_from_path(&null_oid, ce->name);
+       sub = submodule_from_path(the_repository, &null_oid, ce->name);
 
        if (suc->recursive_prefix)
                displaypath = relative_path(suc->recursive_prefix,
@@ -1105,6 +1457,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
                for_each_string_list_item(item, &suc->references)
                        argv_array_pushl(&child->args, "--reference", item->string, NULL);
        }
+       if (suc->dissociate)
+               argv_array_push(&child->args, "--dissociate");
        if (suc->depth)
                argv_array_push(&child->args, suc->depth);
 
@@ -1238,6 +1592,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
                           N_("rebase, merge, checkout or none")),
                OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"),
                           N_("reference repository")),
+               OPT_BOOL(0, "dissociate", &suc.dissociate,
+                          N_("use --reference only while cloning")),
                OPT_STRING(0, "depth", &suc.depth, "<depth>",
                           N_("Create a shallow clone truncated to the "
                              "specified number of revisions")),
@@ -1313,7 +1669,7 @@ static const char *remote_submodule_branch(const char *path)
        const char *branch = NULL;
        char *key;
 
-       sub = submodule_from_path(&null_oid, path);
+       sub = submodule_from_path(the_repository, &null_oid, path);
        if (!sub)
                return NULL;
 
@@ -1398,13 +1754,14 @@ static int push_check(int argc, const char **argv, const char *prefix)
 
        /* Check the refspec */
        if (argc > 2) {
-               int i, refspec_nr = argc - 2;
+               int i;
                struct ref *local_refs = get_local_heads();
-               struct refspec *refspec = parse_push_refspec(refspec_nr,
-                                                            argv + 2);
+               struct refspec refspec = REFSPEC_INIT_PUSH;
+
+               refspec_appendn(&refspec, argv + 2, argc - 2);
 
-               for (i = 0; i < refspec_nr; i++) {
-                       struct refspec *rs = refspec + i;
+               for (i = 0; i < refspec.nr; i++) {
+                       const struct refspec_item *rs = &refspec.items[i];
 
                        if (rs->pattern || rs->matching)
                                continue;
@@ -1431,7 +1788,7 @@ static int push_check(int argc, const char **argv, const char *prefix)
                                    rs->src);
                        }
                }
-               free_refspec(refspec_nr, refspec);
+               refspec_clear(&refspec);
        }
        free(head);
 
@@ -1521,6 +1878,9 @@ static struct cmd_struct commands[] = {
        {"resolve-relative-url-test", resolve_relative_url_test, 0},
        {"init", module_init, SUPPORT_SUPER_PREFIX},
        {"status", module_status, SUPPORT_SUPER_PREFIX},
+       {"print-default-remote", print_default_remote, 0},
+       {"sync", module_sync, SUPPORT_SUPER_PREFIX},
+       {"deinit", module_deinit, 0},
        {"remote-branch", resolve_remote_submodule_branch, 0},
        {"push-check", push_check, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},