Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Adding Checkin and hooking it up to the viewlet #100

Merged
merged 6 commits into from
Feb 10, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export class TelemetryEvents {

export class TfvcTelemetryEvents {
static TelemetryPrefix: string = "tfvc/";
static Checkin: string = TfvcTelemetryEvents.TelemetryPrefix + "checkin";
static OpenFileHistory: string = TfvcTelemetryEvents.TelemetryPrefix + "openfilehistory";
static OpenRepositoryHistory: string = TfvcTelemetryEvents.TelemetryPrefix + "openrepohistory";
static Status: string = TfvcTelemetryEvents.TelemetryPrefix + "status";
Expand Down
88 changes: 88 additions & 0 deletions src/tfvc/commands/checkin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* 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 { TeamServerContext} from "../../contexts/servercontext";
import { IArgumentProvider, IExecutionResult, ITfvcCommand } from "../interfaces";
import { ArgumentBuilder } from "./argumentbuilder";
import { CommandHelper } from "./commandhelper";

/**
* This command checks in files into TFVC
* <p/>
* checkin [/all] [/author:<value>] [/comment:<value>|@valuefile] [/notes:"note"="value"[;"note2"="value2"[;...]]|@notefile]
* [/override:<value>|@valuefile] [/recursive] [/validate] [/bypass] [/force] [/noautoresolve] [/associate:<workItemID>[,<workItemID>...]]
* [/resolve:<workItemID>[,<workItemID>...]] [/saved] [<itemSpec>...]
*/
export class Checkin implements ITfvcCommand<string> {
private _serverContext: TeamServerContext;
private _files: string[];
private _comment: string;
private _workItemIds: number[];

public constructor(serverContext: TeamServerContext, files: string[], comment?: string, workItemIds?: number[]) {
CommandHelper.RequireStringArrayArgument(files, "files");
this._serverContext = serverContext;
this._files = files;
this._comment = comment;
this._workItemIds = workItemIds;
}

public GetArguments(): IArgumentProvider {
const builder: ArgumentBuilder = new ArgumentBuilder("checkin", this._serverContext)
.AddAll(this._files);
if (this._comment) {
builder.AddSwitchWithValue("comment", this.getComment(), false);
}
if (this._workItemIds && this._workItemIds.length > 0) {
builder.AddSwitchWithValue("associate", this.getAssociatedWorkItems(), false);
}
return builder;
}

public GetOptions(): any {
return {};
}

private getComment(): string {
// replace newlines with spaces
return this._comment.replace(/\r\n/g, " ").replace(/\n/g, " ");
}

private getAssociatedWorkItems(): string {
return this._workItemIds.join(",");
}

/**
* Returns the files that were checked in
* <p/>
* Output example for success:
* /Users/leantk/tfvc-tfs/tfsTest_01/addFold:
* Checking in edit: testHere.txt
* <p/>
* /Users/leantk/tfvc-tfs/tfsTest_01:
* Checking in edit: test3.txt
* Checking in edit: TestAdd.txt
* <p/>
* Changeset #20 checked in.
* <p/>
* Output example for failure:
* <p/>
* /Users/leantk/tfvc-tfs/tfsTest_01:
* Checking in edit: test3.txt
* Checking in edit: TestAdd.txt
* Unable to perform operation on $/tfsTest_01/TestAdd.txt. The item $/tfsTest_01/TestAdd.txt is locked in workspace new;Leah Antkiewicz.
* No files checked in.
* <p/>
* No files checked in.
*/
public async ParseOutput(executionResult: IExecutionResult): Promise<string> {
if (executionResult.exitCode === 100) {
CommandHelper.ProcessErrors(this.GetArguments().GetCommand(), executionResult, true);
} else {
return CommandHelper.GetChangesetNumber(executionResult.stdout);
}
}
}
28 changes: 21 additions & 7 deletions src/tfvc/commands/commandhelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class CommandHelper {
return new RegExp(errorPattern, "i").test(result.stderr);
}

public static ProcessErrors(command: string, result: IExecutionResult): void {
public static ProcessErrors(command: string, result: IExecutionResult, showFirstError?: boolean): void {
if (result.exitCode) {
let tfvcErrorCode: string = null;
let message: string;
Expand All @@ -36,14 +36,8 @@ export class CommandHelper {
tfvcErrorCode = TfvcErrorCodes.AuthenticationFailed;
} else if (/workspace could not be determined/.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.NotATfvcRepository;
} else if (/bad config file/.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.BadConfigFile;
} else if (/cannot make pipe for command substitution|cannot create standard input pipe/.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.CantCreatePipe;
} else if (/Repository not found/.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.RepositoryNotFound;
} else if (/unable to access/.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.CantAccessRemote;
} else if (/project collection URL to use could not be determined/i.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.NotATfvcRepository;
message = Strings.NotATfvcRepository;
Expand All @@ -55,6 +49,8 @@ export class CommandHelper {
message = Strings.TfInitializeFailureError;
} else if (/There is no working folder mapping/i.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.FileNotInMappings;
} else if (showFirstError) {
message = result.stderr ? result.stderr : result.stdout;
}

Logger.LogDebug(`TFVC errors: ${result.stderr}`);
Expand All @@ -70,6 +66,24 @@ export class CommandHelper {
}
}

/**
* This method is used by Checkin to parse out the changeset number.
*/
public static GetChangesetNumber(stdout: string): string {
// parse output for changeset number
if (stdout) {
let prefix: string = "Changeset #";
let start: number = stdout.indexOf(prefix) + prefix.length;
if (start >= 0) {
let end: number = stdout.indexOf(" ", start);
if (end > start) {
return stdout.slice(start, end);
}
}
}
return "";
}

public static GetNewLineCharacter(stdout: string): string {
if (stdout && /\r\n/.test(stdout)) {
return "\r\n";
Expand Down
1 change: 0 additions & 1 deletion src/tfvc/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export class Status implements ITfvcCommand<IPendingChange[]> {
this._localPaths = localPaths;
}

//TODO need to pass in context here as an optional parameter
public GetArguments(): IArgumentProvider {
const builder: ArgumentBuilder = new ArgumentBuilder("status", this._serverContext)
.AddSwitchWithValue("format", "xml", false)
Expand Down
6 changes: 6 additions & 0 deletions src/tfvc/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ export interface IItemInfo {
fileSize?: string;
}

export interface ICheckinInfo {
comment: string;
files: string[];
workItemIds: number[];
}

export interface IWorkspace {
name: string;
server: string;
Expand Down
11 changes: 9 additions & 2 deletions src/tfvc/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import { Logger } from "../helpers/logger";
import { ITfvcCommand, IExecutionResult } from "./interfaces";
import { Tfvc } from "./tfvc";
import { IArgumentProvider, IItemInfo, IWorkspace, IPendingChange } from "./interfaces";
import { GetVersion } from "./commands/getversion";
import { Checkin } from "./commands/checkin";
import { FindWorkspace } from "./commands/findworkspace";
import { Status } from "./commands/status";
import { GetInfo } from "./commands/getinfo";
import { GetFileContent } from "./commands/getfilecontent";
import { GetVersion } from "./commands/getversion";
import { Status } from "./commands/status";
import { Undo } from "./commands/undo";

var _ = require("underscore");
Expand Down Expand Up @@ -50,6 +51,12 @@ export class Repository {
return this._repositoryRootFolder;
}

public async Checkin(files: string[], comment: string, workItemIds: number[]): Promise<string> {
Logger.LogDebug(`TFVC Repository.Undo`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undo ==> Checkin

return this.RunCommand<string>(
new Checkin(this._serverContext, files, comment, workItemIds));
}

public async FindWorkspace(localPath: string): Promise<IWorkspace> {
Logger.LogDebug(`TFVC Repository.FindWorkspace with localPath='${localPath}'`);
return this.RunCommand<IWorkspace>(
Expand Down
102 changes: 102 additions & 0 deletions src/tfvc/scm/commithoverprovider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* 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 { workspace, window, languages, Disposable, Uri, HoverProvider, Hover, TextEditor, Position, TextDocument, Range, TextEditorDecorationType, WorkspaceEdit } from "vscode";
import { Model } from "./model";
import { filterEvent } from "../util";

const scmInputUri = Uri.parse("scm:input");

function isSCMInput(uri: Uri) {
return uri.toString() === scmInputUri.toString();
}

interface Diagnostic {
range: Range;
message: string;
}

export class CommitHoverProvider implements HoverProvider {

private decorationType: TextEditorDecorationType;
private diagnostics: Diagnostic[] = [];
private disposables: Disposable[] = [];
private editor: TextEditor;
private visibleTextEditorsDisposable: Disposable;

constructor(private model: Model) {
this.visibleTextEditorsDisposable = window.onDidChangeVisibleTextEditors(this.onVisibleTextEditors, this);
this.onVisibleTextEditors(window.visibleTextEditors);

this.decorationType = window.createTextEditorDecorationType({
isWholeLine: true,
color: "rgb(228, 157, 43)",
dark: {
color: "rgb(220, 211, 71)"
}
});
}

public get message(): string | undefined {
if (!this.editor) {
return;
}

return this.editor.document.getText();
}

public set message(message: string | undefined) {
if (!this.editor || message === undefined) {
return;
}

const document = this.editor.document;
const start = document.lineAt(0).range.start;
const end = document.lineAt(document.lineCount - 1).range.end;
const range = new Range(start, end);
const edit = new WorkspaceEdit();
edit.replace(scmInputUri, range, message);
workspace.applyEdit(edit);
}

private onVisibleTextEditors(editors: TextEditor[]): void {
const [editor] = editors.filter(e => isSCMInput(e.document.uri));

if (!editor) {
return;
}

this.visibleTextEditorsDisposable.dispose();
this.editor = editor;

const onDidChange = filterEvent(workspace.onDidChangeTextDocument, e => e.document && isSCMInput(e.document.uri));
onDidChange(this.update, this, this.disposables);

workspace.onDidChangeConfiguration(this.update, this, this.disposables);
languages.registerHoverProvider({ scheme: "scm" }, this);
}

private update(): void {
this.diagnostics = [];
//TODO provide any diagnostic info based on the message here (see git commitcontroller)
this.editor.setDecorations(this.decorationType, this.diagnostics.map(d => d.range));
}

/* Implement HoverProvider */
provideHover(document: TextDocument, position: Position): Hover | undefined {
const [decoration] = this.diagnostics.filter(d => d.range.contains(position));

if (!decoration) {
return;
}

return new Hover(decoration.message, decoration.range);
}

dispose(): void {
this.disposables.forEach(d => d.dispose());
}
}
15 changes: 12 additions & 3 deletions src/tfvc/scm/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class Model {
private _disposables: Disposable[] = [];
private _repositoryRoot: string;
private _repository: Repository;
private _statusAlreadyInProgress: boolean;

private _onDidChange = new EventEmitter<SCMResourceGroup[]>();
public get onDidChange(): Event<SCMResourceGroup[]> {
Expand All @@ -35,8 +36,8 @@ export class Model {
}

public get MergeGroup(): MergeGroup { return this._mergeGroup; }
public get IndexGroup(): IncludedGroup { return this._includedGroup; }
public get WorkingTreeGroup(): ExcludedGroup { return this._excludedGroup; }
public get IncludedGroup(): IncludedGroup { return this._includedGroup; }
public get ExcludedGroup(): ExcludedGroup { return this._excludedGroup; }

public get Resources(): ResourceGroup[] {
const result: ResourceGroup[] = [];
Expand All @@ -51,7 +52,15 @@ export class Model {
}

private async status(): Promise<void> {
await this.run(undefined);
if (this._statusAlreadyInProgress) {
return;
}
this._statusAlreadyInProgress = true;
try {
await this.run(undefined);
} finally {
this._statusAlreadyInProgress = false;
}
}

private onFileSystemChange(uri: Uri): void {
Expand Down
27 changes: 25 additions & 2 deletions src/tfvc/tfvc-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import { TfvcSCMProvider } from "./tfvcscmprovider";
import { TfvcErrorCodes } from "./tfvcerror";
import { Repository } from "./repository";
import { UIHelper } from "./uihelper";
import { IItemInfo, IPendingChange } from "./interfaces";
import { ICheckinInfo, IItemInfo, IPendingChange } from "./interfaces";
import { TfvcSCMProvider } from "./tfvcscmprovider";
import { TfvcOutput } from "./tfvcoutput";

export class TfvcExtension {
Expand All @@ -31,7 +32,29 @@ export class TfvcExtension {
}

public async TfvcCheckin(): Promise<void> {
//
if (!this._manager.EnsureInitialized(RepositoryType.TFVC)) {
this._manager.DisplayErrorMessage();
return;
}

try {
this._manager.Telemetry.SendEvent(TfvcTelemetryEvents.Checkin);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this down?


// get the checkin info from the SCM viewlet
const checkinInfo: ICheckinInfo = TfvcSCMProvider.GetCheckinInfo();
if (!checkinInfo) {
// TODO localize
window.showInformationMessage("There are no changes to checkin. Changes must be added to the 'Included' section to be checked in.");
return;
}

const changeset: string =
await this._repo.Checkin(checkinInfo.files, checkinInfo.comment, checkinInfo.workItemIds);
//TODO should this be localized?
TfvcOutput.AppendLine("Changeset " + changeset + " checked in.");
} catch (err) {
this._manager.DisplayErrorMessage(err.message);
}
}

public async TfvcExclude(): Promise<void> {
Expand Down
Loading