mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
fix(git): scope the dirty-main squash guard to staged changes only
The previous guard rejected any uncommitted tracked change (git status --untracked-files=no), which also caught unstaged edits like a ktx.yaml that setup writes during the flow and commits only after the context build — so the guard wrongly blocked setup context builds and ingest (8 local-bundle-ingest cases failed with 'uncommitted changes (ktx.yaml)'). The actual hazard is the index, not the working tree: 'git commit' captures only staged changes, and the auto_commit:false residue is staged by 'git merge --squash'. Narrow the check to 'git diff --cached' so only pre-existing staged changes are refused; unstaged working-tree edits proceed untouched (never committed by the squash). Adds a regression test that an unstaged tracked change does not block the merge and is neither committed nor discarded.
This commit is contained in:
parent
9ac37166f5
commit
852ac8a836
4 changed files with 45 additions and 13 deletions
|
|
@ -744,19 +744,21 @@ export class GitService {
|
|||
| { ok: false; conflict: true; conflictPaths: string[] }
|
||||
| { ok: false; dirty: true; dirtyPaths: string[] }
|
||||
> {
|
||||
// The squash flow commits the whole index and, on conflict, `reset --hard`s the tree, so
|
||||
// it must start from a clean target. Refuse if the target has uncommitted tracked changes
|
||||
// (e.g. residue from a prior `auto_commit: false` run) rather than committing them under
|
||||
// this run's message or discarding them. Untracked files (gitignored .ktx state, stray
|
||||
// files) are not committed by the squash and are intentionally ignored here.
|
||||
const dirtyPaths = (await this.git.raw(['status', '--porcelain', '--untracked-files=no']))
|
||||
// `git commit` after the squash commits the WHOLE index, and conflict cleanup `reset
|
||||
// --hard`s the tree. So pre-existing *staged* changes are the hazard: they would be
|
||||
// committed under this run's message, or discarded on conflict. The residue from a prior
|
||||
// `auto_commit: false` run is exactly that — `git merge --squash` stages without
|
||||
// committing. Refuse when the index already differs from HEAD. Unstaged working-tree edits
|
||||
// (e.g. a `ktx.yaml` written by setup and committed later, or user edits) are NOT captured
|
||||
// by `git commit`, so they must not block the squash; untracked files are likewise ignored.
|
||||
const dirtyPaths = (await this.git.raw(['diff', '--cached', '--name-only']))
|
||||
.split('\n')
|
||||
.map((line) => line.slice(3).trim())
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
if (dirtyPaths.length > 0) {
|
||||
this.logger.warn(
|
||||
`applySquashToIndex: target worktree has ${dirtyPaths.length} uncommitted change(s) ` +
|
||||
`(${dirtyPaths.slice(0, 5).join(', ')}); refusing to squash ${branch} to avoid contaminating or discarding them`,
|
||||
`applySquashToIndex: target index has ${dirtyPaths.length} staged change(s) ` +
|
||||
`(${dirtyPaths.slice(0, 5).join(', ')}); refusing to squash ${branch} to avoid committing or discarding them`,
|
||||
);
|
||||
return { ok: false, dirty: true, dirtyPaths };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2700,8 +2700,8 @@ export class IngestBundleRunner {
|
|||
await this.deps.runs.markFailed(runRow.id);
|
||||
if ('dirty' in squashResult.merge) {
|
||||
throw new Error(
|
||||
'The project working tree has uncommitted changes ' +
|
||||
`(${squashResult.merge.dirtyPaths.slice(0, 5).join(', ')}); commit or discard them before ingesting ` +
|
||||
'The project has staged but uncommitted changes ' +
|
||||
`(${squashResult.merge.dirtyPaths.slice(0, 5).join(', ')}); commit or unstage them before ingesting ` +
|
||||
'(this typically means a previous run with storage.git.auto_commit: false was left staged).',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -276,8 +276,8 @@ export class MemoryAgentService {
|
|||
sessionOutcome = 'conflict';
|
||||
if ('dirty' in mergeResult) {
|
||||
this.logger.warn(
|
||||
`[memory-agent] chat=${chatId} not landed: project working tree has uncommitted changes ` +
|
||||
`(${mergeResult.dirtyPaths.slice(0, 5).join(', ')}); commit or discard them and re-run ` +
|
||||
`[memory-agent] chat=${chatId} not landed: project has staged but uncommitted changes ` +
|
||||
`(${mergeResult.dirtyPaths.slice(0, 5).join(', ')}); commit or unstage them and re-run ` +
|
||||
'(a previous run with auto_commit disabled may have been left staged).',
|
||||
);
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue