Observe original Git objects consistently

This commit is contained in:
2026-11-02 03:12:05 +00:00
parent aecab00ff6
commit 7e6e0cc7d1
6 changed files with 37 additions and 50 deletions

View File

@@ -7,7 +7,7 @@ The eleven [audit reports](audit/) supply evidence and reproduction details;
the target document owns the accepted outcomes and policy choices. This plan the target document owns the accepted outcomes and policy choices. This plan
owns execution order, concrete implementation choices, and completion evidence. owns execution order, concrete implementation choices, and completion evidence.
All stages are pending. Follow Stage 1 through Stage 19 in order as one Follow Stage 1 through Stage 19 in order as one
end-to-end sprint. Stages separate work into units suitable for a Luna coding end-to-end sprint. Stages separate work into units suitable for a Luna coding
agent. Intermediate stages may leave related code or tests uncompilable; agent. Intermediate stages may leave related code or tests uncompilable;
complete the dependencies before evaluating the final implementation. Run complete the dependencies before evaluating the final implementation. Run
@@ -37,7 +37,7 @@ the agent policy; an intermediate compilation dependency is recorded accurately.
| Stage | Work items | Status | | Stage | Work items | Status |
| --- | --- | --- | | --- | --- | --- |
| 1 | A01; publication assertion portion of A19 | Complete — regression failed before the fix and passed afterward; focused publication and reconciliation tests passed. | | 1 | A01; publication assertion portion of A19 | Complete — regression failed before the fix and passed afterward; focused publication and reconciliation tests passed. |
| 2 | A02, A12: common Git mechanics | Pending | | 2 | A02, A12: common Git mechanics | Complete — focused Git, candidate, identity, and public build/publication/delivery tests passed; A02 regression evidence follows in Stage 3. |
| 3 | A02: source identity regression evidence | Pending | | 3 | A02: source identity regression evidence | Pending |
| 4 | A03: buffered command teardown | Pending | | 4 | A03: buffered command teardown | Pending |
| 5 | A03, A18: piped lifetime and queued cancellation | Pending | | 5 | A03, A18: piped lifetime and queued cancellation | Pending |

View File

