summaryrefslogtreecommitdiff
path: root/submodule.c
diff options
context:
space:
mode:
Diffstat (limited to 'submodule.c')
-rw-r--r--submodule.c919
1 files changed, 582 insertions, 337 deletions
diff --git a/submodule.c b/submodule.c
index a52b93a..ce2d032 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1,5 +1,5 @@
-
-#include "cache.h"
+#include "git-compat-util.h"
+#include "abspath.h"
#include "repository.h"
#include "config.h"
#include "submodule-config.h"
@@ -7,6 +7,9 @@
#include "dir.h"
#include "diff.h"
#include "commit.h"
+#include "environment.h"
+#include "gettext.h"
+#include "hex.h"
#include "revision.h"
#include "run-command.h"
#include "diffcore.h"
@@ -14,14 +17,18 @@
#include "string-list.h"
#include "oid-array.h"
#include "strvec.h"
-#include "blob.h"
#include "thread-utils.h"
-#include "quote.h"
+#include "path.h"
#include "remote.h"
#include "worktree.h"
#include "parse-options.h"
-#include "object-store.h"
+#include "object-file.h"
+#include "object-name.h"
+#include "object-store-ll.h"
#include "commit-reach.h"
+#include "read-cache-ll.h"
+#include "setup.h"
+#include "trace2.h"
static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
static int initialized_fetch_ref_tips;
@@ -33,7 +40,7 @@ static struct oid_array ref_tips_after_fetch;
* will be disabled because we can't guess what might be configured in
* .gitmodules unless the user resolves the conflict.
*/
-int is_gitmodules_unmerged(const struct index_state *istate)
+int is_gitmodules_unmerged(struct index_state *istate)
{
int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE));
if (pos < 0) { /* .gitmodules not found or isn't merged */
@@ -64,7 +71,7 @@ int is_writing_gitmodules_ok(void)
{
struct object_id oid;
return file_exists(GITMODULES_FILE) ||
- (get_oid(GITMODULES_INDEX, &oid) < 0 && get_oid(GITMODULES_HEAD, &oid) < 0);
+ (repo_get_oid(the_repository, GITMODULES_INDEX, &oid) < 0 && repo_get_oid(the_repository, GITMODULES_HEAD, &oid) < 0);
}
/*
@@ -113,7 +120,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath)
if (is_gitmodules_unmerged(the_repository->index))
die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
- submodule = submodule_from_path(the_repository, &null_oid, oldpath);
+ submodule = submodule_from_path(the_repository, null_oid(), oldpath);
if (!submodule || !submodule->name) {
warning(_("Could not find section in .gitmodules where path=%s"), oldpath);
return -1;
@@ -142,7 +149,7 @@ int remove_path_from_gitmodules(const char *path)
if (is_gitmodules_unmerged(the_repository->index))
die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
- submodule = submodule_from_path(the_repository, &null_oid, path);
+ submodule = submodule_from_path(the_repository, null_oid(), path);
if (!submodule || !submodule->name) {
warning(_("Could not find section in .gitmodules where path=%s"), path);
return -1;
@@ -165,22 +172,27 @@ void stage_updated_gitmodules(struct index_state *istate)
die(_("staging updated .gitmodules failed"));
}
-/* TODO: remove this function, use repo_submodule_init instead. */
-int add_submodule_odb(const char *path)
+static struct string_list added_submodule_odb_paths = STRING_LIST_INIT_NODUP;
+
+void add_submodule_odb_by_path(const char *path)
{
- struct strbuf objects_directory = STRBUF_INIT;
- int ret = 0;
+ string_list_insert(&added_submodule_odb_paths, xstrdup(path));
+}
- ret = strbuf_git_path_submodule(&objects_directory, path, "objects/");
- if (ret)
- goto done;
- if (!is_directory(objects_directory.buf)) {
- ret = -1;
- goto done;
+int register_all_submodule_odb_as_alternates(void)
+{
+ int i;
+ int ret = added_submodule_odb_paths.nr;
+
+ for (i = 0; i < added_submodule_odb_paths.nr; i++)
+ add_to_alternates_memory(added_submodule_odb_paths.items[i].string);
+ if (ret) {
+ string_list_clear(&added_submodule_odb_paths, 0);
+ trace2_data_intmax("submodule", the_repository,
+ "register_all_submodule_odb_as_alternates/registered", ret);
+ if (git_env_bool("GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB", 0))
+ BUG("register_all_submodule_odb_as_alternates() called");
}
- add_to_alternates_memory(objects_directory.buf);
-done:
- strbuf_release(&objects_directory);
return ret;
}
@@ -188,13 +200,14 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path)
{
const struct submodule *submodule = submodule_from_path(the_repository,
- &null_oid, path);
+ null_oid(),
+ path);
if (submodule) {
const char *ignore;
char *key;
key = xstrfmt("submodule.%s.ignore", submodule->name);
- if (repo_config_get_string_const(the_repository, key, &ignore))
+ if (repo_config_get_string_tmp(the_repository, key, &ignore))
ignore = submodule->ignore;
free(key);
@@ -206,7 +219,8 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
}
/* Cheap function that only determines if we're interested in submodules at all */
-int git_default_submodule_config(const char *var, const char *value, void *cb)
+int git_default_submodule_config(const char *var, const char *value,
+ void *cb UNUSED)
{
if (!strcmp(var, "submodule.recurse")) {
int v = git_config_bool(var, value) ?
@@ -236,7 +250,14 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
/*
* Determine if a submodule has been initialized at a given 'path'
*/
-int is_submodule_active(struct repository *repo, const char *path)
+/*
+ * NEEDSWORK: Emit a warning if submodule.active exists, but is valueless,
+ * ie, the config looks like: "[submodule] active\n".
+ * Since that is an invalid pathspec, we should inform the user.
+ */
+int is_tree_submodule_active(struct repository *repo,
+ const struct object_id *treeish_name,
+ const char *path)
{
int ret = 0;
char *key = NULL;
@@ -244,7 +265,7 @@ int is_submodule_active(struct repository *repo, const char *path)
const struct string_list *sl;
const struct submodule *module;
- module = submodule_from_path(repo, &null_oid, path);
+ module = submodule_from_path(repo, treeish_name, path);
/* early return if there isn't a path->module mapping */
if (!module)
@@ -259,8 +280,7 @@ int is_submodule_active(struct repository *repo, const char *path)
free(key);
/* submodule.active is set */
- sl = repo_config_get_value_multi(repo, "submodule.active");
- if (sl) {
+ if (!repo_config_get_string_multi(repo, "submodule.active", &sl)) {
struct pathspec ps;
struct strvec args = STRVEC_INIT;
const struct string_list_item *item;
@@ -286,6 +306,11 @@ int is_submodule_active(struct repository *repo, const char *path)
return ret;
}
+int is_submodule_active(struct repository *repo, const char *path)
+{
+ return is_tree_submodule_active(repo, null_oid(), path);
+}
+
int is_submodule_populated_gently(const char *path, int *return_error_code)
{
int ret = 0;
@@ -301,7 +326,7 @@ int is_submodule_populated_gently(const char *path, int *return_error_code)
/*
* Dies if the provided 'prefix' corresponds to an unpopulated submodule
*/
-void die_in_unpopulated_submodule(const struct index_state *istate,
+void die_in_unpopulated_submodule(struct index_state *istate,
const char *prefix)
{
int i, prefixlen;
@@ -331,7 +356,7 @@ void die_in_unpopulated_submodule(const struct index_state *istate,
/*
* Dies if any paths in the provided pathspec descends into a submodule
*/
-void die_path_inside_submodule(const struct index_state *istate,
+void die_path_inside_submodule(struct index_state *istate,
const struct pathspec *ps)
{
int i, j;
@@ -396,10 +421,9 @@ int parse_submodule_update_strategy(const char *value,
return 0;
}
-const char *submodule_strategy_to_string(const struct submodule_update_strategy *s)
+const char *submodule_update_type_to_string(enum submodule_update_type type)
{
- struct strbuf sb = STRBUF_INIT;
- switch (s->type) {
+ switch (type) {
case SM_UPDATE_CHECKOUT:
return "checkout";
case SM_UPDATE_MERGE:
@@ -409,17 +433,17 @@ const char *submodule_strategy_to_string(const struct submodule_update_strategy
case SM_UPDATE_NONE:
return "none";
case SM_UPDATE_UNSPECIFIED:
- return NULL;
case SM_UPDATE_COMMAND:
- strbuf_addf(&sb, "!%s", s->command);
- return strbuf_detach(&sb, NULL);
+ BUG("init_submodule() should handle type %d", type);
+ default:
+ BUG("unexpected update strategy type: %d", type);
}
- return NULL;
}
void handle_ignore_submodules_arg(struct diff_options *diffopt,
const char *arg)
{
+ diffopt->flags.ignore_submodule_set = 1;
diffopt->flags.ignore_submodules = 0;
diffopt->flags.ignore_untracked_in_submodules = 0;
diffopt->flags.ignore_dirty_submodules = 0;
@@ -438,13 +462,14 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt,
*/
}
-static int prepare_submodule_summary(struct rev_info *rev, const char *path,
- struct commit *left, struct commit *right,
- struct commit_list *merge_bases)
+static int prepare_submodule_diff_summary(struct repository *r, struct rev_info *rev,
+ const char *path,
+ struct commit *left, struct commit *right,
+ struct commit_list *merge_bases)
{
struct commit_list *list;
- repo_init_revisions(the_repository, rev, NULL);
+ repo_init_revisions(r, rev, NULL);
setup_revisions(0, NULL, rev, NULL);
rev->left_right = 1;
rev->first_parent_only = 1;
@@ -459,7 +484,7 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path,
return prepare_revision_walk(rev);
}
-static void print_submodule_summary(struct repository *r, struct rev_info *rev, struct diff_options *o)
+static void print_submodule_diff_summary(struct repository *r, struct rev_info *rev, struct diff_options *o)
{
static const char format[] = " %m %s";
struct strbuf sb = STRBUF_INIT;
@@ -481,35 +506,19 @@ static void print_submodule_summary(struct repository *r, struct rev_info *rev,
strbuf_release(&sb);
}
-static void prepare_submodule_repo_env_no_git_dir(struct strvec *out)
-{
- const char * const *var;
-
- for (var = local_repo_env; *var; var++) {
- if (strcmp(*var, CONFIG_DATA_ENVIRONMENT))
- strvec_push(out, *var);
- }
-}
-
void prepare_submodule_repo_env(struct strvec *out)
{
- prepare_submodule_repo_env_no_git_dir(out);
- strvec_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT,
- DEFAULT_GIT_DIR_ENVIRONMENT);
+ prepare_other_repo_env(out, DEFAULT_GIT_DIR_ENVIRONMENT);
}
static void prepare_submodule_repo_env_in_gitdir(struct strvec *out)
{
- prepare_submodule_repo_env_no_git_dir(out);
- strvec_pushf(out, "%s=.", GIT_DIR_ENVIRONMENT);
+ prepare_other_repo_env(out, ".");
}
/*
* Initialize a repository struct for a submodule based on the provided 'path'.
*
- * Unlike repo_submodule_init, this tolerates submodules not present
- * in .gitmodules. This function exists only to preserve historical behavior,
- *
* Returns the repository struct on success,
* NULL when the submodule is not present.
*/
@@ -583,7 +592,12 @@ static void show_submodule_header(struct diff_options *o,
(!is_null_oid(two) && !*right))
message = "(commits not present)";
- *merge_bases = repo_get_merge_bases(sub, *left, *right);
+ *merge_bases = NULL;
+ if (repo_get_merge_bases(sub, *left, *right, merge_bases) < 0) {
+ message = "(corrupt repository)";
+ goto output_header;
+ }
+
if (*merge_bases) {
if ((*merge_bases)->item == *left)
fast_forward = 1;
@@ -610,11 +624,11 @@ output_header:
strbuf_release(&sb);
}
-void show_submodule_summary(struct diff_options *o, const char *path,
+void show_submodule_diff_summary(struct diff_options *o, const char *path,
struct object_id *one, struct object_id *two,
unsigned dirty_submodule)
{
- struct rev_info rev;
+ struct rev_info rev = REV_INFO_INIT;
struct commit *left = NULL, *right = NULL;
struct commit_list *merge_bases = NULL;
struct repository *sub;
@@ -632,16 +646,16 @@ void show_submodule_summary(struct diff_options *o, const char *path,
goto out;
/* Treat revision walker failure the same as missing commits */
- if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) {
+ if (prepare_submodule_diff_summary(sub, &rev, path, left, right, merge_bases)) {
diff_emit_submodule_error(o, "(revision walker failed)\n");
goto out;
}
- print_submodule_summary(sub, &rev, o);
+ print_submodule_diff_summary(sub, &rev, o);
out:
- if (merge_bases)
- free_commit_list(merge_bases);
+ free_commit_list(merge_bases);
+ release_revisions(&rev);
clear_commit_marks(left, ~0);
clear_commit_marks(right, ~0);
if (sub) {
@@ -706,9 +720,21 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
if (!(dirty_submodule & DIRTY_SUBMODULE_MODIFIED))
strvec_push(&cp.args, oid_to_hex(new_oid));
- prepare_submodule_repo_env(&cp.env_array);
- if (start_command(&cp))
+ prepare_submodule_repo_env(&cp.env);
+
+ if (!is_directory(path)) {
+ /* fall back to absorbed git dir, if any */
+ if (!sub)
+ goto done;
+ cp.dir = sub->gitdir;
+ strvec_push(&cp.env, GIT_DIR_ENVIRONMENT "=.");
+ strvec_push(&cp.env, GIT_WORK_TREE_ENVIRONMENT "=.");
+ }
+
+ if (start_command(&cp)) {
diff_emit_submodule_error(o, "(diff failed)\n");
+ goto done;
+ }
while (strbuf_getwholeline_fd(&sb, cp.out, '\n') != EOF)
diff_emit_submodule_pipethrough(o, sb.buf, sb.len);
@@ -718,8 +744,7 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path,
done:
strbuf_release(&sb);
- if (merge_bases)
- free_commit_list(merge_bases);
+ free_commit_list(merge_bases);
if (left)
clear_commit_marks(left, ~0);
if (right)
@@ -743,22 +768,9 @@ const struct submodule *submodule_from_ce(const struct cache_entry *ce)
if (!should_update_submodules())
return NULL;
- return submodule_from_path(the_repository, &null_oid, ce->name);
+ return submodule_from_path(the_repository, null_oid(), ce->name);
}
-static struct oid_array *submodule_commits(struct string_list *submodules,
- const char *name)
-{
- struct string_list_item *item;
-
- item = string_list_insert(submodules, name);
- if (item->util)
- return (struct oid_array *) item->util;
-
- /* NEEDSWORK: should we have oid_array_init()? */
- item->util = xcalloc(1, sizeof(struct oid_array));
- return (struct oid_array *) item->util;
-}
struct collect_changed_submodules_cb_data {
struct repository *repo;
@@ -783,8 +795,54 @@ static const char *default_name_or_path(const char *path_or_name)
return path_or_name;
}
+/*
+ * Holds relevant information for a changed submodule. Used as the .util
+ * member of the changed submodule name string_list_item.
+ *
+ * (super_oid, path) allows the submodule config to be read from _some_
+ * .gitmodules file. We store this information the first time we find a
+ * superproject commit that points to the submodule, but this is
+ * arbitrary - we can choose any (super_oid, path) that matches the
+ * submodule's name.
+ *
+ * NEEDSWORK: Storing an arbitrary commit is undesirable because we can't
+ * guarantee that we're reading the commit that the user would expect. A better
+ * scheme would be to just fetch a submodule by its name. This requires two
+ * steps:
+ * - Create a function that behaves like repo_submodule_init(), but accepts a
+ * submodule name instead of treeish_name and path. This should be easy
+ * because repo_submodule_init() internally uses the submodule's name.
+ *
+ * - Replace most instances of 'struct submodule' (which is the .gitmodules
+ * config) with just the submodule name. This is OK because we expect
+ * submodule settings to be stored in .git/config (via "git submodule init"),
+ * not .gitmodules. This also lets us delete get_non_gitmodules_submodule(),
+ * which constructs a bogus 'struct submodule' for the sake of giving a
+ * placeholder name to a gitlink.
+ */
+struct changed_submodule_data {
+ /*
+ * The first superproject commit in the rev walk that points to
+ * the submodule.
+ */
+ const struct object_id *super_oid;
+ /*
+ * Path to the submodule in the superproject commit referenced
+ * by 'super_oid'.
+ */
+ char *path;
+ /* The submodule commits that have changed in the rev walk. */
+ struct oid_array new_commits;
+};
+
+static void changed_submodule_data_clear(struct changed_submodule_data *cs_data)
+{
+ oid_array_clear(&cs_data->new_commits);
+ free(cs_data->path);
+}
+
static void collect_changed_submodules_cb(struct diff_queue_struct *q,
- struct diff_options *options,
+ struct diff_options *options UNUSED,
void *data)
{
struct collect_changed_submodules_cb_data *me = data;
@@ -794,9 +852,10 @@ static void collect_changed_submodules_cb(struct diff_queue_struct *q,
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
- struct oid_array *commits;
const struct submodule *submodule;
const char *name;
+ struct string_list_item *item;
+ struct changed_submodule_data *cs_data;
if (!S_ISGITLINK(p->two->mode))
continue;
@@ -823,8 +882,16 @@ static void collect_changed_submodules_cb(struct diff_queue_struct *q,
if (!name)
continue;
- commits = submodule_commits(changed, name);
- oid_array_append(commits, &p->two->oid);
+ item = string_list_insert(changed, name);
+ if (item->util)
+ cs_data = item->util;
+ else {
+ item->util = xcalloc(1, sizeof(struct changed_submodule_data));
+ cs_data = item->util;
+ cs_data->super_oid = commit_oid;
+ cs_data->path = xstrdup(p->two->path);
+ }
+ oid_array_append(&cs_data->new_commits, &p->two->oid);
}
}
@@ -840,9 +907,16 @@ static void collect_changed_submodules(struct repository *r,
{
struct rev_info rev;
const struct commit *commit;
+ int save_warning;
+ struct setup_revision_opt s_r_opt = {
+ .assume_dashdash = 1,
+ };
+ save_warning = warn_on_object_refname_ambiguity;
+ warn_on_object_refname_ambiguity = 0;
repo_init_revisions(r, &rev, NULL);
- setup_revisions(argv->nr, argv->v, &rev, NULL);
+ setup_revisions(argv->nr, argv->v, &rev, &s_r_opt);
+ warn_on_object_refname_ambiguity = save_warning;
if (prepare_revision_walk(&rev))
die(_("revision walk setup failed"));
@@ -857,22 +931,27 @@ static void collect_changed_submodules(struct repository *r,
diff_rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
diff_rev.diffopt.format_callback = collect_changed_submodules_cb;
diff_rev.diffopt.format_callback_data = &data;
- diff_tree_combined_merge(commit, 1, &diff_rev);
+ diff_rev.dense_combined_merges = 1;
+ diff_tree_combined_merge(commit, &diff_rev);
+ release_revisions(&diff_rev);
}
reset_revision_walk();
+ release_revisions(&rev);
}
-static void free_submodules_oids(struct string_list *submodules)
+static void free_submodules_data(struct string_list *submodules)
{
struct string_list_item *item;
for_each_string_list_item(item, submodules)
- oid_array_clear((struct oid_array *) item->util);
+ changed_submodule_data_clear(item->util);
+
string_list_clear(submodules, 1);
}
-static int has_remote(const char *refname, const struct object_id *oid,
- int flags, void *cb_data)
+static int has_remote(const char *refname UNUSED,
+ const struct object_id *oid UNUSED,
+ int flags UNUSED, void *cb_data UNUSED)
{
return 1;
}
@@ -888,47 +967,53 @@ struct has_commit_data {
struct repository *repo;
int result;
const char *path;
+ const struct object_id *super_oid;
};
static int check_has_commit(const struct object_id *oid, void *data)
{
struct has_commit_data *cb = data;
+ struct repository subrepo;
+ enum object_type type;
+
+ if (repo_submodule_init(&subrepo, cb->repo, cb->path, cb->super_oid)) {
+ cb->result = 0;
+ /* subrepo failed to init, so don't clean it up. */
+ return 0;
+ }
- enum object_type type = oid_object_info(cb->repo, oid, NULL);
+ type = oid_object_info(&subrepo, oid, NULL);
switch (type) {
case OBJ_COMMIT:
- return 0;
+ goto cleanup;
case OBJ_BAD:
/*
* Object is missing or invalid. If invalid, an error message
* has already been printed.
*/
cb->result = 0;
- return 0;
+ goto cleanup;
default:
die(_("submodule entry '%s' (%s) is a %s, not a commit"),
cb->path, oid_to_hex(oid), type_name(type));
}
+cleanup:
+ repo_clear(&subrepo);
+ return 0;
}
static int submodule_has_commits(struct repository *r,
const char *path,
+ const struct object_id *super_oid,
struct oid_array *commits)
{
- struct has_commit_data has_commit = { r, 1, path };
-
- /*
- * Perform a cheap, but incorrect check for the existence of 'commits'.
- * This is done by adding the submodule's object store to the in-core
- * object store, and then querying for each commit's existence. If we
- * do not have the commit object anywhere, there is no chance we have
- * it in the object store of the correct submodule and have it
- * reachable from a ref, so we can fail early without spawning rev-list
- * which is expensive.
- */
- if (add_submodule_odb(path))
- return 0;
+ struct has_commit_data has_commit = {
+ .repo = r,
+ .result = 1,
+ .path = path,
+ .super_oid = super_oid
+ };
oid_array_for_each_unique(commits, check_has_commit, &has_commit);
@@ -945,7 +1030,7 @@ static int submodule_has_commits(struct repository *r,
oid_array_for_each_unique(commits, append_oid_to_argv, &cp.args);
strvec_pushl(&cp.args, "--not", "--all", NULL);
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.dir = path;
@@ -963,7 +1048,7 @@ static int submodule_needs_pushing(struct repository *r,
const char *path,
struct oid_array *commits)
{
- if (!submodule_has_commits(r, path, commits))
+ if (!submodule_has_commits(r, path, null_oid(), commits))
/*
* NOTE: We do consider it safe to return "no" here. The
* correct answer would be "We do not know" instead of
@@ -986,7 +1071,7 @@ static int submodule_needs_pushing(struct repository *r,
oid_array_for_each_unique(commits, append_oid_to_argv, &cp.args);
strvec_pushl(&cp.args, "--not", "--remotes", "-n", "1" , NULL);
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.out = -1;
@@ -1023,11 +1108,11 @@ int find_unpushed_submodules(struct repository *r,
collect_changed_submodules(r, &submodules, &argv);
for_each_string_list_item(name, &submodules) {
- struct oid_array *commits = name->util;
+ struct changed_submodule_data *cs_data = name->util;
const struct submodule *submodule;
const char *path = NULL;
- submodule = submodule_from_name(r, &null_oid, name->string);
+ submodule = submodule_from_name(r, null_oid(), name->string);
if (submodule)
path = submodule->path;
else
@@ -1036,11 +1121,11 @@ int find_unpushed_submodules(struct repository *r,
if (!path)
continue;
- if (submodule_needs_pushing(r, path, commits))
+ if (submodule_needs_pushing(r, path, &cs_data->new_commits))
string_list_insert(needs_pushing, path);
}
- free_submodules_oids(&submodules);
+ free_submodules_data(&submodules);
strvec_clear(&argv);
return needs_pushing->nr;
@@ -1055,6 +1140,12 @@ static int push_submodule(const char *path,
if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
struct child_process cp = CHILD_PROCESS_INIT;
strvec_push(&cp.args, "push");
+ /*
+ * When recursing into a submodule, treat any "only" configurations as "on-
+ * demand", since "only" would not work (we need all submodules to be pushed
+ * in order to be able to push the superproject).
+ */
+ strvec_push(&cp.args, "--recurse-submodules=only-is-on-demand");
if (dry_run)
strvec_push(&cp.args, "--dry-run");
@@ -1072,7 +1163,7 @@ static int push_submodule(const char *path,
strvec_push(&cp.args, rs->raw[i]);
}
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.dir = path;
@@ -1103,7 +1194,7 @@ static void submodule_push_check(const char *path, const char *head,
for (i = 0; i < rs->raw_nr; i++)
strvec_push(&cp.args, rs->raw[i]);
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.no_stdout = 1;
@@ -1168,8 +1259,9 @@ int push_unpushed_submodules(struct repository *r,
return ret;
}
-static int append_oid_to_array(const char *ref, const struct object_id *oid,
- int flags, void *data)
+static int append_oid_to_array(const char *ref UNUSED,
+ const struct object_id *oid,
+ int flags UNUSED, void *data)
{
struct oid_array *array = data;
oid_array_append(array, oid);
@@ -1186,14 +1278,36 @@ void check_for_new_submodule_commits(struct object_id *oid)
oid_array_append(&ref_tips_after_fetch, oid);
}
+/*
+ * Returns 1 if there is at least one submodule gitdir in
+ * $GIT_DIR/modules and 0 otherwise. This follows
+ * submodule_name_to_gitdir(), which looks for submodules in
+ * $GIT_DIR/modules, not $GIT_COMMON_DIR.
+ *
+ * A submodule can be moved to $GIT_DIR/modules manually by running "git
+ * submodule absorbgitdirs", or it may be initialized there by "git
+ * submodule update".
+ */
+static int repo_has_absorbed_submodules(struct repository *r)
+{
+ int ret;
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_repo_git_path(&buf, r, "modules/");
+ ret = file_exists(buf.buf) && !is_empty_dir(buf.buf);
+ strbuf_release(&buf);
+ return ret;
+}
+
static void calculate_changed_submodule_paths(struct repository *r,
struct string_list *changed_submodule_names)
{
struct strvec argv = STRVEC_INIT;
struct string_list_item *name;
- /* No need to check if there are no submodules configured */
- if (!submodule_from_path(r, NULL, NULL))
+ /* No need to check if no submodules would be fetched */
+ if (!submodule_from_path(r, NULL, NULL) &&
+ !repo_has_absorbed_submodules(r))
return;
strvec_push(&argv, "--"); /* argv[0] program name */
@@ -1210,11 +1324,11 @@ static void calculate_changed_submodule_paths(struct repository *r,
collect_changed_submodules(r, changed_submodule_names, &argv);
for_each_string_list_item(name, changed_submodule_names) {
- struct oid_array *commits = name->util;
+ struct changed_submodule_data *cs_data = name->util;
const struct submodule *submodule;
const char *path = NULL;
- submodule = submodule_from_name(r, &null_oid, name->string);
+ submodule = submodule_from_name(r, null_oid(), name->string);
if (submodule)
path = submodule->path;
else
@@ -1223,8 +1337,8 @@ static void calculate_changed_submodule_paths(struct repository *r,
if (!path)
continue;
- if (submodule_has_commits(r, path, commits)) {
- oid_array_clear(commits);
+ if (submodule_has_commits(r, path, null_oid(), &cs_data->new_commits)) {
+ changed_submodule_data_clear(cs_data);
*name->string = '\0';
}
}
@@ -1261,12 +1375,21 @@ int submodule_touches_in_range(struct repository *r,
strvec_clear(&args);
- free_submodules_oids(&subs);
+ free_submodules_data(&subs);
return ret;
}
struct submodule_parallel_fetch {
- int count;
+ /*
+ * The index of the last index entry processed by
+ * get_fetch_task_from_index().
+ */
+ int index_count;
+ /*
+ * The index of the last string_list entry processed by
+ * get_fetch_task_from_changed().
+ */
+ int changed_count;
struct strvec args;
struct repository *r;
const char *prefix;
@@ -1275,7 +1398,16 @@ struct submodule_parallel_fetch {
int quiet;
int result;
+ /*
+ * Names of submodules that have new commits. Generated by
+ * walking the newly fetched superproject commits.
+ */
struct string_list changed_submodule_names;
+ /*
+ * Names of submodules that have already been processed. Lets us
+ * avoid fetching the same submodule more than once.
+ */
+ struct string_list seen_submodule_names;
/* Pending fetches by OIDs */
struct fetch_task **oid_fetch_tasks;
@@ -1283,9 +1415,12 @@ struct submodule_parallel_fetch {
struct strbuf submodules_with_errors;
};
-#define SPF_INIT {0, STRVEC_INIT, NULL, NULL, 0, 0, 0, 0, \
- STRING_LIST_INIT_DUP, \
- NULL, 0, 0, STRBUF_INIT}
+#define SPF_INIT { \
+ .args = STRVEC_INIT, \
+ .changed_submodule_names = STRING_LIST_INIT_DUP, \
+ .seen_submodule_names = STRING_LIST_INIT_DUP, \
+ .submodules_with_errors = STRBUF_INIT, \
+}
static int get_fetch_recurse_config(const struct submodule *submodule,
struct submodule_parallel_fetch *spf)
@@ -1299,7 +1434,7 @@ static int get_fetch_recurse_config(const struct submodule *submodule,
int fetch_recurse = submodule->fetch_recurse;
key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name);
- if (!repo_config_get_string_const(spf->r, key, &value)) {
+ if (!repo_config_get_string_tmp(spf->r, key, &value)) {
fetch_recurse = parse_fetch_recurse_submodules_arg(key, value);
}
free(key);
@@ -1320,6 +1455,8 @@ struct fetch_task {
struct repository *repo;
const struct submodule *sub;
unsigned free_sub : 1; /* Do we need to free the submodule? */
+ const char *default_argv; /* The default fetch mode. */
+ struct strvec git_args; /* Args for the child git process. */
struct oid_array *commits; /* Ensure these commits are fetched */
};
@@ -1345,31 +1482,6 @@ static const struct submodule *get_non_gitmodules_submodule(const char *path)
return (const struct submodule *) ret;
}
-static struct fetch_task *fetch_task_create(struct repository *r,
- const char *path)
-{
- struct fetch_task *task = xmalloc(sizeof(*task));
- memset(task, 0, sizeof(*task));
-
- task->sub = submodule_from_path(r, &null_oid, path);
- if (!task->sub) {
- /*
- * No entry in .gitmodules? Technically not a submodule,
- * but historically we supported repositories that happen to be
- * in-place where a gitlink is. Keep supporting them.
- */
- task->sub = get_non_gitmodules_submodule(path);
- if (!task->sub) {
- free(task);
- return NULL;
- }
-
- task->free_sub = 1;
- }
-
- return task;
-}
-
static void fetch_task_release(struct fetch_task *p)
{
if (p->free_sub)
@@ -1380,94 +1492,103 @@ static void fetch_task_release(struct fetch_task *p)
if (p->repo)
repo_clear(p->repo);
FREE_AND_NULL(p->repo);
+
+ strvec_clear(&p->git_args);
}
static struct repository *get_submodule_repo_for(struct repository *r,
- const struct submodule *sub)
+ const char *path,
+ const struct object_id *treeish_name)
{
struct repository *ret = xmalloc(sizeof(*ret));
- if (repo_submodule_init(ret, r, sub)) {
+ if (repo_submodule_init(ret, r, path, treeish_name)) {
+ free(ret);
+ return NULL;
+ }
+
+ return ret;
+}
+
+static struct fetch_task *fetch_task_create(struct submodule_parallel_fetch *spf,
+ const char *path,
+ const struct object_id *treeish_name)
+{
+ struct fetch_task *task = xmalloc(sizeof(*task));
+ memset(task, 0, sizeof(*task));
+
+ task->sub = submodule_from_path(spf->r, treeish_name, path);
+
+ if (!task->sub) {
/*
* No entry in .gitmodules? Technically not a submodule,
* but historically we supported repositories that happen to be
* in-place where a gitlink is. Keep supporting them.
*/
- struct strbuf gitdir = STRBUF_INIT;
- strbuf_repo_worktree_path(&gitdir, r, "%s/.git", sub->path);
- if (repo_init(ret, gitdir.buf, NULL)) {
- strbuf_release(&gitdir);
- free(ret);
- return NULL;
- }
- strbuf_release(&gitdir);
+ task->sub = get_non_gitmodules_submodule(path);
+ if (!task->sub)
+ goto cleanup;
+
+ task->free_sub = 1;
}
- return ret;
+ if (string_list_lookup(&spf->seen_submodule_names, task->sub->name))
+ goto cleanup;
+
+ switch (get_fetch_recurse_config(task->sub, spf))
+ {
+ default:
+ case RECURSE_SUBMODULES_DEFAULT:
+ case RECURSE_SUBMODULES_ON_DEMAND:
+ if (!task->sub ||
+ !string_list_lookup(
+ &spf->changed_submodule_names,
+ task->sub->name))
+ goto cleanup;
+ task->default_argv = "on-demand";
+ break;
+ case RECURSE_SUBMODULES_ON:
+ task->default_argv = "yes";
+ break;
+ case RECURSE_SUBMODULES_OFF:
+ goto cleanup;
+ }
+
+ task->repo = get_submodule_repo_for(spf->r, path, treeish_name);
+
+ return task;
+
+ cleanup:
+ fetch_task_release(task);
+ free(task);
+ return NULL;
}
-static int get_next_submodule(struct child_process *cp,
- struct strbuf *err, void *data, void **task_cb)
+static struct fetch_task *
+get_fetch_task_from_index(struct submodule_parallel_fetch *spf,
+ struct strbuf *err)
{
- struct submodule_parallel_fetch *spf = data;
-
- for (; spf->count < spf->r->index->cache_nr; spf->count++) {
- const struct cache_entry *ce = spf->r->index->cache[spf->count];
- const char *default_argv;
+ for (; spf->index_count < spf->r->index->cache_nr; spf->index_count++) {
+ const struct cache_entry *ce =
+ spf->r->index->cache[spf->index_count];
struct fetch_task *task;
if (!S_ISGITLINK(ce->ce_mode))
continue;
- task = fetch_task_create(spf->r, ce->name);
+ task = fetch_task_create(spf, ce->name, null_oid());
if (!task)
continue;
- switch (get_fetch_recurse_config(task->sub, spf))
- {
- default:
- case RECURSE_SUBMODULES_DEFAULT:
- case RECURSE_SUBMODULES_ON_DEMAND:
- if (!task->sub ||
- !string_list_lookup(
- &spf->changed_submodule_names,
- task->sub->name))
- continue;
- default_argv = "on-demand";
- break;
- case RECURSE_SUBMODULES_ON:
- default_argv = "yes";
- break;
- case RECURSE_SUBMODULES_OFF:
- continue;
- }
-
- task->repo = get_submodule_repo_for(spf->r, task->sub);
if (task->repo) {
- struct strbuf submodule_prefix = STRBUF_INIT;
- child_process_init(cp);
- cp->dir = task->repo->gitdir;
- prepare_submodule_repo_env_in_gitdir(&cp->env_array);
- cp->git_cmd = 1;
if (!spf->quiet)
strbuf_addf(err, _("Fetching submodule %s%s\n"),
spf->prefix, ce->name);
- strvec_init(&cp->args);
- strvec_pushv(&cp->args, spf->args.v);
- strvec_push(&cp->args, default_argv);
- strvec_push(&cp->args, "--submodule-prefix");
-
- strbuf_addf(&submodule_prefix, "%s%s/",
- spf->prefix,
- task->sub->path);
- strvec_push(&cp->args, submodule_prefix.buf);
- spf->count++;
- *task_cb = task;
-
- strbuf_release(&submodule_prefix);
- return 1;
+ spf->index_count++;
+ return task;
} else {
+ struct strbuf empty_submodule_path = STRBUF_INIT;
fetch_task_release(task);
free(task);
@@ -1476,35 +1597,134 @@ static int get_next_submodule(struct child_process *cp,
* An empty directory is normal,
* the submodule is not initialized
*/
+ strbuf_addf(&empty_submodule_path, "%s/%s/",
+ spf->r->worktree,
+ ce->name);
if (S_ISGITLINK(ce->ce_mode) &&
- !is_empty_dir(ce->name)) {
+ !is_empty_dir(empty_submodule_path.buf)) {
spf->result = 1;
strbuf_addf(err,
_("Could not access submodule '%s'\n"),
ce->name);
}
+ strbuf_release(&empty_submodule_path);
+ }
+ }
+ return NULL;
+}
+
+static struct fetch_task *
+get_fetch_task_from_changed(struct submodule_parallel_fetch *spf,
+ struct strbuf *err)
+{
+ for (; spf->changed_count < spf->changed_submodule_names.nr;
+ spf->changed_count++) {
+ struct string_list_item item =
+ spf->changed_submodule_names.items[spf->changed_count];
+ struct changed_submodule_data *cs_data = item.util;
+ struct fetch_task *task;
+
+ if (!is_tree_submodule_active(spf->r, cs_data->super_oid,cs_data->path))
+ continue;
+
+ task = fetch_task_create(spf, cs_data->path,
+ cs_data->super_oid);
+ if (!task)
+ continue;
+
+ if (!task->repo) {
+ strbuf_addf(err, _("Could not access submodule '%s' at commit %s\n"),
+ cs_data->path,
+ repo_find_unique_abbrev(the_repository, cs_data->super_oid, DEFAULT_ABBREV));
+
+ fetch_task_release(task);
+ free(task);
+ continue;
}
+
+ if (!spf->quiet)
+ strbuf_addf(err,
+ _("Fetching submodule %s%s at commit %s\n"),
+ spf->prefix, task->sub->path,
+ repo_find_unique_abbrev(the_repository, cs_data->super_oid,
+ DEFAULT_ABBREV));
+
+ spf->changed_count++;
+ /*
+ * NEEDSWORK: Submodules set/unset a value for
+ * core.worktree when they are populated/unpopulated by
+ * "git checkout" (and similar commands, see
+ * submodule_move_head() and
+ * connect_work_tree_and_git_dir()), but if the
+ * submodule is unpopulated in another way (e.g. "git
+ * rm", "rm -r"), core.worktree will still be set even
+ * though the directory doesn't exist, and the child
+ * process will crash while trying to chdir into the
+ * nonexistent directory.
+ *
+ * In this case, we know that the submodule has no
+ * working tree, so we can work around this by
+ * setting "--work-tree=." (--bare does not work because
+ * worktree settings take precedence over bare-ness).
+ * However, this is not necessarily true in other cases,
+ * so a generalized solution is still necessary.
+ *
+ * Possible solutions:
+ * - teach "git [add|rm]" to unset core.worktree and
+ * discourage users from removing submodules without
+ * using a Git command.
+ * - teach submodule child processes to ignore stale
+ * core.worktree values.
+ */
+ strvec_push(&task->git_args, "--work-tree=.");
+ return task;
+ }
+ return NULL;
+}
+
+static int get_next_submodule(struct child_process *cp, struct strbuf *err,
+ void *data, void **task_cb)
+{
+ struct submodule_parallel_fetch *spf = data;
+ struct fetch_task *task =
+ get_fetch_task_from_index(spf, err);
+ if (!task)
+ task = get_fetch_task_from_changed(spf, err);
+
+ if (task) {
+ child_process_init(cp);
+ cp->dir = task->repo->gitdir;
+ prepare_submodule_repo_env_in_gitdir(&cp->env);
+ cp->git_cmd = 1;
+ strvec_init(&cp->args);
+ if (task->git_args.nr)
+ strvec_pushv(&cp->args, task->git_args.v);
+ strvec_pushv(&cp->args, spf->args.v);
+ strvec_push(&cp->args, task->default_argv);
+ strvec_pushf(&cp->args, "--submodule-prefix=%s%s/",
+ spf->prefix, task->sub->path);
+
+ *task_cb = task;
+
+ string_list_insert(&spf->seen_submodule_names, task->sub->name);
+ return 1;
}
if (spf->oid_fetch_tasks_nr) {
struct fetch_task *task =
spf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1];
- struct strbuf submodule_prefix = STRBUF_INIT;
spf->oid_fetch_tasks_nr--;
- strbuf_addf(&submodule_prefix, "%s%s/",
- spf->prefix, task->sub->path);
-
child_process_init(cp);
- prepare_submodule_repo_env_in_gitdir(&cp->env_array);
+ prepare_submodule_repo_env_in_gitdir(&cp->env);
cp->git_cmd = 1;
cp->dir = task->repo->gitdir;
strvec_init(&cp->args);
strvec_pushv(&cp->args, spf->args.v);
strvec_push(&cp->args, "on-demand");
- strvec_push(&cp->args, "--submodule-prefix");
- strvec_push(&cp->args, submodule_prefix.buf);
+ strvec_pushf(&cp->args, "--submodule-prefix=%s%s/",
+ spf->prefix, task->sub->path);
/* NEEDSWORK: have get_default_remote from submodule--helper */
strvec_push(&cp->args, "origin");
@@ -1512,14 +1732,13 @@ static int get_next_submodule(struct child_process *cp,
append_oid_to_argv, &cp->args);
*task_cb = task;
- strbuf_release(&submodule_prefix);
return 1;
}
return 0;
}
-static int fetch_start_failure(struct strbuf *err,
+static int fetch_start_failure(struct strbuf *err UNUSED,
void *cb, void *task_cb)
{
struct submodule_parallel_fetch *spf = cb;
@@ -1540,14 +1759,14 @@ static int commit_missing_in_sub(const struct object_id *oid, void *data)
return type != OBJ_COMMIT;
}
-static int fetch_finish(int retvalue, struct strbuf *err,
+static int fetch_finish(int retvalue, struct strbuf *err UNUSED,
void *cb, void *task_cb)
{
struct submodule_parallel_fetch *spf = cb;
struct fetch_task *task = task_cb;
struct string_list_item *it;
- struct oid_array *commits;
+ struct changed_submodule_data *cs_data;
if (!task || !task->sub)
BUG("callback cookie bogus");
@@ -1575,14 +1794,14 @@ static int fetch_finish(int retvalue, struct strbuf *err,
/* Could be an unchanged submodule, not contained in the list */
goto out;
- commits = it->util;
- oid_array_filter(commits,
+ cs_data = it->util;
+ oid_array_filter(&cs_data->new_commits,
commit_missing_in_sub,
task->repo);
/* Are there commits we want, but do not exist? */
- if (commits->nr) {
- task->commits = commits;
+ if (cs_data->new_commits.nr) {
+ task->commits = &cs_data->new_commits;
ALLOC_GROW(spf->oid_fetch_tasks,
spf->oid_fetch_tasks_nr + 1,
spf->oid_fetch_tasks_alloc);
@@ -1597,14 +1816,25 @@ out:
return 0;
}
-int fetch_populated_submodules(struct repository *r,
- const struct strvec *options,
- const char *prefix, int command_line_option,
- int default_option,
- int quiet, int max_parallel_jobs)
+int fetch_submodules(struct repository *r,
+ const struct strvec *options,
+ const char *prefix, int command_line_option,
+ int default_option,
+ int quiet, int max_parallel_jobs)
{
int i;
struct submodule_parallel_fetch spf = SPF_INIT;
+ const struct run_process_parallel_opts opts = {
+ .tr2_category = "submodule",
+ .tr2_label = "parallel/fetch",
+
+ .processes = max_parallel_jobs,
+
+ .get_next_task = get_next_submodule,
+ .start_failure = fetch_start_failure,
+ .task_finished = fetch_finish,
+ .data = &spf,
+ };
spf.r = r;
spf.command_line_option = command_line_option;
@@ -1626,12 +1856,7 @@ int fetch_populated_submodules(struct repository *r,
calculate_changed_submodule_paths(r, &spf.changed_submodule_names);
string_list_sort(&spf.changed_submodule_names);
- run_processes_parallel_tr2(max_parallel_jobs,
- get_next_submodule,
- fetch_start_failure,
- fetch_finish,
- &spf,
- "submodule", "parallel/fetch");
+ run_processes_parallel(&opts);
if (spf.submodules_with_errors.len > 0)
fprintf(stderr, _("Errors during submodule fetch:\n%s"),
@@ -1640,7 +1865,7 @@ int fetch_populated_submodules(struct repository *r,
strvec_clear(&spf.args);
out:
- free_submodules_oids(&spf.changed_submodule_names);
+ free_submodules_data(&spf.changed_submodule_names);
return spf.result;
}
@@ -1670,7 +1895,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
if (ignore_untracked)
strvec_push(&cp.args, "-uno");
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.out = -1;
@@ -1726,14 +1951,6 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
int submodule_uses_gitfile(const char *path)
{
struct child_process cp = CHILD_PROCESS_INIT;
- const char *argv[] = {
- "submodule",
- "foreach",
- "--quiet",
- "--recursive",
- "test -f .git",
- NULL,
- };
struct strbuf buf = STRBUF_INIT;
const char *git_dir;
@@ -1746,8 +1963,11 @@ int submodule_uses_gitfile(const char *path)
strbuf_release(&buf);
/* Now test that all nested submodules use a gitfile too */
- cp.argv = argv;
- prepare_submodule_repo_env(&cp.env_array);
+ strvec_pushl(&cp.args,
+ "submodule", "foreach", "--quiet", "--recursive",
+ "test -f .git", NULL);
+
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.no_stderr = 1;
@@ -1790,7 +2010,7 @@ int bad_to_remove_submodule(const char *path, unsigned flags)
if (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED))
strvec_push(&cp.args, "--ignored");
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.out = -1;
@@ -1821,29 +2041,23 @@ out:
void submodule_unset_core_worktree(const struct submodule *sub)
{
- char *config_path = xstrfmt("%s/modules/%s/config",
- get_git_dir(), sub->name);
+ struct strbuf config_path = STRBUF_INIT;
+
+ submodule_name_to_gitdir(&config_path, the_repository, sub->name);
+ strbuf_addstr(&config_path, "/config");
- if (git_config_set_in_file_gently(config_path, "core.worktree", NULL))
+ if (git_config_set_in_file_gently(config_path.buf, "core.worktree", NULL, NULL))
warning(_("Could not unset core.worktree setting in submodule '%s'"),
sub->path);
- free(config_path);
-}
-
-static const char *get_super_prefix_or_empty(void)
-{
- const char *s = get_super_prefix();
- if (!s)
- s = "";
- return s;
+ strbuf_release(&config_path);
}
static int submodule_has_dirty_index(const struct submodule *sub)
{
struct child_process cp = CHILD_PROCESS_INIT;
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
strvec_pushl(&cp.args, "diff-index", "--quiet",
@@ -1857,18 +2071,19 @@ static int submodule_has_dirty_index(const struct submodule *sub)
return finish_command(&cp);
}
-static void submodule_reset_index(const char *path)
+static void submodule_reset_index(const char *path, const char *super_prefix)
{
struct child_process cp = CHILD_PROCESS_INIT;
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.dir = path;
- strvec_pushf(&cp.args, "--super-prefix=%s%s/",
- get_super_prefix_or_empty(), path);
+ /* TODO: determine if this might overwright untracked files */
strvec_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
+ strvec_pushf(&cp.args, "--super-prefix=%s%s/",
+ (super_prefix ? super_prefix : ""), path);
strvec_push(&cp.args, empty_tree_oid_hex());
@@ -1881,10 +2096,9 @@ static void submodule_reset_index(const char *path)
* For edge cases (a submodule coming into existence or removing a submodule)
* pass NULL for old or new respectively.
*/
-int submodule_move_head(const char *path,
- const char *old_head,
- const char *new_head,
- unsigned flags)
+int submodule_move_head(const char *path, const char *super_prefix,
+ const char *old_head, const char *new_head,
+ unsigned flags)
{
int ret = 0;
struct child_process cp = CHILD_PROCESS_INIT;
@@ -1907,7 +2121,7 @@ int submodule_move_head(const char *path,
if (old_head && !is_submodule_populated_gently(path, error_code_ptr))
return 0;
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
BUG("could not get submodule information for '%s'", path);
@@ -1922,34 +2136,36 @@ int submodule_move_head(const char *path,
if (old_head) {
if (!submodule_uses_gitfile(path))
absorb_git_dir_into_superproject(path,
- ABSORB_GITDIR_RECURSE_SUBMODULES);
+ super_prefix);
} else {
- char *gitdir = xstrfmt("%s/modules/%s",
- get_git_dir(), sub->name);
- connect_work_tree_and_git_dir(path, gitdir, 0);
- free(gitdir);
+ struct strbuf gitdir = STRBUF_INIT;
+ submodule_name_to_gitdir(&gitdir, the_repository,
+ sub->name);
+ connect_work_tree_and_git_dir(path, gitdir.buf, 0);
+ strbuf_release(&gitdir);
/* make sure the index is clean as well */
- submodule_reset_index(path);
+ submodule_reset_index(path, super_prefix);
}
if (old_head && (flags & SUBMODULE_MOVE_HEAD_FORCE)) {
- char *gitdir = xstrfmt("%s/modules/%s",
- get_git_dir(), sub->name);
- connect_work_tree_and_git_dir(path, gitdir, 1);
- free(gitdir);
+ struct strbuf gitdir = STRBUF_INIT;
+ submodule_name_to_gitdir(&gitdir, the_repository,
+ sub->name);
+ connect_work_tree_and_git_dir(path, gitdir.buf, 1);
+ strbuf_release(&gitdir);
}
}
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
cp.no_stdin = 1;
cp.dir = path;
- strvec_pushf(&cp.args, "--super-prefix=%s%s/",
- get_super_prefix_or_empty(), path);
strvec_pushl(&cp.args, "read-tree", "--recurse-submodules", NULL);
+ strvec_pushf(&cp.args, "--super-prefix=%s%s/",
+ (super_prefix ? super_prefix : ""), path);
if (flags & SUBMODULE_MOVE_HEAD_DRY_RUN)
strvec_push(&cp.args, "-n");
@@ -1979,7 +2195,7 @@ int submodule_move_head(const char *path,
cp.no_stdin = 1;
cp.dir = path;
- prepare_submodule_repo_env(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
strvec_pushl(&cp.args, "update-ref", "HEAD",
"--no-deref", new_head, NULL);
@@ -2049,10 +2265,11 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
* Embeds a single submodules git directory into the superprojects git dir,
* non recursively.
*/
-static void relocate_single_git_dir_into_superproject(const char *path)
+static void relocate_single_git_dir_into_superproject(const char *path,
+ const char *super_prefix)
{
char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL;
- char *new_git_dir;
+ struct strbuf new_gitdir = STRBUF_INIT;
const struct submodule *sub;
if (submodule_uses_worktrees(path))
@@ -2066,21 +2283,20 @@ static void relocate_single_git_dir_into_superproject(const char *path)
real_old_git_dir = real_pathdup(old_git_dir, 1);
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
die(_("could not lookup name for submodule '%s'"), path);
- new_git_dir = git_pathdup("modules/%s", sub->name);
- if (validate_submodule_git_dir(new_git_dir, sub->name) < 0)
+ submodule_name_to_gitdir(&new_gitdir, the_repository, sub->name);
+ if (validate_submodule_git_dir(new_gitdir.buf, sub->name) < 0)
die(_("refusing to move '%s' into an existing git dir"),
real_old_git_dir);
- if (safe_create_leading_directories_const(new_git_dir) < 0)
- die(_("could not create directory '%s'"), new_git_dir);
- real_new_git_dir = real_pathdup(new_git_dir, 1);
- free(new_git_dir);
+ if (safe_create_leading_directories_const(new_gitdir.buf) < 0)
+ die(_("could not create directory '%s'"), new_gitdir.buf);
+ real_new_git_dir = real_pathdup(new_gitdir.buf, 1);
fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"),
- get_super_prefix_or_empty(), path,
+ super_prefix ? super_prefix : "", path,
real_old_git_dir, real_new_git_dir);
relocate_gitdir(path, real_old_git_dir, real_new_git_dir);
@@ -2088,6 +2304,26 @@ static void relocate_single_git_dir_into_superproject(const char *path)
free(old_git_dir);
free(real_old_git_dir);
free(real_new_git_dir);
+ strbuf_release(&new_gitdir);
+}
+
+static void absorb_git_dir_into_superproject_recurse(const char *path,
+ const char *super_prefix)
+{
+
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.dir = path;
+ cp.git_cmd = 1;
+ cp.no_stdin = 1;
+ strvec_pushl(&cp.args, "submodule--helper",
+ "absorbgitdirs", NULL);
+ strvec_pushf(&cp.args, "--super-prefix=%s%s/", super_prefix ?
+ super_prefix : "", path);
+
+ prepare_submodule_repo_env(&cp.env);
+ if (run_command(&cp))
+ die(_("could not recurse into submodule '%s'"), path);
}
/*
@@ -2096,7 +2332,7 @@ static void relocate_single_git_dir_into_superproject(const char *path)
* in its superprojects git dir under modules/.
*/
void absorb_git_dir_into_superproject(const char *path,
- unsigned flags)
+ const char *super_prefix)
{
int err_code;
const char *sub_git_dir;
@@ -2107,6 +2343,7 @@ void absorb_git_dir_into_superproject(const char *path,
/* Not populated? */
if (!sub_git_dir) {
const struct submodule *sub;
+ struct strbuf sub_gitdir = STRBUF_INIT;
if (err_code == READ_GITFILE_ERR_STAT_FAILED) {
/* unpopulated as expected */
@@ -2125,47 +2362,26 @@ void absorb_git_dir_into_superproject(const char *path,
* superproject did not rewrite the git file links yet,
* fix it now.
*/
- sub = submodule_from_path(the_repository, &null_oid, path);
+ sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub)
die(_("could not lookup name for submodule '%s'"), path);
- connect_work_tree_and_git_dir(path,
- git_path("modules/%s", sub->name), 0);
+ submodule_name_to_gitdir(&sub_gitdir, the_repository, sub->name);
+ connect_work_tree_and_git_dir(path, sub_gitdir.buf, 0);
+ strbuf_release(&sub_gitdir);
} else {
/* Is it already absorbed into the superprojects git dir? */
char *real_sub_git_dir = real_pathdup(sub_git_dir, 1);
char *real_common_git_dir = real_pathdup(get_git_common_dir(), 1);
if (!starts_with(real_sub_git_dir, real_common_git_dir))
- relocate_single_git_dir_into_superproject(path);
+ relocate_single_git_dir_into_superproject(path, super_prefix);
free(real_sub_git_dir);
free(real_common_git_dir);
}
strbuf_release(&gitdir);
- if (flags & ABSORB_GITDIR_RECURSE_SUBMODULES) {
- struct child_process cp = CHILD_PROCESS_INIT;
- struct strbuf sb = STRBUF_INIT;
-
- if (flags & ~ABSORB_GITDIR_RECURSE_SUBMODULES)
- BUG("we don't know how to pass the flags down?");
-
- strbuf_addstr(&sb, get_super_prefix_or_empty());
- strbuf_addstr(&sb, path);
- strbuf_addch(&sb, '/');
-
- cp.dir = path;
- cp.git_cmd = 1;
- cp.no_stdin = 1;
- strvec_pushl(&cp.args, "--super-prefix", sb.buf,
- "submodule--helper",
- "absorb-git-dirs", NULL);
- prepare_submodule_repo_env(&cp.env_array);
- if (run_command(&cp))
- die(_("could not recurse into submodule '%s'"), path);
-
- strbuf_release(&sb);
- }
+ absorb_git_dir_into_superproject_recurse(path, super_prefix);
}
int get_superproject_working_tree(struct strbuf *buf)
@@ -2173,7 +2389,7 @@ int get_superproject_working_tree(struct strbuf *buf)
struct child_process cp = CHILD_PROCESS_INIT;
struct strbuf sb = STRBUF_INIT;
struct strbuf one_up = STRBUF_INIT;
- const char *cwd = xgetcwd();
+ char *cwd = xgetcwd();
int ret = 0;
const char *subpath;
int code;
@@ -2193,8 +2409,8 @@ int get_superproject_working_tree(struct strbuf *buf)
subpath = relative_path(cwd, one_up.buf, &sb);
strbuf_release(&one_up);
- prepare_submodule_repo_env(&cp.env_array);
- strvec_pop(&cp.env_array);
+ prepare_submodule_repo_env(&cp.env);
+ strvec_pop(&cp.env);
strvec_pushl(&cp.args, "--literal-pathspecs", "-C", "..",
"ls-files", "-z", "--stage", "--full-name", "--",
@@ -2236,6 +2452,7 @@ int get_superproject_working_tree(struct strbuf *buf)
ret = 1;
free(super_wt);
}
+ free(cwd);
strbuf_release(&sb);
code = finish_command(&cp);
@@ -2273,15 +2490,43 @@ int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
strbuf_addstr(buf, git_dir);
}
if (!is_git_directory(buf->buf)) {
- sub = submodule_from_path(the_repository, &null_oid, submodule);
+ sub = submodule_from_path(the_repository, null_oid(),
+ submodule);
if (!sub) {
ret = -1;
goto cleanup;
}
strbuf_reset(buf);
- strbuf_git_path(buf, "%s/%s", "modules", sub->name);
+ submodule_name_to_gitdir(buf, the_repository, sub->name);
}
cleanup:
return ret;
}
+
+void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
+ const char *submodule_name)
+{
+ /*
+ * NEEDSWORK: The current way of mapping a submodule's name to
+ * its location in .git/modules/ has problems with some naming
+ * schemes. For example, if a submodule is named "foo" and
+ * another is named "foo/bar" (whether present in the same
+ * superproject commit or not - the problem will arise if both
+ * superproject commits have been checked out at any point in
+ * time), or if two submodule names only have different cases in
+ * a case-insensitive filesystem.
+ *
+ * There are several solutions, including encoding the path in
+ * some way, introducing a submodule.<name>.gitdir config in
+ * .git/config (not .gitmodules) that allows overriding what the
+ * gitdir of a submodule would be (and teach Git, upon noticing
+ * a clash, to automatically determine a non-clashing name and
+ * to write such a config), or introducing a
+ * submodule.<name>.gitdir config in .gitmodules that repo
+ * administrators can explicitly set. Nothing has been decided,
+ * so for now, just append the name at the end of the path.
+ */
+ strbuf_repo_git_path(buf, r, "modules/");
+ strbuf_addstr(buf, submodule_name);
+}