git/builtin/submodule--helper.c
Jacob Keller 14111fc492 git: submodule honor -c credential.* from command line
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>
2016-03-01 12:24:22 -08:00

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]);
}