diff options
Diffstat (limited to 'plumbing')
-rw-r--r-- | plumbing/reference.go | 36 | ||||
-rw-r--r-- | plumbing/reference_test.go | 5 | ||||
-rw-r--r-- | plumbing/transport/file/client.go | 18 | ||||
-rw-r--r-- | plumbing/transport/internal/common/common.go | 34 |
4 files changed, 54 insertions, 39 deletions
diff --git a/plumbing/reference.go b/plumbing/reference.go index 8fa103e..5d477b9 100644 --- a/plumbing/reference.go +++ b/plumbing/reference.go @@ -15,15 +15,16 @@ const ( symrefPrefix = "ref: " ) -var ( - refPrefixes = []string{ - refHeadPrefix, - refTagPrefix, - refRemotePrefix, - refNotePrefix, - refPrefix, - } -) +// refRevParseRules are a set of rules to parse references into short names. +// These are the same rules as used by git in shorten_unambiguous_ref. +// See: https://github.com/git/git/blob/e0aaa1b6532cfce93d87af9bc813fb2e7a7ce9d7/refs.c#L417 +var refRevParseRules = []string{ + "refs/%s", + "refs/tags/%s", + "refs/heads/%s", + "refs/remotes/%s", + "refs/remotes/%s/HEAD", +} var ( ErrReferenceNotFound = errors.New("reference not found") @@ -60,17 +61,16 @@ func (r ReferenceName) String() string { // Short returns the short name of a ReferenceName func (r ReferenceName) Short() string { - return r.removeRefPrefix() -} - -// Instead of hardcoding a number of components, we should remove the prefixes -// refHeadPrefix, refTagPrefix, refRemotePrefix, refNotePrefix and refPrefix -func (r ReferenceName) removeRefPrefix() string { s := string(r) - for _, prefix := range refPrefixes { - s = strings.TrimPrefix(s, prefix) + res := s + for _, format := range refRevParseRules { + _, err := fmt.Sscanf(s, format, &res) + if err == nil { + continue + } } - return s + + return res } const ( diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go index 6a695f4..97c8772 100644 --- a/plumbing/reference_test.go +++ b/plumbing/reference_test.go @@ -23,6 +23,11 @@ func (s *ReferenceSuite) TestReferenceNameWithSlash(c *C) { c.Assert(r.Short(), Equals, "origin/feature/AllowSlashes") } +func (s *ReferenceSuite) TestReferenceNameNote(c *C) { + r := ReferenceName("refs/notes/foo") + c.Assert(r.Short(), Equals, "notes/foo") +} + func (s *ReferenceSuite) TestNewReferenceFromStrings(c *C) { r := NewReferenceFromStrings("refs/heads/v4", "6ecf0ef2c2dffb796033e5a02219af86ec6584e5") c.Assert(r.Type(), Equals, HashReference) diff --git a/plumbing/transport/file/client.go b/plumbing/transport/file/client.go index a199b01..b6d60c1 100644 --- a/plumbing/transport/file/client.go +++ b/plumbing/transport/file/client.go @@ -46,8 +46,9 @@ func (r *runner) Command(cmd string, ep transport.Endpoint, auth transport.AuthM } type command struct { - cmd *exec.Cmd - closed bool + cmd *exec.Cmd + stderrCloser io.Closer + closed bool } func (c *command) Start() error { @@ -55,7 +56,12 @@ func (c *command) Start() error { } func (c *command) StderrPipe() (io.Reader, error) { - return c.cmd.StderrPipe() + // Pipe returned by Command.StderrPipe has a race with Read + Command.Wait. + // We use an io.Pipe and close it after the command finishes. + r, w := io.Pipe() + c.cmd.Stderr = w + c.stderrCloser = r + return r, nil } func (c *command) StdinPipe() (io.WriteCloser, error) { @@ -72,7 +78,11 @@ func (c *command) Close() error { return nil } - defer func() { c.closed = true }() + defer func() { + c.closed = true + _ = c.stderrCloser.Close() + }() + err := c.cmd.Wait() if _, ok := err.(*os.PathError); ok { return nil diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go index c1e1518..04db770 100644 --- a/plumbing/transport/internal/common/common.go +++ b/plumbing/transport/internal/common/common.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + stdioutil "io/ioutil" "strings" "time" @@ -22,7 +23,6 @@ import ( const ( readErrorSecondsTimeout = 10 - errLinesBuffer = 1000 ) var ( @@ -96,7 +96,7 @@ type session struct { advRefs *packp.AdvRefs packRun bool finished bool - errLines chan string + firstErrLine chan string } func (c *client) newSession(s string, ep transport.Endpoint, auth transport.AuthMethod) (*session, error) { @@ -128,26 +128,29 @@ func (c *client) newSession(s string, ep transport.Endpoint, auth transport.Auth Stdin: stdin, Stdout: stdout, Command: cmd, - errLines: c.listenErrors(stderr), + firstErrLine: c.listenFirstError(stderr), isReceivePack: s == transport.ReceivePackServiceName, }, nil } -func (c *client) listenErrors(r io.Reader) chan string { +func (c *client) listenFirstError(r io.Reader) chan string { if r == nil { return nil } - errLines := make(chan string, errLinesBuffer) + errLine := make(chan string, 1) go func() { s := bufio.NewScanner(r) - for s.Scan() { - line := string(s.Bytes()) - errLines <- line + if s.Scan() { + errLine <- s.Text() + } else { + close(errLine) } + + _, _ = io.Copy(stdioutil.Discard, r) }() - return errLines + return errLine } // AdvertisedReferences retrieves the advertised references from the server. @@ -296,13 +299,10 @@ func (s *session) finish() error { return nil } -func (s *session) Close() error { - if err := s.finish(); err != nil { - _ = s.Command.Close() - return err - } - - return s.Command.Close() +func (s *session) Close() (err error) { + defer ioutil.CheckClose(s.Command, &err) + err = s.finish() + return } func (s *session) checkNotFoundError() error { @@ -312,7 +312,7 @@ func (s *session) checkNotFoundError() error { select { case <-t.C: return ErrTimeoutExceeded - case line, ok := <-s.errLines: + case line, ok := <-s.firstErrLine: if !ok { return nil } |