/* * Copyright (C) 2012 the libgit2 contributors * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "git2/config.h" #include "git2/types.h" #include "git2/repository.h" #include "git2/index.h" #include "git2/submodule.h" #include "buffer.h" #include "vector.h" #include "posix.h" #include "config_file.h" #include "config.h" #include "repository.h" static git_cvar_map _sm_update_map[] = { {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE} }; static git_cvar_map _sm_ignore_map[] = { {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE} }; static inline khint_t str_hash_no_trailing_slash(const char *s) { khint_t h; for (h = 0; *s; ++s) if (s[1] || *s != '/') h = (h << 5) - h + *s; return h; } static inline int str_equal_no_trailing_slash(const char *a, const char *b) { size_t alen = a ? strlen(a) : 0; size_t blen = b ? strlen(b) : 0; if (alen && a[alen] == '/') alen--; if (blen && b[blen] == '/') blen--; return (alen == blen && strncmp(a, b, alen) == 0); } __KHASH_IMPL(str, static inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash); static git_submodule *submodule_alloc(const char *name) { git_submodule *sm = git__calloc(1, sizeof(git_submodule)); if (sm == NULL) return sm; sm->path = sm->name = git__strdup(name); if (!sm->name) { git__free(sm); return NULL; } return sm; } static void submodule_release(git_submodule *sm, int decr) { if (!sm) return; sm->refcount -= decr; if (sm->refcount == 0) { if (sm->name != sm->path) git__free(sm->path); git__free(sm->name); git__free(sm->url); git__free(sm); } } static int submodule_from_entry( git_strmap *smcfg, git_index_entry *entry) { git_submodule *sm; void *old_sm; khiter_t pos; int error; pos = git_strmap_lookup_index(smcfg, entry->path); if (git_strmap_valid_index(smcfg, pos)) sm = git_strmap_value_at(smcfg, pos); else sm = submodule_alloc(entry->path); git_oid_cpy(&sm->oid, &entry->oid); if (strcmp(sm->path, entry->path) != 0) { if (sm->path != sm->name) { git__free(sm->path); sm->path = sm->name; } sm->path = git__strdup(entry->path); if (!sm->path) goto fail; } git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); if (error < 0) goto fail; sm->refcount++; if (old_sm && ((git_submodule *)old_sm) != sm) { /* TODO: log warning about multiple entrys for same submodule path */ submodule_release(old_sm, 1); } return 0; fail: submodule_release(sm, 0); return -1; } static int submodule_from_config( const char *key, const char *value, void *data) { git_strmap *smcfg = data; const char *namestart; const char *property; git_buf name = GIT_BUF_INIT; git_submodule *sm; void *old_sm = NULL; bool is_path; khiter_t pos; int error; if (git__prefixcmp(key, "submodule.") != 0) return 0; namestart = key + strlen("submodule."); property = strrchr(namestart, '.'); if (property == NULL) return 0; property++; is_path = (strcmp(property, "path") == 0); if (git_buf_set(&name, namestart, property - namestart - 1) < 0) return -1; pos = git_strmap_lookup_index(smcfg, name.ptr); if (!git_strmap_valid_index(smcfg, pos) && is_path) pos = git_strmap_lookup_index(smcfg, value); if (!git_strmap_valid_index(smcfg, pos)) sm = submodule_alloc(name.ptr); else sm = git_strmap_value_at(smcfg, pos); if (!sm) goto fail; if (strcmp(sm->name, name.ptr) != 0) { assert(sm->path == sm->name); sm->name = git_buf_detach(&name); git_strmap_insert2(smcfg, sm->name, sm, old_sm, error); if (error < 0) goto fail; sm->refcount++; } else if (is_path && strcmp(sm->path, value) != 0) { assert(sm->path == sm->name); sm->path = git__strdup(value); if (sm->path == NULL) goto fail; git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); if (error < 0) goto fail; sm->refcount++; } git_buf_free(&name); if (old_sm && ((git_submodule *)old_sm) != sm) { /* TODO: log warning about multiple submodules with same path */ submodule_release(old_sm, 1); } if (is_path) return 0; /* copy other properties into submodule entry */ if (strcmp(property, "url") == 0) { if (sm->url) { git__free(sm->url); sm->url = NULL; } if ((sm->url = git__strdup(value)) == NULL) goto fail; } else if (strcmp(property, "update") == 0) { int val; if (git_config_lookup_map_value( _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) { giterr_set(GITERR_INVALID, "Invalid value for submodule update property: '%s'", value); goto fail; } sm->update = (git_submodule_update_t)val; } else if (strcmp(property, "fetchRecurseSubmodules") == 0) { if (git__parse_bool(&sm->fetch_recurse, value) < 0) { giterr_set(GITERR_INVALID, "Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value); goto fail; } } else if (strcmp(property, "ignore") == 0) { int val; if (git_config_lookup_map_value( _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) { giterr_set(GITERR_INVALID, "Invalid value for submodule ignore property: '%s'", value); goto fail; } sm->ignore = (git_submodule_ignore_t)val; } /* ignore other unknown submodule properties */ return 0; fail: submodule_release(sm, 0); git_buf_free(&name); return -1; } static int load_submodule_config(git_repository *repo) { int error; git_index *index; unsigned int i, max_i; git_oid gitmodules_oid; git_strmap *smcfg; struct git_config_file *mods = NULL; if (repo->submodules) return 0; /* submodule data is kept in a hashtable with each submodule stored * under both its name and its path. These are usually the same, but * that is not guaranteed. */ smcfg = git_strmap_alloc(); GITERR_CHECK_ALLOC(smcfg); /* scan index for gitmodules (and .gitmodules entry) */ if ((error = git_repository_index__weakptr(&index, repo)) < 0) goto cleanup; memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); max_i = git_index_entrycount(index); for (i = 0; i < max_i; i++) { git_index_entry *entry = git_index_get(index, i); if (S_ISGITLINK(entry->mode)) { if ((error = submodule_from_entry(smcfg, entry)) < 0) goto cleanup; } else if (strcmp(entry->path, ".gitmodules") == 0) git_oid_cpy(&gitmodules_oid, &entry->oid); } /* load .gitmodules from workdir if it exists */ if (git_repository_workdir(repo) != NULL) { /* look in workdir for .gitmodules */ git_buf path = GIT_BUF_INIT; if (!git_buf_joinpath( &path, git_repository_workdir(repo), ".gitmodules") && git_path_isfile(path.ptr)) { if (!(error = git_config_file__ondisk(&mods, path.ptr))) error = git_config_file_open(mods); } git_buf_free(&path); } /* load .gitmodules from object cache if not in workdir */ if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) { /* TODO: is it worth loading gitmodules from object cache? */ } /* process .gitmodules info */ if (!error && mods != NULL) error = git_config_file_foreach(mods, submodule_from_config, smcfg); /* store submodule config in repo */ if (!error) repo->submodules = smcfg; cleanup: if (mods != NULL) git_config_file_free(mods); if (error) git_strmap_free(smcfg); return error; } void git_submodule_config_free(git_repository *repo) { git_strmap *smcfg = repo->submodules; git_submodule *sm; repo->submodules = NULL; if (smcfg == NULL) return; git_strmap_foreach_value(smcfg, sm, { submodule_release(sm,1); }); git_strmap_free(smcfg); } static int submodule_cmp(const void *a, const void *b) { return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); } int git_submodule_foreach( git_repository *repo, int (*callback)(const char *name, void *payload), void *payload) { int error; git_submodule *sm; git_vector seen = GIT_VECTOR_INIT; seen._cmp = submodule_cmp; if ((error = load_submodule_config(repo)) < 0) return error; git_strmap_foreach_value(repo->submodules, sm, { /* usually the following will not come into play */ if (sm->refcount > 1) { if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) continue; if ((error = git_vector_insert(&seen, sm)) < 0) break; } if ((error = callback(sm->name, payload)) < 0) break; }); git_vector_free(&seen); return error; } int git_submodule_lookup( git_submodule **sm_ptr, /* NULL allowed if user only wants to test */ git_repository *repo, const char *name) /* trailing slash is allowed */ { khiter_t pos; if (load_submodule_config(repo) < 0) return -1; pos = git_strmap_lookup_index(repo->submodules, name); if (!git_strmap_valid_index(repo->submodules, pos)) return GIT_ENOTFOUND; if (sm_ptr) *sm_ptr = git_strmap_value_at(repo->submodules, pos); return 0; }