Contents
Very simply, this article covers the basics of how to roll back an entire project hierarchy to a particular label. This is accomplished by using a combination of the SourceSafe API (SourceSafeTypeLib) and the SS.exe command line utility.
I have been programming for a short 4 years. Over the last 1/2 year, I have "discovered" CodeProject and the wonderful resources this site has to offer. This is my first CodeProject article, written with the intention of:
- learning to write articles on CP;
- passing some information back to this community that has given me help;
- offer some insight on a task in Visual Source Safe that doesn't seem to have a lot of help around it.
I was trying to upgrade my version of Microsoft Pattern and Practices team's Enterprise Library code. I had the January release and had made modifications to that; I then wanted to merge in the June release, keeping my changes. Of course, I did it wrong the first time, and wanted to roll the entire project back to a label (which I had thankfully thought to put on before I tried to merge).
I researched on Google and found that what I wanted to do could not be done, at least not in the way I expected to do it. You can use the command line tool (described later in this article) to roll back all files in a project; however, you could not recurse a project tree. And if any of you have seen Enterprise Library, there are about 4-6 levels in the hierarchy. There are about 38 projects or so. Yikes!
So, as any developer would, I decided to come up with my own solution, and pass that on to you. Here are a couple of ways you may find this article useful.
Recursive rollbacks are not supported by the VSS Explorer
In the Visual Source Safe Explorer, you cannot roll back a project or a project tree (i.e. recurse). You are forced to roll back file-by-file, which gets tedious even in small projects. My sample code can be used as a lightweight tool that will help you to automatically recurse through a project tree and roll back all the files.
It is important to note that behind-the-scenes we are still rolling back file-by-file; however, we are automating this by looking at the project tree in VSS, so you do not have to roll back each project or file yourself.
Automation tutorial
This article also serves as a very basic VSS automation and SS.exe command line tutorial. There are actually quite a few things you can do with VSS automation (that are not covered in this article) such as:
- Automated builds
- Automated labels
- Advanced reporting
- Automated change logs or email notification
See the References section at the bottom of this article for some handy articles that discuss the many uses of SourceSafe automation.
I am trying to pass on what I have learned, but I have only been programming for a short time. It only stands to reason that there are bigger and better coding practices out there that I do not follow, or technology that I am not aware of.
I encourage you to use the comments section at the bottom of the article to ask questions, and to let me know about ways in which I can improve this article.
This tool will roll back versions of your SourceSafe files to a certain label or version number. To my knowledge, there is no way to recover these files once you have rolled back. I strongly recommend that you back up your SourceSafe database before using this or any tool that interacts with SS. The author will take no responsibility for lost or destroyed files, or any loss of business resulting from the loss of said files. By downloading the sample project, you agree to abide by these terms.
Please look at this code first; it would be better if you first try this on a test SourceSafe database. I do not provide the executable as a download, as a small precaution that will encourage you to open the project (I hope).
This article assumes you have Visual SourceSafe 6.0 and Visual Studio .NET 2003. (I have not tried this with VS 2002). I am running this code on a Windows XP Professional machine with service pack 1, and have not tested with other platforms. I would appreciate any feedback from anyone using VS 2002 or running the code on other platforms.
The two files we will be working with are SS.exe and SSAPI.dll. If you have installed SourceSafe using the default settings, they should both be located in this directory:
C:\Program Files\Microsoft Visual Studio\VSS\win32
Below is an explanation of the business problem, some approaches I took to solve it, and some explanation of the actual code. To see how to use this program from a UI perspective, see the section "Using the Sample Program".
As I mentioned in the introduction above, I was trying to upgrade my version of Microsoft Pattern and Practices team's Enterprise Library code. I had downloaded the January 2005 release, added the project to SourceSafe, and then made some minor modifications.
In June, they announced a new release which fixed some bugs and which also had some support for Visual Studio 2005. I wanted to merge this new release into my old changed release, and frankly I didn't really know a "good" way to do it. So, taking a precaution, I put a label on my updated January 2005 version that was in SourceSafe.
Good thing I did.
Don't ask me what I did wrong (how could someone mess up copying and pasting files?) but I had the distinct impression that I had messed up my projects. No problem, I thought: I had just put a label on the old, good, version; I'll just completely roll back to that label, and start over. That should be quick. [Insert evil chuckle here]
I went in to SourceSafe Explorer, and immediately realized I could not roll back a whole project to a label or version. I could only roll back file-by-file. So I went looking on Google and finally came to an article which mentioned the following:
While the rollback command does not have a recursive option, you can at least use it to specify all files in a given project. Therefore, you'd have to walk through each project in your database yourself. Here's an example:
ss rollback $/proj1/* -vlbeforechange -i-y
I found out that this person was referring to a command line tool, SS.exe, which I already had in the SourceSafe directory. The problem was - well take a look at this project hierarchy:
Each one of those folders you see has several levels such as the expanded Caching/Configuration project shown. If I wanted to roll back the entire project hierarchy using this methodology, I would have to run it for each folder.
Simply put, theory # 1 was to use the command line tool to rollback * files within each project, like was mentioned in the post above:
[Editor comment: Line breaks used to avoid scrolling.]
ss rollback $/MILB/EnterpriseLibrary/Caching/*
-vlMyLabel -i-y
ss rollback $/MILB/EnterpriseLibrary/Caching/Configuration/*
-vlMyLabel -i-y
ss rollback $/MILB/EnterpriseLibrary/Caching/Configuration/Design/*
-vlMyLabel -i-y
REM ** where "MILB" actually reads
"Microsoft Enterprise Library Application Blocks 1.0"
I figured I could just use the System.IO.DirectoryInfo
object to get a starting directory on my file system, and then use directoryInfoObject.GetDirectories()
in a recursive manner to get all of the sub-folders. The problem with this is that I was relying on the Working Directory to contain everything that was in the SourceSafe tree. In other words, I would be using my file system directory structure to get the hierarchy, and not the actual SourceSafe project structure.
I then started to research if there was a way to get the hierarchy right out of SourceSafe using the command line utility. As I was searching, CodeProject's Marc Clifton turned me on to an article about the SourceSafe API.
I figured, now, that I'd just use the SourceSafe API and trash the command line utility. I could get all of the projects and files using the API. All I had to do was recurse through all of the files, and roll back each file individually, right?
I started happily coding down this path, and when I got all the way through setting up my recursive function, checking for errors, setting up an acceptable UI, and setting up confirmations for the user, I then went to code the next line to do the actual rollback: I was thinking it would be something like vssItemObject.Rollback("LabelName");
. The VssItemObject
has the following methods:
Add
Branch
Checkin
Checkout
Destroy
Get
Label
Move
Share
UndoCheckout
But - you guessed it - no Rollback method.
After pounding my head on the desk for a few minutes, I decided to take a bit of both worlds: I could use the API to get the projects and files, and I could use the command line utility to actually perform the rollback. In the next section, I will very briefly discuss some of the code I ended up using in my "final" (beta?) version that I've included here.
We could have a whole discussion about my commenting style, but without diving into that, here are some short explanations of the key classes and methods.
Enums and helper classes
public enum RollbackConfirmOptions
{
ConfirmOnce,
ConfirmAllFiles,
DoNotConfirm
}
This enum
gives the user the option to have a confirmation dialog just once (Default), a dialog for each and every rollback, or (not recommended) none at all.
public enum RollbackToType
{
RollbackToLabel,
RollbackToVersion
}
Perhaps better described as something like RollbackToType
, this enum
gives the user the option to roll back to a Label, or to roll back to a version number.
public class VSSOptions
{
protected string selectedProject;
public string SelectedProject
{
get { return selectedProject; }
set { selectedProject = value; }
}
protected bool recurse;
public bool Recurse
{
get { return recurse; }
set { recurse = value; }
}
protected RollbackToType rollbackType;
public RollbackToType RollbackType
{
get { return rollbackType; }
set { rollbackType = value; }
}
protected RollbackConfirmOptions confirmOption;
public RollbackConfirmOptions ConfirmOption
{
get { return confirmOption; }
set { confirmOption = value; }
}
protected string rollbackCriteria;
public string RollbackCriteria
{
get { return rollbackCriteria; }
set { rollbackCriteria = value; }
}
protected bool getLatest;
public bool GetLatest
{
get { return getLatest; }
set { getLatest = value; }
}
public VSSOptions()
{
}
}
This class holds the options set when rolling back the files.
SelectedProject
- The VSS project or file to roll back.
Recurse
- true
if you want to recurse every file in the selected project's hierarchy.
RollbackType
- Roll back to a label or to a version number.
ConfirmOption
- Confirm rollback once only, every time, or not at all.
RollbackCriteria
- The actual label or version number to roll back to. If you wish to roll back to label 'My_Label
', enter My_Label
without quotes. If you wish to roll back to each file's version 4, enter 4
.
GetLatest
- true
(default) if you want to replace your working folder's copy of the file after you roll the file back.
We could have used a struct
here, but that would force you to set each and every property when instantiating a new VSSOptions
object, which looks a bit messy in code. Code maintainability won out over the miniscule (if any?) performance increase. We've already lost tons of performance on building the project TreeView
, which you'll see later in this section.
On another note, I originally had all of the properties in this class listed as public
variables. I was advised not to do that, and to always use public
properties, for the following reasons:
- Public properties are a part of what is required to support data binding.
- Extensibility
- Supports declarative instantiation
- Promotes encapsulation
- Avoids entanglement and dependencies
I don't pretend to understand all of this, but I had never heard good arguments before against using public
variables in place of "simple" getters and setters.
OpenSourceSafeDatabase method
This method is responsible for picking a srcsafe.ini file (which represents a SourceSafe database), gathering login credentials, opening the actual database, and populating the TreeView
you see on the right-hand side of the UI.
private void OpenSourceSafeDatabase()
{
SetOpenFileDirectory();
if (openFile.ShowDialog() == DialogResult.Cancel ||
!File.Exists(openFile.FileName))
{
return;
}
The SetOpenFileDirectory()
method is a helper method which searches a registry key to see if you have opened up a SourceSafe database before. If you have, the openFile
component's InitialDirectory
property is set to that path. If you have not, we try to default to C:\Program Files\Microsoft Visual Studio\VSS. Finally, if all else fails, default to the Program Files directory.
The Open File dialog is then opened, and if the user cancels or enters a blank file name, we simply stop and don't do anything.
frmLogon loginForm = new frmLogon();
if (loginForm.ShowDialog(this) != DialogResult.Cancel)
{
frmLogon
is a very simple form which collects the credentials for the SourceSafe database. It exposes two public
properties which retrieve what the user types in.
Cursor = Cursors.WaitCursor;
try
{
vssDB = new VSSDatabaseClass();
vssDB.Open(openFile.FileName,
loginForm.UserName, loginForm.Password);
sbPnlDatabase.Text = "Logged In";
sbPnlMessages.Text =
"Current Project: '" + vssDB.CurrentProject + "'";
rtbMessages.AppendText("Successfully opened " +
"Database '" + openFile.FileName + "'\n");
Microsoft.Win32.RegistryKey key =
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
REGISTRY_KEY, true);
if (key == null)
{
key =
Microsoft.Win32.Registry.CurrentUser.CreateSubKey(
REGISTRY_KEY);
}
key.SetValue("LastDB", openFile.FileName);
if (Convert.ToBoolean(key.GetValue("LoadTreeview", true)) ==
true)
{
BuildTreeView();
}
else
{
pnlTreeview.Visible = false;
}
pnlRecurse.Visible = true;
}
The VSSDatabaseClass
is the key class here from the SourceSafe API that allows you to work with the database. We provide the credentials and open the database, and then we store the database you just opened so that we can try to open the same file next time.
BuildTreeView()
populates the TreeView
control on the right of the UI with the hierarchy of the database you just opened. We will discuss that function shortly. When the TreeView
is properly populated, we show the panel that allows the user to actually run the rollback.
catch(System.Runtime.InteropServices.COMException comex)
{
pnlRecurse.Visible = false;
if (comex.ErrorCode == -2147166526 ||
comex.ErrorCode == -2147352566)
{
rtbMessages.Text =
"User Name or Password is incorrect.";
}
else
{
rtbMessages.Text =
"Unknown VSS COM Exception:\n\n" +
comex.ToString();
}
}
catch(Exception ex)
{
pnlRecurse.Visible = false;
rtbMessages.Text =
"System Exception:\n\n" + ex.ToString();
sbPnlDatabase.Text = "Not Logged In";
sbPnlMessages.Text = "No Project Loaded";
}
finally
{
Cursor = Cursors.Default;
}
}
}
Finally, we catch exceptions. We trap specifically for two particular exceptions, "User Name Not Found" and "Bad Password", because that is a bit of a bad practice - we do not want to give the user that information. We can also trap here for any other error thrown specifically by the SourceSafe API, and then we trap for any other exception resulting from our code.
In any exception case, we don't show the main panel, and we update the status bar showing that we're not logged in.
BuildTreeView method
The intention of the BuildTreeView
method is to populate the TreeView
control, on the right hand side of the UI, with the hierarchy of the database just opened. We have to show folders (projects) first and then files, so I have chosen to build a data table with all of the information necessary for building and sorting the tree.
We use the vssDB.get_VSSItem
method to retrieve the Root
item of the database, and then the vssProj.get_Items
method to retrieve all files and projects of the Root
. Using the BuildNode
helper method, all levels of projects are traversed recursively and added to the data table, which has been passed in By Ref. Finally, we sort the data table, cycle through the sorted rows, and add nodes to the TreeView
accordingly.
The problem I've run in to is that this method seems to take far longer than expected with a database of any significant size. I have tried a methodology where I did not return a data table and instead returned nested TreeNodes directly; however, taking this approach, I'd have to cycle through the VSSItem
twice, once looking for projects, and a second time looking for files. It seems that the vssProj.get_Items
method itself is very slow.
Because this hierarchy takes so long to build, I have given the user the option to "turn off" the TreeView
feature. See the Using the sample program section below.
RecursiveRollback and RollbackNode methods
When the user clicks the cmdRollback
button, we first set up our VSSOptions
object with their choices, such as label name or version number, and whether to get the latest version after rollback. After the options are set, the RecursiveRollback
method is called, which does the actual work, and returns a string which can be displayed in the message box. This string will hold error messages, for instance in a case where a file could not be rolled back.
The first step, after confirming the rollback to the user, is to get the project that is in the TextBox
from the user having typed it in, or from the user having selected the project from the TreeView
.
VSSItem vssProj =
vssDB.get_VSSItem(options.SelectedProject, false);
We set up a global Boolean variable that will catch if the user wants to cancel the rollback, and we call the helper method RollbackNode
which recursively performs the rollback.
private string RollbackNode(VSSItem item, VSSOptions options)
{
if (stopRollback)
{
return "";
}
StringBuilder sb = new StringBuilder();
switch(item.Type)
{
The method checks to see if rollback is cancelled, and then sets up a StringBuilder
variable to hold the current iteration's error messages. The type of the VSSItem
is checked, to see if we are working with a project or a file.
case 0:
if (!options.Recurse)
{
return "";
}
IVSSItems childItems = item.get_Items(false);
if (childItems != null && childItems.Count > 0)
{
foreach(VSSItem childItem in childItems)
{
sb.Append(RollbackNode(childItem, options));
}
}
break;
If the item
type is 0, then the object we are on is a folder/project. We take no action on the project itself other than to recurse through all of its children. If the user has specified that they do not want to recurse, we simply return an empty string. (Not recursing pretty much defeats the purpose of the project, but the option is there anyway.)
case 1:
DialogResult result = DialogResult.Yes;
string fullPath = item.Spec;
string ver = options.RollbackCriteria;
bool isLabel = options.RollbackType ==
RollbackToType.RollbackToLabel;
if (options.ConfirmOption ==
RollbackConfirmOptions.ConfirmAllFiles)
{
string msg = String.Format(
"Confirm rollback of '{0}' to {1} '{2}'?\n",
fullPath, isLabel ? "label" : "version", ver);
msg += "\n'Yes' will roll back the file, " +
"and rollback will continue";
msg += "\n'No' will not roll back the file, " +
"and rollback will continue";
msg += "\n'Cancel' stops the rollback";
result = MessageBox.Show(msg, "Confirm File",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question);
}
if (result == DialogResult.Cancel)
{
stopRollback = true;
}
else if (result == DialogResult.Yes)
{
If the user has specified that they want confirmation on each and every file, we construct a confirmation dialog for the current file. The user can choose to roll the file back or not to roll the file back, or to cancel completely out of the rollback operation. It is important to note that any rollbacks already performed will remain in effect.
[Editor comment: Line breaks used to avoid scrolling.]
if (options.RollbackType ==
RollbackToType.RollbackToLabel)
{
ver = "L" + ver;
}
System.Diagnostics.ProcessStartInfo pInfo =
new System.Diagnostics.ProcessStartInfo();
pInfo.FileName = "C:\\Program Files\\
Microsoft Visual Studio\\VSS\\win32\\SS.exe";
pInfo.UseShellExecute = false;
pInfo.RedirectStandardError = true;
pInfo.CreateNoWindow = true;
if (ver.IndexOf(" ") > -1)
{
pInfo.Arguments = String.Format(
"rollback \"{0}\" \"-V{1}\"{2} -I-Y\n",
fullPath, ver, options.GetLatest ? "" : " -G-");
}
else
{
pInfo.Arguments = String.Format(
"rollback \"{0}\" -V{1}{2} -I-Y\n", fullPath,
ver, options.GetLatest ? "" : " -G-");
}
System.Diagnostics.Process proc =
System.Diagnostics.Process.Start(pInfo);
string err = proc.StandardError.ReadToEnd();
if (err != "" && !err.StartsWith(
"Rollback cannot be undone; some versions" +
"will be lost irretrievably!"))
{
sb.Append(err + "\n");
}
}
break;
}
return sb.ToString();
}
A command line string is constructed to run the SS.exe program, giving it the proper settings to roll back the file with the user's options. If the rollback type is "Label", a "V" and an "L" must be prepended to the argument whereas if it is a version number, just the "V" gets prepended. Also, spaces in the label name must be accounted for.
For example, to roll back MyFile.cs to the version labeled "Release31" and get the latest version of the file to its working directory, the command line would look like this:
rollback "MyFile.cs" -VLRelease31 -I-Y
To roll back MyFile.cs to the version number 5 and not get the latest version of the file to its working directory, the command line would look like this:
rollback "MyFile.cs" -V5 -G- -I-Y
Finally, to roll back MyFile.cs to the version labeled "Release Version 2.5" and get the latest version of the file to its working directory, the command line would look like this:
rollback "MyFile.cs" "-VLRelease Version 2.5" -I-Y
You will notice that when a label has spaces, we put quotes around the entire argument including the switch indicator (-).
Lastly in the RollbackNode
method, we capture (and ignore) extra warnings thrown by the process, and return any other error string returned from the Process
object.
The UI is pretty self-explanatory, but here are some tips:
To perform a rollback on a SourceSafe database
- Open an existing SourceSafe database by clicking on the Open icon or clicking File -> Open SS Database. Navigate to the srcsafe.ini file and click Open.
- Enter your user name and password, and click OK.
- Click on the project in the
TreeView
on the right-hand side, or type in the project path directly into the text box.
- Set your options:
- To recurse a project tree (default), check the checkbox marked Recurse Tree.
- Type in the label name or version number in the Roll Back To text box.
- To roll back to a labeled version (default), click the radio button marked Label. To roll back to a version number, click the radio button marked Version Number.
- To confirm the rollback operation once in the beginning of the rollback, select Confirm Once in the Confirmation Mode dropdown. To confirm every file before rolling back, select Confirm Every File. To forgo confirmation (silent rollback) [not recommended], select Do Not Confirm.
- To get the latest version of a file into the working directory of the project after rollback (default), check the checkbox marked Get Latest Version.
- Press the Roll Back button when you are ready to roll back your files.
- Results of the rollback appear in the text box at the bottom of the screen.
To turn off the TreeView (for large databases)
- Select Tools -> Options on the file menu. Check or uncheck the option Build Treeview after loading Database.
- The
TreeView
will not appear the next time a database is opened if the option is unchecked.
- To manually build and show the
TreeView
when the option is unchecked, select View -> Rebuild Treeview from the file menu.
- Your settings are saved - next time the application is run, the Build Treeview after loading Database option will remain how you had left it when you last exited the application.
In summary, we end up with a rather slow and clunky program, but a program that can recursively roll back a project hierarchy using the SourceSafe project structure directly.
While I wouldn't recommend using this for simple rollbacks, we also see by looking at the API and command line utilities that there are other possibilities for exploring advanced capabilities when the SourceSafe Explorer does not meet our needs. Automated change log creation, based on labels or comments, for example, might be a good application of the API. You could create automated bug-fix logs, you could link SourceSafe checkin versions to bug tickets; the possibilities are certainly there.
I'd be interested in finding out if the next version of SourceSafe and/or Team System makes source control any easier. Do these systems address some of the problems we've covered here? Do they provide any advanced features or functionality that we don't see today in SourceSafe 6.0?
The following is a list of issues or problems that exist in this tool:
- It is very slow to traverse through the hierarchy and build a
TreeView
with larger SourceSafe databases.
- If labels exist for dates later than the label you are rolling back to, those later labels do not get removed. For example if we had a history (with the oldest being listed first) such as this:
Version 1
Label "A"
Version 2
Version 3
Label "B"
Version 4
Label "C"
If we were to roll back to Label "B", the version history in SourceSafe now looks like this (but your file versions would only go up to 3):
Version 1
Label "A"
Version 2
Version 3
Label "B"
Label "C"
I would appreciate any suggestions you may have on tackling these issues.
- 9/26/2005 - First draft published to review site (not CP).
- 9/29/2005 - Second draft published to review site (not CP).