blob: d0ef16b10034ce3890a06cba6e48f27091d8bd08 [file] [log] [blame]
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* 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/sys/config.h"
#include "git2/types.h"
#include "git2/repository.h"
#include "git2/index.h"
#include "git2/submodule.h"
#include "buffer.h"
#include "buf_text.h"
#include "vector.h"
#include "posix.h"
#include "config_file.h"
#include "config.h"
#include "repository.h"
#include "submodule.h"
#include "tree.h"
#include "iterator.h"
#include "path.h"
#include "index.h"
#define GIT_MODULES_FILE ".gitmodules"
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},
{GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE},
};
static git_cvar_map _sm_ignore_map[] = {
{GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE},
{GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
{GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
{GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
};
static kh_inline khint_t str_hash_no_trailing_slash(const char *s)
{
khint_t h;
for (h = 0; *s; ++s)
if (s[1] != '\0' || *s != '/')
h = (h << 5) - h + *s;
return h;
}
static kh_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 > 0 && a[alen - 1] == '/')
alen--;
if (blen > 0 && b[blen - 1] == '/')
blen--;
return (alen == blen && strncmp(a, b, alen) == 0);
}
__KHASH_IMPL(
str, static kh_inline, const char *, void *, 1,
str_hash_no_trailing_slash, str_equal_no_trailing_slash);
static int load_submodule_config(git_repository *repo);
static git_config_backend *open_gitmodules(git_repository *, bool, const git_oid *);
static int lookup_head_remote(git_buf *url, git_repository *repo);
static int submodule_get(git_submodule **, git_repository *, const char *, const char *);
static void submodule_release(git_submodule *sm, int decr);
static int submodule_load_from_index(git_repository *, const git_index_entry *);
static int submodule_load_from_head(git_repository*, const char*, const git_oid*);
static int submodule_load_from_config(const git_config_entry *, void *);
static int submodule_load_from_wd_lite(git_submodule *, const char *, void *);
static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool);
static void submodule_mode_mismatch(git_repository *, const char *, unsigned int);
static int submodule_index_status(unsigned int *status, git_submodule *sm);
static int submodule_wd_status(unsigned int *status, git_submodule *sm);
static int submodule_cmp(const void *a, const void *b)
{
return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
}
static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix)
{
ssize_t idx = git_buf_rfind(key, '.');
git_buf_truncate(key, (size_t)(idx + 1));
return git_buf_puts(key, suffix);
}
/*
* PUBLIC APIS
*/
int git_submodule_lookup(
git_submodule **sm_ptr, /* NULL if user only wants to test existence */
git_repository *repo,
const char *name) /* trailing slash is allowed */
{
int error;
khiter_t pos;
assert(repo && name);
if ((error = load_submodule_config(repo)) < 0)
return error;
pos = git_strmap_lookup_index(repo->submodules, name);
if (!git_strmap_valid_index(repo->submodules, pos)) {
error = GIT_ENOTFOUND;
/* check if a plausible submodule exists at path */
if (git_repository_workdir(repo)) {
git_buf path = GIT_BUF_INIT;
if (git_buf_joinpath(&path, git_repository_workdir(repo), name) < 0)
return -1;
if (git_path_contains_dir(&path, DOT_GIT))
error = GIT_EEXISTS;
git_buf_free(&path);
}
giterr_set(GITERR_SUBMODULE, (error == GIT_ENOTFOUND) ?
"No submodule named '%s'" :
"Submodule '%s' has not been added yet", name);
return error;
}
if (sm_ptr)
*sm_ptr = git_strmap_value_at(repo->submodules, pos);
return 0;
}
int git_submodule_foreach(
git_repository *repo,
int (*callback)(git_submodule *sm, const char *name, void *payload),
void *payload)
{
int error;
git_submodule *sm;
git_vector seen = GIT_VECTOR_INIT;
git_vector_set_cmp(&seen, submodule_cmp);
assert(repo && callback);
if ((error = load_submodule_config(repo)) < 0)
return error;
git_strmap_foreach_value(repo->submodules, sm, {
/* Usually the following will not come into play - it just prevents
* us from issuing a callback twice for a submodule where the name
* and path are not the same.
*/
if (sm->refcount > 1) {
if (git_vector_bsearch(NULL, &seen, sm) != GIT_ENOTFOUND)
continue;
if ((error = git_vector_insert(&seen, sm)) < 0)
break;
}
if (callback(sm, sm->name, payload)) {
giterr_clear();
error = GIT_EUSER;
break;
}
});
git_vector_free(&seen);
return error;
}
void git_submodule_config_free(git_repository *repo)
{
git_strmap *smcfg;
git_submodule *sm;
assert(repo);
smcfg = repo->submodules;
repo->submodules = NULL;
if (smcfg == NULL)
return;
git_strmap_foreach_value(smcfg, sm, {
submodule_release(sm,1);
});
git_strmap_free(smcfg);
}
int git_submodule_add_setup(
git_submodule **submodule,
git_repository *repo,
const char *url,
const char *path,
int use_gitlink)
{
int error = 0;
git_config_backend *mods = NULL;
git_submodule *sm;
git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT;
git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
git_repository *subrepo = NULL;
assert(repo && url && path);
/* see if there is already an entry for this submodule */
if (git_submodule_lookup(&sm, repo, path) < 0)
giterr_clear();
else {
giterr_set(GITERR_SUBMODULE,
"Attempt to add a submodule that already exists");
return GIT_EEXISTS;
}
/* resolve parameters */
if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) {
if (!(error = lookup_head_remote(&real_url, repo)))
error = git_path_apply_relative(&real_url, url);
} else if (strchr(url, ':') != NULL || url[0] == '/') {
error = git_buf_sets(&real_url, url);
} else {
giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL");
error = -1;
}
if (error)
goto cleanup;
/* validate and normalize path */
if (git__prefixcmp(path, git_repository_workdir(repo)) == 0)
path += strlen(git_repository_workdir(repo));
if (git_path_root(path) >= 0) {
giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path");
error = -1;
goto cleanup;
}
/* update .gitmodules */
if ((mods = open_gitmodules(repo, true, NULL)) == NULL) {
giterr_set(GITERR_SUBMODULE,
"Adding submodules to a bare repository is not supported (for now)");
return -1;
}
if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 ||
(error = git_config_file_set_string(mods, name.ptr, path)) < 0)
goto cleanup;
if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||
(error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0)
goto cleanup;
git_buf_clear(&name);
/* init submodule repository and add origin remote as needed */
error = git_buf_joinpath(&name, git_repository_workdir(repo), path);
if (error < 0)
goto cleanup;
/* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a
* gitlink in the sub-repo workdir directory to that repository
*
* Old style: sub-repo goes directly into repo/<name>/.git/
*/
initopt.flags = GIT_REPOSITORY_INIT_MKPATH |
GIT_REPOSITORY_INIT_NO_REINIT;
initopt.origin_url = real_url.ptr;
if (git_path_exists(name.ptr) &&
git_path_contains(&name, DOT_GIT))
{
/* repo appears to already exist - reinit? */
}
else if (use_gitlink) {
git_buf repodir = GIT_BUF_INIT;
error = git_buf_join_n(
&repodir, '/', 3, git_repository_path(repo), "modules", path);
if (error < 0)
goto cleanup;
initopt.workdir_path = name.ptr;
initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
git_buf_free(&repodir);
}
else {
error = git_repository_init_ext(&subrepo, name.ptr, &initopt);
}
if (error < 0)
goto cleanup;
/* add submodule to hash and "reload" it */
if (!(error = submodule_get(&sm, repo, path, NULL)) &&
!(error = git_submodule_reload(sm)))
error = git_submodule_init(sm, false);
cleanup:
if (submodule != NULL)
*submodule = !error ? sm : NULL;
if (mods != NULL)
git_config_file_free(mods);
git_repository_free(subrepo);
git_buf_free(&real_url);
git_buf_free(&name);
return error;
}
int git_submodule_add_finalize(git_submodule *sm)
{
int error;
git_index *index;
assert(sm);
if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 ||
(error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0)
return error;
return git_submodule_add_to_index(sm, true);
}
int git_submodule_add_to_index(git_submodule *sm, int write_index)
{
int error;
git_repository *repo, *sm_repo = NULL;
git_index *index;
git_buf path = GIT_BUF_INIT;
git_commit *head;
git_index_entry entry;
struct stat st;
assert(sm);
repo = sm->owner;
/* force reload of wd OID by git_submodule_open */
sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;
if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
(error = git_buf_joinpath(
&path, git_repository_workdir(repo), sm->path)) < 0 ||
(error = git_submodule_open(&sm_repo, sm)) < 0)
goto cleanup;
/* read stat information for submodule working directory */
if (p_stat(path.ptr, &st) < 0) {
giterr_set(GITERR_SUBMODULE,
"Cannot add submodule without working directory");
error = -1;
goto cleanup;
}
memset(&entry, 0, sizeof(entry));
entry.path = sm->path;
git_index_entry__init_from_stat(&entry, &st);
/* calling git_submodule_open will have set sm->wd_oid if possible */
if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {
giterr_set(GITERR_SUBMODULE,
"Cannot add submodule without HEAD to index");
error = -1;
goto cleanup;
}
git_oid_cpy(&entry.oid, &sm->wd_oid);
if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0)
goto cleanup;
entry.ctime.seconds = git_commit_time(head);
entry.ctime.nanoseconds = 0;
entry.mtime.seconds = git_commit_time(head);
entry.mtime.nanoseconds = 0;
git_commit_free(head);
/* add it */
error = git_index_add(index, &entry);
/* write it, if requested */
if (!error && write_index) {
error = git_index_write(index);
if (!error)
git_oid_cpy(&sm->index_oid, &sm->wd_oid);
}
cleanup:
git_repository_free(sm_repo);
git_buf_free(&path);
return error;
}
int git_submodule_save(git_submodule *submodule)
{
int error = 0;
git_config_backend *mods;
git_buf key = GIT_BUF_INIT;
assert(submodule);
mods = open_gitmodules(submodule->owner, true, NULL);
if (!mods) {
giterr_set(GITERR_SUBMODULE,
"Adding submodules to a bare repository is not supported (for now)");
return -1;
}
if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0)
goto cleanup;
/* save values for path, url, update, ignore, fetchRecurseSubmodules */
if ((error = submodule_config_key_trunc_puts(&key, "path")) < 0 ||
(error = git_config_file_set_string(mods, key.ptr, submodule->path)) < 0)
goto cleanup;
if ((error = submodule_config_key_trunc_puts(&key, "url")) < 0 ||
(error = git_config_file_set_string(mods, key.ptr, submodule->url)) < 0)
goto cleanup;
if (!(error = submodule_config_key_trunc_puts(&key, "update")) &&
submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)
{
const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
NULL : _sm_update_map[submodule->update].str_match;
error = git_config_file_set_string(mods, key.ptr, val);
}
if (error < 0)
goto cleanup;
if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) &&
submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT)
{
const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ?
NULL : _sm_ignore_map[submodule->ignore].str_match;
error = git_config_file_set_string(mods, key.ptr, val);
}
if (error < 0)
goto cleanup;
if ((error = submodule_config_key_trunc_puts(
&key, "fetchRecurseSubmodules")) < 0 ||
(error = git_config_file_set_string(
mods, key.ptr, submodule->fetch_recurse ? "true" : "false")) < 0)
goto cleanup;
/* update internal defaults */
submodule->ignore_default = submodule->ignore;
submodule->update_default = submodule->update;
submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
cleanup:
if (mods != NULL)
git_config_file_free(mods);
git_buf_free(&key);
return error;
}
git_repository *git_submodule_owner(git_submodule *submodule)
{
assert(submodule);
return submodule->owner;
}
const char *git_submodule_name(git_submodule *submodule)
{
assert(submodule);
return submodule->name;
}
const char *git_submodule_path(git_submodule *submodule)
{
assert(submodule);
return submodule->path;
}
const char *git_submodule_url(git_submodule *submodule)
{
assert(submodule);
return submodule->url;
}
int git_submodule_set_url(git_submodule *submodule, const char *url)
{
assert(submodule && url);
git__free(submodule->url);
submodule->url = git__strdup(url);
GITERR_CHECK_ALLOC(submodule->url);
return 0;
}
const git_oid *git_submodule_index_id(git_submodule *submodule)
{
assert(submodule);
if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID)
return &submodule->index_oid;
else
return NULL;
}
const git_oid *git_submodule_head_id(git_submodule *submodule)
{
assert(submodule);
if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID)
return &submodule->head_oid;
else
return NULL;
}
const git_oid *git_submodule_wd_id(git_submodule *submodule)
{
assert(submodule);
if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {
git_repository *subrepo;
/* calling submodule open grabs the HEAD OID if possible */
if (!git_submodule_open(&subrepo, submodule))
git_repository_free(subrepo);
else
giterr_clear();
}
if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)
return &submodule->wd_oid;
else
return NULL;
}
git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule)
{
assert(submodule);
return submodule->ignore;
}
git_submodule_ignore_t git_submodule_set_ignore(
git_submodule *submodule, git_submodule_ignore_t ignore)
{
git_submodule_ignore_t old;
assert(submodule);
if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT)
ignore = submodule->ignore_default;
old = submodule->ignore;
submodule->ignore = ignore;
return old;
}
git_submodule_update_t git_submodule_update(git_submodule *submodule)
{
assert(submodule);
return submodule->update;
}
git_submodule_update_t git_submodule_set_update(
git_submodule *submodule, git_submodule_update_t update)
{
git_submodule_update_t old;
assert(submodule);
if (update == GIT_SUBMODULE_UPDATE_DEFAULT)
update = submodule->update_default;
old = submodule->update;
submodule->update = update;
return old;
}
int git_submodule_fetch_recurse_submodules(
git_submodule *submodule)
{
assert(submodule);
return submodule->fetch_recurse;
}
int git_submodule_set_fetch_recurse_submodules(
git_submodule *submodule,
int fetch_recurse_submodules)
{
int old;
assert(submodule);
old = submodule->fetch_recurse;
submodule->fetch_recurse = (fetch_recurse_submodules != 0);
return old;
}
int git_submodule_init(git_submodule *submodule, int overwrite)
{
int error;
/* write "submodule.NAME.url" */
if (!submodule->url) {
giterr_set(GITERR_SUBMODULE,
"No URL configured for submodule '%s'", submodule->name);
return -1;
}
error = submodule_update_config(
submodule, "url", submodule->url, overwrite != 0, false);
if (error < 0)
return error;
/* write "submodule.NAME.update" if not default */
if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT)
error = submodule_update_config(
submodule, "update", NULL, (overwrite != 0), false);
else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)
error = submodule_update_config(
submodule, "update",
_sm_update_map[submodule->update].str_match,
(overwrite != 0), false);
return error;
}
int git_submodule_sync(git_submodule *submodule)
{
if (!submodule->url) {
giterr_set(GITERR_SUBMODULE,
"No URL configured for submodule '%s'", submodule->name);
return -1;
}
/* copy URL over to config only if it already exists */
return submodule_update_config(
submodule, "url", submodule->url, true, true);
}
int git_submodule_open(
git_repository **subrepo,
git_submodule *submodule)
{
int error;
git_buf path = GIT_BUF_INIT;
git_repository *repo;
const char *workdir;
assert(submodule && subrepo);
repo = submodule->owner;
workdir = git_repository_workdir(repo);
if (!workdir) {
giterr_set(GITERR_REPOSITORY,
"Cannot open submodule repository in a bare repo");
return GIT_ENOTFOUND;
}
if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) {
giterr_set(GITERR_REPOSITORY,
"Cannot open submodule repository that is not checked out");
return GIT_ENOTFOUND;
}
if (git_buf_joinpath(&path, workdir, submodule->path) < 0)
return -1;
error = git_repository_open(subrepo, path.ptr);
git_buf_free(&path);
/* if we have opened the submodule successfully, let's grab the HEAD OID */
if (!error) {
if (!git_reference_name_to_id(
&submodule->wd_oid, *subrepo, GIT_HEAD_FILE))
submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
else
giterr_clear();
}
return error;
}
int git_submodule_reload_all(git_repository *repo)
{
assert(repo);
git_submodule_config_free(repo);
return load_submodule_config(repo);
}
int git_submodule_reload(git_submodule *submodule)
{
git_repository *repo;
git_index *index;
int error;
size_t pos;
git_tree *head;
git_config_backend *mods;
assert(submodule);
/* refresh index data */
repo = submodule->owner;
if (git_repository_index__weakptr(&index, repo) < 0)
return -1;
submodule->flags = submodule->flags &
~(GIT_SUBMODULE_STATUS_IN_INDEX |
GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
if (!git_index_find(&pos, index, submodule->path)) {
const git_index_entry *entry = git_index_get_byindex(index, pos);
if (S_ISGITLINK(entry->mode)) {
if ((error = submodule_load_from_index(repo, entry)) < 0)
return error;
} else {
submodule_mode_mismatch(
repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
}
}
/* refresh HEAD tree data */
if (!(error = git_repository_head_tree(&head, repo))) {
git_tree_entry *te;
submodule->flags = submodule->flags &
~(GIT_SUBMODULE_STATUS_IN_HEAD |
GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) {
if (S_ISGITLINK(te->attr)) {
error = submodule_load_from_head(repo, submodule->path, &te->oid);
} else {
submodule_mode_mismatch(
repo, submodule->path,
GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
}
git_tree_entry_free(te);
}
else if (error == GIT_ENOTFOUND) {
giterr_clear();
error = 0;
}
git_tree_free(head);
}
if (error < 0)
return error;
/* refresh config data */
if ((mods = open_gitmodules(repo, false, NULL)) != NULL) {
git_buf path = GIT_BUF_INIT;
git_buf_sets(&path, "submodule\\.");
git_buf_text_puts_escape_regex(&path, submodule->name);
git_buf_puts(&path, ".*");
if (git_buf_oom(&path))
error = -1;
else
error = git_config_file_foreach_match(
mods, path.ptr, submodule_load_from_config, repo);
git_buf_free(&path);
git_config_file_free(mods);
}
if (error < 0)
return error;
/* refresh wd data */
submodule->flags = submodule->flags &
~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID);
error = submodule_load_from_wd_lite(submodule, submodule->path, NULL);
return error;
}
int git_submodule_status(
unsigned int *status,
git_submodule *submodule)
{
int error = 0;
unsigned int status_val;
assert(status && submodule);
status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags);
if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) {
if (!(error = submodule_index_status(&status_val, submodule)))
error = submodule_wd_status(&status_val, submodule);
}
*status = status_val;
return error;
}
int git_submodule_location(
unsigned int *location_status,
git_submodule *submodule)
{
assert(location_status && submodule);
*location_status = submodule->flags &
(GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX |
GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD);
return 0;
}
/*
* INTERNAL FUNCTIONS
*/
static git_submodule *submodule_alloc(git_repository *repo, const char *name)
{
git_submodule *sm;
if (!name || !strlen(name)) {
giterr_set(GITERR_SUBMODULE, "Invalid submodule name");
return NULL;
}
sm = git__calloc(1, sizeof(git_submodule));
if (sm == NULL)
goto fail;
sm->path = sm->name = git__strdup(name);
if (!sm->name)
goto fail;
sm->owner = repo;
sm->refcount = 1;
return sm;
fail:
submodule_release(sm, 0);
return NULL;
}
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);
sm->path = NULL;
}
git__free(sm->name);
sm->name = NULL;
git__free(sm->url);
sm->url = NULL;
sm->owner = NULL;
git__free(sm);
}
}
static int submodule_get(
git_submodule **sm_ptr,
git_repository *repo,
const char *name,
const char *alternate)
{
git_strmap *smcfg = repo->submodules;
khiter_t pos;
git_submodule *sm;
int error;
assert(repo && name);
pos = git_strmap_lookup_index(smcfg, name);
if (!git_strmap_valid_index(smcfg, pos) && alternate)
pos = git_strmap_lookup_index(smcfg, alternate);
if (!git_strmap_valid_index(smcfg, pos)) {
sm = submodule_alloc(repo, name);
/* insert value at name - if another thread beats us to it, then use
* their record and release our own.
*/
pos = kh_put(str, smcfg, sm->name, &error);
if (error < 0) {
submodule_release(sm, 1);
sm = NULL;
} else if (error == 0) {
submodule_release(sm, 1);
sm = git_strmap_value_at(smcfg, pos);
} else {
git_strmap_set_value_at(smcfg, pos, sm);
}
} else {
sm = git_strmap_value_at(smcfg, pos);
}
*sm_ptr = sm;
return (sm != NULL) ? 0 : -1;
}
static int submodule_load_from_index(
git_repository *repo, const git_index_entry *entry)
{
git_submodule *sm;
if (submodule_get(&sm, repo, entry->path, NULL) < 0)
return -1;
if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) {
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
return 0;
}
sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX;
git_oid_cpy(&sm->index_oid, &entry->oid);
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
return 0;
}
static int submodule_load_from_head(
git_repository *repo, const char *path, const git_oid *oid)
{
git_submodule *sm;
if (submodule_get(&sm, repo, path, NULL) < 0)
return -1;
sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD;
git_oid_cpy(&sm->head_oid, oid);
sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
return 0;
}
static int submodule_config_error(const char *property, const char *value)
{
giterr_set(GITERR_INVALID,
"Invalid value for submodule '%s' property: '%s'", property, value);
return -1;
}
static int submodule_load_from_config(
const git_config_entry *entry, void *data)
{
git_repository *repo = data;
git_strmap *smcfg = repo->submodules;
const char *namestart, *property, *alternate = NULL;
const char *key = entry->name, *value = entry->value;
git_buf name = GIT_BUF_INIT;
git_submodule *sm;
bool is_path;
int error = 0;
if (git__prefixcmp(key, "submodule.") != 0)
return 0;
namestart = key + strlen("submodule.");
property = strrchr(namestart, '.');
if (property == NULL)
return 0;
property++;
is_path = (strcasecmp(property, "path") == 0);
if (git_buf_set(&name, namestart, property - namestart - 1) < 0)
return -1;
if (submodule_get(&sm, repo, name.ptr, is_path ? value : NULL) < 0) {
git_buf_free(&name);
return -1;
}
sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
/* Only from config might we get differing names & paths. If so, then
* update the submodule and insert under the alternative key.
*/
/* TODO: if case insensitive filesystem, then the following strcmps
* should be strcasecmp
*/
if (strcmp(sm->name, name.ptr) != 0) {
alternate = sm->name = git_buf_detach(&name);
} else if (is_path && value && strcmp(sm->path, value) != 0) {
alternate = sm->path = git__strdup(value);
if (!sm->path)
error = -1;
}
if (alternate) {
void *old_sm = NULL;
git_strmap_insert2(smcfg, alternate, sm, old_sm, error);
if (error >= 0)
sm->refcount++; /* inserted under a new key */
/* if we replaced an old module under this key, release the old one */
if (old_sm && ((git_submodule *)old_sm) != sm) {
submodule_release(old_sm, 1);
/* TODO: log warning about multiple submodules with same path */
}
}
git_buf_free(&name);
if (error < 0)
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 (is_path)
return 0;
/* 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)
return -1;
}
else if (strcasecmp(property, "update") == 0) {
int val;
if (git_config_lookup_map_value(
&val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0)
return submodule_config_error("update", value);
sm->update_default = sm->update = (git_submodule_update_t)val;
}
else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) {
if (git__parse_bool(&sm->fetch_recurse, value) < 0)
return submodule_config_error("fetchRecurseSubmodules", value);
}
else if (strcasecmp(property, "ignore") == 0) {
int val;
if (git_config_lookup_map_value(
&val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0)
return submodule_config_error("ignore", value);
sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;
}
/* ignore other unknown submodule properties */
return 0;
}
static int submodule_load_from_wd_lite(
git_submodule *sm, const char *name, void *payload)
{
git_repository *repo = git_submodule_owner(sm);
git_buf path = GIT_BUF_INIT;
GIT_UNUSED(name);
GIT_UNUSED(payload);
if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0)
return -1;
if (git_path_isdir(path.ptr))
sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
if (git_path_contains(&path, DOT_GIT))
sm->flags |= GIT_SUBMODULE_STATUS_IN_WD;
git_buf_free(&path);
return 0;
}
static void submodule_mode_mismatch(
git_repository *repo, const char *path, unsigned int flag)
{
khiter_t pos = git_strmap_lookup_index(repo->submodules, path);
if (git_strmap_valid_index(repo->submodules, pos)) {
git_submodule *sm = git_strmap_value_at(repo->submodules, pos);
sm->flags |= flag;
}
}
static int load_submodule_config_from_index(
git_repository *repo, git_oid *gitmodules_oid)
{
int error;
git_index *index;
git_iterator *i;
const git_index_entry *entry;
if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
(error = git_iterator_for_index(&i, index, 0, NULL, NULL)) < 0)
return error;
while (!(error = git_iterator_advance(&entry, i))) {
if (S_ISGITLINK(entry->mode)) {
error = submodule_load_from_index(repo, entry);
if (error < 0)
break;
} else {
submodule_mode_mismatch(
repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);
if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
git_oid_cpy(gitmodules_oid, &entry->oid);
}
}
if (error == GIT_ITEROVER)
error = 0;
git_iterator_free(i);
return error;
}
static int load_submodule_config_from_head(
git_repository *repo, git_oid *gitmodules_oid)
{
int error;
git_tree *head;
git_iterator *i;
const git_index_entry *entry;
if ((error = git_repository_head_tree(&head, repo)) < 0)
return error;
if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) {
git_tree_free(head);
return error;
}
while (!(error = git_iterator_advance(&entry, i))) {
if (S_ISGITLINK(entry->mode)) {
error = submodule_load_from_head(repo, entry->path, &entry->oid);
if (error < 0)
break;
} else {
submodule_mode_mismatch(
repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);
if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&
git_oid_iszero(gitmodules_oid))
git_oid_cpy(gitmodules_oid, &entry->oid);
}
}
if (error == GIT_ITEROVER)
error = 0;
git_iterator_free(i);
git_tree_free(head);
return error;
}
static git_config_backend *open_gitmodules(
git_repository *repo,
bool okay_to_create,
const git_oid *gitmodules_oid)
{
const char *workdir = git_repository_workdir(repo);
git_buf path = GIT_BUF_INIT;
git_config_backend *mods = NULL;
if (workdir != NULL) {
if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0)
return NULL;
if (okay_to_create || git_path_isfile(path.ptr)) {
/* git_config_file__ondisk should only fail if OOM */
if (git_config_file__ondisk(&mods, path.ptr) < 0)
mods = NULL;
/* open should only fail here if the file is malformed */
else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL) < 0) {
git_config_file_free(mods);
mods = NULL;
}
}
}
if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) {
/* TODO: Retrieve .gitmodules content from ODB */
/* Should we actually do this? Core git does not, but it means you
* can't really get much information about submodules on bare repos.
*/
}
git_buf_free(&path);
return mods;
}
static int load_submodule_config(git_repository *repo)
{
int error;
git_oid gitmodules_oid;
git_buf path = GIT_BUF_INIT;
git_config_backend *mods = NULL;
if (repo->submodules)
return 0;
memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
/* Submodule data is kept in a hashtable keyed by both name and path.
* These are usually the same, but that is not guaranteed.
*/
if (!repo->submodules) {
repo->submodules = git_strmap_alloc();
GITERR_CHECK_ALLOC(repo->submodules);
}
/* add submodule information from index */
if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0)
goto cleanup;
/* add submodule information from HEAD */
load_submodule_config_from_head(repo, &gitmodules_oid);
/* add submodule information from .gitmodules */
if ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL)
error = git_config_file_foreach(mods, submodule_load_from_config, repo);
if (error != 0)
goto cleanup;
/* shallow scan submodules in work tree */
if (!git_repository_is_bare(repo))
error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL);
cleanup:
git_buf_free(&path);
if (mods != NULL)
git_config_file_free(mods);
if (error)
git_submodule_config_free(repo);
return error;
}
static int lookup_head_remote(git_buf *url, git_repository *repo)
{
int error;
git_config *cfg;
git_reference *head = NULL, *remote = NULL;
const char *tgt, *scan;
git_buf key = GIT_BUF_INIT;
/* 1. resolve HEAD -> refs/heads/BRANCH
* 2. lookup config branch.BRANCH.remote -> ORIGIN
* 3. lookup remote.ORIGIN.url
*/
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
return error;
if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD cannot be resolved");
error = GIT_ENOTFOUND;
goto cleanup;
}
if (git_reference_type(head) != GIT_REF_SYMBOLIC) {
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD is not symbolic");
error = GIT_ENOTFOUND;
goto cleanup;
}
if ((error = git_branch_upstream(&remote, head)) < 0)
goto cleanup;
/* remote should refer to something like refs/remotes/ORIGIN/BRANCH */
if (git_reference_type(remote) != GIT_REF_SYMBOLIC ||
git__prefixcmp(git_reference_symbolic_target(remote), GIT_REFS_REMOTES_DIR) != 0)
{
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD is not symbolic");
error = GIT_ENOTFOUND;
goto cleanup;
}
scan = tgt = git_reference_symbolic_target(remote) + strlen(GIT_REFS_REMOTES_DIR);
while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\')))
scan++; /* find non-escaped slash to end ORIGIN name */
error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt);
if (error < 0)
goto cleanup;
if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0)
goto cleanup;
error = git_buf_sets(url, tgt);
cleanup:
git_buf_free(&key);
git_reference_free(head);
git_reference_free(remote);
return error;
}
static int submodule_update_config(
git_submodule *submodule,
const char *attr,
const char *value,
bool overwrite,
bool only_existing)
{
int error;
git_config *config;
git_buf key = GIT_BUF_INIT;
const char *old = NULL;
assert(submodule);
error = git_repository_config__weakptr(&config, submodule->owner);
if (error < 0)
return error;
error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr);
if (error < 0)
goto cleanup;
if (git_config_get_string(&old, config, key.ptr) < 0)
giterr_clear();
if (!old && only_existing)
goto cleanup;
if (old && !overwrite)
goto cleanup;
if ((!old && !value) || (old && value && strcmp(old, value) == 0))
goto cleanup;
if (!value)
error = git_config_delete_entry(config, key.ptr);
else
error = git_config_set_string(config, key.ptr, value);
cleanup:
git_buf_free(&key);
return error;
}
static int submodule_index_status(unsigned int *status, git_submodule *sm)
{
const git_oid *head_oid = git_submodule_head_id(sm);
const git_oid *index_oid = git_submodule_index_id(sm);
if (!head_oid) {
if (index_oid)
*status |= GIT_SUBMODULE_STATUS_INDEX_ADDED;
}
else if (!index_oid)
*status |= GIT_SUBMODULE_STATUS_INDEX_DELETED;
else if (!git_oid_equal(head_oid, index_oid))
*status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;
return 0;
}
static int submodule_wd_status(unsigned int *status, git_submodule *sm)
{
int error = 0;
const git_oid *wd_oid, *index_oid;
git_repository *sm_repo = NULL;
/* open repo now if we need it (so wd_id() call won't reopen) */
if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE ||
sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) &&
(sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0)
{
if ((error = git_submodule_open(&sm_repo, sm)) < 0)
return error;
}
index_oid = git_submodule_index_id(sm);
wd_oid = git_submodule_wd_id(sm);
if (!index_oid) {
if (wd_oid)
*status |= GIT_SUBMODULE_STATUS_WD_ADDED;
}
else if (!wd_oid) {
if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 &&
(sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0)
*status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED;
else
*status |= GIT_SUBMODULE_STATUS_WD_DELETED;
}
else if (!git_oid_equal(index_oid, wd_oid))
*status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;
if (sm_repo != NULL) {
git_tree *sm_head;
git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
git_diff_list *diff;
/* the diffs below could be optimized with an early termination
* option to the git_diff functions, but for now this is sufficient
* (and certainly no worse that what core git does).
*/
/* perform head-to-index diff on submodule */
if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0)
return error;
if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE)
opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
error = git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt);
if (!error) {
if (git_diff_num_deltas(diff) > 0)
*status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
git_diff_list_free(diff);
diff = NULL;
}
git_tree_free(sm_head);
if (error < 0)
return error;
/* perform index-to-workdir diff on submodule */
error = git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt);
if (!error) {
size_t untracked =
git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
if (untracked > 0)
*status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
if (git_diff_num_deltas(diff) != untracked)
*status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
git_diff_list_free(diff);
diff = NULL;
}
git_repository_free(sm_repo);
}
return error;
}