Fast implementation of Git in pure Go

receivepack/hooks: Add pre-defined hooks

runxiyu.tngl.sh ab312b30 26f7f0e3

verified
+115
+51
receivepack/hooks/chain.go
···
··· 1 + package hooks 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + 7 + receivepack "codeberg.org/lindenii/furgit/receivepack" 8 + ) 9 + 10 + // Chain combines hooks by running them in order and intersecting their 11 + // decisions. The first rejecting message for each update is preserved. 12 + func Chain(hooks ...receivepack.Hook) receivepack.Hook { 13 + return func( 14 + ctx context.Context, 15 + req receivepack.HookRequest, 16 + ) ([]receivepack.UpdateDecision, error) { 17 + decisions := make([]receivepack.UpdateDecision, len(req.Updates)) 18 + for i := range decisions { 19 + decisions[i].Accept = true 20 + } 21 + 22 + for _, hook := range hooks { 23 + if hook == nil { 24 + continue 25 + } 26 + 27 + hookDecisions, err := hook(ctx, req) 28 + if err != nil { 29 + return nil, err 30 + } 31 + 32 + if len(hookDecisions) != len(req.Updates) { 33 + return nil, fmt.Errorf("hook returned %d decisions for %d updates", len(hookDecisions), len(req.Updates)) 34 + } 35 + 36 + for i, decision := range hookDecisions { 37 + if decision.Accept { 38 + continue 39 + } 40 + 41 + if decisions[i].Accept { 42 + decisions[i].Message = decision.Message 43 + } 44 + 45 + decisions[i].Accept = false 46 + } 47 + } 48 + 49 + return decisions, nil 50 + } 51 + }
+64
receivepack/hooks/reject_force_push.go
···
··· 1 + package hooks 2 + 3 + import ( 4 + "context" 5 + "errors" 6 + "fmt" 7 + 8 + "codeberg.org/lindenii/furgit/ancestor" 9 + "codeberg.org/lindenii/furgit/objectid" 10 + objectmix "codeberg.org/lindenii/furgit/objectstore/mix" 11 + receivepack "codeberg.org/lindenii/furgit/receivepack" 12 + "codeberg.org/lindenii/furgit/refstore" 13 + ) 14 + 15 + // RejectForcePush rejects updates whose new value is not a fast-forward of the 16 + // currently resolved reference. 17 + func RejectForcePush() receivepack.Hook { 18 + return func( 19 + ctx context.Context, 20 + req receivepack.HookRequest, 21 + ) ([]receivepack.UpdateDecision, error) { 22 + _ = ctx 23 + 24 + objects := objectmix.New(req.QuarantinedObjects, req.ExistingObjects) 25 + 26 + decisions := make([]receivepack.UpdateDecision, len(req.Updates)) 27 + for i := range decisions { 28 + decisions[i].Accept = true 29 + } 30 + 31 + for i, update := range req.Updates { 32 + if update.OldID == objectid.Zero(update.OldID.Algorithm()) || update.NewID == objectid.Zero(update.NewID.Algorithm()) { 33 + continue 34 + } 35 + 36 + current, err := req.Refs.ResolveFully(update.Name) 37 + switch { 38 + case err == nil: 39 + case errors.Is(err, refstore.ErrReferenceNotFound): 40 + continue 41 + default: 42 + return nil, fmt.Errorf("resolve %s: %w", update.Name, err) 43 + } 44 + 45 + if current.ID == update.NewID { 46 + continue 47 + } 48 + 49 + ok, err := ancestor.Is(objects, nil, current.ID, update.NewID) 50 + if err != nil { 51 + return nil, fmt.Errorf("check fast-forward %s: %w", update.Name, err) 52 + } 53 + 54 + if !ok { 55 + decisions[i] = receivepack.UpdateDecision{ 56 + Accept: false, 57 + Message: "non-fast-forward", 58 + } 59 + } 60 + } 61 + 62 + return decisions, nil 63 + } 64 + }