From 435bf91f8825499dc5d9f7d3d22d47ee1102d649 Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Tue, 21 Mar 2017 10:29:07 -0400 Subject: [PATCH] Add checks for non-ENU tf(.exe) (#158) * Remove optional settings * Add return type for CheckVersion * Check for non-ENU tf(.exe) * Add support for Spanish, French and German These are 3 of the supported tf (VS) languages and are all a bit different from each other. * Ensure initialize and reinitialize only occur for TFVC --- src/contexts/repocontextfactory.ts | 4 +-- src/contexts/tfvccontext.ts | 3 ++ src/extensionmanager.ts | 23 +++++++------ src/helpers/strings.ts | 1 + src/tfvc/commands/findworkspace.ts | 10 ++++++ src/tfvc/commands/getversion.ts | 22 +++++++++++- src/tfvc/tfcommandlinerunner.ts | 2 +- src/tfvc/tfvcerror.ts | 1 + test/tfvc/commands/findworkspace.test.ts | 40 +++++++++++++++++++++ test/tfvc/commands/getversion.test.ts | 44 ++++++++++++++++++++++++ 10 files changed, 136 insertions(+), 14 deletions(-) diff --git a/src/contexts/repocontextfactory.ts b/src/contexts/repocontextfactory.ts index 99219b60d8..c45a7aeeb6 100644 --- a/src/contexts/repocontextfactory.ts +++ b/src/contexts/repocontextfactory.ts @@ -26,11 +26,11 @@ export class RepositoryContextFactory { //Check for Git next since it should be faster to determine and this code will //be called on Reinitialize (when config changes, for example) repoContext = new GitContext(path); - initialized = await repoContext.Initialize(settings); + initialized = await repoContext.Initialize(); if (!repoContext || repoContext.IsTeamFoundation === false || !initialized) { //Check if we have a TFVC repository repoContext = new TfvcContext(path); - initialized = await repoContext.Initialize(settings); + initialized = await repoContext.Initialize(); if (!initialized) { return undefined; } diff --git a/src/contexts/tfvccontext.ts b/src/contexts/tfvccontext.ts index a04b985776..0250b5408f 100644 --- a/src/contexts/tfvccontext.ts +++ b/src/contexts/tfvccontext.ts @@ -30,6 +30,9 @@ export class TfvcContext implements IRepositoryContext { public async Initialize(): Promise { Logger.LogDebug(`Looking for TFVC repository at ${this._tfvcFolder}`); this._repo = TfCommandLineRunner.CreateRepository(undefined, this._tfvcFolder); + //Ensure we have an appropriate ENU version of tf executable + //The call will throw if we have tf configured properly but it isn't ENU + await this._repo.CheckVersion(); this._tfvcWorkspace = await this._repo.FindWorkspace(this._tfvcFolder); this._tfvcRemoteUrl = this._tfvcWorkspace.server; this._isTeamServicesUrl = RepoUtils.IsTeamFoundationServicesRepo(this._tfvcRemoteUrl); diff --git a/src/extensionmanager.ts b/src/extensionmanager.ts index ef3fc9225b..b1fda72524 100644 --- a/src/extensionmanager.ts +++ b/src/extensionmanager.ts @@ -275,6 +275,16 @@ export class ExtensionManager implements Disposable { } } } + + // Now that everything else is ready, create the SCM provider + if (this._repoContext.Type === RepositoryType.TFVC) { + if (!this._scmProvider) { + this._scmProvider = new TfvcSCMProvider(this); + await this._scmProvider.Initialize(); + } else { + await this._scmProvider.Reinitialize(); + } + } }).fail((err) => { this.setErrorStatus(Utils.GetMessageForStatusCode(err, err.message), (err.statusCode === 401 ? CommandNames.Signin : undefined), false); //If we can't get a requestHandler, report the error via the feedbackclient @@ -283,15 +293,6 @@ export class ExtensionManager implements Disposable { Telemetry.SendException(err); }); } - - // Now that everything else is ready, create the SCM provider - if (!this._scmProvider) { - this._scmProvider = new TfvcSCMProvider(this); - await this._scmProvider.Initialize(); - } else { - await this._scmProvider.Reinitialize(); - } - } catch (err) { Logger.LogError(err.message); //For now, don't report these errors via the _feedbackClient @@ -317,7 +318,8 @@ export class ExtensionManager implements Disposable { //Determines which Tfvc errors to display in the status bar ui private shouldDisplayTfvcError(errorCode: string): boolean { if (TfvcErrorCodes.TfvcMinVersionWarning === errorCode || - TfvcErrorCodes.TfvcNotFound === errorCode) { + TfvcErrorCodes.TfvcNotFound === errorCode || + TfvcErrorCodes.NotAnEnuTfCommandLine === errorCode) { return true; } return false; @@ -383,6 +385,7 @@ export class ExtensionManager implements Disposable { private setErrorStatus(message: string, commandOnClick?: string, showRetryMessage?: boolean) { this._errorMessage = message; if (this._teamServicesStatusBarItem !== undefined) { + //TODO: Should the default command be to do nothing? Or perhaps to display the message? this._teamServicesStatusBarItem.command = commandOnClick === undefined ? CommandNames.Reinitialize : commandOnClick; this._teamServicesStatusBarItem.text = "Team " + `$(icon octicon-stop)`; let message: string = this._errorMessage + (showRetryMessage !== undefined && showRetryMessage === true ? " " + Strings.ClickToRetryConnection : "") ; diff --git a/src/helpers/strings.ts b/src/helpers/strings.ts index d74805ce30..cb65f12f33 100644 --- a/src/helpers/strings.ts +++ b/src/helpers/strings.ts @@ -58,6 +58,7 @@ export class Strings { static ChooseItemQuickPickPlaceHolder: string = "Choose a file to open it."; static NotAGitRepository: string = "The open folder is not a Git repository. Please check the folder location and try again."; static NotATfvcRepository: string = "The open folder is not a TFVC repository. Please check the folder location and try again."; + static NotAnEnuTfCommandLine: string = "It appears you have configured a non-English version of the TF executable. Please ensure an English version is properly configured."; static TokenNotAllScopes: string = "The personal access token provided does not have All Scopes. All Scopes is required for TFVC support."; static TfvcLocationMissingError: string = "The path to the TFVC command line was not found in the user settings. Please set this value (tfvc.location) and try again."; static TfMissingError: string = "Unable to find the TF executable at the expected location. Please verify the installation and location of TF. Expected path: "; diff --git a/src/tfvc/commands/findworkspace.ts b/src/tfvc/commands/findworkspace.ts index 917ca205b6..67299d4333 100644 --- a/src/tfvc/commands/findworkspace.ts +++ b/src/tfvc/commands/findworkspace.ts @@ -95,6 +95,16 @@ export class FindWorkspace implements ITfvcCommand { tfvcErrorCode: TfvcErrorCodes.NotATfvcRepository }); } + //If there are mappings but no workspace name, the term 'workspace' couldn't be parsed. According to Bing + //translate, other than Klingon, no other supported language translates 'workspace' as 'workspace'. + //So if we determine there are mappings but can't get the workspace name, we assume it's a non-ENU + //tf executable. One example of this is German. + if (mappings.length > 0 && !workspaceName) { + throw new TfvcError( { + message: Strings.NotAnEnuTfCommandLine, + tfvcErrorCode: TfvcErrorCodes.NotAnEnuTfCommandLine + }); + } const workspace: IWorkspace = { name: workspaceName, diff --git a/src/tfvc/commands/getversion.ts b/src/tfvc/commands/getversion.ts index 2780f6a8c5..31c9f64f5f 100644 --- a/src/tfvc/commands/getversion.ts +++ b/src/tfvc/commands/getversion.ts @@ -7,6 +7,8 @@ import { IArgumentProvider, IExecutionResult, ITfvcCommand } from "../interfaces"; import { ArgumentBuilder } from "./argumentbuilder"; import { CommandHelper } from "./commandhelper"; +import { TfvcError, TfvcErrorCodes } from "../tfvcerror"; +import { Strings } from "../../helpers/strings"; /** * This command calls the command line doing a simple call to get the help for the add command. @@ -48,7 +50,25 @@ export class GetVersion implements ITfvcCommand { 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(expression, "$2"); + let value: string = lines[0].replace(expression, "$2"); //Example: 14.111.1.201612142018 + //Spanish tf.exe example: "Microsoft (R) TF - Herramienta Control de versiones de Team Foundation, versi�n 14.102.25619.0" + //value = "Microsoft (R) TF - Herramienta Control de versiones de Team Foundation, versi�n 14.102.25619.0" + //French tf.exe example: "Microsoft (R) TF�- Outil Team Foundation Version Control, version�14.102.25619.0" + //value = "" + //German tf.exe example: "Microsoft (R) TF - Team Foundation-Versionskontrolltool, Version 14.102.25619.0" + //value = "14.102.25619.0" + //Check to see if we have either less or more than the version string. Less is the French case (and the like). More is + //the Spanish case (and the like). If we do, we assume that we are using a non-ENU tf executable. So if we get here, + //we were able to run tf but we might not have gotten the version fron an ENU tf. So even if we did get a version string, + //we will still need to check later if this version is from a non-ENU SKU. + let items: string[] = value.split(" "); + if (!value || items.length > 1) { + //This error is an early-out in the cases of Spanish and French (which translate version). German will continue normally. + throw new TfvcError({ + message: Strings.NotAnEnuTfCommandLine, + tfvcErrorCode: TfvcErrorCodes.NotAnEnuTfCommandLine + }); + } return value; } else { return ""; diff --git a/src/tfvc/tfcommandlinerunner.ts b/src/tfvc/tfcommandlinerunner.ts index 0b02e1257a..8553b920bd 100644 --- a/src/tfvc/tfcommandlinerunner.ts +++ b/src/tfvc/tfcommandlinerunner.ts @@ -98,7 +98,7 @@ export class TfCommandLineRunner { * This method checks the version of the CLC against the minimum version that we expect. * It throws an error if the version does not meet or exceed the minimum. */ - public static CheckVersion(tfvc: ITfCommandLine, version: string) { + public static CheckVersion(tfvc: ITfCommandLine, version: string): void { if (!version) { // If the version isn't set just return Logger.LogDebug(`TFVC CheckVersion called without a version.`); diff --git a/src/tfvc/tfvcerror.ts b/src/tfvc/tfvcerror.ts index 70850bf150..f63f4bbfbd 100644 --- a/src/tfvc/tfvcerror.ts +++ b/src/tfvc/tfvcerror.ts @@ -81,6 +81,7 @@ export class TfvcErrorCodes { public static get MissingArgument(): string { return "MissingArgument"; } public static get AuthenticationFailed(): string { return "AuthenticationFailed"; } public static get NotATfvcRepository(): string { return "NotATfvcRepository"; } + public static get NotAnEnuTfCommandLine(): string { return "NotAnEnuTfCommandLine"; } public static get TfvcLocationMissing(): string { return "TfvcLocationMissing"; } public static get TfvcNotFound(): string { return "TfvcNotFound"; } public static get TfvcMinVersionWarning(): string { return "TfvcMinVersionWarning"; } diff --git a/test/tfvc/commands/findworkspace.test.ts b/test/tfvc/commands/findworkspace.test.ts index 004ce87ea9..68ed664602 100644 --- a/test/tfvc/commands/findworkspace.test.ts +++ b/test/tfvc/commands/findworkspace.test.ts @@ -101,6 +101,26 @@ describe("Tfvc-FindWorkspaceCommand", function() { assert.equal(workspace.mappings.length, 1); }); + it("should verify parse output - German - no 'workspace' and 'collection'", async function() { + let localPath: string = "/path/to/workspace"; + let cmd: FindWorkspace = new FindWorkspace(localPath); + let executionResult: IExecutionResult = { + exitCode: 0, + stdout: "=====================================================================================================================================================\n" + + "Arbeitsbereich: DESKTOP-KI56MCL (Jeff Young (TFS))\n" + + "Sammlung : http://java-tfs2015:8081/tfs/defaultcollection\n" + + "$/project1: /path", + stderr: undefined + }; + + try { + await cmd.ParseOutput(executionResult); + } catch (err) { + assert.isTrue(err.message.startsWith(Strings.NotAnEnuTfCommandLine)); + assert.equal(err.tfvcErrorCode, TfvcErrorCodes.NotAnEnuTfCommandLine); + } + }); + it("should verify parse output - not a tf workspace", async function() { let localPath: string = "/path/to/workspace"; let cmd: FindWorkspace = new FindWorkspace(localPath); @@ -177,6 +197,26 @@ describe("Tfvc-FindWorkspaceCommand", function() { assert.equal(workspace.mappings.length, 1); }); + it("should verify parse EXE output - German - no 'workspace' and 'collection'", async function() { + let localPath: string = "/path/to/workspace"; + let cmd: FindWorkspace = new FindWorkspace(localPath); + let executionResult: IExecutionResult = { + exitCode: 0, + stdout: "=====================================================================================================================================================\n" + + "Arbeitsbereich: DESKTOP-KI56MCL (Jeff Young (TFS))\n" + + "Sammlung : http://java-tfs2015:8081/tfs/defaultcollection\n" + + "$/project1: /path", + stderr: undefined + }; + + try { + await cmd.ParseExeOutput(executionResult); + } catch (err) { + assert.isTrue(err.message.startsWith(Strings.NotAnEnuTfCommandLine)); + assert.equal(err.tfvcErrorCode, TfvcErrorCodes.NotAnEnuTfCommandLine); + } + }); + it("should verify parse EXE output - not a tf workspace", async function() { let localPath: string = "/path/to/workspace"; let cmd: FindWorkspace = new FindWorkspace(localPath); diff --git a/test/tfvc/commands/getversion.test.ts b/test/tfvc/commands/getversion.test.ts index 76543ab71f..4cfd678eca 100644 --- a/test/tfvc/commands/getversion.test.ts +++ b/test/tfvc/commands/getversion.test.ts @@ -7,6 +7,7 @@ import { assert } from "chai"; import { GetVersion } from "../../../src/tfvc/commands/getversion"; import { IExecutionResult } from "../../../src/tfvc/interfaces"; +import { TfvcErrorCodes } from "../../../src/tfvc/tfvcerror"; import { Strings } from "../../../src/helpers/strings"; describe("Tfvc-GetVersionCommand", function() { @@ -115,4 +116,47 @@ describe("Tfvc-GetVersionCommand", function() { } }); + it("should verify parse EXE output - Spanish version", async function() { + let cmd: GetVersion = new GetVersion(); + let executionResult: IExecutionResult = { + exitCode: 0, + stdout: "Microsoft (R) TF - Herramienta Control de versiones de Team Foundation, versi�n 14.102.25619.0", + stderr: undefined + }; + + try { + await cmd.ParseExeOutput(executionResult); + } catch (err) { + assert.equal(err.tfvcErrorCode, TfvcErrorCodes.NotAnEnuTfCommandLine); + assert.isTrue(err.message.startsWith(Strings.NotAnEnuTfCommandLine)); + } + }); + + it("should verify parse EXE output - French version", async function() { + let cmd: GetVersion = new GetVersion(); + let executionResult: IExecutionResult = { + exitCode: 0, + stdout: "Microsoft (R) TF�- Outil Team Foundation Version Control, version�14.102.25619.0", + stderr: undefined + }; + + try { + await cmd.ParseExeOutput(executionResult); + } catch (err) { + assert.equal(err.tfvcErrorCode, TfvcErrorCodes.NotAnEnuTfCommandLine); + assert.isTrue(err.message.startsWith(Strings.NotAnEnuTfCommandLine)); + } + }); + + it("should verify parse EXE output - German version", async function() { + let cmd: GetVersion = new GetVersion(); + let executionResult: IExecutionResult = { + exitCode: 0, + stdout: "Microsoft (R) TF - Team Foundation-Versionskontrolltool, Version 14.102.25619.0", + stderr: undefined + }; + + let version: string = await cmd.ParseExeOutput(executionResult); + assert.equal(version, "14.102.25619.0"); + }); });