Repository: go-git/go-git
Pull request #1520 (2025-04-15 05:10:06)

v6-transport: Bump dependencies and forward-ports
Bump core dependencies and forward-port recent commits from `main`:
- 9328bf5b9b193d9f4d004bb5d55da6e42d10df53
- 023e32ad90c40c72ef2b394614ad9e8fd5d48d5e
- f3841e1483a8e13d1c1ccc59834e7a4a4aefda8f
- d2afec99cc9ae6a89486bb0b946fc0e140479dc1
- 6e2afe0697855a813c4d89def5a55794cc90f121
- 96914bff9f8bf0331d93737fdcc0c98211d7cf72
- c560cf5a4ab3c9a06e9c8658fbd9c49a5368ea80
- 9dc151a791d5076853b61141eedf342362adff16
- e8c9fcd5571191ca3227c5e67bf9d2ebc2533135


Patch:
diff --git a/blame.go b/blame.go
index 36c0d9e4..264d236a 100644
--- a/blame.go
+++ b/blame.go
@@ -107,10 +107,7 @@ func Blame(c *object.Commit, path string) (*BlameResult, error) {
 		b.lineToCommit[i] = needsMap[i].Commit
 	}
 
-	lines, err := newLines(finalLines, b.lineToCommit)
-	if err != nil {
-		return nil, err
-	}
+	lines := newLines(finalLines, b.lineToCommit)
 
 	return &BlameResult{
 		Path:  path,
@@ -143,7 +140,7 @@ func newLine(author, authorName, text string, date time.Time, hash plumbing.Hash
 	}
 }
 
