aboutsummaryrefslogtreecommitdiff
path: root/packed_write_test.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-02-20 12:51:16 +0800
committerGravatar Runxi Yu2026-02-20 12:57:31 +0800
commit9730560a82426408243cb15349f6955a4ba34f60 (patch)
tree00cfcfc75db7b1de795b822f70246abd40199b86 /packed_write_test.go
parentRevert "packed: Use random delta seed" (diff)
signatureNo signature
Revert "packed: Write packs with deltas"
This reverts commit 17c9aee0e781026353ead4ac749a3ae89c83d007.
Diffstat (limited to 'packed_write_test.go')
-rw-r--r--packed_write_test.go299
1 files changed, 7 insertions, 292 deletions
diff --git a/packed_write_test.go b/packed_write_test.go
index 63fd9266..da7ecfa7 100644
--- a/packed_write_test.go
+++ b/packed_write_test.go
@@ -3,15 +3,12 @@ package furgit
import (
"bytes"
"crypto/rand"
+ "errors"
"fmt"
"os"
- "os/exec"
"path/filepath"
"strings"
"testing"
-
- "codeberg.org/lindenii/furgit/internal/bufpool"
- "codeberg.org/lindenii/furgit/internal/zlibx"
)
func TestPackHeaderEncodeParseRoundtrip(t *testing.T) {
@@ -172,20 +169,7 @@ func TestPackWriteNoDeltas(t *testing.T) {
_ = os.Remove(idxPath)
}()
- if err := checkPackStream(packPath, repo.hashAlgo, len(objects)); err != nil {
- t.Fatalf("pack stream invalid: %v", err)
- }
-
- cmd := exec.Command("git", "index-pack", "-o", idxPath, packPath)
- cmd.Dir = repoPath
- cmd.Env = append(os.Environ(),
- "GIT_CONFIG_GLOBAL=/dev/null",
- "GIT_CONFIG_SYSTEM=/dev/null",
- )
- output, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("git index-pack failed: %v\n%s", err, output)
- }
+ _ = gitCmd(t, repoPath, "index-pack", "-o", idxPath, packPath)
verifyOut := gitCmd(t, repoPath, "verify-pack", "-v", idxPath)
seen := make(map[string]struct{})
@@ -220,290 +204,21 @@ func TestPackWriteNoDeltas(t *testing.T) {
_ = gitCmd(t, repoPath, "fsck", "--full", "--strict")
}
-func TestPackWriteDeltas(t *testing.T) {
+func TestPackWriteDeltasUnimplemented(t *testing.T) {
repoPath, cleanup := setupTestRepo(t)
defer cleanup()
- workDir, cleanupWork := setupWorkDir(t)
- defer cleanupWork()
-
- const (
- fileCount = 200
- fileSize = 2048
- )
- base := bytes.Repeat([]byte("delta-base-"), fileSize/10)
- for i := 0; i < fileCount; i++ {
- buf := make([]byte, len(base))
- copy(buf, base)
- buf[i%len(buf)] ^= byte(i)
- name := filepath.Join(workDir, fmt.Sprintf("delta%04d.txt", i))
- if err := os.WriteFile(name, buf, 0o644); err != nil {
- t.Fatalf("failed to write %s: %v", name, err)
- }
- }
-
- gitCmd(t, repoPath, "--work-tree="+workDir, "add", ".")
- gitCmd(t, repoPath, "--work-tree="+workDir, "commit", "-m", "Delta commit")
- commitHash := gitCmd(t, repoPath, "rev-parse", "HEAD")
-
- commitBody := gitCatFile(t, repoPath, "commit", commitHash)
- lines := bytes.Split(commitBody, []byte{'\n'})
- if len(lines) == 0 || !bytes.HasPrefix(lines[0], []byte("tree ")) {
- t.Fatalf("commit missing tree header")
- }
- treeHash := strings.TrimSpace(string(bytes.TrimPrefix(lines[0], []byte("tree "))))
-
- lsTree := gitCmd(t, repoPath, "ls-tree", "-r", treeHash)
- var blobHashes []string
- for _, line := range strings.Split(lsTree, "\n") {
- if line == "" {
- continue
- }
- fields := strings.Fields(line)
- if len(fields) < 3 {
- t.Fatalf("unexpected ls-tree line: %q", line)
- }
- blobHashes = append(blobHashes, fields[2])
- }
-
repo, err := OpenRepository(repoPath)
if err != nil {
t.Fatalf("OpenRepository failed: %v", err)
}
defer func() { _ = repo.Close() }()
- var objects []Hash
- commitID, _ := repo.ParseHash(commitHash)
- objects = append(objects, commitID)
- treeID, _ := repo.ParseHash(treeHash)
- objects = append(objects, treeID)
- for _, bh := range blobHashes {
- id, _ := repo.ParseHash(bh)
- objects = append(objects, id)
- }
- expectedOids := append([]string{commitHash, treeHash}, blobHashes...)
-
- packDir := filepath.Join(repoPath, "objects", "pack")
- if err := os.MkdirAll(packDir, 0o755); err != nil {
- t.Fatalf("failed to create pack dir: %v", err)
- }
- pf, err := os.CreateTemp(packDir, "furgit-delta-test-*.pack")
- if err != nil {
- t.Fatalf("failed to create pack file: %v", err)
- }
- packPath := pf.Name()
- idxPath := strings.TrimSuffix(packPath, ".pack") + ".idx"
- if _, err := repo.packWrite(pf, objects, packWriteOptions{
- EnableDeltas: true,
- MinDeltaSavings: 1,
- }); err != nil {
- _ = pf.Close()
- t.Fatalf("packWrite failed: %v", err)
- }
- if err := pf.Close(); err != nil {
- t.Fatalf("failed to close pack file: %v", err)
- }
-
- defer func() {
- _ = os.Remove(packPath)
- _ = os.Remove(idxPath)
- }()
-
- if err := checkPackStream(packPath, repo.hashAlgo, len(objects)); err != nil {
- t.Fatalf("pack stream invalid: %v", err)
- }
-
- cmd := exec.Command("git", "index-pack", "-o", idxPath, packPath)
- cmd.Dir = repoPath
- cmd.Env = append(os.Environ(),
- "GIT_CONFIG_GLOBAL=/dev/null",
- "GIT_CONFIG_SYSTEM=/dev/null",
- )
- output, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("git index-pack failed: %v\n%s", err, output)
- }
-
- verifyOut := gitCmd(t, repoPath, "verify-pack", "-v", idxPath)
- seen := make(map[string]struct{})
- for _, line := range strings.Split(verifyOut, "\n") {
- if strings.TrimSpace(line) == "" {
- continue
- }
- if strings.HasPrefix(line, "chain length") || strings.HasPrefix(line, "non delta") {
- continue
- }
- parts := strings.Fields(line)
- if len(parts) == 0 {
- continue
- }
- seen[parts[0]] = struct{}{}
- }
- for _, oid := range expectedOids {
- if _, ok := seen[oid]; !ok {
- t.Fatalf("verify-pack missing object %s", oid)
- }
- }
-
- for _, oid := range expectedOids {
- if err := removeLooseObject(repoPath, oid); err != nil {
- t.Fatalf("remove loose object %s: %v", oid, err)
- }
- }
- for _, oid := range expectedOids {
- _ = gitCmd(t, repoPath, "cat-file", "-p", oid)
- }
-
- _ = gitCmd(t, repoPath, "fsck", "--full", "--strict")
-}
-
-func checkPackStream(path string, algo hashAlgorithm, objectCount int) error {
- data, err := os.ReadFile(path)
- if err != nil {
- return err
- }
- if len(data) < 12 {
- return ErrInvalidObject
- }
- if readBE32(data[0:4]) != packMagic || readBE32(data[4:8]) != packVersion2 {
- return ErrInvalidObject
- }
- pos := 12
- hashSize := algo.Size()
- type objEntry struct {
- offset uint64
- ty ObjectType
- body []byte
- }
- byOffset := make(map[uint64]objEntry, objectCount)
- byHash := make(map[string]objEntry, objectCount)
- for i := 0; i < objectCount; i++ {
- objOffset := uint64(pos)
- ty, size, consumed, err := packHeaderParse(data[pos:])
- if err != nil {
- return fmt.Errorf("obj %d header at %d: %v", i, pos, err)
- }
- pos += consumed
- baseOffset := uint64(0)
- baseTy := ObjectTypeInvalid
- var baseBody []byte
- var baseHash Hash
- switch ty {
- case ObjectTypeOfsDelta:
- dist, distConsumed, err := packDeltaReadOfsDistance(data[pos:])
- if err != nil {
- return fmt.Errorf("obj %d ofs at %d: %v", i, pos, err)
- }
- pos += distConsumed
- if dist == 0 || dist > objOffset {
- return fmt.Errorf("obj %d ofs at %d: invalid dist", i, pos)
- }
- baseOffset = objOffset - dist
- base, ok := byOffset[baseOffset]
- if !ok {
- return fmt.Errorf("obj %d ofs at %d: missing base", i, pos)
- }
- baseTy = base.ty
- baseBody = base.body
- case ObjectTypeRefDelta:
- if pos+hashSize > len(data) {
- return ErrInvalidObject
- }
- copy(baseHash.data[:], data[pos:pos+hashSize])
- baseHash.algo = algo
- baseEntry, ok := byHash[baseHash.String()]
- if !ok {
- return fmt.Errorf("obj %d ref base not found", i)
- }
- baseTy = baseEntry.ty
- baseBody = baseEntry.body
- pos += hashSize
- default:
- }
-
- payloadBuf, zconsumed, err := zlibx.DecompressSized(data[pos:], size)
- if err != nil {
- return fmt.Errorf("obj %d zlib at %d: %v", i, pos, err)
- }
- payload := append([]byte(nil), payloadBuf.Bytes()...)
- payloadBuf.Release()
- pos += zconsumed
- switch ty {
- case ObjectTypeOfsDelta:
- if baseBody == nil {
- return fmt.Errorf("obj %d missing base body", i)
- }
- baseSize, resultSize, err := readDeltaSizes(payload)
- if err != nil {
- return fmt.Errorf("obj %d delta sizes: %v", i, err)
- }
- if baseSize != len(baseBody) {
- return fmt.Errorf("obj %d delta base size mismatch: got %d want %d", i, baseSize, len(baseBody))
- }
- out, err := packDeltaApply(bufpool.FromOwned(baseBody), bufpool.FromOwned(payload))
- if err != nil {
- return fmt.Errorf("obj %d delta apply: %v", i, err)
- }
- body := append([]byte(nil), out.Bytes()...)
- out.Release()
- if resultSize != len(body) {
- return fmt.Errorf("obj %d delta result size mismatch: got %d want %d", i, len(body), resultSize)
- }
- byOffset[objOffset] = objEntry{offset: objOffset, ty: baseTy, body: body}
- case ObjectTypeRefDelta:
- if baseBody == nil {
- return fmt.Errorf("obj %d missing ref base body", i)
- }
- baseSize, resultSize, err := readDeltaSizes(payload)
- if err != nil {
- return fmt.Errorf("obj %d ref delta sizes: %v", i, err)
- }
- if baseSize != len(baseBody) {
- return fmt.Errorf("obj %d ref delta base size mismatch: got %d want %d", i, baseSize, len(baseBody))
- }
- out, err := packDeltaApply(bufpool.FromOwned(baseBody), bufpool.FromOwned(payload))
- if err != nil {
- return fmt.Errorf("obj %d ref delta apply: %v", i, err)
- }
- body := append([]byte(nil), out.Bytes()...)
- out.Release()
- if resultSize != len(body) {
- return fmt.Errorf("obj %d ref delta result size mismatch: got %d want %d", i, len(body), resultSize)
- }
- byOffset[objOffset] = objEntry{offset: objOffset, ty: baseTy, body: body}
- default:
- if size >= 0 && len(payload) != size {
- return fmt.Errorf("obj %d size mismatch: got %d want %d", i, len(payload), size)
- }
- body := append([]byte(nil), payload...)
- byOffset[objOffset] = objEntry{offset: objOffset, ty: ty, body: body}
- }
-
- entry := byOffset[objOffset]
- if entry.body != nil && entry.ty != ObjectTypeInvalid {
- hdr, err := headerForType(entry.ty, entry.body)
- if err != nil {
- return err
- }
- raw := append(hdr, entry.body...)
- hash := algo.Sum(raw)
- byHash[hash.String()] = entry
- }
- }
- return nil
-}
-
-func readDeltaSizes(delta []byte) (int, int, error) {
- pos := 0
- baseSize, err := packVarintRead(delta, &pos)
- if err != nil {
- return 0, 0, err
- }
- resultSize, err := packVarintRead(delta, &pos)
- if err != nil {
- return 0, 0, err
+ buf := new(bytes.Buffer)
+ _, err = repo.packWrite(buf, nil, packWriteOptions{EnableDeltas: true})
+ if !errors.Is(err, errPackDeltaUnimplemented) {
+ t.Fatalf("expected errPackDeltaUnimplemented, got %v", err)
}
- return baseSize, resultSize, nil
}
func removeLooseObject(repoPath, oid string) error {