shithub: furgit

ref: 9053c85456bd9b4457b588610eeef1b8dfff2b89
dir: /receivepack/hooks/reject_force_push.go/

View raw version
package hooks

import (
	"context"
	"errors"
	"fmt"

	"codeberg.org/lindenii/furgit/ancestor"
	"codeberg.org/lindenii/furgit/objectid"
	objectmix "codeberg.org/lindenii/furgit/objectstore/mix"
	receivepack "codeberg.org/lindenii/furgit/receivepack"
	"codeberg.org/lindenii/furgit/refstore"
)

// 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.ResolveFully(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 := ancestor.Is(objects, nil, 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
	}
}