1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
20 #include ".\cacheddirectory.h"
\r
21 #include "SVNHelpers.h"
\r
22 #include "SVNStatusCache.h"
\r
23 #include "SVNStatus.h"
\r
26 CCachedDirectory::CCachedDirectory(void)
\r
28 m_entriesFileTime = 0;
\r
29 m_propsFileTime = 0;
\r
30 m_currentStatusFetchingPathTicks = 0;
\r
31 m_bCurrentFullStatusValid = false;
\r
32 m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;
\r
33 m_bRecursive = true;
\r
36 CCachedDirectory::~CCachedDirectory(void)
\r
40 CCachedDirectory::CCachedDirectory(const CTSVNPath& directoryPath)
\r
42 ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));
\r
44 m_directoryPath = directoryPath;
\r
45 m_entriesFileTime = 0;
\r
46 m_propsFileTime = 0;
\r
47 m_currentStatusFetchingPathTicks = 0;
\r
48 m_bCurrentFullStatusValid = false;
\r
49 m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;
\r
50 m_bRecursive = true;
\r
53 BOOL CCachedDirectory::SaveToDisk(FILE * pFile)
\r
55 AutoLocker lock(m_critSec);
\r
56 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;
\r
58 unsigned int value = 1;
\r
59 WRITEVALUETOFILE(value); // 'version' of this save-format
\r
60 value = (int)m_entryCache.size();
\r
61 WRITEVALUETOFILE(value); // size of the cache map
\r
62 // now iterate through the maps and save every entry.
\r
63 for (CacheEntryMap::iterator I = m_entryCache.begin(); I != m_entryCache.end(); ++I)
\r
65 const CString& key = I->first;
\r
66 value = key.GetLength();
\r
67 WRITEVALUETOFILE(value);
\r
70 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
\r
72 if (!I->second.SaveToDisk(pFile))
\r
76 value = (int)m_childDirectories.size();
\r
77 WRITEVALUETOFILE(value);
\r
78 for (ChildDirStatus::iterator I = m_childDirectories.begin(); I != m_childDirectories.end(); ++I)
\r
80 const CString& path = I->first.GetWinPathString();
\r
81 value = path.GetLength();
\r
82 WRITEVALUETOFILE(value);
\r
85 if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)
\r
87 svn_wc_status_kind status = I->second;
\r
88 WRITEVALUETOFILE(status);
\r
91 WRITEVALUETOFILE(m_entriesFileTime);
\r
92 WRITEVALUETOFILE(m_propsFileTime);
\r
93 value = m_directoryPath.GetWinPathString().GetLength();
\r
94 WRITEVALUETOFILE(value);
\r
97 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
\r
100 if (!m_ownStatus.SaveToDisk(pFile))
\r
102 WRITEVALUETOFILE(m_currentFullStatus);
\r
103 WRITEVALUETOFILE(m_mostImportantFileStatus);
\r
107 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)
\r
109 AutoLocker lock(m_critSec);
\r
110 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
\r
113 unsigned int value = 0;
\r
114 LOADVALUEFROMFILE(value);
\r
116 return false; // not the correct version
\r
118 LOADVALUEFROMFILE(mapsize);
\r
119 for (int i=0; i<mapsize; ++i)
\r
121 LOADVALUEFROMFILE(value);
\r
122 if (value > MAX_PATH)
\r
127 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
\r
129 sKey.ReleaseBuffer(0);
\r
132 sKey.ReleaseBuffer(value);
\r
133 CStatusCacheEntry entry;
\r
134 if (!entry.LoadFromDisk(pFile))
\r
136 m_entryCache[sKey] = entry;
\r
139 LOADVALUEFROMFILE(mapsize);
\r
140 for (int i=0; i<mapsize; ++i)
\r
142 LOADVALUEFROMFILE(value);
\r
143 if (value > MAX_PATH)
\r
148 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)
\r
150 sPath.ReleaseBuffer(0);
\r
153 sPath.ReleaseBuffer(value);
\r
154 svn_wc_status_kind status;
\r
155 LOADVALUEFROMFILE(status);
\r
156 m_childDirectories[CTSVNPath(sPath)] = status;
\r
159 LOADVALUEFROMFILE(m_entriesFileTime);
\r
160 LOADVALUEFROMFILE(m_propsFileTime);
\r
161 LOADVALUEFROMFILE(value);
\r
162 if (value > MAX_PATH)
\r
167 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
\r
169 sPath.ReleaseBuffer(0);
\r
172 sPath.ReleaseBuffer(value);
\r
173 m_directoryPath.SetFromWin(sPath);
\r
175 if (!m_ownStatus.LoadFromDisk(pFile))
\r
178 LOADVALUEFROMFILE(m_currentFullStatus);
\r
179 LOADVALUEFROMFILE(m_mostImportantFileStatus);
\r
181 catch ( CAtlException )
\r
189 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTSVNPath& path, bool bRecursive, bool bFetch /* = true */)
\r
191 CString strCacheKey;
\r
192 bool bThisDirectoryIsUnversioned = false;
\r
193 bool bRequestForSelf = false;
\r
194 if(path.IsEquivalentToWithoutCase(m_directoryPath))
\r
196 bRequestForSelf = true;
\r
199 // In all most circumstances, we ask for the status of a member of this directory.
\r
200 ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);
\r
202 // Check if the entries file has been changed
\r
203 CTSVNPath entriesFilePath(m_directoryPath);
\r
204 CTSVNPath propsDirPath(m_directoryPath);
\r
205 if (g_SVNAdminDir.IsVSNETHackActive())
\r
207 entriesFilePath.AppendPathString(g_SVNAdminDir.GetVSNETAdminDirName() + _T("\\entries"));
\r
208 propsDirPath.AppendPathString(g_SVNAdminDir.GetVSNETAdminDirName() + _T("\\dir-props"));
\r
212 entriesFilePath.AppendPathString(g_SVNAdminDir.GetAdminDirName() + _T("\\entries"));
\r
213 propsDirPath.AppendPathString(g_SVNAdminDir.GetAdminDirName() + _T("\\dir-props"));
\r
215 if ( (m_entriesFileTime == entriesFilePath.GetLastWriteTime()) && ((entriesFilePath.GetLastWriteTime() == 0) || (m_propsFileTime == propsDirPath.GetLastWriteTime())) )
\r
217 m_entriesFileTime = entriesFilePath.GetLastWriteTime();
\r
218 if (m_entriesFileTime)
\r
219 m_propsFileTime = propsDirPath.GetLastWriteTime();
\r
221 if(m_entriesFileTime == 0)
\r
223 // We are a folder which is not in a working copy
\r
224 bThisDirectoryIsUnversioned = true;
\r
225 m_ownStatus.SetStatus(NULL);
\r
227 // If a user removes the .svn directory, we get here with m_entryCache
\r
228 // not being empty, but still us being unversioned
\r
229 if (!m_entryCache.empty())
\r
231 m_entryCache.clear();
\r
233 ATLASSERT(m_entryCache.empty());
\r
235 // However, a member *DIRECTORY* might be the top of WC
\r
236 // so we need to ask them to get their own status
\r
237 if(!path.IsDirectory())
\r
239 if ((PathFileExists(path.GetWinPath()))||(bRequestForSelf))
\r
240 return CStatusCacheEntry();
\r
241 // the entry doesn't exist anymore!
\r
242 // but we can't remove it from the cache here:
\r
243 // the GetStatusForMember() method is called only with a read
\r
244 // lock and not a write lock!
\r
245 // So mark it for crawling, and let the crawler remove it
\r
247 CSVNStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory());
\r
248 return CStatusCacheEntry();
\r
252 // If we're in the special case of a directory being asked for its own status
\r
253 // and this directory is unversioned, then we should just return that here
\r
254 if(bRequestForSelf)
\r
256 return CStatusCacheEntry();
\r
261 if(path.IsDirectory())
\r
263 // We don't have directory status in our cache
\r
264 // Ask the directory if it knows its own status
\r
265 CCachedDirectory * dirEntry = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);
\r
266 if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))
\r
268 // To keep recursive status up to date, we'll request that children are all crawled again
\r
269 // This will be very quick if nothings changed, because it will all be cache hits
\r
272 AutoLocker lock(dirEntry->m_critSec);
\r
273 ChildDirStatus::const_iterator it;
\r
274 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
\r
276 CSVNStatusCache::Instance().AddFolderForCrawling(it->first);
\r
280 return dirEntry->GetOwnStatus(bRecursive);
\r
286 // if we currently are fetching the status of the directory
\r
287 // we want the status for, we just return an empty entry here
\r
288 // and don't wait for that fetching to finish.
\r
289 // That's because fetching the status can take a *really* long
\r
290 // time (e.g. if a commit is also in progress on that same
\r
291 // directory), and we don't want to make the explorer appear
\r
293 AutoLocker pathlock(m_critSecPath);
\r
294 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
\r
296 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
\r
298 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
\r
299 m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;
\r
300 return CStatusCacheEntry();
\r
304 // Look up a file in our own cache
\r
305 AutoLocker lock(m_critSec);
\r
306 strCacheKey = GetCacheKey(path);
\r
307 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
\r
308 if(itMap != m_entryCache.end())
\r
310 // We've hit the cache - check for timeout
\r
311 if(!itMap->second.HasExpired((long)GetTickCount()))
\r
313 if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))
\r
315 if ((itMap->second.GetEffectiveStatus()!=svn_wc_status_missing)||(!PathFileExists(path.GetWinPath())))
\r
317 // Note: the filetime matches after a modified has been committed too.
\r
318 // So in that case, we would return a wrong status (e.g. 'modified' instead
\r
319 // of 'normal') here.
\r
320 return itMap->second;
\r
329 AutoLocker pathlock(m_critSecPath);
\r
330 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
\r
332 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
\r
334 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
\r
335 m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;
\r
336 return CStatusCacheEntry();
\r
339 // if we're fetching the status for the explorer,
\r
340 // we don't refresh the status but use the one
\r
341 // we already have (to save time and make the explorer
\r
342 // more responsive in stress conditions).
\r
343 // We leave the refreshing to the crawler.
\r
344 if ((!bFetch)&&(m_entriesFileTime))
\r
346 CSVNStatusCache::Instance().AddFolderForCrawling(path.GetDirectory());
\r
347 return CStatusCacheEntry();
\r
349 AutoLocker lock(m_critSec);
\r
350 m_entriesFileTime = entriesFilePath.GetLastWriteTime();
\r
351 m_propsFileTime = propsDirPath.GetLastWriteTime();
\r
352 m_entryCache.clear();
\r
353 strCacheKey = GetCacheKey(path);
\r
356 svn_opt_revision_t revision;
\r
357 revision.kind = svn_opt_revision_unspecified;
\r
359 // We've not got this item in the cache - let's add it
\r
360 // We never bother asking SVN for the status of just one file, always for its containing directory
\r
362 if (g_SVNAdminDir.IsAdminDirPath(path.GetWinPathString()))
\r
364 // We're being asked for the status of an .SVN directory
\r
365 // It's not worth asking for this
\r
366 return CStatusCacheEntry();
\r
371 AutoLocker pathlock(m_critSecPath);
\r
372 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
\r
374 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
\r
376 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
\r
377 m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;
\r
378 return CStatusCacheEntry();
\r
382 SVNPool subPool(CSVNStatusCache::Instance().m_svnHelp.Pool());
\r
384 AutoLocker lock(m_critSec);
\r
385 m_mostImportantFileStatus = svn_wc_status_none;
\r
386 m_childDirectories.clear();
\r
387 m_entryCache.clear();
\r
388 m_ownStatus.SetStatus(NULL);
\r
389 m_bRecursive = bRecursive;
\r
391 if(!bThisDirectoryIsUnversioned)
\r
394 AutoLocker pathlock(m_critSecPath);
\r
395 m_currentStatusFetchingPath = m_directoryPath;
\r
396 m_currentStatusFetchingPathTicks = GetTickCount();
\r
398 ATLTRACE(_T("svn_cli_stat for '%s' (req %s)\n"), m_directoryPath.GetWinPath(), path.GetWinPath());
\r
399 svn_error_t* pErr = svn_client_status4 (
\r
401 m_directoryPath.GetSVNApiPath(subPool),
\r
405 svn_depth_immediates,
\r
409 FALSE, //ignore externals
\r
410 NULL, //changelists
\r
411 CSVNStatusCache::Instance().m_svnHelp.ClientContext(),
\r
415 AutoLocker pathlock(m_critSecPath);
\r
416 m_currentStatusFetchingPath.Reset();
\r
418 ATLTRACE(_T("svn_cli_stat finished for '%s'\n"), m_directoryPath.GetWinPath(), path.GetWinPath());
\r
422 // The most likely error on a folder is that it's not part of a WC
\r
423 // In most circumstances, this will have been caught earlier,
\r
424 // but in some situations, we'll get this error.
\r
425 // If we allow ourselves to fall on through, then folders will be asked
\r
426 // for their own status, and will set themselves as unversioned, for the
\r
427 // benefit of future requests
\r
428 ATLTRACE("svn_cli_stat err: '%s'\n", pErr->message);
\r
429 svn_error_clear(pErr);
\r
430 // No assert here! Since we _can_ get here, an assertion is not an option!
\r
431 // Reasons to get here:
\r
432 // - renaming a folder with many sub folders --> results in "not a working copy" if the revert
\r
433 // happens between our checks and the svn_client_status() call.
\r
434 // - reverting a move/copy --> results in "not a working copy" (as above)
\r
435 if (!m_directoryPath.HasAdminDir())
\r
437 m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none;
\r
438 return CStatusCacheEntry();
\r
442 ATLTRACE("svn_cli_stat error, assume none status\n");
\r
443 // Since we only assume a none status here due to svn_client_status()
\r
444 // returning an error, make sure that this status times out soon.
\r
445 CSVNStatusCache::Instance().m_folderCrawler.BlockPath(m_directoryPath, 2000);
\r
446 CSVNStatusCache::Instance().AddFolderForCrawling(m_directoryPath);
\r
447 return CStatusCacheEntry();
\r
453 ATLTRACE("Skipped SVN status for unversioned folder\n");
\r
456 // Now that we've refreshed our SVN status, we can see if it's
\r
457 // changed the 'most important' status value for this directory.
\r
458 // If it has, then we should tell our parent
\r
459 UpdateCurrentStatus();
\r
461 if (path.IsDirectory())
\r
463 CCachedDirectory * dirEntry = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);
\r
464 if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))
\r
466 CSVNStatusCache::Instance().AddFolderForCrawling(path);
\r
467 return dirEntry->GetOwnStatus(bRecursive);
\r
470 // If the status *still* isn't valid here, it means that
\r
471 // the current directory is unversioned, and we shall need to ask its children for info about themselves
\r
473 return dirEntry->GetStatusForMember(path,bRecursive);
\r
474 CSVNStatusCache::Instance().AddFolderForCrawling(path);
\r
475 return CStatusCacheEntry();
\r
479 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
\r
480 if(itMap != m_entryCache.end())
\r
482 return itMap->second;
\r
486 AddEntry(path, NULL);
\r
487 return CStatusCacheEntry();
\r
491 CCachedDirectory::AddEntry(const CTSVNPath& path, const svn_wc_status2_t* pSVNStatus, DWORD validuntil /* = 0*/)
\r
493 AutoLocker lock(m_critSec);
\r
494 if(path.IsDirectory())
\r
496 CCachedDirectory * childDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path);
\r
499 if ((childDir->GetCurrentFullStatus() != svn_wc_status_ignored)||(pSVNStatus==NULL)||(pSVNStatus->text_status != svn_wc_status_unversioned))
\r
500 childDir->m_ownStatus.SetStatus(pSVNStatus);
\r
501 childDir->m_ownStatus.SetKind(svn_node_dir);
\r
506 CString cachekey = GetCacheKey(path);
\r
507 CacheEntryMap::iterator entry_it = m_entryCache.lower_bound(cachekey);
\r
508 if (entry_it != m_entryCache.end() && entry_it->first == cachekey)
\r
512 if (entry_it->second.GetEffectiveStatus() > svn_wc_status_none &&
\r
513 entry_it->second.GetEffectiveStatus() != SVNStatus::GetMoreImportant(pSVNStatus->prop_status, pSVNStatus->text_status))
\r
515 CSVNStatusCache::Instance().UpdateShell(path);
\r
516 ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
\r
522 entry_it = m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
\r
524 entry_it->second = CStatusCacheEntry(pSVNStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);
\r
530 CCachedDirectory::GetCacheKey(const CTSVNPath& path)
\r
532 // All we put into the cache as a key is just the end portion of the pathname
\r
533 // There's no point storing the path of the containing directory for every item
\r
534 return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength());
\r
538 CCachedDirectory::GetFullPathString(const CString& cacheKey)
\r
540 return m_directoryPath.GetWinPathString() + _T("\\") + cacheKey;
\r
543 svn_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, svn_wc_status2_t *status, apr_pool_t * /*pool*/)
\r
545 CCachedDirectory* pThis = (CCachedDirectory*)baton;
\r
548 return SVN_NO_ERROR;
\r
554 if ((status->text_status != svn_wc_status_none)&&(status->text_status != svn_wc_status_ignored))
\r
555 svnPath.SetFromSVN(path, (status->entry->kind == svn_node_dir));
\r
557 svnPath.SetFromSVN(path);
\r
559 if(svnPath.IsDirectory())
\r
561 if(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))
\r
563 if (pThis->m_bRecursive)
\r
565 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
\r
566 CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);
\r
569 // Make sure we know about this child directory
\r
570 // This initial status value is likely to be overwritten from below at some point
\r
571 svn_wc_status_kind s = SVNStatus::GetMoreImportant(status->text_status, status->prop_status);
\r
572 CCachedDirectory * cdir = CSVNStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath);
\r
575 // This child directory is already in our cache!
\r
576 // So ask this dir about its recursive status
\r
577 svn_wc_status_kind st = SVNStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
\r
578 AutoLocker lock(pThis->m_critSec);
\r
579 pThis->m_childDirectories[svnPath] = st;
\r
583 // the child directory is not in the cache. Create a new entry for it in the cache which is
\r
584 // initially 'unversioned'. But we added that directory to the crawling list above, which
\r
585 // means the cache will be updated soon.
\r
586 CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
\r
587 AutoLocker lock(pThis->m_critSec);
\r
588 pThis->m_childDirectories[svnPath] = s;
\r
594 // Keep track of the most important status of all the files in this directory
\r
595 // Don't include subdirectories in this figure, because they need to provide their
\r
596 // own 'most important' value
\r
597 pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->text_status);
\r
598 pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->prop_status);
\r
599 if (((status->text_status == svn_wc_status_unversioned)||(status->text_status == svn_wc_status_none))
\r
600 &&(CSVNStatusCache::Instance().IsUnversionedAsModified()))
\r
602 // treat unversioned files as modified
\r
603 if (pThis->m_mostImportantFileStatus != svn_wc_status_added)
\r
604 pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, svn_wc_status_modified);
\r
610 svnPath.SetFromSVN(path);
\r
611 // Subversion returns no 'entry' field for versioned folders if they're
\r
612 // part of another working copy (nested layouts).
\r
613 // So we have to make sure that such an 'unversioned' folder really
\r
615 if (((status->text_status == svn_wc_status_unversioned)||(status->text_status == svn_wc_status_ignored))&&(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))&&(svnPath.IsDirectory()))
\r
617 if (svnPath.HasAdminDir())
\r
619 CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);
\r
620 // Mark the directory as 'versioned' (status 'normal' for now).
\r
621 // This initial value will be overwritten from below some time later
\r
623 AutoLocker lock(pThis->m_critSec);
\r
624 pThis->m_childDirectories[svnPath] = svn_wc_status_normal;
\r
626 // Make sure the entry is also in the cache
\r
627 CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
\r
628 // also mark the status in the status object as normal
\r
629 status->text_status = svn_wc_status_normal;
\r
632 else if (status->text_status == svn_wc_status_external)
\r
634 CSVNStatusCache::Instance().AddFolderForCrawling(svnPath);
\r
635 // Mark the directory as 'versioned' (status 'normal' for now).
\r
636 // This initial value will be overwritten from below some time later
\r
638 AutoLocker lock(pThis->m_critSec);
\r
639 pThis->m_childDirectories[svnPath] = svn_wc_status_normal;
\r
641 // we have added a directory to the child-directory list of this
\r
642 // directory. We now must make sure that this directory also has
\r
643 // an entry in the cache.
\r
644 CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
\r
645 // also mark the status in the status object as normal
\r
646 status->text_status = svn_wc_status_normal;
\r
650 if (svnPath.IsDirectory())
\r
652 AutoLocker lock(pThis->m_critSec);
\r
653 pThis->m_childDirectories[svnPath] = SVNStatus::GetMoreImportant(status->text_status, status->prop_status);
\r
655 else if ((CSVNStatusCache::Instance().IsUnversionedAsModified())&&(status->text_status != svn_wc_status_ignored))
\r
657 // make this unversioned item change the most important status of this
\r
658 // folder to modified if it doesn't already have another status
\r
659 if (pThis->m_mostImportantFileStatus != svn_wc_status_added)
\r
660 pThis->m_mostImportantFileStatus = SVNStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, svn_wc_status_modified);
\r
665 pThis->AddEntry(svnPath, status);
\r
667 return SVN_NO_ERROR;
\r
671 CCachedDirectory::IsOwnStatusValid() const
\r
673 return m_ownStatus.HasBeenSet() &&
\r
674 !m_ownStatus.HasExpired(GetTickCount()) &&
\r
675 // 'external' isn't a valid status. That just
\r
676 // means the folder is not part of the current working
\r
677 // copy but it still has its own 'real' status
\r
678 m_ownStatus.GetEffectiveStatus()!=svn_wc_status_external &&
\r
679 m_ownStatus.IsKindKnown();
\r
682 void CCachedDirectory::Invalidate()
\r
684 m_ownStatus.Invalidate();
\r
687 svn_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()
\r
689 // Combine our OWN folder status with the most important of our *FILES'* status.
\r
690 svn_wc_status_kind retVal = SVNStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());
\r
692 if ((retVal != svn_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
\r
694 if ((retVal == svn_wc_status_added)||(retVal == svn_wc_status_deleted)||(retVal == svn_wc_status_missing))
\r
695 retVal = svn_wc_status_modified;
\r
698 // Now combine all our child-directorie's status
\r
700 AutoLocker lock(m_critSec);
\r
701 ChildDirStatus::const_iterator it;
\r
702 for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)
\r
704 retVal = SVNStatus::GetMoreImportant(retVal, it->second);
\r
705 if ((retVal != svn_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
\r
707 if ((retVal == svn_wc_status_added)||(retVal == svn_wc_status_deleted)||(retVal == svn_wc_status_missing))
\r
708 retVal = svn_wc_status_modified;
\r
715 // Update our composite status and deal with things if it's changed
\r
716 void CCachedDirectory::UpdateCurrentStatus()
\r
718 svn_wc_status_kind newStatus = CalculateRecursiveStatus();
\r
720 if ((newStatus != m_currentFullStatus)&&(m_ownStatus.IsVersioned()))
\r
722 if ((m_currentFullStatus != svn_wc_status_none)&&(m_ownStatus.GetEffectiveStatus() != svn_wc_status_ignored))
\r
724 // Our status has changed - tell the shell
\r
725 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);
\r
726 CSVNStatusCache::Instance().UpdateShell(m_directoryPath);
\r
728 if (m_ownStatus.GetEffectiveStatus() != svn_wc_status_ignored)
\r
729 m_currentFullStatus = newStatus;
\r
731 m_currentFullStatus = svn_wc_status_ignored;
\r
733 // And tell our parent, if we've got one...
\r
734 // we tell our parent *always* about our status, even if it hasn't
\r
735 // changed. This is to make sure that the parent has really our current
\r
736 // status - the parent can decide itself if our status has changed
\r
738 CTSVNPath parentPath = m_directoryPath.GetContainingDirectory();
\r
739 if(!parentPath.IsEmpty())
\r
741 // We have a parent
\r
742 CCachedDirectory * cachedDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(parentPath);
\r
744 cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);
\r
749 // Receive a notification from a child that its status has changed
\r
750 void CCachedDirectory::UpdateChildDirectoryStatus(const CTSVNPath& childDir, svn_wc_status_kind childStatus)
\r
752 svn_wc_status_kind currentStatus = svn_wc_status_none;
\r
754 AutoLocker lock(m_critSec);
\r
755 currentStatus = m_childDirectories[childDir];
\r
757 if ((currentStatus != childStatus)||(!IsOwnStatusValid()))
\r
760 AutoLocker lock(m_critSec);
\r
761 m_childDirectories[childDir] = childStatus;
\r
763 UpdateCurrentStatus();
\r
767 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)
\r
769 // Don't return recursive status if we're unversioned ourselves.
\r
770 if(bRecursive && m_ownStatus.GetEffectiveStatus() > svn_wc_status_unversioned)
\r
772 CStatusCacheEntry recursiveStatus(m_ownStatus);
\r
773 UpdateCurrentStatus();
\r
774 recursiveStatus.ForceStatus(m_currentFullStatus);
\r
775 return recursiveStatus;
\r
779 return m_ownStatus;
\r
783 void CCachedDirectory::RefreshStatus(bool bRecursive)
\r
785 // Make sure that our own status is up-to-date
\r
786 GetStatusForMember(m_directoryPath,bRecursive);
\r
788 AutoLocker lock(m_critSec);
\r
789 // We also need to check if all our file members have the right date on them
\r
790 CacheEntryMap::iterator itMembers;
\r
791 std::set<CTSVNPath> refreshedpaths;
\r
792 DWORD now = GetTickCount();
\r
793 if (m_entryCache.size() == 0)
\r
795 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
\r
797 if (itMembers->first)
\r
799 CTSVNPath filePath(m_directoryPath);
\r
800 filePath.AppendPathString(itMembers->first);
\r
801 std::set<CTSVNPath>::iterator refr_it;
\r
802 if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&
\r
803 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))
\r
805 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))
\r
808 // We need to request this item as well
\r
809 GetStatusForMember(filePath,bRecursive);
\r
810 // GetStatusForMember now has recreated the m_entryCache map.
\r
811 // So start the loop again, but add this path to the refreshed paths set
\r
812 // to make sure we don't refresh this path again. This is to make sure
\r
813 // that we don't end up in an endless loop.
\r
815 refreshedpaths.insert(refr_it, filePath);
\r
816 itMembers = m_entryCache.begin();
\r
817 if (m_entryCache.size()==0)
\r
821 else if ((bRecursive)&&(itMembers->second.IsDirectory()))
\r
823 // crawl all sub folders too! Otherwise a change deep inside the
\r
824 // tree which has changed won't get propagated up the tree.
\r
825 CSVNStatusCache::Instance().AddFolderForCrawling(filePath);
\r
832 void CCachedDirectory::RefreshMostImportant()
\r
834 CacheEntryMap::iterator itMembers;
\r
835 svn_wc_status_kind newStatus = m_ownStatus.GetEffectiveStatus();
\r
836 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
\r
838 newStatus = SVNStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());
\r
839 if (((itMembers->second.GetEffectiveStatus() == svn_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == svn_wc_status_none))
\r
840 &&(CSVNStatusCache::Instance().IsUnversionedAsModified()))
\r
842 // treat unversioned files as modified
\r
843 if (newStatus != svn_wc_status_added)
\r
844 newStatus = SVNStatus::GetMoreImportant(newStatus, svn_wc_status_modified);
\r
847 if (newStatus != m_mostImportantFileStatus)
\r
849 ATLTRACE(_T("status change of path %s\n"), m_directoryPath.GetWinPath());
\r
850 CSVNStatusCache::Instance().UpdateShell(m_directoryPath);
\r
852 m_mostImportantFileStatus = newStatus;
\r