diff options
| author | 2026-03-25 16:22:03 +0000 | |
|---|---|---|
| committer | 2026-03-25 16:22:03 +0000 | |
| commit | 311edcd50f3a84f4b860bde3cb887451d74eaa11 (patch) | |
| tree | be7aa5e9a51e636358f33b1c90637b5024b70dc3 /network/protocol/v0v1/server/receivepack/capabilities.go | |
| parent | README: Split off contrib, benchmarks, remove history for now I guess, etc. (diff) | |
| signature | No signature | |
network/protocol: Rename from protocol v0.1.110
Diffstat (limited to 'network/protocol/v0v1/server/receivepack/capabilities.go')
| -rw-r--r-- | network/protocol/v0v1/server/receivepack/capabilities.go | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/network/protocol/v0v1/server/receivepack/capabilities.go b/network/protocol/v0v1/server/receivepack/capabilities.go new file mode 100644 index 00000000..e0ff51a3 --- /dev/null +++ b/network/protocol/v0v1/server/receivepack/capabilities.go @@ -0,0 +1,192 @@ +package receivepack + +import ( + "fmt" + "slices" + "strings" + + objectid "codeberg.org/lindenii/furgit/object/id" +) + +// Capabilities describes one receive-pack capability set. +type Capabilities struct { + ReportStatus bool + ReportStatusV2 bool + DeleteRefs bool + SideBand64K bool + Quiet bool + Atomic bool + OfsDelta bool + PushOptions bool + PushCertNonce string + ObjectFormat objectid.Algorithm + SessionID string + Agent string +} + +// Normalize returns one normalized copy of caps. +func (caps Capabilities) Normalize(defaultAlgorithm objectid.Algorithm) Capabilities { + if caps.ObjectFormat == objectid.AlgorithmUnknown { + caps.ObjectFormat = defaultAlgorithm + } + + return caps +} + +// Tokens returns capabilities in Git advertisement order. +func (caps Capabilities) Tokens(defaultAlgorithm objectid.Algorithm) []string { + caps = caps.Normalize(defaultAlgorithm) + + tokens := make([]string, 0, 11) + if caps.ReportStatus { + tokens = append(tokens, "report-status") + } + + if caps.ReportStatusV2 { + tokens = append(tokens, "report-status-v2") + } + + if caps.DeleteRefs { + tokens = append(tokens, "delete-refs") + } + + if caps.SideBand64K { + tokens = append(tokens, "side-band-64k") + } + + if caps.Quiet { + tokens = append(tokens, "quiet") + } + + if caps.Atomic { + tokens = append(tokens, "atomic") + } + + if caps.OfsDelta { + tokens = append(tokens, "ofs-delta") + } + + if caps.PushCertNonce != "" { + tokens = append(tokens, "push-cert="+caps.PushCertNonce) + } + + if caps.PushOptions { + tokens = append(tokens, "push-options") + } + + if caps.SessionID != "" { + tokens = append(tokens, "session-id="+caps.SessionID) + } + + if caps.ObjectFormat != objectid.AlgorithmUnknown { + tokens = append(tokens, "object-format="+caps.ObjectFormat.String()) + } + + if caps.Agent != "" { + tokens = append(tokens, "agent="+caps.Agent) + } + + return tokens +} + +func (caps Capabilities) supportsToken(token string, defaultAlgorithm objectid.Algorithm) bool { + name, value, _ := strings.Cut(token, "=") + + switch name { + case "report-status": + return caps.ReportStatus && value == "" + case "report-status-v2": + return caps.ReportStatusV2 && value == "" + case "delete-refs": + return caps.DeleteRefs && value == "" + case "side-band-64k": + return caps.SideBand64K && value == "" + case "quiet": + return caps.Quiet && value == "" + case "atomic": + return caps.Atomic && value == "" + case "ofs-delta": + return caps.OfsDelta && value == "" + case "push-options": + return caps.PushOptions && value == "" + case "push-cert": + return caps.PushCertNonce != "" && value != "" + case "object-format": + if value == "" { + return false + } + + algo, ok := objectid.ParseAlgorithm(value) + + return ok && algo == caps.Normalize(defaultAlgorithm).ObjectFormat + case "session-id": + return caps.SessionID != "" && value != "" + case "agent": + return caps.Agent != "" && value != "" + default: + return false + } +} + +func parseCapabilityList(s string) ([]string, error) { + s = strings.TrimSuffix(s, "\n") + if s == "" { + return nil, nil + } + + tokens := strings.Fields(s) + if slices.Contains(tokens, "") { + return nil, &ProtocolError{Reason: "empty capability token"} + } + + return tokens, nil +} + +func parseRequestedCapabilities( + tokens []string, + supported Capabilities, + defaultAlgorithm objectid.Algorithm, +) (Capabilities, error) { + var requested Capabilities + + requested.ObjectFormat = defaultAlgorithm + + for _, token := range tokens { + if !supported.supportsToken(token, defaultAlgorithm) { + return Capabilities{}, &ProtocolError{ + Reason: fmt.Sprintf("unsupported capability %q", token), + } + } + + name, value, _ := strings.Cut(token, "=") + switch name { + case "report-status": + requested.ReportStatus = true + case "report-status-v2": + requested.ReportStatusV2 = true + case "delete-refs": + requested.DeleteRefs = true + case "side-band-64k": + requested.SideBand64K = true + case "quiet": + requested.Quiet = true + case "atomic": + requested.Atomic = true + case "ofs-delta": + requested.OfsDelta = true + case "push-options": + requested.PushOptions = true + case "push-cert": + requested.PushCertNonce = value + case "object-format": + algo, _ := objectid.ParseAlgorithm(value) + requested.ObjectFormat = algo + case "session-id": + requested.SessionID = value + case "agent": + requested.Agent = value + } + } + + return requested, nil +} |
