Skip to content

Commit 9979f9c

Browse files
authored
Git - close repository improvements (microsoft#184708)
* Initial implementation * Move ObservableSet into a separate file * Add quick pick for reopening closed repositories * Fix issue with initializing the context key * Add welcome views
1 parent e913a2e commit 9979f9c

File tree

7 files changed

+177
-11
lines changed

7 files changed

+177
-11
lines changed

extensions/git/package.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@
8484
"category": "Git",
8585
"enablement": "!operationInProgress"
8686
},
87+
{
88+
"command": "git.reopenClosedRepositories",
89+
"title": "%command.reopenClosedRepositories%",
90+
"category": "Git",
91+
"enablement": "!operationInProgress && git.ClosedRepositoryCount != 0"
92+
},
8793
{
8894
"command": "git.close",
8995
"title": "%command.close%",
@@ -2860,14 +2866,14 @@
28602866
{
28612867
"view": "scm",
28622868
"contents": "%view.workbench.scm.empty%",
2863-
"when": "config.git.enabled && !git.missing && workbenchState == empty && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0",
2869+
"when": "config.git.enabled && !git.missing && workbenchState == empty && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0",
28642870
"enablement": "git.state == initialized",
28652871
"group": "2_open@1"
28662872
},
28672873
{
28682874
"view": "scm",
28692875
"contents": "%view.workbench.scm.emptyWorkspace%",
2870-
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0",
2876+
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0",
28712877
"enablement": "git.state == initialized",
28722878
"group": "2_open@1"
28732879
},
@@ -2884,13 +2890,13 @@
28842890
{
28852891
"view": "scm",
28862892
"contents": "%view.workbench.scm.folder%",
2887-
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == folder && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && remoteName != 'codespaces'",
2893+
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == folder && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0 && remoteName != 'codespaces'",
28882894
"group": "5_scm@1"
28892895
},
28902896
{
28912897
"view": "scm",
28922898
"contents": "%view.workbench.scm.workspace%",
2893-
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && remoteName != 'codespaces'",
2899+
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0 && remoteName != 'codespaces'",
28942900
"group": "5_scm@1"
28952901
},
28962902
{
@@ -2913,6 +2919,16 @@
29132919
"contents": "%view.workbench.scm.unsafeRepositories%",
29142920
"when": "config.git.enabled && !git.missing && git.state == initialized && git.unsafeRepositoryCount > 1"
29152921
},
2922+
{
2923+
"view": "scm",
2924+
"contents": "%view.workbench.scm.closedRepository%",
2925+
"when": "config.git.enabled && !git.missing && git.state == initialized && git.closedRepositoryCount == 1"
2926+
},
2927+
{
2928+
"view": "scm",
2929+
"contents": "%view.workbench.scm.closedRepositories%",
2930+
"when": "config.git.enabled && !git.missing && git.state == initialized && git.closedRepositoryCount > 1"
2931+
},
29162932
{
29172933
"view": "explorer",
29182934
"contents": "%view.workbench.cloneRepository%",

extensions/git/package.nls.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"command.cloneRecursive": "Clone (Recursive)",
88
"command.init": "Initialize Repository",
99
"command.openRepository": "Open Repository",
10+
"command.reopenClosedRepositories": "Reopen Closed Repositories...",
1011
"command.close": "Close Repository",
1112
"command.refresh": "Refresh",
1213
"command.openChange": "Open Changes",
@@ -378,6 +379,22 @@
378379
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
379380
]
380381
},
382+
"view.workbench.scm.closedRepository": {
383+
"message": "A git repository was found that was previously closed.\n[Reopen Closed Repository](command:git.reopenClosedRepositories)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
384+
"comment": [
385+
"{Locked='](command:git.reopenClosedRepositories'}",
386+
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
387+
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
388+
]
389+
},
390+
"view.workbench.scm.closedRepositories": {
391+
"message": "Git repositories were found that were previously closed.\n[Reopen Closed Repositories](command:git.reopenClosedRepositories)\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
392+
"comment": [
393+
"{Locked='](command:git.reopenClosedRepositories'}",
394+
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
395+
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
396+
]
397+
},
381398
"view.workbench.cloneRepository": {
382399
"message": "You can clone a repository locally.\n[Clone Repository](command:git.clone 'Clone a repository once the git extension has activated')",
383400
"comment": [

extensions/git/src/commands.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,43 @@ export class CommandCenter {
880880
path = result[0].fsPath;
881881
}
882882

883-
await this.model.openRepository(path);
883+
await this.model.openRepository(path, true);
884+
}
885+
886+
@command('git.reopenClosedRepositories', { repository: false })
887+
async reopenClosedRepositories(): Promise<void> {
888+
if (this.model.closedRepositories.length === 0) {
889+
return;
890+
}
891+
892+
const closedRepositories: string[] = [];
893+
894+
const title = l10n.t('Reopen Closed Repositories');
895+
const placeHolder = l10n.t('Pick a repository to reopen');
896+
897+
const allRepositoriesLabel = l10n.t('All Repositories');
898+
const allRepositoriesQuickPickItem: QuickPickItem = { label: allRepositoriesLabel };
899+
const repositoriesQuickPickItems: QuickPickItem[] = this.model.closedRepositories.sort().map(r => new RepositoryItem(r));
900+
901+
const items = this.model.closedRepositories.length === 1 ? [...repositoriesQuickPickItems] :
902+
[...repositoriesQuickPickItems, { label: '', kind: QuickPickItemKind.Separator }, allRepositoriesQuickPickItem];
903+
904+
const repositoryItem = await window.showQuickPick(items, { title, placeHolder });
905+
if (!repositoryItem) {
906+
return;
907+
}
908+
909+
if (repositoryItem === allRepositoriesQuickPickItem) {
910+
// All Repositories
911+
closedRepositories.push(...this.model.closedRepositories.values());
912+
} else {
913+
// One Repository
914+
closedRepositories.push((repositoryItem as RepositoryItem).path);
915+
}
916+
917+
for (const repository of closedRepositories) {
918+
await this.model.openRepository(repository, true);
919+
}
884920
}
885921

886922
@command('git.close', { repository: true })

extensions/git/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel,
8686
version: info.version,
8787
env: environment,
8888
});
89-
const model = new Model(git, askpass, context.globalState, logger, telemetryReporter);
89+
const model = new Model(git, askpass, context.globalState, context.workspaceState, logger, telemetryReporter);
9090
disposables.push(model);
9191

