Files
git/commit-reach.h
Kristofer Karlsson 9305e559d2 commit-reach: remove get_reachable_subset()
get_reachable_subset() and tips_reachable_from_bases() both answer
the same reachability question but use different traversal
strategies: priority queue vs depth-first search.  Consolidate them
into tips_reachable_from_bases() with a mode parameter to select
between DFS and PQ traversal, preserving the preferred strategy for
each caller.

This works cleanly because prio_queue already supports LIFO mode
(when compare is NULL), so a single prio_queue acts as either a
stack or a heap depending on the mode.

The unified traversal pushes all unseen parents at once rather than
peeking and pushing one parent at a time.  This eliminates merge
commit revisits entirely: a 2-parent merge now requires 1 visit
instead of 3.  For DFS (LIFO) mode, the first parent is pushed
last so it ends up on top of the stack, preserving first-parent
traversal order.

Parsing is deferred to pop time for DFS since parent objects carry
valid flags without a full repo_parse_commit() call.  PQ mode
parses before push so the heap can order by generation number.

Add exhaustive reachability tests that use every commit in the
grid as a tip, protecting against subtle traversal bugs such as
wrong parent ordering or premature pruning.  The existing tests
are also extended to exercise both DFS and PQ modes.

The flag in remote.c changes from 1 (bit 0) to TMP_MARK (bit 4)
because tips_reachable_from_bases() uses SEEN (bit 0) internally.
TMP_MARK is already used for deduplication earlier in the same
function and is cleared before the reachability check.

Signed-off-by: Kristofer Karlsson <krka@spotify.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2026-06-11 06:09:44 -07:00

161 lines
5.0 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;
int repo_get_merge_bases(struct repository *r,
struct commit *rev1,
struct commit *rev2,
struct commit_list **result);
int repo_get_merge_bases_many(struct repository *r,
struct commit *one, size_t n,
struct commit **twos,
struct commit_list **result);
enum merge_base_flags {
MERGE_BASE_IGNORE_MISSING_COMMITS = (1 << 0),
MERGE_BASE_FIND_ALL = (1 << 1),
};
/*
* To be used only when object flags after this call no longer matter.
* Without MERGE_BASE_FIND_ALL and with generation numbers available,
* returns after finding the first merge-base, skipping the STALE drain.
*/
int repo_get_merge_bases_many_dirty(struct repository *r,
struct commit *one, size_t n,
struct commit **twos,
enum merge_base_flags mb_flags,
struct commit_list **result);
int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result);
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,
int ignore_missing_commits);
/*
* 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,
timestamp_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);
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'.
*/
enum tips_reachable_mode {
TIPS_REACHABLE_DFS,
TIPS_REACHABLE_PQ,
};
void tips_reachable_from_bases(struct repository *r,
struct commit_list *bases,
struct commit **tips, size_t tips_nr,
int mark, enum tips_reachable_mode mode);
/*
* Given a 'tip' commit and a list potential 'bases', return the index 'i' that
* minimizes the number of commits in the first-parent history of 'tip' and not
* in the first-parent history of 'bases[i]'.
*
* Among a list of long-lived branches that are updated only by merges (with the
* first parent being the previous position of the branch), this would inform
* which branch was used to create the tip reference.
*
* Returns -1 if no common point is found in first-parent histories, which is
* rare, but possible with multiple root commits.
*/
int get_branch_base_for_tip(struct repository *r,
struct commit *tip,
struct commit **bases,
size_t bases_nr);
#endif