]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ak/corrected-commit-date'
authorJunio C Hamano <gitster@pobox.com>
Thu, 18 Feb 2021 01:21:40 +0000 (17:21 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 18 Feb 2021 01:21:40 +0000 (17:21 -0800)
The commit-graph learned to use corrected commit dates instead of
the generation number to help topological revision traversal.

* ak/corrected-commit-date:
  doc: add corrected commit date info
  commit-reach: use corrected commit dates in paint_down_to_common()
  commit-graph: use generation v2 only if entire chain does
  commit-graph: implement generation data chunk
  commit-graph: implement corrected commit date
  commit-graph: return 64-bit generation number
  commit-graph: add a slab to store topological levels
  t6600-test-reach: generalize *_three_modes
  commit-graph: consolidate fill_commit_graph_info
  revision: parse parent in indegree_walk_step()
  commit-graph: fix regression when computing Bloom filters

1  2 
commit-graph.c
commit.c
commit.h
revision.c
t/README
t/t4216-log-bloom.sh
t/t6404-recursive-merge.sh
t/test-lib-functions.sh
upload-pack.c

diff --combined commit-graph.c
index 65410602714e642922bc5f6060b4b085db8464a5,f3bde2ad95a16ad81c657296d999e7f7c417ff4b..36e481c67e7b9fed449193ff2a868144572bd80f
@@@ -7,7 -7,7 +7,7 @@@
  #include "object.h"
  #include "refs.h"
  #include "revision.h"
 -#include "sha1-lookup.h"
 +#include "hash-lookup.h"
  #include "commit-graph.h"
  #include "object-store.h"
  #include "alloc.h"
@@@ -38,11 -38,13 +38,13 @@@ void git_test_write_commit_graph_or_die
  #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
  #define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
  #define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */
+ #define GRAPH_CHUNKID_GENERATION_DATA 0x47444154 /* "GDAT" */
+ #define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f56 /* "GDOV" */
  #define GRAPH_CHUNKID_EXTRAEDGES 0x45444745 /* "EDGE" */
  #define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458 /* "BIDX" */
  #define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */
  #define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */
- #define MAX_NUM_CHUNKS 7
+ #define MAX_NUM_CHUNKS 9
  
  #define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16)
  
  #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \
                        + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)
  
+ #define CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW (1ULL << 31)
  /* Remember to update object flag allocation in object.h */
  #define REACHABLE       (1u<<15)
  
+ define_commit_slab(topo_level_slab, uint32_t);
  /* Keep track of the order in which commits are added to our list. */
  define_commit_slab(commit_pos, int);
  static struct commit_pos commit_pos = COMMIT_SLAB_INIT(1, commit_pos);
@@@ -99,7 -105,7 +105,7 @@@ uint32_t commit_graph_position(const st
        return data ? data->graph_pos : COMMIT_NOT_FROM_GRAPH;
  }
  
uint32_t commit_graph_generation(const struct commit *c)
timestamp_t commit_graph_generation(const struct commit *c)
  {
        struct commit_graph_data *data =
                commit_graph_data_slab_peek(&commit_graph_data_slab, c);
@@@ -139,13 -145,17 +145,17 @@@ static struct commit_graph_data *commit
        return data;
  }
  
+ /*
+  * Should be used only while writing commit-graph as it compares
+  * generation value of commits by directly accessing commit-slab.
+  */
  static int commit_gen_cmp(const void *va, const void *vb)
  {
        const struct commit *a = *(const struct commit **)va;
        const struct commit *b = *(const struct commit **)vb;
  
-       uint32_t generation_a = commit_graph_generation(a);
-       uint32_t generation_b = commit_graph_generation(b);
+       const timestamp_t generation_a = commit_graph_data_at(a)->generation;
+       const timestamp_t generation_b = commit_graph_data_at(b)->generation;
        /* lower generation commits first */
        if (generation_a < generation_b)
                return -1;
@@@ -388,6 -398,20 +398,20 @@@ struct commit_graph *parse_commit_graph
                                graph->chunk_commit_data = data + chunk_offset;
                        break;
  
+               case GRAPH_CHUNKID_GENERATION_DATA:
+                       if (graph->chunk_generation_data)
+                               chunk_repeated = 1;
+                       else
+                               graph->chunk_generation_data = data + chunk_offset;
+                       break;
+               case GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW:
+                       if (graph->chunk_generation_data_overflow)
+                               chunk_repeated = 1;
+                       else
+                               graph->chunk_generation_data_overflow = data + chunk_offset;
+                       break;
                case GRAPH_CHUNKID_EXTRAEDGES:
                        if (graph->chunk_extra_edges)
                                chunk_repeated = 1;
@@@ -590,6 -614,21 +614,21 @@@ static struct commit_graph *load_commit
        return graph_chain;
  }
  
+ static void validate_mixed_generation_chain(struct commit_graph *g)
+ {
+       int read_generation_data;
+       if (!g)
+               return;
+       read_generation_data = !!g->chunk_generation_data;
+       while (g) {
+               g->read_generation_data = read_generation_data;
+               g = g->base_graph;
+       }
+ }
  struct commit_graph *read_commit_graph_one(struct repository *r,
                                           struct object_directory *odb)
  {
        if (!g)
                g = load_commit_graph_chain(r, odb);
  
+       validate_mixed_generation_chain(g);
        return g;
  }
  
@@@ -673,6 -714,20 +714,20 @@@ int generation_numbers_enabled(struct r
        return !!first_generation;
  }
  
