aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing/protocol/packp
diff options
context:
space:
mode:
Diffstat (limited to 'plumbing/protocol/packp')
-rw-r--r--plumbing/protocol/packp/filter.go76
-rw-r--r--plumbing/protocol/packp/filter_test.go58
-rw-r--r--plumbing/protocol/packp/sideband/demux.go2
-rw-r--r--plumbing/protocol/packp/sideband/demux_test.go29
-rw-r--r--plumbing/protocol/packp/ulreq.go1
-rw-r--r--plumbing/protocol/packp/ulreq_encode.go11
-rw-r--r--plumbing/protocol/packp/ulreq_encode_test.go14
7 files changed, 188 insertions, 3 deletions
diff --git a/plumbing/protocol/packp/filter.go b/plumbing/protocol/packp/filter.go
new file mode 100644
index 0000000..145fc71
--- /dev/null
+++ b/plumbing/protocol/packp/filter.go
@@ -0,0 +1,76 @@
+package packp
+
+import (
+ "errors"
+ "fmt"
+ "github.com/go-git/go-git/v5/plumbing"
+ "net/url"
+ "strings"
+)
+
+var ErrUnsupportedObjectFilterType = errors.New("unsupported object filter type")
+
+// Filter values enable the partial clone capability which causes
+// the server to omit objects that match the filter.
+//
+// See [Git's documentation] for more details.
+//
+// [Git's documentation]: https://github.com/git/git/blob/e02ecfcc534e2021aae29077a958dd11c3897e4c/Documentation/rev-list-options.txt#L948
+type Filter string
+
+type BlobLimitPrefix string
+
+const (
+ BlobLimitPrefixNone BlobLimitPrefix = ""
+ BlobLimitPrefixKibi BlobLimitPrefix = "k"
+ BlobLimitPrefixMebi BlobLimitPrefix = "m"
+ BlobLimitPrefixGibi BlobLimitPrefix = "g"
+)
+
+// FilterBlobNone omits all blobs.
+func FilterBlobNone() Filter {
+ return "blob:none"
+}
+
+// FilterBlobLimit omits blobs of size at least n bytes (when prefix is
+// BlobLimitPrefixNone), n kibibytes (when prefix is BlobLimitPrefixKibi),
+// n mebibytes (when prefix is BlobLimitPrefixMebi) or n gibibytes (when
+// prefix is BlobLimitPrefixGibi). n can be zero, in which case all blobs
+// will be omitted.
+func FilterBlobLimit(n uint64, prefix BlobLimitPrefix) Filter {
+ return Filter(fmt.Sprintf("blob:limit=%d%s", n, prefix))
+}
+
+// FilterTreeDepth omits all blobs and trees whose depth from the root tree
+// is larger or equal to depth.
+func FilterTreeDepth(depth uint64) Filter {
+ return Filter(fmt.Sprintf("tree:%d", depth))
+}
+
+// FilterObjectType omits all objects which are not of the requested type t.
+// Supported types are TagObject, CommitObject, TreeObject and BlobObject.
+func FilterObjectType(t plumbing.ObjectType) (Filter, error) {
+ switch t {
+ case plumbing.TagObject:
+ fallthrough
+ case plumbing.CommitObject:
+ fallthrough
+ case plumbing.TreeObject:
+ fallthrough
+ case plumbing.BlobObject:
+ return Filter(fmt.Sprintf("object:type=%s", t.String())), nil
+ default:
+ return "", fmt.Errorf("%w: %s", ErrUnsupportedObjectFilterType, t.String())
+ }
+}
+
+// FilterCombine combines multiple Filter values together.
+func FilterCombine(filters ...Filter) Filter {
+ var escapedFilters []string
+
+ for _, filter := range filters {
+ escapedFilters = append(escapedFilters, url.QueryEscape(string(filter)))
+ }
+
+ return Filter(fmt.Sprintf("combine:%s", strings.Join(escapedFilters, "+")))
+}
diff --git a/plumbing/protocol/packp/filter_test.go b/plumbing/protocol/packp/filter_test.go
new file mode 100644
index 0000000..266670f
--- /dev/null
+++ b/plumbing/protocol/packp/filter_test.go
@@ -0,0 +1,58 @@
+package packp
+
+import (
+ "github.com/go-git/go-git/v5/plumbing"
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func TestFilterBlobNone(t *testing.T) {
+ require.EqualValues(t, "blob:none", FilterBlobNone())
+}
+
+func TestFilterBlobLimit(t *testing.T) {
+ require.EqualValues(t, "blob:limit=0", FilterBlobLimit(0, BlobLimitPrefixNone))
+ require.EqualValues(t, "blob:limit=1000", FilterBlobLimit(1000, BlobLimitPrefixNone))
+ require.EqualValues(t, "blob:limit=4k", FilterBlobLimit(4, BlobLimitPrefixKibi))
+ require.EqualValues(t, "blob:limit=4m", FilterBlobLimit(4, BlobLimitPrefixMebi))
+ require.EqualValues(t, "blob:limit=4g", FilterBlobLimit(4, BlobLimitPrefixGibi))
+}
+
+func TestFilterTreeDepth(t *testing.T) {
+ require.EqualValues(t, "tree:0", FilterTreeDepth(0))
+ require.EqualValues(t, "tree:1", FilterTreeDepth(1))
+ require.EqualValues(t, "tree:2", FilterTreeDepth(2))
+}
+
+func TestFilterObjectType(t *testing.T) {
+ filter, err := FilterObjectType(plumbing.TagObject)
+ require.NoError(t, err)
+ require.EqualValues(t, "object:type=tag", filter)
+
+ filter, err = FilterObjectType(plumbing.CommitObject)
+ require.NoError(t, err)
+ require.EqualValues(t, "object:type=commit", filter)
+
+ filter, err = FilterObjectType(plumbing.TreeObject)
+ require.NoError(t, err)
+ require.EqualValues(t, "object:type=tree", filter)
+
+ filter, err = FilterObjectType(plumbing.BlobObject)
+ require.NoError(t, err)
+ require.EqualValues(t, "object:type=blob", filter)
+
+ _, err = FilterObjectType(plumbing.InvalidObject)
+ require.Error(t, err)
+
+ _, err = FilterObjectType(plumbing.OFSDeltaObject)
+ require.Error(t, err)
+}
+
+func TestFilterCombine(t *testing.T) {
+ require.EqualValues(t, "combine:tree%3A2+blob%3Anone",
+ FilterCombine(
+ FilterTreeDepth(2),
+ FilterBlobNone(),
+ ),
+ )
+}
diff --git a/plumbing/protocol/packp/sideband/demux.go b/plumbing/protocol/packp/sideband/demux.go
index 0116f96..01d95a3 100644
--- a/plumbing/protocol/packp/sideband/demux.go
+++ b/plumbing/protocol/packp/sideband/demux.go
@@ -114,7 +114,7 @@ func (d *Demuxer) nextPackData() ([]byte, error) {
size := len(content)
if size == 0 {
- return nil, nil
+ return nil, io.EOF
} else if size > d.max {
return nil, ErrMaxPackedExceeded
}
diff --git a/plumbing/protocol/packp/sideband/demux_test.go b/plumbing/protocol/packp/sideband/demux_test.go
index 8f23353..1ba3ad9 100644
--- a/plumbing/protocol/packp/sideband/demux_test.go
+++ b/plumbing/protocol/packp/sideband/demux_test.go
@@ -105,8 +105,34 @@ func (s *SidebandSuite) TestDecodeWithProgress(c *C) {
c.Assert(progress, DeepEquals, []byte{'F', 'O', 'O', '\n'})
}
-func (s *SidebandSuite) TestDecodeWithUnknownChannel(c *C) {
+func (s *SidebandSuite) TestDecodeFlushEOF(c *C) {
+ expected := []byte("abcdefghijklmnopqrstuvwxyz")
+
+ input := bytes.NewBuffer(nil)
+ e := pktline.NewEncoder(input)
+ e.Encode(PackData.WithPayload(expected[0:8]))
+ e.Encode(ProgressMessage.WithPayload([]byte{'F', 'O', 'O', '\n'}))
+ e.Encode(PackData.WithPayload(expected[8:16]))
+ e.Encode(PackData.WithPayload(expected[16:26]))
+ e.Flush()
+ e.Encode(PackData.WithPayload([]byte("bar\n")))
+
+ output := bytes.NewBuffer(nil)
+ content := bytes.NewBuffer(nil)
+ d := NewDemuxer(Sideband64k, input)
+ d.Progress = output
+
+ n, err := content.ReadFrom(d)
+ c.Assert(err, IsNil)
+ c.Assert(n, Equals, int64(26))
+ c.Assert(content.Bytes(), DeepEquals, expected)
+ progress, err := io.ReadAll(output)
+ c.Assert(err, IsNil)
+ c.Assert(progress, DeepEquals, []byte{'F', 'O', 'O', '\n'})
+}
+
+func (s *SidebandSuite) TestDecodeWithUnknownChannel(c *C) {
buf := bytes.NewBuffer(nil)
e := pktline.NewEncoder(buf)
e.Encode([]byte{'4', 'F', 'O', 'O', '\n'})
@@ -150,5 +176,4 @@ func (s *SidebandSuite) TestDecodeErrMaxPacked(c *C) {
n, err := io.ReadFull(d, content)
c.Assert(err, Equals, ErrMaxPackedExceeded)
c.Assert(n, Equals, 0)
-
}
diff --git a/plumbing/protocol/packp/ulreq.go b/plumbing/protocol/packp/ulreq.go
index 344f8c7..ef4e08a 100644
--- a/plumbing/protocol/packp/ulreq.go
+++ b/plumbing/protocol/packp/ulreq.go
@@ -17,6 +17,7 @@ type UploadRequest struct {
Wants []plumbing.Hash
Shallows []plumbing.Hash
Depth Depth
+ Filter Filter
}
// Depth values stores the desired depth of the requested packfile: see
diff --git a/plumbing/protocol/packp/ulreq_encode.go b/plumbing/protocol/packp/ulreq_encode.go
index c451e23..8b19c0f 100644
--- a/plumbing/protocol/packp/ulreq_encode.go
+++ b/plumbing/protocol/packp/ulreq_encode.go
@@ -132,6 +132,17 @@ func (e *ulReqEncoder) encodeDepth() stateFn {
return nil
}
+ return e.encodeFilter
+}
+
+func (e *ulReqEncoder) encodeFilter() stateFn {
+ if filter := e.data.Filter; filter != "" {
+ if err := e.pe.Encodef("filter %s\n", filter); err != nil {
+ e.err = fmt.Errorf("encoding filter %s: %s", filter, err)
+ return nil
+ }
+ }
+
return e.encodeFlush
}
diff --git a/plumbing/protocol/packp/ulreq_encode_test.go b/plumbing/protocol/packp/ulreq_encode_test.go
index ba6df1a..247de27 100644
--- a/plumbing/protocol/packp/ulreq_encode_test.go
+++ b/plumbing/protocol/packp/ulreq_encode_test.go
@@ -273,6 +273,20 @@ func (s *UlReqEncodeSuite) TestDepthReference(c *C) {
testUlReqEncode(c, ur, expected)
}
+func (s *UlReqEncodeSuite) TestFilter(c *C) {
+ ur := NewUploadRequest()
+ ur.Wants = append(ur.Wants, plumbing.NewHash("1111111111111111111111111111111111111111"))
+ ur.Filter = FilterTreeDepth(0)
+
+ expected := []string{
+ "want 1111111111111111111111111111111111111111\n",
+ "filter tree:0\n",
+ pktline.FlushString,
+ }
+
+ testUlReqEncode(c, ur, expected)
+}
+
func (s *UlReqEncodeSuite) TestAll(c *C) {
ur := NewUploadRequest()
ur.Wants = append(ur.Wants,