diff options
author | Carlos Martín Nieto <cmn@dwim.me> | 2015-09-13 06:03:12 +0200 |
---|---|---|
committer | Carlos Martín Nieto <cmn@dwim.me> | 2015-09-13 06:03:12 +0200 |
commit | 305407e1bb7a097aba7a0304397bf280437b608e (patch) | |
tree | 57e7b381e703f823bbb3ca39cfaa97a263dc1046 | |
parent | dc5b678fda918386f01630d2bc928edb546ac4f2 (diff) | |
parent | a3b9731ff8846870a4c5124cd57d2d797913ad1f (diff) | |
download | libgit2-305407e1bb7a097aba7a0304397bf280437b608e.tar.gz |
Merge pull request #3370 from libgit2/cmn/submodule-refactor
submodule: refactor to be more explicit in the search
-rw-r--r-- | src/submodule.c | 363 | ||||
-rw-r--r-- | tests/submodule/lookup.c | 55 |
2 files changed, 274 insertions, 144 deletions
diff --git a/src/submodule.c b/src/submodule.c index 7f52c3616..1d73dc24e 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -89,9 +89,11 @@ __KHASH_IMPL( static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name); static git_config_backend *open_gitmodules(git_repository *repo, int gitmod); +static git_config *gitmodules_snapshot(git_repository *repo); static int get_url_base(git_buf *url, git_repository *repo); static int lookup_head_remote_key(git_buf *remote_key, git_repository *repo); -static int submodule_load_from_config(const git_config_entry *, void *); +static int submodule_load_each(const git_config_entry *entry, void *payload); +static int submodule_read_config(git_submodule *sm, git_config *cfg); static int submodule_load_from_wd_lite(git_submodule *); static void submodule_get_index_status(unsigned int *, git_submodule *); static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t); @@ -144,6 +146,41 @@ static int find_by_path(const git_config_entry *entry, void *payload) return 0; } +/** + * Find out the name of a submodule from its path + */ +static int name_from_path(git_buf *out, git_config *cfg, const char *path) +{ + const char *key = "submodule\\..*\\.path"; + git_config_iterator *iter; + git_config_entry *entry; + int error; + + if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) + return error; + + while ((error = git_config_next(&entry, iter)) == 0) { + const char *fdot, *ldot; + /* TODO: this should maybe be strcasecmp on a case-insensitive fs */ + if (strcmp(path, entry->value) != 0) + continue; + + fdot = strchr(entry->name, '.'); + ldot = strrchr(entry->name, '.'); + + git_buf_clear(out); + git_buf_put(out, fdot + 1, ldot - fdot - 1); + return 0; + } + + if (error == GIT_ITEROVER) { + giterr_set(GITERR_SUBMODULE, "could not find a submodule name for '%s'", path); + error = GIT_ENOTFOUND; + } + + return error; +} + int git_submodule_lookup( git_submodule **out, /* NULL if user only wants to test existence */ git_repository *repo, @@ -280,11 +317,12 @@ done: return 0; } -static int submodules_from_index(git_strmap *map, git_index *idx) +static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg) { int error; git_iterator *i; const git_index_entry *entry; + git_buf name = GIT_BUF_INIT; if ((error = git_iterator_for_index(&i, idx, NULL)) < 0) return error; @@ -293,6 +331,11 @@ static int submodules_from_index(git_strmap *map, git_index *idx) khiter_t pos = git_strmap_lookup_index(map, entry->path); git_submodule *sm; + git_buf_clear(&name); + if (!name_from_path(&name, cfg, entry->path)) { + git_strmap_lookup_index(map, name.ptr); + } + if (git_strmap_valid_index(map, pos)) { sm = git_strmap_value_at(map, pos); @@ -301,7 +344,7 @@ static int submodules_from_index(git_strmap *map, git_index *idx) else sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; } else if (S_ISGITLINK(entry->mode)) { - if (!submodule_get_or_create(&sm, git_index_owner(idx), map, entry->path)) { + if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name.ptr ? name.ptr : entry->path)) { submodule_update_from_index_entry(sm, entry); git_submodule_free(sm); } @@ -311,16 +354,18 @@ static int submodules_from_index(git_strmap *map, git_index *idx) if (error == GIT_ITEROVER) error = 0; + git_buf_free(&name); git_iterator_free(i); return error; } -static int submodules_from_head(git_strmap *map, git_tree *head) +static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg) { int error; git_iterator *i; const git_index_entry *entry; + git_buf name = GIT_BUF_INIT; if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) return error; @@ -329,6 +374,11 @@ static int submodules_from_head(git_strmap *map, git_tree *head) khiter_t pos = git_strmap_lookup_index(map, entry->path); git_submodule *sm; + git_buf_clear(&name); + if (!name_from_path(&name, cfg, entry->path)) { + git_strmap_lookup_index(map, name.ptr); + } + if (git_strmap_valid_index(map, pos)) { sm = git_strmap_value_at(map, pos); @@ -337,7 +387,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) else sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; } else if (S_ISGITLINK(entry->mode)) { - if (!submodule_get_or_create(&sm, git_tree_owner(head), map, entry->path)) { + if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name.ptr ? name.ptr : entry->path)) { submodule_update_from_head_data( sm, entry->mode, &entry->id); git_submodule_free(sm); @@ -348,6 +398,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) if (error == GIT_ITEROVER) error = 0; + git_buf_free(&name); git_iterator_free(i); return error; @@ -355,8 +406,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) /* If have_sm is true, sm is populated, otherwise map an repo are. */ typedef struct { - int have_sm; - git_submodule *sm; + git_config *mods; git_strmap *map; git_repository *repo; } lfc_data; @@ -369,7 +419,7 @@ static int all_submodules(git_repository *repo, git_strmap *map) const char *wd = NULL; git_buf path = GIT_BUF_INIT; git_submodule *sm; - git_config_backend *mods = NULL; + git_config *mods = NULL; uint32_t mask; assert(repo && map); @@ -400,24 +450,28 @@ static int all_submodules(git_repository *repo, git_strmap *map) GIT_SUBMODULE_STATUS__WD_FLAGS | GIT_SUBMODULE_STATUS__WD_OID_VALID; + /* add submodule information from .gitmodules */ + if (wd) { + lfc_data data = { 0 }; + data.map = map; + data.repo = repo; + + if ((mods = gitmodules_snapshot(repo)) == NULL) + goto cleanup; + + data.mods = mods; + if ((error = git_config_foreach( + mods, submodule_load_each, &data)) < 0) + goto cleanup; + } /* add back submodule information from index */ if (idx) { - if ((error = submodules_from_index(map, idx)) < 0) + if ((error = submodules_from_index(map, idx, mods)) < 0) goto cleanup; } /* add submodule information from HEAD */ if (head) { - if ((error = submodules_from_head(map, head)) < 0) - goto cleanup; - } - /* add submodule information from .gitmodules */ - if (wd) { - lfc_data data = { 0 }; - data.map = map; - data.repo = repo; - if ((mods = open_gitmodules(repo, false)) != NULL && - (error = git_config_file_foreach( - mods, submodule_load_from_config, &data)) < 0) + if ((error = submodules_from_head(map, head, mods)) < 0) goto cleanup; } /* shallow scan submodules in work tree as needed */ @@ -428,7 +482,7 @@ static int all_submodules(git_repository *repo, git_strmap *map) } cleanup: - git_config_file_free(mods); + git_config_free(mods); /* TODO: if we got an error, mark submodule config as invalid? */ git_index_free(idx); git_tree_free(head); @@ -1370,8 +1424,7 @@ static int submodule_update_head(git_submodule *submodule) int git_submodule_reload(git_submodule *sm, int force) { int error = 0; - git_config_backend *mods; - lfc_data data = { 0 }; + git_config *mods; GIT_UNUSED(force); @@ -1390,28 +1443,14 @@ int git_submodule_reload(git_submodule *sm, int force) return error; /* refresh config data */ - mods = open_gitmodules(sm->repo, GITMODULES_EXISTING); + mods = gitmodules_snapshot(sm->repo); if (mods != NULL) { - git_buf path = GIT_BUF_INIT; - - git_buf_sets(&path, "submodule\\."); - git_buf_text_puts_escape_regex(&path, sm->name); - git_buf_puts(&path, "\\..*"); - - if (git_buf_oom(&path)) { - error = -1; - } else { - data.have_sm = 1; - data.sm = sm; - error = git_config_file_foreach_match( - mods, path.ptr, submodule_load_from_config, &data); - } + error = submodule_read_config(sm, mods); + git_config_free(mods); - git_buf_free(&path); - git_config_file_free(mods); - - if (error < 0) + if (error < 0) { return error; + } } /* refresh wd data */ @@ -1627,134 +1666,141 @@ int git_submodule_parse_recurse(git_submodule_recurse_t *out, const char *value) return 0; } -static int submodule_load_from_config( - const git_config_entry *entry, void *payload) +static int get_value(const char **out, git_config *cfg, git_buf *buf, const char *name, const char *field) { - const char *namestart, *property; - const char *key = entry->name, *value = entry->value, *path; - char *alternate = NULL, *replaced = NULL; - git_buf name = GIT_BUF_INIT; - lfc_data *data = payload; - git_submodule *sm; - int error = 0; - - if (git__prefixcmp(key, "submodule.") != 0) - return 0; - - namestart = key + strlen("submodule."); - property = strrchr(namestart, '.'); - - if (!property || (property == namestart)) - return 0; - - property++; - path = !strcasecmp(property, "path") ? value : NULL; + int error; - if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0) - goto done; + git_buf_clear(buf); - if (data->have_sm) { - sm = data->sm; - } else { - khiter_t pos; - git_strmap *map = data->map; - pos = git_strmap_lookup_index(map, path ? path : name.ptr); - if (git_strmap_valid_index(map, pos)) { - sm = git_strmap_value_at(map, pos); - } else { - if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) - goto done; + if ((error = git_buf_printf(buf, "submodule.%s.%s", name, field)) < 0 || + (error = git_config_get_string(out, cfg, buf->ptr)) < 0) + return error; - git_strmap_insert(map, sm->name, sm, error); - assert(error != 0); - if (error < 0) - goto done; - error = 0; - } - } + return error; +} - sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; +static int submodule_read_config(git_submodule *sm, git_config *cfg) +{ + git_buf key = GIT_BUF_INIT; + const char *value; + int error, in_config = 0; - /* Only from config might we get differing names & paths. If so, then - * update the submodule and insert under the alternative key. + /* + * TODO: Look up path in index and if it is present but not a GITLINK + * then this should be deleted (at least to match git's behavior) */ - /* TODO: if case insensitive filesystem, then the following strcmps + if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) { + in_config = 1; + /* + * TODO: if case insensitive filesystem, then the following strcmp * should be strcasecmp */ - - if (strcmp(sm->name, name.ptr) != 0) { /* name changed */ - if (sm->path && !strcmp(sm->path, name.ptr)) { /* already set as path */ - replaced = sm->name; - sm->name = sm->path; - } else { - if (sm->name != sm->path) - replaced = sm->name; - alternate = sm->name = git_buf_detach(&name); - } - } - else if (path && strcmp(path, sm->path) != 0) { /* path changed */ - if (!strcmp(sm->name, value)) { /* already set as name */ - replaced = sm->path; - sm->path = sm->name; - } else { - if (sm->path != sm->name) - replaced = sm->path; - if ((alternate = git__strdup(value)) == NULL) { - error = -1; - goto done; - } - sm->path = alternate; + if (strcmp(sm->name, value) != 0) { + sm->path = git__strdup(value); + GITERR_CHECK_ALLOC(sm->path); } + } else if (error != GIT_ENOTFOUND) { + return error; } - /* Deregister under name being replaced */ - if (replaced) { - git__free(replaced); + if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { + in_config = 1; + sm->url = git__strdup(value); + GITERR_CHECK_ALLOC(sm->url); + } else if (error != GIT_ENOTFOUND) { + return error; } - /* TODO: Look up path in index and if it is present but not a GITLINK - * then this should be deleted (at least to match git's behavior) - */ - - if (path) - goto done; - - /* copy other properties into submodule entry */ - if (strcasecmp(property, "url") == 0) { - git__free(sm->url); - sm->url = NULL; - - if (value != NULL && (sm->url = git__strdup(value)) == NULL) { - error = -1; - goto done; - } + if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) { + in_config = 1; + sm->branch = git__strdup(value); + GITERR_CHECK_ALLOC(sm->branch); + } else if (error != GIT_ENOTFOUND) { + return error; } - else if (strcasecmp(property, "branch") == 0) { - git__free(sm->branch); - sm->branch = NULL; - if (value != NULL && (sm->branch = git__strdup(value)) == NULL) { - error = -1; - goto done; - } - } - else if (strcasecmp(property, "update") == 0) { + if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) { + in_config = 1; if ((error = git_submodule_parse_update(&sm->update, value)) < 0) - goto done; + return error; sm->update_default = sm->update; + } else if (error != GIT_ENOTFOUND) { + return error; } - else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) { + + if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) { + in_config = 1; if ((error = git_submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) - goto done; + return error; sm->fetch_recurse_default = sm->fetch_recurse; + } else if (error != GIT_ENOTFOUND) { + return error; } - else if (strcasecmp(property, "ignore") == 0) { + + if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) { + in_config = 1; if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) - goto done; + return error; sm->ignore_default = sm->ignore; + } else if (error != GIT_ENOTFOUND) { + return error; } - /* ignore other unknown submodule properties */ + + if (in_config) + sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; + + return 0; +} + +static int submodule_load_each(const git_config_entry *entry, void *payload) +{ + lfc_data *data = payload; + const char *namestart, *property; + git_strmap_iter pos; + git_strmap *map = data->map; + git_buf name = GIT_BUF_INIT; + git_submodule *sm; + int error; + + if (git__prefixcmp(entry->name, "submodule.") != 0) + return 0; + + namestart = entry->name + strlen("submodule."); + property = strrchr(namestart, '.'); + + if (!property || (property == namestart)) + return 0; + + property++; + + if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0) + return error; + + /* + * Now that we have the submodule's name, we can use that to + * figure out whether it's in the map. If it's not, we create + * a new submodule, load the config and insert it. If it's + * already inserted, we've already loaded it, so we skip. + */ + pos = git_strmap_lookup_index(map, name.ptr); + if (git_strmap_valid_index(map, pos)) + return 0; + + if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) + goto done; + + if ((error = submodule_read_config(sm, data->mods)) < 0) { + git_submodule_free(sm); + goto done; + } + + git_strmap_insert(map, sm->name, sm, error); + assert(error != 0); + if (error < 0) + goto done; + + error = 0; done: git_buf_free(&name); @@ -1778,6 +1824,35 @@ static int submodule_load_from_wd_lite(git_submodule *sm) return 0; } +/** + * Returns a snapshot of $WORK_TREE/.gitmodules. + * + * We ignore any errors and just pretend the file isn't there. + */ +static git_config *gitmodules_snapshot(git_repository *repo) +{ + const char *workdir = git_repository_workdir(repo); + git_config *mods = NULL, *snap = NULL; + git_buf path = GIT_BUF_INIT; + + if (workdir != NULL) { + if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0) + return NULL; + + if (git_config_open_ondisk(&mods, path.ptr) < 0) + mods = NULL; + } + + git_buf_free(&path); + + if (mods) { + git_config_snapshot(&snap, mods); + git_config_free(mods); + } + + return snap; +} + static git_config_backend *open_gitmodules( git_repository *repo, int okay_to_create) diff --git a/tests/submodule/lookup.c b/tests/submodule/lookup.c index ecea694e5..5f1614871 100644 --- a/tests/submodule/lookup.c +++ b/tests/submodule/lookup.c @@ -333,3 +333,58 @@ void test_submodule_lookup__prefix_name(void) git_submodule_free(sm); } + +void test_submodule_lookup__renamed(void) +{ + const char *newpath = "sm_actually_changed"; + git_index *idx; + sm_lookup_data data; + + cl_git_pass(git_repository_index__weakptr(&idx, g_repo)); + + /* We're replicating 'git mv sm_unchanged sm_actually_changed' in this test */ + + cl_git_pass(p_rename("submod2/sm_unchanged", "submod2/sm_actually_changed")); + + /* Change the path in .gitmodules and stage it*/ + { + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); + cl_git_pass(git_config_set_string(cfg, "submodule.sm_unchanged.path", newpath)); + git_config_free(cfg); + + cl_git_pass(git_index_add_bypath(idx, ".gitmodules")); + } + + /* Change the worktree info in the the submodule's config */ + { + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.git/modules/sm_unchanged/config")); + cl_git_pass(git_config_set_string(cfg, "core.worktree", "../../../sm_actually_changed")); + git_config_free(cfg); + } + + /* Rename the entry in the index */ + { + const git_index_entry *e; + git_index_entry entry = { 0 }; + + e = git_index_get_bypath(idx, "sm_unchanged", 0); + cl_assert(e); + cl_assert_equal_i(GIT_FILEMODE_COMMIT, e->mode); + + entry.path = newpath; + entry.mode = GIT_FILEMODE_COMMIT; + git_oid_cpy(&entry.id, &e->id); + + cl_git_pass(git_index_remove(idx, "sm_unchanged", 0)); + cl_git_pass(git_index_add(idx, &entry)); + cl_git_pass(git_index_write(idx)); + } + + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(8, data.count); +} |