Skip to content

Commit 6c7bd61

Browse files
committed
refactor: move worktree hooks to core/hooks and add post-create hook
- Relocate pre-remove command execution from util to hooks/preRemoveWorktree - Run postCreateCmd via postCreateWorktree after successful worktree creation - Wire createWorktreeFromInfo and removeWorktreeCmd to the new hook modules - Remove legacy util/preRemoveWorktree implementation
1 parent dbf8155 commit 6c7bd61

6 files changed

Lines changed: 96 additions & 94 deletions

File tree

src/core/command/createWorktreeFromInfo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { addWorktree } from '@/core/git/addWorktree';
33
import { getMainFolder } from '@/core/git/getMainFolder';
44
import { confirmModal } from '@/core/ui/modal';
55
import { copyWorktreeFiles } from '@/core/util/copyWorktreeFiles';
6-
import { postCreateWorktree } from '@/core/util/postCreateWorktree';
6+
import { postCreateWorktree } from '@/core/hooks/postCreateWorktree';
77
import { actionProgressWrapper } from '@/core/ui/progress';
88
import { withResolvers } from '@/core/util/promise';
99
import type { ICreateWorktreeInfo } from '@/types';

src/core/command/removeWorktreeCmd.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import logger from '@/core/log/logger';
1010
import { Config } from '@/core/config/setting';
1111
import { actionProgressWrapper } from '@/core/ui/progress';
1212
import { withResolvers } from '@/core/util/promise';
13-
import { preRemoveWorktree } from '@/core/util/preRemoveWorktree';
13+
import { preRemoveWorktree } from '@/core/hooks/preRemoveWorktree';
1414
import { IBranchForWorktree, IWorktreeLess } from '@/types';
1515

