diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/git/packfile.c | 257 |
1 files changed, 140 insertions, 117 deletions
diff --git a/third_party/git/packfile.c b/third_party/git/packfile.c index fc43a6c52c75..0929ebe4fc74 100644 --- a/third_party/git/packfile.c +++ b/third_party/git/packfile.c @@ -6,7 +6,6 @@ #include "mergesort.h" #include "packfile.h" #include "delta.h" -#include "list.h" #include "streaming.h" #include "sha1-lookup.h" #include "commit.h" @@ -17,14 +16,15 @@ #include "object-store.h" #include "midx.h" #include "commit-graph.h" +#include "promisor-remote.h" char *odb_pack_name(struct strbuf *buf, - const unsigned char *sha1, + const unsigned char *hash, const char *ext) { strbuf_reset(buf); strbuf_addf(buf, "%s/pack/pack-%s.%s", get_object_directory(), - sha1_to_hex(sha1), ext); + hash_to_hex(hash), ext); return buf->buf; } @@ -178,6 +178,7 @@ int load_idx(const char *path, const unsigned int hashsz, void *idx_map, */ (sizeof(off_t) <= 4)) return error("pack too large for current definition of off_t in %s", path); + p->crc_offset = 8 + 4 * 256 + nr * hashsz; } p->index_version = version; @@ -287,13 +288,6 @@ static int unuse_one_window(struct packed_git *current) return 0; } -void release_pack_memory(size_t need) -{ - size_t cur = pack_mapped; - while (need >= (cur - pack_mapped) && unuse_one_window(NULL)) - ; /* nothing */ -} - void close_pack_windows(struct packed_git *p) { while (p->windows) { @@ -517,7 +511,6 @@ static int open_packed_git_1(struct packed_git *p) struct pack_header hdr; unsigned char hash[GIT_MAX_RAWSZ]; unsigned char *idx_hash; - long fd_flag; ssize_t read_result; const unsigned hashsz = the_hash_algo->rawsz; @@ -561,16 +554,6 @@ static int open_packed_git_1(struct packed_git *p) } else if (p->pack_size != st.st_size) return error("packfile %s size changed", p->pack_name); - /* We leave these file descriptors open with sliding mmap; - * there is no point keeping them open across exec(), though. - */ - fd_flag = fcntl(p->pack_fd, F_GETFD, 0); - if (fd_flag < 0) - return error("cannot determine file descriptor flags"); - fd_flag |= FD_CLOEXEC; - if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) - return error("cannot set FD_CLOEXEC"); - /* Verify we recognize this pack file format. */ read_result = read_in_full(p->pack_fd, &hdr, sizeof(hdr)); if (read_result < 0) @@ -594,9 +577,8 @@ static int open_packed_git_1(struct packed_git *p) " while index indicates %"PRIu32" objects", p->pack_name, ntohl(hdr.hdr_entries), p->num_objects); - if (lseek(p->pack_fd, p->pack_size - hashsz, SEEK_SET) == -1) - return error("end of packfile %s is unavailable", p->pack_name); - read_result = read_in_full(p->pack_fd, hash, hashsz); + read_result = pread_in_full(p->pack_fd, hash, hashsz, + p->pack_size - hashsz); if (read_result < 0) return error_errno("error reading from %s", p->pack_name); if (read_result != hashsz) @@ -710,23 +692,12 @@ void unuse_pack(struct pack_window **w_cursor) } } -static void try_to_free_pack_memory(size_t size) -{ - release_pack_memory(size); -} - struct packed_git *add_packed_git(const char *path, size_t path_len, int local) { - static int have_set_try_to_free_routine; struct stat st; size_t alloc; struct packed_git *p; - if (!have_set_try_to_free_routine) { - have_set_try_to_free_routine = 1; - set_try_to_free_routine(try_to_free_pack_memory); - } - /* * Make sure a corresponding .pack file exists and that * the index looks sane. @@ -775,6 +746,9 @@ void install_packed_git(struct repository *r, struct packed_git *pack) pack->next = r->objects->packed_git; r->objects->packed_git = pack; + + hashmap_entry_init(&pack->packmap_ent, strhash(pack->pack_name)); + hashmap_add(&r->objects->pack_map, &pack->packmap_ent); } void (*report_garbage)(unsigned seen_bits, const char *path); @@ -874,20 +848,18 @@ static void prepare_pack(const char *full_name, size_t full_name_len, if (strip_suffix_mem(full_name, &base_len, ".idx") && !(data->m && midx_contains_pack(data->m, file_name))) { - /* Don't reopen a pack we already have. */ - for (p = data->r->objects->packed_git; p; p = p->next) { - size_t len; - if (strip_suffix(p->pack_name, ".pack", &len) && - len == base_len && - !memcmp(p->pack_name, full_name, len)) - break; - } + struct hashmap_entry hent; + char *pack_name = xstrfmt("%.*s.pack", (int)base_len, full_name); + unsigned int hash = strhash(pack_name); + hashmap_entry_init(&hent, hash); - if (!p) { + /* Don't reopen a pack we already have. */ + if (!hashmap_get(&data->r->objects->pack_map, &hent, pack_name)) { p = add_packed_git(full_name, full_name_len, data->local); if (p) install_packed_git(data->r, p); } + free(pack_name); } if (!report_garbage) @@ -951,6 +923,7 @@ unsigned long repo_approximate_object_count(struct repository *r) count += p->num_objects; } r->objects->approximate_object_count = count; + r->objects->approximate_object_count_valid = 1; } return r->objects->approximate_object_count; } @@ -1033,12 +1006,14 @@ void reprepare_packed_git(struct repository *r) { struct object_directory *odb; + obj_read_lock(); for (odb = r->objects->odb; odb; odb = odb->next) odb_clear_loose_cache(odb); r->objects->approximate_object_count_valid = 0; r->objects->packed_git_initialized = 0; prepare_packed_git(r); + obj_read_unlock(); } struct packed_git *get_packed_git(struct repository *r) @@ -1053,6 +1028,17 @@ struct multi_pack_index *get_multi_pack_index(struct repository *r) return r->objects->multi_pack_index; } +struct multi_pack_index *get_local_multi_pack_index(struct repository *r) +{ + struct multi_pack_index *m = get_multi_pack_index(r); + + /* no need to iterate; we always put the local one first (if any) */ + if (m && m->local) + return m; + + return NULL; +} + struct packed_git *get_all_packs(struct repository *r) { struct multi_pack_index *m; @@ -1115,7 +1101,23 @@ unsigned long get_size_from_delta(struct packed_git *p, do { in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; + /* + * Note: the window section returned by use_pack() must be + * available throughout git_inflate()'s unlocked execution. To + * ensure no other thread will modify the window in the + * meantime, we rely on the packed_window.inuse_cnt. This + * counter is incremented before window reading and checked + * before window disposal. + * + * Other worrying sections could be the call to close_pack_fd(), + * which can close packs even with in-use windows, and to + * reprepare_packed_git(). Regarding the former, mmap doc says: + * "closing the file descriptor does not unmap the region". And + * for the latter, it won't re-open already available packs. + */ + obj_read_unlock(); st = git_inflate(&stream, Z_FINISH); + obj_read_lock(); curpos += stream.next_in - in; } while ((st == Z_OK || st == Z_BUF_ERROR) && stream.total_out < sizeof(delta_head)); @@ -1191,11 +1193,11 @@ const struct packed_git *has_packed_and_bad(struct repository *r, return NULL; } -static off_t get_delta_base(struct packed_git *p, - struct pack_window **w_curs, - off_t *curpos, - enum object_type type, - off_t delta_obj_offset) +off_t get_delta_base(struct packed_git *p, + struct pack_window **w_curs, + off_t *curpos, + enum object_type type, + off_t delta_obj_offset) { unsigned char *base_info = use_pack(p, w_curs, *curpos, NULL); off_t base_offset; @@ -1236,30 +1238,32 @@ static off_t get_delta_base(struct packed_git *p, * the final object lookup), but more expensive for OFS deltas (we * have to load the revidx to convert the offset back into a sha1). */ -static const unsigned char *get_delta_base_sha1(struct packed_git *p, - struct pack_window **w_curs, - off_t curpos, - enum object_type type, - off_t delta_obj_offset) +static int get_delta_base_oid(struct packed_git *p, + struct pack_window **w_curs, + off_t curpos, + struct object_id *oid, + enum object_type type, + off_t delta_obj_offset) { if (type == OBJ_REF_DELTA) { unsigned char *base = use_pack(p, w_curs, curpos, NULL); - return base; + oidread(oid, base); + return 0; } else if (type == OBJ_OFS_DELTA) { struct revindex_entry *revidx; off_t base_offset = get_delta_base(p, w_curs, &curpos, type, delta_obj_offset); if (!base_offset) - return NULL; + return -1; revidx = find_pack_revindex(p, base_offset); if (!revidx) - return NULL; + return -1; - return nth_packed_object_sha1(p, revidx->nr); + return nth_packed_object_id(oid, p, revidx->nr); } else - return NULL; + return -1; } static int retry_bad_packed_offset(struct repository *r, @@ -1272,7 +1276,7 @@ static int retry_bad_packed_offset(struct repository *r, revidx = find_pack_revindex(p, obj_offset); if (!revidx) return OBJ_BAD; - nth_packed_object_oid(&oid, p, revidx->nr); + nth_packed_object_id(&oid, p, revidx->nr); mark_bad_packed_object(p, oid.hash); type = oid_object_info(r, &oid, NULL); if (type <= OBJ_NONE) @@ -1361,7 +1365,7 @@ struct delta_base_cache_key { }; struct delta_base_cache_entry { - struct hashmap hash; + struct hashmap_entry ent; struct delta_base_cache_key key; struct list_head lru; void *data; @@ -1381,7 +1385,7 @@ static unsigned int pack_entry_hash(struct packed_git *p, off_t base_offset) static struct delta_base_cache_entry * get_delta_base_cache_entry(struct packed_git *p, off_t base_offset) { - struct hashmap_entry entry; + struct hashmap_entry entry, *e; struct delta_base_cache_key key; if (!delta_base_cache.cmpfn) @@ -1390,7 +1394,8 @@ get_delta_base_cache_entry(struct packed_git *p, off_t base_offset) hashmap_entry_init(&entry, pack_entry_hash(p, base_offset)); key.p = p; key.base_offset = base_offset; - return hashmap_get(&delta_base_cache, &entry, &key); + e = hashmap_get(&delta_base_cache, &entry, &key); + return e ? container_of(e, struct delta_base_cache_entry, ent) : NULL; } static int delta_base_cache_key_eq(const struct delta_base_cache_key *a, @@ -1400,11 +1405,16 @@ static int delta_base_cache_key_eq(const struct delta_base_cache_key *a, } static int delta_base_cache_hash_cmp(const void *unused_cmp_data, - const void *va, const void *vb, + const struct hashmap_entry *va, + const struct hashmap_entry *vb, const void *vkey) { - const struct delta_base_cache_entry *a = va, *b = vb; + const struct delta_base_cache_entry *a, *b; const struct delta_base_cache_key *key = vkey; + + a = container_of(va, const struct delta_base_cache_entry, ent); + b = container_of(vb, const struct delta_base_cache_entry, ent); + if (key) return !delta_base_cache_key_eq(&a->key, key); else @@ -1423,7 +1433,7 @@ static int in_delta_base_cache(struct packed_git *p, off_t base_offset) */ static void detach_delta_base_cache_entry(struct delta_base_cache_entry *ent) { - hashmap_remove(&delta_base_cache, ent, &ent->key); + hashmap_remove(&delta_base_cache, &ent->ent, &ent->key); list_del(&ent->lru); delta_base_cached -= ent->size; free(ent); @@ -1465,9 +1475,19 @@ void clear_delta_base_cache(void) static void add_delta_base_cache(struct packed_git *p, off_t base_offset, void *base, unsigned long base_size, enum object_type type) { - struct delta_base_cache_entry *ent = xmalloc(sizeof(*ent)); + struct delta_base_cache_entry *ent; struct list_head *lru, *tmp; + /* + * Check required to avoid redundant entries when more than one thread + * is unpacking the same object, in unpack_entry() (since its phases I + * and III might run concurrently across multiple threads). + */ + if (in_delta_base_cache(p, base_offset)) { + free(base); + return; + } + delta_base_cached += base_size; list_for_each_safe(lru, tmp, &delta_base_cache_lru) { @@ -1478,6 +1498,7 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, release_delta_base_cache(f); } + ent = xmalloc(sizeof(*ent)); ent->key.p = p; ent->key.base_offset = base_offset; ent->type = type; @@ -1487,8 +1508,8 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, if (!delta_base_cache.cmpfn) hashmap_init(&delta_base_cache, delta_base_cache_hash_cmp, NULL, 0); - hashmap_entry_init(ent, pack_entry_hash(p, base_offset)); - hashmap_add(&delta_base_cache, ent); + hashmap_entry_init(&ent->ent, pack_entry_hash(p, base_offset)); + hashmap_add(&delta_base_cache, &ent->ent); } int packed_object_info(struct repository *r, struct packed_git *p, @@ -1553,20 +1574,16 @@ int packed_object_info(struct repository *r, struct packed_git *p, } } - if (oi->delta_base_sha1) { + if (oi->delta_base_oid) { if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) { - const unsigned char *base; - - base = get_delta_base_sha1(p, &w_curs, curpos, - type, obj_offset); - if (!base) { + if (get_delta_base_oid(p, &w_curs, curpos, + oi->delta_base_oid, + type, obj_offset) < 0) { type = OBJ_BAD; goto out; } - - hashcpy(oi->delta_base_sha1, base); } else - hashclr(oi->delta_base_sha1); + oidclr(oi->delta_base_oid); } oi->whence = in_delta_base_cache(p, obj_offset) ? OI_DBCACHED : @@ -1597,7 +1614,15 @@ static void *unpack_compressed_entry(struct packed_git *p, do { in = use_pack(p, w_curs, curpos, &stream.avail_in); stream.next_in = in; + /* + * Note: we must ensure the window section returned by + * use_pack() will be available throughout git_inflate()'s + * unlocked execution. Please refer to the comment at + * get_size_from_delta() to see how this is done. + */ + obj_read_unlock(); st = git_inflate(&stream, Z_FINISH); + obj_read_lock(); if (!stream.avail_out) break; /* the payload is larger than it should be */ curpos += stream.next_in - in; @@ -1682,7 +1707,7 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset, off_t len = revidx[1].offset - obj_offset; if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) { struct object_id oid; - nth_packed_object_oid(&oid, p, revidx->nr); + nth_packed_object_id(&oid, p, revidx->nr); error("bad packed object CRC for %s", oid_to_hex(&oid)); mark_bad_packed_object(p, oid.hash); @@ -1754,12 +1779,10 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset, void *external_base = NULL; unsigned long delta_size, base_size = size; int i; + off_t base_obj_offset = obj_offset; data = NULL; - if (base) - add_delta_base_cache(p, obj_offset, base, base_size, type); - if (!base) { /* * We're probably in deep shit, but let's try to fetch @@ -1771,7 +1794,7 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset, struct object_id base_oid; revidx = find_pack_revindex(p, obj_offset); if (revidx) { - nth_packed_object_oid(&base_oid, p, revidx->nr); + nth_packed_object_id(&base_oid, p, revidx->nr); error("failed to read delta base object %s" " at offset %"PRIuMAX" from %s", oid_to_hex(&base_oid), (uintmax_t)obj_offset, @@ -1797,24 +1820,33 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset, "at offset %"PRIuMAX" from %s", (uintmax_t)curpos, p->pack_name); data = NULL; - free(external_base); - continue; - } + } else { + data = patch_delta(base, base_size, delta_data, + delta_size, &size); - data = patch_delta(base, base_size, - delta_data, delta_size, - &size); + /* + * We could not apply the delta; warn the user, but + * keep going. Our failure will be noticed either in + * the next iteration of the loop, or if this is the + * final delta, in the caller when we return NULL. + * Those code paths will take care of making a more + * explicit warning and retrying with another copy of + * the object. + */ + if (!data) + error("failed to apply delta"); + } /* - * We could not apply the delta; warn the user, but keep going. - * Our failure will be noticed either in the next iteration of - * the loop, or if this is the final delta, in the caller when - * we return NULL. Those code paths will take care of making - * a more explicit warning and retrying with another copy of - * the object. + * We delay adding `base` to the cache until the end of the loop + * because unpack_compressed_entry() momentarily releases the + * obj_read_mutex, giving another thread the chance to access + * the cache. Therefore, if `base` was already there, this other + * thread could free() it (e.g. to make space for another entry) + * before we are done using it. */ - if (!data) - error("failed to apply delta"); + if (!external_base) + add_delta_base_cache(p, base_obj_offset, base, base_size, type); free(delta_data); free(external_base); @@ -1858,36 +1890,27 @@ int bsearch_pack(const struct object_id *oid, const struct packed_git *p, uint32 index_lookup, index_lookup_width, result); } -const unsigned char *nth_packed_object_sha1(struct packed_git *p, - uint32_t n) +int nth_packed_object_id(struct object_id *oid, + struct packed_git *p, + uint32_t n) { const unsigned char *index = p->index_data; const unsigned int hashsz = the_hash_algo->rawsz; if (!index) { if (open_pack_index(p)) - return NULL; + return -1; index = p->index_data; } if (n >= p->num_objects) - return NULL; + return -1; index += 4 * 256; if (p->index_version == 1) { - return index + (hashsz + 4) * n + 4; + oidread(oid, index + (hashsz + 4) * n + 4); } else { index += 8; - return index + hashsz * n; + oidread(oid, index + hashsz * n); } -} - -const struct object_id *nth_packed_object_oid(struct object_id *oid, - struct packed_git *p, - uint32_t n) -{ - const unsigned char *hash = nth_packed_object_sha1(p, n); - if (!hash) - return NULL; - hashcpy(oid->hash, hash); - return oid; + return 0; } void check_pack_index_ptr(const struct packed_git *p, const void *vptr) @@ -2066,7 +2089,7 @@ int for_each_object_in_pack(struct packed_git *p, else pos = i; - if (!nth_packed_object_oid(&oid, p, pos)) + if (nth_packed_object_id(&oid, p, pos) < 0) return error("unable to get sha1 of object %u in %s", pos, p->pack_name); @@ -2139,7 +2162,7 @@ static int add_promisor_object(const struct object_id *oid, oidset_insert(set, &parents->item->object.oid); } else if (obj->type == OBJ_TAG) { struct tag *tag = (struct tag *) obj; - oidset_insert(set, &tag->tagged->oid); + oidset_insert(set, get_tagged_oid(tag)); } return 0; } @@ -2150,7 +2173,7 @@ int is_promisor_object(const struct object_id *oid) static int promisor_objects_prepared; if (!promisor_objects_prepared) { - if (repository_format_partial_clone) { + if (has_promisor_remote()) { for_each_packed_object(add_promisor_object, &promisor_objects, FOR_EACH_OBJECT_PROMISOR_ONLY); |