Introduction
A few years ago, I had a project to automate our team's build process which involves several SourceSafe operations. I searched in vain for good sample programs but none were comprehensive enough; and most guides and samples that I could find were in Visual Basic. This sample project is therefore some kind of repository of VC++ codes of how to interface with SourceSafe operations, namely:
- Connecting to SourceSafe database
- Operations such as CheckIn, CheckOut, Undo Checkout, Label, Add projects, files etc.
- Traversing the SourceSafe repository and representing it in a tree control
- Getting a file history list and retrieving the version from it
I have recently seen a lot more samples and articles about SourceSafe here in CodeProject, but I'll share this article anyway. Who knows, it may help others as well.
Caveat
The sample program provided is ONLY a demonstration of what you can do with SourceSafe automation. Error checking was grossly omitted for brevity. The author is not responsible for any loss of data resulting from running the program or using the codes in the sample program.
Points of Interest
As a sample application article, I believe the source code will speak for itself. I will however just highlight several aspects of SourceSafe automation in Visual C++.
Interfacing with Visual SourceSafe
There are two ways to interface with VSS.
1. Type Library - Import via SSAPI.DLL
This is done by generating and using ssapi.tlh and ssapi.tli. It has the advantage of having the smart pointers already declared and wrapper methods for error handling (throws COM error HRESULT
, is non S_OK
). However, this method is less portable because it needs the path to the SSAPI.dll. For example: this could be in your stdafx.h.
#import "C:\Program Files\DevStudio\Vss\win32\ssapi.dll" no_namespace
Creating a SS database connection is as follows:
IVSSDatabasePtr mp_vssDatabase;
mp_vssDatabase.CreateInstance(__uuidof(VSSDatabase);
mp_vssDatabase->Open(psz_VSSini, psz_User, psz_Password);
Note the differences when using the ssauto.h.
2. Raw COM interface - Microsoft provided ssauto.h
The file is available from the MSDN site. The header contains only the raw COM interface class and defined constants. If you want to use smart pointers, you'll have to define them yourself. The attached sample project does exactly that. I defined them in the stdafx.h as follows.
#include "ssauto.h"
extern "C" const GUID __declspec(selectany) LIBID_SourceSafeTypeLib =
{0x783cd4e0,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSItem =
{0x783cd4e1,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSVersions =
{0x783cd4e7,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSVersion =
{0x783cd4e8,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSItems =
{0x783cd4e5,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSCheckouts =
{0x8903a770,0xf55f,0x11cf,{0x92,0x27,0x00,0xaa,0x00,0xa1,0xeb,0x95}};
extern "C" const GUID __declspec(selectany) IID_IVSSCheckout =
{0x783cd4e6,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSDatabase =
{0x783cd4e2,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) CLSID_VSSItem =
{0x783cd4e3,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) CLSID_VSSVersion =
{0x783cd4ec,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) CLSID_VSSDatabase =
{0x783cd4e4,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSEvents =
{0x783cd4e9,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSS =
{0x783cd4eb,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSEventHandler =
{0x783cd4ea,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
_COM_SMARTPTR_TYPEDEF(IVSSItem, IID_IVSSItem);
_COM_SMARTPTR_TYPEDEF(IVSSVersions, IID_IVSSVersions);
_COM_SMARTPTR_TYPEDEF(IVSSVersion, IID_IVSSVersion);
_COM_SMARTPTR_TYPEDEF(IVSSItems, IID_IVSSItems);
_COM_SMARTPTR_TYPEDEF(IVSSCheckouts, IID_IVSSCheckouts);
_COM_SMARTPTR_TYPEDEF(IVSSCheckout, IID_IVSSCheckout);
_COM_SMARTPTR_TYPEDEF(IVSSDatabase, IID_IVSSDatabase);
_COM_SMARTPTR_TYPEDEF(IVSSEvents,IID_IVSSEvents);
_COM_SMARTPTR_TYPEDEF(IVSS, IID_IVSS);
_COM_SMARTPTR_TYPEDEF(IVSSEventHandler, IID_IVSSEventHandler);
Creating a SS database connection is as follows:
IVSSDatabasePtr mp_vssDatabase;
CLSIDFromProgID(L"SourceSafe", &clsid ));
CoGetClassObject( clsid, CLSCTX_ALL, NULL, IID_IClassFactory, (void**)&pClf );
pClf->CreateInstance( NULL, IID_IVSSDatabase, (void **) &mp_vssDatabase );
mp_vssDatabase->Open((CComBSTR)psz_VSSini,
(CComBSTR)psz_User, (CComBSTR)psz_Password);
Note the differences when using the ssapi.dll.
Traversing the Visual SourceSafe Database
The sample has a tree control (CSSTreeCtrl
) which provides a good example of navigating the database. The tree loads up fast because, when the tree is initialized, it only populates the nodes which are directly connected to the path when traversing from root to the default project (taken from VSSDatabase::get_CurrentProject
method). Other nodes are opened when the user actually opens them up by clicking on the tree control graphics.
The h_InsertPath
method below shows how to recursively load the partial structure into the tree control. The deepestNodeFound
item can be used later to select/expand the tree.
HTREEITEM CSSTreeCtrl::h_InsertPath(IVSSItemPtr vss_item, HTREEITEM h_parent,
CString & rstr_traverseToPath, HTREEITEM & rh_deepestNodeFound)
{
HTREEITEM h_item = NULL;
try
{
CComBSTR str_name;
v_TestHr(vss_item->get_Name(&str_name));
CString str_Filename = static_cast<LPCTSTR>(str_name);
if (h_parent == NULL)
{
str_Filename = gsz_root;
}
TV_INSERTSTRUCT tvis;
ZeroMemory(&tvis, sizeof(TV_INSERTSTRUCT));
tvis.hParent = h_parent;
tvis.hInsertAfter = TVI_LAST;
tvis.item.mask = TVIF_CHILDREN | TVIF_IMAGE |
TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_PARAM;
tvis.item.lParam = NULL;
tvis.item.pszText = str_Filename.GetBuffer(str_Filename.GetLength());
IVSSItemsPtr vss_items;
int i_type;
long l_count, l_checkOut;
vss_item->get_Type(&i_type);
vss_item->get_IsCheckedOut(&l_checkOut);
if (i_type == VSSITEM_FILE)
{
if (l_checkOut == VSSFILE_NOTCHECKEDOUT)
{
tvis.item.iImage = ITEM_FILE_NORMAL_IMAGE;
tvis.item.iSelectedImage = ITEM_FILE_NORMAL_IMAGE;
}
else
{
tvis.item.iImage = ITEM_FILE_CHECKOUT_IMAGE;
tvis.item.iSelectedImage = ITEM_FILE_CHECKOUT_IMAGE;
}
}
else if (i_type == VSSITEM_PROJECT)
{
vss_item->get_Items(_variant_t(false), &vss_items);
tvis.item.iImage = ITEM_PROJECT_NORMAL_IMAGE;
tvis.item.iSelectedImage = ITEM_PROJECT_SELECTED_IMAGE;
vss_items->get_Count(&l_count);
tvis.item.cChildren = (l_count > 0) ? 1:0;
}
h_item = InsertItem(&tvis);
CString str_currentPath = str_ItemToPath(h_item);
ATLTRACE(_T("%s\n"),str_currentPath);
if (rstr_traverseToPath == str_currentPath)
{
rh_deepestNodeFound = h_item;
}
if (h_parent && rstr_traverseToPath.Find(str_currentPath) != 0)
{
return h_item;
}
if (i_type == VSSITEM_PROJECT)
{
for (long i = 0; i < l_count; i++)
{
IVSSItemPtr vss_ChildItem;
vss_items->get_Item(_variant_t(i+1L), &vss_ChildItem);
h_InsertPath(vss_ChildItem, h_item,
rstr_traverseToPath, rh_deepestNodeFound);
}
}
}
catch (_com_error &e)
{
ATLTRACE(_T("COM error: %s\n"), e.ErrorMessage());
}
return h_item;
}
For the rest of the code, please refer to the CSSTreeCtrl
source code in the sample project.
Retrieving a SourceSafe Item History
The history of an item is accessible via IVSSVersions
which provides an interface to IEnum
from which we could iterate each history item.
void v_DumpItemHistory(IVSSItemPtr vss_item)
{
int i_type = 0;
IVSSVersionsPtr versions;
vss_item->get_Type(&i_type);
if (vss_item->get_Versions((i_type == VSSITEM_PROJECT)?
VSSFLAG_HISTIGNOREFILES:0, &versions) == S_OK)
{
CComPtr<IENUMVARIANT> p_versionsEnum;
CComPtr<IUNKNOWN> spunk;
versions->_NewEnum(&spunk);
spunk->QueryInterface(IID_IEnumVARIANT, (LPVOID*)&p_versionsEnum);
ULONG l_fetched;
long l_value;
CString str_display;
CComBSTR bstr_text;
DATE date;
_variant_t object;
while (p_versionsEnum->Next(1, &object, &l_fetched) == S_OK)
{
try
{
IVSSVersionPtr p_version(object);
p_version->get_VersionNumber(&l_value);
p_version->get_Username(&bstr_text);
ATLTRACE(_T("Version %d by %s"), l_value, bstr_text);
p_version->get_Date(&date);
p_version->get_Action(&bstr_text);
ATLTRACE(_T(" On %s %s\n"),
str_FormatDateTime(date), bstr_text);
}
catch (_com_error & e)
{
ATLTRACE(_T("%s\n"), e.ErrorMessage());
}
}
}
}
A more comprehensive example is in the sample project's CMainDialog::OnLoadHistory()
.
Downloading an Old Version of an Item
You can retrieve an old version as long as you have the version number. The old version of an item is also represented as a VSSItem
object where you can call Get()
method to download into the working directory. Note that you cannot checkout an old version.
IVSSItemPtr vss_oldItem;
if (vss_item->get_Version(_variant_t(l_oldVersionNumber),
&vss_oldItem) == S_OK)
{
CComBSTR bstr_localSpec;
vss_item->get_LocalSpec(&bstr_localSpec);
vss_oldItem->Get(&bstr_localSpec, VSSFLAG_REPREPLACE);
}
The Sample Application
I highlight the sample application itself because it is also a good example of:
- WTL Application
- Subclassing tree control in WTL
- Message reflection
- Resizing dialog
Other CodeProject articles about SourceSafe.
Here are links to references I used during my "struggle":