DEV Community

nita daniel
nita daniel

Posted on

2 3

Yes, We Still Use TFS

My employer uses Microsofty technologies in its backbone. We have a large, entertwined codebase that loads 150+ projects at once, and yes, we still use TFS, not git.

We mark some items for manual merge due to the quirks of our processes, using Visual Studio slns, and Reasons, okay, for reasons.

The problem?

No, the answer isn’t that we still use TFS, and not distributed version control. I mean it is an aspect, but that’s not the problem. The problem is we have years of unmerged items due to some checkins requiring careful hand merging or manual changes.

Just trust me. For Reasons.

TFS means VisualStudio, and merging in the Visual Studio UI means retrieving merge candidates. And that list is long.

I don’t much care for lifting my hands from the keyboard, much less waiting for long lists to load OVER AND OVER, so, for the standard merge, I wrote a powershell script. Wait. No. I went further and wrote a MODULE.

Demo of performing a merge with my powershell

I could have used a library but our needs are simple.

  • Merge a changeset or range of changesets
  • Make the original comment (where we have a standard of including the work item ID as well) easily accessible.

This module isn’t not perfect. It doesn’t automatically setup or detect workspaces. It doesn’t checkin the subsequent merged files. You have to open it up and update

  • The path tfs.exe is suitable for your machine, are any paths to where you keep your local versions of the codebase.
  • That you’ve defined the names of any projects and your project default
  • The url to the collection on the server

If you want a reminder, this is what command line merging with tf.exe looks like when you have it in your path, and no workplace shenanigans or mismatches

cd <local directory with repo files>
tf vc merge /recursive $/VersionControl/Source/Path $/VersionControl/Destination/Path /version:C<ChangesetId>~C<ChangesetId>

And now I have

Merge-Code Dev Test 12345

Instead. Less to remember. Less to edit. Faster.

Merge conflicts open in their own window, and the pending items show up in VS. The script copies the checkin comment of the first changeset in the range, and I paste that in without having to look it up.

Not exciting, is it? But it vastly speeds up our workflows.

Get the goods

