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

protocol/packp: add ReferenceUpdateRequest message #147

Merged
merged 1 commit into from
Dec 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plumbing/protocol/packp/advrefs_decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions plumbing/protocol/packp/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
// common
sp = []byte(" ")
eol = []byte("\n")
eq = []byte{'='}

// advrefs
null = []byte("\x00")
Expand All @@ -28,4 +29,7 @@ var (
deepenCommits = []byte("deepen ")
deepenSince = []byte("deepen-since ")
deepenReference = []byte("deepen-not ")

// updreq
shallowNoSp = []byte("shallow")
)
84 changes: 84 additions & 0 deletions plumbing/protocol/packp/updreq.go
Original file line number Diff line number Diff line change
@@ -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
}
231 changes: 231 additions & 0 deletions plumbing/protocol/packp/updreq_decode.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading