diff options
Diffstat (limited to 'submodule.c')
-rw-r--r-- | submodule.c | 1091 |
1 files changed, 668 insertions, 423 deletions
diff --git a/submodule.c b/submodule.c index e2ef569..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,21 +7,28 @@ #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" #include "refs.h" #include "string-list.h" #include "oid-array.h" -#include "argv-array.h" -#include "blob.h" +#include "strvec.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,20 +280,19 @@ 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 argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; const struct string_list_item *item; for_each_string_list_item(item, sl) { - argv_array_push(&args, item->string); + strvec_push(&args, item->string); } - parse_pathspec(&ps, 0, 0, NULL, args.argv); + parse_pathspec(&ps, 0, 0, NULL, args.v); ret = match_pathspec(repo->index, &ps, path, strlen(path), 0, NULL, 1); - argv_array_clear(&args); + strvec_clear(&args); clear_pathspec(&ps); return ret; } @@ -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 argv_array *out) +void prepare_submodule_repo_env(struct strvec *out) { - const char * const *var; - - for (var = local_repo_env; *var; var++) { - if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) - argv_array_push(out, *var); - } -} - -void prepare_submodule_repo_env(struct argv_array *out) -{ - prepare_submodule_repo_env_no_git_dir(out); - argv_array_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 argv_array *out) +static void prepare_submodule_repo_env_in_gitdir(struct strvec *out) { - prepare_submodule_repo_env_no_git_dir(out); - argv_array_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) { @@ -681,22 +695,22 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path, cp.no_stdin = 1; /* TODO: other options may need to be passed here. */ - argv_array_pushl(&cp.args, "diff", "--submodule=diff", NULL); - argv_array_pushf(&cp.args, "--color=%s", want_color(o->use_color) ? + strvec_pushl(&cp.args, "diff", "--submodule=diff", NULL); + strvec_pushf(&cp.args, "--color=%s", want_color(o->use_color) ? "always" : "never"); if (o->flags.reverse_diff) { - argv_array_pushf(&cp.args, "--src-prefix=%s%s/", - o->b_prefix, path); - argv_array_pushf(&cp.args, "--dst-prefix=%s%s/", - o->a_prefix, path); + strvec_pushf(&cp.args, "--src-prefix=%s%s/", + o->b_prefix, path); + strvec_pushf(&cp.args, "--dst-prefix=%s%s/", + o->a_prefix, path); } else { - argv_array_pushf(&cp.args, "--src-prefix=%s%s/", - o->a_prefix, path); - argv_array_pushf(&cp.args, "--dst-prefix=%s%s/", - o->b_prefix, path); + strvec_pushf(&cp.args, "--src-prefix=%s%s/", + o->a_prefix, path); + strvec_pushf(&cp.args, "--dst-prefix=%s%s/", + o->b_prefix, path); } - argv_array_push(&cp.args, oid_to_hex(old_oid)); + strvec_push(&cp.args, oid_to_hex(old_oid)); /* * If the submodule has modified content, we will diff against the * work tree, under the assumption that the user has asked for the @@ -704,11 +718,23 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path, * haven't yet been committed to the submodule yet. */ if (!(dirty_submodule & DIRTY_SUBMODULE_MODIFIED)) - argv_array_push(&cp.args, oid_to_hex(new_oid)); + 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); } } @@ -836,13 +903,20 @@ static void collect_changed_submodules_cb(struct diff_queue_struct *q, */ static void collect_changed_submodules(struct repository *r, struct string_list *changed, - struct argv_array *argv) + struct strvec *argv) { 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->argc, argv->argv, &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,30 +931,35 @@ 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; } static int append_oid_to_argv(const struct object_id *oid, void *data) { - struct argv_array *argv = data; - argv_array_push(argv, oid_to_hex(oid)); + struct strvec *argv = data; + strvec_push(argv, oid_to_hex(oid)); return 0; } @@ -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); @@ -941,11 +1026,11 @@ static int submodule_has_commits(struct repository *r, struct child_process cp = CHILD_PROCESS_INIT; struct strbuf out = STRBUF_INIT; - argv_array_pushl(&cp.args, "rev-list", "-n", "1", NULL); + strvec_pushl(&cp.args, "rev-list", "-n", "1", NULL); oid_array_for_each_unique(commits, append_oid_to_argv, &cp.args); - argv_array_pushl(&cp.args, "--not", "--all", NULL); + 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 @@ -982,11 +1067,11 @@ static int submodule_needs_pushing(struct repository *r, struct strbuf buf = STRBUF_INIT; int needs_pushing = 0; - argv_array_push(&cp.args, "rev-list"); + strvec_push(&cp.args, "rev-list"); oid_array_for_each_unique(commits, append_oid_to_argv, &cp.args); - argv_array_pushl(&cp.args, "--not", "--remotes", "-n", "1" , NULL); + 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; @@ -1012,22 +1097,22 @@ int find_unpushed_submodules(struct repository *r, { struct string_list submodules = STRING_LIST_INIT_DUP; struct string_list_item *name; - struct argv_array argv = ARGV_ARRAY_INIT; + struct strvec argv = STRVEC_INIT; - /* argv.argv[0] will be ignored by setup_revisions */ - argv_array_push(&argv, "find_unpushed_submodules"); + /* argv.v[0] will be ignored by setup_revisions */ + strvec_push(&argv, "find_unpushed_submodules"); oid_array_for_each_unique(commits, append_oid_to_argv, &argv); - argv_array_push(&argv, "--not"); - argv_array_pushf(&argv, "--remotes=%s", remotes_name); + strvec_push(&argv, "--not"); + strvec_pushf(&argv, "--remotes=%s", remotes_name); 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,12 +1121,12 @@ 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); - argv_array_clear(&argv); + free_submodules_data(&submodules); + strvec_clear(&argv); return needs_pushing->nr; } @@ -1054,25 +1139,31 @@ 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; - argv_array_push(&cp.args, "push"); + 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) - argv_array_push(&cp.args, "--dry-run"); + strvec_push(&cp.args, "--dry-run"); if (push_options && push_options->nr) { const struct string_list_item *item; for_each_string_list_item(item, push_options) - argv_array_pushf(&cp.args, "--push-option=%s", - item->string); + strvec_pushf(&cp.args, "--push-option=%s", + item->string); } if (remote->origin != REMOTE_UNCONFIGURED) { int i; - argv_array_push(&cp.args, remote->name); + strvec_push(&cp.args, remote->name); for (i = 0; i < rs->raw_nr; i++) - argv_array_push(&cp.args, rs->raw[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.dir = path; @@ -1095,15 +1186,15 @@ static void submodule_push_check(const char *path, const char *head, struct child_process cp = CHILD_PROCESS_INIT; int i; - argv_array_push(&cp.args, "submodule--helper"); - argv_array_push(&cp.args, "push-check"); - argv_array_push(&cp.args, head); - argv_array_push(&cp.args, remote->name); + strvec_push(&cp.args, "submodule--helper"); + strvec_push(&cp.args, "push-check"); + strvec_push(&cp.args, head); + strvec_push(&cp.args, remote->name); for (i = 0; i < rs->raw_nr; i++) - argv_array_push(&cp.args, rs->raw[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,20 +1278,42 @@ 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 argv_array argv = ARGV_ARRAY_INIT; + 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; - argv_array_push(&argv, "--"); /* argv[0] program name */ + strvec_push(&argv, "--"); /* argv[0] program name */ oid_array_for_each_unique(&ref_tips_after_fetch, append_oid_to_argv, &argv); - argv_array_push(&argv, "--not"); + strvec_push(&argv, "--not"); oid_array_for_each_unique(&ref_tips_before_fetch, append_oid_to_argv, &argv); @@ -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,15 +1337,15 @@ 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'; } } string_list_remove_empty_items(changed_submodule_names, 1); - argv_array_clear(&argv); + strvec_clear(&argv); oid_array_clear(&ref_tips_before_fetch); oid_array_clear(&ref_tips_after_fetch); initialized_fetch_ref_tips = 0; @@ -1242,32 +1356,41 @@ int submodule_touches_in_range(struct repository *r, struct object_id *incl_oid) { struct string_list subs = STRING_LIST_INIT_DUP; - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; int ret; /* No need to check if there are no submodules configured */ if (!submodule_from_path(r, NULL, NULL)) return 0; - argv_array_push(&args, "--"); /* args[0] program name */ - argv_array_push(&args, oid_to_hex(incl_oid)); + strvec_push(&args, "--"); /* args[0] program name */ + strvec_push(&args, oid_to_hex(incl_oid)); if (!is_null_oid(excl_oid)) { - argv_array_push(&args, "--not"); - argv_array_push(&args, oid_to_hex(excl_oid)); + strvec_push(&args, "--not"); + strvec_push(&args, oid_to_hex(excl_oid)); } collect_changed_submodules(r, &subs, &args); ret = subs.nr; - argv_array_clear(&args); + strvec_clear(&args); - free_submodules_oids(&subs); + free_submodules_data(&subs); return ret; } struct submodule_parallel_fetch { - int count; - struct argv_array args; + /* + * 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; int command_line_option; @@ -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, ARGV_ARRAY_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); - argv_array_init(&cp->args); - argv_array_pushv(&cp->args, spf->args.argv); - argv_array_push(&cp->args, default_argv); - argv_array_push(&cp->args, "--submodule-prefix"); - - strbuf_addf(&submodule_prefix, "%s%s/", - spf->prefix, - task->sub->path); - argv_array_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,50 +1597,148 @@ 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; - argv_array_init(&cp->args); - argv_array_pushv(&cp->args, spf->args.argv); - argv_array_push(&cp->args, "on-demand"); - argv_array_push(&cp->args, "--submodule-prefix"); - argv_array_push(&cp->args, submodule_prefix.buf); + strvec_init(&cp->args); + strvec_pushv(&cp->args, spf->args.v); + strvec_push(&cp->args, "on-demand"); + strvec_pushf(&cp->args, "--submodule-prefix=%s%s/", + spf->prefix, task->sub->path); /* NEEDSWORK: have get_default_remote from submodule--helper */ - argv_array_push(&cp->args, "origin"); + strvec_push(&cp->args, "origin"); oid_array_for_each_unique(task->commits, 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 argv_array *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; @@ -1618,29 +1848,24 @@ int fetch_populated_submodules(struct repository *r, if (repo_read_index(r) < 0) die(_("index file corrupt")); - argv_array_push(&spf.args, "fetch"); - for (i = 0; i < options->argc; i++) - argv_array_push(&spf.args, options->argv[i]); - argv_array_push(&spf.args, "--recurse-submodules-default"); + strvec_push(&spf.args, "fetch"); + for (i = 0; i < options->nr; i++) + strvec_push(&spf.args, options->v[i]); + strvec_push(&spf.args, "--recurse-submodules-default"); /* default value, "--submodule-prefix" and its value are added later */ 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"), spf.submodules_with_errors.buf); - argv_array_clear(&spf.args); + strvec_clear(&spf.args); out: - free_submodules_oids(&spf.changed_submodule_names); + free_submodules_data(&spf.changed_submodule_names); return spf.result; } @@ -1666,11 +1891,11 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked) } strbuf_reset(&buf); - argv_array_pushl(&cp.args, "status", "--porcelain=2", NULL); + strvec_pushl(&cp.args, "status", "--porcelain=2", NULL); if (ignore_untracked) - argv_array_push(&cp.args, "-uno"); + 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; @@ -1779,18 +1999,18 @@ int bad_to_remove_submodule(const char *path, unsigned flags) if (!submodule_uses_gitfile(path)) return 1; - argv_array_pushl(&cp.args, "status", "--porcelain", - "--ignore-submodules=none", NULL); + strvec_pushl(&cp.args, "status", "--porcelain", + "--ignore-submodules=none", NULL); if (flags & SUBMODULE_REMOVAL_IGNORE_UNTRACKED) - argv_array_push(&cp.args, "-uno"); + strvec_push(&cp.args, "-uno"); else - argv_array_push(&cp.args, "-uall"); + strvec_push(&cp.args, "-uall"); if (!(flags & SUBMODULE_REMOVAL_IGNORE_IGNORED_UNTRACKED)) - argv_array_push(&cp.args, "--ignored"); + 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,33 +2041,27 @@ 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; - if (git_config_set_in_file_gently(config_path, "core.worktree", NULL)) + submodule_name_to_gitdir(&config_path, the_repository, sub->name); + strbuf_addstr(&config_path, "/config"); + + 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; - argv_array_pushl(&cp.args, "diff-index", "--quiet", - "--cached", "HEAD", NULL); + strvec_pushl(&cp.args, "diff-index", "--quiet", + "--cached", "HEAD", NULL); cp.no_stdin = 1; cp.no_stdout = 1; cp.dir = sub->path; @@ -1857,20 +2071,21 @@ 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; - argv_array_pushf(&cp.args, "--super-prefix=%s%s/", - get_super_prefix_or_empty(), path); - argv_array_pushl(&cp.args, "read-tree", "-u", "--reset", NULL); + /* 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); - argv_array_push(&cp.args, empty_tree_oid_hex()); + strvec_push(&cp.args, empty_tree_oid_hex()); if (run_command(&cp)) die(_("could not reset submodule index")); @@ -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,49 +2136,51 @@ 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; - argv_array_pushf(&cp.args, "--super-prefix=%s%s/", - get_super_prefix_or_empty(), path); - argv_array_pushl(&cp.args, "read-tree", "--recurse-submodules", NULL); + 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) - argv_array_push(&cp.args, "-n"); + strvec_push(&cp.args, "-n"); else - argv_array_push(&cp.args, "-u"); + strvec_push(&cp.args, "-u"); if (flags & SUBMODULE_MOVE_HEAD_FORCE) - argv_array_push(&cp.args, "--reset"); + strvec_push(&cp.args, "--reset"); else - argv_array_push(&cp.args, "-m"); + strvec_push(&cp.args, "-m"); if (!(flags & SUBMODULE_MOVE_HEAD_FORCE)) - argv_array_push(&cp.args, old_head ? old_head : empty_tree_oid_hex()); + strvec_push(&cp.args, old_head ? old_head : empty_tree_oid_hex()); - argv_array_push(&cp.args, new_head ? new_head : empty_tree_oid_hex()); + strvec_push(&cp.args, new_head ? new_head : empty_tree_oid_hex()); if (run_command(&cp)) { ret = error(_("Submodule '%s' could not be updated."), path); @@ -1979,9 +2195,9 @@ int submodule_move_head(const char *path, cp.no_stdin = 1; cp.dir = path; - prepare_submodule_repo_env(&cp.env_array); - argv_array_pushl(&cp.args, "update-ref", "HEAD", - "--no-deref", new_head, NULL); + prepare_submodule_repo_env(&cp.env); + strvec_pushl(&cp.args, "update-ref", "HEAD", + "--no-deref", new_head, NULL); if (run_command(&cp)) { ret = -1; @@ -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; - argv_array_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,12 +2409,12 @@ 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); - argv_array_pop(&cp.env_array); + prepare_submodule_repo_env(&cp.env); + strvec_pop(&cp.env); - argv_array_pushl(&cp.args, "--literal-pathspecs", "-C", "..", - "ls-files", "-z", "--stage", "--full-name", "--", - subpath, NULL); + strvec_pushl(&cp.args, "--literal-pathspecs", "-C", "..", + "ls-files", "-z", "--stage", "--full-name", "--", + subpath, NULL); strbuf_reset(&sb); cp.no_stdin = 1; @@ -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); +} |