aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/format/packp/advrefs
diff options
context:
space:
mode:
authorMáximo Cuadros <mcuadros@gmail.com>2016-11-08 23:46:38 +0100
committerGitHub <noreply@github.com>2016-11-08 23:46:38 +0100
commitac095bb12c4d29722b60ba9f20590fa7cfa6bc7d (patch)
tree223f36f336ba3414b1e45cac8af6c4744a5d7ef6 /plumbing/format/packp/advrefs
parente523701393598f4fa241dd407af9ff8925507a1a (diff)
downloadgo-git-ac095bb12c4d29722b60ba9f20590fa7cfa6bc7d.tar.gz
new plumbing package (#118)
* plumbing: now core was renamed to core, and formats and clients moved inside
Diffstat (limited to 'plumbing/format/packp/advrefs')
-rw-r--r--plumbing/format/packp/advrefs/advrefs.go58
-rw-r--r--plumbing/format/packp/advrefs/advrefs_test.go315
-rw-r--r--plumbing/format/packp/advrefs/decoder.go288
-rw-r--r--plumbing/format/packp/advrefs/decoder_test.go500
-rw-r--r--plumbing/format/packp/advrefs/encoder.go155
-rw-r--r--plumbing/format/packp/advrefs/encoder_test.go249
6 files changed, 1565 insertions, 0 deletions
diff --git a/plumbing/format/packp/advrefs/advrefs.go b/plumbing/format/packp/advrefs/advrefs.go
new file mode 100644
index 0000000..4d7c897
--- /dev/null
+++ b/plumbing/format/packp/advrefs/advrefs.go
@@ -0,0 +1,58 @@
+// Package advrefs implements encoding and decoding advertised-refs
+// messages from a git-upload-pack command.
+package advrefs
+
+import (
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp"
+)
+
+const (
+ hashSize = 40
+ head = "HEAD"
+ noHead = "capabilities^{}"
+)
+
+var (
+ sp = []byte(" ")
+ null = []byte("\x00")
+ eol = []byte("\n")
+ peeled = []byte("^{}")
+ shallow = []byte("shallow ")
+ noHeadMark = []byte(" capabilities^{}\x00")
+)
+
+// AdvRefs values represent the information transmitted on an
+// advertised-refs message. Values from this type are not zero-value
+// safe, use the New function instead.
+//
+// When using this messages over (smart) HTTP, you have to add a pktline
+// before the whole thing with the following payload:
+//
+// '# service=$servicename" LF
+//
+// Moreover, some (all) git HTTP smart servers will send a flush-pkt
+// just after the first pkt-line.
+//
+// To accomodate both situations, the Prefix field allow you to store
+// any data you want to send before the actual pktlines. It will also
+// be filled up with whatever is found on the line.
+type AdvRefs struct {
+ Prefix [][]byte // payloads of the prefix
+ Head *plumbing.Hash
+ Capabilities *packp.Capabilities
+ References map[string]plumbing.Hash
+ Peeled map[string]plumbing.Hash
+ Shallows []plumbing.Hash
+}
+
+// New returns a pointer to a new AdvRefs value, ready to be used.
+func New() *AdvRefs {
+ return &AdvRefs{
+ Prefix: [][]byte{},
+ Capabilities: packp.NewCapabilities(),
+ References: make(map[string]plumbing.Hash),
+ Peeled: make(map[string]plumbing.Hash),
+ Shallows: []plumbing.Hash{},
+ }
+}
diff --git a/plumbing/format/packp/advrefs/advrefs_test.go b/plumbing/format/packp/advrefs/advrefs_test.go
new file mode 100644
index 0000000..2639b6e
--- /dev/null
+++ b/plumbing/format/packp/advrefs/advrefs_test.go
@@ -0,0 +1,315 @@
+package advrefs_test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+ "testing"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/advrefs"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/pktline"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type SuiteDecodeEncode struct{}
+
+var _ = Suite(&SuiteDecodeEncode{})
+
+func (s *SuiteDecodeEncode) test(c *C, in []string, exp []string) {
+ var err error
+ var input io.Reader
+ {
+ var buf bytes.Buffer
+ p := pktline.NewEncoder(&buf)
+ err = p.EncodeString(in...)
+ c.Assert(err, IsNil)
+ input = &buf
+ }
+
+ var expected []byte
+ {
+ var buf bytes.Buffer
+ p := pktline.NewEncoder(&buf)
+ err = p.EncodeString(exp...)
+ c.Assert(err, IsNil)
+
+ expected = buf.Bytes()
+ }
+
+ var obtained []byte
+ {
+ ar := advrefs.New()
+ d := advrefs.NewDecoder(input)
+ err = d.Decode(ar)
+ c.Assert(err, IsNil)
+
+ var buf bytes.Buffer
+ e := advrefs.NewEncoder(&buf)
+ err := e.Encode(ar)
+ c.Assert(err, IsNil)
+
+ obtained = buf.Bytes()
+ }
+
+ c.Assert(obtained, DeepEquals, expected,
+ Commentf("input = %v\nobtained = %q\nexpected = %q\n",
+ in, string(obtained), string(expected)))
+}
+
+func (s *SuiteDecodeEncode) TestNoHead(c *C) {
+ input := []string{
+ "0000000000000000000000000000000000000000 capabilities^{}\x00",
+ pktline.FlushString,
+ }
+
+ expected := []string{
+ "0000000000000000000000000000000000000000 capabilities^{}\x00\n",
+ pktline.FlushString,
+ }
+
+ s.test(c, input, expected)
+}
+
+func (s *SuiteDecodeEncode) TestNoHeadSmart(c *C) {
+ input := []string{
+ "# service=git-upload-pack\n",
+ "0000000000000000000000000000000000000000 capabilities^{}\x00",
+ pktline.FlushString,
+ }
+
+ expected := []string{
+ "# service=git-upload-pack\n",
+ "0000000000000000000000000000000000000000 capabilities^{}\x00\n",
+ pktline.FlushString,
+ }
+
+ s.test(c, input, expected)
+}
+
+func (s *SuiteDecodeEncode) TestNoHeadSmartBug(c *C) {
+ input := []string{
+ "# service=git-upload-pack\n",
+ pktline.FlushString,
+ "0000000000000000000000000000000000000000 capabilities^{}\x00\n",
+ pktline.FlushString,
+ }
+
+ expected := []string{
+ "# service=git-upload-pack\n",
+ pktline.FlushString,
+ "0000000000000000000000000000000000000000 capabilities^{}\x00\n",
+ pktline.FlushString,
+ }
+
+ s.test(c, input, expected)
+}
+
+func (s *SuiteDecodeEncode) TestRefs(c *C) {
+ input := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:/refs/heads/master ofs-delta multi_ack",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree",
+ pktline.FlushString,
+ }
+
+ expected := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ pktline.FlushString,
+ }
+
+ s.test(c, input, expected)
+}
+
+func (s *SuiteDecodeEncode) TestPeeled(c *C) {
+ input := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:/refs/heads/master ofs-delta multi_ack",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ "8888888888888888888888888888888888888888 refs/tags/v2.6.12-tree^{}",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ pktline.FlushString,
+ }
+
+ expected := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ "8888888888888888888888888888888888888888 refs/tags/v2.6.12-tree^{}\n",
+ pktline.FlushString,
+ }
+
+ s.test(c, input, expected)
+}
+
+func (s *SuiteDecodeEncode) TestAll(c *C) {
+ input := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:/refs/heads/master ofs-delta multi_ack\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ "8888888888888888888888888888888888888888 refs/tags/v2.6.12-tree^{}",
+ "shallow 1111111111111111111111111111111111111111",
+ "shallow 2222222222222222222222222222222222222222\n",
+ pktline.FlushString,
+ }
+
+ expected := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ "8888888888888888888888888888888888888888 refs/tags/v2.6.12-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222\n",
+ pktline.FlushString,
+ }
+
+ s.test(c, input, expected)
+}
+
+func (s *SuiteDecodeEncode) TestAllSmart(c *C) {
+ input := []string{
+ "# service=git-upload-pack\n",
+ pktline.FlushString,
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:/refs/heads/master ofs-delta multi_ack\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ "8888888888888888888888888888888888888888 refs/tags/v2.6.12-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222\n",
+ pktline.FlushString,
+ }
+
+ expected := []string{
+ "# service=git-upload-pack\n",
+ pktline.FlushString,
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ "8888888888888888888888888888888888888888 refs/tags/v2.6.12-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222\n",
+ pktline.FlushString,
+ }
+
+ s.test(c, input, expected)
+}
+
+func (s *SuiteDecodeEncode) TestAllSmartBug(c *C) {
+ input := []string{
+ "# service=git-upload-pack\n",
+ pktline.FlushString,
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:/refs/heads/master ofs-delta multi_ack\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ "8888888888888888888888888888888888888888 refs/tags/v2.6.12-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222\n",
+ pktline.FlushString,
+ }
+
+ expected := []string{
+ "# service=git-upload-pack\n",
+ pktline.FlushString,
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "7777777777777777777777777777777777777777 refs/tags/v2.6.12-tree\n",
+ "8888888888888888888888888888888888888888 refs/tags/v2.6.12-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222\n",
+ pktline.FlushString,
+ }
+
+ s.test(c, input, expected)
+}
+
+func ExampleDecoder_Decode() {
+ // Here is a raw advertised-ref message.
+ raw := "" +
+ "0065a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n" +
+ "003fa6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n" +
+ "00441111111111111111111111111111111111111111 refs/tags/v2.6.11-tree\n" +
+ "00475555555555555555555555555555555555555555 refs/tags/v2.6.11-tree^{}\n" +
+ "0035shallow 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c\n" +
+ "0000"
+
+ // Use the raw message as our input.
+ input := strings.NewReader(raw)
+
+ // Create a advref.Decoder reading from our input.
+ d := advrefs.NewDecoder(input)
+
+ // Decode the input into a newly allocated AdvRefs value.
+ ar := advrefs.New()
+ _ = d.Decode(ar) // error check ignored for brevity
+
+ // Do something interesting with the AdvRefs, e.g. print its contents.
+ fmt.Println("head =", ar.Head)
+ fmt.Println("capabilities =", ar.Capabilities.String())
+ fmt.Println("...")
+ fmt.Println("shallows =", ar.Shallows)
+ // Output: head = a6930aaee06755d1bdcfd943fbf614e4d92bb0c7
+ // capabilities = multi_ack ofs-delta symref=HEAD:/refs/heads/master
+ // ...
+ // shallows = [5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c]
+}
+
+func ExampleEncoder_Encode() {
+ // Create an AdvRefs with the contents you want...
+ ar := advrefs.New()
+
+ // ...add a hash for the HEAD...
+ head := plumbing.NewHash("1111111111111111111111111111111111111111")
+ ar.Head = &head
+
+ // ...add some server capabilities...
+ ar.Capabilities.Add("symref", "HEAD:/refs/heads/master")
+ ar.Capabilities.Add("ofs-delta")
+ ar.Capabilities.Add("multi_ack")
+
+ // ...add a couple of references...
+ ar.References["refs/heads/master"] = plumbing.NewHash("2222222222222222222222222222222222222222")
+ ar.References["refs/tags/v1"] = plumbing.NewHash("3333333333333333333333333333333333333333")
+
+ // ...including a peeled ref...
+ ar.Peeled["refs/tags/v1"] = plumbing.NewHash("4444444444444444444444444444444444444444")
+
+ // ...and finally add a shallow
+ ar.Shallows = append(ar.Shallows, plumbing.NewHash("5555555555555555555555555555555555555555"))
+
+ // Encode the advrefs.Contents to a bytes.Buffer.
+ // You can encode into stdout too, but you will not be able
+ // see the '\x00' after "HEAD".
+ var buf bytes.Buffer
+ e := advrefs.NewEncoder(&buf)
+ _ = e.Encode(ar) // error checks ignored for brevity
+
+ // Print the contents of the buffer as a quoted string.
+ // Printing is as a non-quoted string will be prettier but you
+ // will miss the '\x00' after "HEAD".
+ fmt.Printf("%q", buf.String())
+ // Output:
+ // "00651111111111111111111111111111111111111111 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n003f2222222222222222222222222222222222222222 refs/heads/master\n003a3333333333333333333333333333333333333333 refs/tags/v1\n003d4444444444444444444444444444444444444444 refs/tags/v1^{}\n0035shallow 5555555555555555555555555555555555555555\n0000"
+}
diff --git a/plumbing/format/packp/advrefs/decoder.go b/plumbing/format/packp/advrefs/decoder.go
new file mode 100644
index 0000000..b654882
--- /dev/null
+++ b/plumbing/format/packp/advrefs/decoder.go
@@ -0,0 +1,288 @@
+package advrefs
+
+import (
+ "bytes"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/pktline"
+)
+
+// A Decoder reads and decodes AdvRef values from an input stream.
+type Decoder struct {
+ s *pktline.Scanner // a pkt-line scanner from the input stream
+ line []byte // current pkt-line contents, use parser.nextLine() to make it advance
+ nLine int // current pkt-line number for debugging, begins at 1
+ hash plumbing.Hash // last hash read
+ err error // sticky error, use the parser.error() method to fill this out
+ data *AdvRefs // parsed data is stored here
+}
+
+// ErrEmpty is returned by Decode when there was no advertised-message at all
+var ErrEmpty = errors.New("empty advertised-ref message")
+
+// NewDecoder returns a new decoder that reads from r.
+//
+// Will not read more data from r than necessary.
+func NewDecoder(r io.Reader) *Decoder {
+ return &Decoder{
+ s: pktline.NewScanner(r),
+ }
+}
+
+// Decode reads the next advertised-refs message form its input and
+// stores it in the value pointed to by v.
+func (d *Decoder) Decode(v *AdvRefs) error {
+ d.data = v
+
+ for state := decodePrefix; state != nil; {
+ state = state(d)
+ }
+
+ return d.err
+}
+
+type decoderStateFn func(*Decoder) decoderStateFn
+
+// fills out the parser stiky error
+func (d *Decoder) error(format string, a ...interface{}) {
+ d.err = fmt.Errorf("pkt-line %d: %s", d.nLine,
+ fmt.Sprintf(format, a...))
+}
+
+// Reads a new pkt-line from the scanner, makes its payload available as
+// p.line and increments p.nLine. A successful invocation returns true,
+// otherwise, false is returned and the sticky error is filled out
+// accordingly. Trims eols at the end of the payloads.
+func (d *Decoder) nextLine() bool {
+ d.nLine++
+
+ if !d.s.Scan() {
+ if d.err = d.s.Err(); d.err != nil {
+ return false
+ }
+
+ if d.nLine == 1 {
+ d.err = ErrEmpty
+ return false
+ }
+
+ d.error("EOF")
+ return false
+ }
+
+ d.line = d.s.Bytes()
+ d.line = bytes.TrimSuffix(d.line, eol)
+
+ return true
+}
+
+// The HTTP smart prefix is often followed by a flush-pkt.
+func decodePrefix(d *Decoder) decoderStateFn {
+ if ok := d.nextLine(); !ok {
+ return nil
+ }
+
+ if isPrefix(d.line) {
+ tmp := make([]byte, len(d.line))
+ copy(tmp, d.line)
+ d.data.Prefix = append(d.data.Prefix, tmp)
+ if ok := d.nextLine(); !ok {
+ return nil
+ }
+ }
+
+ if isFlush(d.line) {
+ d.data.Prefix = append(d.data.Prefix, pktline.Flush)
+ if ok := d.nextLine(); !ok {
+ return nil
+ }
+ }
+
+ return decodeFirstHash
+}
+
+func isPrefix(payload []byte) bool {
+ return payload[0] == '#'
+}
+
+func isFlush(payload []byte) bool {
+ return len(payload) == 0
+}
+
+// If the first hash is zero, then a no-refs is comming. Otherwise, a
+// list-of-refs is comming, and the hash will be followed by the first
+// advertised ref.
+func decodeFirstHash(p *Decoder) decoderStateFn {
+ if len(p.line) < hashSize {
+ p.error("cannot read hash, pkt-line too short")
+ return nil
+ }
+
+ if _, err := hex.Decode(p.hash[:], p.line[:hashSize]); err != nil {
+ p.error("invalid hash text: %s", err)
+ return nil
+ }
+
+ p.line = p.line[hashSize:]
+
+ if p.hash.IsZero() {
+ return decodeSkipNoRefs
+ }
+
+ return decodeFirstRef
+}
+
+// Skips SP "capabilities^{}" NUL
+func decodeSkipNoRefs(p *Decoder) decoderStateFn {
+ if len(p.line) < len(noHeadMark) {
+ p.error("too short zero-id ref")
+ return nil
+ }
+
+ if !bytes.HasPrefix(p.line, noHeadMark) {
+ p.error("malformed zero-id ref")
+ return nil
+ }
+
+ p.line = p.line[len(noHeadMark):]
+
+ return decodeCaps
+}
+
+// decode the refname, expectes SP refname NULL
+func decodeFirstRef(l *Decoder) decoderStateFn {
+ if len(l.line) < 3 {
+ l.error("line too short after hash")
+ return nil
+ }
+
+ if !bytes.HasPrefix(l.line, sp) {
+ l.error("no space after hash")
+ return nil
+ }
+ l.line = l.line[1:]
+
+ chunks := bytes.SplitN(l.line, null, 2)
+ if len(chunks) < 2 {
+ l.error("NULL not found")
+ return nil
+ }
+ ref := chunks[0]
+ l.line = chunks[1]
+
+ if bytes.Equal(ref, []byte(head)) {
+ l.data.Head = &l.hash
+ } else {
+ l.data.References[string(ref)] = l.hash
+ }
+
+ return decodeCaps
+}
+
+func decodeCaps(p *Decoder) decoderStateFn {
+ if len(p.line) == 0 {
+ return decodeOtherRefs
+ }
+
+ for _, c := range bytes.Split(p.line, sp) {
+ name, values := readCapability(c)
+ p.data.Capabilities.Add(name, values...)
+ }
+
+ return decodeOtherRefs
+}
+
+// Capabilities are a single string or a name=value.
+// Even though we are only going to read at moust 1 value, we return
+// a slice of values, as Capability.Add receives that.
+func readCapability(data []byte) (name string, values []string) {
+ pair := bytes.SplitN(data, []byte{'='}, 2)
+ if len(pair) == 2 {
+ values = append(values, string(pair[1]))
+ }
+
+ return string(pair[0]), values
+}
+
+// The refs are either tips (obj-id SP refname) or a peeled (obj-id SP refname^{}).
+// If there are no refs, then there might be a shallow or flush-ptk.
+func decodeOtherRefs(p *Decoder) decoderStateFn {
+ if ok := p.nextLine(); !ok {
+ return nil
+ }
+
+ if bytes.HasPrefix(p.line, shallow) {
+ return decodeShallow
+ }
+
+ if len(p.line) == 0 {
+ return nil
+ }
+
+ saveTo := p.data.References
+ if bytes.HasSuffix(p.line, peeled) {
+ p.line = bytes.TrimSuffix(p.line, peeled)
+ saveTo = p.data.Peeled
+ }
+
+ ref, hash, err := readRef(p.line)
+ if err != nil {
+ p.error("%s", err)
+ return nil
+ }
+ saveTo[ref] = hash
+
+ return decodeOtherRefs
+}
+
+// Reads a ref-name
+func readRef(data []byte) (string, plumbing.Hash, error) {
+ chunks := bytes.Split(data, sp)
+ switch {
+ case len(chunks) == 1:
+ return "", plumbing.ZeroHash, fmt.Errorf("malformed ref data: no space was found")
+ case len(chunks) > 2:
+ return "", plumbing.ZeroHash, fmt.Errorf("malformed ref data: more than one space found")
+ default:
+ return string(chunks[1]), plumbing.NewHash(string(chunks[0])), nil
+ }
+}
+
+// Keeps reading shallows until a flush-pkt is found
+func decodeShallow(p *Decoder) decoderStateFn {
+ if !bytes.HasPrefix(p.line, shallow) {
+ p.error("malformed shallow prefix, found %q... instead", p.line[:len(shallow)])
+ return nil
+ }
+ p.line = bytes.TrimPrefix(p.line, shallow)
+
+ if len(p.line) != hashSize {
+ p.error(fmt.Sprintf(
+ "malformed shallow hash: wrong length, expected 40 bytes, read %d bytes",
+ len(p.line)))
+ return nil
+ }
+
+ text := p.line[:hashSize]
+ var h plumbing.Hash
+ if _, err := hex.Decode(h[:], text); err != nil {
+ p.error("invalid hash text: %s", err)
+ return nil
+ }
+
+ p.data.Shallows = append(p.data.Shallows, h)
+
+ if ok := p.nextLine(); !ok {
+ return nil
+ }
+
+ if len(p.line) == 0 {
+ return nil // succesfull parse of the advertised-refs message
+ }
+
+ return decodeShallow
+}
diff --git a/plumbing/format/packp/advrefs/decoder_test.go b/plumbing/format/packp/advrefs/decoder_test.go
new file mode 100644
index 0000000..03867d3
--- /dev/null
+++ b/plumbing/format/packp/advrefs/decoder_test.go
@@ -0,0 +1,500 @@
+package advrefs_test
+
+import (
+ "bytes"
+ "io"
+ "strings"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/advrefs"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/pktline"
+
+ . "gopkg.in/check.v1"
+)
+
+type SuiteDecoder struct{}
+
+var _ = Suite(&SuiteDecoder{})
+
+func (s *SuiteDecoder) TestEmpty(c *C) {
+ ar := advrefs.New()
+ var buf bytes.Buffer
+ d := advrefs.NewDecoder(&buf)
+
+ err := d.Decode(ar)
+ c.Assert(err, Equals, advrefs.ErrEmpty)
+}
+
+func (s *SuiteDecoder) TestShortForHash(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*too short")
+}
+
+func toPktLines(c *C, payloads []string) io.Reader {
+ var buf bytes.Buffer
+ e := pktline.NewEncoder(&buf)
+ err := e.EncodeString(payloads...)
+ c.Assert(err, IsNil)
+
+ return &buf
+}
+
+func testDecoderErrorMatches(c *C, input io.Reader, pattern string) {
+ ar := advrefs.New()
+ d := advrefs.NewDecoder(input)
+
+ err := d.Decode(ar)
+ c.Assert(err, ErrorMatches, pattern)
+}
+
+func (s *SuiteDecoder) TestInvalidFirstHash(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796alberto2219af86ec6584e5 HEAD\x00multi_ack thin-pack\n",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*invalid hash.*")
+}
+
+func (s *SuiteDecoder) TestZeroId(c *C) {
+ payloads := []string{
+ "0000000000000000000000000000000000000000 capabilities^{}\x00multi_ack thin-pack\n",
+ pktline.FlushString,
+ }
+ ar := testDecodeOK(c, payloads)
+ c.Assert(ar.Head, IsNil)
+}
+
+func testDecodeOK(c *C, payloads []string) *advrefs.AdvRefs {
+ var buf bytes.Buffer
+ e := pktline.NewEncoder(&buf)
+ err := e.EncodeString(payloads...)
+ c.Assert(err, IsNil)
+
+ ar := advrefs.New()
+ d := advrefs.NewDecoder(&buf)
+
+ err = d.Decode(ar)
+ c.Assert(err, IsNil)
+
+ return ar
+}
+
+func (s *SuiteDecoder) TestMalformedZeroId(c *C) {
+ payloads := []string{
+ "0000000000000000000000000000000000000000 wrong\x00multi_ack thin-pack\n",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*malformed zero-id.*")
+}
+
+func (s *SuiteDecoder) TestShortZeroId(c *C) {
+ payloads := []string{
+ "0000000000000000000000000000000000000000 capabi",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*too short zero-id.*")
+}
+
+func (s *SuiteDecoder) TestHead(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00",
+ pktline.FlushString,
+ }
+ ar := testDecodeOK(c, payloads)
+ c.Assert(*ar.Head, Equals,
+ plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
+}
+
+func (s *SuiteDecoder) TestFirstIsNotHead(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 refs/heads/master\x00",
+ pktline.FlushString,
+ }
+ ar := testDecodeOK(c, payloads)
+ c.Assert(ar.Head, IsNil)
+ c.Assert(ar.References["refs/heads/master"], Equals,
+ plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
+}
+
+func (s *SuiteDecoder) TestShortRef(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 H",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*too short.*")
+}
+
+func (s *SuiteDecoder) TestNoNULL(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEADofs-delta multi_ack",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*NULL not found.*")
+}
+
+func (s *SuiteDecoder) TestNoSpaceAfterHash(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5-HEAD\x00",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*no space after hash.*")
+}
+
+func (s *SuiteDecoder) TestNoCaps(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00",
+ pktline.FlushString,
+ }
+ ar := testDecodeOK(c, payloads)
+ c.Assert(ar.Capabilities.IsEmpty(), Equals, true)
+}
+
+func (s *SuiteDecoder) TestCaps(c *C) {
+ for _, test := range [...]struct {
+ input []string
+ capabilities []packp.Capability
+ }{
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00",
+ pktline.FlushString,
+ },
+ capabilities: []packp.Capability{},
+ },
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00\n",
+ pktline.FlushString,
+ },
+ capabilities: []packp.Capability{},
+ },
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta",
+ pktline.FlushString,
+ },
+ capabilities: []packp.Capability{
+ {
+ Name: "ofs-delta",
+ Values: []string(nil),
+ },
+ },
+ },
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta multi_ack",
+ pktline.FlushString,
+ },
+ capabilities: []packp.Capability{
+ {Name: "ofs-delta", Values: []string(nil)},
+ {Name: "multi_ack", Values: []string(nil)},
+ },
+ },
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta multi_ack\n",
+ pktline.FlushString,
+ },
+ capabilities: []packp.Capability{
+ {Name: "ofs-delta", Values: []string(nil)},
+ {Name: "multi_ack", Values: []string(nil)},
+ },
+ },
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:refs/heads/master agent=foo=bar\n",
+ pktline.FlushString,
+ },
+ capabilities: []packp.Capability{
+ {Name: "symref", Values: []string{"HEAD:refs/heads/master"}},
+ {Name: "agent", Values: []string{"foo=bar"}},
+ },
+ },
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:refs/heads/master agent=foo=bar agent=new-agent\n",
+ pktline.FlushString,
+ },
+ capabilities: []packp.Capability{
+ {Name: "symref", Values: []string{"HEAD:refs/heads/master"}},
+ {Name: "agent", Values: []string{"foo=bar", "new-agent"}},
+ },
+ },
+ } {
+ ar := testDecodeOK(c, test.input)
+ for _, fixCap := range test.capabilities {
+ c.Assert(ar.Capabilities.Supports(fixCap.Name), Equals, true,
+ Commentf("input = %q, capability = %q", test.input, fixCap.Name))
+ c.Assert(ar.Capabilities.Get(fixCap.Name).Values, DeepEquals, fixCap.Values,
+ Commentf("input = %q, capability = %q", test.input, fixCap.Name))
+ }
+ }
+}
+
+func (s *SuiteDecoder) TestWithPrefix(c *C) {
+ payloads := []string{
+ "# this is a prefix\n",
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00foo\n",
+ pktline.FlushString,
+ }
+ ar := testDecodeOK(c, payloads)
+ c.Assert(len(ar.Prefix), Equals, 1)
+ c.Assert(ar.Prefix[0], DeepEquals, []byte("# this is a prefix"))
+}
+
+func (s *SuiteDecoder) TestWithPrefixAndFlush(c *C) {
+ payloads := []string{
+ "# this is a prefix\n",
+ pktline.FlushString,
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00foo\n",
+ pktline.FlushString,
+ }
+ ar := testDecodeOK(c, payloads)
+ c.Assert(len(ar.Prefix), Equals, 2)
+ c.Assert(ar.Prefix[0], DeepEquals, []byte("# this is a prefix"))
+ c.Assert(ar.Prefix[1], DeepEquals, []byte(pktline.FlushString))
+}
+
+func (s *SuiteDecoder) TestOtherRefs(c *C) {
+ for _, test := range [...]struct {
+ input []string
+ references map[string]plumbing.Hash
+ peeled map[string]plumbing.Hash
+ }{
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ pktline.FlushString,
+ },
+ references: make(map[string]plumbing.Hash),
+ peeled: make(map[string]plumbing.Hash),
+ }, {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "1111111111111111111111111111111111111111 ref/foo",
+ pktline.FlushString,
+ },
+ references: map[string]plumbing.Hash{
+ "ref/foo": plumbing.NewHash("1111111111111111111111111111111111111111"),
+ },
+ peeled: make(map[string]plumbing.Hash),
+ }, {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "1111111111111111111111111111111111111111 ref/foo\n",
+ pktline.FlushString,
+ },
+ references: map[string]plumbing.Hash{
+ "ref/foo": plumbing.NewHash("1111111111111111111111111111111111111111"),
+ },
+ peeled: make(map[string]plumbing.Hash),
+ }, {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "1111111111111111111111111111111111111111 ref/foo\n",
+ "2222222222222222222222222222222222222222 ref/bar",
+ pktline.FlushString,
+ },
+ references: map[string]plumbing.Hash{
+ "ref/foo": plumbing.NewHash("1111111111111111111111111111111111111111"),
+ "ref/bar": plumbing.NewHash("2222222222222222222222222222222222222222"),
+ },
+ peeled: make(map[string]plumbing.Hash),
+ }, {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "1111111111111111111111111111111111111111 ref/foo^{}\n",
+ pktline.FlushString,
+ },
+ references: make(map[string]plumbing.Hash),
+ peeled: map[string]plumbing.Hash{
+ "ref/foo": plumbing.NewHash("1111111111111111111111111111111111111111"),
+ },
+ }, {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "1111111111111111111111111111111111111111 ref/foo\n",
+ "2222222222222222222222222222222222222222 ref/bar^{}",
+ pktline.FlushString,
+ },
+ references: map[string]plumbing.Hash{
+ "ref/foo": plumbing.NewHash("1111111111111111111111111111111111111111"),
+ },
+ peeled: map[string]plumbing.Hash{
+ "ref/bar": plumbing.NewHash("2222222222222222222222222222222222222222"),
+ },
+ }, {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "51b8b4fb32271d39fbdd760397406177b2b0fd36 refs/pull/10/head\n",
+ "02b5a6031ba7a8cbfde5d65ff9e13ecdbc4a92ca refs/pull/100/head\n",
+ "c284c212704c43659bf5913656b8b28e32da1621 refs/pull/100/merge\n",
+ "3d6537dce68c8b7874333a1720958bd8db3ae8ca refs/pull/101/merge\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ pktline.FlushString,
+ },
+ references: map[string]plumbing.Hash{
+ "refs/heads/master": plumbing.NewHash("a6930aaee06755d1bdcfd943fbf614e4d92bb0c7"),
+ "refs/pull/10/head": plumbing.NewHash("51b8b4fb32271d39fbdd760397406177b2b0fd36"),
+ "refs/pull/100/head": plumbing.NewHash("02b5a6031ba7a8cbfde5d65ff9e13ecdbc4a92ca"),
+ "refs/pull/100/merge": plumbing.NewHash("c284c212704c43659bf5913656b8b28e32da1621"),
+ "refs/pull/101/merge": plumbing.NewHash("3d6537dce68c8b7874333a1720958bd8db3ae8ca"),
+ "refs/tags/v2.6.11": plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"),
+ "refs/tags/v2.6.11-tree": plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"),
+ },
+ peeled: map[string]plumbing.Hash{
+ "refs/tags/v2.6.11": plumbing.NewHash("c39ae07f393806ccf406ef966e9a15afc43cc36a"),
+ "refs/tags/v2.6.11-tree": plumbing.NewHash("c39ae07f393806ccf406ef966e9a15afc43cc36a"),
+ },
+ },
+ } {
+ ar := testDecodeOK(c, test.input)
+ comment := Commentf("input = %v\n", test.input)
+ c.Assert(ar.References, DeepEquals, test.references, comment)
+ c.Assert(ar.Peeled, DeepEquals, test.peeled, comment)
+ }
+}
+
+func (s *SuiteDecoder) TestMalformedOtherRefsNoSpace(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack thin-pack\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8crefs/tags/v2.6.11\n",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*malformed ref data.*")
+}
+
+func (s *SuiteDecoder) TestMalformedOtherRefsMultipleSpaces(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack thin-pack\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags v2.6.11\n",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*malformed ref data.*")
+}
+
+func (s *SuiteDecoder) TestShallow(c *C) {
+ for _, test := range [...]struct {
+ input []string
+ shallows []plumbing.Hash
+ }{
+ {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ pktline.FlushString,
+ },
+ shallows: []plumbing.Hash{},
+ }, {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ pktline.FlushString,
+ },
+ shallows: []plumbing.Hash{plumbing.NewHash("1111111111111111111111111111111111111111")},
+ }, {
+ input: []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222\n",
+ pktline.FlushString,
+ },
+ shallows: []plumbing.Hash{
+ plumbing.NewHash("1111111111111111111111111111111111111111"),
+ plumbing.NewHash("2222222222222222222222222222222222222222"),
+ },
+ },
+ } {
+ ar := testDecodeOK(c, test.input)
+ comment := Commentf("input = %v\n", test.input)
+ c.Assert(ar.Shallows, DeepEquals, test.shallows, comment)
+ }
+}
+
+func (s *SuiteDecoder) TestInvalidShallowHash(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "shallow 11111111alcortes111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222\n",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*invalid hash text.*")
+}
+
+func (s *SuiteDecoder) TestGarbageAfterShallow(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222\n",
+ "b5be40b90dbaa6bd337f3b77de361bfc0723468b refs/tags/v4.4",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*malformed shallow prefix.*")
+}
+
+func (s *SuiteDecoder) TestMalformedShallowHash(c *C) {
+ payloads := []string{
+ "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n",
+ "a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n",
+ "5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n",
+ "c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n",
+ "shallow 1111111111111111111111111111111111111111\n",
+ "shallow 2222222222222222222222222222222222222222 malformed\n",
+ pktline.FlushString,
+ }
+ r := toPktLines(c, payloads)
+ testDecoderErrorMatches(c, r, ".*malformed shallow hash.*")
+}
+
+func (s *SuiteDecoder) TestEOFRefs(c *C) {
+ input := strings.NewReader("" +
+ "005b6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n" +
+ "003fa6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n" +
+ "00355dc01c595e6c6ec9ccda4f6ffbf614e4d92bb0c7 refs/foo\n",
+ )
+ testDecoderErrorMatches(c, input, ".*invalid pkt-len.*")
+}
+
+func (s *SuiteDecoder) TestEOFShallows(c *C) {
+ input := strings.NewReader("" +
+ "005b6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00ofs-delta symref=HEAD:/refs/heads/master\n" +
+ "003fa6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n" +
+ "00445dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n" +
+ "0047c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11-tree^{}\n" +
+ "0035shallow 1111111111111111111111111111111111111111\n" +
+ "0034shallow 222222222222222222222222")
+ testDecoderErrorMatches(c, input, ".*unexpected EOF.*")
+}
diff --git a/plumbing/format/packp/advrefs/encoder.go b/plumbing/format/packp/advrefs/encoder.go
new file mode 100644
index 0000000..8c52f14
--- /dev/null
+++ b/plumbing/format/packp/advrefs/encoder.go
@@ -0,0 +1,155 @@
+package advrefs
+
+import (
+ "bytes"
+ "io"
+ "sort"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/pktline"
+)
+
+// An Encoder writes AdvRefs values to an output stream.
+type Encoder struct {
+ data *AdvRefs // data to encode
+ pe *pktline.Encoder // where to write the encoded data
+ err error // sticky error
+}
+
+// NewEncoder returns a new encoder that writes to w.
+func NewEncoder(w io.Writer) *Encoder {
+ return &Encoder{
+ pe: pktline.NewEncoder(w),
+ }
+}
+
+// Encode writes the AdvRefs encoding of v to the stream.
+//
+// All the payloads will end with a newline character. Capabilities,
+// references and shallows are writen in alphabetical order, except for
+// peeled references that always follow their corresponding references.
+func (e *Encoder) Encode(v *AdvRefs) error {
+ e.data = v
+
+ for state := encodePrefix; state != nil; {
+ state = state(e)
+ }
+
+ return e.err
+}
+
+type encoderStateFn func(*Encoder) encoderStateFn
+
+func encodePrefix(e *Encoder) encoderStateFn {
+ for _, p := range e.data.Prefix {
+ if bytes.Equal(p, pktline.Flush) {
+ if e.err = e.pe.Flush(); e.err != nil {
+ return nil
+ }
+ continue
+ }
+ if e.err = e.pe.Encodef("%s\n", string(p)); e.err != nil {
+ return nil
+ }
+ }
+
+ return encodeFirstLine
+}
+
+// Adds the first pkt-line payload: head hash, head ref and capabilities.
+// Also handle the special case when no HEAD ref is found.
+func encodeFirstLine(e *Encoder) encoderStateFn {
+ head := formatHead(e.data.Head)
+ separator := formatSeparator(e.data.Head)
+ capabilities := formatCaps(e.data.Capabilities)
+
+ if e.err = e.pe.Encodef("%s %s\x00%s\n", head, separator, capabilities); e.err != nil {
+ return nil
+ }
+
+ return encodeRefs
+}
+
+func formatHead(h *plumbing.Hash) string {
+ if h == nil {
+ return plumbing.ZeroHash.String()
+ }
+
+ return h.String()
+}
+
+func formatSeparator(h *plumbing.Hash) string {
+ if h == nil {
+ return noHead
+ }
+
+ return head
+}
+
+func formatCaps(c *packp.Capabilities) string {
+ if c == nil {
+ return ""
+ }
+
+ c.Sort()
+
+ return c.String()
+}
+
+// Adds the (sorted) refs: hash SP refname EOL
+// and their peeled refs if any.
+func encodeRefs(e *Encoder) encoderStateFn {
+ refs := sortRefs(e.data.References)
+ for _, r := range refs {
+ hash, _ := e.data.References[r]
+ if e.err = e.pe.Encodef("%s %s\n", hash.String(), r); e.err != nil {
+ return nil
+ }
+
+ if hash, ok := e.data.Peeled[r]; ok {
+ if e.err = e.pe.Encodef("%s %s^{}\n", hash.String(), r); e.err != nil {
+ return nil
+ }
+ }
+ }
+
+ return encodeShallow
+}
+
+func sortRefs(m map[string]plumbing.Hash) []string {
+ ret := make([]string, 0, len(m))
+ for k := range m {
+ ret = append(ret, k)
+ }
+ sort.Strings(ret)
+
+ return ret
+}
+
+// Adds the (sorted) shallows: "shallow" SP hash EOL
+func encodeShallow(e *Encoder) encoderStateFn {
+ sorted := sortShallows(e.data.Shallows)
+ for _, hash := range sorted {
+ if e.err = e.pe.Encodef("shallow %s\n", hash); e.err != nil {
+ return nil
+ }
+ }
+
+ return encodeFlush
+}
+
+func sortShallows(c []plumbing.Hash) []string {
+ ret := []string{}
+ for _, h := range c {
+ ret = append(ret, h.String())
+ }
+ sort.Strings(ret)
+
+ return ret
+}
+
+func encodeFlush(e *Encoder) encoderStateFn {
+ e.err = e.pe.Flush()
+ return nil
+}
diff --git a/plumbing/format/packp/advrefs/encoder_test.go b/plumbing/format/packp/advrefs/encoder_test.go
new file mode 100644
index 0000000..b4b085c
--- /dev/null
+++ b/plumbing/format/packp/advrefs/encoder_test.go
@@ -0,0 +1,249 @@
+package advrefs_test
+
+import (
+ "bytes"
+ "strings"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/advrefs"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/pktline"
+
+ . "gopkg.in/check.v1"
+)
+
+type SuiteEncoder struct{}
+
+var _ = Suite(&SuiteEncoder{})
+
+// returns a byte slice with the pkt-lines for the given payloads.
+func pktlines(c *C, payloads ...[]byte) []byte {
+ var buf bytes.Buffer
+ e := pktline.NewEncoder(&buf)
+ err := e.Encode(payloads...)
+ c.Assert(err, IsNil, Commentf("building pktlines for %v\n", payloads))
+
+ return buf.Bytes()
+}
+
+func testEncode(c *C, input *advrefs.AdvRefs, expected []byte) {
+ var buf bytes.Buffer
+ e := advrefs.NewEncoder(&buf)
+ err := e.Encode(input)
+ c.Assert(err, IsNil)
+ obtained := buf.Bytes()
+
+ comment := Commentf("\nobtained = %s\nexpected = %s\n", string(obtained), string(expected))
+
+ c.Assert(obtained, DeepEquals, expected, comment)
+}
+
+func (s *SuiteEncoder) TestZeroValue(c *C) {
+ ar := &advrefs.AdvRefs{}
+
+ expected := pktlines(c,
+ []byte("0000000000000000000000000000000000000000 capabilities^{}\x00\n"),
+ pktline.Flush,
+ )
+
+ testEncode(c, ar, expected)
+}
+
+func (s *SuiteEncoder) TestHead(c *C) {
+ hash := plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+ ar := &advrefs.AdvRefs{
+ Head: &hash,
+ }
+
+ expected := pktlines(c,
+ []byte("6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00\n"),
+ pktline.Flush,
+ )
+
+ testEncode(c, ar, expected)
+}
+
+func (s *SuiteEncoder) TestCapsNoHead(c *C) {
+ capabilities := packp.NewCapabilities()
+ capabilities.Add("symref", "HEAD:/refs/heads/master")
+ capabilities.Add("ofs-delta")
+ capabilities.Add("multi_ack")
+ ar := &advrefs.AdvRefs{
+ Capabilities: capabilities,
+ }
+
+ expected := pktlines(c,
+ []byte("0000000000000000000000000000000000000000 capabilities^{}\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n"),
+ pktline.Flush,
+ )
+
+ testEncode(c, ar, expected)
+}
+
+func (s *SuiteEncoder) TestCapsWithHead(c *C) {
+ hash := plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+ capabilities := packp.NewCapabilities()
+ capabilities.Add("symref", "HEAD:/refs/heads/master")
+ capabilities.Add("ofs-delta")
+ capabilities.Add("multi_ack")
+ ar := &advrefs.AdvRefs{
+ Head: &hash,
+ Capabilities: capabilities,
+ }
+
+ expected := pktlines(c,
+ []byte("6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n"),
+ pktline.Flush,
+ )
+
+ testEncode(c, ar, expected)
+}
+
+func (s *SuiteEncoder) TestRefs(c *C) {
+ references := map[string]plumbing.Hash{
+ "refs/heads/master": plumbing.NewHash("a6930aaee06755d1bdcfd943fbf614e4d92bb0c7"),
+ "refs/tags/v2.6.12-tree": plumbing.NewHash("1111111111111111111111111111111111111111"),
+ "refs/tags/v2.7.13-tree": plumbing.NewHash("3333333333333333333333333333333333333333"),
+ "refs/tags/v2.6.13-tree": plumbing.NewHash("2222222222222222222222222222222222222222"),
+ "refs/tags/v2.6.11-tree": plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"),
+ }
+ ar := &advrefs.AdvRefs{
+ References: references,
+ }
+
+ expected := pktlines(c,
+ []byte("0000000000000000000000000000000000000000 capabilities^{}\x00\n"),
+ []byte("a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n"),
+ []byte("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n"),
+ []byte("1111111111111111111111111111111111111111 refs/tags/v2.6.12-tree\n"),
+ []byte("2222222222222222222222222222222222222222 refs/tags/v2.6.13-tree\n"),
+ []byte("3333333333333333333333333333333333333333 refs/tags/v2.7.13-tree\n"),
+ pktline.Flush,
+ )
+
+ testEncode(c, ar, expected)
+}
+
+func (s *SuiteEncoder) TestPeeled(c *C) {
+ references := map[string]plumbing.Hash{
+ "refs/heads/master": plumbing.NewHash("a6930aaee06755d1bdcfd943fbf614e4d92bb0c7"),
+ "refs/tags/v2.6.12-tree": plumbing.NewHash("1111111111111111111111111111111111111111"),
+ "refs/tags/v2.7.13-tree": plumbing.NewHash("3333333333333333333333333333333333333333"),
+ "refs/tags/v2.6.13-tree": plumbing.NewHash("2222222222222222222222222222222222222222"),
+ "refs/tags/v2.6.11-tree": plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"),
+ }
+ peeled := map[string]plumbing.Hash{
+ "refs/tags/v2.7.13-tree": plumbing.NewHash("4444444444444444444444444444444444444444"),
+ "refs/tags/v2.6.12-tree": plumbing.NewHash("5555555555555555555555555555555555555555"),
+ }
+ ar := &advrefs.AdvRefs{
+ References: references,
+ Peeled: peeled,
+ }
+
+ expected := pktlines(c,
+ []byte("0000000000000000000000000000000000000000 capabilities^{}\x00\n"),
+ []byte("a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n"),
+ []byte("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n"),
+ []byte("1111111111111111111111111111111111111111 refs/tags/v2.6.12-tree\n"),
+ []byte("5555555555555555555555555555555555555555 refs/tags/v2.6.12-tree^{}\n"),
+ []byte("2222222222222222222222222222222222222222 refs/tags/v2.6.13-tree\n"),
+ []byte("3333333333333333333333333333333333333333 refs/tags/v2.7.13-tree\n"),
+ []byte("4444444444444444444444444444444444444444 refs/tags/v2.7.13-tree^{}\n"),
+ pktline.Flush,
+ )
+
+ testEncode(c, ar, expected)
+}
+
+func (s *SuiteEncoder) TestShallow(c *C) {
+ shallows := []plumbing.Hash{
+ plumbing.NewHash("1111111111111111111111111111111111111111"),
+ plumbing.NewHash("4444444444444444444444444444444444444444"),
+ plumbing.NewHash("3333333333333333333333333333333333333333"),
+ plumbing.NewHash("2222222222222222222222222222222222222222"),
+ }
+ ar := &advrefs.AdvRefs{
+ Shallows: shallows,
+ }
+
+ expected := pktlines(c,
+ []byte("0000000000000000000000000000000000000000 capabilities^{}\x00\n"),
+ []byte("shallow 1111111111111111111111111111111111111111\n"),
+ []byte("shallow 2222222222222222222222222222222222222222\n"),
+ []byte("shallow 3333333333333333333333333333333333333333\n"),
+ []byte("shallow 4444444444444444444444444444444444444444\n"),
+ pktline.Flush,
+ )
+
+ testEncode(c, ar, expected)
+}
+
+func (s *SuiteEncoder) TestAll(c *C) {
+ hash := plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+
+ capabilities := packp.NewCapabilities()
+ capabilities.Add("symref", "HEAD:/refs/heads/master")
+ capabilities.Add("ofs-delta")
+ capabilities.Add("multi_ack")
+
+ references := map[string]plumbing.Hash{
+ "refs/heads/master": plumbing.NewHash("a6930aaee06755d1bdcfd943fbf614e4d92bb0c7"),
+ "refs/tags/v2.6.12-tree": plumbing.NewHash("1111111111111111111111111111111111111111"),
+ "refs/tags/v2.7.13-tree": plumbing.NewHash("3333333333333333333333333333333333333333"),
+ "refs/tags/v2.6.13-tree": plumbing.NewHash("2222222222222222222222222222222222222222"),
+ "refs/tags/v2.6.11-tree": plumbing.NewHash("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c"),
+ }
+
+ peeled := map[string]plumbing.Hash{
+ "refs/tags/v2.7.13-tree": plumbing.NewHash("4444444444444444444444444444444444444444"),
+ "refs/tags/v2.6.12-tree": plumbing.NewHash("5555555555555555555555555555555555555555"),
+ }
+
+ shallows := []plumbing.Hash{
+ plumbing.NewHash("1111111111111111111111111111111111111111"),
+ plumbing.NewHash("4444444444444444444444444444444444444444"),
+ plumbing.NewHash("3333333333333333333333333333333333333333"),
+ plumbing.NewHash("2222222222222222222222222222222222222222"),
+ }
+
+ ar := &advrefs.AdvRefs{
+ Head: &hash,
+ Capabilities: capabilities,
+ References: references,
+ Peeled: peeled,
+ Shallows: shallows,
+ }
+
+ expected := pktlines(c,
+ []byte("6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00multi_ack ofs-delta symref=HEAD:/refs/heads/master\n"),
+ []byte("a6930aaee06755d1bdcfd943fbf614e4d92bb0c7 refs/heads/master\n"),
+ []byte("5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11-tree\n"),
+ []byte("1111111111111111111111111111111111111111 refs/tags/v2.6.12-tree\n"),
+ []byte("5555555555555555555555555555555555555555 refs/tags/v2.6.12-tree^{}\n"),
+ []byte("2222222222222222222222222222222222222222 refs/tags/v2.6.13-tree\n"),
+ []byte("3333333333333333333333333333333333333333 refs/tags/v2.7.13-tree\n"),
+ []byte("4444444444444444444444444444444444444444 refs/tags/v2.7.13-tree^{}\n"),
+ []byte("shallow 1111111111111111111111111111111111111111\n"),
+ []byte("shallow 2222222222222222222222222222222222222222\n"),
+ []byte("shallow 3333333333333333333333333333333333333333\n"),
+ []byte("shallow 4444444444444444444444444444444444444444\n"),
+ pktline.Flush,
+ )
+
+ testEncode(c, ar, expected)
+}
+
+func (s *SuiteEncoder) TestErrorTooLong(c *C) {
+ references := map[string]plumbing.Hash{
+ strings.Repeat("a", pktline.MaxPayloadSize): plumbing.NewHash("a6930aaee06755d1bdcfd943fbf614e4d92bb0c7"),
+ }
+ ar := &advrefs.AdvRefs{
+ References: references,
+ }
+
+ var buf bytes.Buffer
+ e := advrefs.NewEncoder(&buf)
+ err := e.Encode(ar)
+ c.Assert(err, ErrorMatches, ".*payload is too long.*")
+}