aboutsummaryrefslogtreecommitdiff
path: root/network/receivepack/service/execute.go
blob: 92d34a630582d96f9fa8a0c3ce7e405abef72898 (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package service

import (
	"context"

	"codeberg.org/lindenii/furgit/internal/utils"
	objectstore "codeberg.org/lindenii/furgit/object/store"
)

// Execute validates one receive-pack request, optionally ingests its pack into
// quarantine, runs the optional hook, and applies allowed ref updates.
//
// Labels: Deps-Borrowed.
func (service *Service) Execute(ctx context.Context, req *Request) (*Result, error) {
	result := &Result{
		Commands: make([]CommandResult, 0, len(req.Commands)),
	}

	var err error

	quarantine, ok := service.ingestQuarantine(result, req.Commands, req)
	if !ok {
		return result, nil
	}

	if quarantine != nil {
		defer func(q objectstore.Quarantine) {
			_ = q.Discard()
		}(quarantine)
	}

	for _, command := range req.Commands {
		result.Planned = append(result.Planned, PlannedUpdate{
			Name:   command.Name,
			OldID:  command.OldID,
			NewID:  command.NewID,
			Delete: isDelete(command),
		})
	}

	if len(req.Commands) == 0 {
		return result, nil
	}

	allowedCommands, allowedIndices, rejected, ok, errText := service.runHook(
		ctx,
		req,
		req.Commands,
		quarantine,
	)
	if !ok {
		fillCommandErrors(result, req.Commands, errText)

		return result, nil
	}

	if req.Atomic && len(rejected) != 0 {
		result.Commands = make([]CommandResult, 0, len(req.Commands))
		for index, command := range req.Commands {
			message := rejected[index]
			if message == "" {
				message = "atomic push rejected by hook"
			}

			result.Commands = append(result.Commands, resultForHookRejection(command, message))
		}

		return result, nil
	}

	if len(allowedCommands) == 0 {
		result.Commands = mergeCommandResults(req.Commands, rejected, nil, nil)

		return result, nil
	}

	if req.PackExpected && quarantine != nil {
		// Git migrates quarantined objects into permanent storage immediately
		// before starting ref updates.
		utils.BestEffortFprintf(service.opts.Progress, "promoting quarantine...\r")

		err := quarantine.Promote()
		if err != nil {
			utils.BestEffortFprintf(service.opts.Progress, "promoting quarantine: failed: %v.\n", err)

			result.UnpackError = err.Error()
			fillCommandErrors(result, req.Commands, err.Error())

			return result, nil
		}

		utils.BestEffortFprintf(service.opts.Progress, "promoting quarantine: done.\n")
	}

	if req.Atomic {
		subresult := &Result{}

		err := service.applyAtomic(subresult, allowedCommands)
		if err != nil {
			return result, err
		}

		result.Commands = mergeCommandResults(req.Commands, rejected, subresult.Commands, allowedIndices)
		result.Applied = subresult.Applied

		return result, nil
	}

	subresult := &Result{}

	err = service.applyBatch(subresult, allowedCommands)
	if err != nil {
		return result, err
	}

	result.Commands = mergeCommandResults(req.Commands, rejected, subresult.Commands, allowedIndices)
	result.Applied = subresult.Applied

	return result, nil
}