166 lines
3.4 KiB
Go
166 lines
3.4 KiB
Go
package packp
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
|
"gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
|
|
)
|
|
|
|
const (
|
|
ok = "ok"
|
|
)
|
|
|
|
// ReportStatus is a report status message, as used in the git-receive-pack
|
|
// process whenever the 'report-status' capability is negotiated.
|
|
type ReportStatus struct {
|
|
UnpackStatus string
|
|
CommandStatuses []*CommandStatus
|
|
}
|
|
|
|
// NewReportStatus creates a new ReportStatus message.
|
|
func NewReportStatus() *ReportStatus {
|
|
return &ReportStatus{}
|
|
}
|
|
|
|
// Error returns the first error if any.
|
|
func (s *ReportStatus) Error() error {
|
|
if s.UnpackStatus != ok {
|
|
return fmt.Errorf("unpack error: %s", s.UnpackStatus)
|
|
}
|
|
|
|
for _, s := range s.CommandStatuses {
|
|
if err := s.Error(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Encode writes the report status to a writer.
|
|
func (s *ReportStatus) Encode(w io.Writer) error {
|
|
e := pktline.NewEncoder(w)
|
|
if err := e.Encodef("unpack %s\n", s.UnpackStatus); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, cs := range s.CommandStatuses {
|
|
if err := cs.encode(w); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return e.Flush()
|
|
}
|
|
|
|
// Decode reads from the given reader and decodes a report-status message. It
|
|
// does not read more input than what is needed to fill the report status.
|
|
func (s *ReportStatus) Decode(r io.Reader) error {
|
|
scan := pktline.NewScanner(r)
|
|
if err := s.scanFirstLine(scan); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := s.decodeReportStatus(scan.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
|
|
flushed := false
|
|
for scan.Scan() {
|
|
b := scan.Bytes()
|
|
if isFlush(b) {
|
|
flushed = true
|
|
break
|
|
}
|
|
|
|
if err := s.decodeCommandStatus(b); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !flushed {
|
|
return fmt.Errorf("missing flush")
|
|
}
|
|
|
|
return scan.Err()
|
|
}
|
|
|
|
func (s *ReportStatus) scanFirstLine(scan *pktline.Scanner) error {
|
|
if scan.Scan() {
|
|
return nil
|
|
}
|
|
|
|
if scan.Err() != nil {
|
|
return scan.Err()
|
|
}
|
|
|
|
return io.ErrUnexpectedEOF
|
|
}
|
|
|
|
func (s *ReportStatus) decodeReportStatus(b []byte) error {
|
|
if isFlush(b) {
|
|
return fmt.Errorf("premature flush")
|
|
}
|
|
|
|
b = bytes.TrimSuffix(b, eol)
|
|
|
|
line := string(b)
|
|
fields := strings.SplitN(line, " ", 2)
|
|
if len(fields) != 2 || fields[0] != "unpack" {
|
|
return fmt.Errorf("malformed unpack status: %s", line)
|
|
}
|
|
|
|
s.UnpackStatus = fields[1]
|
|
return nil
|
|
}
|
|
|
|
func (s *ReportStatus) decodeCommandStatus(b []byte) error {
|
|
b = bytes.TrimSuffix(b, eol)
|
|
|
|
line := string(b)
|
|
fields := strings.SplitN(line, " ", 3)
|
|
status := ok
|
|
if len(fields) == 3 && fields[0] == "ng" {
|
|
status = fields[2]
|
|
} else if len(fields) != 2 || fields[0] != "ok" {
|
|
return fmt.Errorf("malformed command status: %s", line)
|
|
}
|
|
|
|
cs := &CommandStatus{
|
|
ReferenceName: plumbing.ReferenceName(fields[1]),
|
|
Status: status,
|
|
}
|
|
s.CommandStatuses = append(s.CommandStatuses, cs)
|
|
return nil
|
|
}
|
|
|
|
// CommandStatus is the status of a reference in a report status.
|
|
// See ReportStatus struct.
|
|
type CommandStatus struct {
|
|
ReferenceName plumbing.ReferenceName
|
|
Status string
|
|
}
|
|
|
|
// Error returns the error, if any.
|
|
func (s *CommandStatus) Error() error {
|
|
if s.Status == ok {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("command error on %s: %s",
|
|
s.ReferenceName.String(), s.Status)
|
|
}
|
|
|
|
func (s *CommandStatus) encode(w io.Writer) error {
|
|
e := pktline.NewEncoder(w)
|
|
if s.Error() == nil {
|
|
return e.Encodef("ok %s\n", s.ReferenceName.String())
|
|
}
|
|
|
|
return e.Encodef("ng %s %s\n", s.ReferenceName.String(), s.Status)
|
|
}
|