|
- package packp
-
- import (
- "bufio"
- "bytes"
- "errors"
- "fmt"
- "io"
-
- "gopkg.in/src-d/go-git.v4/plumbing"
- "gopkg.in/src-d/go-git.v4/plumbing/format/pktline"
- )
-
- const ackLineLen = 44
-
- // ServerResponse object acknowledgement from upload-pack service
- type ServerResponse struct {
- ACKs []plumbing.Hash
- }
-
- // Decode decodes the response into the struct, isMultiACK should be true, if
- // the request was done with multi_ack or multi_ack_detailed capabilities.
- func (r *ServerResponse) Decode(reader *bufio.Reader, isMultiACK bool) error {
- // TODO: implement support for multi_ack or multi_ack_detailed responses
- if isMultiACK {
- return errors.New("multi_ack and multi_ack_detailed are not supported")
- }
-
- s := pktline.NewScanner(reader)
-
- for s.Scan() {
- line := s.Bytes()
-
- if err := r.decodeLine(line); err != nil {
- return err
- }
-
- // we need to detect when the end of a response header and the beginning
- // of a packfile header happened, some requests to the git daemon
- // produces a duplicate ACK header even when multi_ack is not supported.
- stop, err := r.stopReading(reader)
- if err != nil {
- return err
- }
-
- if stop {
- break
- }
- }
-
- return s.Err()
- }
-
- // stopReading detects when a valid command such as ACK or NAK is found to be
- // read in the buffer without moving the read pointer.
- func (r *ServerResponse) stopReading(reader *bufio.Reader) (bool, error) {
- ahead, err := reader.Peek(7)
- if err == io.EOF {
- return true, nil
- }
-
- if err != nil {
- return false, err
- }
-
- if len(ahead) > 4 && r.isValidCommand(ahead[0:3]) {
- return false, nil
- }
-
- if len(ahead) == 7 && r.isValidCommand(ahead[4:]) {
- return false, nil
- }
-
- return true, nil
- }
-
- func (r *ServerResponse) isValidCommand(b []byte) bool {
- commands := [][]byte{ack, nak}
- for _, c := range commands {
- if bytes.Equal(b, c) {
- return true
- }
- }
-
- return false
- }
-
- func (r *ServerResponse) decodeLine(line []byte) error {
- if len(line) == 0 {
- return fmt.Errorf("unexpected flush")
- }
-
- if bytes.Equal(line[0:3], ack) {
- return r.decodeACKLine(line)
- }
-
- if bytes.Equal(line[0:3], nak) {
- return nil
- }
-
- return fmt.Errorf("unexpected content %q", string(line))
- }
-
- func (r *ServerResponse) decodeACKLine(line []byte) error {
- if len(line) < ackLineLen {
- return fmt.Errorf("malformed ACK %q", line)
- }
-
- sp := bytes.Index(line, []byte(" "))
- h := plumbing.NewHash(string(line[sp+1 : sp+41]))
- r.ACKs = append(r.ACKs, h)
- return nil
- }
-
- // Encode encodes the ServerResponse into a writer.
- func (r *ServerResponse) Encode(w io.Writer) error {
- if len(r.ACKs) > 1 {
- return errors.New("multi_ack and multi_ack_detailed are not supported")
- }
-
- e := pktline.NewEncoder(w)
- if len(r.ACKs) == 0 {
- return e.Encodef("%s\n", nak)
- }
-
- return e.Encodef("%s %s\n", ack, r.ACKs[0].String())
- }
|