workspace.export: add base_commit to capture worker git commits

Without base_commit, export diffs against the current guest HEAD.
If the worker ran git commit inside the VM, HEAD advanced and the
diff came back empty — committed work was silently lost.

With base_commit set to the head_commit from workspace.prepare,
the diff uses that fixed point instead. After git add -A the index
holds the full working state, so git diff --cached <base_commit>
captures everything: committed deltas (HEAD moved past base) and
any uncommitted changes on top, in one patch, applied with the
same git apply flow.

- WorkspaceExportParams gains base_commit
- WorkspaceExportResult echoes back the ref actually used
- CLI gains --base-commit flag
- Tests assert scripts use the caller-supplied ref and that
  omitting it falls back to HEAD
This commit is contained in:
Thales Maciel 2026-04-14 16:13:05 -03:00
parent 94c353f317
commit ff51b7ce21
No known key found for this signature in database
GPG key ID: 33112E6833C34679
6 changed files with 162 additions and 10 deletions

View file

@ -53,11 +53,23 @@ func (d *Daemon) ExportVMWorkspace(ctx context.Context, params api.WorkspaceExpo
}
defer client.Close()
// Stage all changes then emit a binary-safe unified diff against HEAD.
// --binary ensures binary files are handled correctly by git apply.
// diffRef is the git ref everything is diffed against.
// When the caller supplies BaseCommit (the HEAD at workspace.prepare time),
// we diff against that fixed point so committed guest changes are included.
// Without it we fall back to HEAD, which silently drops them.
diffRef := strings.TrimSpace(params.BaseCommit)
if diffRef == "" {
diffRef = "HEAD"
}
// Stage all changes then emit a binary-safe unified diff against diffRef.
// After git add -A the index contains the full working state, so
// git diff --cached <diffRef> captures both committed deltas (HEAD moved
// past diffRef) and any additional uncommitted changes on top.
patchScript := fmt.Sprintf(
"set -euo pipefail\ncd %s\ngit add -A\ngit diff --cached HEAD --binary\n",
"set -euo pipefail\ncd %s\ngit add -A\ngit diff --cached %s --binary\n",
guestShellQuote(guestPath),
guestShellQuote(diffRef),
)
patch, err := client.RunScriptOutput(ctx, patchScript)
if err != nil {
@ -66,8 +78,9 @@ func (d *Daemon) ExportVMWorkspace(ctx context.Context, params api.WorkspaceExpo
// Enumerate changed paths (index already staged; this is a cheap read).
namesScript := fmt.Sprintf(
"set -euo pipefail\ncd %s\ngit diff --cached HEAD --name-only\n",
"set -euo pipefail\ncd %s\ngit diff --cached %s --name-only\n",
guestShellQuote(guestPath),
guestShellQuote(diffRef),
)
namesOut, _ := client.RunScriptOutput(ctx, namesScript)
var changed []string
@ -79,6 +92,7 @@ func (d *Daemon) ExportVMWorkspace(ctx context.Context, params api.WorkspaceExpo
return api.WorkspaceExportResult{
GuestPath: guestPath,
BaseCommit: diffRef,
Patch: patch,
ChangedFiles: changed,
HasChanges: len(patch) > 0,