diff options
| author | 2026-03-04 08:26:56 +0800 | |
|---|---|---|
| committer | 2026-03-04 08:59:53 +0800 | |
| commit | ab7501be34032fb9e5c48726a68ae90a917af9eb (patch) | |
| tree | 20d005647569befea8133e953c3270e8fd2a2a5b /internal | |
| parent | *: gofumpt (diff) | |
| signature | No signature | |
*: Lint
Diffstat (limited to 'internal')
25 files changed, 211 insertions, 14 deletions
diff --git a/internal/adler32/adler32_amd64.go b/internal/adler32/adler32_amd64.go index cb67f21c..7dfab299 100644 --- a/internal/adler32/adler32_amd64.go +++ b/internal/adler32/adler32_amd64.go @@ -27,8 +27,10 @@ func New() hash.Hash32 { if !hasAVX2 { return adler32.New() } + d := new(digest) d.Reset() + return d } @@ -36,6 +38,7 @@ func (d *digest) MarshalBinary() ([]byte, error) { b := make([]byte, 0, marshaledSize) b = append(b, magic...) b = binary.BigEndian.AppendUint32(b, uint32(*d)) + return b, nil } @@ -43,10 +46,13 @@ func (d *digest) UnmarshalBinary(b []byte) error { if len(b) < len(magic) || string(b[:len(magic)]) != magic { return errors.New("hash/adler32: invalid hash state identifier") } + if len(b) != marshaledSize { return errors.New("hash/adler32: invalid hash state size") } + *d = digest(binary.BigEndian.Uint32(b[len(magic):])) + return nil } @@ -62,6 +68,7 @@ func (d *digest) Write(data []byte) (nn int, err error) { h := update(uint32(*d), data) *d = digest(h) } + return len(data), nil } @@ -76,5 +83,6 @@ func Checksum(data []byte) uint32 { if hasAVX2 && len(data) >= 64 { return adler32_avx2(1, data) } + return adler32.Checksum(data) } diff --git a/internal/adler32/adler32_generic.go b/internal/adler32/adler32_generic.go index 0908d8f7..56e3ff8b 100644 --- a/internal/adler32/adler32_generic.go +++ b/internal/adler32/adler32_generic.go @@ -16,11 +16,13 @@ const ( // Add p to the running checksum d. func update(d uint32, p []byte) uint32 { s1, s2 := d&0xffff, d>>16 + for len(p) > 0 { var q []byte if len(p) > nmax { p, q = p[:nmax], p[nmax:] } + for len(p) >= 4 { s1 += uint32(p[0]) s2 += s1 @@ -32,13 +34,16 @@ func update(d uint32, p []byte) uint32 { s2 += s1 p = p[4:] } + for _, x := range p { s1 += uint32(x) s2 += s1 } + s1 %= mod s2 %= mod p = q } + return s2<<16 | s1 } diff --git a/internal/adler32/bench_test.go b/internal/adler32/bench_test.go index 6c6f75ea..d2aebe8f 100644 --- a/internal/adler32/bench_test.go +++ b/internal/adler32/bench_test.go @@ -10,7 +10,7 @@ const benchmarkSize = 64 * 1024 var data = make([]byte, benchmarkSize) -func init() { +func init() { //nolint:gochecknoinits for i := range benchmarkSize { data[i] = byte(i % 256) } @@ -18,6 +18,7 @@ func init() { func BenchmarkChecksum(b *testing.B) { b.ReportAllocs() + for b.Loop() { adler32.Checksum(data) } diff --git a/internal/bufpool/buffers.go b/internal/bufpool/buffers.go index a5c27b67..91e30a31 100644 --- a/internal/bufpool/buffers.go +++ b/internal/bufpool/buffers.go @@ -62,9 +62,11 @@ var bufferPools = func() []sync.Pool { capCopy := classCap pools[i].New = func() any { buf := make([]byte, 0, capCopy) + return &buf } } + return pools }() @@ -80,9 +82,11 @@ func Borrow(capHint int) Buffer { if capHint < DefaultBufferCap { capHint = DefaultBufferCap } + classIdx, classCap, pooled := classFor(capHint) if !pooled { newBuf := make([]byte, 0, capHint) + return Buffer{buf: newBuf, pool: unpooled} } //nolint:forcetypeassert @@ -90,7 +94,9 @@ func Borrow(capHint int) Buffer { if cap(*buf) < classCap { *buf = make([]byte, 0, classCap) } + slice := (*buf)[:0] + return Buffer{buf: slice, pool: poolIndex(classIdx)} //#nosec G115 } @@ -110,6 +116,7 @@ func (buf *Buffer) Resize(n int) { if n < 0 { n = 0 } + buf.ensureCapacity(n) buf.buf = buf.buf[:n] } @@ -122,6 +129,7 @@ func (buf *Buffer) Append(src []byte) { if len(src) == 0 { return } + start := len(buf.buf) buf.ensureCapacity(start + len(src)) buf.buf = buf.buf[:start+len(src)] @@ -144,6 +152,7 @@ func (buf *Buffer) Release() { if buf.buf == nil { return } + buf.returnToPool() buf.buf = nil buf.pool = unpooled @@ -157,20 +166,26 @@ func (buf *Buffer) ensureCapacity(needed int) { if cap(buf.buf) >= needed { return } + classIdx, classCap, pooled := classFor(needed) + var newBuf []byte + if pooled { //nolint:forcetypeassert raw := bufferPools[classIdx].Get().(*[]byte) if cap(*raw) < classCap { *raw = make([]byte, 0, classCap) } + newBuf = (*raw)[:len(buf.buf)] } else { newBuf = make([]byte, len(buf.buf), classCap) } + copy(newBuf, buf.buf) buf.returnToPool() + buf.buf = newBuf if pooled { buf.pool = poolIndex(classIdx) //#nosec G115 @@ -185,6 +200,7 @@ func classFor(size int) (idx, classCap int, ok bool) { return i, class, true } } + return -1, size, false } @@ -192,6 +208,7 @@ func (buf *Buffer) returnToPool() { if buf.pool == unpooled { return } + tmp := buf.buf[:0] bufferPools[int(buf.pool)].Put(&tmp) } diff --git a/internal/bufpool/buffers_test.go b/internal/bufpool/buffers_test.go index 70861d33..224fa98c 100644 --- a/internal/bufpool/buffers_test.go +++ b/internal/bufpool/buffers_test.go @@ -15,19 +15,23 @@ func TestBorrowBufferResizeAndAppend(t *testing.T) { b.Append([]byte("alpha")) b.Append([]byte("beta")) + if got := string(b.Bytes()); got != "alphabeta" { t.Fatalf("unexpected contents: %q", got) } b.Resize(3) + if got := string(b.Bytes()); got != "alp" { t.Fatalf("resize shrink mismatch: %q", got) } b.Resize(8) + if len(b.Bytes()) != 8 { t.Fatalf("expected len 8 after grow, got %d", len(b.Bytes())) } + if prefix := string(b.Bytes()[:3]); prefix != "alp" { t.Fatalf("prefix lost after grow: %q", prefix) } @@ -39,6 +43,7 @@ func TestBorrowBufferRelease(t *testing.T) { b := Borrow(DefaultBufferCap / 2) b.Append([]byte("data")) b.Release() + if b.buf != nil { t.Fatal("expected buffer cleared after release") } @@ -59,9 +64,11 @@ func TestBorrowUsesLargerPools(t *testing.T) { if b.pool != poolIndex(classIdx) { t.Fatalf("expected pooled buffer in class %d, got %d", classIdx, b.pool) } + if cap(b.buf) != classCap { t.Fatalf("expected capacity %d, got %d", classCap, cap(b.buf)) } + b.Release() b2 := Borrow(request) @@ -70,6 +77,7 @@ func TestBorrowUsesLargerPools(t *testing.T) { if b2.pool != poolIndex(classIdx) { t.Fatalf("expected pooled buffer in class %d on reuse, got %d", classIdx, b2.pool) } + if cap(b2.buf) != classCap { t.Fatalf("expected capacity %d on reuse, got %d", classCap, cap(b2.buf)) } @@ -82,6 +90,7 @@ func TestGrowingBufferStaysPooled(t *testing.T) { defer b.Release() b.Append(make([]byte, DefaultBufferCap*3)) + if b.pool == unpooled { t.Fatal("buffer should stay pooled after growth within limit") } diff --git a/internal/intconv/intconv.go b/internal/intconv/intconv.go index 8bc77d8e..67f99a14 100644 --- a/internal/intconv/intconv.go +++ b/internal/intconv/intconv.go @@ -11,6 +11,7 @@ func Uint64ToInt(v uint64) (int, error) { if v > uint64(math.MaxInt) { return 0, fmt.Errorf("intconv: uint64 %d overflows int", v) } + return int(v), nil } @@ -19,6 +20,7 @@ func UintptrToInt(v uintptr) (int, error) { if v > uintptr(math.MaxInt) { return 0, fmt.Errorf("intconv: uintptr %d overflows int", v) } + return int(v), nil } @@ -27,6 +29,7 @@ func IntToUint64(v int) (uint64, error) { if v < 0 { return 0, fmt.Errorf("intconv: int %d is negative", v) } + return uint64(v), nil } @@ -35,5 +38,6 @@ func Int64ToInt32(v int64) (int32, error) { if v < math.MinInt32 || v > math.MaxInt32 { return 0, fmt.Errorf("intconv: int64 %d overflows int32", v) } + return int32(v), nil } diff --git a/internal/iolimit/expect_length_reader.go b/internal/iolimit/expect_length_reader.go index 477c207f..288e0e62 100644 --- a/internal/iolimit/expect_length_reader.go +++ b/internal/iolimit/expect_length_reader.go @@ -39,13 +39,16 @@ func (reader *expectLengthReader) Read(dst []byte) (int, error) { if reader.remaining == 0 { var probe [1]byte + n, err := reader.src.Read(probe[:]) if n > 0 { return 0, ErrExpectedLengthExceeded } + if err == nil { return 0, nil } + return 0, err } @@ -66,9 +69,11 @@ func (reader *expectLengthReader) Read(dst []byte) (int, error) { if reader.remaining > 0 { return n, io.ErrUnexpectedEOF } + if n > 0 { return n, nil } + return 0, io.EOF } diff --git a/internal/iolimit/expect_length_reader_test.go b/internal/iolimit/expect_length_reader_test.go index 503c88ed..e2cfeab0 100644 --- a/internal/iolimit/expect_length_reader_test.go +++ b/internal/iolimit/expect_length_reader_test.go @@ -13,15 +13,18 @@ func TestExpectLengthReaderExact(t *testing.T) { t.Parallel() r := iolimit.ExpectLengthReader(bytes.NewReader([]byte("hello")), 5) + got, err := io.ReadAll(r) if err != nil { t.Fatalf("ReadAll error: %v", err) } + if !bytes.Equal(got, []byte("hello")) { t.Fatalf("ReadAll = %q, want %q", got, "hello") } buf := make([]byte, 1) + n, err := r.Read(buf) if n != 0 || !errors.Is(err, io.EOF) { t.Fatalf("post-boundary Read = (%d,%v), want (0,EOF)", n, err) @@ -32,6 +35,7 @@ func TestExpectLengthReaderShort(t *testing.T) { t.Parallel() r := iolimit.ExpectLengthReader(bytes.NewReader([]byte("hey")), 5) + _, err := io.ReadAll(r) if !errors.Is(err, io.ErrUnexpectedEOF) { t.Fatalf("ReadAll error = %v, want ErrUnexpectedEOF", err) @@ -43,15 +47,18 @@ func TestExpectLengthReaderLongDetectedOnNextRead(t *testing.T) { r := iolimit.ExpectLengthReader(bytes.NewReader([]byte("hello!")), 5) buf := make([]byte, 5) + n, err := io.ReadFull(r, buf) if err != nil { t.Fatalf("ReadFull error: %v", err) } + if n != 5 || !bytes.Equal(buf, []byte("hello")) { t.Fatalf("ReadFull = (%d,%q), want (5,hello)", n, buf) } probe := make([]byte, 1) + n, err = r.Read(probe) if n != 0 || !errors.Is(err, iolimit.ErrExpectedLengthExceeded) { t.Fatalf("overflow Read = (%d,%v), want (0,ErrExpectedLengthExceeded)", n, err) @@ -63,6 +70,7 @@ func TestExpectLengthReaderEmptyExpected(t *testing.T) { r := iolimit.ExpectLengthReader(bytes.NewReader(nil), 0) buf := make([]byte, 1) + n, err := r.Read(buf) if n != 0 || !errors.Is(err, io.EOF) { t.Fatalf("Read = (%d,%v), want (0,EOF)", n, err) diff --git a/internal/lru/lru.go b/internal/lru/lru.go index 585aaa3f..fcbab646 100644 --- a/internal/lru/lru.go +++ b/internal/lru/lru.go @@ -39,9 +39,11 @@ func New[K comparable, V any](maxWeight int64, weightFn WeightFunc[K, V], onEvic if maxWeight < 0 { panic("lru: negative max weight") } + if weightFn == nil { panic("lru: nil weight function") } + return &Cache[K, V]{ maxWeight: maxWeight, weightFn: weightFn, @@ -61,6 +63,7 @@ func (cache *Cache[K, V]) Add(key K, value V) bool { if w < 0 { panic("lru: negative entry weight") } + if w > cache.maxWeight { return false } @@ -79,6 +82,7 @@ func (cache *Cache[K, V]) Add(key K, value V) bool { cache.weight += w cache.evictOverBudget() + return true } @@ -87,8 +91,10 @@ func (cache *Cache[K, V]) Get(key K) (V, bool) { elem, ok := cache.items[key] if !ok { var zero V + return zero, false } + cache.lru.MoveToBack(elem) //nolint:forcetypeassert return elem.Value.(*entry[K, V]).value, true @@ -99,6 +105,7 @@ func (cache *Cache[K, V]) Peek(key K) (V, bool) { elem, ok := cache.items[key] if !ok { var zero V + return zero, false } //nolint:forcetypeassert @@ -110,9 +117,12 @@ func (cache *Cache[K, V]) Remove(key K) (V, bool) { elem, ok := cache.items[key] if !ok { var zero V + return zero, false } + ent := cache.removeElem(elem) + return ent.value, true } @@ -148,6 +158,7 @@ func (cache *Cache[K, V]) SetMaxWeight(maxWeight int64) { if maxWeight < 0 { panic("lru: negative max weight") } + cache.maxWeight = maxWeight cache.evictOverBudget() } @@ -158,6 +169,7 @@ func (cache *Cache[K, V]) evictOverBudget() { if elem == nil { return } + cache.removeElem(elem) } } @@ -167,9 +179,11 @@ func (cache *Cache[K, V]) removeElem(elem *list.Element) *entry[K, V] { ent := elem.Value.(*entry[K, V]) cache.lru.Remove(elem) delete(cache.items, ent.key) + cache.weight -= ent.weight if cache.onEvict != nil { cache.onEvict(ent.key, ent.value) } + return ent } diff --git a/internal/lru/lru_test.go b/internal/lru/lru_test.go index adfec403..006a32b8 100644 --- a/internal/lru/lru_test.go +++ b/internal/lru/lru_test.go @@ -27,9 +27,11 @@ func TestCacheEvictsLRUAndGetUpdatesRecency(t *testing.T) { if _, ok := cache.Peek("a"); ok { t.Fatalf("expected a to be evicted") } + if _, ok := cache.Peek("b"); !ok { t.Fatalf("expected b to be present") } + if _, ok := cache.Peek("c"); !ok { t.Fatalf("expected c to be present") } @@ -37,14 +39,17 @@ func TestCacheEvictsLRUAndGetUpdatesRecency(t *testing.T) { if _, ok := cache.Get("b"); !ok { t.Fatalf("Get(b) should hit") } + cache.Add("d", testValue{weight: 4, label: "d"}) if _, ok := cache.Peek("c"); ok { t.Fatalf("expected c to be evicted after b was touched") } + if _, ok := cache.Peek("b"); !ok { t.Fatalf("expected b to remain present") } + if _, ok := cache.Peek("d"); !ok { t.Fatalf("expected d to be present") } @@ -60,11 +65,13 @@ func TestCachePeekDoesNotUpdateRecency(t *testing.T) { if _, ok := cache.Peek("a"); !ok { t.Fatalf("Peek(a) should hit") } + cache.Add("c", testValue{weight: 2, label: "c"}) if _, ok := cache.Peek("a"); ok { t.Fatalf("expected a to be evicted; Peek must not update recency") } + if _, ok := cache.Peek("b"); !ok { t.Fatalf("expected b to remain present") } @@ -74,6 +81,7 @@ func TestCacheReplaceAndResize(t *testing.T) { t.Parallel() var evicted []string + cache := lru.New[string, testValue](10, weightFn, func(key string, value testValue) { evicted = append(evicted, key+":"+value.label) }) @@ -85,17 +93,21 @@ func TestCacheReplaceAndResize(t *testing.T) { if cache.Weight() != 10 { t.Fatalf("Weight() = %d, want 10", cache.Weight()) } + if got, ok := cache.Peek("a"); !ok || got.label != "new" { t.Fatalf("Peek(a) = (%+v,%v), want new,true", got, ok) } + if !slices.Equal(evicted, []string{"a:old"}) { t.Fatalf("evicted = %v, want [a:old]", evicted) } cache.SetMaxWeight(8) + if _, ok := cache.Peek("b"); ok { t.Fatalf("expected b to be evicted after shrinking max weight") } + if !slices.Equal(evicted, []string{"a:old", "b:b"}) { t.Fatalf("evicted = %v, want [a:old b:b]", evicted) } @@ -105,6 +117,7 @@ func TestCacheRejectsOversizedWithoutMutation(t *testing.T) { t.Parallel() var evicted []string + cache := lru.New[string, testValue](5, weightFn, func(key string, value testValue) { evicted = append(evicted, key) }) @@ -113,12 +126,15 @@ func TestCacheRejectsOversizedWithoutMutation(t *testing.T) { if ok := cache.Add("b", testValue{weight: 6, label: "b"}); ok { t.Fatalf("Add oversized should return false") } + if got, ok := cache.Peek("a"); !ok || got.label != "a" { t.Fatalf("cache should remain unchanged after oversized add") } + if cache.Weight() != 3 { t.Fatalf("Weight() = %d, want 3", cache.Weight()) } + if len(evicted) != 0 { t.Fatalf("evicted = %v, want none", evicted) } @@ -126,9 +142,11 @@ func TestCacheRejectsOversizedWithoutMutation(t *testing.T) { if ok := cache.Add("a", testValue{weight: 6, label: "new"}); ok { t.Fatalf("oversized replace should return false") } + if got, ok := cache.Peek("a"); !ok || got.label != "a" { t.Fatalf("existing key should remain unchanged after oversized replace") } + if len(evicted) != 0 { t.Fatalf("evicted = %v, want none", evicted) } @@ -138,6 +156,7 @@ func TestCacheRemoveAndClear(t *testing.T) { t.Parallel() var evicted []string + cache := lru.New[string, testValue](10, weightFn, func(key string, value testValue) { evicted = append(evicted, key) }) @@ -150,11 +169,13 @@ func TestCacheRemoveAndClear(t *testing.T) { if !ok || removed.label != "b" { t.Fatalf("Remove(b) = (%+v,%v), want b,true", removed, ok) } + if cache.Len() != 2 || cache.Weight() != 6 { t.Fatalf("post-remove Len/Weight = %d/%d, want 2/6", cache.Len(), cache.Weight()) } cache.Clear() + if cache.Len() != 0 || cache.Weight() != 0 { t.Fatalf("post-clear Len/Weight = %d/%d, want 0/0", cache.Len(), cache.Weight()) } @@ -170,45 +191,55 @@ func TestCachePanicsForInvalidConfiguration(t *testing.T) { t.Run("negative max", func(t *testing.T) { t.Parallel() + defer func() { if recover() == nil { t.Fatalf("expected panic") } }() + _ = lru.New[string, testValue](-1, weightFn, nil) }) t.Run("nil weight function", func(t *testing.T) { t.Parallel() + defer func() { if recover() == nil { t.Fatalf("expected panic") } }() + _ = lru.New[string, testValue](1, nil, nil) }) t.Run("negative entry weight", func(t *testing.T) { t.Parallel() + cache := lru.New[string, testValue](10, func(_ string, _ testValue) int64 { return -1 }, nil) + defer func() { if recover() == nil { t.Fatalf("expected panic") } }() + cache.Add("x", testValue{weight: 1, label: "x"}) }) t.Run("set negative max", func(t *testing.T) { t.Parallel() + cache := lru.New[string, testValue](10, weightFn, nil) + defer func() { if recover() == nil { t.Fatalf("expected panic") } }() + cache.SetMaxWeight(-1) }) } diff --git a/internal/testgit/algorithms.go b/internal/testgit/algorithms.go index 81af4f75..5534aad0 100644 --- a/internal/testgit/algorithms.go +++ b/internal/testgit/algorithms.go @@ -9,6 +9,7 @@ import ( // ForEachAlgorithm runs a subtest for every supported algorithm. func ForEachAlgorithm(t *testing.T, fn func(t *testing.T, algo objectid.Algorithm)) { t.Helper() + for _, algo := range objectid.SupportedAlgorithms() { t.Run(algo.String(), func(t *testing.T) { fn(t, algo) diff --git a/internal/testgit/repo_cat_file.go b/internal/testgit/repo_cat_file.go index 9cc56db6..1325cf6f 100644 --- a/internal/testgit/repo_cat_file.go +++ b/internal/testgit/repo_cat_file.go @@ -9,5 +9,6 @@ import ( // CatFile returns raw output from git cat-file. func (testRepo *TestRepo) CatFile(tb testing.TB, mode string, id objectid.ObjectID) []byte { tb.Helper() + return testRepo.RunBytes(tb, "cat-file", mode, id.String()) } diff --git a/internal/testgit/repo_commit_tree.go b/internal/testgit/repo_commit_tree.go index 763474c2..5eee21ba 100644 --- a/internal/testgit/repo_commit_tree.go +++ b/internal/testgit/repo_commit_tree.go @@ -9,16 +9,21 @@ import ( // CommitTree creates a commit from a tree and message, optionally with parents. func (testRepo *TestRepo) CommitTree(tb testing.TB, tree objectid.ObjectID, message string, parents ...objectid.ObjectID) objectid.ObjectID { tb.Helper() + args := make([]string, 0, 2+2*len(parents)+2) + args = append(args, "commit-tree", tree.String()) for _, p := range parents { args = append(args, "-p", p.String()) } + args = append(args, "-m", message) hex := testRepo.Run(tb, args...) + id, err := objectid.ParseHex(testRepo.algo, hex) if err != nil { tb.Fatalf("parse commit-tree output %q: %v", hex, err) } + return id } diff --git a/internal/testgit/repo_hash_object.go b/internal/testgit/repo_hash_object.go index 10a05381..bc2def72 100644 --- a/internal/testgit/repo_hash_object.go +++ b/internal/testgit/repo_hash_object.go @@ -10,9 +10,11 @@ import ( func (testRepo *TestRepo) HashObject(tb testing.TB, objType string, body []byte) objectid.ObjectID { tb.Helper() hex := testRepo.RunInput(tb, body, "hash-object", "-t", objType, "-w", "--stdin") + id, err := objectid.ParseHex(testRepo.algo, hex) if err != nil { tb.Fatalf("parse git hash-object output %q: %v", hex, err) } + return id } diff --git a/internal/testgit/repo_make_commit.go b/internal/testgit/repo_make_commit.go index a569dfb1..c8bdc428 100644 --- a/internal/testgit/repo_make_commit.go +++ b/internal/testgit/repo_make_commit.go @@ -11,5 +11,6 @@ func (testRepo *TestRepo) MakeCommit(tb testing.TB, message string) (objectid.Ob tb.Helper() blobID, treeID := testRepo.MakeSingleFileTree(tb, "file.txt", []byte("commit-body\n")) commitID := testRepo.CommitTree(tb, treeID, message) + return blobID, treeID, commitID } diff --git a/internal/testgit/repo_make_single_file_tree.go b/internal/testgit/repo_make_single_file_tree.go index 7c53c658..e7a235a7 100644 --- a/internal/testgit/repo_make_single_file_tree.go +++ b/internal/testgit/repo_make_single_file_tree.go @@ -13,5 +13,6 @@ func (testRepo *TestRepo) MakeSingleFileTree(tb testing.TB, fileName string, fil blobID := testRepo.HashObject(tb, "blob", fileContent) treeInput := fmt.Sprintf("100644 blob %s\t%s\n", blobID.String(), fileName) treeID := testRepo.Mktree(tb, treeInput) + return blobID, treeID } diff --git a/internal/testgit/repo_mktree.go b/internal/testgit/repo_mktree.go index 34e6388d..565a0083 100644 --- a/internal/testgit/repo_mktree.go +++ b/internal/testgit/repo_mktree.go @@ -10,9 +10,11 @@ import ( func (testRepo *TestRepo) Mktree(tb testing.TB, input string) objectid.ObjectID { tb.Helper() hex := testRepo.RunInput(tb, []byte(input), "mktree") + id, err := objectid.ParseHex(testRepo.algo, hex) if err != nil { tb.Fatalf("parse mktree output %q: %v", hex, err) } + return id } diff --git a/internal/testgit/repo_new.go b/internal/testgit/repo_new.go index 8120a9a2..8a71e406 100644 --- a/internal/testgit/repo_new.go +++ b/internal/testgit/repo_new.go @@ -21,6 +21,7 @@ type RepoOptions struct { // NewRepo creates a temporary repository initialized with the requested options. func NewRepo(tb testing.TB, opts RepoOptions) *TestRepo { tb.Helper() + algo := opts.ObjectFormat if algo.Size() == 0 { tb.Fatalf("invalid algorithm: %v", algo) @@ -47,10 +48,13 @@ func NewRepo(tb testing.TB, opts RepoOptions) *TestRepo { if opts.Bare { args = append(args, "--bare") } + if opts.RefFormat != "" { args = append(args, "--ref-format="+opts.RefFormat) } + args = append(args, dir) testRepo.runBytes(tb, nil, "", args...) + return testRepo } diff --git a/internal/testgit/repo_refs.go b/internal/testgit/repo_refs.go index eb09a78b..66e08561 100644 --- a/internal/testgit/repo_refs.go +++ b/internal/testgit/repo_refs.go @@ -28,6 +28,7 @@ func (testRepo *TestRepo) SymbolicRef(tb testing.TB, name, target string) { // PackRefs runs git pack-refs with args. func (testRepo *TestRepo) PackRefs(tb testing.TB, args ...string) { tb.Helper() + cmd := append([]string{"pack-refs"}, args...) testRepo.Run(tb, cmd...) } @@ -35,10 +36,13 @@ func (testRepo *TestRepo) PackRefs(tb testing.TB, args ...string) { // ShowRef returns lines from git show-ref output. func (testRepo *TestRepo) ShowRef(tb testing.TB, args ...string) []string { tb.Helper() + cmd := append([]string{"show-ref"}, args...) + out := testRepo.Run(tb, cmd...) if strings.TrimSpace(out) == "" { return nil } + return strings.Split(strings.TrimSpace(out), "\n") } diff --git a/internal/testgit/repo_repack.go b/internal/testgit/repo_repack.go index 29fa8a4f..7773ac13 100644 --- a/internal/testgit/repo_repack.go +++ b/internal/testgit/repo_repack.go @@ -5,6 +5,7 @@ import "testing" // Repack runs "git repack" with args in the repository. func (testRepo *TestRepo) Repack(tb testing.TB, args ...string) { tb.Helper() + cmdArgs := make([]string, 0, len(args)+1) cmdArgs = append(cmdArgs, "repack") cmdArgs = append(cmdArgs, args...) diff --git a/internal/testgit/repo_rev_parse.go b/internal/testgit/repo_rev_parse.go index bebdfa8e..3bee6108 100644 --- a/internal/testgit/repo_rev_parse.go +++ b/internal/testgit/repo_rev_parse.go @@ -10,9 +10,11 @@ import ( func (testRepo *TestRepo) RevParse(tb testing.TB, spec string) objectid.ObjectID { tb.Helper() hex := testRepo.Run(tb, "rev-parse", spec) + id, err := objectid.ParseHex(testRepo.algo, hex) if err != nil { tb.Fatalf("parse rev-parse output %q: %v", hex, err) } + return id } diff --git a/internal/testgit/repo_run.go b/internal/testgit/repo_run.go index 8022835e..162a0d72 100644 --- a/internal/testgit/repo_run.go +++ b/internal/testgit/repo_run.go @@ -11,12 +11,14 @@ import ( func (testRepo *TestRepo) Run(tb testing.TB, args ...string) string { tb.Helper() out := testRepo.runBytes(tb, nil, testRepo.dir, args...) + return strings.TrimSpace(string(out)) } // RunBytes executes git and returns raw output bytes. func (testRepo *TestRepo) RunBytes(tb testing.TB, args ...string) []byte { tb.Helper() + return testRepo.runBytes(tb, nil, testRepo.dir, args...) } @@ -24,12 +26,14 @@ func (testRepo *TestRepo) RunBytes(tb testing.TB, args ...string) []byte { func (testRepo *TestRepo) RunInput(tb testing.TB, stdin []byte, args ...string) string { tb.Helper() out := testRepo.runBytes(tb, stdin, testRepo.dir, args...) + return strings.TrimSpace(string(out)) } // RunInputBytes executes git with stdin and returns raw output bytes. func (testRepo *TestRepo) RunInputBytes(tb testing.TB, stdin []byte, args ...string) []byte { tb.Helper() + return testRepo.runBytes(tb, stdin, testRepo.dir, args...) } @@ -38,13 +42,16 @@ func (testRepo *TestRepo) runBytes(tb testing.TB, stdin []byte, dir string, args //nolint:noctx cmd := exec.Command("git", args...) //#nosec G204 cmd.Dir = dir + cmd.Env = testRepo.env if stdin != nil { cmd.Stdin = bytes.NewReader(stdin) } + out, err := cmd.CombinedOutput() if err != nil { tb.Fatalf("git %v failed: %v\n%s", args, err, out) } + return out } diff --git a/internal/testgit/repo_tag_annotated.go b/internal/testgit/repo_tag_annotated.go index a3ffafa6..7e9bfbf5 100644 --- a/internal/testgit/repo_tag_annotated.go +++ b/internal/testgit/repo_tag_annotated.go @@ -11,5 +11,6 @@ import ( func (testRepo *TestRepo) TagAnnotated(tb testing.TB, name string, target objectid.ObjectID, message string) objectid.ObjectID { tb.Helper() testRepo.Run(tb, "tag", "-a", name, target.String(), "-m", message) + return testRepo.RevParse(tb, fmt.Sprintf("refs/tags/%s", name)) } diff --git a/internal/zlib/reader.go b/internal/zlib/reader.go index 5d6dcd88..e4babb9e 100644 --- a/internal/zlib/reader.go +++ b/internal/zlib/reader.go @@ -63,6 +63,7 @@ var ( var readerPool = sync.Pool{ New: func() any { r := new(reader) + return r }, } @@ -89,14 +90,17 @@ func NewReader(r io.Reader) (io.ReadCloser, error) { // If the compressed data refers to a different dictionary, NewReaderDict returns [ErrDictionary]. func NewReaderDict(r io.Reader, dict []byte) (io.ReadCloser, error) { v := readerPool.Get() + z, ok := v.(*reader) if !ok { panic("zlib: pool returned unexpected type") } + err := z.Reset(r, dict) if err != nil { return nil, err } + return z, nil } @@ -106,30 +110,40 @@ func (z *reader) Read(p []byte) (int, error) { } var n int + n, z.err = z.decompressor.Read(p) - if _, err := z.digest.Write(p[0:n]); err != nil { + + _, err := z.digest.Write(p[0:n]) + if err != nil { z.err = err + return n, z.err } + if !errors.Is(z.err, io.EOF) { // In the normal case we return here. return n, z.err } // Finished file; check checksum. - if _, err := io.ReadFull(z.r, z.scratch[0:4]); err != nil { - if err == io.EOF { + _, err = io.ReadFull(z.r, z.scratch[0:4]) + if err != nil { + if errors.Is(err, io.EOF) { err = io.ErrUnexpectedEOF } + z.err = err + return n, z.err } // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952). checksum := binary.BigEndian.Uint32(z.scratch[:4]) if checksum != z.digest.Sum32() { z.err = ErrChecksum + return n, z.err } + return n, io.EOF } @@ -140,12 +154,14 @@ func (z *reader) Close() error { if z.err != nil && !errors.Is(z.err, io.EOF) { return z.err } + z.err = z.decompressor.Close() if z.err != nil { return z.err } readerPool.Put(z) + return nil } @@ -163,13 +179,17 @@ func (z *reader) Reset(r io.Reader, dict []byte) error { if errors.Is(z.err, io.EOF) { z.err = io.ErrUnexpectedEOF } + return z.err } + h := binary.BigEndian.Uint16(z.scratch[:2]) if (z.scratch[0]&0x0f != zlibDeflate) || (z.scratch[0]>>4 > zlibMaxWindow) || (h%31 != 0) { z.err = ErrHeader + return z.err } + haveDict := z.scratch[1]&0x20 != 0 if haveDict { _, z.err = io.ReadFull(z.r, z.scratch[0:4]) @@ -177,31 +197,41 @@ func (z *reader) Reset(r io.Reader, dict []byte) error { if errors.Is(z.err, io.EOF) { z.err = io.ErrUnexpectedEOF } + return z.err } + checksum := binary.BigEndian.Uint32(z.scratch[:4]) if checksum != adler32.Checksum(dict) { z.err = ErrDictionary + return z.err } } - if z.decompressor == nil { - if haveDict { - z.decompressor = flate.NewReaderDict(z.r, dict) - } else { - z.decompressor = flate.NewReader(z.r) - } - } else { + if z.decompressor != nil { resetter, ok := z.decompressor.(flate.Resetter) if !ok { panic("zlib: pooled decompressor does not implement flate.Resetter") } + z.err = resetter.Reset(z.r, dict) if z.err != nil { return z.err } + + z.digest = adler32.New() + + return nil + } + + if haveDict { + z.decompressor = flate.NewReaderDict(z.r, dict) + } else { + z.decompressor = flate.NewReader(z.r) } + z.digest = adler32.New() + return nil } diff --git a/internal/zlib/writer.go b/internal/zlib/writer.go index 75a8ec1d..bfc52889 100644 --- a/internal/zlib/writer.go +++ b/internal/zlib/writer.go @@ -52,6 +52,7 @@ var writerPool = sync.Pool{ // Writes may be buffered and not flushed until Close. func NewWriter(w io.Writer) *Writer { z, _ := NewWriterLevelDict(w, DefaultCompression, nil) + return z } @@ -74,7 +75,9 @@ func NewWriterLevelDict(w io.Writer, level int, dict []byte) (*Writer, error) { if level < HuffmanOnly || level > BestCompression { return nil, fmt.Errorf("zlib: invalid compression level: %d", level) } + v := writerPool.Get() + z, ok := v.(*Writer) if !ok { panic("zlib: pool returned unexpected type") @@ -86,6 +89,7 @@ func NewWriterLevelDict(w io.Writer, level int, dict []byte) (*Writer, error) { if !reuseCompressor { z.compressor = nil } + if z.digest != nil { z.digest.Reset() } @@ -100,6 +104,7 @@ func NewWriterLevelDict(w io.Writer, level int, dict []byte) (*Writer, error) { if z.compressor != nil { z.compressor.Reset(w) } + return z, nil } @@ -112,9 +117,11 @@ func (z *Writer) Reset(w io.Writer) { if z.compressor != nil { z.compressor.Reset(w) } + if z.digest != nil { z.digest.Reset() } + z.err = nil z.scratch = [4]byte{} z.wroteHeader = false @@ -127,21 +134,29 @@ func (z *Writer) Write(p []byte) (n int, err error) { if !z.wroteHeader { z.err = z.writeHeader() } + if z.err != nil { return 0, z.err } + if len(p) == 0 { return 0, nil } + n, err = z.compressor.Write(p) if err != nil { z.err = err + return n, err } - if _, err = z.digest.Write(p); err != nil { + + _, err = z.digest.Write(p) + if err != nil { z.err = err + return 0, z.err } + return n, err } @@ -150,10 +165,13 @@ func (z *Writer) Flush() error { if !z.wroteHeader { z.err = z.writeHeader() } + if z.err != nil { return z.err } + z.err = z.compressor.Flush() + return z.err } @@ -163,22 +181,27 @@ func (z *Writer) Close() error { if !z.wroteHeader { z.err = z.writeHeader() } + if z.err != nil { return z.err } + z.err = z.compressor.Close() if z.err != nil { return z.err } + checksum := z.digest.Sum32() // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952). binary.BigEndian.PutUint32(z.scratch[:], checksum) + _, z.err = z.w.Write(z.scratch[0:4]) if z.err != nil { return z.err } writerPool.Put(z) + return nil } @@ -205,20 +228,28 @@ func (z *Writer) writeHeader() (err error) { default: panic("unreachable") } + if z.dict != nil { z.scratch[1] |= 1 << 5 } + z.scratch[1] += uint8(31 - binary.BigEndian.Uint16(z.scratch[:2])%31) //#nosec G115 - if _, err = z.w.Write(z.scratch[0:2]); err != nil { + + _, err = z.w.Write(z.scratch[0:2]) + if err != nil { return err } + if z.dict != nil { // The next four bytes are the Adler-32 checksum of the dictionary. binary.BigEndian.PutUint32(z.scratch[:], adler32.Checksum(z.dict)) - if _, err = z.w.Write(z.scratch[0:4]); err != nil { + + _, err = z.w.Write(z.scratch[0:4]) + if err != nil { return err } } + if z.compressor == nil { // Initialize deflater unless the Writer is being reused // after a Reset call. @@ -226,7 +257,9 @@ func (z *Writer) writeHeader() (err error) { if err != nil { return err } + z.digest = adler32.New() } + return nil } |
