Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git/src/api/api1.ts
5221 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
/* eslint-disable local/code-no-native-private */
7
8
import { Model } from '../model';
9
import { Repository as BaseRepository, Resource } from '../repository';
10
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions, CommitShortStat, DiffChange, Worktree, RepositoryKind, RepositoryAccessDetails } from './git';
11
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode';
12
import { combinedDisposable, filterEvent, mapEvent } from '../util';
13
import { toGitUri } from '../uri';
14
import { GitExtensionImpl } from './extension';
15
import { GitBaseApi } from '../git-base';
16
import { PickRemoteSourceOptions } from '../typings/git-base';
17
import { OperationKind, OperationResult } from '../operation';
18
import { CloneManager } from '../cloneManager';
19
20
class ApiInputBox implements InputBox {
21
#inputBox: SourceControlInputBox;
22
23
constructor(inputBox: SourceControlInputBox) { this.#inputBox = inputBox; }
24
25
set value(value: string) { this.#inputBox.value = value; }
26
get value(): string { return this.#inputBox.value; }
27
}
28
29
export class ApiChange implements Change {
30
#resource: Resource;
31
constructor(resource: Resource) { this.#resource = resource; }
32
33
get uri(): Uri { return this.#resource.resourceUri; }
34
get originalUri(): Uri { return this.#resource.original; }
35
get renameUri(): Uri | undefined { return this.#resource.renameResourceUri; }
36
get status(): Status { return this.#resource.type; }
37
}
38
39
export class ApiRepositoryState implements RepositoryState {
40
#repository: BaseRepository;
41
readonly onDidChange: Event<void>;
42
43
constructor(repository: BaseRepository) {
44
this.#repository = repository;
45
this.onDidChange = this.#repository.onDidRunGitStatus;
46
}
47
48
get HEAD(): Branch | undefined { return this.#repository.HEAD; }
49
/**
50
* @deprecated Use ApiRepository.getRefs() instead.
51
*/
52
get refs(): Ref[] { console.warn('Deprecated. Use ApiRepository.getRefs() instead.'); return []; }
53
get remotes(): Remote[] { return [...this.#repository.remotes]; }
54
get submodules(): Submodule[] { return [...this.#repository.submodules]; }
55
get worktrees(): Worktree[] { return this.#repository.worktrees; }
56
get rebaseCommit(): Commit | undefined { return this.#repository.rebaseCommit; }
57
58
get mergeChanges(): Change[] { return this.#repository.mergeGroup.resourceStates.map(r => new ApiChange(r)); }
59
get indexChanges(): Change[] { return this.#repository.indexGroup.resourceStates.map(r => new ApiChange(r)); }
60
get workingTreeChanges(): Change[] { return this.#repository.workingTreeGroup.resourceStates.map(r => new ApiChange(r)); }
61
get untrackedChanges(): Change[] { return this.#repository.untrackedGroup.resourceStates.map(r => new ApiChange(r)); }
62
}
63
64
export class ApiRepositoryUIState implements RepositoryUIState {
65
#sourceControl: SourceControl;
66
readonly onDidChange: Event<void>;
67
68
constructor(sourceControl: SourceControl) {
69
this.#sourceControl = sourceControl;
70
this.onDidChange = mapEvent<boolean, void>(this.#sourceControl.onDidChangeSelection, () => null);
71
}
72
73
get selected(): boolean { return this.#sourceControl.selected; }
74
}
75
76
export class ApiRepository implements Repository {
77
#repository: BaseRepository;
78
79
readonly rootUri: Uri;
80
readonly inputBox: InputBox;
81
readonly kind: RepositoryKind;
82
readonly state: RepositoryState;
83
readonly ui: RepositoryUIState;
84
85
readonly onDidCommit: Event<void>;
86
readonly onDidCheckout: Event<void>;
87
88
constructor(repository: BaseRepository) {
89
this.#repository = repository;
90
91
this.kind = this.#repository.kind;
92
this.rootUri = Uri.file(this.#repository.root);
93
this.inputBox = new ApiInputBox(this.#repository.inputBox);
94
this.state = new ApiRepositoryState(this.#repository);
95
this.ui = new ApiRepositoryUIState(this.#repository.sourceControl);
96
97
this.onDidCommit = mapEvent<OperationResult, void>(
98
filterEvent(this.#repository.onDidRunOperation, e => e.operation.kind === OperationKind.Commit), () => null);
99
this.onDidCheckout = mapEvent<OperationResult, void>(
100
filterEvent(this.#repository.onDidRunOperation, e => e.operation.kind === OperationKind.Checkout || e.operation.kind === OperationKind.CheckoutTracking), () => null);
101
}
102
103
apply(patch: string, reverse?: boolean): Promise<void>;
104
apply(patch: string, options?: { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean }): Promise<void>;
105
apply(patch: string, reverseOrOptions?: boolean | { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean }): Promise<void> {
106
const options = typeof reverseOrOptions === 'boolean' ? { reverse: reverseOrOptions } : reverseOrOptions;
107
return this.#repository.apply(patch, options);
108
}
109
110
getConfigs(): Promise<{ key: string; value: string }[]> {
111
return this.#repository.getConfigs();
112
}
113
114
getConfig(key: string): Promise<string> {
115
return this.#repository.getConfig(key);
116
}
117
118
setConfig(key: string, value: string): Promise<string> {
119
return this.#repository.setConfig(key, value);
120
}
121
122
unsetConfig(key: string): Promise<string> {
123
return this.#repository.unsetConfig(key);
124
}
125
126
getGlobalConfig(key: string): Promise<string> {
127
return this.#repository.getGlobalConfig(key);
128
}
129
130
getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number }> {
131
return this.#repository.getObjectDetails(treeish, path);
132
}
133
134
detectObjectType(object: string): Promise<{ mimetype: string; encoding?: string }> {
135
return this.#repository.detectObjectType(object);
136
}
137
138
buffer(ref: string, filePath: string): Promise<Buffer> {
139
return this.#repository.buffer(ref, filePath);
140
}
141
142
show(ref: string, path: string): Promise<string> {
143
return this.#repository.show(ref, path);
144
}
145
146
getCommit(ref: string): Promise<Commit> {
147
return this.#repository.getCommit(ref);
148
}
149
150
add(paths: string[]) {
151
return this.#repository.add(paths.map(p => Uri.file(p)));
152
}
153
154
revert(paths: string[]) {
155
return this.#repository.revert(paths.map(p => Uri.file(p)));
156
}
157
158
clean(paths: string[]) {
159
return this.#repository.clean(paths.map(p => Uri.file(p)));
160
}
161
162
diff(cached?: boolean) {
163
return this.#repository.diff(cached);
164
}
165
166
diffWithHEAD(): Promise<Change[]>;
167
diffWithHEAD(path: string): Promise<string>;
168
diffWithHEAD(path?: string): Promise<string | Change[]> {
169
return this.#repository.diffWithHEAD(path);
170
}
171
172
diffWithHEADShortStats(path?: string): Promise<CommitShortStat> {
173
return this.#repository.diffWithHEADShortStats(path);
174
}
175
176
diffWith(ref: string): Promise<Change[]>;
177
diffWith(ref: string, path: string): Promise<string>;
178
diffWith(ref: string, path?: string): Promise<string | Change[]> {
179
return this.#repository.diffWith(ref, path);
180
}
181
182
diffIndexWithHEAD(): Promise<Change[]>;
183
diffIndexWithHEAD(path: string): Promise<string>;
184
diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
185
return this.#repository.diffIndexWithHEAD(path);
186
}
187
188
diffIndexWithHEADShortStats(path?: string): Promise<CommitShortStat> {
189
return this.#repository.diffIndexWithHEADShortStats(path);
190
}
191
192
diffIndexWith(ref: string): Promise<Change[]>;
193
diffIndexWith(ref: string, path: string): Promise<string>;
194
diffIndexWith(ref: string, path?: string): Promise<string | Change[]> {
195
return this.#repository.diffIndexWith(ref, path);
196
}
197
198
diffBlobs(object1: string, object2: string): Promise<string> {
199
return this.#repository.diffBlobs(object1, object2);
200
}
201
202
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
203
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
204
diffBetween(ref1: string, ref2: string, path?: string): Promise<string | Change[]> {
205
return this.#repository.diffBetween(ref1, ref2, path);
206
}
207
208
diffBetweenPatch(ref1: string, ref2: string, path?: string): Promise<string> {
209
return this.#repository.diffBetweenPatch(ref1, ref2, path);
210
}
211
212
diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise<DiffChange[]> {
213
return this.#repository.diffBetweenWithStats(ref1, ref2, path);
214
}
215
216
hashObject(data: string): Promise<string> {
217
return this.#repository.hashObject(data);
218
}
219
220
createBranch(name: string, checkout: boolean, ref?: string | undefined): Promise<void> {
221
return this.#repository.branch(name, checkout, ref);
222
}
223
224
deleteBranch(name: string, force?: boolean): Promise<void> {
225
return this.#repository.deleteBranch(name, force);
226
}
227
228
getBranch(name: string): Promise<Branch> {
229
return this.#repository.getBranch(name);
230
}
231
232
getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise<Ref[]> {
233
return this.#repository.getBranches(query, cancellationToken);
234
}
235
236
getBranchBase(name: string): Promise<Branch | undefined> {
237
return this.#repository.getBranchBase(name);
238
}
239
240
setBranchUpstream(name: string, upstream: string): Promise<void> {
241
return this.#repository.setBranchUpstream(name, upstream);
242
}
243
244
getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise<Ref[]> {
245
return this.#repository.getRefs(query, cancellationToken);
246
}
247
248
checkIgnore(paths: string[]): Promise<Set<string>> {
249
return this.#repository.checkIgnore(paths);
250
}
251
252
getMergeBase(ref1: string, ref2: string): Promise<string | undefined> {
253
return this.#repository.getMergeBase(ref1, ref2);
254
}
255
256
tag(name: string, message: string, ref?: string | undefined): Promise<void> {
257
return this.#repository.tag({ name, message, ref });
258
}
259
260
deleteTag(name: string): Promise<void> {
261
return this.#repository.deleteTag(name);
262
}
263
264
status(): Promise<void> {
265
return this.#repository.status();
266
}
267
268
checkout(treeish: string): Promise<void> {
269
return this.#repository.checkout(treeish);
270
}
271
272
addRemote(name: string, url: string): Promise<void> {
273
return this.#repository.addRemote(name, url);
274
}
275
276
removeRemote(name: string): Promise<void> {
277
return this.#repository.removeRemote(name);
278
}
279
280
renameRemote(name: string, newName: string): Promise<void> {
281
return this.#repository.renameRemote(name, newName);
282
}
283
284
fetch(arg0?: FetchOptions | string | undefined,
285
ref?: string | undefined,
286
depth?: number | undefined,
287
prune?: boolean | undefined
288
): Promise<void> {
289
if (arg0 !== undefined && typeof arg0 !== 'string') {
290
return this.#repository.fetch(arg0);
291
}
292
293
return this.#repository.fetch({ remote: arg0, ref, depth, prune });
294
}
295
296
pull(unshallow?: boolean): Promise<void> {
297
return this.#repository.pull(undefined, unshallow);
298
}
299
300
push(remoteName?: string, branchName?: string, setUpstream: boolean = false, force?: ForcePushMode): Promise<void> {
301
return this.#repository.pushTo(remoteName, branchName, setUpstream, force);
302
}
303
304
blame(path: string): Promise<string> {
305
return this.#repository.blame(path);
306
}
307
308
log(options?: LogOptions): Promise<Commit[]> {
309
return this.#repository.log(options);
310
}
311
312
commit(message: string, opts?: CommitOptions): Promise<void> {
313
return this.#repository.commit(message, { ...opts, postCommitCommand: null });
314
}
315
316
merge(ref: string): Promise<void> {
317
return this.#repository.merge(ref);
318
}
319
320
mergeAbort(): Promise<void> {
321
return this.#repository.mergeAbort();
322
}
323
324
createStash(options?: { message?: string; includeUntracked?: boolean; staged?: boolean }): Promise<void> {
325
return this.#repository.createStash(options?.message, options?.includeUntracked, options?.staged);
326
}
327
328
applyStash(index?: number): Promise<void> {
329
return this.#repository.applyStash(index);
330
}
331
332
popStash(index?: number): Promise<void> {
333
return this.#repository.popStash(index);
334
}
335
336
dropStash(index?: number): Promise<void> {
337
return this.#repository.dropStash(index);
338
}
339
340
createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise<string> {
341
return this.#repository.createWorktree(options);
342
}
343
344
deleteWorktree(path: string, options?: { force?: boolean }): Promise<void> {
345
return this.#repository.deleteWorktree(path, options);
346
}
347
348
migrateChanges(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise<void> {
349
return this.#repository.migrateChanges(sourceRepositoryPath, options);
350
}
351
}
352
353
export class ApiGit implements Git {
354
#model: Model;
355
356
private _env: { [key: string]: string } | undefined;
357
358
constructor(model: Model) { this.#model = model; }
359
360
get path(): string { return this.#model.git.path; }
361
362
get env(): { [key: string]: string } {
363
if (this._env === undefined) {
364
this._env = Object.freeze(this.#model.git.env);
365
}
366
367
return this._env;
368
}
369
}
370
371
export class ApiImpl implements API {
372
#model: Model;
373
#cloneManager: CloneManager;
374
readonly git: ApiGit;
375
376
constructor(privates: { model: Model; cloneManager: CloneManager }) {
377
this.#model = privates.model;
378
this.#cloneManager = privates.cloneManager;
379
this.git = new ApiGit(this.#model);
380
}
381
382
get state(): APIState {
383
return this.#model.state;
384
}
385
386
get onDidChangeState(): Event<APIState> {
387
return this.#model.onDidChangeState;
388
}
389
390
get onDidPublish(): Event<PublishEvent> {
391
return this.#model.onDidPublish;
392
}
393
394
get onDidOpenRepository(): Event<Repository> {
395
return mapEvent(this.#model.onDidOpenRepository, r => new ApiRepository(r));
396
}
397
398
get onDidCloseRepository(): Event<Repository> {
399
return mapEvent(this.#model.onDidCloseRepository, r => new ApiRepository(r));
400
}
401
402
get repositories(): Repository[] {
403
return this.#model.repositories.map(r => new ApiRepository(r));
404
}
405
406
get recentRepositories(): Iterable<RepositoryAccessDetails> {
407
return this.#model.repositoryCache.recentRepositories;
408
}
409
410
toGitUri(uri: Uri, ref: string): Uri {
411
return toGitUri(uri, ref);
412
}
413
414
getRepository(uri: Uri): Repository | null {
415
const result = this.#model.getRepository(uri);
416
return result ? new ApiRepository(result) : null;
417
}
418
419
async getRepositoryRoot(uri: Uri): Promise<Uri | null> {
420
const repository = this.getRepository(uri);
421
if (repository) {
422
return repository.rootUri;
423
}
424
425
try {
426
const root = await this.#model.git.getRepositoryRoot(uri.fsPath);
427
return Uri.file(root);
428
} catch (err) {
429
if (
430
err.gitErrorCode === GitErrorCodes.NotAGitRepository ||
431
err.gitErrorCode === GitErrorCodes.NotASafeGitRepository
432
) {
433
return null;
434
}
435
436
throw err;
437
}
438
}
439
440
async getRepositoryWorkspace(uri: Uri): Promise<Uri[] | null> {
441
const workspaces = this.#model.repositoryCache.get(uri.toString());
442
return workspaces ? workspaces.map(r => Uri.file(r.workspacePath)) : null;
443
}
444
445
async init(root: Uri, options?: InitOptions): Promise<Repository | null> {
446
const path = root.fsPath;
447
await this.#model.git.init(path, options);
448
await this.#model.openRepository(path);
449
return this.getRepository(root) || null;
450
}
451
452
async clone(uri: Uri, options?: CloneOptions): Promise<Uri | null> {
453
const parentPath = options?.parentPath?.fsPath;
454
const result = await this.#cloneManager.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction });
455
return result ? Uri.file(result) : null;
456
}
457
458
async openRepository(root: Uri): Promise<Repository | null> {
459
if (root.scheme !== 'file') {
460
return null;
461
}
462
463
await this.#model.openRepository(root.fsPath);
464
return this.getRepository(root) || null;
465
}
466
467
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
468
const disposables: Disposable[] = [];
469
470
if (provider.publishRepository) {
471
disposables.push(this.#model.registerRemoteSourcePublisher(provider as RemoteSourcePublisher));
472
}
473
disposables.push(GitBaseApi.getAPI().registerRemoteSourceProvider(provider));
474
475
return combinedDisposable(disposables);
476
}
477
478
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable {
479
return this.#model.registerRemoteSourcePublisher(publisher);
480
}
481
482
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
483
return this.#model.registerCredentialsProvider(provider);
484
}
485
486
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable {
487
return this.#model.registerPostCommitCommandsProvider(provider);
488
}
489
490
registerPushErrorHandler(handler: PushErrorHandler): Disposable {
491
return this.#model.registerPushErrorHandler(handler);
492
}
493
494
registerSourceControlHistoryItemDetailsProvider(provider: SourceControlHistoryItemDetailsProvider): Disposable {
495
return this.#model.registerSourceControlHistoryItemDetailsProvider(provider);
496
}
497
498
registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable {
499
return this.#model.registerBranchProtectionProvider(root, provider);
500
}
501
}
502
503
function getRefType(type: RefType): string {
504
switch (type) {
505
case RefType.Head: return 'Head';
506
case RefType.RemoteHead: return 'RemoteHead';
507
case RefType.Tag: return 'Tag';
508
}
509
510
return 'unknown';
511
}
512
513
function getStatus(status: Status): string {
514
switch (status) {
515
case Status.INDEX_MODIFIED: return 'INDEX_MODIFIED';
516
case Status.INDEX_ADDED: return 'INDEX_ADDED';
517
case Status.INDEX_DELETED: return 'INDEX_DELETED';
518
case Status.INDEX_RENAMED: return 'INDEX_RENAMED';
519
case Status.INDEX_COPIED: return 'INDEX_COPIED';
520
case Status.MODIFIED: return 'MODIFIED';
521
case Status.DELETED: return 'DELETED';
522
case Status.UNTRACKED: return 'UNTRACKED';
523
case Status.IGNORED: return 'IGNORED';
524
case Status.INTENT_TO_ADD: return 'INTENT_TO_ADD';
525
case Status.INTENT_TO_RENAME: return 'INTENT_TO_RENAME';
526
case Status.TYPE_CHANGED: return 'TYPE_CHANGED';
527
case Status.ADDED_BY_US: return 'ADDED_BY_US';
528
case Status.ADDED_BY_THEM: return 'ADDED_BY_THEM';
529
case Status.DELETED_BY_US: return 'DELETED_BY_US';
530
case Status.DELETED_BY_THEM: return 'DELETED_BY_THEM';
531
case Status.BOTH_ADDED: return 'BOTH_ADDED';
532
case Status.BOTH_DELETED: return 'BOTH_DELETED';
533
case Status.BOTH_MODIFIED: return 'BOTH_MODIFIED';
534
}
535
536
return 'UNKNOWN';
537
}
538
539
export function registerAPICommands(extension: GitExtensionImpl): Disposable {
540
const disposables: Disposable[] = [];
541
542
disposables.push(commands.registerCommand('git.api.getRepositories', () => {
543
const api = extension.getAPI(1);
544
return api.repositories.map(r => r.rootUri.toString());
545
}));
546
547
disposables.push(commands.registerCommand('git.api.getRepositoryState', (uri: string) => {
548
const api = extension.getAPI(1);
549
const repository = api.getRepository(Uri.parse(uri));
550
551
if (!repository) {
552
return null;
553
}
554
555
const state = repository.state;
556
557
const ref = (ref: Ref | undefined) => (ref && { ...ref, type: getRefType(ref.type) });
558
const change = (change: Change) => ({
559
uri: change.uri.toString(),
560
originalUri: change.originalUri.toString(),
561
renameUri: change.renameUri?.toString(),
562
status: getStatus(change.status)
563
});
564
565
return {
566
HEAD: ref(state.HEAD),
567
refs: state.refs.map(ref),
568
remotes: state.remotes,
569
submodules: state.submodules,
570
worktrees: state.worktrees,
571
rebaseCommit: state.rebaseCommit,
572
mergeChanges: state.mergeChanges.map(change),
573
indexChanges: state.indexChanges.map(change),
574
workingTreeChanges: state.workingTreeChanges.map(change)
575
};
576
}));
577
578
disposables.push(commands.registerCommand('git.api.getRemoteSources', (opts?: PickRemoteSourceOptions) => {
579
return commands.executeCommand('git-base.api.getRemoteSources', opts);
580
}));
581
582
return Disposable.from(...disposables);
583
}
584
585