Restrict orphan worktree cleanup
This commit is contained in:
@@ -3663,6 +3663,7 @@ export async function removeWorktree(directory, input = {}) {
|
|||||||
if (targetCanonical === primaryCanonical) {
|
if (targetCanonical === primaryCanonical) {
|
||||||
throw new Error('Cannot remove the primary workspace');
|
throw new Error('Cannot remove the primary workspace');
|
||||||
}
|
}
|
||||||
|
const worktreeRootCanonical = await canonicalPath(context.worktreeRoot);
|
||||||
|
|
||||||
const entries = await listWorktreeEntries(context.primaryWorktree);
|
const entries = await listWorktreeEntries(context.primaryWorktree);
|
||||||
const matchedEntry = await (async () => {
|
const matchedEntry = await (async () => {
|
||||||
@@ -3679,6 +3680,10 @@ export async function removeWorktree(directory, input = {}) {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
if (!matchedEntry?.worktree) {
|
if (!matchedEntry?.worktree) {
|
||||||
|
if (targetCanonical === worktreeRootCanonical || !isInsideOrSameDirectory(worktreeRootCanonical, targetCanonical)) {
|
||||||
|
throw new Error('Cannot remove unmanaged worktree directory');
|
||||||
|
}
|
||||||
|
|
||||||
const targetExists = await checkPathExists(targetDirectory);
|
const targetExists = await checkPathExists(targetDirectory);
|
||||||
if (targetExists) {
|
if (targetExists) {
|
||||||
await fsp.rm(targetDirectory, { recursive: true, force: true });
|
await fsp.rm(targetDirectory, { recursive: true, force: true });
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
checkoutCommit,
|
checkoutCommit,
|
||||||
cherryPick,
|
cherryPick,
|
||||||
getStatus,
|
getStatus,
|
||||||
|
removeWorktree,
|
||||||
resetToCommit,
|
resetToCommit,
|
||||||
resolveBaseRefForLog,
|
resolveBaseRefForLog,
|
||||||
revertCommit,
|
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
|
// checkoutCommit
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user