From 66bd25182b8deaf603afda03ca83fa7ada38b1fe Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Wed, 22 Feb 2017 14:30:13 -0500 Subject: [PATCH] Initial tf.exe framework (#120) * Initial tf.exe framework * PR feedback * Explicitly exclude files from code coverage --- gulpfile.js | 35 ++++++++++++++++++++++++--- src/tfvc/commands/add.ts | 12 +++++++++ src/tfvc/commands/checkin.ts | 12 +++++++++ src/tfvc/commands/delete.ts | 12 +++++++++ src/tfvc/commands/findconflicts.ts | 13 ++++++++++ src/tfvc/commands/findworkspace.ts | 18 +++++++++++++- src/tfvc/commands/getfilecontent.ts | 12 +++++++++ src/tfvc/commands/getinfo.ts | 12 +++++++++ src/tfvc/commands/getversion.ts | 23 ++++++++++++++++++ src/tfvc/commands/rename.ts | 12 +++++++++ src/tfvc/commands/resolveconflicts.ts | 12 +++++++++ src/tfvc/commands/status.ts | 24 ++++++++++++++++++ src/tfvc/commands/sync.ts | 12 +++++++++ src/tfvc/commands/undo.ts | 12 +++++++++ src/tfvc/interfaces.ts | 3 +++ src/tfvc/repository.ts | 17 ++++++++++--- src/tfvc/tfvc.ts | 16 +++++++++--- 17 files changed, 245 insertions(+), 12 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 5d4c81f99..ca2c9cf34 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -104,9 +104,38 @@ gulp.task('test-integration', function() { gulp.task('test-coverage', function() { //credentialstore is brought in from separate repository, exclude it here - return gulp.src(['out/src/**/*.js', '!out/src/credentialstore/**']) - //.pipe(istanbul({includeUntested: true})) - .pipe(istanbul()) + //exclude the files we know we can't get coverage on (e.g., vscode, etc.) + return gulp.src(['out/src/**/*.js', '!out/src/credentialstore/**' + ,'!out/src/extension.js' + ,'!out/src/extensionmanager.js' + ,'!out/src/team-extension.js' + ,'!out/src/clients/buildclient.js' + ,'!out/src/clients/coreapiclient.js' + ,'!out/src/clients/feedbackclient.js' + ,'!out/src/clients/gitclient.js' + ,'!out/src/clients/repositoryinfoclient.js' + ,'!out/src/clients/witclient.js' + ,'!out/src/contexts/tfvccontext.js' + ,'!out/src/helpers/settings.js' + ,'!out/src/helpers/vscodeutils.js' + ,'!out/src/services/telemetry.js' + ,'!out/src/services/coreapi.js' + ,'!out/src/tfvc/repository.js' + ,'!out/src/tfvc/tfvc-extension.js' + ,'!out/src/tfvc/tfvc.js' + ,'!out/src/tfvc/tfvcoutput.js' + ,'!out/src/tfvc/tfvcscmprovider.js' + ,'!out/src/tfvc/tfvcsettings.js' + ,'!out/src/tfvc/uihelper.js' + ,'!out/src/tfvc/util.js' + ,'!out/src/tfvc/scm/commithoverprovider.js' + ,'!out/src/tfvc/scm/decorationprovider.js' + ,'!out/src/tfvc/scm/model.js' + ,'!out/src/tfvc/scm/resource.js' + ,'!out/src/tfvc/scm/tfvccontentprovider.js' + ]) + .pipe(istanbul({includeUntested: true})) + //.pipe(istanbul()) .pipe(istanbul.hookRequire()) //for using node.js .on('finish', function() { gulp.src('out/test*/**/*.js') diff --git a/src/tfvc/commands/add.ts b/src/tfvc/commands/add.ts index 6dd59cfd9..a7edadd74 100644 --- a/src/tfvc/commands/add.ts +++ b/src/tfvc/commands/add.ts @@ -62,6 +62,18 @@ export class Add implements ITfvcCommand { return filesAdded; } + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } + private getFileFromLine(line: string): string { //There's no prefix on the filename line for the Add command return line; diff --git a/src/tfvc/commands/checkin.ts b/src/tfvc/commands/checkin.ts index caeb6f919..667a169b6 100644 --- a/src/tfvc/commands/checkin.ts +++ b/src/tfvc/commands/checkin.ts @@ -85,4 +85,16 @@ export class Checkin implements ITfvcCommand { return CommandHelper.GetChangesetNumber(executionResult.stdout); } } + + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } } diff --git a/src/tfvc/commands/delete.ts b/src/tfvc/commands/delete.ts index 04c7b1056..4e1aa290a 100644 --- a/src/tfvc/commands/delete.ts +++ b/src/tfvc/commands/delete.ts @@ -79,6 +79,18 @@ export class Delete implements ITfvcCommand { return filesUndone; } + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } + private getFileFromLine(line: string): string { //There's no prefix on the filename line for the Delete command return line; diff --git a/src/tfvc/commands/findconflicts.ts b/src/tfvc/commands/findconflicts.ts index e69cbb700..2a26d5fb1 100644 --- a/src/tfvc/commands/findconflicts.ts +++ b/src/tfvc/commands/findconflicts.ts @@ -80,4 +80,17 @@ export class FindConflicts implements ITfvcCommand { return conflicts; } + + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } + } diff --git a/src/tfvc/commands/findworkspace.ts b/src/tfvc/commands/findworkspace.ts index e338388c1..d73eda71c 100644 --- a/src/tfvc/commands/findworkspace.ts +++ b/src/tfvc/commands/findworkspace.ts @@ -71,7 +71,8 @@ export class FindWorkspace implements ITfvcCommand { continue; } - if (line.startsWith("Workspace:")) { + //CLC returns 'Workspace:', tf.exe returns 'Workspace :' + if (line.startsWith("Workspace:") || line.startsWith("Workspace :")) { workspaceName = this.getValue(line); } else if (line.startsWith("Collection:")) { collectionUrl = this.getValue(line); @@ -86,6 +87,9 @@ export class FindWorkspace implements ITfvcCommand { } } } + if (mappings.length === 0) { + throw new Error("Could not find a workspace with mappings. Perhaps the wrong version of TF is being used with the selected folder?"); + } const workspace: IWorkspace = { name: workspaceName, @@ -97,6 +101,18 @@ export class FindWorkspace implements ITfvcCommand { return workspace; } + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } + /** * This method parses a line of the form "name: value" and returns the value part. */ diff --git a/src/tfvc/commands/getfilecontent.ts b/src/tfvc/commands/getfilecontent.ts index c1f730c70..32f685cfe 100644 --- a/src/tfvc/commands/getfilecontent.ts +++ b/src/tfvc/commands/getfilecontent.ts @@ -58,4 +58,16 @@ export class GetFileContent implements ITfvcCommand { const lines: string[] = CommandHelper.SplitIntoLines(executionResult.stdout); return lines.join(CommandHelper.GetNewLineCharacter(executionResult.stdout)); } + + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } } diff --git a/src/tfvc/commands/getinfo.ts b/src/tfvc/commands/getinfo.ts index 47a9d941c..6bac8807f 100644 --- a/src/tfvc/commands/getinfo.ts +++ b/src/tfvc/commands/getinfo.ts @@ -99,6 +99,18 @@ export class GetInfo implements ITfvcCommand { return itemInfos; } + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } + private getPropertyName(name: string): string { switch (name) { case "server path": return "serverItem"; diff --git a/src/tfvc/commands/getversion.ts b/src/tfvc/commands/getversion.ts index bf7904f57..299a73932 100644 --- a/src/tfvc/commands/getversion.ts +++ b/src/tfvc/commands/getversion.ts @@ -35,4 +35,27 @@ export class GetVersion implements ITfvcCommand { return ""; } } + + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + //TODO: Refactor this with ParseOutput (pass in just the line and the regex?) + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + // Throw if any errors are found in stderr or if exitcode is not 0 + CommandHelper.ProcessErrors(this.GetArguments().GetCommand(), executionResult); + + const lines: string[] = CommandHelper.SplitIntoLines(executionResult.stdout); + // Find just the version number and return it. Ex. Microsoft (R) TF - Team Foundation Version Control Tool, Version 14.102.25619.0 + if (lines && lines.length > 0) { + let value: string = lines[0].replace(/(.*version )([\.\d]*)(.*)/i, "$2"); + return value; + } else { + return ""; + } + } } diff --git a/src/tfvc/commands/rename.ts b/src/tfvc/commands/rename.ts index 4544b0abb..1dbbb9456 100644 --- a/src/tfvc/commands/rename.ts +++ b/src/tfvc/commands/rename.ts @@ -75,6 +75,18 @@ export class Rename implements ITfvcCommand { return ""; } + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } + private getFileFromLine(line: string): string { //There's no prefix on the filename line for the Add command return line; diff --git a/src/tfvc/commands/resolveconflicts.ts b/src/tfvc/commands/resolveconflicts.ts index 5dd6dbd94..71f8cc4a4 100644 --- a/src/tfvc/commands/resolveconflicts.ts +++ b/src/tfvc/commands/resolveconflicts.ts @@ -66,4 +66,16 @@ export class ResolveConflicts implements ITfvcCommand { return conflicts; } + + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } } diff --git a/src/tfvc/commands/status.ts b/src/tfvc/commands/status.ts index 8c034d345..eac8b3a90 100644 --- a/src/tfvc/commands/status.ts +++ b/src/tfvc/commands/status.ts @@ -86,6 +86,30 @@ export class Status implements ITfvcCommand { return changes; } + public GetExeArguments(): IArgumentProvider { + //return this.GetArguments(); + const builder: ArgumentBuilder = new ArgumentBuilder("status", this._serverContext) + .AddSwitchWithValue("format", "detailed", false) + .AddSwitch("recursive"); + + if (this._localPaths && this._localPaths.length > 0) { + for (let i = 0; i < this._localPaths.length; i++) { + builder.Add(this._localPaths[i]); + } + } + + return builder; + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + //TODO: Parse this with format:detailed + return this.ParseOutput(executionResult); + } + private add(changes: IPendingChange[], newChange: IPendingChange, ignoreFolders: boolean) { // Deleted files won't exist, but we still include them in the results if (ignoreFolders && fs.existsSync(newChange.localItem)) { diff --git a/src/tfvc/commands/sync.ts b/src/tfvc/commands/sync.ts index 2fd92e73e..38d354798 100644 --- a/src/tfvc/commands/sync.ts +++ b/src/tfvc/commands/sync.ts @@ -90,6 +90,18 @@ export class Sync implements ITfvcCommand { } } + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } + private getItemResults(stdout: string): ISyncItemResult[] { let itemResults: ISyncItemResult[] = []; let folderPath: string = ""; diff --git a/src/tfvc/commands/undo.ts b/src/tfvc/commands/undo.ts index 1d9fc249c..94688f940 100644 --- a/src/tfvc/commands/undo.ts +++ b/src/tfvc/commands/undo.ts @@ -75,6 +75,18 @@ export class Undo implements ITfvcCommand { return filesUndone; } + public GetExeArguments(): IArgumentProvider { + return this.GetArguments(); + } + + public GetExeOptions(): any { + return this.GetOptions(); + } + + public async ParseExeOutput(executionResult: IExecutionResult): Promise { + return this.ParseOutput(executionResult); + } + //line could be 'Undoing edit: file1.txt', 'Undoing add: file1.txt' private getFileFromLine(line: string): string { const prefix: string = ": "; //"Undoing edit: ", "Undoing add: ", etc. diff --git a/src/tfvc/interfaces.ts b/src/tfvc/interfaces.ts index b50ca8631..aa4158caa 100644 --- a/src/tfvc/interfaces.ts +++ b/src/tfvc/interfaces.ts @@ -123,6 +123,9 @@ export interface IArgumentProvider { export interface ITfvcCommand { GetArguments(): IArgumentProvider; + GetExeArguments(): IArgumentProvider; GetOptions(): any; + GetExeOptions(): any; ParseOutput(executionResult: IExecutionResult): Promise; + ParseExeOutput(executionResult: IExecutionResult): Promise; } diff --git a/src/tfvc/repository.ts b/src/tfvc/repository.ts index 226414080..04e00006b 100644 --- a/src/tfvc/repository.ts +++ b/src/tfvc/repository.ts @@ -147,10 +147,19 @@ export class Repository { } public async RunCommand(cmd: ITfvcCommand): Promise { - const result: IExecutionResult = await this.exec(cmd.GetArguments(), cmd.GetOptions()); - // We will call ParseOutput to give the command a chance to handle any specific errors itself. - const output: T = await cmd.ParseOutput(result); - return output; + if (this._tfvc.isExe) { + //This is the tf.exe path + const result: IExecutionResult = await this.exec(cmd.GetExeArguments(), cmd.GetExeOptions()); + // We will call ParseExeOutput to give the command a chance to handle any specific errors itself. + const output: T = await cmd.ParseExeOutput(result); + return output; + } else { + //This is the CLC path + const result: IExecutionResult = await this.exec(cmd.GetArguments(), cmd.GetOptions()); + // We will call ParseOutput to give the command a chance to handle any specific errors itself. + const output: T = await cmd.ParseOutput(result); + return output; + } } private async exec(args: IArgumentProvider, options: any = {}): Promise { diff --git a/src/tfvc/tfvc.ts b/src/tfvc/tfvc.ts index 70da9eebf..dc6a61a0b 100644 --- a/src/tfvc/tfvc.ts +++ b/src/tfvc/tfvc.ts @@ -17,11 +17,14 @@ import { TfvcVersion } from "./tfvcversion"; import { TfvcOutput } from "./tfvcoutput"; var _ = require("underscore"); -var fs = require("fs"); +import * as fs from "fs"; +import * as path from "path"; export class Tfvc { private _tfvcPath: string; private _proxy: string; + private _isExe: boolean = false; + private _minVersion: string = "14.0.4"; //Minimum CLC version public constructor(localPath?: string) { Logger.LogDebug(`TFVC Creating Tfvc object with localPath='${localPath}'`); @@ -49,7 +52,7 @@ export class Tfvc { let exists: boolean = fs.existsSync(this._tfvcPath); if (exists) { // if it exists, check to ensure that it's a file and not a folder - const stats: any = fs.lstatSync(this._tfvcPath); + const stats: fs.Stats = fs.lstatSync(this._tfvcPath); if (!stats || !stats.isFile()) { Logger.LogWarning(`TFVC ${this._tfvcPath} exists but isn't a file.`); throw new TfvcError({ @@ -57,6 +60,10 @@ export class Tfvc { tfvcErrorCode: TfvcErrorCodes.TfvcNotFound }); } + this._isExe = path.extname(this._tfvcPath) === ".exe"; + if (this._isExe) { + this._minVersion = "14.102.0"; //Minimum tf.exe version + } } else { Logger.LogWarning(`TFVC ${this._tfvcPath} does not exist.`); throw new TfvcError({ @@ -66,6 +73,7 @@ export class Tfvc { } } + public get isExe(): boolean { return this._isExe; } public get Location(): string { return this._tfvcPath; } /** @@ -80,10 +88,10 @@ export class Tfvc { } // check the version of TFVC command line - const minVersion: TfvcVersion = TfvcVersion.FromString("14.0.4"); + const minVersion: TfvcVersion = TfvcVersion.FromString(this._minVersion); const curVersion: TfvcVersion = TfvcVersion.FromString(version); if (TfvcVersion.Compare(curVersion, minVersion) < 0) { - Logger.LogWarning(`TFVC ${version} is less that the min version of 14.0.4.`); + Logger.LogWarning(`TFVC ${version} is less that the min version of ${this._minVersion}.`); throw new TfvcError({ message: Strings.TfVersionWarning + minVersion.ToString(), tfvcErrorCode: TfvcErrorCodes.TfvcMinVersionWarning