65.9K
CodeProject 正在变化。 阅读更多。
Home

VSS 工具 – 大量取消选中

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2010年3月30日

CPOL

4分钟阅读

viewsIcon

21802

VSS 批量取消签出(undo-checkout)的解决方法。

引言

在 Code Project 上有很多关于自定义 VSS 工具的文章。

很久以前,我创建了自己的 VSS 管理员工具版本。我的工具没有特别出色的地方,只是满足了我日常的需求。

我在其他关于 VSS 工具的文章中没有看到的一个功能是“批量取消签出”功能。一个典型的场景是:当开发人员离开公司后,VSS 管理员发现许多文件被离职员工签出了。

Microsoft Visual SourceSafe Explorer 提供了一个取消签出功能,甚至可以递归地应用于任何选定的项目(子)树。但 SourceSafe Explorer 会对每个签出文件的每个细节提出太多问题。而且,最令人恼火的是,对于每个有多个签出的文件,SourceSafe Explorer 都会弹出一个对话框,列出签出项,并要求你选择要撤销的那个。

对于文中描述的情况,当员工已经离开时,一个好用的功能是能够指向项目(子)树并为特定用户撤销所有签出。

挑战

VSS COM 模型没有提供任何方法来取消除自己之外的任何用户的签出。(尽管 MS VSS Explorer 知道如何做到这一点,但并未公开。)

让我们回顾一下 VSS 类型库。

// typelib filename: ssapi.dll

[
  uuid(783CD4E0-9D54-11CF-B8EE-00608CC9A71F),
  version(5.2),
  helpstring("Microsoft SourceSafe 8.0 Type Library")
]
library SourceSafeTypeLib
...

interface IVSSItemOld : IDispatch {
...
[id(0x0000000d)]
HRESULT UndoCheckout(
                [in, optional, defaultvalue("")] BSTR Local, 
                [in, optional, defaultvalue(0)] long iFlags);

这就是我们拥有的全部。

其中一个选项是作为离职用户登录。但没有该离职用户的密码,我们就无法这样做。不用说,没有公开的方法可以检索密码。

解决方法

VSS 类型库显示,IVSSUser 接口有一个只写密码属性

interface IVSSUser : IDispatch {
...
[id(0x00000003), propput]
HRESULT Password([in] BSTR rhs);
...

所以我们有以下解决方法

  • (以管理员身份登录)重置离职用户的密码;例如,将其设置为“exuser”。
  • 以离职用户身份登录。
  • 为离职用户撤销所有选定的签出。
  • 再次以管理员身份登录。

通过这个解决方法,我为我的 VSS 工具实现了对项目节点以及文件列表的批量取消签出功能。

取消签出文件列表

取消签出项目节点

工作原理

在 Code Project 上有很多关于 VSS API 的优秀文章。例如

我不想让网站充斥着类似的重复代码。所以,我将只发布与“撤销签出”功能相关的代码。

主对话框中实现了四个主要方法。还有一些辅助函数,用于日志记录和检索集合项,但它们都很简单。

  • OnUnCheck()
  • 当用户从项列表视图控件或项目树视图控件中选择 **Uncheck** 菜单项时,将调用 OnUnCheck() 方法。

    OnUnCheck() 从对话框控件中获取选定的用户,并检测它是从树视图(项目)还是从列表视图(文件)调用的。

    然后它执行以下操作:

    • “黑客”用户 – 以选定的离职用户身份登录 VSS;
    • 取消签出列表或项目节点中的文件;
    • 再次以管理员身份登录 VSS。
  • HackUser()
  • // Hack the user: change the user's password and log in as the user
    bool CMainDlg::HackUser(CString sUser, CString pw);

    将离职用户的 ID 和新用户密码作为参数传递给 HackUser()

    HackUser() 执行以下步骤:

    • 使用字符串 ID 查找离职用户对象:IVSSUserPtr pUser;
    • 重置用户密码:HRESULT hr = pUser->put_Password(bsPW);
    • 并以离职用户身份打开 VSS 数据库:hr = m_pVssDB->raw_Open(bsDB, bsUser, bsPW);

  • UndoCheckouts()
  • int CMainDlg::UndoCheckouts(CString strVssPath, LPCTSTR theUser);
    int CMainDlg::UndoCheckouts(IVSSItemPtr vssi, CString strVssPath, LPCTSTR theUser);

