SCM - Initial implementation of the Sync view (#193440)

This commit is contained in:
Ladislau Szomoru
2023-09-19 15:58:22 +02:00
committed by GitHub
parent e20eb064c6
commit 1545aeab06
20 changed files with 1359 additions and 128 deletions
+1
View File
@@ -18,6 +18,7 @@
"editSessionIdentityProvider",
"quickDiffProvider",
"scmActionButton",
"scmHistoryProvider",
"scmSelectedProvider",
"scmValidation",
"tabInputTextMerge",
+163 -109
View File
@@ -20,24 +20,24 @@ interface ActionButtonState {
readonly repositoryHasChangesToCommit: boolean;
}
export class ActionButtonCommand {
private _onDidChange = new EventEmitter<void>();
abstract class AbstractActionButton {
protected _onDidChange = new EventEmitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
private _state: ActionButtonState;
private get state() { return this._state; }
private set state(state: ActionButtonState) {
protected get state() { return this._state; }
protected set state(state: ActionButtonState) {
if (JSON.stringify(this._state) !== JSON.stringify(state)) {
this._state = state;
this._onDidChange.fire();
}
}
private disposables: Disposable[] = [];
abstract get button(): SourceControlActionButton | undefined;
constructor(
readonly repository: Repository,
readonly postCommitCommandCenter: CommitCommandsCenter) {
protected disposables: Disposable[] = [];
constructor(readonly repository: Repository) {
this._state = {
HEAD: undefined,
isCheckoutInProgress: false,
@@ -50,6 +50,126 @@ export class ActionButtonCommand {
repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables);
repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables);
}
protected getPublishBranchActionButton(): SourceControlActionButton | undefined {
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(cloud-upload)';
return {
command: {
command: 'git.publish',
title: l10n.t({ message: '{0} Publish Branch', args: [icon], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }),
tooltip: this.state.isSyncInProgress ?
(this.state.HEAD?.name ?
l10n.t({ message: 'Publishing Branch "{0}"...', args: [this.state.HEAD.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) :
l10n.t({ message: 'Publishing Branch...', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })) :
(this.repository.HEAD?.name ?
l10n.t({ message: 'Publish Branch "{0}"', args: [this.state.HEAD?.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) :
l10n.t({ message: 'Publish Branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })),
arguments: [this.repository.sourceControl],
},
enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress && !this.state.isCommitInProgress && !this.state.isMergeInProgress && !this.state.isRebaseInProgress
};
}
protected getSyncChangesActionButton(): SourceControlActionButton | undefined {
const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0;
const ahead = this.state.HEAD?.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : '';
const behind = this.state.HEAD?.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : '';
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)';
return {
command: {
command: 'git.sync',
title: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead),
tooltip: this.state.isSyncInProgress ?
l10n.t('Synchronizing Changes...')
: this.repository.syncTooltip,
arguments: [this.repository.sourceControl],
},
description: `${icon}${behind}${ahead}`,
enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress && !this.state.isCommitInProgress && !this.state.isMergeInProgress && !this.state.isRebaseInProgress && branchIsAheadOrBehind
};
}
private onDidChangeOperations(): void {
const isCheckoutInProgress
= this.repository.operations.isRunning(OperationKind.Checkout) ||
this.repository.operations.isRunning(OperationKind.CheckoutTracking);
const isCommitInProgress =
this.repository.operations.isRunning(OperationKind.Commit) ||
this.repository.operations.isRunning(OperationKind.PostCommitCommand) ||
this.repository.operations.isRunning(OperationKind.RebaseContinue);
const isSyncInProgress =
this.repository.operations.isRunning(OperationKind.Sync) ||
this.repository.operations.isRunning(OperationKind.Push) ||
this.repository.operations.isRunning(OperationKind.Pull);
this.state = { ...this.state, isCheckoutInProgress, isCommitInProgress, isSyncInProgress };
}
private onDidRunGitStatus(): void {
this.state = {
...this.state,
HEAD: this.repository.HEAD,
isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0,
isRebaseInProgress: !!this.repository.rebaseCommit,
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
};
}
protected repositoryHasChangesToCommit(): boolean {
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all');
const resources = [...this.repository.indexGroup.resourceStates];
if (
// Smart commit enabled (all)
(enableSmartCommit && smartCommitChanges === 'all') ||
// Smart commit disabled, smart suggestion enabled
(!enableSmartCommit && suggestSmartCommit)
) {
resources.push(...this.repository.workingTreeGroup.resourceStates);
}
// Smart commit enabled (tracked only)
if (enableSmartCommit && smartCommitChanges === 'tracked') {
resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED));
}
return resources.length !== 0;
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
export class CommitActionButton extends AbstractActionButton {
override get button(): SourceControlActionButton | undefined {
if (!this.state.HEAD) { return undefined; }
let actionButton: SourceControlActionButton | undefined;
if (this.state.repositoryHasChangesToCommit) {
// Commit Changes (enabled)
actionButton = this.getCommitActionButton();
}
// Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled)
return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton();
}
constructor(
repository: Repository,
readonly postCommitCommandCenter: CommitCommandsCenter) {
super(repository);
this.disposables.push(repository.onDidChangeBranchProtection(() => this._onDidChange.fire()));
this.disposables.push(postCommitCommandCenter.onDidChange(() => this._onDidChange.fire()));
@@ -62,7 +182,8 @@ export class ActionButtonCommand {
this.onDidChangeSmartCommitSettings();
}
if (e.affectsConfiguration('git.branchProtectionPrompt', root) ||
if (e.affectsConfiguration('scm.experimental.showSyncView') ||
e.affectsConfiguration('git.branchProtectionPrompt', root) ||
e.affectsConfiguration('git.postCommitCommand', root) ||
e.affectsConfiguration('git.rememberPostCommitCommand', root) ||
e.affectsConfiguration('git.showActionButton', root)) {
@@ -71,20 +192,6 @@ export class ActionButtonCommand {
}));
}
get button(): SourceControlActionButton | undefined {
if (!this.state.HEAD) { return undefined; }
let actionButton: SourceControlActionButton | undefined;
if (this.state.repositoryHasChangesToCommit) {
// Commit Changes (enabled)
actionButton = this.getCommitActionButton();
}
// Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled)
return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton();
}
private getCommitActionButton(): SourceControlActionButton | undefined {
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const showActionButton = config.get<{ commit: boolean }>('showActionButton', { commit: true });
@@ -133,34 +240,27 @@ export class ActionButtonCommand {
return commandGroups;
}
private getPublishBranchActionButton(): SourceControlActionButton | undefined {
protected override getPublishBranchActionButton(): SourceControlActionButton | undefined {
const scmConfig = workspace.getConfiguration('scm');
if (scmConfig.get<boolean>('experimental.showSyncView', false)) {
return undefined;
}
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true });
// Not a branch (tag, detached), branch does have an upstream, commit/merge/rebase is in progress, or the button is disabled
if (this.state.HEAD?.type === RefType.Tag || !this.state.HEAD?.name || this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.publish) { return undefined; }
// Button icon
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(cloud-upload)';
return {
command: {
command: 'git.publish',
title: l10n.t({ message: '{0} Publish Branch', args: [icon], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }),
tooltip: this.state.isSyncInProgress ?
(this.state.HEAD?.name ?
l10n.t({ message: 'Publishing Branch "{0}"...', args: [this.state.HEAD.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) :
l10n.t({ message: 'Publishing Branch...', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })) :
(this.repository.HEAD?.name ?
l10n.t({ message: 'Publish Branch "{0}"', args: [this.state.HEAD?.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) :
l10n.t({ message: 'Publish Branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })),
arguments: [this.repository.sourceControl],
},
enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress
};
return super.getPublishBranchActionButton();
}
private getSyncChangesActionButton(): SourceControlActionButton | undefined {
protected override getSyncChangesActionButton(): SourceControlActionButton | undefined {
const scmConfig = workspace.getConfiguration('scm');
if (scmConfig.get<boolean>('experimental.showSyncView', false)) {
return undefined;
}
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true });
const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0;
@@ -168,40 +268,7 @@ export class ActionButtonCommand {
// Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge/rebase is in progress, or the button is disabled
if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.sync) { return undefined; }
const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : '';
const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : '';
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)';
return {
command: {
command: 'git.sync',
title: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead),
tooltip: this.state.isSyncInProgress ?
l10n.t('Synchronizing Changes...')
: this.repository.syncTooltip,
arguments: [this.repository.sourceControl],
},
description: `${icon}${behind}${ahead}`,
enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress
};
}
private onDidChangeOperations(): void {
const isCheckoutInProgress
= this.repository.operations.isRunning(OperationKind.Checkout) ||
this.repository.operations.isRunning(OperationKind.CheckoutTracking);
const isCommitInProgress =
this.repository.operations.isRunning(OperationKind.Commit) ||
this.repository.operations.isRunning(OperationKind.PostCommitCommand) ||
this.repository.operations.isRunning(OperationKind.RebaseContinue);
const isSyncInProgress =
this.repository.operations.isRunning(OperationKind.Sync) ||
this.repository.operations.isRunning(OperationKind.Push) ||
this.repository.operations.isRunning(OperationKind.Pull);
this.state = { ...this.state, isCheckoutInProgress, isCommitInProgress, isSyncInProgress };
return super.getSyncChangesActionButton();
}
private onDidChangeSmartCommitSettings(): void {
@@ -210,43 +277,30 @@ export class ActionButtonCommand {
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
};
}
}
private onDidRunGitStatus(): void {
this.state = {
...this.state,
HEAD: this.repository.HEAD,
isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0,
isRebaseInProgress: !!this.repository.rebaseCommit,
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
};
export class SyncActionButton extends AbstractActionButton {
override get button(): SourceControlActionButton | undefined {
if (!this.state.HEAD) { return undefined; }
// Publish Branch -> Sync Changes
return this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton();
}
private repositoryHasChangesToCommit(): boolean {
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all');
constructor(repository: Repository) {
super(repository);
const resources = [...this.repository.indexGroup.resourceStates];
if (
// Smart commit enabled (all)
(enableSmartCommit && smartCommitChanges === 'all') ||
// Smart commit disabled, smart suggestion enabled
(!enableSmartCommit && suggestSmartCommit)
) {
resources.push(...this.repository.workingTreeGroup.resourceStates);
}
// Smart commit enabled (tracked only)
if (enableSmartCommit && smartCommitChanges === 'tracked') {
resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED));
}
return resources.length !== 0;
this.disposables.push(workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('scm.experimental.showSyncView')) {
this._onDidChange.fire();
}
}));
}
dispose(): void {
this.disposables = dispose(this.disposables);
protected override getPublishBranchActionButton(): SourceControlActionButton | undefined {
// Not a branch (tag, detached), branch does have an upstream
if (this.state.HEAD?.type === RefType.Tag || this.state.HEAD?.upstream) { return undefined; }
return super.getPublishBranchActionButton();
}
}
+2
View File
@@ -131,6 +131,8 @@ export interface LogOptions {
readonly path?: string;
/** A commit range, such as "0a47c67f0fb52dd11562af48658bc1dff1d75a38..0bb4bdea78e1db44d728fd6894720071e303304f" */
readonly range?: string;
readonly reverse?: boolean;
readonly sortByAuthorDate?: boolean;
}
export interface CommitOptions {
+40 -6
View File
@@ -1023,17 +1023,24 @@ export class Repository {
}
async log(options?: LogOptions): Promise<Commit[]> {
const maxEntries = options?.maxEntries ?? 32;
const args = ['log', `-n${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z'];
const args = ['log', `--format=${COMMIT_FORMAT}`, '-z'];
if (options?.reverse) {
args.push('--reverse', '--ancestry-path');
}
if (options?.sortByAuthorDate) {
args.push('--author-date-order');
}
if (options?.range) {
args.push(options.range);
} else {
args.push(`-n${options?.maxEntries ?? 32}`);
}
args.push('--');
if (options?.path) {
args.push(options.path);
args.push('--', options.path);
}
const result = await this.exec(args);
@@ -1258,7 +1265,7 @@ export class Repository {
diffIndexWithHEAD(): Promise<Change[]>;
diffIndexWithHEAD(path: string): Promise<string>;
diffIndexWithHEAD(path?: string | undefined): Promise<string | Change[]>;
diffIndexWithHEAD(path?: string | undefined): Promise<Change[]>;
async diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
if (!path) {
return await this.diffFiles(true);
@@ -1303,6 +1310,17 @@ export class Repository {
return result.stdout.trim();
}
async diffBetweenShortStat(ref1: string, ref2: string): Promise<string> {
const args = ['diff', '--shortstat', `${ref1}...${ref2}`];
const result = await this.exec(args);
if (result.exitCode) {
return '';
}
return result.stdout.trim();
}
private async diffFiles(cached: boolean, ref?: string): Promise<Change[]> {
const args = ['diff', '--name-status', '-z', '--diff-filter=ADMR'];
if (cached) {
@@ -2450,6 +2468,15 @@ export class Repository {
return Promise.reject<Branch>(new Error('No such branch'));
}
async getDefaultBranch(): Promise<Branch> {
const result = await this.exec(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD']);
if (!result.stdout) {
throw new Error('No default branch');
}
return this.getBranch(result.stdout.trim());
}
// TODO: Support core.commentChar
stripCommitMessageComments(message: string): string {
return message.replace(/^\s*#.*$\n?/gm, '').trim();
@@ -2510,6 +2537,13 @@ export class Repository {
return commits[0];
}
async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> {
const result = await this.exec(['rev-list', '--count', '--left-right', range]);
const [ahead, behind] = result.stdout.trim().split('\t');
return { ahead: Number(ahead) || 0, behind: Number(behind) || 0 };
}
async updateSubmodules(paths: string[]): Promise<void> {
const args = ['submodule', 'update'];
+132
View File
@@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, Event, EventEmitter, SourceControlActionButton, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryItemGroup, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon } from 'vscode';
import { Repository } from './repository';
import { IDisposable } from './util';
import { toGitUri } from './uri';
import { SyncActionButton } from './actionButton';
export class GitHistoryProvider implements SourceControlHistoryProvider, IDisposable {
private readonly _onDidChangeActionButton = new EventEmitter<void>();
readonly onDidChangeActionButton: Event<void> = this._onDidChangeActionButton.event;
private readonly _onDidChangeCurrentHistoryItemGroup = new EventEmitter<void>();
readonly onDidChangeCurrentHistoryItemGroup: Event<void> = this._onDidChangeCurrentHistoryItemGroup.event;
private _actionButton: SourceControlActionButton | undefined;
get actionButton(): SourceControlActionButton | undefined { return this._actionButton; }
set actionButton(button: SourceControlActionButton | undefined) {
this._actionButton = button;
this._onDidChangeActionButton.fire();
}
private _currentHistoryItemGroup: SourceControlHistoryItemGroup | undefined;
get currentHistoryItemGroup(): SourceControlHistoryItemGroup | undefined { return this._currentHistoryItemGroup; }
set currentHistoryItemGroup(value: SourceControlHistoryItemGroup | undefined) {
this._currentHistoryItemGroup = value;
this._onDidChangeCurrentHistoryItemGroup.fire();
}
private disposables: Disposable[] = [];
constructor(protected readonly repository: Repository) {
const actionButton = new SyncActionButton(repository);
this.actionButton = actionButton.button;
this.disposables.push(actionButton);
this.disposables.push(repository.onDidRunGitStatus(this.onDidRunGitStatus, this));
this.disposables.push(actionButton.onDidChange(() => this.actionButton = actionButton.button));
}
private async onDidRunGitStatus(): Promise<void> {
if (!this.repository.HEAD?.name || !this.repository.HEAD?.commit) { return; }
this.currentHistoryItemGroup = {
id: `refs/heads/${this.repository.HEAD.name}`,
label: this.repository.HEAD.name,
upstream: this.repository.HEAD.upstream ?
{
id: `refs/remotes/${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`,
label: `${this.repository.HEAD.upstream.remote}/${this.repository.HEAD.upstream.name}`,
} : undefined
};
}
async provideHistoryItems(historyItemGroupId: string, options: SourceControlHistoryOptions): Promise<SourceControlHistoryItem[]> {
//TODO@lszomoru - support limit and cursor
if (typeof options.limit === 'number') {
throw new Error('Unsupported options.');
}
if (typeof options.limit?.id !== 'string') {
throw new Error('Unsupported options.');
}
const optionsRef = options.limit.id;
const [commits, summary] = await Promise.all([
this.repository.log({ range: `${optionsRef}..${historyItemGroupId}`, sortByAuthorDate: true }),
this.getSummaryHistoryItem(optionsRef, historyItemGroupId)
]);
const historyItems = commits.length === 0 ? [] : [summary];
historyItems.push(...commits.map(commit => {
const newLineIndex = commit.message.indexOf('\n');
const subject = newLineIndex !== -1 ? commit.message.substring(0, newLineIndex) : commit.message;
return {
id: commit.hash,
parentIds: commit.parents,
label: subject,
description: commit.authorName,
icon: new ThemeIcon('account'),
timestamp: commit.authorDate?.getTime()
};
}));
return historyItems;
}
async provideHistoryItemChanges(historyItemId: string): Promise<SourceControlHistoryItemChange[]> {
const [ref1, ref2] = historyItemId.includes('..')
? historyItemId.split('..')
: [`${historyItemId}^`, historyItemId];
const changes = await this.repository.diffBetween(ref1, ref2);
return changes.map(change => ({
uri: change.uri.with({ query: `ref=${historyItemId}` }),
originalUri: toGitUri(change.originalUri, ref1),
modifiedUri: toGitUri(change.originalUri, ref2),
renameUri: change.renameUri,
}));
}
async resolveHistoryItemGroupCommonAncestor(refId1: string, refId2: string | undefined): Promise<{ id: string; ahead: number; behind: number } | undefined> {
refId2 = refId2 ?? (await this.repository.getDefaultBranch()).name ?? '';
if (refId2 === '') {
return undefined;
}
const ancestor = await this.repository.getMergeBase(refId1, refId2);
if (ancestor === '') {
return undefined;
}
const commitCount = await this.repository.getCommitCount(`${refId1}...${refId2}`);
return { id: ancestor, ahead: commitCount.ahead, behind: commitCount.behind };
}
private async getSummaryHistoryItem(ref1: string, ref2: string): Promise<SourceControlHistoryItem> {
const diffShortStat = await this.repository.diffBetweenShortStat(ref1, ref2);
return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: 'Changes', description: diffShortStat };
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
}
}
+6 -2
View File
@@ -53,6 +53,7 @@ export const enum OperationKind {
RebaseContinue = 'RebaseContinue',
RevertFiles = 'RevertFiles',
RevertFilesNoProgress = 'RevertFilesNoProgress',
RevList = 'RevList',
SetBranchUpstream = 'SetBranchUpstream',
Show = 'Show',
Stage = 'Stage',
@@ -69,8 +70,9 @@ export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchO
GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation |
HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation |
MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation |
ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | SetBranchUpstreamOperation |
ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation | TagOperation;
ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | RevListOperation |
SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation |
TagOperation;
type BaseOperation = { kind: OperationKind; blocking: boolean; readOnly: boolean; remote: boolean; retry: boolean; showProgress: boolean };
export type AddOperation = BaseOperation & { kind: OperationKind.Add };
@@ -116,6 +118,7 @@ export type RebaseOperation = BaseOperation & { kind: OperationKind.Rebase };
export type RebaseAbortOperation = BaseOperation & { kind: OperationKind.RebaseAbort };
export type RebaseContinueOperation = BaseOperation & { kind: OperationKind.RebaseContinue };
export type RevertFilesOperation = BaseOperation & { kind: OperationKind.RevertFiles };
export type RevListOperation = BaseOperation & { kind: OperationKind.RevList };
export type SetBranchUpstreamOperation = BaseOperation & { kind: OperationKind.SetBranchUpstream };
export type ShowOperation = BaseOperation & { kind: OperationKind.Show };
export type StageOperation = BaseOperation & { kind: OperationKind.Stage };
@@ -169,6 +172,7 @@ export const Operation = {
RebaseAbort: { kind: OperationKind.RebaseAbort, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseAbortOperation,
RebaseContinue: { kind: OperationKind.RebaseContinue, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseContinueOperation,
RevertFiles: (showProgress: boolean) => ({ kind: OperationKind.RevertFiles, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as RevertFilesOperation),
RevList: { kind: OperationKind.RevList, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as RevListOperation,
SetBranchUpstream: { kind: OperationKind.SetBranchUpstream, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as SetBranchUpstreamOperation,
Show: { kind: OperationKind.Show, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as ShowOperation,
Stage: { kind: OperationKind.Stage, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StageOperation,
+24 -6
View File
@@ -19,10 +19,11 @@ import { IFileWatcher, watch } from './watch';
import { IPushErrorHandlerRegistry } from './pushError';
import { ApiRepository } from './api/api1';
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
import { ActionButtonCommand } from './actionButton';
import { CommitActionButton } from './actionButton';
import { IPostCommitCommandsProviderRegistry, CommitCommandsCenter } from './postCommitCommands';
import { Operation, OperationKind, OperationManager, OperationResult } from './operation';
import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection';
import { GitHistoryProvider } from './historyProvider';
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
@@ -833,8 +834,13 @@ export class Repository implements Disposable {
const root = Uri.file(repository.root);
this._sourceControl = scm.createSourceControl('git', 'Git', root);
this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] };
this._sourceControl.quickDiffProvider = this;
const historyProvider = new GitHistoryProvider(this);
this._sourceControl.historyProvider = historyProvider;
this.disposables.push(historyProvider);
this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] };
this._sourceControl.inputBox.validateInput = this.validateInput.bind(this);
this.disposables.push(this._sourceControl);
@@ -921,10 +927,10 @@ export class Repository implements Disposable {
this.commitCommandCenter = new CommitCommandsCenter(globalState, this, postCommitCommandsProviderRegistry);
this.disposables.push(this.commitCommandCenter);
const actionButton = new ActionButtonCommand(this, this.commitCommandCenter);
this.disposables.push(actionButton);
actionButton.onDidChange(() => this._sourceControl.actionButton = actionButton.button);
this._sourceControl.actionButton = actionButton.button;
const commitActionButton = new CommitActionButton(this, this.commitCommandCenter);
this.disposables.push(commitActionButton);
commitActionButton.onDidChange(() => this._sourceControl.actionButton = commitActionButton.button);
this._sourceControl.actionButton = commitActionButton.button;
const progressManager = new ProgressManager(this);
this.disposables.push(progressManager);
@@ -1115,6 +1121,10 @@ export class Repository implements Disposable {
return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path));
}
diffBetweenShortStat(ref1: string, ref2: string): Promise<string> {
return this.run(Operation.Diff, () => this.repository.diffBetweenShortStat(ref1, ref2));
}
getMergeBase(ref1: string, ref2: string): Promise<string> {
return this.run(Operation.MergeBase, () => this.repository.getMergeBase(ref1, ref2));
}
@@ -1421,6 +1431,10 @@ export class Repository implements Disposable {
await this.run(Operation.Move, () => this.repository.move(from, to));
}
async getDefaultBranch(): Promise<Branch> {
return await this.run(Operation.GetBranch, () => this.repository.getDefaultBranch());
}
async getBranch(name: string): Promise<Branch> {
return await this.run(Operation.GetBranch, () => this.repository.getBranch(name));
}
@@ -1506,6 +1520,10 @@ export class Repository implements Disposable {
return await this.repository.getCommit(ref);
}
async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> {
return await this.run(Operation.RevList, () => this.repository.getCommitCount(range));
}
async reset(treeish: string, hard?: boolean): Promise<void> {
await this.run(Operation.Reset, () => this.repository.reset(treeish, hard));
}
+1
View File
@@ -12,6 +12,7 @@
"../../src/vscode-dts/vscode.d.ts",
"../../src/vscode-dts/vscode.proposed.diffCommand.d.ts",
"../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts",
"../../src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts",
"../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts",
"../../src/vscode-dts/vscode.proposed.scmValidation.d.ts",
"../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts",