diff --git a/knotmirror/xrpc/git_get_entry.go b/knotmirror/xrpc/git_get_entry.go --- a/knotmirror/xrpc/git_get_entry.go +++ b/knotmirror/xrpc/git_get_entry.go @@ -56,6 +56,12 @@ 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 @@ 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 --- a/knotmirror/xrpc/gitea/batch.go +++ b/knotmirror/xrpc/gitea/batch.go @@ -109,6 +109,44 @@ 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 --- a/knotmirror/xrpc/gitea/blob.go +++ b/knotmirror/xrpc/gitea/blob.go @@ -12,6 +12,16 @@ "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)