feat: add MergeBase and multi-parent commit support#377
Conversation
Add a merge-base (common-ancestor) primitive to the Client interface so callers can compute three-dot diffs (a...b): comparing the merge base against a ref yields only what that ref introduced, excluding commits that landed on the other branch after the fork point. This is the building block a PR-preview needs to stop reporting files the branch author never touched. MergeBase walks the commit graph from both inputs following ALL parents, ordered by committer time, painting reachability flags and terminating as soon as common ancestors are found rather than descending to the root. The walk is bounded (WithMergeBaseMaxCommits, default 2000) and returns ErrNoMergeBase for unrelated histories or ErrMergeBaseLimitExceeded when the budget is exhausted. Correct merge-base computation requires every parent of a merge commit, so this also adds multi-parent support: - protocol: PackfileCommit gains Parents; the parser appends each parent line instead of overwriting, and Build() emits all parents. Parent still mirrors Parents[0], and writer-created (single-parent) commits serialize byte-for-byte as before, keeping their object hashes stable. - nanogit.Commit gains Parents, populated from the parsed object. Tested with unit coverage for multi-parent parse/build and the walk's ordering/selection helpers, plus integration tests against Gitea that reproduce the escalation scenario (two-dot reports an unrelated main file; three-dot via the merge base returns only the branch's change), order-independence, ancestor/identical inputs, the no-common-ancestor error, and a real merge commit exposing both parents. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #377 +/- ##
==========================================
+ Coverage 72.34% 72.36% +0.02%
==========================================
Files 59 60 +1
Lines 7391 7574 +183
==========================================
+ Hits 5347 5481 +134
- Misses 1636 1671 +35
- Partials 408 422 +14
🚀 New features to boost your workflow:
|
|
Did some exploring and I have a proposed long term solution here: Why not nanogit? #377If we add a method in nanogit to derive the mergeBase, it requires complex logic and has to pull in a lot of commit history. When compared to just reading the merge base from the git provider's servers via provided REST apis, this is not optimal. Why not use merge refs? grafana/grafana#127545GitHub publishes Other ConsiderationsMerge Requests/Pull Requests are not native to git, they're a feature offered by git providers and it makes sense to rely on their REST APIs to interface with them rather than using native git apis/nanogit. |
Summary
Adds a merge-base (common-ancestor) primitive to the
Clientinterface, the building block for three-dot diffs (a...b). Comparing the merge base against a ref yields only what that ref actually introduced, excluding commits that landed on the other branch after the fork point.This is Stage 1 of the fix for the Git Sync PR-preview issue (support-escalations #23127), where the preview listed files the PR author never touched because
CompareCommitsdoes a two-dot (main..branch) snapshot diff with no common-ancestor step. nanogit had no merge-base primitive and only tracked a single commit parent, so the fix has to originate here.Stage 2 (wiring a three-dot path into
apps/provisioning's PR-preview and bumping nanogit) is a separate PR in the Grafana repo, gated on a tagged release of this change.Changes
MergeBase(ctx, a, b, ...MergeBaseOption) (hash.Hash, error)on theClientinterface (merge_base.go): walks the commit graph from both inputs following all parents, ordered by committer time, painting reachability flags and terminating as soon as common ancestors are found rather than descending to the root. Bounded byWithMergeBaseMaxCommits(default 2000).protocol:PackfileCommitgainsParents; the parser appends eachparentline instead of overwriting, andBuild()emits all parents.Parentstill mirrorsParents[0], and writer-created single-parent commits serialize byte-for-byte as before (object hashes unchanged).nanogit.CommitgainsParents, populated from the parsed object.ErrNoMergeBase,ErrMergeBaseLimitExceeded.mocks/client.go.Testing
allStale, and candidate selection.mainfile while three-dot via the merge base returns only the branch's change; plus order-independence, identical/ancestor inputs, the no-common-ancestor error, and a real merge commit exposing both parents.go build ./..., unit tests,make lint(0 issues),make lint-staticcheck(0 issues),MergeBaseintegration specs (8/8), and the fullCommit-focused integration regression (53/53).Notes / limitations
MergeBasereturns a single one (most recent by committer time), which is sufficient for computing a three-dot file diff. Documented in the godoc.Parentsis additive;Parentbehavior is preserved.Checklist
🤖 Generated with Claude Code