FileDiffDlg: Switch Version 1 and Version 2 (Base)
[TortoiseGit.git] / src / TGitCache / CachedDirectory.cpp
bloba6b9d7df7360e1e9d72ca725c35dd97e961223f8
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
4 // Copyright (C) 2008-2016 - 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 "GitStatusCache.h"
23 #include "GitStatus.h"
24 #include <set>
26 CCachedDirectory::CCachedDirectory(void)
28 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
29 m_bRecursive = true;
32 CCachedDirectory::~CCachedDirectory(void)
36 CCachedDirectory::CCachedDirectory(const CTGitPath& directoryPath)
38 ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));
40 m_directoryPath = directoryPath;
41 m_directoryPath.GetGitPathString(); // make sure git path string is set
43 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
44 m_bRecursive = true;
47 BOOL CCachedDirectory::SaveToDisk(FILE * pFile)
49 AutoLocker lock(m_critSec);
50 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;
52 unsigned int value = GIT_CACHE_VERSION;
53 WRITEVALUETOFILE(value); // 'version' of this save-format
54 value = (int)m_entryCache.size();
55 WRITEVALUETOFILE(value); // size of the cache map
56 // now iterate through the maps and save every entry.
57 for (const auto& entry : m_entryCache)
59 const CString& key = entry.first;
60 value = key.GetLength();
61 WRITEVALUETOFILE(value);
62 if (value)
64 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
65 return false;
66 if (!entry.second.SaveToDisk(pFile))
67 return false;
70 value = (int)m_childDirectories.size();
71 WRITEVALUETOFILE(value);
72 for (const auto& entry : m_childDirectories)
74 const CString& path = entry.first.GetWinPathString();
75 value = path.GetLength();
76 WRITEVALUETOFILE(value);
77 if (value)
79 if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)
80 return false;
81 git_wc_status_kind status = entry.second;
82 WRITEVALUETOFILE(status);
85 // WRITEVALUETOFILE(m_propsFileTime);
86 value = m_directoryPath.GetWinPathString().GetLength();
87 WRITEVALUETOFILE(value);
88 if (value)
90 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
91 return false;
93 if (!m_ownStatus.SaveToDisk(pFile))
94 return false;
95 WRITEVALUETOFILE(m_currentFullStatus);
96 WRITEVALUETOFILE(m_mostImportantFileStatus);
97 return true;
100 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)
102 AutoLocker lock(m_critSec);
103 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
106 unsigned int value = 0;
107 LOADVALUEFROMFILE(value);
108 if (value != GIT_CACHE_VERSION)
109 return false; // not the correct version
110 int mapsize = 0;
111 LOADVALUEFROMFILE(mapsize);
112 for (int i=0; i<mapsize; ++i)
114 LOADVALUEFROMFILE(value);
115 if (value > MAX_PATH)
116 return false;
117 if (value)
119 CString sKey;
120 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
122 sKey.ReleaseBuffer(0);
123 return false;
125 sKey.ReleaseBuffer(value);
126 CStatusCacheEntry entry;
127 if (!entry.LoadFromDisk(pFile))
128 return false;
129 // only read non empty keys (just needed for transition from old TGit clients)
130 if (!sKey.IsEmpty())
131 m_entryCache[sKey] = entry;
134 LOADVALUEFROMFILE(mapsize);
135 for (int i=0; i<mapsize; ++i)
137 LOADVALUEFROMFILE(value);
138 if (value > MAX_PATH)
139 return false;
140 if (value)
142 CString sPath;
143 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)
145 sPath.ReleaseBuffer(0);
146 return false;
148 sPath.ReleaseBuffer(value);
149 git_wc_status_kind status;
150 LOADVALUEFROMFILE(status);
151 m_childDirectories[CTGitPath(sPath)] = status;
154 LOADVALUEFROMFILE(value);
155 if (value > MAX_PATH)
156 return false;
157 if (value)
159 CString sPath;
160 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
162 sPath.ReleaseBuffer(0);
163 return false;
165 sPath.ReleaseBuffer(value);
166 // make sure paths do not end with backslash (just needed for transition from old TGit clients)
167 if (sPath.GetLength() > 3 && sPath[sPath.GetLength() - 1] == _T('\\'))
168 sPath.TrimRight(_T("\\"));
169 m_directoryPath.SetFromWin(sPath);
170 m_directoryPath.GetGitPathString(); // make sure git path string is set
172 if (!m_ownStatus.LoadFromDisk(pFile))
173 return false;
175 LOADVALUEFROMFILE(m_currentFullStatus);
176 LOADVALUEFROMFILE(m_mostImportantFileStatus);
178 catch ( CAtlException )
180 return false;
182 return true;
186 CStatusCacheEntry CCachedDirectory::GetStatusFromCache(const CTGitPath& path, bool bRecursive)
188 if(path.IsDirectory())
190 // We don't have directory status in our cache
191 // Ask the directory if it knows its own status
192 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
193 if( dirEntry)
195 if (dirEntry->IsOwnStatusValid())
196 return dirEntry->GetOwnStatus(bRecursive);
197 else
199 /* cache have outof date, need crawl again*/
201 /*AutoLocker lock(dirEntry->m_critSec);
202 ChildDirStatus::const_iterator it;
203 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
205 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
208 CGitStatusCache::Instance().AddFolderForCrawling(path);
210 /*Return old status during crawling*/
211 return dirEntry->GetOwnStatus(bRecursive);
214 else
216 CGitStatusCache::Instance().AddFolderForCrawling(path);
218 return CStatusCacheEntry();
220 else
222 //All file ignored if under ignore directory
223 if (m_ownStatus.GetEffectiveStatus() == git_wc_status_ignored)
224 return CStatusCacheEntry(git_wc_status_ignored);
225 if (m_ownStatus.GetEffectiveStatus() == git_wc_status_unversioned)
226 return CStatusCacheEntry(git_wc_status_unversioned);
228 // Look up a file in our own cache
229 AutoLocker lock(m_critSec);
230 CString strCacheKey = GetCacheKey(path);
231 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
232 if(itMap != m_entryCache.end())
234 // We've hit the cache - check for timeout
235 if (!itMap->second.HasExpired((LONGLONG)GetTickCount64()))
237 if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))
239 if ((itMap->second.GetEffectiveStatus()!=git_wc_status_missing)||(!PathFileExists(path.GetWinPath())))
241 // Note: the filetime matches after a modified has been committed too.
242 // So in that case, we would return a wrong status (e.g. 'modified' instead
243 // of 'normal') here.
244 return itMap->second;
250 CGitStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory());
251 return CStatusCacheEntry();
255 CStatusCacheEntry CCachedDirectory::GetStatusFromGit(const CTGitPath &path, CString sProjectRoot)
257 CString subpaths = path.GetGitPathString();
258 if(subpaths.GetLength() >= sProjectRoot.GetLength())
260 if(subpaths[sProjectRoot.GetLength()] == _T('/'))
261 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength()-1);
262 else
263 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength());
266 GitStatus *pGitStatus = &CGitStatusCache::Instance().m_GitStatus;
267 UNREFERENCED_PARAMETER(pGitStatus);
269 bool isVersion =true;
270 pGitStatus->IsUnderVersionControl(sProjectRoot, subpaths, path.IsDirectory(), &isVersion);
271 if(!isVersion)
272 { //untracked file
273 bool isDir = path.IsDirectory();
274 bool isIgnoreFileChanged = pGitStatus->HasIgnoreFilesChanged(sProjectRoot, subpaths, isDir);
276 if( isIgnoreFileChanged)
278 pGitStatus->LoadIgnoreFile(sProjectRoot, subpaths, isDir);
281 if (isDir)
283 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path,
284 false); /* we needn't watch untracked directory*/
286 if(dirEntry)
288 AutoLocker lock(dirEntry->m_critSec);
290 git_wc_status_kind dirstatus = dirEntry->GetCurrentFullStatus() ;
291 if (CGitStatusCache::Instance().IsUnversionedAsModified() || dirstatus == git_wc_status_none || dirstatus >= git_wc_status_normal || isIgnoreFileChanged)
292 {/* status have not initialized*/
293 bool isignore = false;
294 pGitStatus->IsIgnore(sProjectRoot, subpaths, &isignore, isDir);
296 if (!isignore && CGitStatusCache::Instance().IsUnversionedAsModified())
298 dirEntry->EnumFiles(path, TRUE);
299 dirEntry->UpdateCurrentStatus();
300 return CStatusCacheEntry(dirEntry->GetCurrentFullStatus());
303 git_wc_status2_t status2;
304 status2.text_status = status2.prop_status =
305 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
307 // we do not know anything about files here, all we know is that there are not versioned files in this dir
308 dirEntry->m_mostImportantFileStatus = git_wc_status_none;
309 dirEntry->m_ownStatus.SetKind(git_node_dir);
310 dirEntry->m_ownStatus.SetStatus(&status2);
311 dirEntry->m_currentFullStatus = status2.text_status;
313 return dirEntry->m_ownStatus;
317 else /* path is file */
319 AutoLocker lock(m_critSec);
320 CString strCacheKey = GetCacheKey(path);
322 if (strCacheKey.IsEmpty())
323 return CStatusCacheEntry();
325 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
326 if(itMap == m_entryCache.end() || isIgnoreFileChanged)
328 git_wc_status2_t status2;
329 bool isignore = false;
330 pGitStatus->IsIgnore(sProjectRoot, subpaths, &isignore, isDir);
331 status2.text_status = status2.prop_status =
332 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
333 AddEntry(path, &status2);
334 return m_entryCache[strCacheKey];
336 else
338 return itMap->second;
341 return CStatusCacheEntry();
344 else
346 EnumFiles(path, TRUE);
347 UpdateCurrentStatus();
348 if (!path.IsDirectory())
349 return GetCacheStatusForMember(path);
350 return CStatusCacheEntry(m_ownStatus);
354 /// bFetch is true, fetch all status, call by crawl.
355 /// bFetch is false, get cache status, return quickly.
357 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */)
359 CString sProjectRoot;
360 bool bIsVersionedPath;
362 bool bRequestForSelf = false;
363 if(path.IsEquivalentToWithoutCase(m_directoryPath))
365 bRequestForSelf = true;
366 AutoLocker lock(m_critSec);
367 // HasAdminDir might modify m_directoryPath, so we need to do it synchronized
368 bIsVersionedPath = m_directoryPath.HasAdminDir(&sProjectRoot);
370 else
371 bIsVersionedPath = path.HasAdminDir(&sProjectRoot);
373 // In all most circumstances, we ask for the status of a member of this directory.
374 ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);
376 //If is not version control path
377 if( !bIsVersionedPath)
379 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": %s is not underversion control\n"), path.GetWinPath());
380 return CStatusCacheEntry();
383 // We've not got this item in the cache - let's add it
384 // We never bother asking SVN for the status of just one file, always for its containing directory
386 if (GitAdminDir::IsAdminDirPath(path.GetWinPathString()))
388 // We're being asked for the status of an .git directory
389 // It's not worth asking for this
390 return CStatusCacheEntry();
394 if(bFetch)
396 return GetStatusFromGit(path, sProjectRoot);
398 else
400 return GetStatusFromCache(path, bRecursive);
404 CStatusCacheEntry CCachedDirectory::GetCacheStatusForMember(const CTGitPath& path)
406 // no disk access!
407 AutoLocker lock(m_critSec);
408 CacheEntryMap::iterator itMap = m_entryCache.find(GetCacheKey(path));
409 if(itMap != m_entryCache.end())
410 return itMap->second;
412 return CStatusCacheEntry();
415 int CCachedDirectory::EnumFiles(const CTGitPath &path , bool IsFull)
417 CString sProjectRoot;
418 path.HasAdminDir(&sProjectRoot);
420 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": EnumFiles %s\n"), path.GetWinPath());
422 ATLASSERT( !m_directoryPath.IsEmpty() );
424 CString sSubPath;
426 CString s = path.GetWinPath();
428 if (s.GetLength() > sProjectRoot.GetLength())
430 // skip initial slash if necessary
431 if(s[sProjectRoot.GetLength()] == _T('\\'))
432 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() -1);
433 else
434 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() );
437 // strip "\" at the end, otherwise cache lookups for drives do not work correctly
438 sProjectRoot.TrimRight(_T("\\"));
440 GitStatus *pStatus = &CGitStatusCache::Instance().m_GitStatus;
441 UNREFERENCED_PARAMETER(pStatus);
442 git_wc_status_kind status = git_wc_status_none;
444 if (!path.IsDirectory())
446 bool assumeValid = false;
447 bool skipWorktree = false;
448 pStatus->GetFileStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback, this, &assumeValid, &skipWorktree);
449 if (status < m_mostImportantFileStatus)
450 RefreshMostImportant();
452 else
454 bool isSelf = path == m_directoryPath;
455 if (isSelf)
457 AutoLocker lock(m_critSec);
458 // clear subdirectory status cache
459 m_childDirectories.clear();
460 // build new files status cache
461 m_entryCache_tmp.clear();
464 m_mostImportantFileStatus = git_wc_status_none;
465 pStatus->EnumDirStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback,this);
466 m_mostImportantFileStatus = GitStatus::GetMoreImportant(m_mostImportantFileStatus, status);
468 if (isSelf)
470 AutoLocker lock(m_critSec);
471 // use a tmp files status cache so that we can still use the old cached values
472 // for deciding whether we have to issue a shell notify
473 m_entryCache = m_entryCache_tmp;
474 m_entryCache_tmp.clear();
477 // need to set/construct m_ownStatus (only unversioned and normal are valid values)
478 m_ownStatus = git_wc_status_unversioned;
479 m_ownStatus.SetKind(git_node_dir);
480 if (m_mostImportantFileStatus > git_wc_status_unversioned)
482 git_wc_status2_t status2;
483 status2.text_status = status2.prop_status = git_wc_status_normal;
484 m_ownStatus.SetStatus(&status2);
486 else
488 if (::PathFileExists(m_directoryPath.GetWinPathString() + _T("\\.git"))) {
489 git_wc_status2_t status2;
490 status2.text_status = status2.prop_status = git_wc_status_normal;
491 m_ownStatus.SetStatus(&status2);
493 else
495 git_wc_status2_t status2;
496 status2.text_status = status2.prop_status = CalculateRecursiveStatus();
497 m_ownStatus.SetStatus(&status2);
502 return 0;
504 void
505 CCachedDirectory::AddEntry(const CTGitPath& path, const git_wc_status2_t* pGitStatus, DWORD validuntil /* = 0*/)
507 AutoLocker lock(m_critSec);
508 if(path.IsDirectory())
510 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
511 if (childDir)
513 if ((childDir->GetCurrentFullStatus() != git_wc_status_missing) || !pGitStatus || (pGitStatus->text_status != git_wc_status_unversioned))
515 if(pGitStatus)
517 if(childDir->GetCurrentFullStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
519 CGitStatusCache::Instance().UpdateShell(path);
520 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": shell update for %s\n"), path.GetWinPath());
521 childDir->m_ownStatus.SetKind(git_node_dir);
522 childDir->m_ownStatus.SetStatus(pGitStatus);
526 childDir->m_ownStatus.SetKind(git_node_dir);
531 else
533 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path.GetContainingDirectory());
534 bool bNotified = false;
536 if(!childDir)
537 return ;
539 AutoLocker lock2(childDir->m_critSec);
540 CString cachekey = GetCacheKey(path);
541 CacheEntryMap::iterator entry_it = childDir->m_entryCache.lower_bound(cachekey);
542 if (entry_it != childDir->m_entryCache.end() && entry_it->first == cachekey)
544 if (pGitStatus)
546 if (entry_it->second.GetEffectiveStatus() > git_wc_status_none &&
547 entry_it->second.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status)
550 bNotified =true;
555 else
557 entry_it = childDir->m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
558 bNotified = true;
561 entry_it->second = CStatusCacheEntry(pGitStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);
562 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
563 entry_it->second.SetKind(git_node_file);
565 childDir->m_entryCache_tmp[cachekey] = entry_it->second;
567 if(bNotified)
569 CGitStatusCache::Instance().UpdateShell(path);
570 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": shell update for %s\n"), path.GetWinPath());
573 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": Path Entry Add %s %s %s %d\n"), path.GetWinPath(), cachekey, m_directoryPath.GetWinPath(), pGitStatus->text_status);
577 CString
578 CCachedDirectory::GetCacheKey(const CTGitPath& path)
580 // All we put into the cache as a key is just the end portion of the pathname
581 // There's no point storing the path of the containing directory for every item
582 return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength()).MakeLower().TrimLeft(_T("\\"));
585 CString
586 CCachedDirectory::GetFullPathString(const CString& cacheKey)
588 CString fullpath(m_directoryPath.GetWinPathString());
589 fullpath += _T('\\');
590 fullpath += cacheKey;
591 return fullpath;
594 BOOL CCachedDirectory::GetStatusCallback(const CString & path, git_wc_status_kind status,bool isDir, void *, bool assumeValid, bool skipWorktree)
596 git_wc_status2_t _status;
597 git_wc_status2_t *status2 = &_status;
599 status2->prop_status = status2->text_status = status;
600 status2->assumeValid = assumeValid;
601 status2->skipWorktree = skipWorktree;
603 CTGitPath gitPath(path);
605 CCachedDirectory *pThis = CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath.GetContainingDirectory());
607 if (!pThis)
608 return FALSE;
610 // if(status->entry)
612 if (isDir)
613 { /*gitpath is directory*/
614 //if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
616 if (!gitPath.Exists())
618 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": Miss dir %s \n"), gitPath.GetWinPath());
619 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_deleted);
622 if ( status < git_wc_status_normal)
624 if( ::PathFileExists(path+_T("\\.git")))
625 { // this is submodule
626 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": skip submodule %s\n"), (LPCTSTR)path);
627 return FALSE;
630 if (pThis->m_bRecursive)
632 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
633 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
634 if (status >= git_wc_status_normal || (CGitStatusCache::Instance().IsUnversionedAsModified() && status == git_wc_status_unversioned))
635 CGitStatusCache::Instance().AddFolderForCrawling(gitPath);
638 // Make sure we know about this child directory
639 // This initial status value is likely to be overwritten from below at some point
640 git_wc_status_kind s = GitStatus::GetMoreImportant(status2->text_status, status2->prop_status);
642 // folders must not be displayed as added or deleted only as modified
643 if (s == git_wc_status_deleted || s == git_wc_status_added)
644 s = git_wc_status_modified;
646 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(gitPath);
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[gitPath] = st;
654 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": call 1 Update dir %s %d\n"), gitPath.GetWinPath(), st);
656 else
658 // the child directory is not in the cache. Create a new entry for it in the cache which is
659 // initially 'unversioned'. But we added that directory to the crawling list above, which
660 // means the cache will be updated soon.
661 CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath);
663 AutoLocker lock(pThis->m_critSec);
664 pThis->m_childDirectories[gitPath] = s;
665 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": call 2 Update dir %s %d\n"), gitPath.GetWinPath(), s);
669 else /* gitpath is file*/
671 // Keep track of the most important status of all the files in this directory
672 // Don't include subdirectories in this figure, because they need to provide their
673 // own 'most important' value
674 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->text_status);
675 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->prop_status);
676 if ((status2->text_status == git_wc_status_unversioned) && (CGitStatusCache::Instance().IsUnversionedAsModified()))
678 // treat unversioned files as modified
679 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
680 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
685 pThis->AddEntry(gitPath, status2);
687 return FALSE;
690 bool
691 CCachedDirectory::IsOwnStatusValid() const
693 return m_ownStatus.HasBeenSet() && !m_ownStatus.HasExpired(GetTickCount64());
696 void CCachedDirectory::Invalidate()
698 m_ownStatus.Invalidate();
701 git_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()
703 // Combine our OWN folder status with the most important of our *FILES'* status.
704 git_wc_status_kind retVal = GitStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());
706 // folders can only be none, unversioned, normal, modified, and conflicted
707 if (retVal == git_wc_status_deleted || retVal == git_wc_status_added)
708 retVal = git_wc_status_modified;
710 // Now combine all our child-directorie's status
711 AutoLocker lock(m_critSec);
712 ChildDirStatus::const_iterator it;
713 for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)
715 retVal = GitStatus::GetMoreImportant(retVal, it->second);
718 return retVal;
721 // Update our composite status and deal with things if it's changed
722 void CCachedDirectory::UpdateCurrentStatus()
724 git_wc_status_kind newStatus = CalculateRecursiveStatus();
725 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": UpdateCurrentStatus %s new:%d old: %d\n"),
726 m_directoryPath.GetWinPath(),
727 newStatus, m_currentFullStatus);
729 if (newStatus != m_currentFullStatus && m_ownStatus.IsDirectory())
731 m_currentFullStatus = newStatus;
733 // Our status has changed - tell the shell
734 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": Dir %s, status change from %d to %d\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);
735 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
737 // And tell our parent, if we've got one...
738 // we tell our parent *always* about our status, even if it hasn't
739 // changed. This is to make sure that the parent has really our current
740 // status - the parent can decide itself if our status has changed
741 // or not.
742 CTGitPath parentPath = m_directoryPath.GetContainingDirectory();
743 if(!parentPath.IsEmpty())
745 // We have a parent
746 // just version controled directory need to cache.
747 CString root1, root2;
748 if (parentPath.HasAdminDir(&root1) && (CGitStatusCache::Instance().IsRecurseSubmodules() || m_directoryPath.HasAdminDir(&root2) && root1 == root2))
750 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath);
751 if (cachedDir)
752 cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);
757 // Receive a notification from a child that its status has changed
758 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath& childDir, git_wc_status_kind childStatus)
760 git_wc_status_kind currentStatus = git_wc_status_none;
762 AutoLocker lock(m_critSec);
763 currentStatus = m_childDirectories[childDir];
765 if ((currentStatus != childStatus)||(!IsOwnStatusValid()))
768 AutoLocker lock(m_critSec);
769 m_childDirectories[childDir] = childStatus;
771 UpdateCurrentStatus();
775 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)
777 // Don't return recursive status if we're unversioned ourselves.
778 if(bRecursive && m_ownStatus.IsDirectory() && m_ownStatus.GetEffectiveStatus() != git_wc_status_ignored)
780 CStatusCacheEntry recursiveStatus(m_ownStatus);
781 UpdateCurrentStatus();
782 recursiveStatus.ForceStatus(m_currentFullStatus);
783 return recursiveStatus;
785 else
787 return m_ownStatus;
791 void CCachedDirectory::RefreshStatus(bool bRecursive)
793 // Make sure that our own status is up-to-date
794 GetStatusForMember(m_directoryPath,bRecursive);
796 AutoLocker lock(m_critSec);
797 // We also need to check if all our file members have the right date on them
798 CacheEntryMap::iterator itMembers;
799 std::set<CTGitPath> refreshedpaths;
800 ULONGLONG now = GetTickCount64();
801 if (m_entryCache.empty())
802 return;
803 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
805 if (itMembers->first)
807 CTGitPath filePath(m_directoryPath);
808 filePath.AppendPathString(itMembers->first);
809 std::set<CTGitPath>::iterator refr_it;
810 if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&
811 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))
813 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))
815 lock.Unlock();
816 // We need to request this item as well
817 GetStatusForMember(filePath,bRecursive);
818 // GetStatusForMember now has recreated the m_entryCache map.
819 // So start the loop again, but add this path to the refreshed paths set
820 // to make sure we don't refresh this path again. This is to make sure
821 // that we don't end up in an endless loop.
822 lock.Lock();
823 refreshedpaths.insert(refr_it, filePath);
824 itMembers = m_entryCache.begin();
825 if (m_entryCache.empty())
826 return;
827 continue;
829 else if ((bRecursive)&&(itMembers->second.IsDirectory()))
831 // crawl all sub folders too! Otherwise a change deep inside the
832 // tree which has changed won't get propagated up the tree.
833 CGitStatusCache::Instance().AddFolderForCrawling(filePath);
840 void CCachedDirectory::RefreshMostImportant()
842 AutoLocker lock(m_critSec);
843 CacheEntryMap::iterator itMembers;
844 git_wc_status_kind newStatus = git_wc_status_unversioned;
845 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
847 newStatus = GitStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());
848 if (((itMembers->second.GetEffectiveStatus() == git_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == git_wc_status_none))
849 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
851 // treat unversioned files as modified
852 if (newStatus != git_wc_status_added)
853 newStatus = GitStatus::GetMoreImportant(newStatus, git_wc_status_modified);
856 if (newStatus != m_mostImportantFileStatus)
858 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": status change of path %s\n"), m_directoryPath.GetWinPath());
859 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
861 m_mostImportantFileStatus = newStatus;