Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 14eabbd

Browse files
committed
plumbing: transport, support multi-ack and multi-ack-detailed capabilities
This adds support for multi-ack and multi-ack-detailed capabilities in the transport package. The support includes both the client and server. Related: go-git@2ebd65f Related: go-git#1204
1 parent 8e38c94 commit 14eabbd

File tree

9 files changed

+369
-139
lines changed

9 files changed

+369
-139
lines changed

internal/reference/refs.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package reference
2+
3+
import (
4+
"io"
5+
6+
"github.com/go-git/go-git/v5/plumbing"
7+
"github.com/go-git/go-git/v5/storage"
8+
)
9+
10+
// References returns all references from the storage.
11+
func References(st storage.Storer) ([]*plumbing.Reference, error) {
12+
var localRefs []*plumbing.Reference
13+
14+
iter, err := st.IterReferences()
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
for {
20+
ref, err := iter.Next()
21+
if err == io.EOF {
22+
break
23+
}
24+
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
localRefs = append(localRefs, ref)
30+
}
31+
32+
return localRefs, nil
33+
}

plumbing/protocol/packp/packp.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package packp
2+
3+
import "io"
4+
5+
// Encoder is the interface implemented by an object that can encode itself
6+
// into a [io.Writer].
7+
type Encoder interface {
8+
Encode(w io.Writer) error
9+
}
10+
11+
// Decoder is the interface implemented by an object that can decode itself
12+
// from a [io.Reader].
13+
type Decoder interface {
14+
Decode(r io.Reader) error
15+
}

plumbing/protocol/packp/srvresp.go

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"strings"
89

