mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-05 15:33:51 +00:00

Without this change, compiling with gcc and pedantic generates warning: ISO C does not allow extra ‘;’ outside of a function.
2229 lines
56 KiB
C
2229 lines
56 KiB
C
/*
|
|
* 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/index.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},
|
|
{GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_UPDATE_NONE},
|
|
{GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_UPDATE_CHECKOUT},
|
|
};
|
|
|
|
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},
|
|
{GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_IGNORE_NONE},
|
|
{GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_IGNORE_ALL},
|
|
};
|
|
|
|
static git_cvar_map _sm_recurse_map[] = {
|
|
{GIT_CVAR_STRING, "on-demand", GIT_SUBMODULE_RECURSE_ONDEMAND},
|
|
{GIT_CVAR_FALSE, NULL, GIT_SUBMODULE_RECURSE_NO},
|
|
{GIT_CVAR_TRUE, NULL, GIT_SUBMODULE_RECURSE_YES},
|
|
};
|
|
|
|
enum {
|
|
CACHE_OK = 0,
|
|
CACHE_REFRESH = 1,
|
|
CACHE_FLUSH = 2
|
|
};
|
|
enum {
|
|
GITMODULES_EXISTING = 0,
|
|
GITMODULES_CREATE = 1,
|
|
};
|
|
|
|
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 submodule_cache_init(git_repository *repo, int refresh);
|
|
static void submodule_cache_free(git_submodule_cache *cache);
|
|
|
|
static git_config_backend *open_gitmodules(git_submodule_cache *, int gitmod);
|
|
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_get(git_submodule **, git_submodule_cache *, const char *, const char *);
|
|
static int submodule_load_from_config(const git_config_entry *, void *);
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
/* lookup submodule or return ENOTFOUND if it doesn't exist */
|
|
static int submodule_lookup(
|
|
git_submodule **out,
|
|
git_submodule_cache *cache,
|
|
const char *name,
|
|
const char *alternate)
|
|
{
|
|
khiter_t pos;
|
|
|
|
/* lock cache */
|
|
|
|
pos = git_strmap_lookup_index(cache->submodules, name);
|
|
|
|
if (!git_strmap_valid_index(cache->submodules, pos) && alternate)
|
|
pos = git_strmap_lookup_index(cache->submodules, alternate);
|
|
|
|
if (!git_strmap_valid_index(cache->submodules, pos)) {
|
|
/* unlock cache */
|
|
return GIT_ENOTFOUND; /* don't set error - caller will cope */
|
|
}
|
|
|
|
if (out != NULL) {
|
|
git_submodule *sm = git_strmap_value_at(cache->submodules, pos);
|
|
GIT_REFCOUNT_INC(sm);
|
|
*out = sm;
|
|
}
|
|
|
|
/* unlock cache */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* clear a set of flags on all submodules */
|
|
static void submodule_cache_clear_flags(
|
|
git_submodule_cache *cache, uint32_t mask)
|
|
{
|
|
git_submodule *sm;
|
|
uint32_t inverted_mask = ~mask;
|
|
|
|
git_strmap_foreach_value(cache->submodules, sm, {
|
|
sm->flags &= inverted_mask;
|
|
});
|
|
}
|
|
|
|
/*
|
|
* PUBLIC APIS
|
|
*/
|
|
|
|
bool git_submodule__is_submodule(git_repository *repo, const char *name)
|
|
{
|
|
git_strmap *map;
|
|
|
|
if (submodule_cache_init(repo, CACHE_OK) < 0) {
|
|
giterr_clear();
|
|
return false;
|
|
}
|
|
|
|
if (!repo->_submodules || !(map = repo->_submodules->submodules))
|
|
return false;
|
|
|
|
return git_strmap_valid_index(map, git_strmap_lookup_index(map, name));
|
|
}
|
|
|
|
static void submodule_set_lookup_error(int error, const char *name)
|
|
{
|
|
if (!error)
|
|
return;
|
|
|
|
giterr_set(GITERR_SUBMODULE, (error == GIT_ENOTFOUND) ?
|
|
"No submodule named '%s'" :
|
|
"Submodule '%s' has not been added yet", name);
|
|
}
|
|
|
|
int git_submodule__lookup(
|
|
git_submodule **out, /* NULL if user only wants to test existence */
|
|
git_repository *repo,
|
|
const char *name) /* trailing slash is allowed */
|
|
{
|
|
int error;
|
|
|
|
assert(repo && name);
|
|
|
|
if ((error = submodule_cache_init(repo, CACHE_OK)) < 0)
|
|
return error;
|
|
|
|
if ((error = submodule_lookup(out, repo->_submodules, name, NULL)) < 0)
|
|
submodule_set_lookup_error(error, name);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_submodule_lookup(
|
|
git_submodule **out, /* NULL if user only wants to test existence */
|
|
git_repository *repo,
|
|
const char *name) /* trailing slash is allowed */
|
|
{
|
|
int error;
|
|
|
|
assert(repo && name);
|
|
|
|
if ((error = submodule_cache_init(repo, CACHE_REFRESH)) < 0)
|
|
return error;
|
|
|
|
if ((error = submodule_lookup(out, repo->_submodules, name, NULL)) < 0) {
|
|
|
|
/* check if a plausible submodule exists at path */
|
|
if (git_repository_workdir(repo)) {
|
|
git_buf path = GIT_BUF_INIT;
|
|
|
|
if (git_buf_join3(&path,
|
|
'/', git_repository_workdir(repo), name, DOT_GIT) < 0)
|
|
return -1;
|
|
|
|
if (git_path_exists(path.ptr))
|
|
error = GIT_EEXISTS;
|
|
|
|
git_buf_free(&path);
|
|
}
|
|
|
|
submodule_set_lookup_error(error, name);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static void submodule_free_dup(void *sm)
|
|
{
|
|
git_submodule_free(sm);
|
|
}
|
|
|
|
int git_submodule_foreach(
|
|
git_repository *repo,
|
|
int (*callback)(git_submodule *sm, const char *name, void *payload),
|
|
void *payload)
|
|
{
|
|
int error;
|
|
size_t i;
|
|
git_submodule *sm;
|
|
git_submodule_cache *cache;
|
|
git_vector snapshot = GIT_VECTOR_INIT;
|
|
|
|
assert(repo && callback);
|
|
|
|
if ((error = submodule_cache_init(repo, CACHE_REFRESH)) < 0)
|
|
return error;
|
|
|
|
cache = repo->_submodules;
|
|
|
|
if (git_mutex_lock(&cache->lock) < 0) {
|
|
giterr_set(GITERR_OS, "Unable to acquire lock on submodule cache");
|
|
return -1;
|
|
}
|
|
|
|
if (!(error = git_vector_init(
|
|
&snapshot, kh_size(cache->submodules), submodule_cmp))) {
|
|
|
|
git_strmap_foreach_value(cache->submodules, sm, {
|
|
if ((error = git_vector_insert(&snapshot, sm)) < 0)
|
|
break;
|
|
GIT_REFCOUNT_INC(sm);
|
|
});
|
|
}
|
|
|
|
git_mutex_unlock(&cache->lock);
|
|
|
|
if (error < 0)
|
|
goto done;
|
|
|
|
git_vector_uniq(&snapshot, submodule_free_dup);
|
|
|
|
git_vector_foreach(&snapshot, i, sm) {
|
|
if ((error = callback(sm, sm->name, payload)) != 0) {
|
|
giterr_set_after_callback(error);
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
git_vector_foreach(&snapshot, i, sm)
|
|
git_submodule_free(sm);
|
|
git_vector_free(&snapshot);
|
|
|
|
return error;
|
|
}
|
|
|
|
void git_submodule_cache_free(git_repository *repo)
|
|
{
|
|
git_submodule_cache *cache;
|
|
|
|
assert(repo);
|
|
|
|
if ((cache = git__swap(repo->_submodules, NULL)) != NULL)
|
|
submodule_cache_free(cache);
|
|
}
|
|
|
|
static int submodule_repo_init(
|
|
git_repository **out,
|
|
git_repository *parent_repo,
|
|
const char *path,
|
|
const char *url,
|
|
bool use_gitlink)
|
|
{
|
|
int error = 0;
|
|
git_buf workdir = GIT_BUF_INIT, repodir = GIT_BUF_INIT;
|
|
git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
|
|
git_repository *subrepo = NULL;
|
|
|
|
error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
initopt.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_NO_REINIT;
|
|
initopt.origin_url = url;
|
|
|
|
/* init submodule repository and add origin remote as needed */
|
|
|
|
/* 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/
|
|
*/
|
|
if (use_gitlink) {
|
|
error = git_buf_join3(
|
|
&repodir, '/', git_repository_path(parent_repo), "modules", path);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
initopt.workdir_path = workdir.ptr;
|
|
initopt.flags |=
|
|
GIT_REPOSITORY_INIT_NO_DOTGIT_DIR |
|
|
GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
|
|
|
|
error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
|
|
} else
|
|
error = git_repository_init_ext(&subrepo, workdir.ptr, &initopt);
|
|
|
|
cleanup:
|
|
git_buf_free(&workdir);
|
|
git_buf_free(&repodir);
|
|
|
|
*out = subrepo;
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_submodule_add_setup(
|
|
git_submodule **out,
|
|
git_repository *repo,
|
|
const char *url,
|
|
const char *path,
|
|
int use_gitlink)
|
|
{
|
|
int error = 0;
|
|
git_config_backend *mods = NULL;
|
|
git_submodule *sm = NULL;
|
|
git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT;
|
|
git_repository *subrepo = NULL;
|
|
|
|
assert(repo && url && path);
|
|
|
|
/* see if there is already an entry for this submodule */
|
|
|
|
if (git_submodule_lookup(NULL, repo, path) < 0)
|
|
giterr_clear();
|
|
else {
|
|
giterr_set(GITERR_SUBMODULE,
|
|
"Attempt to add submodule '%s' that already exists", path);
|
|
return GIT_EEXISTS;
|
|
}
|
|
|
|
/* 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->_submodules, GITMODULES_CREATE))) {
|
|
giterr_set(GITERR_SUBMODULE,
|
|
"Adding submodules to a bare repository is not supported");
|
|
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, url)) < 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;
|
|
|
|
/* if the repo does not already exist, then init a new repo and add it.
|
|
* Otherwise, just add the existing repo.
|
|
*/
|
|
if (!(git_path_exists(name.ptr) &&
|
|
git_path_contains(&name, DOT_GIT))) {
|
|
|
|
/* resolve the actual URL to use */
|
|
if ((error = git_submodule_resolve_url(&real_url, repo, url)) < 0)
|
|
goto cleanup;
|
|
|
|
if ((error = submodule_repo_init(&subrepo, repo, path, real_url.ptr, use_gitlink)) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* add submodule to hash and "reload" it */
|
|
|
|
if (git_mutex_lock(&repo->_submodules->lock) < 0) {
|
|
giterr_set(GITERR_OS, "Unable to acquire lock on submodule cache");
|
|
error = -1;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(error = submodule_get(&sm, repo->_submodules, path, NULL)) &&
|
|
!(error = git_submodule_reload(sm, false)))
|
|
error = git_submodule_init(sm, false);
|
|
|
|
git_mutex_unlock(&repo->_submodules->lock);
|
|
|
|
cleanup:
|
|
if (error && sm) {
|
|
git_submodule_free(sm);
|
|
sm = NULL;
|
|
}
|
|
if (out != NULL)
|
|
*out = sm;
|
|
|
|
git_config_file_free(mods);
|
|
git_repository_free(subrepo);
|
|
git_buf_free(&real_url);
|
|
git_buf_free(&name);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_submodule_repo_init(
|
|
git_repository **out,
|
|
const git_submodule *sm,
|
|
int use_gitlink)
|
|
{
|
|
int error;
|
|
git_repository *sub_repo = NULL;
|
|
const char *configured_url;
|
|
git_config *cfg = NULL;
|
|
git_buf buf = GIT_BUF_INIT;
|
|
|
|
assert(out && sm);
|
|
|
|
/* get the configured remote url of the submodule */
|
|
if ((error = git_buf_printf(&buf, "submodule.%s.url", sm->name)) < 0 ||
|
|
(error = git_repository_config_snapshot(&cfg, sm->repo)) < 0 ||
|
|
(error = git_config_get_string(&configured_url, cfg, buf.ptr)) < 0 ||
|
|
(error = submodule_repo_init(&sub_repo, sm->repo, sm->path, configured_url, use_gitlink)) < 0)
|
|
goto done;
|
|
|
|
*out = sub_repo;
|
|
|
|
done:
|
|
git_config_free(cfg);
|
|
git_buf_free(&buf);
|
|
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->repo)) < 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 *sm_repo = NULL;
|
|
git_index *index;
|
|
git_buf path = GIT_BUF_INIT;
|
|
git_commit *head;
|
|
git_index_entry entry;
|
|
struct stat st;
|
|
|
|
assert(sm);
|
|
|
|
/* 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, sm->repo)) < 0 ||
|
|
(error = git_buf_joinpath(
|
|
&path, git_repository_workdir(sm->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, !(git_index_caps(index) & GIT_INDEXCAP_NO_FILEMODE));
|
|
|
|
/* 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.id, &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;
|
|
}
|
|
|
|
const char *git_submodule_ignore_to_str(git_submodule_ignore_t ignore)
|
|
{
|
|
int i;
|
|
for (i = 0; i < (int)ARRAY_SIZE(_sm_ignore_map); ++i)
|
|
if (_sm_ignore_map[i].map_value == ignore)
|
|
return _sm_ignore_map[i].str_match;
|
|
return NULL;
|
|
}
|
|
|
|
const char *git_submodule_update_to_str(git_submodule_update_t update)
|
|
{
|
|
int i;
|
|
for (i = 0; i < (int)ARRAY_SIZE(_sm_update_map); ++i)
|
|
if (_sm_update_map[i].map_value == update)
|
|
return _sm_update_map[i].str_match;
|
|
return NULL;
|
|
}
|
|
|
|
const char *git_submodule_recurse_to_str(git_submodule_recurse_t recurse)
|
|
{
|
|
int i;
|
|
for (i = 0; i < (int)ARRAY_SIZE(_sm_recurse_map); ++i)
|
|
if (_sm_recurse_map[i].map_value == recurse)
|
|
return _sm_recurse_map[i].str_match;
|
|
return NULL;
|
|
}
|
|
|
|
int git_submodule_save(git_submodule *submodule)
|
|
{
|
|
int error = 0;
|
|
git_config_backend *mods;
|
|
git_buf key = GIT_BUF_INIT;
|
|
const char *val;
|
|
|
|
assert(submodule);
|
|
|
|
mods = open_gitmodules(submodule->repo->_submodules, GITMODULES_CREATE);
|
|
if (!mods) {
|
|
giterr_set(GITERR_SUBMODULE,
|
|
"Adding submodules to a bare repository is not supported");
|
|
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, "branch")) < 0 ||
|
|
(error = git_config_file_set_string(mods, key.ptr, submodule->branch)) < 0)
|
|
goto cleanup;
|
|
|
|
if (!(error = submodule_config_key_trunc_puts(&key, "update")) &&
|
|
(val = git_submodule_update_to_str(submodule->update)) != NULL)
|
|
error = git_config_file_set_string(mods, key.ptr, val);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) &&
|
|
(val = git_submodule_ignore_to_str(submodule->ignore)) != NULL)
|
|
error = git_config_file_set_string(mods, key.ptr, val);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
if (!(error = submodule_config_key_trunc_puts(&key, "fetchRecurseSubmodules")) &&
|
|
(val = git_submodule_recurse_to_str(submodule->fetch_recurse)) != NULL)
|
|
error = git_config_file_set_string(mods, key.ptr, val);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
/* update internal defaults */
|
|
|
|
submodule->ignore_default = submodule->ignore;
|
|
submodule->update_default = submodule->update;
|
|
submodule->fetch_recurse_default = submodule->fetch_recurse;
|
|
submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
|
|
|
|
cleanup:
|
|
git_config_file_free(mods);
|
|
git_buf_free(&key);
|
|
|
|
return error;
|
|
}
|
|
|
|
git_repository *git_submodule_owner(git_submodule *submodule)
|
|
{
|
|
assert(submodule);
|
|
return submodule->repo;
|
|
}
|
|
|
|
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_resolve_url(git_buf *out, git_repository *repo, const char *url)
|
|
{
|
|
int error = 0;
|
|
|
|
assert(out && repo && url);
|
|
|
|
git_buf_sanitize(out);
|
|
|
|
if (git_path_is_relative(url)) {
|
|
if (!(error = get_url_base(out, repo)))
|
|
error = git_path_apply_relative(out, url);
|
|
} else if (strchr(url, ':') != NULL || url[0] == '/') {
|
|
error = git_buf_sets(out, url);
|
|
} else {
|
|
giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL");
|
|
error = -1;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
const char *git_submodule_branch(git_submodule *submodule)
|
|
{
|
|
assert(submodule);
|
|
return submodule->branch;
|
|
}
|
|
|
|
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);
|
|
|
|
/* load unless we think we have a valid oid */
|
|
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_bare(&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_NONE) ?
|
|
GIT_SUBMODULE_IGNORE_NONE : 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_RESET)
|
|
ignore = submodule->ignore_default;
|
|
|
|
old = submodule->ignore;
|
|
submodule->ignore = ignore;
|
|
return old;
|
|
}
|
|
|
|
git_submodule_update_t git_submodule_update_strategy(git_submodule *submodule)
|
|
{
|
|
assert(submodule);
|
|
return (submodule->update < GIT_SUBMODULE_UPDATE_CHECKOUT) ?
|
|
GIT_SUBMODULE_UPDATE_CHECKOUT : 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_RESET)
|
|
update = submodule->update_default;
|
|
|
|
old = submodule->update;
|
|
submodule->update = update;
|
|
return old;
|
|
}
|
|
|
|
git_submodule_recurse_t git_submodule_fetch_recurse_submodules(
|
|
git_submodule *submodule)
|
|
{
|
|
assert(submodule);
|
|
return submodule->fetch_recurse;
|
|
}
|
|
|
|
git_submodule_recurse_t git_submodule_set_fetch_recurse_submodules(
|
|
git_submodule *submodule,
|
|
git_submodule_recurse_t fetch_recurse_submodules)
|
|
{
|
|
git_submodule_recurse_t old;
|
|
|
|
assert(submodule);
|
|
|
|
if (fetch_recurse_submodules == GIT_SUBMODULE_RECURSE_RESET)
|
|
fetch_recurse_submodules = submodule->fetch_recurse_default;
|
|
|
|
old = submodule->fetch_recurse;
|
|
submodule->fetch_recurse = fetch_recurse_submodules;
|
|
return old;
|
|
}
|
|
|
|
static int submodule_repo_create(
|
|
git_repository **out,
|
|
git_repository *parent_repo,
|
|
const char *path)
|
|
{
|
|
int error = 0;
|
|
git_buf workdir = GIT_BUF_INIT, repodir = GIT_BUF_INIT;
|
|
git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;
|
|
git_repository *subrepo = NULL;
|
|
|
|
initopt.flags =
|
|
GIT_REPOSITORY_INIT_MKPATH |
|
|
GIT_REPOSITORY_INIT_NO_REINIT |
|
|
GIT_REPOSITORY_INIT_NO_DOTGIT_DIR |
|
|
GIT_REPOSITORY_INIT_RELATIVE_GITLINK;
|
|
|
|
/* Workdir: path to sub-repo working directory */
|
|
error = git_buf_joinpath(&workdir, git_repository_workdir(parent_repo), path);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
initopt.workdir_path = workdir.ptr;
|
|
|
|
/**
|
|
* Repodir: path to the sub-repo. sub-repo goes in:
|
|
* <repo-dir>/modules/<name>/ with a gitlink in the
|
|
* sub-repo workdir directory to that repository.
|
|
*/
|
|
error = git_buf_join3(
|
|
&repodir, '/', git_repository_path(parent_repo), "modules", path);
|
|
if (error < 0)
|
|
goto cleanup;
|
|
|
|
error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);
|
|
|
|
cleanup:
|
|
git_buf_free(&workdir);
|
|
git_buf_free(&repodir);
|
|
|
|
*out = subrepo;
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* Callback to override sub-repository creation when
|
|
* cloning a sub-repository.
|
|
*/
|
|
static int git_submodule_update_repo_init_cb(
|
|
git_repository **out,
|
|
const char *path,
|
|
int bare,
|
|
void *payload)
|
|
{
|
|
git_submodule *sm;
|
|
|
|
GIT_UNUSED(bare);
|
|
|
|
sm = payload;
|
|
|
|
return submodule_repo_create(out, sm->repo, path);
|
|
}
|
|
|
|
int git_submodule_update_init_options(git_submodule_update_options *opts, unsigned int version)
|
|
{
|
|
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
|
|
opts, version, git_submodule_update_options, GIT_SUBMODULE_UPDATE_OPTIONS_INIT);
|
|
return 0;
|
|
}
|
|
|
|
int git_submodule_update(git_submodule *sm, int init, git_submodule_update_options *_update_options)
|
|
{
|
|
int error;
|
|
unsigned int submodule_status;
|
|
git_config *config = NULL;
|
|
const char *submodule_url;
|
|
git_repository *sub_repo = NULL;
|
|
git_remote *remote = NULL;
|
|
git_object *target_commit = NULL;
|
|
git_buf buf = GIT_BUF_INIT;
|
|
git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
|
|
git_clone_options clone_options = GIT_CLONE_OPTIONS_INIT;
|
|
|
|
assert(sm);
|
|
|
|
if (_update_options)
|
|
memcpy(&update_options, _update_options, sizeof(git_submodule_update_options));
|
|
|
|
GITERR_CHECK_VERSION(&update_options, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options");
|
|
|
|
/* Copy over the remote callbacks */
|
|
clone_options.remote_callbacks = update_options.remote_callbacks;
|
|
clone_options.signature = update_options.signature;
|
|
|
|
/* Get the status of the submodule to determine if it is already initialized */
|
|
if ((error = git_submodule_status(&submodule_status, sm)) < 0)
|
|
goto done;
|
|
|
|
/*
|
|
* If submodule work dir is not already initialized, check to see
|
|
* what we need to do (initialize, clone, return error...)
|
|
*/
|
|
if (submodule_status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) {
|
|
/*
|
|
* Work dir is not initialized, check to see if the submodule
|
|
* info has been copied into .git/config
|
|
*/
|
|
if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 ||
|
|
(error = git_buf_printf(&buf, "submodule.%s.url", git_submodule_name(sm))) < 0)
|
|
goto done;
|
|
|
|
if ((error = git_config_get_string(&submodule_url, config, git_buf_cstr(&buf))) < 0) {
|
|
/*
|
|
* If the error is not "not found" or if it is "not found" and we are not
|
|
* initializing the submodule, then return error.
|
|
*/
|
|
if (error != GIT_ENOTFOUND)
|
|
goto done;
|
|
|
|
if (error == GIT_ENOTFOUND && !init) {
|
|
giterr_set(GITERR_SUBMODULE, "Submodule is not initialized.");
|
|
error = GIT_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
/* The submodule has not been initialized yet - initialize it now.*/
|
|
if ((error = git_submodule_init(sm, 0)) < 0)
|
|
goto done;
|
|
|
|
git_config_free(config);
|
|
config = NULL;
|
|
|
|
if ((error = git_repository_config_snapshot(&config, sm->repo)) < 0 ||
|
|
(error = git_config_get_string(&submodule_url, config, git_buf_cstr(&buf))) < 0)
|
|
goto done;
|
|
}
|
|
|
|
/** submodule is initialized - now clone it **/
|
|
/* override repo creation */
|
|
clone_options.repository_cb = git_submodule_update_repo_init_cb;
|
|
clone_options.repository_cb_payload = sm;
|
|
|
|
/*
|
|
* Do not perform checkout as part of clone, instead we
|
|
* will checkout the specific commit manually.
|
|
*/
|
|
clone_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_NONE;
|
|
update_options.checkout_opts.checkout_strategy = update_options.clone_checkout_strategy;
|
|
|
|
if ((error = git_clone(&sub_repo, submodule_url, sm->path, &clone_options)) < 0 ||
|
|
(error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm), update_options.signature, NULL)) < 0 ||
|
|
(error = git_checkout_head(sub_repo, &update_options.checkout_opts)) != 0)
|
|
goto done;
|
|
} else {
|
|
/**
|
|
* Work dir is initialized - look up the commit in the parent repository's index,
|
|
* update the workdir contents of the subrepository, and set the subrepository's
|
|
* head to the new commit.
|
|
*/
|
|
if ((error = git_submodule_open(&sub_repo, sm)) < 0 ||
|
|
(error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0 ||
|
|
(error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 ||
|
|
(error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm), update_options.signature, NULL)) < 0)
|
|
goto done;
|
|
|
|
/* Invalidate the wd flags as the workdir has been updated. */
|
|
sm->flags = sm->flags &
|
|
~(GIT_SUBMODULE_STATUS_IN_WD |
|
|
GIT_SUBMODULE_STATUS__WD_OID_VALID |
|
|
GIT_SUBMODULE_STATUS__WD_SCANNED);
|
|
}
|
|
|
|
done:
|
|
git_buf_free(&buf);
|
|
git_config_free(config);
|
|
git_object_free(target_commit);
|
|
git_remote_free(remote);
|
|
git_repository_free(sub_repo);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_submodule_init(git_submodule *sm, int overwrite)
|
|
{
|
|
int error;
|
|
const char *val;
|
|
git_buf key = GIT_BUF_INIT, effective_submodule_url = GIT_BUF_INIT;
|
|
git_config *cfg = NULL;
|
|
|
|
if (!sm->url) {
|
|
giterr_set(GITERR_SUBMODULE,
|
|
"No URL configured for submodule '%s'", sm->name);
|
|
return -1;
|
|
}
|
|
|
|
if ((error = git_repository_config(&cfg, sm->repo)) < 0)
|
|
return error;
|
|
|
|
/* write "submodule.NAME.url" */
|
|
|
|
if ((error = git_submodule_resolve_url(&effective_submodule_url, sm->repo, sm->url)) < 0 ||
|
|
(error = git_buf_printf(&key, "submodule.%s.url", sm->name)) < 0 ||
|
|
(error = git_config__update_entry(
|
|
cfg, key.ptr, effective_submodule_url.ptr, overwrite != 0, false)) < 0)
|
|
goto cleanup;
|
|
|
|
/* write "submodule.NAME.update" if not default */
|
|
|
|
val = (sm->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
|
|
NULL : git_submodule_update_to_str(sm->update);
|
|
|
|
if ((error = git_buf_printf(&key, "submodule.%s.update", sm->name)) < 0 ||
|
|
(error = git_config__update_entry(
|
|
cfg, key.ptr, val, overwrite != 0, false)) < 0)
|
|
goto cleanup;
|
|
|
|
/* success */
|
|
|
|
cleanup:
|
|
git_config_free(cfg);
|
|
git_buf_free(&key);
|
|
git_buf_free(&effective_submodule_url);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_submodule_sync(git_submodule *sm)
|
|
{
|
|
int error = 0;
|
|
git_config *cfg = NULL;
|
|
git_buf key = GIT_BUF_INIT;
|
|
git_repository *smrepo = NULL;
|
|
|
|
if (!sm->url) {
|
|
giterr_set(GITERR_SUBMODULE,
|
|
"No URL configured for submodule '%s'", sm->name);
|
|
return -1;
|
|
}
|
|
|
|
/* copy URL over to config only if it already exists */
|
|
|
|
if (!(error = git_repository_config__weakptr(&cfg, sm->repo)) &&
|
|
!(error = git_buf_printf(&key, "submodule.%s.url", sm->name)))
|
|
error = git_config__update_entry(cfg, key.ptr, sm->url, true, true);
|
|
|
|
/* if submodule exists in the working directory, update remote url */
|
|
|
|
if (!error &&
|
|
(sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
|
|
!(error = git_submodule_open(&smrepo, sm)))
|
|
{
|
|
git_buf remote_name = GIT_BUF_INIT;
|
|
|
|
if ((error = git_repository_config__weakptr(&cfg, smrepo)) < 0)
|
|
/* return error from reading submodule config */;
|
|
else if ((error = lookup_head_remote_key(&remote_name, smrepo)) < 0) {
|
|
giterr_clear();
|
|
error = git_buf_sets(&key, "branch.origin.remote");
|
|
} else {
|
|
error = git_buf_join3(
|
|
&key, '.', "branch", remote_name.ptr, "remote");
|
|
git_buf_free(&remote_name);
|
|
}
|
|
|
|
if (!error)
|
|
error = git_config__update_entry(cfg, key.ptr, sm->url, true, false);
|
|
|
|
git_repository_free(smrepo);
|
|
}
|
|
|
|
git_buf_free(&key);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int git_submodule__open(
|
|
git_repository **subrepo, git_submodule *sm, bool bare)
|
|
{
|
|
int error;
|
|
git_buf path = GIT_BUF_INIT;
|
|
unsigned int flags = GIT_REPOSITORY_OPEN_NO_SEARCH;
|
|
const char *wd;
|
|
|
|
assert(sm && subrepo);
|
|
|
|
if (git_repository__ensure_not_bare(
|
|
sm->repo, "open submodule repository") < 0)
|
|
return GIT_EBAREREPO;
|
|
|
|
wd = git_repository_workdir(sm->repo);
|
|
|
|
if (git_buf_joinpath(&path, wd, sm->path) < 0 ||
|
|
git_buf_joinpath(&path, path.ptr, DOT_GIT) < 0)
|
|
return -1;
|
|
|
|
sm->flags = sm->flags &
|
|
~(GIT_SUBMODULE_STATUS_IN_WD |
|
|
GIT_SUBMODULE_STATUS__WD_OID_VALID |
|
|
GIT_SUBMODULE_STATUS__WD_SCANNED);
|
|
|
|
if (bare)
|
|
flags |= GIT_REPOSITORY_OPEN_BARE;
|
|
|
|
error = git_repository_open_ext(subrepo, path.ptr, flags, wd);
|
|
|
|
/* if we opened the submodule successfully, grab HEAD OID, etc. */
|
|
if (!error) {
|
|
sm->flags |= GIT_SUBMODULE_STATUS_IN_WD |
|
|
GIT_SUBMODULE_STATUS__WD_SCANNED;
|
|
|
|
if (!git_reference_name_to_id(&sm->wd_oid, *subrepo, GIT_HEAD_FILE))
|
|
sm->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;
|
|
else
|
|
giterr_clear();
|
|
} else if (git_path_exists(path.ptr)) {
|
|
sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED |
|
|
GIT_SUBMODULE_STATUS_IN_WD;
|
|
} else {
|
|
git_buf_rtruncate_at_char(&path, '/'); /* remove "/.git" */
|
|
|
|
if (git_path_isdir(path.ptr))
|
|
sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;
|
|
}
|
|
|
|
git_buf_free(&path);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_submodule_open_bare(git_repository **subrepo, git_submodule *sm)
|
|
{
|
|
return git_submodule__open(subrepo, sm, true);
|
|
}
|
|
|
|
int git_submodule_open(git_repository **subrepo, git_submodule *sm)
|
|
{
|
|
return git_submodule__open(subrepo, sm, false);
|
|
}
|
|
|
|
int git_submodule_reload_all(git_repository *repo, int force)
|
|
{
|
|
return submodule_cache_init(repo, force ? CACHE_FLUSH : CACHE_REFRESH);
|
|
}
|
|
|
|
static void submodule_update_from_index_entry(
|
|
git_submodule *sm, const git_index_entry *ie)
|
|
{
|
|
bool already_found = (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) != 0;
|
|
|
|
if (!S_ISGITLINK(ie->mode)) {
|
|
if (!already_found)
|
|
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
|
|
} else {
|
|
if (already_found)
|
|
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
|
|
else
|
|
git_oid_cpy(&sm->index_oid, &ie->id);
|
|
|
|
sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX |
|
|
GIT_SUBMODULE_STATUS__INDEX_OID_VALID;
|
|
}
|
|
}
|
|
|
|
static int submodule_update_index(git_submodule *sm)
|
|
{
|
|
git_index *index;
|
|
const git_index_entry *ie;
|
|
|
|
if (git_repository_index__weakptr(&index, sm->repo) < 0)
|
|
return -1;
|
|
|
|
sm->flags = sm->flags &
|
|
~(GIT_SUBMODULE_STATUS_IN_INDEX |
|
|
GIT_SUBMODULE_STATUS__INDEX_OID_VALID);
|
|
|
|
if (!(ie = git_index_get_bypath(index, sm->path, 0)))
|
|
return 0;
|
|
|
|
submodule_update_from_index_entry(sm, ie);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void submodule_update_from_head_data(
|
|
git_submodule *sm, mode_t mode, const git_oid *id)
|
|
{
|
|
if (!S_ISGITLINK(mode))
|
|
sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
|
|
else {
|
|
git_oid_cpy(&sm->head_oid, id);
|
|
|
|
sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD |
|
|
GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
|
|
}
|
|
}
|
|
|
|
static int submodule_update_head(git_submodule *submodule)
|
|
{
|
|
git_tree *head = NULL;
|
|
git_tree_entry *te = NULL;
|
|
|
|
submodule->flags = submodule->flags &
|
|
~(GIT_SUBMODULE_STATUS_IN_HEAD |
|
|
GIT_SUBMODULE_STATUS__HEAD_OID_VALID);
|
|
|
|
/* if we can't look up file in current head, then done */
|
|
if (git_repository_head_tree(&head, submodule->repo) < 0 ||
|
|
git_tree_entry_bypath(&te, head, submodule->path) < 0)
|
|
giterr_clear();
|
|
else
|
|
submodule_update_from_head_data(submodule, te->attr, &te->oid);
|
|
|
|
git_tree_entry_free(te);
|
|
git_tree_free(head);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int git_submodule_reload(git_submodule *sm, int force)
|
|
{
|
|
int error = 0;
|
|
git_config_backend *mods;
|
|
git_submodule_cache *cache;
|
|
|
|
GIT_UNUSED(force);
|
|
|
|
assert(sm);
|
|
|
|
cache = sm->repo->_submodules;
|
|
|
|
/* refresh index data */
|
|
if ((error = submodule_update_index(sm)) < 0)
|
|
return error;
|
|
|
|
/* refresh HEAD tree data */
|
|
if ((error = submodule_update_head(sm)) < 0)
|
|
return error;
|
|
|
|
/* done if bare */
|
|
if (git_repository_is_bare(sm->repo))
|
|
return error;
|
|
|
|
/* refresh config data */
|
|
mods = open_gitmodules(cache, GITMODULES_EXISTING);
|
|
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
|
|
error = git_config_file_foreach_match(
|
|
mods, path.ptr, submodule_load_from_config, cache);
|
|
|
|
git_buf_free(&path);
|
|
git_config_file_free(mods);
|
|
|
|
if (error < 0)
|
|
return error;
|
|
}
|
|
|
|
/* refresh wd data */
|
|
sm->flags &=
|
|
~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID |
|
|
GIT_SUBMODULE_STATUS__WD_FLAGS);
|
|
|
|
return submodule_load_from_wd_lite(sm);
|
|
}
|
|
|
|
static void submodule_copy_oid_maybe(
|
|
git_oid *tgt, const git_oid *src, bool valid)
|
|
{
|
|
if (tgt) {
|
|
if (valid)
|
|
memcpy(tgt, src, sizeof(*tgt));
|
|
else
|
|
memset(tgt, 0, sizeof(*tgt));
|
|
}
|
|
}
|
|
|
|
int git_submodule__status(
|
|
unsigned int *out_status,
|
|
git_oid *out_head_id,
|
|
git_oid *out_index_id,
|
|
git_oid *out_wd_id,
|
|
git_submodule *sm,
|
|
git_submodule_ignore_t ign)
|
|
{
|
|
unsigned int status;
|
|
git_repository *smrepo = NULL;
|
|
|
|
if (ign < GIT_SUBMODULE_IGNORE_NONE)
|
|
ign = sm->ignore;
|
|
|
|
/* only return location info if ignore == all */
|
|
if (ign == GIT_SUBMODULE_IGNORE_ALL) {
|
|
*out_status = (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS);
|
|
return 0;
|
|
}
|
|
|
|
/* refresh the index OID */
|
|
if (submodule_update_index(sm) < 0)
|
|
return -1;
|
|
|
|
/* refresh the HEAD OID */
|
|
if (submodule_update_head(sm) < 0)
|
|
return -1;
|
|
|
|
/* for ignore == dirty, don't scan the working directory */
|
|
if (ign == GIT_SUBMODULE_IGNORE_DIRTY) {
|
|
/* git_submodule_open_bare will load WD OID data */
|
|
if (git_submodule_open_bare(&smrepo, sm) < 0)
|
|
giterr_clear();
|
|
else
|
|
git_repository_free(smrepo);
|
|
smrepo = NULL;
|
|
} else if (git_submodule_open(&smrepo, sm) < 0) {
|
|
giterr_clear();
|
|
smrepo = NULL;
|
|
}
|
|
|
|
status = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(sm->flags);
|
|
|
|
submodule_get_index_status(&status, sm);
|
|
submodule_get_wd_status(&status, sm, smrepo, ign);
|
|
|
|
git_repository_free(smrepo);
|
|
|
|
*out_status = status;
|
|
|
|
submodule_copy_oid_maybe(out_head_id, &sm->head_oid,
|
|
(sm->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) != 0);
|
|
submodule_copy_oid_maybe(out_index_id, &sm->index_oid,
|
|
(sm->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) != 0);
|
|
submodule_copy_oid_maybe(out_wd_id, &sm->wd_oid,
|
|
(sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) != 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_submodule_status(unsigned int *status, git_submodule *sm)
|
|
{
|
|
assert(status && sm);
|
|
|
|
return git_submodule__status(status, NULL, NULL, NULL, sm, 0);
|
|
}
|
|
|
|
int git_submodule_location(unsigned int *location, git_submodule *sm)
|
|
{
|
|
assert(location && sm);
|
|
|
|
return git_submodule__status(
|
|
location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL);
|
|
}
|
|
|
|
|
|
/*
|
|
* INTERNAL FUNCTIONS
|
|
*/
|
|
|
|
static int submodule_alloc(
|
|
git_submodule **out, git_submodule_cache *cache, const char *name)
|
|
{
|
|
size_t namelen;
|
|
git_submodule *sm;
|
|
|
|
if (!name || !(namelen = strlen(name))) {
|
|
giterr_set(GITERR_SUBMODULE, "Invalid submodule name");
|
|
return -1;
|
|
}
|
|
|
|
sm = git__calloc(1, sizeof(git_submodule));
|
|
GITERR_CHECK_ALLOC(sm);
|
|
|
|
sm->name = sm->path = git__strdup(name);
|
|
if (!sm->name) {
|
|
git__free(sm);
|
|
return -1;
|
|
}
|
|
|
|
GIT_REFCOUNT_INC(sm);
|
|
sm->ignore = sm->ignore_default = GIT_SUBMODULE_IGNORE_NONE;
|
|
sm->update = sm->update_default = GIT_SUBMODULE_UPDATE_CHECKOUT;
|
|
sm->fetch_recurse = sm->fetch_recurse_default = GIT_SUBMODULE_RECURSE_NO;
|
|
sm->repo = cache->repo;
|
|
sm->branch = NULL;
|
|
|
|
*out = sm;
|
|
return 0;
|
|
}
|
|
|
|
static void submodule_cache_remove_item(
|
|
git_submodule_cache *cache,
|
|
git_submodule *item,
|
|
bool free_after_remove)
|
|
{
|
|
git_strmap *map;
|
|
const char *name, *alt;
|
|
|
|
if (!cache || !(map = cache->submodules) || !item)
|
|
return;
|
|
|
|
name = item->name;
|
|
alt = (item->path != item->name) ? item->path : NULL;
|
|
|
|
for (; name; name = alt, alt = NULL) {
|
|
khiter_t pos = git_strmap_lookup_index(map, name);
|
|
git_submodule *found;
|
|
|
|
if (!git_strmap_valid_index(map, pos))
|
|
continue;
|
|
|
|
found = git_strmap_value_at(map, pos);
|
|
|
|
if (found != item)
|
|
continue;
|
|
|
|
git_strmap_set_value_at(map, pos, NULL);
|
|
git_strmap_delete_at(map, pos);
|
|
|
|
if (free_after_remove)
|
|
git_submodule_free(found);
|
|
}
|
|
}
|
|
|
|
static void submodule_release(git_submodule *sm)
|
|
{
|
|
if (!sm)
|
|
return;
|
|
|
|
if (sm->repo) {
|
|
git_submodule_cache *cache = sm->repo->_submodules;
|
|
sm->repo = NULL;
|
|
submodule_cache_remove_item(cache, sm, false);
|
|
}
|
|
|
|
if (sm->path != sm->name)
|
|
git__free(sm->path);
|
|
git__free(sm->name);
|
|
git__free(sm->url);
|
|
git__free(sm->branch);
|
|
git__memzero(sm, sizeof(*sm));
|
|
git__free(sm);
|
|
}
|
|
|
|
void git_submodule_free(git_submodule *sm)
|
|
{
|
|
if (!sm)
|
|
return;
|
|
GIT_REFCOUNT_DEC(sm, submodule_release);
|
|
}
|
|
|
|
static int submodule_get(
|
|
git_submodule **out,
|
|
git_submodule_cache *cache,
|
|
const char *name,
|
|
const char *alternate)
|
|
{
|
|
int error = 0;
|
|
khiter_t pos;
|
|
git_submodule *sm;
|
|
|
|
pos = git_strmap_lookup_index(cache->submodules, name);
|
|
|
|
if (!git_strmap_valid_index(cache->submodules, pos) && alternate)
|
|
pos = git_strmap_lookup_index(cache->submodules, alternate);
|
|
|
|
if (!git_strmap_valid_index(cache->submodules, pos)) {
|
|
if ((error = submodule_alloc(&sm, cache, name)) < 0)
|
|
return error;
|
|
|
|
/* insert value at name - if another thread beats us to it, then use
|
|
* their record and release our own.
|
|
*/
|
|
pos = kh_put(str, cache->submodules, sm->name, &error);
|
|
|
|
if (error < 0)
|
|
goto done;
|
|
else if (error == 0) {
|
|
git_submodule_free(sm);
|
|
sm = git_strmap_value_at(cache->submodules, pos);
|
|
} else {
|
|
error = 0;
|
|
git_strmap_set_value_at(cache->submodules, pos, sm);
|
|
}
|
|
} else {
|
|
sm = git_strmap_value_at(cache->submodules, pos);
|
|
}
|
|
|
|
done:
|
|
if (error < 0)
|
|
git_submodule_free(sm);
|
|
else if (out) {
|
|
GIT_REFCOUNT_INC(sm);
|
|
*out = sm;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int git_submodule_parse_ignore(git_submodule_ignore_t *out, const char *value)
|
|
{
|
|
int val;
|
|
|
|
if (git_config_lookup_map_value(
|
|
&val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0) {
|
|
*out = GIT_SUBMODULE_IGNORE_NONE;
|
|
return submodule_config_error("ignore", value);
|
|
}
|
|
|
|
*out = (git_submodule_ignore_t)val;
|
|
return 0;
|
|
}
|
|
|
|
int git_submodule_parse_update(git_submodule_update_t *out, const char *value)
|
|
{
|
|
int val;
|
|
|
|
if (git_config_lookup_map_value(
|
|
&val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0) {
|
|
*out = GIT_SUBMODULE_UPDATE_CHECKOUT;
|
|
return submodule_config_error("update", value);
|
|
}
|
|
|
|
*out = (git_submodule_update_t)val;
|
|
return 0;
|
|
}
|
|
|
|
int git_submodule_parse_recurse(git_submodule_recurse_t *out, const char *value)
|
|
{
|
|
int val;
|
|
|
|
if (git_config_lookup_map_value(
|
|
&val, _sm_recurse_map, ARRAY_SIZE(_sm_recurse_map), value) < 0) {
|
|
*out = GIT_SUBMODULE_RECURSE_YES;
|
|
return submodule_config_error("recurse", value);
|
|
}
|
|
|
|
*out = (git_submodule_recurse_t)val;
|
|
return 0;
|
|
}
|
|
|
|
static int submodule_load_from_config(
|
|
const git_config_entry *entry, void *payload)
|
|
{
|
|
git_submodule_cache *cache = payload;
|
|
const char *namestart, *property;
|
|
const char *key = entry->name, *value = entry->value, *path;
|
|
char *alternate = NULL, *replaced = NULL;
|
|
git_buf name = GIT_BUF_INIT;
|
|
git_submodule *sm = NULL;
|
|
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;
|
|
|
|
if ((error = git_buf_set(&name, namestart, property - namestart - 1)) < 0 ||
|
|
(error = submodule_get(&sm, cache, name.ptr, path)) < 0)
|
|
goto done;
|
|
|
|
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) { /* name changed */
|
|
if (!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;
|
|
}
|
|
}
|
|
|
|
/* Deregister under name being replaced */
|
|
if (replaced) {
|
|
git_strmap_delete(cache->submodules, replaced);
|
|
git_submodule_free(sm);
|
|
git__free(replaced);
|
|
}
|
|
|
|
/* Insert under alternate key */
|
|
if (alternate) {
|
|
void *old_sm = NULL;
|
|
git_strmap_insert2(cache->submodules, alternate, sm, old_sm, error);
|
|
|
|
if (error < 0)
|
|
goto done;
|
|
if (error > 0)
|
|
error = 0;
|
|
|
|
GIT_REFCOUNT_INC(sm); /* increase refcount for new key */
|
|
|
|
/* if we replaced an old module under this key, release the old one */
|
|
if (old_sm && ((git_submodule *)old_sm) != sm) {
|
|
git_submodule_free(old_sm);
|
|
/* TODO: log warning about multiple submodules with same path */
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
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 = git_submodule_parse_update(&sm->update, value)) < 0)
|
|
goto done;
|
|
sm->update_default = sm->update;
|
|
}
|
|
else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) {
|
|
if ((error = git_submodule_parse_recurse(&sm->fetch_recurse, value)) < 0)
|
|
goto done;
|
|
sm->fetch_recurse_default = sm->fetch_recurse;
|
|
}
|
|
else if (strcasecmp(property, "ignore") == 0) {
|
|
if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0)
|
|
goto done;
|
|
sm->ignore_default = sm->ignore;
|
|
}
|
|
/* ignore other unknown submodule properties */
|
|
|
|
done:
|
|
git_submodule_free(sm); /* offset refcount inc from submodule_get() */
|
|
git_buf_free(&name);
|
|
return error;
|
|
}
|
|
|
|
static int submodule_load_from_wd_lite(git_submodule *sm)
|
|
{
|
|
git_buf path = GIT_BUF_INIT;
|
|
|
|
if (git_buf_joinpath(&path, git_repository_workdir(sm->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 int submodule_cache_refresh_from_index(
|
|
git_submodule_cache *cache, git_index *idx)
|
|
{
|
|
int error;
|
|
git_iterator *i;
|
|
const git_index_entry *entry;
|
|
|
|
if ((error = git_iterator_for_index(&i, idx, 0, NULL, NULL)) < 0)
|
|
return error;
|
|
|
|
while (!(error = git_iterator_advance(&entry, i))) {
|
|
khiter_t pos = git_strmap_lookup_index(cache->submodules, entry->path);
|
|
git_submodule *sm;
|
|
|
|
if (git_strmap_valid_index(cache->submodules, pos)) {
|
|
sm = git_strmap_value_at(cache->submodules, pos);
|
|
|
|
if (S_ISGITLINK(entry->mode))
|
|
submodule_update_from_index_entry(sm, entry);
|
|
else
|
|
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
|
|
} else if (S_ISGITLINK(entry->mode)) {
|
|
if (!submodule_get(&sm, cache, entry->path, NULL)) {
|
|
submodule_update_from_index_entry(sm, entry);
|
|
git_submodule_free(sm);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (error == GIT_ITEROVER)
|
|
error = 0;
|
|
|
|
git_iterator_free(i);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int submodule_cache_refresh_from_head(
|
|
git_submodule_cache *cache, git_tree *head)
|
|
{
|
|
int error;
|
|
git_iterator *i;
|
|
const git_index_entry *entry;
|
|
|
|
if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0)
|
|
return error;
|
|
|
|
while (!(error = git_iterator_advance(&entry, i))) {
|
|
khiter_t pos = git_strmap_lookup_index(cache->submodules, entry->path);
|
|
git_submodule *sm;
|
|
|
|
if (git_strmap_valid_index(cache->submodules, pos)) {
|
|
sm = git_strmap_value_at(cache->submodules, pos);
|
|
|
|
if (S_ISGITLINK(entry->mode))
|
|
submodule_update_from_head_data(sm, entry->mode, &entry->id);
|
|
else
|
|
sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
|
|
} else if (S_ISGITLINK(entry->mode)) {
|
|
if (!submodule_get(&sm, cache, entry->path, NULL)) {
|
|
submodule_update_from_head_data(
|
|
sm, entry->mode, &entry->id);
|
|
git_submodule_free(sm);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (error == GIT_ITEROVER)
|
|
error = 0;
|
|
|
|
git_iterator_free(i);
|
|
|
|
return error;
|
|
}
|
|
|
|
static git_config_backend *open_gitmodules(
|
|
git_submodule_cache *cache,
|
|
int okay_to_create)
|
|
{
|
|
const char *workdir = git_repository_workdir(cache->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;
|
|
}
|
|
}
|
|
}
|
|
|
|
git_buf_free(&path);
|
|
|
|
return mods;
|
|
}
|
|
|
|
static void submodule_cache_free(git_submodule_cache *cache)
|
|
{
|
|
git_submodule *sm;
|
|
|
|
if (!cache)
|
|
return;
|
|
|
|
git_strmap_foreach_value(cache->submodules, sm, {
|
|
sm->repo = NULL; /* disconnect from repo */
|
|
git_submodule_free(sm);
|
|
});
|
|
git_strmap_free(cache->submodules);
|
|
|
|
git_buf_free(&cache->gitmodules_path);
|
|
git_mutex_free(&cache->lock);
|
|
git__free(cache);
|
|
}
|
|
|
|
static int submodule_cache_alloc(
|
|
git_submodule_cache **out, git_repository *repo)
|
|
{
|
|
git_submodule_cache *cache = git__calloc(1, sizeof(git_submodule_cache));
|
|
GITERR_CHECK_ALLOC(cache);
|
|
|
|
if (git_mutex_init(&cache->lock) < 0) {
|
|
giterr_set(GITERR_OS, "Unable to initialize submodule cache lock");
|
|
git__free(cache);
|
|
return -1;
|
|
}
|
|
|
|
if (git_strmap_alloc(&cache->submodules) < 0) {
|
|
submodule_cache_free(cache);
|
|
return -1;
|
|
}
|
|
|
|
cache->repo = repo;
|
|
git_buf_init(&cache->gitmodules_path, 0);
|
|
|
|
*out = cache;
|
|
return 0;
|
|
}
|
|
|
|
static int submodule_cache_refresh(git_submodule_cache *cache, int refresh)
|
|
{
|
|
int error = 0, update_index, update_head, update_gitmod;
|
|
git_index *idx = NULL;
|
|
git_tree *head = NULL;
|
|
const char *wd = NULL;
|
|
git_buf path = GIT_BUF_INIT;
|
|
git_submodule *sm;
|
|
git_config_backend *mods = NULL;
|
|
uint32_t mask;
|
|
|
|
if (!cache || !cache->repo || !refresh)
|
|
return 0;
|
|
|
|
if (git_mutex_lock(&cache->lock) < 0) {
|
|
giterr_set(GITERR_OS, "Unable to acquire lock on submodule cache");
|
|
return -1;
|
|
}
|
|
|
|
/* get sources that we will need to check */
|
|
|
|
if (git_repository_index(&idx, cache->repo) < 0)
|
|
giterr_clear();
|
|
if (git_repository_head_tree(&head, cache->repo) < 0)
|
|
giterr_clear();
|
|
|
|
wd = git_repository_workdir(cache->repo);
|
|
if (wd && (error = git_buf_joinpath(&path, wd, GIT_MODULES_FILE)) < 0)
|
|
goto cleanup;
|
|
|
|
/* check for invalidation */
|
|
|
|
if (refresh == CACHE_FLUSH)
|
|
update_index = update_head = update_gitmod = true;
|
|
else {
|
|
update_index =
|
|
!idx || git_index__changed_relative_to(idx, &cache->index_stamp);
|
|
update_head =
|
|
!head || !git_oid_equal(&cache->head_id, git_tree_id(head));
|
|
|
|
update_gitmod = (wd != NULL) ?
|
|
git_futils_filestamp_check(&cache->gitmodules_stamp, path.ptr) :
|
|
(cache->gitmodules_stamp.mtime != 0);
|
|
}
|
|
|
|
/* clear submodule flags that are to be refreshed */
|
|
|
|
mask = 0;
|
|
if (!idx || update_index)
|
|
mask |= GIT_SUBMODULE_STATUS_IN_INDEX |
|
|
GIT_SUBMODULE_STATUS__INDEX_FLAGS |
|
|
GIT_SUBMODULE_STATUS__INDEX_OID_VALID |
|
|
GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;
|
|
if (!head || update_head)
|
|
mask |= GIT_SUBMODULE_STATUS_IN_HEAD |
|
|
GIT_SUBMODULE_STATUS__HEAD_OID_VALID;
|
|
if (update_gitmod)
|
|
mask |= GIT_SUBMODULE_STATUS_IN_CONFIG;
|
|
if (mask != 0)
|
|
mask |= GIT_SUBMODULE_STATUS_IN_WD |
|
|
GIT_SUBMODULE_STATUS__WD_SCANNED |
|
|
GIT_SUBMODULE_STATUS__WD_FLAGS |
|
|
GIT_SUBMODULE_STATUS__WD_OID_VALID;
|
|
else
|
|
goto cleanup; /* nothing to do */
|
|
|
|
submodule_cache_clear_flags(cache, mask);
|
|
|
|
/* add back submodule information from index */
|
|
|
|
if (idx && update_index) {
|
|
if ((error = submodule_cache_refresh_from_index(cache, idx)) < 0)
|
|
goto cleanup;
|
|
|
|
git_futils_filestamp_set(
|
|
&cache->index_stamp, git_index__filestamp(idx));
|
|
}
|
|
|
|
/* add submodule information from HEAD */
|
|
|
|
if (head && update_head) {
|
|
if ((error = submodule_cache_refresh_from_head(cache, head)) < 0)
|
|
goto cleanup;
|
|
|
|
git_oid_cpy(&cache->head_id, git_tree_id(head));
|
|
}
|
|
|
|
/* add submodule information from .gitmodules */
|
|
|
|
if (wd && update_gitmod > 0) {
|
|
if ((mods = open_gitmodules(cache, false)) != NULL &&
|
|
(error = git_config_file_foreach(
|
|
mods, submodule_load_from_config, cache)) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* shallow scan submodules in work tree as needed */
|
|
|
|
if (wd && mask != 0) {
|
|
git_strmap_foreach_value(cache->submodules, sm, {
|
|
submodule_load_from_wd_lite(sm);
|
|
});
|
|
}
|
|
|
|
/* remove submodules that no longer exist */
|
|
|
|
git_strmap_foreach_value(cache->submodules, sm, {
|
|
/* purge unless in HEAD, index, or .gitmodules; no sm for wd only */
|
|
if (sm != NULL &&
|
|
!(sm->flags &
|
|
(GIT_SUBMODULE_STATUS_IN_HEAD |
|
|
GIT_SUBMODULE_STATUS_IN_INDEX |
|
|
GIT_SUBMODULE_STATUS_IN_CONFIG)))
|
|
submodule_cache_remove_item(cache, sm, true);
|
|
});
|
|
|
|
cleanup:
|
|
git_config_file_free(mods);
|
|
|
|
/* TODO: if we got an error, mark submodule config as invalid? */
|
|
|
|
git_mutex_unlock(&cache->lock);
|
|
|
|
git_index_free(idx);
|
|
git_tree_free(head);
|
|
git_buf_free(&path);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int submodule_cache_init(git_repository *repo, int cache_refresh)
|
|
{
|
|
int error = 0;
|
|
git_submodule_cache *cache = NULL;
|
|
|
|
/* if submodules already exist, just refresh as requested */
|
|
if (repo->_submodules)
|
|
return submodule_cache_refresh(repo->_submodules, cache_refresh);
|
|
|
|
/* otherwise create a new cache, load it, and atomically swap it in */
|
|
if (!(error = submodule_cache_alloc(&cache, repo)) &&
|
|
!(error = submodule_cache_refresh(cache, CACHE_FLUSH)))
|
|
cache = git__compare_and_swap(&repo->_submodules, NULL, cache);
|
|
|
|
/* might have raced with another thread to set cache, so free if needed */
|
|
if (cache)
|
|
submodule_cache_free(cache);
|
|
|
|
return error;
|
|
}
|
|
|
|
/* Lookup name of remote of the local tracking branch HEAD points to */
|
|
static int lookup_head_remote_key(git_buf *remote_name, git_repository *repo)
|
|
{
|
|
int error;
|
|
git_reference *head = NULL;
|
|
git_buf upstream_name = GIT_BUF_INIT;
|
|
|
|
/* lookup and dereference HEAD */
|
|
if ((error = git_repository_head(&head, repo)) < 0)
|
|
return error;
|
|
|
|
/**
|
|
* If head does not refer to a branch, then return
|
|
* GIT_ENOTFOUND to indicate that we could not find
|
|
* a remote key for the local tracking branch HEAD points to.
|
|
**/
|
|
if (!git_reference_is_branch(head)) {
|
|
giterr_set(GITERR_INVALID,
|
|
"HEAD does not refer to a branch.");
|
|
error = GIT_ENOTFOUND;
|
|
goto done;
|
|
}
|
|
|
|
/* lookup remote tracking branch of HEAD */
|
|
if ((error = git_branch_upstream_name(
|
|
&upstream_name,
|
|
repo,
|
|
git_reference_name(head))) < 0)
|
|
goto done;
|
|
|
|
/* lookup remote of remote tracking branch */
|
|
if ((error = git_branch_remote_name(remote_name, repo, upstream_name.ptr)) < 0)
|
|
goto done;
|
|
|
|
done:
|
|
git_buf_free(&upstream_name);
|
|
git_reference_free(head);
|
|
|
|
return error;
|
|
}
|
|
|
|
/* Lookup the remote of the local tracking branch HEAD points to */
|
|
static int lookup_head_remote(git_remote **remote, git_repository *repo)
|
|
{
|
|
int error;
|
|
git_buf remote_name = GIT_BUF_INIT;
|
|
|
|
/* lookup remote of remote tracking branch name */
|
|
if (!(error = lookup_head_remote_key(&remote_name, repo)))
|
|
error = git_remote_lookup(remote, repo, remote_name.ptr);
|
|
|
|
git_buf_free(&remote_name);
|
|
|
|
return error;
|
|
}
|
|
|
|
/* Lookup remote, either from HEAD or fall back on origin */
|
|
static int lookup_default_remote(git_remote **remote, git_repository *repo)
|
|
{
|
|
int error = lookup_head_remote(remote, repo);
|
|
|
|
/* if that failed, use 'origin' instead */
|
|
if (error == GIT_ENOTFOUND)
|
|
error = git_remote_lookup(remote, repo, "origin");
|
|
|
|
if (error == GIT_ENOTFOUND)
|
|
giterr_set(
|
|
GITERR_SUBMODULE,
|
|
"Cannot get default remote for submodule - no local tracking "
|
|
"branch for HEAD and origin does not exist");
|
|
|
|
return error;
|
|
}
|
|
|
|
static int get_url_base(git_buf *url, git_repository *repo)
|
|
{
|
|
int error;
|
|
git_remote *remote = NULL;
|
|
|
|
if (!(error = lookup_default_remote(&remote, repo))) {
|
|
error = git_buf_sets(url, git_remote_url(remote));
|
|
git_remote_free(remote);
|
|
}
|
|
else if (error == GIT_ENOTFOUND) {
|
|
/* if repository does not have a default remote, use workdir instead */
|
|
giterr_clear();
|
|
error = git_buf_sets(url, git_repository_workdir(repo));
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static void submodule_get_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);
|
|
|
|
*status = *status & ~GIT_SUBMODULE_STATUS__INDEX_FLAGS;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
static void submodule_get_wd_status(
|
|
unsigned int *status,
|
|
git_submodule *sm,
|
|
git_repository *sm_repo,
|
|
git_submodule_ignore_t ign)
|
|
{
|
|
const git_oid *index_oid = git_submodule_index_id(sm);
|
|
const git_oid *wd_oid =
|
|
(sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) ? &sm->wd_oid : NULL;
|
|
git_tree *sm_head = NULL;
|
|
git_index *index = NULL;
|
|
git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
|
|
git_diff *diff;
|
|
|
|
*status = *status & ~GIT_SUBMODULE_STATUS__WD_FLAGS;
|
|
|
|
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 we have no repo, then we're done */
|
|
if (!sm_repo)
|
|
return;
|
|
|
|
/* 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).
|
|
*/
|
|
|
|
if (ign == GIT_SUBMODULE_IGNORE_NONE)
|
|
opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
|
|
|
|
(void)git_repository_index__weakptr(&index, sm_repo);
|
|
|
|
/* if we don't have an unborn head, check diff with index */
|
|
if (git_repository_head_tree(&sm_head, sm_repo) < 0)
|
|
giterr_clear();
|
|
else {
|
|
/* perform head to index diff on submodule */
|
|
if (git_diff_tree_to_index(&diff, sm_repo, sm_head, index, &opt) < 0)
|
|
giterr_clear();
|
|
else {
|
|
if (git_diff_num_deltas(diff) > 0)
|
|
*status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
|
|
git_diff_free(diff);
|
|
diff = NULL;
|
|
}
|
|
|
|
git_tree_free(sm_head);
|
|
}
|
|
|
|
/* perform index-to-workdir diff on submodule */
|
|
if (git_diff_index_to_workdir(&diff, sm_repo, index, &opt) < 0)
|
|
giterr_clear();
|
|
else {
|
|
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_free(diff);
|
|
diff = NULL;
|
|
}
|
|
}
|