Repository: go-git/go-git
Pull request #1375 (2025-01-13 11:31:40)

git: fetch and clone --filter support
This PR adds Filter support for git clone and fetch. #713 

I see an open PR #1108 that implemented this for fetch but was left open somehow. This is a more complete version of #1108.



Patch:
diff --git a/options.go b/options.go
index aba409a6..71e4e084 100644
--- a/options.go
+++ b/options.go
@@ -12,6 +12,7 @@ import (
 	"github.com/go-git/go-git/v5/plumbing"
 	formatcfg "github.com/go-git/go-git/v5/plumbing/format/config"
 	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/go-git/go-git/v5/plumbing/protocol/packp"
 	"github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 )
@@ -85,6 +86,9 @@ type CloneOptions struct {
 	//
 	// [Reference]: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---shared
 	Shared bool
+	// Filter requests that the server to send only a subset of the objects.
+	// See https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--filterltfilter-specgtcode
+	Filter packp.Filter
 }
 
 // MergeOptions describes how a merge should be performed.
@@ -220,6 +224,9 @@ type FetchOptions struct {
 	// Prune specify that local refs that match given RefSpecs and that do
 	// not exist remotely will be removed.
 	Prune bool
+	// Filter requests that the server to send only a subset of the objects.
+	// See https://git-scm.com/docs/git-clone#Documentation/git-clone.txt-code--filterltfilter-specgtcode
+	Filter packp.Filter
 }
 
 // Validate validates the fields and sets the default values.
diff --git a/remote.go b/remote.go
index 5b980fb6..9d6f2682 100644
--- a/remote.go
+++ b/remote.go
@@ -34,6 +34,7 @@ var (
 	ErrForceNeeded           = errors.New("some refs were not updated")
 	ErrExactSHA1NotSupported = errors.New("server does not support exact SHA1 refspec")
 	ErrEmptyUrls             = errors.New("URLs cannot be empty")
+	ErrFilterNotSupported    = errors.New("server does not support filters")
 )
 
 type NoMatchingRefSpecError struct {
@@ -1168,6 +1169,16 @@ func (r *Remote) newUploadPackRequest(o *FetchOptions,
 		}
 	}
 
+	if o.Filter != "" {
+		if ar.Capabilities.Supports(capability.Filter) {
+			req.Filter = o.Filter
+			if err := req.Capabilities.Set(capability.Filter); err != nil {
+				return nil, err
+			}
+		} else {
+			return nil, ErrFilterNotSupported
+		}
+	}
 	isWildcard := true
 	for _, s := range o.RefSpecs {
 		if !s.IsWildcard() {
diff --git a/repository.go b/repository.go
index 99ca0566..979eb0c3 100644
--- a/repository.go
+++ b/repository.go
@@ -951,6 +951,7 @@ func (r *Repository) clone(ctx context.Context, o *CloneOptions) error {
 		InsecureSkipTLS: o.InsecureSkipTLS,
 		CABundle:        o.CABundle,
 		ProxyOptions:    o.ProxyOptions,
+		Filter:          o.Filter,
 	}, o.ReferenceName)
 	if err != nil {
 		return err


Test patch:
diff --git a/repository_test.go b/repository_test.go
index 891deaf0..4562c468 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -28,6 +28,7 @@ import (
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/cache"
 	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/go-git/go-git/v5/plumbing/protocol/packp"
 	"github.com/go-git/go-git/v5/plumbing/storer"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/storage"
@@ -1298,6 +1299,36 @@ func (s *RepositorySuite) TestFetchContext() {
 	s.NotNil(r.FetchContext(ctx, &FetchOptions{}))
 }
 
+func (s *RepositorySuite) TestFetchWithFilters() {
+	r, _ := Init(memory.NewStorage(), nil)
+	_, err := r.CreateRemote(&config.RemoteConfig{
+		Name: DefaultRemoteName,
+		URLs: []string{s.GetBasicLocalRepositoryURL()},
+	})
+	s.NoError(err)
+
+	err = r.Fetch(&FetchOptions{
+		Filter: packp.FilterBlobNone(),
+	})
+	s.ErrorIs(err, ErrFilterNotSupported)
+
+}
+func (s *RepositorySuite) TestFetchWithFiltersReal() {
+	r, _ := Init(memory.NewStorage(), nil)
+	_, err := r.CreateRemote(&config.RemoteConfig{
+		Name: DefaultRemoteName,
+		URLs: []string{"https://github.com/git-fixtures/basic.git"},
+	})
+	s.NoError(err)
+	err = r.Fetch(&FetchOptions{
+		Filter: packp.FilterBlobNone(),
+	})
+	s.NoError(err)
+	blob, err := r.BlobObject(plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492"))
+	s.NotNil(err)
+	s.Nil(blob)
+
+}
 func (s *RepositorySuite) TestCloneWithProgress() {
 	fs := memfs.New()
 
@@ -1636,6 +1667,18 @@ func (s *RepositorySuite) TestCloneDetachedHEADAnnotatedTag() {
 	s.Equal(7, count)
 }
 
+func (s *RepositorySuite) TestCloneWithFilter() {
+	r, _ := Init(memory.NewStorage(), nil)
+
+	err := r.clone(context.Background(), &CloneOptions{
+		URL:    "https://github.com/git-fixtures/basic.git",
+		Filter: packp.FilterTreeDepth(0),
+	})
+	s.NoError(err)
+	blob, err := r.BlobObject(plumbing.NewHash("9a48f23120e880dfbe41f7c9b7b708e9ee62a492"))
+	s.NotNil(err)
+	s.Nil(blob)
+}
 func (s *RepositorySuite) TestPush() {
 	url, err := os.MkdirTemp("", "")
 	s.NoError(err)