src/vs/platform/git/node/localGitService.ts

160 LOC · 125 covered · 35 uncovered · 41 ranges · 16 concepts · 15 introducers · 8 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the filelocalGitService.ts ×5 · 15 introduced LOClocalGitService.ts ×5localGitService.ts ×1 · 2 introduced LOClocalGitService.ts ×1localGitService.ts ×1 · 2 introduced LOClocalGitService.ts ×1localGitService.ts ×1 · 2 introduced LOClocalGitService.ts ×1localGitService.ts ×1 · 5 introduced LOClocalGitService.ts ×1localGitService.ts ×4 · 7 introduced LOClocalGitService.ts ×4localGitService.ts ×1 · 2 introduced LOClocalGitService.ts ×1localGitService.ts ×1 · 2 introduced LOClocalGitService.ts ×1localGitService.ts ×1 · 1 introduced LOClocalGitService.ts ×1localGitService.ts ×2 · 2 introduced LOClocalGitService.ts ×2localGitService.test|title=LocalGitService pull succeeds on second ff-only attempt after fetch|occurrence=1 · 0 introduced LOClocalGitService.test|tit…localGitService.ts ×2 · 6 introduced LOClocalGitService.ts ×2localGitService.ts ×1 · 2 introduced LOClocalGitService.ts ×1localGitService.ts ×6 · 14 introduced LOClocalGitService.ts ×6localGitService.ts ×1 · 3 introduced LOClocalGitService.ts ×1localGitService.ts ×13 · 60 introduced LOClocalGitService.ts ×13localGitService.test|title=LocalGitService pull recovers from diverged history by resetting to upstream|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/git/test/node/localGitService.test|title=LocalGitService pull recovers from diverged history by resetting to upstream|occurrence=1localGitService.test|tit…localGitService.test|title=LocalGitService pull rejects hard reset recovery when working tree is dirty|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/git/test/node/localGitService.test|title=LocalGitService pull rejects hard reset recovery when working tree is dirty|occurrence=1localGitService.test|tit…localGitService.test|title=LocalGitService pull rethrows non-fast-forward errors without retrying|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/git/test/node/localGitService.test|title=LocalGitService pull rethrows non-fast-forward errors without retrying|occurrence=1localGitService.test|tit…localGitService.test|title=LocalGitService pull rethrows retry failures that are not fast-forward related|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/git/test/node/localGitService.test|title=LocalGitService pull rethrows retry failures that are not fast-forward related|occurrence=1localGitService.test|tit…localGitService.test|title=LocalGitService pull rethrows when upstream cannot be resolved during recovery|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/git/test/node/localGitService.test|title=LocalGitService pull rethrows when upstream cannot be resolved during recovery|occurrence=1localGitService.test|tit…localGitService.test|title=LocalGitService pull runs ff-only for normal updates|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/git/test/node/localGitService.test|title=LocalGitService pull runs ff-only for normal updates|occurrence=1localGitService.test|tit…localGitService.test|title=LocalGitService pull succeeds on second ff-only attempt after fetch|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/git/test/node/localGitService.test|title=LocalGitService pull succeeds on second ff-only attempt after fetch|occurrence=1localGitService.test|tit…localGitService.test|title=LocalGitService pull without hard-reset option does not attempt destructive recovery|occurrence=1 · introduced test · mocha:v1|namespace=vscode@05c208e9e28d8c1c723fa08f85e2b7a96092e8e5|file=vs/platform/git/test/node/localGitService.test|title=LocalGitService pull without hard-reset option does not attempt destructive recovery|occurrence=1localGitService.test|tit…Focused file · src/vs/platform/git/node/localGitService.ts · 160 LOCnode/localGitService.ts

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- localGitService.ts ×13
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as cp from 'child_process';
7 > import { CancellationError } from '../../../base/common/errors.js';
8 > import { generateUuid } from '../../../base/common/uuid.js';
9 > import { IGitPullOptions, ILocalGitService } from '../common/localGitService.js';
10 > import { ILogService } from '../../log/common/log.js';
11 >
12 > export class LocalGitService implements ILocalGitService {
13 > declare readonly _serviceBrand: undefined;
14 >
15 > private _runningProcesses = new Map<string, cp.ChildProcess>();
16 >
17 > constructor(
18 > @ILogService private readonly _logService: ILogService,
19 > private readonly _execFile: typeof cp.execFile = cp.execFile,
20 > ) { }
21 >
22 > private _exec(operationId: string, args: string[], cwd?: string): Promise<string> {
23 > return new Promise((resolve, reject) => {
24 > this._logService.trace(`[LocalGitService] git ${args.join(' ')}${cwd ? ` (cwd: ${cwd})` : ''}`);
25 > const proc = this._execFile('git', args, { cwd, encoding: 'utf8' }, (err, stdout, stderr) => {
26 > if (!this._runningProcesses.delete(operationId)) {
27 reject(new CancellationError());
28 return;
29 }
30 > if (err) { localGitService.ts ×13
31 > this._logService.error(`[LocalGitService] git ${args[0]} failed:`, err.message, stderr); localGitService.ts ×6
32 > reject(err);
33 > return;
34 > }
35 > resolve(stdout); localGitService.ts ×13
36 > });
37 >
38 > this._runningProcesses.set(operationId, proc);
39 > });
40 > }
41 >
42 > async clone(operationId: string, cloneUrl: string, targetPath: string, ref?: string): Promise<void> {
43 const args = ['clone'];
44 if (ref) {
45 args.push('--branch', ref);
46 }
47 args.push('--', cloneUrl, targetPath);
48 await this._exec(operationId, args);
49 }
51 > async pull(operationId: string, repoPath: string, options?: IGitPullOptions): Promise<boolean> {
52 > const before = (await this._exec(operationId, ['rev-parse', 'HEAD'], repoPath)).trim();
53 >
54 > try {
55 > await this._exec(operationId, ['pull', '--ff-only'], repoPath);
56 > } catch (err) {
57 > if (!this._isFastForwardPullFailure(err)) { localGitService.ts ×6
58 > throw err; localGitService.ts ×1
59 > }
61 > const error = err as { message?: string };
62 > this._logService.warn(`[LocalGitService] Fast-forward pull failed for ${repoPath}: ${error?.message ?? String(err)}. Retrying after fetch.`); localGitService.ts ×6
63 > await this._exec(operationId, ['fetch', '--prune'], repoPath);
65 > try {
66 > await this._exec(operationId, ['pull', '--ff-only'], repoPath);
67 > } catch (retryErr) {
68 > if (!this._isFastForwardPullFailure(retryErr)) { localGitService.ts ×2
69 > throw retryErr; localGitService.ts ×1
70 > }
72 > if (!options?.allowHardResetOnDivergence) { localGitService.ts ×2
73 > throw retryErr; localGitService.ts ×1
74 > }
76 > const upstream = await this._getSafeHardResetTarget(operationId, repoPath);
77 > if (!upstream) {
78 > throw retryErr; localGitService.ts ×1
79 > }
81 > this._logService.warn(`[LocalGitService] Pull retries exhausted for ${repoPath}. Performing hard reset to ${upstream}.`);
82 > await this._exec(operationId, ['reset', '--hard', upstream], repoPath);
83 > }
86 > const after = (await this._exec(operationId, ['rev-parse', 'HEAD'], repoPath)).trim();
87 > return before !== after;
89 >
90 > private _isFastForwardPullFailure(err: unknown): err is cp.ExecFileException & { stderr?: string } {
91 > const error = err as (cp.ExecFileException & { stderr?: string; message?: string }) | undefined; localGitService.ts ×6
92 > if (error?.code !== 128) {
93 return false;
94 }
96 > const details = `${error.stderr ?? ''}\n${error.message ?? ''}`;
97 > return /not possible to fast-forward|non-fast-forward/i.test(details);
98 > }
100 > private async _getSafeHardResetTarget(operationId: string, repoPath: string): Promise<string | undefined> {
101 > const status = (await this._exec(operationId, ['status', '--porcelain'], repoPath)).trim(); localGitService.ts ×4
102 > if (status.length > 0) {
103 > return undefined; localGitService.ts ×1
104 > }
106 > let upstream: string;
107 > try {
108 > upstream = (await this._exec(operationId, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], repoPath)).trim();
109 > } catch {
110 > return undefined; localGitService.ts ×1
111 > }
113 > const behind = await this._revListCount(operationId, repoPath, 'HEAD', '@{u}');
114 > const ahead = await this._revListCount(operationId, repoPath, '@{u}', 'HEAD');
115 > if (ahead === undefined || behind === undefined || ahead <= 0 || behind <= 0) { localGitService.ts ×4
116 return undefined;
117 }
119 > return upstream;
122 > private async _revListCount(operationId: string, repoPath: string, fromRef: string, toRef: string): Promise<number | undefined> {
123 > const result = await this._exec(operationId, ['rev-list', '--count', `${fromRef}..${toRef}`], repoPath); localGitService.ts ×5
124 > const parsed = Number(result.trim());
125 > if (!Number.isFinite(parsed)) {
126 this._logService.warn(`[LocalGitService] Failed to parse rev-list count for ${fromRef}..${toRef} in ${repoPath}: ${result}`);
127 return undefined;
128 }
130 > return parsed;
131 > }
133 > async checkout(operationId: string, repoPath: string, treeish: string, detached?: boolean): Promise<void> {
134 const args = detached
135 ? ['checkout', '--detach', treeish]
136 : ['checkout', treeish];
137 await this._exec(operationId, args, repoPath);
138 }
140 > async revParse(repoPath: string, ref: string): Promise<string> {
141 return (await this._exec(generateUuid(), ['rev-parse', ref], repoPath)).trim();
142 }
144 > async fetch(operationId: string, repoPath: string): Promise<void> {
145 await this._exec(operationId, ['fetch'], repoPath);
146 }
148 > async revListCount(repoPath: string, fromRef: string, toRef: string): Promise<number> {
149 const result = await this._exec(generateUuid(), ['rev-list', '--count', `${fromRef}..${toRef}`], repoPath);
150 return Number(result.trim()) || 0;
151 }
153 > async cancel(operationId: string): Promise<void> {
154 const proc = this._runningProcesses.get(operationId);
155 if (proc) {
156 this._runningProcesses.delete(operationId);
157 proc.kill();
158 }
159 }