9292
const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`);

extensions/git/src/model.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ApiRepository } from './api/api1';
1919
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
2020
import { IPostCommitCommandsProviderRegistry } from './postCommitCommands';
2121
import { IBranchProtectionProviderRegistry } from './branchProtection';
22+
import { ObservableSet } from './observable';
2223

2324
class RepositoryPick implements QuickPickItem {
2425
@memoize get label(): string {
@@ -169,6 +170,11 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
169170
return this._parentRepositories;
170171
}
171172

173+
private _closedRepositories: ObservableSet<string>;
174+
get closedRepositories(): string[] {
175+
return [...this._closedRepositories.values()];
176+
}
177+
172178
/**
173179
* We maintain a map containing both the path and the canonical path of the
174180
* workspace folders. We are doing this as `git.exe` expands the symbolic links
@@ -181,7 +187,11 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
181187

182188
private disposables: Disposable[] = [];
183189

184-
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {
190+
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {
191+
this._closedRepositories = new ObservableSet<string>(workspaceState.get<string[]>('closedRepositories', []));
192+
this._closedRepositories.onDidChange(this.onDidChangeClosedRepositories, this, this.disposables);
193+
this.onDidChangeClosedRepositories();
194+
185195
workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);
186196
window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);
187197
workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);
@@ -369,6 +379,11 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
369379
openRepositoriesToDispose.forEach(r => r.dispose());
370380
}
371381

382+
private onDidChangeClosedRepositories(): void {
383+
this.workspaceState.update('closedRepositories', [...this._closedRepositories.values()]);
384+
commands.executeCommand('setContext', 'git.closedRepositoryCount', this._closedRepositories.size);
385+
}
386+
372387
private async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise<void> {
373388
if (!workspace.isTrusted) {
374389
this.logger.trace('[svte] Workspace is not trusted.');
@@ -403,7 +418,7 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
403418
}
404419

405420
@sequentialize
406-
async openRepository(repoPath: string): Promise<void> {
421+
async openRepository(repoPath: string, openIfClosed = false): Promise<void> {
407422
this.logger.trace(`Opening repository: ${repoPath}`);
408423
if (this.getRepositoryExact(repoPath)) {
409424
this.logger.trace(`Repository for path ${repoPath} already exists`);
@@ -480,12 +495,22 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
480495
return;
481496
}
482497

498+
// Handle repositories that were closed by the user
499+
if (!openIfClosed && this._closedRepositories.has(repositoryRoot)) {
500+
this.logger.trace(`Repository for path ${repositoryRoot} is closed`);
501+
return;
502+
}
503+
483504
// Open repository
484505
const dotGit = await this.git.getRepositoryDotGit(repositoryRoot);
485506
const repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter);
486507

487508
this.open(repository);
488-
repository.status(); // do not await this, we want SCM to know about the repo asap
509+
this._closedRepositories.delete(repository.root);
510+
511+
// Do not await this, we want SCM
512+
// to know about the repo asap
513+
repository.status();
489514
} catch (err) {
490515
// noop
491516
this.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`);
@@ -633,6 +658,8 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
633658
}
634659

