Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TFS 2008 Source Control - Quick way to destroy all deleted items

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.

like image 275
Andras Zoltan Avatar asked Feb 07 '11 11:02

Andras Zoltan


People also ask

How do I delete a file from source control in TFS?

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.

How do I permanently delete a file in TFS?

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.

Can I delete $TF folder?

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)

How do I delete a Visual Studio code?

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.


2 Answers

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.

like image 187
Andras Zoltan Avatar answered Sep 28 '22 03:09

Andras Zoltan


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.

like image 44
Mo Chavoshi Avatar answered Sep 28 '22 03:09

Mo Chavoshi