    UndoCheckouts() 取消签出列表视图中的文件或树视图中选定的项目节点。

    根据传递的字符串参数的项的 VSS 路径,UndoCheckouts() 获取 IVSSItemPtr

    _bstr_t bsVssPath = strVssPath;
    IVSSItemPtr vssi = m_treeVss.m_pVssDB->GetVSSItem(bsVssPath, 0);
    
    return UndoCheckouts(vssi, strVssPath, theUser);

    如果 IVSSItemPtr vssi 指向项目

    if (vssi->GetType() == VSSITEM_PROJECT)

    该函数会循环遍历项目的子项,递归调用自身。

    UndoCheckouts(vssi2, strVssPath, theUser);

    UndoCheckouts() 最终“触及底部”——该项是 VSSITEM_FILE 类型时,它会调用该方法

    UndoCheckout(vssi, strVssPath, theUser)

    该方法执行项的实际取消签出操作。

  • UndoCheckout()
  • // undo checkout for the single file.
    bool CMainDlg::UndoCheckout(IVSSItemPtr vssi, LPCTSTR strVssPath, 
                                LPCTSTR theUser);

    作为参数传递的文件项 IVSSItemPtr vssi 可能有多个链接——该项可能在 VSS 数据库的不同位置之间共享。

    UndoCheckout() 必须遍历该项的所有链接。

    IVSSItemPtr link;
    IVSSItemsPtr links = vssi->Links;
    int nLinks = vssi->Links->Count;    
    ATLTRACE(_T("Item %s has %d links\n"), (LPCTSTR)vssi->Spec, nLinks);
    for(int i = 0; i < nLinks; i++)
    {
        IVSSItemPtr link = GetOneOfItems(links, i);
        ...
    }

    UndoCheckout() 会跳过已删除的链接。然后,对于每个 VSS 项链接,该函数会获取签出集合——一个链接可能存在多个签出。

    IVSSCheckoutsPtr lnkCheckouts = link->Checkouts;
    long nCheckouts = lnkCheckouts->Count;
    IVSSCheckoutPtr lnkCheckout;
    for(long idx = 0; idx < nCheckouts; idx++) {...}

    UndoCheckout() 最终会找到签出给离职用户的链接,并尝试取消签出(使用默认参数)。

