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)
......
......@@ -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