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.
21 #include "CachedDirectory.h"
22 #include "GitAdminDir.h"
23 #include "GitStatusCache.h"
24 #include "GitStatus.h"
27 CCachedDirectory::CCachedDirectory(void)
28 : m_currentFullStatus(git_wc_status_none
)
29 , m_mostImportantFileStatus(git_wc_status_none
)
34 CCachedDirectory::~CCachedDirectory(void)
38 CCachedDirectory::CCachedDirectory(const CTGitPath
& directoryPath
)
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
);
65 if (fwrite((LPCTSTR
)key
, sizeof(TCHAR
), value
, pFile
)!=value
)
67 if (!entry
.second
.SaveToDisk(pFile
))
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
);
80 if (fwrite((LPCTSTR
)path
, sizeof(TCHAR
), value
, pFile
)!=value
)
82 git_wc_status_kind status
= entry
.second
;
83 WRITEVALUETOFILE(status
);
86 // WRITEVALUETOFILE(m_propsFileTime);
87 value
= m_directoryPath
.GetWinPathString().GetLength();
88 WRITEVALUETOFILE(value
);
91 if (fwrite(m_directoryPath
.GetWinPath(), sizeof(TCHAR
), value
, pFile
)!=value
)
94 if (!m_ownStatus
.SaveToDisk(pFile
))
96 WRITEVALUETOFILE(m_currentFullStatus
);
97 WRITEVALUETOFILE(m_mostImportantFileStatus
);
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
112 LOADVALUEFROMFILE(mapsize
);
113 for (int i
=0; i
<mapsize
; ++i
)
115 LOADVALUEFROMFILE(value
);
116 if (value
> MAX_PATH
)
121 if (fread(sKey
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
123 sKey
.ReleaseBuffer(0);
126 sKey
.ReleaseBuffer(value
);
127 CStatusCacheEntry entry
;
128 if (!entry
.LoadFromDisk(pFile
))
130 // only read non empty keys (just needed for transition from old TGit clients)
132 m_entryCache
[sKey
] = entry
;
135 LOADVALUEFROMFILE(mapsize
);
136 for (int i
=0; i
<mapsize
; ++i
)
138 LOADVALUEFROMFILE(value
);
139 if (value
> MAX_PATH
)
144 if (fread(sPath
.GetBuffer(value
), sizeof(TCHAR
), value
, pFile
)!=value
)
146 sPath
.ReleaseBuffer(0);
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
)
161 if (fread(sPath
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
163 sPath
.ReleaseBuffer(0);
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
))
176 LOADVALUEFROMFILE(m_currentFullStatus
);
177 LOADVALUEFROMFILE(m_mostImportantFileStatus
);
179 catch ( CAtlException
)
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
);
196 if (dirEntry
->IsOwnStatusValid())
197 return dirEntry
->GetOwnStatus(bRecursive
);
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
);
217 CGitStatusCache::Instance().AddFolderForCrawling(path
);
219 return CStatusCacheEntry();
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
)
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);
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
);
275 bool isDir
= path
.IsDirectory();
276 bool isIgnoreFileChanged
= pGitStatus
->CheckAndUpdateIgnoreFiles(sProjectRoot
, subpaths
, isDir
);
280 CCachedDirectory
* dirEntry
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
,
281 false); /* we needn't watch untracked directory*/
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
];
333 return itMap
->second
;
336 return CStatusCacheEntry();
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
);
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();
391 return GetStatusFromGit(path
, sProjectRoot
, bRequestForSelf
);
395 return GetStatusFromCache(path
, bRecursive
);
399 CStatusCacheEntry
CCachedDirectory::GetCacheStatusForMember(const CTGitPath
& path
)
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();
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
);
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
);
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
);
471 git_wc_status2_t status2
;
472 status2
.text_status
= status2
.prop_status
= CalculateRecursiveStatus();
473 m_ownStatus
.SetStatus(&status2
);
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
);
489 if ((childDir
->GetCurrentFullStatus() != git_wc_status_missing
) || !pGitStatus
|| (pGitStatus
->text_status
!= git_wc_status_unversioned
))
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
);
509 CCachedDirectory
* childDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
.GetContainingDirectory());
510 bool bNotified
= false;
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
)
522 if (entry_it
->second
.GetEffectiveStatus() > git_wc_status_none
&&
523 entry_it
->second
.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
)
533 entry_it
= childDir
->m_entryCache
.insert(entry_it
, std::make_pair(cachekey
, CStatusCacheEntry()));
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
;
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);
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
'\\');
562 CCachedDirectory::GetFullPathString(const CString
& cacheKey
)
564 CString
fullpath(m_directoryPath
.GetWinPathString());
566 fullpath
+= cacheKey
;
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());
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
);
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
);
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
))
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
);
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
);
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
);
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
732 CTGitPath parentPath
= m_directoryPath
.GetContainingDirectory();
733 if(!parentPath
.IsEmpty())
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
);
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
;
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())
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())))
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.
813 refreshedpaths
.insert(refr_it
, filePath
);
814 itMembers
= m_entryCache
.begin();
815 if (m_entryCache
.empty())
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
;