mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-25 08:48:08 +02:00
97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
|
|
import { readFile } from 'node:fs/promises';
|
||
|
|
import type { GitService } from '../../core/index.js';
|
||
|
|
import type { IngestTraceWriter } from '../ingest-trace.js';
|
||
|
|
import { traceTimed } from '../ingest-trace.js';
|
||
|
|
import { assertPatchAllowedForWorkUnit } from './git-patch.js';
|
||
|
|
|
||
|
|
export type PatchIntegrationResult =
|
||
|
|
| { status: 'accepted'; commitSha: string; touchedPaths: string[] }
|
||
|
|
| { status: 'textual_conflict'; reason: string; touchedPaths: string[] }
|
||
|
|
| { status: 'semantic_conflict'; reason: string; touchedPaths: string[] };
|
||
|
|
|
||
|
|
export interface IntegrateWorkUnitPatchInput {
|
||
|
|
unitKey: string;
|
||
|
|
patchPath: string;
|
||
|
|
integrationGit: GitService;
|
||
|
|
trace: IngestTraceWriter;
|
||
|
|
author: { name: string; email: string };
|
||
|
|
slDisallowed: boolean;
|
||
|
|
validateAppliedTree(touchedPaths: string[]): Promise<void>;
|
||
|
|
}
|
||
|
|
|
||
|
|
function errorMessage(error: unknown): string {
|
||
|
|
return error instanceof Error ? error.message : String(error);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function integrateWorkUnitPatch(input: IntegrateWorkUnitPatchInput): Promise<PatchIntegrationResult> {
|
||
|
|
const preApplyHead = await input.integrationGit.revParseHead();
|
||
|
|
const patch = await readFile(input.patchPath, 'utf-8');
|
||
|
|
const touched = assertPatchAllowedForWorkUnit({
|
||
|
|
unitKey: input.unitKey,
|
||
|
|
patch,
|
||
|
|
slDisallowed: input.slDisallowed,
|
||
|
|
});
|
||
|
|
const touchedPaths = touched.map((entry) => entry.path);
|
||
|
|
|
||
|
|
try {
|
||
|
|
await traceTimed(
|
||
|
|
input.trace,
|
||
|
|
'integration',
|
||
|
|
'patch_apply',
|
||
|
|
{ unitKey: input.unitKey, patchPath: input.patchPath, touchedPaths },
|
||
|
|
async () => {
|
||
|
|
await input.integrationGit.applyPatchFile3WayIndex(input.patchPath);
|
||
|
|
await input.integrationGit.assertWorktreeClean();
|
||
|
|
},
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
if (preApplyHead) {
|
||
|
|
await input.integrationGit.resetHardTo(preApplyHead);
|
||
|
|
}
|
||
|
|
await input.trace.event('error', 'integration', 'patch_textual_conflict', {
|
||
|
|
unitKey: input.unitKey,
|
||
|
|
patchPath: input.patchPath,
|
||
|
|
touchedPaths,
|
||
|
|
reason: errorMessage(error),
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
status: 'textual_conflict',
|
||
|
|
reason: errorMessage(error),
|
||
|
|
touchedPaths,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
await traceTimed(input.trace, 'integration', 'semantic_gate', { unitKey: input.unitKey, touchedPaths }, async () => {
|
||
|
|
await input.validateAppliedTree(touchedPaths);
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
if (preApplyHead) {
|
||
|
|
await input.integrationGit.resetHardTo(preApplyHead);
|
||
|
|
}
|
||
|
|
await input.trace.event('error', 'integration', 'patch_semantic_conflict', {
|
||
|
|
unitKey: input.unitKey,
|
||
|
|
patchPath: input.patchPath,
|
||
|
|
touchedPaths,
|
||
|
|
reason: errorMessage(error),
|
||
|
|
});
|
||
|
|
return {
|
||
|
|
status: 'semantic_conflict',
|
||
|
|
reason: errorMessage(error),
|
||
|
|
touchedPaths,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
const commit = await input.integrationGit.commitStaged(
|
||
|
|
`ingest: accept WorkUnit ${input.unitKey}`,
|
||
|
|
input.author.name,
|
||
|
|
input.author.email,
|
||
|
|
);
|
||
|
|
await input.trace.event('debug', 'integration', 'patch_accepted', {
|
||
|
|
unitKey: input.unitKey,
|
||
|
|
commitSha: commit.commitHash,
|
||
|
|
touchedPaths,
|
||
|
|
});
|
||
|
|
return { status: 'accepted', commitSha: commit.commitHash, touchedPaths };
|
||
|
|
}
|