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
;
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 (CacheEntryMap::iterator I
= m_entryCache
.begin(); I
!= m_entryCache
.end(); ++I
)
59 const CString
& key
= I
->first
;
60 value
= key
.GetLength();
61 WRITEVALUETOFILE(value
);
64 if (fwrite((LPCTSTR
)key
, sizeof(TCHAR
), value
, pFile
)!=value
)
66 if (!I
->second
.SaveToDisk(pFile
))
70 value
= (int)m_childDirectories
.size();
71 WRITEVALUETOFILE(value
);
72 for (ChildDirStatus::iterator I
= m_childDirectories
.begin(); I
!= m_childDirectories
.end(); ++I
)
74 const CString
& path
= I
->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
= I
->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 m_entryCache
[sKey
] = entry
;
132 LOADVALUEFROMFILE(mapsize
);
133 for (int i
=0; i
<mapsize
; ++i
)
135 LOADVALUEFROMFILE(value
);
136 if (value
> MAX_PATH
)
141 if (fread(sPath
.GetBuffer(value
), sizeof(TCHAR
), value
, pFile
)!=value
)
143 sPath
.ReleaseBuffer(0);
146 sPath
.ReleaseBuffer(value
);
147 git_wc_status_kind status
;
148 LOADVALUEFROMFILE(status
);
149 m_childDirectories
[CTGitPath(sPath
)] = status
;
152 LOADVALUEFROMFILE(value
);
153 if (value
> MAX_PATH
)
158 if (fread(sPath
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
160 sPath
.ReleaseBuffer(0);
163 sPath
.ReleaseBuffer(value
);
164 m_directoryPath
.SetFromWin(sPath
);
166 if (!m_ownStatus
.LoadFromDisk(pFile
))
169 LOADVALUEFROMFILE(m_currentFullStatus
);
170 LOADVALUEFROMFILE(m_mostImportantFileStatus
);
172 catch ( CAtlException
)
181 CStatusCacheEntry
CCachedDirectory::GetStatusFromCache(const CTGitPath
& path
, bool bRecursive
)
183 if(path
.IsDirectory())
185 // We don't have directory status in our cache
186 // Ask the directory if it knows its own status
187 CCachedDirectory
* dirEntry
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
);
190 if (dirEntry
->IsOwnStatusValid())
191 return dirEntry
->GetOwnStatus(bRecursive
);
194 /* cache have outof date, need crawl again*/
196 /*AutoLocker lock(dirEntry->m_critSec);
197 ChildDirStatus::const_iterator it;
198 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
200 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
203 CGitStatusCache::Instance().AddFolderForCrawling(path
);
205 /*Return old status during crawling*/
206 return dirEntry
->GetOwnStatus(bRecursive
);
211 CGitStatusCache::Instance().AddFolderForCrawling(path
);
213 return CStatusCacheEntry();
217 // Look up a file in our own cache
218 AutoLocker
lock(m_critSec
);
219 CString strCacheKey
= GetCacheKey(path
);
220 CacheEntryMap::iterator itMap
= m_entryCache
.find(strCacheKey
);
221 if(itMap
!= m_entryCache
.end())
223 // We've hit the cache - check for timeout
224 if(!itMap
->second
.HasExpired((long)GetTickCount()))
226 if(itMap
->second
.DoesFileTimeMatch(path
.GetLastWriteTime()))
228 if ((itMap
->second
.GetEffectiveStatus()!=git_wc_status_missing
)||(!PathFileExists(path
.GetWinPath())))
230 // Note: the filetime matches after a modified has been committed too.
231 // So in that case, we would return a wrong status (e.g. 'modified' instead
232 // of 'normal') here.
233 return itMap
->second
;
240 //All file ignored if under ignore directory
241 if( m_currentFullStatus
== git_wc_status_ignored
)
242 return CStatusCacheEntry(git_wc_status_ignored
);
245 CGitStatusCache::Instance().AddFolderForCrawling(path
);
246 return CStatusCacheEntry();
251 CStatusCacheEntry
CCachedDirectory::GetStatusFromGit(const CTGitPath
&path
, CString sProjectRoot
)
253 CString subpaths
= path
.GetGitPathString();
254 if(subpaths
.GetLength() >= sProjectRoot
.GetLength())
256 if(subpaths
[sProjectRoot
.GetLength()] == _T('/'))
257 subpaths
=subpaths
.Right(subpaths
.GetLength() - sProjectRoot
.GetLength()-1);
259 subpaths
=subpaths
.Right(subpaths
.GetLength() - sProjectRoot
.GetLength());
262 GitStatus
*pGitStatus
= &CGitStatusCache::Instance().m_GitStatus
;
266 pGitStatus
->GetHeadHash(sProjectRoot
,head
);
268 bool isVersion
=true;
269 pGitStatus
->IsUnderVersionControl(sProjectRoot
, subpaths
, path
.IsDirectory(), &isVersion
);
272 bool isIgnoreFileChanged
=false;
274 isIgnoreFileChanged
= pGitStatus
->IsGitReposChanged(sProjectRoot
, subpaths
, GIT_MODE_IGNORE
);
276 if( isIgnoreFileChanged
)
278 pGitStatus
->LoadIgnoreFile(sProjectRoot
, subpaths
);
281 if(path
.IsDirectory())
284 CCachedDirectory
* dirEntry
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
,
285 false); /* we needn't watch untracked directory*/
289 AutoLocker
lock(dirEntry
->m_critSec
);
291 git_wc_status_kind dirstatus
= dirEntry
->GetCurrentFullStatus() ;
292 if( dirstatus
== git_wc_status_none
|| dirstatus
>= git_wc_status_normal
|| isIgnoreFileChanged
)
293 {/* status have not initialized*/
294 git_wc_status2_t status2
;
295 bool isignore
= false;
296 pGitStatus
->IsIgnore(sProjectRoot
,subpaths
,&isignore
);
297 status2
.text_status
= status2
.prop_status
=
298 (isignore
? git_wc_status_ignored
:git_wc_status_unversioned
);
300 dirEntry
->m_ownStatus
.SetStatus(&status2
);
301 dirEntry
->m_ownStatus
.SetKind(git_node_dir
);
304 return dirEntry
->m_ownStatus
;
308 else /* path is file */
310 AutoLocker
lock(m_critSec
);
311 CString strCacheKey
= GetCacheKey(path
);
313 CacheEntryMap::iterator itMap
= m_entryCache
.find(strCacheKey
);
314 if(itMap
== m_entryCache
.end() || isIgnoreFileChanged
)
316 git_wc_status2_t status2
;
317 bool isignore
= false;
318 pGitStatus
->IsIgnore(sProjectRoot
,subpaths
,&isignore
);
319 status2
.text_status
= status2
.prop_status
=
320 (isignore
? git_wc_status_ignored
:git_wc_status_unversioned
);
321 AddEntry(path
, &status2
);
322 return m_entryCache
[strCacheKey
];
326 return itMap
->second
;
329 return CStatusCacheEntry();
334 EnumFiles((CTGitPath
*)&path
, TRUE
);
335 UpdateCurrentStatus();
336 return CStatusCacheEntry(m_ownStatus
);
341 /// bFetch is true, fetch all status, call by crawl.
342 /// bFetch is false, get cache status, return quickly.
344 CStatusCacheEntry
CCachedDirectory::GetStatusForMember(const CTGitPath
& path
, bool bRecursive
, bool bFetch
/* = true */)
347 bool bRequestForSelf
= false;
348 if(path
.IsEquivalentToWithoutCase(m_directoryPath
))
350 bRequestForSelf
= true;
352 //OutputDebugStringA("GetStatusForMember: ");OutputDebugStringW(path.GetWinPathString());OutputDebugStringA("\r\n");
353 // In all most circumstances, we ask for the status of a member of this directory.
354 ATLASSERT(m_directoryPath
.IsEquivalentToWithoutCase(path
.GetContainingDirectory()) || bRequestForSelf
);
356 CString sProjectRoot
;
357 const BOOL bIsVersionedPath
= path
.HasAdminDir(&sProjectRoot
);
359 //If is not version control path
360 if( !bIsVersionedPath
)
362 ATLTRACE(_T("%s is not underversion control\n"), path
.GetWinPath());
363 return CStatusCacheEntry();
366 // We've not got this item in the cache - let's add it
367 // We never bother asking SVN for the status of just one file, always for its containing directory
369 if (g_GitAdminDir
.IsAdminDirPath(path
.GetWinPathString()))
371 // We're being asked for the status of an .git directory
372 // It's not worth asking for this
373 return CStatusCacheEntry();
379 return GetStatusFromGit(path
, sProjectRoot
);
383 return GetStatusFromCache(path
, bRecursive
);
387 CStatusCacheEntry
CCachedDirectory::GetCacheStatusForMember(const CTGitPath
& path
)
390 AutoLocker
lock(m_critSec
);
391 CacheEntryMap::iterator itMap
= m_entryCache
.find(GetCacheKey(path
));
392 if(itMap
!= m_entryCache
.end())
393 return itMap
->second
;
395 return CStatusCacheEntry();
398 int CCachedDirectory::EnumFiles(CTGitPath
*path
, bool IsFull
)
400 CString sProjectRoot
;
402 path
->HasAdminDir(&sProjectRoot
);
404 m_directoryPath
.HasAdminDir(&sProjectRoot
);
406 ATLTRACE(_T("EnumFiles %s\n"), path
->GetWinPath());
408 ATLASSERT( !m_directoryPath
.IsEmpty() );
414 s
=path
->GetWinPath();
416 s
=m_directoryPath
.GetDirectory().GetWinPathString();
418 if (s
.GetLength() > sProjectRoot
.GetLength())
420 // skip initial slash if necessary
421 if(s
[sProjectRoot
.GetLength()] == _T('\\'))
422 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() -1);
424 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() );
427 GitStatus
*pStatus
= &CGitStatusCache::Instance().m_GitStatus
;
428 UNREFERENCED_PARAMETER(pStatus
);
429 git_wc_status_kind status
;
431 if(!path
->IsDirectory())
432 pStatus
->GetFileStatus(sProjectRoot
, sSubPath
, &status
, IsFull
, false,true, GetStatusCallback
,this);
435 m_mostImportantFileStatus
= git_wc_status_normal
;
436 pStatus
->EnumDirStatus(sProjectRoot
, sSubPath
, &status
, IsFull
, false, true, GetStatusCallback
,this);
438 m_ownStatus
= git_wc_status_normal
;
441 m_mostImportantFileStatus
= GitStatus::GetMoreImportant(m_mostImportantFileStatus
, status
);
446 CCachedDirectory::AddEntry(const CTGitPath
& path
, const git_wc_status2_t
* pGitStatus
, DWORD validuntil
/* = 0*/)
448 AutoLocker
lock(m_critSec
);
449 if(path
.IsDirectory())
451 CCachedDirectory
* childDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
);
454 if ((childDir
->GetCurrentFullStatus() != git_wc_status_missing
)||(pGitStatus
==NULL
)||(pGitStatus
->text_status
!= git_wc_status_unversioned
))
458 if(childDir
->GetCurrentFullStatus() != GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
))
460 CGitStatusCache::Instance().UpdateShell(path
);
461 //ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
462 childDir
->m_ownStatus
.SetStatus(pGitStatus
);
463 childDir
->m_ownStatus
.SetKind(git_node_dir
);
467 childDir
->m_ownStatus
.SetKind(git_node_dir
);
474 CCachedDirectory
* childDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
.GetContainingDirectory());
475 bool bNotified
= false;
480 CString cachekey
= GetCacheKey(path
);
481 CacheEntryMap::iterator entry_it
= childDir
->m_entryCache
.lower_bound(cachekey
);
482 if (entry_it
!= childDir
->m_entryCache
.end() && entry_it
->first
== cachekey
)
486 if (entry_it
->second
.GetEffectiveStatus() > git_wc_status_none
&&
487 entry_it
->second
.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
)
497 entry_it
= childDir
->m_entryCache
.insert(entry_it
, std::make_pair(cachekey
, CStatusCacheEntry()));
501 entry_it
->second
= CStatusCacheEntry(pGitStatus
, path
.GetLastWriteTime(), path
.IsReadOnly(), validuntil
);
502 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
503 entry_it
->second
.SetKind(git_node_file
);
507 CGitStatusCache::Instance().UpdateShell(path
);
508 //ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
511 //ATLTRACE(_T("Path Entry Add %s %s %s %d\n"), path.GetWinPath(), cachekey, m_directoryPath.GetWinPath(), pGitStatus->text_status);
514 CCachedDirectory
* parent
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
.GetContainingDirectory());
518 if ((parent
->GetCurrentFullStatus() != git_wc_status_missing
)||(pGitStatus
==NULL
)||(pGitStatus
->text_status
!= git_wc_status_unversioned
))
522 if(parent
->GetCurrentFullStatus() < GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
))
524 CGitStatusCache::Instance().UpdateShell(parent
->m_directoryPath
);
525 //ATLTRACE(_T("shell update for %s\n"), parent->m_directoryPath.GetWinPathString());
526 parent
->m_ownStatus
.SetStatus(pGitStatus
);
527 parent
->m_ownStatus
.SetKind(git_node_dir
);
536 CCachedDirectory::GetCacheKey(const CTGitPath
& path
)
538 // All we put into the cache as a key is just the end portion of the pathname
539 // There's no point storing the path of the containing directory for every item
540 return path
.GetWinPathString().Mid(m_directoryPath
.GetWinPathString().GetLength()).MakeLower();
544 CCachedDirectory::GetFullPathString(const CString
& cacheKey
)
546 return m_directoryPath
.GetWinPathString() + _T("\\") + cacheKey
;
549 BOOL
CCachedDirectory::GetStatusCallback(const CString
& path
, git_wc_status_kind status
,bool isDir
, void *pUserData
)
551 git_wc_status2_t _status
;
552 git_wc_status2_t
*status2
= &_status
;
554 status2
->prop_status
= status2
->text_status
= status
;
558 CString lowcasepath
= path
;
559 lowcasepath
.MakeLower();
560 gitPath
.SetFromUnknown(lowcasepath
);
562 CCachedDirectory
*pThis
= CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath
.GetContainingDirectory());
570 { /*gitpath is directory*/
571 //if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
573 if (!gitPath
.Exists())
575 ATLTRACE(_T("Miss dir %s \n"), gitPath
.GetWinPath());
576 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_deleted
);
579 if ( status
< git_wc_status_normal
)
581 if( ::PathFileExists(path
+_T("\\.git")))
582 { // this is submodule
583 ATLTRACE(_T("skip submodule %s\n"), path
);
587 if (pThis
->m_bRecursive
)
589 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
590 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
591 if(status
>= git_wc_status_normal
)
592 if(status
!= git_wc_status_missing
)
593 if(status
!= git_wc_status_deleted
)
594 CGitStatusCache::Instance().AddFolderForCrawling(gitPath
);
597 // Make sure we know about this child directory
598 // This initial status value is likely to be overwritten from below at some point
599 git_wc_status_kind s
= GitStatus::GetMoreImportant(status2
->text_status
, status2
->prop_status
);
600 CCachedDirectory
* cdir
= CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(gitPath
);
603 // This child directory is already in our cache!
604 // So ask this dir about its recursive status
605 git_wc_status_kind st
= GitStatus::GetMoreImportant(s
, cdir
->GetCurrentFullStatus());
606 AutoLocker
lock(pThis
->m_critSec
);
607 pThis
->m_childDirectories
[gitPath
] = st
;
608 ATLTRACE(_T("call 1 Update dir %s %d\n"), gitPath
.GetWinPath(), st
);
612 AutoLocker
lock(pThis
->m_critSec
);
613 // the child directory is not in the cache. Create a new entry for it in the cache which is
614 // initially 'unversioned'. But we added that directory to the crawling list above, which
615 // means the cache will be updated soon.
616 CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath
);
618 pThis
->m_childDirectories
[gitPath
] = s
;
619 ATLTRACE(_T("call 2 Update dir %s %d\n"), gitPath
.GetWinPath(), s
);
623 else /* gitpath is file*/
625 // Keep track of the most important status of all the files in this directory
626 // Don't include subdirectories in this figure, because they need to provide their
627 // own 'most important' value
628 if (status2
->text_status
== git_wc_status_deleted
|| status2
->text_status
== git_wc_status_added
)
630 // if just a file in a folder is deleted or added report back that the folder is modified and not deleted or added
631 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
635 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status2
->text_status
);
636 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status2
->prop_status
);
637 if (((status2
->text_status
== git_wc_status_unversioned
)||(status2
->text_status
== git_wc_status_none
))
638 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
640 // treat unversioned files as modified
641 if (pThis
->m_mostImportantFileStatus
!= git_wc_status_added
)
642 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
648 pThis
->AddEntry(gitPath
, status2
);
654 git_error_t
* CCachedDirectory::GetStatusCallback(void *baton
, const char *path
, git_wc_status2_t
*status
)
656 CCachedDirectory
* pThis
= (CCachedDirectory
*)baton
;
665 if ((status
->text_status
!= git_wc_status_none
)&&(status
->text_status
!= git_wc_status_missing
))
666 svnPath
.SetFromSVN(path
, (status
->entry
->kind
== svn_node_dir
));
668 svnPath
.SetFromSVN(path
);
670 if(svnPath
.IsDirectory())
672 if(!svnPath
.IsEquivalentToWithoutCase(pThis
->m_directoryPath
))
674 if (pThis
->m_bRecursive
)
676 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
677 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
680 // Make sure we know about this child directory
681 // This initial status value is likely to be overwritten from below at some point
682 git_wc_status_kind s
= GitStatus::GetMoreImportant(status
->text_status
, status
->prop_status
);
683 CCachedDirectory
* cdir
= CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath
);
686 // This child directory is already in our cache!
687 // So ask this dir about its recursive status
688 git_wc_status_kind st
= GitStatus::GetMoreImportant(s
, cdir
->GetCurrentFullStatus());
689 AutoLocker
lock(pThis
->m_critSec
);
690 pThis
->m_childDirectories
[svnPath
] = st
;
694 // the child directory is not in the cache. Create a new entry for it in the cache which is
695 // initially 'unversioned'. But we added that directory to the crawling list above, which
696 // means the cache will be updated soon.
697 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
698 AutoLocker
lock(pThis
->m_critSec
);
699 pThis
->m_childDirectories
[svnPath
] = s
;
705 // Keep track of the most important status of all the files in this directory
706 // Don't include subdirectories in this figure, because they need to provide their
707 // own 'most important' value
708 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status
->text_status
);
709 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status
->prop_status
);
710 if (((status
->text_status
== git_wc_status_unversioned
)||(status
->text_status
== git_wc_status_none
))
711 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
713 // treat unversioned files as modified
714 if (pThis
->m_mostImportantFileStatus
!= git_wc_status_added
)
715 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
721 svnPath
.SetFromSVN(path
);
722 // Subversion returns no 'entry' field for versioned folders if they're
723 // part of another working copy (nested layouts).
724 // So we have to make sure that such an 'unversioned' folder really
726 if (((status
->text_status
== git_wc_status_unversioned
)||(status
->text_status
== git_wc_status_missing
))&&(!svnPath
.IsEquivalentToWithoutCase(pThis
->m_directoryPath
))&&(svnPath
.IsDirectory()))
728 if (svnPath
.HasAdminDir())
730 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
731 // Mark the directory as 'versioned' (status 'normal' for now).
732 // This initial value will be overwritten from below some time later
734 AutoLocker
lock(pThis
->m_critSec
);
735 pThis
->m_childDirectories
[svnPath
] = git_wc_status_normal
;
737 // Make sure the entry is also in the cache
738 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
739 // also mark the status in the status object as normal
740 status
->text_status
= git_wc_status_normal
;
743 else if (status
->text_status
== git_wc_status_external
)
745 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
746 // Mark the directory as 'versioned' (status 'normal' for now).
747 // This initial value will be overwritten from below some time later
749 AutoLocker
lock(pThis
->m_critSec
);
750 pThis
->m_childDirectories
[svnPath
] = git_wc_status_normal
;
752 // we have added a directory to the child-directory list of this
753 // directory. We now must make sure that this directory also has
754 // an entry in the cache.
755 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
756 // also mark the status in the status object as normal
757 status
->text_status
= git_wc_status_normal
;
761 if (svnPath
.IsDirectory())
763 AutoLocker
lock(pThis
->m_critSec
);
764 pThis
->m_childDirectories
[svnPath
] = GitStatus::GetMoreImportant(status
->text_status
, status
->prop_status
);
766 else if ((CGitStatusCache::Instance().IsUnversionedAsModified())&&(status
->text_status
!= git_wc_status_missing
))
768 // make this unversioned item change the most important status of this
769 // folder to modified if it doesn't already have another status
770 if (pThis
->m_mostImportantFileStatus
!= git_wc_status_added
)
771 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
776 pThis
->AddEntry(svnPath
, status
);
783 CCachedDirectory::IsOwnStatusValid() const
785 return m_ownStatus
.HasBeenSet() &&
786 !m_ownStatus
.HasExpired(GetTickCount());
789 void CCachedDirectory::Invalidate()
791 m_ownStatus
.Invalidate();
794 git_wc_status_kind
CCachedDirectory::CalculateRecursiveStatus()
796 // Combine our OWN folder status with the most important of our *FILES'* status.
797 git_wc_status_kind retVal
= GitStatus::GetMoreImportant(m_mostImportantFileStatus
, m_ownStatus
.GetEffectiveStatus());
799 // NOTE: TSVN marks dir as modified if it contains added/deleted/missing files, but we prefer the most important
800 // status to propagate upward in its original state
801 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
803 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
804 retVal = git_wc_status_modified;
807 // Now combine all our child-directorie's status
809 AutoLocker
lock(m_critSec
);
810 ChildDirStatus::const_iterator it
;
811 for(it
= m_childDirectories
.begin(); it
!= m_childDirectories
.end(); ++it
)
813 retVal
= GitStatus::GetMoreImportant(retVal
, it
->second
);
814 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
816 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
817 retVal = git_wc_status_modified;
824 // Update our composite status and deal with things if it's changed
825 void CCachedDirectory::UpdateCurrentStatus()
827 git_wc_status_kind newStatus
= CalculateRecursiveStatus();
828 ATLTRACE(_T("UpdateCurrentStatus %s new:%d old: %d\n"),
829 m_directoryPath
.GetWinPath(),
830 newStatus
, m_currentFullStatus
);
832 if ( this->m_ownStatus
.GetEffectiveStatus() < git_wc_status_normal
)
834 if (::PathFileExists(this->m_directoryPath
.GetWinPathString()+_T("\\.git")))
836 //project root must be normal status at least.
837 ATLTRACE(_T("force update project root directory as normal status\n"));
838 this->m_ownStatus
.ForceStatus(git_wc_status_normal
);
842 if ((newStatus
!= m_currentFullStatus
) && m_ownStatus
.IsVersioned())
844 if ((m_currentFullStatus
!= git_wc_status_none
)&&(m_ownStatus
.GetEffectiveStatus() != git_wc_status_missing
))
846 // Our status has changed - tell the shell
847 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath
.GetWinPath(), m_currentFullStatus
, newStatus
);
848 CGitStatusCache::Instance().UpdateShell(m_directoryPath
);
850 if (m_ownStatus
.GetEffectiveStatus() != git_wc_status_missing
)
851 m_currentFullStatus
= newStatus
;
853 m_currentFullStatus
= git_wc_status_missing
;
855 // And tell our parent, if we've got one...
856 // we tell our parent *always* about our status, even if it hasn't
857 // changed. This is to make sure that the parent has really our current
858 // status - the parent can decide itself if our status has changed
860 CTGitPath parentPath
= m_directoryPath
.GetContainingDirectory();
861 if(!parentPath
.IsEmpty())
864 // just version controled directory need to cache.
865 CString root1
, root2
;
866 if(parentPath
.HasAdminDir(&root1
) && m_directoryPath
.HasAdminDir(&root2
) && root1
== root2
)
868 CCachedDirectory
* cachedDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath
);
870 cachedDir
->UpdateChildDirectoryStatus(m_directoryPath
, m_currentFullStatus
);
876 // Receive a notification from a child that its status has changed
877 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath
& childDir
, git_wc_status_kind childStatus
)
879 git_wc_status_kind currentStatus
= git_wc_status_none
;
881 AutoLocker
lock(m_critSec
);
882 currentStatus
= m_childDirectories
[childDir
];
884 if ((currentStatus
!= childStatus
)||(!IsOwnStatusValid()))
887 AutoLocker
lock(m_critSec
);
888 m_childDirectories
[childDir
] = childStatus
;
890 UpdateCurrentStatus();
894 CStatusCacheEntry
CCachedDirectory::GetOwnStatus(bool bRecursive
)
896 // Don't return recursive status if we're unversioned ourselves.
897 if(bRecursive
&& m_ownStatus
.GetEffectiveStatus() > git_wc_status_unversioned
)
899 CStatusCacheEntry
recursiveStatus(m_ownStatus
);
900 UpdateCurrentStatus();
901 recursiveStatus
.ForceStatus(m_currentFullStatus
);
902 return recursiveStatus
;
910 void CCachedDirectory::RefreshStatus(bool bRecursive
)
912 // Make sure that our own status is up-to-date
913 GetStatusForMember(m_directoryPath
,bRecursive
);
915 AutoLocker
lock(m_critSec
);
916 // We also need to check if all our file members have the right date on them
917 CacheEntryMap::iterator itMembers
;
918 std::set
<CTGitPath
> refreshedpaths
;
919 DWORD now
= GetTickCount();
920 if (m_entryCache
.size() == 0)
922 for (itMembers
= m_entryCache
.begin(); itMembers
!= m_entryCache
.end(); ++itMembers
)
924 if (itMembers
->first
)
926 CTGitPath
filePath(m_directoryPath
);
927 filePath
.AppendPathString(itMembers
->first
);
928 std::set
<CTGitPath
>::iterator refr_it
;
929 if ((!filePath
.IsEquivalentToWithoutCase(m_directoryPath
))&&
930 (((refr_it
= refreshedpaths
.lower_bound(filePath
)) == refreshedpaths
.end()) || !filePath
.IsEquivalentToWithoutCase(*refr_it
)))
932 if ((itMembers
->second
.HasExpired(now
))||(!itMembers
->second
.DoesFileTimeMatch(filePath
.GetLastWriteTime())))
935 // We need to request this item as well
936 GetStatusForMember(filePath
,bRecursive
);
937 // GetStatusForMember now has recreated the m_entryCache map.
938 // So start the loop again, but add this path to the refreshed paths set
939 // to make sure we don't refresh this path again. This is to make sure
940 // that we don't end up in an endless loop.
942 refreshedpaths
.insert(refr_it
, filePath
);
943 itMembers
= m_entryCache
.begin();
944 if (m_entryCache
.size()==0)
948 else if ((bRecursive
)&&(itMembers
->second
.IsDirectory()))
950 // crawl all sub folders too! Otherwise a change deep inside the
951 // tree which has changed won't get propagated up the tree.
952 CGitStatusCache::Instance().AddFolderForCrawling(filePath
);
959 void CCachedDirectory::RefreshMostImportant()
961 CacheEntryMap::iterator itMembers
;
962 git_wc_status_kind newStatus
= m_ownStatus
.GetEffectiveStatus();
963 for (itMembers
= m_entryCache
.begin(); itMembers
!= m_entryCache
.end(); ++itMembers
)
965 newStatus
= GitStatus::GetMoreImportant(newStatus
, itMembers
->second
.GetEffectiveStatus());
966 if (((itMembers
->second
.GetEffectiveStatus() == git_wc_status_unversioned
)||(itMembers
->second
.GetEffectiveStatus() == git_wc_status_none
))
967 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
969 // treat unversioned files as modified
970 if (newStatus
!= git_wc_status_added
)
971 newStatus
= GitStatus::GetMoreImportant(newStatus
, git_wc_status_modified
);
974 if (newStatus
!= m_mostImportantFileStatus
)
976 ATLTRACE(_T("status change of path %s\n"), m_directoryPath
.GetWinPath());
977 CGitStatusCache::Instance().UpdateShell(m_directoryPath
);
979 m_mostImportantFileStatus
= newStatus
;