[PATCH] Fix for sparse updates

Patrick Steinhardt <[email protected]>
Newsgroups gmane.comp.version-control.subversion.tortoisesvn.devel
Message-ID <20161011140307.g6gvwlyoj224cg64@pks-xps>
Hi,

Please find attached a patch to an issue with updating sparse
working copies via 'Update to revision…'.

The first bug causes TSVN to update certain directories in a
sparse working copy fully recursive whereby the folder has
previously been checked out with all contained files being
excluded. To reproduce:

    1. Sparsely check out any repository. The sparse checkout
       needs to contain a directory structure '^/a/b/', where all
       other files are set to excluded (most importantly, all files
       inside 'b/' need to be excluded.
    2. Select 'Update to revision…'
    3. Click 'Choose items…'
    4. Do not modify any checkboxes. Expand the directory '/a',
       but do _not_ expand '/a/b'.
    5. Accept and let TSVN update the working copy.

Expected is that '/a/b' will be updated using the working copy
depth, that is its contents should stay excluded. But in fact,
TSVN will update '/a/b' fully recursive. The issue is fixed by
using working copy depth for directories which have not been
expanded.

This causes new issue where users are now unable to recursively
check out a sparse directory without expanding its children. This
is handled by doing a fully recursive checkout of the folder if
the user has toggled its checkbox (that is unchecked and
re-checked the checkbox).


Thanks and regards
Patrick
SparseUpdate.patch (text/x-diff, 8 KB)
Index: RepositoryBrowser.cpp
===================================================================
--- RepositoryBrowser.cpp	(revision 27484)
+++ RepositoryBrowser.cpp	(working copy)
@@ -845,6 +845,7 @@
         CheckoutDepthForItem(hRoot);
         FilterInfinityDepthItems(m_checkoutDepths);
         FilterInfinityDepthItems(m_updateDepths);
+        FilterUnknownDepthItems(m_updateDepths);
     }
 
     ++m_blockEvents;
@@ -4709,31 +4710,21 @@
     {
         m_checkoutDepths[pItem->url] = svn_depth_infinity;
         m_updateDepths[pItem->url] = svn_depth_infinity;
-        if (!pItem->children_fetched && (pItem->kind == svn_node_dir))
+
+        // If the directory was not expanded and a sticky depth is already set for it in the
+        // working copy, we can simply fetch the node with unknown depth, causing SVN to update
+        // the directory with working-copy depth. Otherwise, if we have no sticky depth yet, we
+        // can assume that the user has just selected the node without expanding it. In that case,
+        // we want to update the directory in a fully recursive way.
+        //
+        // Furthermore, if the user has re-checked the item without expanding its children, we assume
+        // that the user is requesting a fully recursive checkout of the directory.
+        if (!pItem->children_fetched && !pItem->checkbox_toggled && (pItem->kind == svn_node_dir))
         {
             auto foundWCDepth = m_wcDepths.find(pItem->url);
             if (foundWCDepth != m_wcDepths.end())
             {
-                m_updateDepths[pItem->url] = foundWCDepth->second;
-                if (foundWCDepth->second != svn_depth_infinity)
-                {
-                    // add all child states as well
-                    for (const auto& wcdepth : m_wcDepths)
-                    {
-                        if (wcdepth.first.GetLength() > pItem->url.GetLength())
-                        {
-                            CString sUrl = wcdepth.first.Left(pItem->url.GetLength());
-                            if (pItem->url.Compare(sUrl) == 0)
-                            {
-                                if (wcdepth.first[pItem->url.GetLength()] == '/')
-                                {
-                                    m_updateDepths[wcdepth.first] = wcdepth.second;
-                                    m_updateDepths[pItem->url] = svn_depth_unknown;
-                                }
-                            }
-                        }
-                    }
-                }
+                m_updateDepths[pItem->url] = svn_depth_unknown;
             }
         }
     }
