From 3d25bda9d5da6814661828adabe8a09f9d01aefb Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Thu, 26 Mar 2026 09:14:59 +0000 Subject: network/receivepack: Rename from receivepack --- network/receivepack/hooks/chain.go | 51 ++++++++++++++++++++ network/receivepack/hooks/doc.go | 2 + network/receivepack/hooks/reject_force_push.go | 64 ++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 network/receivepack/hooks/chain.go create mode 100644 network/receivepack/hooks/doc.go create mode 100644 network/receivepack/hooks/reject_force_push.go (limited to 'network/receivepack/hooks') diff --git a/network/receivepack/hooks/chain.go b/network/receivepack/hooks/chain.go new file mode 100644 index 00000000..f98c06f8 --- /dev/null +++ b/network/receivepack/hooks/chain.go @@ -0,0 +1,51 @@ +package hooks + +import ( + "context" + "fmt" + + receivepack "codeberg.org/lindenii/furgit/network/receivepack" +) + +// Chain combines hooks by running them in order and intersecting their +// decisions. The first rejecting message for each update is preserved. +func Chain(hooks ...receivepack.Hook) receivepack.Hook { + return func( + ctx context.Context, + req receivepack.HookRequest, + ) ([]receivepack.UpdateDecision, error) { + decisions := make([]receivepack.UpdateDecision, len(req.Updates)) + for i := range decisions { + decisions[i].Accept = true + } + + for _, hook := range hooks { + if hook == nil { + continue + } + + hookDecisions, err := hook(ctx, req) + if err != nil { + return nil, err + } + + if len(hookDecisions) != len(req.Updates) { + return nil, fmt.Errorf("hook returned %d decisions for %d updates", len(hookDecisions), len(req.Updates)) + } + + for i, decision := range hookDecisions { + if decision.Accept { + continue + } + + if decisions[i].Accept { + decisions[i].Message = decision.Message + } + + decisions[i].Accept = false + } + } + + return decisions, nil + } +} diff --git a/network/receivepack/hooks/doc.go b/network/receivepack/hooks/doc.go new file mode 100644 index 00000000..bef2baf9 --- /dev/null +++ b/network/receivepack/hooks/doc.go @@ -0,0 +1,2 @@ +// Package hooks provides a few pre-defined hooks that callers might find useful. +package hooks diff --git a/network/receivepack/hooks/reject_force_push.go b/network/receivepack/hooks/reject_force_push.go new file mode 100644 index 00000000..79714c8b --- /dev/null +++ b/network/receivepack/hooks/reject_force_push.go @@ -0,0 +1,64 @@ +package hooks + +import ( + "context" + "errors" + "fmt" + + "codeberg.org/lindenii/furgit/commitquery" + objectid "codeberg.org/lindenii/furgit/object/id" + objectmix "codeberg.org/lindenii/furgit/object/storer/mix" + receivepack "codeberg.org/lindenii/furgit/network/receivepack" + refstore "codeberg.org/lindenii/furgit/ref/store" +) + +// RejectForcePush rejects updates whose new value is not a fast-forward of the +// currently resolved reference. +func RejectForcePush() receivepack.Hook { + return func( + ctx context.Context, + req receivepack.HookRequest, + ) ([]receivepack.UpdateDecision, error) { + _ = ctx + + objects := objectmix.New(req.QuarantinedObjects, req.ExistingObjects) + + decisions := make([]receivepack.UpdateDecision, len(req.Updates)) + for i := range decisions { + decisions[i].Accept = true + } + + for i, update := range req.Updates { + if update.OldID == objectid.Zero(update.OldID.Algorithm()) || update.NewID == objectid.Zero(update.NewID.Algorithm()) { + continue + } + + current, err := req.Refs.ResolveToDetached(update.Name) + switch { + case err == nil: + case errors.Is(err, refstore.ErrReferenceNotFound): + continue + default: + return nil, fmt.Errorf("resolve %s: %w", update.Name, err) + } + + if current.ID == update.NewID { + continue + } + + ok, err := commitquery.New(objects, nil).IsAncestor(current.ID, update.NewID) + if err != nil { + return nil, fmt.Errorf("check fast-forward %s: %w", update.Name, err) + } + + if !ok { + decisions[i] = receivepack.UpdateDecision{ + Accept: false, + Message: "non-fast-forward", + } + } + } + + return decisions, nil + } +} -- cgit v1.3.1-10-gc9f91