Skip to content

Commit 129b709

Browse files
author
Max Jonas Werner
committed
plumbing: transport/common, Improve handling of remote errors
Instead of simply returning the first line that the remote returned, go-git now actively searches all of stderr for lines that may contain a more actionable error message and returns that. In addition, this change adds a case to map the GitLab-specific error message to an ErrRepositoryNotFound error. Signed-off-by: Max Jonas Werner <[email protected]>
1 parent 19fe126 commit 129b709

File tree

3 files changed

+124
-4
lines changed

3 files changed

+124
-4
lines changed

plumbing/transport/internal/common/common.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"errors"
1212
"fmt"
1313
"io"
14+
"regexp"
1415
"strings"
1516
"time"
1617

@@ -28,6 +29,10 @@ const (
2829

2930
var (
3031
ErrTimeoutExceeded = errors.New("timeout exceeded")
32+
// stdErrSkipPattern is used for skipping lines from a command's stderr output.
33+
// Any line matching this pattern will be skipped from further
34+
// processing and not be returned to calling code.
35+
stdErrSkipPattern = regexp.MustCompile("^remote:( =*){0,1}$")
3136
)
3237

3338
// Commander creates Command instances. This is the main entry point for
@@ -149,10 +154,17 @@ func (c *client) listenFirstError(r io.Reader) chan string {
149154
errLine := make(chan string, 1)
150155
go func() {
151156
s := bufio.NewScanner(r)
152-
if s.Scan() {
153-
errLine <- s.Text()
154-
} else {
155-
close(errLine)
157+
for {
158+
if s.Scan() {
159+
line := s.Text()
160+
if !stdErrSkipPattern.MatchString(line) {
161+
errLine <- line
162+
break
163+
}
164+
} else {
165+
close(errLine)
166+
break
167+
}
156168
}
157169

158170
_, _ = io.Copy(io.Discard, r)
@@ -393,6 +405,7 @@ var (
393405
gitProtocolNoSuchErr = "ERR no such repository"
394406
gitProtocolAccessDeniedErr = "ERR access denied"
395407
gogsAccessDeniedErr = "Gogs: Repository does not exist or you do not have access"
408+
gitlabRepoNotFoundErr = "remote: ERROR: The project you were looking for could not be found"
396409
)
397410

398411
func isRepoNotFoundError(s string) bool {
@@ -424,6 +437,10 @@ func isRepoNotFoundError(s string) bool {
424437
return true
425438
}
426439

440+
if strings.HasPrefix(s, gitlabRepoNotFoundErr) {
441+
return true
442+
}
443+
427444
return false
428445
}
429446

plumbing/transport/internal/common/common_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"testing"
66

7+
"github.com/go-git/go-git/v5/plumbing/transport"
78
. "gopkg.in/check.v1"
89
)
910

@@ -77,6 +78,14 @@ func (s *CommonSuite) TestIsRepoNotFoundErrorForGogsAccessDenied(c *C) {
7778
c.Assert(isRepoNotFound, Equals, true)
7879
}
7980

81+
func (s *CommonSuite) TestIsRepoNotFoundErrorForGitlab(c *C) {
82+
msg := fmt.Sprintf("%s : some error stuf", gitlabRepoNotFoundErr)
83+
84+
isRepoNotFound := isRepoNotFoundError(msg)
85+
86+
c.Assert(isRepoNotFound, Equals, true)
87+
}
88+
8089
func (s *CommonSuite) TestCheckNotFoundError(c *C) {
8190
firstErrLine := make(chan string, 1)
8291

@@ -90,3 +99,51 @@ func (s *CommonSuite) TestCheckNotFoundError(c *C) {
9099

91100
c.Assert(err, IsNil)
92101
}
102+
103+
func TestAdvertisedReferencesWithRemoteError(t *testing.T) {
104+
tests := []struct {
105+
name string
106+
stderr string
107+
wantErr error
108+
}{
109+
{
110+
name: "unknown error",
111+
stderr: "something",
112+
wantErr: fmt.Errorf("unknown error: something"),
113+
},
114+
{
115+
name: "GitLab: repository not found",
116+
stderr: `remote:
117+
remote: ========================================================================
118+
remote:
119+
remote: ERROR: The project you were looking for could not be found or you don't have permission to view it.
120+
121+
remote:
122+
remote: ========================================================================
123+
remote:`,
124+
wantErr: transport.ErrRepositoryNotFound,
125+
},
126+
}
127+
128+
for _, tt := range tests {
129+
t.Run(tt.name, func(t *testing.T) {
130+
client := NewClient(MockCommander{stderr: tt.stderr})
131+
sess, err := client.NewUploadPackSession(nil, nil)
132+
if err != nil {
133+
t.Fatalf("unexpected error: %s", err)
134+
}
135+
136+
_, err = sess.AdvertisedReferences()
137+
138+
if tt.wantErr != nil {
139+
if tt.wantErr != err {
140+
if tt.wantErr.Error() != err.Error() {
141+
t.Fatalf("expected a different error: got '%s', expected '%s'", err, tt.wantErr)
142+
}
143+
}
144+
} else if err != nil {
145+
t.Fatalf("unexpected error: %s", err)
146+
}
147+
})
148+
}
149+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package common
2+
3+
import (
4+
"bytes"
5+
"io"
6+
7+
gogitioutil "github.com/go-git/go-git/v5/utils/ioutil"
8+
9+
"github.com/go-git/go-git/v5/plumbing/transport"
10+
)
11+
12+
type MockCommand struct {
13+
stdin bytes.Buffer
14+
stdout bytes.Buffer
15+
stderr bytes.Buffer
16+
}
17+
18+
func (c MockCommand) StderrPipe() (io.Reader, error) {
19+
return &c.stderr, nil
20+
}
21+
22+
func (c MockCommand) StdinPipe() (io.WriteCloser, error) {
23+
return gogitioutil.WriteNopCloser(&c.stdin), nil
24+
}
25+
26+
func (c MockCommand) StdoutPipe() (io.Reader, error) {
27+
return &c.stdout, nil
28+
}
29+
30+
func (c MockCommand) Start() error {
31+
return nil
32+
}
33+
34+
func (c MockCommand) Close() error {
35+
panic("not implemented")
36+
}
37+
38+
type MockCommander struct {
39+
stderr string
40+
}
41+
42+
func (c MockCommander) Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod) (Command, error) {
43+
return &MockCommand{
44+
stderr: *bytes.NewBufferString(c.stderr),
45+
}, nil
46+
}

0 commit comments

Comments
 (0)