@@ -4806,9 +4797,15 @@
 // unchecked -> checked whole subtree -> unchecked whole subtree
 void CRepositoryBrowser::CheckTreeItem( HTREEITEM hItem, bool bCheck )
 {
+    CTreeItem * pItem = (CTreeItem *)m_RepoTree.GetItemData(hItem);
     bool itemExpanded = !!(m_RepoTree.GetItemState(hItem, TVIS_EXPANDED) & TVIS_EXPANDED);
     bool multiselectMode = m_RepoTree.GetSelectedCount() > 1;
 
+    if (pItem)
+    {
+        pItem->checkbox_toggled = true;
+    }
+
     // tri-state logic will be emulated for expanded, checked items, in single selection mode,
     // with at least one child, when all children unchecked
     if(!bCheck && !multiselectMode && itemExpanded && m_RepoTree.GetChildItem(hItem) != NULL && HaveAllChildrenSameCheckState(hItem))
@@ -4840,7 +4837,6 @@
         {
             // clear the wc depth state of this item so it won't get used
             // later in the OnOK handler in case this item is not expanded.
-            CTreeItem * pItem = (CTreeItem *)m_RepoTree.GetItemData(hItem);
             if (pItem)
             {
                 m_wcDepths.erase(pItem->url);
@@ -4929,6 +4925,35 @@
     }
 }
 
+void CRepositoryBrowser::FilterUnknownDepthItems(std::map<CString,svn_depth_t>& depths)
+{
+    if (depths.empty())
+        return;
+
+    // now go through the whole list and remove all children with unknown depth having a parent with unknown depth
+    for (std::map<CString,svn_depth_t>::iterator it = depths.begin(); it != depths.end(); ++it)
+    {
+        if (it->second != svn_depth_unknown)
+            continue;
+
+        for (std::map<CString,svn_depth_t>::iterator it2 = depths.begin(); it2 != depths.end(); ++it2)
+        {
+            if (it->first.Compare(it2->first)==0)
+                continue;
+            if (it2->second != svn_depth_unknown)
+                continue;
+
+            CString url1 = it->first + L"/";
+            if (url1.Compare(it2->first.Left(url1.GetLength()))==0)
+            {
+                std::map<CString,svn_depth_t>::iterator kill = it2;
+                --it2;
+                depths.erase(kill);
+            }
+        }
+    }
+}
+
 void CRepositoryBrowser::SetListItemInfo( int index, const CItem * it )
 {
     if (it->is_external)
Index: RepositoryBrowser.h
===================================================================
--- RepositoryBrowser.h	(revision 27484)
+++ RepositoryBrowser.h	(working copy)
@@ -48,6 +48,7 @@
 public:
     CTreeItem()
         : children_fetched(false)
+        , checkbox_toggled(false)
         , has_child_folders(false)
         , is_external(false)
         , kind(svn_node_unknown)
@@ -64,6 +65,7 @@
     CString         logicalPath;                ///< concatenated unescapedname values
     bool            is_external;                ///< if set, several operations may not be available
     bool            children_fetched;           ///< whether the contents of the folder are known/fetched or not
+    bool            checkbox_toggled;           ///< whether the checkbox has been modified by the user
     bool            has_child_folders;
     bool            unversioned;
     std::deque<CItem>    children;
@@ -292,6 +294,7 @@
 
     void ShowText(const CString& sText, bool forceupdate = false);
     static void FilterInfinityDepthItems(std::map<CString,svn_depth_t>& depths);
+    static void CRepositoryBrowser::FilterUnknownDepthItems(std::map<CString,svn_depth_t>& depths);
     void SetListItemInfo( int index, const CItem * it );
 
     bool RunStartCommit(const CTSVNPathList& pathlist, CString& sLogMsg);
Index: SVNProgressDlg.cpp
===================================================================
--- SVNProgressDlg.cpp	(revision 27484)
+++ SVNProgressDlg.cpp	(working copy)
@@ -2564,9 +2564,11 @@
     sWindowTitle.LoadString(IDS_PROGRS_TITLE_CHECKOUT);
     SetBackgroundImage(IDI_CHECKOUT_BKG);
 
+    std::map<CString, svn_depth_t> unknowns;
     CString sCmdInfo;
     int index = 0;
     CString rootUrl;
+
     for (std::map<CString,svn_depth_t>::iterator it = m_pathdepths.begin(); it != m_pathdepths.end(); ++it)
     {
         if (index == 0)
@@ -2583,6 +2585,7 @@
         }
         if ((depth == svn_depth_unknown)&&(checkoutdir.Exists()))
         {
+            unknowns[it->first] = it->second;
             ++index;
             continue;
         }
@@ -2640,6 +2643,30 @@
         ++index;
     }
 
+    for (std::map<CString,svn_depth_t>::iterator it = unknowns.begin(); it != unknowns.end(); ++it)
+    {
+        CTSVNPath url = CTSVNPath(it->first);
+        CTSVNPath checkoutdir = m_targetPathList[0];
+        CString dir = it->first.Mid(rootUrl.GetLength());
+        if (it->first.Compare((LPCWSTR)rootUrl)!=0)
+        {
+            dir = CPathUtils::PathUnescape(dir);
+            checkoutdir.AppendPathString(dir);
+            dir.TrimLeft('/');
+        }
+
+        CAppUtils::SetWindowTitle(m_hWnd, url.GetUIFileOrDirectoryName(), sWindowTitle);
+        sCmdInfo.FormatMessage(IDS_PROGRS_CMD_SPARSEUPDATE, (LPCTSTR)dir, (LPCTSTR)SVNStatus::GetDepthString(svn_depth_unknown));
+        ReportCmd(sCmdInfo);
+
+        CBlockCacheForPath cacheBlock(checkoutdir.GetWinPath());
+        if (!Update(CTSVNPathList(checkoutdir), m_Revision, svn_depth_unknown, true, (m_options & ProgOptIgnoreExternals) != 0, true, false))
+        {
+            ReportSVNError();
+            return false;
+        }
+    }
+
     DWORD exitcode = 0;
     CString error;
     CHooks::Instance().SetProjectProperties(m_targetPathList.GetCommonRoot(), m_ProjectProperties);
signature.asc (application/pgp-signature, 801 B)
-----BEGIN PGP SIGNATURE-----

iQIcBAABCAAGBQJX/PEbAAoJEBF8Z7aeq/Es/bwP/jD8WJmOlwk9fLMDsSTXOPmZ
DCoUSHZZfu+OtDSd3OHEnrR9EBkWWJu+eiShhbZ21D33tx07vsoJvdByN+2yhQ/G
O1nUqYf+LHHmXBjLVhTwq/lNTdODLXw4f6GutzSIMhNNqAgm0uqgsWH3s3bNM4Uu
Q9p5OSOrfBzlCZvQ0hhzIBfXa7kADuEHKpNLd7ok1hBOTcrQa5QeXux7xzMV4/mx
qMVedYyWYBu71SjBS27uFpB/hHMGXcTsQ9bIhhan75qbgN6JH9kijzO6Ny2BbCGn
/LFGBkKGzuu+D91cYfkargOXn+cBJnoJ3A9Om4QKJBTr69L2dI/Fj3hKU19HEd0a
z4uh9e3Rl+PQGuH57pfcqQ/MvFGZmKBZGgLKw+Agv/pA51PF3If2tQsb0q45j4JG
MmLLcsV07prmIF5kyf4v0d4IuzWuGjamDjcer37YPkVnDZOinNC1PCF1ZuuPgLoz
oU5VrXwSac4YmGGphgaSW6Br03ZKv6CUz3ABTTMLdVnC0+IMkLL/yK+TObTv7gza
q4hF/l+apa6yNLZQ+2+nd6TXBrrPM89MZOSWA511vo5OgQynt2eZecoRfkpIM3l7
sSvPjRUHqpc6ZhKx0dXUyZTgDnb6EyXO5gQAHrm3y6tVO+wZ6fMBvq2DrigL9zPA
uvp00hvY1pYZbAjUgfEx
=pf4b
-----END PGP SIGNATURE-----
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.