diff options
author | Carlos Martín Nieto <cmn@dwim.me> | 2015-02-12 02:34:58 +0100 |
---|---|---|
committer | Carlos Martín Nieto <cmn@dwim.me> | 2015-02-12 02:34:58 +0100 |
commit | d24a5312d8ab6d3cdb259e450ec9f1e2e6f3399d (patch) | |
tree | 6bdb4b732e9c14560dcf941bf1cc6a1344e169a3 | |
parent | 8e29ae7310a731e3a4dc8f952ae2245f83960673 (diff) | |
parent | fa89ff20cd777fafc3e971bbf8081d97ce082c15 (diff) | |
download | libgit2-d24a5312d8ab6d3cdb259e450ec9f1e2e6f3399d.tar.gz |
Merge pull request #2866 from ethomson/checkout_perf2
Checkout performance
-rw-r--r-- | src/attr.c | 129 | ||||
-rw-r--r-- | src/attr_file.c | 68 | ||||
-rw-r--r-- | src/attr_file.h | 47 | ||||
-rw-r--r-- | src/attrcache.c | 15 | ||||
-rw-r--r-- | src/attrcache.h | 1 | ||||
-rw-r--r-- | src/checkout.c | 90 | ||||
-rw-r--r-- | src/fileops.c | 35 | ||||
-rw-r--r-- | src/fileops.h | 13 | ||||
-rw-r--r-- | src/filter.c | 29 | ||||
-rw-r--r-- | src/filter.h | 10 | ||||
-rw-r--r-- | src/ignore.c | 4 | ||||
-rw-r--r-- | src/repository.h | 2 | ||||
-rw-r--r-- | tests/checkout/tree.c | 54 | ||||
-rw-r--r-- | tests/refs/list.c | 4 | ||||
-rw-r--r-- | tests/repo/iterator.c | 3 | ||||
-rw-r--r-- | tests/resources/testrepo/.gitted/objects/6f/d5c7dd2ab27b48c493023f794be09861e9045f | 1 | ||||
-rw-r--r-- | tests/resources/testrepo/.gitted/objects/c3/6d8ea75da8cb510fcb0c408c1d7e53f9a99dbe | bin | 0 -> 192 bytes | |||
-rw-r--r-- | tests/resources/testrepo/.gitted/objects/e3/6900c3224db4adf4c7f7a09d4ac80247978a13 | bin | 0 -> 59 bytes | |||
-rw-r--r-- | tests/resources/testrepo/.gitted/refs/heads/ident | 1 | ||||
-rw-r--r-- | tests/revwalk/basic.c | 2 |
20 files changed, 379 insertions, 129 deletions
diff --git a/src/attr.c b/src/attr.c index a02172689..2e1ef7c2a 100644 --- a/src/attr.c +++ b/src/attr.c @@ -29,6 +29,7 @@ git_attr_t git_attr_value(const char *attr) static int collect_attr_files( git_repository *repo, + git_attr_session *attr_session, uint32_t flags, const char *path, git_vector *files); @@ -57,7 +58,7 @@ int git_attr_get( if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) return -1; - if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0) goto cleanup; memset(&attr, 0, sizeof(attr)); @@ -90,9 +91,10 @@ typedef struct { git_attr_assignment *found; } attr_get_many_info; -int git_attr_get_many( +int git_attr_get_many_with_session( const char **values, git_repository *repo, + git_attr_session *attr_session, uint32_t flags, const char *pathname, size_t num_attr, @@ -115,7 +117,7 @@ int git_attr_get_many( if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) return -1; - if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0) goto cleanup; info = git__calloc(num_attr, sizeof(attr_get_many_info)); @@ -161,6 +163,17 @@ cleanup: return error; } +int git_attr_get_many( + const char **values, + git_repository *repo, + uint32_t flags, + const char *pathname, + size_t num_attr, + const char **names) +{ + return git_attr_get_many_with_session( + values, repo, NULL, flags, pathname, num_attr, names); +} int git_attr_foreach( git_repository *repo, @@ -183,7 +196,7 @@ int git_attr_foreach( if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) return -1; - if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0 || + if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 || (error = git_strmap_alloc(&seen)) < 0) goto cleanup; @@ -219,6 +232,7 @@ cleanup: static int preload_attr_file( git_repository *repo, + git_attr_session *attr_session, git_attr_file_source source, const char *base, const char *file) @@ -229,19 +243,61 @@ static int preload_attr_file( if (!file) return 0; if (!(error = git_attr_cache__get( - &preload, repo, source, base, file, git_attr_file__parse_buffer))) + &preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer))) git_attr_file__free(preload); return error; } -static int attr_setup(git_repository *repo) +static int system_attr_file( + git_buf *out, + git_attr_session *attr_session) +{ + int error; + + if (!attr_session) { + error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM); + + if (error == GIT_ENOTFOUND) + giterr_clear(); + + return error; + } + + if (!attr_session->init_sysdir) { + error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM); + + if (error == GIT_ENOTFOUND) + giterr_clear(); + else if (error) + return error; + + attr_session->init_sysdir = 1; + } + + if (attr_session->sysdir.size == 0) + return GIT_ENOTFOUND; + + /* We can safely provide a git_buf with no allocation (asize == 0) to + * a consumer. This allows them to treat this as a regular `git_buf`, + * but their call to `git_buf_free` will not attempt to free it. + */ + out->ptr = attr_session->sysdir.ptr; + out->size = attr_session->sysdir.size; + out->asize = 0; + return 0; +} + +static int attr_setup(git_repository *repo, git_attr_session *attr_session) { int error = 0; const char *workdir = git_repository_workdir(repo); git_index *idx = NULL; git_buf sys = GIT_BUF_INIT; + if (attr_session && attr_session->init_setup) + return 0; + if ((error = git_attr_cache__init(repo)) < 0) return error; @@ -249,39 +305,39 @@ static int attr_setup(git_repository *repo) * definitions will be available for later file parsing */ - if (!(error = git_sysdir_find_system_file(&sys, GIT_ATTR_FILE_SYSTEM))) { + error = system_attr_file(&sys, attr_session); + + if (error == 0) error = preload_attr_file( - repo, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr); - git_buf_free(&sys); - } - if (error < 0) { - if (error == GIT_ENOTFOUND) { - giterr_clear(); - error = 0; - } else - return error; - } + repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr); + else if (error != GIT_ENOTFOUND) + return error; + + git_buf_free(&sys); if ((error = preload_attr_file( - repo, GIT_ATTR_FILE__FROM_FILE, + repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0) return error; if ((error = preload_attr_file( - repo, GIT_ATTR_FILE__FROM_FILE, + repo, attr_session, GIT_ATTR_FILE__FROM_FILE, git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0) return error; if (workdir != NULL && (error = preload_attr_file( - repo, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0) + repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0) return error; if ((error = git_repository_index__weakptr(&idx, repo)) < 0 || (error = preload_attr_file( - repo, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0) + repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0) return error; + if (attr_session) + attr_session->init_setup = 1; + return error; } @@ -321,6 +377,7 @@ int git_attr_add_macro( typedef struct { git_repository *repo; + git_attr_session *attr_session; uint32_t flags; const char *workdir; git_index *index; @@ -356,6 +413,7 @@ static int attr_decide_sources( static int push_attr_file( git_repository *repo, + git_attr_session *attr_session, git_vector *list, git_attr_file_source source, const char *base, @@ -364,8 +422,9 @@ static int push_attr_file( int error = 0; git_attr_file *file = NULL; - error = git_attr_cache__get( - &file, repo, source, base, filename, git_attr_file__parse_buffer); + error = git_attr_cache__get(&file, repo, attr_session, + source, base, filename, git_attr_file__parse_buffer); + if (error < 0) return error; @@ -387,8 +446,8 @@ static int push_one_attr(void *ref, const char *path) info->flags, info->workdir != NULL, info->index != NULL, src); for (i = 0; !error && i < n_src; ++i) - error = push_attr_file( - info->repo, info->files, src[i], path, GIT_ATTR_FILE); + error = push_attr_file(info->repo, info->attr_session, + info->files, src[i], path, GIT_ATTR_FILE); return error; } @@ -407,6 +466,7 @@ static void release_attr_files(git_vector *files) static int collect_attr_files( git_repository *repo, + git_attr_session *attr_session, uint32_t flags, const char *path, git_vector *files) @@ -416,7 +476,7 @@ static int collect_attr_files( const char *workdir = git_repository_workdir(repo); attr_walk_up_info info = { NULL }; - if ((error = attr_setup(repo)) < 0) + if ((error = attr_setup(repo, attr_session)) < 0) return error; /* Resolve path in a non-bare repo */ @@ -435,12 +495,13 @@ static int collect_attr_files( */ error = push_attr_file( - repo, files, GIT_ATTR_FILE__FROM_FILE, + repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, git_repository_path(repo), GIT_ATTR_FILE_INREPO); if (error < 0) goto cleanup; - info.repo = repo; + info.repo = repo; + info.attr_session = attr_session; info.flags = flags; info.workdir = workdir; if (git_repository_index__weakptr(&info.index, repo) < 0) @@ -457,21 +518,21 @@ static int collect_attr_files( if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { error = push_attr_file( - repo, files, GIT_ATTR_FILE__FROM_FILE, + repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, NULL, git_repository_attr_cache(repo)->cfg_attr_file); if (error < 0) goto cleanup; } if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { - error = git_sysdir_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM); + error = system_attr_file(&dir, attr_session); + if (!error) error = push_attr_file( - repo, files, GIT_ATTR_FILE__FROM_FILE, NULL, dir.ptr); - else if (error == GIT_ENOTFOUND) { - giterr_clear(); + repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE, + NULL, dir.ptr); + else if (error == GIT_ENOTFOUND) error = 0; - } } cleanup: diff --git a/src/attr_file.c b/src/attr_file.c index 5b008b0e3..8997946b9 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -96,6 +96,7 @@ static int attr_file_oid_from_index( int git_attr_file__load( git_attr_file **out, git_repository *repo, + git_attr_session *attr_session, git_attr_file_entry *entry, git_attr_file_source source, git_attr_file_parser parser) @@ -105,6 +106,7 @@ int git_attr_file__load( git_buf content = GIT_BUF_INIT; git_attr_file *file; struct stat st; + bool nonexistent = false; *out = NULL; @@ -127,22 +129,16 @@ int git_attr_file__load( case GIT_ATTR_FILE__FROM_FILE: { int fd; - if (p_stat(entry->fullpath, &st) < 0) - return git_path_set_error(errno, entry->fullpath, "stat"); - if (S_ISDIR(st.st_mode)) - return GIT_ENOTFOUND; - - /* For open or read errors, return ENOTFOUND to skip item */ + /* For open or read errors, pretend that we got ENOTFOUND. */ /* TODO: issue warning when warning API is available */ - if ((fd = git_futils_open_ro(entry->fullpath)) < 0) - return GIT_ENOTFOUND; - - error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size); - p_close(fd); - - if (error < 0) - return GIT_ENOTFOUND; + if (p_stat(entry->fullpath, &st) < 0 || + S_ISDIR(st.st_mode) || + (fd = git_futils_open_ro(entry->fullpath)) < 0 || + (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0) + nonexistent = true; + else + p_close(fd); break; } @@ -154,13 +150,21 @@ int git_attr_file__load( if ((error = git_attr_file__new(&file, entry, source)) < 0) goto cleanup; + /* store the key of the attr_reader; don't bother with cache + * invalidation during the same attr reader session. + */ + if (attr_session) + file->session_key = attr_session->key; + if (parser && (error = parser(repo, file, git_buf_cstr(&content))) < 0) { git_attr_file__free(file); goto cleanup; } - /* write cache breaker */ - if (source == GIT_ATTR_FILE__FROM_INDEX) + /* write cache breakers */ + if (nonexistent) + file->nonexistent = 1; + else if (source == GIT_ATTR_FILE__FROM_INDEX) git_oid_cpy(&file->cache_data.oid, git_blob_id(blob)); else if (source == GIT_ATTR_FILE__FROM_FILE) git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st); @@ -175,11 +179,22 @@ cleanup: return error; } -int git_attr_file__out_of_date(git_repository *repo, git_attr_file *file) +int git_attr_file__out_of_date( + git_repository *repo, + git_attr_session *attr_session, + git_attr_file *file) { if (!file) return 1; + /* we are never out of date if we just created this data in the same + * attr_session; otherwise, nonexistent files must be invalidated + */ + if (attr_session && attr_session->key == file->session_key) + return 0; + else if (file->nonexistent) + return 1; + switch (file->source) { case GIT_ATTR_FILE__IN_MEMORY: return 0; @@ -831,3 +846,22 @@ void git_attr_rule__free(git_attr_rule *rule) git__free(rule); } +int git_attr_session__init(git_attr_session *session, git_repository *repo) +{ + assert(repo); + + session->key = git_atomic_inc(&repo->attr_session_key); + + return 0; +} + +void git_attr_session__free(git_attr_session *session) +{ + if (!session) + return; + + git_buf_free(&session->sysdir); + git_buf_free(&session->tmp); + + memset(session, 0, sizeof(git_attr_session)); +} diff --git a/src/attr_file.h b/src/attr_file.h index 93de84d12..aa9a16de0 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -38,11 +38,11 @@ GIT_ATTR_FNMATCH_ALLOWMACRO | GIT_ATTR_FNMATCH_NOLEADINGDIR) typedef enum { - GIT_ATTR_FILE__IN_MEMORY = 0, - GIT_ATTR_FILE__FROM_FILE = 1, - GIT_ATTR_FILE__FROM_INDEX = 2, + GIT_ATTR_FILE__IN_MEMORY = 0, + GIT_ATTR_FILE__FROM_FILE = 1, + GIT_ATTR_FILE__FROM_INDEX = 2, - GIT_ATTR_FILE_NUM_SOURCES = 3 + GIT_ATTR_FILE_NUM_SOURCES = 3 } git_attr_file_source; extern const char *git_attr__true; @@ -84,6 +84,8 @@ typedef struct { git_attr_file_source source; git_vector rules; /* vector of <rule*> or <fnmatch*> */ git_pool pool; + unsigned int nonexistent:1; + int session_key; union { git_oid oid; git_futils_filestamp stamp; @@ -96,11 +98,6 @@ struct git_attr_file_entry { char fullpath[GIT_FLEX_ARRAY]; }; -typedef int (*git_attr_file_parser)( - git_repository *repo, - git_attr_file *file, - const char *data); - typedef struct { git_buf full; char *path; @@ -108,6 +105,35 @@ typedef struct { int is_dir; } git_attr_path; +/* A git_attr_session can provide an "instance" of reading, to prevent cache + * invalidation during a single operation instance (like checkout). + */ + +typedef struct { + int key; + unsigned int init_setup:1, + init_sysdir:1; + git_buf sysdir; + git_buf tmp; +} git_attr_session; + +extern int git_attr_session__init(git_attr_session *attr_session, git_repository *repo); +extern void git_attr_session__free(git_attr_session *session); + +extern int git_attr_get_many_with_session( + const char **values_out, + git_repository *repo, + git_attr_session *attr_session, + uint32_t flags, + const char *path, + size_t num_attr, + const char **names); + +typedef int (*git_attr_file_parser)( + git_repository *repo, + git_attr_file *file, + const char *data); + /* * git_attr_file API */ @@ -122,6 +148,7 @@ void git_attr_file__free(git_attr_file *file); int git_attr_file__load( git_attr_file **out, git_repository *repo, + git_attr_session *attr_session, git_attr_file_entry *ce, git_attr_file_source source, git_attr_file_parser parser); @@ -130,7 +157,7 @@ int git_attr_file__load_standalone( git_attr_file **out, const char *path); int git_attr_file__out_of_date( - git_repository *repo, git_attr_file *file); + git_repository *repo, git_attr_session *session, git_attr_file *file); int git_attr_file__parse_buffer( git_repository *repo, git_attr_file *attrs, const char *data); diff --git a/src/attrcache.c b/src/attrcache.c index b4579bfc0..9a9b9d09d 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -149,6 +149,7 @@ static int attr_cache_lookup( git_attr_file **out_file, git_attr_file_entry **out_entry, git_repository *repo, + git_attr_session *attr_session, git_attr_file_source source, const char *base, const char *filename) @@ -162,9 +163,12 @@ static int attr_cache_lookup( /* join base and path as needed */ if (base != NULL && git_path_root(filename) < 0) { - if (git_buf_joinpath(&path, base, filename) < 0) + git_buf *p = attr_session ? &attr_session->tmp : &path; + + if (git_buf_joinpath(p, base, filename) < 0) return -1; - filename = path.ptr; + + filename = p->ptr; } relfile = filename; @@ -196,6 +200,7 @@ cleanup: int git_attr_cache__get( git_attr_file **out, git_repository *repo, + git_attr_session *attr_session, git_attr_file_source source, const char *base, const char *filename, @@ -207,12 +212,12 @@ int git_attr_cache__get( git_attr_file *file = NULL, *updated = NULL; if ((error = attr_cache_lookup( - &file, &entry, repo, source, base, filename)) < 0) + &file, &entry, repo, attr_session, source, base, filename)) < 0) return error; /* load file if we don't have one or if existing one is out of date */ - if (!file || (error = git_attr_file__out_of_date(repo, file)) > 0) - error = git_attr_file__load(&updated, repo, entry, source, parser); + if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0) + error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser); /* if we loaded the file, insert into and/or update cache */ if (updated) { diff --git a/src/attrcache.h b/src/attrcache.h index be0a22f5c..44e1ffdce 100644 --- a/src/attrcache.h +++ b/src/attrcache.h @@ -31,6 +31,7 @@ extern int git_attr_cache__do_init(git_repository *repo); extern int git_attr_cache__get( git_attr_file **file, git_repository *repo, + git_attr_session *attr_session, git_attr_file_source source, const char *base, const char *filename, diff --git a/src/checkout.c b/src/checkout.c index 52a076da6..3f65a9ed7 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -28,6 +28,11 @@ #include "buf_text.h" #include "merge_file.h" #include "path.h" +#include "attr.h" +#include "pool.h" +#include "strmap.h" + +GIT__USE_STRMAP; /* See docs/checkout-internals.md for more information */ @@ -68,7 +73,8 @@ typedef struct { size_t total_steps; size_t completed_steps; git_checkout_perfdata perfdata; - git_buf last_mkdir; + git_strmap *mkdir_map; + git_attr_session attr_session; } checkout_data; typedef struct { @@ -1291,25 +1297,6 @@ fail: return error; } -static int checkout_mkdir( - checkout_data *data, - const char *path, - const char *base, - mode_t mode, - unsigned int flags) -{ - struct git_futils_mkdir_perfdata mkdir_perfdata = {0}; - - int error = git_futils_mkdir_withperf( - path, base, mode, flags, &mkdir_perfdata); - - data->perfdata.mkdir_calls += mkdir_perfdata.mkdir_calls; - data->perfdata.stat_calls += mkdir_perfdata.stat_calls; - data->perfdata.chmod_calls += mkdir_perfdata.chmod_calls; - - return error; -} - static bool should_remove_existing(checkout_data *data) { int ignorecase = 0; @@ -1325,31 +1312,43 @@ static bool should_remove_existing(checkout_data *data) #define MKDIR_REMOVE_EXISTING \ MKDIR_NORMAL | GIT_MKDIR_REMOVE_FILES | GIT_MKDIR_REMOVE_SYMLINKS +static int checkout_mkdir( + checkout_data *data, + const char *path, + const char *base, + mode_t mode, + unsigned int flags) +{ + struct git_futils_mkdir_options mkdir_opts = {0}; + int error; + + mkdir_opts.dir_map = data->mkdir_map; + mkdir_opts.pool = &data->pool; + + error = git_futils_mkdir_ext( + path, base, mode, flags, &mkdir_opts); + + data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls; + data->perfdata.stat_calls += mkdir_opts.perfdata.stat_calls; + data->perfdata.chmod_calls += mkdir_opts.perfdata.chmod_calls; + + return error; +} + static int mkpath2file( checkout_data *data, const char *path, unsigned int mode) { - git_buf *mkdir_path = &data->tmp; struct stat st; bool remove_existing = should_remove_existing(data); + unsigned int flags = + (remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL) | + GIT_MKDIR_SKIP_LAST; int error; - if ((error = git_buf_sets(mkdir_path, path)) < 0) + if ((error = checkout_mkdir( + data, path, data->opts.target_directory, mode, flags)) < 0) return error; - git_buf_rtruncate_at_char(mkdir_path, '/'); - - if (!data->last_mkdir.size || - data->last_mkdir.size != mkdir_path->size || - memcmp(mkdir_path->ptr, data->last_mkdir.ptr, mkdir_path->size) != 0) { - - if ((error = checkout_mkdir( - data, mkdir_path->ptr, data->opts.target_directory, mode, - remove_existing ? MKDIR_REMOVE_EXISTING : MKDIR_NORMAL)) < 0) - return error; - - git_buf_swap(&data->last_mkdir, mkdir_path); - } - if (remove_existing) { data->perfdata.stat_calls++; @@ -1425,8 +1424,8 @@ static int blob_content_to_file( hint_path = path; if (!data->opts.disable_filters) - error = git_filter_list_load( - &fl, git_blob_owner(blob), blob, hint_path, + error = git_filter_list__load_with_attr_session( + &fl, data->repo, &data->attr_session, blob, hint_path, GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT); if (!error) @@ -2008,7 +2007,8 @@ static int checkout_write_merge( in_data.ptr = (char *)result.ptr; in_data.size = result.len; - if ((error = git_filter_list_load(&fl, data->repo, NULL, git_buf_cstr(&path_workdir), + if ((error = git_filter_list__load_with_attr_session( + &fl, data->repo, &data->attr_session, NULL, git_buf_cstr(&path_workdir), GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT)) < 0 || (error = git_filter_list_apply_to_data(&out_data, fl, &in_data)) < 0) goto done; @@ -2212,12 +2212,17 @@ static void checkout_data_clear(checkout_data *data) git__free(data->pfx); data->pfx = NULL; - git_buf_free(&data->last_mkdir); + git_strmap_free(data->mkdir_map); + git_buf_free(&data->path); git_buf_free(&data->tmp); git_index_free(data->index); data->index = NULL; + + git_strmap_free(data->mkdir_map); + + git_attr_session__free(&data->attr_session); } static int checkout_data_init( @@ -2355,11 +2360,14 @@ static int checkout_data_init( (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 || (error = git_pool_init(&data->pool, 1, 0)) < 0 || (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 || - (error = git_path_to_dir(&data->path)) < 0) + (error = git_path_to_dir(&data->path)) < 0 || + (error = git_strmap_alloc(&data->mkdir_map)) < 0) goto cleanup; data->workdir_len = git_buf_len(&data->path); + git_attr_session__init(&data->attr_session, data->repo); + cleanup: if (error < 0) checkout_data_clear(data); diff --git a/src/fileops.c b/src/fileops.c index 4a62d210d..eddd5a804 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -7,11 +7,14 @@ #include "common.h" #include "fileops.h" #include "global.h" +#include "strmap.h" #include <ctype.h> #if GIT_WIN32 #include "win32/findfile.h" #endif +GIT__USE_STRMAP; + int git_futils_mkpath2file(const char *file_path, const mode_t mode) { return git_futils_mkdir( @@ -321,12 +324,12 @@ GIT_INLINE(int) validate_existing( return 0; } -int git_futils_mkdir_withperf( +int git_futils_mkdir_ext( const char *path, const char *base, mode_t mode, uint32_t flags, - struct git_futils_mkdir_perfdata *perfdata) + struct git_futils_mkdir_options *opts) { int error = -1; git_buf make_path = GIT_BUF_INIT; @@ -401,11 +404,14 @@ int git_futils_mkdir_withperf( *tail = '\0'; st.st_mode = 0; + if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) + continue; + /* See what's going on with this path component */ - perfdata->stat_calls++; + opts->perfdata.stat_calls++; if (p_lstat(make_path.ptr, &st) < 0) { - perfdata->mkdir_calls++; + opts->perfdata.mkdir_calls++; if (errno != ENOENT || p_mkdir(make_path.ptr, mode) < 0) { giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); @@ -423,7 +429,7 @@ int git_futils_mkdir_withperf( } if ((error = validate_existing( - make_path.ptr, &st, mode, flags, perfdata)) < 0) + make_path.ptr, &st, mode, flags, &opts->perfdata)) < 0) goto done; } @@ -432,7 +438,7 @@ int git_futils_mkdir_withperf( (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) && st.st_mode != mode) { - perfdata->chmod_calls++; + opts->perfdata.chmod_calls++; if ((error = p_chmod(make_path.ptr, mode)) < 0 && lastch == '\0') { @@ -441,6 +447,17 @@ int git_futils_mkdir_withperf( goto done; } } + + if (opts->dir_map && opts->pool) { + char *cache_path = git_pool_malloc(opts->pool, make_path.size + 1); + GITERR_CHECK_ALLOC(cache_path); + + memcpy(cache_path, make_path.ptr, make_path.size + 1); + + git_strmap_insert(opts->dir_map, cache_path, cache_path, error); + if (error < 0) + goto done; + } } error = 0; @@ -448,7 +465,7 @@ int git_futils_mkdir_withperf( /* check that full path really is a directory if requested & needed */ if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && lastch != '\0') { - perfdata->stat_calls++; + opts->perfdata.stat_calls++; if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) { giterr_set(GITERR_OS, "Path is not a directory '%s'", @@ -468,8 +485,8 @@ int git_futils_mkdir( mode_t mode, uint32_t flags) { - struct git_futils_mkdir_perfdata perfdata = {0}; - return git_futils_mkdir_withperf(path, base, mode, flags, &perfdata); + struct git_futils_mkdir_options options = {0}; + return git_futils_mkdir_ext(path, base, mode, flags, &options); } int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) diff --git a/src/fileops.h b/src/fileops.h index 4aaf1781c..0f6466c59 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -11,6 +11,8 @@ #include "map.h" #include "posix.h" #include "path.h" +#include "pool.h" +#include "strmap.h" /** * Filebuffer methods @@ -95,6 +97,13 @@ struct git_futils_mkdir_perfdata size_t chmod_calls; }; +struct git_futils_mkdir_options +{ + git_strmap *dir_map; + git_pool *pool; + struct git_futils_mkdir_perfdata perfdata; +}; + /** * Create a directory or entire path. * @@ -106,10 +115,10 @@ struct git_futils_mkdir_perfdata * @param base Root for relative path. These directories will never be made. * @param mode The mode to use for created directories. * @param flags Combination of the mkdir flags above. - * @param perfdata Performance data, use `git_futils_mkdir` if you don't want this data. + * @param opts Extended options, use `git_futils_mkdir` if you are not interested. * @return 0 on success, else error code */ -extern int git_futils_mkdir_withperf(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_perfdata *perfdata); +extern int git_futils_mkdir_ext(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); /** * Create a directory or entire path. Similar to `git_futils_mkdir_withperf` diff --git a/src/filter.c b/src/filter.c index b5a8bdd66..22eaf51a2 100644 --- a/src/filter.c +++ b/src/filter.c @@ -394,15 +394,19 @@ static int filter_list_new( } static int filter_list_check_attributes( - const char ***out, git_filter_def *fdef, const git_filter_source *src) + const char ***out, + git_repository *repo, + git_attr_session *attr_session, + git_filter_def *fdef, + const git_filter_source *src) { int error; size_t i; const char **strs = git__calloc(fdef->nattrs, sizeof(const char *)); GITERR_CHECK_ALLOC(strs); - error = git_attr_get_many( - strs, src->repo, 0, src->path, fdef->nattrs, fdef->attrs); + error = git_attr_get_many_with_session( + strs, repo, attr_session, 0, src->path, fdef->nattrs, fdef->attrs); /* if no values were found but no matches are needed, it's okay! */ if (error == GIT_ENOTFOUND && !fdef->nmatches) { @@ -448,9 +452,10 @@ int git_filter_list_new( return filter_list_new(out, &src); } -int git_filter_list_load( +int git_filter_list__load_with_attr_session( git_filter_list **filters, git_repository *repo, + git_attr_session *attr_session, git_blob *blob, /* can be NULL */ const char *path, git_filter_mode_t mode, @@ -481,7 +486,9 @@ int git_filter_list_load( continue; if (fdef->nattrs > 0) { - error = filter_list_check_attributes(&values, fdef, &src); + error = filter_list_check_attributes( + &values, repo, attr_session, fdef, &src); + if (error == GIT_ENOTFOUND) { error = 0; continue; @@ -523,6 +530,18 @@ int git_filter_list_load( return error; } +int git_filter_list_load( + git_filter_list **filters, + git_repository *repo, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + uint32_t options) +{ + return git_filter_list__load_with_attr_session( + filters, repo, NULL, blob, path, mode, options); +} + void git_filter_list_free(git_filter_list *fl) { uint32_t i; diff --git a/src/filter.h b/src/filter.h index 5a366108b..390ffebad 100644 --- a/src/filter.h +++ b/src/filter.h @@ -8,6 +8,7 @@ #define INCLUDE_filter_h__ #include "common.h" +#include "attr_file.h" #include "git2/filter.h" /* Amount of file to examine for NUL byte when checking binary-ness */ @@ -25,6 +26,15 @@ typedef enum { extern void git_filter_free(git_filter *filter); +extern int git_filter_list__load_with_attr_session( + git_filter_list **filters, + git_repository *repo, + git_attr_session *attr_session, + git_blob *blob, /* can be NULL */ + const char *path, + git_filter_mode_t mode, + uint32_t options); + /* * Available filters */ diff --git a/src/ignore.c b/src/ignore.c index 6e00f7db5..dd299f076 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -161,7 +161,7 @@ static int push_ignore_file( git_attr_file *file = NULL; error = git_attr_cache__get( - &file, ignores->repo, GIT_ATTR_FILE__FROM_FILE, + &file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE, base, filename, parse_ignore_file); if (error < 0) return error; @@ -189,7 +189,7 @@ static int get_internal_ignores(git_attr_file **out, git_repository *repo) return error; error = git_attr_cache__get( - out, repo, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL); + out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL); /* if internal rules list is empty, insert default rules */ if (!error && !(*out)->rules.length) diff --git a/src/repository.h b/src/repository.h index 6da8c289b..dffa9a8ae 100644 --- a/src/repository.h +++ b/src/repository.h @@ -133,6 +133,8 @@ struct git_repository { has_8dot3_default:1; unsigned int lru_counter; + git_atomic attr_session_key; + git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; }; diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index f37e6d359..239ee55bc 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -1130,3 +1130,57 @@ void test_checkout_tree__can_collect_perfdata(void) git_object_free(obj); } + +void update_attr_callback( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload) +{ + GIT_UNUSED(completed_steps); + GIT_UNUSED(total_steps); + GIT_UNUSED(payload); + + if (path && strcmp(path, "ident1.txt") == 0) + cl_git_write2file("testrepo/.gitattributes", + "*.txt ident\n", 12, O_RDWR|O_CREAT, 0666); +} + +void test_checkout_tree__caches_attributes_during_checkout(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_oid oid; + git_object *obj = NULL; + git_buf ident1 = GIT_BUF_INIT, ident2 = GIT_BUF_INIT; + char *ident_paths[] = { "ident1.txt", "ident2.txt" }; + + opts.progress_cb = update_attr_callback; + + assert_on_branch(g_repo, "master"); + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + opts.paths.strings = ident_paths; + opts.paths.count = 2; + + cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/ident")); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass(git_futils_readbuffer(&ident1, "testrepo/ident1.txt")); + cl_git_pass(git_futils_readbuffer(&ident2, "testrepo/ident2.txt")); + + cl_assert_equal_strn(ident1.ptr, "# $Id$", 6); + cl_assert_equal_strn(ident2.ptr, "# $Id$", 6); + + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass(git_futils_readbuffer(&ident1, "testrepo/ident1.txt")); + cl_git_pass(git_futils_readbuffer(&ident2, "testrepo/ident2.txt")); + + cl_assert_equal_strn(ident1.ptr, "# $Id: ", 7); + cl_assert_equal_strn(ident2.ptr, "# $Id: ", 7); + + git_buf_free(&ident1); + git_buf_free(&ident2); + git_object_free(obj); +} diff --git a/tests/refs/list.c b/tests/refs/list.c index de5c0fd3d..374943b05 100644 --- a/tests/refs/list.c +++ b/tests/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 12 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert_equal_i((int)ref_list.count, 14); + cl_assert_equal_i((int)ref_list.count, 15); git_strarray_free(&ref_list); } @@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten "144344043ba4d4a405da03de3844aa829ae8be0e\n"); cl_git_pass(git_reference_list(&ref_list, g_repo)); - cl_assert_equal_i((int)ref_list.count, 14); + cl_assert_equal_i((int)ref_list.count, 15); git_strarray_free(&ref_list); } diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index a1aa4b2c7..26e8954fe 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -906,6 +906,7 @@ void test_repo_iterator__fs2(void) static const char *expect_base[] = { "heads/br2", "heads/dir", + "heads/ident", "heads/long-file-name", "heads/master", "heads/packed-test", @@ -923,7 +924,7 @@ void test_repo_iterator__fs2(void) cl_git_pass(git_iterator_for_filesystem( &i, "testrepo/.git/refs", 0, NULL, NULL)); - expect_iterator_items(i, 12, expect_base, 12, expect_base); + expect_iterator_items(i, 13, expect_base, 13, expect_base); git_iterator_free(i); } diff --git a/tests/resources/testrepo/.gitted/objects/6f/d5c7dd2ab27b48c493023f794be09861e9045f b/tests/resources/testrepo/.gitted/objects/6f/d5c7dd2ab27b48c493023f794be09861e9045f new file mode 100644 index 000000000..7f0c6fe4e --- /dev/null +++ b/tests/resources/testrepo/.gitted/objects/6f/d5c7dd2ab27b48c493023f794be09861e9045f @@ -0,0 +1 @@ +xA!D}G
Mr\m[F11gȢ(GRr3Co"v^hq<7AY{"&$DSg([B!ΡwƳYgl$%Eֲ'\d_w-[k'1hZtB&;:A"m%V
\ No newline at end of file diff --git a/tests/resources/testrepo/.gitted/objects/c3/6d8ea75da8cb510fcb0c408c1d7e53f9a99dbe b/tests/resources/testrepo/.gitted/objects/c3/6d8ea75da8cb510fcb0c408c1d7e53f9a99dbe Binary files differnew file mode 100644 index 000000000..0975f7fdf --- /dev/null +++ b/tests/resources/testrepo/.gitted/objects/c3/6d8ea75da8cb510fcb0c408c1d7e53f9a99dbe diff --git a/tests/resources/testrepo/.gitted/objects/e3/6900c3224db4adf4c7f7a09d4ac80247978a13 b/tests/resources/testrepo/.gitted/objects/e3/6900c3224db4adf4c7f7a09d4ac80247978a13 Binary files differnew file mode 100644 index 000000000..e74291fc1 --- /dev/null +++ b/tests/resources/testrepo/.gitted/objects/e3/6900c3224db4adf4c7f7a09d4ac80247978a13 diff --git a/tests/resources/testrepo/.gitted/refs/heads/ident b/tests/resources/testrepo/.gitted/refs/heads/ident new file mode 100644 index 000000000..2cfd880a3 --- /dev/null +++ b/tests/resources/testrepo/.gitted/refs/heads/ident @@ -0,0 +1 @@ +6fd5c7dd2ab27b48c493023f794be09861e9045f diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c index ff05fa249..4ae952775 100644 --- a/tests/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -177,7 +177,7 @@ void test_revwalk_basic__glob_heads_with_invalid(void) /* walking */; /* git log --branches --oneline | wc -l => 16 */ - cl_assert_equal_i(17, i); + cl_assert_equal_i(18, i); } void test_revwalk_basic__push_head(void) |