156 lines
6.2 KiB
Plaintext
156 lines
6.2 KiB
Plaintext
Merge-Base Computation and paint_down_to_common()
|
|
==================================================
|
|
|
|
The function `paint_down_to_common()` in `commit-reach.c` computes merge
|
|
bases by walking the commit graph backwards from two sets of tips and
|
|
finding where their ancestry meets.
|
|
|
|
Use cases
|
|
---------
|
|
|
|
Computing merge bases is used in two different ways:
|
|
|
|
1. *Finding all merge bases* (`merge-base --all`, `merge-tree`,
|
|
`merge`, `rebase`). A merge base is a common ancestor that is
|
|
not itself an ancestor of another common ancestor.
|
|
|
|
2. *Ancestry checks* (`in_merge_bases`, used by `merge-base
|
|
--is-ancestor`, `branch -d`, `fetch`). These ask: "is commit A
|
|
an ancestor of commit B?" If a common ancestor equals one of the
|
|
inputs, that input is necessarily the only merge base -- no other
|
|
common ancestor can be both as recent and not an ancestor of it.
|
|
|
|
Both use cases share the same algorithm and implementation.
|
|
|
|
Algorithm
|
|
---------
|
|
|
|
Given a commit `one` and a set of commits `twos[]`, the walk paints
|
|
commits with two colors:
|
|
|
|
- PARENT1: reachable from `one`
|
|
- PARENT2: reachable from any commit in `twos[]`
|
|
|
|
The walk uses a priority queue ordered by generation number
|
|
(highest first), breaking ties by commit date. Each step dequeues
|
|
the highest-priority commit and propagates its paint flags to its
|
|
parents, enqueuing any parent that gained new flags. When a
|
|
commit receives both PARENT1 and PARENT2, it is a merge-base
|
|
candidate. A candidate gains the STALE flag so its ancestors
|
|
propagate staleness -- any deeper common ancestor is necessarily
|
|
redundant.
|
|
|
|
[[generation-regions]]
|
|
Topologically ordered and unordered generation regions
|
|
------------------------------------------------------
|
|
|
|
Commits fall into two regions based on whether their generation
|
|
numbers provide a topological ordering guarantee:
|
|
|
|
....
|
|
+------------------------------------------+
|
|
| Unordered region |
|
|
| generation = INFINITY or V1_MAX |
|
|
| queue order: heuristic (commit date) |
|
|
+------------------------------------------+
|
|
|
|
|
v
|
|
+------------------------------------------+
|
|
| Ordered region |
|
|
| generation = finite, unsaturated |
|
|
| queue order: topological |
|
|
+------------------------------------------+
|
|
....
|
|
|
|
In the ordered region, a child's generation is strictly greater
|
|
than its parent's. Same-generation commits are necessarily
|
|
independent, so the queue always processes children before
|
|
their parents.
|
|
|
|
In the unordered region, parent-child pairs can share the same
|
|
generation number, so topological order is not guaranteed. The
|
|
queue uses commit-date as a heuristic, which typically produces
|
|
a reasonable traversal order but may process a parent before
|
|
its child.
|
|
|
|
Commits not in the commit-graph have generation INFINITY; v1
|
|
commit-graphs saturate at V1_MAX. Both place commits in the
|
|
unordered region. Any optimization that depends on generation
|
|
ordering must account for this saturation boundary. The early
|
|
exit gates compare against a topological ceiling --
|
|
`GENERATION_NUMBER_V1_MAX` for v1 graphs and
|
|
`GENERATION_NUMBER_INFINITY` for v2 graphs -- so that saturated
|
|
commits are treated as unordered.
|
|
|
|
With generation ordering, values in the unordered region exceed
|
|
those in the ordered region. The walk may therefore transition
|
|
from the unordered region into the ordered region, but never in
|
|
the reverse direction. Without a commit-graph, every commit has INFINITY
|
|
and the walk operates entirely in the unordered region.
|
|
|
|
In the ordered region, paint on a dequeued commit is final --
|
|
no future step can add flags to it. In the unordered region,
|
|
a dequeued commit may later gain additional paint. Paint flags
|
|
are only added, never removed, bounding the number of
|
|
re-enqueues per commit.
|
|
|
|
Termination
|
|
-----------
|
|
|
|
The walk tracks the number of commits of each type in the queue
|
|
(PARENT1-only, PARENT2-only, pending merge-base). The main loop
|
|
ends when one of the following conditions holds:
|
|
|
|
1. The queue is empty.
|
|
2. The queue contains only stale entries.
|
|
3. Generation cutoff: the dequeued commit's generation is below
|
|
a caller-supplied `min_generation` threshold.
|
|
4. Single result: the caller only needs one merge base, one has
|
|
been found, and the walk has entered the ordered region.
|
|
5. Side exhaustion: no pure PARENT1 or pure PARENT2 commits
|
|
remain in the queue, no pending merge-base candidates exist,
|
|
and the walk has entered the ordered region.
|
|
|
|
Stale entry condition
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
Once all queued entries are stale, no new merge-base candidates can
|
|
be discovered -- that requires at least one non-stale commit from
|
|
each side meeting. Continuing the walk could still invalidate
|
|
existing candidates by proving one is an ancestor of another, but
|
|
`remove_redundant()` handles that as a post-processing step, so it
|
|
is safe to exit early.
|
|
|
|
Side-exhaustion condition
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
A new merge-base requires commits from both sides to meet. When one
|
|
side's exclusive counter reaches zero and there are no pending
|
|
merge-base candidates, no future traversal step can produce a new
|
|
candidate. This optimization only activates in the ordered region,
|
|
where paint flags are final at visit time; in the unordered region,
|
|
a side that appears exhausted could reappear through late paint
|
|
propagation.
|
|
|
|
Generation cutoff
|
|
~~~~~~~~~~~~~~~~~
|
|
Some callers (notably `remove_redundant()`) supply a `min_generation`
|
|
threshold equal to the minimum generation of the input commits.
|
|
These callers only need to determine reachability among the inputs,
|
|
not find deep merge bases, so the walk can safely terminate when it
|
|
dequeues a commit below this threshold.
|
|
|
|
Single result
|
|
~~~~~~~~~~~~~
|
|
When only one merge base is needed and the walk is in the
|
|
ordered region with generation ordering, the first candidate
|
|
found is necessarily the highest-generation common ancestor.
|
|
No remaining commit in the queue can be a descendant of this
|
|
candidate (generation ordering guarantees children are visited
|
|
first), so it cannot be redundant and the walk can stop
|
|
immediately.
|
|
|
|
Related documentation
|
|
---------------------
|
|
|
|
- `Documentation/technical/commit-graph.adoc` -- generation numbers
|
|
and the reachability closure property.
|