155 lines
3.3 KiB
Go
155 lines
3.3 KiB
Go
package gitnaturalapi
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var NecessaryCapabilities = []string{
|
|
"multi_ack_detailed",
|
|
"side-band-64k",
|
|
}
|
|
|
|
var RequiredCapabilities = []string{
|
|
"shallow",
|
|
"object-format=sha1",
|
|
}
|
|
|
|
var DefaultCapabilities = []string{
|
|
"ofs-delta",
|
|
"no-progress",
|
|
}
|
|
|
|
type MissingRef struct{}
|
|
|
|
func (e *MissingRef) Error() string { return "missing ref" }
|
|
|
|
type InvalidCommit struct {
|
|
Commit string
|
|
}
|
|
|
|
func (e *InvalidCommit) Error() string {
|
|
return fmt.Sprintf("invalid commit '%s', must be 20 byte hex", e.Commit)
|
|
}
|
|
|
|
func FetchPackfile(url string, want string) (*PackfileResult, error) {
|
|
req, err := http.NewRequest("POST", url+"/git-upload-pack", strings.NewReader(want))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create git-upload-pack request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-git-upload-pack-request")
|
|
req.Header.Set("Accept", "application/x-git-upload-pack-result")
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call git-upload-pack: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return nil, fmt.Errorf("failed to call git-upload-pack: %s", string(body))
|
|
}
|
|
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read git-upload-pack response: %w", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("empty response")
|
|
}
|
|
|
|
offset := 0
|
|
for offset < len(data) {
|
|
prev := offset
|
|
if prev+1 >= len(data) {
|
|
break
|
|
}
|
|
nlIdx := bytes.IndexByte(data[prev+1:], '\n')
|
|
if nlIdx == -1 {
|
|
if len(data) >= 32 && string(data[4:32]) == "ERR upload-pack: not our ref" {
|
|
return nil, &MissingRef{}
|
|
}
|
|
end := len(data)
|
|
if end > 63 {
|
|
end = 63
|
|
}
|
|
return nil, fmt.Errorf("unexpected '%s'", string(data[:end]))
|
|
}
|
|
offset = prev + nlIdx + 1
|
|
if offset >= 3 && string(data[offset-3:offset]) == "NAK" {
|
|
break
|
|
}
|
|
}
|
|
offset++
|
|
|
|
var packfileData []byte
|
|
for offset < len(data) {
|
|
if offset+5 > len(data) {
|
|
break
|
|
}
|
|
pktLen, err := strconv.ParseInt(string(data[offset:offset+4]), 16, 32)
|
|
if err != nil {
|
|
break
|
|
}
|
|
length := int(pktLen)
|
|
if length == 0 {
|
|
break
|
|
}
|
|
if offset+length > len(data) {
|
|
break
|
|
}
|
|
if data[offset+4] == 2 {
|
|
// progress message, ignore
|
|
} else if data[offset+4] == 1 {
|
|
packfileData = append(packfileData, data[offset+5:offset+length]...)
|
|
}
|
|
offset += length
|
|
}
|
|
|
|
if len(packfileData) == 0 {
|
|
return nil, &MissingRef{}
|
|
}
|
|
|
|
return ParsePackfile(packfileData)
|
|
}
|
|
|
|
func CreateWantRequest(commitSha string, capabilities []string, deepen *int, filter string) (string, error) {
|
|
if len(commitSha) != 40 {
|
|
return "", &InvalidCommit{Commit: commitSha}
|
|
}
|
|
|
|
var buf strings.Builder
|
|
|
|
wantLine := fmt.Sprintf("want %s %s agent=nsa/1.0.0\n", commitSha, strings.Join(capabilities, " "))
|
|
buf.WriteString(pktEncode(wantLine))
|
|
|
|
if deepen != nil {
|
|
deepenLine := fmt.Sprintf("deepen %d\n", *deepen)
|
|
buf.WriteString(pktEncode(deepenLine))
|
|
}
|
|
|
|
if filter != "" {
|
|
filterLine := fmt.Sprintf("filter %s\n", filter)
|
|
buf.WriteString(pktEncode(filterLine))
|
|
}
|
|
|
|
buf.WriteString("0000")
|
|
buf.WriteString(pktEncode("done\n"))
|
|
|
|
return buf.String(), nil
|
|
}
|
|
|
|
func pktEncode(data string) string {
|
|
if len(data) == 0 {
|
|
return "0000"
|
|
}
|
|
length := len(data) + 4
|
|
return fmt.Sprintf("%04x%s", length, data)
|
|
}
|