This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

knotmirror/xrpc/gitea: read tree blob sizes from cat-file batch

Lewis: May this revision serve well! <did:plc:3fwecdnvtcscjnrx2p4n7alz>

author did:plc:3fwecdnvtcscjnrx2p4n7a… date (Aug 1, 2026, 9:36 AM +0300) commit edfa807f parent 5a2fc1dc change-id zolsnxkx
+124
+32
knotmirror/xrpc/gitea/blob.go
··· 10 10 "io" 11 11 12 12 "github.com/go-git/go-git/v5/plumbing" 13 + "github.com/go-git/go-git/v5/plumbing/filemode" 14 + "github.com/go-git/go-git/v5/plumbing/object" 15 + "github.com/samber/lo" 13 16 ) 14 17 15 18 func GetBlobSize(ctx context.Context, repoPath string, hash plumbing.Hash) (int64, error) { ··· 20 23 } 21 24 _, _, size, err := ReadBatchLine(rd) 22 25 return size, err 26 + } 27 + 28 + func EntrySizes(ctx context.Context, repoPath string, entries []object.TreeEntry) ([]int64, error) { 29 + sizes := make([]int64, len(entries)) 30 + blobs := lo.Filter(lo.Range(len(entries)), func(i int, _ int) bool { 31 + return isBlobMode(entries[i].Mode) 32 + }) 33 + if len(blobs) == 0 { 34 + return sizes, nil 35 + } 36 + wr, rd, cancel := CatFileBatchCheck(ctx, repoPath) 37 + defer cancel() 38 + for _, i := range blobs { 39 + if _, err := wr.Write([]byte(entries[i].Hash.String() + "\n")); err != nil { 40 + return sizes, err 41 + } 42 + _, typ, size, err := ReadBatchLine(rd) 43 + if err != nil { 44 + return sizes, err 45 + } 46 + if typ == "blob" { 47 + sizes[i] = size 48 + } 49 + } 50 + return sizes, nil 51 + } 52 + 53 + func isBlobMode(mode filemode.FileMode) bool { 54 + return mode == filemode.Regular || mode == filemode.Executable || mode == filemode.Symlink 23 55 } 24 56 25 57 // ReadBlob returns blob size and [io.ReadCloser] of that blob.
+92
knotmirror/xrpc/gitea/blob_test.go
··· 1 + package gitea 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "os" 7 + "os/exec" 8 + "path/filepath" 9 + "testing" 10 + 11 + "github.com/stretchr/testify/assert" 12 + "github.com/stretchr/testify/require" 13 + ) 14 + 15 + func initRepo(t *testing.T) (string, func(args ...string) []byte) { 16 + t.Helper() 17 + repo := t.TempDir() 18 + runGit := func(args ...string) []byte { 19 + t.Helper() 20 + cmd := exec.Command("git", args...) 21 + cmd.Dir = repo 22 + out, err := cmd.CombinedOutput() 23 + require.NoError(t, err, string(out)) 24 + return out 25 + } 26 + runGit("init") 27 + runGit("config", "user.name", "nel") 28 + runGit("config", "user.email", "nel@oyster.cafe") 29 + return repo, runGit 30 + } 31 + 32 + func sizesByName(t *testing.T, repo string) map[string]int64 { 33 + t.Helper() 34 + ctx := context.Background() 35 + tree, err := GetTree(ctx, repo, "HEAD^{tree}") 36 + require.NoError(t, err) 37 + 38 + sizes, err := EntrySizes(ctx, repo, tree.Entries) 39 + require.NoError(t, err, "the batch survives an entry pointing outside this repo") 40 + require.Len(t, sizes, len(tree.Entries)) 41 + 42 + byName := map[string]int64{} 43 + for i, entry := range tree.Entries { 44 + byName[entry.Name] = sizes[i] 45 + } 46 + return byName 47 + } 48 + 49 + func TestEntrySizesReportsEveryEntryKind(t *testing.T) { 50 + repo, runGit := initRepo(t) 51 + require.NoError(t, os.WriteFile(filepath.Join(repo, "conch.txt"), make([]byte, 10), 0o644)) 52 + require.NoError(t, os.WriteFile(filepath.Join(repo, "kelp.txt"), make([]byte, 9000), 0o644)) 53 + require.NoError(t, os.Mkdir(filepath.Join(repo, "limpet"), 0o755)) 54 + require.NoError(t, os.WriteFile(filepath.Join(repo, "limpet", "uni.txt"), make([]byte, 3), 0o644)) 55 + require.NoError(t, os.Symlink("conch.txt", filepath.Join(repo, "cuttle.txt"))) 56 + runGit("add", ".") 57 + runGit("update-index", "--add", "--cacheinfo", 58 + "160000,0000000000000000000000000000000000000001,mussel") 59 + require.NoError(t, os.WriteFile(filepath.Join(repo, "zzz-nautilus.txt"), make([]byte, 4096), 0o644)) 60 + runGit("add", "zzz-nautilus.txt") 61 + runGit("commit", "-m", "initial") 62 + 63 + byName := sizesByName(t, repo) 64 + assert.Equal(t, int64(10), byName["conch.txt"]) 65 + assert.Equal(t, int64(9000), byName["kelp.txt"]) 66 + assert.Equal(t, int64(0), byName["limpet"], "a tree doesn't have a blob size") 67 + assert.Equal(t, int64(len("conch.txt")), byName["cuttle.txt"], "a symlink is its target's length") 68 + assert.Equal(t, int64(0), byName["mussel"], "a submodule doesn't have a blob size") 69 + assert.Equal(t, int64(4096), byName["zzz-nautilus.txt"], "entries after a submodule keep their size") 70 + 71 + sizes, err := EntrySizes(context.Background(), repo, nil) 72 + require.NoError(t, err) 73 + assert.Empty(t, sizes, "we don't spawn a git process for an empty tree") 74 + } 75 + 76 + func TestEntrySizesReadsATreeLargerThanThePipeBuffer(t *testing.T) { 77 + repo, runGit := initRepo(t) 78 + const entries = 2000 79 + for i := range entries { 80 + name := fmt.Sprintf("scallop-%04d.txt", i) 81 + require.NoError(t, os.WriteFile(filepath.Join(repo, name), make([]byte, i), 0o644)) 82 + } 83 + runGit("add", ".") 84 + runGit("commit", "-m", "initial") 85 + 86 + byName := sizesByName(t, repo) 87 + require.Len(t, byName, entries) 88 + for i := range entries { 89 + name := fmt.Sprintf("scallop-%04d.txt", i) 90 + require.Equal(t, int64(i), byName[name], name) 91 + } 92 + }