1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
4 // Copyright (C) 2008-2012 - 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 "SVNHelpers.h"
23 #include "GitStatusCache.h"
24 #include "GitStatus.h"
27 CCachedDirectory::CCachedDirectory(void)
29 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
33 CCachedDirectory::~CCachedDirectory(void)
37 CCachedDirectory::CCachedDirectory(const CTGitPath
& directoryPath
)
39 ATLASSERT(directoryPath
.IsDirectory() || !PathFileExists(directoryPath
.GetWinPath()));
41 m_directoryPath
= directoryPath
;
42 m_directoryPath
.GetGitPathString(); // make sure git path string is set
44 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
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 (CacheEntryMap::iterator I
= m_entryCache
.begin(); I
!= m_entryCache
.end(); ++I
)
60 const CString
& key
= I
->first
;
61 value
= key
.GetLength();
62 WRITEVALUETOFILE(value
);
65 if (fwrite((LPCTSTR
)key
, sizeof(TCHAR
), value
, pFile
)!=value
)
67 if (!I
->second
.SaveToDisk(pFile
))
71 value
= (int)m_childDirectories
.size();
72 WRITEVALUETOFILE(value
);
73 for (ChildDirStatus::iterator I
= m_childDirectories
.begin(); I
!= m_childDirectories
.end(); ++I
)
75 const CString
& path
= I
->first
.GetWinPathString();
76 value
= path
.GetLength();
77 WRITEVALUETOFILE(value
);
80 if (fwrite((LPCTSTR
)path
, sizeof(TCHAR
), value
, pFile
)!=value
)
82 git_wc_status_kind status
= I
->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
[CTGitPath(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] == _T('\\'))
169 sPath
.TrimRight(_T("\\"));
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
)
188 CStatusCacheEntry
CCachedDirectory::GetStatusFromCache(const CTGitPath
& path
, bool bRecursive
)
190 if(path
.IsDirectory())
192 // We don't have directory status in our cache
193 // Ask the directory if it knows its own status
194 CCachedDirectory
* dirEntry
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
);
197 if (dirEntry
->IsOwnStatusValid())
198 return dirEntry
->GetOwnStatus(bRecursive
);
201 /* cache have outof date, need crawl again*/
203 /*AutoLocker lock(dirEntry->m_critSec);
204 ChildDirStatus::const_iterator it;
205 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
207 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
210 CGitStatusCache::Instance().AddFolderForCrawling(path
);
212 /*Return old status during crawling*/
213 return dirEntry
->GetOwnStatus(bRecursive
);
218 CGitStatusCache::Instance().AddFolderForCrawling(path
);
220 return CStatusCacheEntry();
224 //All file ignored if under ignore directory
225 if (m_ownStatus
.GetEffectiveStatus() == git_wc_status_ignored
)
226 return CStatusCacheEntry(git_wc_status_ignored
);
227 if (m_ownStatus
.GetEffectiveStatus() == git_wc_status_unversioned
)
228 return CStatusCacheEntry(git_wc_status_unversioned
);
230 // Look up a file in our own cache
231 AutoLocker
lock(m_critSec
);
232 CString strCacheKey
= GetCacheKey(path
);
233 CacheEntryMap::iterator itMap
= m_entryCache
.find(strCacheKey
);
234 if(itMap
!= m_entryCache
.end())
236 // We've hit the cache - check for timeout
237 if(!itMap
->second
.HasExpired((long)GetTickCount()))
239 if(itMap
->second
.DoesFileTimeMatch(path
.GetLastWriteTime()))
241 if ((itMap
->second
.GetEffectiveStatus()!=git_wc_status_missing
)||(!PathFileExists(path
.GetWinPath())))
243 // Note: the filetime matches after a modified has been committed too.
244 // So in that case, we would return a wrong status (e.g. 'modified' instead
245 // of 'normal') here.
246 return itMap
->second
;
252 CGitStatusCache::Instance().AddFolderForCrawling(path
.GetContainingDirectory());
253 return CStatusCacheEntry();
258 CStatusCacheEntry
CCachedDirectory::GetStatusFromGit(const CTGitPath
&path
, CString sProjectRoot
)
260 CString subpaths
= path
.GetGitPathString();
261 if(subpaths
.GetLength() >= sProjectRoot
.GetLength())
263 if(subpaths
[sProjectRoot
.GetLength()] == _T('/'))
264 subpaths
=subpaths
.Right(subpaths
.GetLength() - sProjectRoot
.GetLength()-1);
266 subpaths
=subpaths
.Right(subpaths
.GetLength() - sProjectRoot
.GetLength());
269 GitStatus
*pGitStatus
= &CGitStatusCache::Instance().m_GitStatus
;
270 UNREFERENCED_PARAMETER(pGitStatus
);
274 pGitStatus
->GetHeadHash(sProjectRoot
,head
);
276 bool isVersion
=true;
277 pGitStatus
->IsUnderVersionControl(sProjectRoot
, subpaths
, path
.IsDirectory(), &isVersion
);
280 bool isIgnoreFileChanged
=false;
282 isIgnoreFileChanged
= pGitStatus
->IsGitReposChanged(sProjectRoot
, subpaths
, GIT_MODE_IGNORE
);
284 if( isIgnoreFileChanged
)
286 pGitStatus
->LoadIgnoreFile(sProjectRoot
, subpaths
);
289 if(path
.IsDirectory())
292 CCachedDirectory
* dirEntry
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
,
293 false); /* we needn't watch untracked directory*/
297 AutoLocker
lock(dirEntry
->m_critSec
);
299 git_wc_status_kind dirstatus
= dirEntry
->GetCurrentFullStatus() ;
300 if( dirstatus
== git_wc_status_none
|| dirstatus
>= git_wc_status_normal
|| isIgnoreFileChanged
)
301 {/* status have not initialized*/
302 git_wc_status2_t status2
;
303 bool isignore
= false;
304 pGitStatus
->IsIgnore(sProjectRoot
,subpaths
,&isignore
);
305 status2
.text_status
= status2
.prop_status
=
306 (isignore
? git_wc_status_ignored
:git_wc_status_unversioned
);
308 dirEntry
->m_ownStatus
.SetKind(git_node_dir
);
309 dirEntry
->m_ownStatus
.SetStatus(&status2
);
312 return dirEntry
->m_ownStatus
;
316 else /* path is file */
318 AutoLocker
lock(m_critSec
);
319 CString strCacheKey
= GetCacheKey(path
);
321 if (strCacheKey
.IsEmpty())
322 return CStatusCacheEntry();
324 CacheEntryMap::iterator itMap
= m_entryCache
.find(strCacheKey
);
325 if(itMap
== m_entryCache
.end() || isIgnoreFileChanged
)
327 git_wc_status2_t status2
;
328 bool isignore
= false;
329 pGitStatus
->IsIgnore(sProjectRoot
,subpaths
,&isignore
);
330 status2
.text_status
= status2
.prop_status
=
331 (isignore
? git_wc_status_ignored
:git_wc_status_unversioned
);
332 AddEntry(path
, &status2
);
333 return m_entryCache
[strCacheKey
];
337 return itMap
->second
;
340 return CStatusCacheEntry();
345 EnumFiles((CTGitPath
*)&path
, TRUE
);
346 UpdateCurrentStatus();
347 return CStatusCacheEntry(m_ownStatus
);
352 /// bFetch is true, fetch all status, call by crawl.
353 /// bFetch is false, get cache status, return quickly.
355 CStatusCacheEntry
CCachedDirectory::GetStatusForMember(const CTGitPath
& path
, bool bRecursive
, bool bFetch
/* = true */)
357 CString sProjectRoot
;
358 bool bIsVersionedPath
;
360 bool bRequestForSelf
= false;
361 if(path
.IsEquivalentToWithoutCase(m_directoryPath
))
363 bRequestForSelf
= true;
364 AutoLocker
lock(m_critSec
);
365 // HasAdminDir might modify m_directoryPath, so we need to do it synchronized
366 bIsVersionedPath
= m_directoryPath
.HasAdminDir(&sProjectRoot
);
369 bIsVersionedPath
= path
.HasAdminDir(&sProjectRoot
);
371 // In all most circumstances, we ask for the status of a member of this directory.
372 ATLASSERT(m_directoryPath
.IsEquivalentToWithoutCase(path
.GetContainingDirectory()) || bRequestForSelf
);
374 //If is not version control path
375 if( !bIsVersionedPath
)
377 ATLTRACE(_T("%s is not underversion control\n"), path
.GetWinPath());
378 return CStatusCacheEntry();
381 // We've not got this item in the cache - let's add it
382 // We never bother asking SVN for the status of just one file, always for its containing directory
384 if (g_GitAdminDir
.IsAdminDirPath(path
.GetWinPathString()))
386 // We're being asked for the status of an .git directory
387 // It's not worth asking for this
388 return CStatusCacheEntry();
394 return GetStatusFromGit(path
, sProjectRoot
);
398 return GetStatusFromCache(path
, bRecursive
);
402 CStatusCacheEntry
CCachedDirectory::GetCacheStatusForMember(const CTGitPath
& path
)
405 AutoLocker
lock(m_critSec
);
406 CacheEntryMap::iterator itMap
= m_entryCache
.find(GetCacheKey(path
));
407 if(itMap
!= m_entryCache
.end())
408 return itMap
->second
;
410 return CStatusCacheEntry();
413 int CCachedDirectory::EnumFiles(CTGitPath
*path
, bool IsFull
)
415 CString sProjectRoot
;
417 path
->HasAdminDir(&sProjectRoot
);
419 m_directoryPath
.HasAdminDir(&sProjectRoot
);
421 ATLTRACE(_T("EnumFiles %s\n"), path
->GetWinPath());
423 ATLASSERT( !m_directoryPath
.IsEmpty() );
429 s
=path
->GetWinPath();
431 s
=m_directoryPath
.GetDirectory().GetWinPathString();
433 if (s
.GetLength() > sProjectRoot
.GetLength())
435 // skip initial slash if necessary
436 if(s
[sProjectRoot
.GetLength()] == _T('\\'))
437 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() -1);
439 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() );
442 // strip "\" at the end, otherwise cache lookups for drives do not work correctly
443 sProjectRoot
.TrimRight(_T("\\"));
445 GitStatus
*pStatus
= &CGitStatusCache::Instance().m_GitStatus
;
446 UNREFERENCED_PARAMETER(pStatus
);
447 git_wc_status_kind status
= git_wc_status_none
;
449 if(!path
->IsDirectory())
451 bool assumeValid
= false;
452 bool skipWorktree
= false;
453 pStatus
->GetFileStatus(sProjectRoot
, sSubPath
, &status
, IsFull
, false, true, GetStatusCallback
, this, &assumeValid
, &skipWorktree
);
457 m_mostImportantFileStatus
= git_wc_status_none
;
458 pStatus
->EnumDirStatus(sProjectRoot
, sSubPath
, &status
, IsFull
, false, true, GetStatusCallback
,this);
460 // folders must not be displayed as added or deleted only as modified
461 if (status
== git_wc_status_deleted
|| status
== git_wc_status_added
)
462 status
= git_wc_status_modified
;
463 // this is necessary, even if the m_mostImportantFileStatus is set in GetStatusCallback,
464 // however, if it's missing, deleted subdirectories won't mark parents as modified
465 m_mostImportantFileStatus
= GitStatus::GetMoreImportant(m_mostImportantFileStatus
, status
);
467 if (sSubPath
.IsEmpty())
469 // request a shell notification for working trees which have not been examined before
470 if (m_currentFullStatus
== git_wc_status_none
)
472 m_currentFullStatus
= git_wc_status_normal
;
473 CGitStatusCache::Instance().UpdateShell(sProjectRoot
);
475 m_ownStatus
= git_wc_status_normal
;
477 // otherwise folders which contain at least one ignored item gets displayed as ignored
478 // or: folders which were displayed as conflicted never recover from that state
479 else if (m_ownStatus
.GetEffectiveStatus() == git_wc_status_ignored
|| m_ownStatus
.GetEffectiveStatus() == git_wc_status_conflicted
)
480 m_ownStatus
= git_wc_status_normal
;
486 CCachedDirectory::AddEntry(const CTGitPath
& path
, const git_wc_status2_t
* pGitStatus
, DWORD validuntil
/* = 0*/)
488 AutoLocker
lock(m_critSec
);
489 if(path
.IsDirectory())
491 CCachedDirectory
* childDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
);
494 if ((childDir
->GetCurrentFullStatus() != git_wc_status_missing
)||(pGitStatus
==NULL
)||(pGitStatus
->text_status
!= git_wc_status_unversioned
))
498 if(childDir
->GetCurrentFullStatus() != GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
))
500 CGitStatusCache::Instance().UpdateShell(path
);
501 //ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
502 childDir
->m_ownStatus
.SetKind(git_node_dir
);
503 childDir
->m_ownStatus
.SetStatus(pGitStatus
);
507 childDir
->m_ownStatus
.SetKind(git_node_dir
);
514 CCachedDirectory
* childDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
.GetContainingDirectory());
515 bool bNotified
= false;
520 AutoLocker
lock(childDir
->m_critSec
);
521 CString cachekey
= GetCacheKey(path
);
522 CacheEntryMap::iterator entry_it
= childDir
->m_entryCache
.lower_bound(cachekey
);
523 if (entry_it
!= childDir
->m_entryCache
.end() && entry_it
->first
== cachekey
)
527 if (entry_it
->second
.GetEffectiveStatus() > git_wc_status_none
&&
528 entry_it
->second
.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
)
538 entry_it
= childDir
->m_entryCache
.insert(entry_it
, std::make_pair(cachekey
, CStatusCacheEntry()));
542 entry_it
->second
= CStatusCacheEntry(pGitStatus
, path
.GetLastWriteTime(), path
.IsReadOnly(), validuntil
);
543 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
544 entry_it
->second
.SetKind(git_node_file
);
548 CGitStatusCache::Instance().UpdateShell(path
);
549 //ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
552 //ATLTRACE(_T("Path Entry Add %s %s %s %d\n"), path.GetWinPath(), cachekey, m_directoryPath.GetWinPath(), pGitStatus->text_status);
555 CCachedDirectory
* parent
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
.GetContainingDirectory());
559 if ((parent
->GetCurrentFullStatus() != git_wc_status_missing
)||(pGitStatus
==NULL
)||(pGitStatus
->text_status
!= git_wc_status_unversioned
))
563 if(parent
->GetCurrentFullStatus() < GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
))
565 CGitStatusCache::Instance().UpdateShell(parent
->m_directoryPath
);
566 //ATLTRACE(_T("shell update for %s\n"), parent->m_directoryPath.GetWinPathString());
567 parent
->m_ownStatus
.SetKind(git_node_dir
);
568 parent
->m_ownStatus
.SetStatus(pGitStatus
);
577 CCachedDirectory::GetCacheKey(const CTGitPath
& path
)
579 // All we put into the cache as a key is just the end portion of the pathname
580 // There's no point storing the path of the containing directory for every item
581 return path
.GetWinPathString().Mid(m_directoryPath
.GetWinPathString().GetLength()).MakeLower().TrimLeft(_T("\\"));
585 CCachedDirectory::GetFullPathString(const CString
& cacheKey
)
587 return m_directoryPath
.GetWinPathString() + _T("\\") + cacheKey
;
590 BOOL
CCachedDirectory::GetStatusCallback(const CString
& path
, git_wc_status_kind status
,bool isDir
, void *, bool assumeValid
, bool skipWorktree
)
592 git_wc_status2_t _status
;
593 git_wc_status2_t
*status2
= &_status
;
595 status2
->prop_status
= status2
->text_status
= status
;
596 status2
->assumeValid
= assumeValid
;
597 status2
->skipWorktree
= skipWorktree
;
601 CString lowcasepath
= path
;
602 lowcasepath
.MakeLower();
603 gitPath
.SetFromUnknown(lowcasepath
);
605 CCachedDirectory
*pThis
= CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath
.GetContainingDirectory());
613 { /*gitpath is directory*/
614 //if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
616 if (!gitPath
.Exists())
618 ATLTRACE(_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 ATLTRACE(_T("skip submodule %s\n"), 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
)
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 ATLTRACE(_T("call 1 Update dir %s %d\n"), gitPath
.GetWinPath(), st
);
658 AutoLocker
lock(pThis
->m_critSec
);
659 // the child directory is not in the cache. Create a new entry for it in the cache which is
660 // initially 'unversioned'. But we added that directory to the crawling list above, which
661 // means the cache will be updated soon.
662 CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath
);
664 pThis
->m_childDirectories
[gitPath
] = s
;
665 ATLTRACE(_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 if (status2
->text_status
== git_wc_status_deleted
|| status2
->text_status
== git_wc_status_added
)
676 // if just a file in a folder is deleted or added report back that the folder is modified and not deleted or added
677 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
681 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status2
->text_status
);
682 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status2
->prop_status
);
683 if ((status2
->text_status
== git_wc_status_unversioned
)
684 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
686 // treat unversioned files as modified
687 if (pThis
->m_mostImportantFileStatus
!= git_wc_status_added
)
688 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
694 pThis
->AddEntry(gitPath
, status2
);
700 CCachedDirectory::IsOwnStatusValid() const
702 return m_ownStatus
.HasBeenSet() &&
703 !m_ownStatus
.HasExpired(GetTickCount());
706 void CCachedDirectory::Invalidate()
708 m_ownStatus
.Invalidate();
711 git_wc_status_kind
CCachedDirectory::CalculateRecursiveStatus()
713 // Combine our OWN folder status with the most important of our *FILES'* status.
714 git_wc_status_kind retVal
= GitStatus::GetMoreImportant(m_mostImportantFileStatus
, m_ownStatus
.GetEffectiveStatus());
716 // Now combine all our child-directorie's status
717 AutoLocker
lock(m_critSec
);
718 ChildDirStatus::const_iterator it
;
719 for(it
= m_childDirectories
.begin(); it
!= m_childDirectories
.end(); ++it
)
721 retVal
= GitStatus::GetMoreImportant(retVal
, it
->second
);
727 // Update our composite status and deal with things if it's changed
728 void CCachedDirectory::UpdateCurrentStatus()
730 git_wc_status_kind newStatus
= CalculateRecursiveStatus();
731 ATLTRACE(_T("UpdateCurrentStatus %s new:%d old: %d\n"),
732 m_directoryPath
.GetWinPath(),
733 newStatus
, m_currentFullStatus
);
735 if ( this->m_ownStatus
.GetEffectiveStatus() < git_wc_status_normal
)
737 if (::PathFileExists(this->m_directoryPath
.GetWinPathString()+_T("\\.git")))
739 //project root must be normal status at least.
740 ATLTRACE(_T("force update project root directory as normal status\n"));
741 this->m_ownStatus
.ForceStatus(git_wc_status_normal
);
745 if ((newStatus
!= m_currentFullStatus
) && m_ownStatus
.IsVersioned())
747 if ((m_currentFullStatus
!= git_wc_status_none
)&&(m_ownStatus
.GetEffectiveStatus() != git_wc_status_missing
))
749 // Our status has changed - tell the shell
750 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath
.GetWinPath(), m_currentFullStatus
, newStatus
);
751 CGitStatusCache::Instance().UpdateShell(m_directoryPath
);
753 if (m_ownStatus
.GetEffectiveStatus() != git_wc_status_missing
)
754 m_currentFullStatus
= newStatus
;
756 m_currentFullStatus
= git_wc_status_missing
;
758 // And tell our parent, if we've got one...
759 // we tell our parent *always* about our status, even if it hasn't
760 // changed. This is to make sure that the parent has really our current
761 // status - the parent can decide itself if our status has changed
763 CTGitPath parentPath
= m_directoryPath
.GetContainingDirectory();
764 if(!parentPath
.IsEmpty())
767 // just version controled directory need to cache.
768 CString root1
, root2
;
769 if(parentPath
.HasAdminDir(&root1
) && m_directoryPath
.HasAdminDir(&root2
) && root1
== root2
)
771 CCachedDirectory
* cachedDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath
);
773 cachedDir
->UpdateChildDirectoryStatus(m_directoryPath
, m_currentFullStatus
);
779 // Receive a notification from a child that its status has changed
780 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath
& childDir
, git_wc_status_kind childStatus
)
782 git_wc_status_kind currentStatus
= git_wc_status_none
;
784 AutoLocker
lock(m_critSec
);
785 currentStatus
= m_childDirectories
[childDir
];
787 if ((currentStatus
!= childStatus
)||(!IsOwnStatusValid()))
790 AutoLocker
lock(m_critSec
);
791 m_childDirectories
[childDir
] = childStatus
;
793 UpdateCurrentStatus();
797 CStatusCacheEntry
CCachedDirectory::GetOwnStatus(bool bRecursive
)
799 // Don't return recursive status if we're unversioned ourselves.
800 if(bRecursive
&& m_ownStatus
.GetEffectiveStatus() > git_wc_status_unversioned
&& m_ownStatus
.GetEffectiveStatus() != git_wc_status_ignored
)
802 CStatusCacheEntry
recursiveStatus(m_ownStatus
);
803 UpdateCurrentStatus();
804 if (m_currentFullStatus
== git_wc_status_deleted
|| m_currentFullStatus
== git_wc_status_added
)
805 m_currentFullStatus
= git_wc_status_modified
;
806 recursiveStatus
.ForceStatus(m_currentFullStatus
);
807 return recursiveStatus
;
815 void CCachedDirectory::RefreshStatus(bool bRecursive
)
817 // Make sure that our own status is up-to-date
818 GetStatusForMember(m_directoryPath
,bRecursive
);
820 AutoLocker
lock(m_critSec
);
821 // We also need to check if all our file members have the right date on them
822 CacheEntryMap::iterator itMembers
;
823 std::set
<CTGitPath
> refreshedpaths
;
824 DWORD now
= GetTickCount();
825 if (m_entryCache
.empty())
827 for (itMembers
= m_entryCache
.begin(); itMembers
!= m_entryCache
.end(); ++itMembers
)
829 if (itMembers
->first
)
831 CTGitPath
filePath(m_directoryPath
);
832 filePath
.AppendPathString(itMembers
->first
);
833 std::set
<CTGitPath
>::iterator refr_it
;
834 if ((!filePath
.IsEquivalentToWithoutCase(m_directoryPath
))&&
835 (((refr_it
= refreshedpaths
.lower_bound(filePath
)) == refreshedpaths
.end()) || !filePath
.IsEquivalentToWithoutCase(*refr_it
)))
837 if ((itMembers
->second
.HasExpired(now
))||(!itMembers
->second
.DoesFileTimeMatch(filePath
.GetLastWriteTime())))
840 // We need to request this item as well
841 GetStatusForMember(filePath
,bRecursive
);
842 // GetStatusForMember now has recreated the m_entryCache map.
843 // So start the loop again, but add this path to the refreshed paths set
844 // to make sure we don't refresh this path again. This is to make sure
845 // that we don't end up in an endless loop.
847 refreshedpaths
.insert(refr_it
, filePath
);
848 itMembers
= m_entryCache
.begin();
849 if (m_entryCache
.empty())
853 else if ((bRecursive
)&&(itMembers
->second
.IsDirectory()))
855 // crawl all sub folders too! Otherwise a change deep inside the
856 // tree which has changed won't get propagated up the tree.
857 CGitStatusCache::Instance().AddFolderForCrawling(filePath
);
864 void CCachedDirectory::RefreshMostImportant()
866 AutoLocker
lock(m_critSec
);
867 CacheEntryMap::iterator itMembers
;
868 git_wc_status_kind newStatus
= m_ownStatus
.GetEffectiveStatus();
869 for (itMembers
= m_entryCache
.begin(); itMembers
!= m_entryCache
.end(); ++itMembers
)
871 newStatus
= GitStatus::GetMoreImportant(newStatus
, itMembers
->second
.GetEffectiveStatus());
872 if (((itMembers
->second
.GetEffectiveStatus() == git_wc_status_unversioned
)||(itMembers
->second
.GetEffectiveStatus() == git_wc_status_none
))
873 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
875 // treat unversioned files as modified
876 if (newStatus
!= git_wc_status_added
)
877 newStatus
= GitStatus::GetMoreImportant(newStatus
, git_wc_status_modified
);
880 if (newStatus
!= m_mostImportantFileStatus
)
882 ATLTRACE(_T("status change of path %s\n"), m_directoryPath
.GetWinPath());
883 CGitStatusCache::Instance().UpdateShell(m_directoryPath
);
885 m_mostImportantFileStatus
= newStatus
;