TGitCache test application basic work.
[TortoiseGit.git] / src / TGitCache / CachedDirectory.cpp
blobaedceb1128cbf4854177016b887c837b8139ce20
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include ".\cacheddirectory.h"
21 //#include "SVNHelpers.h"
22 #include "GitStatusCache.h"
23 #include "GitStatus.h"
24 #include <set>
26 CCachedDirectory::CCachedDirectory(void)
28 m_indexFileTime = 0;
29 // m_propsFileTime = 0;
30 m_currentStatusFetchingPathTicks = 0;
31 m_bCurrentFullStatusValid = false;
32 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
33 m_bRecursive = true;
36 CCachedDirectory::~CCachedDirectory(void)
40 CCachedDirectory::CCachedDirectory(const CTGitPath& directoryPath)
42 ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));
44 m_directoryPath = directoryPath;
45 m_indexFileTime = 0;
46 // m_propsFileTime = 0;
47 m_currentStatusFetchingPathTicks = 0;
48 m_bCurrentFullStatusValid = false;
49 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
50 m_bRecursive = true;
53 BOOL CCachedDirectory::SaveToDisk(FILE * pFile)
55 AutoLocker lock(m_critSec);
56 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;
58 unsigned int value = 1;
59 WRITEVALUETOFILE(value); // 'version' of this save-format
60 value = (int)m_entryCache.size();
61 WRITEVALUETOFILE(value); // size of the cache map
62 // now iterate through the maps and save every entry.
63 for (CacheEntryMap::iterator I = m_entryCache.begin(); I != m_entryCache.end(); ++I)
65 const CString& key = I->first;
66 value = key.GetLength();
67 WRITEVALUETOFILE(value);
68 if (value)
70 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
71 return false;
72 if (!I->second.SaveToDisk(pFile))
73 return false;
76 value = (int)m_childDirectories.size();
77 WRITEVALUETOFILE(value);
78 for (ChildDirStatus::iterator I = m_childDirectories.begin(); I != m_childDirectories.end(); ++I)
80 const CString& path = I->first.GetWinPathString();
81 value = path.GetLength();
82 WRITEVALUETOFILE(value);
83 if (value)
85 if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)
86 return false;
87 git_wc_status_kind status = I->second;
88 WRITEVALUETOFILE(status);
91 WRITEVALUETOFILE(m_indexFileTime);
92 // WRITEVALUETOFILE(m_propsFileTime);
93 value = m_directoryPath.GetWinPathString().GetLength();
94 WRITEVALUETOFILE(value);
95 if (value)
97 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
98 return false;
100 if (!m_ownStatus.SaveToDisk(pFile))
101 return false;
102 WRITEVALUETOFILE(m_currentFullStatus);
103 WRITEVALUETOFILE(m_mostImportantFileStatus);
104 return true;
107 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)
109 AutoLocker lock(m_critSec);
110 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
113 unsigned int value = 0;
114 LOADVALUEFROMFILE(value);
115 if (value != 1)
116 return false; // not the correct version
117 int mapsize = 0;
118 LOADVALUEFROMFILE(mapsize);
119 for (int i=0; i<mapsize; ++i)
121 LOADVALUEFROMFILE(value);
122 if (value > MAX_PATH)
123 return false;
124 if (value)
126 CString sKey;
127 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
129 sKey.ReleaseBuffer(0);
130 return false;
132 sKey.ReleaseBuffer(value);
133 CStatusCacheEntry entry;
134 if (!entry.LoadFromDisk(pFile))
135 return false;
136 m_entryCache[sKey] = entry;
139 LOADVALUEFROMFILE(mapsize);
140 for (int i=0; i<mapsize; ++i)
142 LOADVALUEFROMFILE(value);
143 if (value > MAX_PATH)
144 return false;
145 if (value)
147 CString sPath;
148 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)
150 sPath.ReleaseBuffer(0);
151 return false;
153 sPath.ReleaseBuffer(value);
154 git_wc_status_kind status;
155 LOADVALUEFROMFILE(status);
156 m_childDirectories[CTGitPath(sPath)] = status;
159 LOADVALUEFROMFILE(m_indexFileTime);
160 // LOADVALUEFROMFILE(m_propsFileTime);
161 LOADVALUEFROMFILE(value);
162 if (value > MAX_PATH)
163 return false;
164 if (value)
166 CString sPath;
167 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
169 sPath.ReleaseBuffer(0);
170 return false;
172 sPath.ReleaseBuffer(value);
173 m_directoryPath.SetFromWin(sPath);
175 if (!m_ownStatus.LoadFromDisk(pFile))
176 return false;
178 LOADVALUEFROMFILE(m_currentFullStatus);
179 LOADVALUEFROMFILE(m_mostImportantFileStatus);
181 catch ( CAtlException )
183 return false;
185 return true;
189 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */)
191 CString strCacheKey;
192 bool bThisDirectoryIsUnversioned = false;
193 bool bRequestForSelf = false;
194 if(path.IsEquivalentToWithoutCase(m_directoryPath))
196 bRequestForSelf = true;
198 //OutputDebugStringA("GetStatusForMember: ");OutputDebugStringW(path.GetWinPathString());OutputDebugStringA("\r\n");
199 // In all most circumstances, we ask for the status of a member of this directory.
200 ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);
202 CString sProjectRoot;
203 const BOOL bIsVersionedPath = m_directoryPath.HasAdminDir(&sProjectRoot);
205 // Check if the index file has been changed
206 CTGitPath indexFilePath(bIsVersionedPath ? sProjectRoot : m_directoryPath);
207 // CTGitPath propsDirPath(m_directoryPath);
208 if (g_GitAdminDir.IsVSNETHackActive())
210 indexFilePath.AppendPathString(g_GitAdminDir.GetVSNETAdminDirName() + _T("\\index"));
211 // propsDirPath.AppendPathString(g_GitAdminDir.GetVSNETAdminDirName() + _T("\\dir-props"));
213 else
215 indexFilePath.AppendPathString(g_GitAdminDir.GetAdminDirName() + _T("\\index"));
216 // propsDirPath.AppendPathString(g_GitAdminDir.GetAdminDirName() + _T("\\dir-props"));
218 if ( (m_indexFileTime == indexFilePath.GetLastWriteTime()) /*&& ((indexFilePath.GetLastWriteTime() == 0) || (m_propsFileTime == propsDirPath.GetLastWriteTime()))*/ )
220 // m_indexFileTime = indexFilePath.GetLastWriteTime();
221 // if (m_indexFileTime)
222 // m_propsFileTime = propsDirPath.GetLastWriteTime();
224 //if(m_indexFileTime == 0)
225 // a newly created project (without commits) has no index file but we still want it to count as versioned
226 if(m_indexFileTime == 0 && !bIsVersionedPath)
228 // We are a folder which is not in a working copy
229 bThisDirectoryIsUnversioned = true;
230 m_ownStatus.SetStatus(NULL);
232 // If a user removes the .git directory, we get here with m_entryCache
233 // not being empty, but still us being unversioned
234 if (!m_entryCache.empty())
236 m_entryCache.clear();
238 ATLASSERT(m_entryCache.empty());
240 // However, a member *DIRECTORY* might be the top of WC
241 // so we need to ask them to get their own status
242 if(!path.IsDirectory())
244 if ((PathFileExists(path.GetWinPath()))||(bRequestForSelf))
245 return CStatusCacheEntry();
246 // the entry doesn't exist anymore!
247 // but we can't remove it from the cache here:
248 // the GetStatusForMember() method is called only with a read
249 // lock and not a write lock!
250 // So mark it for crawling, and let the crawler remove it
251 // later
252 CGitStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory());
254 return CStatusCacheEntry();
256 else
258 // If we're in the special case of a directory being asked for its own status
259 // and this directory is unversioned, then we should just return that here
260 if(bRequestForSelf)
261 return CStatusCacheEntry();
265 if(path.IsDirectory())
267 // We don't have directory status in our cache
268 // Ask the directory if it knows its own status
269 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
270 if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))
272 // To keep recursive status up to date, we'll request that children are all crawled again
273 // This will be very quick if nothings changed, because it will all be cache hits
274 if (bRecursive)
276 AutoLocker lock(dirEntry->m_critSec);
277 ChildDirStatus::const_iterator it;
278 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
280 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
283 return dirEntry->GetOwnStatus(bRecursive);
286 else
289 // if we currently are fetching the status of the directory
290 // we want the status for, we just return an empty entry here
291 // and don't wait for that fetching to finish.
292 // That's because fetching the status can take a *really* long
293 // time (e.g. if a commit is also in progress on that same
294 // directory), and we don't want to make the explorer appear
295 // to hang.
296 AutoLocker pathlock(m_critSecPath);
297 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
299 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
301 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
302 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
303 return CStatusCacheEntry();
307 // Look up a file in our own cache
308 AutoLocker lock(m_critSec);
309 strCacheKey = GetCacheKey(path);
310 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
311 if(itMap != m_entryCache.end())
313 // We've hit the cache - check for timeout
314 if(!itMap->second.HasExpired((long)GetTickCount()))
316 if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))
318 if ((itMap->second.GetEffectiveStatus()!=git_wc_status_missing)||(!PathFileExists(path.GetWinPath())))
320 // Note: the filetime matches after a modified has been committed too.
321 // So in that case, we would return a wrong status (e.g. 'modified' instead
322 // of 'normal') here.
323 return itMap->second;
330 else
332 AutoLocker pathlock(m_critSecPath);
333 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
335 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
337 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
338 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
339 return CStatusCacheEntry();
342 // if we're fetching the status for the explorer,
343 // we don't refresh the status but use the one
344 // we already have (to save time and make the explorer
345 // more responsive in stress conditions).
346 // We leave the refreshing to the crawler.
347 if ((!bFetch)&&(m_indexFileTime))
349 CGitStatusCache::Instance().AddFolderForCrawling(path.GetDirectory());
350 return CStatusCacheEntry();
352 AutoLocker lock(m_critSec);
353 m_indexFileTime = indexFilePath.GetLastWriteTime();
354 // m_propsFileTime = propsDirPath.GetLastWriteTime();
355 m_entryCache.clear();
356 strCacheKey = GetCacheKey(path);
359 // svn_opt_revision_t revision;
360 // revision.kind = svn_opt_revision_unspecified;
362 // We've not got this item in the cache - let's add it
363 // We never bother asking SVN for the status of just one file, always for its containing directory
365 if (g_GitAdminDir.IsAdminDirPath(path.GetWinPathString()))
367 // We're being asked for the status of an .git directory
368 // It's not worth asking for this
369 return CStatusCacheEntry();
374 AutoLocker pathlock(m_critSecPath);
375 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
377 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
379 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
380 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
381 return CStatusCacheEntry();
385 // SVNPool subPool(CGitStatusCache::Instance().m_svnHelp.Pool());
387 AutoLocker lock(m_critSec);
388 m_mostImportantFileStatus = git_wc_status_none;
389 m_childDirectories.clear();
390 m_entryCache.clear();
391 m_ownStatus.SetStatus(NULL);
392 m_bRecursive = bRecursive;
394 if(!bThisDirectoryIsUnversioned)
397 AutoLocker pathlock(m_critSecPath);
398 m_currentStatusFetchingPath = m_directoryPath;
399 m_currentStatusFetchingPathTicks = GetTickCount();
401 //ATLTRACE(_T("git_enum_files for '%s' (req %s)\n"), m_directoryPath.GetWinPath(), path.GetWinPath());
404 BOOL pErr = EnumFiles();
407 AutoLocker pathlock(m_critSecPath);
408 m_currentStatusFetchingPath.Reset();
410 ATLTRACE(_T("git_enum_files finished for '%s'\n"), m_directoryPath.GetWinPath(), path.GetWinPath());
411 if(pErr)
413 // Handle an error
414 // The most likely error on a folder is that it's not part of a WC
415 // In most circumstances, this will have been caught earlier,
416 // but in some situations, we'll get this error.
417 // If we allow ourselves to fall on through, then folders will be asked
418 // for their own status, and will set themselves as unversioned, for the
419 // benefit of future requests
420 // ATLTRACE("git_enum_files err: '%s'\n", pErr->message);
421 // svn_error_clear(pErr);
422 // No assert here! Since we _can_ get here, an assertion is not an option!
423 // Reasons to get here:
424 // - renaming a folder with many sub folders --> results in "not a working copy" if the revert
425 // happens between our checks and the svn_client_status() call.
426 // - reverting a move/copy --> results in "not a working copy" (as above)
427 if (!m_directoryPath.HasAdminDir())
429 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
430 return CStatusCacheEntry();
432 else
434 ATLTRACE("git_enum_files error, assume none status\n");
435 // Since we only assume a none status here due to svn_client_status()
436 // returning an error, make sure that this status times out soon.
437 CGitStatusCache::Instance().m_folderCrawler.BlockPath(m_directoryPath, 2000);
438 CGitStatusCache::Instance().AddFolderForCrawling(m_directoryPath);
439 return CStatusCacheEntry();
443 else
445 ATLTRACE("Skipped git status for unversioned folder\n");
448 // Now that we've refreshed our SVN status, we can see if it's
449 // changed the 'most important' status value for this directory.
450 // If it has, then we should tell our parent
451 UpdateCurrentStatus();
453 if (path.IsDirectory())
455 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
456 if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))
458 CGitStatusCache::Instance().AddFolderForCrawling(path);
459 return dirEntry->GetOwnStatus(bRecursive);
462 // If the status *still* isn't valid here, it means that
463 // the current directory is unversioned, and we shall need to ask its children for info about themselves
464 if (dirEntry)
465 return dirEntry->GetStatusForMember(path,bRecursive);
466 CGitStatusCache::Instance().AddFolderForCrawling(path);
467 return CStatusCacheEntry();
469 else
471 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
472 if(itMap != m_entryCache.end())
474 return itMap->second;
478 AddEntry(path, NULL);
479 return CStatusCacheEntry();
482 int CCachedDirectory::EnumFiles()
484 CString sProjectRoot;
485 m_directoryPath.HasAdminDir(&sProjectRoot);
486 ATLASSERT( !m_directoryPath.IsEmpty() );
488 LPCTSTR lpszSubPath = NULL;
489 CString sSubPath;
490 CString s = m_directoryPath.GetDirectory().GetWinPathString();
491 if (s.GetLength() > sProjectRoot.GetLength())
493 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength());
494 lpszSubPath = sSubPath;
495 // skip initial slash if necessary
496 if (*lpszSubPath == _T('\\'))
497 lpszSubPath++;
500 WIN32_FIND_DATA data;
501 CString str = m_directoryPath.GetWinPathString() + _T("\\*.*");
502 git_wc_status_kind status;
504 git_wc_status2_t status2;
506 GitStatus *pStatus = &CGitStatusCache::Instance().m_GitStatus;
508 pStatus->GetDirStatus(sProjectRoot, sSubPath, &status, true, false);
509 status2.prop_status = status2.text_status = status;
510 this->m_ownStatus.SetStatus(&status2);
512 CTGitPath gitPath;
514 HANDLE handle = FindFirstFile(str,&data);
515 while(FindNextFile(handle,&data))
517 if( _tcsnccmp(data.cFileName, _T(".."),2) == 0)
518 continue;
519 if( _tcsnccmp(data.cFileName, _T("."),1) == 0)
520 continue;
522 if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
524 status = git_wc_status_none;
525 CString subpath;
526 subpath = sSubPath+_T("\\")+data.cFileName,
527 pStatus->GetDirStatus(sProjectRoot, subpath, &status, true, false);
528 if (m_bRecursive)
530 gitPath.SetFromWin(subpath);
532 //just need crawling tracked directory
533 if(status > git_wc_status_ignored)
534 CGitStatusCache::Instance().AddFolderForCrawling(gitPath);
537 AutoLocker lock(m_critSec);
538 m_childDirectories[gitPath] = status;
540 }else
542 status = git_wc_status_none;
543 pStatus->GetFileStatus(sProjectRoot, sSubPath+data.cFileName, &status,true, false);
546 status2.prop_status=status2.text_status=status;
547 AddEntry(gitPath, &status2);
549 return 0;
551 void
552 CCachedDirectory::AddEntry(const CTGitPath& path, const git_wc_status2_t* pGitStatus, DWORD validuntil /* = 0*/)
554 AutoLocker lock(m_critSec);
555 if(path.IsDirectory())
557 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
558 if (childDir)
560 if ((childDir->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
561 childDir->m_ownStatus.SetStatus(pGitStatus);
562 childDir->m_ownStatus.SetKind(git_node_dir);
565 else
567 CString cachekey = GetCacheKey(path);
568 CacheEntryMap::iterator entry_it = m_entryCache.lower_bound(cachekey);
569 if (entry_it != m_entryCache.end() && entry_it->first == cachekey)
571 if (pGitStatus)
573 if (entry_it->second.GetEffectiveStatus() > git_wc_status_none &&
574 entry_it->second.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
576 CGitStatusCache::Instance().UpdateShell(path);
577 ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
581 else
583 entry_it = m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
585 entry_it->second = CStatusCacheEntry(pGitStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);
586 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
587 entry_it->second.SetKind(git_node_file);
592 CString
593 CCachedDirectory::GetCacheKey(const CTGitPath& path)
595 // All we put into the cache as a key is just the end portion of the pathname
596 // There's no point storing the path of the containing directory for every item
597 return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength());
600 CString
601 CCachedDirectory::GetFullPathString(const CString& cacheKey)
603 return m_directoryPath.GetWinPathString() + _T("\\") + cacheKey;
606 BOOL CCachedDirectory::GetStatusCallback(const struct wgFile_s *pFile, void *pUserData)
608 CCachedDirectory* pThis = (CCachedDirectory*)pUserData;
610 const TCHAR *path = pFile->sFileName;
612 if (path == NULL)
613 return FALSE;
615 git_wc_status2_t _status;
616 git_wc_status2_t *status = &_status;
618 if ((pFile->nFlags & WGFF_Directory) && pFile->nStatus == WGFS_Unknown)
619 status->prop_status = status->text_status = git_wc_status_incomplete;
620 else
621 status->prop_status = status->text_status = GitStatusFromWingit(pFile->nStatus);
622 //if (pFile->nStatus > WGFS_Normal) {CStringA s; s.Format("==>%s %d\r\n",pFile->sFileName,pFile->nStatus); OutputDebugStringA(s);}
623 CTGitPath svnPath;
625 // if(status->entry)
627 //if ((status->text_status != git_wc_status_none)&&(status->text_status != git_wc_status_missing))
628 svnPath.SetFromGit(path, pFile->nFlags & WGFF_Directory);
629 /*else
630 svnPath.SetFromGit(path);*/
632 if (pFile->nFlags & WGFF_Directory)
634 if ( !svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
636 if (pThis->m_bRecursive)
638 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
639 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
640 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
643 // Make sure we know about this child directory
644 // This initial status value is likely to be overwritten from below at some point
645 git_wc_status_kind s = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
646 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath);
647 if (cdir)
649 // This child directory is already in our cache!
650 // So ask this dir about its recursive status
651 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
652 AutoLocker lock(pThis->m_critSec);
653 pThis->m_childDirectories[svnPath] = st;
655 else
657 // the child directory is not in the cache. Create a new entry for it in the cache which is
658 // initially 'unversioned'. But we added that directory to the crawling list above, which
659 // means the cache will be updated soon.
660 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
661 AutoLocker lock(pThis->m_critSec);
662 pThis->m_childDirectories[svnPath] = s;
666 else
668 // Keep track of the most important status of all the files in this directory
669 // Don't include subdirectories in this figure, because they need to provide their
670 // own 'most important' value
671 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->text_status);
672 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->prop_status);
673 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_none))
674 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
676 // treat unversioned files as modified
677 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
678 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
683 pThis->AddEntry(svnPath, status);
685 return FALSE;
688 #if 0
689 git_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, git_wc_status2_t *status)
691 CCachedDirectory* pThis = (CCachedDirectory*)baton;
693 if (path == NULL)
694 return 0;
696 CTGitPath svnPath;
698 if(status->entry)
700 if ((status->text_status != git_wc_status_none)&&(status->text_status != git_wc_status_missing))
701 svnPath.SetFromSVN(path, (status->entry->kind == svn_node_dir));
702 else
703 svnPath.SetFromSVN(path);
705 if(svnPath.IsDirectory())
707 if(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))
709 if (pThis->m_bRecursive)
711 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
712 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
715 // Make sure we know about this child directory
716 // This initial status value is likely to be overwritten from below at some point
717 git_wc_status_kind s = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
718 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath);
719 if (cdir)
721 // This child directory is already in our cache!
722 // So ask this dir about its recursive status
723 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
724 AutoLocker lock(pThis->m_critSec);
725 pThis->m_childDirectories[svnPath] = st;
727 else
729 // the child directory is not in the cache. Create a new entry for it in the cache which is
730 // initially 'unversioned'. But we added that directory to the crawling list above, which
731 // means the cache will be updated soon.
732 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
733 AutoLocker lock(pThis->m_critSec);
734 pThis->m_childDirectories[svnPath] = s;
738 else
740 // Keep track of the most important status of all the files in this directory
741 // Don't include subdirectories in this figure, because they need to provide their
742 // own 'most important' value
743 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->text_status);
744 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->prop_status);
745 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_none))
746 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
748 // treat unversioned files as modified
749 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
750 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
754 else
756 svnPath.SetFromSVN(path);
757 // Subversion returns no 'entry' field for versioned folders if they're
758 // part of another working copy (nested layouts).
759 // So we have to make sure that such an 'unversioned' folder really
760 // is unversioned.
761 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_missing))&&(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))&&(svnPath.IsDirectory()))
763 if (svnPath.HasAdminDir())
765 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
766 // Mark the directory as 'versioned' (status 'normal' for now).
767 // This initial value will be overwritten from below some time later
769 AutoLocker lock(pThis->m_critSec);
770 pThis->m_childDirectories[svnPath] = git_wc_status_normal;
772 // Make sure the entry is also in the cache
773 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
774 // also mark the status in the status object as normal
775 status->text_status = git_wc_status_normal;
778 else if (status->text_status == git_wc_status_external)
780 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
781 // Mark the directory as 'versioned' (status 'normal' for now).
782 // This initial value will be overwritten from below some time later
784 AutoLocker lock(pThis->m_critSec);
785 pThis->m_childDirectories[svnPath] = git_wc_status_normal;
787 // we have added a directory to the child-directory list of this
788 // directory. We now must make sure that this directory also has
789 // an entry in the cache.
790 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
791 // also mark the status in the status object as normal
792 status->text_status = git_wc_status_normal;
794 else
796 if (svnPath.IsDirectory())
798 AutoLocker lock(pThis->m_critSec);
799 pThis->m_childDirectories[svnPath] = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
801 else if ((CGitStatusCache::Instance().IsUnversionedAsModified())&&(status->text_status != git_wc_status_missing))
803 // make this unversioned item change the most important status of this
804 // folder to modified if it doesn't already have another status
805 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
806 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
811 pThis->AddEntry(svnPath, status);
813 return 0;
815 #endif
817 bool
818 CCachedDirectory::IsOwnStatusValid() const
820 return m_ownStatus.HasBeenSet() &&
821 !m_ownStatus.HasExpired(GetTickCount()) &&
822 // 'external' isn't a valid status. That just
823 // means the folder is not part of the current working
824 // copy but it still has its own 'real' status
825 m_ownStatus.GetEffectiveStatus()!=git_wc_status_external &&
826 m_ownStatus.IsKindKnown();
829 void CCachedDirectory::Invalidate()
831 m_ownStatus.Invalidate();
834 git_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()
836 // Combine our OWN folder status with the most important of our *FILES'* status.
837 git_wc_status_kind retVal = GitStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());
839 // NOTE: TSVN marks dir as modified if it contains added/deleted/missing files, but we prefer the most important
840 // status to propagate upward in its original state
841 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
843 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
844 retVal = git_wc_status_modified;
847 // Now combine all our child-directorie's status
849 AutoLocker lock(m_critSec);
850 ChildDirStatus::const_iterator it;
851 for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)
853 retVal = GitStatus::GetMoreImportant(retVal, it->second);
854 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
856 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
857 retVal = git_wc_status_modified;
861 return retVal;
864 // Update our composite status and deal with things if it's changed
865 void CCachedDirectory::UpdateCurrentStatus()
867 git_wc_status_kind newStatus = CalculateRecursiveStatus();
869 if ((newStatus != m_currentFullStatus)&&(m_ownStatus.IsVersioned()))
871 if ((m_currentFullStatus != git_wc_status_none)&&(m_ownStatus.GetEffectiveStatus() != git_wc_status_missing))
873 // Our status has changed - tell the shell
874 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);
875 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
877 if (m_ownStatus.GetEffectiveStatus() != git_wc_status_missing)
878 m_currentFullStatus = newStatus;
879 else
880 m_currentFullStatus = git_wc_status_missing;
882 // And tell our parent, if we've got one...
883 // we tell our parent *always* about our status, even if it hasn't
884 // changed. This is to make sure that the parent has really our current
885 // status - the parent can decide itself if our status has changed
886 // or not.
887 CTGitPath parentPath = m_directoryPath.GetContainingDirectory();
888 if(!parentPath.IsEmpty())
890 // We have a parent
891 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath);
892 if (cachedDir)
893 cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);
898 // Receive a notification from a child that its status has changed
899 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath& childDir, git_wc_status_kind childStatus)
901 git_wc_status_kind currentStatus = git_wc_status_none;
903 AutoLocker lock(m_critSec);
904 currentStatus = m_childDirectories[childDir];
906 if ((currentStatus != childStatus)||(!IsOwnStatusValid()))
909 AutoLocker lock(m_critSec);
910 m_childDirectories[childDir] = childStatus;
912 UpdateCurrentStatus();
916 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)
918 // Don't return recursive status if we're unversioned ourselves.
919 if(bRecursive && m_ownStatus.GetEffectiveStatus() > git_wc_status_unversioned)
921 CStatusCacheEntry recursiveStatus(m_ownStatus);
922 UpdateCurrentStatus();
923 recursiveStatus.ForceStatus(m_currentFullStatus);
924 return recursiveStatus;
926 else
928 return m_ownStatus;
932 void CCachedDirectory::RefreshStatus(bool bRecursive)
934 // Make sure that our own status is up-to-date
935 GetStatusForMember(m_directoryPath,bRecursive);
937 AutoLocker lock(m_critSec);
938 // We also need to check if all our file members have the right date on them
939 CacheEntryMap::iterator itMembers;
940 std::set<CTGitPath> refreshedpaths;
941 DWORD now = GetTickCount();
942 if (m_entryCache.size() == 0)
943 return;
944 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
946 if (itMembers->first)
948 CTGitPath filePath(m_directoryPath);
949 filePath.AppendPathString(itMembers->first);
950 std::set<CTGitPath>::iterator refr_it;
951 if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&
952 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))
954 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))
956 lock.Unlock();
957 // We need to request this item as well
958 GetStatusForMember(filePath,bRecursive);
959 // GetStatusForMember now has recreated the m_entryCache map.
960 // So start the loop again, but add this path to the refreshed paths set
961 // to make sure we don't refresh this path again. This is to make sure
962 // that we don't end up in an endless loop.
963 lock.Lock();
964 refreshedpaths.insert(refr_it, filePath);
965 itMembers = m_entryCache.begin();
966 if (m_entryCache.size()==0)
967 return;
968 continue;
970 else if ((bRecursive)&&(itMembers->second.IsDirectory()))
972 // crawl all sub folders too! Otherwise a change deep inside the
973 // tree which has changed won't get propagated up the tree.
974 CGitStatusCache::Instance().AddFolderForCrawling(filePath);
981 void CCachedDirectory::RefreshMostImportant()
983 CacheEntryMap::iterator itMembers;
984 git_wc_status_kind newStatus = m_ownStatus.GetEffectiveStatus();
985 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
987 newStatus = GitStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());
988 if (((itMembers->second.GetEffectiveStatus() == git_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == git_wc_status_none))
989 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
991 // treat unversioned files as modified
992 if (newStatus != git_wc_status_added)
993 newStatus = GitStatus::GetMoreImportant(newStatus, git_wc_status_modified);
996 if (newStatus != m_mostImportantFileStatus)
998 ATLTRACE(_T("status change of path %s\n"), m_directoryPath.GetWinPath());
999 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
1001 m_mostImportantFileStatus = newStatus;