Artifact 3782ce1685fe995ab9593ad8c4afba9aea3f31baf17c9de9dfd76859659745d3:
- File
required_content_merge.go
— part of check-in
[d8458ad960]
at
2026-10-22 17:53:21
on branch feat/horizon-cones
— feat(merge): refuse a base below the structural horizon, or deepen to it
Section 5.5 forbids merging against a base the store does not hold. In a structural store the walk can reach a boundary check-in as the base, or find none where both sides' ancestry leaves the store, and the merge then failed on a phantom with an error that never named the horizon. content.MergeBaseCrossing decides the crossing for checkout merges and branch merges alike, and the error names the horizon, the base and the deepen, matching libfossil.ErrBelowHorizon.
Checkout.MergeContext is the remedy zit's merge applies: given a peer it asks the origin for the base, deepens to reach it and merges again over the real base. 'repo merge' takes the recovery flags and contacts the remote only when a merge crosses the horizon. (user: [email protected], size: 2512) [annotate] [blame] [check-ins using] [more...]
package libfossil import ( "context" "errors" "fmt" "github.com/danmestas/go-libfossil/internal/content" internalsync "github.com/danmestas/go-libfossil/internal/sync" ) // ErrBelowHorizon is matched by the error of an operation that needs history // below the store's structural horizon (draft-fossil-horizon-00 §5.5), such // as a merge whose base lies there. The message names the horizon and the // remedy, a deepen (Repo.DeepenHorizon). var ErrBelowHorizon = content.ErrBelowHorizon // MergeContext is Merge with the remedy of draft-fossil-horizon-00 §5.5. When // the merge base lies below the store's structural horizon and Peer is // given, it asks the origin for the base (§6.2.4), deepens the horizon to // reach it (Repo.DeepenHorizon), and merges once more over the real base the // store then holds. Without a Peer, or when the merge does not cross the // horizon, it is Merge. A merge never runs against a base the store does not // hold. func (c *Checkout) MergeContext( ctx context.Context, opts CheckoutMergeOpts, required RequiredContentOpts, ) (CheckoutMergeResult, error) { if err := required.validate(ctx); err != nil { return CheckoutMergeResult{}, err } res, crossing := c.Merge(opts) if !errors.Is(crossing, ErrBelowHorizon) || required.Peer == nil { return res, crossing } r := c.inner.Repo() _, current, err := c.Version() if err != nil { return CheckoutMergeResult{}, fmt.Errorf("libfossil: merge: %w", err) } var side string if err := r.DB().QueryRow("SELECT uuid FROM blob WHERE rid=?", opts.Version).Scan(&side); err != nil { return CheckoutMergeResult{}, fmt.Errorf("libfossil: merge: %w", err) } peer := &transportAdapter{pub: required.Peer} ask := required.internalSync() ask.MergeBaseOf = [2]string{current, side} answer, err := internalsync.Sync(ctx, r, peer, ask) if err != nil { return CheckoutMergeResult{}, fmt.Errorf("libfossil: merge: asking the origin for the merge base: %w", err) } if !answer.MergeBaseAnswered { return CheckoutMergeResult{}, fmt.Errorf("%w; the origin did not answer a merge-base query", crossing) } if answer.MergeBase == "" { return CheckoutMergeResult{}, fmt.Errorf("libfossil: merge: %s shares no history with %s, the origin says", side, current) } if _, err := internalsync.Deepen(ctx, r, peer, answer.MergeBase, required.internalSync()); err != nil { return CheckoutMergeResult{}, fmt.Errorf("libfossil: merge: deepening to the base %s: %w", answer.MergeBase, err) } return c.Merge(opts) }