Skip to content

Commit

Permalink
plumbing: transport: use git-proto-request and decode error-line errors
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Nov 23, 2023
1 parent e2c6ae3 commit f46d04a
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 114 deletions.
9 changes: 7 additions & 2 deletions plumbing/protocol/packp/gitproto.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ func (g *GitProtoRequest) Encode(w io.Writer) error {
func (g *GitProtoRequest) Decode(r io.Reader) error {
s := pktline.NewScanner(r)
if !s.Scan() {
return s.Err()
err := s.Err()
if err == nil {
return ErrInvalidGitProtoRequest
}
return err
}

line := string(s.Bytes())
Expand All @@ -99,8 +103,9 @@ func (g *GitProtoRequest) Decode(r io.Reader) error {
return fmt.Errorf("%w: missing pathname", ErrInvalidGitProtoRequest)
}

g.Pathname = params[0]
if len(params) > 1 {
g.Host = params[1]
g.Host = strings.TrimPrefix(params[1], "host=")
}

if len(params) > 2 {
Expand Down
99 changes: 99 additions & 0 deletions plumbing/protocol/packp/gitproto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package packp

import (
"bytes"
"testing"
)

func TestEncodeEmptyGitProtoRequest(t *testing.T) {
var buf bytes.Buffer
var p GitProtoRequest
err := p.Encode(&buf)
if err == nil {
t.Fatal("expected error")
}
}

func TestEncodeGitProtoRequest(t *testing.T) {
var buf bytes.Buffer
p := GitProtoRequest{
RequestCommand: "command",
Pathname: "pathname",
Host: "host",
ExtraParams: []string{"param1", "param2"},
}
err := p.Encode(&buf)
if err != nil {
t.Fatal(err)
}
expected := "002ecommand pathname\x00host=host\x00\x00param1\x00param2\x00"
if buf.String() != expected {
t.Fatalf("expected %q, got %q", expected, buf.String())
}
}

func TestEncodeInvalidGitProtoRequest(t *testing.T) {
var buf bytes.Buffer
p := GitProtoRequest{
RequestCommand: "command",
}
err := p.Encode(&buf)
if err == nil {
t.Fatal("expected error")
}
}

func TestDecodeEmptyGitProtoRequest(t *testing.T) {
var buf bytes.Buffer
var p GitProtoRequest
err := p.Decode(&buf)
if err == nil {
t.Fatal("expected error")
}
}

func TestDecodeGitProtoRequest(t *testing.T) {
var buf bytes.Buffer
buf.WriteString("002ecommand pathname\x00host=host\x00\x00param1\x00param2\x00")
var p GitProtoRequest
err := p.Decode(&buf)
if err != nil {
t.Fatal(err)
}
expected := GitProtoRequest{
RequestCommand: "command",
Pathname: "pathname",
Host: "host",
ExtraParams: []string{"param1", "param2"},
}
if p.RequestCommand != expected.RequestCommand {
t.Fatalf("expected %q, got %q", expected.RequestCommand, p.RequestCommand)
}
if p.Pathname != expected.Pathname {
t.Fatalf("expected %q, got %q", expected.Pathname, p.Pathname)
}
if p.Host != expected.Host {
t.Fatalf("expected %q, got %q", expected.Host, p.Host)
}
if len(p.ExtraParams) != len(expected.ExtraParams) {
t.Fatalf("expected %d, got %d", len(expected.ExtraParams), len(p.ExtraParams))
}
}

func TestDecodeInvalidGitProtoRequest(t *testing.T) {
var buf bytes.Buffer
buf.WriteString("0026command \x00host=host\x00\x00param1\x00param2")
var p GitProtoRequest
err := p.Decode(&buf)
if err == nil {
t.Fatal("expected error")
}
}

func TestValidateEmptyGitProtoRequest(t *testing.T) {
var p GitProtoRequest
err := p.validate()
if err == nil {
t.Fatal("expected error")
}
}
26 changes: 12 additions & 14 deletions plumbing/transport/git/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
package git

import (
"fmt"
"io"
"net"
"strconv"

"github.com/go-git/go-git/v5/plumbing/format/pktline"
"github.com/go-git/go-git/v5/plumbing/protocol/packp"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/internal/common"
"github.com/go-git/go-git/v5/utils/ioutil"
Expand Down Expand Up @@ -42,10 +41,18 @@ type command struct {

// Start executes the command sending the required message to the TCP connection
func (c *command) Start() error {
cmd := endpointToCommand(c.command, c.endpoint)
req := packp.GitProtoRequest{
RequestCommand: c.command,
Pathname: c.endpoint.Path,
}
host := c.endpoint.Host
if c.endpoint.Port != DefaultPort {
host = net.JoinHostPort(c.endpoint.Host, strconv.Itoa(c.endpoint.Port))
}

req.Host = host

e := pktline.NewEncoder(c.conn)
return e.Encode([]byte(cmd))
return req.Encode(c.conn)
}

func (c *command) connect() error {
Expand Down Expand Up @@ -90,15 +97,6 @@ func (c *command) StdoutPipe() (io.Reader, error) {
return c.conn, nil
}

func endpointToCommand(cmd string, ep *transport.Endpoint) string {
host := ep.Host
if ep.Port != DefaultPort {
host = net.JoinHostPort(ep.Host, strconv.Itoa(ep.Port))
}

return fmt.Sprintf("%s %s%chost=%s%c", cmd, ep.Path, 0, host, 0)
}

// Close closes the TCP connection and connection.
func (c *command) Close() error {
if !c.connected {
Expand Down
77 changes: 37 additions & 40 deletions plumbing/transport/internal/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,22 @@ func (s *session) AdvertisedReferencesContext(ctx context.Context) (*packp.AdvRe
}

func (s *session) handleAdvRefDecodeError(err error) error {
var errLine *pktline.ErrorLine
if errors.As(err, &errLine) {
if isRepoNotFoundError(errLine.Text) {
return transport.ErrRepositoryNotFound
}

return errLine
}

// If repository is not found, we get empty stdout and server writes an
// error to stderr.
if err == packp.ErrEmptyInput {
if errors.Is(err, packp.ErrEmptyInput) {
// TODO:(v6): handle this error in a better way.
// Instead of checking the stderr output for a specific error message,
// define an ExitError and embed the stderr output and exit (if one
// exists) in the error struct. Just like exec.ExitError.
s.finished = true
if err := s.checkNotFoundError(); err != nil {
return err
Expand Down Expand Up @@ -399,59 +412,43 @@ func (s *session) checkNotFoundError() error {
return transport.ErrRepositoryNotFound
}

// TODO:(v6): return server error just as it is without a prefix
return fmt.Errorf("unknown error: %s", line)
}
}

var (
githubRepoNotFoundErr = "ERROR: Repository not found."
bitbucketRepoNotFoundErr = "conq: repository does not exist."
const (
githubRepoNotFoundErr = "Repository not found."
bitbucketRepoNotFoundErr = "repository does not exist."
localRepoNotFoundErr = "does not appear to be a git repository"
gitProtocolNotFoundErr = "ERR \n Repository not found."
gitProtocolNoSuchErr = "ERR no such repository"
gitProtocolAccessDeniedErr = "ERR access denied"
gogsAccessDeniedErr = "Gogs: Repository does not exist or you do not have access"
gitlabRepoNotFoundErr = "remote: ERROR: The project you were looking for could not be found"
gitProtocolNotFoundErr = "Repository not found."
gitProtocolNoSuchErr = "no such repository"
gitProtocolAccessDeniedErr = "access denied"
gogsAccessDeniedErr = "Repository does not exist or you do not have access"
gitlabRepoNotFoundErr = "The project you were looking for could not be found"
)

func isRepoNotFoundError(s string) bool {
if strings.HasPrefix(s, githubRepoNotFoundErr) {
return true
}

if strings.HasPrefix(s, bitbucketRepoNotFoundErr) {
return true
}

if strings.HasSuffix(s, localRepoNotFoundErr) {
return true
}

if strings.HasPrefix(s, gitProtocolNotFoundErr) {
return true
}

if strings.HasPrefix(s, gitProtocolNoSuchErr) {
return true
}

if strings.HasPrefix(s, gitProtocolAccessDeniedErr) {
return true
}

if strings.HasPrefix(s, gogsAccessDeniedErr) {
return true
}

if strings.HasPrefix(s, gitlabRepoNotFoundErr) {
return true
for _, err := range []string{
githubRepoNotFoundErr,
bitbucketRepoNotFoundErr,
localRepoNotFoundErr,
gitProtocolNotFoundErr,
gitProtocolNoSuchErr,
gitProtocolAccessDeniedErr,
gogsAccessDeniedErr,
gitlabRepoNotFoundErr,
} {
if strings.Contains(s, err) {
return true
}
}

return false
}

// uploadPack implements the git-upload-pack protocol.
func uploadPack(w io.WriteCloser, r io.Reader, req *packp.UploadPackRequest) error {
func uploadPack(w io.WriteCloser, _ io.Reader, req *packp.UploadPackRequest) error {
// TODO support multi_ack mode
// TODO support multi_ack_detailed mode
// TODO support acks for common objects
Expand Down
60 changes: 2 additions & 58 deletions plumbing/transport/internal/common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,64 +22,8 @@ func (s *CommonSuite) TestIsRepoNotFoundErrorForUnknownSource(c *C) {
c.Assert(isRepoNotFound, Equals, false)
}

func (s *CommonSuite) TestIsRepoNotFoundErrorForGithub(c *C) {
msg := fmt.Sprintf("%s : some error stuf", githubRepoNotFoundErr)

isRepoNotFound := isRepoNotFoundError(msg)

c.Assert(isRepoNotFound, Equals, true)
}

func (s *CommonSuite) TestIsRepoNotFoundErrorForBitBucket(c *C) {
msg := fmt.Sprintf("%s : some error stuf", bitbucketRepoNotFoundErr)

isRepoNotFound := isRepoNotFoundError(msg)

c.Assert(isRepoNotFound, Equals, true)
}

func (s *CommonSuite) TestIsRepoNotFoundErrorForLocal(c *C) {
msg := fmt.Sprintf("some error stuf : %s", localRepoNotFoundErr)

isRepoNotFound := isRepoNotFoundError(msg)

c.Assert(isRepoNotFound, Equals, true)
}

func (s *CommonSuite) TestIsRepoNotFoundErrorForGitProtocolNotFound(c *C) {
msg := fmt.Sprintf("%s : some error stuf", gitProtocolNotFoundErr)

isRepoNotFound := isRepoNotFoundError(msg)

c.Assert(isRepoNotFound, Equals, true)
}

func (s *CommonSuite) TestIsRepoNotFoundErrorForGitProtocolNoSuch(c *C) {
msg := fmt.Sprintf("%s : some error stuf", gitProtocolNoSuchErr)

isRepoNotFound := isRepoNotFoundError(msg)

c.Assert(isRepoNotFound, Equals, true)
}

func (s *CommonSuite) TestIsRepoNotFoundErrorForGitProtocolAccessDenied(c *C) {
msg := fmt.Sprintf("%s : some error stuf", gitProtocolAccessDeniedErr)

isRepoNotFound := isRepoNotFoundError(msg)

c.Assert(isRepoNotFound, Equals, true)
}

func (s *CommonSuite) TestIsRepoNotFoundErrorForGogsAccessDenied(c *C) {
msg := fmt.Sprintf("%s : some error stuf", gogsAccessDeniedErr)

isRepoNotFound := isRepoNotFoundError(msg)

c.Assert(isRepoNotFound, Equals, true)
}

func (s *CommonSuite) TestIsRepoNotFoundErrorForGitlab(c *C) {
msg := fmt.Sprintf("%s : some error stuf", gitlabRepoNotFoundErr)
func (s *CommonSuite) TestIsRepoNotFoundError(c *C) {
msg := "no such repository : some error stuf"

isRepoNotFound := isRepoNotFoundError(msg)

Expand Down

0 comments on commit f46d04a

Please sign in to comment.