shithub: furgit

ref: dc634ee5daef8268203b26c1b14ab59c11f7f59c
dir: /receivepack/receivepack.go/

View raw version
package receivepack

import (
	"context"
	"io"

	"codeberg.org/lindenii/furgit/format/pktline"
	common "codeberg.org/lindenii/furgit/protocol/v0v1/server"
	protoreceive "codeberg.org/lindenii/furgit/protocol/v0v1/server/receivepack"
	"codeberg.org/lindenii/furgit/receivepack/service"
)

// TODO: Some more designing to do. In particular, we'd like to have access to
// commit graphs and stored object abstractions and such here, especially because
// hooks might want to access full repos, but we risk creating
// circular dependencies if we import repository/ here. Might need an interface-ish
// design, but that risks being over-complicated.
// Theoretically we could also just give the hooks an os.Root but that
// feels a bit ugly.

// ReceivePack serves one receive-pack session over r/w.
func ReceivePack(
	ctx context.Context,
	w pktline.WriteFlusher,
	r io.Reader,
	opts Options,
) error {
	err := validateOptions(opts)
	if err != nil {
		return err
	}

	version := parseVersion(opts.GitProtocol)

	base := common.NewSession(r, w, common.Options{
		Version:   version,
		Algorithm: opts.Algorithm,
	})

	protoSession := protoreceive.NewSession(base, protoreceive.Capabilities{
		ReportStatus:   true,
		ReportStatusV2: true,
		DeleteRefs:     true,
		SideBand64K:    true,
		Quiet:          true,
		Atomic:         true,
		OfsDelta:       true,
		PushOptions:    true,
		ObjectFormat:   opts.Algorithm,
		// TODO: PushCertNonce, SessionID, Agent, whatever.
	})

	refs, err := advertisedRefs(opts)
	if err != nil {
		return err
	}

	err = protoSession.AdvertiseRefs(common.Advertisement{Refs: refs})
	if err != nil {
		return err
	}

	req, err := protoSession.ReadRequest()
	if err != nil {
		return err
	}

	serviceReq := &service.Request{
		Commands:     translateCommands(req.Commands),
		PushOptions:  append([]string(nil), req.PushOptions...),
		Atomic:       req.Capabilities.Atomic,
		DeleteOnly:   req.DeleteOnly,
		PackExpected: req.PackExpected,
		Pack:         r,
	}

	svc := service.New(service.Options{
		Algorithm:       opts.Algorithm,
		Refs:            opts.Refs,
		ExistingObjects: opts.ExistingObjects,
		ObjectsRoot:     opts.ObjectsRoot,
		PromotedObjectPermissions: translatePromotedObjectPermissions(
			opts.PromotedObjectPermissions,
		),
		Hook: translateHook(opts.Hook),
		HookIO: service.HookIO{
			Progress: base.ProgressWriter(),
			Error:    base.ErrorWriter(),
		},
	})

	result, err := svc.Execute(ctx, serviceReq)
	if err != nil {
		return err
	}

	protoResult := translateResult(result)

	if req.Capabilities.ReportStatusV2 {
		return protoSession.WriteReportStatusV2(protoResult)
	}

	if req.Capabilities.ReportStatus {
		return protoSession.WriteReportStatus(protoResult)
	}

	return nil
}