diff --git a/src/vs/workbench/parts/git/browser/gitActions.contribution.ts b/src/vs/workbench/parts/git/browser/gitActions.contribution.ts index 6442cf9abf589cefb6e755407b50542af67f7610..6c238d363da1cbf0582dd70983c7d6c85abc2991 100644 --- a/src/vs/workbench/parts/git/browser/gitActions.contribution.ts +++ b/src/vs/workbench/parts/git/browser/gitActions.contribution.ts @@ -33,17 +33,17 @@ import wbar = require('vs/workbench/browser/actionRegistry'); import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { OpenChangeAction, SyncAction } from './gitActions'; import Severity from 'vs/base/common/severity'; +import paths = require('vs/base/common/paths'); +import URI from 'vs/base/common/uri'; function getStatus(gitService: IGitService, contextService: IWorkspaceContextService, input: WorkbenchEditorCommon.IFileEditorInput): IFileStatus { - var statusModel = gitService.getModel().getStatus(); + const model = gitService.getModel(); + const repositoryRoot = model.getRepositoryRoot(); + const statusModel = model.getStatus(); + const repositoryRelativePath = paths.normalize(paths.relative(repositoryRoot, input.getResource().fsPath)); - var workspaceRelativePath = contextService.toWorkspaceRelativePath(input.getResource()); - if (!workspaceRelativePath) { - return null; // out of workspace not yet supported - } - - return statusModel.getWorkingTreeStatus().find(workspaceRelativePath) || - statusModel.getIndexStatus().find(workspaceRelativePath); + return statusModel.getWorkingTreeStatus().find(repositoryRelativePath) || + statusModel.getIndexStatus().find(repositoryRelativePath); } class OpenInDiffAction extends baseeditor.EditorInputAction { @@ -78,6 +78,10 @@ class OpenInDiffAction extends baseeditor.EditorInputAction { return false; } + if (!(typeof this.gitService.getModel().getRepositoryRoot() === 'string')) { + return false; + } + var status = this.getStatus(); return status && ( @@ -164,6 +168,10 @@ class OpenInEditorAction extends baseeditor.EditorInputAction { return false; } + if (!(typeof this.gitService.getModel().getRepositoryRoot() === 'string')) { + return false; + } + var status:IFileStatus = (this.input).getFileStatus(); if (OpenInEditorAction.DELETED_STATES.indexOf(status.getStatus()) > -1) { return false; @@ -173,18 +181,19 @@ class OpenInEditorAction extends baseeditor.EditorInputAction { } public run(event?: any): Promise { - var sideBySide = !!(event && (event.ctrlKey || event.metaKey)); - var modifiedViewState = this.saveTextViewState(); - var path = this.getPath(); + const model = this.gitService.getModel(); + const resource = URI.file(paths.join(model.getRepositoryRoot(), this.getRepositoryRelativePath())); + const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); + const modifiedViewState = this.saveTextViewState(); - return this.fileService.resolveFile(this.contextService.toResource(path)).then((stat: IFileStat) => { + return this.fileService.resolveFile(resource).then(stat => { return this.editorService.openEditor({ resource: stat.resource, mime: stat.mime, options: { forceOpen: true } - }, sideBySide).then((editor)=> { + }, sideBySide).then(editor => { this.restoreTextViewState(modifiedViewState); if (this.partService.isVisible(Parts.SIDEBAR_PART)) { @@ -222,7 +231,7 @@ class OpenInEditorAction extends baseeditor.EditorInputAction { return null; } - private getPath():string { + private getRepositoryRelativePath():string { var status: IFileStatus = ( this.input).getFileStatus(); if (status.getStatus() === Status.INDEX_RENAMED) { diff --git a/src/vs/workbench/parts/git/browser/gitServices.ts b/src/vs/workbench/parts/git/browser/gitServices.ts index 713fb2eb7a9ca244d9a6f68c582bd06b39b26428..5b11b5fd223acaa0e61e636cbdb86cb89ffcd23e 100644 --- a/src/vs/workbench/parts/git/browser/gitServices.ts +++ b/src/vs/workbench/parts/git/browser/gitServices.ts @@ -32,6 +32,7 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IMessageService} from 'vs/platform/message/common/message'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; +import URI from 'vs/base/common/uri'; function toReadablePath(path: string): string { if (!platform.isWindows) { @@ -158,9 +159,9 @@ class EditorInputCache } private createRightInput(status: git.IFileStatus): winjs.Promise { - var path = status.getPath(); - var resource = this.contextService.toResource(path); - var model = this.gitService.getModel(); + const model = this.gitService.getModel(); + const path = status.getPath(); + let resource = URI.file(paths.join(model.getRepositoryRoot(), path)); switch (status.getStatus()) { case git.Status.INDEX_MODIFIED: @@ -181,13 +182,13 @@ class EditorInputCache var indexStatus = model.getStatus().find(path, git.StatusType.INDEX); if (indexStatus && indexStatus.getStatus() === git.Status.INDEX_RENAMED) { - return this.editorService.inputToType({ resource: this.contextService.toResource(indexStatus.getRename()) }); + resource = URI.file(paths.join(model.getRepositoryRoot(), indexStatus.getRename())); } - return this.editorService.inputToType({ resource: resource }); + return this.editorService.inputToType({ resource }); case git.Status.BOTH_MODIFIED: - return this.editorService.inputToType({ resource: resource }); + return this.editorService.inputToType({ resource }); default: return winjs.Promise.as(null); @@ -391,7 +392,16 @@ export class GitService extends ee.EventEmitter private refreshDelayer: async.ThrottledDelayer; private autoFetcher: AutoFetcher; - constructor(raw: git.IRawGitService, @IInstantiationService instantiationService: IInstantiationService, @IEventService eventService: IEventService, @IMessageService messageService: IMessageService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IOutputService outputService: IOutputService, @IWorkspaceContextService contextService: IWorkspaceContextService, @ILifecycleService lifecycleService: ILifecycleService) { + constructor( + raw: git.IRawGitService, + @IInstantiationService instantiationService: IInstantiationService, + @IEventService eventService: IEventService, + @IMessageService messageService: IMessageService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IOutputService outputService: IOutputService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @ILifecycleService lifecycleService: ILifecycleService + ) { super(); this.instantiationService = instantiationService; diff --git a/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts b/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts index cdb3ebcf6d63e5b941e04738855a9547797df765..b05be24d7e345c7ac8f392da1d4ff2e963976cb6 100644 --- a/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts +++ b/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts @@ -9,6 +9,7 @@ import 'vs/css!./media/git.contribution'; import nls = require('vs/nls'); import async = require('vs/base/common/async'); import errors = require('vs/base/common/errors'); +import paths = require('vs/base/common/paths'); import actions = require('vs/base/common/actions'); import lifecycle = require('vs/base/common/lifecycle'); import Severity from 'vs/base/common/severity'; @@ -335,7 +336,19 @@ export class DirtyDiffDecorator implements ext.IWorkbenchContribution { // HACK: This is the best current way of figuring out whether to draw these decorations // or not. Needs context from the editor, to know whether it is a diff editor, in place editor // etc. - var models = this.editorService.getVisibleEditors() + + const repositoryRoot = this.gitService.getModel().getRepositoryRoot(); + + // If there is no repository root, just wait until that changes + if (typeof repositoryRoot !== 'string') { + this.gitService.addOneTimeListener(git.ServiceEvents.STATE_CHANGED, () => this.onEditorInputChange()); + + this.models.forEach(m => this.onModelInvisible(m)); + this.models = []; + return; + } + + const models = this.editorService.getVisibleEditors() // map to the editor controls .map(e => e.getControl()) @@ -354,12 +367,12 @@ export class DirtyDiffDecorator implements ext.IWorkbenchContribution { // remove nulls .filter(p => !!p.resource && - // and ivalid resources - (p.resource.scheme === 'file' && !!this.contextService.isInsideWorkspace(p.resource)) + // and invalid resources + (p.resource.scheme === 'file' && paths.isEqualOrParent(p.resource.fsPath, repositoryRoot)) ) // get paths - .map(p => ({ model: p.model, path: this.contextService.toWorkspaceRelativePath(p.resource) })) + .map(p => ({ model: p.model, path: paths.normalize(paths.relative(repositoryRoot, p.resource.fsPath)) })) // remove nulls and inside .git files .filter(p => !!p.path && p.path.indexOf('.git/') === -1); diff --git a/src/vs/workbench/parts/git/browser/views/changes/changesView.css b/src/vs/workbench/parts/git/browser/views/changes/changesView.css index 205d1b4c3126d73ea2e28e7f6e9a159c40c162ad..ce38ce654880ac34c4703d78501504072c525809 100644 --- a/src/vs/workbench/parts/git/browser/views/changes/changesView.css +++ b/src/vs/workbench/parts/git/browser/views/changes/changesView.css @@ -60,7 +60,7 @@ .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .status-group { font-size: 11px; - font-weight: bold; + font-weight: bold; text-transform: uppercase; cursor: default; } @@ -84,6 +84,10 @@ color: inherit; } +.git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.out-of-workspace { + opacity: 0.5; +} + .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status .status { position: absolute; top: 4px; @@ -99,7 +103,7 @@ } .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status .name { - margin-left: 20px; + margin-left: 20px; } .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.modified .status { background-color: #007ACC; } @@ -123,7 +127,7 @@ .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.both-deleted .name, .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.deleted-by-them .name, .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.deleted-by-us .name { - text-decoration: line-through; + text-decoration: line-through; } .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status:not(.renamed) > .rename { @@ -159,8 +163,8 @@ .hc-black .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.untracked .status, .hc-black .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.ignored .status, .hc-black .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .file-status.conflict .status, -.hc-black .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row.selected .file-status .status { - background-color: #000; +.hc-black .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row.selected .file-status .status { + background-color: #000; color: #fff; border: 1px solid #6FC3DF; } \ No newline at end of file diff --git a/src/vs/workbench/parts/git/browser/views/changes/changesView.ts b/src/vs/workbench/parts/git/browser/views/changes/changesView.ts index ddd4cc5450fd549c1bb97f0768e6b4865535300a..6c886ac5204943997ba74d83c0e38560f656c31b 100644 --- a/src/vs/workbench/parts/git/browser/views/changes/changesView.ts +++ b/src/vs/workbench/parts/git/browser/views/changes/changesView.ts @@ -12,6 +12,7 @@ import Lifecycle = require('vs/base/common/lifecycle'); import EventEmitter = require('vs/base/common/eventEmitter'); import Strings = require('vs/base/common/strings'); import Errors = require('vs/base/common/errors'); +import * as paths from 'vs/base/common/paths'; import WinJS = require('vs/base/common/winjs.base'); import Builder = require('vs/base/browser/builder'); import Keyboard = require('vs/base/browser/keyboardEvent'); @@ -400,19 +401,27 @@ export class ChangesView extends EventEmitter.EventEmitter implements GitView.IV } if (input instanceof Files.FileEditorInput) { - var fileInput = input; + const fileInput = input; + const resource = fileInput.getResource(); - var workspaceRelativePath = this.contextService.toWorkspaceRelativePath(fileInput.getResource()); - if (!workspaceRelativePath) { + const workspaceRoot = this.contextService.getWorkspace().resource.fsPath; + if (!paths.isEqualOrParent(resource.fsPath, workspaceRoot)) { return null; // out of workspace not yet supported } - var status = this.gitService.getModel().getStatus().getWorkingTreeStatus().find(workspaceRelativePath); + const repositoryRoot = this.gitService.getModel().getRepositoryRoot(); + if (!paths.isEqualOrParent(resource.fsPath, repositoryRoot)) { + return null; // out of repository not supported + } + + const repositoryRelativePath = paths.normalize(paths.relative(repositoryRoot, resource.fsPath)); + + var status = this.gitService.getModel().getStatus().getWorkingTreeStatus().find(repositoryRelativePath); if (status && (status.getStatus() === git.Status.UNTRACKED || status.getStatus() === git.Status.IGNORED)) { return status; } - status = this.gitService.getModel().getStatus().getMergeStatus().find(workspaceRelativePath); + status = this.gitService.getModel().getStatus().getMergeStatus().find(repositoryRelativePath); if (status) { return status; } diff --git a/src/vs/workbench/parts/git/browser/views/changes/changesViewer.ts b/src/vs/workbench/parts/git/browser/views/changes/changesViewer.ts index 1d5cff6f5e99f5222ff0b875f68aee54cb4fc76d..05c61e0ec7092609e9aa82f749763464e24dd213 100644 --- a/src/vs/workbench/parts/git/browser/views/changes/changesViewer.ts +++ b/src/vs/workbench/parts/git/browser/views/changes/changesViewer.ts @@ -8,6 +8,7 @@ import winjs = require('vs/base/common/winjs.base'); import nls = require('vs/nls'); import platform = require('vs/base/common/platform'); import errors = require('vs/base/common/errors'); +import paths = require('vs/base/common/paths'); import severity from 'vs/base/common/severity'; import lifecycle = require('vs/base/common/lifecycle'); import dom = require('vs/base/browser/dom'); @@ -24,10 +25,12 @@ import actionsrenderer = require('vs/base/parts/tree/browser/actionsRenderer'); import git = require('vs/workbench/parts/git/common/git'); import gitmodel = require('vs/workbench/parts/git/common/gitModel'); import gitactions = require('vs/workbench/parts/git/browser/gitActions'); -import {IContextMenuService} from 'vs/platform/contextview/browser/contextView'; -import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import {IMessageService} from 'vs/platform/message/common/message'; -import {CommonKeybindings} from 'vs/base/common/keyCodes'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IMessageService } from 'vs/platform/message/common/message'; +import { CommonKeybindings } from 'vs/base/common/keyCodes'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import URI from 'vs/base/common/uri'; import IGitService = git.IGitService @@ -218,10 +221,14 @@ interface IStatusGroupTemplateData { export class Renderer implements tree.IRenderer { - private messageService: IMessageService; - - constructor(private actionProvider:ActionProvider, private actionRunner: actions.IActionRunner, @IMessageService messageService: IMessageService) { - this.messageService = messageService; + constructor( + private actionProvider:ActionProvider, + private actionRunner: actions.IActionRunner, + @IMessageService private messageService: IMessageService, + @IGitService private gitService: IGitService, + @IWorkspaceContextService private contextService: IWorkspaceContextService + ) { + // noop } public getHeight(tree:tree.ITree, element:any): number { @@ -298,7 +305,7 @@ export class Renderer implements tree.IRenderer { public renderElement(tree: tree.ITree, element: any, templateId: string, templateData: any): void { if (/^file:/.test(templateId)) { - Renderer.renderFileStatus(tree, element, templateData); + this.renderFileStatus(tree, element, templateData); } else { Renderer.renderStatusGroup( element, templateData); } @@ -309,30 +316,49 @@ export class Renderer implements tree.IRenderer { data.count.setCount(statusGroup.all().length); } - private static renderFileStatus(tree: tree.ITree, fileStatus: git.IFileStatus, data: IFileStatusTemplateData): void { + private renderFileStatus(tree: tree.ITree, fileStatus: git.IFileStatus, data: IFileStatusTemplateData): void { data.actionBar.context = { tree: tree, fileStatus: fileStatus }; - var status = fileStatus.getStatus(); - var renamePath = fileStatus.getRename(); - var path = fileStatus.getPath(); - var lastSlashIndex = path.lastIndexOf('/'); - var name = lastSlashIndex === -1 ? path : path.substr(lastSlashIndex + 1, path.length); - var folder = (lastSlashIndex === -1 ? '' : path.substr(0, lastSlashIndex)); + const repositoryRoot = this.gitService.getModel().getRepositoryRoot(); + const workspaceRoot = this.contextService.getWorkspace().resource.fsPath; + + const status = fileStatus.getStatus(); + const renamePath = fileStatus.getRename(); + const path = fileStatus.getPath(); + const lastSlashIndex = path.lastIndexOf('/'); + const name = lastSlashIndex === -1 ? path : path.substr(lastSlashIndex + 1, path.length); + const folder = (lastSlashIndex === -1 ? '' : path.substr(0, lastSlashIndex)); data.root.className = 'file-status ' + Renderer.statusToClass(status); data.status.textContent = Renderer.statusToChar(status); data.status.title = Renderer.statusToTitle(status); + const resource = URI.file(paths.normalize(paths.join(repositoryRoot, path))); + let isInWorkspace = paths.isEqualOrParent(resource.fsPath, workspaceRoot); + + let rename = ''; + let renameFolder = ''; + if (renamePath) { - var renameLastSlashIndex = renamePath.lastIndexOf('/'); - var rename = renameLastSlashIndex === -1 ? renamePath : renamePath.substr(renameLastSlashIndex + 1, renamePath.length); - var renameFolder = (renameLastSlashIndex === -1 ? '' : renamePath.substr(0, renameLastSlashIndex)); + const renameLastSlashIndex = renamePath.lastIndexOf('/'); + rename = renameLastSlashIndex === -1 ? renamePath : renamePath.substr(renameLastSlashIndex + 1, renamePath.length); + renameFolder = (renameLastSlashIndex === -1 ? '' : renamePath.substr(0, renameLastSlashIndex)); data.renameName.textContent = name; data.renameFolder.textContent = folder; + + const resource = URI.file(paths.normalize(paths.join(repositoryRoot, renamePath))); + isInWorkspace = paths.isEqualOrParent(resource.fsPath, workspaceRoot) + } + + if (isInWorkspace) { + data.root.title = ''; + } else { + data.root.title = nls.localize('outsideOfWorkspace', "This file is located outside the current workspace."); + data.root.className += ' out-of-workspace'; } data.name.textContent = rename || name; diff --git a/src/vs/workbench/parts/git/common/git.ts b/src/vs/workbench/parts/git/common/git.ts index 9d40c76d5b6481193e68209fbfa6f0e8d4a63011..0e006bd1db42d0ffb5b81ea43dd46e4199f282fb 100644 --- a/src/vs/workbench/parts/git/common/git.ts +++ b/src/vs/workbench/parts/git/common/git.ts @@ -38,6 +38,7 @@ export interface ITag { } export interface IRawStatus { + repositoryRoot: string; state?: ServiceState; status: IRawFileStatus[]; HEAD: IBranch; @@ -113,7 +114,7 @@ export interface IStatusSummary { export interface IStatusModel extends EventEmitter.IEventEmitter { getSummary(): IStatusSummary; - update(rawStatuses: IRawFileStatus[]): void; + update(status: IRawFileStatus[]): void; getIndexStatus(): IStatusGroup; getWorkingTreeStatus(): IStatusGroup; getMergeStatus(): IStatusGroup; @@ -122,6 +123,7 @@ export interface IStatusModel extends EventEmitter.IEventEmitter { } export interface IModel extends EventEmitter.IEventEmitter { + getRepositoryRoot(): string; getStatus(): IStatusModel; getHEAD(): IBranch; getHeads(): IBranch[]; diff --git a/src/vs/workbench/parts/git/common/gitModel.ts b/src/vs/workbench/parts/git/common/gitModel.ts index f535be16ff142e9d7e341d80203fb626abd23c5b..0a74d1d1139125536d6a21fd3f823eaee67d6bda 100644 --- a/src/vs/workbench/parts/git/common/gitModel.ts +++ b/src/vs/workbench/parts/git/common/gitModel.ts @@ -12,19 +12,17 @@ import Git = require('vs/workbench/parts/git/common/git'); export class FileStatus implements Git.IFileStatus { private id: string; - private path: string; private pathComponents: string[]; - private mimetype: string; - private status: Git.Status; - private rename: string; - constructor(path: string, mimetype: string, status: Git.Status, rename?: string, isModifiedInIndex?: boolean) { + constructor( + private path: string, + private mimetype: string, + private status: Git.Status, + private rename?: string, + isModifiedInIndex?: boolean + ) { this.id = FileStatus.typeOf(status) + ':' + path + (rename ? ':' + rename : '') + (isModifiedInIndex ? '$' : ''); - this.path = path; this.pathComponents = path.split('/'); - this.mimetype = mimetype; - this.rename = rename; - this.status = status; } public getPath(): string { @@ -211,27 +209,25 @@ export class StatusModel extends EventEmitter.EventEmitter implements Git.IStatu }; } - public update(rawStatuses: Git.IRawFileStatus[]): void { + public update(status: Git.IRawFileStatus[]): void { var index: FileStatus[] = []; var workingTree: FileStatus[] = []; var merge: FileStatus[] = []; - for (var i = 0; i < rawStatuses.length; i++) { - var raw = rawStatuses[i]; - + status.forEach(raw => { switch(raw.x + raw.y) { - case '??': workingTree.push(new FileStatus(raw.path, raw.mimetype, Git.Status.UNTRACKED)); continue; - case '!!': workingTree.push(new FileStatus(raw.path, raw.mimetype, Git.Status.IGNORED)); continue; - case 'DD': merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.BOTH_DELETED)); continue; - case 'AU': merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.ADDED_BY_US)); continue; - case 'UD': merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.DELETED_BY_THEM)); continue; - case 'UA': merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.ADDED_BY_THEM)); continue; - case 'DU': merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.DELETED_BY_US)); continue; - case 'AA': merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.BOTH_ADDED)); continue; - case 'UU': merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.BOTH_MODIFIED)); continue; + case '??': return workingTree.push(new FileStatus(raw.path, raw.mimetype, Git.Status.UNTRACKED)); + case '!!': return workingTree.push(new FileStatus(raw.path, raw.mimetype, Git.Status.IGNORED)); + case 'DD': return merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.BOTH_DELETED)); + case 'AU': return merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.ADDED_BY_US)); + case 'UD': return merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.DELETED_BY_THEM)); + case 'UA': return merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.ADDED_BY_THEM)); + case 'DU': return merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.DELETED_BY_US)); + case 'AA': return merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.BOTH_ADDED)); + case 'UU': return merge.push(new FileStatus(raw.path, raw.mimetype, Git.Status.BOTH_MODIFIED)); } - var isModifiedInIndex = false; + let isModifiedInIndex = false; switch (raw.x) { case 'M': index.push(new FileStatus(raw.path, raw.mimetype, Git.Status.INDEX_MODIFIED)); isModifiedInIndex = true; break; @@ -245,7 +241,7 @@ export class StatusModel extends EventEmitter.EventEmitter implements Git.IStatu case 'M': workingTree.push(new FileStatus(raw.path, raw.mimetype, Git.Status.MODIFIED, raw.rename, isModifiedInIndex)); break; case 'D': workingTree.push(new FileStatus(raw.path, raw.mimetype, Git.Status.DELETED, raw.rename)); break; } - } + }); this.indexStatus.update(index); this.workingTreeStatus.update(workingTree); @@ -311,6 +307,7 @@ export class StatusModel extends EventEmitter.EventEmitter implements Git.IStatu export class Model extends EventEmitter.EventEmitter implements Git.IModel { + private repositoryRoot: string; private status: Git.IStatusModel; private HEAD: Git.IBranch; private heads: Git.IBranch[]; @@ -322,6 +319,7 @@ export class Model extends EventEmitter.EventEmitter implements Git.IModel { this.toDispose = []; + this.repositoryRoot = null; this.status = new StatusModel(); this.toDispose.push(this.addEmitter2(this.status)); @@ -330,6 +328,10 @@ export class Model extends EventEmitter.EventEmitter implements Git.IModel { this.tags = []; } + public getRepositoryRoot(): string { + return this.repositoryRoot; + } + public getStatus(): Git.IStatusModel { return this.status; } @@ -349,6 +351,7 @@ export class Model extends EventEmitter.EventEmitter implements Git.IModel { public update(status: Git.IRawStatus): void { if (!status) { status = { + repositoryRoot: null, status: [], HEAD: null, heads: [], @@ -356,6 +359,7 @@ export class Model extends EventEmitter.EventEmitter implements Git.IModel { }; } + this.repositoryRoot = status.repositoryRoot; this.status.update(status.status); this.HEAD = status.HEAD; diff --git a/src/vs/workbench/parts/git/common/noopGitService.ts b/src/vs/workbench/parts/git/common/noopGitService.ts index 9a160826506999ff41fba9d086d8641b167727f3..295df50a36a5bfe7fef7eb45d1000f769e9da993 100644 --- a/src/vs/workbench/parts/git/common/noopGitService.ts +++ b/src/vs/workbench/parts/git/common/noopGitService.ts @@ -9,6 +9,7 @@ import winjs = require('vs/base/common/winjs.base'); export class NoOpGitService implements git.IRawGitService { private static STATUS:git.IRawStatus = { + repositoryRoot: null, state: git.ServiceState.NotAWorkspace, status: [], HEAD: null, diff --git a/src/vs/workbench/parts/git/electron-browser/electronGitService.ts b/src/vs/workbench/parts/git/electron-browser/electronGitService.ts index 2323c3ba04dd61c333f5180784e4ce327a8b82b7..cef426839e79e1cf2fd8ce30d3f5125f36f6ea32 100644 --- a/src/vs/workbench/parts/git/electron-browser/electronGitService.ts +++ b/src/vs/workbench/parts/git/electron-browser/electronGitService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Promise, TPromise } from 'vs/base/common/winjs.base'; -import { IRawGitService, IRawStatus, RawServiceState } from 'vs/workbench/parts/git/common/git'; +import { RawServiceState } from 'vs/workbench/parts/git/common/git'; import { NoOpGitService } from 'vs/workbench/parts/git/common/noopGitService'; import { GitService } from 'vs/workbench/parts/git/browser/gitServices'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -12,12 +12,11 @@ import { IOutputService } from 'vs/workbench/parts/output/common/output'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEventService } from 'vs/platform/event/common/event'; -import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService } from 'vs/platform/message/common/message'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Client } from 'vs/base/node/service.cp'; -import { RawGitService } from 'vs/workbench/parts/git/node/rawGitService'; +import { RawGitService, DelayedRawGitService } from 'vs/workbench/parts/git/node/rawGitService'; import URI from 'vs/base/common/uri'; import { spawn, exec } from 'child_process'; import { join } from 'path'; @@ -102,17 +101,16 @@ class DisabledRawGitService extends RawGitService { } } -export function createNativeRawGitService(basePath: string, gitPath: string, defaultEncoding: string): Promise { +export function createNativeRawGitService(workspaceRoot: string, gitPath: string, defaultEncoding: string): Promise { return findGit(gitPath).then(gitPath => { const client = new Client( URI.parse(require.toUrl('bootstrap')).fsPath, { serverName: 'Git', timeout: 1000 * 60, - args: [gitPath, basePath, defaultEncoding, remote.process.execPath], + args: [gitPath, workspaceRoot, defaultEncoding, remote.process.execPath], env: { ATOM_SHELL_INTERNAL_RUN_AS_NODE: 1, - PIPE_LOGGING: 'true', AMD_ENTRYPOINT: 'vs/workbench/parts/git/electron-browser/gitApp' } } @@ -122,12 +120,9 @@ export function createNativeRawGitService(basePath: string, gitPath: string, def }, () => new UnavailableRawGitService()); } -class ElectronRawGitService implements IRawGitService { - - private raw: TPromise; - - constructor(basePath: string, @IConfigurationService configurationService: IConfigurationService) { - this.raw = configurationService.loadConfiguration().then(conf => { +class ElectronRawGitService extends DelayedRawGitService { + constructor(workspaceRoot: string, @IConfigurationService configurationService: IConfigurationService) { + super(configurationService.loadConfiguration().then(conf => { var enabled = conf.git ? conf.git.enabled : true; if (!enabled) { @@ -137,89 +132,12 @@ class ElectronRawGitService implements IRawGitService { var gitPath = (conf.git && conf.git.path) || null; var encoding = (conf.files && conf.files.encoding) || 'utf8'; - return createNativeRawGitService(basePath, gitPath, encoding); - }); - } - - public serviceState(): TPromise { - return this.raw.then(raw => raw.serviceState()); - } - - public status(): TPromise { - return this.raw.then(raw => raw.status()); - } - - public init(): TPromise { - return this.raw.then(raw => raw.init()); - } - - public add(filesPaths?: string[]): TPromise { - return this.raw.then(raw => raw.add(filesPaths)); - } - - public stage(filePath: string, content: string): TPromise { - return this.raw.then(raw => raw.stage(filePath, content)); - } - - public branch(name: string, checkout?: boolean): TPromise { - return this.raw.then(raw => raw.branch(name, checkout)); - } - - public checkout(treeish?: string, filePaths?: string[]): TPromise { - return this.raw.then(raw => raw.checkout(treeish, filePaths)); - } - - public clean(filePaths: string[]): TPromise { - return this.raw.then(raw => raw.clean(filePaths)); - } - - public undo(): TPromise { - return this.raw.then(raw => raw.undo()); - } - - public reset(treeish: string, hard?: boolean): TPromise { - return this.raw.then(raw => raw.reset(treeish, hard)); - } - - public revertFiles(treeish: string, filePaths?: string[]): TPromise { - return this.raw.then(raw => raw.revertFiles(treeish, filePaths)); - } - - public fetch(): TPromise { - return this.raw.then(raw => raw.fetch()); - } - - public pull(): TPromise { - return this.raw.then(raw => raw.pull()); - } - - public push(): TPromise { - return this.raw.then(raw => raw.push()); - } - - public sync(): TPromise { - return this.raw.then(raw => raw.sync()); - } - - public commit(message: string, amend?: boolean, stage?: boolean): TPromise { - return this.raw.then(raw => raw.commit(message, amend, stage)); - } - - public detectMimetypes(path: string, treeish?: string): TPromise { - return this.raw.then(raw => raw.detectMimetypes(path, treeish)); - } - - public show(path: string, treeish?: string): TPromise { - return this.raw.then(raw => raw.show(path, treeish)); - } - - public onOutput(): Promise { - return this.raw.then(raw => raw.onOutput()); + return createNativeRawGitService(workspaceRoot, gitPath, encoding); + })); } } export class ElectronGitService extends GitService { - constructor( @IInstantiationService instantiationService: IInstantiationService, @IEventService eventService: IEventService, diff --git a/src/vs/workbench/parts/git/electron-browser/gitApp.ts b/src/vs/workbench/parts/git/electron-browser/gitApp.ts index 0b81bf5eb6d22c80e4569e1d2f77c2b9553931bf..aa031d25ce6cf611db3eac828f9378c2960d40ac 100644 --- a/src/vs/workbench/parts/git/electron-browser/gitApp.ts +++ b/src/vs/workbench/parts/git/electron-browser/gitApp.ts @@ -4,60 +4,50 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import { TPromise } from 'vs/base/common/winjs.base'; import { Server } from 'vs/base/node/service.cp'; import objects = require('vs/base/common/objects'); import uri from 'vs/base/common/uri'; +import { IRawGitService } from 'vs/workbench/parts/git/common/git'; import gitlib = require('vs/workbench/parts/git/node/git.lib'); -import rawgitservice = require('vs/workbench/parts/git/node/rawGitService'); +import { RawGitService, DelayedRawGitService } from 'vs/workbench/parts/git/node/rawGitService'; +import { join, dirname, normalize } from 'path'; +import { tmpdir } from 'os'; +import { realpath } from 'vs/base/node/pfs'; -import path = require('path'); -import fs = require('fs'); +class IPCRawGitService extends DelayedRawGitService { -class NativeRawGitService extends rawgitservice.RawGitService { - - constructor(gitPath: string, basePath: string, defaultEncoding: string, exePath: string) { + constructor(gitPath: string, workspaceRoot: string, defaultEncoding: string, exePath: string) { if (!gitPath) { - super(null); - return; - } - - var gitRootPath = uri.parse(require.toUrl('vs/workbench/parts/git/electron-main')).fsPath; - - var env = objects.assign(objects.assign({}, process.env), { - GIT_ASKPASS: path.join(gitRootPath, 'askpass.sh'), - VSCODE_GIT_ASKPASS_BOOTSTRAP: path.join(path.dirname(path.dirname(path.dirname(path.dirname(path.dirname(gitRootPath))))), 'bootstrap.js'), - VSCODE_GIT_ASKPASS_NODE: exePath, - VSCODE_GIT_ASKPASS_MODULE_ID: 'vs/workbench/parts/git/electron-main/askpass' - }); - - var git = new gitlib.Git({ - gitPath: gitPath, - tmpPath: tmpdirSync(), // TODO@Joao os.tmpdir()??? - defaultEncoding: defaultEncoding, - env: env - }); - - super(git.open(path.normalize(basePath))); - } -} - -function tmpdirSync(): string { - var path: string; - var paths = /^win/i.test(process.platform) ? [process.env.TMP, process.env.TEMP] : ['/tmp', '/var/tmp', '/private/tmp', '/private/var/tmp']; - - for (var i = 0; i < paths.length; i++) { - path = paths[i]; - try { - if (fs.statSync(path).isDirectory()) { - return path; - } - } catch (e) { - // Ignore + super(TPromise.as(new RawGitService(null))); + } else { + const gitRootPath = uri.parse(require.toUrl('vs/workbench/parts/git/electron-main')).fsPath; + const bootstrapPath = `${ uri.parse(require.toUrl('bootstrap')).fsPath }.js`; + + const env = objects.assign({}, process.env, { + GIT_ASKPASS: join(gitRootPath, 'askpass.sh'), + VSCODE_GIT_ASKPASS_BOOTSTRAP: bootstrapPath, + VSCODE_GIT_ASKPASS_NODE: exePath, + VSCODE_GIT_ASKPASS_MODULE_ID: 'vs/workbench/parts/git/electron-main/askpass' + }); + + const git = new gitlib.Git({ + gitPath: gitPath, + tmpPath: tmpdir(), + defaultEncoding: defaultEncoding, + env: env + }); + + const repo = git.open(normalize(workspaceRoot)); + const promise = repo.getRoot() + .then(root => realpath(root)) + .then(root => git.open(root)) + .then(repo => new RawGitService(repo)); + + super(promise); } } - - throw new Error('Temp dir not found'); } const server = new Server(); -server.registerService('GitService', new NativeRawGitService(process.argv[2], process.argv[3], process.argv[4], process.argv[5])); \ No newline at end of file +server.registerService('GitService', new IPCRawGitService(process.argv[2], process.argv[3], process.argv[4], process.argv[5])); \ No newline at end of file diff --git a/src/vs/workbench/parts/git/node/git.lib.ts b/src/vs/workbench/parts/git/node/git.lib.ts index e55d21b2aab1d2c5d8a6a191fd1ac4f9fb95465b..102b012f8d54ae72a824802ea5881e71ca016364 100644 --- a/src/vs/workbench/parts/git/node/git.lib.ts +++ b/src/vs/workbench/parts/git/node/git.lib.ts @@ -2,12 +2,11 @@ * 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 winjs = require('vs/base/common/winjs.base'); +import { Promise, TPromise } from 'vs/base/common/winjs.base'; import extfs = require('vs/base/node/extfs'); import { guessMimeTypes, isBinaryMime } from 'vs/base/common/mime'; +import { IDisposable, toDisposable, disposeAll } from 'vs/base/common/lifecycle'; import objects = require('vs/base/common/objects'); import uuid = require('vs/base/common/uuid'); import nls = require('vs/nls'); @@ -15,16 +14,54 @@ import strings = require('vs/base/common/strings'); import { IRawFileStatus, IHead, ITag, IBranch, GitErrorCodes } from 'vs/workbench/parts/git/common/git'; import { detectMimesFromStream } from 'vs/base/node/mime' import files = require('vs/platform/files/common/files'); - -import cp = require('child_process'); +import { spawn, ChildProcess } from 'child_process'; import iconv = require('iconv-lite'); export interface IExecutionResult { - code: number; + exitCode: number; stdout: string; stderr: string; } +function exec(child: ChildProcess, encoding = 'utf8'): TPromise { + const disposables: IDisposable[] = []; + + const once = (ee: EventEmitter, name: string, fn: Function) => { + ee.once(name, fn); + disposables.push(toDisposable(() => ee.removeListener(name, fn))); + }; + + const on = (ee: EventEmitter, name: string, fn: Function) => { + ee.on(name, fn); + disposables.push(toDisposable(() => ee.removeListener(name, fn))); + }; + + return TPromise.join([ + new TPromise((c, e) => { + once(child, 'error', e); + once(child, 'exit', c); + }), + new TPromise(c => { + let buffers: Buffer[] = []; + on(child.stdout, 'data', b => buffers.push(b)); + once(child.stdout, 'close', () => c(Buffer.concat(buffers).toString(encoding))); + }), + new TPromise(c => { + let buffers: Buffer[] = []; + on(child.stderr, 'data', b => buffers.push(b)); + once(child.stderr, 'close', () => c(Buffer.concat(buffers).toString(encoding))); + }) + ]).then(values => { + disposeAll(disposables); + + return { + exitCode: values[0], + stdout: values[1], + stderr: values[2] + }; + }); +} + export interface IGitErrorData { error?: Error; message?: string; @@ -62,7 +99,7 @@ export class GitError { } public toString(): string { - var result = this.message + ' ' + JSON.stringify({ + let result = this.message + ' ' + JSON.stringify({ exitCode: this.exitCode, gitErrorCode: this.gitErrorCode, gitCommand: this.gitCommand, @@ -97,19 +134,19 @@ export class Git { this.gitPath = options.gitPath; this.tmpPath = options.tmpPath; - var encoding = options.defaultEncoding || 'utf8'; + const encoding = options.defaultEncoding || 'utf8'; this.defaultEncoding = iconv.encodingExists(encoding) ? encoding : 'utf8'; this.env = options.env || {}; this.outputListeners = []; } - public run(cwd: string, args: string[], options: any = {}): winjs.TPromise { + public run(cwd: string, args: string[], options: any = {}): TPromise { options = objects.assign({ cwd: cwd }, options || {}); return this.exec(args, options); } - public stream(cwd: string, args: string[], options: any = {}): cp.ChildProcess { + public stream(cwd: string, args: string[], options: any = {}): ChildProcess { options = objects.assign({ cwd: cwd }, options || {}); return this.spawn(args, options); } @@ -118,9 +155,9 @@ export class Git { return new Repository(this, repository, this.defaultEncoding, env); } - public clone(repository: string, repoURL: string): winjs.TPromise { + public clone(repository: string, repoURL: string): TPromise { return this.exec(['clone', repoURL, repository]).then(() => true, (err) => { - return new winjs.TPromise((c, e) => { + return new TPromise((c, e) => { // If there's any error, git will still leave the folder in the FS, // so we need to remove it. @@ -132,77 +169,52 @@ export class Git { }); } - public config(name: string, value: string): winjs.Promise { + public config(name: string, value: string): Promise { return this.exec(['config', '--global', name, value]); } - private exec(args: string[], options: any = {}): winjs.TPromise { - var child = this.spawn(args, options); + private exec(args: string[], options: any = {}): TPromise { + const child = this.spawn(args, options); if (options.input) { child.stdin.end(options.input, 'utf8'); } - return winjs.TPromise.join([ - new winjs.TPromise((c, e) => { - child.on('error', e); - child.on('exit', c); - }), - new winjs.TPromise((c) => { - var buffer:string = ''; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data: string) => buffer += data); - child.stdout.on('close', () => c(buffer)); - }), - new winjs.TPromise((c) => { - var buffer:string = ''; - child.stderr.setEncoding('utf8'); - child.stderr.on('data', (data: string) => buffer += data); - child.stderr.on('close', () => c(buffer)); - }) - ]).then((values) => { - var exitCode = values[0]; - var stdout = values[1]; - var stderr = values[2]; + return exec(child).then(result => { + if (result.exitCode) { + let gitErrorCode: string = null; - if (exitCode) { - var gitErrorCode: string = null; - - if (/Authentication failed/.test(stderr)) { + if (/Authentication failed/.test(result.stderr)) { gitErrorCode = GitErrorCodes.AuthenticationFailed; - } else if (/bad config file/.test(stderr)) { + } else if (/bad config file/.test(result.stderr)) { gitErrorCode = GitErrorCodes.BadConfigFile; - } else if (/cannot make pipe for command substitution|cannot create standard input pipe/.test(stderr)) { + } else if (/cannot make pipe for command substitution|cannot create standard input pipe/.test(result.stderr)) { gitErrorCode = GitErrorCodes.CantCreatePipe; - } else if (/Repository not found/.test(stderr)) { + } else if (/Repository not found/.test(result.stderr)) { gitErrorCode = GitErrorCodes.RepositoryNotFound; - } else if (/unable to access/.test(stderr)) { + } else if (/unable to access/.test(result.stderr)) { gitErrorCode = GitErrorCodes.CantAccessRemote; } if (options.log !== false) { - this.log(stderr); + this.log(result.stderr); } - return winjs.TPromise.wrapError(new GitError({ + return TPromise.wrapError(new GitError({ message: 'Failed to execute git', - stdout: stdout, - stderr: stderr, - exitCode: exitCode, - gitErrorCode: gitErrorCode, + stdout: result.stdout, + stderr: result.stderr, + exitCode: result.exitCode, + gitErrorCode, gitCommand: args[0] })); } - return winjs.TPromise.as({ - code: values[0], - stdout: values[1], - stderr: values[2] - }); + return result; }); } - public spawn(args: string[], options: any = {}): cp.ChildProcess { + public spawn(args: string[], options: any = {}): ChildProcess { if (!this.gitPath) { throw new Error('git could not be found in the system.'); } @@ -227,7 +239,7 @@ export class Git { this.log(strings.format('git {0}\n', args.join(' '))); } - return cp.spawn(this.gitPath, args, options); + return spawn(this.gitPath, args, options); } public onOutput(listener: (output: string) => void): () => void { @@ -258,33 +270,33 @@ export class Repository { return this.repository; } - public run(args: string[], options: any = {}): winjs.TPromise { + public run(args: string[], options: any = {}): TPromise { options.env = objects.assign({}, options.env || {}); options.env = objects.assign(options.env, this.env); return this.git.run(this.repository, args, options); } - public stream(args: string[], options: any = {}): cp.ChildProcess { + public stream(args: string[], options: any = {}): ChildProcess { options.env = objects.assign({}, options.env || {}); options.env = objects.assign(options.env, this.env); return this.git.stream(this.repository, args, options); } - public spawn(args: string[], options: any = {}): cp.ChildProcess { + public spawn(args: string[], options: any = {}): ChildProcess { options.env = objects.assign({}, options.env || {}); options.env = objects.assign(options.env, this.env); return this.git.spawn(args, options); } - public init(): winjs.Promise { + public init(): Promise { return this.run(['init']); } - public config(scope: string, key:string, value:any, options:any): winjs.TPromise { - var args = ['config']; + public config(scope: string, key:string, value:any, options:any): TPromise { + const args = ['config']; if (scope) { args.push('--' + scope); @@ -299,14 +311,14 @@ export class Repository { return this.run(args, options).then((result) => result.stdout); } - public show(object: string): cp.ChildProcess { + public show(object: string): ChildProcess { return this.stream(['show', object]); } - public buffer(object: string): winjs.TPromise { - var child = this.show(object); + public buffer(object: string): TPromise { + const child = this.show(object); - return new winjs.Promise((c, e) => { + return new Promise((c, e) => { detectMimesFromStream(child.stdout, null, (err, result) => { if (err) { e(err); @@ -322,40 +334,23 @@ export class Repository { }); } - private doBuffer(object: string): winjs.TPromise { - var child = this.show(object); - - return winjs.TPromise.join([ - new winjs.TPromise((c, e) => { - child.on('error', e); - child.on('exit', c); - }), - new winjs.TPromise((c) => { - var buffers: NodeBuffer[] = []; - child.stdout.on('data', (b: NodeBuffer) => buffers.push(b)); - child.stdout.on('close', () => c(iconv.decode(Buffer.concat(buffers), this.defaultEncoding))); - }), - new winjs.Promise((c) => { - child.stderr.on('data', (data:string) => { /* Read to free buffer but do not handle */ }); - child.stderr.on('close', () => c(null)); - }) - ]).then((values) => { - var exitCode = values[0]; - var result = values[1]; + private doBuffer(object: string): TPromise { + const child = this.show(object); + return exec(child, this.defaultEncoding).then(({ exitCode, stdout }) => { if (exitCode) { - return winjs.TPromise.wrapError(new GitError({ + return TPromise.wrapError(new GitError({ message: 'Could not buffer object.', - exitCode: exitCode + exitCode })); } - return winjs.TPromise.as(result); + return TPromise.as(stdout); }); } - public add(paths: string[]): winjs.Promise { - var args = ['add', '-A', '--']; + public add(paths: string[]): Promise { + const args = ['add', '-A', '--']; if (paths && paths.length) { args.push.apply(args, paths); @@ -366,42 +361,24 @@ export class Repository { return this.run(args); } - public stage(path: string, data: string): winjs.Promise { - var child = this.stream(['hash-object', '--stdin', '-w'], { stdio: [null, null, null] }); + public stage(path: string, data: string): Promise { + const child = this.stream(['hash-object', '--stdin', '-w'], { stdio: [null, null, null] }); child.stdin.end(data, 'utf8'); - return winjs.TPromise.join([ - new winjs.TPromise((c, e) => { - child.on('error', e); - child.on('exit', c); - }), - new winjs.TPromise((c) => { - var id = ''; - child.stdout.setEncoding('utf8'); - child.stdout.on('data', (data:string) => id += data); - child.stdout.on('close', () => c(id)); - }), - new winjs.Promise((c) => { - child.stderr.on('data', (data:string) => { /* Read to free buffer but do not handle */ }); - child.stderr.on('close', () => c(null)); - }) - ]).then((values) => { - var exitCode = values[0]; - var id = values[1]; - + return exec(child).then(({ exitCode, stdout }) => { if (exitCode) { - return winjs.TPromise.wrapError(new GitError({ + return TPromise.wrapError(new GitError({ message: 'Could not hash object.', exitCode: exitCode })); } - return this.run(['update-index', '--cacheinfo', '100644', id, path]); + return this.run(['update-index', '--cacheinfo', '100644', stdout, path]); }); } - public checkout(treeish: string, paths: string[]): winjs.Promise { - var args = [ 'checkout', '-q' ]; + public checkout(treeish: string, paths: string[]): Promise { + const args = [ 'checkout', '-q' ]; if (treeish) { args.push(treeish); @@ -417,12 +394,12 @@ export class Repository { err.gitErrorCode = GitErrorCodes.DirtyWorkTree; } - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }); } - public commit(message: string, all: boolean, amend: boolean): winjs.Promise { - var args = ['commit', '--quiet', '--allow-empty-message', '--file', '-']; + public commit(message: string, all: boolean, amend: boolean): Promise { + const args = ['commit', '--quiet', '--allow-empty-message', '--file', '-']; if (all) { args.push('--all'); @@ -435,47 +412,47 @@ export class Repository { return this.run(args, { input: message || '' }).then(null, (commitErr: GitError) => { if (/not possible because you have unmerged files/.test(commitErr.stderr)) { commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges; - return winjs.Promise.wrapError(commitErr); + return Promise.wrapError(commitErr); } return this.run(['config', '--get-all', 'user.name']).then(null, (err: GitError) => { err.gitErrorCode = GitErrorCodes.NoUserNameConfigured; - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }).then(() => { return this.run(['config', '--get-all', 'user.email']).then(null, (err: GitError) => { err.gitErrorCode = GitErrorCodes.NoUserEmailConfigured; - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }).then(() => { - return winjs.Promise.wrapError(commitErr); + return Promise.wrapError(commitErr); }); }); }); } - public branch(name: string, checkout: boolean): winjs.Promise { - var args = checkout ? ['checkout', '-q', '-b', name] : [ 'branch', '-q', name ]; + public branch(name: string, checkout: boolean): Promise { + const args = checkout ? ['checkout', '-q', '-b', name] : [ 'branch', '-q', name ]; return this.run(args); } - public clean(paths: string[]): winjs.Promise { - var args = [ 'clean', '-f', '-q', '--' ].concat(paths); + public clean(paths: string[]): Promise { + const args = [ 'clean', '-f', '-q', '--' ].concat(paths); return this.run(args); } - public undo(): winjs.Promise { + public undo(): Promise { return this.run([ 'clean', '-fd' ]).then(() => { return this.run([ 'checkout', '--', '.' ]).then(null, (err: GitError) => { if (/did not match any file\(s\) known to git\./.test(err.stderr)) { - return winjs.Promise.as(null); + return Promise.as(null); } - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }); }); } - public reset(treeish: string, hard: boolean = false): winjs.Promise { - var args = ['reset']; + public reset(treeish: string, hard: boolean = false): Promise { + const args = ['reset']; if (hard) { args.push('--hard'); @@ -486,9 +463,9 @@ export class Repository { return this.run(args); } - public revertFiles(treeish: string, paths: string[]): winjs.Promise { + public revertFiles(treeish: string, paths: string[]): Promise { return this.run([ 'branch' ]).then((result) => { - var args: string[]; + let args: string[]; // In case there are no branches, we must use rm --cached if (!result.stdout) { @@ -507,15 +484,15 @@ export class Repository { // In case there are merge conflicts to be resolved, git reset will output // some "needs merge" data. We try to get around that. if (/([^:]+: needs merge\n)+/m.test(err.stdout)) { - return winjs.Promise.as(null); + return Promise.as(null); } - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }); }); } - public fetch(): winjs.Promise { + public fetch(): Promise { return this.run(['fetch']).then(null, (err: GitError) => { if (/No remote repository specified\./.test(err.stderr)) { err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified; @@ -525,11 +502,11 @@ export class Repository { err.gitErrorCode = GitErrorCodes.RemoteConnectionError; } - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }); } - public pull(): winjs.Promise { + public pull(): Promise { return this.run(['pull']).then(null, (err: GitError) => { if (/^CONFLICT \([^)]+\): \b/m.test(err.stdout)) { err.gitErrorCode = GitErrorCodes.Conflict; @@ -541,11 +518,11 @@ export class Repository { err.gitErrorCode = GitErrorCodes.DirtyWorkTree; } - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }); } - public push(): winjs.Promise { + public push(): Promise { return this.run(['push']).then(null, (err: GitError) => { if (/^error: failed to push some refs to\b/m.test(err.stderr)) { err.gitErrorCode = GitErrorCodes.PushRejected; @@ -553,27 +530,28 @@ export class Repository { err.gitErrorCode = GitErrorCodes.RemoteConnectionError; } - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }); } - public sync(): winjs.Promise { + public sync(): Promise { return this.pull().then(() => this.push()); } - public getRoot(): winjs.TPromise { + public getRoot(): TPromise { return this.run(['rev-parse', '--show-toplevel'], { log: false }).then(result => result.stdout.trim()); } - public getStatus(): winjs.TPromise { + public getStatus(): TPromise { return this.run(['status', '-z', '-u'], { log: false }).then((executionResult) => { - var status = executionResult.stdout; - var result:IRawFileStatus[] = []; - var current:IRawFileStatus; - var i = 0; + const status = executionResult.stdout; + const result:IRawFileStatus[] = []; + let current:IRawFileStatus; + let i = 0; function readName():string { - var start = i, c:string; + const start = i; + let c:string; while ((c = status.charAt(i)) !== '\u0000') { i++; } return status.substring(start, i++); } @@ -603,29 +581,29 @@ export class Repository { result.push(current); } - return winjs.TPromise.as(result); + return TPromise.as(result); }); } - public getHEAD(): winjs.TPromise { + public getHEAD(): TPromise { return this.run(['symbolic-ref', '--short', 'HEAD'], { log: false }).then((result) => { if (!result.stdout) { - return winjs.TPromise.wrapError(new Error('Not in a branch')); + return TPromise.wrapError(new Error('Not in a branch')); } - return winjs.TPromise.as({ name: result.stdout.trim() }); + return TPromise.as({ name: result.stdout.trim() }); }, (err) => { return this.run(['rev-parse', 'HEAD'], { log: false }).then((result) => { if (!result.stdout) { - return winjs.TPromise.wrapError(new Error('Error parsing HEAD')); + return TPromise.wrapError(new Error('Error parsing HEAD')); } - return winjs.TPromise.as({ commit: result.stdout.trim() }); + return TPromise.as({ commit: result.stdout.trim() }); }); }); } - public getHeads(): winjs.TPromise { + public getHeads(): TPromise { return this.run(['for-each-ref', '--format', '%(refname:short) %(objectname)', 'refs/heads/'], { log: false }).then((result) => { return result.stdout.trim().split('\n') .filter(b => !!b) @@ -634,7 +612,7 @@ export class Repository { }); } - public getTags(): winjs.TPromise { + public getTags(): TPromise { return this.run(['for-each-ref', '--format', '%(refname:short) %(objectname)', 'refs/tags/'], { log: false }).then((result) => { return result.stdout.trim().split('\n') .filter(b => !!b) @@ -643,24 +621,24 @@ export class Repository { }); } - public getBranch(branch: string): winjs.TPromise { + public getBranch(branch: string): TPromise { if (branch === 'HEAD') { return this.getHEAD(); } return this.run(['rev-parse', branch], { log: false }).then((result) => { if (!result.stdout) { - return winjs.TPromise.wrapError(new Error('No such branch')); + return TPromise.wrapError(new Error('No such branch')); } - var commit = result.stdout.trim(); + const commit = result.stdout.trim(); return this.run(['rev-parse', '--symbolic-full-name', '--abbrev-ref', branch + '@{u}'], { log: false }).then((result: IExecutionResult) => { - var upstream = result.stdout.trim(); + const upstream = result.stdout.trim(); return this.run(['rev-list', '--left-right', branch + '...' + upstream], { log: false }).then((result) => { - var ahead = 0, behind = 0; - var i = 0; + let ahead = 0, behind = 0; + let i = 0; while (i < result.stdout.length) { switch (result.stdout.charAt(i)) { diff --git a/src/vs/workbench/parts/git/node/rawGitService.ts b/src/vs/workbench/parts/git/node/rawGitService.ts index 18cbd3c63b3468fad6bac7ebd0bc1477a45f22f5..0cb084750c5e8217e0a1b06ee8f085bf876d6278 100644 --- a/src/vs/workbench/parts/git/node/rawGitService.ts +++ b/src/vs/workbench/parts/git/node/rawGitService.ts @@ -5,7 +5,7 @@ 'use strict'; import path = require('path'); -import winjs = require('vs/base/common/winjs.base'); +import { TPromise, Promise } from 'vs/base/common/winjs.base'; import mime = require('vs/base/node/mime'); import pfs = require('vs/base/node/pfs'); import { Repository, GitError } from 'vs/workbench/parts/git/node/git.lib'; @@ -23,23 +23,25 @@ function pathsAreEqual(p1: string, p2: string): boolean { export class RawGitService implements IRawGitService { private repo: Repository; - private repoRealRootPath: winjs.TPromise; + private _repositoryRoot: TPromise; constructor(repo: Repository) { this.repo = repo; - this.repoRealRootPath = null; } - public serviceState(): winjs.TPromise { - return winjs.TPromise.as(this.repo + private getRepositoryRoot(): TPromise { + return this._repositoryRoot || (this._repositoryRoot = pfs.realpath(this.repo.path)); + } + + public serviceState(): TPromise { + return TPromise.as(this.repo ? RawServiceState.OK : RawServiceState.GitNotFound ); } - public status(): winjs.TPromise { - return this.checkRoot() - .then(() => this.repo.getStatus()) + public status(): TPromise { + return this.repo.getStatus() .then(status => this.repo.getHEAD() .then(HEAD => { if (HEAD.name) { @@ -48,85 +50,86 @@ export class RawGitService implements IRawGitService { return HEAD; } }, (): IHead => null) - .then(HEAD => winjs.Promise.join([this.repo.getHeads(), this.repo.getTags()]).then(r => { + .then(HEAD => Promise.join([this.getRepositoryRoot(), this.repo.getHeads(), this.repo.getTags()]).then(r => { return { + repositoryRoot: r[0], status: status, HEAD: HEAD, - heads: r[0], - tags: r[1] + heads: r[1], + tags: r[2] }; }))) .then(null, (err) => { if (err.gitErrorCode === GitErrorCodes.BadConfigFile) { - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); } else if (err.gitErrorCode === GitErrorCodes.NotAtRepositoryRoot) { - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); } return null; }); } - public init(): winjs.TPromise { + public init(): TPromise { return this.repo.init().then(() => this.status()); } - public add(filePaths?: string[]): winjs.TPromise { + public add(filePaths?: string[]): TPromise { return this.repo.add(filePaths).then(() => this.status()); } - public stage(filePath: string, content: string): winjs.TPromise { + public stage(filePath: string, content: string): TPromise { return this.repo.stage(filePath, content).then(() => this.status()); } - public branch(name: string, checkout?: boolean): winjs.TPromise { + public branch(name: string, checkout?: boolean): TPromise { return this.repo.branch(name, checkout).then(() => this.status()); } - public checkout(treeish?: string, filePaths?: string[]): winjs.TPromise { + public checkout(treeish?: string, filePaths?: string[]): TPromise { return this.repo.checkout(treeish, filePaths).then(() => this.status()); } - public clean(filePaths: string[]): winjs.TPromise { + public clean(filePaths: string[]): TPromise { return this.repo.clean(filePaths).then(() => this.status()); } - public undo(): winjs.TPromise { + public undo(): TPromise { return this.repo.undo().then(() => this.status()); } - public reset(treeish: string, hard?: boolean): winjs.TPromise { + public reset(treeish: string, hard?: boolean): TPromise { return this.repo.reset(treeish, hard).then(() => this.status()); } - public revertFiles(treeish: string, filePaths?: string[]): winjs.TPromise { + public revertFiles(treeish: string, filePaths?: string[]): TPromise { return this.repo.revertFiles(treeish, filePaths).then(() => this.status()); } - public fetch(): winjs.TPromise { + public fetch(): TPromise { return this.repo.fetch().then(null, (err) => { if (err.gitErrorCode === GitErrorCodes.NoRemoteRepositorySpecified) { - return winjs.Promise.as(null); + return Promise.as(null); } - return winjs.Promise.wrapError(err); + return Promise.wrapError(err); }).then(() => this.status()); } - public pull(): winjs.TPromise { + public pull(): TPromise { return this.repo.pull().then(() => this.status()); } - public push(): winjs.TPromise { + public push(): TPromise { return this.repo.push().then(() => this.status()); } - public sync(): winjs.TPromise { + public sync(): TPromise { return this.repo.sync().then(() => this.status()); } - public commit(message:string, amend?: boolean, stage?: boolean): winjs.TPromise { - var promise: winjs.Promise = winjs.Promise.as(null); + public commit(message:string, amend?: boolean, stage?: boolean): TPromise { + var promise: Promise = Promise.as(null); if (stage) { promise = this.repo.add(null); @@ -137,10 +140,10 @@ export class RawGitService implements IRawGitService { .then(() => this.status()); } - public detectMimetypes(filePath: string, treeish?: string): winjs.TPromise { + public detectMimetypes(filePath: string, treeish?: string): TPromise { return pfs.exists(path.join(this.repo.path, filePath)).then((exists) => { if (exists) { - return new winjs.TPromise((c, e) => { + return new TPromise((c, e) => { mime.detectMimesFromFile(path.join(this.repo.path, filePath), (err, result) => { if (err) { e(err); } else { c(result.mimes); } @@ -150,7 +153,7 @@ export class RawGitService implements IRawGitService { var child = this.repo.show(treeish + ':' + filePath); - return new winjs.TPromise((c, e) => { + return new TPromise((c, e) => { mime.detectMimesFromStream(child.stdout, filePath, (err, result) => { if (err) { e(err); } else { c(result.mimes); } @@ -160,42 +163,103 @@ export class RawGitService implements IRawGitService { } // careful, this buffers the whole object into memory - public show(filePath: string, treeish?: string): winjs.TPromise { + public show(filePath: string, treeish?: string): TPromise { treeish = treeish === '~' ? '' : treeish; return this.repo.buffer(treeish + ':' + filePath).then(null, e => { if (e instanceof GitError) { return ''; // mostly untracked files end up in a git error } - return winjs.TPromise.wrapError(e); + return TPromise.wrapError(e); }); } - public onOutput(): winjs.Promise { + public onOutput(): Promise { var cancel: () => void; - return new winjs.Promise((c, e, p) => { + return new Promise((c, e, p) => { cancel = this.repo.onOutput(p); }, () => cancel()); } +} - private checkRoot(): winjs.Promise { - if (!this.repoRealRootPath) { - this.repoRealRootPath = pfs.realpath(this.repo.path); - } +export class DelayedRawGitService implements IRawGitService { - return this.repo.getRoot().then(root => { - return winjs.Promise.join([ - this.repoRealRootPath, - pfs.realpath(root) - ]).then(paths => { - if (!pathsAreEqual(paths[0], paths[1])) { - return winjs.Promise.wrapError(new GitError({ - message: 'Not at the repository root', - gitErrorCode: GitErrorCodes.NotAtRepositoryRoot - })); - } - }); - }); + constructor(private raw: TPromise) { } + + public serviceState(): TPromise { + return this.raw.then(raw => raw.serviceState()); } -} + + public status(): TPromise { + return this.raw.then(raw => raw.status()); + } + + public init(): TPromise { + return this.raw.then(raw => raw.init()); + } + + public add(filesPaths?: string[]): TPromise { + return this.raw.then(raw => raw.add(filesPaths)); + } + + public stage(filePath: string, content: string): TPromise { + return this.raw.then(raw => raw.stage(filePath, content)); + } + + public branch(name: string, checkout?: boolean): TPromise { + return this.raw.then(raw => raw.branch(name, checkout)); + } + + public checkout(treeish?: string, filePaths?: string[]): TPromise { + return this.raw.then(raw => raw.checkout(treeish, filePaths)); + } + + public clean(filePaths: string[]): TPromise { + return this.raw.then(raw => raw.clean(filePaths)); + } + + public undo(): TPromise { + return this.raw.then(raw => raw.undo()); + } + + public reset(treeish: string, hard?: boolean): TPromise { + return this.raw.then(raw => raw.reset(treeish, hard)); + } + + public revertFiles(treeish: string, filePaths?: string[]): TPromise { + return this.raw.then(raw => raw.revertFiles(treeish, filePaths)); + } + + public fetch(): TPromise { + return this.raw.then(raw => raw.fetch()); + } + + public pull(): TPromise { + return this.raw.then(raw => raw.pull()); + } + + public push(): TPromise { + return this.raw.then(raw => raw.push()); + } + + public sync(): TPromise { + return this.raw.then(raw => raw.sync()); + } + + public commit(message: string, amend?: boolean, stage?: boolean): TPromise { + return this.raw.then(raw => raw.commit(message, amend, stage)); + } + + public detectMimetypes(path: string, treeish?: string): TPromise { + return this.raw.then(raw => raw.detectMimetypes(path, treeish)); + } + + public show(path: string, treeish?: string): TPromise { + return this.raw.then(raw => raw.show(path, treeish)); + } + + public onOutput(): Promise { + return this.raw.then(raw => raw.onOutput()); + } +} \ No newline at end of file