-func newLines(contents []string, commits []*object.Commit) ([]*Line, error) {
+func newLines(contents []string, commits []*object.Commit) []*Line {
 	result := make([]*Line, 0, len(contents))
 	for i := range contents {
 		result = append(result, newLine(
@@ -152,7 +149,7 @@ func newLines(contents []string, commits []*object.Commit) ([]*Line, error) {
 		))
 	}
 
-	return result, nil
+	return result
 }
 
 // this struct is internally used by the blame function to hold its
diff --git a/go.mod b/go.mod
index e42e149a..e3de2945 100644
--- a/go.mod
+++ b/go.mod
@@ -14,13 +14,13 @@ replace (
 require (
 	dario.cat/mergo v1.0.1
 	github.com/Microsoft/go-winio v0.6.2
-	github.com/ProtonMail/go-crypto v1.1.6
+	github.com/ProtonMail/go-crypto v1.2.0
 	github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
 	github.com/elazarl/goproxy v1.7.2
 	github.com/emirpasic/gods v1.18.1
 	github.com/gliderlabs/ssh v0.3.8
 	github.com/go-git/gcfg/v2 v2.0.1
-	github.com/go-git/go-billy/v5 v5.6.0
+	github.com/go-git/go-billy/v5 v5.6.2
 	github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399
 	github.com/go-git/go-git-fixtures/v5 v5.0.0-20241203230421-0753e18f8f03
 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8
@@ -28,16 +28,16 @@ require (
 	github.com/pjbgf/sha1cd v0.3.2
 	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 	github.com/stretchr/testify v1.10.0
-	golang.org/x/crypto v0.36.0
-	golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
-	golang.org/x/net v0.37.0
-	golang.org/x/sys v0.31.0
-	golang.org/x/text v0.23.0
+	golang.org/x/crypto v0.37.0
+	golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
+	golang.org/x/net v0.39.0
+	golang.org/x/sys v0.32.0
+	golang.org/x/text v0.24.0
 )
 
 require (
 	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
-	github.com/cloudflare/circl v1.6.0 // indirect
+	github.com/cloudflare/circl v1.6.1 // indirect
 	github.com/cyphar/filepath-securejoin v0.4.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/kr/pretty v0.3.1 // indirect
diff --git a/go.sum b/go.sum
index a7d57c1b..0805c389 100644
--- a/go.sum
+++ b/go.sum
@@ -2,14 +2,14 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
 dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
-github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
+github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
+github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
-github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
+github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
+github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
 github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
@@ -56,18 +56,18 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
-golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
-golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
-golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
-golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
+golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
+golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
+golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
+golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
+golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
+golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
+golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
+golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
+golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
+golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
diff --git a/plumbing/format/commitgraph/doc.go b/plumbing/format/commitgraph/doc.go
index 851afc2a..79e45c46 100644
--- a/plumbing/format/commitgraph/doc.go
+++ b/plumbing/format/commitgraph/doc.go
@@ -102,5 +102,5 @@
 //	H-byte HASH-checksum of all of the above.
 //
 // Source:
-// https://raw.githubusercontent.com/git/git/master/Documentation/technical/commit-graph-format.txt
+// https://raw.githubusercontent.com/git/git/master/Documentation/technical/commit-graph.adoc
 package commitgraph
diff --git a/plumbing/format/commitgraph/file.go b/plumbing/format/commitgraph/file.go
index c6d189fa..86f3634c 100644
--- a/plumbing/format/commitgraph/file.go
+++ b/plumbing/format/commitgraph/file.go
@@ -60,13 +60,13 @@ type ReaderAtCloser interface {
 }
 
 // OpenFileIndex opens a serialized commit graph file in the format described at
-// https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.txt
+// https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.adoc
 func OpenFileIndex(reader ReaderAtCloser) (Index, error) {
 	return OpenFileIndexWithParent(reader, nil)
 }
 
 // OpenFileIndexWithParent opens a serialized commit graph file in the format described at
-// https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.txt
+// https://github.com/git/git/blob/master/Documentation/technical/commit-graph-format.adoc
 func OpenFileIndexWithParent(reader ReaderAtCloser, parent Index) (Index, error) {
 	if reader == nil {
 		return nil, io.ErrUnexpectedEOF
diff --git a/plumbing/format/gitignore/pattern.go b/plumbing/format/gitignore/pattern.go
index 450b3cdf..c80e8417 100644
--- a/plumbing/format/gitignore/pattern.go
+++ b/plumbing/format/gitignore/pattern.go
@@ -146,6 +146,10 @@ func (p *pattern) globMatch(path []string, isDir bool) bool {
 			}
 			matched = true
 			path = path[1:]
+			// files matching dir globs, don't match
+			if len(path) == 0 && i < len(p.pattern)-1 {
+				matched = false
+			}
 		}
 	}
 	if matched && p.dirOnly && !isDir && len(path) == 0 {
diff --git a/plumbing/format/objfile/writer.go b/plumbing/format/objfile/writer.go
index feb56435..070ffb47 100644
--- a/plumbing/format/objfile/writer.go
+++ b/plumbing/format/objfile/writer.go
@@ -25,6 +25,8 @@ type Writer struct {
 
 	closed  bool
 	pending int64 // number of unwritten bytes
+
+	closeErr error
 }
 
 // NewWriter returns a new Writer writing to w.
@@ -101,9 +103,17 @@ func (w *Writer) Hash() plumbing.Hash {
 //
 // Calling Close does not close the wrapped io.Writer originally passed to
 // NewWriter.
+//
+// It returns an error, if any. Close will return the same error if called
+// multiple times.
 func (w *Writer) Close() error {
+	if w.closed {
+		return w.closeErr
+	}
+
 	defer sync.PutZlibWriter(w.zlib)
 	if err := w.zlib.Close(); err != nil {
+		w.closeErr = err
 		return err
 	}
 
diff --git a/plumbing/format/packfile/parser.go b/plumbing/format/packfile/parser.go
index a6746ba6..0b678303 100644
--- a/plumbing/format/packfile/parser.go
+++ b/plumbing/format/packfile/parser.go
@@ -190,7 +190,10 @@ func (p *Parser) processDelta(oh *ObjectHeader) error {
 
 	var deltaData bytes.Buffer
 	if oh.content.Len() > 0 {
-		oh.content.WriteTo(&deltaData)
+		_, err = oh.content.WriteTo(&deltaData)
+		if err != nil {
+			return err
+		}
 	} else {
 		deltaData = *bytes.NewBuffer(make([]byte, 0, oh.Size))
 		err = p.scanner.inflateContent(oh.ContentOffset, &deltaData)
diff --git a/plumbing/storer/reference.go b/plumbing/storer/reference.go
index 9ae7a218..fa973347 100644
--- a/plumbing/storer/reference.go
+++ b/plumbing/storer/reference.go
@@ -215,8 +215,11 @@ func (iter *MultiReferenceIter) Close() {
 // ResolveReference resolves a SymbolicReference to a HashReference.
 func ResolveReference(s ReferenceStorer, n plumbing.ReferenceName) (*plumbing.Reference, error) {
 	r, err := s.Reference(n)
-	if err != nil || r == nil {
-		return r, err
+	if err != nil {
+		return nil, err
+	}
+	if r == nil {
+		return nil, plumbing.ErrReferenceNotFound
 	}
 	return resolveReference(s, r, 0)
 }
diff --git a/utils/merkletrie/change.go b/utils/merkletrie/change.go
index 80f09e02..d18662ac 100644
--- a/utils/merkletrie/change.go
+++ b/utils/merkletrie/change.go
@@ -131,7 +131,9 @@ func (l *Changes) addRecursive(root noder.Path, ctor noderToChangeFn) error {
 	}
 
 	if !root.IsDir() {
-		l.Add(ctor(root))
+		if !root.Skip() {
+			l.Add(ctor(root))
+		}
 		return nil
 	}
 
@@ -148,7 +150,7 @@ func (l *Changes) addRecursive(root noder.Path, ctor noderToChangeFn) error {
 			}
 			return err
 		}
-		if current.IsDir() {
+		if current.IsDir() || current.Skip() {
 			continue
 		}
 		l.Add(ctor(current))
diff --git a/utils/merkletrie/difftree.go b/utils/merkletrie/difftree.go
index 0ca349f7..ced7c595 100644
--- a/utils/merkletrie/difftree.go
+++ b/utils/merkletrie/difftree.go
@@ -297,18 +297,16 @@ func DiffTreeContext(ctx context.Context, fromTree, toTree noder.Noder,
 		case noMoreNoders:
 			return ret, nil
 		case onlyFromRemains:
-			if err = ret.AddRecursiveDelete(from); err != nil {
-				return nil, err
+			if !from.Skip() {
+				if err = ret.AddRecursiveDelete(from); err != nil {
+					return nil, err
+				}
 			}
 			if err = ii.nextFrom(); err != nil {
 				return nil, err
 			}
 		case onlyToRemains:
-			if to.Skip() {
-				if err = ret.AddRecursiveDelete(to); err != nil {
-					return nil, err
-				}
-			} else {
+			if !to.Skip() {
 				if err = ret.AddRecursiveInsert(to); err != nil {
 					return nil, err
 				}
@@ -317,26 +315,25 @@ func DiffTreeContext(ctx context.Context, fromTree, toTree noder.Noder,
 				return nil, err
 			}
 		case bothHaveNodes:
-			if from.Skip() {
-				if err = ret.AddRecursiveDelete(from); err != nil {
-					return nil, err
-				}
-				if err := ii.nextBoth(); err != nil {
-					return nil, err
+			var err error
+			switch {
+			case from.Skip():
+				if from.Name() == to.Name() {
+					err = ii.nextBoth()
+				} else {
+					err = ii.nextFrom()
 				}
-				break
-			}
-			if to.Skip() {
-				if err = ret.AddRecursiveDelete(to); err != nil {
-					return nil, err
-				}
-				if err := ii.nextBoth(); err != nil {
-					return nil, err
+			case to.Skip():
+				if from.Name() == to.Name() {
+					err = ii.nextBoth()
+				} else {
+					err = ii.nextTo()
 				}
-				break
+			default:
+				err = diffNodes(&ret, ii)
 			}
 
-			if err = diffNodes(&ret, ii); err != nil {
+			if err != nil {
 				return nil, err
 			}
 		default:
diff --git a/utils/merkletrie/index/node.go b/utils/merkletrie/index/node.go
index 7da25de7..73456938 100644
--- a/utils/merkletrie/index/node.go
+++ b/utils/merkletrie/index/node.go
@@ -36,7 +36,15 @@ func NewRootNode(idx *index.Index) noder.Noder {
 			parent := fullpath
 			fullpath = path.Join(fullpath, part)
 
-			if _, ok := m[fullpath]; ok {
+			// It's possible that the first occurrence of subdirectory is skipped.
+			// The parent node can be created with SkipWorktree set to true, but
+			// if any future children do not skip their subtree, the entire lineage
+			// of the tree needs to have this value set to false so that subdirectories
+			// are not ignored.
+			if parentNode, ok := m[fullpath]; ok {
+				if e.SkipWorktree == false {
+					parentNode.skip = false
+				}
 				continue
 			}
 


Test patch:
diff --git a/blame_test.go b/blame_test.go
index 7a68784d..077b0643 100644
--- a/blame_test.go
+++ b/blame_test.go
@@ -22,24 +22,22 @@ func TestBlameSuite(t *testing.T) {
 
 func (s *BlameSuite) TestNewLines() {
 	h := plumbing.NewHash("ce9f123d790717599aaeb76bc62510de437761be")
-	lines, err := newLines([]string{"foo"}, []*object.Commit{{
+	lines := newLines([]string{"foo"}, []*object.Commit{{
 		Hash:    h,
 		Message: "foo",
 	}})
 
-	s.NoError(err)
 	s.Len(lines, 1)
 	s.Equal("foo", lines[0].Text)
 	s.Equal(h, lines[0].Hash)
 }
 
 func (s *BlameSuite) TestNewLinesWithNewLine() {
-	lines, err := newLines([]string{"foo", ""}, []*object.Commit{
+	lines := newLines([]string{"foo", ""}, []*object.Commit{
 		{Message: "foo"},
 		{Message: "bar"},
 	})
 
-	s.NoError(err)
 	s.Len(lines, 2)
 	s.Equal("foo", lines[0].Text)
 	s.Equal("", lines[1].Text)
diff --git a/plumbing/format/gitignore/pattern_test.go b/plumbing/format/gitignore/pattern_test.go
index 54623c6c..17aa16ac 100644
--- a/plumbing/format/gitignore/pattern_test.go
+++ b/plumbing/format/gitignore/pattern_test.go
@@ -221,7 +221,7 @@ func (s *PatternSuite) TestGlobMatch_tailingAsterisks() {
 func (s *PatternSuite) TestGlobMatch_tailingAsterisks_exactMatch() {
 	p := ParsePattern("/*lue/vol?ano/**", nil)
 	r := p.Match([]string{"value", "volcano"}, false)
-	s.Equal(Exclude, r)
+	s.Equal(NoMatch, r)
 }
 
 func (s *PatternSuite) TestGlobMatch_middleAsterisks_emptyMatch() {
@@ -289,3 +289,15 @@ func (s *PatternSuite) TestGlobMatch_issue_923() {
 	r := p.Match([]string{"packages", "flutter_tools", "lib", "src", "android", "gradle.dart"}, false)
 	s.Equal(NoMatch, r)
 }
+
+func (s *PatternSuite) TestGlobMatch_folderVersusFile() {
+	p := ParsePattern("/a*/**", nil)
+	r := p.Match([]string{"ab"}, false)
+	s.Equal(NoMatch, r)
+}
+
+func (s *PatternSuite) TestGlobMatch_folderVersusFileAgain() {
+	p := ParsePattern("/a*/**/a*", nil)
+	r := p.Match([]string{"ab", "ab"}, false)
+	s.Equal(Exclude, r)
+}
diff --git a/plumbing/transport/ssh/knownhosts/knownhosts_test.go b/plumbing/transport/ssh/knownhosts/knownhosts_test.go
index 99ca9aff..2ff0162b 100644
--- a/plumbing/transport/ssh/knownhosts/knownhosts_test.go
+++ b/plumbing/transport/ssh/knownhosts/knownhosts_test.go
@@ -106,7 +106,7 @@ func TestHostKeys(t *testing.T) {
 		"only-rsa.example.test:22":     {"ssh-rsa"},
 		"only-ecdsa.example.test:22":   {"ecdsa-sha2-nistp256"},
 		"only-ed25519.example.test:22": {"ssh-ed25519"},
-		"multi.example.test:2233":      {"ssh-rsa", "ecdsa-sha2-nistp256", "ssh-ed25519"},
+		"multi.example.test:2233":      {"ssh-rsa", "ecdsa-sha2-nistp256", "ssh-ed25519", "ssh-ed25519"},
 		"192.168.1.102:2222":           {"ecdsa-sha2-nistp256", "ssh-ed25519"},
 		"unknown-host.example.test":    {}, // host not in file
 		"multi.example.test:22":        {}, // different port than entry in file
diff --git a/utils/merkletrie/index/node_test.go b/utils/merkletrie/index/node_test.go
index 6d766a7a..bc6ebd5a 100644
--- a/utils/merkletrie/index/node_test.go
+++ b/utils/merkletrie/index/node_test.go
@@ -2,7 +2,7 @@ package index
 
 import (
 	"bytes"
-	"path/filepath"
+	"path"
 	"testing"
 
 	"github.com/go-git/go-git/v6/plumbing"
@@ -47,14 +47,14 @@ func (s *NoderSuite) TestDiff() {
 func (s *NoderSuite) TestDiffChange() {
 	indexA := &index.Index{
 		Entries: []*index.Entry{{
-			Name: filepath.Join("bar", "baz", "bar"),
+			Name: path.Join("bar", "baz", "bar"),
 			Hash: plumbing.NewHash("8ab686eafeb1f44702738c8b0f24f2567c36da6d"),
 		}},
 	}
 
 	indexB := &index.Index{
 		Entries: []*index.Entry{{
-			Name: filepath.Join("bar", "baz", "foo"),
+			Name: path.Join("bar", "baz", "foo"),
 			Hash: plumbing.NewHash("8ab686eafeb1f44702738c8b0f24f2567c36da6d"),
 		}},
 	}
@@ -64,6 +64,32 @@ func (s *NoderSuite) TestDiffChange() {
 	s.Len(ch, 2)
 }
 
+func (s *NoderSuite) TestDiffSkipIssue1455() {
+	indexA := &index.Index{
+		Entries: []*index.Entry{
+			{
+				Name:         path.Join("bar", "baz", "bar"),
+				Hash:         plumbing.NewHash("8ab686eafeb1f44702738c8b0f24f2567c36da6d"),
+				SkipWorktree: true,
+			},
+			{
+				Name:         path.Join("bar", "biz", "bat"),
+				Hash:         plumbing.NewHash("8ab686eafeb1f44702738c8b0f24f2567c36da6d"),
+				SkipWorktree: false,
+			},
+		},
+	}
+
+	indexB := &index.Index{}
+
+	ch, err := merkletrie.DiffTree(NewRootNode(indexB), NewRootNode(indexA), isEquals)
+	s.NoError(err)
+	s.Len(ch, 1)
+	a, err := ch[0].Action()
+	s.NoError(err)
+	s.Equal(a, merkletrie.Insert)
+}
+
 func (s *NoderSuite) TestDiffDir() {
 	indexA := &index.Index{
 		Entries: []*index.Entry{{
@@ -74,7 +100,7 @@ func (s *NoderSuite) TestDiffDir() {
 
 	indexB := &index.Index{
 		Entries: []*index.Entry{{
-			Name: filepath.Join("foo", "bar"),
+			Name: path.Join("foo", "bar"),
 			Hash: plumbing.NewHash("8ab686eafeb1f44702738c8b0f24f2567c36da6d"),
 		}},
 	}
diff --git a/worktree_test.go b/worktree_test.go
index 6feb9d71..4632778e 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -1381,6 +1381,24 @@ func (s *WorktreeSuite) TestStatusAfterCheckout() {
 	s.True(status.IsClean())
 }
 
+func (s *WorktreeSuite) TestStatusAfterSparseCheckout() {
+	fs := memfs.New()
+	w := &Worktree{
+		r:          s.Repository,
+		Filesystem: fs,
+	}
+
+	err := w.Checkout(&CheckoutOptions{
+		SparseCheckoutDirectories: []string{"php"},
+		Force:                     true,
+	})
+	s.Require().NoError(err)
+
+	status, err := w.Status()
+	s.Require().NoError(err)
+	s.True(status.IsClean(), status)
+}
+
 func (s *WorktreeSuite) TestStatusModified() {
 	fs := s.TemporalFilesystem()