Minor cleanup
[TortoiseGit.git] / src / TGitCache / CachedDirectory.cpp
blob218713e10f0b5119d7024bf0ad81dd3cbc6e172f
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
4 // Copyright (C) 2008-2017 - 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 "GitAdminDir.h"
23 #include "GitStatusCache.h"
24 #include "GitStatus.h"
25 #include <set>
27 CCachedDirectory::CCachedDirectory(void)
28 : m_currentFullStatus(git_wc_status_none)
29 , m_mostImportantFileStatus(git_wc_status_none)
30 , m_bRecursive(true)
34 CCachedDirectory::~CCachedDirectory(void)
38 CCachedDirectory::CCachedDirectory(const CTGitPath& directoryPath)
39 : CCachedDirectory()
41 ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));
43 directoryPath.HasAdminDir(); // make sure HasAdminDir is always initialized
44 m_directoryPath = directoryPath;
45 m_directoryPath.GetGitPathString(); // make sure git path string is set
48 BOOL CCachedDirectory::SaveToDisk(FILE * pFile)
50 AutoLocker lock(m_critSec);
51 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;
53 unsigned int value = GIT_CACHE_VERSION;
54 WRITEVALUETOFILE(value); // 'version' of this save-format
55 value = (int)m_entryCache.size();
56 WRITEVALUETOFILE(value); // size of the cache map
57 // now iterate through the maps and save every entry.
58 for (const auto& entry : m_entryCache)
60 const CString& key = entry.first;
61 value = key.GetLength();
62 WRITEVALUETOFILE(value);
63 if (value)
65 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
66 return false;
67 if (!entry.second.SaveToDisk(pFile))
68 return false;
71 value = (int)m_childDirectories.size();
72 WRITEVALUETOFILE(value);
73 for (const auto& entry : m_childDirectories)
75 const CString& path = entry.first;
76 value = path.GetLength();
77 WRITEVALUETOFILE(value);
78 if (value)
80 if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)
81 return false;
82 git_wc_status_kind status = entry.second;
83 WRITEVALUETOFILE(status);
86 // WRITEVALUETOFILE(m_propsFileTime);
87 value = m_directoryPath.GetWinPathString().GetLength();
88 WRITEVALUETOFILE(value);
89 if (value)
91 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
92 return false;
94 if (!m_ownStatus.SaveToDisk(pFile))
95 return false;
96 WRITEVALUETOFILE(m_currentFullStatus);
97 WRITEVALUETOFILE(m_mostImportantFileStatus);
98 return true;
101 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)
103 AutoLocker lock(m_critSec);
104 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
107 unsigned int value = 0;
108 LOADVALUEFROMFILE(value);
109 if (value != GIT_CACHE_VERSION)
110 return false; // not the correct version
111 int mapsize = 0;
112 LOADVALUEFROMFILE(mapsize);
113 for (int i=0; i<mapsize; ++i)
115 LOADVALUEFROMFILE(value);
116 if (value > MAX_PATH)
117 return false;
118 if (value)
120 CString sKey;
121 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
123 sKey.ReleaseBuffer(0);
124 return false;
126 sKey.ReleaseBuffer(value);
127 CStatusCacheEntry entry;
128 if (!entry.LoadFromDisk(pFile))
129 return false;
130 // only read non empty keys (just needed for transition from old TGit clients)
131 if (!sKey.IsEmpty())
132 m_entryCache[sKey] = entry;
135 LOADVALUEFROMFILE(mapsize);
136 for (int i=0; i<mapsize; ++i)
138 LOADVALUEFROMFILE(value);
139 if (value > MAX_PATH)
140 return false;
141 if (value)
143 CString sPath;
144 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)
146 sPath.ReleaseBuffer(0);
147 return false;
149 sPath.ReleaseBuffer(value);
150 git_wc_status_kind status;
151 LOADVALUEFROMFILE(status);
152 m_childDirectories[sPath] = status;
155 LOADVALUEFROMFILE(value);
156 if (value > MAX_PATH)
157 return false;
158 if (value)
160 CString sPath;
161 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
163 sPath.ReleaseBuffer(0);
164 return false;
166 sPath.ReleaseBuffer(value);
167 // make sure paths do not end with backslash (just needed for transition from old TGit clients)
168 if (sPath.GetLength() > 3 && sPath[sPath.GetLength() - 1] == L'\\')
169 sPath.TrimRight(L'\\');
170 m_directoryPath.SetFromWin(sPath);
171 m_directoryPath.GetGitPathString(); // make sure git path string is set
173 if (!m_ownStatus.LoadFromDisk(pFile))
174 return false;
176 LOADVALUEFROMFILE(m_currentFullStatus);
177 LOADVALUEFROMFILE(m_mostImportantFileStatus);
179 catch ( CAtlException )
181 return false;
183 return true;
187 CStatusCacheEntry CCachedDirectory::GetStatusFromCache(const CTGitPath& path, bool bRecursive)
189 if(path.IsDirectory())
191 // We don't have directory status in our cache
192 // Ask the directory if it knows its own status
193 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
194 if( dirEntry)
196 if (dirEntry->IsOwnStatusValid())
197 return dirEntry->GetOwnStatus(bRecursive);
198 else
200 /* cache have outof date, need crawl again*/
202 /*AutoLocker lock(dirEntry->m_critSec);
203 ChildDirStatus::const_iterator it;
204 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
206 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
209 CGitStatusCache::Instance().AddFolderForCrawling(path);
211 /*Return old status during crawling*/
212 return dirEntry->GetOwnStatus(bRecursive);
215 else
217 CGitStatusCache::Instance().AddFolderForCrawling(path);
219 return CStatusCacheEntry();
221 else
223 //All file ignored if under ignore directory
224 if (m_ownStatus.GetEffectiveStatus() == git_wc_status_ignored)
225 return CStatusCacheEntry(git_wc_status_ignored);
226 if (m_ownStatus.GetEffectiveStatus() == git_wc_status_unversioned)
227 return CStatusCacheEntry(git_wc_status_unversioned);
229 // Look up a file in our own cache
230 AutoLocker lock(m_critSec);
231 CString strCacheKey = GetCacheKey(path);
232 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
233 if(itMap != m_entryCache.end())
235 // We've hit the cache - check for timeout
236 if (!itMap->second.HasExpired((LONGLONG)GetTickCount64()))
238 if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))
240 if ((itMap->second.GetEffectiveStatus()!=git_wc_status_missing)||(!PathFileExists(path.GetWinPath())))
242 // Note: the filetime matches after a modified has been committed too.
243 // So in that case, we would return a wrong status (e.g. 'modified' instead
244 // of 'normal') here.
245 return itMap->second;
251 CGitStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory());
252 return CStatusCacheEntry();
256 CStatusCacheEntry CCachedDirectory::GetStatusFromGit(const CTGitPath &path, const CString& sProjectRoot, bool isSelf)
258 CString subpaths;
259 CString s = path.GetGitPathString();
260 if (s.GetLength() > sProjectRoot.GetLength())
262 if (s[sProjectRoot.GetLength()] == L'/')
263 subpaths = s.Right(s.GetLength() - sProjectRoot.GetLength() - 1);
264 else
265 subpaths = s.Right(s.GetLength() - sProjectRoot.GetLength());
268 GitStatus *pGitStatus = &CGitStatusCache::Instance().m_GitStatus;
269 UNREFERENCED_PARAMETER(pGitStatus);
271 bool isVersion =true;
272 pGitStatus->IsUnderVersionControl(sProjectRoot, subpaths, path.IsDirectory(), &isVersion);
273 if(!isVersion)
274 { //untracked file
275 bool isDir = path.IsDirectory();
276 bool isIgnoreFileChanged = pGitStatus->CheckAndUpdateIgnoreFiles(sProjectRoot, subpaths, isDir);
278 if (isDir)
280 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path,
281 false); /* we needn't watch untracked directory*/
283 if(dirEntry)
285 AutoLocker lock(dirEntry->m_critSec);
287 git_wc_status_kind dirstatus = dirEntry->GetCurrentFullStatus() ;
288 if (CGitStatusCache::Instance().IsUnversionedAsModified() || dirstatus == git_wc_status_none || dirstatus >= git_wc_status_normal || isIgnoreFileChanged)
289 {/* status have not initialized*/
290 bool isignore = pGitStatus->IsIgnored(sProjectRoot, subpaths, isDir);
292 if (!isignore && CGitStatusCache::Instance().IsUnversionedAsModified())
294 dirEntry->EnumFiles(path, sProjectRoot, subpaths, isSelf);
295 dirEntry->UpdateCurrentStatus();
296 return CStatusCacheEntry(dirEntry->GetCurrentFullStatus());
299 git_wc_status2_t status2;
300 status2.text_status = status2.prop_status =
301 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
303 // we do not know anything about files here, all we know is that there are not versioned files in this dir
304 dirEntry->m_mostImportantFileStatus = git_wc_status_none;
305 dirEntry->m_ownStatus.SetKind(git_node_dir);
306 dirEntry->m_ownStatus.SetStatus(&status2);
307 dirEntry->m_currentFullStatus = status2.text_status;
309 return dirEntry->m_ownStatus;
313 else /* path is file */
315 AutoLocker lock(m_critSec);
316 CString strCacheKey = GetCacheKey(path);
318 if (strCacheKey.IsEmpty())
319 return CStatusCacheEntry();
321 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
322 if(itMap == m_entryCache.end() || isIgnoreFileChanged)
324 git_wc_status2_t status2;
325 bool isignore = pGitStatus->IsIgnored(sProjectRoot, subpaths, isDir);
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];
331 else
333 return itMap->second;
336 return CStatusCacheEntry();
339 else
341 EnumFiles(path, sProjectRoot, subpaths, isSelf);
342 UpdateCurrentStatus();
343 if (!path.IsDirectory())
344 return GetCacheStatusForMember(path);
345 return CStatusCacheEntry(m_ownStatus);
349 /// bFetch is true, fetch all status, call by crawl.
350 /// bFetch is false, get cache status, return quickly.
352 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */)
354 CString sProjectRoot;
355 bool bIsVersionedPath;
357 bool bRequestForSelf = false;
358 if(path.IsEquivalentToWithoutCase(m_directoryPath))
360 bRequestForSelf = true;
361 AutoLocker lock(m_critSec);
362 // HasAdminDir might modify m_directoryPath, so we need to do it synchronized
363 bIsVersionedPath = m_directoryPath.HasAdminDir(&sProjectRoot);
365 else
366 bIsVersionedPath = path.HasAdminDir(&sProjectRoot);
368 // In all most circumstances, we ask for the status of a member of this directory.
369 ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);
371 //If is not version control path
372 if( !bIsVersionedPath)
374 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": %s is not underversion control\n", path.GetWinPath());
375 return CStatusCacheEntry();
378 // We've not got this item in the cache - let's add it
379 // We never bother asking SVN for the status of just one file, always for its containing directory
381 if (GitAdminDir::IsAdminDirPath(path.GetWinPathString()))
383 // We're being asked for the status of an .git directory
384 // It's not worth asking for this
385 return CStatusCacheEntry();
389 if(bFetch)
391 return GetStatusFromGit(path, sProjectRoot, bRequestForSelf);
393 else
395 return GetStatusFromCache(path, bRecursive);
399 CStatusCacheEntry CCachedDirectory::GetCacheStatusForMember(const CTGitPath& path)
401 // no disk access!
402 AutoLocker lock(m_critSec);
403 CacheEntryMap::iterator itMap = m_entryCache.find(GetCacheKey(path));
404 if(itMap != m_entryCache.end())
405 return itMap->second;
407 return CStatusCacheEntry();
410 int CCachedDirectory::EnumFiles(const CTGitPath& path, CString sProjectRoot, const CString& sSubPath, bool isSelf)
412 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": EnumFiles %s\n", path.GetWinPath());
414 // strip "\" at the end, otherwise cache lookups for drives do not work correctly
415 sProjectRoot.TrimRight(L'\\');
417 GitStatus *pStatus = &CGitStatusCache::Instance().m_GitStatus;
418 UNREFERENCED_PARAMETER(pStatus);
419 git_wc_status_kind status = git_wc_status_none;
421 if (!path.IsDirectory())
423 bool assumeValid = false;
424 bool skipWorktree = false;
425 pStatus->GetFileStatus(sProjectRoot, sSubPath, &status, TRUE, false, true, GetStatusCallback, this, &assumeValid, &skipWorktree);
426 if (status < m_mostImportantFileStatus)
427 RefreshMostImportant();
429 else
431 if (isSelf)
433 AutoLocker lock(m_critSec);
434 // clear subdirectory status cache
435 m_childDirectories.clear();
436 // build new files status cache
437 m_entryCache_tmp.clear();
440 m_mostImportantFileStatus = git_wc_status_none;
441 pStatus->EnumDirStatus(sProjectRoot, sSubPath, &status, TRUE, false, true, GetStatusCallback, this);
442 m_mostImportantFileStatus = GitStatus::GetMoreImportant(m_mostImportantFileStatus, status);
444 if (isSelf)
446 AutoLocker lock(m_critSec);
447 // use a tmp files status cache so that we can still use the old cached values
448 // for deciding whether we have to issue a shell notify
449 m_entryCache = std::move(m_entryCache_tmp);
450 m_entryCache_tmp.clear();
453 // need to set/construct m_ownStatus (only unversioned and normal are valid values)
454 m_ownStatus = git_wc_status_unversioned;
455 m_ownStatus.SetKind(git_node_dir);
456 if (m_mostImportantFileStatus > git_wc_status_unversioned)
458 git_wc_status2_t status2;
459 status2.text_status = status2.prop_status = git_wc_status_normal;
460 m_ownStatus.SetStatus(&status2);
462 else
464 if (::PathFileExists(m_directoryPath.GetWinPathString() + L"\\.git")) {
465 git_wc_status2_t status2;
466 status2.text_status = status2.prop_status = git_wc_status_normal;
467 m_ownStatus.SetStatus(&status2);
469 else
471 git_wc_status2_t status2;
472 status2.text_status = status2.prop_status = CalculateRecursiveStatus();
473 m_ownStatus.SetStatus(&status2);
478 return 0;
480 void
481 CCachedDirectory::AddEntry(const CTGitPath& path, const git_wc_status2_t* pGitStatus, DWORD validuntil /* = 0*/)
483 AutoLocker lock(m_critSec);
484 if(path.IsDirectory())
486 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
487 if (childDir)
489 if ((childDir->GetCurrentFullStatus() != git_wc_status_missing) || !pGitStatus || (pGitStatus->text_status != git_wc_status_unversioned))
491 if(pGitStatus)
493 if(childDir->GetCurrentFullStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
495 CGitStatusCache::Instance().UpdateShell(path);
496 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": shell update for %s\n", path.GetWinPath());
497 childDir->m_ownStatus.SetKind(git_node_dir);
498 childDir->m_ownStatus.SetStatus(pGitStatus);
502 childDir->m_ownStatus.SetKind(git_node_dir);
507 else
509 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path.GetContainingDirectory());
510 bool bNotified = false;
512 if(!childDir)
513 return ;
515 AutoLocker lock2(childDir->m_critSec);
516 CString cachekey = GetCacheKey(path);
517 CacheEntryMap::iterator entry_it = childDir->m_entryCache.lower_bound(cachekey);
518 if (entry_it != childDir->m_entryCache.end() && entry_it->first == cachekey)
520 if (pGitStatus)
522 if (entry_it->second.GetEffectiveStatus() > git_wc_status_none &&
523 entry_it->second.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status)
526 bNotified =true;
531 else
533 entry_it = childDir->m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
534 bNotified = true;
537 entry_it->second = CStatusCacheEntry(pGitStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);
538 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
539 entry_it->second.SetKind(git_node_file);
541 childDir->m_entryCache_tmp[cachekey] = entry_it->second;
543 if(bNotified)
545 CGitStatusCache::Instance().UpdateShell(path);
546 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": shell update for %s\n", path.GetWinPath());
549 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Path Entry Add %s %s %s %d\n", path.GetWinPath(), cachekey, m_directoryPath.GetWinPath(), pGitStatus->text_status);
553 CString
554 CCachedDirectory::GetCacheKey(const CTGitPath& path)
556 // All we put into the cache as a key is just the end portion of the pathname
557 // There's no point storing the path of the containing directory for every item
558 return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength()).TrimLeft(L'\\');
561 CString
562 CCachedDirectory::GetFullPathString(const CString& cacheKey)
564 CString fullpath(m_directoryPath.GetWinPathString());
565 fullpath += L'\\';
566 fullpath += cacheKey;
567 return fullpath;
570 BOOL CCachedDirectory::GetStatusCallback(const CString & path, git_wc_status_kind status,bool isDir, void *, bool assumeValid, bool skipWorktree)
572 git_wc_status2_t _status;
573 git_wc_status2_t *status2 = &_status;
575 status2->prop_status = status2->text_status = status;
576 status2->assumeValid = assumeValid;
577 status2->skipWorktree = skipWorktree;
579 CTGitPath gitPath(path);
581 CCachedDirectory *pThis = CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath.GetContainingDirectory());
583 if (!pThis)
584 return FALSE;
586 // if(status->entry)
588 if (isDir)
589 { /*gitpath is directory*/
590 //if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
592 if (!gitPath.Exists())
594 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Miss dir %s \n", gitPath.GetWinPath());
595 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_deleted);
598 if ( status < git_wc_status_normal)
600 if (::PathFileExists(path + L"\\.git"))
601 { // this is submodule
602 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": skip submodule %s\n", (LPCTSTR)path);
603 return FALSE;
606 if (pThis->m_bRecursive)
608 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
609 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
610 if (status >= git_wc_status_normal || (CGitStatusCache::Instance().IsUnversionedAsModified() && status == git_wc_status_unversioned))
611 CGitStatusCache::Instance().AddFolderForCrawling(gitPath);
614 // Make sure we know about this child directory
615 // This initial status value is likely to be overwritten from below at some point
616 git_wc_status_kind s = GitStatus::GetMoreImportant(status2->text_status, status2->prop_status);
618 // folders must not be displayed as added or deleted only as modified
619 if (s == git_wc_status_deleted || s == git_wc_status_added)
620 s = git_wc_status_modified;
622 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(gitPath);
623 if (cdir)
625 // This child directory is already in our cache!
626 // So ask this dir about its recursive status
627 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
629 // only propagate real status of submodules to parent repo if enabled
630 if (!CGitStatusCache::Instance().IsRecurseSubmodules())
632 CString root1, root2;
633 bool pThisIsVersioned;
635 AutoLocker lock(pThis->m_critSec);
636 pThisIsVersioned = pThis->m_directoryPath.HasAdminDir(&root1);
638 AutoLocker lock(cdir->m_critSec);
639 if (pThisIsVersioned && cdir->m_directoryPath.HasAdminDir(&root2) && !CTGitPath::ArePathStringsEqualWithCase(root1, root2))
640 st = s;
642 AutoLocker lock(pThis->m_critSec);
643 pThis->m_childDirectories[gitPath.GetWinPathString()] = st;
644 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": call 1 Update dir %s %d\n", gitPath.GetWinPath(), st);
646 else
648 // the child directory is not in the cache. Create a new entry for it in the cache which is
649 // initially 'unversioned'. But we added that directory to the crawling list above, which
650 // means the cache will be updated soon.
651 CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath);
653 AutoLocker lock(pThis->m_critSec);
654 pThis->m_childDirectories[gitPath.GetWinPathString()] = s;
655 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": call 2 Update dir %s %d\n", gitPath.GetWinPath(), s);
659 else /* gitpath is file*/
661 // Keep track of the most important status of all the files in this directory
662 // Don't include subdirectories in this figure, because they need to provide their
663 // own 'most important' value
664 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->text_status);
665 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->prop_status);
666 if ((status2->text_status == git_wc_status_unversioned) && (CGitStatusCache::Instance().IsUnversionedAsModified()))
668 // treat unversioned files as modified
669 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
670 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
675 pThis->AddEntry(gitPath, status2);
677 return FALSE;
680 bool
681 CCachedDirectory::IsOwnStatusValid() const
683 return m_ownStatus.HasBeenSet() && !m_ownStatus.HasExpired(GetTickCount64());
686 void CCachedDirectory::Invalidate()
688 m_ownStatus.Invalidate();
691 git_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()
693 // Combine our OWN folder status with the most important of our *FILES'* status.
694 git_wc_status_kind retVal = GitStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());
696 // folders can only be none, unversioned, normal, modified, and conflicted
697 if (retVal == git_wc_status_deleted || retVal == git_wc_status_added)
698 retVal = git_wc_status_modified;
700 // Now combine all our child-directorie's status
701 AutoLocker lock(m_critSec);
702 ChildDirStatus::const_iterator it;
703 for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)
705 retVal = GitStatus::GetMoreImportant(retVal, it->second);
708 return retVal;
711 // Update our composite status and deal with things if it's changed
712 void CCachedDirectory::UpdateCurrentStatus()
714 git_wc_status_kind newStatus = CalculateRecursiveStatus();
715 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": UpdateCurrentStatus %s new:%d old: %d\n",
716 m_directoryPath.GetWinPath(),
717 newStatus, m_currentFullStatus);
719 if (newStatus != m_currentFullStatus && m_ownStatus.IsDirectory())
721 m_currentFullStatus = newStatus;
723 // Our status has changed - tell the shell
724 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Dir %s, status change from %d to %d\n", m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);
725 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
727 // And tell our parent, if we've got one...
728 // we tell our parent *always* about our status, even if it hasn't
729 // changed. This is to make sure that the parent has really our current
730 // status - the parent can decide itself if our status has changed
731 // or not.
732 CTGitPath parentPath = m_directoryPath.GetContainingDirectory();
733 if(!parentPath.IsEmpty())
735 // We have a parent
736 // just version controled directory need to cache.
737 CString root1, root2;
738 if (parentPath.HasAdminDir(&root1) && (CGitStatusCache::Instance().IsRecurseSubmodules() || m_directoryPath.HasAdminDir(&root2) && CTGitPath::ArePathStringsEqualWithCase(root1, root2)))
740 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath);
741 if (cachedDir)
742 cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);
747 // Receive a notification from a child that its status has changed
748 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath& childDir, git_wc_status_kind childStatus)
750 git_wc_status_kind currentStatus = git_wc_status_none;
752 AutoLocker lock(m_critSec);
753 currentStatus = m_childDirectories[childDir.GetWinPathString()];
755 if ((currentStatus != childStatus)||(!IsOwnStatusValid()))
758 AutoLocker lock(m_critSec);
759 m_childDirectories[childDir.GetWinPathString()] = childStatus;
761 UpdateCurrentStatus();
765 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)
767 // Don't return recursive status if we're unversioned ourselves.
768 if(bRecursive && m_ownStatus.IsDirectory() && m_ownStatus.GetEffectiveStatus() != git_wc_status_ignored)
770 CStatusCacheEntry recursiveStatus(m_ownStatus);
771 UpdateCurrentStatus();
772 recursiveStatus.ForceStatus(m_currentFullStatus);
773 return recursiveStatus;
775 else
777 return m_ownStatus;
781 void CCachedDirectory::RefreshStatus(bool bRecursive)
783 // Make sure that our own status is up-to-date
784 GetStatusForMember(m_directoryPath,bRecursive);
786 AutoLocker lock(m_critSec);
787 // We also need to check if all our file members have the right date on them
788 CacheEntryMap::iterator itMembers;
789 std::set<CTGitPath> refreshedpaths;
790 ULONGLONG now = GetTickCount64();
791 if (m_entryCache.empty())
792 return;
793 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
795 if (itMembers->first)
797 CTGitPath filePath(m_directoryPath);
798 filePath.AppendPathString(itMembers->first);
799 std::set<CTGitPath>::iterator refr_it;
800 if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&
801 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))
803 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))
805 lock.Unlock();
806 // We need to request this item as well
807 GetStatusForMember(filePath,bRecursive);
808 // GetStatusForMember now has recreated the m_entryCache map.
809 // So start the loop again, but add this path to the refreshed paths set
810 // to make sure we don't refresh this path again. This is to make sure
811 // that we don't end up in an endless loop.
812 lock.Lock();
813 refreshedpaths.insert(refr_it, filePath);
814 itMembers = m_entryCache.begin();
815 if (m_entryCache.empty())
816 return;
817 continue;
819 else if ((bRecursive)&&(itMembers->second.IsDirectory()))
821 // crawl all sub folders too! Otherwise a change deep inside the
822 // tree which has changed won't get propagated up the tree.
823 CGitStatusCache::Instance().AddFolderForCrawling(filePath);
830 void CCachedDirectory::RefreshMostImportant()
832 AutoLocker lock(m_critSec);
833 CacheEntryMap::iterator itMembers;
834 git_wc_status_kind newStatus = git_wc_status_unversioned;
835 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
837 newStatus = GitStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());
838 if (((itMembers->second.GetEffectiveStatus() == git_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == git_wc_status_none))
839 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
841 // treat unversioned files as modified
842 if (newStatus != git_wc_status_added)
843 newStatus = GitStatus::GetMoreImportant(newStatus, git_wc_status_modified);
846 if (newStatus != m_mostImportantFileStatus)
848 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": status change of path %s\n", m_directoryPath.GetWinPath());
849 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
851 m_mostImportantFileStatus = newStatus;