diff options
| -rw-r--r-- | CHANGELOG.md | 9 | ||||
| -rw-r--r-- | CONTRIBUTING.md | 64 | ||||
| -rw-r--r-- | PROJECTS.md | 44 | ||||
| -rw-r--r-- | include/git2/tree.h | 6 | ||||
| -rw-r--r-- | src/filebuf.c | 2 | ||||
| -rw-r--r-- | src/indexer.c | 9 | ||||
| -rw-r--r-- | src/mwindow.c | 124 | ||||
| -rw-r--r-- | src/mwindow.h | 10 | ||||
| -rw-r--r-- | src/odb_pack.c | 9 | ||||
| -rw-r--r-- | src/pack.c | 19 | ||||
| -rw-r--r-- | src/pack.h | 3 | ||||
| -rw-r--r-- | src/tree.c | 115 | ||||
| -rw-r--r-- | src/tree.h | 5 | ||||
| -rw-r--r-- | src/win32/posix_w32.c | 30 | ||||
| -rw-r--r-- | tests/object/tree/write.c | 8 | ||||
| -rw-r--r-- | tests/pack/sharing.c | 42 |
16 files changed, 386 insertions, 113 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..51650ed7c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +v0.21 + 1 +------ + +* File unlocks are atomic again via rename. Read-only files on Windows are + made read-write if necessary. + +* Share open packfiles across repositories to share descriptors and mmaps. + +* Use a map for the treebuilder, making insertion O(1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4efe28ed3..8ebb99154 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,17 +22,25 @@ Also, feel free to open an about any concerns you have. We like to use Issues for that so there is an easily accessible permanent record of the conversation. +## Libgit2 Versions + +The `master` branch is the main branch where development happens. +Releases are tagged +(e.g. [v0.21.0](https://github.com/libgit2/libgit2/releases/tag/v0.21.0) ) +and when a critical bug fix needs to be backported, it will be done on a +`<tag>-maint` maintenance branch. + ## Reporting Bugs First, know which version of libgit2 your problem is in and include it in your bug report. This can either be a tag (e.g. -[v0.17.0](https://github.com/libgit2/libgit2/tree/v0.17.0) ) or a commit -SHA (e.g. +[v0.17.0](https://github.com/libgit2/libgit2/releases/tag/v0.17.0) ) or a +commit SHA (e.g. [01be7863](https://github.com/libgit2/libgit2/commit/01be786319238fd6507a08316d1c265c1a89407f) -). Using [`git describe`](http://git-scm.com/docs/git-describe) is a great -way to tell us what version you're working with. +). Using [`git describe`](http://git-scm.com/docs/git-describe) is a +great way to tell us what version you're working with. -If you're not running against the latest `development` branch version, +If you're not running against the latest `master` branch version, please compile and test against that to avoid re-reporting an issue that's already been fixed. @@ -44,25 +52,33 @@ out a way to help you. ## Pull Requests -Our work flow is a typical GitHub flow, where contributors fork the -[libgit2 repository](https://github.com/libgit2/libgit2), make their changes -on branch, and submit a -[Pull Request](https://help.github.com/articles/using-pull-requests) -(a.k.a. "PR"). +Our work flow is a [typical GitHub flow](https://guides.github.com/introduction/flow/index.html), +where contributors fork the [libgit2 repository](https://github.com/libgit2/libgit2), +make their changes on branch, and submit a +[Pull Request](https://help.github.com/articles/using-pull-requests) (a.k.a. "PR"). +Pull requests should usually be targeted at the `master` branch. Life will be a lot easier for you (and us) if you follow this pattern -(i.e. fork, named branch, submit PR). If you use your fork's `development` -branch, things can get messy. +(i.e. fork, named branch, submit PR). If you use your fork's `master` +branch directly, things can get messy. + +Please include a nice description of your changes when you submit your PR; +if we have to read the whole diff to figure out why you're contributing +in the first place, you're less likely to get feedback and have your change +merged in. + +If you are starting to work on a particular area, feel free to submit a PR +that highlights your work in progress (and note in the PR title that it's +not ready to merge). These early PRs are welcome and will help in getting +visibility for your fix, allow others to comment early on the changes and +also let others know that you are currently working on something. -Please include a nice description of your changes with your PR; if we have -to read the whole diff to figure out why you're contributing in the first -place, you're less likely to get feedback and have your change merged in. +Before wrapping up a PR, you should be sure to: -If you are working on a particular area then feel free to submit a PR that -highlights your work in progress (and flag in the PR title that it's not -ready to merge). This will help in getting visibility for your fix, allow -others to comment early on the changes and also let others know that you -are currently working on something. +* Write tests to cover any functional changes (ideally tests that would + have failed before the PR and now pass) +* Update documentation for any changed public APIs +* Add to the [`CHANGELOG.md`](CHANGELOG.md) file describing any major changes ## Porting Code From Other Open-Source Projects @@ -80,10 +96,10 @@ you're porting code *from* to see what you need to do. As a general rule, MIT and BSD (3-clause) licenses are typically no problem. Apache 2.0 license typically doesn't work due to GPL incompatibility. -If you are pulling in code from core Git, another project or code you've -pulled from a forum / Stack Overflow then please flag this in your PR and -also make sure you've given proper credit to the original author in the -code snippet. +If your pull request uses code from core Git, another project, or code +from a forum / Stack Overflow, then *please* flag this in your PR and make +sure you've given proper credit to the original author in the code +snippet. ## Style Guide diff --git a/PROJECTS.md b/PROJECTS.md index d17214471..5164d95b6 100644 --- a/PROJECTS.md +++ b/PROJECTS.md @@ -10,10 +10,11 @@ ideas that no one is actively working on. ## Before You Start -Please start by reading the README.md, CONTRIBUTING.md, and CONVENTIONS.md -files before diving into one of these projects. Those will explain our -work flow and coding conventions to help ensure that your work will be -easily integrated into libgit2. +Please start by reading the [README.md](README.md), +[CONTRIBUTING.md](CONTRIBUTING.md), and [CONVENTIONS.md](CONVENTIONS.md) +files before diving into one of these projects. Those explain our work +flow and coding conventions to help ensure that your work will be easily +integrated into libgit2. Next, work through the build instructions and make sure you can clone the repository, compile it, and run the tests successfully. That will make @@ -27,7 +28,7 @@ These are good small projects to get started with libgit2. * Look at the `examples/` programs, find an existing one that mirrors a core Git command and add a missing command-line option. There are many gaps right now and this helps demonstrate how to use the library. Here - are some specific ideas: + are some specific ideas (though there are many more): * Fix the `examples/diff.c` implementation of the `-B` (a.k.a. `--break-rewrites`) command line option to actually look for the optional `[<n>][/<m>]` configuration values. There is an @@ -67,19 +68,44 @@ into one of these as a first project for libgit2 - we'd rather get to know you first by successfully shipping your work on one of the smaller projects above. +Some of these projects are broken down into subprojects and/or have +some incremental steps listed towards the larger goal. Those steps +might make good smaller projects by themselves. + * Port part of the Git test suite to run against the command line emulation in examples/ + * Pick a Git command that is emulated in our examples/ area + * Extract the Git tests that exercise that command + * Convert the tests to call our emulation + * These tests could go in examples/tests/... * Fix symlink support for files in the .git directory (i.e. don't overwrite the symlinks when writing the file contents back out) * Implement a 'git describe' like API * Add hooks API to enumerate and manage hooks (not run them at this point) + * Enumeration of available hooks + * Lookup API to see which hooks have a script and get the script + * Read/write API to load a hook script and write a hook script + * Eventually, callback API to invoke a hook callback when libgit2 + executes the action in question * Isolate logic of ignore evaluation into a standalone API * Upgrade internal libxdiff code to latest from core Git -* Add a hashtable lookup for files in the index instead of binary search - every time +* Improve index internals with hashtable lookup for files instead of + using binary search every time * Make the index write the cache out to disk (with tests to gain confidence that the caching invalidation works correctly) -* Have the tree builder use a hash table when building instead of a - list. +* Tree builder improvements: + * Use a hash table when building instead of a list + * Extend to allow building a tree hierarchy * Move the tagopt mechanism to the newer git 1.9 interpretation of --tags [#2120](https://github.com/libgit2/libgit2/issues/2120) +* Apply-patch API +* Add a patch editing API to enable "git add -p" type operations +* Textconv API to filter binary data before generating diffs (something + like the current Filter API, probably). +* Performance profiling and improvement +* Build in handling of "empty tree" and "empty blob" SHAs +* Support "git replace" ref replacements +* Include conflicts in diff results and in status + * GIT_DELTA_CONFLICT for items in conflict (with multiple files) + * Appropriate flags for status +* Support sparse checkout (i.e. "core.sparsecheckout" and ".git/info/sparse-checkout") diff --git a/include/git2/tree.h b/include/git2/tree.h index 56922d40b..42b68193e 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -301,8 +301,10 @@ GIT_EXTERN(const git_tree_entry *) git_treebuilder_get( * If an entry named `filename` already exists, its attributes * will be updated with the given ones. * - * The optional pointer `out` can be used to retrieve a pointer to - * the newly created/updated entry. Pass NULL if you do not need it. + * The optional pointer `out` can be used to retrieve a pointer to the + * newly created/updated entry. Pass NULL if you do not need it. The + * pointer may not be valid past the next operation in this + * builder. Duplicate the entry if you want to keep it. * * No attempt is being made to ensure that the provided oid points * to an existing git object in the object database, nor that the diff --git a/src/filebuf.c b/src/filebuf.c index d23bcc11c..25f6e52ef 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -334,8 +334,6 @@ int git_filebuf_commit(git_filebuf *file) file->fd = -1; - p_unlink(file->path_original); - if (p_rename(file->path_lock, file->path_original) < 0) { giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original); goto on_error; diff --git a/src/indexer.c b/src/indexer.c index 25c3d0537..eb8b23e92 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -18,6 +18,8 @@ #include "oidmap.h" #include "zstream.h" +extern git_mutex git__mwindow_mutex; + #define UINT31_MAX (0x7FFFFFFF) struct entry { @@ -1044,6 +1046,11 @@ void git_indexer_free(git_indexer *idx) } git_vector_free_deep(&idx->deltas); - git_packfile_free(idx->pack); + + if (!git_mutex_lock(&git__mwindow_mutex)) { + git_packfile_free(idx->pack); + git_mutex_unlock(&git__mwindow_mutex); + } + git__free(idx); } diff --git a/src/mwindow.c b/src/mwindow.c index 7e5fcdfbc..0d6535056 100644 --- a/src/mwindow.c +++ b/src/mwindow.c @@ -11,6 +11,10 @@ #include "fileops.h" #include "map.h" #include "global.h" +#include "strmap.h" +#include "pack.h" + +GIT__USE_STRMAP; #define DEFAULT_WINDOW_SIZE \ (sizeof(void*) >= 8 \ @@ -26,20 +30,126 @@ size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; /* Whenever you want to read or modify this, grab git__mwindow_mutex */ static git_mwindow_ctl mem_ctl; -/* - * Free all the windows in a sequence, typically because we're done - * with the file +/* Global list of mwindow files, to open packs once across repos */ +git_strmap *git__pack_cache = NULL; + +/** + * Run under mwindow lock */ -void git_mwindow_free_all(git_mwindow_file *mwf) +int git_mwindow_files_init(void) { - git_mwindow_ctl *ctl = &mem_ctl; - size_t i; + if (git__pack_cache) + return 0; + + return git_strmap_alloc(&git__pack_cache); +} + +void git_mwindow_files_free(void) +{ + git_strmap *tmp = git__pack_cache; + + git__pack_cache = NULL; + git_strmap_free(tmp); +} + +int git_mwindow_get_pack(struct git_pack_file **out, const char *path) +{ + int error; + char *packname; + git_strmap_iter pos; + struct git_pack_file *pack; + + if ((error = git_packfile__name(&packname, path)) < 0) + return error; + + if (git_mutex_lock(&git__mwindow_mutex) < 0) + return -1; + + if (git_mwindow_files_init() < 0) { + git_mutex_unlock(&git__mwindow_mutex); + return -1; + } + + pos = git_strmap_lookup_index(git__pack_cache, packname); + git__free(packname); + + if (git_strmap_valid_index(git__pack_cache, pos)) { + pack = git_strmap_value_at(git__pack_cache, pos); + git_atomic_inc(&pack->refcount); + + git_mutex_unlock(&git__mwindow_mutex); + *out = pack; + return 0; + } + + /* If we didn't find it, we need to create it */ + if ((error = git_packfile_alloc(&pack, path)) < 0) { + git_mutex_unlock(&git__mwindow_mutex); + return error; + } + + git_atomic_inc(&pack->refcount); + + git_strmap_insert(git__pack_cache, pack->pack_name, pack, error); + git_mutex_unlock(&git__mwindow_mutex); + + if (error < 0) + return -1; + *out = pack; + return 0; +} + +int git_mwindow_put_pack(struct git_pack_file *pack) +{ + int count; + git_strmap_iter pos; + + if (git_mutex_lock(&git__mwindow_mutex) < 0) + return -1; + + if (git_mwindow_files_init() < 0) { + git_mutex_unlock(&git__mwindow_mutex); + return -1; + } + + pos = git_strmap_lookup_index(git__pack_cache, pack->pack_name); + if (!git_strmap_valid_index(git__pack_cache, pos)) { + git_mutex_unlock(&git__mwindow_mutex); + return GIT_ENOTFOUND; + } + + count = git_atomic_dec(&pack->refcount); + if (count == 0) { + git_strmap_delete_at(git__pack_cache, pos); + git_packfile_free(pack); + } + + git_mutex_unlock(&git__mwindow_mutex); + return 0; +} + +void git_mwindow_free_all(git_mwindow_file *mwf) +{ if (git_mutex_lock(&git__mwindow_mutex)) { giterr_set(GITERR_THREAD, "unable to lock mwindow mutex"); return; } + git_mwindow_free_all_locked(mwf); + + git_mutex_unlock(&git__mwindow_mutex); +} + +/* + * Free all the windows in a sequence, typically because we're done + * with the file + */ +void git_mwindow_free_all_locked(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &mem_ctl; + size_t i; + /* * Remove these windows from the global list */ @@ -67,8 +177,6 @@ void git_mwindow_free_all(git_mwindow_file *mwf) mwf->windows = w->next; git__free(w); } - - git_mutex_unlock(&git__mwindow_mutex); } /* diff --git a/src/mwindow.h b/src/mwindow.h index 0018ebbf0..57fabae70 100644 --- a/src/mwindow.h +++ b/src/mwindow.h @@ -36,10 +36,18 @@ typedef struct git_mwindow_ctl { } git_mwindow_ctl; int git_mwindow_contains(git_mwindow *win, git_off_t offset); -void git_mwindow_free_all(git_mwindow_file *mwf); +void git_mwindow_free_all(git_mwindow_file *mwf); /* locks */ +void git_mwindow_free_all_locked(git_mwindow_file *mwf); /* run under lock */ unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left); int git_mwindow_file_register(git_mwindow_file *mwf); void git_mwindow_file_deregister(git_mwindow_file *mwf); void git_mwindow_close(git_mwindow **w_cursor); +int git_mwindow_files_init(void); +void git_mwindow_files_free(void); + +struct git_pack_file; /* just declaration to avoid cyclical includes */ +int git_mwindow_get_pack(struct git_pack_file **out, const char *path); +int git_mwindow_put_pack(struct git_pack_file *pack); + #endif diff --git a/src/odb_pack.c b/src/odb_pack.c index 3750da37f..1757cf920 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -210,7 +210,7 @@ static int packfile_load__cb(void *data, git_buf *path) return 0; } - error = git_packfile_alloc(&pack, path->ptr); + error = git_mwindow_get_pack(&pack, path->ptr); /* ignore missing .pack file as git does */ if (error == GIT_ENOTFOUND) { @@ -605,7 +605,7 @@ static void pack_backend__free(git_odb_backend *_backend) for (i = 0; i < backend->packs.length; ++i) { struct git_pack_file *p = git_vector_get(&backend->packs, i); - git_packfile_free(p); + git_mwindow_put_pack(p); } git_vector_free(&backend->packs); @@ -647,7 +647,7 @@ int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx) if (pack_backend__alloc(&backend, 1) < 0) return -1; - if (git_packfile_alloc(&packfile, idx) < 0 || + if (git_mwindow_get_pack(&packfile, idx) < 0 || git_vector_insert(&backend->packs, packfile) < 0) { pack_backend__free((git_odb_backend *)backend); @@ -664,6 +664,9 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) struct pack_backend *backend = NULL; git_buf path = GIT_BUF_INIT; + if (git_mwindow_files_init() < 0) + return -1; + if (pack_backend__alloc(&backend, 8) < 0) return -1; diff --git a/src/pack.c b/src/pack.c index ace7abb58..767efb6c3 100644 --- a/src/pack.c +++ b/src/pack.c @@ -968,7 +968,7 @@ void git_packfile_free(struct git_pack_file *p) cache_free(&p->bases); - git_mwindow_free_all(&p->mwf); + git_mwindow_free_all_locked(&p->mwf); if (p->mwf.fd >= 0) p_close(p->mwf.fd); @@ -1063,6 +1063,23 @@ cleanup: return -1; } +int git_packfile__name(char **out, const char *path) +{ + size_t path_len; + git_buf buf = GIT_BUF_INIT; + + path_len = strlen(path); + + if (path_len < strlen(".idx")) + return git_odb__error_notfound("invalid packfile path", NULL); + + if (git_buf_printf(&buf, "%.*s.pack", (int)(path_len - strlen(".idx")), path) < 0) + return -1; + + *out = git_buf_detach(&buf); + return 0; +} + int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) { struct stat st; diff --git a/src/pack.h b/src/pack.h index 610e70c18..34d37d907 100644 --- a/src/pack.h +++ b/src/pack.h @@ -90,6 +90,7 @@ struct git_pack_file { git_mwindow_file mwf; git_map index_map; git_mutex lock; /* protect updates to mwf and index_map */ + git_atomic refcount; uint32_t num_objects; uint32_t num_bad_objects; @@ -123,6 +124,8 @@ typedef struct git_packfile_stream { size_t git_packfile__object_header(unsigned char *hdr, size_t size, git_otype type); +int git_packfile__name(char **out, const char *path); + int git_packfile_unpack_header( size_t *size_p, git_otype *type_p, diff --git a/src/tree.c b/src/tree.c index b64efe460..e0e2dbebf 100644 --- a/src/tree.c +++ b/src/tree.c @@ -17,6 +17,8 @@ #define DEFAULT_TREE_SIZE 16 #define MAX_FILEMODE_BYTES 6 +GIT__USE_STRMAP; + static bool valid_filemode(const int filemode) { return (filemode == GIT_FILEMODE_TREE @@ -365,7 +367,8 @@ size_t git_tree_entrycount(const git_tree *tree) unsigned int git_treebuilder_entrycount(git_treebuilder *bld) { assert(bld); - return (unsigned int)bld->entrycount; + + return git_strmap_num_entries(bld->map); } static int tree_error(const char *str, const char *path) @@ -450,6 +453,7 @@ static int append_entry( git_filemode_t filemode) { git_tree_entry *entry; + int error = 0; if (!valid_entry_name(filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); @@ -460,12 +464,12 @@ static int append_entry( git_oid_cpy(&entry->oid, id); entry->attr = (uint16_t)filemode; - if (git_vector_insert_sorted(&bld->entries, entry, NULL) < 0) { - git__free(entry); + git_strmap_insert(bld->map, entry->filename, entry, error); + if (error < 0) { + giterr_set(GITERR_TREE, "failed to append entry %s to the tree builder", filename); return -1; } - bld->entrycount++; return 0; } @@ -610,18 +614,16 @@ int git_tree__write_index( int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) { git_treebuilder *bld; - size_t i, source_entries = DEFAULT_TREE_SIZE; + size_t i; assert(builder_p); bld = git__calloc(1, sizeof(git_treebuilder)); GITERR_CHECK_ALLOC(bld); - if (source != NULL) - source_entries = source->entries.length; - - if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < 0) - goto on_error; + if (git_strmap_alloc(&bld->map) < 0) { + return -1; + } if (source != NULL) { git_tree_entry *entry_src; @@ -651,7 +653,8 @@ int git_treebuilder_insert( git_filemode_t filemode) { git_tree_entry *entry; - size_t pos; + int error; + git_strmap_iter pos; assert(bld && id && filename); @@ -661,22 +664,20 @@ int git_treebuilder_insert( if (!valid_entry_name(filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); - if (!tree_key_search(&pos, &bld->entries, filename, strlen(filename))) { - entry = git_vector_get(&bld->entries, pos); - if (entry->removed) { - entry->removed = 0; - bld->entrycount++; - } + pos = git_strmap_lookup_index(bld->map, filename); + if (git_strmap_valid_index(bld->map, pos)) { + entry = git_strmap_value_at(bld->map, pos); } else { entry = alloc_entry(filename); GITERR_CHECK_ALLOC(entry); - if (git_vector_insert_sorted(&bld->entries, entry, NULL) < 0) { - git__free(entry); + git_strmap_insert(bld->map, entry->filename, entry, error); + + if (error < 0) { + git_tree_entry_free(entry); + giterr_set(GITERR_TREE, "failed to insert %s", filename); return -1; } - - bld->entrycount++; } git_oid_cpy(&entry->oid, id); @@ -690,17 +691,14 @@ int git_treebuilder_insert( static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) { - size_t idx; - git_tree_entry *entry; + git_tree_entry *entry = NULL; + git_strmap_iter pos; assert(bld && filename); - if (tree_key_search(&idx, &bld->entries, filename, strlen(filename)) < 0) - return NULL; - - entry = git_vector_get(&bld->entries, idx); - if (entry->removed) - return NULL; + pos = git_strmap_lookup_index(bld->map, filename); + if (git_strmap_valid_index(bld->map, pos)) + entry = git_strmap_value_at(bld->map, pos); return entry; } @@ -712,35 +710,44 @@ const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *file int git_treebuilder_remove(git_treebuilder *bld, const char *filename) { - git_tree_entry *remove_ptr = treebuilder_get(bld, filename); + git_tree_entry *entry = treebuilder_get(bld, filename); - if (remove_ptr == NULL || remove_ptr->removed) + if (entry == NULL) return tree_error("Failed to remove entry. File isn't in the tree", filename); - remove_ptr->removed = 1; - bld->entrycount--; + git_strmap_delete(bld->map, filename); + git_tree_entry_free(entry); + return 0; } int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) { int error = 0; - size_t i; + size_t i, entrycount; git_buf tree = GIT_BUF_INIT; git_odb *odb; + git_tree_entry *entry; + git_vector entries; assert(bld); - git_vector_sort(&bld->entries); + entrycount = git_strmap_num_entries(bld->map); + if (git_vector_init(&entries, entrycount, entry_sort_cmp) < 0) + return -1; - /* Grow the buffer beforehand to an estimated size */ - error = git_buf_grow(&tree, bld->entries.length * 72); + git_strmap_foreach_value(bld->map, entry, { + if (git_vector_insert(&entries, entry) < 0) + return -1; + }); - for (i = 0; i < bld->entries.length && !error; ++i) { - git_tree_entry *entry = git_vector_get(&bld->entries, i); + git_vector_sort(&entries); + + /* Grow the buffer beforehand to an estimated size */ + error = git_buf_grow(&tree, entrycount * 72); - if (entry->removed) - continue; + for (i = 0; i < entries.length && !error; ++i) { + git_tree_entry *entry = git_vector_get(&entries, i); git_buf_printf(&tree, "%o ", entry->attr); git_buf_put(&tree, entry->filename, entry->filename_len + 1); @@ -750,6 +757,8 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b error = -1; } + git_vector_free(&entries); + if (!error && !(error = git_repository_odb__weakptr(&odb, repo))) error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE); @@ -763,31 +772,27 @@ void git_treebuilder_filter( git_treebuilder_filter_cb filter, void *payload) { - size_t i; + const char *filename; git_tree_entry *entry; assert(bld && filter); - git_vector_foreach(&bld->entries, i, entry) { - if (!entry->removed && filter(entry, payload)) { - entry->removed = 1; - bld->entrycount--; - } - } + git_strmap_foreach(bld->map, filename, entry, { + if (filter(entry, payload)) { + git_strmap_delete(bld->map, filename); + git_tree_entry_free(entry); + } + }); } void git_treebuilder_clear(git_treebuilder *bld) { - size_t i; git_tree_entry *e; assert(bld); - git_vector_foreach(&bld->entries, i, e) - git_tree_entry_free(e); - - git_vector_clear(&bld->entries); - bld->entrycount = 0; + git_strmap_foreach_value(bld->map, e, git_tree_entry_free(e)); + git_strmap_clear(bld->map); } void git_treebuilder_free(git_treebuilder *bld) @@ -796,7 +801,7 @@ void git_treebuilder_free(git_treebuilder *bld) return; git_treebuilder_clear(bld); - git_vector_free(&bld->entries); + git_strmap_free(bld->map); git__free(bld); } diff --git a/src/tree.h b/src/tree.h index f07039a07..5d27eb7c9 100644 --- a/src/tree.h +++ b/src/tree.h @@ -11,9 +11,9 @@ #include "repository.h" #include "odb.h" #include "vector.h" +#include "strmap.h" struct git_tree_entry { - uint16_t removed; uint16_t attr; git_oid oid; size_t filename_len; @@ -26,8 +26,7 @@ struct git_tree { }; struct git_treebuilder { - git_vector entries; - size_t entrycount; /* vector may contain "removed" entries */ + git_strmap *map; }; GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 34938431a..fbadb1c9e 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -591,6 +591,31 @@ int p_access(const char* path, mode_t mode) return _waccess(buf, mode); } +static int ensure_writable(wchar_t *fpath) +{ + DWORD attrs; + + attrs = GetFileAttributesW(fpath); + if (attrs == INVALID_FILE_ATTRIBUTES) { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + return 0; + + giterr_set(GITERR_OS, "failed to get attributes"); + return -1; + } + + if (!(attrs & FILE_ATTRIBUTE_READONLY)) + return 0; + + attrs &= ~FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributesW(fpath, attrs)) { + giterr_set(GITERR_OS, "failed to set attributes"); + return -1; + } + + return 0; +} + int p_rename(const char *from, const char *to) { git_win32_path wfrom; @@ -602,12 +627,13 @@ int p_rename(const char *from, const char *to) if (utf8_to_16_with_errno(wfrom, from) < 0 || utf8_to_16_with_errno(wto, to) < 0) return -1; - + /* wait up to 50ms if file is locked by another thread or process */ rename_tries = 0; rename_succeeded = 0; while (rename_tries < 10) { - if (MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) { + if (ensure_writable(wto) == 0 && + MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) != 0) { rename_succeeded = 1; break; } diff --git a/tests/object/tree/write.c b/tests/object/tree/write.c index 45356e807..ddb62e278 100644 --- a/tests/object/tree/write.c +++ b/tests/object/tree/write.c @@ -104,6 +104,7 @@ void test_object_tree_write__subtree(void) void test_object_tree_write__sorted_subtrees(void) { git_treebuilder *builder; + git_tree *tree; unsigned int i; int position_c = -1, position_cake = -1, position_config = -1; @@ -143,8 +144,9 @@ void test_object_tree_write__sorted_subtrees(void) cl_git_pass(git_treebuilder_write(&tree_oid, g_repo, builder)); - for (i = 0; i < builder->entries.length; ++i) { - git_tree_entry *entry = git_vector_get(&builder->entries, i); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid)); + for (i = 0; i < git_tree_entrycount(tree); i++) { + const git_tree_entry *entry = git_tree_entry_byindex(tree, i); if (strcmp(entry->filename, "c") == 0) position_c = i; @@ -156,6 +158,8 @@ void test_object_tree_write__sorted_subtrees(void) position_config = i; } + git_tree_free(tree); + cl_assert(position_c != -1); cl_assert(position_cake != -1); cl_assert(position_config != -1); diff --git a/tests/pack/sharing.c b/tests/pack/sharing.c new file mode 100644 index 000000000..a67d65588 --- /dev/null +++ b/tests/pack/sharing.c @@ -0,0 +1,42 @@ +#include "clar_libgit2.h" +#include <git2.h> +#include "strmap.h" +#include "mwindow.h" +#include "pack.h" + +extern git_strmap *git__pack_cache; + +void test_pack_sharing__open_two_repos(void) +{ + git_repository *repo1, *repo2; + git_object *obj1, *obj2; + git_oid id; + git_strmap_iter pos; + void *data; + int error; + + cl_git_pass(git_repository_open(&repo1, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git"))); + + git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + cl_git_pass(git_object_lookup(&obj1, repo1, &id, GIT_OBJ_ANY)); + cl_git_pass(git_object_lookup(&obj2, repo2, &id, GIT_OBJ_ANY)); + + pos = 0; + while ((error = git_strmap_next(&data, &pos, git__pack_cache)) == 0) { + struct git_pack_file *pack = (struct git_pack_file *) data; + + cl_assert_equal_i(2, pack->refcount.val); + } + + cl_assert_equal_i(3, git_strmap_num_entries(git__pack_cache)); + + git_object_free(obj1); + git_object_free(obj2); + git_repository_free(repo1); + git_repository_free(repo2); + + /* we don't want to keep the packs open after the repos go away */ + cl_assert_equal_i(0, git_strmap_num_entries(git__pack_cache)); +} |
