-
Notifications
You must be signed in to change notification settings - Fork 451
Add ability to include/exclude changes #101
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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[]> { | ||
|
@@ -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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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[] = []; | ||
|
@@ -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); | ||
} | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> { | ||
|
There was a problem hiding this comment.
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.