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