Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 5f7d340

Browse files
alcortesmmcuadros
authored andcommitted
Substitute old pktline encoder/decoder with new pktline scanner (#84)
* replace old pktline package with new pktline scanner * remove error checks on pktline.NewFromString * fix deppend bug * reduce memory garbage when pktline.NewFromStrings * improve int to hex conversion to help gc * make intToHex func private * clean function names
1 parent 6c6a37b commit 5f7d340

File tree

13 files changed

+723
-417
lines changed

13 files changed

+723
-417
lines changed

clients/common/common.go

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"strings"
1212

1313
"gopkg.in/src-d/go-git.v4/core"
14-
"gopkg.in/src-d/go-git.v4/formats/pktline"
14+
"gopkg.in/src-d/go-git.v4/formats/packp/pktline"
1515
"gopkg.in/src-d/go-git.v4/storage/memory"
1616
)
1717

@@ -204,8 +204,8 @@ func NewGitUploadPackInfo() *GitUploadPackInfo {
204204
return &GitUploadPackInfo{Capabilities: NewCapabilities()}
205205
}
206206

207-
func (r *GitUploadPackInfo) Decode(d *pktline.Decoder) error {
208-
if err := r.read(d); err != nil {
207+
func (r *GitUploadPackInfo) Decode(s *pktline.Scanner) error {
208+
if err := r.read(s); err != nil {
209209
if err == ErrEmptyGitUploadPack {
210210
return core.NewPermanentError(err)
211211
}
@@ -216,16 +216,29 @@ func (r *GitUploadPackInfo) Decode(d *pktline.Decoder) error {
216216
return nil
217217
}
218218

219-
func (r *GitUploadPackInfo) read(d *pktline.Decoder) error {
220-
lines, err := d.ReadAll()
221-
if err != nil {
222-
return err
223-
}
224-
219+
func (r *GitUploadPackInfo) read(s *pktline.Scanner) error {
225220
isEmpty := true
226221
r.Refs = make(memory.ReferenceStorage, 0)
227-
for _, line := range lines {
228-
if !r.isValidLine(line) {
222+
smartCommentIgnore := false
223+
for s.Scan() {
224+
line := string(s.Bytes())
225+
226+
if smartCommentIgnore {
227+
// some servers like Github add a flush-pkt after the smart http comment
228+
// that we must ignore to prevent a premature termination of the read.
229+
if len(line) == 0 {
230+
continue
231+
}
232+
smartCommentIgnore = false
233+
}
234+
235+
// exit on first flush-pkt
236+
if len(line) == 0 {
237+
break
238+
}
239+
240+
if isSmartHttpComment(line) {
241+
smartCommentIgnore = true
229242
continue
230243
}
231244

@@ -240,11 +253,11 @@ func (r *GitUploadPackInfo) read(d *pktline.Decoder) error {
240253
return ErrEmptyGitUploadPack
241254
}
242255

243-
return nil
256+
return s.Err()
244257
}
245258

246-
func (r *GitUploadPackInfo) isValidLine(line string) bool {
247-
return line[0] != '#'
259+
func isSmartHttpComment(line string) bool {
260+
return line[0] == '#'
248261
}
249262

250263
func (r *GitUploadPackInfo) readLine(line string) error {
@@ -280,21 +293,28 @@ func (r *GitUploadPackInfo) String() string {
280293
}
281294

282295
func (r *GitUploadPackInfo) Bytes() []byte {
283-
e := pktline.NewEncoder()
284-
e.AddLine("# service=git-upload-pack")
285-
e.AddFlush()
286-
e.AddLine(fmt.Sprintf("%s HEAD\x00%s", r.Head().Hash(), r.Capabilities.String()))
296+
payloads := []string{}
297+
payloads = append(payloads, "# service=git-upload-pack\n")
298+
// inserting a flush-pkt here violates the protocol spec, but some
299+
// servers do it, like Github.com
300+
payloads = append(payloads, "")
301+
302+
firstLine := fmt.Sprintf("%s HEAD\x00%s\n", r.Head().Hash(), r.Capabilities.String())
303+
payloads = append(payloads, firstLine)
287304

288305
for _, ref := range r.Refs {
289306
if ref.Type() != core.HashReference {
290307
continue
291308
}
292309

293-
e.AddLine(fmt.Sprintf("%s %s", ref.Hash(), ref.Name()))
310+
ref := fmt.Sprintf("%s %s\n", ref.Hash(), ref.Name())
311+
payloads = append(payloads, ref)
294312
}
295313

296-
e.AddFlush()
297-
b, _ := ioutil.ReadAll(e.Reader())
314+
payloads = append(payloads, "")
315+
pktlines, _ := pktline.NewFromStrings(payloads...)
316+
b, _ := ioutil.ReadAll(pktlines)
317+
298318
return b
299319
}
300320

@@ -318,21 +338,25 @@ func (r *GitUploadPackRequest) String() string {
318338
}
319339

320340
func (r *GitUploadPackRequest) Reader() *strings.Reader {
321-
e := pktline.NewEncoder()
341+
payloads := []string{}
342+
322343
for _, want := range r.Wants {
323-
e.AddLine(fmt.Sprintf("want %s", want))
344+
payloads = append(payloads, fmt.Sprintf("want %s\n", want))
324345
}
325346

326347
for _, have := range r.Haves {
327-
e.AddLine(fmt.Sprintf("have %s", have))
348+
payloads = append(payloads, fmt.Sprintf("have %s\n", have))
328349
}
329350

330351
if r.Depth != 0 {
331-
e.AddLine(fmt.Sprintf("deepen %d", r.Depth))
352+
payloads = append(payloads, fmt.Sprintf("deepen %d\n", r.Depth))
332353
}
333354

334-
e.AddFlush()
335-
e.AddLine("done")
355+
payloads = append(payloads, "")
356+
payloads = append(payloads, "done\n")
357+
358+
pktlines, _ := pktline.NewFromStrings(payloads...)
359+
b, _ := ioutil.ReadAll(pktlines)
336360

337-
return e.Reader()
361+
return strings.NewReader(string(b))
338362
}

clients/common/common_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import (
55
"encoding/base64"
66
"testing"
77

8-
. "gopkg.in/check.v1"
98
"gopkg.in/src-d/go-git.v4/core"
10-
"gopkg.in/src-d/go-git.v4/formats/pktline"
9+
"gopkg.in/src-d/go-git.v4/formats/packp/pktline"
10+
11+
. "gopkg.in/check.v1"
1112
)
1213

1314
func Test(t *testing.T) { TestingT(t) }
@@ -48,7 +49,7 @@ func (s *SuiteCommon) TestGitUploadPackInfo(c *C) {
4849
b, _ := base64.StdEncoding.DecodeString(GitUploadPackInfoFixture)
4950

5051
i := NewGitUploadPackInfo()
51-
err := i.Decode(pktline.NewDecoder(bytes.NewBuffer(b)))
52+
err := i.Decode(pktline.NewScanner(bytes.NewBuffer(b)))
5253
c.Assert(err, IsNil)
5354

5455
name := i.Capabilities.SymbolicReference("HEAD")
@@ -70,7 +71,7 @@ func (s *SuiteCommon) TestGitUploadPackInfoNoHEAD(c *C) {
7071
b, _ := base64.StdEncoding.DecodeString(GitUploadPackInfoNoHEADFixture)
7172

7273
i := NewGitUploadPackInfo()
73-
err := i.Decode(pktline.NewDecoder(bytes.NewBuffer(b)))
74+
err := i.Decode(pktline.NewScanner(bytes.NewBuffer(b)))
7475
c.Assert(err, IsNil)
7576

7677
name := i.Capabilities.SymbolicReference("HEAD")
@@ -86,7 +87,7 @@ func (s *SuiteCommon) TestGitUploadPackInfoEmpty(c *C) {
8687
b := bytes.NewBuffer(nil)
8788

8889
i := NewGitUploadPackInfo()
89-
err := i.Decode(pktline.NewDecoder(b))
90+
err := i.Decode(pktline.NewScanner(b))
9091
c.Assert(err, ErrorMatches, "permanent.*empty.*")
9192
}
9293

clients/http/git_upload_pack.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package http
22

33
import (
44
"bufio"
5+
"bytes"
56
"fmt"
67
"io"
78
"net/http"
89
"strings"
910

1011
"gopkg.in/src-d/go-git.v4/clients/common"
1112
"gopkg.in/src-d/go-git.v4/core"
12-
"gopkg.in/src-d/go-git.v4/formats/pktline"
13+
"gopkg.in/src-d/go-git.v4/formats/packp/pktline"
1314
)
1415

1516
// GitUploadPackService git-upoad-pack service over HTTP
@@ -77,7 +78,7 @@ func (s *GitUploadPackService) Info() (*common.GitUploadPackInfo, error) {
7778
defer res.Body.Close()
7879

7980
i := common.NewGitUploadPackInfo()
80-
return i, i.Decode(pktline.NewDecoder(res.Body))
81+
return i, i.Decode(pktline.NewScanner(res.Body))
8182
}
8283

8384
// Fetch request and returns a reader to a packfile
@@ -101,27 +102,22 @@ func (s *GitUploadPackService) Fetch(r *common.GitUploadPackRequest) (io.ReadClo
101102
return nil, err
102103
}
103104

104-
if err := s.discardResponseInfo(reader); err != nil {
105+
if err := discardResponseInfo(reader); err != nil {
105106
return nil, err
106107
}
107108

108109
return reader, nil
109110
}
110111

111-
func (s *GitUploadPackService) discardResponseInfo(r io.Reader) error {
112-
decoder := pktline.NewDecoder(r)
113-
for {
114-
line, err := decoder.ReadLine()
115-
if err != nil {
116-
break
117-
}
118-
119-
if line == "NAK\n" {
112+
func discardResponseInfo(r io.Reader) error {
113+
s := pktline.NewScanner(r)
114+
for s.Scan() {
115+
if bytes.Equal(s.Bytes(), []byte{'N', 'A', 'K', '\n'}) {
120116
break
121117
}
122118
}
123119

124-
return nil
120+
return s.Err()
125121
}
126122

127123
func (s *GitUploadPackService) doRequest(method, url string, content *strings.Reader) (*http.Response, error) {

clients/ssh/git_upload_pack.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"strings"
1212

1313
"gopkg.in/src-d/go-git.v4/clients/common"
14-
"gopkg.in/src-d/go-git.v4/formats/pktline"
14+
"gopkg.in/src-d/go-git.v4/formats/packp/pktline"
1515

1616
"golang.org/x/crypto/ssh"
1717
)
@@ -123,7 +123,7 @@ func (s *GitUploadPackService) Info() (i *common.GitUploadPackInfo, err error) {
123123
}
124124

125125
i = common.NewGitUploadPackInfo()
126-
return i, i.Decode(pktline.NewDecoder(bytes.NewReader(out)))
126+
return i, i.Decode(pktline.NewScanner(bytes.NewReader(out)))
127127
}
128128

129129
// Disconnect the SSH client.

formats/packp/pktline/pktline.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Package pktline implements reading and creating pkt-lines as per
2+
// https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt.
3+
package pktline
4+
5+
import (
6+
"bytes"
7+
"errors"
8+
"io"
9+
"strings"
10+
)
11+
12+
const (
13+
// MaxPayloadSize is the maximum payload size of a pkt-line in bytes.
14+
MaxPayloadSize = 65516
15+
)
16+
17+
var (
18+
flush = []byte{'0', '0', '0', '0'}
19+
)
20+
21+
// PktLine values represent a succession of pkt-lines.
22+
// Values from this type are not zero-value safe, see the functions New
23+
// and NewFromString below.
24+
type PktLine struct {
25+
io.Reader
26+
}
27+
28+
// ErrPayloadTooLong is returned by New and NewFromString when any of
29+
// the provided payloads is bigger than MaxPayloadSize.
30+
var ErrPayloadTooLong = errors.New("payload is too long")
31+
32+
// New returns the concatenation of several pkt-lines, each of them with
33+
// the payload specified by the contents of each input byte slice. An
34+
// empty payload byte slice will produce a flush-pkt.
35+
func New(payloads ...[]byte) (PktLine, error) {
36+
ret := []io.Reader{}
37+
for _, p := range payloads {
38+
if err := add(&ret, p); err != nil {
39+
return PktLine{}, err
40+
}
41+
}
42+
43+
return PktLine{io.MultiReader(ret...)}, nil
44+
}
45+
46+
func add(dst *[]io.Reader, e []byte) error {
47+
if len(e) > MaxPayloadSize {
48+
return ErrPayloadTooLong
49+
}
50+
51+
if len(e) == 0 {
52+
*dst = append(*dst, bytes.NewReader(flush))
53+
return nil
54+
}
55+
56+
n := len(e) + 4
57+
*dst = append(*dst, bytes.NewReader(int16ToHex(n)))
58+
*dst = append(*dst, bytes.NewReader(e))
59+
60+
return nil
61+
}
62+
63+
// susbtitutes fmt.Sprintf("%04x", n) to avoid memory garbage
64+
// generation.
65+
func int16ToHex(n int) []byte {
66+
var ret [4]byte
67+
ret[0] = byteToAsciiHex(byte(n & 0xf000 >> 12))
68+
ret[1] = byteToAsciiHex(byte(n & 0x0f00 >> 8))
69+
ret[2] = byteToAsciiHex(byte(n & 0x00f0 >> 4))
70+
ret[3] = byteToAsciiHex(byte(n & 0x000f))
71+
72+
return ret[:]
73+
}
74+
75+
// turns a byte into its hexadecimal ascii representation. Example:
76+
// from 11 (0xb) into 'b'.
77+
func byteToAsciiHex(n byte) byte {
78+
if n < 10 {
79+
return byte('0' + n)
80+
}
81+
82+
return byte('a' - 10 + n)
83+
}
84+
85+
// NewFromStrings returns the concatenation of several pkt-lines, each
86+
// of them with the payload specified by the contents of each input
87+
// string. An empty payload string will produce a flush-pkt.
88+
func NewFromStrings(payloads ...string) (PktLine, error) {
89+
ret := []io.Reader{}
90+
for _, p := range payloads {
91+
if err := addString(&ret, p); err != nil {
92+
return PktLine{}, err
93+
}
94+
}
95+
96+
return PktLine{io.MultiReader(ret...)}, nil
97+
}
98+
99+
func addString(dst *[]io.Reader, s string) error {
100+
if len(s) > MaxPayloadSize {
101+
return ErrPayloadTooLong
102+
}
103+
104+
if len(s) == 0 {
105+
*dst = append(*dst, bytes.NewReader(flush))
106+
return nil
107+
}
108+
109+
n := len(s) + 4
110+
*dst = append(*dst, bytes.NewReader(int16ToHex(n)))
111+
*dst = append(*dst, strings.NewReader(s))
112+
113+
return nil
114+
}

0 commit comments

Comments
 (0)