I have a bunch of source control folders for which I want to get rid of all items that are no longer required. These items have been deleted (code has been moved or rewritten) and, because most of us use the 'Show Deleted Items' option by default some of these folders now show more deleted items and folders and legitimate items. I want to make sure all this redundant code is gone, forever - as it most definitely will not ever be required. These are new projects being built from branches of older ones that, as yet, nobody is using.
There's quite a lot of files spread across multiple folders, though, so I'd rather avoid having to do each one individually. I'm at the command-line too, not using the API.
I know ultimately I will need the tf destroy
command.
I also know that tf dir [wildcard] /recursive /deleted
will return all deleted items within a path (unfortunately alongside all legitimate items).
Can anyone think of a good way of doing this quickly?
I've thought of two solutions:
1) Take the output of the dir command and find all the items that have :Xnnnnnnn
after - these are the deleted items; then simply spit out a bunch of tf destroy calls, or construct a response file (not sure about this bit though). This sounds like a potential use for Powershell, but haven't actually done anything with that yet...
2) Get all the projects ready, and then simply destroy them from TFS and then re-add them so that only the required stuff is then in TFS. However, this does remove the branch relationship which could be useful because for a while I'm going to have to maintain two versions of some of these libraries (pre and post upgrade). Not ideal, but nothing I can do about it.
Obviously Option 2 is a cheat but it'll work - I'd just ideally like a reusable script that could be used for any folder in TFS in the future (a couple of other teams have other long-lived projects that could do with a full purge!).
Thanks in advance.
To delete an item In either Solution Explorer or Source Control Explorer, browse to the folder or file that you want to delete. Select the items that you want to delete, open their context menu (right-click), and choose Delete.
Use the tf destroy command to destroy, or permanently delete, version-controlled files from Team Foundation version control. The destroy action cannot be reversed. You must not destroy files that are still needed.
Deleting these files will be only temporarily since TFS will force their recreation as soon as you perform a Get operation. You can reduce the size of this folder by doing a few things: Create small, targeted workspaces (only grab the items you need to do the changes you need to make)
In VSCode, make sure you are in Explorer . Also make sure you are right-clicking the file shown in the directory/folder . Here either hitting Del for windows and command+del for Mac will delete the file.
Okay so I wrote a console app (.Net 4):
IT GOES WITHOUT SAYING I OFFER NO WARRANTY ABOUT THIS - IT WILL DESTROY ITEMS IN TFS!!!!
Update (8th May 2012) If you run this on a folder that has masses and masses (I mean thousands or tens-of-thousands) of deleted items it might not complete before the TFS command-line times out. The majority of the time taken by this command is in generating the .tfc script. If you run it and find this happening, try to targetting some child folders first
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;
namespace TFDestroyDeleted
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 1 || args.Length > 3)
Usage();
bool prepareOnly = false;
bool previewOnly = false;
if (args.Any(s => StringComparer.InvariantCultureIgnoreCase
.Compare(s, "preview") == 0)) previewOnly = true;
if (args.Any(s => StringComparer.InvariantCultureIgnoreCase
.Compare(s, "norun") == 0)) prepareOnly = true;
string tfOutput = null;
Process p = new Process();
p.StartInfo = new ProcessStartInfo("tf")
{
Arguments = string.Format
("dir /recursive /deleted \"{0}\"", args[0]),
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true
};
p.Start();
tfOutput = p.StandardOutput.ReadToEnd();
p.WaitForExit();
string basePath = null;
string nextDelete = null;
List<string> toDelete = new List<string>();
using (var ms =
new MemoryStream(Encoding.Default.GetBytes(tfOutput)))
{
using (StreamReader sr = new StreamReader(ms))
{
while (!sr.EndOfStream)
{
nextDelete = null;
string line = sr.ReadLine();
if (string.IsNullOrWhiteSpace(line))
basePath = null;
else
{
if (basePath == null)
{
if (line.EndsWith(":"))
basePath = line.Substring(0, line.Length - 1);
else
continue;
}
else
{
nextDelete = Regex.Match(line, @"^.*?;X[0-9]+").Value;
if (!string.IsNullOrWhiteSpace(nextDelete))
{
toDelete.Add(
string.Format
(
"{0}/{1}", basePath,
nextDelete.StartsWith("$") ? nextDelete.Substring(1)
: nextDelete
));
}
}
}
}
}
}
using (var fs = File.OpenWrite("destroy.tfc"))
{
fs.SetLength(0);
using (var sw = new StreamWriter(fs))
{
//do the longest items first, naturally deleting items before their
//parent folders
foreach (var s in toDelete.OrderByDescending(s => s.Length))
{
if (!previewOnly)
sw.WriteLine("destroy \"{0}\" /i", s);
else
sw.WriteLine("destroy \"{0}\" /i /preview", s);
}
sw.Flush();
}
}
if (!prepareOnly)
{
p.StartInfo = new ProcessStartInfo("tf")
{
Arguments = string.Format("@{0}", "destroy.tfc"),
UseShellExecute = false
};
p.Start();
p.WaitForExit();
}
p.Close();
}
static void Usage()
{
Console.WriteLine(@"Usage:
TFDestroyDeleted [TFFolder] (preview) (norun)
Where [TFFolder] is the TFS root folder to be purged - it should be quoted if there are spaces. E.g: ""$/folder/subfolder"".
norun - Specify this if you only want a command file prepared for tf.
preview - Specify this if you want each destroy to be only a preview (i.e. when run, it won't actually do the destroy) ");
Environment.Exit(0);
}
}
}
You must pass the TFS folder to be deleted e.g '$/folder'. If you just pass that, then all matching deleted items will be detected and destroyed, one by one.
For some reason - if you accidentally pass a folder that doesn't actually exist then the operation takes forever. A CTRL+C will stop it, of course.
The app does a recursive dir on the folder, with the /deleted
switch.
It then runs through each line in the output, looking for the delete hint, i.e. items with ;Xnnnnnnn
. If found, it adds the full tfs path for that item to a list.
After complete, the list is sorted by length in descending order and the contents written out to a tfc response file for the tf.exe
command line.
If the preview
option is specified then the tf
commands are written out with the /preview switch (see TFS Destroy on MSDN)
Then the deletions aren't actually performed.
Finally, you can specify norun
which causes the tfc file to be created, but not actually run.
I know it is an old question but I think it can be helpful.
We have an old collection with 20+ team projects under VSO and really needed to clean up our team projects. This code worked perfectly for us.
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
static void Main(string[] args)
{
TfsTeamProjectCollection tfs = new TfsTeamProjectCollection(new Uri("COLLECTION_URL")); //Example: https://xxxx.visualstudio.com
var versionControl = tfs.GetService<VersionControlServer>();
ItemSpec spec = new ItemSpec("$/", RecursionType.Full);
var folderItemSet = versionControl.GetItems(spec, VersionSpec.Latest, DeletedState.Deleted, ItemType.Folder, true);
DestoryItemSet(versionControl, folderItemSet);
//Delete remaining files
var fileItemSet = versionControl.GetItems(spec, VersionSpec.Latest, DeletedState.Deleted, ItemType.File, true);
DestoryItemSet(versionControl, fileItemSet);
}
private static void DestoryItemSet(VersionControlServer versionControl, ItemSet itemSet)
{
foreach (var deletedItem in itemSet.Items)
{
try
{
versionControl.Destroy(new ItemSpec(deletedItem.ServerItem, RecursionType.Full, deletedItem.DeletionId), VersionSpec.Latest, null, Microsoft.TeamFoundation.VersionControl.Common.DestroyFlags.None);
Console.WriteLine("{0} destroyed successfully.", deletedItem.ServerItem);
}
catch (ItemNotFoundException) //For get rid of exception for deleting the nested objects
{
}
catch (Exception)
{
throw;
}
}
}
I used Microsoft.TeamFoundationServer.ExtendedClient NuGet package.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With