@@ -15,6 +15,7 @@ import (
"go.maximumdirect.net/releasekit/internal/artifact" "go.maximumdirect.net/releasekit/internal/artifact"
"go.maximumdirect.net/releasekit/internal/candidate" "go.maximumdirect.net/releasekit/internal/candidate"
"go.maximumdirect.net/releasekit/internal/git"
"go.maximumdirect.net/releasekit/internal/gotool" "go.maximumdirect.net/releasekit/internal/gotool"
"go.maximumdirect.net/releasekit/internal/releaseidentity" "go.maximumdirect.net/releasekit/internal/releaseidentity"
"go.maximumdirect.net/releasekit/internal/tempdir" "go.maximumdirect.net/releasekit/internal/tempdir"
@@ -102,14 +103,14 @@ func BuildResolved(ctx context.Context, request Request) (Result, error) {
return result, fmt.Errorf("resolve module directory: %w", err) return result, fmt.Errorf("resolve module directory: %w", err)
} }
result.ModuleDir = moduleDir result.ModuleDir = moduleDir
commit, err := gitCommit(ctx, repositoryDir) commit, err := git.CaptureHead(ctx, repositoryDir)
if err != nil { if err != nil {
return result, err return result, fmt.Errorf("read source commit: %w", err)
} }
result.CandidateCommit = commit result.CandidateCommit = commit
cleanBefore, err := cleanSource(ctx, repositoryDir) statusBefore, err := git.WorktreeStatus(ctx, repositoryDir)
if err != nil { if err != nil {
return result, err return result, fmt.Errorf("inspect build source: %w", err)
} }
if len(result.Targets) == 0 { if len(result.Targets) == 0 {
@@ -192,30 +193,18 @@ func BuildResolved(ctx context.Context, request Request) (Result, error) {
if err := checkVersion(ctx, smokePath, settings.BinaryName, identity.Version); err != nil { if err := checkVersion(ctx, smokePath, settings.BinaryName, identity.Version); err != nil {
return result, err return result, err
} }
commitAfter, err := gitCommit(ctx, repositoryDir) commitAfter, err := git.CaptureHead(ctx, repositoryDir)
if err != nil { if err != nil {
return result, err return result, fmt.Errorf("read source commit: %w", err)
} }
cleanAfter, err := cleanSource(ctx, repositoryDir) statusAfter, err := git.WorktreeStatus(ctx, repositoryDir)
if err != nil { if err != nil {
return result, err return result, fmt.Errorf("inspect build source: %w", err)
} }
result.SourceVerified = cleanBefore && cleanAfter && commitAfter == commit result.SourceVerified = statusBefore == "" && statusAfter == "" && commitAfter == commit
return result, nil return result, nil
} }
func gitCommit(ctx context.Context, repositoryDir string) (string, error) {
command := exec.CommandContext(ctx, "git", "-C", repositoryDir, "rev-parse", "--verify", "HEAD^{commit}")
output, err := command.Output()
if err != nil {
if ctx.Err() != nil {
return "", fmt.Errorf("read source commit: %w", ctx.Err())
}
return "", fmt.Errorf("read source commit: %w", err)
}
return strings.TrimSpace(string(output)), nil
}
func buildTarget(ctx context.Context, goTool *gotool.Tool, moduleDir, mainPackage, outputPath, version string, target Target) error { func buildTarget(ctx context.Context, goTool *gotool.Tool, moduleDir, mainPackage, outputPath, version string, target Target) error {
ldflags := "-s -w -X " + versioncontract.InjectedVersionLinkerSymbol + "=" + version ldflags := "-s -w -X " + versioncontract.InjectedVersionLinkerSymbol + "=" + version
err := goTool.Run(ctx, moduleDir, gotool.Static, gotool.Target{OS: target.OS, Arch: target.Arch}, nil, err := goTool.Run(ctx, moduleDir, gotool.Static, gotool.Target{OS: target.OS, Arch: target.Arch}, nil,
@@ -252,15 +241,3 @@ func checkVersion(ctx context.Context, binaryPath, binaryName, version string) e
} }
return nil return nil
} }
func cleanSource(ctx context.Context, repositoryDir string) (bool, error) {
command := exec.CommandContext(ctx, "git", "-C", repositoryDir, "status", "--porcelain", "--untracked-files=all")
output, err := command.Output()
if err != nil {
if ctx.Err() != nil {
return false, fmt.Errorf("inspect build source: %w", ctx.Err())
}
return false, fmt.Errorf("inspect build source: %w", err)
}
return len(output) == 0, nil
}

View File

@@ -194,18 +194,18 @@ func inspectLocalState(ctx context.Context, repositoryDir string) (localState, e
if err != nil { if err != nil {
return localState{}, fmt.Errorf("inspect current branch: %w", err) return localState{}, fmt.Errorf("inspect current branch: %w", err)
} }
commitOutput, err := gitOutput(ctx, repositoryDir, "rev-parse", "--verify", "HEAD^{commit}") commit, err := git.CaptureHead(ctx, repositoryDir)
if err != nil { if err != nil {
return localState{}, fmt.Errorf("capture HEAD commit: %w", err) return localState{}, fmt.Errorf("capture HEAD commit: %w", err)
} }
statusOutput, err := gitOutput(ctx, repositoryDir, "status", "--porcelain=v1", "--untracked-files=all") status, err := git.WorktreeStatus(ctx, repositoryDir)
if err != nil { if err != nil {
return localState{}, fmt.Errorf("inspect worktree and index: %w", err) return localState{}, fmt.Errorf("inspect worktree and index: %w", err)
} }
return localState{ return localState{
branch: strings.TrimSpace(string(branchOutput)), branch: strings.TrimSpace(string(branchOutput)),
commit: strings.TrimSpace(string(commitOutput)), commit: commit,
status: strings.TrimSpace(string(statusOutput)), status: status,
}, nil }, nil
} }
@@ -215,6 +215,7 @@ func gitOutput(ctx context.Context, repositoryDir string, args ...string) ([]byt
err := process.Run(ctx, process.Invocation{ err := process.Run(ctx, process.Invocation{
Args: commandArgs, Args: commandArgs,
Dir: repositoryDir, Dir: repositoryDir,
Env: map[string]string{"GIT_NO_REPLACE_OBJECTS": "1"},
Stdout: &stdout, Stdout: &stdout,
Stderr: &stderr, Stderr: &stderr,
}) })
@@ -233,6 +234,7 @@ func hasLocalTag(ctx context.Context, repositoryDir, tag string) (bool, error) {
err := process.Run(ctx, process.Invocation{ err := process.Run(ctx, process.Invocation{
Args: []string{"git", "show-ref", "--verify", "--quiet", "refs/tags/" + tag}, Args: []string{"git", "show-ref", "--verify", "--quiet", "refs/tags/" + tag},
Dir: repositoryDir, Dir: repositoryDir,
Env: map[string]string{"GIT_NO_REPLACE_OBJECTS": "1"},
Stderr: &stderr, Stderr: &stderr,
}) })
if err == nil { if err == nil {

View File

@@ -128,8 +128,9 @@ func (reader *BlobReader) startLocked() error {
Args: []string{"git", "cat-file", "--batch"}, Args: []string{"git", "cat-file", "--batch"},
Dir: reader.repositoryDir, Dir: reader.repositoryDir,
Env: map[string]string{ Env: map[string]string{
"GIT_NO_LAZY_FETCH": "1", "GIT_NO_LAZY_FETCH": "1",
"GIT_ALLOW_PROTOCOL": "", "GIT_ALLOW_PROTOCOL": "",
"GIT_NO_REPLACE_OBJECTS": "1",
}, },
Stderr: &reader.stderr, Stderr: &reader.stderr,
}) })

View File

@@ -234,6 +234,7 @@ func run(ctx context.Context, dir string, config []string, args ...string) (stri
err := process.Run(ctx, process.Invocation{ err := process.Run(ctx, process.Invocation{
Args: append([]string{"git"}, commandArgs...), Args: append([]string{"git"}, commandArgs...),
Dir: dir, Dir: dir,
Env: map[string]string{"GIT_NO_REPLACE_OBJECTS": "1"},
Stdout: &stdout, Stdout: &stdout,
Stderr: &stderr, Stderr: &stderr,
}) })

View File

@@ -64,7 +64,7 @@ func DiscoverRepositoryRoot(ctx context.Context, directory string) (string, bool
var stdout, stderr strings.Builder var stdout, stderr strings.Builder
err := process.Run(ctx, process.Invocation{ err := process.Run(ctx, process.Invocation{
Args: []string{"git", "rev-parse", "--show-toplevel"}, Dir: directory, Args: []string{"git", "rev-parse", "--show-toplevel"}, Dir: directory,
Env: map[string]string{"LC_ALL": "C"}, Stdout: &stdout, Stderr: &stderr, Env: map[string]string{"LC_ALL": "C", "GIT_NO_REPLACE_OBJECTS": "1"}, Stdout: &stdout, Stderr: &stderr,
}) })
if err == nil { if err == nil {
root, err := normalizeRepositoryRoot(stdout.String()) root, err := normalizeRepositoryRoot(stdout.String())
@@ -199,6 +199,15 @@ func CaptureHead(ctx context.Context, repositoryDir string) (string, error) {
return head, nil return head, nil
} }
// WorktreeStatus observes repository-wide worktree and index changes.
func WorktreeStatus(ctx context.Context, repositoryDir string) (string, error) {
status, err := run(ctx, repositoryDir, nil, "status", "--porcelain=v1", "--untracked-files=all")
if err != nil {
return "", err
}
return strings.TrimSpace(status), nil
}
// InspectTaggedSource reads the local HEAD, worktree state, and selected tag. // InspectTaggedSource reads the local HEAD, worktree state, and selected tag.
// Both lightweight historical tags and annotated tags are supported. // Both lightweight historical tags and annotated tags are supported.
func InspectTaggedSource(ctx context.Context, repositoryDir, tag string) (TaggedSource, error) { func InspectTaggedSource(ctx context.Context, repositoryDir, tag string) (TaggedSource, error) {
@@ -206,15 +215,12 @@ func InspectTaggedSource(ctx context.Context, repositoryDir, tag string) (Tagged
return TaggedSource{}, err return TaggedSource{}, err
} }
var source TaggedSource var source TaggedSource
head, err := run(ctx, repositoryDir, nil, "rev-parse", "--verify", "HEAD^{commit}") head, err := CaptureHead(ctx, repositoryDir)
if err != nil { if err != nil {
return TaggedSource{}, fmt.Errorf("read checkout HEAD: %w", err) return TaggedSource{}, err
} }
source.Head = strings.TrimSpace(head) source.Head = head
if !validObjectID(source.Head) { status, err := WorktreeStatus(ctx, repositoryDir)
return TaggedSource{}, fmt.Errorf("Git returned an invalid checkout commit %q", source.Head)
}
status, err := run(ctx, repositoryDir, nil, "status", "--porcelain", "--untracked-files=all")
if err != nil { if err != nil {
return TaggedSource{}, fmt.Errorf("inspect tagged checkout state: %w", err) return TaggedSource{}, fmt.Errorf("inspect tagged checkout state: %w", err)
} }
@@ -411,7 +417,7 @@ func readLocalObjects(ctx context.Context, repositoryDir string, input io.Reader
Args: append([]string{"git"}, args...), Args: append([]string{"git"}, args...),
Dir: repositoryDir, Dir: repositoryDir,
// Disable transports as well for Git versions predating lazy-fetch control. // Disable transports as well for Git versions predating lazy-fetch control.
Env: map[string]string{"GIT_NO_LAZY_FETCH": "1", "GIT_ALLOW_PROTOCOL": ""}, Env: map[string]string{"GIT_NO_LAZY_FETCH": "1", "GIT_ALLOW_PROTOCOL": "", "GIT_NO_REPLACE_OBJECTS": "1"},
Stdin: input, Stdin: input,
Stdout: &stdout, Stdout: &stdout,
Stderr: &stderr, Stderr: &stderr,