mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-30 01:39:29 +00:00
Merge pull request #4016 from novalis/submodule-optimization
Submodule optimization
This commit is contained in:
commit
98f5387233
@ -135,6 +135,35 @@ GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index
|
|||||||
*/
|
*/
|
||||||
GIT_EXTERN(int) git_repository_set_bare(git_repository *repo);
|
GIT_EXTERN(int) git_repository_set_bare(git_repository *repo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and cache all submodules.
|
||||||
|
*
|
||||||
|
* Because the `.gitmodules` file is unstructured, loading submodules is an
|
||||||
|
* O(N) operation. Any operation (such as `git_rebase_init`) that requires
|
||||||
|
* accessing all submodules is O(N^2) in the number of submodules, if it
|
||||||
|
* has to look each one up individually. This function loads all submodules
|
||||||
|
* and caches them so that subsequent calls to `git_submodule_lookup` are O(1).
|
||||||
|
*
|
||||||
|
* @param repo the repository whose submodules will be cached.
|
||||||
|
*/
|
||||||
|
GIT_EXTERN(int) git_repository_submodule_cache_all(
|
||||||
|
git_repository *repo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the submodule cache.
|
||||||
|
*
|
||||||
|
* Clear the submodule cache populated by `git_repository_submodule_cache_all`.
|
||||||
|
* If there is no cache, do nothing.
|
||||||
|
*
|
||||||
|
* The cache incorporates data from the repository's configuration, as well
|
||||||
|
* as the state of the working tree, the index, and HEAD. So any time any
|
||||||
|
* of these has changed, the cache might become invalid.
|
||||||
|
*
|
||||||
|
* @param repo the repository whose submodule cache will be cleared
|
||||||
|
*/
|
||||||
|
GIT_EXTERN(int) git_repository_submodule_cache_clear(
|
||||||
|
git_repository *repo);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
GIT_END_DECL
|
GIT_END_DECL
|
||||||
#endif
|
#endif
|
||||||
|
@ -27,6 +27,10 @@
|
|||||||
#include "merge.h"
|
#include "merge.h"
|
||||||
#include "diff_driver.h"
|
#include "diff_driver.h"
|
||||||
#include "annotated_commit.h"
|
#include "annotated_commit.h"
|
||||||
|
#include "submodule.h"
|
||||||
|
|
||||||
|
GIT__USE_STRMAP
|
||||||
|
#include "strmap.h"
|
||||||
|
|
||||||
#ifdef GIT_WIN32
|
#ifdef GIT_WIN32
|
||||||
# include "win32/w32_util.h"
|
# include "win32/w32_util.h"
|
||||||
@ -109,6 +113,7 @@ void git_repository__cleanup(git_repository *repo)
|
|||||||
{
|
{
|
||||||
assert(repo);
|
assert(repo);
|
||||||
|
|
||||||
|
git_repository_submodule_cache_clear(repo);
|
||||||
git_cache_clear(&repo->objects);
|
git_cache_clear(&repo->objects);
|
||||||
git_attr_cache_flush(repo);
|
git_attr_cache_flush(repo);
|
||||||
|
|
||||||
@ -2541,3 +2546,31 @@ int git_repository_set_ident(git_repository *repo, const char *name, const char
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int git_repository_submodule_cache_all(git_repository *repo)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
|
||||||
|
assert(repo);
|
||||||
|
|
||||||
|
if ((error = git_strmap_alloc(&repo->submodule_cache)))
|
||||||
|
return error;
|
||||||
|
|
||||||
|
error = git_submodule__map(repo, repo->submodule_cache);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int git_repository_submodule_cache_clear(git_repository *repo)
|
||||||
|
{
|
||||||
|
git_submodule *sm;
|
||||||
|
assert(repo);
|
||||||
|
if (repo->submodule_cache == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
git_strmap_foreach_value(repo->submodule_cache, sm, {
|
||||||
|
git_submodule_free(sm);
|
||||||
|
});
|
||||||
|
git_strmap_free(repo->submodule_cache);
|
||||||
|
repo->submodule_cache = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -143,6 +143,7 @@ struct git_repository {
|
|||||||
git_atomic attr_session_key;
|
git_atomic attr_session_key;
|
||||||
|
|
||||||
git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX];
|
git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX];
|
||||||
|
git_strmap *submodule_cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
|
GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
|
||||||
|
230
src/submodule.c
230
src/submodule.c
@ -149,40 +149,53 @@ static int find_by_path(const git_config_entry *entry, void *payload)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find out the name of a submodule from its path
|
* Release the name map returned by 'load_submodule_names'.
|
||||||
*/
|
*/
|
||||||
static int name_from_path(git_buf *out, git_config *cfg, const char *path)
|
static void free_submodule_names(git_strmap *names)
|
||||||
|
{
|
||||||
|
git_buf *name = 0;
|
||||||
|
if (names == NULL)
|
||||||
|
return;
|
||||||
|
git_strmap_foreach_value(names, name, {
|
||||||
|
git__free(name);
|
||||||
|
});
|
||||||
|
git_strmap_free(names);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map submodule paths to names.
|
||||||
|
* TODO: for some use-cases, this might need case-folding on a
|
||||||
|
* case-insensitive filesystem
|
||||||
|
*/
|
||||||
|
static int load_submodule_names(git_strmap *out, git_config *cfg)
|
||||||
{
|
{
|
||||||
const char *key = "submodule\\..*\\.path";
|
const char *key = "submodule\\..*\\.path";
|
||||||
git_config_iterator *iter;
|
git_config_iterator *iter;
|
||||||
git_config_entry *entry;
|
git_config_entry *entry;
|
||||||
int error;
|
git_buf buf = GIT_BUF_INIT;
|
||||||
|
int rval;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0)
|
if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
while ((error = git_config_next(&entry, iter)) == 0) {
|
while (git_config_next(&entry, iter) == 0) {
|
||||||
const char *fdot, *ldot;
|
const char *fdot, *ldot;
|
||||||
/* TODO: this should maybe be strcasecmp on a case-insensitive fs */
|
|
||||||
if (strcmp(path, entry->value) != 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
fdot = strchr(entry->name, '.');
|
fdot = strchr(entry->name, '.');
|
||||||
ldot = strrchr(entry->name, '.');
|
ldot = strrchr(entry->name, '.');
|
||||||
|
|
||||||
git_buf_clear(out);
|
git_buf_put(&buf, fdot + 1, ldot - fdot - 1);
|
||||||
git_buf_put(out, fdot + 1, ldot - fdot - 1);
|
git_strmap_insert(out, entry->value, git_buf_detach(&buf), rval);
|
||||||
goto cleanup;
|
if (rval < 0) {
|
||||||
|
giterr_set(GITERR_NOMEMORY, "Error inserting submodule into hash table");
|
||||||
|
free_submodule_names(out);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error == GIT_ITEROVER) {
|
|
||||||
giterr_set(GITERR_SUBMODULE, "could not find a submodule name for '%s'", path);
|
|
||||||
error = GIT_ENOTFOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
git_config_iterator_free(iter);
|
git_config_iterator_free(iter);
|
||||||
return error;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int git_submodule_lookup(
|
int git_submodule_lookup(
|
||||||
@ -196,6 +209,17 @@ int git_submodule_lookup(
|
|||||||
|
|
||||||
assert(repo && name);
|
assert(repo && name);
|
||||||
|
|
||||||
|
if (repo->submodule_cache != NULL) {
|
||||||
|
khiter_t pos = git_strmap_lookup_index(repo->submodule_cache, name);
|
||||||
|
if (git_strmap_valid_index(repo->submodule_cache, pos)) {
|
||||||
|
if (out) {
|
||||||
|
*out = git_strmap_value_at(repo->submodule_cache, pos);
|
||||||
|
GIT_REFCOUNT_INC(*out);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((error = submodule_alloc(&sm, repo, name)) < 0)
|
if ((error = submodule_alloc(&sm, repo, name)) < 0)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
@ -324,89 +348,107 @@ done:
|
|||||||
|
|
||||||
static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg)
|
static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
git_iterator *i;
|
git_iterator *i;
|
||||||
const git_index_entry *entry;
|
const git_index_entry *entry;
|
||||||
git_buf name = GIT_BUF_INIT;
|
git_strmap *names = 0;
|
||||||
|
git_strmap_alloc(&names);
|
||||||
|
if ((error = load_submodule_names(names, cfg)))
|
||||||
|
goto done;
|
||||||
|
|
||||||
if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0)
|
if ((error = git_iterator_for_index(&i, git_index_owner(idx), idx, NULL)) < 0)
|
||||||
return error;
|
goto done;
|
||||||
|
|
||||||
while (!(error = git_iterator_advance(&entry, i))) {
|
while (!(error = git_iterator_advance(&entry, i))) {
|
||||||
khiter_t pos = git_strmap_lookup_index(map, entry->path);
|
khiter_t pos = git_strmap_lookup_index(map, entry->path);
|
||||||
git_submodule *sm;
|
git_submodule *sm;
|
||||||
|
|
||||||
git_buf_clear(&name);
|
if (git_strmap_valid_index(map, pos)) {
|
||||||
if (!name_from_path(&name, cfg, entry->path)) {
|
sm = git_strmap_value_at(map, pos);
|
||||||
git_strmap_lookup_index(map, name.ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (git_strmap_valid_index(map, pos)) {
|
if (S_ISGITLINK(entry->mode))
|
||||||
sm = git_strmap_value_at(map, pos);
|
submodule_update_from_index_entry(sm, entry);
|
||||||
|
else
|
||||||
|
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
|
||||||
|
} else if (S_ISGITLINK(entry->mode)) {
|
||||||
|
khiter_t name_pos;
|
||||||
|
const char *name;
|
||||||
|
|
||||||
if (S_ISGITLINK(entry->mode))
|
name_pos = git_strmap_lookup_index(names, entry->path);
|
||||||
submodule_update_from_index_entry(sm, entry);
|
if (git_strmap_valid_index(names, name_pos)) {
|
||||||
else
|
name = git_strmap_value_at(names, name_pos);
|
||||||
sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE;
|
} else {
|
||||||
} else if (S_ISGITLINK(entry->mode)) {
|
name = entry->path;
|
||||||
if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name.ptr ? name.ptr : entry->path)) {
|
}
|
||||||
submodule_update_from_index_entry(sm, entry);
|
|
||||||
git_submodule_free(sm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error == GIT_ITEROVER)
|
if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name)) {
|
||||||
error = 0;
|
submodule_update_from_index_entry(sm, entry);
|
||||||
|
git_submodule_free(sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
git_buf_free(&name);
|
if (error == GIT_ITEROVER)
|
||||||
git_iterator_free(i);
|
error = 0;
|
||||||
|
|
||||||
return error;
|
done:
|
||||||
|
git_iterator_free(i);
|
||||||
|
free_submodule_names(names);
|
||||||
|
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg)
|
static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
git_iterator *i;
|
git_iterator *i;
|
||||||
const git_index_entry *entry;
|
const git_index_entry *entry;
|
||||||
git_buf name = GIT_BUF_INIT;
|
git_strmap *names = 0;
|
||||||
|
git_strmap_alloc(&names);
|
||||||
|
if ((error = load_submodule_names(names, cfg)))
|
||||||
|
goto done;
|
||||||
|
|
||||||
if ((error = git_iterator_for_tree(&i, head, NULL)) < 0)
|
if ((error = git_iterator_for_tree(&i, head, NULL)) < 0)
|
||||||
return error;
|
goto done;
|
||||||
|
|
||||||
while (!(error = git_iterator_advance(&entry, i))) {
|
while (!(error = git_iterator_advance(&entry, i))) {
|
||||||
khiter_t pos = git_strmap_lookup_index(map, entry->path);
|
khiter_t pos = git_strmap_lookup_index(map, entry->path);
|
||||||
git_submodule *sm;
|
git_submodule *sm;
|
||||||
|
|
||||||
git_buf_clear(&name);
|
if (git_strmap_valid_index(map, pos)) {
|
||||||
if (!name_from_path(&name, cfg, entry->path)) {
|
sm = git_strmap_value_at(map, pos);
|
||||||
git_strmap_lookup_index(map, name.ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (git_strmap_valid_index(map, pos)) {
|
if (S_ISGITLINK(entry->mode))
|
||||||
sm = git_strmap_value_at(map, pos);
|
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)) {
|
||||||
|
khiter_t name_pos;
|
||||||
|
const char *name;
|
||||||
|
|
||||||
if (S_ISGITLINK(entry->mode))
|
name_pos = git_strmap_lookup_index(names, entry->path);
|
||||||
submodule_update_from_head_data(sm, entry->mode, &entry->id);
|
if (git_strmap_valid_index(names, name_pos)) {
|
||||||
else
|
name = git_strmap_value_at(names, name_pos);
|
||||||
sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE;
|
} else {
|
||||||
} else if (S_ISGITLINK(entry->mode)) {
|
name = entry->path;
|
||||||
if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name.ptr ? name.ptr : entry->path)) {
|
}
|
||||||
submodule_update_from_head_data(
|
|
||||||
sm, entry->mode, &entry->id);
|
|
||||||
git_submodule_free(sm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error == GIT_ITEROVER)
|
if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name)) {
|
||||||
error = 0;
|
submodule_update_from_head_data(
|
||||||
|
sm, entry->mode, &entry->id);
|
||||||
|
git_submodule_free(sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
git_buf_free(&name);
|
if (error == GIT_ITEROVER)
|
||||||
git_iterator_free(i);
|
error = 0;
|
||||||
|
|
||||||
return error;
|
done:
|
||||||
|
git_iterator_free(i);
|
||||||
|
free_submodule_names(names);
|
||||||
|
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If have_sm is true, sm is populated, otherwise map an repo are. */
|
/* If have_sm is true, sm is populated, otherwise map an repo are. */
|
||||||
@ -416,7 +458,7 @@ typedef struct {
|
|||||||
git_repository *repo;
|
git_repository *repo;
|
||||||
} lfc_data;
|
} lfc_data;
|
||||||
|
|
||||||
static int all_submodules(git_repository *repo, git_strmap *map)
|
int git_submodule__map(git_repository *repo, git_strmap *map)
|
||||||
{
|
{
|
||||||
int error = 0;
|
int error = 0;
|
||||||
git_index *idx = NULL;
|
git_index *idx = NULL;
|
||||||
@ -509,7 +551,7 @@ int git_submodule_foreach(
|
|||||||
if ((error = git_strmap_alloc(&submodules)) < 0)
|
if ((error = git_strmap_alloc(&submodules)) < 0)
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
if ((error = all_submodules(repo, submodules)) < 0)
|
if ((error = git_submodule__map(repo, submodules)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
if (!(error = git_vector_init(
|
if (!(error = git_vector_init(
|
||||||
@ -1502,13 +1544,22 @@ int git_submodule__status(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* refresh the index OID */
|
/* If the user has requested caching submodule state, performing these
|
||||||
if (submodule_update_index(sm) < 0)
|
* expensive operations (especially `submodule_update_head`, which is
|
||||||
return -1;
|
* bottlenecked on `git_repository_head_tree`) eliminates much of the
|
||||||
|
* advantage. We will, therefore, interpret the request for caching to
|
||||||
|
* apply here to and skip them.
|
||||||
|
*/
|
||||||
|
|
||||||
/* refresh the HEAD OID */
|
if (sm->repo->submodule_cache == NULL) {
|
||||||
if (submodule_update_head(sm) < 0)
|
/* refresh the index OID */
|
||||||
return -1;
|
if (submodule_update_index(sm) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* refresh the HEAD OID */
|
||||||
|
if (submodule_update_head(sm) < 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* for ignore == dirty, don't scan the working directory */
|
/* for ignore == dirty, don't scan the working directory */
|
||||||
if (ign == GIT_SUBMODULE_IGNORE_DIRTY) {
|
if (ign == GIT_SUBMODULE_IGNORE_DIRTY) {
|
||||||
@ -1566,7 +1617,6 @@ int git_submodule_location(unsigned int *location, git_submodule *sm)
|
|||||||
location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL);
|
location, NULL, NULL, NULL, sm, GIT_SUBMODULE_IGNORE_ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* INTERNAL FUNCTIONS
|
* INTERNAL FUNCTIONS
|
||||||
*/
|
*/
|
||||||
|
@ -143,4 +143,7 @@ extern int git_submodule_parse_ignore(
|
|||||||
extern int git_submodule_parse_update(
|
extern int git_submodule_parse_update(
|
||||||
git_submodule_update_t *out, const char *value);
|
git_submodule_update_t *out, const char *value);
|
||||||
|
|
||||||
|
extern int git_submodule__map(
|
||||||
|
git_repository *repo,
|
||||||
|
git_strmap *map);
|
||||||
#endif
|
#endif
|
||||||
|
@ -388,3 +388,28 @@ void test_submodule_lookup__renamed(void)
|
|||||||
cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data));
|
cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data));
|
||||||
cl_assert_equal_i(8, data.count);
|
cl_assert_equal_i(8, data.count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_submodule_lookup_cached(void) {
|
||||||
|
git_submodule *sm;
|
||||||
|
git_submodule *sm2;
|
||||||
|
/* See that the simple tests still pass. */
|
||||||
|
|
||||||
|
git_repository_submodule_cache_all(g_repo);
|
||||||
|
test_submodule_lookup__simple_lookup();
|
||||||
|
git_repository_submodule_cache_clear(g_repo);
|
||||||
|
test_submodule_lookup__simple_lookup();
|
||||||
|
|
||||||
|
/* Check that subsequent calls return different objects when cached. */
|
||||||
|
git_repository_submodule_cache_all(g_repo);
|
||||||
|
cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged"));
|
||||||
|
cl_git_pass(git_submodule_lookup(&sm2, g_repo, "sm_unchanged"));
|
||||||
|
cl_assert_equal_p(sm, sm2);
|
||||||
|
git_submodule_free(sm2);
|
||||||
|
|
||||||
|
/* and that we get new objects again after clearing the cache. */
|
||||||
|
git_repository_submodule_cache_clear(g_repo);
|
||||||
|
cl_git_pass(git_submodule_lookup(&sm2, g_repo, "sm_unchanged"));
|
||||||
|
cl_assert(sm != sm2);
|
||||||
|
git_submodule_free(sm);
|
||||||
|
git_submodule_free(sm2);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user