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

Add ability to include/exclude changes #101

Merged
merged 2 commits into from
Feb 13, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
28 changes: 3 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"when": "scmProvider == tfvc"
},
{
"command" : "tfvc.Status",
"command" : "tfvc.Refresh",
"group": "navigation@2",
"when": "scmProvider == tfvc"
},
Expand Down Expand Up @@ -74,18 +74,6 @@
"when": "scmProvider == tfvc"
}
],
"scm/resourceGroup/context": [
{
"command": "tfvc.ExcludeAll",
"when": "scmProvider == tfvc && scmResourceGroup == included",
"group": "navigation"
},
{
"command": "tfvc.IncludeAll",
"when": "scmProvider == tfvc && scmResourceGroup == excluded",
"group": "navigation"
}
],
"scm/resource/context": [
{
"command": "tfvc.Include",
Expand Down Expand Up @@ -240,11 +228,6 @@
"dark": "resources/icons/dark/check.svg"
}
},
{
"command": "tfvc.ExcludeAll",
"title": "Exclude All",
"category": "TFVC"
},
{
"command": "tfvc.Exclude",
"title": "Exclude",
Expand All @@ -254,11 +237,6 @@
"dark": "resources/icons/dark/unstage.svg"
}
},
{
"command": "tfvc.IncludeAll",
"title": "Include All",
"category": "TFVC"
},
{
"command": "tfvc.Include",
"title": "Include",
Expand All @@ -274,8 +252,8 @@
"category": "TFVC"
},
{
"command": "tfvc.Status",
"title": "Status",
"command": "tfvc.Refresh",
"title": "Refresh",
"category": "TFVC",
"icon": {
"light": "resources/icons/light/refresh.svg",
Expand Down
19 changes: 12 additions & 7 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,22 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.Status, () => _extensionManager.Tfvc.TfvcStatus()));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.Undo, (...args) => {
if (args) {
//TODO: Multi-select?
_extensionManager.Tfvc.TfvcUndo(args[0]);
} else {
_extensionManager.Tfvc.TfvcUndo();
}
}
));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.Exclude, () => _extensionManager.Tfvc.TfvcExclude()));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.ExcludeAll, () => _extensionManager.Tfvc.TfvcExcludeAll()));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.Include, () => _extensionManager.Tfvc.TfvcInclude()));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.IncludeAll, () => _extensionManager.Tfvc.TfvcIncludeAll()));
}));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.Exclude, (...args) => {
if (args) {
Copy link
Member

Choose a reason for hiding this comment

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

Since most commands will need this logic, it would be nice to somehow push this into a helper method. Not sure if that is easy or not.

_extensionManager.Tfvc.TfvcExclude(args[0]);
}
}));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.Include, (...args) => {
if (args) {
_extensionManager.Tfvc.TfvcInclude(args[0]);
}
}));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.Refresh, () => _extensionManager.Tfvc.TfvcRefresh()));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.ShowOutput, () => _extensionManager.Tfvc.TfvcShowOutput()));
context.subscriptions.push(commands.registerCommand(TfvcCommandNames.Checkin, () => _extensionManager.Tfvc.TfvcCheckin()));
}
1 change: 1 addition & 0 deletions src/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class TfvcCommandNames {
static ExcludeAll: string = TfvcCommandNames.CommandPrefix + "ExcludeAll";
static Include: string = TfvcCommandNames.CommandPrefix + "Include";
static IncludeAll: string = TfvcCommandNames.CommandPrefix + "IncludeAll";
static Refresh: string = TfvcCommandNames.CommandPrefix + "Refresh";
static ShowOutput: string = TfvcCommandNames.CommandPrefix + "ShowOutput";
static Status: string = TfvcCommandNames.CommandPrefix + "Status";
static Undo: string = TfvcCommandNames.CommandPrefix + "Undo";
Expand Down
94 changes: 94 additions & 0 deletions src/tfvc/commands/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* 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 * as path from "path";
import { TeamServerContext} from "../../contexts/servercontext";
import { IArgumentProvider, IExecutionResult, ITfvcCommand } from "../interfaces";
import { ArgumentBuilder } from "./argumentbuilder";
import { CommandHelper } from "./commandhelper";

/**
* This command adds the files passed in.
* It returns the list of files that were successfully added.
* add [/lock:none|checkin|checkout] [/type:<value>] [/recursive] [/silent] [/noignore] <localItemSpec>...
*/
export class Add implements ITfvcCommand<string[]> {
private _serverContext: TeamServerContext;
private _itemPaths: string[];

public constructor(serverContext: TeamServerContext, itemPaths: string[]) {
CommandHelper.RequireStringArrayArgument(itemPaths, "itemPaths");
this._serverContext = serverContext;
this._itemPaths = itemPaths;
}

public GetArguments(): IArgumentProvider {
return new ArgumentBuilder("add", this._serverContext)
.AddAll(this._itemPaths);
}

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

/**
* Example of output
* folder1\folder2:
* file5.txt
* file2.java
*/
public async ParseOutput(executionResult: IExecutionResult): Promise<string[]> {
// Throw if any errors are found in stderr or if exitcode is not 0
CommandHelper.ProcessErrors(this.GetArguments().GetCommand(), executionResult);

let lines: string[] = CommandHelper.SplitIntoLines(executionResult.stdout, false, true /*filterEmptyLines*/);

//Remove any lines indicating that there were no files to add (e.g., calling add on files that don't exist)
lines = lines.filter(e => !e.startsWith("No arguments matched any files to add."));

let filesAdded: string[] = [];
let path: string = "";
for (let index: number = 0; index < lines.length; index++) {
let line: string = lines[index];
if (this.isFilePath(line)) {
path = line;
} else if (line) {
Copy link
Member

Choose a reason for hiding this comment

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

line can't be empty here since you excluded them in the split above.

let file: string = this.getFileFromLine(line);
filesAdded.push(this.getFilePath(path, file, ""));
}
}
return filesAdded;
}

private getFileFromLine(line: string): string {
//There's no prefix on the filename line for the Add command
return line;
}

//line could be '', 'file1.txt', 'folder1:', 'folder1\folder2:'
private isFilePath(line: string): boolean {
if (line && line.length > 0 && line.endsWith(":", line.length)) {
//'folder1:', 'folder1\folder2:'
return true;
}
return false;
}

//filePath could be 'folder1\folder2:'
private getFilePath(filePath: string, filename: string, pathRoot: string): string {
let folderPath: string = filePath;
//Remove any ending ':'
if (filePath && filePath.length > 0 && filePath.endsWith(":", filePath.length)) {
folderPath = filePath.slice(0, filePath.length - 1);
}
//If path isn't rooted, add in the root
if (!path.isAbsolute(folderPath) && pathRoot) {
folderPath = path.join(pathRoot, folderPath);
}
return path.join(folderPath, filename);
}

}
2 changes: 1 addition & 1 deletion src/tfvc/commands/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class Status implements ITfvcCommand<IPendingChange[]> {
const candidate: any = json.status.candidatependingchanges[0].pendingchange;
if (candidate) {
for (let i = 0; i < candidate.length; i++) {
this.add(changes, this.convert(candidate[i].$, false), this._ignoreFolders);
this.add(changes, this.convert(candidate[i].$, true), this._ignoreFolders);
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/tfvc/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { GetFileContent } from "./commands/getfilecontent";
import { GetVersion } from "./commands/getversion";
import { Status } from "./commands/status";
import { Undo } from "./commands/undo";
import { Add } from "./commands/add";

var _ = require("underscore");

Expand Down Expand Up @@ -57,6 +58,12 @@ export class Repository {
new Checkin(this._serverContext, files, comment, workItemIds));
}

public async Add(itemPaths: string[]): Promise<string[]> {
Logger.LogDebug(`TFVC Repository.Add`);
return this.RunCommand<string[]>(
new Add(this._serverContext, itemPaths));
}

public async FindWorkspace(localPath: string): Promise<IWorkspace> {
Logger.LogDebug(`TFVC Repository.FindWorkspace with localPath='${localPath}'`);
return this.RunCommand<IWorkspace>(
Expand Down
43 changes: 42 additions & 1 deletion src/tfvc/scm/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import { ResourceGroup, IncludedGroup, ExcludedGroup, MergeGroup } from "./resou
import { IPendingChange } from "../interfaces";
import { Status } from "./status";

import * as _ from "underscore";

export class Model {
private _disposables: Disposable[] = [];
private _repositoryRoot: string;
private _repository: Repository;
private _statusAlreadyInProgress: boolean;
private _explicitlyExcluded: string[] = [];

private _onDidChange = new EventEmitter<SCMResourceGroup[]>();
public get onDidChange(): Event<SCMResourceGroup[]> {
Expand Down Expand Up @@ -78,6 +81,30 @@ export class Model {
});
}

//Add the item to the explicitly excluded list.
public async Exclude(path: string): Promise<void> {
if (path) {
if (!_.contains(this._explicitlyExcluded, path)) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this contains case insensitive? It probably should be.

this._explicitlyExcluded.push(path);
await this.update();
}
}
}

//Unexclude doesn't explicitly INclude. It defers to the status of the individual item.
public async Unexclude(path: string): Promise<void> {
if (path) {
if (_.contains(this._explicitlyExcluded, path)) {
Copy link
Member

Choose a reason for hiding this comment

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

Case insensitive?

this._explicitlyExcluded = _.without(this._explicitlyExcluded, path);
await this.update();
}
}
}

public async Refresh(): Promise<void> {
await this.update();
}

private async update(): Promise<void> {
const changes: IPendingChange[] = await this._repository.GetStatus();
const included: Resource[] = [];
Expand All @@ -90,7 +117,21 @@ export class Model {
if (resource.HasStatus(Status.MERGE)) {
return merge.push(resource);
} else {
return included.push(resource);
//If explicitly excluded, that has highest priority
if (_.contains(this._explicitlyExcluded, resource.uri.fsPath)) {
return excluded.push(resource);
}
//Versioned changes should always be included
if (resource.IsVersioned) {
return included.push(resource);
}
//Pending changes should be included
if (!resource.PendingChange.isCandidate) {
return included.push(resource);
}
//Others:
//Candidate changes should be excluded
return excluded.push(resource);
}
});

Expand Down
4 changes: 4 additions & 0 deletions src/tfvc/scm/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ export class Resource implements SCMResource {
private _uri: Uri;
private _statuses: Status[];
private _change: IPendingChange;
private _version: string;

constructor(change: IPendingChange) {
this._change = change;
this._uri = Uri.file(change.localItem);
this._statuses = GetStatuses(change.changeType);
this._version = change.version;
}

public get PendingChange(): IPendingChange { return this._change; }
Expand All @@ -28,6 +30,8 @@ export class Resource implements SCMResource {
return this._statuses.findIndex(s => s === status) >= 0;
}

get IsVersioned(): boolean { return this._version !== "0"; }

/**
* This method gets a vscode file uri that represents the server path and version that the local item is based on.
*/
Expand Down
51 changes: 42 additions & 9 deletions src/tfvc/tfvc-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TfvcTelemetryEvents } from "../helpers/constants";
import { Strings } from "../helpers/strings";
import { Utils } from "../helpers/utils";
import { Tfvc } from "./tfvc";
import { Resource } from "./scm/resource";
import { TfvcSCMProvider } from "./tfvcscmprovider";
import { TfvcErrorCodes } from "./tfvcerror";
import { Repository } from "./repository";
Expand Down Expand Up @@ -48,25 +49,57 @@ export class TfvcExtension {
const changeset: string =
await this._repo.Checkin(checkinInfo.files, checkinInfo.comment, checkinInfo.workItemIds);
TfvcOutput.AppendLine("Changeset " + changeset + " checked in.");
TfvcSCMProvider.ClearCheckinMessage();
} catch (err) {
this._manager.DisplayErrorMessage(err.message);
}
}

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

public async TfvcExcludeAll(): Promise<void> {
//
if (uri) {
//Keep an in-memory list of items that were explicitly excluded. The list is not persisted at this time.
await TfvcSCMProvider.Exclude(TfvcSCMProvider.GetPathFromUri(uri));
}
}

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

if (uri) {
let resource: Resource = TfvcSCMProvider.ResolveTfvcResource(uri);
let path: string = TfvcSCMProvider.GetPathFromUri(uri);

//At this point, an unversioned file could be a candidate file, so call Add. Once it is added, it should be a Pending change.
if (!resource.IsVersioned) {
await this._repo.Add([path]);
//Even if adding a file, Unexclude it (it may have been excluded previously)
Copy link
Member

Choose a reason for hiding this comment

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

The comments are a bit confusing. Looks like you used to have an else clause here and then removed it.

}

//Otherwise, ensure its not in the explicitly excluded list (if it's already there)
//Unexclude doesn't explicitly INclude. It defers to the status of the individual item.
await TfvcSCMProvider.Unexclude(path);
}
}

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

try {
TfvcSCMProvider.Refresh();
} catch (err) {
this._manager.DisplayErrorMessage(err.message);
}
}

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