TGitCache: Add current directory to crawl thread
[TortoiseGit.git] / src / TGitCache / CachedDirectory.cpp
blobd84d138c1a9615fcc555f164868842181be5f386
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
4 // Copyright (C) 2008-2011 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "StdAfx.h"
21 #include ".\cacheddirectory.h"
22 //#include "SVNHelpers.h"
23 #include "GitStatusCache.h"
24 #include "GitStatus.h"
25 #include <set>
27 CCachedDirectory::CCachedDirectory(void)
29 m_currentStatusFetchingPathTicks = 0;
30 m_bCurrentFullStatusValid = false;
31 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
32 m_bRecursive = true;
33 m_indexFileTime = 0;
34 m_FullStatusFetched = false;
37 CCachedDirectory::~CCachedDirectory(void)
41 CCachedDirectory::CCachedDirectory(const CTGitPath& directoryPath)
43 ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));
45 m_directoryPath = directoryPath;
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 = GIT_CACHE_VERSION;
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_Head.m_hash);
93 // WRITEVALUETOFILE(m_propsFileTime);
94 value = m_directoryPath.GetWinPathString().GetLength();
95 WRITEVALUETOFILE(value);
96 if (value)
98 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
99 return false;
101 if (!m_ownStatus.SaveToDisk(pFile))
102 return false;
103 WRITEVALUETOFILE(m_currentFullStatus);
104 WRITEVALUETOFILE(m_mostImportantFileStatus);
105 return true;
108 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)
110 AutoLocker lock(m_critSec);
111 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
114 unsigned int value = 0;
115 LOADVALUEFROMFILE(value);
116 if (value != GIT_CACHE_VERSION)
117 return false; // not the correct version
118 int mapsize = 0;
119 LOADVALUEFROMFILE(mapsize);
120 for (int i=0; i<mapsize; ++i)
122 LOADVALUEFROMFILE(value);
123 if (value > MAX_PATH)
124 return false;
125 if (value)
127 CString sKey;
128 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
130 sKey.ReleaseBuffer(0);
131 return false;
133 sKey.ReleaseBuffer(value);
134 CStatusCacheEntry entry;
135 if (!entry.LoadFromDisk(pFile))
136 return false;
137 m_entryCache[sKey] = entry;
140 LOADVALUEFROMFILE(mapsize);
141 for (int i=0; i<mapsize; ++i)
143 LOADVALUEFROMFILE(value);
144 if (value > MAX_PATH)
145 return false;
146 if (value)
148 CString sPath;
149 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)
151 sPath.ReleaseBuffer(0);
152 return false;
154 sPath.ReleaseBuffer(value);
155 git_wc_status_kind status;
156 LOADVALUEFROMFILE(status);
157 m_childDirectories[CTGitPath(sPath)] = status;
160 LOADVALUEFROMFILE(m_indexFileTime);
161 LOADVALUEFROMFILE(m_Head.m_hash);
162 // LOADVALUEFROMFILE(m_propsFileTime);
163 LOADVALUEFROMFILE(value);
164 if (value > MAX_PATH)
165 return false;
166 if (value)
168 CString sPath;
169 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
171 sPath.ReleaseBuffer(0);
172 return false;
174 sPath.ReleaseBuffer(value);
175 m_directoryPath.SetFromWin(sPath);
177 if (!m_ownStatus.LoadFromDisk(pFile))
178 return false;
180 LOADVALUEFROMFILE(m_currentFullStatus);
181 LOADVALUEFROMFILE(m_mostImportantFileStatus);
183 catch ( CAtlException )
185 return false;
187 return true;
192 CStatusCacheEntry CCachedDirectory::GetStatusFromCache(const CTGitPath& path, bool bRecursive)
194 if(path.IsDirectory())
196 // We don't have directory status in our cache
197 // Ask the directory if it knows its own status
198 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
199 if( dirEntry)
201 if (dirEntry->IsOwnStatusValid())
202 return dirEntry->GetOwnStatus(bRecursive);
203 else
205 /* cache have outof date, need crawl again*/
207 /*AutoLocker lock(dirEntry->m_critSec);
208 ChildDirStatus::const_iterator it;
209 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
211 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
214 CGitStatusCache::Instance().AddFolderForCrawling(path);
218 else
220 CGitStatusCache::Instance().AddFolderForCrawling(path);
222 return CStatusCacheEntry();
224 else
226 // Look up a file in our own cache
227 AutoLocker lock(m_critSec);
228 CString strCacheKey = GetCacheKey(path);
229 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
230 if(itMap != m_entryCache.end())
232 // We've hit the cache - check for timeout
233 if(!itMap->second.HasExpired((long)GetTickCount()))
235 if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))
237 if ((itMap->second.GetEffectiveStatus()!=git_wc_status_missing)||(!PathFileExists(path.GetWinPath())))
239 // Note: the filetime matches after a modified has been committed too.
240 // So in that case, we would return a wrong status (e.g. 'modified' instead
241 // of 'normal') here.
242 return itMap->second;
246 }else
248 //All file ignored if under ignore directory
249 if( m_currentFullStatus == git_wc_status_ignored)
250 return CStatusCacheEntry(git_wc_status_ignored);
253 CGitStatusCache::Instance().AddFolderForCrawling(path);
254 return CStatusCacheEntry();
259 CStatusCacheEntry CCachedDirectory::GetStatusFromGit(const CTGitPath &path)
261 CString sProjectRoot;
262 CString subpaths = path.GetGitPathString();
263 if(subpaths.GetLength() >= sProjectRoot.GetLength())
265 if(subpaths[sProjectRoot.GetLength()] == _T('/'))
266 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength()-1);
267 else
268 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength());
271 GitStatus *pGitStatus = &CGitStatusCache::Instance().m_GitStatus;
273 CGitHash head;
275 pGitStatus->GetHeadHash(sProjectRoot,head);
277 bool isVersion =true;
278 pGitStatus->IsUnderVersionControl(sProjectRoot, subpaths, path.IsDirectory(), &isVersion);
279 if(!isVersion)
280 { //untracked file
281 bool isIgnoreFileChanged=false;
283 isIgnoreFileChanged = pGitStatus->IsGitReposChanged(sProjectRoot, subpaths, GIT_MODE_IGNORE);
285 if( isIgnoreFileChanged)
287 pGitStatus->LoadIgnoreFile(sProjectRoot, subpaths);
290 if(path.IsDirectory())
293 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path,
294 false); /* we needn't watch untracked directory*/
296 if(dirEntry)
298 AutoLocker lock(dirEntry->m_critSec);
300 git_wc_status_kind dirstatus = dirEntry->GetCurrentFullStatus() ;
301 if( dirstatus == git_wc_status_none || dirstatus >= git_wc_status_normal || isIgnoreFileChanged )
302 {/* status have not initialized*/
303 git_wc_status2_t status2;
304 bool isignore = false;
305 pGitStatus->IsIgnore(sProjectRoot,subpaths,&isignore);
306 status2.text_status = status2.prop_status =
307 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
309 dirEntry->m_ownStatus.SetStatus(&status2);
312 return dirEntry->m_ownStatus;
315 }else /* path is file */
317 AutoLocker lock(m_critSec);
318 CString strCacheKey = GetCacheKey(path);
320 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
321 if(itMap == m_entryCache.end() || isIgnoreFileChanged)
323 git_wc_status2_t status2;
324 bool isignore = false;
325 pGitStatus->IsIgnore(sProjectRoot,subpaths,&isignore);
326 status2.text_status = status2.prop_status =
327 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
328 AddEntry(path, &status2);
329 return m_entryCache[strCacheKey];
330 }else
332 return itMap->second;
335 return CStatusCacheEntry();
337 }else
339 EnumFiles((CTGitPath*)&path, TRUE);
340 return CStatusCacheEntry(git_wc_status_normal);
345 /// bFetch is true, fetch all status, call by crawl.
346 /// bFetch is false, get cache status, return quickly.
348 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */)
350 CString strCacheKey;
351 bool bRequestForSelf = false;
352 if(path.IsEquivalentToWithoutCase(m_directoryPath))
354 bRequestForSelf = true;
356 //OutputDebugStringA("GetStatusForMember: ");OutputDebugStringW(path.GetWinPathString());OutputDebugStringA("\r\n");
357 // In all most circumstances, we ask for the status of a member of this directory.
358 ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);
360 CString sProjectRoot;
361 const BOOL bIsVersionedPath = path.HasAdminDir(&sProjectRoot);
363 //If is not version control path
364 if( !bIsVersionedPath)
366 ATLTRACE(_T("%s is not underversion control\n"), path.GetWinPath());
367 return CStatusCacheEntry();
370 // We've not got this item in the cache - let's add it
371 // We never bother asking SVN for the status of just one file, always for its containing directory
373 if (g_GitAdminDir.IsAdminDirPath(path.GetWinPathString()))
375 // We're being asked for the status of an .git directory
376 // It's not worth asking for this
377 return CStatusCacheEntry();
381 if(bFetch)
383 GetStatusFromGit(path);
384 return CStatusCacheEntry();
385 }else
387 return GetStatusFromCache(path, bRecursive);
391 int CCachedDirectory::EnumFiles(CTGitPath *path , bool IsFull)
393 CString sProjectRoot;
394 if(path)
395 path->HasAdminDir(&sProjectRoot);
396 else
397 m_directoryPath.HasAdminDir(&sProjectRoot);
399 ATLTRACE(_T("EnumFiles %s\n"), path->GetWinPath());
401 ATLASSERT( !m_directoryPath.IsEmpty() );
403 CString sSubPath;
405 CString s;
406 if(path)
407 s=path->GetWinPath();
408 else
409 s=m_directoryPath.GetDirectory().GetWinPathString();
411 if (s.GetLength() > sProjectRoot.GetLength())
413 // skip initial slash if necessary
414 if(s[sProjectRoot.GetLength()] == _T('\\'))
415 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() -1);
416 else
417 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() );
420 GitStatus *pStatus = &CGitStatusCache::Instance().m_GitStatus;
421 UNREFERENCED_PARAMETER(pStatus);
422 git_wc_status_kind status;
424 if(!path->IsDirectory())
425 pStatus->GetFileStatus(sProjectRoot, sSubPath, &status, IsFull, false,true, GetStatusCallback,this);
426 else
428 pStatus->EnumDirStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback,this);
431 m_mostImportantFileStatus = GitStatus::GetMoreImportant(m_mostImportantFileStatus, status);
433 return 0;
435 void
436 CCachedDirectory::AddEntry(const CTGitPath& path, const git_wc_status2_t* pGitStatus, DWORD validuntil /* = 0*/)
438 AutoLocker lock(m_critSec);
439 if(path.IsDirectory())
441 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
442 if (childDir)
444 if ((childDir->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
446 if(pGitStatus)
448 if(childDir->GetCurrentFullStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
450 CGitStatusCache::Instance().UpdateShell(path);
451 ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
454 childDir->m_ownStatus.SetStatus(pGitStatus);
456 childDir->m_ownStatus.SetKind(git_node_dir);
461 else
463 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path.GetContainingDirectory());
464 bool bNotified = false;
466 if(!childDir)
467 return ;
469 CString cachekey = GetCacheKey(path);
470 CacheEntryMap::iterator entry_it = childDir->m_entryCache.lower_bound(cachekey);
471 if (entry_it != childDir->m_entryCache.end() && entry_it->first == cachekey)
473 if (pGitStatus)
475 if (entry_it->second.GetEffectiveStatus() > git_wc_status_none &&
476 entry_it->second.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status)
479 bNotified =true;
484 else
486 entry_it = childDir->m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
487 bNotified = true;
490 entry_it->second = CStatusCacheEntry(pGitStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);
491 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
492 entry_it->second.SetKind(git_node_file);
494 if(bNotified)
496 CGitStatusCache::Instance().UpdateShell(path);
497 ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
500 ATLTRACE(_T("Path Entry Add %s %s %s %d\n"), path.GetWinPath(), cachekey, m_directoryPath.GetWinPath(), pGitStatus->text_status);
505 CString
506 CCachedDirectory::GetCacheKey(const CTGitPath& path)
508 // All we put into the cache as a key is just the end portion of the pathname
509 // There's no point storing the path of the containing directory for every item
510 return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength()).MakeLower();
513 CString
514 CCachedDirectory::GetFullPathString(const CString& cacheKey)
516 return m_directoryPath.GetWinPathString() + _T("\\") + cacheKey;
519 BOOL CCachedDirectory::GetStatusCallback(const CString & path, git_wc_status_kind status,bool isDir, void *pUserData)
521 CCachedDirectory* pThis = (CCachedDirectory*)pUserData;
523 git_wc_status2_t _status;
524 git_wc_status2_t *status2 = &_status;
526 status2->prop_status = status2->text_status = status;
528 CTGitPath gitPath;
531 // if(status->entry)
533 //if ((status->text_status != git_wc_status_none)&&(status->text_status != git_wc_status_missing))
534 CString lowcasepath = path;
535 lowcasepath.MakeLower();
536 gitPath.SetFromUnknown(lowcasepath);
538 //status.SetFromGit(path, pFile->nFlags & WGFF_Directory);
539 /*else
540 svnPath.SetFromGit(path);*/
542 if (isDir)
543 { /*gitpath is directory*/
544 if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
546 if (!gitPath.Exists())
548 ATLTRACE(_T("Miss dir %s \n"), gitPath.GetWinPath());
549 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_deleted);
552 if (pThis->m_bRecursive)
554 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
555 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
556 if(status >= git_wc_status_normal)
557 if(status != git_wc_status_missing)
558 if(status != git_wc_status_deleted)
559 CGitStatusCache::Instance().AddFolderForCrawling(gitPath);
562 // Make sure we know about this child directory
563 // This initial status value is likely to be overwritten from below at some point
564 git_wc_status_kind s = GitStatus::GetMoreImportant(status2->text_status, status2->prop_status);
565 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(gitPath);
566 if (cdir)
568 // This child directory is already in our cache!
569 // So ask this dir about its recursive status
570 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
571 AutoLocker lock(pThis->m_critSec);
572 //pThis->m_childDirectories[gitPath] = st;
573 ATLTRACE(_T("call 1 Update dir %s %d\n"), gitPath.GetWinPath(), st);
575 else
577 AutoLocker lock(pThis->m_critSec);
578 // the child directory is not in the cache. Create a new entry for it in the cache which is
579 // initially 'unversioned'. But we added that directory to the crawling list above, which
580 // means the cache will be updated soon.
581 CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath);
583 //pThis->m_childDirectories[gitPath] = s;
584 ATLTRACE(_T("call 2 Update dir %s %d\n"), gitPath.GetWinPath(), s);
588 else /* gitpath is file*/
590 // Keep track of the most important status of all the files in this directory
591 // Don't include subdirectories in this figure, because they need to provide their
592 // own 'most important' value
593 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->text_status);
594 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->prop_status);
595 if (((status2->text_status == git_wc_status_unversioned)||(status2->text_status == git_wc_status_none))
596 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
598 // treat unversioned files as modified
599 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
600 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
605 pThis->AddEntry(gitPath, status2);
607 return FALSE;
610 #if 0
611 git_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, git_wc_status2_t *status)
613 CCachedDirectory* pThis = (CCachedDirectory*)baton;
615 if (path == NULL)
616 return 0;
618 CTGitPath svnPath;
620 if(status->entry)
622 if ((status->text_status != git_wc_status_none)&&(status->text_status != git_wc_status_missing))
623 svnPath.SetFromSVN(path, (status->entry->kind == svn_node_dir));
624 else
625 svnPath.SetFromSVN(path);
627 if(svnPath.IsDirectory())
629 if(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))
631 if (pThis->m_bRecursive)
633 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
634 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
637 // Make sure we know about this child directory
638 // This initial status value is likely to be overwritten from below at some point
639 git_wc_status_kind s = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
640 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath);
641 if (cdir)
643 // This child directory is already in our cache!
644 // So ask this dir about its recursive status
645 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
646 AutoLocker lock(pThis->m_critSec);
647 pThis->m_childDirectories[svnPath] = st;
649 else
651 // the child directory is not in the cache. Create a new entry for it in the cache which is
652 // initially 'unversioned'. But we added that directory to the crawling list above, which
653 // means the cache will be updated soon.
654 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
655 AutoLocker lock(pThis->m_critSec);
656 pThis->m_childDirectories[svnPath] = s;
660 else
662 // Keep track of the most important status of all the files in this directory
663 // Don't include subdirectories in this figure, because they need to provide their
664 // own 'most important' value
665 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->text_status);
666 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->prop_status);
667 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_none))
668 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
670 // treat unversioned files as modified
671 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
672 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
676 else
678 svnPath.SetFromSVN(path);
679 // Subversion returns no 'entry' field for versioned folders if they're
680 // part of another working copy (nested layouts).
681 // So we have to make sure that such an 'unversioned' folder really
682 // is unversioned.
683 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_missing))&&(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))&&(svnPath.IsDirectory()))
685 if (svnPath.HasAdminDir())
687 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
688 // Mark the directory as 'versioned' (status 'normal' for now).
689 // This initial value will be overwritten from below some time later
691 AutoLocker lock(pThis->m_critSec);
692 pThis->m_childDirectories[svnPath] = git_wc_status_normal;
694 // Make sure the entry is also in the cache
695 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
696 // also mark the status in the status object as normal
697 status->text_status = git_wc_status_normal;
700 else if (status->text_status == git_wc_status_external)
702 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
703 // Mark the directory as 'versioned' (status 'normal' for now).
704 // This initial value will be overwritten from below some time later
706 AutoLocker lock(pThis->m_critSec);
707 pThis->m_childDirectories[svnPath] = git_wc_status_normal;
709 // we have added a directory to the child-directory list of this
710 // directory. We now must make sure that this directory also has
711 // an entry in the cache.
712 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
713 // also mark the status in the status object as normal
714 status->text_status = git_wc_status_normal;
716 else
718 if (svnPath.IsDirectory())
720 AutoLocker lock(pThis->m_critSec);
721 pThis->m_childDirectories[svnPath] = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
723 else if ((CGitStatusCache::Instance().IsUnversionedAsModified())&&(status->text_status != git_wc_status_missing))
725 // make this unversioned item change the most important status of this
726 // folder to modified if it doesn't already have another status
727 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
728 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
733 pThis->AddEntry(svnPath, status);
735 return 0;
737 #endif
739 bool
740 CCachedDirectory::IsOwnStatusValid() const
742 return m_ownStatus.HasBeenSet() &&
743 !m_ownStatus.HasExpired(GetTickCount()) &&
744 // 'external' isn't a valid status. That just
745 // means the folder is not part of the current working
746 // copy but it still has its own 'real' status
747 m_ownStatus.GetEffectiveStatus()!=git_wc_status_external &&
748 m_ownStatus.IsKindKnown();
751 void CCachedDirectory::Invalidate()
753 m_ownStatus.Invalidate();
756 git_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()
758 // Combine our OWN folder status with the most important of our *FILES'* status.
759 git_wc_status_kind retVal = GitStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());
761 // NOTE: TSVN marks dir as modified if it contains added/deleted/missing files, but we prefer the most important
762 // status to propagate upward in its original state
763 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
765 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
766 retVal = git_wc_status_modified;
769 // Now combine all our child-directorie's status
771 AutoLocker lock(m_critSec);
772 ChildDirStatus::const_iterator it;
773 for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)
775 retVal = GitStatus::GetMoreImportant(retVal, it->second);
776 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
778 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
779 retVal = git_wc_status_modified;
783 return retVal;
786 // Update our composite status and deal with things if it's changed
787 void CCachedDirectory::UpdateCurrentStatus()
789 git_wc_status_kind newStatus = CalculateRecursiveStatus();
790 ATLTRACE(_T("UpdateCurrentStatus %s new:%d old: %d\n"),
791 m_directoryPath.GetWinPath(),
792 newStatus, m_currentFullStatus);
793 if ((newStatus != m_currentFullStatus)&&(m_ownStatus.IsVersioned()))
795 if ((m_currentFullStatus != git_wc_status_none)&&(m_ownStatus.GetEffectiveStatus() != git_wc_status_missing))
797 // Our status has changed - tell the shell
798 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);
799 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
801 if (m_ownStatus.GetEffectiveStatus() != git_wc_status_missing)
802 m_currentFullStatus = newStatus;
803 else
804 m_currentFullStatus = git_wc_status_missing;
806 // And tell our parent, if we've got one...
807 // we tell our parent *always* about our status, even if it hasn't
808 // changed. This is to make sure that the parent has really our current
809 // status - the parent can decide itself if our status has changed
810 // or not.
811 CTGitPath parentPath = m_directoryPath.GetContainingDirectory();
812 if(!parentPath.IsEmpty())
814 // We have a parent
815 // just version controled directory need to cache.
816 if(parentPath.HasAdminDir())
818 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath);
819 if (cachedDir)
820 cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);
826 // Receive a notification from a child that its status has changed
827 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath& childDir, git_wc_status_kind childStatus)
829 git_wc_status_kind currentStatus = git_wc_status_none;
831 AutoLocker lock(m_critSec);
832 currentStatus = m_childDirectories[childDir];
834 if ((currentStatus != childStatus)||(!IsOwnStatusValid()))
837 AutoLocker lock(m_critSec);
838 m_childDirectories[childDir] = childStatus;
840 UpdateCurrentStatus();
844 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)
846 // Don't return recursive status if we're unversioned ourselves.
847 if(bRecursive && m_ownStatus.GetEffectiveStatus() > git_wc_status_unversioned)
849 CStatusCacheEntry recursiveStatus(m_ownStatus);
850 UpdateCurrentStatus();
851 recursiveStatus.ForceStatus(m_currentFullStatus);
852 return recursiveStatus;
854 else
856 return m_ownStatus;
860 void CCachedDirectory::RefreshStatus(bool bRecursive)
862 // Make sure that our own status is up-to-date
863 GetStatusForMember(m_directoryPath,bRecursive);
865 AutoLocker lock(m_critSec);
866 // We also need to check if all our file members have the right date on them
867 CacheEntryMap::iterator itMembers;
868 std::set<CTGitPath> refreshedpaths;
869 DWORD now = GetTickCount();
870 if (m_entryCache.size() == 0)
871 return;
872 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
874 if (itMembers->first)
876 CTGitPath filePath(m_directoryPath);
877 filePath.AppendPathString(itMembers->first);
878 std::set<CTGitPath>::iterator refr_it;
879 if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&
880 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))
882 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))
884 lock.Unlock();
885 // We need to request this item as well
886 GetStatusForMember(filePath,bRecursive);
887 // GetStatusForMember now has recreated the m_entryCache map.
888 // So start the loop again, but add this path to the refreshed paths set
889 // to make sure we don't refresh this path again. This is to make sure
890 // that we don't end up in an endless loop.
891 lock.Lock();
892 refreshedpaths.insert(refr_it, filePath);
893 itMembers = m_entryCache.begin();
894 if (m_entryCache.size()==0)
895 return;
896 continue;
898 else if ((bRecursive)&&(itMembers->second.IsDirectory()))
900 // crawl all sub folders too! Otherwise a change deep inside the
901 // tree which has changed won't get propagated up the tree.
902 CGitStatusCache::Instance().AddFolderForCrawling(filePath);
909 void CCachedDirectory::RefreshMostImportant()
911 CacheEntryMap::iterator itMembers;
912 git_wc_status_kind newStatus = m_ownStatus.GetEffectiveStatus();
913 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
915 newStatus = GitStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());
916 if (((itMembers->second.GetEffectiveStatus() == git_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == git_wc_status_none))
917 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
919 // treat unversioned files as modified
920 if (newStatus != git_wc_status_added)
921 newStatus = GitStatus::GetMoreImportant(newStatus, git_wc_status_modified);
924 if (newStatus != m_mostImportantFileStatus)
926 ATLTRACE(_T("status change of path %s\n"), m_directoryPath.GetWinPath());
927 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
929 m_mostImportantFileStatus = newStatus;