From 02270a212da099a3891f18136433797fc95679ff Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Thu, 18 Jun 2026 00:23:29 +0900 Subject: [PATCH] knotmirror/xrpc: include blob size on `git.getEntry` Signed-off-by: Seongmin Lee --- knotmirror/xrpc/git_get_entry.go | 7 ++++++ knotmirror/xrpc/gitea/batch.go | 38 ++++++++++++++++++++++++++++++++ knotmirror/xrpc/gitea/blob.go | 10 +++++++++ 3 files changed, 55 insertions(+) diff --git a/knotmirror/xrpc/git_get_entry.go b/knotmirror/xrpc/git_get_entry.go index f96c21d2..361b301a 100644 --- a/knotmirror/xrpc/git_get_entry.go +++ b/knotmirror/xrpc/git_get_entry.go @@ -56,6 +56,12 @@ func (x *Xrpc) GetEntry(w http.ResponseWriter, r *http.Request) { writeJson(w, http.StatusNotFound, atclient.ErrorBody{Name: "EntryNotFound", Message: fmt.Sprintf("entry %q not found", path)}) return } + size, err := gitea.GetBlobSize(ctx, repoPath, entry.Hash) + if err != nil { + l.Error("failed to read blob size", "err", err) + writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to read blob size"}) + return + } var outLastCommit *tangled.GitTempDefs_Commit var outSubmodule *tangled.GitTempDefs_Submodule @@ -106,6 +112,7 @@ func (x *Xrpc) GetEntry(w http.ResponseWriter, r *http.Request) { Name: entry.Name, Mode: entry.Mode.String(), Oid: entry.Hash.String(), + Size: size, LastCommit: outLastCommit, Submodule: outSubmodule, }) diff --git a/knotmirror/xrpc/gitea/batch.go b/knotmirror/xrpc/gitea/batch.go index a8e3191d..8d17b5d2 100644 --- a/knotmirror/xrpc/gitea/batch.go +++ b/knotmirror/xrpc/gitea/batch.go @@ -109,6 +109,44 @@ loop: return entries, nil } +func CatFileBatchCheck(ctx context.Context, repoPath string) (io.WriteCloser, *bufio.Reader, func()) { + batchStdinReader, batchStdinWriter := io.Pipe() + batchStdoutReader, batchStdoutWriter := nio.Pipe(buffer.New(32 * 1024)) + ctx, ctxCancel := context.WithCancel(ctx) + closed := make(chan struct{}) + cancel := func() { + ctxCancel() + _ = batchStdinWriter.Close() + _ = batchStdoutReader.Close() + <-closed + } + + // Ensure cancel is called as soon as the provided context is cancelled + go func() { + <-ctx.Done() + cancel() + }() + + go func() { + stderr := &strings.Builder{} + cmd := exec.CommandContext(ctx, "git", "-C", repoPath, "cat-file", "--batch-check") + cmd.Stdin = batchStdinReader + cmd.Stdout = batchStdoutWriter + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { + _ = batchStdinReader.CloseWithError(fmt.Errorf("%w\n%s", err, stderr.String())) + _ = batchStdoutWriter.CloseWithError(fmt.Errorf("%w\n%s", err, stderr.String())) + } else { + _ = batchStdoutWriter.Close() + _ = batchStdinReader.Close() + } + close(closed) + }() + + batchReader := bufio.NewReaderSize(batchStdoutReader, 32*1024) + return batchStdinWriter, batchReader, cancel +} + func CatFileBatch(ctx context.Context, repoPath string) (io.WriteCloser, *bufio.Reader, func()) { batchStdinReader, batchStdinWriter := io.Pipe() batchStdoutReader, batchStdoutWriter := nio.Pipe(buffer.New(32 * 1024)) diff --git a/knotmirror/xrpc/gitea/blob.go b/knotmirror/xrpc/gitea/blob.go index d87770e5..2b4cb7cc 100644 --- a/knotmirror/xrpc/gitea/blob.go +++ b/knotmirror/xrpc/gitea/blob.go @@ -12,6 +12,16 @@ import ( "github.com/go-git/go-git/v5/plumbing" ) +func GetBlobSize(ctx context.Context, repoPath string, hash plumbing.Hash) (int64, error) { + wr, rd, cancel := CatFileBatchCheck(ctx, repoPath) + defer cancel() + if _, err := wr.Write([]byte(hash.String() + "\n")); err != nil { + return 0, err + } + _, _, size, err := ReadBatchLine(rd) + return size, err +} + // ReadBlob returns blob size and [io.ReadCloser] of that blob. func ReadBlob(ctx context.Context, repoPath string, hash plumbing.Hash) (int64, io.ReadCloser, error) { wr, rd, cancel := CatFileBatch(ctx, repoPath) -- 2.51.2