mirror of
https://github.com/git/git
synced 2024-07-17 02:57:48 +00:00
![Jacob Keller](/assets/img/avatar_default.png)
Due to the way that the git-submodule code works, it clears all local git environment variables before entering submodules. This is normally a good thing since we want to clear settings such as GIT_WORKTREE and other variables which would affect the operation of submodule commands. However, GIT_CONFIG_PARAMETERS is special, and we actually do want to preserve these settings. However, we do not want to preserve all configuration as many things should be left specific to the parent project. Add a git submodule--helper function, sanitize-config, which shall be used to sanitize GIT_CONFIG_PARAMETERS, removing all key/value pairs except a small subset that are known to be safe and necessary. Replace all the calls to clear_local_git_env with a wrapped function that filters GIT_CONFIG_PARAMETERS using the new helper and then restores it to the filtered subset after clearing the rest of the environment. Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
353 lines
9.2 KiB
C
353 lines
9.2 KiB
C
#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=<path>] [<path>...]"),
|
|
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 <path>"));
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Rules to sanitize configuration variables that are Ok to be passed into
|
|
* submodule operations from the parent project using "-c". Should only
|
|
* include keys which are both (a) safe and (b) necessary for proper
|
|
* operation.
|
|
*/
|
|
static int submodule_config_ok(const char *var)
|
|
{
|
|
if (starts_with(var, "credential."))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int sanitize_submodule_config(const char *var, const char *value, void *data)
|
|
{
|
|
struct strbuf *out = data;
|
|
|
|
if (submodule_config_ok(var)) {
|
|
if (out->len)
|
|
strbuf_addch(out, ' ');
|
|
|
|
if (value)
|
|
sq_quotef(out, "%s=%s", var, value);
|
|
else
|
|
sq_quote_buf(out, var);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void prepare_submodule_repo_env(struct argv_array *out)
|
|
{
|
|
const char * const *var;
|
|
|
|
for (var = local_repo_env; *var; var++) {
|
|
if (!strcmp(*var, CONFIG_DATA_ENVIRONMENT)) {
|
|
struct strbuf sanitized_config = STRBUF_INIT;
|
|
git_config_from_parameters(sanitize_submodule_config,
|
|
&sanitized_config);
|
|
argv_array_pushf(out, "%s=%s", *var, sanitized_config.buf);
|
|
strbuf_release(&sanitized_config);
|
|
} else {
|
|
argv_array_push(out, *var);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
prepare_submodule_repo_env(&cp.env_array);
|
|
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=<path>] [--quiet] "
|
|
"[--reference <repository>] [--name <name>] [--depth <depth>] "
|
|
"--url <url> --path <path>"),
|
|
NULL
|
|
};
|
|
|
|
argc = parse_options(argc, argv, prefix, module_clone_options,
|
|
git_submodule_helper_usage, 0);
|
|
|
|
if (argc || !url || !path)
|
|
usage_with_options(git_submodule_helper_usage,
|
|
module_clone_options);
|
|
|
|
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;
|
|
}
|
|
|
|
static int module_sanitize_config(int argc, const char **argv, const char *prefix)
|
|
{
|
|
struct strbuf sanitized_config = STRBUF_INIT;
|
|
|
|
if (argc > 1)
|
|
usage(_("git submodule--helper sanitize-config"));
|
|
|
|
git_config_from_parameters(sanitize_submodule_config, &sanitized_config);
|
|
if (sanitized_config.len)
|
|
printf("%s\n", sanitized_config.buf);
|
|
|
|
strbuf_release(&sanitized_config);
|
|
|
|
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},
|
|
{"sanitize-config", module_sanitize_config},
|
|
};
|
|
|
|
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]);
|
|
}
|