aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/protocol/packp/ulreq.go
blob: faf08856c20063c3b13c55a8532f0a9876a143f6 (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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package packp

import (
	"fmt"
	"time"

	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability"
)

const DefaultAgent = "go-git/4.x"

// UploadRequest values represent the information transmitted on a
// upload-request message.  Values from this type are not zero-value
// safe, use the New function instead.
// This is a low level type, use UploadPackRequest instead.
type UploadRequest struct {
	Capabilities *capability.List
	Wants        []plumbing.Hash
	Shallows     []plumbing.Hash
	Depth        Depth
}

// Depth values stores the desired depth of the requested packfile: see
// DepthCommit, DepthSince and DepthReference.
type Depth interface {
	isDepth()
	IsZero() bool
}

// DepthCommits values stores the maximum number of requested commits in
// the packfile.  Zero means infinite.  A negative value will have
// undefined consecuences.
type DepthCommits int

func (d DepthCommits) isDepth() {}

func (d DepthCommits) IsZero() bool {
	return d == 0
}

// DepthSince values requests only commits newer than the specified time.
type DepthSince time.Time

func (d DepthSince) isDepth() {}

func (d DepthSince) IsZero() bool {
	return time.Time(d).IsZero()
}

// DepthReference requests only commits not to found in the specified reference.
type DepthReference string

func (d DepthReference) isDepth() {}

func (d DepthReference) IsZero() bool {
	return string(d) == ""
}

// NewUploadRequest returns a pointer to a new UploadRequest value, ready to be
// used. It has no capabilities, wants or shallows and an infinite depth. Please
// note that to encode an upload-request it has to have at least one wanted hash.
func NewUploadRequest() *UploadRequest {
	return &UploadRequest{
		Capabilities: capability.NewList(),
		Wants:        []plumbing.Hash{},
		Shallows:     []plumbing.Hash{},
		Depth:        DepthCommits(0),
	}
}

// NewUploadRequestFromCapabilities returns a pointer to a new UploadRequest
// value, the request capabilities are filled with the most optiomal ones, based
// on the adv value (advertaised capabilities), the UploadRequest generated it
// has no wants or shallows and an infinite depth.
func NewUploadRequestFromCapabilities(adv *capability.List) *UploadRequest {
	r := NewUploadRequest()

	if adv.Supports(capability.MultiACKDetailed) {
		r.Capabilities.Set(capability.MultiACKDetailed)
	} else if adv.Supports(capability.MultiACK) {
		r.Capabilities.Set(capability.MultiACK)
	}

	if adv.Supports(capability.Sideband64k) {
		r.Capabilities.Set(capability.Sideband64k)
	} else if adv.Supports(capability.Sideband) {
		r.Capabilities.Set(capability.Sideband)
	}

	if adv.Supports(capability.ThinPack) {
		r.Capabilities.Set(capability.ThinPack)
	}

	if adv.Supports(capability.OFSDelta) {
		r.Capabilities.Set(capability.OFSDelta)
	}

	if adv.Supports(capability.Agent) {
		r.Capabilities.Set(capability.Agent, DefaultAgent)
	}

	return r
}

// Validate validates the content of UploadRequest, following the next rules:
//   - Wants MUST have at least one reference
//   - capability.Shallow MUST be present if Shallows is not empty
//   - is a non-zero DepthCommits is given capability.Shallow MUST be present
//   - is a DepthSince is given capability.Shallow MUST be present
//   - is a DepthReference is given capability.DeepenNot MUST be present
//   - MUST contain only maximum of one of capability.Sideband and capability.Sideband64k
//   - MUST contain only maximum of one of capability.MultiACK and capability.MultiACKDetailed
func (r *UploadRequest) Validate() error {
	if len(r.Wants) == 0 {
		return fmt.Errorf("want can't be empty")
	}

	if err := r.validateRequiredCapabilities(); err != nil {
		return err
	}

	if err := r.validateConflictCapabilities(); err != nil {
		return err
	}

	return nil
}

func (r *UploadRequest) validateRequiredCapabilities() error {
	msg := "missing capability %s"

	if len(r.Shallows) != 0 && !r.Capabilities.Supports(capability.Shallow) {
		return fmt.Errorf(msg, capability.Shallow)
	}

	switch r.Depth.(type) {
	case DepthCommits:
		if r.Depth != DepthCommits(0) {
			if !r.Capabilities.Supports(capability.Shallow) {
				return fmt.Errorf(msg, capability.Shallow)
			}
		}
	case DepthSince:
		if !r.Capabilities.Supports(capability.DeepenSince) {
			return fmt.Errorf(msg, capability.DeepenSince)
		}
	case DepthReference:
		if !r.Capabilities.Supports(capability.DeepenNot) {
			return fmt.Errorf(msg, capability.DeepenNot)
		}
	}

	return nil
}

func (r *UploadRequest) validateConflictCapabilities() error {
	msg := "capabilities %s and %s are mutually exclusive"
	if r.Capabilities.Supports(capability.Sideband) &&
		r.Capabilities.Supports(capability.Sideband64k) {
		return fmt.Errorf(msg, capability.Sideband, capability.Sideband64k)
	}

	if r.Capabilities.Supports(capability.MultiACK) &&
		r.Capabilities.Supports(capability.MultiACKDetailed) {
		return fmt.Errorf(msg, capability.MultiACK, capability.MultiACKDetailed)
	}

	return nil
}