910
"github.com/go-git/go-git/v5/plumbing"
1011
"github.com/go-git/go-git/v5/plumbing/format/pktline"
@@ -13,25 +14,52 @@ import (
1314
const ackLineLen = 44
1415

1516
// ServerResponse object acknowledgement from upload-pack service
16-
// TODO: support multi_ack and multi_ack_detailed capabilities
1717
type ServerResponse struct {
18-
ACKs []plumbing.Hash
18+
ACKs []ACK
1919
}
2020

21-
// Decode decodes the response into the struct, isMultiACK should be true, if
22-
// the request was done with multi_ack or multi_ack_detailed capabilities.
21+
// ACKStatus represents the status of an object acknowledgement.
22+
type ACKStatus byte
23+
24+
// String returns the string representation of the ACKStatus.
25+
func (s ACKStatus) String() string {
26+
switch s {
27+
case ACKContinue:
28+
return "continue"
29+
case ACKCommon:
30+
return "common"
31+
case ACKReady:
32+
return "ready"
33+
}
34+
35+
return ""
36+
}
37+
38+
// ACKStatus values
39+
const (
40+
ACKContinue ACKStatus = iota + 1
41+
ACKCommon
42+
ACKReady
43+
)
44+
45+
// ACK represents an object acknowledgement. A status can be zero when the
46+
// response doesn't support multi_ack and multi_ack_detailed capabilities.
47+
type ACK struct {
48+
Hash plumbing.Hash
49+
Status ACKStatus
50+
}
51+
52+
// Decode decodes the response into the struct.
2353
func (r *ServerResponse) Decode(reader io.Reader) error {
2454
var err error
25-
for {
55+
for err == nil {
2656
var p []byte
2757
_, p, err = pktline.ReadLine(reader)
2858
if err != nil {
2959
break
3060
}
3161

32-
if err := r.decodeLine(p); err != nil {
33-
return err
34-
}
62+
err = r.decodeLine(p)
3563
}
3664

3765
if errors.Is(err, io.EOF) {
@@ -52,31 +80,71 @@ func (r *ServerResponse) decodeLine(line []byte) error {
5280
}
5381

5482
if bytes.Equal(line[0:3], nak) {
55-
return nil
83+
return io.EOF
5684
}
5785
}
5886

5987
return fmt.Errorf("unexpected content %q", string(line))
6088
}
6189

62-
func (r *ServerResponse) decodeACKLine(line []byte) error {
63-
if len(line) < ackLineLen {
90+
func (r *ServerResponse) decodeACKLine(line []byte) (err error) {
91+
parts := bytes.Split(line, []byte(" "))
92+
if len(line) < ackLineLen || len(parts) < 2 {
6493
return fmt.Errorf("malformed ACK %q", line)
6594
}
6695

67-
sp := bytes.Index(line, []byte(" "))
68-
h := plumbing.NewHash(string(line[sp+1 : sp+41]))
69-
r.ACKs = append(r.ACKs, h)
70-
return nil
96+
var ack ACK
97+
// TODO: Dynamic hash size and sha256 support
98+
ack.Hash = plumbing.NewHash(string(parts[1]))
99+
err = io.EOF
100+
101+
if len(parts) > 2 {
102+
err = nil
103+
switch status := strings.TrimSpace(string(parts[2])); status {
104+
case "continue":
105+
ack.Status = ACKContinue
106+
case "common":
107+
ack.Status = ACKCommon
108+
case "ready":
109+
ack.Status = ACKReady
110+
}
111+
}
112+
113+
r.ACKs = append(r.ACKs, ack)
114+
return
71115
}
72116

73117
// Encode encodes the ServerResponse into a writer.
74118
func (r *ServerResponse) Encode(w io.Writer) error {
75-
if len(r.ACKs) == 0 {
119+
return encodeServerResponse(w, r.ACKs)
120+
}
121+
122+
// encodeServerResponse encodes the ServerResponse into a writer.
123+
func encodeServerResponse(w io.Writer, acks []ACK) error {
124+
if len(acks) == 0 {
76125
_, err := pktline.WriteString(w, string(nak)+"\n")
77126
return err
78127
}
79128

80-
_, err := pktline.Writef(w, "%s %s\n", ack, r.ACKs[0].String())
81-
return err
129+
var multiAck bool
130+
for _, a := range acks {
131+
var err error
132+
if a.Status > 0 {
133+
_, err = pktline.Writef(w, "%s %s %s\n", ack, a.Hash, a.Status)
134+
if !multiAck {
135+
multiAck = true
136+
}
137+
} else {
138+
_, err = pktline.Writef(w, "%s %s\n", ack, acks[0].Hash)
139+
}
140+
if err != nil {
141+
return err
142+
}
143+
144+
if !multiAck {
145+
break
146+
}
147+
}
148+
149+
return nil
82150
}

plumbing/protocol/packp/uphav.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ import (
44
"bytes"
55
"fmt"
66
"io"
7+
"strings"
78

89
"github.com/go-git/go-git/v5/plumbing"
910
"github.com/go-git/go-git/v5/plumbing/format/pktline"
1011
)
1112

1213
// UploadHaves is a message to signal the references that a client has in a
13-
// upload-pack. Do not use this directly. Use UploadPackRequest request instead.
14+
// upload-pack. Done is true when the client has sent a "done" message.
15+
// Otherwise, it means that the client has more haves to send and this request
16+
// was completed with a flush.
1417
type UploadHaves struct {
1518
Haves []plumbing.Hash
19+
Done bool
1620
}
1721

1822
// Encode encodes the UploadHaves into the Writer.
@@ -32,5 +36,48 @@ func (u *UploadHaves) Encode(w io.Writer) error {
3236
last = have
3337
}
3438

39+
if u.Done {
40+
if _, err := pktline.Writeln(w, "done"); err != nil {
41+
return fmt.Errorf("sending done: %s", err)
42+
}
43+
} else {
44+
if err := pktline.WriteFlush(w); err != nil {
45+
return fmt.Errorf("sending flush-pkt: %s", err)
46+
}
47+
}
48+
return nil
49+
}
50+
51+
// Decode decodes the UploadHaves from the Reader.
52+
func (u *UploadHaves) Decode(r io.Reader) error {
53+
u.Haves = make([]plumbing.Hash, 0)
54+
55+
for {
56+
l, line, err := pktline.ReadLine(r)
57+
if err != nil {
58+
if err == io.EOF {
59+
break
60+
}
61+
62+
return fmt.Errorf("decoding haves: %s", err)
63+
}
64+
65+
if l == pktline.Flush {
66+
break
67+
}
68+
69+
if bytes.Equal(line, []byte("done\n")) {
70+
u.Done = true
71+
break
72+
}
73+
74+
if !bytes.HasPrefix(line, []byte("have ")) {
75+
return fmt.Errorf("invalid have line: %q", line)
76+
}
77+
78+
have := plumbing.NewHash(strings.TrimSpace(string(line[5:])))
79+
u.Haves = append(u.Haves, have)
80+
}
81+
3582
return nil
3683
}

0 commit comments

Comments
 (0)