Commit 923c8400 by Vicent Marti

Merge pull request #2215 from libgit2/rb/submodule-cache-fixes

Improve submodule cache management
parents f34408a7 eedeeb9e
......@@ -13,6 +13,7 @@
*/
#include "common.h"
#include <unistd.h>
/**
* This example demonstrates the use of the libgit2 status APIs,
......@@ -44,19 +45,22 @@ enum {
#define MAX_PATHSPEC 8
struct opts {
git_status_options statusopt;
char *repodir;
char *pathspec[MAX_PATHSPEC];
int npaths;
int format;
int zterm;
int showbranch;
git_status_options statusopt;
char *repodir;
char *pathspec[MAX_PATHSPEC];
int npaths;
int format;
int zterm;
int showbranch;
int showsubmod;
int repeat;
};
static void parse_opts(struct opts *o, int argc, char *argv[]);
static void show_branch(git_repository *repo, int format);
static void print_long(git_status_list *status);
static void print_short(git_repository *repo, git_status_list *status);
static int print_submod(git_submodule *sm, const char *name, void *payload);
int main(int argc, char *argv[])
{
......@@ -84,6 +88,10 @@ int main(int argc, char *argv[])
fatal("Cannot report status on bare repository",
git_repository_path(repo));
show_status:
if (o.repeat)
printf("\033[H\033[2J");
/**
* Run status on the repository
*
......@@ -98,17 +106,29 @@ int main(int argc, char *argv[])
* about what results are presented.
*/
check_lg2(git_status_list_new(&status, repo, &o.statusopt),
"Could not get status", NULL);
"Could not get status", NULL);
if (o.showbranch)
show_branch(repo, o.format);
if (o.showsubmod) {
int submod_count = 0;
check_lg2(git_submodule_foreach(repo, print_submod, &submod_count),
"Cannot iterate submodules", o.repodir);
}
if (o.format == FORMAT_LONG)
print_long(status);
else
print_short(repo, status);
git_status_list_free(status);
if (o.repeat) {
sleep(o.repeat);
goto show_status;
}
git_repository_free(repo);
git_threads_shutdown();
......@@ -381,7 +401,7 @@ static void print_short(git_repository *repo, git_status_list *status)
}
/**
* Now that we have all the information, it's time to format the output.
* Now that we have all the information, format the output.
*/
if (s->head_to_index) {
......@@ -417,6 +437,21 @@ static void print_short(git_repository *repo, git_status_list *status)
}
}
static int print_submod(git_submodule *sm, const char *name, void *payload)
{
int *count = payload;
(void)name;
if (*count == 0)
printf("# Submodules\n");
(*count)++;
printf("# - submodule '%s' at %s\n",
git_submodule_name(sm), git_submodule_path(sm));
return 0;
}
/**
* Parse options that git's status command supports.
*/
......@@ -462,6 +497,12 @@ static void parse_opts(struct opts *o, int argc, char *argv[])
o->statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
o->repodir = a + strlen("--git-dir=");
else if (!strcmp(a, "--repeat"))
o->repeat = 10;
else if (match_int_arg(&o->repeat, &args, "--repeat", 0))
/* okay */;
else if (!strcmp(a, "--list-submodules"))
o->showsubmod = 1;
else
check_lg2(-1, "Unsupported option", a);
}
......
......@@ -467,6 +467,59 @@ int git_buf_join(
return 0;
}
int git_buf_join3(
git_buf *buf,
char separator,
const char *str_a,
const char *str_b,
const char *str_c)
{
size_t len_a = strlen(str_a), len_b = strlen(str_b), len_c = strlen(str_c);
int sep_a = 0, sep_b = 0;
char *tgt;
/* for this function, disallow pointers into the existing buffer */
assert(str_a < buf->ptr || str_a >= buf->ptr + buf->size);
assert(str_b < buf->ptr || str_b >= buf->ptr + buf->size);
assert(str_c < buf->ptr || str_c >= buf->ptr + buf->size);
if (separator) {
if (len_a > 0) {
while (*str_b == separator) { str_b++; len_b--; }
sep_a = (str_a[len_a - 1] != separator);
}
if (len_a > 0 || len_b > 0)
while (*str_c == separator) { str_c++; len_c--; }
if (len_b > 0)
sep_b = (str_b[len_b - 1] != separator);
}
if (git_buf_grow(buf, len_a + sep_a + len_b + sep_b + len_c + 1) < 0)
return -1;
tgt = buf->ptr;
if (len_a) {
memcpy(tgt, str_a, len_a);
tgt += len_a;
}
if (sep_a)
*tgt++ = separator;
if (len_b) {
memcpy(tgt, str_b, len_b);
tgt += len_b;
}
if (sep_b)
*tgt++ = separator;
if (len_c)
memcpy(tgt, str_c, len_c);
buf->size = len_a + sep_a + len_b + sep_b + len_c;
buf->ptr[buf->size] = '\0';
return 0;
}
void git_buf_rtrim(git_buf *buf)
{
while (buf->size > 0) {
......
......@@ -98,8 +98,12 @@ void git_buf_truncate(git_buf *buf, size_t len);
void git_buf_shorten(git_buf *buf, size_t amount);
void git_buf_rtruncate_at_char(git_buf *path, char separator);
/** General join with separator */
int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...);
/** Fast join of two strings - first may legally point into `buf` data */
int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b);
/** Fast join of three strings - cannot reference `buf` data */
int git_buf_join3(git_buf *buf, char separator, const char *str_a, const char *str_b, const char *str_c);
/**
* Join two strings as paths, inserting a slash between as needed.
......
......@@ -615,6 +615,36 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
return error;
}
int git_config__update_entry(
git_config *config,
const char *key,
const char *value,
bool overwrite_existing,
bool only_if_existing)
{
int error = 0;
const git_config_entry *ce = NULL;
if ((error = git_config__lookup_entry(&ce, config, key, false)) < 0)
return error;
if (!ce && only_if_existing) /* entry doesn't exist */
return 0;
if (ce && !overwrite_existing) /* entry would be overwritten */
return 0;
if (value && ce && ce->value && !strcmp(ce->value, value)) /* no change */
return 0;
if (!value && (!ce || !ce->value)) /* asked to delete absent entry */
return 0;
if (!value)
error = git_config_delete_entry(config, key);
else
error = git_config_set_string(config, key, value);
return error;
}
/***********
* Getters
***********/
......
......@@ -53,6 +53,14 @@ extern int git_config__lookup_entry(
const char *key,
bool no_errors);
/* internal only: update and/or delete entry string with constraints */
extern int git_config__update_entry(
git_config *cfg,
const char *key,
const char *value,
bool overwrite_existing,
bool only_if_existing);
/*
* Lookup functions that cannot fail. These functions look up a config
* value and return a fallback value if the value is missing or if any
......
......@@ -16,7 +16,8 @@ GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level
GIT_INLINE(void) git_config_file_free(git_config_backend *cfg)
{
cfg->free(cfg);
if (cfg)
cfg->free(cfg);
}
GIT_INLINE(int) git_config_file_get_string(
......
......@@ -517,6 +517,18 @@ int git_index_read(git_index *index, int force)
return error;
}
int git_index__changed_relative_to(
git_index *index, const git_futils_filestamp *fs)
{
/* attempt to update index (ignoring errors) */
if (git_index_read(index, false) < 0)
giterr_clear();
return (index->stamp.mtime != fs->mtime ||
index->stamp.size != fs->size ||
index->stamp.ino != fs->ino);
}
int git_index_write(git_index *index)
{
git_filebuf file = GIT_FILEBUF_INIT;
......
......@@ -62,4 +62,11 @@ extern void git_index__set_ignore_case(git_index *index, bool ignore_case);
extern unsigned int git_index__create_mode(unsigned int mode);
GIT_INLINE(const git_futils_filestamp *) git_index__filestamp(git_index *index)
{
return &index->stamp;
}
extern int git_index__changed_relative_to(git_index *index, const git_futils_filestamp *fs);
#endif
......@@ -119,6 +119,14 @@ GIT_INLINE(void) git_path_mkposix(char *path)
# define git_path_mkposix(p) /* blank */
#endif
/**
* Check if string is a relative path (i.e. starts with "./" or "../")
*/
GIT_INLINE(int) git_path_is_relative(const char *p)
{
return (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/')));
}
extern int git__percent_decode(git_buf *decoded_out, const char *input);
/**
......
......@@ -1432,7 +1432,7 @@ static int create_new_reflog_file(const char *filepath)
GIT_INLINE(int) retrieve_reflog_path(git_buf *path, git_repository *repo, const char *name)
{
return git_buf_join_n(path, '/', 3, repo->path_repository, GIT_REFLOG_DIR, name);
return git_buf_join3(path, '/', repo->path_repository, GIT_REFLOG_DIR, name);
}
static int refdb_reflog_fs__ensure_log(git_refdb_backend *_backend, const char *name)
......
......@@ -495,9 +495,10 @@ cleanup:
int git_remote_save(const git_remote *remote)
{
int error;
git_config *config;
git_config *cfg;
const char *tagopt = NULL;
git_buf buf = GIT_BUF_INIT;
const git_config_entry *existing;
assert(remote);
......@@ -509,43 +510,31 @@ int git_remote_save(const git_remote *remote)
if ((error = ensure_remote_name_is_valid(remote->name)) < 0)
return error;
if (git_repository_config__weakptr(&config, remote->repo) < 0)
return -1;
if ((error = git_repository_config__weakptr(&cfg, remote->repo)) < 0)
return error;
if (git_buf_printf(&buf, "remote.%s.url", remote->name) < 0)
return -1;
if ((error = git_buf_printf(&buf, "remote.%s.url", remote->name)) < 0)
return error;
if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) {
git_buf_free(&buf);
return -1;
}
/* after this point, buffer is allocated so end with cleanup */
if ((error = git_config_set_string(
cfg, git_buf_cstr(&buf), remote->url)) < 0)
goto cleanup;
git_buf_clear(&buf);
if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0)
return -1;
if ((error = git_buf_printf(&buf, "remote.%s.pushurl", remote->name)) < 0)
goto cleanup;
if (remote->pushurl) {
if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) {
git_buf_free(&buf);
return -1;
}
} else {
int error = git_config_delete_entry(config, git_buf_cstr(&buf));
if (error == GIT_ENOTFOUND) {
error = 0;
giterr_clear();
}
if (error < 0) {
git_buf_free(&buf);
return error;
}
}
if ((error = git_config__update_entry(
cfg, git_buf_cstr(&buf), remote->pushurl, true, false)) < 0)
goto cleanup;
if (update_config_refspec(remote, config, GIT_DIRECTION_FETCH) < 0)
goto on_error;
if ((error = update_config_refspec(remote, cfg, GIT_DIRECTION_FETCH)) < 0)
goto cleanup;
if (update_config_refspec(remote, config, GIT_DIRECTION_PUSH) < 0)
goto on_error;
if ((error = update_config_refspec(remote, cfg, GIT_DIRECTION_PUSH)) < 0)
goto cleanup;
/*
* What action to take depends on the old and new values. This
......@@ -561,31 +550,26 @@ int git_remote_save(const git_remote *remote)
*/
git_buf_clear(&buf);
if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)
goto on_error;
error = git_config_get_string(&tagopt, config, git_buf_cstr(&buf));
if (error < 0 && error != GIT_ENOTFOUND)
goto on_error;
if ((error = git_buf_printf(&buf, "remote.%s.tagopt", remote->name)) < 0)
goto cleanup;
if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
if (git_config_set_string(config, git_buf_cstr(&buf), "--tags") < 0)
goto on_error;
} else if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
if (git_config_set_string(config, git_buf_cstr(&buf), "--no-tags") < 0)
goto on_error;
} else if (tagopt) {
if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
goto on_error;
}
if ((error = git_config__lookup_entry(
&existing, cfg, git_buf_cstr(&buf), false)) < 0)
goto cleanup;
git_buf_free(&buf);
if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL)
tagopt = "--tags";
else if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_NONE)
tagopt = "--no-tags";
else if (existing != NULL)
tagopt = NULL;
return 0;
error = git_config__update_entry(
cfg, git_buf_cstr(&buf), tagopt, true, false);
on_error:
cleanup:
git_buf_free(&buf);
return -1;
return error;
}
const char *git_remote_name(const git_remote *remote)
......
......@@ -93,6 +93,7 @@ void git_repository__cleanup(git_repository *repo)
git_cache_clear(&repo->objects);
git_attr_cache_flush(repo);
git_submodule_cache_free(repo);
set_config(repo, NULL);
set_index(repo, NULL);
......@@ -108,7 +109,6 @@ void git_repository_free(git_repository *repo)
git_repository__cleanup(repo);
git_cache_free(&repo->objects);
git_submodule_config_free(repo);
git_diff_driver_registry_free(repo->diff_drivers);
repo->diff_drivers = NULL;
......
......@@ -19,7 +19,7 @@
#include "buffer.h"
#include "object.h"
#include "attrcache.h"
#include "strmap.h"
#include "submodule.h"
#include "diff_driver.h"
#define DOT_GIT ".git"
......@@ -105,10 +105,10 @@ struct git_repository {
git_refdb *_refdb;
git_config *_config;
git_index *_index;
git_submodule_cache *_submodules;
git_cache objects;
git_attr_cache attrcache;
git_strmap *submodules;
git_diff_driver_registry *diff_drivers;
char *path_repository;
......@@ -149,11 +149,6 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo);
int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar);
void git_repository__cvar_cache_clear(git_repository *repo);
/*
* Submodule cache
*/
extern void git_submodule_config_free(git_repository *repo);
GIT_INLINE(int) git_repository__ensure_not_bare(
git_repository *repo,
const char *operation_name)
......
......@@ -49,6 +49,16 @@ static git_cvar_map _sm_recurse_map[] = {
{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;
......@@ -77,13 +87,15 @@ __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, bool reload);
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 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 int submodule_update_config(git_submodule *, const char *, const char *, bool, bool);
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);
......@@ -102,7 +114,7 @@ static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix)
/* lookup submodule or return ENOTFOUND if it doesn't exist */
static int submodule_lookup(
git_submodule **out,
git_strmap *cache,
git_submodule_cache *cache,
const char *name,
const char *alternate)
{
......@@ -110,18 +122,18 @@ static int submodule_lookup(
/* lock cache */
pos = git_strmap_lookup_index(cache, name);
pos = git_strmap_lookup_index(cache->submodules, name);
if (!git_strmap_valid_index(cache, pos) && alternate)
pos = git_strmap_lookup_index(cache, alternate);
if (!git_strmap_valid_index(cache->submodules, pos) && alternate)
pos = git_strmap_lookup_index(cache->submodules, alternate);
if (!git_strmap_valid_index(cache, pos)) {
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, pos);
git_submodule *sm = git_strmap_value_at(cache->submodules, pos);
GIT_REFCOUNT_INC(sm);
*out = sm;
}
......@@ -131,6 +143,18 @@ static int submodule_lookup(
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
*/
......@@ -139,17 +163,45 @@ bool git_submodule__is_submodule(git_repository *repo, const char *name)
{
git_strmap *map;
if (load_submodule_config(repo, false) < 0) {
if (submodule_cache_init(repo, CACHE_OK) < 0) {
giterr_clear();
return false;
}
if (!(map = repo->submodules))
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,
......@@ -159,88 +211,99 @@ int git_submodule_lookup(
assert(repo && name);
if ((error = load_submodule_config(repo, false)) < 0)
if ((error = submodule_cache_init(repo, CACHE_REFRESH)) < 0)
return error;
if ((error = submodule_lookup(out, repo->submodules, name, NULL)) < 0) {
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_joinpath(&path, git_repository_workdir(repo), name) < 0)
if (git_buf_join3(&path,
'/', git_repository_workdir(repo), name, DOT_GIT) < 0)
return -1;
if (git_path_contains(&path, DOT_GIT))
if (git_path_exists(path.ptr))
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);
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_vector seen = GIT_VECTOR_INIT;
git_vector_set_cmp(&seen, submodule_cmp);
git_submodule_cache *cache;
git_vector snapshot = GIT_VECTOR_INIT;
assert(repo && callback);
if ((error = load_submodule_config(repo, true)) < 0)
if ((error = submodule_cache_init(repo, CACHE_REFRESH)) < 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 (GIT_REFCOUNT_VAL(sm) > 1) {
if (git_vector_bsearch(NULL, &seen, sm) != GIT_ENOTFOUND)
continue;
if ((error = git_vector_insert(&seen, sm)) < 0)
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;
}
});
}
git_vector_free(&seen);
done:
git_vector_foreach(&snapshot, i, sm)
git_submodule_free(sm);
git_vector_free(&snapshot);
return error;
}
void git_submodule_config_free(git_repository *repo)
void git_submodule_cache_free(git_repository *repo)
{
git_strmap *smcfg;
git_submodule *sm;
git_submodule_cache *cache;
assert(repo);
smcfg = repo->submodules;
repo->submodules = NULL;
if (smcfg == NULL)
return;
git_strmap_foreach_value(smcfg, sm, {
sm->repo = NULL; /* disconnect from repo */;
git_submodule_free(sm);
});
git_strmap_free(smcfg);
if ((cache = git__swap(repo->_submodules, NULL)) != NULL)
submodule_cache_free(cache);
}
int git_submodule_add_setup(
......@@ -261,18 +324,16 @@ int git_submodule_add_setup(
/* see if there is already an entry for this submodule */
if (git_submodule_lookup(&sm, repo, path) < 0)
if (git_submodule_lookup(NULL, repo, path) < 0)
giterr_clear();
else {
git_submodule_free(sm);
giterr_set(GITERR_SUBMODULE,
"Attempt to add a submodule that already exists");
"Attempt to add submodule '%s' that already exists", path);
return GIT_EEXISTS;
}
/* resolve parameters */
error = git_submodule_resolve_url(&real_url, repo, url);
if (error)
if ((error = git_submodule_resolve_url(&real_url, repo, url)) < 0)
goto cleanup;
/* validate and normalize path */
......@@ -288,9 +349,9 @@ int git_submodule_add_setup(
/* update .gitmodules */
if ((mods = open_gitmodules(repo, true, NULL)) == NULL) {
if (!(mods = open_gitmodules(repo->_submodules, GITMODULES_CREATE))) {
giterr_set(GITERR_SUBMODULE,
"Adding submodules to a bare repository is not supported (for now)");
"Adding submodules to a bare repository is not supported");
return -1;
}
......@@ -328,8 +389,8 @@ int git_submodule_add_setup(
else if (use_gitlink) {
git_buf repodir = GIT_BUF_INIT;
error = git_buf_join_n(
&repodir, '/', 3, git_repository_path(repo), "modules", path);
error = git_buf_join3(
&repodir, '/', git_repository_path(repo), "modules", path);
if (error < 0)
goto cleanup;
......@@ -348,10 +409,18 @@ int git_submodule_add_setup(
/* add submodule to hash and "reload" it */
if (!(error = submodule_get(&sm, repo, path, NULL)) &&
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);
......@@ -360,8 +429,7 @@ cleanup:
if (out != NULL)
*out = sm;
if (mods != NULL)
git_config_file_free(mods);
git_config_file_free(mods);
git_repository_free(subrepo);
git_buf_free(&real_url);
git_buf_free(&name);
......@@ -489,10 +557,10 @@ int git_submodule_save(git_submodule *submodule)
assert(submodule);
mods = open_gitmodules(submodule->repo, true, NULL);
mods = open_gitmodules(submodule->repo->_submodules, GITMODULES_CREATE);
if (!mods) {
giterr_set(GITERR_SUBMODULE,
"Adding submodules to a bare repository is not supported (for now)");
"Adding submodules to a bare repository is not supported");
return -1;
}
......@@ -539,8 +607,7 @@ int git_submodule_save(git_submodule *submodule)
submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
cleanup:
if (mods != NULL)
git_config_file_free(mods);
git_config_file_free(mods);
git_buf_free(&key);
return error;
......@@ -576,8 +643,8 @@ int git_submodule_resolve_url(git_buf *out, git_repository *repo, const char *ur
assert(url);
if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) {
if (!(error = lookup_head_remote(out, repo)))
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);
......@@ -715,46 +782,95 @@ git_submodule_recurse_t git_submodule_set_fetch_recurse_submodules(
return old;
}
int git_submodule_init(git_submodule *submodule, int overwrite)
int git_submodule_init(git_submodule *sm, int overwrite)
{
int error;
const char *val;
git_buf key = GIT_BUF_INIT;
git_config *cfg = NULL;
/* write "submodule.NAME.url" */
if (!submodule->url) {
if (!sm->url) {
giterr_set(GITERR_SUBMODULE,
"No URL configured for submodule '%s'", submodule->name);
"No URL configured for submodule '%s'", sm->name);
return -1;
}
error = submodule_update_config(
submodule, "url", submodule->url, overwrite != 0, false);
if (error < 0)
if ((error = git_repository_config(&cfg, sm->repo)) < 0)
return error;
/* write "submodule.NAME.url" */
if ((error = git_buf_printf(&key, "submodule.%s.url", sm->name)) < 0 ||
(error = git_config__update_entry(
cfg, key.ptr, sm->url, overwrite != 0, false)) < 0)
goto cleanup;
/* write "submodule.NAME.update" if not default */
val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?
NULL : git_submodule_update_to_str(submodule->update);
error = submodule_update_config(
submodule, "update", val, (overwrite != 0), false);
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);
return error;
}
int git_submodule_sync(git_submodule *submodule)
int git_submodule_sync(git_submodule *sm)
{
if (!submodule->url) {
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'", submodule->name);
"No URL configured for submodule '%s'", sm->name);
return -1;
}
/* copy URL over to config only if it already exists */
return submodule_update_config(
submodule, "url", submodule->url, true, true);
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(
......@@ -821,64 +937,9 @@ int git_submodule_open(git_repository **subrepo, git_submodule *sm)
return git_submodule__open(subrepo, sm, false);
}
static void submodule_cache_remove_item(
git_strmap *cache,
const char *name,
git_submodule *expected,
bool free_after_remove)
{
khiter_t pos;
git_submodule *found;
if (!cache)
return;
pos = git_strmap_lookup_index(cache, name);
if (!git_strmap_valid_index(cache, pos))
return;
found = git_strmap_value_at(cache, pos);
if (expected && found != expected)
return;
git_strmap_set_value_at(cache, pos, NULL);
git_strmap_delete_at(cache, pos);
if (free_after_remove)
git_submodule_free(found);
}
int git_submodule_reload_all(git_repository *repo, int force)
{
int error = 0;
git_submodule *sm;
GIT_UNUSED(force);
assert(repo);
if (repo->submodules)
git_strmap_foreach_value(repo->submodules, sm, { sm->flags = 0; });
if ((error = load_submodule_config(repo, true)) < 0)
return error;
git_strmap_foreach_value(repo->submodules, sm, {
git_strmap *cache = repo->submodules;
if (sm && (sm->flags & GIT_SUBMODULE_STATUS__IN_FLAGS) == 0) {
/* we must check path != name before first remove, in case
* that call frees the submodule */
bool free_as_path = (sm->path != sm->name);
submodule_cache_remove_item(cache, sm->name, sm, true);
if (free_as_path)
submodule_cache_remove_item(cache, sm->path, sm, true);
}
});
return error;
return submodule_cache_init(repo, force ? CACHE_FLUSH : CACHE_REFRESH);
}
static void submodule_update_from_index_entry(
......@@ -955,41 +1016,44 @@ static int submodule_update_head(git_submodule *submodule)
}
int git_submodule_reload(git_submodule *submodule, int force)
int git_submodule_reload(git_submodule *sm, int force)
{
int error = 0;
git_config_backend *mods;
git_submodule_cache *cache;
GIT_UNUSED(force);
assert(submodule);
assert(sm);
cache = sm->repo->_submodules;
/* refresh index data */
if ((error = submodule_update_index(submodule)) < 0)
if ((error = submodule_update_index(sm)) < 0)
return error;
/* refresh HEAD tree data */
if ((error = submodule_update_head(submodule)) < 0)
if ((error = submodule_update_head(sm)) < 0)
return error;
/* done if bare */
if (git_repository_is_bare(submodule->repo))
if (git_repository_is_bare(sm->repo))
return error;
/* refresh config data */
mods = open_gitmodules(submodule->repo, false, NULL);
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, submodule->name);
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, submodule->repo);
mods, path.ptr, submodule_load_from_config, cache);
git_buf_free(&path);
git_config_file_free(mods);
......@@ -999,9 +1063,11 @@ int git_submodule_reload(git_submodule *submodule, int force)
}
/* refresh wd data */
submodule->flags &= ~GIT_SUBMODULE_STATUS__ALL_WD_FLAGS;
sm->flags &=
~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID |
GIT_SUBMODULE_STATUS__WD_FLAGS);
return submodule_load_from_wd_lite(submodule);
return submodule_load_from_wd_lite(sm);
}
static void submodule_copy_oid_maybe(
......@@ -1095,34 +1161,69 @@ int git_submodule_location(unsigned int *location, git_submodule *sm)
* INTERNAL FUNCTIONS
*/
static git_submodule *submodule_alloc(git_repository *repo, const char *name)
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 NULL;
return -1;
}
sm = git__calloc(1, sizeof(git_submodule));
if (sm == NULL)
return NULL;
GITERR_CHECK_ALLOC(sm);
sm->name = sm->path = git__strdup(name);
if (!sm->name) {
git__free(sm);
return NULL;
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 = repo;
sm->repo = cache->repo;
sm->branch = NULL;
return sm;
*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)
......@@ -1131,10 +1232,9 @@ static void submodule_release(git_submodule *sm)
return;
if (sm->repo) {
git_strmap *cache = sm->repo->submodules;
submodule_cache_remove_item(cache, sm->name, sm, false);
if (sm->path != sm->name)
submodule_cache_remove_item(cache, sm->path, sm, false);
git_submodule_cache *cache = sm->repo->_submodules;
sm->repo = NULL;
submodule_cache_remove_item(cache, sm, false);
}
if (sm->path != sm->name)
......@@ -1155,40 +1255,39 @@ void git_submodule_free(git_submodule *sm)
static int submodule_get(
git_submodule **out,
git_repository *repo,
git_submodule_cache *cache,
const char *name,
const char *alternate)
{
int error = 0;
git_strmap *smcfg = repo->submodules;
khiter_t pos;
git_submodule *sm;
pos = git_strmap_lookup_index(smcfg, name);
pos = git_strmap_lookup_index(cache->submodules, name);
if (!git_strmap_valid_index(smcfg, pos) && alternate)
pos = git_strmap_lookup_index(smcfg, alternate);
if (!git_strmap_valid_index(cache->submodules, pos) && alternate)
pos = git_strmap_lookup_index(cache->submodules, alternate);
if (!git_strmap_valid_index(smcfg, pos)) {
sm = submodule_alloc(repo, name);
GITERR_CHECK_ALLOC(sm);
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, smcfg, sm->name, &error);
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(smcfg, pos);
sm = git_strmap_value_at(cache->submodules, pos);
} else {
error = 0;
git_strmap_set_value_at(smcfg, pos, sm);
git_strmap_set_value_at(cache->submodules, pos, sm);
}
} else {
sm = git_strmap_value_at(smcfg, pos);
sm = git_strmap_value_at(cache->submodules, pos);
}
done:
......@@ -1254,10 +1353,10 @@ int git_submodule_parse_recurse(git_submodule_recurse_t *out, const char *value)
static int submodule_load_from_config(
const git_config_entry *entry, void *payload)
{
git_repository *repo = payload;
git_strmap *smcfg = repo->submodules;
const char *namestart, *property, *alternate = NULL;
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;
......@@ -1275,7 +1374,7 @@ static int submodule_load_from_config(
path = !strcasecmp(property, "path") ? value : NULL;
if ((error = git_buf_set(&name, namestart, property - namestart - 1)) < 0 ||
(error = submodule_get(&sm, repo, name.ptr, path)) < 0)
(error = submodule_get(&sm, cache, name.ptr, path)) < 0)
goto done;
sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;
......@@ -1288,20 +1387,42 @@ static int submodule_load_from_config(
* should be strcasecmp
*/
if (strcmp(sm->name, name.ptr) != 0) {
alternate = sm->name = git_buf_detach(&name);
} else if (path && strcmp(path, sm->path) != 0) {
alternate = sm->path = git__strdup(value);
if (!sm->path) {
error = -1;
goto done;
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;
}
}
/* Found a alternate key for the submodule */
/* 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(smcfg, alternate, sm, old_sm, error);
git_strmap_insert2(cache->submodules, alternate, sm, old_sm, error);
if (error < 0)
goto done;
......@@ -1383,36 +1504,33 @@ static int submodule_load_from_wd_lite(git_submodule *sm)
return 0;
}
static int load_submodule_config_from_index(
git_repository *repo, git_oid *gitmodules_oid)
static int submodule_cache_refresh_from_index(
git_submodule_cache *cache, git_index *idx)
{
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)
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(repo->submodules, entry->path);
khiter_t pos = git_strmap_lookup_index(cache->submodules, entry->path);
git_submodule *sm;
if (git_strmap_valid_index(repo->submodules, pos)) {
sm = git_strmap_value_at(repo->submodules, pos);
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, repo, entry->path, NULL)) {
if (!submodule_get(&sm, cache, entry->path, NULL)) {
submodule_update_from_index_entry(sm, entry);
git_submodule_free(sm);
}
} else if (strcmp(entry->path, GIT_MODULES_FILE) == 0)
git_oid_cpy(gitmodules_oid, &entry->id);
}
}
if (error == GIT_ITEROVER)
......@@ -1423,46 +1541,33 @@ static int load_submodule_config_from_index(
return error;
}
static int load_submodule_config_from_head(
git_repository *repo, git_oid *gitmodules_oid)
static int submodule_cache_refresh_from_head(
git_submodule_cache *cache, git_tree *head)
{
int error;
git_tree *head;
git_iterator *i;
const git_index_entry *entry;
/* if we can't look up current head, then there's no submodule in it */
if (git_repository_head_tree(&head, repo) < 0) {
giterr_clear();
return 0;
}
if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) {
git_tree_free(head);
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(repo->submodules, entry->path);
khiter_t pos = git_strmap_lookup_index(cache->submodules, entry->path);
git_submodule *sm;
if (git_strmap_valid_index(repo->submodules, pos)) {
sm = git_strmap_value_at(repo->submodules, pos);
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);
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, repo, entry->path, NULL)) {
if (!submodule_get(&sm, cache, entry->path, NULL)) {
submodule_update_from_head_data(
sm, entry->mode, &entry->id);
git_submodule_free(sm);
}
} else if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&
git_oid_iszero(gitmodules_oid)) {
git_oid_cpy(gitmodules_oid, &entry->id);
}
}
......@@ -1470,17 +1575,15 @@ static int load_submodule_config_from_head(
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)
git_submodule_cache *cache,
int okay_to_create)
{
const char *workdir = git_repository_workdir(repo);
const char *workdir = git_repository_workdir(cache->repo);
git_buf path = GIT_BUF_INIT;
git_config_backend *mods = NULL;
......@@ -1500,186 +1603,281 @@ static git_config_backend *open_gitmodules(
}
}
if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) {
/* TODO: Retrieve .gitmodules content from ODB */
git_buf_free(&path);
/* Should we actually do this? Core git does not, but it means you
* can't really get much information about submodules on bare repos.
*/
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;
}
git_buf_free(&path);
cache->submodules = git_strmap_alloc();
if (!cache->submodules) {
submodule_cache_free(cache);
return -1;
}
return mods;
cache->repo = repo;
git_buf_init(&cache->gitmodules_path, 0);
*out = cache;
return 0;
}
static int load_submodule_config(git_repository *repo, bool reload)
static int submodule_cache_refresh(git_submodule_cache *cache, int refresh)
{
int error;
git_oid gitmodules_oid;
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 (!reload && repo->submodules)
if (!cache || !cache->repo || !refresh)
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);
if (git_mutex_lock(&cache->lock) < 0) {
giterr_set(GITERR_OS, "Unable to acquire lock on submodule cache");
return -1;
}
/* TODO: only do the following if the sources appear modified */
/* get sources that we will need to check */
/* add submodule information from index */
if (git_repository_index(&idx, cache->repo) < 0)
giterr_clear();
if (git_repository_head_tree(&head, cache->repo) < 0)
giterr_clear();
if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0)
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);
if (update_gitmod < 0)
giterr_clear();
}
/* 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 ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0)
goto cleanup;
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 ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL &&
(error = git_config_file_foreach(
mods, submodule_load_from_config, repo)) < 0)
goto cleanup;
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 */
/* shallow scan submodules in work tree as needed */
if (!git_repository_is_bare(repo)) {
git_submodule *sm;
git_strmap_foreach_value(repo->submodules, sm, {
sm->flags &= ~GIT_SUBMODULE_STATUS__ALL_WD_FLAGS;
});
git_strmap_foreach_value(repo->submodules, sm, {
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:
if (mods != NULL)
git_config_file_free(mods);
git_config_file_free(mods);
/* TODO: if we got an error, mark submodule config as invalid? */
if (error)
git_submodule_config_free(repo);
git_mutex_unlock(&cache->lock);
git_index_free(idx);
git_tree_free(head);
git_buf_free(&path);
return error;
}
static int lookup_head_remote(git_buf *url, git_repository *repo)
static int submodule_cache_init(git_repository *repo, int cache_refresh)
{
int error;
git_config *cfg;
git_reference *head = NULL, *remote = NULL;
const char *tgt, *scan;
git_buf key = GIT_BUF_INIT;
int error = 0;
git_submodule_cache *cache = NULL;
/* 1. resolve HEAD -> refs/heads/BRANCH
* 2. lookup config branch.BRANCH.remote -> ORIGIN
* 3. lookup remote.ORIGIN.url
*/
/* if submodules already exist, just refresh as requested */
if (repo->_submodules)
return submodule_cache_refresh(repo->_submodules, cache_refresh);
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
return error;
/* 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);
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;
}
/* might have raced with another thread to set cache, so free if needed */
if (cache)
submodule_cache_free(cache);
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;
}
return error;
}
if ((error = git_branch_upstream(&remote, head)) < 0)
goto cleanup;
/* 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;
/* remote should refer to something like refs/remotes/ORIGIN/BRANCH */
/* lookup and dereference HEAD */
if ((error = git_repository_head(&head, repo)) < 0)
return error;
if (git_reference_type(remote) != GIT_REF_SYMBOLIC ||
git__prefixcmp(git_reference_symbolic_target(remote), GIT_REFS_REMOTES_DIR) != 0)
/* lookup remote tracking branch of HEAD */
if (!(error = git_branch_upstream_name(
&upstream_name, repo, git_reference_name(head))))
{
giterr_set(GITERR_SUBMODULE,
"Cannot resolve relative URL when HEAD is not symbolic");
error = GIT_ENOTFOUND;
goto cleanup;
/* lookup remote of remote tracking branch */
error = git_branch_remote_name(remote_name, repo, upstream_name.ptr);
git_buf_free(&upstream_name);
}
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 */
git_reference_free(head);
error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt);
if (error < 0)
goto cleanup;
return error;
}
if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0)
goto cleanup;
/* 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;
error = git_buf_sets(url, tgt);
/* lookup remote of remote tracking branch name */
if (!(error = lookup_head_remote_key(&remote_name, repo)))
error = git_remote_load(remote, repo, remote_name.ptr);
cleanup:
git_buf_free(&key);
git_reference_free(head);
git_reference_free(remote);
git_buf_free(&remote_name);
return error;
}
static int submodule_update_config(
git_submodule *submodule,
const char *attr,
const char *value,
bool overwrite,
bool only_existing)
/* Lookup remote, either from HEAD or fall back on origin */
static int lookup_default_remote(git_remote **remote, git_repository *repo)
{
int error;
git_config *config;
git_buf key = GIT_BUF_INIT;
const git_config_entry *ce = NULL;
int error = lookup_head_remote(remote, repo);
assert(submodule);
/* if that failed, use 'origin' instead */
if (error == GIT_ENOTFOUND)
error = git_remote_load(remote, repo, "origin");
error = git_repository_config__weakptr(&config, submodule->repo);
if (error < 0)
return error;
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");
error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr);
if (error < 0)
goto cleanup;
if ((error = git_config__lookup_entry(&ce, config, key.ptr, false)) < 0)
goto cleanup;
return error;
}
if (!ce && only_existing)
goto cleanup;
if (ce && !overwrite)
goto cleanup;
if (value && ce && ce->value && !strcmp(ce->value, value))
goto cleanup;
if (!value && (!ce || !ce->value))
goto cleanup;
static int get_url_base(git_buf *url, git_repository *repo)
{
int error;
git_remote *remote = NULL;
if (!value)
error = git_config_delete_entry(config, key.ptr);
else
error = git_config_set_string(config, key.ptr, value);
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));
}
cleanup:
git_buf_free(&key);
return error;
}
......
......@@ -99,6 +99,29 @@ struct git_submodule {
git_oid wd_oid;
};
/**
* The git_submodule_cache stores known submodules along with timestamps,
* etc. about when they were loaded
*/
typedef struct {
git_repository *repo;
git_strmap *submodules;
git_mutex lock;
/* cache invalidation data */
git_oid head_id;
git_futils_filestamp index_stamp;
git_buf gitmodules_path;
git_futils_filestamp gitmodules_stamp;
git_futils_filestamp config_stamp;
} git_submodule_cache;
/* Force revalidation of submodule data cache (alloc as needed) */
extern int git_submodule_cache_refresh(git_repository *repo);
/* Release all submodules */
extern void git_submodule_cache_free(git_repository *repo);
/* Additional flags on top of public GIT_SUBMODULE_STATUS values */
enum {
GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20),
......@@ -111,17 +134,16 @@ enum {
GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27),
};
#define GIT_SUBMODULE_STATUS__ALL_WD_FLAGS \
(GIT_SUBMODULE_STATUS_IN_WD | \
GIT_SUBMODULE_STATUS__WD_OID_VALID | \
GIT_SUBMODULE_STATUS__WD_FLAGS)
#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \
((S) & ~(0xFFFFFFFFu << 20))
/* Internal submodule check does not attempt to refresh cached data */
extern bool git_submodule__is_submodule(git_repository *repo, const char *name);
/* Internal lookup does not attempt to refresh cached data */
extern int git_submodule__lookup(
git_submodule **out, git_repository *repo, const char *path);
/* Internal status fn returns status and optionally the various OIDs */
extern int git_submodule__status(
unsigned int *out_status,
......@@ -143,5 +165,6 @@ extern int git_submodule_parse_update(
extern const char *git_submodule_ignore_to_str(git_submodule_ignore_t);
extern const char *git_submodule_update_to_str(git_submodule_update_t);
extern const char *git_submodule_recurse_to_str(git_submodule_recurse_t);
#endif
......@@ -54,7 +54,7 @@ int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp)
bytes = src->length * sizeof(void *);
v->_alloc_size = src->length;
v->_cmp = cmp;
v->_cmp = cmp ? cmp : src->_cmp;
v->length = src->length;
v->flags = src->flags;
if (cmp != src->_cmp)
......
......@@ -599,6 +599,38 @@ void test_core_buffer__10(void)
git_buf_free(&a);
}
void test_core_buffer__join3(void)
{
git_buf a = GIT_BUF_INIT;
cl_git_pass(git_buf_join3(&a, '/', "test", "string", "join"));
cl_assert_equal_s("test/string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "test/", "string", "join"));
cl_assert_equal_s("test/string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "test/", "/string", "join"));
cl_assert_equal_s("test/string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "test/", "/string/", "join"));
cl_assert_equal_s("test/string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "test/", "/string/", "/join"));
cl_assert_equal_s("test/string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "", "string", "join"));
cl_assert_equal_s("string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "", "string/", "join"));
cl_assert_equal_s("string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "", "string/", "/join"));
cl_assert_equal_s("string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "string", "", "join"));
cl_assert_equal_s("string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "string/", "", "join"));
cl_assert_equal_s("string/join", a.ptr);
cl_git_pass(git_buf_join3(&a, '/', "string/", "", "/join"));
cl_assert_equal_s("string/join", a.ptr);
git_buf_free(&a);
}
void test_core_buffer__11(void)
{
git_buf a = GIT_BUF_INIT;
......
......@@ -131,8 +131,6 @@ void test_diff_submodules__dirty_submodule_2(void)
g_repo = setup_fixture_submodules();
cl_git_pass(git_submodule_reload_all(g_repo, 1));
opts.flags = GIT_DIFF_INCLUDE_UNTRACKED |
GIT_DIFF_SHOW_UNTRACKED_CONTENT |
GIT_DIFF_RECURSE_UNTRACKED_DIRS |
......@@ -165,8 +163,6 @@ void test_diff_submodules__dirty_submodule_2(void)
git_diff_free(diff);
cl_git_pass(git_submodule_reload_all(g_repo, 1));
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
check_diff_patches(diff, expected_dirty);
git_diff_free(diff);
......@@ -299,7 +295,6 @@ void test_diff_submodules__invalid_cache(void)
git_submodule_free(sm);
cl_git_pass(git_submodule_reload_all(g_repo, 1));
cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath));
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
......
#include "clar_libgit2.h"
#include "posix.h"
#include "path.h"
#include "submodule_helpers.h"
static git_repository *g_repo = NULL;
void test_submodule_add__cleanup(void)
{
cl_git_sandbox_cleanup();
}
static void assert_submodule_url(const char* name, const char *url)
{
git_config *cfg;
const char *s;
git_buf key = GIT_BUF_INIT;
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(git_buf_printf(&key, "submodule.%s.url", name));
cl_git_pass(git_config_get_string(&s, cfg, git_buf_cstr(&key)));
cl_assert_equal_s(s, url);
git_config_free(cfg);
git_buf_free(&key);
}
void test_submodule_add__url_absolute(void)
{
git_submodule *sm;
g_repo = setup_fixture_submod2();
/* re-add existing submodule */
cl_git_fail_with(
GIT_EEXISTS,
git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1));
/* add a submodule using a gitlink */
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2", 1)
);
git_submodule_free(sm);
cl_assert(git_path_isfile("submod2/" "sm_libgit2" "/.git"));
cl_assert(git_path_isdir("submod2/.git/modules"));
cl_assert(git_path_isdir("submod2/.git/modules/" "sm_libgit2"));
cl_assert(git_path_isfile("submod2/.git/modules/" "sm_libgit2" "/HEAD"));
assert_submodule_url("sm_libgit2", "https://github.com/libgit2/libgit2.git");
/* add a submodule not using a gitlink */
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_libgit2b", 0)
);
git_submodule_free(sm);
cl_assert(git_path_isdir("submod2/" "sm_libgit2b" "/.git"));
cl_assert(git_path_isfile("submod2/" "sm_libgit2b" "/.git/HEAD"));
cl_assert(!git_path_exists("submod2/.git/modules/" "sm_libgit2b"));
assert_submodule_url("sm_libgit2b", "https://github.com/libgit2/libgit2.git");
}
void test_submodule_add__url_relative(void)
{
git_submodule *sm;
git_remote *remote;
/* default remote url is https://github.com/libgit2/false.git */
g_repo = cl_git_sandbox_init("testrepo2");
/* make sure we don't default to origin - rename origin -> test_remote */
cl_git_pass(git_remote_load(&remote, g_repo, "origin"));
cl_git_pass(git_remote_rename(remote, "test_remote", NULL, NULL));
cl_git_fail(git_remote_load(&remote, g_repo, "origin"));
git_remote_free(remote);
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1)
);
git_submodule_free(sm);
assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository");
}
void test_submodule_add__url_relative_to_origin(void)
{
git_submodule *sm;
/* default remote url is https://github.com/libgit2/false.git */
g_repo = cl_git_sandbox_init("testrepo2");
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, "../TestGitRepository", "TestGitRepository", 1)
);
git_submodule_free(sm);
assert_submodule_url("TestGitRepository", "https://github.com/libgit2/TestGitRepository");
}
void test_submodule_add__url_relative_to_workdir(void)
{
git_submodule *sm;
/* In this repo, HEAD (master) has no remote tracking branc h*/
g_repo = cl_git_sandbox_init("testrepo");
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, "./", "TestGitRepository", 1)
);
git_submodule_free(sm);
assert_submodule_url("TestGitRepository", git_repository_workdir(g_repo));
}
#include "clar_libgit2.h"
#include "submodule_helpers.h"
#include "posix.h"
#include "git2/sys/repository.h"
#include "fileops.h"
static git_repository *g_repo = NULL;
......@@ -115,13 +115,7 @@ void test_submodule_lookup__lookup_even_with_unborn_head(void)
&head, g_repo, "HEAD", "refs/heads/garbage", 1, NULL, NULL));
git_reference_free(head);
assert_submodule_exists(g_repo, "sm_unchanged");
assert_submodule_exists(g_repo, "sm_added_and_uncommited");
assert_submodule_exists(g_repo, "sm_gitmodules_only");
refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "just_a_file", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "no_such_file", GIT_ENOTFOUND);
test_submodule_lookup__simple_lookup(); /* baseline should still pass */
}
void test_submodule_lookup__lookup_even_with_missing_index(void)
......@@ -133,44 +127,145 @@ void test_submodule_lookup__lookup_even_with_missing_index(void)
git_repository_set_index(g_repo, idx);
git_index_free(idx);
test_submodule_lookup__simple_lookup(); /* baseline should still pass */
}
static void baseline_tests(void)
{
/* small baseline that should work even if we change the index or make
* commits from the index
*/
assert_submodule_exists(g_repo, "sm_unchanged");
assert_submodule_exists(g_repo, "sm_added_and_uncommited");
assert_submodule_exists(g_repo, "sm_gitmodules_only");
refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "just_a_file", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "no_such_file", GIT_ENOTFOUND);
}
void test_submodule_lookup__just_added(void)
static void add_submodule_with_commit(const char *name)
{
git_submodule *sm;
git_repository *smrepo;
git_index *idx;
git_buf p = GIT_BUF_INIT;
cl_git_pass(git_submodule_add_setup(&sm, g_repo,
"https://github.com/libgit2/libgit2.git", name, 1));
assert_submodule_exists(g_repo, name);
cl_git_pass(git_submodule_open(&smrepo, sm));
cl_git_pass(git_repository_index(&idx, smrepo));
cl_git_pass(git_buf_joinpath(&p, git_repository_workdir(smrepo), "file"));
cl_git_mkfile(p.ptr, "new file");
git_buf_free(&p);
cl_git_pass(git_index_add_bypath(idx, "file"));
cl_git_pass(git_index_write(idx));
git_index_free(idx);
cl_repo_commit_from_index(NULL, smrepo, NULL, 0, "initial commit");
git_repository_free(smrepo);
cl_git_pass(git_submodule_add_finalize(sm));
cl_git_pass(git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_just_added", 1));
git_submodule_free(sm);
}
void test_submodule_lookup__just_added(void)
{
git_submodule *sm;
git_buf snap1 = GIT_BUF_INIT, snap2 = GIT_BUF_INIT;
git_reference *original_head = NULL;
refute_submodule_exists(g_repo, "sm_just_added", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "sm_just_added_2", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "sm_just_added_idx", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "sm_just_added_head", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND);
baseline_tests();
cl_git_pass(git_futils_readbuffer(&snap1, "submod2/.gitmodules"));
cl_git_pass(git_repository_head(&original_head, g_repo));
cl_git_pass(git_submodule_add_setup(&sm, g_repo,
"https://github.com/libgit2/libgit2.git", "sm_just_added", 1));
git_submodule_free(sm);
assert_submodule_exists(g_repo, "sm_just_added");
cl_git_pass(git_submodule_add_setup(&sm, g_repo, "https://github.com/libgit2/libgit2.git", "sm_just_added_2", 1));
cl_git_pass(git_submodule_add_setup(&sm, g_repo,
"https://github.com/libgit2/libgit2.git", "sm_just_added_2", 1));
assert_submodule_exists(g_repo, "sm_just_added_2");
cl_git_fail(git_submodule_add_finalize(sm)); /* fails if no HEAD */
git_submodule_free(sm);
cl_git_append2file("submod2/.gitmodules", "\n[submodule \"mismatch_name\"]\n\tpath = mismatch_path\n\turl = https://example.com/example.git\n\n");
add_submodule_with_commit("sm_just_added_head");
cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit new sm to head");
assert_submodule_exists(g_repo, "sm_just_added_head");
add_submodule_with_commit("sm_just_added_idx");
assert_submodule_exists(g_repo, "sm_just_added_idx");
cl_git_pass(git_submodule_reload_all(g_repo, 1));
cl_git_pass(git_futils_readbuffer(&snap2, "submod2/.gitmodules"));
cl_git_append2file(
"submod2/.gitmodules",
"\n[submodule \"mismatch_name\"]\n"
"\tpath = mismatch_path\n"
"\turl = https://example.com/example.git\n\n");
assert_submodule_exists(g_repo, "mismatch_name");
assert_submodule_exists(g_repo, "mismatch_path");
assert_submodule_exists(g_repo, "sm_just_added");
assert_submodule_exists(g_repo, "sm_just_added_2");
assert_submodule_exists(g_repo, "sm_just_added_idx");
assert_submodule_exists(g_repo, "sm_just_added_head");
baseline_tests();
/* all the regular ones should still be working right, too */
cl_git_rewritefile("submod2/.gitmodules", snap2.ptr);
git_buf_free(&snap2);
assert_submodule_exists(g_repo, "sm_unchanged");
assert_submodule_exists(g_repo, "sm_added_and_uncommited");
assert_submodule_exists(g_repo, "sm_gitmodules_only");
refute_submodule_exists(g_repo, "not-submodule", GIT_EEXISTS);
refute_submodule_exists(g_repo, "just_a_dir", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "just_a_file", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "no_such_file", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND);
assert_submodule_exists(g_repo, "sm_just_added");
assert_submodule_exists(g_repo, "sm_just_added_2");
assert_submodule_exists(g_repo, "sm_just_added_idx");
assert_submodule_exists(g_repo, "sm_just_added_head");
baseline_tests();
cl_git_rewritefile("submod2/.gitmodules", snap1.ptr);
git_buf_free(&snap1);
refute_submodule_exists(g_repo, "mismatch_name", GIT_ENOTFOUND);
refute_submodule_exists(g_repo, "mismatch_path", GIT_ENOTFOUND);
/* note error code change, because add_setup made a repo in the workdir */
refute_submodule_exists(g_repo, "sm_just_added", GIT_EEXISTS);
refute_submodule_exists(g_repo, "sm_just_added_2", GIT_EEXISTS);
/* these still exist in index and head respectively */
assert_submodule_exists(g_repo, "sm_just_added_idx");
assert_submodule_exists(g_repo, "sm_just_added_head");
baseline_tests();
{
git_index *idx;
cl_git_pass(git_repository_index(&idx, g_repo));
cl_git_pass(git_index_remove_bypath(idx, "sm_just_added_idx"));
cl_git_pass(git_index_remove_bypath(idx, "sm_just_added_head"));
cl_git_pass(git_index_write(idx));
git_index_free(idx);
}
refute_submodule_exists(g_repo, "sm_just_added_idx", GIT_EEXISTS);
assert_submodule_exists(g_repo, "sm_just_added_head");
{
git_signature *sig;
cl_git_pass(git_signature_now(&sig, "resetter", "resetter@email.com"));
cl_git_pass(git_reference_create(NULL, g_repo, "refs/heads/master", git_reference_target(original_head), 1, sig, "move head back"));
git_signature_free(sig);
git_reference_free(original_head);
}
refute_submodule_exists(g_repo, "sm_just_added_head", GIT_EEXISTS);
}
......@@ -7,61 +7,12 @@ static git_repository *g_repo = NULL;
#define SM_LIBGIT2_URL "https://github.com/libgit2/libgit2.git"
#define SM_LIBGIT2 "sm_libgit2"
#define SM_LIBGIT2B "sm_libgit2b"
void test_submodule_modify__initialize(void)
{
g_repo = setup_fixture_submod2();
}
void test_submodule_modify__add(void)
{
git_submodule *sm;
git_config *cfg;
const char *s;
/* re-add existing submodule */
cl_assert_equal_i(
GIT_EEXISTS,
git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1));
/* add a submodule using a gitlink */
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, SM_LIBGIT2_URL, SM_LIBGIT2, 1)
);
git_submodule_free(sm);
cl_assert(git_path_isfile("submod2/" SM_LIBGIT2 "/.git"));
cl_assert(git_path_isdir("submod2/.git/modules"));
cl_assert(git_path_isdir("submod2/.git/modules/" SM_LIBGIT2));
cl_assert(git_path_isfile("submod2/.git/modules/" SM_LIBGIT2 "/HEAD"));
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(
git_config_get_string(&s, cfg, "submodule." SM_LIBGIT2 ".url"));
cl_assert_equal_s(s, SM_LIBGIT2_URL);
git_config_free(cfg);
/* add a submodule not using a gitlink */
cl_git_pass(
git_submodule_add_setup(&sm, g_repo, SM_LIBGIT2_URL, SM_LIBGIT2B, 0)
);
git_submodule_free(sm);
cl_assert(git_path_isdir("submod2/" SM_LIBGIT2B "/.git"));
cl_assert(git_path_isfile("submod2/" SM_LIBGIT2B "/.git/HEAD"));
cl_assert(!git_path_exists("submod2/.git/modules/" SM_LIBGIT2B));
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(
git_config_get_string(&s, cfg, "submodule." SM_LIBGIT2B ".url"));
cl_assert_equal_s(s, SM_LIBGIT2_URL);
git_config_free(cfg);
}
static int delete_one_config(const git_config_entry *entry, void *payload)
{
git_config *cfg = payload;
......@@ -118,6 +69,26 @@ static int sync_one_submodule(
return git_submodule_sync(sm);
}
static void assert_submodule_url_is_synced(
git_submodule *sm, const char *parent_key, const char *child_key)
{
git_config *cfg;
const char *str;
git_repository *smrepo;
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(git_config_get_string(&str, cfg, parent_key));
cl_assert_equal_s(git_submodule_url(sm), str);
git_config_free(cfg);
cl_git_pass(git_submodule_open(&smrepo, sm));
cl_git_pass(git_repository_config(&cfg, smrepo));
cl_git_pass(git_config_get_string(&str, cfg, child_key));
cl_assert_equal_s(git_submodule_url(sm), str);
git_config_free(cfg);
git_repository_free(smrepo);
}
void test_submodule_modify__sync(void)
{
git_submodule *sm1, *sm2, *sm3;
......@@ -153,14 +124,12 @@ void test_submodule_modify__sync(void)
cl_git_pass(git_submodule_foreach(g_repo, sync_one_submodule, NULL));
/* check that submodule config is updated */
cl_git_pass(git_repository_config(&cfg, g_repo));
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM1".url"));
cl_assert_equal_s(git_submodule_url(sm1), str);
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM2".url"));
cl_assert_equal_s(git_submodule_url(sm2), str);
cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM3".url"));
cl_assert_equal_s(git_submodule_url(sm3), str);
git_config_free(cfg);
assert_submodule_url_is_synced(
sm1, "submodule."SM1".url", "branch.origin.remote");
assert_submodule_url_is_synced(
sm2, "submodule."SM2".url", "branch.origin.remote");
assert_submodule_url_is_synced(
sm3, "submodule."SM3".url", "branch.origin.remote");
git_submodule_free(sm1);
git_submodule_free(sm2);
......
......@@ -2,6 +2,7 @@
#include "clar_libgit2.h"
#include "posix.h"
#include "fileops.h"
void test_submodule_nosubs__cleanup(void)
{
......@@ -68,7 +69,10 @@ void test_submodule_nosubs__reload_add_reload(void)
cl_git_pass(git_submodule_reload_all(repo, 0));
cl_git_pass(git_submodule_add_setup(&sm, repo, "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1));
/* try one add with a reload (to make sure no errors happen) */
cl_git_pass(git_submodule_add_setup(&sm, repo,
"https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1));
cl_git_pass(git_submodule_reload_all(repo, 0));
......@@ -78,6 +82,17 @@ void test_submodule_nosubs__reload_add_reload(void)
cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm));
git_submodule_free(sm);
/* try one add without a reload (to make sure cache inval works, too) */
cl_git_pass(git_submodule_add_setup(&sm, repo,
"https://github.com/libgit2/libgit2.git", "libgit2-again", 1));
cl_assert_equal_s("libgit2-again", git_submodule_name(sm));
git_submodule_free(sm);
cl_git_pass(git_submodule_lookup(&sm, repo, "libgit2-again"));
cl_assert_equal_s("libgit2-again", git_submodule_name(sm));
git_submodule_free(sm);
}
void test_submodule_nosubs__bad_gitmodules(void)
......@@ -93,3 +108,69 @@ void test_submodule_nosubs__bad_gitmodules(void)
cl_git_pass(git_submodule_lookup(NULL, repo, "foobar"));
cl_assert_equal_i(GIT_ENOTFOUND, git_submodule_lookup(NULL, repo, "subdir"));
}
void test_submodule_nosubs__add_and_delete(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_submodule *sm;
git_buf buf = GIT_BUF_INIT;
/* note lack of calls to git_submodule_reload_all - this *should* work */
cl_git_fail(git_submodule_lookup(NULL, repo, "libgit2"));
cl_git_fail(git_submodule_lookup(NULL, repo, "submodules/libgit2"));
/* create */
cl_git_pass(git_submodule_add_setup(
&sm, repo, "https://github.com/libgit2/libgit2.git", "submodules/libgit2", 1));
cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm));
cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm));
git_submodule_free(sm);
cl_git_pass(git_futils_readbuffer(&buf, "status/.gitmodules"));
cl_assert(strstr(buf.ptr, "[submodule \"submodules/libgit2\"]") != NULL);
cl_assert(strstr(buf.ptr, "path = submodules/libgit2") != NULL);
git_buf_free(&buf);
/* lookup */
cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2"));
cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
cl_assert_equal_s("submodules/libgit2", git_submodule_name(sm));
cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm));
git_submodule_free(sm);
/* update name */
cl_git_rewritefile(
"status/.gitmodules",
"[submodule \"libgit2\"]\n"
" path = submodules/libgit2\n"
" url = https://github.com/libgit2/libgit2.git\n");
cl_git_pass(git_submodule_lookup(&sm, repo, "libgit2"));
cl_assert_equal_s("libgit2", git_submodule_name(sm));
cl_assert_equal_s("submodules/libgit2", git_submodule_path(sm));
git_submodule_free(sm);
cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
git_submodule_free(sm);
/* revert name update */
cl_git_rewritefile(
"status/.gitmodules",
"[submodule \"submodules/libgit2\"]\n"
" path = submodules/libgit2\n"
" url = https://github.com/libgit2/libgit2.git\n");
cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2"));
cl_git_pass(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
git_submodule_free(sm);
/* remove completely */
cl_must_pass(p_unlink("status/.gitmodules"));
cl_git_fail(git_submodule_lookup(&sm, repo, "libgit2"));
cl_git_fail(git_submodule_lookup(&sm, repo, "submodules/libgit2"));
}
......@@ -126,20 +126,26 @@ git_repository *setup_fixture_submod2(void)
return repo;
}
void assert_submodule_exists(git_repository *repo, const char *name)
void assert__submodule_exists(
git_repository *repo, const char *name,
const char *msg, const char *file, int line)
{
git_submodule *sm;
cl_git_pass(git_submodule_lookup(&sm, repo, name));
cl_assert(sm);
int error = git_submodule_lookup(&sm, repo, name);
if (error)
cl_git_report_failure(error, file, line, msg);
cl_assert_at_line(sm != NULL, file, line);
git_submodule_free(sm);
}
void refute_submodule_exists(
git_repository *repo, const char *name, int expected_error)
void refute__submodule_exists(
git_repository *repo, const char *name, int expected_error,
const char *msg, const char *file, int line)
{
git_submodule *sm;
cl_assert_equal_i(
expected_error, git_submodule_lookup(&sm, repo, name));
clar__assert_equal(
file, line, msg, 1, "%i",
expected_error, (int)(git_submodule_lookup(&sm, repo, name)));
}
unsigned int get_submodule_status(git_repository *repo, const char *name)
......@@ -154,3 +160,19 @@ unsigned int get_submodule_status(git_repository *repo, const char *name)
return status;
}
static int print_submodules(git_submodule *sm, const char *name, void *p)
{
unsigned int loc = 0;
GIT_UNUSED(p);
git_submodule_location(&loc, sm);
fprintf(stderr, "# submodule %s (at %s) flags %x\n",
name, git_submodule_path(sm), loc);
return 0;
}
void dump_submodules(git_repository *repo)
{
git_submodule_foreach(repo, print_submodules, NULL);
}
......@@ -6,5 +6,16 @@ extern git_repository *setup_fixture_submod2(void);
extern unsigned int get_submodule_status(git_repository *, const char *);
extern void assert_submodule_exists(git_repository *, const char *);
extern void refute_submodule_exists(git_repository *, const char *, int err);
extern void assert__submodule_exists(
git_repository *, const char *, const char *, const char *, int);
#define assert_submodule_exists(repo,name) \
assert__submodule_exists(repo, name, "git_submodule_lookup(" #name ") failed", __FILE__, __LINE__)
extern void refute__submodule_exists(
git_repository *, const char *, int err, const char *, const char *, int);
#define refute_submodule_exists(repo,name,code) \
refute__submodule_exists(repo, name, code, "expected git_submodule_lookup(" #name ") to fail with error " #code, __FILE__, __LINE__)
extern void dump_submodules(git_repository *repo);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment