Merge branch 'hv/submodule-config'
authorJunio C Hamano <gitster@pobox.com>
Mon, 31 Aug 2015 22:38:52 +0000 (15:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 31 Aug 2015 22:38:52 +0000 (15:38 -0700)
The gitmodules API accessed from the C code learned to cache stuff
lazily.

* hv/submodule-config:
submodule: allow erroneous values for the fetchRecurseSubmodules option
submodule: use new config API for worktree configurations
submodule: extract functions for config set and lookup
submodule: implement a config API for lookup of .gitmodules values

12 files changed:
.gitignore
Documentation/technical/api-submodule-config.txt [new file with mode: 0644]
Makefile
builtin/checkout.c
builtin/fetch.c
diff.c
submodule-config.c [new file with mode: 0644]
submodule-config.h [new file with mode: 0644]
submodule.c
submodule.h
t/t7411-submodule-config.sh [new file with mode: 0755]
test-submodule-config.c [new file with mode: 0644]
index a685ec1fb0ca49607431a65f1ccf035bb9b95a3a..4fd81baf856669fb984a5a0b82a1115e840fc16d 100644 (file)
 /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 (file)
index 0000000..941fa17
--- /dev/null
@@ -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.
index 34101e252bcf80cbf151b58e359e604ad2ec365d..e326fa09c0ac3f1b8700495ea7a1bf007aa170d6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -593,6 +593,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
@@ -784,6 +785,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 += tempfile.o
index 52d59eba80048f507267002a6a88135ca46376b8..bc703c0f5ed9644b2380ed1f2e20b47238c80e5a 100644 (file)
@@ -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[] = {
index d3a08545ad66b518a88ff94d21af4234391a986f..9a3869f4ffd81f84f738e54c6405aaedb2514f18 100644 (file)
@@ -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 976362a8ce0ae46160d44fd8bbabef4050769dc9..08508f6a2017eb35a350268ea78a44cfc1d867e4 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -14,6 +14,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 (file)
index 0000000..393de53
--- /dev/null
@@ -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,
+                       &parameter);
+       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, &parameter);
+}
+
+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 (file)
index 0000000..9061e4e
--- /dev/null
@@ -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 */
index 700bbf4fcb4cd93b79304d934bed8901c63a853a..9fcc86faa2ca48223eeaeaabc334d3f94d771c92 100644 (file)
@@ -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(&sect, "submodule.");
-       strbuf_addstr(&sect, path_option->util);
+       strbuf_addstr(&sect, 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 @@ static int add_submodule_odb(const char *path)
 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";
index 7beec4822b9a35a6286a303f6cf877be1c734568..5507c3d9a098b1da016855c1fa4233ffc6e0a8cf 100644 (file)
@@ -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 (executable)
index 0000000..fc97c33
--- /dev/null
@@ -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 <<EOF
+Submodule name: 'a' for path 'a'
+Submodule name: 'a' for path 'b'
+Submodule name: 'submodule' for path 'submodule'
+Submodule name: 'submodule' for path 'submodule'
+EOF
+
+test_expect_success 'test parsing and lookup of submodule config by path' '
+       (cd super &&
+               test-submodule-config \
+                       HEAD^ a \
+                       HEAD b \
+                       HEAD^ submodule \
+                       HEAD submodule \
+                               >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 <<EOF
+Submodule name: 'a' for path 'b'
+Submodule name: 'submodule' for path 'submodule'
+EOF
+
+test_expect_success 'error in one submodule config lets continue' '
+       (cd super &&
+               cp .gitmodules .gitmodules.bak &&
+               echo "  value = \"" >>.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 <<EOF
+Submodule url: 'git@somewhere.else.net:a.git' for path 'b'
+Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule'
+EOF
+
+cat >super/expect_local_path <<EOF
+Submodule name: 'a' for path 'c'
+Submodule name: 'submodule' for path 'submodule'
+EOF
+
+test_expect_success 'reading of local configuration' '
+       (cd super &&
+               old_a=$(git config submodule.a.url) &&
+               old_submodule=$(git config submodule.submodule.url) &&
+               git config submodule.a.url git@somewhere.else.net:a.git &&
+               git config submodule.submodule.url git@somewhere.else.net:submodule.git &&
+               test-submodule-config --url \
+                       "" b \
+                       "" submodule \
+                               >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 <<EOF
+fatal: bad submodule.submodule.fetchrecursesubmodules argument: blabla
+EOF
+
+test_expect_success 'local error in fetchrecursesubmodule dies early' '
+       (cd super &&
+               git config submodule.submodule.fetchrecursesubmodules blabla &&
+               test_must_fail test-submodule-config \
+                       "" b \
+                       "" submodule \
+                               >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 (file)
index 0000000..dab8c27
--- /dev/null
@@ -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 [<commit> <submodulepath>] ...\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;
+}