Restrict orphan worktree cleanup

This commit is contained in:
Bohdan Triapitsyn
2026-06-12 18:33:22 +03:00
parent 106b31a407
commit ea3bb103eb
2 changed files with 46 additions and 0 deletions
+5
View File
@@ -3663,6 +3663,7 @@ export async function removeWorktree(directory, input = {}) {
if (targetCanonical === primaryCanonical) {
throw new Error('Cannot remove the primary workspace');
}
const worktreeRootCanonical = await canonicalPath(context.worktreeRoot);
const entries = await listWorktreeEntries(context.primaryWorktree);
const matchedEntry = await (async () => {
@@ -3679,6 +3680,10 @@ export async function removeWorktree(directory, input = {}) {
})();
if (!matchedEntry?.worktree) {
if (targetCanonical === worktreeRootCanonical || !isInsideOrSameDirectory(worktreeRootCanonical, targetCanonical)) {
throw new Error('Cannot remove unmanaged worktree directory');
}
const targetExists = await checkPathExists(targetDirectory);
if (targetExists) {
await fsp.rm(targetDirectory, { recursive: true, force: true });
@@ -9,6 +9,7 @@ import {
checkoutCommit,
cherryPick,
getStatus,
removeWorktree,
resetToCommit,
resolveBaseRefForLog,
revertCommit,
@@ -139,6 +140,46 @@ describe('getStatus', () => {
});
});
// ---------------------------------------------------------------------------
// removeWorktree
// ---------------------------------------------------------------------------
describe('removeWorktree', () => {
it('refuses orphan cleanup outside the managed worktree root', async () => {
if (!canRunGit()) return;
const previousXdgDataHome = process.env.XDG_DATA_HOME;
const dataHome = createTempDir();
process.env.XDG_DATA_HOME = dataHome;
try {
const repo = createTempDir();
const sentinel = createTempDir();
const canary = path.join(sentinel, 'canary.txt');
runGit(repo, ['init', '-b', 'main']);
runGit(repo, ['config', 'user.email', 'test@example.com']);
runGit(repo, ['config', 'user.name', 'Test User']);
fs.writeFileSync(path.join(repo, 'README.md'), '# Test\n');
runGit(repo, ['add', 'README.md']);
runGit(repo, ['commit', '-m', 'Initial commit']);
fs.writeFileSync(canary, 'sentinel');
await expect(removeWorktree(repo, {
directory: sentinel,
deleteLocalBranch: false,
})).rejects.toThrow('Cannot remove unmanaged worktree directory');
expect(fs.existsSync(canary)).toBe(true);
} finally {
if (previousXdgDataHome === undefined) {
delete process.env.XDG_DATA_HOME;
} else {
process.env.XDG_DATA_HOME = previousXdgDataHome;
}
}
});
});
// ---------------------------------------------------------------------------
// checkoutCommit
// ---------------------------------------------------------------------------