diff --git a/Documentation/gitformat-pack.adoc b/Documentation/gitformat-pack.adoc index 3416edceab..19afbc881e 100644 --- a/Documentation/gitformat-pack.adoc +++ b/Documentation/gitformat-pack.adoc @@ -318,6 +318,81 @@ Pack file entry: <+ Index checksum of all of the above. +== Version 3 pack-*.idx files have the format: + + - A header appears at the beginning and consists of the following: + * The 4-byte pack index signature: '\377t0c' + * 4-byte version number: 3 + * 4-byte length of the header section, including the signature and + version number + * 4-byte number of objects contained in the pack + * 4-byte number of object formats in this pack index: 1 or 2 + * For each object format: + ** 4-byte format identifier (e.g., 'sha1' for SHA-1) + ** 4-byte length in bytes of shortened object names. This is the + shortest possible length needed to make names in the shortened + object name table unambiguous. + ** 8-byte integer, recording where tables relating to this format + are stored in this index file, as an offset from the beginning. + * 8-byte offset to the trailer from the beginning of this file. + * Zero or more additional key/value pairs (4-byte key, 4-byte + value). Only one key is supported: 'PSRC'. See the "Loose objects + and unreachable objects" section of the hash function transition + document (`Documentation/technical/hash-function-transition.adoc`) + for supported values and how this is used. All other keys are reserved. Readers must ignore + unrecognized keys. + - Zero or more NUL bytes. This can optionally be used to improve the + alignment of the full object name table below. + - Tables for the first object format: + * A sorted table of shortened object names. These are prefixes of + the names of all objects in this pack file, packed together + without offset values to reduce the cache footprint of the binary + search for a specific object name. + + * A table of full object names in pack order. This allows resolving + a reference to "the nth object in the pack file" (from a + reachability bitmap or from the next table of another object + format) to its object name. + + * A table of 4-byte values mapping object name order to pack order. + For an object in the table of sorted shortened object names, the + value at the corresponding index in this table is the index in the + previous table for that same object. + This can be used to look up the object in reachability bitmaps or + to look up its name in another object format. + + * A table of 4-byte CRC32 values of the packed object data, in the + order that the objects appear in the pack file. This is to allow + compressed data to be copied directly from pack to pack during + repacking without undetected data corruption. + + * A table of 4-byte offset values. The index of this table in pack order + indicates where that object can be found in the pack file. These are + usually 31-bit pack file offsets, but large offsets are encoded as + an index into the next table with the most significant bit set. + + * A table of 8-byte offset entries (empty for pack files less than + 2 GiB). Pack files are organized with heavily used objects toward + the front, so most object references should not need to refer to + this table. + - Zero or more NUL bytes. + - Tables for the second object format, with the same layout as above, + up to and not including the table of CRC32 values. + - Zero or more NUL bytes. + - The trailer consists of the following: + * A copy of the hash checksum at the end of the corresponding + packfile. + + * Hash checksum of all of the above. + +Note that the two hashes at the end of the index file are in whatever the +repository's main algorithm is. In the usual case when there are multiple +algorithms, the main algorithm will be SHA-256 and the compatibility algorithm +will be SHA-1. Git always writes the main algorithm first, but doesn't require this. + +All 4-byte numbers are in network order and must be 4-byte aligned in the file, +so the NUL padding may be required in some cases. + == pack-*.rev files have the format: - A 4-byte magic number '0x52494458' ('RIDX'). diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 0264c18c5f..4db3f18951 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -31,6 +31,8 @@ #include "run-command.h" #include "setup.h" #include "strvec.h" +#include "loose.h" +#include "object-file-convert.h" static const char index_pack_usage[] = "git index-pack [-v] [-o ] [--keep | --keep=] [--[no-]rev-index] [--verify] [--strict[==...]] [--fsck-objects[==...]] ( | --stdin [--fix-thin] [])"; @@ -182,6 +184,10 @@ static pthread_mutex_t deepest_delta_mutex; #define deepest_delta_lock() lock_mutex(&deepest_delta_mutex) #define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex) +static pthread_mutex_t repo_mutex; +#define repo_lock() lock_mutex(&repo_mutex) +#define repo_unlock() unlock_mutex(&repo_mutex) + static pthread_key_t key; static inline void lock_mutex(pthread_mutex_t *mutex) @@ -205,6 +211,7 @@ static void init_thread(void) init_recursive_mutex(&read_mutex); pthread_mutex_init(&counter_mutex, NULL); pthread_mutex_init(&work_mutex, NULL); + pthread_mutex_init(&repo_mutex, NULL); if (show_stat) pthread_mutex_init(&deepest_delta_mutex, NULL); pthread_key_create(&key, NULL); @@ -225,6 +232,7 @@ static void cleanup_thread(void) pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&counter_mutex); pthread_mutex_destroy(&work_mutex); + pthread_mutex_destroy(&repo_mutex); if (show_stat) pthread_mutex_destroy(&deepest_delta_mutex); for (i = 0; i < nr_threads; i++) @@ -469,13 +477,15 @@ static int is_delta_type(enum object_type type) } static void *unpack_entry_data(off_t offset, size_t size, - enum object_type type, struct object_id *oid) + enum object_type type, struct object_id *oid, + struct object_id *compat_oid) { static char fixed_buf[8192]; int status; git_zstream stream; void *buf; - struct git_hash_ctx c; + struct git_hash_ctx c, c_compat; + const struct git_hash_algo *compat = the_repository->compat_hash_algo; char hdr[32]; int hdrlen; @@ -483,8 +493,15 @@ static void *unpack_entry_data(off_t offset, size_t size, hdrlen = format_object_header(hdr, sizeof(hdr), type, size); git_hash_init(&c, the_hash_algo); git_hash_update(&c, hdr, hdrlen); - } else + if (compat && compat_oid) { + git_hash_init(&c_compat, compat); + if (type == OBJ_BLOB) + git_hash_update(&c_compat, hdr, hdrlen); + } + } else { oid = NULL; + compat_oid = NULL; + } if (type == OBJ_BLOB && size > repo_settings_get_big_file_threshold(the_repository)) buf = fixed_buf; @@ -504,6 +521,8 @@ static void *unpack_entry_data(off_t offset, size_t size, use(input_len - stream.avail_in); if (oid) git_hash_update(&c, last_out, stream.next_out - last_out); + if (compat && compat_oid && type == OBJ_BLOB) + git_hash_update(&c_compat, last_out, stream.next_out - last_out); if (buf == fixed_buf) { stream.next_out = buf; stream.avail_out = sizeof(fixed_buf); @@ -514,13 +533,43 @@ static void *unpack_entry_data(off_t offset, size_t size, git_inflate_end(&stream); if (oid) git_hash_final_oid(oid, &c); + if (compat && compat_oid) { + if (type != OBJ_BLOB) { + /* + * This is not a blob. Let's remap it to the proper + * algorithm. + */ + struct strbuf outbuf = STRBUF_INIT; + int ret; + repo_lock(); + ret = convert_object_file(the_repository, &outbuf, + the_repository->hash_algo, + compat, + buf, size, type, 0); + repo_unlock(); + if (ret < 0) + bad_object(offset, _("could not convert object")); + + hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX, + type_name(type),(uintmax_t)outbuf.len) + 1; + + git_hash_update(&c_compat, hdr, hdrlen); + git_hash_update(&c_compat, outbuf.buf, outbuf.len); + strbuf_release(&outbuf); + } + git_hash_final_oid(compat_oid, &c_compat); + repo_lock(); + odb_write_oid_map(the_repository->objects, oid, compat_oid, 0); + repo_unlock(); + } return buf == fixed_buf ? NULL : buf; } static void *unpack_raw_entry(struct object_entry *obj, off_t *ofs_offset, struct object_id *ref_oid, - struct object_id *oid) + struct object_id *oid, + struct object_id *compat_oid) { unsigned char *p; size_t size, c; @@ -582,7 +631,7 @@ static void *unpack_raw_entry(struct object_entry *obj, } obj->hdr_size = consumed_bytes - obj->idx.offset; - data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, oid); + data = unpack_entry_data(obj->idx.offset, obj->size, obj->type, oid, compat_oid); obj->idx.crc32 = input_crc32; return data; } @@ -1069,6 +1118,45 @@ static struct base_data *resolve_delta(struct object_entry *delta_obj, bad_object(delta_obj->idx.offset, _("failed to apply delta")); hash_object_file(the_hash_algo, result_data, result_size, delta_obj->real_type, &delta_obj->idx.oid); + if (the_repository->compat_hash_algo) { + struct strbuf outbuf = STRBUF_INIT; + int ret = 0; + char *buf; + size_t len; + + if (delta_obj->real_type != OBJ_BLOB) { + repo_lock(); + ret = convert_object_file(the_repository, &outbuf, + the_repository->hash_algo, + the_repository->compat_hash_algo, + result_data, result_size, + delta_obj->real_type, 0); + repo_unlock(); + buf = outbuf.buf; + len = outbuf.len; + } else { + buf = result_data; + len = result_size; + } + if (ret < 0) + bad_object(delta_obj->idx.offset, + _("could not convert object from delta")); + + hash_object_file(the_repository->compat_hash_algo, buf, len, + delta_obj->real_type, &delta_obj->idx.compat_oid); + /* + * We add this entry to the loose object map but do not write it + * to disk because we may need it available when mapping other + * objects. Storing it temporarily in the loose object map lets + * us keep it around for that purpose. + */ + repo_lock(); + odb_write_oid_map(the_repository->objects, + &delta_obj->idx.oid, + &delta_obj->idx.compat_oid, 0); + repo_unlock(); + strbuf_release(&outbuf); + } sha1_object(result_data, NULL, result_size, delta_obj->real_type, &delta_obj->idx.oid); @@ -1265,7 +1353,8 @@ static void parse_pack_objects(unsigned char *hash) struct object_entry *obj = &objects[i]; void *data = unpack_raw_entry(obj, &ofs_delta->offset, &ref_delta_oid, - &obj->idx.oid); + &obj->idx.oid, + &obj->idx.compat_oid); obj->real_type = obj->type; if (obj->type == OBJ_OFS_DELTA) { nr_ofs_deltas++; @@ -1669,7 +1758,7 @@ static int git_index_pack_config(const char *k, const char *v, if (!strcmp(k, "pack.indexversion")) { opts->version = git_config_int(k, v, ctx->kvi); - if (opts->version > 2) + if (opts->version > 3) die(_("bad pack.indexVersion=%"PRIu32), opts->version); return 0; } @@ -1752,7 +1841,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) /* Read the attributes from the existing idx file */ opts->version = p->index_version; - if (opts->version == 2) + if (opts->version >= 2) read_v2_anomalous_offsets(p, opts); /* @@ -1991,7 +2080,7 @@ int cmd_index_pack(int argc, } else if (starts_with(arg, "--index-version=")) { char *c; opts.version = strtoul(arg + 16, &c, 10); - if (opts.version > 2) + if (opts.version > 3) die(_("bad %s"), arg); if (*c == ',') opts.off32_limit = strtoul(c+1, &c, 0); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 18e0ecb170..b0fba60580 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -47,6 +47,9 @@ #include "blob.h" #include "tree.h" #include "path-walk.h" +#include "trace2.h" +#include "loose.h" +#include "object-file-convert.h" /* * Objects we are going to pack are collected in the `to_pack` structure. @@ -1849,6 +1852,9 @@ static struct object_entry *create_object_entry(const struct object_id *oid, entry = packlist_alloc(&to_pack, oid); entry->hash = hash; + if (repo_oid_to_algop(the_repository, &entry->idx.oid, + the_repository->compat_hash_algo, &entry->idx.compat_oid) < 0) + die(_("can't map object %s while writing pack"), oid_to_hex(oid)); oe_set_type(entry, type); if (exclude) entry->preferred_base = 1; @@ -5137,7 +5143,7 @@ static int option_parse_index_version(const struct option *opt, BUG_ON_OPT_NEG(unset); popts->version = strtoul(val, &c, 10); - if (popts->version > 2) + if (popts->version > 3) die(_("unsupported index version %s"), val); if (*c == ',' && c[1]) popts->off32_limit = strtoul(c+1, &c, 0); diff --git a/hash-lookup.c b/hash-lookup.c index 5f808caa51..3f0725f7ec 100644 --- a/hash-lookup.c +++ b/hash-lookup.c @@ -104,18 +104,13 @@ int oid_pos(const struct object_id *oid, const void *table, size_t nr, return index_pos_to_insert_pos(lo); } -int bsearch_hash(const unsigned char *hash, const uint32_t *fanout_nbo, - const unsigned char *table, size_t stride, uint32_t *result) +static int bsearch_hash_1(const unsigned char *hash, const unsigned char *table, + size_t stride, size_t cmpsize, uint32_t hi, + uint32_t lo, uint32_t *result) { - uint32_t hi, lo; - - hi = ntohl(fanout_nbo[*hash]); - lo = ((*hash == 0x0) ? 0 : ntohl(fanout_nbo[*hash - 1])); - while (lo < hi) { unsigned mi = lo + (hi - lo) / 2; - int cmp = hashcmp(table + mi * stride, hash, - the_repository->hash_algo); + int cmp = memcmp(table + mi * stride, hash, cmpsize); if (!cmp) { if (result) @@ -132,3 +127,40 @@ int bsearch_hash(const unsigned char *hash, const uint32_t *fanout_nbo, *result = lo; return 0; } + +int bsearch_hash(const unsigned char *hash, const uint32_t *fanout_nbo, + const unsigned char *table, size_t stride, uint32_t *result) +{ + uint32_t hi, lo; + + hi = ntohl(fanout_nbo[*hash]); + lo = ((*hash == 0x0) ? 0 : ntohl(fanout_nbo[*hash - 1])); + + return bsearch_hash_1(hash, table, stride, the_hash_algo->rawsz, hi, lo, result); +} + +/* full_oid is assumed to follow directly behind short_oid in memory. */ +int bsearch_hash_v3(const unsigned char *hash, const unsigned char *full_oid, + const uint32_t *pack_map, const unsigned char *short_oid, + size_t stride, size_t hashsz, uint32_t *result) +{ + uint32_t nr = (full_oid - short_oid) / stride; + uint32_t obj_off, index; + int res = bsearch_hash_1(hash, short_oid, stride, stride, nr, + 0, &obj_off); + if (result) + *result = obj_off; + if (!res) + return 0; + /* + * obj_off is now the offset in the shortened list of object names that + * we've found. We now need to look it up in the mapping of pack order + * to find the full object ID. index is the offset in pack order. + */ + index = ntohl(pack_map[obj_off]); + if (index >= nr) + return 0; + if (!memcmp(hash, full_oid + (hashsz * index), hashsz)) + return 1; + return 0; +} diff --git a/hash-lookup.h b/hash-lookup.h index dbd71ebaf7..03a9be4929 100644 --- a/hash-lookup.h +++ b/hash-lookup.h @@ -20,7 +20,7 @@ int oid_pos(const struct object_id *oid, * first byte is less than or equal to i * - table: a sorted list of hashes with optional extra information in between * - stride: distance between two consecutive elements in table (should be - * GIT_MAX_RAWSZ or greater) + * GIT_MAX_RAWSZ or less) * - result: if not NULL, this function stores the element index of the * position found (if the search is successful) or the index of the least * element that is greater than hash (if the search is not successful) @@ -29,4 +29,21 @@ int oid_pos(const struct object_id *oid, */ int bsearch_hash(const unsigned char *hash, const uint32_t *fanout_nbo, const unsigned char *table, size_t stride, uint32_t *result); + +/* + * Performs a binary search for hash, returning 1 if it is found and 0 if not. + * + * - hash is the object ID to search for. + * - short_oid is a sorted table of equal-length prefixes of the hashes (each of + * which is stride bytes in length. + * - full_oid, which must follow in memory directly behind short_oid, is a list + * of full object IDs in an arbitrary order (usually pack order); each item is + * hashsz bytes in length. + * - pack_map is a 4-byte mapping of indices of items in short_oid to full_oid. + * - result may be NULL; if not, it is the index of the item in the list of + * object IDs if found and the least element greater than it otherwise. + */ +int bsearch_hash_v3(const unsigned char *hash, const unsigned char *full_oid, + const uint32_t *pack_map, const unsigned char *short_oid, + size_t stride, size_t hashsz, uint32_t *result); #endif diff --git a/loose.c b/loose.c index 5d9cd77512..2a0e3067c3 100644 --- a/loose.c +++ b/loose.c @@ -220,9 +220,10 @@ errout: return -1; } -int repo_add_loose_object_map(struct odb_source_loose *loose, - const struct object_id *oid, - const struct object_id *compat_oid) +int source_add_loose_object_map(struct odb_source_loose *loose, + const struct object_id *oid, + const struct object_id *compat_oid, + int flags) { int inserted = 0; @@ -230,32 +231,49 @@ int repo_add_loose_object_map(struct odb_source_loose *loose, return 0; inserted = insert_loose_map(loose->map, loose->cache, oid, compat_oid); - if (inserted) + if (inserted && (flags & OBJ_MAP_WRITE)) return write_one_object(loose, oid, compat_oid); return 0; } +int source_loose_object_map_oid(struct odb_source_loose *loose, + const struct object_id *src, + const struct git_hash_algo *to, + struct object_id *dest) +{ + kh_oid_map_t *map; + khiter_t pos; + struct loose_object_map *loose_map = loose->map; + struct repository *repo = loose->base.odb->repo; + + if (!loose_map) + return -1; + map = (to == repo->compat_hash_algo) ? + loose_map->to_compat : + loose_map->to_storage; + pos = kh_get_oid_map(map, *src); + if (pos < kh_end(map)) { + oidcpy(dest, kh_value(map, pos)); + return 0; + } + return -1; +} + int repo_loose_object_map_oid(struct repository *repo, const struct object_id *src, const struct git_hash_algo *to, struct object_id *dest) { struct odb_source_files *files = odb_source_files_downcast(repo->objects->source); - kh_oid_map_t *map; - khiter_t pos; for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) { - struct loose_object_map *loose_map = dir->loose->map; - if (!loose_map) + int ret; + + if (!dir->loose) continue; - map = (to == repo->compat_hash_algo) ? - loose_map->to_compat : - loose_map->to_storage; - pos = kh_get_oid_map(map, *src); - if (pos < kh_end(map)) { - oidcpy(dest, kh_value(map, pos)); - return 0; - } + ret = source_loose_object_map_oid(dir->loose, src, to, dest); + if (ret != -1) + return ret; } return -1; } diff --git a/loose.h b/loose.h index c69155c9ea..fc6a02be8f 100644 --- a/loose.h +++ b/loose.h @@ -3,6 +3,9 @@ #include "khash.h" +/* Should we write this object to disk? */ +#define OBJ_MAP_WRITE (1 << 6) + struct repository; struct odb_source_loose; @@ -18,9 +21,6 @@ int repo_loose_object_map_oid(struct repository *repo, const struct object_id *src, const struct git_hash_algo *dest_algo, struct object_id *dest); -int repo_add_loose_object_map(struct odb_source_loose *loose, - const struct object_id *oid, - const struct object_id *compat_oid); int repo_read_loose_object_map(struct repository *repo); /* @@ -32,4 +32,13 @@ int repo_read_loose_object_map(struct repository *repo); int loose_object_idx_merge(struct repository *repo, const char *src, const char *dst); +int source_loose_object_map_oid(struct odb_source_loose *loose, + const struct object_id *src, + const struct git_hash_algo *to, + struct object_id *dest); +int source_add_loose_object_map(struct odb_source_loose *loose, + const struct object_id *oid, + const struct object_id *compat_oid, + int flags); + #endif diff --git a/object-file-convert.c b/object-file-convert.c index 63ee18630b..90a75a6c21 100644 --- a/object-file-convert.c +++ b/object-file-convert.c @@ -7,6 +7,7 @@ #include "repository.h" #include "hash.h" #include "object.h" +#include "odb.h" #include "loose.h" #include "commit.h" #include "gpg-interface.h" @@ -35,18 +36,7 @@ int repo_oid_to_algop(struct repository *repo, const struct object_id *srcoid, oidcpy(dest, src); return 0; } - if (repo_loose_object_map_oid(repo, src, to, dest)) { - /* - * We may have loaded the object map at repo initialization but - * another process (perhaps upstream of a pipe from us) may have - * written a new object into the map. If the object is missing, - * let's reload the map to see if the object has appeared. - */ - repo_read_loose_object_map(repo); - if (repo_loose_object_map_oid(repo, src, to, dest)) - return -1; - } - return 0; + return odb_map_oid(repo->objects, src, to, dest, 0); } static int decode_tree_entry_raw(struct object_id *oid, const char **path, diff --git a/object-file.c b/object-file.c index e04ce0bc11..9451b15594 100644 --- a/object-file.c +++ b/object-file.c @@ -748,6 +748,7 @@ static int hash_stream(struct odb_stream *stream, */ static void stream_to_pack(struct transaction_packfile *state, struct git_hash_ctx *ctx, + struct git_hash_ctx *compat_ctx, struct odb_stream *stream) { git_zstream s; @@ -775,6 +776,9 @@ static void stream_to_pack(struct transaction_packfile *state, is_finished = true; git_hash_update(ctx, ibuf, rsize); + if (compat_ctx) + git_hash_update(compat_ctx, + ibuf, rsize); s.next_in = ibuf; s.avail_in = rsize; @@ -883,16 +887,22 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas struct odb_transaction_files, base); struct transaction_packfile *state = &transaction->packfile; - struct git_hash_ctx ctx; + struct git_hash_ctx ctx, compat_ctx; + struct object_id compat_oid; unsigned char obuf[16384]; unsigned header_len; struct hashfile_checkpoint checkpoint; struct pack_idx_entry *idx; + const struct git_hash_algo *compat = transaction->base.source->odb->repo->compat_hash_algo; header_len = format_object_header((char *)obuf, sizeof(obuf), stream->type, stream->size); git_hash_init(&ctx, transaction->base.source->odb->repo->hash_algo); git_hash_update(&ctx, obuf, header_len); + if (compat) { + git_hash_init(&compat_ctx, compat); + git_hash_update(&compat_ctx, obuf, header_len); + } /* * If writing another object to the packfile could result in it @@ -917,8 +927,10 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas hashfile_checkpoint(state->f, &checkpoint); idx->offset = state->offset; crc32_begin(state->f); - stream_to_pack(state, &ctx, stream); + stream_to_pack(state, &ctx, compat ? &compat_ctx : NULL, stream); git_hash_final_oid(result_oid, &ctx); + if (compat) + git_hash_final_oid(&compat_oid, &compat_ctx); idx->crc32 = crc32_end(state->f); if (already_written(transaction, result_oid)) { @@ -927,6 +939,8 @@ static int odb_transaction_files_write_object_stream(struct odb_transaction *bas free(idx); } else { oidcpy(&idx->oid, result_oid); + if (compat) + oidcpy(&idx->compat_oid, &compat_oid); ALLOC_GROW(state->written, state->nr_written + 1, state->alloc_written); diff --git a/odb.c b/odb.c index de5be285d3..03cf96c8ed 100644 --- a/odb.c +++ b/odb.c @@ -625,6 +625,23 @@ out: return ret; } +int odb_map_oid(struct object_database *odb, + const struct object_id *src, + const struct git_hash_algo *to, + struct object_id *dest, + enum odb_map_oid_flags flags) +{ + return odb_source_map_oid(odb->source, src, to, dest, flags); +} + +int odb_write_oid_map(struct object_database *odb, + const struct object_id *oid, + const struct object_id *compat_oid, + enum odb_write_oid_map_flags flags) +{ + return odb_source_write_oid_map(odb->source, oid, compat_oid, flags); +} + /* * Return the slot of the most-significant bit set in "val". There are various * ways to do this quickly with fls() or __builtin_clzl(), but speed is diff --git a/odb.h b/odb.h index 857637f980..4cd0b339f0 100644 --- a/odb.h +++ b/odb.h @@ -2,6 +2,7 @@ #define ODB_H #include "hashmap.h" +#include "loose.h" #include "object.h" #include "oid-array.h" #include "oidset.h" @@ -650,6 +651,29 @@ static inline int odb_write_object(struct object_database *odb, struct odb_stream; +enum odb_map_oid_flags { + /* Reload this source to obtain any new information. */ + ODB_MAP_OID_RELOAD = (1 << 0), +}; + +int odb_map_oid(struct object_database *odb, + const struct object_id *src, + const struct git_hash_algo *to, + struct object_id *dest, + enum odb_map_oid_flags flags); + +enum odb_write_oid_map_flags { + /* + * Serialize this data to disk instead of persisting it only in memory. + */ + ODB_WRITE_OID_MAP_WRITE = OBJ_MAP_WRITE, +}; + +int odb_write_oid_map(struct object_database *odb, + const struct object_id *oid, + const struct object_id *compat_oid, + enum odb_write_oid_map_flags flags); + int odb_write_object_stream(struct object_database *odb, struct odb_stream *stream, struct object_id *oid); diff --git a/odb/source-files.c b/odb/source-files.c index 0bf15031c8..3591b2cbbc 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -6,6 +6,7 @@ #include "gettext.h" #include "hex.h" #include "lockfile.h" +#include "loose.h" #include "object-file.h" #include "odb.h" #include "odb/source.h" @@ -1290,6 +1291,41 @@ struct odb_files_dir *odb_source_files_find_dir(struct object_database *odb, con return dir; } +static int odb_source_files_map_oid(struct odb_source *source, + const struct object_id *src, + const struct git_hash_algo *to, + struct object_id *dest, + enum odb_map_oid_flags flags) +{ + struct odb_source_files *files = odb_source_files_downcast(source); + + for (struct odb_files_dir *dir = files->dirs; dir; dir = dir->next) { + int ret; + + ret = odb_source_map_oid(&dir->loose->base, src, to, dest, flags); + if (ret != -1) + return ret; + ret = odb_source_map_oid(&dir->packed->base, src, to, dest, flags); + if (ret != -1) + return ret; + ret = odb_source_map_oid(&dir->loose->base, src, to, dest, + flags | ODB_MAP_OID_RELOAD); + if (ret != -1) + return ret; + } + + return -1; +} + +static inline int odb_source_files_write_oid_map(struct odb_source *source, + const struct object_id *oid, + const struct object_id *compat_oid, + enum odb_write_oid_map_flags flags) +{ + struct odb_source_files *files = odb_source_files_downcast(source); + return odb_source_write_oid_map(&files->dirs->loose->base, oid, compat_oid, flags); +} + struct odb_source_files *odb_source_files_new(struct object_database *odb, const char *payload, enum odb_new_flags flags) @@ -1332,6 +1368,8 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb, files->base.write_object = odb_source_files_write_object; files->base.write_object_stream = odb_source_files_write_object_stream; files->base.begin_transaction = odb_source_files_begin_transaction; + files->base.map_oid = odb_source_files_map_oid; + files->base.write_oid_map = odb_source_files_write_oid_map; files->base.optimize = odb_source_files_optimize; files->base.optimize_required = odb_source_files_optimize_required; files->base.generate_pack = odb_source_files_generate_pack; diff --git a/odb/source-loose.c b/odb/source-loose.c index bcbd29ce3a..6847a0adda 100644 --- a/odb/source-loose.c +++ b/odb/source-loose.c @@ -864,7 +864,7 @@ static int odb_source_loose_write_object(struct odb_source *source, return -1; if (compat_oid) - return repo_add_loose_object_map(loose, oid, compat_oid); + return source_add_loose_object_map(loose, oid, compat_oid, OBJ_MAP_WRITE); return 0; } @@ -991,7 +991,7 @@ static int odb_source_loose_write_object_stream(struct odb_source *source, loose_cache_mark_seen(loose, oid); if (compat) - err = repo_add_loose_object_map(loose, oid, &compat_oid); + err = source_add_loose_object_map(loose, oid, &compat_oid, OBJ_MAP_WRITE); cleanup: strbuf_release(&tmp_file); @@ -1039,6 +1039,31 @@ static void odb_source_loose_reparent(const char *old_cwd, loose->base.path = path; } +static int odb_source_loose_map_oid(struct odb_source *source, const struct object_id *src, + const struct git_hash_algo *to, struct object_id *dest, + enum odb_map_oid_flags flags) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + /* + * We may have loaded the object map at repo initialization but + * another process (perhaps upstream of a pipe from us) may have + * written a new object into the map. If the object is missing, + * let's reload the map to see if the object has appeared. + */ + if (flags & ODB_MAP_OID_RELOAD) + loose_object_map_load(loose); + return source_loose_object_map_oid(loose, src, to, dest); +} + +static inline int odb_source_loose_write_oid_map(struct odb_source *source, + const struct object_id *oid, + const struct object_id *compat_oid, + enum odb_write_oid_map_flags flags) +{ + struct odb_source_loose *loose = odb_source_loose_downcast(source); + return source_add_loose_object_map(loose, oid, compat_oid, flags); +} + static void odb_source_loose_free(struct odb_source *source) { struct odb_source_loose *loose = odb_source_loose_downcast(source); @@ -1160,6 +1185,8 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb, loose->base.write_object = odb_source_loose_write_object; loose->base.write_object_stream = odb_source_loose_write_object_stream; loose->base.begin_transaction = odb_source_loose_begin_transaction; + loose->base.map_oid = odb_source_loose_map_oid; + loose->base.write_oid_map = odb_source_loose_write_oid_map; if (!is_absolute_path(loose->base.path)) chdir_notify_register(odb_source_loose_reparent, loose); diff --git a/odb/source-packed.c b/odb/source-packed.c index 7fcbbb4fe9..c4b20caf53 100644 --- a/odb/source-packed.c +++ b/odb/source-packed.c @@ -286,37 +286,64 @@ static int for_each_prefixed_object_in_pack( int len = opts->prefix_hex_len > p->repo->hash_algo->hexsz ? p->repo->hash_algo->hexsz : opts->prefix_hex_len; int ret; + struct object_id wanted; num = p->num_objects; - bsearch_pack(opts->prefix, p, &first); - /* - * At this point, "first" is the location of the lowest object - * with an object name that could match "bin_pfx". See if we have - * 0, 1 or more objects that actually match(es). - */ - for (i = first; i < num; i++) { - struct object_id oid; + for (uint32_t algo = 1; algo < GIT_HASH_NALGOS; algo++) { + const struct git_hash_algo *algop = &hash_algos[algo]; - nth_packed_object_id(&oid, p, i); - if (!match_hash(len, opts->prefix->hash, oid.hash)) - break; + /* + * Skip this algorithm unless it's the desired one or we've + * asked for any algorithm. + */ + if (opts->prefix->algo != GIT_HASH_UNKNOWN && + opts->prefix->algo != algo) + continue; + /* + * Skip this algorithm unless it's the main algorithm or we have + * a compatibility algorithm and it's that algorithm. + */ + if (algop != p->repo->hash_algo && + (!p->repo->compat_hash_algo || + algop != p->repo->compat_hash_algo)) + continue; + if (p->index_version < 3 && algop != p->repo->hash_algo) + continue; - if (data->request) { - struct object_info oi = *data->request; - off_t offset = nth_packed_object_offset(p, i); + oidcpy(&wanted, opts->prefix); + wanted.algo = algo; - ret = packed_object_info(source, p, offset, &oi); - if (ret) - goto out; + bsearch_pack(&wanted, p, &first); - ret = data->cb(&oid, &oi, data->cb_data); - if (ret) - goto out; - } else { - ret = data->cb(&oid, NULL, data->cb_data); - if (ret) - goto out; + /* + * At this point, "first" is the location of the lowest object + * with an object name that could match "bin_pfx". See if we have + * 0, 1 or more objects that actually match(es). + */ + for (i = first; i < num; i++) { + struct object_id oid; + + if (nth_packed_object_id_algop(&oid, p, i, algop, algop) || + !match_hash(len, opts->prefix->hash, oid.hash)) + break; + + if (data->request) { + struct object_info oi = *data->request; + off_t offset = nth_packed_object_offset_algop(p, i, algop); + + ret = packed_object_info(source, p, offset, &oi); + if (ret) + goto out; + + ret = data->cb(&oid, &oi, data->cb_data); + if (ret) + goto out; + } else { + ret = data->cb(&oid, NULL, data->cb_data); + if (ret) + goto out; + } } } @@ -854,6 +881,45 @@ static void odb_source_packed_close(struct odb_source *source) packed->midx = NULL; } +static int odb_source_packed_map_oid(struct odb_source *source, + const struct object_id *src, + const struct git_hash_algo *to, + struct object_id *dest, + enum odb_map_oid_flags flags UNUSED) +{ + struct odb_source_packed *packed = odb_source_packed_downcast(source); + struct repository *repo = packed->base.odb->repo; + struct packfile_list_entry *e; + + for (e = packfile_store_get_packs(packed); e; e = e->next) { + struct packed_git *p = e->pack; + uint32_t off; + const struct git_hash_algo *src_algo = src->algo ? + &hash_algos[src->algo] : + repo->hash_algo; + + if (open_pack_index(p) || !p->num_objects) + continue; + /* Only v3 indices store object IDs in both algorithms. */ + if (p->index_version < 3) + continue; + if (!bsearch_pack(src, p, &off)) + continue; + if (nth_packed_object_id_algop(dest, p, off, src_algo, to)) + continue; + return 0; + } + return -1; +} + +static inline int odb_source_packed_write_oid_map(struct odb_source *source UNUSED, + const struct object_id *oid UNUSED, + const struct object_id *compat_oid UNUSED, + enum odb_write_oid_map_flags flags UNUSED) +{ + return -1; +} + static void odb_source_packed_free(struct odb_source *source) { struct odb_source_packed *packed = odb_source_packed_downcast(source); @@ -1005,6 +1071,8 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb, packed->base.write_object = odb_source_packed_write_object; packed->base.write_object_stream = odb_source_packed_write_object_stream; packed->base.begin_transaction = odb_source_packed_begin_transaction; + packed->base.map_oid = odb_source_packed_map_oid; + packed->base.write_oid_map = odb_source_packed_write_oid_map; if (!is_absolute_path(path)) chdir_notify_register(odb_source_packed_reparent, packed); diff --git a/odb/source.h b/odb/source.h index 7b56f51b82..da8a11461d 100644 --- a/odb/source.h +++ b/odb/source.h @@ -285,6 +285,42 @@ struct odb_source { */ int (*fsck)(struct odb_source *source, struct odb_fsck_options *options); + + /* + * This callback is expected to provide the mapping of an object from + * the algorithm specified in src to the algorithm specified in to, + * leaving the result in dest. flags is a combination of `enum + * odb_map_oid_flags` values: with ODB_MAP_OID_RELOAD, the source should + * reload its mapping data before the lookup, as another process may + * have written new mappings in the meantime. + * + * The algo field in src must point to a valid algorithm (specifically, + * it must not be zero). to must likewise point to a valid algorithm; + * it must be non-NULL. + * + * The callback is expected to return 0 on success, -1 if the object is + * not found, and a different negative error code on error. + */ + int (*map_oid)(struct odb_source *source, const struct object_id *src, + const struct git_hash_algo *to, struct object_id *dest, + enum odb_map_oid_flags flags); + + /* + * This callback is expected to write a mapping of an object between oid + * and compat_oid. flags is a combination of `enum + * odb_write_oid_map_flags` values: with ODB_WRITE_OID_MAP_WRITE, the + * mapping should be persisted to disk; otherwise it may only be kept in + * memory. + * + * The algo fields in oid and compat_oid must point to valid algorithms + * (specifically, they must not be zero). + * + * The callback is expected to return 0 on success, and a negative + * error code on error. + */ + int (*write_oid_map)(struct odb_source *source, const struct object_id *oid, + const struct object_id *compat_oid, + enum odb_write_oid_map_flags flags); }; /* @@ -540,4 +576,33 @@ static inline int odb_source_fsck(struct odb_source *source, return source->fsck(source, opts); } +/* + * Map the specified object ID into the specified algorithm. + * + * Returns 0 on success, -1 if the object is not found, and a different negative + * error code otherwise. + */ +static inline int odb_source_map_oid(struct odb_source *source, + const struct object_id *src, + const struct git_hash_algo *to, + struct object_id *dest, + int flags) +{ + return source->map_oid(source, src, to, dest, flags); +} + +/* + * Create a mapping between the two object IDs, which should be in the main and + * compatibility hash algorithms. + * + * Returns 0 on success, a negative error otherwise. + */ +static inline int odb_source_write_oid_map(struct odb_source *source, + const struct object_id *oid, + const struct object_id *compat_oid, + int flags) +{ + return source->write_oid_map(source, oid, compat_oid, flags); +} + #endif diff --git a/pack-check.c b/pack-check.c index 605811b7b0..3ce1e4134f 100644 --- a/pack-check.c +++ b/pack-check.c @@ -44,7 +44,9 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, } while (len); index_crc = p->index_data; - index_crc += 2 + 256 + (size_t)p->num_objects * (p->repo->hash_algo->rawsz/4) + nr; + if (p->index_version == 3) + nr = nth_packed_object_pack_order_algop(p, nr, p->repo->hash_algo); + index_crc += (p->crc_offset / 4) + nr; return data_crc != ntohl(*index_crc); } diff --git a/pack-revindex.c b/pack-revindex.c index 62387ae632..4e6923cfd3 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -138,11 +138,28 @@ static void create_pack_revindex(struct packed_git *p) const unsigned hashsz = p->repo->hash_algo->rawsz; ALLOC_ARRAY(p->revindex, num_ent + 1); - index += 4 * 256; - if (p->index_version > 1) { + if (p->index_version == 3) { const uint32_t *off_32 = - (uint32_t *)(index + 8 + (size_t)p->num_objects * (hashsz + 4)); + (uint32_t *)(index + st_add(p->crc_offset, st_mult(p->num_objects, 4))); + const uint32_t *off_64 = off_32 + p->num_objects; + for (i = 0; i < num_ent; i++) { + const uint32_t pack_order = nth_packed_object_pack_order_algop(p, i, p->repo->hash_algo); + const uint32_t off = ntohl(off_32[pack_order]); + const uint32_t lowoff = off & 0x7fffffff; + if (!(off & 0x80000000)) { + p->revindex[i].offset = off; + } else { + const uint32_t *ptr = off_64 + st_mult(lowoff, 2); + + check_pack_index_ptr(p, ptr); + p->revindex[i].offset = get_be64(ptr); + } + p->revindex[i].nr = i; + } + } else if (p->index_version == 2) { + const uint32_t *off_32 = + (uint32_t *)(index + st_add(p->crc_offset, st_mult(p->num_objects, 4))); const uint32_t *off_64 = off_32 + p->num_objects; for (i = 0; i < num_ent; i++) { const uint32_t off = ntohl(*off_32++); @@ -155,6 +172,7 @@ static void create_pack_revindex(struct packed_git *p) p->revindex[i].nr = i; } } else { + index += 4 * 256; for (i = 0; i < num_ent; i++) { const uint32_t hl = *((uint32_t *)(index + (hashsz + 4) * i)); p->revindex[i].offset = ntohl(hl); diff --git a/pack-write.c b/pack-write.c index 2ed33448ba..6c4eefd1ed 100644 --- a/pack-write.c +++ b/pack-write.c @@ -22,13 +22,20 @@ void reset_pack_idx_option(struct pack_idx_option *opts) opts->delta_base_cache_limit = DEFAULT_DELTA_BASE_CACHE_LIMIT; } -static int sha1_compare(const void *_a, const void *_b) +static int oid_compare(const void *_a, const void *_b) { struct pack_idx_entry *a = *(struct pack_idx_entry **)_a; struct pack_idx_entry *b = *(struct pack_idx_entry **)_b; return oidcmp(&a->oid, &b->oid); } +static int oid_compare_compat(const void *_a, const void *_b) +{ + struct pack_idx_entry *a = *(struct pack_idx_entry **)_a; + struct pack_idx_entry *b = *(struct pack_idx_entry **)_b; + return oidcmp(&a->compat_oid, &b->compat_oid); +} + static int cmp_uint32(const void *a_, const void *b_) { uint32_t a = *((uint32_t *)a_); @@ -50,30 +57,156 @@ static int need_large_offset(off_t offset, const struct pack_idx_option *opts) sizeof(ofsval), cmp_uint32); } +static inline int last_matching_offset(const struct object_id *a, const struct object_id *b, + const struct git_hash_algo *algop) +{ + size_t i; + for (i = 0; i < algop->rawsz; i++) + if (a->hash[i] != b->hash[i]) + return i; + /* We should never hit this case. */ + return i; +} + +static const struct object_id *main_oid(const struct pack_idx_entry *ent) +{ + return &ent->oid; +} + +static const struct object_id *compat_oid(const struct pack_idx_entry *ent) +{ + return &ent->compat_oid; +} + +static uint32_t find_short_name_len(struct pack_idx_entry **sorted, size_t nr_objects, + const struct git_hash_algo *algop, + const struct object_id *(*fn)(const struct pack_idx_entry *)) +{ + size_t i, len = 1; + for (i = 1; i < nr_objects; i++) { + const struct object_id *oid1 = fn(sorted[i - 1]); + const struct object_id *oid2 = fn(sorted[i]); + size_t offset = last_matching_offset(oid1, oid2, algop); + + if (offset >= len) + len = offset + 1; + } + return len > algop->rawsz ? algop->rawsz : len; +} + +struct pack_idx_format_header { + uint32_t id; + uint32_t short_name_len; + uint64_t offset; +}; + +/* This struct contains fields beyond the signature and version number. */ +struct pack_idx_header_v3 { + uint32_t hdr_len; + uint32_t nr_objects; + uint32_t nr_formats; +}; + +static void write_v3_header(struct repository *repo, + int nr_objects, int nr_large_offset, + uint32_t short_name_len[2], int padding[2], + struct hashfile *f) +{ + struct pack_idx_header_v3 hdr; + struct pack_idx_format_header format[2]; + const struct git_hash_algo *compat = repo->compat_hash_algo; + uint32_t nformats = compat ? 2 : 1; + /* + * signature (4) + version (4) + length (4) + nobjects(4) + nformats (4) + + * each format: identifier (4) + length of shortened names (4) + + * table offset (8) + + * offset to trailer (8) + */ + uint32_t hdrlen = 4 + 4 + 4 + 4 + 4 + nformats * (4 + 4 + 8) + 8; + uint64_t offset = hdrlen; + uint64_t short_table = short_name_len[0] * nr_objects; + + hdr.hdr_len = htonl(hdrlen); + hdr.nr_objects = htonl(nr_objects); + hdr.nr_formats = htonl(nformats); + hashwrite(f, &hdr, sizeof(hdr)); + + /* padding */ + if (short_table & 3) { + padding[0] = 4 - (short_table & 3); + offset += padding[0]; + } + + format[0].id = htonl(repo->hash_algo->format_id); + format[0].offset = htonll(offset); + format[0].short_name_len = htonl(short_name_len[0]); + hashwrite(f, &format[0], sizeof(format[0])); + offset += (uint64_t)(short_name_len[0] + repo->hash_algo->rawsz + 4 + 4 + 4) * nr_objects + + 8 * nr_large_offset; + + if (compat) { + short_table = short_name_len[1] * nr_objects; + /* padding */ + if (short_table & 3) { + padding[1] = 4 - (short_table & 3); + offset += padding[1]; + } + + format[1].id = htonl(compat->format_id); + format[1].offset = htonll(offset); + format[1].short_name_len = htonl(short_name_len[1]); + hashwrite(f, &format[1], sizeof(format[1])); + offset += (uint64_t)(short_name_len[1] + compat->rawsz + 4) * nr_objects; + } + + offset = htonll(offset); + hashwrite(f, &offset, sizeof(offset)); +} + /* - * The *sha1 contains the pack content SHA1 hash. - * The objects array passed in will be sorted by SHA1 on exit. + * The *hash contains the pack content hash. + * The objects array passed in should be in pack order and will be sorted by + * object ID on return. */ const char *write_idx_file(struct repository *repo, const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *opts, - const unsigned char *sha1) + const unsigned char *hash) { struct hashfile *f; - struct pack_idx_entry **sorted_by_sha, **list, **last; - off_t last_obj_offset = 0; + struct pack_idx_entry **sorted_by_sha, **sorted_by_sha_compat = NULL, **list, **last; int i, fd; uint32_t index_version; + unsigned int nr_large_offset = 0; + const struct git_hash_algo *compat = repo->compat_hash_algo; + uint32_t short_name_len[2] = { 0 }; + int padding_size[2] = { 0 }; + uint8_t zero[4] = { 0 }; + + index_version = opts->version; if (nr_objects) { sorted_by_sha = objects; list = sorted_by_sha; last = sorted_by_sha + nr_objects; for (i = 0; i < nr_objects; ++i) { - if (objects[i]->offset > last_obj_offset) - last_obj_offset = objects[i]->offset; + objects[i]->idx = i; + if (need_large_offset(objects[i]->offset, opts)) + nr_large_offset++; } - QSORT(sorted_by_sha, nr_objects, sha1_compare); + /* + * If we're using index v3, we need both pack order and sorted + * order. + */ + if (index_version >= 3) { + DUP_ARRAY(sorted_by_sha, objects, nr_objects); + if (compat) { + DUP_ARRAY(sorted_by_sha_compat, objects, + nr_objects); + QSORT(sorted_by_sha_compat, nr_objects, oid_compare_compat); + } + } + QSORT(sorted_by_sha, nr_objects, oid_compare); } else sorted_by_sha = list = last = NULL; @@ -95,7 +228,8 @@ const char *write_idx_file(struct repository *repo, } /* if last object's offset is >= 2^31 we should use index V2 */ - index_version = need_large_offset(last_obj_offset, opts) ? 2 : opts->version; + if (index_version == 1 && nr_large_offset) + index_version = 2; /* index versions 2 and above need a header */ if (index_version >= 2) { @@ -105,50 +239,84 @@ const char *write_idx_file(struct repository *repo, hashwrite(f, &hdr, sizeof(hdr)); } - /* - * Write the first-level table (the list is sorted, - * but we use a 256-entry lookup to be able to avoid - * having to do eight extra binary search iterations). - */ - for (i = 0; i < 256; i++) { - struct pack_idx_entry **next = list; - while (next < last) { - struct pack_idx_entry *obj = *next; - if (obj->oid.hash[0] != i) - break; - next++; + if (index_version >= 3) { + short_name_len[0] = find_short_name_len(sorted_by_sha, + nr_objects, + repo->hash_algo, + main_oid); + if (compat) + short_name_len[1] = find_short_name_len(sorted_by_sha_compat, + nr_objects, + compat, compat_oid); + write_v3_header(repo, nr_objects, nr_large_offset, short_name_len, padding_size, f); + hashwrite(f, zero, padding_size[0]); + + /* Write the sorted abbreviated names */ + list = sorted_by_sha; + for (i = 0; i < nr_objects; i++) { + struct pack_idx_entry *obj = *list++; + hashwrite(f, obj->oid.hash, short_name_len[0]); + if ((opts->flags & WRITE_IDX_STRICT) && + (i && oideq(&list[-2]->oid, &obj->oid))) + die("The same object %s appears twice in the pack", + oid_to_hex(&obj->oid)); + } + } else { + /* + * Write the first-level table (the list is sorted, + * but we use a 256-entry lookup to be able to avoid + * having to do eight extra binary search iterations). + */ + for (i = 0; i < 256; i++) { + struct pack_idx_entry **next = list; + while (next < last) { + struct pack_idx_entry *obj = *next; + if (obj->oid.hash[0] != i) + break; + next++; + } + hashwrite_be32(f, next - sorted_by_sha); + list = next; } - hashwrite_be32(f, next - sorted_by_sha); - list = next; } /* * Write the actual SHA1 entries.. */ - list = sorted_by_sha; + list = objects; for (i = 0; i < nr_objects; i++) { struct pack_idx_entry *obj = *list++; if (index_version < 2) hashwrite_be32(f, obj->offset); hashwrite(f, obj->oid.hash, repo->hash_algo->rawsz); - if ((opts->flags & WRITE_IDX_STRICT) && + if ((opts->flags & WRITE_IDX_STRICT) && index_version < 3 && (i && oideq(&list[-2]->oid, &obj->oid))) die("The same object %s appears twice in the pack", oid_to_hex(&obj->oid)); } + if (index_version >= 3) { + /* write the sorted-to-pack index */ + list = sorted_by_sha; + for (i = 0; i < nr_objects; i++) { + struct pack_idx_entry *obj = *list++; + uint32_t index = htonl(obj->idx); + hashwrite(f, &index, 4); + } + } + if (index_version >= 2) { unsigned int nr_large_offset = 0; /* write the crc32 table */ - list = sorted_by_sha; + list = objects; for (i = 0; i < nr_objects; i++) { struct pack_idx_entry *obj = *list++; hashwrite_be32(f, obj->crc32); } /* write the 32-bit offset table */ - list = sorted_by_sha; + list = objects; for (i = 0; i < nr_objects; i++) { struct pack_idx_entry *obj = *list++; uint32_t offset; @@ -160,7 +328,7 @@ const char *write_idx_file(struct repository *repo, } /* write the large offset table */ - list = sorted_by_sha; + list = objects; while (nr_large_offset) { struct pack_idx_entry *obj = *list++; uint64_t offset = obj->offset; @@ -172,10 +340,45 @@ const char *write_idx_file(struct repository *repo, } } - hashwrite(f, sha1, repo->hash_algo->rawsz); + if (index_version >= 3 && compat) { + hashwrite(f, zero, padding_size[1]); + + /* Write the sorted abbreviated names */ + list = sorted_by_sha_compat; + for (i = 0; i < nr_objects; i++) { + struct pack_idx_entry *obj = *list++; + hashwrite(f, obj->compat_oid.hash, short_name_len[1]); + if ((opts->flags & WRITE_IDX_STRICT) && + (i && oideq(&list[-2]->compat_oid, &obj->compat_oid))) + die("The same object %s appears twice in the pack", + oid_to_hex(&obj->compat_oid)); + } + + list = objects; + for (i = 0; i < nr_objects; i++) { + struct pack_idx_entry *obj = *list++; + hashwrite(f, obj->compat_oid.hash, compat->rawsz); + } + + /* write the sorted-to-pack index */ + list = sorted_by_sha_compat; + for (i = 0; i < nr_objects; i++) { + struct pack_idx_entry *obj = *list++; + uint32_t index = htonl(obj->idx); + hashwrite(f, &index, 4); + } + } + + hashwrite(f, hash, repo->hash_algo->rawsz); finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK_METADATA, CSUM_HASH_IN_STREAM | CSUM_CLOSE | ((opts->flags & WRITE_IDX_VERIFY) ? 0 : CSUM_FSYNC)); + + if (sorted_by_sha != objects) { + QSORT(objects, nr_objects, oid_compare); + free(sorted_by_sha); + } + free(sorted_by_sha_compat); return index_name; } diff --git a/pack.h b/pack.h index 2f2ce97c11..d459dc55aa 100644 --- a/pack.h +++ b/pack.h @@ -79,7 +79,9 @@ struct pack_idx_header { */ struct pack_idx_entry { struct object_id oid; + struct object_id compat_oid; uint32_t crc32; + uint32_t idx; off_t offset; }; diff --git a/packfile.c b/packfile.c index 213f930edb..594cc78916 100644 --- a/packfile.c +++ b/packfile.c @@ -88,7 +88,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) return -1; } idx_size = xsize_t(st.st_size); - if (idx_size < 4 * 256 + hashsz + hashsz) { + if (idx_size < hashsz * 3) { close(fd); return error("index file %s is too small", path); } @@ -109,14 +109,14 @@ int load_idx(const char *path, const unsigned int hashsz, void *idx_map, struct pack_idx_header *hdr = idx_map; uint32_t version, nr, i, *index; - if (idx_size < 4 * 256 + hashsz + hashsz) + if (idx_size < hashsz * 3) return error("index file %s is too small", path); if (!idx_map) return error("empty data"); if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { version = ntohl(hdr->idx_version); - if (version < 2 || version > 2) + if (version < 2 || version > 3) return error("index file %s is version %"PRIu32 " and is not supported by this binary" " (try upgrading GIT to a newer version)", @@ -124,15 +124,20 @@ int load_idx(const char *path, const unsigned int hashsz, void *idx_map, } else version = 1; + if (version < 3 && idx_size < 4 * 256 + hashsz + hashsz) + return error("index file %s is too small", path); + nr = 0; index = idx_map; if (version > 1) index += 2; /* skip index header */ - for (i = 0; i < 256; i++) { - uint32_t n = ntohl(index[i]); - if (n < nr) - return error("non-monotonic index %s", path); - nr = n; + if (version <= 2) { + for (i = 0; i < 256; i++) { + uint32_t n = ntohl(index[i]); + if (n < nr) + return error("non-monotonic index %s", path); + nr = n; + } } if (version == 1) { @@ -174,6 +179,82 @@ 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 = st_add(8 + 4 * 256, st_mult(nr, hashsz)); + } else if (version == 3) { + uint32_t hdrlen, num_formats, main_algo; + uint64_t trailer_offset; + const uint32_t *q = index; + + /* Fixed part of the header (after signature and version) */ + hdrlen = ntohl(*q++); + nr = ntohl(*q++); + num_formats = ntohl(*q++); + if (num_formats < 1 || num_formats > ARRAY_SIZE(p->formats)) + return error("v3 index %s has an invalid number of formats: %"PRIu32, + path, num_formats); + + /* Then 16 bytes per format and the 8-byte offset of the trailer */ + if (hdrlen < 20 + num_formats * (4 + 4 + 8) + 8 || + hdrlen > idx_size - 2 * hashsz) + return error("v3 index %s has an invalid header length", path); + + trailer_offset = get_be64(q + num_formats * 4); + if (trailer_offset != idx_size - 2 * hashsz) + return error("v3 index %s has a bad size", path); + + memset(p->formats, 0, sizeof(p->formats)); + for (i = 0; i < num_formats; i++) { + uint32_t format_id = ntohl(*q++); + uint32_t format = hash_algo_by_id(format_id); + struct packed_git_format *pfp; + uint64_t end; + + if (format == GIT_HASH_UNKNOWN) + return error("v3 index %s has an unknown format_id 0x%08"PRIx32, + path, format_id); + /* + * We store at the value of the constant minus 1 to + * avoid having to store a useless entry at offset 0. + */ + pfp = &p->formats[format-1]; + if (pfp->data_offset) + return error("v3 index %s has duplicate format_id 0x%08"PRIx32, + path, format_id); + + pfp->short_name_len = ntohl(*q++); + pfp->data_offset = get_be64(q); + q += 2; + + if (!pfp->short_name_len || + pfp->short_name_len > hash_algos[format].rawsz) + return error("v3 index %s has an invalid short name length", + path); + + if (pfp->data_offset < hdrlen || + pfp->data_offset > trailer_offset) + return error("v3 index %s has an invalid table offset", path); + + /* No overflow as pfp->data_offset < idx_size and nr < 2^32 */ + pfp->full_oid_offset = pfp->data_offset + + (uint64_t)pfp->short_name_len * nr; + pfp->order_map_offset = pfp->full_oid_offset + + (uint64_t)hash_algos[format].rawsz * nr; + end = pfp->order_map_offset + (uint64_t)nr * 4; + + /* After the first format tables: CRC32 and 4-byte offset tables */ + if (!i) { + p->crc_offset = end; + end += (uint64_t)nr * (4 + 4); /* CRC32 + offset */ + } + + if (pfp->order_map_offset % 4 || end > trailer_offset) + return error("v3 index %s has invalid tables", path); + } + + main_algo = hash_algo_by_length(hashsz); + if (main_algo == GIT_HASH_UNKNOWN || + !p->formats[main_algo - 1].data_offset) + return error("v3 index %s is missing data for the main hash algorithm", + path); } p->index_version = version; @@ -1730,6 +1811,26 @@ out: return data; } +static int bsearch_pack_v3(const struct object_id *oid, const struct packed_git *p, uint32_t *result) +{ + int algo = oid->algo ? oid->algo : hash_algo_by_ptr(p->repo->hash_algo); + const struct packed_git_format *pfp = &p->formats[algo - 1]; + const unsigned char *data = p->index_data; + + /* This index has no data for the algorithm of the object ID. */ + if (!pfp->data_offset) { + if (result) + *result = 0; + return 0; + } + + return bsearch_hash_v3(oid->hash, data + pfp->full_oid_offset, + (const uint32_t *)(data + pfp->order_map_offset), + data + pfp->data_offset, + pfp->short_name_len, + hash_algos[algo].rawsz, result); +} + int bsearch_pack(const struct object_id *oid, const struct packed_git *p, uint32_t *result) { const unsigned char *index_fanout = p->index_data; @@ -1740,6 +1841,9 @@ int bsearch_pack(const struct object_id *oid, const struct packed_git *p, uint32 if (!index_fanout) BUG("bsearch_pack called without a valid pack-index"); + if (p->index_version == 3) + return bsearch_pack_v3(oid, p, result); + index_lookup = index_fanout + 4 * 256; if (p->index_version == 1) { index_lookup_width = hashsz + 4; @@ -1757,9 +1861,19 @@ int bsearch_pack(const struct object_id *oid, const struct packed_git *p, uint32 int nth_packed_object_id(struct object_id *oid, struct packed_git *p, uint32_t n) +{ + return nth_packed_object_id_algop(oid, p, n, p->repo->hash_algo, + p->repo->hash_algo); +} + +int nth_packed_object_id_algop(struct object_id *oid, + struct packed_git *p, + uint32_t n, + const struct git_hash_algo *src, + const struct git_hash_algo *dest) { const unsigned char *index = p->index_data; - const unsigned int hashsz = p->repo->hash_algo->rawsz; + const unsigned int hashsz = dest->rawsz; if (!index) { if (open_pack_index(p)) return -1; @@ -1767,13 +1881,25 @@ int nth_packed_object_id(struct object_id *oid, } if (n >= p->num_objects) return -1; - index += 4 * 256; + if (p->index_version < 3 && (src != dest || dest != p->repo->hash_algo)) + return -1; /* older indices only store main object IDs */ if (p->index_version == 1) { + index += 4 * 256; oidread(oid, index + st_add(st_mult(hashsz + 4, n), 4), - p->repo->hash_algo); + dest); + } else if (p->index_version == 2) { + index += 4 * 256 + 8; + oidread(oid, index + st_mult(hashsz, n), dest); } else { - index += 8; - oidread(oid, index + st_mult(hashsz, n), p->repo->hash_algo); + int algo = hash_algo_by_ptr(dest); + const struct packed_git_format *pfp = &p->formats[algo-1]; + uint32_t pack_order_idx; + + if (!pfp->data_offset || + !p->formats[hash_algo_by_ptr(src) - 1].data_offset) + return -1; /* no data for one of the algorithms */ + pack_order_idx = nth_packed_object_pack_order_algop(p, n, src); + oidread(oid, index + st_add(pfp->full_oid_offset, st_mult(hashsz, pack_order_idx)), dest); } return 0; } @@ -1793,15 +1919,28 @@ void check_pack_index_ptr(const struct packed_git *p, const void *vptr) } off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n) +{ + return nth_packed_object_offset_algop(p, n, p->repo->hash_algo); +} + +off_t nth_packed_object_offset_algop(const struct packed_git *p, uint32_t n, + const struct git_hash_algo *algop) { const unsigned char *index = p->index_data; const unsigned int hashsz = p->repo->hash_algo->rawsz; - index += 4 * 256; + + if (p->index_version < 3 && algop != p->repo->hash_algo) + BUG("pack index v%d only stores main object IDs", + p->index_version); + if (p->index_version == 1) { + index += 4 * 256; return ntohl(*((uint32_t *)(index + st_mult(hashsz + 4, n)))); } else { uint32_t off; - index += st_add(8, st_mult(p->num_objects, hashsz + 4)); + if (p->index_version == 3) + n = nth_packed_object_pack_order_algop(p, n, algop); + index += st_add(p->crc_offset, st_mult(p->num_objects, 4)); off = ntohl(*((uint32_t *)(index + st_mult(4, n)))); if (!(off & 0x80000000)) return off; @@ -1812,6 +1951,22 @@ off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n) } } +uint32_t nth_packed_object_pack_order_algop(const struct packed_git *p, uint32_t n, + const struct git_hash_algo *algop) +{ + const unsigned char *index = p->index_data; + const unsigned int algo = hash_algo_by_ptr(algop); + const struct packed_git_format *pfp = &p->formats[algo-1]; + uint32_t pos = ntohl(*(uint32_t *)(index + + st_add(pfp->order_map_offset, + st_mult(4, n)))); + + if (pos >= p->num_objects) + die(_("invalid object position in pack index for %s (corrupt index?)"), + p->pack_name); + return pos; +} + off_t find_pack_entry_one(const struct object_id *oid, struct packed_git *p) { diff --git a/packfile.h b/packfile.h index 0ead9e2821..41a56bffde 100644 --- a/packfile.h +++ b/packfile.h @@ -1,6 +1,7 @@ #ifndef PACKFILE_H #define PACKFILE_H +#include "hash.h" #include "list.h" #include "object.h" #include "odb.h" @@ -14,12 +15,22 @@ struct object_info; struct odb_stream; +struct packed_git_format { + /* This is also the location of the short OID table. */ + uint64_t data_offset; + uint64_t full_oid_offset; + uint64_t order_map_offset; + uint32_t short_name_len; +}; + struct packed_git { struct pack_window *windows; off_t pack_size; const void *index_data; size_t index_size; uint32_t num_objects; + uint32_t num_formats; + struct packed_git_format formats[GIT_HASH_NALGOS - 1]; size_t crc_offset; struct oidset bad_objects; int index_version; @@ -285,12 +296,34 @@ int bsearch_pack(const struct object_id *oid, const struct packed_git *p, uint32 */ int nth_packed_object_id(struct object_id *, struct packed_git *, uint32_t n); +/* + * The same as nth_packed_object_id, but the nth object is counted in src + * and the object ID looked up is in dest. + */ +int nth_packed_object_id_algop(struct object_id *, struct packed_git *, + uint32_t n, const struct git_hash_algo *src, + const struct git_hash_algo *dest); + /* * Return the offset of the nth object within the specified packfile. * The index must already be opened. */ off_t nth_packed_object_offset(const struct packed_git *, uint32_t n); +/* + * The same as nth_packed_object_offset, but the nth object is counted in + * algop. For indices older than v3, algop must be the main algorithm. + */ +off_t nth_packed_object_offset_algop(const struct packed_git *, uint32_t n, + const struct git_hash_algo *algop); + +/* + * Return the index into pack order of the nth object within the specified + * packfile. The index must already be opened and it must be a v3 index. + */ +uint32_t nth_packed_object_pack_order_algop(const struct packed_git *, uint32_t n, + const struct git_hash_algo *algop); + /* * If the object named by oid is present in the specified packfile, * return its offset within the packfile; otherwise, return 0. diff --git a/t/t1016-compatObjectFormat.sh b/t/t1016-compatObjectFormat.sh index 6d0f48226f..0b0e13db8d 100755 --- a/t/t1016-compatObjectFormat.sh +++ b/t/t1016-compatObjectFormat.sh @@ -45,6 +45,15 @@ compat_hash () { esac } +create_compat_repo () { + local hash="$1" + local repo="$2" + + git init --object-format="$hash" "$repo" && + git -C "$repo" config set core.repositoryformatversion 1 && + git -C "$repo" config set extensions.compatObjectFormat $(compat_hash "$hash") +} + hello_oid () { case "$1" in "sha1") @@ -192,9 +201,7 @@ do for repo in alt borrow do test_when_finished "rm -rf $repo" && - git init --object-format=$hash $repo && - git -C $repo config set core.repositoryformatversion 1 && - git -C $repo config set extensions.compatObjectFormat $(compat_hash $hash) || exit 1 + create_compat_repo $hash $repo || exit 1 done && git -C alt commit --allow-empty --message A && @@ -205,6 +212,93 @@ do git -C borrow rev-parse --output-object-format=$(compat_hash $hash) "$oid" >actual && test_cmp expect actual ' + + test_expect_success 'index-pack writes pack index v3' ' + test_when_finished "rm -rf v3-repo v3.idx" && + create_compat_repo $hash v3-repo && + echo content >v3-repo/file && + git -C v3-repo add file && + git -C v3-repo commit --message A && + git -C v3-repo commit --allow-empty --message B && + git -C v3-repo repack -a -d && + pack=$(echo v3-repo/.git/objects/pack/pack-*.pack) && + git -C v3-repo index-pack --index-version=3 \ + -o "$(pwd)/v3.idx" "$(pwd)/$pack" && + echo "ff 74 4f 63 00 00 00 03 " >expect && + test_copy_bytes 8 actual && + test_cmp expect actual + ' + + test_expect_success 'add a large file streamed into a pack' ' + test_when_finished "rm -rf large-repo" && + create_compat_repo $hash large-repo && + git -C large-repo config set core.bigFileThreshold 1k && + test-tool genrandom large 5000 >large-repo/large && + git -C large-repo add large && + ls large-repo/.git/objects/pack/pack-*.pack && + git -C large-repo hash-object large >expect && + git -C large-repo rev-parse :large >actual && + test_cmp expect actual && + git -C large-repo cat-file blob :large >actual && + test_cmp large-repo/large actual + ' + + test_expect_success 'packed objects are only mapped using pack index v3' ' + test_when_finished "rm -rf map-repo v3.idx loose-object-idx" && + create_compat_repo $hash map-repo && + echo content >map-repo/file && + git -C map-repo add file && + git -C map-repo commit --message A && + oid=$(git -C map-repo rev-parse HEAD) && + git -C map-repo rev-parse --output-object-format=$(compat_hash $hash) "$oid" >expect && + git -C map-repo repack -a -d && + pack=$(echo map-repo/.git/objects/pack/pack-*.pack) && + git -C map-repo index-pack --index-version=3 \ + -o "$(pwd)/v3.idx" "$(pwd)/$pack" && + mv map-repo/.git/objects/loose-object-idx loose-object-idx && + + # A v2 index only has object IDs in the main algorithm, so it + # must not provide a mapping. + git -C map-repo rev-parse --output-object-format=$(compat_hash $hash) "$oid" >actual && + ! test_cmp expect actual && + test $(wc -c actual && + test_cmp expect actual + ' + + test_expect_success 'abbreviated compat object IDs are found using pack index v3' ' + test_when_finished "rm -rf abbrev-repo v3.idx" && + create_compat_repo $hash abbrev-repo && + echo content >abbrev-repo/file && + git -C abbrev-repo add file && + git -C abbrev-repo commit --message A && + compat_oid=$(git -C abbrev-repo rev-parse --output-object-format=$(compat_hash $hash) HEAD) && + abbrev=$(echo $compat_oid | cut -c 1-12) && + + # A multi-pack-index only stores object IDs in the main + # algorithm. + GIT_TEST_MULTI_PACK_INDEX=0 git -C abbrev-repo repack -a -d && + pack=$(echo abbrev-repo/.git/objects/pack/pack-*.pack) && + git -C abbrev-repo index-pack --index-version=3 \ + -o "$(pwd)/v3.idx" "$(pwd)/$pack" && + rm abbrev-repo/.git/objects/loose-object-idx && + + # A v2 index only has object IDs in the main algorithm. + test_must_fail git -C abbrev-repo cat-file -t $abbrev && + + rm -f "${pack%.pack}.idx" "${pack%.pack}.rev" && + mv v3.idx "${pack%.pack}.idx" && + echo commit >expect && + git -C abbrev-repo cat-file -t $abbrev >actual && + test_cmp expect actual && + echo $compat_oid >expect && + git -C abbrev-repo rev-parse --output-object-format=$(compat_hash $hash) $abbrev >actual && + test_cmp expect actual + ' done cd "$base" diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 735de1023e..9ea0ed487e 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -36,50 +36,55 @@ test_expect_success 'setup' ' git update-ref HEAD $commit ' -test_expect_success 'pack-objects with index version 1' ' +test_expect_success !COMPAT_HASH 'pack-objects with index version 1' ' pack1=$(git pack-objects --index-version=1 test-1 file_101_foo1 ' -test_expect_success \ +test_expect_success !COMPAT_HASH \ '[index v1] 3) corrupted delta happily returned wrong data' ' test -f file_101_foo1 && ! cmp file_101 file_101_foo1 ' -test_expect_success \ +test_expect_success !COMPAT_HASH \ '[index v1] 4) confirm that the pack is actually corrupted' ' test_must_fail git fsck --full $commit ' -test_expect_success \ +test_expect_success !COMPAT_HASH \ '[index v1] 5) pack-objects happily reuses corrupted data' ' pack4=$(git pack-objects test-4 file_101_foo2 ' -test_expect_success \ +test_expect_success !COMPAT_HASH \ '[index v2] 3) corrupted delta happily returned wrong data' ' test -f file_101_foo2 && ! cmp file_101 file_101_foo2 ' -test_expect_success \ +test_expect_success !COMPAT_HASH \ '[index v2] 4) confirm that the pack is actually corrupted' ' test_must_fail git fsck --full $commit ' -test_expect_success \ +test_expect_success !COMPAT_HASH \ '[index v2] 5) pack-objects refuses to reuse corrupted data' ' test_must_fail git pack-objects test-5 err && test_grep "^warning:.* expected .tagger. line" err ' diff --git a/t/t5308-pack-detect-duplicates.sh b/t/t5308-pack-detect-duplicates.sh index 0f84137867..7b43db870a 100755 --- a/t/t5308-pack-detect-duplicates.sh +++ b/t/t5308-pack-detect-duplicates.sh @@ -79,4 +79,11 @@ test_expect_success 'index-pack can reject packs with duplicates' ' test_expect_code 1 git cat-file -e $LO_SHA1 ' +test_expect_success 'index-pack can reject packs with duplicates with index v3' ' + clear_packs && + create_pack dups.pack 2 && + test_must_fail git index-pack --strict --index-version=3 --stdin err && + test_grep "corrupt index" err +' + test_expect_success 'bogus OFS_DELTA in packfile' ' # Generate a pack with a delta in it. base=$(test-tool genrandom foo 3000 | git hash-object --stdin -w) &&