aboutsummaryrefslogtreecommitdiffstats
path: root/builtin/submodule--helper.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/submodule--helper.c')
-rw-r--r--builtin/submodule--helper.c215
1 files changed, 164 insertions, 51 deletions
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 4c173d8b37..880ab4456e 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -1,12 +1,19 @@
-#define USE_THE_INDEX_VARIABLE
#include "builtin.h"
+#include "abspath.h"
+#include "environment.h"
+#include "gettext.h"
+#include "hex.h"
#include "repository.h"
-#include "cache.h"
#include "config.h"
#include "parse-options.h"
#include "quote.h"
+#include "path.h"
#include "pathspec.h"
+#include "preload-index.h"
#include "dir.h"
+#include "read-cache.h"
+#include "setup.h"
+#include "sparse-index.h"
#include "submodule.h"
#include "submodule-config.h"
#include "string-list.h"
@@ -14,11 +21,12 @@
#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"
+#include "object-file.h"
+#include "object-name.h"
+#include "object-store-ll.h"
#include "advice.h"
#include "branch.h"
#include "list-objects-filter-options.h"
@@ -198,18 +206,18 @@ static int module_list_compute(const char **argv,
if (repo_read_index(the_repository) < 0)
die(_("index file corrupt"));
- for (i = 0; i < the_index.cache_nr; i++) {
- const struct cache_entry *ce = the_index.cache[i];
+ for (i = 0; i < the_repository->index->cache_nr; i++) {
+ const struct cache_entry *ce = the_repository->index->cache[i];
- if (!match_pathspec(&the_index, pathspec, ce->name, ce_namelen(ce),
+ if (!match_pathspec(the_repository->index, pathspec, ce->name, ce_namelen(ce),
0, ps_matched, 1) ||
!S_ISGITLINK(ce->ce_mode))
continue;
ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
list->entries[list->nr++] = ce;
- while (i + 1 < the_index.cache_nr &&
- !strcmp(ce->name, the_index.cache[i + 1]->name))
+ while (i + 1 < the_repository->index->cache_nr &&
+ !strcmp(ce->name, the_repository->index->cache[i + 1]->name))
/*
* Skip entries with the same name in different stages
* to make sure an entry is returned only once.
@@ -248,11 +256,9 @@ static void module_list_active(struct module_list *list)
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, "../");
+ strbuf_addstrings(&sb, "../", count_slashes(path));
/*
* Check if 'path' ends with slash or not
@@ -294,6 +300,9 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
struct child_process cp = CHILD_PROCESS_INIT;
char *displaypath;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
displaypath = get_submodule_displaypath(path, info->prefix,
info->super_prefix);
@@ -557,7 +566,7 @@ static int module_init(int argc, const char **argv, const char *prefix)
* If there are no path args and submodule.active is set then,
* by default, only initialize 'active' modules.
*/
- if (!argc && git_config_get_value_multi("submodule.active"))
+ if (!argc && !git_config_get("submodule.active"))
module_list_active(&list);
info.prefix = prefix;
@@ -619,13 +628,15 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
char *displaypath;
struct strvec diff_files_args = STRVEC_INIT;
struct rev_info rev = REV_INFO_INIT;
- int diff_files_result;
struct strbuf buf = STRBUF_INIT;
const char *git_dir;
struct setup_revision_opt opt = {
.free_removed_argv_elements = 1,
};
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
if (!submodule_from_path(the_repository, null_oid(), path))
die(_("no submodule mapping found in .gitmodules for path '%s'"),
path);
@@ -659,14 +670,15 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
repo_init_revisions(the_repository, &rev, NULL);
rev.abbrev = 0;
setup_revisions(diff_files_args.nr, diff_files_args.v, &rev, &opt);
- diff_files_result = run_diff_files(&rev, 0);
+ run_diff_files(&rev, 0);
- if (!diff_result_code(&rev.diffopt, diff_files_result)) {
+ if (!diff_result_code(&rev.diffopt)) {
print_status(flags, ' ', path, ce_oid,
displaypath);
} else if (!(flags & OPT_CACHED)) {
struct object_id oid;
- struct ref_store *refs = get_submodule_ref_store(path);
+ struct ref_store *refs = repo_get_submodule_ref_store(the_repository,
+ path);
if (!refs) {
print_status(flags, '-', path, ce_oid, displaypath);
@@ -890,7 +902,8 @@ static void generate_submodule_summary(struct summary_cb *info,
if (!info->cached && oideq(&p->oid_dst, null_oid())) {
if (S_ISGITLINK(p->mod_dst)) {
- struct ref_store *refs = get_submodule_ref_store(p->sm_path);
+ struct ref_store *refs = repo_get_submodule_ref_store(the_repository,
+ p->sm_path);
if (refs)
refs_head_ref(refs, handle_submodule_head_ref, &p->oid_dst);
@@ -899,7 +912,7 @@ static void generate_submodule_summary(struct summary_cb *info,
int fd = open(p->sm_path, O_RDONLY);
if (fd < 0 || fstat(fd, &st) < 0 ||
- index_fd(&the_index, &p->oid_dst, fd, &st, OBJ_BLOB,
+ index_fd(the_repository->index, &p->oid_dst, fd, &st, OBJ_BLOB,
p->sm_path, 0))
error(_("couldn't hash object from '%s'"), p->sm_path);
} else {
@@ -1108,7 +1121,7 @@ static int compute_summary_module_list(struct object_id *head_oid,
strvec_pushv(&diff_args, info->argv);
git_config(git_diff_basic_config, NULL);
- init_revisions(&rev, info->prefix);
+ repo_init_revisions(the_repository, &rev, info->prefix);
rev.abbrev = 0;
precompose_argv_prefix(diff_args.nr, diff_args.v, NULL);
setup_revisions(diff_args.nr, diff_args.v, &rev, &opt);
@@ -1131,7 +1144,7 @@ static int compute_summary_module_list(struct object_id *head_oid,
}
if (diff_cmd == DIFF_INDEX)
- run_diff_index(&rev, info->cached);
+ run_diff_index(&rev, info->cached ? DIFF_INDEX_CACHED : 0);
else
run_diff_files(&rev, 0);
prepare_submodule_summary(info, &list);
@@ -1174,7 +1187,7 @@ static int module_summary(int argc, const char **argv, const char *prefix)
if (!summary_limit)
return 0;
- if (!get_oid(argc ? argv[0] : "HEAD", &head_oid)) {
+ if (!repo_get_oid(the_repository, argc ? argv[0] : "HEAD", &head_oid)) {
if (argc) {
argv++;
argc--;
@@ -1187,7 +1200,7 @@ static int module_summary(int argc, const char **argv, const char *prefix)
argc--;
}
} else {
- if (get_oid("HEAD", &head_oid))
+ if (repo_get_oid(the_repository, "HEAD", &head_oid))
die(_("could not fetch a revision for HEAD"));
}
@@ -1230,6 +1243,9 @@ static void sync_submodule(const char *path, const char *prefix,
if (!is_submodule_active(the_repository, path))
return;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
sub = submodule_from_path(the_repository, null_oid(), path);
if (sub && sub->url) {
@@ -1275,7 +1291,7 @@ static void sync_submodule(const char *path, const char *prefix,
submodule_to_gitdir(&sb, path);
strbuf_addstr(&sb, "/config");
- if (git_config_set_in_file_gently(sb.buf, remote_key, sub_origin_url))
+ if (git_config_set_in_file_gently(sb.buf, remote_key, NULL, sub_origin_url))
die(_("failed to update remote for submodule '%s'"),
path);
@@ -1373,6 +1389,9 @@ static void deinit_submodule(const char *path, const char *prefix,
struct strbuf sb_config = STRBUF_INIT;
char *sub_git_dir = xstrfmt("%s/.git", path);
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub || !sub->name)
@@ -1654,16 +1673,42 @@ static char *clone_submodule_sm_gitdir(const char *name)
return sm_gitdir;
}
+static int dir_contains_only_dotgit(const char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *e;
+ int ret = 1;
+
+ if (!dir)
+ return 0;
+
+ e = readdir_skip_dot_and_dotdot(dir);
+ if (!e)
+ ret = 0;
+ else if (strcmp(DEFAULT_GIT_DIR_ENVIRONMENT, e->d_name) ||
+ (e = readdir_skip_dot_and_dotdot(dir))) {
+ error("unexpected item '%s' in '%s'", e->d_name, path);
+ ret = 0;
+ }
+
+ closedir(dir);
+ return ret;
+}
+
static int clone_submodule(const struct module_clone_data *clone_data,
struct string_list *reference)
{
char *p;
char *sm_gitdir = clone_submodule_sm_gitdir(clone_data->name);
char *sm_alternate = NULL, *error_strategy = NULL;
+ struct stat st;
struct child_process cp = CHILD_PROCESS_INIT;
const char *clone_data_path = clone_data->path;
char *to_free = NULL;
+ if (validate_submodule_path(clone_data_path) < 0)
+ exit(128);
+
if (!is_absolute_path(clone_data->path))
clone_data_path = to_free = xstrfmt("%s/%s", get_git_work_tree(),
clone_data->path);
@@ -1673,6 +1718,10 @@ static int clone_submodule(const struct module_clone_data *clone_data,
"git dir"), sm_gitdir);
if (!file_exists(sm_gitdir)) {
+ if (clone_data->require_init && !stat(clone_data_path, &st) &&
+ !is_empty_dir(clone_data_path))
+ die(_("directory not empty: '%s'"), clone_data_path);
+
if (safe_create_leading_directories_const(sm_gitdir) < 0)
die(_("could not create directory '%s'"), sm_gitdir);
@@ -1717,10 +1766,18 @@ static int clone_submodule(const struct module_clone_data *clone_data,
if(run_command(&cp))
die(_("clone of '%s' into submodule path '%s' failed"),
clone_data->url, clone_data_path);
+
+ if (clone_data->require_init && !stat(clone_data_path, &st) &&
+ !dir_contains_only_dotgit(clone_data_path)) {
+ char *dot_git = xstrfmt("%s/.git", clone_data_path);
+ unlink(dot_git);
+ free(dot_git);
+ die(_("directory not empty: '%s'"), clone_data_path);
+ }
} else {
char *path;
- if (clone_data->require_init && !access(clone_data_path, X_OK) &&
+ if (clone_data->require_init && !stat(clone_data_path, &st) &&
!is_empty_dir(clone_data_path))
die(_("directory not empty: '%s'"), clone_data_path);
if (safe_create_leading_directories_const(clone_data_path) < 0)
@@ -1730,6 +1787,23 @@ static int clone_submodule(const struct module_clone_data *clone_data,
free(path);
}
+ /*
+ * We already performed this check at the beginning of this function,
+ * before cloning the objects. This tries to detect racy behavior e.g.
+ * in parallel clones, where another process could easily have made the
+ * gitdir nested _after_ it was created.
+ *
+ * To prevent further harm coming from this unintentionally-nested
+ * gitdir, let's disable it by deleting the `HEAD` file.
+ */
+ if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) {
+ char *head = xstrfmt("%s/HEAD", sm_gitdir);
+ unlink(head);
+ free(head);
+ die(_("refusing to create/use '%s' in another submodule's "
+ "git dir"), sm_gitdir);
+ }
+
connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
p = git_pathdup_submodule(clone_data_path, "config");
@@ -2016,14 +2090,17 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
strbuf_reset(&sb);
strbuf_addf(&sb, "submodule.%s.url", sub->name);
if (repo_config_get_string_tmp(the_repository, sb.buf, &url)) {
- if (starts_with_dot_slash(sub->url) ||
- starts_with_dot_dot_slash(sub->url)) {
+ if (sub->url && (starts_with_dot_slash(sub->url) ||
+ starts_with_dot_dot_slash(sub->url))) {
url = resolve_relative_url(sub->url, NULL, 0);
need_free_url = 1;
} else
url = sub->url;
}
+ if (!url)
+ die(_("cannot clone submodule '%s' without a URL"), sub->name);
+
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/.git", ce->name);
needs_cloning = !file_exists(sb.buf);
@@ -2132,9 +2209,9 @@ static int update_clone_get_next_task(struct child_process *child,
return 0;
}
-static int update_clone_start_failure(struct strbuf *err,
+static int update_clone_start_failure(struct strbuf *err UNUSED,
void *suc_cb,
- void *idx_task_cb)
+ void *idx_task_cb UNUSED)
{
struct submodule_update_clone *suc = suc_cb;
@@ -2181,12 +2258,13 @@ static int update_clone_task_finished(int result,
}
static int git_update_clone_config(const char *var, const char *value,
+ const struct config_context *ctx,
void *cb)
{
int *max_jobs = cb;
if (!strcmp(var, "submodule.fetchjobs"))
- *max_jobs = parse_submodule_fetchjobs(var, value);
+ *max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);
return 0;
}
@@ -2378,7 +2456,9 @@ static int remote_submodule_branch(const char *path, const char **branch)
}
if (!strcmp(*branch, ".")) {
- const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+ const char *refname = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL,
+ NULL);
if (!refname)
return die_message(_("No such ref: %s"), "HEAD");
@@ -2505,6 +2585,9 @@ static int update_submodule(struct update_data *update_data)
{
int ret;
+ if (validate_submodule_path(update_data->sm_path) < 0)
+ return -1;
+
ret = determine_submodule_update_strategy(the_repository,
update_data->just_cloned,
update_data->sm_path,
@@ -2515,7 +2598,8 @@ static int update_submodule(struct update_data *update_data)
if (update_data->just_cloned)
oidcpy(&update_data->suboid, null_oid());
- else if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid))
+ else if (repo_resolve_gitlink_ref(the_repository, update_data->sm_path,
+ "HEAD", &update_data->suboid))
return die_message(_("Unable to find current revision in submodule path '%s'"),
update_data->displaypath);
@@ -2542,7 +2626,8 @@ static int update_submodule(struct update_data *update_data)
update_data->sm_path);
}
- if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid))
+ if (repo_resolve_gitlink_ref(the_repository, update_data->sm_path,
+ remote_ref, &update_data->oid))
return die_message(_("Unable to find %s revision in submodule path '%s'"),
remote_ref, update_data->sm_path);
@@ -2612,12 +2697,21 @@ static int update_submodules(struct update_data *update_data)
for (i = 0; i < suc.update_clone_nr; i++) {
struct update_clone_data ucd = suc.update_clone[i];
- int code;
+ int code = 128;
oidcpy(&update_data->oid, &ucd.oid);
update_data->just_cloned = ucd.just_cloned;
update_data->sm_path = ucd.sub->path;
+ /*
+ * Verify that the submodule path does not contain any
+ * symlinks; if it does, it might have been tampered with.
+ * TODO: allow exempting it via
+ * `safe.submodule.path` or something
+ */
+ if (validate_submodule_path(update_data->sm_path) < 0)
+ goto fail;
+
code = ensure_core_worktree(update_data->sm_path);
if (code)
goto fail;
@@ -2743,7 +2837,7 @@ static int module_update(int argc, const char **argv, const char *prefix)
* If there are no path args and submodule.active is set then,
* by default, only initialize 'active' modules.
*/
- if (!argc && git_config_get_value_multi("submodule.active"))
+ if (!argc && !git_config_get("submodule.active"))
module_list_active(&list);
info.prefix = opt.prefix;
@@ -2764,7 +2858,7 @@ cleanup:
return ret;
}
-static int push_check(int argc, const char **argv, const char *prefix)
+static int push_check(int argc, const char **argv, const char *prefix UNUSED)
{
struct remote *remote;
const char *superproject_head;
@@ -2784,7 +2878,8 @@ static int push_check(int argc, const char **argv, const char *prefix)
argv++;
argc--;
/* Get the submodule's head ref and determine if it is detached */
- head = resolve_refdup("HEAD", 0, &head_oid, NULL);
+ head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD",
+ 0, &head_oid, NULL);
if (!head)
die(_("Failed to resolve HEAD as a valid ref."));
if (!strcmp(head, "HEAD"))
@@ -2876,7 +2971,7 @@ cleanup:
static int module_set_url(int argc, const char **argv, const char *prefix)
{
- int quiet = 0;
+ int quiet = 0, ret;
const char *newurl;
const char *path;
char *config_name;
@@ -2888,20 +2983,29 @@ static int module_set_url(int argc, const char **argv, const char *prefix)
N_("git submodule set-url [--quiet] <path> <newurl>"),
NULL
};
+ const struct submodule *sub;
argc = parse_options(argc, argv, prefix, options, usage, 0);
if (argc != 2 || !(path = argv[0]) || !(newurl = argv[1]))
usage_with_options(usage, options);
- config_name = xstrfmt("submodule.%s.url", path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
+
+ if (!sub)
+ die(_("no submodule mapping found in .gitmodules for path '%s'"),
+ path);
+
+ config_name = xstrfmt("submodule.%s.url", sub->name);
+ ret = config_set_in_gitmodules_file_gently(config_name, newurl);
- config_set_in_gitmodules_file_gently(config_name, newurl);
- sync_submodule(path, prefix, NULL, quiet ? OPT_QUIET : 0);
+ if (!ret) {
+ repo_read_gitmodules(the_repository, 0);
+ sync_submodule(sub->path, prefix, NULL, quiet ? OPT_QUIET : 0);
+ }
free(config_name);
-
- return 0;
+ return !!ret;
}
static int module_set_branch(int argc, const char **argv, const char *prefix)
@@ -2928,6 +3032,7 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
N_("git submodule set-branch [-q|--quiet] (-b|--branch) <branch> <path>"),
NULL
};
+ const struct submodule *sub;
argc = parse_options(argc, argv, prefix, options, usage, 0);
@@ -2940,7 +3045,13 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
if (argc != 1 || !(path = argv[0]))
usage_with_options(usage, options);
- config_name = xstrfmt("submodule.%s.branch", path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
+
+ if (!sub)
+ die(_("no submodule mapping found in .gitmodules for path '%s'"),
+ path);
+
+ config_name = xstrfmt("submodule.%s.branch", sub->name);
ret = config_set_in_gitmodules_file_gently(config_name, opt_branch);
free(config_name);
@@ -3140,7 +3251,6 @@ static int config_submodule_in_gitmodules(const char *name, const char *var, con
static void configure_added_submodule(struct add_data *add_data)
{
char *key;
- const char *val;
struct child_process add_submod = CHILD_PROCESS_INIT;
struct child_process add_gitmodules = CHILD_PROCESS_INIT;
@@ -3185,7 +3295,7 @@ static void configure_added_submodule(struct add_data *add_data)
* is_submodule_active(), since that function needs to find
* out the value of "submodule.active" again anyway.
*/
- if (!git_config_get_string_tmp("submodule.active", &val)) {
+ if (!git_config_get("submodule.active")) {
/*
* If the submodule being added isn't already covered by the
* current configured pathspec, set the submodule's active flag
@@ -3216,21 +3326,21 @@ static void die_on_index_match(const char *path, int force)
char *ps_matched = xcalloc(ps.nr, 1);
/* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
+ ensure_full_index(the_repository->index);
/*
* Since there is only one pathspec, we just need to
* check ps_matched[0] to know if a cache entry matched.
*/
- for (i = 0; i < the_index.cache_nr; i++) {
- ce_path_match(&the_index, the_index.cache[i], &ps,
+ for (i = 0; i < the_repository->index->cache_nr; i++) {
+ ce_path_match(the_repository->index, the_repository->index->cache[i], &ps,
ps_matched);
if (ps_matched[0]) {
if (!force)
die(_("'%s' already exists in the index"),
path);
- if (!S_ISGITLINK(the_index.cache[i]->ce_mode))
+ if (!S_ISGITLINK(the_repository->index->cache[i]->ce_mode))
die(_("'%s' already exists in the index "
"and is not a submodule"), path);
break;
@@ -3247,7 +3357,7 @@ static void die_on_repo_without_commits(const char *path)
strbuf_addstr(&sb, path);
if (is_nonbare_repository_dir(&sb)) {
struct object_id oid;
- if (resolve_gitlink_ref(path, "HEAD", &oid) < 0)
+ if (repo_resolve_gitlink_ref(the_repository, path, "HEAD", &oid) < 0)
die(_("'%s' does not have a commit checked out"), path);
}
strbuf_release(&sb);
@@ -3329,6 +3439,9 @@ static int module_add(int argc, const char **argv, const char *prefix)
normalize_path_copy(add_data.sm_path, add_data.sm_path);
strip_dir_trailing_slashes(add_data.sm_path);
+ if (validate_submodule_path(add_data.sm_path) < 0)
+ exit(128);
+
die_on_index_match(add_data.sm_path, force);
die_on_repo_without_commits(add_data.sm_path);