blob: 3c07e657d101d3407b6715250378a3f7395f7617 [file] [log] [blame]
/*
* 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;
}