89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
import { spawn } from 'child_process';
|
|
|
|
export interface CommitWorkspaceOptions {
|
|
workspacePath: string;
|
|
branchName: string;
|
|
commitMessage: string;
|
|
ignoreEntries?: string[];
|
|
}
|
|
|
|
export interface CommitWorkspaceResult {
|
|
changed: boolean;
|
|
committed: boolean;
|
|
pushed: boolean;
|
|
}
|
|
|
|
interface GitResult {
|
|
code: number;
|
|
stdout: string;
|
|
stderr: string;
|
|
}
|
|
|
|
function runGit(workspacePath: string, args: string[], extraEnv?: Record<string, string>): Promise<GitResult> {
|
|
return new Promise((resolve) => {
|
|
const child = spawn('git', args, {
|
|
cwd: workspacePath,
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
timeout: 30_000,
|
|
env: { ...process.env, ...extraEnv },
|
|
});
|
|
let stdout = '';
|
|
let stderr = '';
|
|
child.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });
|
|
child.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });
|
|
child.on('error', () => resolve({ code: -1, stdout, stderr }));
|
|
child.on('close', (code) => resolve({ code: code ?? -1, stdout, stderr }));
|
|
});
|
|
}
|
|
|
|
export async function ensureWorkspaceGitRepo(workspacePath: string): Promise<void> {
|
|
if (existsSync(join(workspacePath, '.git'))) return;
|
|
mkdirSync(workspacePath, { recursive: true });
|
|
const init = await runGit(workspacePath, ['init', '--initial-branch=main']);
|
|
if (init.code !== 0) {
|
|
throw new Error(`git init failed: ${init.stderr.slice(0, 200)}`);
|
|
}
|
|
}
|
|
|
|
export async function commitWorkspaceChanges(options: CommitWorkspaceOptions): Promise<CommitWorkspaceResult> {
|
|
const ignoreEntries = options.ignoreEntries ?? ['input/', 'logs/'];
|
|
await ensureWorkspaceGitRepo(options.workspacePath);
|
|
|
|
const status = await runGit(options.workspacePath, ['status', '--porcelain']);
|
|
if (status.stdout.trim() === '') {
|
|
return { changed: false, committed: false, pushed: false };
|
|
}
|
|
|
|
const checkout = await runGit(options.workspacePath, ['checkout', '-b', options.branchName]);
|
|
if (checkout.code !== 0) {
|
|
await runGit(options.workspacePath, ['checkout', options.branchName]);
|
|
}
|
|
|
|
const excludePath = join(options.workspacePath, '.git', 'info', 'exclude');
|
|
let excludeContent = '';
|
|
try {
|
|
excludeContent = readFileSync(excludePath, 'utf-8');
|
|
} catch {
|
|
// ignore
|
|
}
|
|
const missingEntries = ignoreEntries.filter(entry => !excludeContent.includes(entry));
|
|
if (missingEntries.length > 0) {
|
|
const nextContent = excludeContent.trimEnd() + '\n' + missingEntries.join('\n') + '\n';
|
|
writeFileSync(excludePath, nextContent);
|
|
}
|
|
|
|
await runGit(options.workspacePath, ['add', '-A']);
|
|
const commit = await runGit(options.workspacePath, [
|
|
'-c', 'user.name=Agent Bot',
|
|
'-c', 'user.email=maestro@noreply',
|
|
'commit', '-m', options.commitMessage,
|
|
]);
|
|
if (commit.code !== 0) {
|
|
return { changed: true, committed: false, pushed: false };
|
|
}
|
|
|
|
return { changed: true, committed: true, pushed: false };
|
|
}
|