mirror of
https://github.com/git/git.git
synced 2024-11-27 03:53:55 +08:00
Merge branch 'ds/reachable'
The code for computing history reachability has been shuffled, obtained a bunch of new tests to cover them, and then being improved. * ds/reachable: commit-reach: correct accidental #include of C file commit-reach: use can_all_from_reach commit-reach: make can_all_from_reach... linear commit-reach: replace ref_newer logic test-reach: test commit_contains test-reach: test can_all_from_reach_with_flags test-reach: test reduce_heads test-reach: test get_merge_bases_many test-reach: test is_descendant_of test-reach: test in_merge_bases test-reach: create new test tool for ref_newer commit-reach: move can_all_from_reach_with_flags upload-pack: generalize commit date cutoff upload-pack: refactor ok_to_give_up() upload-pack: make reachable() more generic commit-reach: move commit_contains from ref-filter commit-reach: move ref_newer from remote.c commit.h: remove method declarations commit-reach: move walk methods from commit.c
This commit is contained in:
commit
1b7a91da71
2
Makefile
2
Makefile
@ -722,6 +722,7 @@ TEST_BUILTINS_OBJS += test-mktemp.o
|
||||
TEST_BUILTINS_OBJS += test-online-cpus.o
|
||||
TEST_BUILTINS_OBJS += test-path-utils.o
|
||||
TEST_BUILTINS_OBJS += test-prio-queue.o
|
||||
TEST_BUILTINS_OBJS += test-reach.o
|
||||
TEST_BUILTINS_OBJS += test-read-cache.o
|
||||
TEST_BUILTINS_OBJS += test-read-midx.o
|
||||
TEST_BUILTINS_OBJS += test-ref-store.o
|
||||
@ -836,6 +837,7 @@ LIB_OBJS += column.o
|
||||
LIB_OBJS += combine-diff.o
|
||||
LIB_OBJS += commit.o
|
||||
LIB_OBJS += commit-graph.o
|
||||
LIB_OBJS += commit-reach.o
|
||||
LIB_OBJS += compat/obstack.o
|
||||
LIB_OBJS += compat/terminal.o
|
||||
LIB_OBJS += config.o
|
||||
|
1
bisect.c
1
bisect.c
@ -13,6 +13,7 @@
|
||||
#include "sha1-array.h"
|
||||
#include "argv-array.h"
|
||||
#include "commit-slab.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static struct oid_array good_revs;
|
||||
static struct oid_array skipped_revs;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "ref-filter.h"
|
||||
#include "worktree.h"
|
||||
#include "help.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static const char * const builtin_branch_usage[] = {
|
||||
N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "sequencer.h"
|
||||
#include "mailmap.h"
|
||||
#include "help.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static const char * const builtin_commit_usage[] = {
|
||||
N_("git commit [<options>] [--] <pathspec>..."),
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "utf8.h"
|
||||
#include "packfile.h"
|
||||
#include "list-objects-filter-options.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static const char * const builtin_fetch_usage[] = {
|
||||
N_("git fetch [<options>] [<repository> [<refspec>...]]"),
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "fmt-merge-msg.h"
|
||||
#include "gpg-interface.h"
|
||||
#include "repository.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static const char * const fmt_merge_msg_usage[] = {
|
||||
N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"),
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "progress.h"
|
||||
#include "commit-slab.h"
|
||||
#include "repository.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
#define MAIL_DEFAULT_WRAP 72
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "revision.h"
|
||||
#include "parse-options.h"
|
||||
#include "repository.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static int show_merge_base(struct commit **rev, int rev_nr, int show_all)
|
||||
{
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "packfile.h"
|
||||
#include "tag.h"
|
||||
#include "alias.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
#define DEFAULT_TWOHEAD (1<<0)
|
||||
#define DEFAULT_OCTOPUS (1<<1)
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "tempfile.h"
|
||||
#include "lockfile.h"
|
||||
#include "wt-status.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
enum rebase_type {
|
||||
REBASE_INVALID = -1,
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "packfile.h"
|
||||
#include "object-store.h"
|
||||
#include "protocol.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static const char * const receive_pack_usage[] = {
|
||||
N_("git receive-pack <git-dir>"),
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "refspec.h"
|
||||
#include "object-store.h"
|
||||
#include "argv-array.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static const char * const builtin_remote_usage[] = {
|
||||
N_("git remote [-v | --verbose]"),
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "revision.h"
|
||||
#include "split-index.h"
|
||||
#include "submodule.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
#define DO_REVS 1
|
||||
#define DO_NOREV 2
|
||||
|
@ -233,6 +233,24 @@ static int prepare_commit_graph(struct repository *r)
|
||||
return !!r->objects->commit_graph;
|
||||
}
|
||||
|
||||
int generation_numbers_enabled(struct repository *r)
|
||||
{
|
||||
uint32_t first_generation;
|
||||
struct commit_graph *g;
|
||||
if (!prepare_commit_graph(r))
|
||||
return 0;
|
||||
|
||||
g = r->objects->commit_graph;
|
||||
|
||||
if (!g->num_commits)
|
||||
return 0;
|
||||
|
||||
first_generation = get_be32(g->chunk_commit_data +
|
||||
g->hash_len + 8) >> 2;
|
||||
|
||||
return !!first_generation;
|
||||
}
|
||||
|
||||
static void close_commit_graph(void)
|
||||
{
|
||||
free_commit_graph(the_repository->objects->commit_graph);
|
||||
|
@ -52,6 +52,12 @@ struct commit_graph {
|
||||
|
||||
struct commit_graph *load_commit_graph_one(const char *graph_file);
|
||||
|
||||
/*
|
||||
* Return 1 if and only if the repository has a commit-graph
|
||||
* file and generation numbers are computed in that file.
|
||||
*/
|
||||
int generation_numbers_enabled(struct repository *r);
|
||||
|
||||
void write_commit_graph_reachable(const char *obj_dir, int append);
|
||||
void write_commit_graph(const char *obj_dir,
|
||||
struct string_list *pack_indexes,
|
||||
|
665
commit-reach.c
Normal file
665
commit-reach.c
Normal file
@ -0,0 +1,665 @@
|
||||
#include "cache.h"
|
||||
#include "commit.h"
|
||||
#include "commit-graph.h"
|
||||
#include "decorate.h"
|
||||
#include "prio-queue.h"
|
||||
#include "tree.h"
|
||||
#include "ref-filter.h"
|
||||
#include "revision.h"
|
||||
#include "tag.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
/* Remember to update object flag allocation in object.h */
|
||||
#define REACHABLE (1u<<15)
|
||||
#define PARENT1 (1u<<16)
|
||||
#define PARENT2 (1u<<17)
|
||||
#define STALE (1u<<18)
|
||||
#define RESULT (1u<<19)
|
||||
|
||||
static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
|
||||
|
||||
static int queue_has_nonstale(struct prio_queue *queue)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < queue->nr; i++) {
|
||||
struct commit *commit = queue->array[i].data;
|
||||
if (!(commit->object.flags & STALE))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* all input commits in one and twos[] must have been parsed! */
|
||||
static struct commit_list *paint_down_to_common(struct commit *one, int n,
|
||||
struct commit **twos,
|
||||
int min_generation)
|
||||
{
|
||||
struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
|
||||
struct commit_list *result = NULL;
|
||||
int i;
|
||||
uint32_t last_gen = GENERATION_NUMBER_INFINITY;
|
||||
|
||||
if (!min_generation)
|
||||
queue.compare = compare_commits_by_commit_date;
|
||||
|
||||
one->object.flags |= PARENT1;
|
||||
if (!n) {
|
||||
commit_list_append(one, &result);
|
||||
return result;
|
||||
}
|
||||
prio_queue_put(&queue, one);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
twos[i]->object.flags |= PARENT2;
|
||||
prio_queue_put(&queue, twos[i]);
|
||||
}
|
||||
|
||||
while (queue_has_nonstale(&queue)) {
|
||||
struct commit *commit = prio_queue_get(&queue);
|
||||
struct commit_list *parents;
|
||||
int flags;
|
||||
|
||||
if (min_generation && commit->generation > last_gen)
|
||||
BUG("bad generation skip %8x > %8x at %s",
|
||||
commit->generation, last_gen,
|
||||
oid_to_hex(&commit->object.oid));
|
||||
last_gen = commit->generation;
|
||||
|
||||
if (commit->generation < min_generation)
|
||||
break;
|
||||
|
||||
flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
|
||||
if (flags == (PARENT1 | PARENT2)) {
|
||||
if (!(commit->object.flags & RESULT)) {
|
||||
commit->object.flags |= RESULT;
|
||||
commit_list_insert_by_date(commit, &result);
|
||||
}
|
||||
/* Mark parents of a found merge stale */
|
||||
flags |= STALE;
|
||||
}
|
||||
parents = commit->parents;
|
||||
while (parents) {
|
||||
struct commit *p = parents->item;
|
||||
parents = parents->next;
|
||||
if ((p->object.flags & flags) == flags)
|
||||
continue;
|
||||
if (parse_commit(p))
|
||||
return NULL;
|
||||
p->object.flags |= flags;
|
||||
prio_queue_put(&queue, p);
|
||||
}
|
||||
}
|
||||
|
||||
clear_prio_queue(&queue);
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
|
||||
{
|
||||
struct commit_list *list = NULL;
|
||||
struct commit_list *result = NULL;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (one == twos[i])
|
||||
/*
|
||||
* We do not mark this even with RESULT so we do not
|
||||
* have to clean it up.
|
||||
*/
|
||||
return commit_list_insert(one, &result);
|
||||
}
|
||||
|
||||
if (parse_commit(one))
|
||||
return NULL;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (parse_commit(twos[i]))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
list = paint_down_to_common(one, n, twos, 0);
|
||||
|
||||
while (list) {
|
||||
struct commit *commit = pop_commit(&list);
|
||||
if (!(commit->object.flags & STALE))
|
||||
commit_list_insert_by_date(commit, &result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct commit_list *get_octopus_merge_bases(struct commit_list *in)
|
||||
{
|
||||
struct commit_list *i, *j, *k, *ret = NULL;
|
||||
|
||||
if (!in)
|
||||
return ret;
|
||||
|
||||
commit_list_insert(in->item, &ret);
|
||||
|
||||
for (i = in->next; i; i = i->next) {
|
||||
struct commit_list *new_commits = NULL, *end = NULL;
|
||||
|
||||
for (j = ret; j; j = j->next) {
|
||||
struct commit_list *bases;
|
||||
bases = get_merge_bases(i->item, j->item);
|
||||
if (!new_commits)
|
||||
new_commits = bases;
|
||||
else
|
||||
end->next = bases;
|
||||
for (k = bases; k; k = k->next)
|
||||
end = k;
|
||||
}
|
||||
ret = new_commits;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int remove_redundant(struct commit **array, int cnt)
|
||||
{
|
||||
/*
|
||||
* Some commit in the array may be an ancestor of
|
||||
* another commit. Move such commit to the end of
|
||||
* the array, and return the number of commits that
|
||||
* are independent from each other.
|
||||
*/
|
||||
struct commit **work;
|
||||
unsigned char *redundant;
|
||||
int *filled_index;
|
||||
int i, j, filled;
|
||||
|
||||
work = xcalloc(cnt, sizeof(*work));
|
||||
redundant = xcalloc(cnt, 1);
|
||||
ALLOC_ARRAY(filled_index, cnt - 1);
|
||||
|
||||
for (i = 0; i < cnt; i++)
|
||||
parse_commit(array[i]);
|
||||
for (i = 0; i < cnt; i++) {
|
||||
struct commit_list *common;
|
||||
uint32_t min_generation = array[i]->generation;
|
||||
|
||||
if (redundant[i])
|
||||
continue;
|
||||
for (j = filled = 0; j < cnt; j++) {
|
||||
if (i == j || redundant[j])
|
||||
continue;
|
||||
filled_index[filled] = j;
|
||||
work[filled++] = array[j];
|
||||
|
||||
if (array[j]->generation < min_generation)
|
||||
min_generation = array[j]->generation;
|
||||
}
|
||||
common = paint_down_to_common(array[i], filled, work,
|
||||
min_generation);
|
||||
if (array[i]->object.flags & PARENT2)
|
||||
redundant[i] = 1;
|
||||
for (j = 0; j < filled; j++)
|
||||
if (work[j]->object.flags & PARENT1)
|
||||
redundant[filled_index[j]] = 1;
|
||||
clear_commit_marks(array[i], all_flags);
|
||||
clear_commit_marks_many(filled, work, all_flags);
|
||||
free_commit_list(common);
|
||||
}
|
||||
|
||||
/* Now collect the result */
|
||||
COPY_ARRAY(work, array, cnt);
|
||||
for (i = filled = 0; i < cnt; i++)
|
||||
if (!redundant[i])
|
||||
array[filled++] = work[i];
|
||||
for (j = filled, i = 0; i < cnt; i++)
|
||||
if (redundant[i])
|
||||
array[j++] = work[i];
|
||||
free(work);
|
||||
free(redundant);
|
||||
free(filled_index);
|
||||
return filled;
|
||||
}
|
||||
|
||||
static struct commit_list *get_merge_bases_many_0(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos,
|
||||
int cleanup)
|
||||
{
|
||||
struct commit_list *list;
|
||||
struct commit **rslt;
|
||||
struct commit_list *result;
|
||||
int cnt, i;
|
||||
|
||||
result = merge_bases_many(one, n, twos);
|
||||
for (i = 0; i < n; i++) {
|
||||
if (one == twos[i])
|
||||
return result;
|
||||
}
|
||||
if (!result || !result->next) {
|
||||
if (cleanup) {
|
||||
clear_commit_marks(one, all_flags);
|
||||
clear_commit_marks_many(n, twos, all_flags);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* There are more than one */
|
||||
cnt = commit_list_count(result);
|
||||
rslt = xcalloc(cnt, sizeof(*rslt));
|
||||
for (list = result, i = 0; list; list = list->next)
|
||||
rslt[i++] = list->item;
|
||||
free_commit_list(result);
|
||||
|
||||
clear_commit_marks(one, all_flags);
|
||||
clear_commit_marks_many(n, twos, all_flags);
|
||||
|
||||
cnt = remove_redundant(rslt, cnt);
|
||||
result = NULL;
|
||||
for (i = 0; i < cnt; i++)
|
||||
commit_list_insert_by_date(rslt[i], &result);
|
||||
free(rslt);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct commit_list *get_merge_bases_many(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos)
|
||||
{
|
||||
return get_merge_bases_many_0(one, n, twos, 1);
|
||||
}
|
||||
|
||||
struct commit_list *get_merge_bases_many_dirty(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos)
|
||||
{
|
||||
return get_merge_bases_many_0(one, n, twos, 0);
|
||||
}
|
||||
|
||||
struct commit_list *get_merge_bases(struct commit *one, struct commit *two)
|
||||
{
|
||||
return get_merge_bases_many_0(one, 1, &two, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" a descendant of one of the elements on the "with_commit" list?
|
||||
*/
|
||||
int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
|
||||
{
|
||||
if (!with_commit)
|
||||
return 1;
|
||||
|
||||
if (generation_numbers_enabled(the_repository)) {
|
||||
struct commit_list *from_list = NULL;
|
||||
int result;
|
||||
commit_list_insert(commit, &from_list);
|
||||
result = can_all_from_reach(from_list, with_commit, 0);
|
||||
free_commit_list(from_list);
|
||||
return result;
|
||||
} else {
|
||||
while (with_commit) {
|
||||
struct commit *other;
|
||||
|
||||
other = with_commit->item;
|
||||
with_commit = with_commit->next;
|
||||
if (in_merge_bases(other, commit))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" an ancestor of one of the "references"?
|
||||
*/
|
||||
int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
|
||||
{
|
||||
struct commit_list *bases;
|
||||
int ret = 0, i;
|
||||
uint32_t min_generation = GENERATION_NUMBER_INFINITY;
|
||||
|
||||
if (parse_commit(commit))
|
||||
return ret;
|
||||
for (i = 0; i < nr_reference; i++) {
|
||||
if (parse_commit(reference[i]))
|
||||
return ret;
|
||||
if (reference[i]->generation < min_generation)
|
||||
min_generation = reference[i]->generation;
|
||||
}
|
||||
|
||||
if (commit->generation > min_generation)
|
||||
return ret;
|
||||
|
||||
bases = paint_down_to_common(commit, nr_reference, reference, commit->generation);
|
||||
if (commit->object.flags & PARENT2)
|
||||
ret = 1;
|
||||
clear_commit_marks(commit, all_flags);
|
||||
clear_commit_marks_many(nr_reference, reference, all_flags);
|
||||
free_commit_list(bases);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" an ancestor of (i.e. reachable from) the "reference"?
|
||||
*/
|
||||
int in_merge_bases(struct commit *commit, struct commit *reference)
|
||||
{
|
||||
return in_merge_bases_many(commit, 1, &reference);
|
||||
}
|
||||
|
||||
struct commit_list *reduce_heads(struct commit_list *heads)
|
||||
{
|
||||
struct commit_list *p;
|
||||
struct commit_list *result = NULL, **tail = &result;
|
||||
struct commit **array;
|
||||
int num_head, i;
|
||||
|
||||
if (!heads)
|
||||
return NULL;
|
||||
|
||||
/* Uniquify */
|
||||
for (p = heads; p; p = p->next)
|
||||
p->item->object.flags &= ~STALE;
|
||||
for (p = heads, num_head = 0; p; p = p->next) {
|
||||
if (p->item->object.flags & STALE)
|
||||
continue;
|
||||
p->item->object.flags |= STALE;
|
||||
num_head++;
|
||||
}
|
||||
array = xcalloc(num_head, sizeof(*array));
|
||||
for (p = heads, i = 0; p; p = p->next) {
|
||||
if (p->item->object.flags & STALE) {
|
||||
array[i++] = p->item;
|
||||
p->item->object.flags &= ~STALE;
|
||||
}
|
||||
}
|
||||
num_head = remove_redundant(array, num_head);
|
||||
for (i = 0; i < num_head; i++)
|
||||
tail = &commit_list_insert(array[i], tail)->next;
|
||||
free(array);
|
||||
return result;
|
||||
}
|
||||
|
||||
void reduce_heads_replace(struct commit_list **heads)
|
||||
{
|
||||
struct commit_list *result = reduce_heads(*heads);
|
||||
free_commit_list(*heads);
|
||||
*heads = result;
|
||||
}
|
||||
|
||||
int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)
|
||||
{
|
||||
struct object *o;
|
||||
struct commit *old_commit, *new_commit;
|
||||
struct commit_list *old_commit_list = NULL;
|
||||
|
||||
/*
|
||||
* Both new_commit and old_commit must be commit-ish and new_commit is descendant of
|
||||
* old_commit. Otherwise we require --force.
|
||||
*/
|
||||
o = deref_tag(the_repository, parse_object(the_repository, old_oid),
|
||||
NULL, 0);
|
||||
if (!o || o->type != OBJ_COMMIT)
|
||||
return 0;
|
||||
old_commit = (struct commit *) o;
|
||||
|
||||
o = deref_tag(the_repository, parse_object(the_repository, new_oid),
|
||||
NULL, 0);
|
||||
if (!o || o->type != OBJ_COMMIT)
|
||||
return 0;
|
||||
new_commit = (struct commit *) o;
|
||||
|
||||
if (parse_commit(new_commit) < 0)
|
||||
return 0;
|
||||
|
||||
commit_list_insert(old_commit, &old_commit_list);
|
||||
return is_descendant_of(new_commit, old_commit_list);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mimicking the real stack, this stack lives on the heap, avoiding stack
|
||||
* overflows.
|
||||
*
|
||||
* At each recursion step, the stack items points to the commits whose
|
||||
* ancestors are to be inspected.
|
||||
*/
|
||||
struct contains_stack {
|
||||
int nr, alloc;
|
||||
struct contains_stack_entry {
|
||||
struct commit *commit;
|
||||
struct commit_list *parents;
|
||||
} *contains_stack;
|
||||
};
|
||||
|
||||
static int in_commit_list(const struct commit_list *want, struct commit *c)
|
||||
{
|
||||
for (; want; want = want->next)
|
||||
if (!oidcmp(&want->item->object.oid, &c->object.oid))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test whether the candidate is contained in the list.
|
||||
* Do not recurse to find out, though, but return -1 if inconclusive.
|
||||
*/
|
||||
static enum contains_result contains_test(struct commit *candidate,
|
||||
const struct commit_list *want,
|
||||
struct contains_cache *cache,
|
||||
uint32_t cutoff)
|
||||
{
|
||||
enum contains_result *cached = contains_cache_at(cache, candidate);
|
||||
|
||||
/* If we already have the answer cached, return that. */
|
||||
if (*cached)
|
||||
return *cached;
|
||||
|
||||
/* or are we it? */
|
||||
if (in_commit_list(want, candidate)) {
|
||||
*cached = CONTAINS_YES;
|
||||
return CONTAINS_YES;
|
||||
}
|
||||
|
||||
/* Otherwise, we don't know; prepare to recurse */
|
||||
parse_commit_or_die(candidate);
|
||||
|
||||
if (candidate->generation < cutoff)
|
||||
return CONTAINS_NO;
|
||||
|
||||
return CONTAINS_UNKNOWN;
|
||||
}
|
||||
|
||||
static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
|
||||
{
|
||||
ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
|
||||
contains_stack->contains_stack[contains_stack->nr].commit = candidate;
|
||||
contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
|
||||
}
|
||||
|
||||
static enum contains_result contains_tag_algo(struct commit *candidate,
|
||||
const struct commit_list *want,
|
||||
struct contains_cache *cache)
|
||||
{
|
||||
struct contains_stack contains_stack = { 0, 0, NULL };
|
||||
enum contains_result result;
|
||||
uint32_t cutoff = GENERATION_NUMBER_INFINITY;
|
||||
const struct commit_list *p;
|
||||
|
||||
for (p = want; p; p = p->next) {
|
||||
struct commit *c = p->item;
|
||||
load_commit_graph_info(the_repository, c);
|
||||
if (c->generation < cutoff)
|
||||
cutoff = c->generation;
|
||||
}
|
||||
|
||||
result = contains_test(candidate, want, cache, cutoff);
|
||||
if (result != CONTAINS_UNKNOWN)
|
||||
return result;
|
||||
|
||||
push_to_contains_stack(candidate, &contains_stack);
|
||||
while (contains_stack.nr) {
|
||||
struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
|
||||
struct commit *commit = entry->commit;
|
||||
struct commit_list *parents = entry->parents;
|
||||
|
||||
if (!parents) {
|
||||
*contains_cache_at(cache, commit) = CONTAINS_NO;
|
||||
contains_stack.nr--;
|
||||
}
|
||||
/*
|
||||
* If we just popped the stack, parents->item has been marked,
|
||||
* therefore contains_test will return a meaningful yes/no.
|
||||
*/
|
||||
else switch (contains_test(parents->item, want, cache, cutoff)) {
|
||||
case CONTAINS_YES:
|
||||
*contains_cache_at(cache, commit) = CONTAINS_YES;
|
||||
contains_stack.nr--;
|
||||
break;
|
||||
case CONTAINS_NO:
|
||||
entry->parents = parents->next;
|
||||
break;
|
||||
case CONTAINS_UNKNOWN:
|
||||
push_to_contains_stack(parents->item, &contains_stack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(contains_stack.contains_stack);
|
||||
return contains_test(candidate, want, cache, cutoff);
|
||||
}
|
||||
|
||||
int commit_contains(struct ref_filter *filter, struct commit *commit,
|
||||
struct commit_list *list, struct contains_cache *cache)
|
||||
{
|
||||
if (filter->with_commit_tag_algo)
|
||||
return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
|
||||
return is_descendant_of(commit, list);
|
||||
}
|
||||
|
||||
static int compare_commits_by_gen(const void *_a, const void *_b)
|
||||
{
|
||||
const struct commit *a = (const struct commit *)_a;
|
||||
const struct commit *b = (const struct commit *)_b;
|
||||
|
||||
if (a->generation < b->generation)
|
||||
return -1;
|
||||
if (a->generation > b->generation)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int can_all_from_reach_with_flag(struct object_array *from,
|
||||
unsigned int with_flag,
|
||||
unsigned int assign_flag,
|
||||
time_t min_commit_date,
|
||||
uint32_t min_generation)
|
||||
{
|
||||
struct commit **list = NULL;
|
||||
int i;
|
||||
int result = 1;
|
||||
|
||||
ALLOC_ARRAY(list, from->nr);
|
||||
for (i = 0; i < from->nr; i++) {
|
||||
list[i] = (struct commit *)from->objects[i].item;
|
||||
|
||||
if (parse_commit(list[i]) ||
|
||||
list[i]->generation < min_generation)
|
||||
return 0;
|
||||
}
|
||||
|
||||
QSORT(list, from->nr, compare_commits_by_gen);
|
||||
|
||||
for (i = 0; i < from->nr; i++) {
|
||||
/* DFS from list[i] */
|
||||
struct commit_list *stack = NULL;
|
||||
|
||||
list[i]->object.flags |= assign_flag;
|
||||
commit_list_insert(list[i], &stack);
|
||||
|
||||
while (stack) {
|
||||
struct commit_list *parent;
|
||||
|
||||
if (stack->item->object.flags & with_flag) {
|
||||
pop_commit(&stack);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (parent = stack->item->parents; parent; parent = parent->next) {
|
||||
if (parent->item->object.flags & (with_flag | RESULT))
|
||||
stack->item->object.flags |= RESULT;
|
||||
|
||||
if (!(parent->item->object.flags & assign_flag)) {
|
||||
parent->item->object.flags |= assign_flag;
|
||||
|
||||
if (parse_commit(parent->item) ||
|
||||
parent->item->date < min_commit_date ||
|
||||
parent->item->generation < min_generation)
|
||||
continue;
|
||||
|
||||
commit_list_insert(parent->item, &stack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parent)
|
||||
pop_commit(&stack);
|
||||
}
|
||||
|
||||
if (!(list[i]->object.flags & (with_flag | RESULT))) {
|
||||
result = 0;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
for (i = 0; i < from->nr; i++) {
|
||||
clear_commit_marks(list[i], RESULT);
|
||||
clear_commit_marks(list[i], assign_flag);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int can_all_from_reach(struct commit_list *from, struct commit_list *to,
|
||||
int cutoff_by_min_date)
|
||||
{
|
||||
struct object_array from_objs = OBJECT_ARRAY_INIT;
|
||||
time_t min_commit_date = cutoff_by_min_date ? from->item->date : 0;
|
||||
struct commit_list *from_iter = from, *to_iter = to;
|
||||
int result;
|
||||
uint32_t min_generation = GENERATION_NUMBER_INFINITY;
|
||||
|
||||
while (from_iter) {
|
||||
add_object_array(&from_iter->item->object, NULL, &from_objs);
|
||||
|
||||
if (!parse_commit(from_iter->item)) {
|
||||
if (from_iter->item->date < min_commit_date)
|
||||
min_commit_date = from_iter->item->date;
|
||||
|
||||
if (from_iter->item->generation < min_generation)
|
||||
min_generation = from_iter->item->generation;
|
||||
}
|
||||
|
||||
from_iter = from_iter->next;
|
||||
}
|
||||
|
||||
while (to_iter) {
|
||||
if (!parse_commit(to_iter->item)) {
|
||||
if (to_iter->item->date < min_commit_date)
|
||||
min_commit_date = to_iter->item->date;
|
||||
|
||||
if (to_iter->item->generation < min_generation)
|
||||
min_generation = to_iter->item->generation;
|
||||
}
|
||||
|
||||
to_iter->item->object.flags |= PARENT2;
|
||||
|
||||
to_iter = to_iter->next;
|
||||
}
|
||||
|
||||
result = can_all_from_reach_with_flag(&from_objs, PARENT2, PARENT1,
|
||||
min_commit_date, min_generation);
|
||||
|
||||
while (from) {
|
||||
clear_commit_marks(from->item, PARENT1);
|
||||
from = from->next;
|
||||
}
|
||||
|
||||
while (to) {
|
||||
clear_commit_marks(to->item, PARENT2);
|
||||
to = to->next;
|
||||
}
|
||||
|
||||
object_array_clear(&from_objs);
|
||||
return result;
|
||||
}
|
77
commit-reach.h
Normal file
77
commit-reach.h
Normal file
@ -0,0 +1,77 @@
|
||||
#ifndef __COMMIT_REACH_H__
|
||||
#define __COMMIT_REACH_H__
|
||||
|
||||
#include "commit-slab.h"
|
||||
|
||||
struct commit;
|
||||
struct commit_list;
|
||||
struct contains_cache;
|
||||
struct ref_filter;
|
||||
|
||||
struct commit_list *get_merge_bases_many(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos);
|
||||
struct commit_list *get_merge_bases_many_dirty(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos);
|
||||
struct commit_list *get_merge_bases(struct commit *one, struct commit *two);
|
||||
struct commit_list *get_octopus_merge_bases(struct commit_list *in);
|
||||
|
||||
/* To be used only when object flags after this call no longer matter */
|
||||
struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos);
|
||||
|
||||
int is_descendant_of(struct commit *commit, struct commit_list *with_commit);
|
||||
int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference);
|
||||
int in_merge_bases(struct commit *commit, struct commit *reference);
|
||||
|
||||
/*
|
||||
* Takes a list of commits and returns a new list where those
|
||||
* have been removed that can be reached from other commits in
|
||||
* the list. It is useful for, e.g., reducing the commits
|
||||
* randomly thrown at the git-merge command and removing
|
||||
* redundant commits that the user shouldn't have given to it.
|
||||
*
|
||||
* This function destroys the STALE bit of the commit objects'
|
||||
* flags.
|
||||
*/
|
||||
struct commit_list *reduce_heads(struct commit_list *heads);
|
||||
|
||||
/*
|
||||
* Like `reduce_heads()`, except it replaces the list. Use this
|
||||
* instead of `foo = reduce_heads(foo);` to avoid memory leaks.
|
||||
*/
|
||||
void reduce_heads_replace(struct commit_list **heads);
|
||||
|
||||
int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
|
||||
|
||||
/*
|
||||
* Unknown has to be "0" here, because that's the default value for
|
||||
* contains_cache slab entries that have not yet been assigned.
|
||||
*/
|
||||
enum contains_result {
|
||||
CONTAINS_UNKNOWN = 0,
|
||||
CONTAINS_NO,
|
||||
CONTAINS_YES
|
||||
};
|
||||
|
||||
define_commit_slab(contains_cache, enum contains_result);
|
||||
|
||||
int commit_contains(struct ref_filter *filter, struct commit *commit,
|
||||
struct commit_list *list, struct contains_cache *cache);
|
||||
|
||||
/*
|
||||
* Determine if every commit in 'from' can reach at least one commit
|
||||
* that is marked with 'with_flag'. As we traverse, use 'assign_flag'
|
||||
* as a marker for commits that are already visited. Do not walk
|
||||
* commits with date below 'min_commit_date' or generation below
|
||||
* 'min_generation'.
|
||||
*/
|
||||
int can_all_from_reach_with_flag(struct object_array *from,
|
||||
unsigned int with_flag,
|
||||
unsigned int assign_flag,
|
||||
time_t min_commit_date,
|
||||
uint32_t min_generation);
|
||||
int can_all_from_reach(struct commit_list *from, struct commit_list *to,
|
||||
int commit_date_cutoff);
|
||||
|
||||
#endif
|
361
commit.c
361
commit.c
@ -843,367 +843,6 @@ void sort_in_topological_order(struct commit_list **list, enum rev_sort_order so
|
||||
clear_author_date_slab(&author_date);
|
||||
}
|
||||
|
||||
/* merge-base stuff */
|
||||
|
||||
/* Remember to update object flag allocation in object.h */
|
||||
#define PARENT1 (1u<<16)
|
||||
#define PARENT2 (1u<<17)
|
||||
#define STALE (1u<<18)
|
||||
#define RESULT (1u<<19)
|
||||
|
||||
static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
|
||||
|
||||
static int queue_has_nonstale(struct prio_queue *queue)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < queue->nr; i++) {
|
||||
struct commit *commit = queue->array[i].data;
|
||||
if (!(commit->object.flags & STALE))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* all input commits in one and twos[] must have been parsed! */
|
||||
static struct commit_list *paint_down_to_common(struct commit *one, int n,
|
||||
struct commit **twos,
|
||||
int min_generation)
|
||||
{
|
||||
struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
|
||||
struct commit_list *result = NULL;
|
||||
int i;
|
||||
uint32_t last_gen = GENERATION_NUMBER_INFINITY;
|
||||
|
||||
if (!min_generation)
|
||||
queue.compare = compare_commits_by_commit_date;
|
||||
|
||||
one->object.flags |= PARENT1;
|
||||
if (!n) {
|
||||
commit_list_append(one, &result);
|
||||
return result;
|
||||
}
|
||||
prio_queue_put(&queue, one);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
twos[i]->object.flags |= PARENT2;
|
||||
prio_queue_put(&queue, twos[i]);
|
||||
}
|
||||
|
||||
while (queue_has_nonstale(&queue)) {
|
||||
struct commit *commit = prio_queue_get(&queue);
|
||||
struct commit_list *parents;
|
||||
int flags;
|
||||
|
||||
if (min_generation && commit->generation > last_gen)
|
||||
BUG("bad generation skip %8x > %8x at %s",
|
||||
commit->generation, last_gen,
|
||||
oid_to_hex(&commit->object.oid));
|
||||
last_gen = commit->generation;
|
||||
|
||||
if (commit->generation < min_generation)
|
||||
break;
|
||||
|
||||
flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
|
||||
if (flags == (PARENT1 | PARENT2)) {
|
||||
if (!(commit->object.flags & RESULT)) {
|
||||
commit->object.flags |= RESULT;
|
||||
commit_list_insert_by_date(commit, &result);
|
||||
}
|
||||
/* Mark parents of a found merge stale */
|
||||
flags |= STALE;
|
||||
}
|
||||
parents = commit->parents;
|
||||
while (parents) {
|
||||
struct commit *p = parents->item;
|
||||
parents = parents->next;
|
||||
if ((p->object.flags & flags) == flags)
|
||||
continue;
|
||||
if (parse_commit(p))
|
||||
return NULL;
|
||||
p->object.flags |= flags;
|
||||
prio_queue_put(&queue, p);
|
||||
}
|
||||
}
|
||||
|
||||
clear_prio_queue(&queue);
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct commit_list *merge_bases_many(struct commit *one, int n, struct commit **twos)
|
||||
{
|
||||
struct commit_list *list = NULL;
|
||||
struct commit_list *result = NULL;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if (one == twos[i])
|
||||
/*
|
||||
* We do not mark this even with RESULT so we do not
|
||||
* have to clean it up.
|
||||
*/
|
||||
return commit_list_insert(one, &result);
|
||||
}
|
||||
|
||||
if (parse_commit(one))
|
||||
return NULL;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (parse_commit(twos[i]))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
list = paint_down_to_common(one, n, twos, 0);
|
||||
|
||||
while (list) {
|
||||
struct commit *commit = pop_commit(&list);
|
||||
if (!(commit->object.flags & STALE))
|
||||
commit_list_insert_by_date(commit, &result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct commit_list *get_octopus_merge_bases(struct commit_list *in)
|
||||
{
|
||||
struct commit_list *i, *j, *k, *ret = NULL;
|
||||
|
||||
if (!in)
|
||||
return ret;
|
||||
|
||||
commit_list_insert(in->item, &ret);
|
||||
|
||||
for (i = in->next; i; i = i->next) {
|
||||
struct commit_list *new_commits = NULL, *end = NULL;
|
||||
|
||||
for (j = ret; j; j = j->next) {
|
||||
struct commit_list *bases;
|
||||
bases = get_merge_bases(i->item, j->item);
|
||||
if (!new_commits)
|
||||
new_commits = bases;
|
||||
else
|
||||
end->next = bases;
|
||||
for (k = bases; k; k = k->next)
|
||||
end = k;
|
||||
}
|
||||
ret = new_commits;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int remove_redundant(struct commit **array, int cnt)
|
||||
{
|
||||
/*
|
||||
* Some commit in the array may be an ancestor of
|
||||
* another commit. Move such commit to the end of
|
||||
* the array, and return the number of commits that
|
||||
* are independent from each other.
|
||||
*/
|
||||
struct commit **work;
|
||||
unsigned char *redundant;
|
||||
int *filled_index;
|
||||
int i, j, filled;
|
||||
|
||||
work = xcalloc(cnt, sizeof(*work));
|
||||
redundant = xcalloc(cnt, 1);
|
||||
ALLOC_ARRAY(filled_index, cnt - 1);
|
||||
|
||||
for (i = 0; i < cnt; i++)
|
||||
parse_commit(array[i]);
|
||||
for (i = 0; i < cnt; i++) {
|
||||
struct commit_list *common;
|
||||
uint32_t min_generation = array[i]->generation;
|
||||
|
||||
if (redundant[i])
|
||||
continue;
|
||||
for (j = filled = 0; j < cnt; j++) {
|
||||
if (i == j || redundant[j])
|
||||
continue;
|
||||
filled_index[filled] = j;
|
||||
work[filled++] = array[j];
|
||||
|
||||
if (array[j]->generation < min_generation)
|
||||
min_generation = array[j]->generation;
|
||||
}
|
||||
common = paint_down_to_common(array[i], filled, work,
|
||||
min_generation);
|
||||
if (array[i]->object.flags & PARENT2)
|
||||
redundant[i] = 1;
|
||||
for (j = 0; j < filled; j++)
|
||||
if (work[j]->object.flags & PARENT1)
|
||||
redundant[filled_index[j]] = 1;
|
||||
clear_commit_marks(array[i], all_flags);
|
||||
clear_commit_marks_many(filled, work, all_flags);
|
||||
free_commit_list(common);
|
||||
}
|
||||
|
||||
/* Now collect the result */
|
||||
COPY_ARRAY(work, array, cnt);
|
||||
for (i = filled = 0; i < cnt; i++)
|
||||
if (!redundant[i])
|
||||
array[filled++] = work[i];
|
||||
for (j = filled, i = 0; i < cnt; i++)
|
||||
if (redundant[i])
|
||||
array[j++] = work[i];
|
||||
free(work);
|
||||
free(redundant);
|
||||
free(filled_index);
|
||||
return filled;
|
||||
}
|
||||
|
||||
static struct commit_list *get_merge_bases_many_0(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos,
|
||||
int cleanup)
|
||||
{
|
||||
struct commit_list *list;
|
||||
struct commit **rslt;
|
||||
struct commit_list *result;
|
||||
int cnt, i;
|
||||
|
||||
result = merge_bases_many(one, n, twos);
|
||||
for (i = 0; i < n; i++) {
|
||||
if (one == twos[i])
|
||||
return result;
|
||||
}
|
||||
if (!result || !result->next) {
|
||||
if (cleanup) {
|
||||
clear_commit_marks(one, all_flags);
|
||||
clear_commit_marks_many(n, twos, all_flags);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* There are more than one */
|
||||
cnt = commit_list_count(result);
|
||||
rslt = xcalloc(cnt, sizeof(*rslt));
|
||||
for (list = result, i = 0; list; list = list->next)
|
||||
rslt[i++] = list->item;
|
||||
free_commit_list(result);
|
||||
|
||||
clear_commit_marks(one, all_flags);
|
||||
clear_commit_marks_many(n, twos, all_flags);
|
||||
|
||||
cnt = remove_redundant(rslt, cnt);
|
||||
result = NULL;
|
||||
for (i = 0; i < cnt; i++)
|
||||
commit_list_insert_by_date(rslt[i], &result);
|
||||
free(rslt);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct commit_list *get_merge_bases_many(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos)
|
||||
{
|
||||
return get_merge_bases_many_0(one, n, twos, 1);
|
||||
}
|
||||
|
||||
struct commit_list *get_merge_bases_many_dirty(struct commit *one,
|
||||
int n,
|
||||
struct commit **twos)
|
||||
{
|
||||
return get_merge_bases_many_0(one, n, twos, 0);
|
||||
}
|
||||
|
||||
struct commit_list *get_merge_bases(struct commit *one, struct commit *two)
|
||||
{
|
||||
return get_merge_bases_many_0(one, 1, &two, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" a descendant of one of the elements on the "with_commit" list?
|
||||
*/
|
||||
int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
|
||||
{
|
||||
if (!with_commit)
|
||||
return 1;
|
||||
while (with_commit) {
|
||||
struct commit *other;
|
||||
|
||||
other = with_commit->item;
|
||||
with_commit = with_commit->next;
|
||||
if (in_merge_bases(other, commit))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" an ancestor of one of the "references"?
|
||||
*/
|
||||
int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
|
||||
{
|
||||
struct commit_list *bases;
|
||||
int ret = 0, i;
|
||||
uint32_t min_generation = GENERATION_NUMBER_INFINITY;
|
||||
|
||||
if (parse_commit(commit))
|
||||
return ret;
|
||||
for (i = 0; i < nr_reference; i++) {
|
||||
if (parse_commit(reference[i]))
|
||||
return ret;
|
||||
if (reference[i]->generation < min_generation)
|
||||
min_generation = reference[i]->generation;
|
||||
}
|
||||
|
||||
if (commit->generation > min_generation)
|
||||
return ret;
|
||||
|
||||
bases = paint_down_to_common(commit, nr_reference, reference, commit->generation);
|
||||
if (commit->object.flags & PARENT2)
|
||||
ret = 1;
|
||||
clear_commit_marks(commit, all_flags);
|
||||
clear_commit_marks_many(nr_reference, reference, all_flags);
|
||||
free_commit_list(bases);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Is "commit" an ancestor of (i.e. reachable from) the "reference"?
|
||||
*/
|
||||
int in_merge_bases(struct commit *commit, struct commit *reference)
|
||||
{
|
||||
return in_merge_bases_many(commit, 1, &reference);
|
||||
}
|
||||
|
||||
struct commit_list *reduce_heads(struct commit_list *heads)
|
||||
{
|
||||
struct commit_list *p;
|
||||
struct commit_list *result = NULL, **tail = &result;
|
||||
struct commit **array;
|
||||
int num_head, i;
|
||||
|
||||
if (!heads)
|
||||
return NULL;
|
||||
|
||||
/* Uniquify */
|
||||
for (p = heads; p; p = p->next)
|
||||
p->item->object.flags &= ~STALE;
|
||||
for (p = heads, num_head = 0; p; p = p->next) {
|
||||
if (p->item->object.flags & STALE)
|
||||
continue;
|
||||
p->item->object.flags |= STALE;
|
||||
num_head++;
|
||||
}
|
||||
array = xcalloc(num_head, sizeof(*array));
|
||||
for (p = heads, i = 0; p; p = p->next) {
|
||||
if (p->item->object.flags & STALE) {
|
||||
array[i++] = p->item;
|
||||
p->item->object.flags &= ~STALE;
|
||||
}
|
||||
}
|
||||
num_head = remove_redundant(array, num_head);
|
||||
for (i = 0; i < num_head; i++)
|
||||
tail = &commit_list_insert(array[i], tail)->next;
|
||||
free(array);
|
||||
return result;
|
||||
}
|
||||
|
||||
void reduce_heads_replace(struct commit_list **heads)
|
||||
{
|
||||
struct commit_list *result = reduce_heads(*heads);
|
||||
free_commit_list(*heads);
|
||||
*heads = result;
|
||||
}
|
||||
|
||||
static const char gpg_sig_header[] = "gpgsig";
|
||||
static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
|
||||
|
||||
|
29
commit.h
29
commit.h
@ -204,13 +204,6 @@ struct commit_graft *read_graft_line(struct strbuf *line);
|
||||
int register_commit_graft(struct repository *r, struct commit_graft *, int);
|
||||
struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid);
|
||||
|
||||
extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2);
|
||||
extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos);
|
||||
extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
|
||||
|
||||
/* To be used only when object flags after this call no longer matter */
|
||||
extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos);
|
||||
|
||||
/* largest positive number a signed 32-bit integer can contain */
|
||||
#define INFINITE_DEPTH 0x7fffffff
|
||||
|
||||
@ -258,32 +251,10 @@ extern int delayed_reachability_test(struct shallow_info *si, int c);
|
||||
extern void prune_shallow(int show_only);
|
||||
extern struct trace_key trace_shallow;
|
||||
|
||||
int is_descendant_of(struct commit *, struct commit_list *);
|
||||
int in_merge_bases(struct commit *, struct commit *);
|
||||
int in_merge_bases_many(struct commit *, int, struct commit **);
|
||||
|
||||
extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
|
||||
extern int run_add_interactive(const char *revision, const char *patch_mode,
|
||||
const struct pathspec *pathspec);
|
||||
|
||||
/*
|
||||
* Takes a list of commits and returns a new list where those
|
||||
* have been removed that can be reached from other commits in
|
||||
* the list. It is useful for, e.g., reducing the commits
|
||||
* randomly thrown at the git-merge command and removing
|
||||
* redundant commits that the user shouldn't have given to it.
|
||||
*
|
||||
* This function destroys the STALE bit of the commit objects'
|
||||
* flags.
|
||||
*/
|
||||
extern struct commit_list *reduce_heads(struct commit_list *heads);
|
||||
|
||||
/*
|
||||
* Like `reduce_heads()`, except it replaces the list. Use this
|
||||
* instead of `foo = reduce_heads(foo);` to avoid memory leaks.
|
||||
*/
|
||||
extern void reduce_heads_replace(struct commit_list **heads);
|
||||
|
||||
struct commit_extra_header {
|
||||
struct commit_extra_header *next;
|
||||
char *key;
|
||||
|
@ -171,6 +171,7 @@ Format of STDIN stream:
|
||||
#include "packfile.h"
|
||||
#include "object-store.h"
|
||||
#include "mem-pool.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
#define PACK_ID_BITS 16
|
||||
#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include "argv-array.h"
|
||||
#include "packfile.h"
|
||||
#include "object-store.h"
|
||||
|
||||
#include "commit-reach.h"
|
||||
|
||||
#ifdef EXPAT_NEEDS_XMLPARSE_H
|
||||
#include <xmlparse.h>
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "dir.h"
|
||||
#include "submodule.h"
|
||||
#include "revision.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
struct path_hashmap_entry {
|
||||
struct hashmap_entry e;
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "notes-merge.h"
|
||||
#include "strbuf.h"
|
||||
#include "notes-utils.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
struct notes_merge_pair {
|
||||
struct object_id obj, base, local, remote;
|
||||
|
4
object.h
4
object.h
@ -63,12 +63,12 @@ struct object_array {
|
||||
* fetch-pack.c: 01
|
||||
* negotiator/default.c: 2--5
|
||||
* walker.c: 0-2
|
||||
* upload-pack.c: 4 11----------------19
|
||||
* upload-pack.c: 4 11-----14 16-----19
|
||||
* builtin/blame.c: 12-13
|
||||
* bisect.c: 16
|
||||
* bundle.c: 16
|
||||
* http-push.c: 16-----19
|
||||
* commit.c: 16-----19
|
||||
* commit-reach.c: 15-------19
|
||||
* sha1-name.c: 20
|
||||
* list-objects-filter.c: 21
|
||||
* builtin/fsck.c: 0--3
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "pack-bitmap.h"
|
||||
#include "sha1-lookup.h"
|
||||
#include "pack-objects.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
struct bitmapped_commit {
|
||||
struct commit *commit;
|
||||
|
146
ref-filter.c
146
ref-filter.c
@ -19,6 +19,7 @@
|
||||
#include "wt-status.h"
|
||||
#include "commit-slab.h"
|
||||
#include "commit-graph.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static struct ref_msg {
|
||||
const char *gone;
|
||||
@ -1673,144 +1674,6 @@ static int get_ref_atom_value(struct ref_array_item *ref, int atom,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unknown has to be "0" here, because that's the default value for
|
||||
* contains_cache slab entries that have not yet been assigned.
|
||||
*/
|
||||
enum contains_result {
|
||||
CONTAINS_UNKNOWN = 0,
|
||||
CONTAINS_NO,
|
||||
CONTAINS_YES
|
||||
};
|
||||
|
||||
define_commit_slab(contains_cache, enum contains_result);
|
||||
|
||||
struct ref_filter_cbdata {
|
||||
struct ref_array *array;
|
||||
struct ref_filter *filter;
|
||||
struct contains_cache contains_cache;
|
||||
struct contains_cache no_contains_cache;
|
||||
};
|
||||
|
||||
/*
|
||||
* Mimicking the real stack, this stack lives on the heap, avoiding stack
|
||||
* overflows.
|
||||
*
|
||||
* At each recursion step, the stack items points to the commits whose
|
||||
* ancestors are to be inspected.
|
||||
*/
|
||||
struct contains_stack {
|
||||
int nr, alloc;
|
||||
struct contains_stack_entry {
|
||||
struct commit *commit;
|
||||
struct commit_list *parents;
|
||||
} *contains_stack;
|
||||
};
|
||||
|
||||
static int in_commit_list(const struct commit_list *want, struct commit *c)
|
||||
{
|
||||
for (; want; want = want->next)
|
||||
if (!oidcmp(&want->item->object.oid, &c->object.oid))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test whether the candidate is contained in the list.
|
||||
* Do not recurse to find out, though, but return -1 if inconclusive.
|
||||
*/
|
||||
static enum contains_result contains_test(struct commit *candidate,
|
||||
const struct commit_list *want,
|
||||
struct contains_cache *cache,
|
||||
uint32_t cutoff)
|
||||
{
|
||||
enum contains_result *cached = contains_cache_at(cache, candidate);
|
||||
|
||||
/* If we already have the answer cached, return that. */
|
||||
if (*cached)
|
||||
return *cached;
|
||||
|
||||
/* or are we it? */
|
||||
if (in_commit_list(want, candidate)) {
|
||||
*cached = CONTAINS_YES;
|
||||
return CONTAINS_YES;
|
||||
}
|
||||
|
||||
/* Otherwise, we don't know; prepare to recurse */
|
||||
parse_commit_or_die(candidate);
|
||||
|
||||
if (candidate->generation < cutoff)
|
||||
return CONTAINS_NO;
|
||||
|
||||
return CONTAINS_UNKNOWN;
|
||||
}
|
||||
|
||||
static void push_to_contains_stack(struct commit *candidate, struct contains_stack *contains_stack)
|
||||
{
|
||||
ALLOC_GROW(contains_stack->contains_stack, contains_stack->nr + 1, contains_stack->alloc);
|
||||
contains_stack->contains_stack[contains_stack->nr].commit = candidate;
|
||||
contains_stack->contains_stack[contains_stack->nr++].parents = candidate->parents;
|
||||
}
|
||||
|
||||
static enum contains_result contains_tag_algo(struct commit *candidate,
|
||||
const struct commit_list *want,
|
||||
struct contains_cache *cache)
|
||||
{
|
||||
struct contains_stack contains_stack = { 0, 0, NULL };
|
||||
enum contains_result result;
|
||||
uint32_t cutoff = GENERATION_NUMBER_INFINITY;
|
||||
const struct commit_list *p;
|
||||
|
||||
for (p = want; p; p = p->next) {
|
||||
struct commit *c = p->item;
|
||||
load_commit_graph_info(the_repository, c);
|
||||
if (c->generation < cutoff)
|
||||
cutoff = c->generation;
|
||||
}
|
||||
|
||||
result = contains_test(candidate, want, cache, cutoff);
|
||||
if (result != CONTAINS_UNKNOWN)
|
||||
return result;
|
||||
|
||||
push_to_contains_stack(candidate, &contains_stack);
|
||||
while (contains_stack.nr) {
|
||||
struct contains_stack_entry *entry = &contains_stack.contains_stack[contains_stack.nr - 1];
|
||||
struct commit *commit = entry->commit;
|
||||
struct commit_list *parents = entry->parents;
|
||||
|
||||
if (!parents) {
|
||||
*contains_cache_at(cache, commit) = CONTAINS_NO;
|
||||
contains_stack.nr--;
|
||||
}
|
||||
/*
|
||||
* If we just popped the stack, parents->item has been marked,
|
||||
* therefore contains_test will return a meaningful yes/no.
|
||||
*/
|
||||
else switch (contains_test(parents->item, want, cache, cutoff)) {
|
||||
case CONTAINS_YES:
|
||||
*contains_cache_at(cache, commit) = CONTAINS_YES;
|
||||
contains_stack.nr--;
|
||||
break;
|
||||
case CONTAINS_NO:
|
||||
entry->parents = parents->next;
|
||||
break;
|
||||
case CONTAINS_UNKNOWN:
|
||||
push_to_contains_stack(parents->item, &contains_stack);
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(contains_stack.contains_stack);
|
||||
return contains_test(candidate, want, cache, cutoff);
|
||||
}
|
||||
|
||||
static int commit_contains(struct ref_filter *filter, struct commit *commit,
|
||||
struct commit_list *list, struct contains_cache *cache)
|
||||
{
|
||||
if (filter->with_commit_tag_algo)
|
||||
return contains_tag_algo(commit, list, cache) == CONTAINS_YES;
|
||||
return is_descendant_of(commit, list);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return 1 if the refname matches one of the patterns, otherwise 0.
|
||||
* A pattern can be a literal prefix (e.g. a refname "refs/heads/master"
|
||||
@ -2046,6 +1909,13 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname)
|
||||
return ref_kind_from_refname(refname);
|
||||
}
|
||||
|
||||
struct ref_filter_cbdata {
|
||||
struct ref_array *array;
|
||||
struct ref_filter *filter;
|
||||
struct contains_cache contains_cache;
|
||||
struct contains_cache no_contains_cache;
|
||||
};
|
||||
|
||||
/*
|
||||
* A call-back given to for_each_ref(). Filter refs and keep them for
|
||||
* later object processing.
|
||||
|
50
remote.c
50
remote.c
@ -12,6 +12,7 @@
|
||||
#include "string-list.h"
|
||||
#include "mergesort.h"
|
||||
#include "argv-array.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
enum map_direction { FROM_SRC, FROM_DST };
|
||||
|
||||
@ -1791,55 +1792,6 @@ int resolve_remote_symref(struct ref *ref, struct ref *list)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void unmark_and_free(struct commit_list *list, unsigned int mark)
|
||||
{
|
||||
while (list) {
|
||||
struct commit *commit = pop_commit(&list);
|
||||
commit->object.flags &= ~mark;
|
||||
}
|
||||
}
|
||||
|
||||
int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid)
|
||||
{
|
||||
struct object *o;
|
||||
struct commit *old_commit, *new_commit;
|
||||
struct commit_list *list, *used;
|
||||
int found = 0;
|
||||
|
||||
/*
|
||||
* Both new_commit and old_commit must be commit-ish and new_commit is descendant of
|
||||
* old_commit. Otherwise we require --force.
|
||||
*/
|
||||
o = deref_tag(the_repository, parse_object(the_repository, old_oid),
|
||||
NULL, 0);
|
||||
if (!o || o->type != OBJ_COMMIT)
|
||||
return 0;
|
||||
old_commit = (struct commit *) o;
|
||||
|
||||
o = deref_tag(the_repository, parse_object(the_repository, new_oid),
|
||||
NULL, 0);
|
||||
if (!o || o->type != OBJ_COMMIT)
|
||||
return 0;
|
||||
new_commit = (struct commit *) o;
|
||||
|
||||
if (parse_commit(new_commit) < 0)
|
||||
return 0;
|
||||
|
||||
used = list = NULL;
|
||||
commit_list_insert(new_commit, &list);
|
||||
while (list) {
|
||||
new_commit = pop_most_recent_commit(&list, TMP_MARK);
|
||||
commit_list_insert(new_commit, &used);
|
||||
if (new_commit == old_commit) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
unmark_and_free(list, TMP_MARK);
|
||||
unmark_and_free(used, TMP_MARK);
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* Lookup the upstream branch for the given branch and if present, optionally
|
||||
* compute the commit ahead/behind values for the pair.
|
||||
|
1
remote.h
1
remote.h
@ -151,7 +151,6 @@ extern struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
|
||||
const struct string_list *server_options);
|
||||
|
||||
int resolve_remote_symref(struct ref *ref, struct ref *list);
|
||||
int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid);
|
||||
|
||||
/*
|
||||
* Remove and free all but the first of any entries in the input list
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "packfile.h"
|
||||
#include "worktree.h"
|
||||
#include "argv-array.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
volatile show_early_output_fn_t show_early_output;
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "oidset.h"
|
||||
#include "commit-slab.h"
|
||||
#include "alias.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "object-store.h"
|
||||
#include "repository.h"
|
||||
#include "midx.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static int get_oid_oneline(const char *, struct object_id *, struct commit_list *);
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "list-objects.h"
|
||||
#include "commit-slab.h"
|
||||
#include "repository.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
void set_alternate_shallow_file(struct repository *r, const char *path, int override)
|
||||
{
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "worktree.h"
|
||||
#include "parse-options.h"
|
||||
#include "object-store.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF;
|
||||
static struct string_list changed_submodule_names = STRING_LIST_INIT_DUP;
|
||||
|
130
t/helper/test-reach.c
Normal file
130
t/helper/test-reach.c
Normal file
@ -0,0 +1,130 @@
|
||||
#include "test-tool.h"
|
||||
#include "cache.h"
|
||||
#include "commit.h"
|
||||
#include "commit-reach.h"
|
||||
#include "config.h"
|
||||
#include "parse-options.h"
|
||||
#include "ref-filter.h"
|
||||
#include "string-list.h"
|
||||
#include "tag.h"
|
||||
|
||||
static void print_sorted_commit_ids(struct commit_list *list)
|
||||
{
|
||||
int i;
|
||||
struct string_list s = STRING_LIST_INIT_DUP;
|
||||
|
||||
while (list) {
|
||||
string_list_append(&s, oid_to_hex(&list->item->object.oid));
|
||||
list = list->next;
|
||||
}
|
||||
|
||||
string_list_sort(&s);
|
||||
|
||||
for (i = 0; i < s.nr; i++)
|
||||
printf("%s\n", s.items[i].string);
|
||||
|
||||
string_list_clear(&s, 0);
|
||||
}
|
||||
|
||||
int cmd__reach(int ac, const char **av)
|
||||
{
|
||||
struct object_id oid_A, oid_B;
|
||||
struct commit *A, *B;
|
||||
struct commit_list *X, *Y;
|
||||
struct commit **X_array;
|
||||
int X_nr, X_alloc;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
struct repository *r = the_repository;
|
||||
|
||||
setup_git_directory();
|
||||
|
||||
if (ac < 2)
|
||||
exit(1);
|
||||
|
||||
A = B = NULL;
|
||||
X = Y = NULL;
|
||||
X_nr = 0;
|
||||
X_alloc = 16;
|
||||
ALLOC_ARRAY(X_array, X_alloc);
|
||||
|
||||
while (strbuf_getline(&buf, stdin) != EOF) {
|
||||
struct object_id oid;
|
||||
struct object *o;
|
||||
struct commit *c;
|
||||
if (buf.len < 3)
|
||||
continue;
|
||||
|
||||
if (get_oid_committish(buf.buf + 2, &oid))
|
||||
die("failed to resolve %s", buf.buf + 2);
|
||||
|
||||
o = parse_object(r, &oid);
|
||||
o = deref_tag_noverify(o);
|
||||
|
||||
if (!o)
|
||||
die("failed to load commit for input %s resulting in oid %s\n",
|
||||
buf.buf, oid_to_hex(&oid));
|
||||
|
||||
c = object_as_type(r, o, OBJ_COMMIT, 0);
|
||||
|
||||
if (!c)
|
||||
die("failed to load commit for input %s resulting in oid %s\n",
|
||||
buf.buf, oid_to_hex(&oid));
|
||||
|
||||
switch (buf.buf[0]) {
|
||||
case 'A':
|
||||
oidcpy(&oid_A, &oid);
|
||||
A = c;
|
||||
break;
|
||||
|
||||
case 'B':
|
||||
oidcpy(&oid_B, &oid);
|
||||
B = c;
|
||||
break;
|
||||
|
||||
case 'X':
|
||||
commit_list_insert(c, &X);
|
||||
ALLOC_GROW(X_array, X_nr + 1, X_alloc);
|
||||
X_array[X_nr++] = c;
|
||||
break;
|
||||
|
||||
case 'Y':
|
||||
commit_list_insert(c, &Y);
|
||||
break;
|
||||
|
||||
default:
|
||||
die("unexpected start of line: %c", buf.buf[0]);
|
||||
}
|
||||
}
|
||||
strbuf_release(&buf);
|
||||
|
||||
if (!strcmp(av[1], "ref_newer"))
|
||||
printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B));
|
||||
else if (!strcmp(av[1], "in_merge_bases"))
|
||||
printf("%s(A,B):%d\n", av[1], in_merge_bases(A, B));
|
||||
else if (!strcmp(av[1], "is_descendant_of"))
|
||||
printf("%s(A,X):%d\n", av[1], is_descendant_of(A, X));
|
||||
else if (!strcmp(av[1], "get_merge_bases_many")) {
|
||||
struct commit_list *list = get_merge_bases_many(A, X_nr, X_array);
|
||||
printf("%s(A,X):\n", av[1]);
|
||||
print_sorted_commit_ids(list);
|
||||
} else if (!strcmp(av[1], "reduce_heads")) {
|
||||
struct commit_list *list = reduce_heads(X);
|
||||
printf("%s(X):\n", av[1]);
|
||||
print_sorted_commit_ids(list);
|
||||
} else if (!strcmp(av[1], "can_all_from_reach")) {
|
||||
printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1));
|
||||
} else if (!strcmp(av[1], "commit_contains")) {
|
||||
struct ref_filter filter;
|
||||
struct contains_cache cache;
|
||||
init_contains_cache(&cache);
|
||||
|
||||
if (ac > 2 && !strcmp(av[2], "--tag"))
|
||||
filter.with_commit_tag_algo = 1;
|
||||
else
|
||||
filter.with_commit_tag_algo = 0;
|
||||
|
||||
printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache));
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
@ -27,6 +27,7 @@ static struct test_cmd cmds[] = {
|
||||
{ "online-cpus", cmd__online_cpus },
|
||||
{ "path-utils", cmd__path_utils },
|
||||
{ "prio-queue", cmd__prio_queue },
|
||||
{ "reach", cmd__reach },
|
||||
{ "read-cache", cmd__read_cache },
|
||||
{ "read-midx", cmd__read_midx },
|
||||
{ "ref-store", cmd__ref_store },
|
||||
|
@ -23,6 +23,7 @@ int cmd__mktemp(int argc, const char **argv);
|
||||
int cmd__online_cpus(int argc, const char **argv);
|
||||
int cmd__path_utils(int argc, const char **argv);
|
||||
int cmd__prio_queue(int argc, const char **argv);
|
||||
int cmd__reach(int argc, const char **argv);
|
||||
int cmd__read_cache(int argc, const char **argv);
|
||||
int cmd__read_midx(int argc, const char **argv);
|
||||
int cmd__ref_store(int argc, const char **argv);
|
||||
|
242
t/t6600-test-reach.sh
Executable file
242
t/t6600-test-reach.sh
Executable file
@ -0,0 +1,242 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='basic commit reachability tests'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
# Construct a grid-like commit graph with points (x,y)
|
||||
# with 1 <= x <= 10, 1 <= y <= 10, where (x,y) has
|
||||
# parents (x-1, y) and (x, y-1), keeping in mind that
|
||||
# we drop a parent if a coordinate is nonpositive.
|
||||
#
|
||||
# (10,10)
|
||||
# / \
|
||||
# (10,9) (9,10)
|
||||
# / \ / \
|
||||
# (10,8) (9,9) (8,10)
|
||||
# / \ / \ / \
|
||||
# ( continued...)
|
||||
# \ / \ / \ /
|
||||
# (3,1) (2,2) (1,3)
|
||||
# \ / \ /
|
||||
# (2,1) (2,1)
|
||||
# \ /
|
||||
# (1,1)
|
||||
#
|
||||
# We use branch 'commit-x-y' to refer to (x,y).
|
||||
# This grid allows interesting reachability and
|
||||
# non-reachability queries: (x,y) can reach (x',y')
|
||||
# if and only if x' <= x and y' <= y.
|
||||
test_expect_success 'setup' '
|
||||
for i in $(test_seq 1 10)
|
||||
do
|
||||
test_commit "1-$i" &&
|
||||
git branch -f commit-1-$i
|
||||
done &&
|
||||
for j in $(test_seq 1 9)
|
||||
do
|
||||
git reset --hard commit-$j-1 &&
|
||||
x=$(($j + 1)) &&
|
||||
test_commit "$x-1" &&
|
||||
git branch -f commit-$x-1 &&
|
||||
|
||||
for i in $(test_seq 2 10)
|
||||
do
|
||||
git merge commit-$j-$i -m "$x-$i" &&
|
||||
git branch -f commit-$x-$i
|
||||
done
|
||||
done &&
|
||||
git commit-graph write --reachable &&
|
||||
mv .git/objects/info/commit-graph commit-graph-full &&
|
||||
git show-ref -s commit-5-5 | git commit-graph write --stdin-commits &&
|
||||
mv .git/objects/info/commit-graph commit-graph-half &&
|
||||
git config core.commitGraph true
|
||||
'
|
||||
|
||||
test_three_modes () {
|
||||
test_when_finished rm -rf .git/objects/info/commit-graph &&
|
||||
test-tool reach $1 <input >actual &&
|
||||
test_cmp expect actual &&
|
||||
cp commit-graph-full .git/objects/info/commit-graph &&
|
||||
test-tool reach $1 <input >actual &&
|
||||
test_cmp expect actual &&
|
||||
cp commit-graph-half .git/objects/info/commit-graph &&
|
||||
test-tool reach $1 <input >actual &&
|
||||
test_cmp expect actual
|
||||
}
|
||||
|
||||
test_expect_success 'ref_newer:miss' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-5-7
|
||||
B:commit-4-9
|
||||
EOF
|
||||
echo "ref_newer(A,B):0" >expect &&
|
||||
test_three_modes ref_newer
|
||||
'
|
||||
|
||||
test_expect_success 'ref_newer:hit' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-5-7
|
||||
B:commit-2-3
|
||||
EOF
|
||||
echo "ref_newer(A,B):1" >expect &&
|
||||
test_three_modes ref_newer
|
||||
'
|
||||
|
||||
test_expect_success 'in_merge_bases:hit' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-5-7
|
||||
B:commit-8-8
|
||||
EOF
|
||||
echo "in_merge_bases(A,B):1" >expect &&
|
||||
test_three_modes in_merge_bases
|
||||
'
|
||||
|
||||
test_expect_success 'in_merge_bases:miss' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-6-8
|
||||
B:commit-5-9
|
||||
EOF
|
||||
echo "in_merge_bases(A,B):0" >expect &&
|
||||
test_three_modes in_merge_bases
|
||||
'
|
||||
|
||||
test_expect_success 'is_descendant_of:hit' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-5-7
|
||||
X:commit-4-8
|
||||
X:commit-6-6
|
||||
X:commit-1-1
|
||||
EOF
|
||||
echo "is_descendant_of(A,X):1" >expect &&
|
||||
test_three_modes is_descendant_of
|
||||
'
|
||||
|
||||
test_expect_success 'is_descendant_of:miss' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-6-8
|
||||
X:commit-5-9
|
||||
X:commit-4-10
|
||||
X:commit-7-6
|
||||
EOF
|
||||
echo "is_descendant_of(A,X):0" >expect &&
|
||||
test_three_modes is_descendant_of
|
||||
'
|
||||
|
||||
test_expect_success 'get_merge_bases_many' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-5-7
|
||||
X:commit-4-8
|
||||
X:commit-6-6
|
||||
X:commit-8-3
|
||||
EOF
|
||||
{
|
||||
echo "get_merge_bases_many(A,X):" &&
|
||||
git rev-parse commit-5-6 \
|
||||
commit-4-7 | sort
|
||||
} >expect &&
|
||||
test_three_modes get_merge_bases_many
|
||||
'
|
||||
|
||||
test_expect_success 'reduce_heads' '
|
||||
cat >input <<-\EOF &&
|
||||
X:commit-1-10
|
||||
X:commit-2-8
|
||||
X:commit-3-6
|
||||
X:commit-4-4
|
||||
X:commit-1-7
|
||||
X:commit-2-5
|
||||
X:commit-3-3
|
||||
X:commit-5-1
|
||||
EOF
|
||||
{
|
||||
echo "reduce_heads(X):" &&
|
||||
git rev-parse commit-5-1 \
|
||||
commit-4-4 \
|
||||
commit-3-6 \
|
||||
commit-2-8 \
|
||||
commit-1-10 | sort
|
||||
} >expect &&
|
||||
test_three_modes reduce_heads
|
||||
'
|
||||
|
||||
test_expect_success 'can_all_from_reach:hit' '
|
||||
cat >input <<-\EOF &&
|
||||
X:commit-2-10
|
||||
X:commit-3-9
|
||||
X:commit-4-8
|
||||
X:commit-5-7
|
||||
X:commit-6-6
|
||||
X:commit-7-5
|
||||
X:commit-8-4
|
||||
X:commit-9-3
|
||||
Y:commit-1-9
|
||||
Y:commit-2-8
|
||||
Y:commit-3-7
|
||||
Y:commit-4-6
|
||||
Y:commit-5-5
|
||||
Y:commit-6-4
|
||||
Y:commit-7-3
|
||||
Y:commit-8-1
|
||||
EOF
|
||||
echo "can_all_from_reach(X,Y):1" >expect &&
|
||||
test_three_modes can_all_from_reach
|
||||
'
|
||||
|
||||
test_expect_success 'can_all_from_reach:miss' '
|
||||
cat >input <<-\EOF &&
|
||||
X:commit-2-10
|
||||
X:commit-3-9
|
||||
X:commit-4-8
|
||||
X:commit-5-7
|
||||
X:commit-6-6
|
||||
X:commit-7-5
|
||||
X:commit-8-4
|
||||
X:commit-9-3
|
||||
Y:commit-1-9
|
||||
Y:commit-2-8
|
||||
Y:commit-3-7
|
||||
Y:commit-4-6
|
||||
Y:commit-5-5
|
||||
Y:commit-6-4
|
||||
Y:commit-8-5
|
||||
EOF
|
||||
echo "can_all_from_reach(X,Y):0" >expect &&
|
||||
test_three_modes can_all_from_reach
|
||||
'
|
||||
|
||||
test_expect_success 'commit_contains:hit' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-7-7
|
||||
X:commit-2-10
|
||||
X:commit-3-9
|
||||
X:commit-4-8
|
||||
X:commit-5-7
|
||||
X:commit-6-6
|
||||
X:commit-7-5
|
||||
X:commit-8-4
|
||||
X:commit-9-3
|
||||
EOF
|
||||
echo "commit_contains(_,A,X,_):1" >expect &&
|
||||
test_three_modes commit_contains &&
|
||||
test_three_modes commit_contains --tag
|
||||
'
|
||||
|
||||
test_expect_success 'commit_contains:miss' '
|
||||
cat >input <<-\EOF &&
|
||||
A:commit-6-5
|
||||
X:commit-2-10
|
||||
X:commit-3-9
|
||||
X:commit-4-8
|
||||
X:commit-5-7
|
||||
X:commit-6-6
|
||||
X:commit-7-5
|
||||
X:commit-8-4
|
||||
X:commit-9-3
|
||||
EOF
|
||||
echo "commit_contains(_,A,X,_):0" >expect &&
|
||||
test_three_modes commit_contains &&
|
||||
test_three_modes commit_contains --tag
|
||||
'
|
||||
|
||||
test_done
|
@ -24,13 +24,13 @@
|
||||
#include "quote.h"
|
||||
#include "upload-pack.h"
|
||||
#include "serve.h"
|
||||
#include "commit-reach.h"
|
||||
|
||||
/* Remember to update object flag allocation in object.h */
|
||||
#define THEY_HAVE (1u << 11)
|
||||
#define OUR_REF (1u << 12)
|
||||
#define WANTED (1u << 13)
|
||||
#define COMMON_KNOWN (1u << 14)
|
||||
#define REACHABLE (1u << 15)
|
||||
|
||||
#define SHALLOW (1u << 16)
|
||||
#define NOT_SHALLOW (1u << 17)
|
||||
@ -337,64 +337,16 @@ static int got_oid(const char *hex, struct object_id *oid)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int reachable(struct commit *want)
|
||||
{
|
||||
struct prio_queue work = { compare_commits_by_commit_date };
|
||||
|
||||
prio_queue_put(&work, want);
|
||||
while (work.nr) {
|
||||
struct commit_list *list;
|
||||
struct commit *commit = prio_queue_get(&work);
|
||||
|
||||
if (commit->object.flags & THEY_HAVE) {
|
||||
want->object.flags |= COMMON_KNOWN;
|
||||
break;
|
||||
}
|
||||
if (!commit->object.parsed)
|
||||
parse_object(the_repository, &commit->object.oid);
|
||||
if (commit->object.flags & REACHABLE)
|
||||
continue;
|
||||
commit->object.flags |= REACHABLE;
|
||||
if (commit->date < oldest_have)
|
||||
continue;
|
||||
for (list = commit->parents; list; list = list->next) {
|
||||
struct commit *parent = list->item;
|
||||
if (!(parent->object.flags & REACHABLE))
|
||||
prio_queue_put(&work, parent);
|
||||
}
|
||||
}
|
||||
want->object.flags |= REACHABLE;
|
||||
clear_commit_marks(want, REACHABLE);
|
||||
clear_prio_queue(&work);
|
||||
return (want->object.flags & COMMON_KNOWN);
|
||||
}
|
||||
|
||||
static int ok_to_give_up(void)
|
||||
{
|
||||
int i;
|
||||
uint32_t min_generation = GENERATION_NUMBER_ZERO;
|
||||
|
||||
if (!have_obj.nr)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < want_obj.nr; i++) {
|
||||
struct object *want = want_obj.objects[i].item;
|
||||
|
||||
if (want->flags & COMMON_KNOWN)
|
||||
continue;
|
||||
want = deref_tag(the_repository, want, "a want line", 0);
|
||||
if (!want || want->type != OBJ_COMMIT) {
|
||||
/* no way to tell if this is reachable by
|
||||
* looking at the ancestry chain alone, so
|
||||
* leave a note to ourselves not to worry about
|
||||
* this object anymore.
|
||||
*/
|
||||
want_obj.objects[i].item->flags |= COMMON_KNOWN;
|
||||
continue;
|
||||
}
|
||||
if (!reachable((struct commit *)want))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
return can_all_from_reach_with_flag(&want_obj, THEY_HAVE,
|
||||
COMMON_KNOWN, oldest_have,
|
||||
min_generation);
|
||||
}
|
||||
|
||||
static int get_common_commits(void)
|
||||
|
Loading…
Reference in New Issue
Block a user