aboutsummaryrefslogtreecommitdiff
path: root/network/receivepack/hooks/reject_force_push.go
diff options
context:
space:
mode:
Diffstat (limited to 'network/receivepack/hooks/reject_force_push.go')
-rw-r--r--network/receivepack/hooks/reject_force_push.go64
1 files changed, 64 insertions, 0 deletions
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
+ }
+}