aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/client/common
diff options
context:
space:
mode:
Diffstat (limited to 'plumbing/client/common')
-rw-r--r--plumbing/client/common/common.go219
-rw-r--r--plumbing/client/common/common_test.go126
2 files changed, 345 insertions, 0 deletions
diff --git a/plumbing/client/common/common.go b/plumbing/client/common/common.go
new file mode 100644
index 0000000..97f78c4
--- /dev/null
+++ b/plumbing/client/common/common.go
@@ -0,0 +1,219 @@
+// Package common contains interfaces and non-specific protocol entities
+package common
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/url"
+ "regexp"
+ "strings"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/advrefs"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp/pktline"
+ "gopkg.in/src-d/go-git.v4/plumbing/storer"
+ "gopkg.in/src-d/go-git.v4/storage/memory"
+)
+
+var (
+ ErrRepositoryNotFound = errors.New("repository not found")
+ ErrAuthorizationRequired = errors.New("authorization required")
+ ErrEmptyGitUploadPack = errors.New("empty git-upload-pack given")
+ ErrInvalidAuthMethod = errors.New("invalid auth method")
+)
+
+const GitUploadPackServiceName = "git-upload-pack"
+
+type GitUploadPackService interface {
+ Connect() error
+ SetAuth(AuthMethod) error
+ Info() (*GitUploadPackInfo, error)
+ Fetch(*GitUploadPackRequest) (io.ReadCloser, error)
+ Disconnect() error
+}
+
+type AuthMethod interface {
+ Name() string
+ String() string
+}
+
+type Endpoint url.URL
+
+var (
+ isSchemeRegExp = regexp.MustCompile("^[^:]+://")
+ scpLikeUrlRegExp = regexp.MustCompile("^(?P<user>[^@]+@)?(?P<host>[^:]+):/?(?P<path>.+)$")
+)
+
+func NewEndpoint(endpoint string) (Endpoint, error) {
+ endpoint = transformSCPLikeIfNeeded(endpoint)
+
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return Endpoint{}, plumbing.NewPermanentError(err)
+ }
+
+ if !u.IsAbs() {
+ return Endpoint{}, plumbing.NewPermanentError(fmt.Errorf(
+ "invalid endpoint: %s", endpoint,
+ ))
+ }
+
+ return Endpoint(*u), nil
+}
+
+func transformSCPLikeIfNeeded(endpoint string) string {
+ if !isSchemeRegExp.MatchString(endpoint) && scpLikeUrlRegExp.MatchString(endpoint) {
+ m := scpLikeUrlRegExp.FindStringSubmatch(endpoint)
+ return fmt.Sprintf("ssh://%s%s/%s", m[1], m[2], m[3])
+ }
+
+ return endpoint
+}
+
+func (e *Endpoint) String() string {
+ u := url.URL(*e)
+ return u.String()
+}
+
+type GitUploadPackInfo struct {
+ Capabilities *packp.Capabilities
+ Refs memory.ReferenceStorage
+}
+
+func NewGitUploadPackInfo() *GitUploadPackInfo {
+ return &GitUploadPackInfo{
+ Capabilities: packp.NewCapabilities(),
+ Refs: make(memory.ReferenceStorage, 0),
+ }
+}
+
+func (i *GitUploadPackInfo) Decode(r io.Reader) error {
+ d := advrefs.NewDecoder(r)
+ ar := advrefs.New()
+ if err := d.Decode(ar); err != nil {
+ if err == advrefs.ErrEmpty {
+ return plumbing.NewPermanentError(err)
+ }
+ return plumbing.NewUnexpectedError(err)
+ }
+
+ i.Capabilities = ar.Capabilities
+
+ if err := i.addRefs(ar); err != nil {
+ return plumbing.NewUnexpectedError(err)
+ }
+
+ return nil
+}
+
+func (i *GitUploadPackInfo) addRefs(ar *advrefs.AdvRefs) error {
+ for name, hash := range ar.References {
+ ref := plumbing.NewReferenceFromStrings(name, hash.String())
+ i.Refs.SetReference(ref)
+ }
+
+ return i.addSymbolicRefs(ar)
+}
+
+func (i *GitUploadPackInfo) addSymbolicRefs(ar *advrefs.AdvRefs) error {
+ if !hasSymrefs(ar) {
+ return nil
+ }
+
+ for _, symref := range ar.Capabilities.Get("symref").Values {
+ chunks := strings.Split(symref, ":")
+ if len(chunks) != 2 {
+ err := fmt.Errorf("bad number of `:` in symref value (%q)", symref)
+ return plumbing.NewUnexpectedError(err)
+ }
+ name := plumbing.ReferenceName(chunks[0])
+ target := plumbing.ReferenceName(chunks[1])
+ ref := plumbing.NewSymbolicReference(name, target)
+ i.Refs.SetReference(ref)
+ }
+
+ return nil
+}
+
+func hasSymrefs(ar *advrefs.AdvRefs) bool {
+ return ar.Capabilities.Supports("symref")
+}
+
+func (i *GitUploadPackInfo) Head() *plumbing.Reference {
+ ref, _ := storer.ResolveReference(i.Refs, plumbing.HEAD)
+ return ref
+}
+
+func (i *GitUploadPackInfo) String() string {
+ return string(i.Bytes())
+}
+
+func (i *GitUploadPackInfo) Bytes() []byte {
+ var buf bytes.Buffer
+ e := pktline.NewEncoder(&buf)
+
+ _ = e.EncodeString("# service=git-upload-pack\n")
+
+ // inserting a flush-pkt here violates the protocol spec, but some
+ // servers do it, like Github.com
+ e.Flush()
+
+ _ = e.Encodef("%s HEAD\x00%s\n", i.Head().Hash(), i.Capabilities.String())
+
+ for _, ref := range i.Refs {
+ if ref.Type() != plumbing.HashReference {
+ continue
+ }
+
+ _ = e.Encodef("%s %s\n", ref.Hash(), ref.Name())
+ }
+
+ e.Flush()
+
+ return buf.Bytes()
+}
+
+type GitUploadPackRequest struct {
+ Wants []plumbing.Hash
+ Haves []plumbing.Hash
+ Depth int
+}
+
+func (r *GitUploadPackRequest) Want(h ...plumbing.Hash) {
+ r.Wants = append(r.Wants, h...)
+}
+
+func (r *GitUploadPackRequest) Have(h ...plumbing.Hash) {
+ r.Haves = append(r.Haves, h...)
+}
+
+func (r *GitUploadPackRequest) String() string {
+ b, _ := ioutil.ReadAll(r.Reader())
+ return string(b)
+}
+
+func (r *GitUploadPackRequest) Reader() *strings.Reader {
+ var buf bytes.Buffer
+ e := pktline.NewEncoder(&buf)
+
+ for _, want := range r.Wants {
+ _ = e.Encodef("want %s\n", want)
+ }
+
+ for _, have := range r.Haves {
+ _ = e.Encodef("have %s\n", have)
+ }
+
+ if r.Depth != 0 {
+ _ = e.Encodef("deepen %d\n", r.Depth)
+ }
+
+ _ = e.Flush()
+ _ = e.EncodeString("done\n")
+
+ return strings.NewReader(buf.String())
+}
diff --git a/plumbing/client/common/common_test.go b/plumbing/client/common/common_test.go
new file mode 100644
index 0000000..cf4d871
--- /dev/null
+++ b/plumbing/client/common/common_test.go
@@ -0,0 +1,126 @@
+package common
+
+import (
+ "bytes"
+ "encoding/base64"
+ "testing"
+
+ "gopkg.in/src-d/go-git.v4/plumbing"
+ "gopkg.in/src-d/go-git.v4/plumbing/format/packp"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type SuiteCommon struct{}
+
+var _ = Suite(&SuiteCommon{})
+
+func (s *SuiteCommon) TestNewEndpoint(c *C) {
+ e, err := NewEndpoint("ssh://git@github.com/user/repository.git")
+ c.Assert(err, IsNil)
+ c.Assert(e.String(), Equals, "ssh://git@github.com/user/repository.git")
+}
+
+func (s *SuiteCommon) TestNewEndpointSCPLike(c *C) {
+ e, err := NewEndpoint("git@github.com:user/repository.git")
+ c.Assert(err, IsNil)
+ c.Assert(e.String(), Equals, "ssh://git@github.com/user/repository.git")
+}
+
+func (s *SuiteCommon) TestNewEndpointWrongForgat(c *C) {
+ e, err := NewEndpoint("foo")
+ c.Assert(err, Not(IsNil))
+ c.Assert(e.Host, Equals, "")
+}
+
+const CapabilitiesFixture = "6ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEADmulti_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag multi_ack_detailed no-done symref=HEAD:refs/heads/master agent=git/2:2.4.8~dbussink-fix-enterprise-tokens-compilation-1167-gc7006cf"
+
+func (s *SuiteCommon) TestCapabilitiesSymbolicReference(c *C) {
+ cap := packp.NewCapabilities()
+ cap.Decode(CapabilitiesFixture)
+ c.Assert(cap.SymbolicReference("HEAD"), Equals, "refs/heads/master")
+}
+
+const GitUploadPackInfoFixture = "MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAxMGM2ZWNmMGVmMmMyZGZmYjc5NjAzM2U1YTAyMjE5YWY4NmVjNjU4NGU1IEhFQUQAbXVsdGlfYWNrIHRoaW4tcGFjayBzaWRlLWJhbmQgc2lkZS1iYW5kLTY0ayBvZnMtZGVsdGEgc2hhbGxvdyBuby1wcm9ncmVzcyBpbmNsdWRlLXRhZyBtdWx0aV9hY2tfZGV0YWlsZWQgbm8tZG9uZSBzeW1yZWY9SEVBRDpyZWZzL2hlYWRzL21hc3RlciBhZ2VudD1naXQvMjoyLjQuOH5kYnVzc2luay1maXgtZW50ZXJwcmlzZS10b2tlbnMtY29tcGlsYXRpb24tMTE2Ny1nYzcwMDZjZgowMDNmZThkM2ZmYWI1NTI4OTVjMTliOWZjZjdhYTI2NGQyNzdjZGUzMzg4MSByZWZzL2hlYWRzL2JyYW5jaAowMDNmNmVjZjBlZjJjMmRmZmI3OTYwMzNlNWEwMjIxOWFmODZlYzY1ODRlNSByZWZzL2hlYWRzL21hc3RlcgowMDNlYjhlNDcxZjU4YmNiY2E2M2IwN2JkYTIwZTQyODE5MDQwOWMyZGI0NyByZWZzL3B1bGwvMS9oZWFkCjAwMDA="
+
+func (s *SuiteCommon) TestGitUploadPackInfo(c *C) {
+ b, _ := base64.StdEncoding.DecodeString(GitUploadPackInfoFixture)
+
+ i := NewGitUploadPackInfo()
+ err := i.Decode(bytes.NewBuffer(b))
+ c.Assert(err, IsNil)
+
+ name := i.Capabilities.SymbolicReference("HEAD")
+ c.Assert(name, Equals, "refs/heads/master")
+ c.Assert(i.Refs, HasLen, 4)
+
+ ref := i.Refs[plumbing.ReferenceName(name)]
+ c.Assert(ref, NotNil)
+ c.Assert(ref.Hash().String(), Equals, "6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+
+ ref = i.Refs[plumbing.HEAD]
+ c.Assert(ref, NotNil)
+ c.Assert(ref.Target(), Equals, plumbing.ReferenceName(name))
+}
+
+const GitUploadPackInfoNoHEADFixture = "MDAxZSMgc2VydmljZT1naXQtdXBsb2FkLXBhY2sKMDAwMDAwYmNkN2UxZmVlMjYxMjM0YmIzYTQzYzA5NmY1NTg3NDhhNTY5ZDc5ZWZmIHJlZnMvaGVhZHMvdjQAbXVsdGlfYWNrIHRoaW4tcGFjayBzaWRlLWJhbmQgc2lkZS1iYW5kLTY0ayBvZnMtZGVsdGEgc2hhbGxvdyBuby1wcm9ncmVzcyBpbmNsdWRlLXRhZyBtdWx0aV9hY2tfZGV0YWlsZWQgbm8tZG9uZSBhZ2VudD1naXQvMS45LjEKMDAwMA=="
+
+func (s *SuiteCommon) TestGitUploadPackInfoNoHEAD(c *C) {
+ b, _ := base64.StdEncoding.DecodeString(GitUploadPackInfoNoHEADFixture)
+
+ i := NewGitUploadPackInfo()
+ err := i.Decode(bytes.NewBuffer(b))
+ c.Assert(err, IsNil)
+
+ name := i.Capabilities.SymbolicReference("HEAD")
+ c.Assert(name, Equals, "")
+ c.Assert(i.Refs, HasLen, 1)
+
+ ref := i.Refs["refs/heads/v4"]
+ c.Assert(ref, NotNil)
+ c.Assert(ref.Hash().String(), Equals, "d7e1fee261234bb3a43c096f558748a569d79eff")
+}
+
+func (s *SuiteCommon) TestGitUploadPackInfoEmpty(c *C) {
+ b := bytes.NewBuffer(nil)
+
+ i := NewGitUploadPackInfo()
+ err := i.Decode(b)
+ c.Assert(err, ErrorMatches, "permanent.*empty.*")
+}
+
+func (s *SuiteCommon) TestGitUploadPackEncode(c *C) {
+ info := NewGitUploadPackInfo()
+ info.Capabilities.Add("symref", "HEAD:refs/heads/master")
+
+ ref := plumbing.ReferenceName("refs/heads/master")
+ hash := plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5")
+ info.Refs = map[plumbing.ReferenceName]*plumbing.Reference{
+ plumbing.HEAD: plumbing.NewSymbolicReference(plumbing.HEAD, ref),
+ ref: plumbing.NewHashReference(ref, hash),
+ }
+
+ c.Assert(info.Head(), NotNil)
+ c.Assert(info.String(), Equals,
+ "001e# service=git-upload-pack\n"+
+ "000000506ecf0ef2c2dffb796033e5a02219af86ec6584e5 HEAD\x00symref=HEAD:refs/heads/master\n"+
+ "003f6ecf0ef2c2dffb796033e5a02219af86ec6584e5 refs/heads/master\n"+
+ "0000",
+ )
+}
+
+func (s *SuiteCommon) TestGitUploadPackRequest(c *C) {
+ r := &GitUploadPackRequest{}
+ r.Want(plumbing.NewHash("d82f291cde9987322c8a0c81a325e1ba6159684c"))
+ r.Want(plumbing.NewHash("2b41ef280fdb67a9b250678686a0c3e03b0a9989"))
+ r.Have(plumbing.NewHash("6ecf0ef2c2dffb796033e5a02219af86ec6584e5"))
+
+ c.Assert(r.String(), Equals,
+ "0032want d82f291cde9987322c8a0c81a325e1ba6159684c\n"+
+ "0032want 2b41ef280fdb67a9b250678686a0c3e03b0a9989\n"+
+ "0032have 6ecf0ef2c2dffb796033e5a02219af86ec6584e5\n0000"+
+ "0009done\n",
+ )
+}