mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-11 14:04:49 -05:00
1206 lines
35 KiB
TypeScript
1206 lines
35 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
'use strict';
|
|
|
|
import { Promise, TPromise } from 'vs/base/common/winjs.base';
|
|
import nls = require('vs/nls');
|
|
import { IEventEmitter } from 'vs/base/common/eventEmitter';
|
|
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
|
import { isString } from 'vs/base/common/types';
|
|
import { Action } from 'vs/base/common/actions';
|
|
import { IDiffEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
|
import model = require('vs/workbench/parts/git/common/gitModel');
|
|
import inputs = require('vs/workbench/parts/git/browser/gitEditorInputs');
|
|
import { TextDiffEditorOptions } from 'vs/workbench/common/editor';
|
|
import errors = require('vs/base/common/errors');
|
|
import platform = require('vs/base/common/platform');
|
|
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
|
import { IEditor } from 'vs/platform/editor/common/editor';
|
|
import { IEventService } from 'vs/platform/event/common/event';
|
|
import { IFileService, IFileStat } from 'vs/platform/files/common/files';
|
|
import { IMessageService, IConfirmation } from 'vs/platform/message/common/message';
|
|
import Severity from 'vs/base/common/severity';
|
|
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
|
|
import { IGitService, IFileStatus, Status, StatusType, ServiceState, IModel, IBranch, GitErrorCodes, IGitConfiguration } from 'vs/workbench/parts/git/common/git';
|
|
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
|
|
import paths = require('vs/base/common/paths');
|
|
import URI from 'vs/base/common/uri';
|
|
|
|
function flatten(context?: any, preferFocus = false): IFileStatus[] {
|
|
if (!context) {
|
|
return context;
|
|
|
|
} else if (Array.isArray(context)) {
|
|
if (context.some((c: any) => !(c instanceof model.FileStatus))) {
|
|
throw new Error('Invalid context.');
|
|
}
|
|
return context;
|
|
|
|
} else if (context instanceof model.FileStatus) {
|
|
return [<model.FileStatus> context];
|
|
|
|
} else if (context instanceof model.StatusGroup) {
|
|
return (<model.StatusGroup> context).all();
|
|
|
|
} else if (context.tree) {
|
|
var elements = (<ITree> context.tree).getSelection();
|
|
return elements.indexOf(context.fileStatus) > -1 ? elements : [context.fileStatus];
|
|
|
|
} else if (context.selection) {
|
|
return !preferFocus && context.selection.indexOf(context.focus) > -1 ? context.selection : [context.focus];
|
|
|
|
} else {
|
|
throw new Error('Invalid context.');
|
|
}
|
|
}
|
|
|
|
export abstract class GitAction extends Action {
|
|
|
|
protected gitService: IGitService;
|
|
protected toDispose: IDisposable[];
|
|
|
|
constructor(id: string, label: string, cssClass: string, gitService: IGitService) {
|
|
super(id, label, cssClass, false);
|
|
|
|
this.gitService = gitService;
|
|
this.toDispose = [this.gitService.addBulkListener2(() => this.onGitServiceChange())];
|
|
this.onGitServiceChange();
|
|
}
|
|
|
|
protected onGitServiceChange(): void {
|
|
this.updateEnablement();
|
|
}
|
|
|
|
protected updateEnablement(): void {
|
|
this.enabled = this.isEnabled();
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return !!this.gitService;
|
|
}
|
|
|
|
public abstract run(e?: any):Promise;
|
|
|
|
public dispose(): void {
|
|
this.gitService = null;
|
|
this.toDispose = dispose(this.toDispose);
|
|
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
export class OpenChangeAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.openChange';
|
|
protected editorService: IWorkbenchEditorService;
|
|
|
|
constructor(@IWorkbenchEditorService editorService: IWorkbenchEditorService, @IGitService gitService: IGitService) {
|
|
super(OpenChangeAction.ID, nls.localize('openChange', "Open Change"), 'git-action open-change', gitService);
|
|
this.editorService = editorService;
|
|
this.onGitServiceChange();
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && !!this.editorService;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
var statuses = flatten(context, true);
|
|
|
|
return this.gitService.getInput(statuses[0]).then((input) => {
|
|
var options = new TextDiffEditorOptions();
|
|
|
|
options.forceOpen = true;
|
|
|
|
return this.editorService.openEditor(input, options);
|
|
});
|
|
}
|
|
}
|
|
|
|
export class OpenFileAction extends GitAction {
|
|
|
|
private static DELETED_STATES = [Status.BOTH_DELETED, Status.DELETED, Status.DELETED_BY_US, Status.INDEX_DELETED];
|
|
static ID = 'workbench.action.git.openFile';
|
|
|
|
protected fileService: IFileService;
|
|
protected editorService: IWorkbenchEditorService;
|
|
protected contextService: IWorkspaceContextService;
|
|
|
|
constructor(@IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, @IGitService gitService: IGitService, @IWorkspaceContextService contextService: IWorkspaceContextService) {
|
|
super(OpenFileAction.ID, nls.localize('openFile', "Open File"), 'git-action open-file', gitService);
|
|
this.fileService = fileService;
|
|
this.editorService = editorService;
|
|
this.contextService = contextService;
|
|
this.onGitServiceChange();
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && !!this.editorService || !!this.fileService;
|
|
}
|
|
|
|
private getPath(status: IFileStatus): string {
|
|
if (status.getStatus() === Status.INDEX_RENAMED) {
|
|
return status.getRename();
|
|
} else {
|
|
var indexStatus = this.gitService.getModel().getStatus().find(status.getPath(), StatusType.INDEX);
|
|
|
|
if (indexStatus && indexStatus.getStatus() === Status.INDEX_RENAMED) {
|
|
return status.getRename();
|
|
} else {
|
|
return status.getPath();
|
|
}
|
|
}
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
var statuses = flatten(context, true);
|
|
var status = statuses[0];
|
|
|
|
if (!(status instanceof model.FileStatus)) {
|
|
return Promise.wrapError(new Error('Can\'t open file.'));
|
|
}
|
|
|
|
if (OpenFileAction.DELETED_STATES.indexOf(status.getStatus()) > -1) {
|
|
return Promise.wrapError(new Error('Can\'t open file which has been deleted.'));
|
|
}
|
|
|
|
const resource = URI.file(paths.join(this.gitService.getModel().getRepositoryRoot(), this.getPath(status)));
|
|
|
|
return this.fileService.resolveFile(resource)
|
|
.then(stat => this.editorService.openEditor({
|
|
resource: stat.resource,
|
|
mime: stat.mime,
|
|
options: { forceOpen: true }
|
|
}));
|
|
}
|
|
}
|
|
|
|
export class InitAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.init';
|
|
|
|
constructor(@IGitService gitService: IGitService) {
|
|
super(InitAction.ID, nls.localize('init', "Init"), 'git-action init', gitService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && this.gitService.getState() === ServiceState.NotARepo;
|
|
}
|
|
|
|
public run():Promise {
|
|
return this.gitService.init();
|
|
}
|
|
}
|
|
|
|
export class RefreshAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.refresh';
|
|
|
|
constructor(@IGitService gitService: IGitService) {
|
|
super(RefreshAction.ID, nls.localize('refresh', "Refresh"), 'git-action refresh', gitService);
|
|
}
|
|
|
|
public run():Promise {
|
|
return this.gitService.status();
|
|
}
|
|
}
|
|
|
|
export abstract class BaseStageAction extends GitAction {
|
|
protected editorService: IWorkbenchEditorService;
|
|
|
|
constructor(id: string, label: string, className: string, gitService: IGitService, editorService: IWorkbenchEditorService) {
|
|
super(id, label, className, gitService);
|
|
this.editorService = editorService;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
var flatContext = flatten(context);
|
|
|
|
return this.gitService.add(flatContext).then(status => {
|
|
var targetEditor = this.findGitWorkingTreeEditor();
|
|
if (!targetEditor) {
|
|
return TPromise.as(status);
|
|
}
|
|
|
|
var currentGitEditorInput = <inputs.IEditorInputWithStatus>(<any>targetEditor.input);
|
|
var currentFileStatus = currentGitEditorInput.getFileStatus();
|
|
|
|
if (flatContext && flatContext.every((f) => f !== currentFileStatus)) {
|
|
return TPromise.as(status);
|
|
}
|
|
|
|
var path = currentGitEditorInput.getFileStatus().getPath();
|
|
var fileStatus = status.getStatus().find(path, StatusType.INDEX);
|
|
|
|
if (!fileStatus) {
|
|
return TPromise.as(status);
|
|
}
|
|
|
|
var editorControl = <any>targetEditor.getControl();
|
|
var viewState = editorControl ? editorControl.saveViewState() : null;
|
|
|
|
return this.gitService.getInput(fileStatus).then(input => {
|
|
var options = new TextDiffEditorOptions();
|
|
options.forceOpen = true;
|
|
|
|
return this.editorService.openEditor(input, options, targetEditor.position).then((editor) => {
|
|
if (viewState) {
|
|
editorControl.restoreViewState(viewState);
|
|
}
|
|
|
|
return status;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
private findGitWorkingTreeEditor(): IEditor {
|
|
var editors = this.editorService.getVisibleEditors();
|
|
for (var i = 0; i < editors.length; i++) {
|
|
var editor = editors[i];
|
|
if (inputs.isGitEditorInput(editor.input)) {
|
|
return editor;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public dispose(): void {
|
|
this.editorService = null;
|
|
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
export class StageAction extends BaseStageAction {
|
|
static ID = 'workbench.action.git.stage';
|
|
static LABEL = nls.localize('stageChanges', "Stage");
|
|
|
|
constructor(
|
|
@IGitService gitService: IGitService,
|
|
@IWorkbenchEditorService editorService: IWorkbenchEditorService
|
|
) {
|
|
super(StageAction.ID, StageAction.LABEL, 'git-action stage', gitService, editorService);
|
|
}
|
|
}
|
|
|
|
export class GlobalStageAction extends BaseStageAction {
|
|
|
|
static ID = 'workbench.action.git.stageAll';
|
|
|
|
constructor(@IGitService gitService: IGitService, @IWorkbenchEditorService editorService: IWorkbenchEditorService) {
|
|
super(GlobalStageAction.ID, nls.localize('stageAllChanges', "Stage All"), 'git-action stage', gitService, editorService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && this.gitService.getModel().getStatus().getWorkingTreeStatus().all().length > 0;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
return super.run();
|
|
}
|
|
}
|
|
|
|
export abstract class BaseUndoAction extends GitAction {
|
|
|
|
private eventService: IEventService;
|
|
private editorService: IWorkbenchEditorService;
|
|
private messageService: IMessageService;
|
|
private fileService: IFileService;
|
|
private contextService: IWorkspaceContextService;
|
|
|
|
constructor(id: string, label: string, className: string, gitService: IGitService, eventService: IEventService, messageService: IMessageService, fileService:IFileService, editorService: IWorkbenchEditorService, contextService: IWorkspaceContextService) {
|
|
super(id, label, className, gitService);
|
|
this.eventService = eventService;
|
|
this.editorService = editorService;
|
|
this.messageService = messageService;
|
|
this.fileService = fileService;
|
|
this.contextService = contextService;
|
|
this.onGitServiceChange();
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && !!this.eventService && !!this.editorService && !!this.fileService;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
if (!this.messageService.confirm(this.getConfirm(context))) {
|
|
return TPromise.as(null);
|
|
}
|
|
|
|
var promises: Promise[] = [];
|
|
|
|
if (context instanceof model.StatusGroup) {
|
|
promises = [ this.gitService.undo() ];
|
|
|
|
} else {
|
|
var all: IFileStatus[] = flatten(context);
|
|
var toClean: IFileStatus[] = [];
|
|
var toCheckout: IFileStatus[] = [];
|
|
|
|
for (var i = 0; i < all.length; i++) {
|
|
var status = all[i].clone();
|
|
|
|
switch (status.getStatus()) {
|
|
case Status.UNTRACKED:
|
|
case Status.IGNORED:
|
|
toClean.push(status);
|
|
break;
|
|
|
|
default:
|
|
toCheckout.push(status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (toClean.length > 0) {
|
|
promises.push(this.gitService.clean(toClean));
|
|
}
|
|
|
|
if (toCheckout.length > 0) {
|
|
promises.push(this.gitService.checkout('', toCheckout));
|
|
}
|
|
}
|
|
|
|
return Promise.join(promises).then((statuses: IModel[]) => {
|
|
if (statuses.length === 0) {
|
|
return TPromise.as(null);
|
|
}
|
|
|
|
var status = statuses[statuses.length - 1];
|
|
|
|
var targetEditor = this.findWorkingTreeDiffEditor();
|
|
if (!targetEditor) {
|
|
return TPromise.as(status);
|
|
}
|
|
|
|
var currentGitEditorInput = <inputs.GitWorkingTreeDiffEditorInput> targetEditor.input;
|
|
var currentFileStatus = currentGitEditorInput.getFileStatus();
|
|
|
|
if (all && all.every((f) => f !== currentFileStatus)) {
|
|
return TPromise.as(status);
|
|
}
|
|
|
|
var path = currentGitEditorInput.getFileStatus().getPath();
|
|
|
|
var editor = <IDiffEditor> targetEditor.getControl();
|
|
var modifiedEditorControl = editor ? <any>editor.getModifiedEditor() : null;
|
|
var modifiedViewState = modifiedEditorControl ? modifiedEditorControl.saveViewState() : null;
|
|
|
|
return this.fileService.resolveFile(this.contextService.toResource(path)).then((stat: IFileStat) => {
|
|
return this.editorService.openEditor({
|
|
resource: stat.resource,
|
|
mime: stat.mime,
|
|
options: {
|
|
forceOpen: true
|
|
}
|
|
}, targetEditor.position).then((editor) => {
|
|
if (modifiedViewState) {
|
|
var codeEditor = <ICodeEditor> targetEditor.getControl();
|
|
|
|
if (codeEditor) {
|
|
codeEditor.restoreViewState(modifiedViewState);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}).then(null, (errors: any[]): Promise => {
|
|
console.error('One or more errors occurred', errors);
|
|
return Promise.wrapError(errors[0]);
|
|
});
|
|
}
|
|
|
|
private findWorkingTreeDiffEditor(): IEditor {
|
|
var editors = this.editorService.getVisibleEditors();
|
|
for (var i = 0; i < editors.length; i++) {
|
|
var editor = editors[i];
|
|
if (editor.input instanceof inputs.GitWorkingTreeDiffEditorInput) {
|
|
return editor;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private getConfirm(context: any): IConfirmation {
|
|
const all = flatten(context);
|
|
|
|
if (all.length > 1) {
|
|
const count = all.length;
|
|
|
|
return {
|
|
message: nls.localize('confirmUndoMessage', "Are you sure you want to clean all changes?"),
|
|
detail: count === 1
|
|
? nls.localize('confirmUndoAllOne', "There are unstaged changes in {0} file.\n\nThis action is irreversible!", count)
|
|
: nls.localize('confirmUndoAllMultiple', "There are unstaged changes in {0} files.\n\nThis action is irreversible!", count),
|
|
primaryButton: nls.localize({ key: 'cleanChangesLabel', comment: ['&& denotes a mnemonic'] }, "&&Clean Changes")
|
|
};
|
|
}
|
|
|
|
const label = all[0].getPathComponents().reverse()[0];
|
|
|
|
return {
|
|
message: nls.localize('confirmUndo', "Are you sure you want to clean changes in '{0}'?", label),
|
|
detail: nls.localize('irreversible', "This action is irreversible!"),
|
|
primaryButton: nls.localize({ key: 'cleanChangesLabel', comment: ['&& denotes a mnemonic'] }, "&&Clean Changes")
|
|
};
|
|
}
|
|
|
|
public dispose(): void {
|
|
this.eventService = null;
|
|
this.editorService = null;
|
|
this.fileService = null;
|
|
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
export class UndoAction extends BaseUndoAction {
|
|
static ID = 'workbench.action.git.undo';
|
|
constructor( @IGitService gitService: IGitService, @IEventService eventService: IEventService, @IMessageService messageService: IMessageService, @IFileService fileService: IFileService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IWorkspaceContextService contextService: IWorkspaceContextService) {
|
|
super(UndoAction.ID, nls.localize('undoChanges', "Clean"), 'git-action undo', gitService, eventService, messageService, fileService, editorService, contextService);
|
|
}
|
|
}
|
|
|
|
export class GlobalUndoAction extends BaseUndoAction {
|
|
|
|
static ID = 'workbench.action.git.undoAll';
|
|
|
|
constructor(@IGitService gitService: IGitService, @IEventService eventService: IEventService, @IMessageService messageService: IMessageService, @IFileService fileService: IFileService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IWorkspaceContextService contextService: IWorkspaceContextService) {
|
|
super(GlobalUndoAction.ID, nls.localize('undoAllChanges', "Clean All"), 'git-action undo', gitService, eventService, messageService, fileService, editorService, contextService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && this.gitService.getModel().getStatus().getWorkingTreeStatus().all().length > 0;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
return super.run(this.gitService.getModel().getStatus().getWorkingTreeStatus());
|
|
}
|
|
}
|
|
|
|
export abstract class BaseUnstageAction extends GitAction {
|
|
|
|
protected editorService: IWorkbenchEditorService;
|
|
|
|
constructor(id: string, label: string, className: string, gitService: IGitService, editorService: IWorkbenchEditorService) {
|
|
super(id, label, className, gitService);
|
|
this.editorService = editorService;
|
|
this.onGitServiceChange();
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && !!this.editorService;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
var flatContext = flatten(context);
|
|
|
|
return this.gitService.revertFiles('HEAD', flatContext).then((status: IModel) => {
|
|
var targetEditor = this.findGitIndexEditor();
|
|
if (!targetEditor) {
|
|
return TPromise.as(status);
|
|
}
|
|
|
|
var currentGitEditorInput = <inputs.IEditorInputWithStatus>(<any>targetEditor.input);
|
|
var currentFileStatus = currentGitEditorInput.getFileStatus();
|
|
|
|
if (flatContext && flatContext.every((f) => f !== currentFileStatus)) {
|
|
return TPromise.as(status);
|
|
}
|
|
|
|
var path = currentGitEditorInput.getFileStatus().getPath();
|
|
var fileStatus = status.getStatus().find(path, StatusType.WORKING_TREE);
|
|
|
|
if (!fileStatus) {
|
|
return TPromise.as(status);
|
|
}
|
|
|
|
var editorControl = <any> targetEditor.getControl();
|
|
var viewState = editorControl ? editorControl.saveViewState() : null;
|
|
|
|
return this.gitService.getInput(fileStatus).then((input) => {
|
|
var options = new TextDiffEditorOptions();
|
|
options.forceOpen = true;
|
|
|
|
return this.editorService.openEditor(input, options, targetEditor.position).then((editor) => {
|
|
if (viewState) {
|
|
editorControl.restoreViewState(viewState);
|
|
}
|
|
|
|
return status;
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
private findGitIndexEditor(): IEditor {
|
|
var editors = this.editorService.getVisibleEditors();
|
|
for (var i = 0; i < editors.length; i++) {
|
|
var editor = editors[i];
|
|
if (inputs.isGitEditorInput(editor.input)) {
|
|
return editor;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public dispose(): void {
|
|
this.editorService = null;
|
|
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
export class UnstageAction extends BaseUnstageAction {
|
|
static ID = 'workbench.action.git.unstage';
|
|
static LABEL = nls.localize('unstage', "Unstage");
|
|
|
|
constructor(
|
|
@IGitService gitService: IGitService,
|
|
@IWorkbenchEditorService editorService: IWorkbenchEditorService
|
|
) {
|
|
super(UnstageAction.ID, UnstageAction.LABEL, 'git-action unstage', gitService, editorService);
|
|
}
|
|
}
|
|
|
|
export class GlobalUnstageAction extends BaseUnstageAction {
|
|
|
|
static ID = 'workbench.action.git.unstageAll';
|
|
|
|
constructor(@IGitService gitService: IGitService, @IWorkbenchEditorService editorService: IWorkbenchEditorService) {
|
|
super(GlobalUnstageAction.ID, nls.localize('unstageAllChanges', "Unstage All"), 'git-action unstage', gitService, editorService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && this.gitService.getModel().getStatus().getIndexStatus().all().length > 0;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
return super.run();
|
|
}
|
|
}
|
|
|
|
enum LifecycleState {
|
|
Alive,
|
|
Disposing,
|
|
Disposed
|
|
}
|
|
|
|
export class CheckoutAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.checkout';
|
|
private editorService: IWorkbenchEditorService;
|
|
private branch: IBranch;
|
|
private HEAD: IBranch;
|
|
|
|
private state: LifecycleState;
|
|
private runPromises: Promise[];
|
|
|
|
constructor(branch: IBranch, @IGitService gitService: IGitService, @IWorkbenchEditorService editorService: IWorkbenchEditorService) {
|
|
super(CheckoutAction.ID, branch.name, 'git-action checkout', gitService);
|
|
|
|
this.editorService = editorService;
|
|
this.branch = branch;
|
|
this.HEAD = null;
|
|
this.state = LifecycleState.Alive;
|
|
this.runPromises = [];
|
|
this.onGitServiceChange();
|
|
}
|
|
|
|
protected onGitServiceChange(): void {
|
|
if (this.gitService.getState() === ServiceState.OK) {
|
|
this.HEAD = this.gitService.getModel().getHEAD();
|
|
|
|
if (this.HEAD && this.HEAD.name === this.branch.name) {
|
|
this.class = 'git-action checkout HEAD';
|
|
} else {
|
|
this.class = 'git-action checkout';
|
|
}
|
|
}
|
|
|
|
super.onGitServiceChange();
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && !!this.HEAD;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
if (this.state !== LifecycleState.Alive) {
|
|
return Promise.wrapError('action disposed');
|
|
} else if (this.HEAD && this.HEAD.name === this.branch.name) {
|
|
return TPromise.as(null);
|
|
}
|
|
|
|
var result = this.gitService.checkout(this.branch.name).then(null, (err) => {
|
|
if (err.gitErrorCode === GitErrorCodes.DirtyWorkTree) {
|
|
return Promise.wrapError(new Error(nls.localize('dirtyTreeCheckout', "Can't checkout. Please commit or stage your work first.")));
|
|
}
|
|
|
|
return Promise.wrapError(err);
|
|
});
|
|
|
|
this.runPromises.push(result);
|
|
result.done(() => this.runPromises.splice(this.runPromises.indexOf(result), 1));
|
|
|
|
return result;
|
|
}
|
|
|
|
public dispose(): void {
|
|
if (this.state !== LifecycleState.Alive) {
|
|
return;
|
|
}
|
|
|
|
this.state = LifecycleState.Disposing;
|
|
Promise.join(this.runPromises).done(() => this.actuallyDispose());
|
|
}
|
|
|
|
private actuallyDispose(): void {
|
|
this.editorService = null;
|
|
this.branch = null;
|
|
this.HEAD = null;
|
|
|
|
super.dispose();
|
|
|
|
this.state = LifecycleState.Disposed;
|
|
}
|
|
}
|
|
|
|
export class BranchAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.branch';
|
|
private checkout:boolean;
|
|
|
|
constructor(checkout: boolean, @IGitService gitService: IGitService) {
|
|
super(BranchAction.ID, 'Branch', 'git-action checkout', gitService);
|
|
this.checkout = checkout;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
if (!isString(context)) {
|
|
return TPromise.as(false);
|
|
}
|
|
|
|
return this.gitService.branch(<string> context, this.checkout);
|
|
}
|
|
}
|
|
|
|
export interface ICommitState extends IEventEmitter {
|
|
getCommitMessage():string;
|
|
onEmptyCommitMessage():void;
|
|
}
|
|
|
|
export abstract class BaseCommitAction extends GitAction {
|
|
protected commitState: ICommitState;
|
|
|
|
constructor(commitState: ICommitState, id: string, label: string, cssClass: string, gitService: IGitService) {
|
|
super(id, label, cssClass, gitService);
|
|
|
|
this.commitState = commitState;
|
|
|
|
this.toDispose.push(commitState.addListener2('change/commitInputBox', () => {
|
|
this.updateEnablement();
|
|
}));
|
|
|
|
this.onGitServiceChange();
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
return super.isEnabled() && this.gitService.getModel().getStatus().getIndexStatus().all().length > 0;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
if (!this.commitState.getCommitMessage()) {
|
|
this.commitState.onEmptyCommitMessage();
|
|
return TPromise.as(null);
|
|
}
|
|
|
|
return this.commit();
|
|
}
|
|
|
|
protected abstract commit(): Promise;
|
|
}
|
|
|
|
export class CommitAction extends BaseCommitAction {
|
|
|
|
static ID = 'workbench.action.git.commit';
|
|
|
|
constructor(commitState: ICommitState, @IGitService gitService: IGitService) {
|
|
super(commitState, CommitAction.ID, nls.localize('commitStaged', "Commit Staged"), 'git-action commit', gitService);
|
|
}
|
|
|
|
protected commit(): Promise {
|
|
return this.gitService.commit(this.commitState.getCommitMessage());
|
|
}
|
|
}
|
|
|
|
export class CommitSignedOffAction extends BaseCommitAction {
|
|
|
|
static ID = 'workbench.action.git.commitSignedOff';
|
|
|
|
constructor(commitState: ICommitState, @IGitService gitService: IGitService) {
|
|
super(commitState, CommitAction.ID, nls.localize('commitStagedSignedOff', "Commit Staged (Signed Off)"), 'git-action commit-signed-off', gitService);
|
|
}
|
|
|
|
protected commit(): Promise {
|
|
return this.gitService.commit(this.commitState.getCommitMessage(), undefined, undefined, true);
|
|
}
|
|
}
|
|
|
|
export class InputCommitAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.input-commit';
|
|
static LABEL = nls.localize('commit', "Commit");
|
|
|
|
constructor(
|
|
id = InputCommitAction.ID,
|
|
label = InputCommitAction.LABEL,
|
|
@IGitService gitService: IGitService,
|
|
@IQuickOpenService private quickOpenService: IQuickOpenService
|
|
) {
|
|
super(id, label, '', gitService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
if (!this.gitService) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.gitService.isIdle()) {
|
|
return false;
|
|
}
|
|
|
|
const status = this.gitService.getModel().getStatus();
|
|
|
|
return status.getIndexStatus().all().length > 0 || status.getWorkingTreeStatus().all().length > 0;
|
|
}
|
|
|
|
run(): TPromise<any> {
|
|
if (!this.enabled) {
|
|
return TPromise.as(null);
|
|
}
|
|
|
|
const status = this.gitService.getModel().getStatus();
|
|
|
|
return this.quickOpenService.input({ prompt: nls.localize('commitMessage', "Commit Message") })
|
|
.then(message => message && this.gitService.commit(message, false, status.getIndexStatus().all().length === 0));
|
|
}
|
|
}
|
|
|
|
export class StageAndCommitAction extends BaseCommitAction {
|
|
|
|
static ID = 'workbench.action.git.stageAndCommit';
|
|
static LABEL = nls.localize('commitAll', "Commit All");
|
|
static CSSCLASS = 'git-action stage-and-commit';
|
|
|
|
constructor(
|
|
commitState: ICommitState,
|
|
id: string = StageAndCommitAction.ID,
|
|
label: string = StageAndCommitAction.LABEL,
|
|
cssClass: string = StageAndCommitAction.CSSCLASS,
|
|
@IGitService gitService: IGitService
|
|
) {
|
|
super(commitState, id, label, cssClass, gitService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
if (!this.gitService) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.gitService.isIdle()) {
|
|
return false;
|
|
}
|
|
|
|
var status = this.gitService.getModel().getStatus();
|
|
|
|
return status.getIndexStatus().all().length > 0
|
|
|| status.getWorkingTreeStatus().all().length > 0;
|
|
}
|
|
|
|
protected commit(): Promise {
|
|
return this.gitService.commit(this.commitState.getCommitMessage(), false, true);
|
|
}
|
|
}
|
|
|
|
export class StageAndCommitSignedOffAction extends StageAndCommitAction {
|
|
|
|
static ID = 'workbench.action.git.stageAndCommitSignedOff';
|
|
|
|
constructor(commitState: ICommitState, @IGitService gitService: IGitService) {
|
|
super(commitState, StageAndCommitAction.ID, nls.localize('commitAllSignedOff', "Commit All (Signed Off)"), 'git-action stage-and-commit-signed-off', gitService);
|
|
}
|
|
|
|
protected commit(): Promise {
|
|
return this.gitService.commit(this.commitState.getCommitMessage(), false, true, true);
|
|
}
|
|
}
|
|
|
|
export class SmartCommitAction extends BaseCommitAction {
|
|
|
|
static ID = 'workbench.action.git.commitAll';
|
|
private static ALL = nls.localize('commitAll2', "Commit All");
|
|
private static STAGED = nls.localize('commitStaged2', "Commit Staged");
|
|
|
|
private messageService: IMessageService;
|
|
|
|
constructor(commitState: ICommitState, @IGitService gitService: IGitService, @IMessageService messageService: IMessageService) {
|
|
super(commitState, SmartCommitAction.ID, SmartCommitAction.ALL, 'git-action smart-commit', gitService);
|
|
this.messageService = messageService;
|
|
this.onGitServiceChange();
|
|
}
|
|
|
|
protected onGitServiceChange(): void {
|
|
super.onGitServiceChange();
|
|
|
|
if (!this.enabled) {
|
|
this.label = SmartCommitAction.ALL;
|
|
return;
|
|
}
|
|
|
|
var status = this.gitService.getModel().getStatus();
|
|
|
|
if (status.getIndexStatus().all().length > 0) {
|
|
this.label = SmartCommitAction.STAGED;
|
|
} else {
|
|
this.label = SmartCommitAction.ALL;
|
|
}
|
|
|
|
this.label += ' (' + (platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter') + ')';
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
if (!this.gitService) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.gitService.isIdle()) {
|
|
return false;
|
|
}
|
|
|
|
var status = this.gitService.getModel().getStatus();
|
|
|
|
return status.getIndexStatus().all().length > 0
|
|
|| status.getWorkingTreeStatus().all().length > 0;
|
|
}
|
|
|
|
protected commit(): Promise {
|
|
const status = this.gitService.getModel().getStatus();
|
|
return this.gitService.commit(this.commitState.getCommitMessage(), false, status.getIndexStatus().all().length === 0);
|
|
}
|
|
}
|
|
|
|
export class PullAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.pull';
|
|
static LABEL = 'Pull';
|
|
|
|
constructor(
|
|
id = PullAction.ID,
|
|
label = PullAction.LABEL,
|
|
@IGitService gitService: IGitService
|
|
) {
|
|
super(id, label, 'git-action pull', gitService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
if (!super.isEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.gitService.isIdle()) {
|
|
return false;
|
|
}
|
|
|
|
var model = this.gitService.getModel();
|
|
var HEAD = model.getHEAD();
|
|
|
|
if (!HEAD || !HEAD.name || !HEAD.upstream) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
return this.pull();
|
|
}
|
|
|
|
protected pull(rebase = false): Promise {
|
|
return this.gitService.pull(rebase).then(null, (err) => {
|
|
if (err.gitErrorCode === GitErrorCodes.DirtyWorkTree) {
|
|
return Promise.wrapError(errors.create(nls.localize('dirtyTreePull', "Can't pull. Please commit or stage your work first."), { severity: Severity.Warning }));
|
|
} else if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
|
|
return Promise.wrapError(errors.create(nls.localize('authFailed', "Authentication failed on the git remote.")));
|
|
}
|
|
|
|
return Promise.wrapError(err);
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PullWithRebaseAction extends PullAction {
|
|
|
|
static ID = 'workbench.action.git.pull.rebase';
|
|
static LABEL = 'Pull (Rebase)';
|
|
|
|
constructor(@IGitService gitService: IGitService) {
|
|
super(PullWithRebaseAction.ID, PullWithRebaseAction.LABEL, gitService);
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
return this.pull(true);
|
|
}
|
|
}
|
|
|
|
export class PushAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.push';
|
|
static LABEL = 'Push';
|
|
|
|
constructor(
|
|
id: string = PushAction.ID,
|
|
label: string = PushAction.LABEL,
|
|
@IGitService gitService: IGitService
|
|
) {
|
|
super(id, label, 'git-action push', gitService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
if (!super.isEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.gitService.isIdle()) {
|
|
return false;
|
|
}
|
|
|
|
var model = this.gitService.getModel();
|
|
var HEAD = model.getHEAD();
|
|
|
|
if (!HEAD || !HEAD.name || !HEAD.upstream) {
|
|
return false;
|
|
}
|
|
|
|
if (!HEAD.ahead) { // no commits to pull or push
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
return this.gitService.push().then(null, (err) => {
|
|
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
|
|
return Promise.wrapError(errors.create(nls.localize('authFailed', "Authentication failed on the git remote.")));
|
|
}
|
|
|
|
return Promise.wrapError(err);
|
|
});
|
|
}
|
|
}
|
|
|
|
export class PublishAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.publish';
|
|
static LABEL = nls.localize('publish', "Publish");
|
|
|
|
constructor(
|
|
id: string = PublishAction.ID,
|
|
label: string = PublishAction.LABEL,
|
|
@IGitService gitService: IGitService,
|
|
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
|
@IMessageService private messageService: IMessageService
|
|
) {
|
|
super(id, label, 'git-action publish', gitService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
if (!super.isEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.gitService.isIdle()) {
|
|
return false;
|
|
}
|
|
|
|
const model = this.gitService.getModel();
|
|
|
|
if (model.getRemotes().length === 0) {
|
|
return false;
|
|
}
|
|
|
|
const HEAD = model.getHEAD();
|
|
|
|
if (!HEAD || !HEAD.name || HEAD.upstream) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
const model = this.gitService.getModel();
|
|
const remotes = model.getRemotes();
|
|
const branchName = model.getHEAD().name;
|
|
let promise: TPromise<string>;
|
|
|
|
if (remotes.length === 1) {
|
|
const remoteName = remotes[0].name;
|
|
|
|
const result = this.messageService.confirm({
|
|
message: nls.localize('confirmPublishMessage', "Are you sure you want to publish '{0}' to '{1}'?", branchName, remoteName),
|
|
primaryButton: nls.localize({ key: 'confirmPublishMessageButton', comment: ['&& denotes a mnemonic'] }, "&&Publish")
|
|
});
|
|
|
|
promise = TPromise.as(result ? remoteName : null);
|
|
} else {
|
|
const picks = remotes.map(({ name, url }) => ({
|
|
label: name,
|
|
description: url
|
|
}));
|
|
|
|
const placeHolder = nls.localize('publishPickMessage', "Pick a remote to publish the branch '{0}' to:", branchName);
|
|
|
|
promise = this.quickOpenService.pick(picks, { placeHolder })
|
|
.then(pick => pick && pick.label);
|
|
}
|
|
|
|
return promise
|
|
.then(remote => remote && this.gitService.push(remote, branchName, { setUpstream: true }))
|
|
.then(null, err => {
|
|
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
|
|
return Promise.wrapError(errors.create(nls.localize('authFailed', "Authentication failed on the git remote.")));
|
|
}
|
|
|
|
return Promise.wrapError(err);
|
|
});
|
|
}
|
|
}
|
|
|
|
export class SyncAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.sync';
|
|
static LABEL = 'Sync';
|
|
|
|
constructor(id: string, label: string,
|
|
@IGitService gitService: IGitService,
|
|
@IConfigurationService private configurationService: IConfigurationService
|
|
) {
|
|
super(id, label, 'git-action sync', gitService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
if (!super.isEnabled()) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.gitService.isIdle()) {
|
|
return false;
|
|
}
|
|
|
|
var model = this.gitService.getModel();
|
|
var HEAD = model.getHEAD();
|
|
|
|
if (!HEAD || !HEAD.name || !HEAD.upstream) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public run(context?: any):Promise {
|
|
if (!this.enabled) {
|
|
return TPromise.as(null);
|
|
}
|
|
|
|
const config = this.configurationService.getConfiguration<IGitConfiguration>('git');
|
|
|
|
if (config.confirmSync && !window.confirm(nls.localize('sureSync', "Are you sure you want to synchronize your git repository?"))) {
|
|
return TPromise.as(null);
|
|
}
|
|
|
|
return this.gitService.sync().then(null, (err) => {
|
|
if (err.gitErrorCode === GitErrorCodes.AuthenticationFailed) {
|
|
return Promise.wrapError(errors.create(nls.localize('authFailed', "Authentication failed on the git remote.")));
|
|
}
|
|
|
|
return Promise.wrapError(err);
|
|
});
|
|
}
|
|
}
|
|
|
|
export class UndoLastCommitAction extends GitAction {
|
|
|
|
static ID = 'workbench.action.git.undoLastCommit';
|
|
static LABEL = nls.localize('undoLastCommit', "Undo Last Commit");
|
|
|
|
constructor(
|
|
id = UndoLastCommitAction.ID,
|
|
label = UndoLastCommitAction.LABEL,
|
|
@IGitService gitService: IGitService
|
|
) {
|
|
super(UndoLastCommitAction.ID, UndoLastCommitAction.LABEL, 'git-action undo-last-commit', gitService);
|
|
}
|
|
|
|
protected isEnabled():boolean {
|
|
if (!this.gitService) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.gitService.isIdle()) {
|
|
return false;
|
|
}
|
|
|
|
var status = this.gitService.getModel().getStatus();
|
|
|
|
return status.getIndexStatus().all().length === 0
|
|
&& status.getWorkingTreeStatus().all().length === 0;
|
|
}
|
|
|
|
public run():Promise {
|
|
return this.gitService.reset('HEAD~');
|
|
}
|
|
}
|
|
|
|
export class StartGitCheckoutAction extends Action {
|
|
|
|
public static ID = 'workbench.action.git.startGitCheckout';
|
|
public static LABEL = 'Checkout';
|
|
private quickOpenService: IQuickOpenService;
|
|
|
|
constructor(id: string, label: string, @IQuickOpenService quickOpenService: IQuickOpenService) {
|
|
super(id, label);
|
|
this.quickOpenService = quickOpenService;
|
|
}
|
|
|
|
public run(event?:any): Promise {
|
|
this.quickOpenService.show('git checkout ');
|
|
return TPromise.as(null);
|
|
}
|
|
}
|
|
|
|
export class StartGitBranchAction extends Action {
|
|
|
|
public static ID = 'workbench.action.git.startGitBranch';
|
|
public static LABEL = 'Branch';
|
|
private quickOpenService: IQuickOpenService;
|
|
|
|
constructor(id: string, label: string, @IQuickOpenService quickOpenService: IQuickOpenService) {
|
|
super(id, label);
|
|
this.quickOpenService = quickOpenService;
|
|
}
|
|
|
|
public run(event?:any): Promise {
|
|
this.quickOpenService.show('git branch ');
|
|
return TPromise.as(null);
|
|
}
|
|
}
|