#include "builtin.h" #include "cache.h" #include "parse-options.h" #include "quote.h" #include "pathspec.h" #include "dir.h" #include "utf8.h" #include "submodule.h" #include "submodule-config.h" #include "string-list.h" #include "run-command.h" struct module_list { const struct cache_entry **entries; int alloc, nr; }; #define MODULE_LIST_INIT { NULL, 0, 0 } static int module_list_compute(int argc, const char **argv, const char *prefix, struct pathspec *pathspec, struct module_list *list) { int i, result = 0; char *max_prefix, *ps_matched = NULL; int max_prefix_len; parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL | PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP, prefix, argv); /* Find common prefix for all pathspec's */ max_prefix = common_prefix(pathspec); max_prefix_len = max_prefix ? strlen(max_prefix) : 0; if (pathspec->nr) ps_matched = xcalloc(pathspec->nr, 1); if (read_cache() < 0) die(_("index file corrupt")); for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; if (!S_ISGITLINK(ce->ce_mode) || !match_pathspec(pathspec, ce->name, ce_namelen(ce), max_prefix_len, ps_matched, 1)) continue; ALLOC_GROW(list->entries, list->nr + 1, list->alloc); list->entries[list->nr++] = ce; while (i + 1 < active_nr && !strcmp(ce->name, active_cache[i + 1]->name)) /* * Skip entries with the same name in different stages * to make sure an entry is returned only once. */ i++; } free(max_prefix); if (ps_matched && report_path_error(ps_matched, pathspec, prefix)) result = -1; free(ps_matched); return result; } static int module_list(int argc, const char **argv, const char *prefix) { int i; struct pathspec pathspec; struct module_list list = MODULE_LIST_INIT; struct option module_list_options[] = { OPT_STRING(0, "prefix", &prefix, N_("path"), N_("alternative anchor for relative paths")), OPT_END() }; const char *const git_submodule_helper_usage[] = { N_("git submodule--helper list [--prefix=] [...]"), NULL }; argc = parse_options(argc, argv, prefix, module_list_options, git_submodule_helper_usage, 0); if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) { printf("#unmatched\n"); return 1; } for (i = 0; i < list.nr; i++) { const struct cache_entry *ce = list.entries[i]; if (ce_stage(ce)) printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1)); else printf("%06o %s %d\t", ce->ce_mode, sha1_to_hex(ce->sha1), ce_stage(ce)); utf8_fprintf(stdout, "%s\n", ce->name); } return 0; } static int module_name(int argc, const char **argv, const char *prefix) { const struct submodule *sub; if (argc != 2) usage(_("git submodule--helper name ")); gitmodules_config(); sub = submodule_from_path(null_sha1, argv[1]); if (!sub) die(_("no submodule mapping found in .gitmodules for path '%s'"), argv[1]); printf("%s\n", sub->name); return 0; } static int clone_submodule(const char *path, const char *gitdir, const char *url, const char *depth, const char *reference, int quiet) { struct child_process cp; child_process_init(&cp); argv_array_push(&cp.args, "clone"); argv_array_push(&cp.args, "--no-checkout"); if (quiet) argv_array_push(&cp.args, "--quiet"); if (depth && *depth) argv_array_pushl(&cp.args, "--depth", depth, NULL); if (reference && *reference) argv_array_pushl(&cp.args, "--reference", reference, NULL); if (gitdir && *gitdir) argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); argv_array_push(&cp.args, url); argv_array_push(&cp.args, path); cp.git_cmd = 1; cp.env = local_repo_env; cp.no_stdin = 1; return run_command(&cp); } static int module_clone(int argc, const char **argv, const char *prefix) { const char *path = NULL, *name = NULL, *url = NULL; const char *reference = NULL, *depth = NULL; int quiet = 0; FILE *submodule_dot_git; char *sm_gitdir, *cwd, *p; struct strbuf rel_path = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &prefix, N_("path"), N_("alternative anchor for relative paths")), OPT_STRING(0, "path", &path, N_("path"), N_("where the new submodule will be cloned to")), OPT_STRING(0, "name", &name, N_("string"), N_("name of the new submodule")), OPT_STRING(0, "url", &url, N_("string"), N_("url where to clone the submodule from")), OPT_STRING(0, "reference", &reference, N_("string"), N_("reference repository")), OPT_STRING(0, "depth", &depth, N_("string"), N_("depth for shallow clones")), OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), OPT_END() }; const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=] [--quiet] " "[--reference ] [--name ] [--url ]" "[--depth ] [--] [...]"), NULL }; argc = parse_options(argc, argv, prefix, module_clone_options, git_submodule_helper_usage, 0); strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); sm_gitdir = strbuf_detach(&sb, NULL); if (!file_exists(sm_gitdir)) { if (safe_create_leading_directories_const(sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); if (clone_submodule(path, sm_gitdir, url, depth, reference, quiet)) die(_("clone of '%s' into submodule path '%s' failed"), url, path); } else { if (safe_create_leading_directories_const(path) < 0) die(_("could not create directory '%s'"), path); strbuf_addf(&sb, "%s/index", sm_gitdir); unlink_or_warn(sb.buf); strbuf_reset(&sb); } /* Write a .git file in the submodule to redirect to the superproject. */ if (safe_create_leading_directories_const(path) < 0) die(_("could not create directory '%s'"), path); if (path && *path) strbuf_addf(&sb, "%s/.git", path); else strbuf_addstr(&sb, ".git"); if (safe_create_leading_directories_const(sb.buf) < 0) die(_("could not create leading directories of '%s'"), sb.buf); submodule_dot_git = fopen(sb.buf, "w"); if (!submodule_dot_git) die_errno(_("cannot open file '%s'"), sb.buf); fprintf(submodule_dot_git, "gitdir: %s\n", relative_path(sm_gitdir, path, &rel_path)); if (fclose(submodule_dot_git)) die(_("could not close file %s"), sb.buf); strbuf_reset(&sb); strbuf_reset(&rel_path); cwd = xgetcwd(); /* Redirect the worktree of the submodule in the superproject's config */ if (!is_absolute_path(sm_gitdir)) { strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir); free(sm_gitdir); sm_gitdir = strbuf_detach(&sb, NULL); } strbuf_addf(&sb, "%s/%s", cwd, path); p = git_pathdup_submodule(path, "config"); if (!p) die(_("could not get submodule directory for '%s'"), path); git_config_set_in_file(p, "core.worktree", relative_path(sb.buf, sm_gitdir, &rel_path)); strbuf_release(&sb); strbuf_release(&rel_path); free(sm_gitdir); free(cwd); free(p); return 0; } struct cmd_struct { const char *cmd; int (*fn)(int, const char **, const char *); }; static struct cmd_struct commands[] = { {"list", module_list}, {"name", module_name}, {"clone", module_clone}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) { int i; if (argc < 2) die(_("fatal: submodule--helper subcommand must be " "called with a subcommand")); for (i = 0; i < ARRAY_SIZE(commands); i++) if (!strcmp(argv[1], commands[i].cmd)) return commands[i].fn(argc - 1, argv + 1, prefix); die(_("fatal: '%s' is not a valid submodule--helper " "subcommand"), argv[1]); }