Skip to content

Commit 770a0b5

Browse files
committed
feat(git): Refactored path normalization logic
Added a new GitApi class to encapsulate interactions with the VSCode Git extension API, enhancing the retrieval of Git path and environment variables. Updated execBase to utilize the new GitApi for executing Git commands. Refactored path normalization logic in toSimplePath to handle Windows disk drive letters correctly.
1 parent 695cb1b commit 770a0b5

4 files changed

Lines changed: 76 additions & 42 deletions

File tree

src/core/bootstrap.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { worktreeEventRegister } from '@/core/event/git';
2424
import { Config } from '@/core/config/setting';
2525
import { Commands, RefreshCacheType } from '@/constants';
2626
import { updateWorkspaceListCache, updateWorktreeCache, updateRecentItems } from '@/core/util/cache';
27+
import { gitApi } from '@/core/git/scmGit';
2728

2829
const setupCacheEvents = (context: vscode.ExtensionContext) => {
2930
const updateWorktreeCacheHandler = updateWorktreeCacheEvent.event((repoPath) => {
@@ -96,6 +97,7 @@ const setupState = (context: vscode.ExtensionContext) => {
9697

9798
const registerAllSubscriptions = (context: vscode.ExtensionContext) => {
9899
context.subscriptions.push(
100+
gitApi,
99101
vscode.window.registerFileDecorationProvider(new WorktreeDecorator()),
100102
folderRoot,
101103
logger,

src/core/git/exec-base.ts

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,22 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22
import * as vscode from 'vscode';
33
import * as cp from 'child_process';
4-
import type { GitExtension, API as ScmGitApi } from '@/@types/vscode.git';
54
import { Config } from '@/core/config/setting';
65
import { withResolvers } from '@/core/util/promise';
76
import logger from '@/core/log/logger';
87
import treeKill from 'tree-kill';
8+
import { gitApi } from '@/core/git/scmGit';
99

1010
export interface ExecResult {
1111
stdout: string;
1212
stderr: string;
1313
code: number | null;
1414
}
1515

16-
let scmGitApi: ScmGitApi | undefined;
17-
18-
const getScmGitApiCore = async (): Promise<ScmGitApi | undefined> => {
19-
try {
20-
const extension = vscode.extensions.getExtension<GitExtension>('vscode.git');
21-
if (!extension) return undefined;
22-
const gitExtension = extension.isActive ? extension.exports : await extension.activate();
23-
return gitExtension?.getAPI(1);
24-
} catch {
25-
return undefined;
26-
}
27-
};
28-
29-
/**
30-
* Get the Git executable path used by VSCode Git extension
31-
* VSCode Git extension uses git.path configuration, or automatically finds system Git if not set
32-
*/
33-
const getGitPath = async (): Promise<string> => {
34-
try {
35-
// Ensure Git extension is activated (if needed)
36-
if (!scmGitApi) {
37-
scmGitApi = await getScmGitApiCore();
38-
}
39-
if (!scmGitApi) return 'git';
40-
return scmGitApi.git.path;
41-
} catch (error) {
42-
logger.error(`Failed to get Git path: ${error}`);
43-
// Fallback to default value on error
44-
return 'git';
45-
}
46-
};
47-
48-
const getGitEnv = (): Record<string, string> => {
49-
if (!scmGitApi) return {};
50-
return (scmGitApi.git as any).env || {};
51-
};
52-
5316
export const execBase = async (cwd: string, args?: string[], token?: vscode.CancellationToken): Promise<ExecResult> => {
54-
const gitPath = await getGitPath();
17+
await gitApi.getAPI();
18+
const gitPath = gitApi.gitPath;
19+
const gitEnv = gitApi.gitEnv;
5520

5621
const { resolve, reject, promise } = withResolvers<ExecResult>();
5722

@@ -70,7 +35,7 @@ export const execBase = async (cwd: string, args?: string[], token?: vscode.Canc
7035
cwd,
7136
env: {
7237
...env,
73-
...getGitEnv(),
38+
...gitEnv,
7439
GCM_INTERACTIVE: 'NEVER',
7540
GCM_PRESERVE_CREDS: 'TRUE',
7641
LC_ALL: 'en_US.UTF-8',

src/core/git/scmGit.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as vscode from 'vscode';
2+
import type { GitExtension, API as ScmGitApi } from '@/@types/vscode.git';
3+
import logger from '@/core/log/logger';
4+
5+
class GitApi implements vscode.Disposable {
6+
private _api: ScmGitApi | undefined;
7+
private _gitPath: string = 'git';
8+
private _gitEnv: Record<string, string> = {};
9+
private _disposed: boolean = false;
10+
constructor() {
11+
this._api = undefined;
12+
}
13+
14+
get gitPath(): string {
15+
return this._gitPath;
16+
}
17+
18+
get gitEnv(): Record<string, string> {
19+
return this._gitEnv;
20+
}
21+
22+
async getAPI(): Promise<ScmGitApi | undefined> {
23+
try {
24+
if (this._disposed) return undefined;
25+
// Check if the API is already cached
26+
if (this._api) return this._api;
27+
28+
const extension = vscode.extensions.getExtension<GitExtension>('vscode.git');
29+
if (!extension) return undefined;
30+
const gitExtension = extension.isActive ? extension.exports : await extension.activate();
31+
32+
// Cache the API and Git path/env
33+
this.cacheAPI(gitExtension?.getAPI(1));
34+
35+
return this._api;
36+
} catch (error) {
37+
logger.error(`Failed to get SCM Git API: ${error}`);
38+
this.cacheAPI(undefined);
39+
return undefined;
40+
}
41+
}
42+
43+
private cacheAPI(api?: ScmGitApi): void {
44+
this._api = api;
45+
this._gitPath = api?.git.path || 'git';
46+
this._gitEnv = (api?.git as any).env || {};
47+
}
48+
49+
dispose(): void {
50+
this._api = undefined;
51+
this._disposed = true;
52+
}
53+
}
54+
55+
const gitApi = new GitApi();
56+
57+
export { gitApi };

src/core/util/path.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import path from 'path';
22

33
function toSimplePath(path: string) {
4-
const normalized = path.replace(/\\/g, '/');
5-
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
4+
// Just handle the disk drive letter on Windows, keep other platforms unchanged
5+
if (process.platform === 'win32') {
6+
const normalized = path.replace(/\\/g, '/');
7+
const diskPattern = /^[a-zA-Z]:/;
8+
const match = normalized.match(diskPattern);
9+
if (match) {
10+
return match[0].toLowerCase() + normalized.slice(match[0].length);
11+
} else {
12+
return normalized;
13+
}
14+
}
15+
return path;
616
}
717

818
function comparePath(path1: string = '', path2: string = '') {

0 commit comments

Comments
 (0)