1616
async function showDeleteConfirmation(worktreePath: string): Promise<'remove' | 'force' | undefined> {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import * as vscode from 'vscode';
2+
import { Config } from '@/core/config/setting';
3+
import { runWorktreeHookCommand } from '@/core/hooks/runWorktreeHookCommand';
4+
import logger from '@/core/log/logger';
5+
6+
interface IPostCreateWorktreeInfo {
7+
worktreePath: string;
8+
basePath: string;
9+
}
10+
11+
export async function postCreateWorktree(info: IPostCreateWorktreeInfo): Promise<void> {
12+
const { worktreePath, basePath } = info;
13+
const postCreateCmd = Config.get('postCreateCmd', '');
14+
15+
if (!postCreateCmd) {
16+
return;
17+
}
18+
19+
const cmdStr = postCreateCmd.replace('$BASE_PATH', basePath).replace('$WORKTREE_PATH', worktreePath);
20+
21+
await runWorktreeHookCommand({
22+
cmdStr,
23+
worktreePath,
24+
progressTitle: vscode.l10n.t('Running post-create command...'),
25+
logTag: 'postCreateWorktree',
26+
onExecError: (error: unknown) => {
27+
const err = error as { name?: string };
28+
if (err.name === 'AbortError') {
29+
return;
30+
}
31+
logger.error(`[postCreateWorktree] ${error}`);
32+
},
33+
});
34+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as vscode from 'vscode';
2+
import { Config } from '@/core/config/setting';
3+
import { runWorktreeHookCommand } from '@/core/hooks/runWorktreeHookCommand';
4+
import logger from '@/core/log/logger';
5+
6+
interface IPreRemoveWorktreeInfo {
7+
worktreePath: string;
8+
basePath: string;
9+
}
10+
11+
/**
12+
* Runs the user-configured `preRemoveCmd` inside the worktree directory
13+
* before it is removed. Throws on failure so the caller can abort removal.
14+
*/
15+
export async function preRemoveWorktree(info: IPreRemoveWorktreeInfo): Promise<void> {
16+
const { worktreePath, basePath } = info;
17+
const preRemoveCmd = Config.get('preRemoveCmd', '');
18+
19+
if (!preRemoveCmd) {
20+
return;
21+
}
22+
23+
const cmdStr = preRemoveCmd.replace('$BASE_PATH', basePath).replace('$WORKTREE_PATH', worktreePath);
24+
25+
await runWorktreeHookCommand({
26+
cmdStr,
27+
worktreePath,
28+
progressTitle: vscode.l10n.t('Running pre-remove command...'),
29+
logTag: 'preRemoveWorktree',
30+
onExecError: (error: unknown) => {
31+
const err = error as { name?: string; message?: string };
32+
logger.error(`[preRemoveWorktree] ${error}`);
33+
if (err.name === 'AbortError') {
34+
throw new Error(vscode.l10n.t('Pre-remove command was cancelled'));
35+
}
36+
throw new Error(vscode.l10n.t('Pre-remove command failed: {0}', err.message ?? String(error)));
37+
},
38+
});
39+
}
Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,38 @@
11
import * as vscode from 'vscode';
2-
import { Config } from '@/core/config/setting';
32
import { exec } from 'child_process';
43
import { promisify } from 'util';
54
import { withResolvers } from '@/core/util/promise';
65
import { actionProgressWrapper } from '@/core/ui/progress';
76
import logger from '@/core/log/logger';
87

9-
interface IPostCreateWorktreeInfo {
8+
export interface RunWorktreeHookCommandParams {
9+
/** Final shell command after the caller substituted placeholders. */
10+
cmdStr: string;
1011
worktreePath: string;
11-
basePath: string;
12+
progressTitle: string;
13+
logTag: string;
14+
/** Invoked when `exec` fails or the child errors; caller may swallow or throw. */
15+
onExecError: (error: unknown) => void;
1216
}
13-
export async function postCreateWorktree(info: IPostCreateWorktreeInfo) {
14-
const { worktreePath, basePath } = info;
15-
const postCreateCmd = Config.get('postCreateCmd', '');
1617

17-
if (!postCreateCmd) return;
18+
/**
19+
* Runs a user-configured worktree hook command (shared by post-create and pre-remove).
20+
* Cancellation and `finally` ordering match legacy behavior; runtime errors go through `onExecError`.
21+
*/
22+
export async function runWorktreeHookCommand(params: RunWorktreeHookCommandParams): Promise<void> {
23+
const { cmdStr, worktreePath, progressTitle, logTag, onExecError } = params;
1824

1925
const waiting = withResolvers<void>();
2026
const abortController = new AbortController();
2127
const tokenSource = new vscode.CancellationTokenSource();
22-
// Setup cancellation handling
28+
2329
const disposeAbortSignal = tokenSource.token.onCancellationRequested(() => {
24-
waiting.resolve();
2530
abortController.abort();
2631
});
2732

2833
try {
29-
const cmdStr = postCreateCmd.replace('$BASE_PATH', basePath).replace('$WORKTREE_PATH', worktreePath);
30-
3134
actionProgressWrapper(
32-
vscode.l10n.t('Running post-create command...'),
35+
progressTitle,
3336
() => waiting.promise,
3437
() => {},
3538
tokenSource,
@@ -38,27 +41,22 @@ export async function postCreateWorktree(info: IPostCreateWorktreeInfo) {
3841
const execPromise = promisify(exec);
3942

4043
const execChild = execPromise(cmdStr, {
41-
cwd: worktreePath, // 默认工作目录是 worktree 目录
44+
cwd: worktreePath,
4245
env: process.env,
4346
signal: abortController.signal,
4447
encoding: 'buffer',
4548
});
4649
execChild.child.stdout?.on('data', (data) => {
47-
logger.log(`[postCreateWorktree] ${data.toString()}`);
50+
logger.log(`[${logTag}] ${data.toString()}`);
4851
});
4952
execChild.child.stderr?.on('data', (data) => {
50-
logger.error(`[postCreateWorktree] ${data.toString()}`);
53+
logger.error(`[${logTag}] ${data.toString()}`);
5154
});
5255

5356
await execChild;
54-
waiting.resolve();
55-
logger.log(`[postCreateWorktree] done`);
56-
} catch (error: any) {
57-
if (error.name === 'AbortError') {
58-
// Ignore abort errors
59-
return;
60-
}
61-
logger.error(`[postCreateWorktree] ${error}`);
57+
logger.log(`[${logTag}] done`);
58+
} catch (error: unknown) {
59+
onExecError(error);
6260
} finally {
6361
disposeAbortSignal.dispose();
6462
tokenSource.dispose();

src/core/util/preRemoveWorktree.ts

Lines changed: 0 additions & 69 deletions
This file was deleted.

0 commit comments

Comments
 (0)