635660
this.logger.info(`Close repository: ${repository.root}`);
661+
this._closedRepositories.add(openRepository.repository.root.toString());
662+
636663
openRepository.dispose();
637664
}
638665

extensions/git/src/observable.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 { EventEmitter } from 'vscode';
7+
8+
export class ObservableSet<T> implements Set<T> {
9+
10+
readonly [Symbol.toStringTag]: string = 'ObservableSet';
11+
12+
private _set: Set<T>;
13+
private _onDidChange = new EventEmitter<void>();
14+
readonly onDidChange = this._onDidChange.event;
15+
16+
constructor(values?: readonly T[] | null) {
17+
this._set = new Set(values);
18+
}
19+
20+
get size(): number {
21+
return this._set.size;
22+
}
23+
24+
add(value: T): this {
25+
this._set.add(value);
26+
this._onDidChange.fire();
27+
28+
return this;
29+
}
30+
31+
clear(): void {
32+
if (this._set.size > 0) {
33+
this._set.clear();
34+
this._onDidChange.fire();
35+
}
36+
}
37+
38+
delete(value: T): boolean {
39+
const result = this._set.delete(value);
40+
if (result) {
41+
this._onDidChange.fire();
42+
}
43+
44+
return result;
45+
}
46+
47+
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void {
48+
this._set.forEach((_value, key) => callbackfn.call(thisArg, key, key, this));
49+
}
50+
51+
has(value: T): boolean {
52+
return this._set.has(value);
53+
}
54+
55+
entries(): IterableIterator<[T, T]> {
56+
return this._set.entries();
57+
}
58+
59+
keys(): IterableIterator<T> {
60+
return this._set.keys();
61+
}
62+
63+
values(): IterableIterator<T> {
64+
return this._set.keys();
65+
}
66+
67+
[Symbol.iterator](): IterableIterator<T> {
68+
return this.keys();
69+
}
70+
}

extensions/github/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,12 @@
160160
{
161161
"view": "scm",
162162
"contents": "%welcome.publishFolder%",
163-
"when": "config.git.enabled && git.state == initialized && workbenchState == folder && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0"
163+
"when": "config.git.enabled && git.state == initialized && workbenchState == folder && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0"
164164
},
165165
{
166166
"view": "scm",
167167
"contents": "%welcome.publishWorkspaceFolder%",
168-
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0"
168+
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0"
169169
}
170170
],
171171
"markdown.previewStyles": [

0 commit comments

Comments
 (0)