diff --git a/.gitignore b/.gitignore index a685ec1fb0..4fd81baf85 100644 --- a/.gitignore +++ b/.gitignore @@ -205,6 +205,7 @@ /test-sha1-array /test-sigchain /test-string-list +/test-submodule-config /test-subprocess /test-svn-fe /test-urlmatch-normalization diff --git a/Documentation/technical/api-submodule-config.txt b/Documentation/technical/api-submodule-config.txt new file mode 100644 index 0000000000..941fa178dd --- /dev/null +++ b/Documentation/technical/api-submodule-config.txt @@ -0,0 +1,62 @@ +submodule config cache API +========================== + +The submodule config cache API allows to read submodule +configurations/information from specified revisions. Internally +information is lazily read into a cache that is used to avoid +unnecessary parsing of the same .gitmodule files. Lookups can be done by +submodule path or name. + +Usage +----- + +To initialize the cache with configurations from the worktree the caller +typically first calls `gitmodules_config()` to read values from the +worktree .gitmodules and then to overlay the local git config values +`parse_submodule_config_option()` from the config parsing +infrastructure. + +The caller can look up information about submodules by using the +`submodule_from_path()` or `submodule_from_name()` functions. They return +a `struct submodule` which contains the values. The API automatically +initializes and allocates the needed infrastructure on-demand. If the +caller does only want to lookup values from revisions the initialization +can be skipped. + +If the internal cache might grow too big or when the caller is done with +the API, all internally cached values can be freed with submodule_free(). + +Data Structures +--------------- + +`struct submodule`:: + + This structure is used to return the information about one + submodule for a certain revision. It is returned by the lookup + functions. + +Functions +--------- + +`void submodule_free()`:: + + Use these to free the internally cached values. + +`int parse_submodule_config_option(const char *var, const char *value)`:: + + Can be passed to the config parsing infrastructure to parse + local (worktree) submodule configurations. + +`const struct submodule *submodule_from_path(const unsigned char *commit_sha1, const char *path)`:: + + Lookup values for one submodule by its commit_sha1 and path. + +`const struct submodule *submodule_from_name(const unsigned char *commit_sha1, const char *name)`:: + + The same as above but lookup by name. + +If given the null_sha1 as commit_sha1 the local configuration of a +submodule will be returned (e.g. consolidated values from local git +configuration and the .gitmodules file in the worktree). + +For an example usage see test-submodule-config.c. diff --git a/Makefile b/Makefile index 7efedbe834..24b636dc58 100644 --- a/Makefile +++ b/Makefile @@ -594,6 +594,7 @@ TEST_PROGRAMS_NEED_X += test-sha1 TEST_PROGRAMS_NEED_X += test-sha1-array TEST_PROGRAMS_NEED_X += test-sigchain TEST_PROGRAMS_NEED_X += test-string-list +TEST_PROGRAMS_NEED_X += test-submodule-config TEST_PROGRAMS_NEED_X += test-subprocess TEST_PROGRAMS_NEED_X += test-svn-fe TEST_PROGRAMS_NEED_X += test-urlmatch-normalization @@ -785,6 +786,7 @@ LIB_OBJS += strbuf.o LIB_OBJS += streaming.o LIB_OBJS += string-list.o LIB_OBJS += submodule.o +LIB_OBJS += submodule-config.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o diff --git a/builtin/checkout.c b/builtin/checkout.c index 9ff5ca4cbe..7ea533e4ab 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -18,6 +18,7 @@ #include "xdiff-interface.h" #include "ll-merge.h" #include "resolve-undo.h" +#include "submodule-config.h" #include "submodule.h" static const char * const checkout_usage[] = { diff --git a/builtin/fetch.c b/builtin/fetch.c index df16d0a56e..ee1f1a93af 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -11,6 +11,7 @@ #include "run-command.h" #include "parse-options.h" #include "sigchain.h" +#include "submodule-config.h" #include "submodule.h" #include "connected.h" #include "argv-array.h" diff --git a/diff.c b/diff.c index 7deac90532..8b382c2b6b 100644 --- a/diff.c +++ b/diff.c @@ -13,6 +13,7 @@ #include "utf8.h" #include "userdiff.h" #include "sigchain.h" +#include "submodule-config.h" #include "submodule.h" #include "ll-merge.h" #include "string-list.h" diff --git a/submodule-config.c b/submodule-config.c new file mode 100644 index 0000000000..393de5357e --- /dev/null +++ b/submodule-config.c @@ -0,0 +1,482 @@ +#include "cache.h" +#include "submodule-config.h" +#include "submodule.h" +#include "strbuf.h" + +/* + * submodule cache lookup structure + * There is one shared set of 'struct submodule' entries which can be + * looked up by their sha1 blob id of the .gitmodule file and either + * using path or name as key. + * for_path stores submodule entries with path as key + * for_name stores submodule entries with name as key + */ +struct submodule_cache { + struct hashmap for_path; + struct hashmap for_name; +}; + +/* + * thin wrapper struct needed to insert 'struct submodule' entries to + * the hashmap + */ +struct submodule_entry { + struct hashmap_entry ent; + struct submodule *config; +}; + +enum lookup_type { + lookup_name, + lookup_path +}; + +static struct submodule_cache cache; +static int is_cache_init; + +static int config_path_cmp(const struct submodule_entry *a, + const struct submodule_entry *b, + const void *unused) +{ + return strcmp(a->config->path, b->config->path) || + hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1); +} + +static int config_name_cmp(const struct submodule_entry *a, + const struct submodule_entry *b, + const void *unused) +{ + return strcmp(a->config->name, b->config->name) || + hashcmp(a->config->gitmodules_sha1, b->config->gitmodules_sha1); +} + +static void cache_init(struct submodule_cache *cache) +{ + hashmap_init(&cache->for_path, (hashmap_cmp_fn) config_path_cmp, 0); + hashmap_init(&cache->for_name, (hashmap_cmp_fn) config_name_cmp, 0); +} + +static void free_one_config(struct submodule_entry *entry) +{ + free((void *) entry->config->path); + free((void *) entry->config->name); + free(entry->config); +} + +static void cache_free(struct submodule_cache *cache) +{ + struct hashmap_iter iter; + struct submodule_entry *entry; + + /* + * We iterate over the name hash here to be symmetric with the + * allocation of struct submodule entries. Each is allocated by + * their .gitmodule blob sha1 and submodule name. + */ + hashmap_iter_init(&cache->for_name, &iter); + while ((entry = hashmap_iter_next(&iter))) + free_one_config(entry); + + hashmap_free(&cache->for_path, 1); + hashmap_free(&cache->for_name, 1); +} + +static unsigned int hash_sha1_string(const unsigned char *sha1, + const char *string) +{ + return memhash(sha1, 20) + strhash(string); +} + +static void cache_put_path(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->path); + struct submodule_entry *e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, hash); + e->config = submodule; + hashmap_put(&cache->for_path, e); +} + +static void cache_remove_path(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->path); + struct submodule_entry e; + struct submodule_entry *removed; + hashmap_entry_init(&e, hash); + e.config = submodule; + removed = hashmap_remove(&cache->for_path, &e, NULL); + free(removed); +} + +static void cache_add(struct submodule_cache *cache, + struct submodule *submodule) +{ + unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, + submodule->name); + struct submodule_entry *e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, hash); + e->config = submodule; + hashmap_add(&cache->for_name, e); +} + +static const struct submodule *cache_lookup_path(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *path) +{ + struct submodule_entry *entry; + unsigned int hash = hash_sha1_string(gitmodules_sha1, path); + struct submodule_entry key; + struct submodule key_config; + + hashcpy(key_config.gitmodules_sha1, gitmodules_sha1); + key_config.path = path; + + hashmap_entry_init(&key, hash); + key.config = &key_config; + + entry = hashmap_get(&cache->for_path, &key, NULL); + if (entry) + return entry->config; + return NULL; +} + +static struct submodule *cache_lookup_name(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *name) +{ + struct submodule_entry *entry; + unsigned int hash = hash_sha1_string(gitmodules_sha1, name); + struct submodule_entry key; + struct submodule key_config; + + hashcpy(key_config.gitmodules_sha1, gitmodules_sha1); + key_config.name = name; + + hashmap_entry_init(&key, hash); + key.config = &key_config; + + entry = hashmap_get(&cache->for_name, &key, NULL); + if (entry) + return entry->config; + return NULL; +} + +static int name_and_item_from_var(const char *var, struct strbuf *name, + struct strbuf *item) +{ + const char *subsection, *key; + int subsection_len, parse; + parse = parse_config_key(var, "submodule", &subsection, + &subsection_len, &key); + if (parse < 0 || !subsection) + return 0; + + strbuf_add(name, subsection, subsection_len); + strbuf_addstr(item, key); + + return 1; +} + +static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache, + const unsigned char *gitmodules_sha1, const char *name) +{ + struct submodule *submodule; + struct strbuf name_buf = STRBUF_INIT; + + submodule = cache_lookup_name(cache, gitmodules_sha1, name); + if (submodule) + return submodule; + + submodule = xmalloc(sizeof(*submodule)); + + strbuf_addstr(&name_buf, name); + submodule->name = strbuf_detach(&name_buf, NULL); + + submodule->path = NULL; + submodule->url = NULL; + submodule->fetch_recurse = RECURSE_SUBMODULES_NONE; + submodule->ignore = NULL; + + hashcpy(submodule->gitmodules_sha1, gitmodules_sha1); + + cache_add(cache, submodule); + + return submodule; +} + +static int parse_fetch_recurse(const char *opt, const char *arg, + int die_on_error) +{ + switch (git_config_maybe_bool(opt, arg)) { + case 1: + return RECURSE_SUBMODULES_ON; + case 0: + return RECURSE_SUBMODULES_OFF; + default: + if (!strcmp(arg, "on-demand")) + return RECURSE_SUBMODULES_ON_DEMAND; + + if (die_on_error) + die("bad %s argument: %s", opt, arg); + else + return RECURSE_SUBMODULES_ERROR; + } +} + +int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) +{ + return parse_fetch_recurse(opt, arg, 1); +} + +static void warn_multiple_config(const unsigned char *commit_sha1, + const char *name, const char *option) +{ + const char *commit_string = "WORKTREE"; + if (commit_sha1) + commit_string = sha1_to_hex(commit_sha1); + warning("%s:.gitmodules, multiple configurations found for " + "'submodule.%s.%s'. Skipping second one!", + commit_string, name, option); +} + +struct parse_config_parameter { + struct submodule_cache *cache; + const unsigned char *commit_sha1; + const unsigned char *gitmodules_sha1; + int overwrite; +}; + +static int parse_config(const char *var, const char *value, void *data) +{ + struct parse_config_parameter *me = data; + struct submodule *submodule; + struct strbuf name = STRBUF_INIT, item = STRBUF_INIT; + int ret = 0; + + /* this also ensures that we only parse submodule entries */ + if (!name_and_item_from_var(var, &name, &item)) + return 0; + + submodule = lookup_or_create_by_name(me->cache, me->gitmodules_sha1, + name.buf); + + if (!strcmp(item.buf, "path")) { + struct strbuf path = STRBUF_INIT; + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (!me->overwrite && submodule->path != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "path"); + goto release_return; + } + + if (submodule->path) + cache_remove_path(me->cache, submodule); + free((void *) submodule->path); + strbuf_addstr(&path, value); + submodule->path = strbuf_detach(&path, NULL); + cache_put_path(me->cache, submodule); + } else if (!strcmp(item.buf, "fetchrecursesubmodules")) { + /* when parsing worktree configurations we can die early */ + int die_on_error = is_null_sha1(me->gitmodules_sha1); + if (!me->overwrite && + submodule->fetch_recurse != RECURSE_SUBMODULES_NONE) { + warn_multiple_config(me->commit_sha1, submodule->name, + "fetchrecursesubmodules"); + goto release_return; + } + + submodule->fetch_recurse = parse_fetch_recurse(var, value, + die_on_error); + } else if (!strcmp(item.buf, "ignore")) { + struct strbuf ignore = STRBUF_INIT; + if (!me->overwrite && submodule->ignore != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "ignore"); + goto release_return; + } + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (strcmp(value, "untracked") && strcmp(value, "dirty") && + strcmp(value, "all") && strcmp(value, "none")) { + warning("Invalid parameter '%s' for config option " + "'submodule.%s.ignore'", value, var); + goto release_return; + } + + free((void *) submodule->ignore); + strbuf_addstr(&ignore, value); + submodule->ignore = strbuf_detach(&ignore, NULL); + } else if (!strcmp(item.buf, "url")) { + struct strbuf url = STRBUF_INIT; + if (!value) { + ret = config_error_nonbool(var); + goto release_return; + } + if (!me->overwrite && submodule->url != NULL) { + warn_multiple_config(me->commit_sha1, submodule->name, + "url"); + goto release_return; + } + + free((void *) submodule->url); + strbuf_addstr(&url, value); + submodule->url = strbuf_detach(&url, NULL); + } + +release_return: + strbuf_release(&name); + strbuf_release(&item); + + return ret; +} + +static int gitmodule_sha1_from_commit(const unsigned char *commit_sha1, + unsigned char *gitmodules_sha1) +{ + struct strbuf rev = STRBUF_INIT; + int ret = 0; + + if (is_null_sha1(commit_sha1)) { + hashcpy(gitmodules_sha1, null_sha1); + return 1; + } + + strbuf_addf(&rev, "%s:.gitmodules", sha1_to_hex(commit_sha1)); + if (get_sha1(rev.buf, gitmodules_sha1) >= 0) + ret = 1; + + strbuf_release(&rev); + return ret; +} + +/* This does a lookup of a submodule configuration by name or by path + * (key) with on-demand reading of the appropriate .gitmodules from + * revisions. + */ +static const struct submodule *config_from(struct submodule_cache *cache, + const unsigned char *commit_sha1, const char *key, + enum lookup_type lookup_type) +{ + struct strbuf rev = STRBUF_INIT; + unsigned long config_size; + char *config; + unsigned char sha1[20]; + enum object_type type; + const struct submodule *submodule = NULL; + struct parse_config_parameter parameter; + + /* + * If any parameter except the cache is a NULL pointer just + * return the first submodule. Can be used to check whether + * there are any submodules parsed. + */ + if (!commit_sha1 || !key) { + struct hashmap_iter iter; + struct submodule_entry *entry; + + hashmap_iter_init(&cache->for_name, &iter); + entry = hashmap_iter_next(&iter); + if (!entry) + return NULL; + return entry->config; + } + + if (!gitmodule_sha1_from_commit(commit_sha1, sha1)) + return NULL; + + switch (lookup_type) { + case lookup_name: + submodule = cache_lookup_name(cache, sha1, key); + break; + case lookup_path: + submodule = cache_lookup_path(cache, sha1, key); + break; + } + if (submodule) + return submodule; + + config = read_sha1_file(sha1, &type, &config_size); + if (!config) + return NULL; + + if (type != OBJ_BLOB) { + free(config); + return NULL; + } + + /* fill the submodule config into the cache */ + parameter.cache = cache; + parameter.commit_sha1 = commit_sha1; + parameter.gitmodules_sha1 = sha1; + parameter.overwrite = 0; + git_config_from_buf(parse_config, rev.buf, config, config_size, + ¶meter); + free(config); + + switch (lookup_type) { + case lookup_name: + return cache_lookup_name(cache, sha1, key); + case lookup_path: + return cache_lookup_path(cache, sha1, key); + default: + return NULL; + } +} + +static const struct submodule *config_from_path(struct submodule_cache *cache, + const unsigned char *commit_sha1, const char *path) +{ + return config_from(cache, commit_sha1, path, lookup_path); +} + +static const struct submodule *config_from_name(struct submodule_cache *cache, + const unsigned char *commit_sha1, const char *name) +{ + return config_from(cache, commit_sha1, name, lookup_name); +} + +static void ensure_cache_init(void) +{ + if (is_cache_init) + return; + + cache_init(&cache); + is_cache_init = 1; +} + +int parse_submodule_config_option(const char *var, const char *value) +{ + struct parse_config_parameter parameter; + parameter.cache = &cache; + parameter.commit_sha1 = NULL; + parameter.gitmodules_sha1 = null_sha1; + parameter.overwrite = 1; + + ensure_cache_init(); + return parse_config(var, value, ¶meter); +} + +const struct submodule *submodule_from_name(const unsigned char *commit_sha1, + const char *name) +{ + ensure_cache_init(); + return config_from_name(&cache, commit_sha1, name); +} + +const struct submodule *submodule_from_path(const unsigned char *commit_sha1, + const char *path) +{ + ensure_cache_init(); + return config_from_path(&cache, commit_sha1, path); +} + +void submodule_free(void) +{ + cache_free(&cache); + is_cache_init = 0; +} diff --git a/submodule-config.h b/submodule-config.h new file mode 100644 index 0000000000..9061e4ed38 --- /dev/null +++ b/submodule-config.h @@ -0,0 +1,29 @@ +#ifndef SUBMODULE_CONFIG_CACHE_H +#define SUBMODULE_CONFIG_CACHE_H + +#include "hashmap.h" +#include "strbuf.h" + +/* + * Submodule entry containing the information about a certain submodule + * in a certain revision. + */ +struct submodule { + const char *path; + const char *name; + const char *url; + int fetch_recurse; + const char *ignore; + /* the sha1 blob id of the responsible .gitmodules file */ + unsigned char gitmodules_sha1[20]; +}; + +int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); +int parse_submodule_config_option(const char *var, const char *value); +const struct submodule *submodule_from_name(const unsigned char *commit_sha1, + const char *name); +const struct submodule *submodule_from_path(const unsigned char *commit_sha1, + const char *path); +void submodule_free(void); + +#endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index 700bbf4fcb..9fcc86faa2 100644 --- a/submodule.c +++ b/submodule.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "submodule-config.h" #include "submodule.h" #include "dir.h" #include "diff.h" @@ -12,9 +13,6 @@ #include "argv-array.h" #include "blob.h" -static struct string_list config_name_for_path; -static struct string_list config_fetch_recurse_submodules_for_name; -static struct string_list config_ignore_for_name; static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static struct string_list changed_submodule_paths; static int initialized_fetch_ref_tips; @@ -41,7 +39,6 @@ static int gitmodules_is_unmerged; */ static int gitmodules_is_modified; - int is_staging_gitmodules_ok(void) { return !gitmodules_is_modified; @@ -55,7 +52,7 @@ int is_staging_gitmodules_ok(void) int update_path_in_gitmodules(const char *oldpath, const char *newpath) { struct strbuf entry = STRBUF_INIT; - struct string_list_item *path_option; + const struct submodule *submodule; if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */ return -1; @@ -63,13 +60,13 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath) if (gitmodules_is_unmerged) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - path_option = unsorted_string_list_lookup(&config_name_for_path, oldpath); - if (!path_option) { + submodule = submodule_from_path(null_sha1, oldpath); + if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), oldpath); return -1; } strbuf_addstr(&entry, "submodule."); - strbuf_addstr(&entry, path_option->util); + strbuf_addstr(&entry, submodule->name); strbuf_addstr(&entry, ".path"); if (git_config_set_in_file(".gitmodules", entry.buf, newpath) < 0) { /* Maybe the user already did that, don't error out here */ @@ -89,7 +86,7 @@ int update_path_in_gitmodules(const char *oldpath, const char *newpath) int remove_path_from_gitmodules(const char *path) { struct strbuf sect = STRBUF_INIT; - struct string_list_item *path_option; + const struct submodule *submodule; if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */ return -1; @@ -97,13 +94,13 @@ int remove_path_from_gitmodules(const char *path) if (gitmodules_is_unmerged) die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first")); - path_option = unsorted_string_list_lookup(&config_name_for_path, path); - if (!path_option) { + submodule = submodule_from_path(null_sha1, path); + if (!submodule || !submodule->name) { warning(_("Could not find section in .gitmodules where path=%s"), path); return -1; } strbuf_addstr(§, "submodule."); - strbuf_addstr(§, path_option->util); + strbuf_addstr(§, submodule->name); if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) { /* Maybe the user already did that, don't error out here */ warning(_("Could not remove .gitmodules entry for %s"), path); @@ -165,12 +162,10 @@ done: void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path) { - struct string_list_item *path_option, *ignore_option; - path_option = unsorted_string_list_lookup(&config_name_for_path, path); - if (path_option) { - ignore_option = unsorted_string_list_lookup(&config_ignore_for_name, path_option->util); - if (ignore_option) - handle_ignore_submodules_arg(diffopt, ignore_option->util); + const struct submodule *submodule = submodule_from_path(null_sha1, path); + if (submodule) { + if (submodule->ignore) + handle_ignore_submodules_arg(diffopt, submodule->ignore); else if (gitmodules_is_unmerged) DIFF_OPT_SET(diffopt, IGNORE_SUBMODULES); } @@ -219,58 +214,6 @@ void gitmodules_config(void) } } -int parse_submodule_config_option(const char *var, const char *value) -{ - struct string_list_item *config; - const char *name, *key; - int namelen; - - if (parse_config_key(var, "submodule", &name, &namelen, &key) < 0 || !name) - return 0; - - if (!strcmp(key, "path")) { - if (!value) - return config_error_nonbool(var); - - config = unsorted_string_list_lookup(&config_name_for_path, value); - if (config) - free(config->util); - else - config = string_list_append(&config_name_for_path, xstrdup(value)); - config->util = xmemdupz(name, namelen); - } else if (!strcmp(key, "fetchrecursesubmodules")) { - char *name_cstr = xmemdupz(name, namelen); - config = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name_cstr); - if (!config) - config = string_list_append(&config_fetch_recurse_submodules_for_name, name_cstr); - else - free(name_cstr); - config->util = (void *)(intptr_t)parse_fetch_recurse_submodules_arg(var, value); - } else if (!strcmp(key, "ignore")) { - char *name_cstr; - - if (!value) - return config_error_nonbool(var); - - if (strcmp(value, "untracked") && strcmp(value, "dirty") && - strcmp(value, "all") && strcmp(value, "none")) { - warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var); - return 0; - } - - name_cstr = xmemdupz(name, namelen); - config = unsorted_string_list_lookup(&config_ignore_for_name, name_cstr); - if (config) { - free(config->util); - free(name_cstr); - } else - config = string_list_append(&config_ignore_for_name, name_cstr); - config->util = xstrdup(value); - return 0; - } - return 0; -} - void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *arg) { @@ -345,20 +288,6 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, strbuf_release(&sb); } -int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) -{ - switch (git_config_maybe_bool(opt, arg)) { - case 1: - return RECURSE_SUBMODULES_ON; - case 0: - return RECURSE_SUBMODULES_OFF; - default: - if (!strcmp(arg, "on-demand")) - return RECURSE_SUBMODULES_ON_DEMAND; - die("bad %s argument: %s", opt, arg); - } -} - void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, unsigned char one[20], unsigned char two[20], @@ -646,7 +575,7 @@ static void calculate_changed_submodule_paths(void) struct argv_array argv = ARGV_ARRAY_INIT; /* No need to check if there are no submodules configured */ - if (!config_name_for_path.nr) + if (!submodule_from_path(NULL, NULL)) return; init_revisions(&rev, NULL); @@ -693,7 +622,6 @@ int fetch_populated_submodules(const struct argv_array *options, int i, result = 0; struct child_process cp = CHILD_PROCESS_INIT; struct argv_array argv = ARGV_ARRAY_INIT; - struct string_list_item *name_for_path; const char *work_tree = get_git_work_tree(); if (!work_tree) goto out; @@ -718,24 +646,26 @@ int fetch_populated_submodules(const struct argv_array *options, struct strbuf submodule_git_dir = STRBUF_INIT; struct strbuf submodule_prefix = STRBUF_INIT; const struct cache_entry *ce = active_cache[i]; - const char *git_dir, *name, *default_argv; + const char *git_dir, *default_argv; + const struct submodule *submodule; if (!S_ISGITLINK(ce->ce_mode)) continue; - name = ce->name; - name_for_path = unsorted_string_list_lookup(&config_name_for_path, ce->name); - if (name_for_path) - name = name_for_path->util; + submodule = submodule_from_path(null_sha1, ce->name); + if (!submodule) + submodule = submodule_from_name(null_sha1, ce->name); default_argv = "yes"; if (command_line_option == RECURSE_SUBMODULES_DEFAULT) { - struct string_list_item *fetch_recurse_submodules_option; - fetch_recurse_submodules_option = unsorted_string_list_lookup(&config_fetch_recurse_submodules_for_name, name); - if (fetch_recurse_submodules_option) { - if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_OFF) + if (submodule && + submodule->fetch_recurse != + RECURSE_SUBMODULES_NONE) { + if (submodule->fetch_recurse == + RECURSE_SUBMODULES_OFF) continue; - if ((intptr_t)fetch_recurse_submodules_option->util == RECURSE_SUBMODULES_ON_DEMAND) { + if (submodule->fetch_recurse == + RECURSE_SUBMODULES_ON_DEMAND) { if (!unsorted_string_list_lookup(&changed_submodule_paths, ce->name)) continue; default_argv = "on-demand"; diff --git a/submodule.h b/submodule.h index 7beec4822b..5507c3d9a0 100644 --- a/submodule.h +++ b/submodule.h @@ -5,6 +5,8 @@ struct diff_options; struct argv_array; enum { + RECURSE_SUBMODULES_ERROR = -3, + RECURSE_SUBMODULES_NONE = -2, RECURSE_SUBMODULES_ON_DEMAND = -1, RECURSE_SUBMODULES_OFF = 0, RECURSE_SUBMODULES_DEFAULT = 1, @@ -19,9 +21,7 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(void); -int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); -int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, unsigned char one[20], unsigned char two[20], diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh new file mode 100755 index 0000000000..fc97c3314e --- /dev/null +++ b/t/t7411-submodule-config.sh @@ -0,0 +1,153 @@ +#!/bin/sh +# +# Copyright (c) 2014 Heiko Voigt +# + +test_description='Test submodules config cache infrastructure + +This test verifies that parsing .gitmodules configurations directly +from the database and from the worktree works. +' + +TEST_NO_CREATE_REPO=1 +. ./test-lib.sh + +test_expect_success 'submodule config cache setup' ' + mkdir submodule && + (cd submodule && + git init && + echo a >a && + git add . && + git commit -ma + ) && + mkdir super && + (cd super && + git init && + git submodule add ../submodule && + git submodule add ../submodule a && + git commit -m "add as submodule and as a" && + git mv a b && + git commit -m "move a to b" + ) +' + +cat >super/expect <actual && + test_cmp expect actual + ) +' + +test_expect_success 'test parsing and lookup of submodule config by name' ' + (cd super && + test-submodule-config --name \ + HEAD^ a \ + HEAD a \ + HEAD^ submodule \ + HEAD submodule \ + >actual && + test_cmp expect actual + ) +' + +cat >super/expect_error <>.gitmodules && + git add .gitmodules && + mv .gitmodules.bak .gitmodules && + git commit -m "add error" && + test-submodule-config \ + HEAD b \ + HEAD submodule \ + >actual && + test_cmp expect_error actual + ) +' + +cat >super/expect_url <super/expect_local_path <actual && + test_cmp expect_url actual && + git config submodule.a.path c && + test-submodule-config \ + "" c \ + "" submodule \ + >actual && + test_cmp expect_local_path actual && + git config submodule.a.url $old_a && + git config submodule.submodule.url $old_submodule && + git config --unset submodule.a.path c + ) +' + +cat >super/expect_fetchrecurse_die.err <actual.out 2>actual.err && + touch expect_fetchrecurse_die.out && + test_cmp expect_fetchrecurse_die.out actual.out && + test_cmp expect_fetchrecurse_die.err actual.err && + git config --unset submodule.submodule.fetchrecursesubmodules + ) +' + +test_expect_success 'error in history in fetchrecursesubmodule lets continue' ' + (cd super && + git config -f .gitmodules \ + submodule.submodule.fetchrecursesubmodules blabla && + git add .gitmodules && + git config --unset -f .gitmodules \ + submodule.submodule.fetchrecursesubmodules && + git commit -m "add error in fetchrecursesubmodules" && + test-submodule-config \ + HEAD b \ + HEAD submodule \ + >actual && + test_cmp expect_error actual && + git reset --hard HEAD^ + ) +' + +test_done diff --git a/test-submodule-config.c b/test-submodule-config.c new file mode 100644 index 0000000000..dab8c27768 --- /dev/null +++ b/test-submodule-config.c @@ -0,0 +1,76 @@ +#include "cache.h" +#include "submodule-config.h" +#include "submodule.h" + +static void die_usage(int argc, char **argv, const char *msg) +{ + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "Usage: %s [ ] ...\n", argv[0]); + exit(1); +} + +static int git_test_config(const char *var, const char *value, void *cb) +{ + return parse_submodule_config_option(var, value); +} + +int main(int argc, char **argv) +{ + char **arg = argv; + int my_argc = argc; + int output_url = 0; + int lookup_name = 0; + + arg++; + my_argc--; + while (starts_with(arg[0], "--")) { + if (!strcmp(arg[0], "--url")) + output_url = 1; + if (!strcmp(arg[0], "--name")) + lookup_name = 1; + arg++; + my_argc--; + } + + if (my_argc % 2 != 0) + die_usage(argc, argv, "Wrong number of arguments."); + + setup_git_directory(); + gitmodules_config(); + git_config(git_test_config, NULL); + + while (*arg) { + unsigned char commit_sha1[20]; + const struct submodule *submodule; + const char *commit; + const char *path_or_name; + + commit = arg[0]; + path_or_name = arg[1]; + + if (commit[0] == '\0') + hashcpy(commit_sha1, null_sha1); + else if (get_sha1(commit, commit_sha1) < 0) + die_usage(argc, argv, "Commit not found."); + + if (lookup_name) { + submodule = submodule_from_name(commit_sha1, path_or_name); + } else + submodule = submodule_from_path(commit_sha1, path_or_name); + if (!submodule) + die_usage(argc, argv, "Submodule not found."); + + if (output_url) + printf("Submodule url: '%s' for path '%s'\n", + submodule->url, submodule->path); + else + printf("Submodule name: '%s' for path '%s'\n", + submodule->name, submodule->path); + + arg += 2; + } + + submodule_free(); + + return 0; +}