cbfe360b14
Both 'git for-each-ref --merged=<X>' and 'git branch --merged=<X>' use the ref-filter machinery to select references or branches (respectively) that are reachable from a set of commits presented by one or more --merged arguments. This happens within reach_filter(), which uses the revision-walk machinery to walk history in a standard way. However, the commit-reach.c file is full of custom searches that are more efficient, especially for reachability queries that can terminate early when reachability is discovered. Add a new tips_reachable_from_bases() method to commit-reach.c and call it from within reach_filter() in ref-filter.c. This affects both 'git branch' and 'git for-each-ref' as tested in p1500-graph-walks.sh. For the Linux kernel repository, we take an already-fast algorithm and make it even faster: Test HEAD~1 HEAD ------------------------------------------------------------------- 1500.5: contains: git for-each-ref --merged 0.13 0.02 -84.6% 1500.6: contains: git branch --merged 0.14 0.02 -85.7% 1500.7: contains: git tag --merged 0.15 0.03 -80.0% (Note that we remove the iterative 'git rev-list' test from p1500 because it no longer makes sense as a comparison to 'git for-each-ref' and would just waste time running it for these comparisons.) The algorithm is implemented in commit-reach.c in the method tips_reachable_from_base(). This method takes a string_list of tips and assigns the 'util' for each item with the value 1 if the base commit can reach those tips. Like other reachability queries in commit-reach.c, the fastest way to search for "can A reach B?" is to do a depth-first search up to the generation number of B, preferring to explore first parents before later parents. While we must walk all reachable commits up to that generation number when the answer is "no", the depth-first search can answer "yes" much faster than other approaches in most cases. This search becomes trickier when there are multiple targets for the depth-first search. The commits with lower generation number are more likely to be within the history of the start commit, but we don't want to waste time searching commits of low generation number if the commit target with lowest generation number has already been found. The trick here is to take the input commits and sort them by generation number in ascending order. Track the index within this order as min_generation_index. When we find a commit, if its index in the list is equal to min_generation_index, then we can increase the generation number boundary of our search to the next-lowest value in the list. With this mechanism, the number of commits to search is minimized with respect to the depth-first search heuristic. We will walk all commits up to the minimum generation number of a commit that is _not_ reachable from the start, but we will walk only the necessary portion of the depth-first search for the reachable commits of lower generation. Add extra tests for this behavior in t6600-test-reach.sh as the interesting data shape of that repository can sometimes demonstrate corner case bugs. Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
148 lines
4.9 KiB
C
148 lines
4.9 KiB
C
#ifndef COMMIT_REACH_H
|
|
#define COMMIT_REACH_H
|
|
|
|
#include "commit.h"
|
|
#include "commit-slab.h"
|
|
|
|
struct commit_list;
|
|
struct ref_filter;
|
|
struct object_id;
|
|
struct object_array;
|
|
|
|
struct commit_list *repo_get_merge_bases(struct repository *r,
|
|
struct commit *rev1,
|
|
struct commit *rev2);
|
|
struct commit_list *repo_get_merge_bases_many(struct repository *r,
|
|
struct commit *one, int n,
|
|
struct commit **twos);
|
|
/* To be used only when object flags after this call no longer matter */
|
|
struct commit_list *repo_get_merge_bases_many_dirty(struct repository *r,
|
|
struct commit *one, int n,
|
|
struct commit **twos);
|
|
#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
|
|
#define get_merge_bases(r1, r2) repo_get_merge_bases(the_repository, r1, r2)
|
|
#define get_merge_bases_many(one, n, two) repo_get_merge_bases_many(the_repository, one, n, two)
|
|
#define get_merge_bases_many_dirty(one, n, twos) repo_get_merge_bases_many_dirty(the_repository, one, n, twos)
|
|
#endif
|
|
|
|
struct commit_list *get_octopus_merge_bases(struct commit_list *in);
|
|
|
|
int repo_is_descendant_of(struct repository *r,
|
|
struct commit *commit,
|
|
struct commit_list *with_commit);
|
|
int repo_in_merge_bases(struct repository *r,
|
|
struct commit *commit,
|
|
struct commit *reference);
|
|
int repo_in_merge_bases_many(struct repository *r,
|
|
struct commit *commit,
|
|
int nr_reference, struct commit **reference);
|
|
#ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS
|
|
#define in_merge_bases(c1, c2) repo_in_merge_bases(the_repository, c1, c2)
|
|
#define in_merge_bases_many(c1, n, cs) repo_in_merge_bases_many(the_repository, c1, n, cs)
|
|
#endif
|
|
|
|
/*
|
|
* 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,
|
|
timestamp_t min_generation);
|
|
int can_all_from_reach(struct commit_list *from, struct commit_list *to,
|
|
int commit_date_cutoff);
|
|
|
|
|
|
/*
|
|
* Return a list of commits containing the commits in the 'to' array
|
|
* that are reachable from at least one commit in the 'from' array.
|
|
* Also add the given 'flag' to each of the commits in the returned list.
|
|
*
|
|
* This method uses the PARENT1 and PARENT2 flags during its operation,
|
|
* so be sure these flags are not set before calling the method.
|
|
*/
|
|
struct commit_list *get_reachable_subset(struct commit **from, int nr_from,
|
|
struct commit **to, int nr_to,
|
|
unsigned int reachable_flag);
|
|
|
|
struct ahead_behind_count {
|
|
/**
|
|
* As input, the *_index members indicate which positions in
|
|
* the 'tips' array correspond to the tip and base of this
|
|
* comparison.
|
|
*/
|
|
size_t tip_index;
|
|
size_t base_index;
|
|
|
|
/**
|
|
* These values store the computed counts for each side of the
|
|
* symmetric difference:
|
|
*
|
|
* 'ahead' stores the number of commits reachable from the tip
|
|
* and not reachable from the base.
|
|
*
|
|
* 'behind' stores the number of commits reachable from the base
|
|
* and not reachable from the tip.
|
|
*/
|
|
unsigned int ahead;
|
|
unsigned int behind;
|
|
};
|
|
|
|
/*
|
|
* Given an array of commits and an array of ahead_behind_count pairs,
|
|
* compute the ahead/behind counts for each pair.
|
|
*/
|
|
void ahead_behind(struct repository *r,
|
|
struct commit **commits, size_t commits_nr,
|
|
struct ahead_behind_count *counts, size_t counts_nr);
|
|
|
|
/*
|
|
* For all tip commits, add 'mark' to their flags if and only if they
|
|
* are reachable from one of the commits in 'bases'.
|
|
*/
|
|
void tips_reachable_from_bases(struct repository *r,
|
|
struct commit_list *bases,
|
|
struct commit **tips, size_t tips_nr,
|
|
int mark);
|
|
|
|
#endif
|