Import-Module $pathToMergeManifest.psd1
#Define your branches here
Add-Type -TypeDefinition @"
public enum BranchName
{
Dev,
Test,
Prod
}
"@
#Define your standard projects here
Add-Type -TypeDefinition @"
public enum ProjectName
{
WebSite,
Databases,
APIs
}
"@
Add-Type -TypeDefinition @"
public enum Format
{
Brief,
Detailed
}
"@
#The url to the collection on your TFS server
$collection=
$comment=''
$tfsContent = tf workspaces /collection:$collection /format:detailed | Select-String -Pattern "$/:" -SimpleMatch | %{$_ -Replace "\$/: ",""}
<#
.SYNOPSIS
The command will
1) Merge code for a specified changeset or changeset range for a specific project
It also copies the comment associated with the first changeset in your merge to the clipboard.
If you do not see your files in the Pending Changes portion of the TFS GUI, close any solutions you have open and check again.
.DESCRIPTION
Streamlined command-line merges for Perfect-Vision tfs changesets
This commandlet makes
Merge-Code Dev Test 23426
Functionally equivalent to
tf vc merge /recursive $/PTNWebAll/Dev $/PTNWebAll/Test /version:C23426~C23426
#be nice and list your enums for anyone that may use the get-help commmand
.PARAMETER Source
Source branch, may be one of: Dev,Test,Prod
.PARAMETER Target
Target branch, may be one of: Dev,Test,Prod
.Parameter First
The changeset number of the first changeset in the range
.PARAMETER Last
The changeset number of the last changeset in the range. If ommitted, it will merge only the first changeset
#be nice and remind people of the enums and what you've set for the default
.PARAMETER ProjectName
One of blah, blah, or blah. Default is Blah.
.PARAMETER Force
Ignores the merge history and merges the specified changes from the source into the destination, even if some or all these changes have been merged before.
If combined with the Discard flag, will resolve any merge conflicts to retain the code in the target branch.
.PARAMETER Discard
If present, tfs does not perform the merge operation, but updates the merge history to track that the merge occurred. This discards a changeset from being used for a particular merge.
.PARAMETER LocalPath
The local path to where you keep tfs files on your system. It attempts to find a workspace path mapped to your system. If not you'll have to specify.
.PARAMETER tfExePath
The full path to tf.exe on your system.
.LINK
https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/use-team-foundation-version-control-commands?view=azure-devops
.LINK
https://github.com/ohmonster/MonstrousPowerShell/tree/master/TFS
#>
function Merge-Code{
[CmdletBinding(PositionalBinding=$true)]
Param(
[Parameter(Position=0,Mandatory=$true)]
[BranchName]$Source,
[Parameter(Position=1,Mandatory=$true)]
[BranchName]$Target,
[Parameter(Position=2,Mandatory=$true)]
[int]$First,
[Parameter(Position=3,Mandatory=$false)]
[int]$Last=$First,
[Parameter(Mandatory=$false)]
#define a default project to make merges faster for the things you use most
[ProjectName]$Project="WebSite",
[Parameter(Mandatory=$false)]
[Switch]$Force,
[Parameter(Mandatory=$false)]
[Switch]$Discard,
[Parameter(Mandatory=$false)]
[String]$LocalPath= "$tfsContent",
[Parameter(Mandatory=$false)]
#tfsExe path -- my box has VS 2017 Professional. Yours may be elsewhere
[String]$tfExePath = "c:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/Common7/IDE/CommonExtensions/Microsoft/TeamFoundation/Team Explorer/tf"
)
#"$Source $Target $First $Last $Discard"
$private:sourcePath = "$LocalPath/$Project/$Source"
$private:targetPath = "$LocalPath/$Project/$Target"
$private:changesets = "C"+$First + "~C" + $Last
if ($Force -And $Discard )
{
& $tfExePath vc merge /recursive $Local:sourcePath $Local:targetPath /version:$changesets /force /conservative /noPrompt
& $tfExePath vc resolve $Local:targetPath /recursive /auto:KeepYours /noPrompt
}
elseif ($Force)
{
& $tfExePath vc merge /recursive $Local:sourcePath $Local:targetPath /version:$changesets /force
}
elseif ($Discard)
{
& $tfExePath vc merge /recursive $Local:sourcePath $Local:targetPath /version:$changesets /discard
}
else {
#Write-Host "$tfExePath vc merge /recursive $Local:sourcePath $Local:targetPath /version:$changesets"
& $tfExePath vc merge /recursive $Local:sourcePath $Local:targetPath /version:$changesets
}
$private:info = & $tfExePath changeset $First /collection:$collection /noPrompt | Select-String -Pattern "Comment:" -Context 1
$comment=$info.Context.PostContext
Set-Clipboard $comment
}
<#
.SYNOPSIS
View Pending Changes
.PARAMETER Format
Brief or Detailed
.PARAMETER tfExePath
The full path to tf.exe on your system. If tf.exe isn't found at the default path
.LINK
https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/use-team-foundation-version-control-commands?view=azure-devops
.LINK
https://github.com/ohmonster/MonstrousPowerShell/tree/master/TFS
#>
function Merge-Status{
Param(
[Parameter(Mandatory=$false)]
[Format]$Format = 'Detailed',
[Parameter(Mandatory=$false)]
[String]$tfExePath = "c:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/Common7/IDE/CommonExtensions/Microsoft/TeamFoundation/Team Explorer/tf"
)
& $tfExePath vc status /collection:$collection /format:$Format
}
Export-ModuleMember -Function Merge-Code, Merge-Status
@{
RootModule='mergeFunction.psm1'
ModuleVersion = '0.8'
GUID = '068efa03-fca4-4d59-9c6d-61e05a733701'
Author = 'Nita Daniel'
Copyright = '2020 Nita Daniel'
Description = 'Shorthand functions for performing tf merges at work'
PowerShellVersion='5.0'
FunctionsToExport=@(
'Merge-Code','Merge-Status'
)
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri ='https://github.com/ohmonster/MonstrousPowerShell/tree/master/TFS'
# ReleaseNotes of this module
# ReleaseNotes = ''
} # End of PSData hashtable
} # End of PrivateData hashtable
}

Top comments (0)