Keep candidate object inspection offline

This commit is contained in:
2026-10-07 20:18:13 +00:00
parent ff83af1e34
commit 02050016c1
4 changed files with 90 additions and 18 deletions

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"io"
"path"
"strconv"
"strings"
@@ -95,11 +96,11 @@ func ReadTree(ctx context.Context, repositoryDir, commit string) ([]TreeEntry, e
if !validObjectID(commit) {
return nil, fmt.Errorf("invalid commit object ID %q", commit)
}
output, err := run(ctx, repositoryDir, nil, "ls-tree", "-r", "-z", "--full-tree", commit)
output, err := readLocalObjects(ctx, repositoryDir, nil, "ls-tree", "-r", "-z", "--full-tree", commit)
if err != nil {
return nil, fmt.Errorf("enumerate committed tree %s: %w", commit, err)
}
entries, err := parseTreeEntries([]byte(output))
entries, err := parseTreeEntries(output)
if err != nil {
return nil, fmt.Errorf("parse committed tree %s: %w", commit, err)
}
@@ -115,11 +116,11 @@ func ReadBlob(ctx context.Context, repositoryDir, objectID string) ([]byte, erro
if !validObjectID(objectID) {
return nil, fmt.Errorf("invalid blob object ID %q", objectID)
}
content, err := run(ctx, repositoryDir, nil, "cat-file", "blob", objectID)
content, err := readLocalObjects(ctx, repositoryDir, nil, "cat-file", "blob", objectID)
if err != nil {
return nil, fmt.Errorf("read blob %s: %w", objectID, err)
}
return []byte(content), nil
return content, nil
}
func parseTreeEntries(output []byte) ([]TreeEntry, error) {
@@ -163,28 +164,21 @@ func populateBlobSizes(ctx context.Context, repositoryDir string, entries []Tree
if len(blobIndexes) == 0 {
return nil
}
var stdout, stderr bytes.Buffer
err := process.Run(ctx, process.Invocation{
Args: []string{"git", "cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)"},
Dir: repositoryDir,
Stdin: strings.NewReader(input.String()),
Stdout: &stdout,
Stderr: &stderr,
})
output, err := readLocalObjects(ctx, repositoryDir, strings.NewReader(input.String()),
"cat-file", "--batch-check=%(objectname) %(objecttype) %(objectsize)")
if err != nil {
message := strings.TrimSpace(stderr.String())
if message != "" {
return fmt.Errorf("git cat-file --batch-check: %s: %w", message, err)
}
return err
}
lines := strings.Split(strings.TrimSuffix(stdout.String(), "\n"), "\n")
lines := strings.Split(strings.TrimSuffix(string(output), "\n"), "\n")
if len(lines) != len(blobIndexes) {
return fmt.Errorf("git returned %d blob sizes for %d blobs", len(lines), len(blobIndexes))
}
for index, line := range lines {
fields := strings.Fields(line)
entry := &entries[blobIndexes[index]]
if len(fields) == 2 && fields[0] == entry.ObjectID && fields[1] == "missing" {
return fmt.Errorf("committed blob %s is unavailable locally", entry.ObjectID)
}
if len(fields) != 3 || fields[0] != entry.ObjectID || fields[1] != "blob" {
return fmt.Errorf("unexpected blob size record %q", line)
}
@@ -197,3 +191,24 @@ func populateBlobSizes(ctx context.Context, repositoryDir string, entries []Tree
}
return nil
}
// readLocalObjects keeps source inspection separate from network preparation.
func readLocalObjects(ctx context.Context, repositoryDir string, input io.Reader, args ...string) ([]byte, error) {
var stdout, stderr bytes.Buffer
err := process.Run(ctx, process.Invocation{
Args: append([]string{"git"}, args...),
Dir: repositoryDir,
// Disable transports as well for Git versions predating lazy-fetch control.
Env: map[string]string{"GIT_NO_LAZY_FETCH": "1", "GIT_ALLOW_PROTOCOL": ""},
Stdin: input,
Stdout: &stdout,
Stderr: &stderr,
})
if err != nil {
if message := strings.TrimSpace(stderr.String()); message != "" {
return nil, fmt.Errorf("read local Git objects: %s: %w", message, err)
}
return nil, err
}
return stdout.Bytes(), nil
}