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.
21 #include "CachedDirectory.h"
22 #include "GitStatusCache.h"
23 #include "GitStatus.h"
26 CCachedDirectory::CCachedDirectory(void)
28 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
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
;
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
);
64 if (fwrite((LPCTSTR
)key
, sizeof(TCHAR
), value
, pFile
)!=value
)
66 if (!entry
.second
.SaveToDisk(pFile
))
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
);
79 if (fwrite((LPCTSTR
)path
, sizeof(TCHAR
), value
, pFile
)!=value
)
81 git_wc_status_kind status
= entry
.second
;
82 WRITEVALUETOFILE(status
);
85 // WRITEVALUETOFILE(m_propsFileTime);
86 value
= m_directoryPath
.GetWinPathString().GetLength();
87 WRITEVALUETOFILE(value
);
90 if (fwrite(m_directoryPath
.GetWinPath(), sizeof(TCHAR
), value
, pFile
)!=value
)
93 if (!m_ownStatus
.SaveToDisk(pFile
))
95 WRITEVALUETOFILE(m_currentFullStatus
);
96 WRITEVALUETOFILE(m_mostImportantFileStatus
);
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
111 LOADVALUEFROMFILE(mapsize
);
112 for (int i
=0; i
<mapsize
; ++i
)
114 LOADVALUEFROMFILE(value
);
115 if (value
> MAX_PATH
)
120 if (fread(sKey
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
122 sKey
.ReleaseBuffer(0);
125 sKey
.ReleaseBuffer(value
);
126 CStatusCacheEntry entry
;
127 if (!entry
.LoadFromDisk(pFile
))
129 // only read non empty keys (just needed for transition from old TGit clients)
131 m_entryCache
[sKey
] = entry
;
134 LOADVALUEFROMFILE(mapsize
);
135 for (int i
=0; i
<mapsize
; ++i
)
137 LOADVALUEFROMFILE(value
);
138 if (value
> MAX_PATH
)
143 if (fread(sPath
.GetBuffer(value
), sizeof(TCHAR
), value
, pFile
)!=value
)
145 sPath
.ReleaseBuffer(0);
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
)
160 if (fread(sPath
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
162 sPath
.ReleaseBuffer(0);
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
))
175 LOADVALUEFROMFILE(m_currentFullStatus
);
176 LOADVALUEFROMFILE(m_mostImportantFileStatus
);
178 catch ( CAtlException
)
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
);
195 if (dirEntry
->IsOwnStatusValid())
196 return dirEntry
->GetOwnStatus(bRecursive
);
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
);
216 CGitStatusCache::Instance().AddFolderForCrawling(path
);
218 return CStatusCacheEntry();
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);
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
);
273 bool isDir
= path
.IsDirectory();
274 bool isIgnoreFileChanged
= pGitStatus
->HasIgnoreFilesChanged(sProjectRoot
, subpaths
, isDir
);
276 if( isIgnoreFileChanged
)
278 pGitStatus
->LoadIgnoreFile(sProjectRoot
, subpaths
, isDir
);
283 CCachedDirectory
* dirEntry
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
,
284 false); /* we needn't watch untracked directory*/
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
];
338 return itMap
->second
;
341 return CStatusCacheEntry();
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
);
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();
396 return GetStatusFromGit(path
, sProjectRoot
);
400 return GetStatusFromCache(path
, bRecursive
);
404 CStatusCacheEntry
CCachedDirectory::GetCacheStatusForMember(const CTGitPath
& path
)
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() );
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);
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();
454 bool isSelf
= path
== m_directoryPath
;
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
);
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
);
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
);
495 git_wc_status2_t status2
;
496 status2
.text_status
= status2
.prop_status
= CalculateRecursiveStatus();
497 m_ownStatus
.SetStatus(&status2
);
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
);
513 if ((childDir
->GetCurrentFullStatus() != git_wc_status_missing
) || !pGitStatus
|| (pGitStatus
->text_status
!= git_wc_status_unversioned
))
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
);
533 CCachedDirectory
* childDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
.GetContainingDirectory());
534 bool bNotified
= false;
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
)
546 if (entry_it
->second
.GetEffectiveStatus() > git_wc_status_none
&&
547 entry_it
->second
.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
)
557 entry_it
= childDir
->m_entryCache
.insert(entry_it
, std::make_pair(cachekey
, CStatusCacheEntry()));
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
;
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);
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("\\"));
586 CCachedDirectory::GetFullPathString(const CString
& cacheKey
)
588 CString
fullpath(m_directoryPath
.GetWinPathString());
589 fullpath
+= _T('\\');
590 fullpath
+= cacheKey
;
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());
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
);
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
);
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
);
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
);
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
);
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
742 CTGitPath parentPath
= m_directoryPath
.GetContainingDirectory();
743 if(!parentPath
.IsEmpty())
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
);
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
;
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())
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())))
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.
823 refreshedpaths
.insert(refr_it
, filePath
);
824 itMembers
= m_entryCache
.begin();
825 if (m_entryCache
.empty())
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
;