Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git/src/main.ts
5225 views
1
/*---------------------------------------------------------------------------------------------
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 { env, ExtensionContext, workspace, window, Disposable, commands, Uri, version as vscodeVersion, WorkspaceFolder, LogOutputChannel, l10n, LogLevel, languages } from 'vscode';
7
import { findGit, Git, IGit } from './git';
8
import { Model } from './model';
9
import { CommandCenter } from './commands';
10
import { GitFileSystemProvider } from './fileSystemProvider';
11
import { GitDecorations } from './decorationProvider';
12
import { Askpass } from './askpass';
13
import { toDisposable, filterEvent, eventToPromise } from './util';
14
import TelemetryReporter from '@vscode/extension-telemetry';
15
import { GitExtension } from './api/git';
16
import { GitProtocolHandler } from './protocolHandler';
17
import { GitExtensionImpl } from './api/extension';
18
import * as path from 'path';
19
import * as fs from 'fs';
20
import * as os from 'os';
21
import { GitTimelineProvider } from './timelineProvider';
22
import { registerAPICommands } from './api/api1';
23
import { TerminalEnvironmentManager, TerminalShellExecutionManager } from './terminal';
24
import { createIPCServer, IPCServer } from './ipc/ipcServer';
25
import { GitEditor, GitEditorDocumentLinkProvider } from './gitEditor';
26
import { GitPostCommitCommandsProvider } from './postCommitCommands';
27
import { GitEditSessionIdentityProvider } from './editSessionIdentityProvider';
28
import { GitCommitInputBoxCodeActionsProvider, GitCommitInputBoxDiagnosticsManager } from './diagnostics';
29
import { GitBlameController } from './blame';
30
import { CloneManager } from './cloneManager';
31
import { getAskpassPaths } from './askpassManager';
32
33
const deactivateTasks: { (): Promise<void> }[] = [];
34
35
export async function deactivate(): Promise<void> {
36
for (const task of deactivateTasks) {
37
await task();
38
}
39
}
40
41
async function createModel(context: ExtensionContext, logger: LogOutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise<{ model: Model; cloneManager: CloneManager }> {
42
const pathValue = workspace.getConfiguration('git').get<string | string[]>('path');
43
let pathHints = Array.isArray(pathValue) ? pathValue : pathValue ? [pathValue] : [];
44
45
const { isTrusted, workspaceFolders = [] } = workspace;
46
const excludes = isTrusted ? [] : workspaceFolders.map(f => path.normalize(f.uri.fsPath).replace(/[\r\n]+$/, ''));
47
48
if (!isTrusted && pathHints.length !== 0) {
49
// Filter out any non-absolute paths
50
pathHints = pathHints.filter(p => path.isAbsolute(p));
51
}
52
53
const info = await findGit(pathHints, gitPath => {
54
logger.info(l10n.t('[main] Validating found git in: "{0}"', gitPath));
55
if (excludes.length === 0) {
56
return true;
57
}
58
59
const normalized = path.normalize(gitPath).replace(/[\r\n]+$/, '');
60
const skip = excludes.some(e => normalized.startsWith(e));
61
if (skip) {
62
logger.info(l10n.t('[main] Skipped found git in: "{0}"', gitPath));
63
}
64
return !skip;
65
}, logger);
66
67
let ipcServer: IPCServer | undefined = undefined;
68
69
try {
70
ipcServer = await createIPCServer(context.storagePath);
71
} catch (err) {
72
logger.error(`[main] Failed to create git IPC: ${err}`);
73
}
74
75
const askpassPaths = await getAskpassPaths(__dirname, context.globalStorageUri.fsPath, logger);
76
const askpass = new Askpass(ipcServer, logger, askpassPaths);
77
disposables.push(askpass);
78
79
const gitEditor = new GitEditor(ipcServer);
80
disposables.push(gitEditor);
81
82
const environment = { ...askpass.getEnv(), ...gitEditor.getEnv(), ...ipcServer?.getEnv() };
83
const terminalEnvironmentManager = new TerminalEnvironmentManager(context, [askpass, gitEditor, ipcServer]);
84
disposables.push(terminalEnvironmentManager);
85
86
logger.info(l10n.t('[main] Using git "{0}" from "{1}"', info.version, info.path));
87
88
const git = new Git({
89
gitPath: info.path,
90
userAgent: `git/${info.version} (${os.version() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) vscode/${vscodeVersion} (${env.appName})`,
91
version: info.version,
92
env: environment,
93
});
94
const model = new Model(git, askpass, context.globalState, context.workspaceState, logger, telemetryReporter);
95
disposables.push(model);
96
const cloneManager = new CloneManager(model, telemetryReporter, model.repositoryCache);
97
98
const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`);
99
model.onDidOpenRepository(onRepository, null, disposables);
100
model.onDidCloseRepository(onRepository, null, disposables);
101
onRepository();
102
103
const onOutput = (str: string) => {
104
const lines = str.split(/\r?\n/mg);
105
106
while (/^\s*$/.test(lines[lines.length - 1])) {
107
lines.pop();
108
}
109
110
logger.appendLine(lines.join('\n'));
111
};
112
git.onOutput.addListener('log', onOutput);
113
disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput)));
114
115
const cc = new CommandCenter(git, model, context.globalState, logger, telemetryReporter, cloneManager);
116
disposables.push(
117
cc,
118
new GitFileSystemProvider(model, logger),
119
new GitDecorations(model),
120
new GitBlameController(model),
121
new GitTimelineProvider(model, cc),
122
new GitEditSessionIdentityProvider(model),
123
new TerminalShellExecutionManager(model, logger)
124
);
125
126
const postCommitCommandsProvider = new GitPostCommitCommandsProvider(model);
127
model.registerPostCommitCommandsProvider(postCommitCommandsProvider);
128
129
const diagnosticsManager = new GitCommitInputBoxDiagnosticsManager(model);
130
disposables.push(diagnosticsManager);
131
132
const codeActionsProvider = new GitCommitInputBoxCodeActionsProvider(diagnosticsManager);
133
disposables.push(codeActionsProvider);
134
135
const gitEditorDocumentLinkProvider = languages.registerDocumentLinkProvider('git-commit', new GitEditorDocumentLinkProvider(model));
136
disposables.push(gitEditorDocumentLinkProvider);
137
138
checkGitVersion(info);
139
commands.executeCommand('setContext', 'gitVersion2.35', git.compareGitVersionTo('2.35') >= 0);
140
141
return { model, cloneManager };
142
}
143
144
async function isGitRepository(folder: WorkspaceFolder): Promise<boolean> {
145
if (folder.uri.scheme !== 'file') {
146
return false;
147
}
148
149
const dotGit = path.join(folder.uri.fsPath, '.git');
150
151
try {
152
const dotGitStat = await new Promise<fs.Stats>((c, e) => fs.stat(dotGit, (err, stat) => err ? e(err) : c(stat)));
153
return dotGitStat.isDirectory();
154
} catch (err) {
155
return false;
156
}
157
}
158
159
async function warnAboutMissingGit(): Promise<void> {
160
const config = workspace.getConfiguration('git');
161
const shouldIgnore = config.get<boolean>('ignoreMissingGitWarning') === true;
162
163
if (shouldIgnore) {
164
return;
165
}
166
167
if (!workspace.workspaceFolders) {
168
return;
169
}
170
171
const areGitRepositories = await Promise.all(workspace.workspaceFolders.map(isGitRepository));
172
173
if (areGitRepositories.every(isGitRepository => !isGitRepository)) {
174
return;
175
}
176
177
const download = l10n.t('Download Git');
178
const neverShowAgain = l10n.t('Don\'t Show Again');
179
const choice = await window.showWarningMessage(
180
l10n.t('Git not found. Install it or configure it using the "git.path" setting.'),
181
download,
182
neverShowAgain
183
);
184
185
if (choice === download) {
186
commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-download-git'));
187
} else if (choice === neverShowAgain) {
188
await config.update('ignoreMissingGitWarning', true, true);
189
}
190
}
191
192
export async function _activate(context: ExtensionContext): Promise<GitExtensionImpl> {
193
const disposables: Disposable[] = [];
194
context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose()));
195
196
const logger = window.createOutputChannel('Git', { log: true });
197
disposables.push(logger);
198
199
const onDidChangeLogLevel = (logLevel: LogLevel) => {
200
logger.appendLine(l10n.t('[main] Log level: {0}', LogLevel[logLevel]));
201
};
202
disposables.push(logger.onDidChangeLogLevel(onDidChangeLogLevel));
203
onDidChangeLogLevel(logger.logLevel);
204
205
const { aiKey } = require('../package.json') as { aiKey: string };
206
const telemetryReporter = new TelemetryReporter(aiKey);
207
deactivateTasks.push(() => telemetryReporter.dispose());
208
209
const config = workspace.getConfiguration('git', null);
210
const enabled = config.get<boolean>('enabled');
211
212
if (!enabled) {
213
const onConfigChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git'));
214
const onEnabled = filterEvent(onConfigChange, () => workspace.getConfiguration('git', null).get<boolean>('enabled') === true);
215
const result = new GitExtensionImpl();
216
217
eventToPromise(onEnabled).then(async () => {
218
const { model, cloneManager } = await createModel(context, logger, telemetryReporter, disposables);
219
result.model = model;
220
result.cloneManager = cloneManager;
221
});
222
return result;
223
}
224
225
try {
226
const { model, cloneManager } = await createModel(context, logger, telemetryReporter, disposables);
227
228
return new GitExtensionImpl({ model, cloneManager });
229
} catch (err) {
230
console.warn(err.message);
231
logger.warn(`[main] Failed to create model: ${err}`);
232
233
if (!/Git installation not found/.test(err.message || '')) {
234
throw err;
235
}
236
237
/* __GDPR__
238
"git.missing" : {
239
"owner": "lszomoru"
240
}
241
*/
242
telemetryReporter.sendTelemetryEvent('git.missing');
243
244
commands.executeCommand('setContext', 'git.missing', true);
245
warnAboutMissingGit();
246
247
return new GitExtensionImpl();
248
} finally {
249
disposables.push(new GitProtocolHandler(logger));
250
}
251
}
252
253
let _context: ExtensionContext;
254
export function getExtensionContext(): ExtensionContext {
255
return _context;
256
}
257
258
export async function activate(context: ExtensionContext): Promise<GitExtension> {
259
_context = context;
260
261
const result = await _activate(context);
262
context.subscriptions.push(registerAPICommands(result));
263
return result;
264
}
265
266
async function checkGitv1(info: IGit): Promise<void> {
267
const config = workspace.getConfiguration('git');
268
const shouldIgnore = config.get<boolean>('ignoreLegacyWarning') === true;
269
270
if (shouldIgnore) {
271
return;
272
}
273
274
if (!/^[01]/.test(info.version)) {
275
return;
276
}
277
278
const update = l10n.t('Update Git');
279
const neverShowAgain = l10n.t('Don\'t Show Again');
280
281
const choice = await window.showWarningMessage(
282
l10n.t('You seem to have git "{0}" installed. Code works best with git >= 2', info.version),
283
update,
284
neverShowAgain
285
);
286
287
if (choice === update) {
288
commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-download-git'));
289
} else if (choice === neverShowAgain) {
290
await config.update('ignoreLegacyWarning', true, true);
291
}
292
}
293
294
async function checkGitWindows(info: IGit): Promise<void> {
295
if (!/^2\.(25|26)\./.test(info.version)) {
296
return;
297
}
298
299
const config = workspace.getConfiguration('git');
300
const shouldIgnore = config.get<boolean>('ignoreWindowsGit27Warning') === true;
301
302
if (shouldIgnore) {
303
return;
304
}
305
306
const update = l10n.t('Update Git');
307
const neverShowAgain = l10n.t('Don\'t Show Again');
308
const choice = await window.showWarningMessage(
309
l10n.t('There are known issues with the installed Git "{0}". Please update to Git >= 2.27 for the git features to work correctly.', info.version),
310
update,
311
neverShowAgain
312
);
313
314
if (choice === update) {
315
commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-download-git'));
316
} else if (choice === neverShowAgain) {
317
await config.update('ignoreWindowsGit27Warning', true, true);
318
}
319
}
320
321
async function checkGitVersion(info: IGit): Promise<void> {
322
await checkGitv1(info);
323
324
if (process.platform === 'win32') {
325
await checkGitWindows(info);
326
}
327
}
328
329