Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SSBrowser: A Sample Visual SourceSafe Automation

0.00/5 (No votes)
3 Jun 2004 1  
A sample VC++ program for performing Visual SourceSafe operations in your application.

Sample Image - SSBrowser.jpg

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.

///////////////////////////////////////////////////////////

// Please edit according to your system's VSS installation

//

#import "C:\Program Files\DevStudio\Vss\win32\ssapi.dll" no_namespace
///////////////////////////////////////////////////////////

Creating a SS database connection is as follows:

// Visual SourceSafe Database

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.

// Smart pointer typedef declarations

//

#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:

// Visual SourceSafe Database

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.

/*****************************************************************
h_InsertPath - Insert a tree structure that represents
               the specified project path.

PARAMETERS:
    IVSSItemPtr vss_item - the sourcesafe item where to start traversing 
    HTREEITEM h_parent - the tree node where to insert 
                         the new tree structure (NULL for tree root)
    CString & rstr_traverseToPath - the path to the project to traverse
    HTREEITEM & rh_deepestNodeFound - (output) the deepest 
                                      node found after traversing the path

RETURNS:
    HTREEITEM - the tree item attached into the specified parent
******************************************************************/
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;
        }

        //Add the item into the tree node

        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)
        {
            // Set image according to check out state

            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);
            // Add (+) sign to indicate the node has child

            tvis.item.cChildren = (l_count > 0) ? 1:0;
        }
        h_item = InsertItem(&tvis);

        // If this node is within the path, consider it as the deepest node

        CString str_currentPath = str_ItemToPath(h_item);
        ATLTRACE(_T("%s\n"),str_currentPath);
        if (rstr_traverseToPath == str_currentPath)
        {
            rh_deepestNodeFound = h_item;
        }

        // If this node is not within the path, we're done

        if (h_parent && rstr_traverseToPath.Find(str_currentPath) != 0)
        {
            return h_item;
        }
        // Otherwise, recursively populate the node path

        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);
    // Get the IVSSVersions object of the item

    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;

        // We use this object to point to each version

        _variant_t object;

        // Now iterate through all the versions

        while (p_versionsEnum->Next(1, &object, &l_fetched) == S_OK)
        {
            try
            {
                // Treat the object as IVSSVersion

                IVSSVersionPtr p_version(object);

                // ...where we can get the version number

                p_version->get_VersionNumber(&l_value);
                // the user who did the changes

                p_version->get_Username(&bstr_text);
                ATLTRACE(_T("Version %d by %s"), l_value, bstr_text); 
                // the date it was checked in

                p_version->get_Date(&date);
                // the action done

                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)
{
    // Download to the working directory

    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 Resources

Other CodeProject articles about SourceSafe.

Here are links to references I used during my "struggle":

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here