#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"
#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);
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);
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;
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;
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;
}
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;
{
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)
{
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);
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;
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;
_("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++;
}
}
}
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);
} 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"));
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;
}
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)
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);
#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"
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)
{
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];
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;
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)
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)
#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;
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);
/* 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 */
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);
#include "tree.h"
#include "commit.h"
#include "diff.h"
+#include "diff-merges.h"
#include "refs.h"
#include "revision.h"
#include "repository.h"
/*
* 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);
revs->repo = r;
revs->abbrev = DEFAULT_ABBREV;
- revs->ignore_merges = -1;
revs->simplify_history = 1;
revs->pruning.repo = r;
revs->pruning.flags.recursive = 1;
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")) {
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;
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)
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;
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;
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)
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;
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.
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
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
------------
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.
#!/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
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
"--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" &&
#!/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.
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 &&
'
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' '
'
test_expect_success 'virtual trees were processed' '
+ # TODO: fragile test, relies on ambigious merge-base resolution
git ls-files --stage >out &&
cat >expect <<-EOF &&
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}"
}
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.
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
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
+}
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++)
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;
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;
}