aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing
diff options
context:
space:
mode:
Diffstat (limited to 'plumbing')
-rw-r--r--plumbing/format/gitattributes/attributes.go14
-rw-r--r--plumbing/format/gitattributes/dir.go3
-rw-r--r--plumbing/format/gitignore/dir.go4
-rw-r--r--plumbing/format/pktline/error.go51
-rw-r--r--plumbing/format/pktline/error_test.go68
-rw-r--r--plumbing/format/pktline/scanner.go9
-rw-r--r--plumbing/object/commit.go6
-rw-r--r--plumbing/object/commit_test.go8
-rw-r--r--plumbing/object/commit_walker_path.go19
-rw-r--r--plumbing/object/commit_walker_test.go26
-rw-r--r--plumbing/object/patch.go99
-rw-r--r--plumbing/object/patch_stats_test.go54
-rw-r--r--plumbing/object/patch_test.go110
-rw-r--r--plumbing/object/tree.go32
-rw-r--r--plumbing/object/tree_test.go26
-rw-r--r--plumbing/object/treenoder.go4
-rw-r--r--plumbing/protocol/packp/common.go5
-rw-r--r--plumbing/protocol/packp/gitproto.go120
-rw-r--r--plumbing/protocol/packp/gitproto_test.go99
-rw-r--r--plumbing/reference.go89
-rw-r--r--plumbing/reference_test.go59
-rw-r--r--plumbing/transport/git/common.go26
-rw-r--r--plumbing/transport/internal/common/common.go77
-rw-r--r--plumbing/transport/internal/common/common_test.go60
-rw-r--r--plumbing/transport/ssh/common.go4
-rw-r--r--plumbing/transport/ssh/common_test.go23
26 files changed, 904 insertions, 191 deletions
diff --git a/plumbing/format/gitattributes/attributes.go b/plumbing/format/gitattributes/attributes.go
index d36ec1b..026d221 100644
--- a/plumbing/format/gitattributes/attributes.go
+++ b/plumbing/format/gitattributes/attributes.go
@@ -1,6 +1,7 @@
package gitattributes
import (
+ "bufio"
"errors"
"io"
"strings"
@@ -88,13 +89,10 @@ func (a attribute) String() string {
// ReadAttributes reads patterns and attributes from the gitattributes format.
func ReadAttributes(r io.Reader, domain []string, allowMacro bool) (attributes []MatchAttribute, err error) {
- data, err := io.ReadAll(r)
- if err != nil {
- return nil, err
- }
+ scanner := bufio.NewScanner(r)
- for _, line := range strings.Split(string(data), eol) {
- attribute, err := ParseAttributesLine(line, domain, allowMacro)
+ for scanner.Scan() {
+ attribute, err := ParseAttributesLine(scanner.Text(), domain, allowMacro)
if err != nil {
return attributes, err
}
@@ -105,6 +103,10 @@ func ReadAttributes(r io.Reader, domain []string, allowMacro bool) (attributes [
attributes = append(attributes, attribute)
}
+ if err := scanner.Err(); err != nil {
+ return attributes, err
+ }
+
return attributes, nil
}
diff --git a/plumbing/format/gitattributes/dir.go b/plumbing/format/gitattributes/dir.go
index 123fe25..d3b0dbe 100644
--- a/plumbing/format/gitattributes/dir.go
+++ b/plumbing/format/gitattributes/dir.go
@@ -4,6 +4,7 @@ import (
"os"
"github.com/go-git/go-billy/v5"
+
"github.com/go-git/go-git/v5/plumbing/format/config"
gioutil "github.com/go-git/go-git/v5/utils/ioutil"
)
@@ -26,6 +27,8 @@ func ReadAttributesFile(fs billy.Filesystem, path []string, attributesFile strin
return nil, err
}
+ defer gioutil.CheckClose(f, &err)
+
return ReadAttributes(f, path, allowMacro)
}
diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go
index d8fb30c..aca5d0d 100644
--- a/plumbing/format/gitignore/dir.go
+++ b/plumbing/format/gitignore/dir.go
@@ -116,7 +116,7 @@ func loadPatterns(fs billy.Filesystem, path string) (ps []Pattern, err error) {
return
}
-// LoadGlobalPatterns loads gitignore patterns from from the gitignore file
+// LoadGlobalPatterns loads gitignore patterns from the gitignore file
// declared in a user's ~/.gitconfig file. If the ~/.gitconfig file does not
// exist the function will return nil. If the core.excludesfile property
// is not declared, the function will return nil. If the file pointed to by
@@ -132,7 +132,7 @@ func LoadGlobalPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
return loadPatterns(fs, fs.Join(home, gitconfigFile))
}
-// LoadSystemPatterns loads gitignore patterns from from the gitignore file
+// LoadSystemPatterns loads gitignore patterns from the gitignore file
// declared in a system's /etc/gitconfig file. If the /etc/gitconfig file does
// not exist the function will return nil. If the core.excludesfile property
// is not declared, the function will return nil. If the file pointed to by
diff --git a/plumbing/format/pktline/error.go b/plumbing/format/pktline/error.go
new file mode 100644
index 0000000..2c0e5a7
--- /dev/null
+++ b/plumbing/format/pktline/error.go
@@ -0,0 +1,51 @@
+package pktline
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "strings"
+)
+
+var (
+ // ErrInvalidErrorLine is returned by Decode when the packet line is not an
+ // error line.
+ ErrInvalidErrorLine = errors.New("expected an error-line")
+
+ errPrefix = []byte("ERR ")
+)
+
+// ErrorLine is a packet line that contains an error message.
+// Once this packet is sent by client or server, the data transfer process is
+// terminated.
+// See https://git-scm.com/docs/pack-protocol#_pkt_line_format
+type ErrorLine struct {
+ Text string
+}
+
+// Error implements the error interface.
+func (e *ErrorLine) Error() string {
+ return e.Text
+}
+
+// Encode encodes the ErrorLine into a packet line.
+func (e *ErrorLine) Encode(w io.Writer) error {
+ p := NewEncoder(w)
+ return p.Encodef("%s%s\n", string(errPrefix), e.Text)
+}
+
+// Decode decodes a packet line into an ErrorLine.
+func (e *ErrorLine) Decode(r io.Reader) error {
+ s := NewScanner(r)
+ if !s.Scan() {
+ return s.Err()
+ }
+
+ line := s.Bytes()
+ if !bytes.HasPrefix(line, errPrefix) {
+ return ErrInvalidErrorLine
+ }
+
+ e.Text = strings.TrimSpace(string(line[4:]))
+ return nil
+}
diff --git a/plumbing/format/pktline/error_test.go b/plumbing/format/pktline/error_test.go
new file mode 100644
index 0000000..3cffd20
--- /dev/null
+++ b/plumbing/format/pktline/error_test.go
@@ -0,0 +1,68 @@
+package pktline
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "testing"
+)
+
+func TestEncodeEmptyErrorLine(t *testing.T) {
+ e := &ErrorLine{}
+ err := e.Encode(io.Discard)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestEncodeErrorLine(t *testing.T) {
+ e := &ErrorLine{
+ Text: "something",
+ }
+ var buf bytes.Buffer
+ err := e.Encode(&buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if buf.String() != "0012ERR something\n" {
+ t.Fatalf("unexpected encoded error line: %q", buf.String())
+ }
+}
+
+func TestDecodeEmptyErrorLine(t *testing.T) {
+ var buf bytes.Buffer
+ e := &ErrorLine{}
+ err := e.Decode(&buf)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if e.Text != "" {
+ t.Fatalf("unexpected error line: %q", e.Text)
+ }
+}
+
+func TestDecodeErrorLine(t *testing.T) {
+ var buf bytes.Buffer
+ buf.WriteString("000eERR foobar")
+ var e *ErrorLine
+ err := e.Decode(&buf)
+ if !errors.As(err, &e) {
+ t.Fatalf("expected error line, got: %T: %v", err, err)
+ }
+ if e.Text != "foobar" {
+ t.Fatalf("unexpected error line: %q", e.Text)
+ }
+}
+
+func TestDecodeErrorLineLn(t *testing.T) {
+ var buf bytes.Buffer
+ buf.WriteString("000fERR foobar\n")
+ var e *ErrorLine
+ err := e.Decode(&buf)
+ if !errors.As(err, &e) {
+ t.Fatalf("expected error line, got: %T: %v", err, err)
+ }
+ if e.Text != "foobar" {
+ t.Fatalf("unexpected error line: %q", e.Text)
+ }
+}
diff --git a/plumbing/format/pktline/scanner.go b/plumbing/format/pktline/scanner.go
index 5e85ed0..fbb137d 100644
--- a/plumbing/format/pktline/scanner.go
+++ b/plumbing/format/pktline/scanner.go
@@ -1,8 +1,10 @@
package pktline
import (
+ "bytes"
"errors"
"io"
+ "strings"
"github.com/go-git/go-git/v5/utils/trace"
)
@@ -69,6 +71,13 @@ func (s *Scanner) Scan() bool {
s.payload = s.payload[:l]
trace.Packet.Printf("packet: < %04x %s", l, s.payload)
+ if bytes.HasPrefix(s.payload, errPrefix) {
+ s.err = &ErrorLine{
+ Text: strings.TrimSpace(string(s.payload[4:])),
+ }
+ return false
+ }
+
return true
}
diff --git a/plumbing/object/commit.go b/plumbing/object/commit.go
index ceed5d0..3d096e1 100644
--- a/plumbing/object/commit.go
+++ b/plumbing/object/commit.go
@@ -27,7 +27,7 @@ const (
// the commit with the "mergetag" header.
headermergetag string = "mergetag"
- defaultUtf8CommitMesageEncoding MessageEncoding = "UTF-8"
+ defaultUtf8CommitMessageEncoding MessageEncoding = "UTF-8"
)
// Hash represents the hash of an object
@@ -189,7 +189,7 @@ func (c *Commit) Decode(o plumbing.EncodedObject) (err error) {
}
c.Hash = o.Hash()
- c.Encoding = defaultUtf8CommitMesageEncoding
+ c.Encoding = defaultUtf8CommitMessageEncoding
reader, err := o.Reader()
if err != nil {
@@ -335,7 +335,7 @@ func (c *Commit) encode(o plumbing.EncodedObject, includeSig bool) (err error) {
}
}
- if string(c.Encoding) != "" && c.Encoding != defaultUtf8CommitMesageEncoding {
+ if string(c.Encoding) != "" && c.Encoding != defaultUtf8CommitMessageEncoding {
if _, err = fmt.Fprintf(w, "\n%s %s", headerencoding, c.Encoding); err != nil {
return err
}
diff --git a/plumbing/object/commit_test.go b/plumbing/object/commit_test.go
index 3e1fe1b..a048926 100644
--- a/plumbing/object/commit_test.go
+++ b/plumbing/object/commit_test.go
@@ -228,7 +228,7 @@ change
Message: "Message\n\nFoo\nBar\nWith trailing blank lines\n\n",
TreeHash: plumbing.NewHash("f000000000000000000000000000000000000001"),
ParentHashes: []plumbing.Hash{plumbing.NewHash("f000000000000000000000000000000000000002")},
- Encoding: defaultUtf8CommitMesageEncoding,
+ Encoding: defaultUtf8CommitMessageEncoding,
},
{
Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts},
@@ -253,7 +253,7 @@ change
plumbing.NewHash("f000000000000000000000000000000000000003"),
},
MergeTag: tag,
- Encoding: defaultUtf8CommitMesageEncoding,
+ Encoding: defaultUtf8CommitMessageEncoding,
},
{
Author: Signature{Name: "Foo", Email: "foo@example.local", When: ts},
@@ -266,7 +266,7 @@ change
},
MergeTag: tag,
PGPSignature: pgpsignature,
- Encoding: defaultUtf8CommitMesageEncoding,
+ Encoding: defaultUtf8CommitMessageEncoding,
},
}
for _, commit := range commits {
@@ -455,7 +455,7 @@ func (s *SuiteCommit) TestStat(c *C) {
c.Assert(fileStats[1].Name, Equals, "php/crappy.php")
c.Assert(fileStats[1].Addition, Equals, 259)
c.Assert(fileStats[1].Deletion, Equals, 0)
- c.Assert(fileStats[1].String(), Equals, " php/crappy.php | 259 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n")
+ c.Assert(fileStats[1].String(), Equals, " php/crappy.php | 259 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n")
}
func (s *SuiteCommit) TestVerify(c *C) {
diff --git a/plumbing/object/commit_walker_path.go b/plumbing/object/commit_walker_path.go
index aa0ca15..c1ec8ba 100644
--- a/plumbing/object/commit_walker_path.go
+++ b/plumbing/object/commit_walker_path.go
@@ -57,6 +57,8 @@ func (c *commitPathIter) Next() (*Commit, error) {
}
func (c *commitPathIter) getNextFileCommit() (*Commit, error) {
+ var parentTree, currentTree *Tree
+
for {
// Parent-commit can be nil if the current-commit is the initial commit
parentCommit, parentCommitErr := c.sourceIter.Next()
@@ -68,13 +70,17 @@ func (c *commitPathIter) getNextFileCommit() (*Commit, error) {
parentCommit = nil
}
- // Fetch the trees of the current and parent commits
- currentTree, currTreeErr := c.currentCommit.Tree()
- if currTreeErr != nil {
- return nil, currTreeErr
+ if parentTree == nil {
+ var currTreeErr error
+ currentTree, currTreeErr = c.currentCommit.Tree()
+ if currTreeErr != nil {
+ return nil, currTreeErr
+ }
+ } else {
+ currentTree = parentTree
+ parentTree = nil
}
- var parentTree *Tree
if parentCommit != nil {
var parentTreeErr error
parentTree, parentTreeErr = parentCommit.Tree()
@@ -115,7 +121,8 @@ func (c *commitPathIter) hasFileChange(changes Changes, parent *Commit) bool {
// filename matches, now check if source iterator contains all commits (from all refs)
if c.checkParent {
- if parent != nil && isParentHash(parent.Hash, c.currentCommit) {
+ // Check if parent is beyond the initial commit
+ if parent == nil || isParentHash(parent.Hash, c.currentCommit) {
return true
}
continue
diff --git a/plumbing/object/commit_walker_test.go b/plumbing/object/commit_walker_test.go
index c47d68b..fa0ca7d 100644
--- a/plumbing/object/commit_walker_test.go
+++ b/plumbing/object/commit_walker_test.go
@@ -228,3 +228,29 @@ func (s *CommitWalkerSuite) TestCommitBSFIteratorWithIgnore(c *C) {
c.Assert(commit.Hash.String(), Equals, expected[i])
}
}
+
+func (s *CommitWalkerSuite) TestCommitPathIteratorInitialCommit(c *C) {
+ commit := s.commit(c, plumbing.NewHash(s.Fixture.Head))
+
+ fileName := "LICENSE"
+
+ var commits []*Commit
+ NewCommitPathIterFromIter(
+ func(path string) bool { return path == fileName },
+ NewCommitIterCTime(commit, nil, nil),
+ true,
+ ).ForEach(func(c *Commit) error {
+ commits = append(commits, c)
+ return nil
+ })
+
+ expected := []string{
+ "b029517f6300c2da0f4b651b8642506cd6aaf45d",
+ }
+
+ c.Assert(commits, HasLen, len(expected))
+
+ for i, commit := range commits {
+ c.Assert(commit.Hash.String(), Equals, expected[i])
+ }
+}
diff --git a/plumbing/object/patch.go b/plumbing/object/patch.go
index 06bc35b..3c61f62 100644
--- a/plumbing/object/patch.go
+++ b/plumbing/object/patch.go
@@ -6,7 +6,7 @@ import (
"errors"
"fmt"
"io"
- "math"
+ "strconv"
"strings"
"github.com/go-git/go-git/v5/plumbing"
@@ -234,69 +234,56 @@ func (fileStats FileStats) String() string {
return printStat(fileStats)
}
+// printStat prints the stats of changes in content of files.
+// Original implementation: https://github.com/git/git/blob/1a87c842ece327d03d08096395969aca5e0a6996/diff.c#L2615
+// Parts of the output:
+// <pad><filename><pad>|<pad><changeNumber><pad><+++/---><newline>
+// example: " main.go | 10 +++++++--- "
func printStat(fileStats []FileStat) string {
- padLength := float64(len(" "))
- newlineLength := float64(len("\n"))
- separatorLength := float64(len("|"))
- // Soft line length limit. The text length calculation below excludes
- // length of the change number. Adding that would take it closer to 80,
- // but probably not more than 80, until it's a huge number.
- lineLength := 72.0
-
- // Get the longest filename and longest total change.
- var longestLength float64
- var longestTotalChange float64
- for _, fs := range fileStats {
- if int(longestLength) < len(fs.Name) {
- longestLength = float64(len(fs.Name))
- }
- totalChange := fs.Addition + fs.Deletion
- if int(longestTotalChange) < totalChange {
- longestTotalChange = float64(totalChange)
- }
- }
-
- // Parts of the output:
- // <pad><filename><pad>|<pad><changeNumber><pad><+++/---><newline>
- // example: " main.go | 10 +++++++--- "
-
- // <pad><filename><pad>
- leftTextLength := padLength + longestLength + padLength
-
- // <pad><number><pad><+++++/-----><newline>
- // Excluding number length here.
- rightTextLength := padLength + padLength + newlineLength
+ maxGraphWidth := uint(53)
+ maxNameLen := 0
+ maxChangeLen := 0
- totalTextArea := leftTextLength + separatorLength + rightTextLength
- heightOfHistogram := lineLength - totalTextArea
+ scaleLinear := func(it, width, max uint) uint {
+ if it == 0 || max == 0 {
+ return 0
+ }
- // Scale the histogram.
- var scaleFactor float64
- if longestTotalChange > heightOfHistogram {
- // Scale down to heightOfHistogram.
- scaleFactor = longestTotalChange / heightOfHistogram
- } else {
- scaleFactor = 1.0
+ return 1 + (it * (width - 1) / max)
}
- finalOutput := ""
for _, fs := range fileStats {
- addn := float64(fs.Addition)
- deln := float64(fs.Deletion)
- addc := int(math.Floor(addn/scaleFactor))
- delc := int(math.Floor(deln/scaleFactor))
- if addc < 0 {
- addc = 0
+ if len(fs.Name) > maxNameLen {
+ maxNameLen = len(fs.Name)
}
- if delc < 0 {
- delc = 0
+
+ changes := strconv.Itoa(fs.Addition + fs.Deletion)
+ if len(changes) > maxChangeLen {
+ maxChangeLen = len(changes)
}
- adds := strings.Repeat("+", addc)
- dels := strings.Repeat("-", delc)
- finalOutput += fmt.Sprintf(" %s | %d %s%s\n", fs.Name, (fs.Addition + fs.Deletion), adds, dels)
}
- return finalOutput
+ result := ""
+ for _, fs := range fileStats {
+ add := uint(fs.Addition)
+ del := uint(fs.Deletion)
+ np := maxNameLen - len(fs.Name)
+ cp := maxChangeLen - len(strconv.Itoa(fs.Addition+fs.Deletion))
+
+ total := add + del
+ if total > maxGraphWidth {
+ add = scaleLinear(add, maxGraphWidth, total)
+ del = scaleLinear(del, maxGraphWidth, total)
+ }
+
+ adds := strings.Repeat("+", int(add))
+ dels := strings.Repeat("-", int(del))
+ namePad := strings.Repeat(" ", np)
+ changePad := strings.Repeat(" ", cp)
+
+ result += fmt.Sprintf(" %s%s | %s%d %s%s\n", fs.Name, namePad, changePad, total, adds, dels)
+ }
+ return result
}
func getFileStatsFromFilePatches(filePatches []fdiff.FilePatch) FileStats {
@@ -317,8 +304,8 @@ func getFileStatsFromFilePatches(filePatches []fdiff.FilePatch) FileStats {
// File is deleted.
cs.Name = from.Path()
} else if from.Path() != to.Path() {
- // File is renamed. Not supported.
- // cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path())
+ // File is renamed.
+ cs.Name = fmt.Sprintf("%s => %s", from.Path(), to.Path())
} else {
cs.Name = from.Path()
}
diff --git a/plumbing/object/patch_stats_test.go b/plumbing/object/patch_stats_test.go
new file mode 100644
index 0000000..f393c30
--- /dev/null
+++ b/plumbing/object/patch_stats_test.go
@@ -0,0 +1,54 @@
+package object_test
+
+import (
+ "time"
+
+ "github.com/go-git/go-billy/v5/memfs"
+ "github.com/go-git/go-billy/v5/util"
+ "github.com/go-git/go-git/v5"
+ "github.com/go-git/go-git/v5/plumbing/object"
+ "github.com/go-git/go-git/v5/storage/memory"
+
+ fixtures "github.com/go-git/go-git-fixtures/v4"
+ . "gopkg.in/check.v1"
+)
+
+type PatchStatsSuite struct {
+ fixtures.Suite
+}
+
+var _ = Suite(&PatchStatsSuite{})
+
+func (s *PatchStatsSuite) TestStatsWithRename(c *C) {
+ cm := &git.CommitOptions{
+ Author: &object.Signature{Name: "Foo", Email: "foo@example.local", When: time.Now()},
+ }
+
+ fs := memfs.New()
+ r, err := git.Init(memory.NewStorage(), fs)
+ c.Assert(err, IsNil)
+
+ w, err := r.Worktree()
+ c.Assert(err, IsNil)
+
+ util.WriteFile(fs, "foo", []byte("foo\nbar\n"), 0644)
+
+ _, err = w.Add("foo")
+ c.Assert(err, IsNil)
+
+ _, err = w.Commit("foo\n", cm)
+ c.Assert(err, IsNil)
+
+ _, err = w.Move("foo", "bar")
+ c.Assert(err, IsNil)
+
+ hash, err := w.Commit("rename foo to bar", cm)
+ c.Assert(err, IsNil)
+
+ commit, err := r.CommitObject(hash)
+ c.Assert(err, IsNil)
+
+ fileStats, err := commit.Stats()
+ c.Assert(err, IsNil)
+ c.Assert(fileStats[0].Name, Equals, "foo => bar")
+}
diff --git a/plumbing/object/patch_test.go b/plumbing/object/patch_test.go
index 2cff795..e0e63a5 100644
--- a/plumbing/object/patch_test.go
+++ b/plumbing/object/patch_test.go
@@ -45,3 +45,113 @@ func (s *PatchSuite) TestStatsWithSubmodules(c *C) {
c.Assert(err, IsNil)
c.Assert(p, NotNil)
}
+
+func (s *PatchSuite) TestFileStatsString(c *C) {
+ testCases := []struct {
+ description string
+ input FileStats
+ expected string
+ }{
+
+ {
+ description: "no files changed",
+ input: []FileStat{},
+ expected: "",
+ },
+ {
+ description: "one file touched - no changes",
+ input: []FileStat{
+ {
+ Name: "file1",
+ },
+ },
+ expected: " file1 | 0 \n",
+ },
+ {
+ description: "one file changed",
+ input: []FileStat{
+ {
+ Name: "file1",
+ Addition: 1,
+ },
+ },
+ expected: " file1 | 1 +\n",
+ },
+ {
+ description: "one file changed with one addition and one deletion",
+ input: []FileStat{
+ {
+ Name: ".github/workflows/git.yml",
+ Addition: 1,
+ Deletion: 1,
+ },
+ },
+ expected: " .github/workflows/git.yml | 2 +-\n",
+ },
+ {
+ description: "two files changed",
+ input: []FileStat{
+ {
+ Name: ".github/workflows/git.yml",
+ Addition: 1,
+ Deletion: 1,
+ },
+ {
+ Name: "cli/go-git/go.mod",
+ Addition: 4,
+ Deletion: 4,
+ },
+ },
+ expected: " .github/workflows/git.yml | 2 +-\n cli/go-git/go.mod | 8 ++++----\n",
+ },
+ {
+ description: "three files changed",
+ input: []FileStat{
+ {
+ Name: ".github/workflows/git.yml",
+ Addition: 3,
+ Deletion: 3,
+ },
+ {
+ Name: "worktree.go",
+ Addition: 107,
+ },
+ {
+ Name: "worktree_test.go",
+ Addition: 75,
+ },
+ },
+ expected: " .github/workflows/git.yml | 6 +++---\n" +
+ " worktree.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n" +
+ " worktree_test.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++\n",
+ },
+ {
+ description: "three files changed with deletions and additions",
+ input: []FileStat{
+ {
+ Name: ".github/workflows/git.yml",
+ Addition: 3,
+ Deletion: 3,
+ },
+ {
+ Name: "worktree.go",
+ Addition: 107,
+ Deletion: 217,
+ },
+ {
+ Name: "worktree_test.go",
+ Addition: 75,
+ Deletion: 275,
+ },
+ },
+ expected: " .github/workflows/git.yml | 6 +++---\n" +
+ " worktree.go | 324 ++++++++++++++++++-----------------------------------\n" +
+ " worktree_test.go | 350 ++++++++++++-----------------------------------------\n",
+ },
+ }
+
+ for _, tc := range testCases {
+ c.Log("Executing test cases:", tc.description)
+ c.Assert(printStat(tc.input), Equals, tc.expected)
+ }
+}
diff --git a/plumbing/object/tree.go b/plumbing/object/tree.go
index e9f7666..0fd0e51 100644
--- a/plumbing/object/tree.go
+++ b/plumbing/object/tree.go
@@ -7,6 +7,7 @@ import (
"io"
"path"
"path/filepath"
+ "sort"
"strings"
"github.com/go-git/go-git/v5/plumbing"
@@ -27,6 +28,7 @@ var (
ErrFileNotFound = errors.New("file not found")
ErrDirectoryNotFound = errors.New("directory not found")
ErrEntryNotFound = errors.New("entry not found")
+ ErrEntriesNotSorted = errors.New("entries in tree are not sorted")
)
// Tree is basically like a directory - it references a bunch of other trees
@@ -270,6 +272,28 @@ func (t *Tree) Decode(o plumbing.EncodedObject) (err error) {
return nil
}
+type TreeEntrySorter []TreeEntry
+
+func (s TreeEntrySorter) Len() int {
+ return len(s)
+}
+
+func (s TreeEntrySorter) Less(i, j int) bool {
+ name1 := s[i].Name
+ name2 := s[j].Name
+ if s[i].Mode == filemode.Dir {
+ name1 += "/"
+ }
+ if s[j].Mode == filemode.Dir {
+ name2 += "/"
+ }
+ return name1 < name2
+}
+
+func (s TreeEntrySorter) Swap(i, j int) {
+ s[i], s[j] = s[j], s[i]
+}
+
// Encode transforms a Tree into a plumbing.EncodedObject.
func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
o.SetType(plumbing.TreeObject)
@@ -279,7 +303,15 @@ func (t *Tree) Encode(o plumbing.EncodedObject) (err error) {
}
defer ioutil.CheckClose(w, &err)
+
+ if !sort.IsSorted(TreeEntrySorter(t.Entries)) {
+ return ErrEntriesNotSorted
+ }
+
for _, entry := range t.Entries {
+ if strings.IndexByte(entry.Name, 0) != -1 {
+ return fmt.Errorf("malformed filename %q", entry.Name)
+ }
if _, err = fmt.Fprintf(w, "%o %s", entry.Mode, entry.Name); err != nil {
return err
}
diff --git a/plumbing/object/tree_test.go b/plumbing/object/tree_test.go
index bb5fc7a..feb058a 100644
--- a/plumbing/object/tree_test.go
+++ b/plumbing/object/tree_test.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"io"
+ "sort"
"testing"
fixtures "github.com/go-git/go-git-fixtures/v4"
@@ -220,6 +221,30 @@ func (o *SortReadCloser) Read(p []byte) (int, error) {
return nw, nil
}
+func (s *TreeSuite) TestTreeEntriesSorted(c *C) {
+ tree := &Tree{
+ Entries: []TreeEntry{
+ {"foo", filemode.Empty, plumbing.NewHash("b029517f6300c2da0f4b651b8642506cd6aaf45d")},
+ {"bar", filemode.Empty, plumbing.NewHash("c029517f6300c2da0f4b651b8642506cd6aaf45d")},
+ {"baz", filemode.Empty, plumbing.NewHash("d029517f6300c2da0f4b651b8642506cd6aaf45d")},
+ },
+ }
+
+ {
+ c.Assert(sort.IsSorted(TreeEntrySorter(tree.Entries)), Equals, false)
+ obj := &plumbing.MemoryObject{}
+ err := tree.Encode(obj)
+ c.Assert(err, Equals, ErrEntriesNotSorted)
+ }
+
+ {
+ sort.Sort(TreeEntrySorter(tree.Entries))
+ obj := &plumbing.MemoryObject{}
+ err := tree.Encode(obj)
+ c.Assert(err, IsNil)
+ }
+}
+
func (s *TreeSuite) TestTreeDecodeEncodeIdempotent(c *C) {
trees := []*Tree{
{
@@ -231,6 +256,7 @@ func (s *TreeSuite) TestTreeDecodeEncodeIdempotent(c *C) {
},
}
for _, tree := range trees {
+ sort.Sort(TreeEntrySorter(tree.Entries))
obj := &plumbing.MemoryObject{}
err := tree.Encode(obj)
c.Assert(err, IsNil)
diff --git a/plumbing/object/treenoder.go b/plumbing/object/treenoder.go
index 6e7b334..2adb645 100644
--- a/plumbing/object/treenoder.go
+++ b/plumbing/object/treenoder.go
@@ -88,7 +88,9 @@ func (t *treeNoder) Children() ([]noder.Noder, error) {
}
}
- return transformChildren(parent)
+ var err error
+ t.children, err = transformChildren(parent)
+ return t.children, err
}
// Returns the children of a tree as treenoders.
diff --git a/plumbing/protocol/packp/common.go b/plumbing/protocol/packp/common.go
index fef50a4..a858323 100644
--- a/plumbing/protocol/packp/common.go
+++ b/plumbing/protocol/packp/common.go
@@ -48,6 +48,11 @@ func isFlush(payload []byte) bool {
return len(payload) == 0
}
+var (
+ // ErrNilWriter is returned when a nil writer is passed to the encoder.
+ ErrNilWriter = fmt.Errorf("nil writer")
+)
+
// ErrUnexpectedData represents an unexpected data decoding a message
type ErrUnexpectedData struct {
Msg string
diff --git a/plumbing/protocol/packp/gitproto.go b/plumbing/protocol/packp/gitproto.go
new file mode 100644
index 0000000..0b7ff8f
--- /dev/null
+++ b/plumbing/protocol/packp/gitproto.go
@@ -0,0 +1,120 @@
+package packp
+
+import (
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/go-git/go-git/v5/plumbing/format/pktline"
+)
+
+var (
+ // ErrInvalidGitProtoRequest is returned by Decode if the input is not a
+ // valid git protocol request.
+ ErrInvalidGitProtoRequest = fmt.Errorf("invalid git protocol request")
+)
+
+// GitProtoRequest is a command request for the git protocol.
+// It is used to send the command, endpoint, and extra parameters to the
+// remote.
+// See https://git-scm.com/docs/pack-protocol#_git_transport
+type GitProtoRequest struct {
+ RequestCommand string
+ Pathname string
+
+ // Optional
+ Host string
+
+ // Optional
+ ExtraParams []string
+}
+
+// validate validates the request.
+func (g *GitProtoRequest) validate() error {
+ if g.RequestCommand == "" {
+ return fmt.Errorf("%w: empty request command", ErrInvalidGitProtoRequest)
+ }
+
+ if g.Pathname == "" {
+ return fmt.Errorf("%w: empty pathname", ErrInvalidGitProtoRequest)
+ }
+
+ return nil
+}
+
+// Encode encodes the request into the writer.
+func (g *GitProtoRequest) Encode(w io.Writer) error {
+ if w == nil {
+ return ErrNilWriter
+ }
+
+ if err := g.validate(); err != nil {
+ return err
+ }
+
+ p := pktline.NewEncoder(w)
+ req := fmt.Sprintf("%s %s\x00", g.RequestCommand, g.Pathname)
+ if host := g.Host; host != "" {
+ req += fmt.Sprintf("host=%s\x00", host)
+ }
+
+ if len(g.ExtraParams) > 0 {
+ req += "\x00"
+ for _, param := range g.ExtraParams {
+ req += param + "\x00"
+ }
+ }
+
+ if err := p.Encode([]byte(req)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Decode decodes the request from the reader.
+func (g *GitProtoRequest) Decode(r io.Reader) error {
+ s := pktline.NewScanner(r)
+ if !s.Scan() {
+ err := s.Err()
+ if err == nil {
+ return ErrInvalidGitProtoRequest
+ }
+ return err
+ }
+
+ line := string(s.Bytes())
+ if len(line) == 0 {
+ return io.EOF
+ }
+
+ if line[len(line)-1] != 0 {
+ return fmt.Errorf("%w: missing null terminator", ErrInvalidGitProtoRequest)
+ }
+
+ parts := strings.SplitN(line, " ", 2)
+ if len(parts) != 2 {
+ return fmt.Errorf("%w: short request", ErrInvalidGitProtoRequest)
+ }
+
+ g.RequestCommand = parts[0]
+ params := strings.Split(parts[1], string(null))
+ if len(params) < 1 {
+ return fmt.Errorf("%w: missing pathname", ErrInvalidGitProtoRequest)
+ }
+
+ g.Pathname = params[0]
+ if len(params) > 1 {
+ g.Host = strings.TrimPrefix(params[1], "host=")
+ }
+
+ if len(params) > 2 {
+ for _, param := range params[2:] {
+ if param != "" {
+ g.ExtraParams = append(g.ExtraParams, param)
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/plumbing/protocol/packp/gitproto_test.go b/plumbing/protocol/packp/gitproto_test.go
new file mode 100644
index 0000000..9cf1049
--- /dev/null
+++ b/plumbing/protocol/packp/gitproto_test.go
@@ -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")
+ }
+}
diff --git a/plumbing/reference.go b/plumbing/reference.go
index 5a67f69..ddba930 100644
--- a/plumbing/reference.go
+++ b/plumbing/reference.go
@@ -3,6 +3,7 @@ package plumbing
import (
"errors"
"fmt"
+ "regexp"
"strings"
)
@@ -29,6 +30,9 @@ var RefRevParseRules = []string{
var (
ErrReferenceNotFound = errors.New("reference not found")
+
+ // ErrInvalidReferenceName is returned when a reference name is invalid.
+ ErrInvalidReferenceName = errors.New("invalid reference name")
)
// ReferenceType reference type's
@@ -124,6 +128,91 @@ func (r ReferenceName) Short() string {
return res
}
+var (
+ ctrlSeqs = regexp.MustCompile(`[\000-\037\177]`)
+)
+
+// Validate validates a reference name.
+// This follows the git-check-ref-format rules.
+// See https://git-scm.com/docs/git-check-ref-format
+//
+// It is important to note that this function does not check if the reference
+// exists in the repository.
+// It only checks if the reference name is valid.
+// This functions does not support the --refspec-pattern, --normalize, and
+// --allow-onelevel options.
+//
+// Git imposes the following rules on how references are named:
+//
+// 1. They can include slash / for hierarchical (directory) grouping, but no
+// slash-separated component can begin with a dot . or end with the
+// sequence .lock.
+// 2. They must contain at least one /. This enforces the presence of a
+// category like heads/, tags/ etc. but the actual names are not
+// restricted. If the --allow-onelevel option is used, this rule is
+// waived.
+// 3. They cannot have two consecutive dots .. anywhere.
+// 4. They cannot have ASCII control characters (i.e. bytes whose values are
+// lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon :
+// anywhere.
+// 5. They cannot have question-mark ?, asterisk *, or open bracket [
+// anywhere. See the --refspec-pattern option below for an exception to this
+// rule.
+// 6. They cannot begin or end with a slash / or contain multiple consecutive
+// slashes (see the --normalize option below for an exception to this rule).
+// 7. They cannot end with a dot ..
+// 8. They cannot contain a sequence @{.
+// 9. They cannot be the single character @.
+// 10. They cannot contain a \.
+func (r ReferenceName) Validate() error {
+ s := string(r)
+ if len(s) == 0 {
+ return ErrInvalidReferenceName
+ }
+
+ // HEAD is a special case
+ if r == HEAD {
+ return nil
+ }
+
+ // rule 7
+ if strings.HasSuffix(s, ".") {
+ return ErrInvalidReferenceName
+ }
+
+ // rule 2
+ parts := strings.Split(s, "/")
+ if len(parts) < 2 {
+ return ErrInvalidReferenceName
+ }
+
+ isBranch := r.IsBranch()
+ isTag := r.IsTag()
+ for _, part := range parts {
+ // rule 6
+ if len(part) == 0 {
+ return ErrInvalidReferenceName
+ }
+
+ if strings.HasPrefix(part, ".") || // rule 1
+ strings.Contains(part, "..") || // rule 3
+ ctrlSeqs.MatchString(part) || // rule 4
+ strings.ContainsAny(part, "~^:?*[ \t\n") || // rule 4 & 5
+ strings.Contains(part, "@{") || // rule 8
+ part == "@" || // rule 9
+ strings.Contains(part, "\\") || // rule 10
+ strings.HasSuffix(part, ".lock") { // rule 1
+ return ErrInvalidReferenceName
+ }
+
+ if (isBranch || isTag) && strings.HasPrefix(part, "-") { // branches & tags can't start with -
+ return ErrInvalidReferenceName
+ }
+ }
+
+ return nil
+}
+
const (
HEAD ReferenceName = "HEAD"
Master ReferenceName = "refs/heads/master"
diff --git a/plumbing/reference_test.go b/plumbing/reference_test.go
index 04dfef9..ce57075 100644
--- a/plumbing/reference_test.go
+++ b/plumbing/reference_test.go
@@ -103,6 +103,65 @@ func (s *ReferenceSuite) TestIsTag(c *C) {
c.Assert(r.IsTag(), Equals, true)
}
+func (s *ReferenceSuite) TestValidReferenceNames(c *C) {
+ valid := []ReferenceName{
+ "refs/heads/master",
+ "refs/notes/commits",
+ "refs/remotes/origin/master",
+ "HEAD",
+ "refs/tags/v3.1.1",
+ "refs/pulls/1/head",
+ "refs/pulls/1/merge",
+ "refs/pulls/1/abc.123",
+ "refs/pulls",
+ "refs/-", // should this be allowed?
+ }
+ for _, v := range valid {
+ c.Assert(v.Validate(), IsNil)
+ }
+
+ invalid := []ReferenceName{
+ "refs",
+ "refs/",
+ "refs//",
+ "refs/heads/\\",
+ "refs/heads/\\foo",
+ "refs/heads/\\foo/bar",
+ "abc",
+ "",
+ "refs/heads/ ",
+ "refs/heads/ /",
+ "refs/heads/ /foo",
+ "refs/heads/.",
+ "refs/heads/..",
+ "refs/heads/foo..",
+ "refs/heads/foo.lock",
+ "refs/heads/foo@{bar}",
+ "refs/heads/foo[",
+ "refs/heads/foo~",
+ "refs/heads/foo^",
+ "refs/heads/foo:",
+ "refs/heads/foo?",
+ "refs/heads/foo*",
+ "refs/heads/foo[bar",
+ "refs/heads/foo\t",
+ "refs/heads/@",
+ "refs/heads/@{bar}",
+ "refs/heads/\n",
+ "refs/heads/-foo",
+ "refs/heads/foo..bar",
+ "refs/heads/-",
+ "refs/tags/-",
+ "refs/tags/-foo",
+ }
+
+ for i, v := range invalid {
+ comment := Commentf("invalid reference name case %d: %s", i, v)
+ c.Assert(v.Validate(), NotNil, comment)
+ c.Assert(v.Validate(), ErrorMatches, "invalid reference name", comment)
+ }
+}
+
func benchMarkReferenceString(r *Reference, b *testing.B) {
for n := 0; n < b.N; n++ {
_ = r.String()
diff --git a/plumbing/transport/git/common.go b/plumbing/transport/git/common.go
index 92fc0be..2b878b0 100644
--- a/plumbing/transport/git/common.go
+++ b/plumbing/transport/git/common.go
@@ -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"
@@ -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 {
@@ -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 {
diff --git a/plumbing/transport/internal/common/common.go b/plumbing/transport/internal/common/common.go
index da1c2ac..9e1d023 100644
--- a/plumbing/transport/internal/common/common.go
+++ b/plumbing/transport/internal/common/common.go
@@ -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
@@ -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
diff --git a/plumbing/transport/internal/common/common_test.go b/plumbing/transport/internal/common/common_test.go
index f6f2f67..9344bb6 100644
--- a/plumbing/transport/internal/common/common_test.go
+++ b/plumbing/transport/internal/common/common_test.go
@@ -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)
diff --git a/plumbing/transport/ssh/common.go b/plumbing/transport/ssh/common.go
index 46fda73..05dea44 100644
--- a/plumbing/transport/ssh/common.go
+++ b/plumbing/transport/ssh/common.go
@@ -49,7 +49,9 @@ type runner struct {
func (r *runner) Command(cmd string, ep *transport.Endpoint, auth transport.AuthMethod) (common.Command, error) {
c := &command{command: cmd, endpoint: ep, config: r.config}
if auth != nil {
- c.setAuth(auth)
+ if err := c.setAuth(auth); err != nil {
+ return nil, err
+ }
}
if err := c.connect(); err != nil {
diff --git a/plumbing/transport/ssh/common_test.go b/plumbing/transport/ssh/common_test.go
index 4cc2a06..a724936 100644
--- a/plumbing/transport/ssh/common_test.go
+++ b/plumbing/transport/ssh/common_test.go
@@ -206,3 +206,26 @@ func (c *mockSSHConfig) Get(alias, key string) string {
return a[key]
}
+
+type invalidAuthMethod struct {
+}
+
+func (a *invalidAuthMethod) Name() string {
+ return "invalid"
+}
+
+func (a *invalidAuthMethod) String() string {
+ return "invalid"
+}
+
+func (s *SuiteCommon) TestCommandWithInvalidAuthMethod(c *C) {
+ uploadPack := &UploadPackSuite{}
+ uploadPack.SetUpSuite(c)
+ r := &runner{}
+ auth := &invalidAuthMethod{}
+
+ _, err := r.Command("command", uploadPack.newEndpoint(c, "endpoint"), auth)
+
+ c.Assert(err, NotNil)
+ c.Assert(err, ErrorMatches, "invalid auth method")
+}