+ int corrected_commit_dates_enabled(struct repository *r)
+ {
+       struct commit_graph *g;
+       if (!prepare_commit_graph(r))
+               return 0;
+       g = r->objects->commit_graph;
+       if (!g->num_commits)
+               return 0;
+       return g->read_generation_data;
+ }
  struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r)
  {
        struct commit_graph *g = r->objects->commit_graph;
@@@ -748,17 -803,41 +803,41 @@@ static void fill_commit_graph_info(stru
  {
        const unsigned char *commit_data;
        struct commit_graph_data *graph_data;
-       uint32_t lex_index;
+       uint32_t lex_index, offset_pos;
+       uint64_t date_high, date_low, offset;
  
        while (pos < g->num_commits_in_base)
                g = g->base_graph;
  
+       if (pos >= g->num_commits + g->num_commits_in_base)
+               die(_("invalid commit position. commit-graph is likely corrupt"));
        lex_index = pos - g->num_commits_in_base;
        commit_data = g->chunk_commit_data + GRAPH_DATA_WIDTH * lex_index;
  
        graph_data = commit_graph_data_at(item);
        graph_data->graph_pos = pos;
-       graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
+       date_high = get_be32(commit_data + g->hash_len + 8) & 0x3;
+       date_low = get_be32(commit_data + g->hash_len + 12);
+       item->date = (timestamp_t)((date_high << 32) | date_low);
+       if (g->read_generation_data) {
+               offset = (timestamp_t)get_be32(g->chunk_generation_data + sizeof(uint32_t) * lex_index);
+               if (offset & CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW) {
+                       if (!g->chunk_generation_data_overflow)
+                               die(_("commit-graph requires overflow generation data but has none"));
+                       offset_pos = offset ^ CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW;
+                       graph_data->generation = get_be64(g->chunk_generation_data_overflow + 8 * offset_pos);
+               } else
+                       graph_data->generation = item->date + offset;
+       } else
+               graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
+       if (g->topo_levels)
+               *topo_level_slab_at(g->topo_levels, item) = get_be32(commit_data + g->hash_len + 8) >> 2;
  }
  
  static inline void set_commit_tree(struct commit *c, struct tree *t)
@@@ -772,38 -851,22 +851,22 @@@ static int fill_commit_in_graph(struct 
  {
        uint32_t edge_value;
        uint32_t *parent_data_ptr;
-       uint64_t date_low, date_high;
        struct commit_list **pptr;
-       struct commit_graph_data *graph_data;
        const unsigned char *commit_data;
        uint32_t lex_index;
  
        while (pos < g->num_commits_in_base)
                g = g->base_graph;
  
-       if (pos >= g->num_commits + g->num_commits_in_base)
-               die(_("invalid commit position. commit-graph is likely corrupt"));
+       fill_commit_graph_info(item, g, pos);
  
-       /*
-        * Store the "full" position, but then use the
-        * "local" position for the rest of the calculation.
-        */
-       graph_data = commit_graph_data_at(item);
-       graph_data->graph_pos = pos;
        lex_index = pos - g->num_commits_in_base;
        commit_data = g->chunk_commit_data + (g->hash_len + 16) * lex_index;
  
        item->object.parsed = 1;
  
        set_commit_tree(item, NULL);
  
-       date_high = get_be32(commit_data + g->hash_len + 8) & 0x3;
-       date_low = get_be32(commit_data + g->hash_len + 12);
-       item->date = (timestamp_t)((date_high << 32) | date_low);
-       graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2;
        pptr = &item->parents;
  
        edge_value = get_be32(commit_data + g->hash_len);
@@@ -943,6 -1006,7 +1006,7 @@@ struct write_commit_graph_context 
        struct oid_array oids;
        struct packed_commit_list commits;
        int num_extra_edges;
+       int num_generation_data_overflows;
        unsigned long approx_nr_objects;
        struct progress *progress;
        int progress_done;
                 report_progress:1,
                 split:1,
                 changed_paths:1,
-                order_by_pack:1;
+                order_by_pack:1,
+                write_generation_data:1;
  
+       struct topo_level_slab *topo_levels;
        const struct commit_graph_opts *opts;
        size_t total_bloom_filter_data_size;
        const struct bloom_filter_settings *bloom_settings;
@@@ -1012,10 -1078,10 +1078,10 @@@ static int write_graph_chunk_oids(struc
        return 0;
  }
  
 -static const unsigned char *commit_to_sha1(size_t index, void *table)
 +static const struct object_id *commit_to_oid(size_t index, const void *table)
  {
 -      struct commit **commits = table;
 -      return commits[index]->object.oid.hash;
 +      const struct commit * const *commits = table;
 +      return &commits[index]->object.oid;
  }
  
  static int write_graph_chunk_data(struct hashfile *f,
                if (!parent)
                        edge_value = GRAPH_PARENT_NONE;
                else {
 -                      edge_value = sha1_pos(parent->item->object.oid.hash,
 -                                            ctx->commits.list,
 -                                            ctx->commits.nr,
 -                                            commit_to_sha1);
 +                      edge_value = oid_pos(&parent->item->object.oid,
 +                                           ctx->commits.list,
 +                                           ctx->commits.nr,
 +                                           commit_to_oid);
  
                        if (edge_value >= 0)
                                edge_value += ctx->new_num_commits_in_base;
                else if (parent->next)
                        edge_value = GRAPH_EXTRA_EDGES_NEEDED | num_extra_edges;
                else {
 -                      edge_value = sha1_pos(parent->item->object.oid.hash,
 -                                            ctx->commits.list,
 -                                            ctx->commits.nr,
 -                                            commit_to_sha1);
 +                      edge_value = oid_pos(&parent->item->object.oid,
 +                                           ctx->commits.list,
 +                                           ctx->commits.nr,
 +                                           commit_to_oid);
  
                        if (edge_value >= 0)
                                edge_value += ctx->new_num_commits_in_base;
                else
                        packedDate[0] = 0;
  
-               packedDate[0] |= htonl(commit_graph_data_at(*list)->generation << 2);
+               packedDate[0] |= htonl(*topo_level_slab_at(ctx->topo_levels, *list) << 2);
  
                packedDate[1] = htonl((*list)->date);
                hashwrite(f, packedDate, 8);
        return 0;
  }
  
+ static int write_graph_chunk_generation_data(struct hashfile *f,
+                                             struct write_commit_graph_context *ctx)
+ {
+       int i, num_generation_data_overflows = 0;
+       for (i = 0; i < ctx->commits.nr; i++) {
+               struct commit *c = ctx->commits.list[i];
+               timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
+               display_progress(ctx->progress, ++ctx->progress_cnt);
+               if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) {
+                       offset = CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows;
+                       num_generation_data_overflows++;
+               }
+               hashwrite_be32(f, offset);
+       }
+       return 0;
+ }
+ static int write_graph_chunk_generation_data_overflow(struct hashfile *f,
+                                                      struct write_commit_graph_context *ctx)
+ {
+       int i;
+       for (i = 0; i < ctx->commits.nr; i++) {
+               struct commit *c = ctx->commits.list[i];
+               timestamp_t offset = commit_graph_data_at(c)->generation - c->date;
+               display_progress(ctx->progress, ++ctx->progress_cnt);
+               if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) {
+                       hashwrite_be32(f, offset >> 32);
+                       hashwrite_be32(f, (uint32_t) offset);
+               }
+       }
+       return 0;
+ }
  static int write_graph_chunk_extra_edges(struct hashfile *f,
                                         struct write_commit_graph_context *ctx)
  {
  
                /* Since num_parents > 2, this initializer is safe. */
                for (parent = (*list)->parents->next; parent; parent = parent->next) {
 -                      int edge_value = sha1_pos(parent->item->object.oid.hash,
 -                                                ctx->commits.list,
 -                                                ctx->commits.nr,
 -                                                commit_to_sha1);
 +                      int edge_value = oid_pos(&parent->item->object.oid,
 +                                               ctx->commits.list,
 +                                               ctx->commits.nr,
 +                                               commit_to_oid);
  
                        if (edge_value >= 0)
                                edge_value += ctx->new_num_commits_in_base;
@@@ -1339,11 -1444,12 +1444,12 @@@ static void compute_generation_numbers(
                                        _("Computing commit graph generation numbers"),
                                        ctx->commits.nr);
        for (i = 0; i < ctx->commits.nr; i++) {
-               uint32_t generation = commit_graph_data_at(ctx->commits.list[i])->generation;
+               uint32_t level = *topo_level_slab_at(ctx->topo_levels, ctx->commits.list[i]);
+               timestamp_t corrected_commit_date = commit_graph_data_at(ctx->commits.list[i])->generation;
  
                display_progress(ctx->progress, i + 1);
-               if (generation != GENERATION_NUMBER_INFINITY &&
-                   generation != GENERATION_NUMBER_ZERO)
+               if (level != GENERATION_NUMBER_ZERO &&
+                   corrected_commit_date != GENERATION_NUMBER_ZERO)
                        continue;
  
                commit_list_insert(ctx->commits.list[i], &list);
                        struct commit *current = list->item;
                        struct commit_list *parent;
                        int all_parents_computed = 1;
-                       uint32_t max_generation = 0;
+                       uint32_t max_level = 0;
+                       timestamp_t max_corrected_commit_date = 0;
  
                        for (parent = current->parents; parent; parent = parent->next) {
-                               generation = commit_graph_data_at(parent->item)->generation;
+                               level = *topo_level_slab_at(ctx->topo_levels, parent->item);
+                               corrected_commit_date = commit_graph_data_at(parent->item)->generation;
  
-                               if (generation == GENERATION_NUMBER_INFINITY ||
-                                   generation == GENERATION_NUMBER_ZERO) {
+                               if (level == GENERATION_NUMBER_ZERO ||
+                                   corrected_commit_date == GENERATION_NUMBER_ZERO) {
                                        all_parents_computed = 0;
                                        commit_list_insert(parent->item, &list);
                                        break;
-                               } else if (generation > max_generation) {
-                                       max_generation = generation;
                                }
+                               if (level > max_level)
+                                       max_level = level;
+                               if (corrected_commit_date > max_corrected_commit_date)
+                                       max_corrected_commit_date = corrected_commit_date;
                        }
  
                        if (all_parents_computed) {
-                               struct commit_graph_data *data = commit_graph_data_at(current);
-                               data->generation = max_generation + 1;
                                pop_commit(&list);
  
-                               if (data->generation > GENERATION_NUMBER_MAX)
-                                       data->generation = GENERATION_NUMBER_MAX;
+                               if (max_level > GENERATION_NUMBER_V1_MAX - 1)
+                                       max_level = GENERATION_NUMBER_V1_MAX - 1;
+                               *topo_level_slab_at(ctx->topo_levels, current) = max_level + 1;
+                               if (current->date && current->date > max_corrected_commit_date)
+                                       max_corrected_commit_date = current->date - 1;
+                               commit_graph_data_at(current)->generation = max_corrected_commit_date + 1;
+                               if (commit_graph_data_at(current)->generation - current->date > GENERATION_NUMBER_V2_OFFSET_MAX)
+                                       ctx->num_generation_data_overflows++;
                        }
                }
        }
@@@ -1458,7 -1575,7 +1575,7 @@@ static int add_ref_to_set(const char *r
        struct object_id peeled;
        struct refs_cb_data *data = (struct refs_cb_data *)cb_data;
  
 -      if (!peel_ref(refname, &peeled))
 +      if (!peel_iterated_oid(oid, &peeled))
                oid = &peeled;
        if (oid_object_info(the_repository, oid, NULL) == OBJ_COMMIT)
                oidset_insert(data->commits, oid);
@@@ -1694,8 -1811,8 +1811,8 @@@ static int write_commit_graph_file(stru
        } else {
                hold_lock_file_for_update_mode(&lk, ctx->graph_name,
                                               LOCK_DIE_ON_ERROR, 0444);
 -              fd = lk.tempfile->fd;
 -              f = hashfd(lk.tempfile->fd, lk.tempfile->filename.buf);
 +              fd = get_lock_file_fd(&lk);
 +              f = hashfd(fd, get_lock_file_path(&lk));
        }
  
        chunks[0].id = GRAPH_CHUNKID_OIDFANOUT;
        chunks[2].id = GRAPH_CHUNKID_DATA;
        chunks[2].size = (hashsz + 16) * ctx->commits.nr;
        chunks[2].write_fn = write_graph_chunk_data;
+       if (git_env_bool(GIT_TEST_COMMIT_GRAPH_NO_GDAT, 0))
+               ctx->write_generation_data = 0;
+       if (ctx->write_generation_data) {
+               chunks[num_chunks].id = GRAPH_CHUNKID_GENERATION_DATA;
+               chunks[num_chunks].size = sizeof(uint32_t) * ctx->commits.nr;
+               chunks[num_chunks].write_fn = write_graph_chunk_generation_data;
+               num_chunks++;
+       }
+       if (ctx->num_generation_data_overflows) {
+               chunks[num_chunks].id = GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW;
+               chunks[num_chunks].size = sizeof(timestamp_t) * ctx->num_generation_data_overflows;
+               chunks[num_chunks].write_fn = write_graph_chunk_generation_data_overflow;
+               num_chunks++;
+       }
        if (ctx->num_extra_edges) {
                chunks[num_chunks].id = GRAPH_CHUNKID_EXTRAEDGES;
                chunks[num_chunks].size = 4 * ctx->num_extra_edges;
                result = rename(ctx->graph_name, final_graph_name);
  
                for (i = 0; i < ctx->num_commit_graphs_after; i++)
 -                      fprintf(lk.tempfile->fp, "%s\n", ctx->commit_graph_hash_after[i]);
 +                      fprintf(get_lock_file_fp(&lk), "%s\n", ctx->commit_graph_hash_after[i]);
  
                if (result) {
                        error(_("failed to rename temporary commit-graph file"));
@@@ -1918,6 -2050,13 +2050,13 @@@ static void split_graph_merge_strategy(
                if (i < ctx->num_commit_graphs_after)
                        ctx->commit_graph_hash_after[i] = xstrdup(oid_to_hex(&g->oid));
  
+               /*
+                * If the topmost remaining layer has generation data chunk, the
+                * resultant layer also has generation data chunk.
+                */
+               if (i == ctx->num_commit_graphs_after - 2)
+                       ctx->write_generation_data = !!g->chunk_generation_data;
                i--;
                g = g->base_graph;
        }
@@@ -2109,6 -2248,7 +2248,7 @@@ int write_commit_graph(struct object_di
        int res = 0;
        int replace = 0;
        struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS;
+       struct topo_level_slab topo_levels;
  
        prepare_repo_settings(the_repository);
        if (!the_repository->settings.core_commit_graph) {
        ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0;
        ctx->opts = opts;
        ctx->total_bloom_filter_data_size = 0;
+       ctx->write_generation_data = 1;
+       ctx->num_generation_data_overflows = 0;
  
        bloom_settings.bits_per_entry = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY",
                                                      bloom_settings.bits_per_entry);
                                                         bloom_settings.max_changed_paths);
        ctx->bloom_settings = &bloom_settings;
  
+       init_topo_level_slab(&topo_levels);
+       ctx->topo_levels = &topo_levels;
+       if (ctx->r->objects->commit_graph) {
+               struct commit_graph *g = ctx->r->objects->commit_graph;
+               while (g) {
+                       g->topo_levels = &topo_levels;
+                       g = g->base_graph;
+               }
+       }
        if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS)
                ctx->changed_paths = 1;
        if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) {
        } else
                ctx->num_commit_graphs_after = 1;
  
+       validate_mixed_generation_chain(ctx->r->objects->commit_graph);
        compute_generation_numbers(ctx);
  
        if (ctx->changed_paths)
@@@ -2355,8 -2511,8 +2511,8 @@@ int verify_commit_graph(struct reposito
        for (i = 0; i < g->num_commits; i++) {
                struct commit *graph_commit, *odb_commit;
                struct commit_list *graph_parents, *odb_parents;
-               uint32_t max_generation = 0;
-               uint32_t generation;
+               timestamp_t max_generation = 0;
+               timestamp_t generation;
  
                display_progress(progress, i + 1);
                hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i);
                        continue;
  
                /*
-                * If one of our parents has generation GENERATION_NUMBER_MAX, then
-                * our generation is also GENERATION_NUMBER_MAX. Decrement to avoid
-                * extra logic in the following condition.
+                * If we are using topological level and one of our parents has
+                * generation GENERATION_NUMBER_V1_MAX, then our generation is
+                * also GENERATION_NUMBER_V1_MAX. Decrement to avoid extra logic
+                * in the following condition.
                 */
-               if (max_generation == GENERATION_NUMBER_MAX)
+               if (!g->read_generation_data && max_generation == GENERATION_NUMBER_V1_MAX)
                        max_generation--;
  
                generation = commit_graph_generation(graph_commit);
-               if (generation != max_generation + 1)
-                       graph_report(_("commit-graph generation for commit %s is %u != %u"),
+               if (generation < max_generation + 1)
+                       graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime),
                                     oid_to_hex(&cur_oid),
                                     generation,
                                     max_generation + 1);
diff --combined commit.c
index fd2831dad3243f32f5bae472f2af6ad15019c67d,17abf92a2d2e2ab2c926400d48cc0db40ef7aa90..4694c4cf9bca07a4e86a9d9701053c9185df9ebd
+++ b/commit.c
@@@ -14,7 -14,7 +14,7 @@@
  #include "mergesort.h"
  #include "commit-slab.h"
  #include "prio-queue.h"
 -#include "sha1-lookup.h"
 +#include "hash-lookup.h"
  #include "wt-status.h"
  #include "advice.h"
  #include "refs.h"
@@@ -105,23 -105,23 +105,23 @@@ static timestamp_t parse_commit_date(co
        return parse_timestamp(dateptr, NULL, 10);
  }
  
 -static const unsigned char *commit_graft_sha1_access(size_t index, void *table)
 +static const struct object_id *commit_graft_oid_access(size_t index, const void *table)
  {
 -      struct commit_graft **commit_graft_table = table;
 -      return commit_graft_table[index]->oid.hash;
 +      const struct commit_graft * const *commit_graft_table = table;
 +      return &commit_graft_table[index]->oid;
  }
  
 -int commit_graft_pos(struct repository *r, const unsigned char *sha1)
 +int commit_graft_pos(struct repository *r, const struct object_id *oid)
  {
 -      return sha1_pos(sha1, r->parsed_objects->grafts,
 -                      r->parsed_objects->grafts_nr,
 -                      commit_graft_sha1_access);
 +      return oid_pos(oid, r->parsed_objects->grafts,
 +                     r->parsed_objects->grafts_nr,
 +                     commit_graft_oid_access);
  }
  
  int register_commit_graft(struct repository *r, struct commit_graft *graft,
                          int ignore_dups)
  {
 -      int pos = commit_graft_pos(r, graft->oid.hash);
 +      int pos = commit_graft_pos(r, &graft->oid);
  
        if (0 <= pos) {
                if (ignore_dups)
@@@ -232,7 -232,7 +232,7 @@@ struct commit_graft *lookup_commit_graf
  {
        int pos;
        prepare_commit_graft(r);
 -      pos = commit_graft_pos(r, oid->hash);
 +      pos = commit_graft_pos(r, oid);
        if (pos < 0)
                return NULL;
        return r->parsed_objects->grafts[pos];
@@@ -544,17 -544,6 +544,17 @@@ struct commit_list *commit_list_insert(
        return new_list;
  }
  
 +int commit_list_contains(struct commit *item, struct commit_list *list)
 +{
 +      while (list) {
 +              if (list->item == item)
 +                      return 1;
 +              list = list->next;
 +      }
 +
 +      return 0;
 +}
 +
  unsigned commit_list_count(const struct commit_list *l)
  {
        unsigned c = 0;
@@@ -574,17 -563,6 +574,17 @@@ struct commit_list *copy_commit_list(st
        return head;
  }
  
 +struct commit_list *reverse_commit_list(struct commit_list *list)
 +{
 +      struct commit_list *next = NULL, *current, *backup;
 +      for (current = list; current; current = backup) {
 +              backup = current->next;
 +              current->next = next;
 +              next = current;
 +      }
 +      return next;
 +}
 +
  void free_commit_list(struct commit_list *list)
  {
        while (list)
@@@ -753,8 -731,8 +753,8 @@@ int compare_commits_by_author_date(cons
  int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused)
  {
        const struct commit *a = a_, *b = b_;
-       const uint32_t generation_a = commit_graph_generation(a),
-                      generation_b = commit_graph_generation(b);
+       const timestamp_t generation_a = commit_graph_generation(a),
+                         generation_b = commit_graph_generation(b);
  
        /* newer commits first */
        if (generation_a < generation_b)
diff --combined commit.h
index ecacf9ade36497d6d30dd14bc926fb7759db020d,251d877fcf67e8b13bd6d11765d6570d0463c82c..0c9714827c6c2634954b41792e9ddc1b1e5352a5
+++ b/commit.h
  #include "commit-slab.h"
  
  #define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF
- #define GENERATION_NUMBER_INFINITY 0xFFFFFFFF
- #define GENERATION_NUMBER_MAX 0x3FFFFFFF
+ #define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1)
+ #define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF
  #define GENERATION_NUMBER_ZERO 0
+ #define GENERATION_NUMBER_V2_OFFSET_MAX ((1ULL << 31) - 1)
  
  struct commit_list {
        struct commit *item;
@@@ -167,8 -168,6 +168,8 @@@ int find_commit_subject(const char *com
  
  struct commit_list *commit_list_insert(struct commit *item,
                                        struct commit_list **list);
 +int commit_list_contains(struct commit *item,
 +                       struct commit_list *list);
  struct commit_list **commit_list_append(struct commit *commit,
                                        struct commit_list **next);
  unsigned commit_list_count(const struct commit_list *l);
@@@ -179,9 -178,6 +180,9 @@@ void commit_list_sort_by_date(struct co
  /* Shallow copy of the input list */
  struct commit_list *copy_commit_list(struct commit_list *list);
  
 +/* Modify list in-place to reverse it, returning new head; list will be tail */
 +struct commit_list *reverse_commit_list(struct commit_list *list);
 +
  void free_commit_list(struct commit_list *list);
  
  struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
@@@ -239,7 -235,7 +240,7 @@@ typedef int (*each_commit_graft_fn)(con
  
  struct commit_graft *read_graft_line(struct strbuf *line);
  /* commit_graft_pos returns an index into r->parsed_objects->grafts. */
 -int commit_graft_pos(struct repository *r, const unsigned char *sha1);
 +int commit_graft_pos(struct repository *r, const struct object_id *oid);
  int register_commit_graft(struct repository *r, struct commit_graft *, int);
  void prepare_commit_graft(struct repository *r);
  struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
diff --combined revision.c
index 3efd994160d95f69f2e9df71a04c72bac6a062e3,d55c2e4d566e17bbd1b04ee963ad75d0eee45d1c..b78733f5089b0dcef8eed71c44243a3b9a6f86db
@@@ -5,7 -5,6 +5,7 @@@
  #include "tree.h"
  #include "commit.h"
  #include "diff.h"
 +#include "diff-merges.h"
  #include "refs.h"
  #include "revision.h"
  #include "repository.h"
@@@ -1242,14 -1241,12 +1242,14 @@@ static void cherry_pick_list(struct com
                /*
                 * Have we seen the same patch id?
                 */
 -              id = has_commit_patch_id(commit, &ids);
 +              id = patch_id_iter_first(commit, &ids);
                if (!id)
                        continue;
  
                commit->object.flags |= cherry_flag;
 -              id->commit->object.flags |= cherry_flag;
 +              do {
 +                      id->commit->object.flags |= cherry_flag;
 +              } while ((id = patch_id_iter_next(id, &ids)));
        }
  
        free_patch_ids(&ids);
@@@ -1809,6 -1806,7 +1809,6 @@@ void repo_init_revisions(struct reposit
  
        revs->repo = r;
        revs->abbrev = DEFAULT_ABBREV;
 -      revs->ignore_merges = -1;
        revs->simplify_history = 1;
        revs->pruning.repo = r;
        revs->pruning.flags.recursive = 1;
@@@ -2343,8 -2341,34 +2343,8 @@@ static int handle_revision_opt(struct r
                revs->diff = 1;
                revs->diffopt.flags.recursive = 1;
                revs->diffopt.flags.tree_in_recursive = 1;
 -      } else if (!strcmp(arg, "-m")) {
 -              /*
 -               * To "diff-index", "-m" means "match missing", and to the "log"
 -               * family of commands, it means "show full diff for merges". Set
 -               * both fields appropriately.
 -               */
 -              revs->ignore_merges = 0;
 -              revs->match_missing = 1;
 -      } else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) {
 -              if (!strcmp(optarg, "off")) {
 -                      revs->ignore_merges = 1;
 -              } else {
 -                      die(_("unknown value for --diff-merges: %s"), optarg);
 -              }
 +      } else if ((argcount = diff_merges_parse_opts(revs, argv))) {
                return argcount;
 -      } else if (!strcmp(arg, "--no-diff-merges")) {
 -              revs->ignore_merges = 1;
 -      } else if (!strcmp(arg, "-c")) {
 -              revs->diff = 1;
 -              revs->dense_combined_merges = 0;
 -              revs->combine_merges = 1;
 -      } else if (!strcmp(arg, "--combined-all-paths")) {
 -              revs->diff = 1;
 -              revs->combined_all_paths = 1;
 -      } else if (!strcmp(arg, "--cc")) {
 -              revs->diff = 1;
 -              revs->dense_combined_merges = 1;
 -              revs->combine_merges = 1;
        } else if (!strcmp(arg, "-v")) {
                revs->verbose_header = 1;
        } else if (!strcmp(arg, "--pretty")) {
        } else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
                add_message_grep(revs, optarg);
                return argcount;
 -      } else if (!strcmp(arg, "--grep-debug")) {
 -              revs->grep_filter.debug = 1;
        } else if (!strcmp(arg, "--basic-regexp")) {
                revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_BRE;
        } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
@@@ -2839,8 -2865,12 +2839,8 @@@ int setup_revisions(int argc, const cha
                        copy_pathspec(&revs->diffopt.pathspec,
                                      &revs->prune_data);
        }
 -      if (revs->combine_merges && revs->ignore_merges < 0)
 -              revs->ignore_merges = 0;
 -      if (revs->ignore_merges < 0)
 -              revs->ignore_merges = 1;
 -      if (revs->combined_all_paths && !revs->combine_merges)
 -              die("--combined-all-paths makes no sense without -c or --cc");
 +
 +      diff_merges_setup_revs(revs);
  
        revs->diffopt.abbrev = revs->abbrev;
  
@@@ -3270,7 -3300,7 +3270,7 @@@ define_commit_slab(indegree_slab, int)
  define_commit_slab(author_date_slab, timestamp_t);
  
  struct topo_walk_info {
-       uint32_t min_generation;
+       timestamp_t min_generation;
        struct prio_queue explore_queue;
        struct prio_queue indegree_queue;
        struct prio_queue topo_queue;
        struct author_date_slab author_date;
  };
  
 +static int topo_walk_atexit_registered;
 +static unsigned int count_explore_walked;
 +static unsigned int count_indegree_walked;
 +static unsigned int count_topo_walked;
 +
 +static void trace2_topo_walk_statistics_atexit(void)
 +{
 +      struct json_writer jw = JSON_WRITER_INIT;
 +
 +      jw_object_begin(&jw, 0);
 +      jw_object_intmax(&jw, "count_explore_walked", count_explore_walked);
 +      jw_object_intmax(&jw, "count_indegree_walked", count_indegree_walked);
 +      jw_object_intmax(&jw, "count_topo_walked", count_topo_walked);
 +      jw_end(&jw);
 +
 +      trace2_data_json("topo_walk", the_repository, "statistics", &jw);
 +
 +      jw_release(&jw);
 +}
 +
  static inline void test_flag_and_insert(struct prio_queue *q, struct commit *c, int flag)
  {
        if (c->object.flags & flag)
@@@ -3319,8 -3329,6 +3319,8 @@@ static void explore_walk_step(struct re
        if (repo_parse_commit_gently(revs->repo, c, 1) < 0)
                return;
  
 +      count_explore_walked++;
 +
        if (revs->sort_order == REV_SORT_BY_AUTHOR_DATE)
                record_author_date(&info->author_date, c);
  
  }
  
  static void explore_to_depth(struct rev_info *revs,
-                            uint32_t gen_cutoff)
+                            timestamp_t gen_cutoff)
  {
        struct topo_walk_info *info = revs->topo_walk_info;
        struct commit *c;
@@@ -3359,14 -3367,15 +3359,17 @@@ static void indegree_walk_step(struct r
        if (repo_parse_commit_gently(revs->repo, c, 1) < 0)
                return;
  
 +      count_indegree_walked++;
 +
        explore_to_depth(revs, commit_graph_generation(c));
  
        for (p = c->parents; p; p = p->next) {
                struct commit *parent = p->item;
                int *pi = indegree_slab_at(&info->indegree, parent);
  
+               if (repo_parse_commit_gently(revs->repo, parent, 1) < 0)
+                       return;
                if (*pi)
                        (*pi)++;
                else
  }
  
  static void compute_indegrees_to_depth(struct rev_info *revs,
-                                      uint32_t gen_cutoff)
+                                      timestamp_t gen_cutoff)
  {
        struct topo_walk_info *info = revs->topo_walk_info;
        struct commit *c;
@@@ -3438,7 -3447,7 +3441,7 @@@ static void init_topo_walk(struct rev_i
        info->min_generation = GENERATION_NUMBER_INFINITY;
        for (list = revs->commits; list; list = list->next) {
                struct commit *c = list->item;
-               uint32_t generation;
+               timestamp_t generation;
  
                if (repo_parse_commit_gently(revs->repo, c, 1))
                        continue;
         */
        if (revs->sort_order == REV_SORT_IN_GRAPH_ORDER)
                prio_queue_reverse(&info->topo_queue);
 +
 +      if (trace2_is_enabled() && !topo_walk_atexit_registered) {
 +              atexit(trace2_topo_walk_statistics_atexit);
 +              topo_walk_atexit_registered = 1;
 +      }
  }
  
  static struct commit *next_topo_commit(struct rev_info *revs)
@@@ -3501,12 -3505,10 +3504,12 @@@ static void expand_topo_walk(struct rev
                            oid_to_hex(&commit->object.oid));
        }
  
 +      count_topo_walked++;
 +
        for (p = commit->parents; p; p = p->next) {
                struct commit *parent = p->item;
                int *pi;
-               uint32_t generation;
+               timestamp_t generation;
  
                if (parent->object.flags & UNINTERESTING)
                        continue;
diff --combined t/README
index af706e0cd62d45759a65e9dc7830a627ca6c6604,8a121487279bf1b98618e1a1fcf79478acff4bcc..bc57006c041bc5113095a13700e5bf475ab515e8
+++ b/t/README
@@@ -358,6 -358,12 +358,6 @@@ whether this mode is active, and e.g. s
  refactor to deal with it. The "SYMLINKS" prerequisite is currently
  excluded as so much relies on it, but this might change in the future.
  
 -GIT_TEST_GETTEXT_POISON=<boolean> turns all strings marked for
 -translation into gibberish if true. Used for spotting those tests that
 -need to be marked with a C_LOCALE_OUTPUT prerequisite when adding more
 -strings for translation. See "Testing marked strings" in po/README for
 -details.
 -
  GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole
  test suite. Accept any boolean values that are accepted by git-config.
  
@@@ -387,6 -393,9 +387,9 @@@ GIT_TEST_COMMIT_GRAPH=<boolean>, when t
  be written after every 'git commit' command, and overrides the
  'core.commitGraph' setting to true.
  
+ GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the
+ commit-graph to be written without generation data chunk.
  GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=<boolean>, when true, forces
  commit-graph write to compute and write changed path Bloom filters for
  every 'git commit-graph write', as if the `--changed-paths` option was
@@@ -433,9 -442,6 +436,9 @@@ GIT_TEST_DEFAULT_HASH=<hash-algo> speci
  use in the test scripts. Recognized values for <hash-algo> are "sha1"
  and "sha256".
  
 +GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the
 +'pack.writeReverseIndex' setting.
 +
  Naming Tests
  ------------
  
@@@ -1101,6 -1107,18 +1104,6 @@@ use these, and "test_set_prereq" for ho
     Git was compiled with support for PCRE. Wrap any tests
     that use git-grep --perl-regexp or git-grep -P in these.
  
 - - LIBPCRE1
 -
 -   Git was compiled with PCRE v1 support via
 -   USE_LIBPCRE1=YesPlease. Wrap any PCRE using tests that for some
 -   reason need v1 of the PCRE library instead of v2 in these.
 -
 - - LIBPCRE2
 -
 -   Git was compiled with PCRE v2 support via
 -   USE_LIBPCRE2=YesPlease. Wrap any PCRE using tests that for some
 -   reason need v2 of the PCRE library instead of v1 in these.
 -
   - CASE_INSENSITIVE_FS
  
     Test is run on a case insensitive file system.
diff --combined t/t4216-log-bloom.sh
index 0f16c4b9d5270c3d3e710de0d58e483516188e2a,dbde0161882b280117fb014d35e031532d81102f..50f206db55043ff3481b4db4b23937b01bc43cab
@@@ -1,9 -1,6 +1,9 @@@
  #!/bin/sh
  
  test_description='git log for a path with Bloom filters'
 +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 +
  . ./test-lib.sh
  
  GIT_TEST_COMMIT_GRAPH=0
@@@ -24,7 -21,7 +24,7 @@@ test_expect_success 'setup test - repo
        test_commit c10 file_to_be_deleted &&
        git checkout -b side HEAD~4 &&
        test_commit side-1 file4 &&
 -      git checkout master &&
 +      git checkout main &&
        git merge side &&
        test_commit c11 file5 &&
        mv file5 file5_renamed &&
  '
  
  graph_read_expect () {
-       NUM_CHUNKS=5
+       NUM_CHUNKS=6
        cat >expect <<- EOF
        header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0
        num_commits: $1
-       chunks: oid_fanout oid_lookup commit_metadata bloom_indexes bloom_data
+       chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data
        EOF
        test-tool read-graph >actual &&
        test_cmp expect actual
@@@ -97,7 -94,7 +97,7 @@@ d
                      "--topo-order" \
                      "--date-order" \
                      "--author-date-order" \
 -                    "--ancestry-path side..master"
 +                    "--ancestry-path side..main"
        do
                test_expect_success "git log option: $option for path: $path" '
                        test_bloom_filters_used "$option -- $path" &&
index c7ab7048f589ec506888135960138dd4c11ca282,86f74ae584797ec128b7a2c24796571a12069d65..eaf48e941e2a3934fde54ac9bdc22f96a7a035cd
@@@ -1,9 -1,6 +1,9 @@@
  #!/bin/sh
  
  test_description='Test merge without common ancestors'
 +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 +
  . ./test-lib.sh
  
  # This scenario is based on a real-world repository of Shawn Pearce.
@@@ -18,15 -15,17 +18,17 @@@ GIT_COMMITTER_DATE="2006-12-12 23:28:0
  export GIT_COMMITTER_DATE
  
  test_expect_success 'setup tests' '
+       GIT_TEST_COMMIT_GRAPH=0 &&
+       export GIT_TEST_COMMIT_GRAPH &&
        echo 1 >a1 &&
        git add a1 &&
        GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
  
 -      git checkout -b A master &&
 +      git checkout -b A main &&
        echo A >a1 &&
        GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 &&
  
 -      git checkout -b B master &&
 +      git checkout -b B main &&
        echo B >a1 &&
        GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 &&
  
@@@ -69,7 -68,7 +71,7 @@@
  '
  
  test_expect_success 'combined merge conflicts' '
-       test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G
+       test_must_fail git merge -m final G
  '
  
  test_expect_success 'result contains a conflict' '
@@@ -85,6 -84,7 +87,7 @@@
  '
  
  test_expect_success 'virtual trees were processed' '
+       # TODO: fragile test, relies on ambigious merge-base resolution
        git ls-files --stage >out &&
  
        cat >expect <<-EOF &&
diff --combined t/test-lib-functions.sh
index 07976af81c81120af0c1aaf9e39f6c3b0f33a2bf,3ad712c3accd64b270cb69d4e08c57e45561f5ba..05dc2cc6be8eed02f7aa5e49c94117287085ae4f
@@@ -178,28 -178,19 +178,28 @@@ debug () 
        GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7
  }
  
 -# Call test_commit with the arguments
 -# [-C <directory>] <message> [<file> [<contents> [<tag>]]]"
 +# Usage: test_commit [options] <message> [<file> [<contents> [<tag>]]]
 +#   -C <dir>:
 +#     Run all git commands in directory <dir>
 +#   --notick
 +#     Do not call test_tick before making a commit
 +#   --append
 +#     Use "echo >>" instead of "echo >" when writing "<contents>" to
 +#     "<file>"
 +#   --signoff
 +#     Invoke "git commit" with --signoff
 +#   --author <author>
 +#     Invoke "git commit" with --author <author>
  #
  # This will commit a file with the given contents and the given commit
  # message, and tag the resulting commit with the given tag name.
  #
  # <file>, <contents>, and <tag> all default to <message>.
 -#
 -# If the first argument is "-C", the second argument is used as a path for
 -# the git invocations.
  
  test_commit () {
        notick= &&
 +      append= &&
 +      author= &&
        signoff= &&
        indir= &&
        while test $# != 0
                --notick)
                        notick=yes
                        ;;
 +              --append)
 +                      append=yes
 +                      ;;
 +              --author)
 +                      author="$2"
 +                      shift
 +                      ;;
                --signoff)
                        signoff="$1"
                        ;;
+               --date)
+                       notick=yes
+                       GIT_COMMITTER_DATE="$2"
+                       GIT_AUTHOR_DATE="$2"
+                       shift
+                       ;;
                -C)
                        indir="$2"
                        shift
        done &&
        indir=${indir:+"$indir"/} &&
        file=${2:-"$1.t"} &&
 -      echo "${3-$1}" > "$indir$file" &&
 +      if test -n "$append"
 +      then
 +              echo "${3-$1}" >>"$indir$file"
 +      else
 +              echo "${3-$1}" >"$indir$file"
 +      fi &&
        git ${indir:+ -C "$indir"} add "$file" &&
        if test -z "$notick"
        then
                test_tick
        fi &&
 -      git ${indir:+ -C "$indir"} commit $signoff -m "$1" &&
 +      git ${indir:+ -C "$indir"} commit \
 +          ${author:+ --author "$author"} \
 +          $signoff -m "$1" &&
        git ${indir:+ -C "$indir"} tag "${4:-$1}"
  }
  
@@@ -390,14 -373,9 +396,14 @@@ test_chmod () 
        git update-index --add "--chmod=$@"
  }
  
 -# Get the modebits from a file or directory.
 +# Get the modebits from a file or directory, ignoring the setgid bit (g+s).
 +# This bit is inherited by subdirectories at their creation. So we remove it
 +# from the returning string to prevent callers from having to worry about the
 +# state of the bit in the test directory.
 +#
  test_modebits () {
 -      ls -ld "$1" | sed -e 's|^\(..........\).*|\1|'
 +      ls -ld "$1" | sed -e 's|^\(..........\).*|\1|' \
 +                        -e 's|^\(......\)S|\1-|' -e 's|^\(......\)s|\1x|'
  }
  
  # Unset a configuration variable, but don't fail if it doesn't exist.
@@@ -1016,16 -994,19 +1022,16 @@@ test_cmp_bin () 
        cmp "$@"
  }
  
 -# Use this instead of test_cmp to compare files that contain expected and
 -# actual output from git commands that can be translated.  When running
 -# under GIT_TEST_GETTEXT_POISON this pretends that the command produced expected
 -# results.
 +# Wrapper for test_cmp which used to be used for
 +# GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other
 +# in-flight changes. Should not be used and will be removed soon.
  test_i18ncmp () {
 -      ! test_have_prereq C_LOCALE_OUTPUT || test_cmp "$@"
 +      test_cmp "$@"
  }
  
 -# Use this instead of "grep expected-string actual" to see if the
 -# output from a git command that can be translated either contains an
 -# expected string, or does not contain an unwanted one.  When running
 -# under GIT_TEST_GETTEXT_POISON this pretends that the command produced expected
 -# results.
 +# Wrapper for grep which used to be used for
 +# GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other
 +# in-flight changes. Should not be used and will be removed soon.
  test_i18ngrep () {
        eval "last_arg=\${$#}"
  
                BUG "too few parameters to test_i18ngrep"
        fi
  
 -      if test_have_prereq !C_LOCALE_OUTPUT
 -      then
 -              # pretend success
 -              return 0
 -      fi
 -
        if test "x!" = "x$1"
        then
                shift
@@@ -1674,45 -1661,3 +1680,45 @@@ test_subcommand () 
                grep "\[$expr\]"
        fi
  }
 +
 +# Check that the given command was invoked as part of the
 +# trace2-format trace on stdin.
 +#
 +#     test_region [!] <category> <label> git <command> <args>...
 +#
 +# For example, to look for trace2_region_enter("index", "do_read_index", repo)
 +# in an invocation of "git checkout HEAD~1", run
 +#
 +#     GIT_TRACE2_EVENT="$(pwd)/trace.txt" GIT_TRACE2_EVENT_NESTING=10 \
 +#             git checkout HEAD~1 &&
 +#     test_region index do_read_index <trace.txt
 +#
 +# If the first parameter passed is !, this instead checks that
 +# the given region was not entered.
 +#
 +test_region () {
 +      local expect_exit=0
 +      if test "$1" = "!"
 +      then
 +              expect_exit=1
 +              shift
 +      fi
 +
 +      grep -e '"region_enter".*"category":"'"$1"'","label":"'"$2"\" "$3"
 +      exitcode=$?
 +
 +      if test $exitcode != $expect_exit
 +      then
 +              return 1
 +      fi
 +
 +      grep -e '"region_leave".*"category":"'"$1"'","label":"'"$2"\" "$3"
 +      exitcode=$?
 +
 +      if test $exitcode != $expect_exit
 +      then
 +              return 1
 +      fi
 +
 +      return 0
 +}
diff --combined upload-pack.c
index abadc930132dcbc3cd1db74c7b5b6630e2fa4f47,b87607e0dd41f47fa9a3a6218c1fcf300e19a96f..e19583ae0fbebbdf6f9d6f4c89580332a8a16307
@@@ -305,7 -305,14 +305,7 @@@ static void create_pack_file(struct upl
        if (pack_data->filter_options.choice) {
                const char *spec =
                        expand_list_objects_filter_spec(&pack_data->filter_options);
 -              if (pack_objects.use_shell) {
 -                      struct strbuf buf = STRBUF_INIT;
 -                      sq_quote_buf(&buf, spec);
 -                      strvec_pushf(&pack_objects.args, "--filter=%s", buf.buf);
 -                      strbuf_release(&buf);
 -              } else {
 -                      strvec_pushf(&pack_objects.args, "--filter=%s", spec);
 -              }
 +              strvec_pushf(&pack_objects.args, "--filter=%s", spec);
        }
        if (uri_protocols) {
                for (i = 0; i < uri_protocols->nr; i++)
@@@ -493,7 -500,7 +493,7 @@@ static int got_oid(struct upload_pack_d
  
  static int ok_to_give_up(struct upload_pack_data *data)
  {
-       uint32_t min_generation = GENERATION_NUMBER_ZERO;
+       timestamp_t min_generation = GENERATION_NUMBER_ZERO;
  
        if (!data->have_obj.nr)
                return 0;
@@@ -1225,7 -1232,7 +1225,7 @@@ static int send_ref(const char *refname
                packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), refname_nons);
        }
        capabilities = NULL;
 -      if (!peel_ref(refname, &peeled))
 +      if (!peel_iterated_oid(oid, &peeled))
                packet_write_fmt(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
        return 0;
  }