smithy

ref: master

pkg/go-git-http/rpc_reader.go


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package githttp

import (
	"io"
	"regexp"
	"strings"
)

// RpcReader scans for events in the incoming rpc request data.
type RpcReader struct {
	// Underlying reader (to relay calls to).
	io.Reader

	// Rpc type (receive-pack or upload-pack).
	Rpc string

	// List of events RpcReader has picked up through scanning.
	// These events do not have the Dir field set.
	Events []Event

	pktLineParser pktLineParser
}

// Read implements the io.Reader interface.
func (r *RpcReader) Read(p []byte) (n int, err error) {
	// Relay call
	n, err = r.Reader.Read(p)

	// Scan for events
	if n > 0 {
		r.scan(p[:n])
	}

	return n, err
}

func (r *RpcReader) scan(data []byte) {
	if r.pktLineParser.state == done {
		return
	}

	r.pktLineParser.Feed(data)

	// If parsing has just finished, process its output once.
	if r.pktLineParser.state == done {
		if r.pktLineParser.Error != nil {
			return
		}

		// When we get here, we're done collecting all pkt-lines successfully
		// and can now extract relevant events.
		switch r.Rpc {
		case "receive-pack":
			for _, line := range r.pktLineParser.Lines {
				events := scanPush(line)
				r.Events = append(r.Events, events...)
			}
		case "upload-pack":
			total := strings.Join(r.pktLineParser.Lines, "")
			events := scanFetch(total)
			r.Events = append(r.Events, events...)
		}
	}
}

// TODO: Avoid using regexp to parse a well documented binary protocol with an open source
//       implementation. There should not be a need for regexp.

// receivePackRegex is used once per pkt-line.
var receivePackRegex = regexp.MustCompile("([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) refs\\/(heads|tags)\\/(.+?)(\x00|$)")

func scanPush(line string) []Event {
	matches := receivePackRegex.FindAllStringSubmatch(line, -1)

	if matches == nil {
		return nil
	}

	var events []Event
	for _, m := range matches {
		e := Event{
			Last:   m[1],
			Commit: m[2],
		}

		// Handle pushes to branches and tags differently
		if m[3] == "heads" {
			e.Type = PUSH
			e.Branch = m[4]
		} else {
			e.Type = TAG
			e.Tag = m[4]
		}

		events = append(events, e)
	}

	return events
}

// uploadPackRegex is used once on the entire header data.
var uploadPackRegex = regexp.MustCompile(`^want ([0-9a-fA-F]{40})`)

func scanFetch(total string) []Event {
	matches := uploadPackRegex.FindAllStringSubmatch(total, -1)

	if matches == nil {
		return nil
	}

	var events []Event
	for _, m := range matches {
		events = append(events, Event{
			Type:   FETCH,
			Commit: m[1],
		})
	}

	return events
}