Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLadislau Szomoru <3372902+lszomoru@users.noreply.github.com>2022-11-04 14:52:28 +0300
committerGitHub <noreply@github.com>2022-11-04 14:52:28 +0300
commitb208b8794d9aa056fa0ba63271cc8f6b7e909efa (patch)
treef5b82873ede51747ba8509570ec7e479d561396a /extensions
parent2423a7a71483c4b560880665d349e56ed13bd019 (diff)
Git - Optimistic UI plumbing (#165237)
Diffstat (limited to 'extensions')
-rw-r--r--extensions/git/package.json9
-rw-r--r--extensions/git/package.nls.json1
-rw-r--r--extensions/git/src/commands.ts30
-rw-r--r--extensions/git/src/repository.ts187
4 files changed, 137 insertions, 90 deletions
diff --git a/extensions/git/package.json b/extensions/git/package.json
index a4ec3efa604..ae77863d17b 100644
--- a/extensions/git/package.json
+++ b/extensions/git/package.json
@@ -2543,6 +2543,15 @@
"default": false,
"markdownDescription": "%config.mergeEditor%",
"scope": "window"
+ },
+ "git.optimisticUpdate": {
+ "type": "boolean",
+ "default": true,
+ "markdownDescription": "%config.optimisticUpdate%",
+ "scope": "resource",
+ "tags": [
+ "experimental"
+ ]
}
}
},
diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json
index 1cbb8966207..eee625d02d4 100644
--- a/extensions/git/package.nls.json
+++ b/extensions/git/package.nls.json
@@ -236,6 +236,7 @@
"config.repositoryScanMaxDepth": "Controls the depth used when scanning workspace folders for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`. Can be set to `-1` for no limit.",
"config.useIntegratedAskPass": "Controls whether GIT_ASKPASS should be overwritten to use the integrated version.",
"config.mergeEditor": "Open the merge editor for files that are currently under conflict.",
+ "config.optimisticUpdate": "Controls whether to optimistically update the state of the Source Control view after running git commands.",
"submenu.explorer": "Git",
"submenu.commit": "Commit",
"submenu.commit.amend": "Amend",
diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts
index 1ff16ab7c39..4a2acd2f2dd 100644
--- a/extensions/git/src/commands.ts
+++ b/extensions/git/src/commands.ts
@@ -1571,7 +1571,7 @@ export class CommandCenter {
repository: Repository,
getCommitMessage: () => Promise<string | undefined>,
opts: CommitOptions
- ): Promise<boolean> {
+ ): Promise<void> {
const config = workspace.getConfiguration('git', Uri.file(repository.root));
let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit');
@@ -1611,7 +1611,7 @@ export class CommandCenter {
noStagedChanges = repository.indexGroup.resourceStates.length === 0;
noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
} else if (pick !== commit) {
- return false; // do not commit on cancel
+ return; // do not commit on cancel
}
}
}
@@ -1621,7 +1621,7 @@ export class CommandCenter {
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
if (!suggestSmartCommit) {
- return false;
+ return;
}
// prompt the user if we want to commit all or not
@@ -1635,9 +1635,9 @@ export class CommandCenter {
config.update('enableSmartCommit', true, true);
} else if (pick === never) {
config.update('suggestSmartCommit', false, true);
- return false;
+ return;
} else if (pick !== yes) {
- return false; // do not commit on cancel
+ return; // do not commit on cancel
}
}
@@ -1683,7 +1683,7 @@ export class CommandCenter {
const answer = await window.showInformationMessage(l10n.t('There are no changes to commit.'), commitAnyway);
if (answer !== commitAnyway) {
- return false;
+ return;
}
opts.empty = true;
@@ -1692,7 +1692,7 @@ export class CommandCenter {
if (opts.noVerify) {
if (!config.get<boolean>('allowNoVerifyCommit')) {
await window.showErrorMessage(l10n.t('Commits without verification are not allowed, please enable them with the "git.allowNoVerifyCommit" setting.'));
- return false;
+ return;
}
if (config.get<boolean>('confirmNoVerifyCommit')) {
@@ -1704,7 +1704,7 @@ export class CommandCenter {
if (pick === neverAgain) {
config.update('confirmNoVerifyCommit', false, true);
} else if (pick !== yes) {
- return false;
+ return;
}
}
}
@@ -1712,7 +1712,7 @@ export class CommandCenter {
const message = await getCommitMessage();
if (!message && !opts.amend && !opts.useEditor) {
- return false;
+ return;
}
if (opts.all && smartCommitChanges === 'tracked') {
@@ -1738,12 +1738,12 @@ export class CommandCenter {
}
if (!pick) {
- return false;
+ return;
} else if (pick === commitToNewBranch) {
const branchName = await this.promptForBranchName(repository);
if (!branchName) {
- return false;
+ return;
}
await repository.branch(branchName, true);
@@ -1751,8 +1751,6 @@ export class CommandCenter {
}
await repository.commit(message, opts);
-
- return true;
}
private async commitWithAnyInput(repository: Repository, opts: CommitOptions): Promise<void> {
@@ -1790,11 +1788,7 @@ export class CommandCenter {
return _message;
};
- const didCommit = await this.smartCommit(repository, getCommitMessage, opts);
-
- if (message && didCommit) {
- repository.inputBox.value = await repository.getInputTemplate();
- }
+ await this.smartCommit(repository, getCommitMessage, opts);
}
@command('git.commit', { repository: true })
diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts
index b937fcdccaa..f86b0549f9a 100644
--- a/extensions/git/src/repository.ts
+++ b/extensions/git/src/repository.ts
@@ -447,6 +447,13 @@ export interface GitResourceGroup extends SourceControlResourceGroup {
resourceStates: Resource[];
}
+interface GitResourceGroups {
+ indexGroup?: Resource[];
+ mergeGroup?: Resource[];
+ untrackedGroup?: Resource[];
+ workingTreeGroup?: Resource[];
+}
+
export interface OperationResult {
operation: Operation;
error: any;
@@ -974,7 +981,7 @@ export class Repository implements Disposable {
|| e.affectsConfiguration('git.ignoreSubmodules', root)
|| e.affectsConfiguration('git.openDiffOnClick', root)
|| e.affectsConfiguration('git.showActionButton', root)
- )(this.updateModelState, this, this.disposables);
+ )(() => this.updateModelState(), this, this.disposables);
const updateInputBoxVisibility = () => {
const config = workspace.getConfiguration('git', root);
@@ -1247,40 +1254,53 @@ export class Repository implements Disposable {
}
async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise<void> {
- const indexResources = [...this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)];
- const workingGroupResources = opts.all && opts.all !== 'tracked' ?
- [...this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath)] : [];
-
if (this.rebaseCommit) {
- await this.run(Operation.RebaseContinue, async () => {
- if (opts.all) {
- const addOpts = opts.all === 'tracked' ? { update: true } : {};
- await this.repository.add([], addOpts);
- }
+ await this.run(
+ Operation.RebaseContinue,
+ async () => {
+ if (opts.all) {
+ const addOpts = opts.all === 'tracked' ? { update: true } : {};
+ await this.repository.add([], addOpts);
+ }
- await this.repository.rebaseContinue();
- this.closeDiffEditors(indexResources, workingGroupResources);
- });
+ await this.repository.rebaseContinue();
+ await this.commitOperationCleanup(message, opts);
+ });
} else {
// Set post-commit command to render the correct action button
this.commitCommandCenter.postCommitCommand = opts.postCommitCommand;
- await this.run(Operation.Commit, async () => {
- if (opts.all) {
- const addOpts = opts.all === 'tracked' ? { update: true } : {};
- await this.repository.add([], addOpts);
- }
+ await this.run(
+ Operation.Commit,
+ async () => {
+ if (opts.all) {
+ const addOpts = opts.all === 'tracked' ? { update: true } : {};
+ await this.repository.add([], addOpts);
+ }
- delete opts.all;
+ delete opts.all;
- if (opts.requireUserConfig === undefined || opts.requireUserConfig === null) {
- const config = workspace.getConfiguration('git', Uri.file(this.root));
- opts.requireUserConfig = config.get<boolean>('requireGitUserConfig');
- }
+ if (opts.requireUserConfig === undefined || opts.requireUserConfig === null) {
+ const config = workspace.getConfiguration('git', Uri.file(this.root));
+ opts.requireUserConfig = config.get<boolean>('requireGitUserConfig');
+ }
- await this.repository.commit(message, opts);
- this.closeDiffEditors(indexResources, workingGroupResources);
- });
+ await this.repository.commit(message, opts);
+ await this.commitOperationCleanup(message, opts);
+ },
+ (): GitResourceGroups => {
+ let untrackedGroup: Resource[] | undefined = undefined,
+ workingTreeGroup: Resource[] | undefined = undefined;
+
+ if (opts.all === 'tracked') {
+ workingTreeGroup = this.workingTreeGroup.resourceStates
+ .filter(r => r.type === Status.UNTRACKED);
+ } else if (opts.all) {
+ untrackedGroup = workingTreeGroup = [];
+ }
+
+ return { indexGroup: [], untrackedGroup, workingTreeGroup };
+ });
// Execute post-commit command
await this.run(Operation.PostCommitCommand, async () => {
@@ -1289,6 +1309,18 @@ export class Repository implements Disposable {
}
}
+ private async commitOperationCleanup(message: string | undefined, opts: CommitOptions) {
+ if (message) {
+ this.inputBox.value = await this.getInputTemplate();
+ }
+
+ const indexResources = [...this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)];
+ const workingGroupResources = opts.all && opts.all !== 'tracked' ?
+ [...this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath)] : [];
+
+ this.closeDiffEditors(indexResources, workingGroupResources);
+ }
+
async clean(resources: Uri[]): Promise<void> {
await this.run(Operation.Clean, async () => {
const toClean: string[] = [];
@@ -1869,7 +1901,10 @@ export class Repository implements Disposable {
}
}
- private async run<T>(operation: Operation, runOperation: () => Promise<T> = () => Promise.resolve<any>(null)): Promise<T> {
+ private async run<T>(
+ operation: Operation,
+ runOperation: () => Promise<T> = () => Promise.resolve<any>(null),
+ getOptimisticResourceGroups: () => GitResourceGroups | undefined = () => undefined): Promise<T> {
if (this.state !== RepositoryState.Idle) {
throw new Error('Repository not initialized');
}
@@ -1883,7 +1918,10 @@ export class Repository implements Disposable {
const result = await this.retryRun(operation, runOperation);
if (!isReadOnly(operation)) {
- await this.updateModelState();
+ const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
+ const optimisticUpdate = scopedConfig.get<boolean>('optimisticUpdate') === true;
+
+ await this.updateModelState(optimisticUpdate ? getOptimisticResourceGroups() : undefined);
}
return result;
@@ -1942,18 +1980,14 @@ export class Repository implements Disposable {
return folderPaths.filter(p => !ignored.has(p));
}
- private async updateModelState() {
+ private async updateModelState(optimisticResourcesGroups?: GitResourceGroups) {
this.updateModelStateCancellationTokenSource?.cancel();
this.updateModelStateCancellationTokenSource = new CancellationTokenSource();
- await this._updateModelState(this.updateModelStateCancellationTokenSource.token);
+ await this._updateModelState(optimisticResourcesGroups, this.updateModelStateCancellationTokenSource.token);
}
- private async _updateModelState(cancellationToken?: CancellationToken): Promise<void> {
- if (cancellationToken && cancellationToken.isCancellationRequested) {
- return;
- }
-
+ private async _updateModelState(optimisticResourcesGroups?: GitResourceGroups, cancellationToken?: CancellationToken): Promise<void> {
try {
const config = workspace.getConfiguration('git');
let sort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder') || 'alphabetically';
@@ -1961,13 +1995,12 @@ export class Repository implements Disposable {
sort = 'alphabetically';
}
- const [HEAD, refs, remotes, submodules, status, rebaseCommit, mergeInProgress, commitTemplate] =
+ const [HEAD, refs, remotes, submodules, rebaseCommit, mergeInProgress, commitTemplate] =
await Promise.all([
this.repository.getHEADBranch(),
this.repository.getRefs({ sort }),
this.repository.getRemotes(),
this.repository.getSubmodules(),
- this.getStatus(cancellationToken),
this.getRebaseCommit(),
this.isMergeInProgress(),
this.getInputTemplate()]);
@@ -1979,18 +2012,15 @@ export class Repository implements Disposable {
this.rebaseCommit = rebaseCommit;
this.mergeInProgress = mergeInProgress;
- // set resource groups
- this.mergeGroup.resourceStates = status.merge;
- this.indexGroup.resourceStates = status.index;
- this.workingTreeGroup.resourceStates = status.workingTree;
- this.untrackedGroup.resourceStates = status.untracked;
-
- // set count badge
- this.setCountBadge();
+ this._sourceControl.commitTemplate = commitTemplate;
- this._onDidChangeStatus.fire();
+ // Optimistically update the resource states
+ if (optimisticResourcesGroups) {
+ this._updateResourceGroupsState(optimisticResourcesGroups);
+ }
- this._sourceControl.commitTemplate = commitTemplate;
+ // Update resource states based on status information
+ this._updateResourceGroupsState(await this.getStatus(cancellationToken));
}
catch (err) {
if (err instanceof CancellationError) {
@@ -2001,7 +2031,20 @@ export class Repository implements Disposable {
}
}
- private async getStatus(cancellationToken?: CancellationToken): Promise<{ index: Resource[]; workingTree: Resource[]; merge: Resource[]; untracked: Resource[] }> {
+ private _updateResourceGroupsState(resourcesGroups: GitResourceGroups): void {
+ // set resource groups
+ if (resourcesGroups.indexGroup) { this.indexGroup.resourceStates = resourcesGroups.indexGroup; }
+ if (resourcesGroups.mergeGroup) { this.mergeGroup.resourceStates = resourcesGroups.mergeGroup; }
+ if (resourcesGroups.untrackedGroup) { this.untrackedGroup.resourceStates = resourcesGroups.untrackedGroup; }
+ if (resourcesGroups.workingTreeGroup) { this.workingTreeGroup.resourceStates = resourcesGroups.workingTreeGroup; }
+
+ // set count badge
+ this.setCountBadge();
+
+ this._onDidChangeStatus.fire();
+ }
+
+ private async getStatus(cancellationToken?: CancellationToken): Promise<GitResourceGroups> {
if (cancellationToken && cancellationToken.isCancellationRequested) {
throw new CancellationError();
}
@@ -2088,10 +2131,10 @@ export class Repository implements Disposable {
}
}
- const index: Resource[] = [],
- workingTree: Resource[] = [],
- merge: Resource[] = [],
- untracked: Resource[] = [];
+ const indexGroup: Resource[] = [],
+ mergeGroup: Resource[] = [],
+ untrackedGroup: Resource[] = [],
+ workingTreeGroup: Resource[] = [];
status.forEach(raw => {
const uri = Uri.file(path.join(this.repository.root, raw.path));
@@ -2101,42 +2144,42 @@ export class Repository implements Disposable {
switch (raw.x + raw.y) {
case '??': switch (untrackedChanges) {
- case 'mixed': return workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons));
- case 'separate': return untracked.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons));
+ case 'mixed': return workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons));
+ case 'separate': return untrackedGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons));
default: return undefined;
}
case '!!': switch (untrackedChanges) {
- case 'mixed': return workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons));
- case 'separate': return untracked.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons));
+ case 'mixed': return workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons));
+ case 'separate': return untrackedGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons));
default: return undefined;
}
- case 'DD': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons));
- case 'AU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons));
- case 'UD': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons));
- case 'UA': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons));
- case 'DU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons));
- case 'AA': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons));
- case 'UU': return merge.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons));
+ case 'DD': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons));
+ case 'AU': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons));
+ case 'UD': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons));
+ case 'UA': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons));
+ case 'DU': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons));
+ case 'AA': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons));
+ case 'UU': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons));
}
switch (raw.x) {
- case 'M': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break;
- case 'A': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break;
- case 'D': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break;
- case 'R': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break;
- case 'C': index.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break;
+ case 'M': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break;
+ case 'A': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break;
+ case 'D': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break;
+ case 'R': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break;
+ case 'C': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break;
}
switch (raw.y) {
- case 'M': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break;
- case 'D': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break;
- case 'A': workingTree.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break;
+ case 'M': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break;
+ case 'D': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break;
+ case 'A': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break;
}
return undefined;
});
- return { index, workingTree, merge, untracked };
+ return { indexGroup, mergeGroup, untrackedGroup, workingTreeGroup };
}
private setCountBadge(): void {