    try
    {
          _bstr_t bsDefault;
          bsDefault = "";
          long nDefault = VSSFLAG_DELNO|VSSFLAG_KEEPYES;
          hr = link->raw_UndoCheckout(bsDefault, nDefault);
    }
    catch (_com_error & e) {...}

代码

////////////////////////////////////////////
// Headers:
//
#import "C:\Program Files\Microsoft Visual SourceSafe\SSAPI.DLL" no_namespace

// UndoCheckout menu-item selected
LRESULT CMainDlg::OnUnCheck(WORD /*wNotifyCode*/, WORD wID, HWND hwnd, BOOL& bHandled);

// Hack the user: change the user's password and log in as the user
bool CMainDlg::HackUser(CString sUser, CString pw);

// undo checkout recursively for the project
// verify strVssPath is part of the item->Spec!
// (there might be multiple links on the item)
int CMainDlg::UndoCheckouts(IVSSItemPtr vssi, CString strVssPath, LPCTSTR theUser);

// undo checkout for the single file; verify spec.
bool CMainDlg::UndoCheckout(IVSSItemPtr vssi, LPCTSTR strVssPath, LPCTSTR theUser);


////////////////////////////////////////////
// Implementation:
//
// UndoCheckout for specific user for:
// all subitems of the item selected on the tree / all items
// in the report / all selected items in the report
// returns the number of unchecked items
LRESULT CMainDlg::OnUnCheck(WORD /*wNotifyCode*/, WORD wID, HWND hwnd, BOOL& bHandled)
{
     int unCount = 0;

     // find out was it called from the tree or from the report
     bool isTree = false;
     if(m_hwndActive == m_wndListView.m_hWnd)
          isTree = false;
     else if(m_hwndActive == m_treeVss.m_hWnd)
          isTree = true;
     else
          return 0;

     // Retrieve the user from dialog control
     CString theUser;
     // ...
     // Retrieve selected project
     CString prj = m_treeVss.GetSelectedProject();
     // ...

     // if called from list view, check if there are selected files
     // (one file, if just one selected; all files, if none selected)
     if(!isTree)
     {
          int count = m_wndListView.GetItemCount();
         int selCount = m_wndListView.GetSelectedCount();
         // ...

         // start unchecking
         m_bReporting = true;
         EnableControls(FALSE);
         CWaitCursor cursor;

         // ImpersonateUser(sUser); reset PW; open DB as the user
         if(theUser.Compare(_T("Admin")))
              bOk = HackUser(theUser, "exuser");

          while((idx = m_wndListView.GetNextItem(idx, flag)) > -1)
         {
              dbg = m_wndListView.GetItemText(idx, 2, buf,
                           sizeof(buf)/sizeof(TCHAR));
              if(!_tcslen(buf))
              continue;

              if(UndoCheckout(buf, theUser))
                  unCount++;
         }

         // reopen DB as admin
         if(theUser.Compare(_T("Admin")))
              OpenDatabase(m_sDbPath);

         m_bReporting = false;
         EnableControls();
     }
     else
     {
         CString message;
         message.Format(_T("Undo '%s' checkouts recursively in %s?"), theUser, prj);
         if(IDOK == MessageBox(message, _T("Uncheck"), MB_OKCANCEL|MB_ICONQUESTION))
         {
              // start unchecking
              m_bReporting = true;
              EnableControls(FALSE);
              CWaitCursor cursor;

              // ImpersonateUser(sUser); reset PW; open DB as the user
              if(theUser.Compare(_T("Admin")))
                  bool bOk = HackUser(theUser, "exuser");

              unCount = UndoCheckouts(prj, theUser);

              // reopen DB as admin
              if(theUser.Compare(_T("Admin")))
                  OpenDatabase(m_sDbPath);
         }
     }
     m_bReporting = false;
     EnableControls();

     // restore selections; refresh controls
     // ...

     return unCount;
}

// Hack the user: change the user's password and log in as the user
bool CMainDlg::HackUser(CString sUser, CString pw)
{
     bool bOk = false;
     IVSSUsersPtr pUsers = NULL;
     try
     {
         pUsers = m_treeVss.m_pVssDB->GetUsers();
         CComVariant var(sUser);
         IVSSUserPtr pUser = pUsers->GetItem(var);
         _bstr_t bsPW = pw;
         HRESULT hr = pUser->put_Password(bsPW);
         if(FAILED(hr))
         {
              ATLTRACE(_T("HackUser::put_Password() error: %X\n"), hr);
              return false;
         }

         _bstr_t bsDB = m_sDbPath;
         _bstr_t bsUser = sUser;

         hr = m_treeVss.m_pVssDB->Close();

         hr = m_treeVss.m_pVssDB->raw_Open(bsDB, bsUser, bsPW);
         if(FAILED(hr))
         {
              ATLTRACE(_T("HackUser:: raw_Open() error: %X\n"), hr);
              return false;
         }

         bOk = true;
     }
     catch (_com_error & e)
     {
         ATLTRACE(_T("HackUser() error: %s\n"), e.ErrorMessage());
     }

     return bOk;
}

// undo checkout recursively for the project
// parameters are retrieved from the dialog controls:
// IVSSItemPtr vssi    - VSS item;
// CString strVssPath  - VSS item path;
// LPCTSTR theUser     - the user to undo checkouts for;
int CMainDlg::UndoCheckouts(IVSSItemPtr vssi, CString strVssPath, LPCTSTR theUser)
{
     int count = 0;
     int  idx = 0;
     _variant_t var;
     IVSSItemPtr   vssi2;
     IVSSItemsPtr  vssis;        // VSS collection
     CWaitCursor cursor;

     // if user clicked Cancel button – cancel checkouts
     GetDlgItem(IDCANCEL).EnableWindow();
     if (!Continue(m_bReporting))
     {
         GetDlgItem(IDCANCEL).EnableWindow(FALSE);
         return idx;
     }

     if (vssi->GetType() == VSSITEM_PROJECT)
     {
         vssis = vssi->GetItems(FALSE);
         for(idx = 0; idx < vssis->GetCount(); idx++)
         {
              var = (long)(idx + 1);
              vssi2 = vssis->GetItem(var);

              UndoCheckouts(vssi2, strVssPath, theUser); 
         }
     }
     else
     {
         // we hit the bottom: it's an item (not the project/folder)
         ATLTRACE(_T("Uncheck item %s, version %d\n"),
                       (LPCTSTR)vssi->Spec, vssi->VersionNumber);
         if(UndoCheckout(vssi, strVssPath, theUser))
              count++;
     }

     return count;
}


// undo checkout for the specified VSS item
// IVSSItemPtr vssi    - VSS item;
// CString strVssPath  - VSS item path;
// LPCTSTR theUser     - the user to undo checkouts for;
bool CMainDlg::UndoCheckout(IVSSItemPtr vssi, LPCTSTR strVssPath, LPCTSTR theUser)
{
     bool bOk = false;
     long nCheckouts = 0;
     long nVersion = (long)(-1);
     CString csUser;
     csUser = theUser;

     // find in the links the one with the right spec
     // NB: there might be multiple links for the VSS items
     //– the item may be shared between the projects
     // and for each link there maight be several checkouts by different users
     IVSSItemPtr link;
     IVSSItemsPtr links = vssi->Links;
     int nLinks = vssi->Links->Count;  
     ATLTRACE(_T("Item %s has %d links\n"), (LPCTSTR)vssi->Spec, nLinks);
     for(int i = 0; i < nLinks; i++)
     {
         IVSSItemPtr link = GetOneOfItems(links, i);
         // simple helper to get links[i]
         
         _variant_t varDeleted = link->Deleted;
         // skip deleted links

         if((BOOL)varDeleted)
              continue;

         CString csLink;
         _bstr_t lnkSpec = link->Spec;
         csLink = (LPCTSTR)lnkSpec;
         long ChkStatus = link->IsCheckedOut;
         switch(ChkStatus)
         {
         case VSSFILE_NOTCHECKEDOUT:
              ATLTRACE(_T"Link %s is NOT checked out\n"), csLink);
              break;
         case VSSFILE_CHECKEDOUT:
              ATLTRACE(_T"Link %s is checked out by OTHER user\n"), csLink);
              break;
         case VSSFILE_CHECKEDOUT_ME:
              ATLTRACE(_T"Link %s is checked out by ME\n"), csLink);
              break;
         }

