TFS Meltdown - How can I recover shelved changes

北战南征 提交于 2019-12-03 17:06:19

I had someone come to me and ask the same question yesterday, fortunately they had a backup of the TFS Project database (tfs_) so we restored that to another database and I poked around and figured it out (so, if you have a backup then yes, you can recover all the files).

First of all a little info on the tables in the database.

A Shelveset can be identified by querying the tbl_Workspace table and looking for all records with Type=1 (Shelveset), you can of course also filter by name with the WorkspaceName column.

The other tables of interest are:

tbl_PendingChanges (which references the WorkspaceId from tbl_Workspace) - which files are part of the ShelveSet

tbl_VersionedItem (linked via ItemId column to tbl_PendingChanges) - parent path and name of files

tbl_Content (linked via FileId to PendingChanges) - this is where your file content is stored in as compressed (gzip) data

Now for the solution; the following query can show you your files:

SELECT c.[CreationDate], c.[Content], vi.[ChildItem], vi.ParentPath
FROM [dbo].[tbl_Content] c 
INNER JOIN [dbo].[tbl_PendingChange] pc ON pc.FileId = c.FileId
INNER JOIN [dbo].[tbl_Workspace] w ON w.WorkspaceId = pc.WorkspaceId
INNER JOIN [dbo].[tbl_VersionedItem] vi ON vi.ItemId = pc.ItemId
WHERE w.WorkspaceName = '<YOUR SHELVESET NAME>'

With that I wrote some code to get the data back from SQL and then decompress the content with the GZipStream class and save the files off to disk.

A week of work was back in an hour or so.

This was done with TFS 2010.

Hope this helps!

Here is an updated response for TFS2015, which had another schema change. Below is a C# Console application for writing the txt files to Desktop. Make sure to fill in connString and shelvesetName variables.

using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.IO.Compression;

namespace RestoreTFSShelve
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            string shelvesetName = "";
            string connString = "";

            SqlConnection cn = new SqlConnection(connString);
            SqlCommand cmd = new SqlCommand(@"
SELECT c.[CreationDate], c.[Content], v.FullPath
FROM [dbo].[tbl_Content] c
INNER JOIN [dbo].tbl_FileMetadata f ON f.ResourceId = c.ResourceId
INNER JOIN [dbo].tbl_FileReference b ON f.ResourceId = b.ResourceId
INNER JOIN [dbo].[tbl_PendingChange] pc ON pc.FileId = b.FileId
INNER JOIN [dbo].[tbl_Workspace] w ON w.WorkspaceId = pc.WorkspaceId
INNER JOIN [dbo].[tbl_Version] v ON v.ItemId = pc.ItemId AND v.VersionTo = 2147483647
WHERE w.WorkspaceName = '@ShelvesetName'", cn);

            cmd.Parameters.AddWithValue("@ShelvesetName", shelvesetName);

            DataTable dt = new DataTable();
            new SqlDataAdapter(cmd).Fill(dt);

            foreach (DataRow row in dt.Rows)
            {
                string[] arrFilePath = row[2].ToString().Split('\\');
                string fileName = arrFilePath[arrFilePath.Length - 2];
                byte[] unzippedContent = Decompress((byte[])row[1]);
                File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), fileName), unzippedContent);
            }
        }

        private static byte[] Decompress(byte[] gzip)
        {
            using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
            {
                const int size = 4096;
                byte[] buffer = new byte[size];
                using (MemoryStream memory = new MemoryStream())
                {
                    int count = 0;
                    do
                    {
                        count = stream.Read(buffer, 0, size);
                        if (count > 0)
                        {
                            memory.Write(buffer, 0, count);
                        }
                    }
                    while (count > 0);
                    return memory.ToArray();
                }
            }
        }
    }
}

I had something similar happen to me with a TFS 2012 instance. My SQL query was a bit different since the schema changed for TFS 2012. Hope this helps someone.

SELECT c.[CreationDate], c.[Content], v.FullPath
FROM [dbo].[tbl_Content] c 
INNER JOIN [dbo].[tbl_File] f ON f.ResourceId = c.ResourceId
INNER JOIN [dbo].[tbl_PendingChange] pc ON pc.FileId = f.FileId--c.FileId
INNER JOIN [dbo].[tbl_Workspace] w ON w.WorkspaceId = pc.WorkspaceId
INNER JOIN [dbo].[tbl_Version] v ON v.ItemId = pc.ItemId AND v.VersionTo = 2147483647
WHERE w.WorkspaceName = @ShelvesetName

2147483647 seems to be 2^32 - 1 which I think may stand for "latest" in TFS 2012. I then also wrote a C# widget to decompress the Gzip-encoded stream and dump it to disk with the proper file name. I am not preserving hierarchy.

string cnstring = string.Format("Server={0};Database={1};Trusted_Connection=True;", txtDbInstance.Text, txtDbName.Text);
SqlConnection cn = new SqlConnection(cnstring);
SqlCommand cmd = new SqlCommand(@"
SELECT c.[CreationDate], c.[Content], v.FullPath
FROM [dbo].[tbl_Content] c 
INNER JOIN [dbo].[tbl_File] f ON f.ResourceId = c.ResourceId
INNER JOIN [dbo].[tbl_PendingChange] pc ON pc.FileId = f.FileId--c.FileId
INNER JOIN [dbo].[tbl_Workspace] w ON w.WorkspaceId = pc.WorkspaceId
INNER JOIN [dbo].[tbl_Version] v ON v.ItemId = pc.ItemId AND v.VersionTo = 2147483647
WHERE w.WorkspaceName = @ShelvesetName", cn);

cmd.Parameters.AddWithValue("@ShelvesetName", txtShelvesetName.Text);

DataTable dt = new DataTable();
new SqlDataAdapter(cmd).Fill(dt);
listBox1.DisplayMember = "FullPath";
listBox1.ValueMember = "FullPath";
listBox1.DataSource = dt;

if(!Directory.Exists(txtOutputLocation.Text)) { Directory.CreateDirectory(txtOutputLocation.Text); }
foreach (DataRow row in dt.Rows)
{
    string[] arrFilePath = row[2].ToString().Split('\\');
    string fileName = arrFilePath[arrFilePath.Length - 2];
    byte[] unzippedContent = Decompress((byte[])row[1]);
    File.WriteAllBytes(Path.Combine(txtOutputLocation.Text, fileName), unzippedContent);
}
}

    static byte[] Decompress(byte[] gzip)
    {
using(GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
{
    const int size = 4096;
    byte[] buffer = new byte[size];
    using(MemoryStream memory = new MemoryStream())
    {
        int count = 0;
        do
        {
    count = stream.Read(buffer, 0, size);
    if(count > 0)
    {
        memory.Write(buffer, 0, count);
    }
        }
        while(count > 0);
        return memory.ToArray();
    }
}
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!