mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
Git - close repository improvements (#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
This commit is contained in:
parent
e913a2e547
commit
9979f9cc3c
|
@ -84,6 +84,12 @@
|
|||
"category": "Git",
|
||||
"enablement": "!operationInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.reopenClosedRepositories",
|
||||
"title": "%command.reopenClosedRepositories%",
|
||||
"category": "Git",
|
||||
"enablement": "!operationInProgress && git.ClosedRepositoryCount != 0"
|
||||
},
|
||||
{
|
||||
"command": "git.close",
|
||||
"title": "%command.close%",
|
||||
|
@ -2860,14 +2866,14 @@
|
|||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.empty%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == empty && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == empty && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.emptyWorkspace%",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
},
|
||||
|
@ -2884,13 +2890,13 @@
|
|||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.folder%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == folder && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == folder && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.workspace%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && remoteName != 'codespaces'",
|
||||
"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'",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
|
@ -2913,6 +2919,16 @@
|
|||
"contents": "%view.workbench.scm.unsafeRepositories%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && git.unsafeRepositoryCount > 1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.closedRepository%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && git.closedRepositoryCount == 1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.closedRepositories%",
|
||||
"when": "config.git.enabled && !git.missing && git.state == initialized && git.closedRepositoryCount > 1"
|
||||
},
|
||||
{
|
||||
"view": "explorer",
|
||||
"contents": "%view.workbench.cloneRepository%",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"command.cloneRecursive": "Clone (Recursive)",
|
||||
"command.init": "Initialize Repository",
|
||||
"command.openRepository": "Open Repository",
|
||||
"command.reopenClosedRepositories": "Reopen Closed Repositories...",
|
||||
"command.close": "Close Repository",
|
||||
"command.refresh": "Refresh",
|
||||
"command.openChange": "Open Changes",
|
||||
|
@ -378,6 +379,22 @@
|
|||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.scm.closedRepository": {
|
||||
"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).",
|
||||
"comment": [
|
||||
"{Locked='](command:git.reopenClosedRepositories'}",
|
||||
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.scm.closedRepositories": {
|
||||
"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).",
|
||||
"comment": [
|
||||
"{Locked='](command:git.reopenClosedRepositories'}",
|
||||
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.cloneRepository": {
|
||||
"message": "You can clone a repository locally.\n[Clone Repository](command:git.clone 'Clone a repository once the git extension has activated')",
|
||||
"comment": [
|
||||
|
|
|
@ -880,7 +880,43 @@ export class CommandCenter {
|
|||
path = result[0].fsPath;
|
||||
}
|
||||
|
||||
await this.model.openRepository(path);
|
||||
await this.model.openRepository(path, true);
|
||||
}
|
||||
|
||||
@command('git.reopenClosedRepositories', { repository: false })
|
||||
async reopenClosedRepositories(): Promise<void> {
|
||||
if (this.model.closedRepositories.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const closedRepositories: string[] = [];
|
||||
|
||||
const title = l10n.t('Reopen Closed Repositories');
|
||||
const placeHolder = l10n.t('Pick a repository to reopen');
|
||||
|
||||
const allRepositoriesLabel = l10n.t('All Repositories');
|
||||
const allRepositoriesQuickPickItem: QuickPickItem = { label: allRepositoriesLabel };
|
||||
const repositoriesQuickPickItems: QuickPickItem[] = this.model.closedRepositories.sort().map(r => new RepositoryItem(r));
|
||||
|
||||
const items = this.model.closedRepositories.length === 1 ? [...repositoriesQuickPickItems] :
|
||||
[...repositoriesQuickPickItems, { label: '', kind: QuickPickItemKind.Separator }, allRepositoriesQuickPickItem];
|
||||
|
||||
const repositoryItem = await window.showQuickPick(items, { title, placeHolder });
|
||||
if (!repositoryItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (repositoryItem === allRepositoriesQuickPickItem) {
|
||||
// All Repositories
|
||||
closedRepositories.push(...this.model.closedRepositories.values());
|
||||
} else {
|
||||
// One Repository
|
||||
closedRepositories.push((repositoryItem as RepositoryItem).path);
|
||||
}
|
||||
|
||||
for (const repository of closedRepositories) {
|
||||
await this.model.openRepository(repository, true);
|
||||
}
|
||||
}
|
||||
|
||||
@command('git.close', { repository: true })
|
||||
|
|
|
@ -86,7 +86,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel,
|
|||
version: info.version,
|
||||
env: environment,
|
||||
});
|
||||
const model = new Model(git, askpass, context.globalState, logger, telemetryReporter);
|
||||
const model = new Model(git, askpass, context.globalState, context.workspaceState, logger, telemetryReporter);
|
||||
disposables.push(model);
|
||||
|
||||
const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`);
|
||||
|
|
|
@ -19,6 +19,7 @@ import { ApiRepository } from './api/api1';
|
|||
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
|
||||
import { IPostCommitCommandsProviderRegistry } from './postCommitCommands';
|
||||
import { IBranchProtectionProviderRegistry } from './branchProtection';
|
||||
import { ObservableSet } from './observable';
|
||||
|
||||
class RepositoryPick implements QuickPickItem {
|
||||
@memoize get label(): string {
|
||||
|
@ -169,6 +170,11 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
|
|||
return this._parentRepositories;
|
||||
}
|
||||
|
||||
private _closedRepositories: ObservableSet<string>;
|
||||
get closedRepositories(): string[] {
|
||||
return [...this._closedRepositories.values()];
|
||||
}
|
||||
|
||||
/**
|
||||
* We maintain a map containing both the path and the canonical path of the
|
||||
* workspace folders. We are doing this as `git.exe` expands the symbolic links
|
||||
|
@ -181,7 +187,11 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
|
|||
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {
|
||||
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) {
|
||||
this._closedRepositories = new ObservableSet<string>(workspaceState.get<string[]>('closedRepositories', []));
|
||||
this._closedRepositories.onDidChange(this.onDidChangeClosedRepositories, this, this.disposables);
|
||||
this.onDidChangeClosedRepositories();
|
||||
|
||||
workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);
|
||||
window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);
|
||||
workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);
|
||||
|
@ -369,6 +379,11 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
|
|||
openRepositoriesToDispose.forEach(r => r.dispose());
|
||||
}
|
||||
|
||||
private onDidChangeClosedRepositories(): void {
|
||||
this.workspaceState.update('closedRepositories', [...this._closedRepositories.values()]);
|
||||
commands.executeCommand('setContext', 'git.closedRepositoryCount', this._closedRepositories.size);
|
||||
}
|
||||
|
||||
private async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise<void> {
|
||||
if (!workspace.isTrusted) {
|
||||
this.logger.trace('[svte] Workspace is not trusted.');
|
||||
|
@ -403,7 +418,7 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
|
|||
}
|
||||
|
||||
@sequentialize
|
||||
async openRepository(repoPath: string): Promise<void> {
|
||||
async openRepository(repoPath: string, openIfClosed = false): Promise<void> {
|
||||
this.logger.trace(`Opening repository: ${repoPath}`);
|
||||
if (this.getRepositoryExact(repoPath)) {
|
||||
this.logger.trace(`Repository for path ${repoPath} already exists`);
|
||||
|
@ -480,12 +495,22 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle repositories that were closed by the user
|
||||
if (!openIfClosed && this._closedRepositories.has(repositoryRoot)) {
|
||||
this.logger.trace(`Repository for path ${repositoryRoot} is closed`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open repository
|
||||
const dotGit = await this.git.getRepositoryDotGit(repositoryRoot);
|
||||
const repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter);
|
||||
|
||||
this.open(repository);
|
||||
repository.status(); // do not await this, we want SCM to know about the repo asap
|
||||
this._closedRepositories.delete(repository.root);
|
||||
|
||||
// Do not await this, we want SCM
|
||||
// to know about the repo asap
|
||||
repository.status();
|
||||
} catch (err) {
|
||||
// noop
|
||||
this.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`);
|
||||
|
@ -633,6 +658,8 @@ export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePu
|
|||
}
|
||||
|
||||
this.logger.info(`Close repository: ${repository.root}`);
|
||||
this._closedRepositories.add(openRepository.repository.root.toString());
|
||||
|
||||
openRepository.dispose();
|
||||
}
|
||||
|
||||
|
|
70
extensions/git/src/observable.ts
Normal file
70
extensions/git/src/observable.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EventEmitter } from 'vscode';
|
||||
|
||||
export class ObservableSet<T> implements Set<T> {
|
||||
|
||||
readonly [Symbol.toStringTag]: string = 'ObservableSet';
|
||||
|
||||
private _set: Set<T>;
|
||||
private _onDidChange = new EventEmitter<void>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
constructor(values?: readonly T[] | null) {
|
||||
this._set = new Set(values);
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._set.size;
|
||||
}
|
||||
|
||||
add(value: T): this {
|
||||
this._set.add(value);
|
||||
this._onDidChange.fire();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
if (this._set.size > 0) {
|
||||
this._set.clear();
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
delete(value: T): boolean {
|
||||
const result = this._set.delete(value);
|
||||
if (result) {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void {
|
||||
this._set.forEach((_value, key) => callbackfn.call(thisArg, key, key, this));
|
||||
}
|
||||
|
||||
has(value: T): boolean {
|
||||
return this._set.has(value);
|
||||
}
|
||||
|
||||
entries(): IterableIterator<[T, T]> {
|
||||
return this._set.entries();
|
||||
}
|
||||
|
||||
keys(): IterableIterator<T> {
|
||||
return this._set.keys();
|
||||
}
|
||||
|
||||
values(): IterableIterator<T> {
|
||||
return this._set.keys();
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<T> {
|
||||
return this.keys();
|
||||
}
|
||||
}
|
|
@ -160,12 +160,12 @@
|
|||
{
|
||||
"view": "scm",
|
||||
"contents": "%welcome.publishFolder%",
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == folder && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0"
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == folder && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%welcome.publishWorkspaceFolder%",
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0"
|
||||
"when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && git.closedRepositoryCount == 0"
|
||||
}
|
||||
],
|
||||
"markdown.previewStyles": [
|
||||
|
|
Loading…
Reference in a new issue