         // verify there is a checkout for the user
         bool bFound = false;
         IVSSCheckoutsPtr lnkCheckouts = link->Checkouts;
         long nCheckouts = lnkCheckouts->Count;
         IVSSCheckoutPtr lnkCheckout;
         for(long idx = 0; idx < nCheckouts; idx++)
         {
              CComVariant var(idx + 1);
              lnkCheckout = lnkCheckouts->GetItem(var);
              _bstr_t user = lnkCheckout->Username;

              // filter by the user (to simplify comparison, make it CString)
              if(0 == csUser.CompareNoCase((LPCTSTR)user))
              {
                  bFound = true;      // checkout for the user was found
                  nVersion = lnkCheckout->VersionNumber;
                  CString sLocal;
                  sLocal = (LPCTSTR)lnkCheckout->LocalSpec;
                  ATLTRACE(_T "Found checkout ver: %d to: %s\n"), nVersion, sLocal);
                  break;
              }
         }
         if(!bFound)
              return false;

         HRESULT hr = S_OK;
         try
         {
              _bstr_t bsDefault;
              bsDefault = "";
              long nDefault = VSSFLAG_DELNO|VSSFLAG_KEEPYES;

              hr = link->raw_UndoCheckout(bsDefault, nDefault);
         }
         catch (_com_error & e)
         {
              ATLTRACE(_T("%s\n"), e.ErrorMessage());
         }
         if(SUCCEEDED(hr))
         {
              bOk = true;
              break;
         }
         else
              LogCOMError(hr, _T("UndoCheckout()"));  // simple helper function
     }

     return bOk;
}

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

关注点

有没有其他人为 VSS 实现过“批量取消签出”功能?有人知道如何做得更好吗?

历史

目前还没有历史记录。

VSS 工具 – 批量取消签出 - CodeProject - 代码之家
© . All rights reserved.