diff options
author | Santiago M. Mola <santi@mola.io> | 2016-12-05 11:59:49 +0100 |
---|---|---|
committer | Máximo Cuadros <mcuadros@gmail.com> | 2016-12-05 11:59:49 +0100 |
commit | 0042bb031676a20ffc789f94e332a6da70e2756d (patch) | |
tree | 22a142575c6f12894b4faec73807b57afc287529 /plumbing/protocol/packp | |
parent | 19f59e782b92d32cc430619c77053c764a3180f9 (diff) | |
download | go-git-0042bb031676a20ffc789f94e332a6da70e2756d.tar.gz |
protocol/packp: add reference update request encoder. (#147)
* add ReferenceUpdateRequest struct.
* add ReferenceUpdateRequest decoder.
* add ReferenceUpdateRequest encoder.
Diffstat (limited to 'plumbing/protocol/packp')
-rw-r--r-- | plumbing/protocol/packp/advrefs_decode.go | 1 | ||||
-rw-r--r-- | plumbing/protocol/packp/common.go | 4 | ||||
-rw-r--r-- | plumbing/protocol/packp/updreq.go | 84 | ||||
-rw-r--r-- | plumbing/protocol/packp/updreq_decode.go | 231 | ||||
-rw-r--r-- | plumbing/protocol/packp/updreq_decode_test.go | 253 | ||||
-rw-r--r-- | plumbing/protocol/packp/updreq_encode.go | 67 | ||||
-rw-r--r-- | plumbing/protocol/packp/updreq_encode_test.go | 119 |
7 files changed, 759 insertions, 0 deletions
diff --git a/plumbing/protocol/packp/advrefs_decode.go b/plumbing/protocol/packp/advrefs_decode.go index c8f8394..5926645 100644 --- a/plumbing/protocol/packp/advrefs_decode.go +++ b/plumbing/protocol/packp/advrefs_decode.go @@ -199,6 +199,7 @@ func decodeFirstRef(l *advRefsDecoder) decoderStateFn { func decodeCaps(p *advRefsDecoder) decoderStateFn { if err := p.data.Capabilities.Decode(p.line); err != nil { p.error("invalid capabilities: %s", err) + return nil } return decodeOtherRefs diff --git a/plumbing/protocol/packp/common.go b/plumbing/protocol/packp/common.go index 3d7786b..c8db931 100644 --- a/plumbing/protocol/packp/common.go +++ b/plumbing/protocol/packp/common.go @@ -15,6 +15,7 @@ var ( // common sp = []byte(" ") eol = []byte("\n") + eq = []byte{'='} // advrefs null = []byte("\x00") @@ -28,4 +29,7 @@ var ( deepenCommits = []byte("deepen ") deepenSince = []byte("deepen-since ") deepenReference = []byte("deepen-not ") + + // updreq + shallowNoSp = []byte("shallow") ) diff --git a/plumbing/protocol/packp/updreq.go b/plumbing/protocol/packp/updreq.go new file mode 100644 index 0000000..90d6e09 --- /dev/null +++ b/plumbing/protocol/packp/updreq.go @@ -0,0 +1,84 @@ +package packp + +import ( + "errors" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" +) + +var ( + ErrEmptyCommands = errors.New("commands cannot be empty") + ErrMalformedCommand = errors.New("malformed command") +) + +// ReferenceUpdateRequest values represent reference upload requests. +// Values from this type are not zero-value safe, use the New function instead. +// +// TODO: Add support for push-cert +type ReferenceUpdateRequest struct { + Capabilities *capability.List + Commands []*Command + Shallow *plumbing.Hash +} + +// New returns a pointer to a new ReferenceUpdateRequest value. +func NewReferenceUpdateRequest() *ReferenceUpdateRequest { + return &ReferenceUpdateRequest{ + Capabilities: capability.NewList(), + Commands: nil, + } +} + +func (r *ReferenceUpdateRequest) validate() error { + if len(r.Commands) == 0 { + return ErrEmptyCommands + } + + for _, c := range r.Commands { + if err := c.validate(); err != nil { + return err + } + } + + return nil +} + +type Action string + +const ( + Create Action = "create" + Update = "update" + Delete = "delete" + Invalid = "invalid" +) + +type Command struct { + Name string + Old plumbing.Hash + New plumbing.Hash +} + +func (c *Command) Action() Action { + if c.Old == plumbing.ZeroHash && c.New == plumbing.ZeroHash { + return Invalid + } + + if c.Old == plumbing.ZeroHash { + return Create + } + + if c.New == plumbing.ZeroHash { + return Delete + } + + return Update +} + +func (c *Command) validate() error { + if c.Action() == Invalid { + return ErrMalformedCommand + } + + return nil +} diff --git a/plumbing/protocol/packp/updreq_decode.go b/plumbing/protocol/packp/updreq_decode.go new file mode 100644 index 0000000..0740871 --- /dev/null +++ b/plumbing/protocol/packp/updreq_decode.go @@ -0,0 +1,231 @@ +package packp + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "io" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" +) + +var ( + shallowLineLength = len(shallow) + hashSize + minCommandLength = hashSize*2 + 2 + 1 + minCommandAndCapsLenth = minCommandLength + 1 +) + +var ( + ErrEmpty = errors.New("empty update-request message") + errNoCommands = errors.New("unexpected EOF before any command") + errMissingCapabilitiesDelimiter = errors.New("capabilities delimiter not found") +) + +func errMalformedRequest(reason string) error { + return fmt.Errorf("malformed request: %s", reason) +} + +func errInvalidHashSize(got int) error { + return fmt.Errorf("invalid hash size: expected %d, got %d", + hashSize, got) +} + +func errInvalidHash(err error) error { + return fmt.Errorf("invalid hash: %s", err.Error()) +} + +func errInvalidShallowLineLength(got int) error { + return errMalformedRequest(fmt.Sprintf( + "invalid shallow line length: expected %d, got %d", + shallowLineLength, got)) +} + +func errInvalidCommandCapabilitiesLineLength(got int) error { + return errMalformedRequest(fmt.Sprintf( + "invalid command and capabilities line length: expected at least %d, got %d", + minCommandAndCapsLenth, got)) +} + +func errInvalidCommandLineLength(got int) error { + return errMalformedRequest(fmt.Sprintf( + "invalid command line length: expected at least %d, got %d", + minCommandLength, got)) +} + +func errInvalidShallowObjId(err error) error { + return errMalformedRequest( + fmt.Sprintf("invalid shallow object id: %s", err.Error())) +} + +func errInvalidOldObjId(err error) error { + return errMalformedRequest( + fmt.Sprintf("invalid old object id: %s", err.Error())) +} + +func errInvalidNewObjId(err error) error { + return errMalformedRequest( + fmt.Sprintf("invalid new object id: %s", err.Error())) +} + +func errMalformedCommand(err error) error { + return errMalformedRequest(fmt.Sprintf( + "malformed command: %s", err.Error())) +} + +// Decode reads the next update-request message form the reader and wr +func (req *ReferenceUpdateRequest) Decode(r io.Reader) error { + d := &updReqDecoder{s: pktline.NewScanner(r)} + return d.Decode(req) +} + +type updReqDecoder struct { + s *pktline.Scanner + r *ReferenceUpdateRequest +} + +func (d *updReqDecoder) Decode(r *ReferenceUpdateRequest) error { + d.r = r + funcs := []func() error{ + d.scanLine, + d.decodeShallow, + d.decodeCommandAndCapabilities, + d.decodeCommands, + r.validate, + } + + for _, f := range funcs { + if err := f(); err != nil { + return err + } + } + + return nil +} + +func (d *updReqDecoder) scanLine() error { + if ok := d.s.Scan(); !ok { + return d.scanErrorOr(ErrEmpty) + } + + return nil +} + +func (d *updReqDecoder) decodeShallow() error { + b := d.s.Bytes() + + if !bytes.HasPrefix(b, shallowNoSp) { + return nil + } + + if len(b) != shallowLineLength { + return errInvalidShallowLineLength(len(b)) + } + + h, err := parseHash(string(b[len(shallow):])) + if err != nil { + return errInvalidShallowObjId(err) + } + + if ok := d.s.Scan(); !ok { + return d.scanErrorOr(errNoCommands) + } + + d.r.Shallow = &h + + return nil +} + +func (d *updReqDecoder) decodeCommands() error { + for { + b := d.s.Bytes() + if bytes.Equal(b, pktline.Flush) { + return nil + } + + c, err := parseCommand(b) + if err != nil { + return err + } + + d.r.Commands = append(d.r.Commands, c) + + if ok := d.s.Scan(); !ok { + return d.s.Err() + } + } +} + +func (d *updReqDecoder) decodeCommandAndCapabilities() error { + b := d.s.Bytes() + i := bytes.IndexByte(b, 0) + if i == -1 { + return errMissingCapabilitiesDelimiter + } + + if len(b) < minCommandAndCapsLenth { + return errInvalidCommandCapabilitiesLineLength(len(b)) + } + + cmd, err := parseCommand(b[:i]) + if err != nil { + return err + } + + d.r.Commands = append(d.r.Commands, cmd) + + if err := d.r.Capabilities.Decode(b[i+1:]); err != nil { + return err + } + + if err := d.scanLine(); err != nil { + return err + } + + return nil +} + +func parseCommand(b []byte) (*Command, error) { + if len(b) < minCommandLength { + return nil, errInvalidCommandLineLength(len(b)) + } + + var os, ns, n string + if _, err := fmt.Sscanf(string(b), "%s %s %s", &os, &ns, &n); err != nil { + return nil, errMalformedCommand(err) + } + + oh, err := parseHash(os) + if err != nil { + return nil, errInvalidOldObjId(err) + } + + nh, err := parseHash(ns) + if err != nil { + return nil, errInvalidNewObjId(err) + } + + return &Command{Old: oh, New: nh, Name: n}, nil +} + +func parseHash(s string) (plumbing.Hash, error) { + if len(s) != hashSize { + return plumbing.ZeroHash, errInvalidHashSize(len(s)) + } + + if _, err := hex.DecodeString(s); err != nil { + return plumbing.ZeroHash, errInvalidHash(err) + } + + h := plumbing.NewHash(s) + return h, nil +} + +func (d *updReqDecoder) scanErrorOr(origErr error) error { + if err := d.s.Err(); err != nil { + return err + } + + return origErr +} diff --git a/plumbing/protocol/packp/updreq_decode_test.go b/plumbing/protocol/packp/updreq_decode_test.go new file mode 100644 index 0000000..66d9180 --- /dev/null +++ b/plumbing/protocol/packp/updreq_decode_test.go @@ -0,0 +1,253 @@ +package packp + +import ( + "bytes" + "io" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" + + . "gopkg.in/check.v1" +) + +type UpdReqDecodeSuite struct{} + +var _ = Suite(&UpdReqDecodeSuite{}) + +func (s *UpdReqDecodeSuite) TestEmpty(c *C) { + r := NewReferenceUpdateRequest() + var buf bytes.Buffer + c.Assert(r.Decode(&buf), Equals, ErrEmpty) + c.Assert(r, DeepEquals, NewReferenceUpdateRequest()) +} + +func (s *UpdReqDecodeSuite) TestInvalidPktlines(c *C) { + r := NewReferenceUpdateRequest() + input := bytes.NewReader([]byte("xxxxxxxxxx")) + c.Assert(r.Decode(input), ErrorMatches, "invalid pkt-len found") +} + +func (s *UpdReqDecodeSuite) TestInvalidShadow(c *C) { + payloads := []string{ + "shallow", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid shallow line length: expected 48, got 7$") + + payloads = []string{ + "shallow ", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid shallow line length: expected 48, got 8$") + + payloads = []string{ + "shallow 1ecf0ef2c2dffb796033e5a02219af86ec65", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid shallow line length: expected 48, got 44$") + + payloads = []string{ + "shallow 1ecf0ef2c2dffb796033e5a02219af86ec6584e54", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid shallow line length: expected 48, got 49$") + + payloads = []string{ + "shallow 1ecf0ef2c2dffb796033e5a02219af86ec6584eu", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid shallow object id: invalid hash: .*") +} + +func (s *UpdReqDecodeSuite) TestMalformedCommand(c *C) { + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5x2ecf0ef2c2dffb796033e5a02219af86ec6584e5xmyref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: malformed command: EOF$") + + payloads = []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5x2ecf0ef2c2dffb796033e5a02219af86ec6584e5xmyref", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: malformed command: EOF$") +} + +func (s *UpdReqDecodeSuite) TestInvalidCommandInvalidHash(c *C) { + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid old object id: invalid hash size: expected 40, got 39$") + + payloads = []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid new object id: invalid hash size: expected 40, got 39$") + + payloads = []string{ + "1ecf0ef2c2dffb796033e5a02219af86e 2ecf0ef2c2dffb796033e5a02219af86ec6 m\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid command and capabilities line length: expected at least 84, got 72$") + + payloads = []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584eu 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid old object id: invalid hash: .*$") + + payloads = []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584eu myref\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid new object id: invalid hash: .*$") +} + +func (s *UpdReqDecodeSuite) TestInvalidCommandMissingNullDelimiter(c *C) { + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "capabilities delimiter not found") +} + +func (s *UpdReqDecodeSuite) TestInvalidCommandMissingName(c *C) { + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5\x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid command and capabilities line length: expected at least 84, got 82$") + + payloads = []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 \x00", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid command and capabilities line length: expected at least 84, got 83$") + + payloads = []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid command line length: expected at least 83, got 81$") + + payloads = []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 ", + pktline.FlushString, + } + s.testDecoderErrorMatches(c, toPktLines(c, payloads), "^malformed request: invalid command line length: expected at least 83, got 82$") +} + +func (s *UpdReqDecodeSuite) TestOneUpdateCommand(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + name := "myref" + + expected := NewReferenceUpdateRequest() + expected.Commands = []*Command{ + {Name: name, Old: hash1, New: hash2}, + } + + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + } + + c.Assert(s.testDecodeOK(c, payloads), DeepEquals, expected) +} + +func (s *UpdReqDecodeSuite) TestMultipleCommands(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + expected := NewReferenceUpdateRequest() + expected.Commands = []*Command{ + {Name: "myref1", Old: hash1, New: hash2}, + {Name: "myref2", Old: plumbing.ZeroHash, New: hash2}, + {Name: "myref3", Old: hash1, New: plumbing.ZeroHash}, + } + + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00", + "0000000000000000000000000000000000000000 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref2", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 0000000000000000000000000000000000000000 myref3", + pktline.FlushString, + } + + c.Assert(s.testDecodeOK(c, payloads).Commands, DeepEquals, expected.Commands) + c.Assert(s.testDecodeOK(c, payloads).Shallow, DeepEquals, expected.Shallow) + c.Assert(s.testDecodeOK(c, payloads).Capabilities, DeepEquals, expected.Capabilities) + c.Assert(s.testDecodeOK(c, payloads), DeepEquals, expected) +} + +func (s *UpdReqDecodeSuite) TestMultipleCommandsAndCapabilities(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + expected := NewReferenceUpdateRequest() + expected.Commands = []*Command{ + {Name: "myref1", Old: hash1, New: hash2}, + {Name: "myref2", Old: plumbing.ZeroHash, New: hash2}, + {Name: "myref3", Old: hash1, New: plumbing.ZeroHash}, + } + expected.Capabilities.Add("shallow") + + payloads := []string{ + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00shallow", + "0000000000000000000000000000000000000000 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref2", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 0000000000000000000000000000000000000000 myref3", + pktline.FlushString, + } + + c.Assert(s.testDecodeOK(c, payloads), DeepEquals, expected) +} + +func (s *UpdReqDecodeSuite) TestMultipleCommandsAndCapabilitiesShallow(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + expected := NewReferenceUpdateRequest() + expected.Commands = []*Command{ + {Name: "myref1", Old: hash1, New: hash2}, + {Name: "myref2", Old: plumbing.ZeroHash, New: hash2}, + {Name: "myref3", Old: hash1, New: plumbing.ZeroHash}, + } + expected.Capabilities.Add("shallow") + expected.Shallow = &hash1 + + payloads := []string{ + "shallow 1ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00shallow", + "0000000000000000000000000000000000000000 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref2", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 0000000000000000000000000000000000000000 myref3", + pktline.FlushString, + } + + c.Assert(s.testDecodeOK(c, payloads), DeepEquals, expected) +} + +func (s *UpdReqDecodeSuite) testDecoderErrorMatches(c *C, input io.Reader, pattern string) { + r := NewReferenceUpdateRequest() + c.Assert(r.Decode(input), ErrorMatches, pattern) +} + +func (s *UpdReqDecodeSuite) testDecodeOK(c *C, payloads []string) *ReferenceUpdateRequest { + var buf bytes.Buffer + e := pktline.NewEncoder(&buf) + err := e.EncodeString(payloads...) + c.Assert(err, IsNil) + + r := NewReferenceUpdateRequest() + c.Assert(r.Decode(&buf), IsNil) + + return r +} diff --git a/plumbing/protocol/packp/updreq_encode.go b/plumbing/protocol/packp/updreq_encode.go new file mode 100644 index 0000000..b2b7944 --- /dev/null +++ b/plumbing/protocol/packp/updreq_encode.go @@ -0,0 +1,67 @@ +package packp + +import ( + "fmt" + "io" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" + "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp/capability" +) + +var ( + zeroHashString = plumbing.ZeroHash.String() +) + +// Encode writes the ReferenceUpdateRequest encoding to the stream. +func (r *ReferenceUpdateRequest) Encode(w io.Writer) error { + if err := r.validate(); err != nil { + return err + } + + e := pktline.NewEncoder(w) + + if err := r.encodeShallow(e, r.Shallow); err != nil { + return err + } + + if err := r.encodeCommands(e, r.Commands, r.Capabilities); err != nil { + return err + } + + return nil +} + +func (r *ReferenceUpdateRequest) encodeShallow(e *pktline.Encoder, + h *plumbing.Hash) error { + + if h == nil { + return nil + } + + objId := []byte(h.String()) + return e.Encodef("%s%s", shallow, objId) +} + +func (r *ReferenceUpdateRequest) encodeCommands(e *pktline.Encoder, + cmds []*Command, cap *capability.List) error { + + if err := e.Encodef("%s\x00%s", + formatCommand(cmds[0]), cap.String()); err != nil { + return err + } + + for _, cmd := range cmds[1:] { + if err := e.Encodef(formatCommand(cmd)); err != nil { + return err + } + } + + return e.Flush() +} + +func formatCommand(cmd *Command) string { + o := cmd.Old.String() + n := cmd.New.String() + return fmt.Sprintf("%s %s %s", o, n, cmd.Name) +} diff --git a/plumbing/protocol/packp/updreq_encode_test.go b/plumbing/protocol/packp/updreq_encode_test.go new file mode 100644 index 0000000..47958fd --- /dev/null +++ b/plumbing/protocol/packp/updreq_encode_test.go @@ -0,0 +1,119 @@ +package packp + +import ( + "bytes" + + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/format/pktline" + + . "gopkg.in/check.v1" +) + +type UpdReqEncodeSuite struct{} + +var _ = Suite(&UpdReqEncodeSuite{}) + +func (s *UpdReqEncodeSuite) testEncode(c *C, input *ReferenceUpdateRequest, + expected []byte) { + + var buf bytes.Buffer + c.Assert(input.Encode(&buf), IsNil) + obtained := buf.Bytes() + + comment := Commentf("\nobtained = %s\nexpected = %s\n", string(obtained), string(expected)) + c.Assert(obtained, DeepEquals, expected, comment) +} + +func (s *UpdReqEncodeSuite) TestZeroValue(c *C) { + r := &ReferenceUpdateRequest{} + var buf bytes.Buffer + c.Assert(r.Encode(&buf), Equals, ErrEmptyCommands) + + r = NewReferenceUpdateRequest() + c.Assert(r.Encode(&buf), Equals, ErrEmptyCommands) +} + +func (s *UpdReqEncodeSuite) TestOneUpdateCommand(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + name := "myref" + + r := NewReferenceUpdateRequest() + r.Commands = []*Command{ + {Name: name, Old: hash1, New: hash2}, + } + + expected := pktlines(c, + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref\x00", + pktline.FlushString, + ) + + s.testEncode(c, r, expected) +} + +func (s *UpdReqEncodeSuite) TestMultipleCommands(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + r := NewReferenceUpdateRequest() + r.Commands = []*Command{ + {Name: "myref1", Old: hash1, New: hash2}, + {Name: "myref2", Old: plumbing.ZeroHash, New: hash2}, + {Name: "myref3", Old: hash1, New: plumbing.ZeroHash}, + } + + expected := pktlines(c, + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00", + "0000000000000000000000000000000000000000 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref2", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 0000000000000000000000000000000000000000 myref3", + pktline.FlushString, + ) + + s.testEncode(c, r, expected) +} + +func (s *UpdReqEncodeSuite) TestMultipleCommandsAndCapabilities(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + r := NewReferenceUpdateRequest() + r.Commands = []*Command{ + {Name: "myref1", Old: hash1, New: hash2}, + {Name: "myref2", Old: plumbing.ZeroHash, New: hash2}, + {Name: "myref3", Old: hash1, New: plumbing.ZeroHash}, + } + r.Capabilities.Add("shallow") + + expected := pktlines(c, + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00shallow", + "0000000000000000000000000000000000000000 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref2", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 0000000000000000000000000000000000000000 myref3", + pktline.FlushString, + ) + + s.testEncode(c, r, expected) +} + +func (s *UpdReqEncodeSuite) TestMultipleCommandsAndCapabilitiesShallow(c *C) { + hash1 := plumbing.NewHash("1ecf0ef2c2dffb796033e5a02219af86ec6584e5") + hash2 := plumbing.NewHash("2ecf0ef2c2dffb796033e5a02219af86ec6584e5") + + r := NewReferenceUpdateRequest() + r.Commands = []*Command{ + {Name: "myref1", Old: hash1, New: hash2}, + {Name: "myref2", Old: plumbing.ZeroHash, New: hash2}, + {Name: "myref3", Old: hash1, New: plumbing.ZeroHash}, + } + r.Capabilities.Add("shallow") + r.Shallow = &hash1 + + expected := pktlines(c, + "shallow 1ecf0ef2c2dffb796033e5a02219af86ec6584e5", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref1\x00shallow", + "0000000000000000000000000000000000000000 2ecf0ef2c2dffb796033e5a02219af86ec6584e5 myref2", + "1ecf0ef2c2dffb796033e5a02219af86ec6584e5 0000000000000000000000000000000000000000 myref3", + pktline.FlushString, + ) + + s.testEncode(c, r, expected) +} |