1 // TortoiseSVN - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include ".\cacheddirectory.h"
21 //#include "SVNHelpers.h"
22 #include "GitStatusCache.h"
23 #include "GitStatus.h"
26 CCachedDirectory::CCachedDirectory(void)
29 // m_propsFileTime = 0;
30 m_currentStatusFetchingPathTicks
= 0;
31 m_bCurrentFullStatusValid
= false;
32 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
36 CCachedDirectory::~CCachedDirectory(void)
40 CCachedDirectory::CCachedDirectory(const CTGitPath
& directoryPath
)
42 ATLASSERT(directoryPath
.IsDirectory() || !PathFileExists(directoryPath
.GetWinPath()));
44 m_directoryPath
= directoryPath
;
46 // m_propsFileTime = 0;
47 m_currentStatusFetchingPathTicks
= 0;
48 m_bCurrentFullStatusValid
= false;
49 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
53 BOOL
CCachedDirectory::SaveToDisk(FILE * pFile
)
55 AutoLocker
lock(m_critSec
);
56 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;
58 unsigned int value
= 1;
59 WRITEVALUETOFILE(value
); // 'version' of this save-format
60 value
= (int)m_entryCache
.size();
61 WRITEVALUETOFILE(value
); // size of the cache map
62 // now iterate through the maps and save every entry.
63 for (CacheEntryMap::iterator I
= m_entryCache
.begin(); I
!= m_entryCache
.end(); ++I
)
65 const CString
& key
= I
->first
;
66 value
= key
.GetLength();
67 WRITEVALUETOFILE(value
);
70 if (fwrite((LPCTSTR
)key
, sizeof(TCHAR
), value
, pFile
)!=value
)
72 if (!I
->second
.SaveToDisk(pFile
))
76 value
= (int)m_childDirectories
.size();
77 WRITEVALUETOFILE(value
);
78 for (ChildDirStatus::iterator I
= m_childDirectories
.begin(); I
!= m_childDirectories
.end(); ++I
)
80 const CString
& path
= I
->first
.GetWinPathString();
81 value
= path
.GetLength();
82 WRITEVALUETOFILE(value
);
85 if (fwrite((LPCTSTR
)path
, sizeof(TCHAR
), value
, pFile
)!=value
)
87 git_wc_status_kind status
= I
->second
;
88 WRITEVALUETOFILE(status
);
91 WRITEVALUETOFILE(m_indexFileTime
);
92 // WRITEVALUETOFILE(m_propsFileTime);
93 value
= m_directoryPath
.GetWinPathString().GetLength();
94 WRITEVALUETOFILE(value
);
97 if (fwrite(m_directoryPath
.GetWinPath(), sizeof(TCHAR
), value
, pFile
)!=value
)
100 if (!m_ownStatus
.SaveToDisk(pFile
))
102 WRITEVALUETOFILE(m_currentFullStatus
);
103 WRITEVALUETOFILE(m_mostImportantFileStatus
);
107 BOOL
CCachedDirectory::LoadFromDisk(FILE * pFile
)
109 AutoLocker
lock(m_critSec
);
110 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
113 unsigned int value
= 0;
114 LOADVALUEFROMFILE(value
);
116 return false; // not the correct version
118 LOADVALUEFROMFILE(mapsize
);
119 for (int i
=0; i
<mapsize
; ++i
)
121 LOADVALUEFROMFILE(value
);
122 if (value
> MAX_PATH
)
127 if (fread(sKey
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
129 sKey
.ReleaseBuffer(0);
132 sKey
.ReleaseBuffer(value
);
133 CStatusCacheEntry entry
;
134 if (!entry
.LoadFromDisk(pFile
))
136 m_entryCache
[sKey
] = entry
;
139 LOADVALUEFROMFILE(mapsize
);
140 for (int i
=0; i
<mapsize
; ++i
)
142 LOADVALUEFROMFILE(value
);
143 if (value
> MAX_PATH
)
148 if (fread(sPath
.GetBuffer(value
), sizeof(TCHAR
), value
, pFile
)!=value
)
150 sPath
.ReleaseBuffer(0);
153 sPath
.ReleaseBuffer(value
);
154 git_wc_status_kind status
;
155 LOADVALUEFROMFILE(status
);
156 m_childDirectories
[CTGitPath(sPath
)] = status
;
159 LOADVALUEFROMFILE(m_indexFileTime
);
160 // LOADVALUEFROMFILE(m_propsFileTime);
161 LOADVALUEFROMFILE(value
);
162 if (value
> MAX_PATH
)
167 if (fread(sPath
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
169 sPath
.ReleaseBuffer(0);
172 sPath
.ReleaseBuffer(value
);
173 m_directoryPath
.SetFromWin(sPath
);
175 if (!m_ownStatus
.LoadFromDisk(pFile
))
178 LOADVALUEFROMFILE(m_currentFullStatus
);
179 LOADVALUEFROMFILE(m_mostImportantFileStatus
);
181 catch ( CAtlException
)
189 CStatusCacheEntry
CCachedDirectory::GetStatusForMember(const CTGitPath
& path
, bool bRecursive
, bool bFetch
/* = true */)
192 bool bThisDirectoryIsUnversioned
= false;
193 bool bRequestForSelf
= false;
194 if(path
.IsEquivalentToWithoutCase(m_directoryPath
))
196 bRequestForSelf
= true;
198 //OutputDebugStringA("GetStatusForMember: ");OutputDebugStringW(path.GetWinPathString());OutputDebugStringA("\r\n");
199 // In all most circumstances, we ask for the status of a member of this directory.
200 ATLASSERT(m_directoryPath
.IsEquivalentToWithoutCase(path
.GetContainingDirectory()) || bRequestForSelf
);
202 CString sProjectRoot
;
203 const BOOL bIsVersionedPath
= m_directoryPath
.HasAdminDir(&sProjectRoot
);
205 // Check if the index file has been changed
206 CTGitPath
indexFilePath(bIsVersionedPath
? sProjectRoot
: m_directoryPath
);
207 // CTGitPath propsDirPath(m_directoryPath);
208 if (g_GitAdminDir
.IsVSNETHackActive())
210 indexFilePath
.AppendPathString(g_GitAdminDir
.GetVSNETAdminDirName() + _T("\\index"));
211 // propsDirPath.AppendPathString(g_GitAdminDir.GetVSNETAdminDirName() + _T("\\dir-props"));
215 indexFilePath
.AppendPathString(g_GitAdminDir
.GetAdminDirName() + _T("\\index"));
216 // propsDirPath.AppendPathString(g_GitAdminDir.GetAdminDirName() + _T("\\dir-props"));
218 if ( (m_indexFileTime
== indexFilePath
.GetLastWriteTime()) /*&& ((indexFilePath.GetLastWriteTime() == 0) || (m_propsFileTime == propsDirPath.GetLastWriteTime()))*/ )
220 // m_indexFileTime = indexFilePath.GetLastWriteTime();
221 // if (m_indexFileTime)
222 // m_propsFileTime = propsDirPath.GetLastWriteTime();
224 //if(m_indexFileTime == 0)
225 // a newly created project (without commits) has no index file but we still want it to count as versioned
226 if(m_indexFileTime
== 0 && !bIsVersionedPath
)
228 // We are a folder which is not in a working copy
229 bThisDirectoryIsUnversioned
= true;
230 m_ownStatus
.SetStatus(NULL
);
232 // If a user removes the .git directory, we get here with m_entryCache
233 // not being empty, but still us being unversioned
234 if (!m_entryCache
.empty())
236 m_entryCache
.clear();
238 ATLASSERT(m_entryCache
.empty());
240 // However, a member *DIRECTORY* might be the top of WC
241 // so we need to ask them to get their own status
242 if(!path
.IsDirectory())
244 if ((PathFileExists(path
.GetWinPath()))||(bRequestForSelf
))
245 return CStatusCacheEntry();
246 // the entry doesn't exist anymore!
247 // but we can't remove it from the cache here:
248 // the GetStatusForMember() method is called only with a read
249 // lock and not a write lock!
250 // So mark it for crawling, and let the crawler remove it
252 CGitStatusCache::Instance().AddFolderForCrawling(path
.GetContainingDirectory());
254 return CStatusCacheEntry();
258 // If we're in the special case of a directory being asked for its own status
259 // and this directory is unversioned, then we should just return that here
261 return CStatusCacheEntry();
265 if(path
.IsDirectory())
267 // We don't have directory status in our cache
268 // Ask the directory if it knows its own status
269 CCachedDirectory
* dirEntry
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
);
270 if ((dirEntry
)&&(dirEntry
->IsOwnStatusValid()))
272 // To keep recursive status up to date, we'll request that children are all crawled again
273 // This will be very quick if nothings changed, because it will all be cache hits
276 AutoLocker
lock(dirEntry
->m_critSec
);
277 ChildDirStatus::const_iterator it
;
278 for(it
= dirEntry
->m_childDirectories
.begin(); it
!= dirEntry
->m_childDirectories
.end(); ++it
)
280 CGitStatusCache::Instance().AddFolderForCrawling(it
->first
);
283 return dirEntry
->GetOwnStatus(bRecursive
);
289 // if we currently are fetching the status of the directory
290 // we want the status for, we just return an empty entry here
291 // and don't wait for that fetching to finish.
292 // That's because fetching the status can take a *really* long
293 // time (e.g. if a commit is also in progress on that same
294 // directory), and we don't want to make the explorer appear
296 AutoLocker
pathlock(m_critSecPath
);
297 if ((!bFetch
)&&(!m_currentStatusFetchingPath
.IsEmpty()))
299 if ((m_currentStatusFetchingPath
.IsAncestorOf(path
))&&((m_currentStatusFetchingPathTicks
+ 1000)<GetTickCount()))
301 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path
.GetWinPath());
302 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
303 return CStatusCacheEntry();
307 // Look up a file in our own cache
308 AutoLocker
lock(m_critSec
);
309 strCacheKey
= GetCacheKey(path
);
310 CacheEntryMap::iterator itMap
= m_entryCache
.find(strCacheKey
);
311 if(itMap
!= m_entryCache
.end())
313 // We've hit the cache - check for timeout
314 if(!itMap
->second
.HasExpired((long)GetTickCount()))
316 if(itMap
->second
.DoesFileTimeMatch(path
.GetLastWriteTime()))
318 if ((itMap
->second
.GetEffectiveStatus()!=git_wc_status_missing
)||(!PathFileExists(path
.GetWinPath())))
320 // Note: the filetime matches after a modified has been committed too.
321 // So in that case, we would return a wrong status (e.g. 'modified' instead
322 // of 'normal') here.
323 return itMap
->second
;
332 AutoLocker
pathlock(m_critSecPath
);
333 if ((!bFetch
)&&(!m_currentStatusFetchingPath
.IsEmpty()))
335 if ((m_currentStatusFetchingPath
.IsAncestorOf(path
))&&((m_currentStatusFetchingPathTicks
+ 1000)<GetTickCount()))
337 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path
.GetWinPath());
338 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
339 return CStatusCacheEntry();
342 // if we're fetching the status for the explorer,
343 // we don't refresh the status but use the one
344 // we already have (to save time and make the explorer
345 // more responsive in stress conditions).
346 // We leave the refreshing to the crawler.
347 if ((!bFetch
)&&(m_indexFileTime
))
349 CGitStatusCache::Instance().AddFolderForCrawling(path
.GetDirectory());
350 return CStatusCacheEntry();
352 AutoLocker
lock(m_critSec
);
353 m_indexFileTime
= indexFilePath
.GetLastWriteTime();
354 // m_propsFileTime = propsDirPath.GetLastWriteTime();
355 m_entryCache
.clear();
356 strCacheKey
= GetCacheKey(path
);
359 // svn_opt_revision_t revision;
360 // revision.kind = svn_opt_revision_unspecified;
362 // We've not got this item in the cache - let's add it
363 // We never bother asking SVN for the status of just one file, always for its containing directory
365 if (g_GitAdminDir
.IsAdminDirPath(path
.GetWinPathString()))
367 // We're being asked for the status of an .git directory
368 // It's not worth asking for this
369 return CStatusCacheEntry();
374 AutoLocker
pathlock(m_critSecPath
);
375 if ((!bFetch
)&&(!m_currentStatusFetchingPath
.IsEmpty()))
377 if ((m_currentStatusFetchingPath
.IsAncestorOf(path
))&&((m_currentStatusFetchingPathTicks
+ 1000)<GetTickCount()))
379 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path
.GetWinPath());
380 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
381 return CStatusCacheEntry();
385 // SVNPool subPool(CGitStatusCache::Instance().m_svnHelp.Pool());
387 AutoLocker
lock(m_critSec
);
388 m_mostImportantFileStatus
= git_wc_status_none
;
389 m_childDirectories
.clear();
390 m_entryCache
.clear();
391 m_ownStatus
.SetStatus(NULL
);
392 m_bRecursive
= bRecursive
;
394 if(!bThisDirectoryIsUnversioned
)
397 AutoLocker
pathlock(m_critSecPath
);
398 m_currentStatusFetchingPath
= m_directoryPath
;
399 m_currentStatusFetchingPathTicks
= GetTickCount();
401 ATLTRACE(_T("git_enum_files for '%s' (req %s)\n"), m_directoryPath
.GetWinPath(), path
.GetWinPath());
403 CString sProjectRoot
;
404 m_directoryPath
.HasAdminDir(&sProjectRoot
);
405 ATLASSERT( !m_directoryPath
.IsEmpty() );
407 LPCTSTR lpszSubPath
= NULL
;
409 CString s
= m_directoryPath
.GetDirectory().GetWinPathString();
410 if (s
.GetLength() > sProjectRoot
.GetLength())
412 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
413 lpszSubPath
= sSubPath
;
414 // skip initial slash if necessary
415 if (*lpszSubPath
== _T('\\'))
418 //MessageBoxA(NULL, CStringA(sProjectRoot), sSubPath, MB_OK);
419 //OutputDebugStringA("###");OutputDebugStringW(sProjectRoot);OutputDebugStringA(" - ");OutputDebugStringA(sSubPath);OutputDebugStringA("\r\n");
420 BOOL pErr
= !wgEnumFiles(sProjectRoot
, lpszSubPath
, WGEFF_NoRecurse
|WGEFF_FullPath
, &GetStatusCallback
, this);
422 /*git_error_t* pErr = svn_client_status4 (
424 m_directoryPath.GetSVNApiPath(subPool),
428 svn_depth_immediates,
432 FALSE, //ignore externals
434 CGitStatusCache::Instance().m_svnHelp.ClientContext(),
438 AutoLocker
pathlock(m_critSecPath
);
439 m_currentStatusFetchingPath
.Reset();
441 ATLTRACE(_T("git_enum_files finished for '%s'\n"), m_directoryPath
.GetWinPath(), path
.GetWinPath());
445 // The most likely error on a folder is that it's not part of a WC
446 // In most circumstances, this will have been caught earlier,
447 // but in some situations, we'll get this error.
448 // If we allow ourselves to fall on through, then folders will be asked
449 // for their own status, and will set themselves as unversioned, for the
450 // benefit of future requests
451 // ATLTRACE("git_enum_files err: '%s'\n", pErr->message);
452 // svn_error_clear(pErr);
453 // No assert here! Since we _can_ get here, an assertion is not an option!
454 // Reasons to get here:
455 // - renaming a folder with many sub folders --> results in "not a working copy" if the revert
456 // happens between our checks and the svn_client_status() call.
457 // - reverting a move/copy --> results in "not a working copy" (as above)
458 if (!m_directoryPath
.HasAdminDir())
460 m_currentFullStatus
= m_mostImportantFileStatus
= git_wc_status_none
;
461 return CStatusCacheEntry();
465 ATLTRACE("git_enum_files error, assume none status\n");
466 // Since we only assume a none status here due to svn_client_status()
467 // returning an error, make sure that this status times out soon.
468 CGitStatusCache::Instance().m_folderCrawler
.BlockPath(m_directoryPath
, 2000);
469 CGitStatusCache::Instance().AddFolderForCrawling(m_directoryPath
);
470 return CStatusCacheEntry();
476 ATLTRACE("Skipped git status for unversioned folder\n");
479 // Now that we've refreshed our SVN status, we can see if it's
480 // changed the 'most important' status value for this directory.
481 // If it has, then we should tell our parent
482 UpdateCurrentStatus();
484 if (path
.IsDirectory())
486 CCachedDirectory
* dirEntry
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
);
487 if ((dirEntry
)&&(dirEntry
->IsOwnStatusValid()))
489 CGitStatusCache::Instance().AddFolderForCrawling(path
);
490 return dirEntry
->GetOwnStatus(bRecursive
);
493 // If the status *still* isn't valid here, it means that
494 // the current directory is unversioned, and we shall need to ask its children for info about themselves
496 return dirEntry
->GetStatusForMember(path
,bRecursive
);
497 CGitStatusCache::Instance().AddFolderForCrawling(path
);
498 return CStatusCacheEntry();
502 CacheEntryMap::iterator itMap
= m_entryCache
.find(strCacheKey
);
503 if(itMap
!= m_entryCache
.end())
505 return itMap
->second
;
509 AddEntry(path
, NULL
);
510 return CStatusCacheEntry();
514 CCachedDirectory::AddEntry(const CTGitPath
& path
, const git_wc_status2_t
* pGitStatus
, DWORD validuntil
/* = 0*/)
516 AutoLocker
lock(m_critSec
);
517 if(path
.IsDirectory())
519 CCachedDirectory
* childDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(path
);
522 if ((childDir
->GetCurrentFullStatus() != git_wc_status_missing
)||(pGitStatus
==NULL
)||(pGitStatus
->text_status
!= git_wc_status_unversioned
))
523 childDir
->m_ownStatus
.SetStatus(pGitStatus
);
524 childDir
->m_ownStatus
.SetKind(git_node_dir
);
529 CString cachekey
= GetCacheKey(path
);
530 CacheEntryMap::iterator entry_it
= m_entryCache
.lower_bound(cachekey
);
531 if (entry_it
!= m_entryCache
.end() && entry_it
->first
== cachekey
)
535 if (entry_it
->second
.GetEffectiveStatus() > git_wc_status_none
&&
536 entry_it
->second
.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus
->prop_status
, pGitStatus
->text_status
))
538 CGitStatusCache::Instance().UpdateShell(path
);
539 ATLTRACE(_T("shell update for %s\n"), path
.GetWinPath());
545 entry_it
= m_entryCache
.insert(entry_it
, std::make_pair(cachekey
, CStatusCacheEntry()));
547 entry_it
->second
= CStatusCacheEntry(pGitStatus
, path
.GetLastWriteTime(), path
.IsReadOnly(), validuntil
);
548 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
549 entry_it
->second
.SetKind(git_node_file
);
555 CCachedDirectory::GetCacheKey(const CTGitPath
& path
)
557 // All we put into the cache as a key is just the end portion of the pathname
558 // There's no point storing the path of the containing directory for every item
559 return path
.GetWinPathString().Mid(m_directoryPath
.GetWinPathString().GetLength());
563 CCachedDirectory::GetFullPathString(const CString
& cacheKey
)
565 return m_directoryPath
.GetWinPathString() + _T("\\") + cacheKey
;
568 BOOL
CCachedDirectory::GetStatusCallback(const struct wgFile_s
*pFile
, void *pUserData
)
570 CCachedDirectory
* pThis
= (CCachedDirectory
*)pUserData
;
572 const TCHAR
*path
= pFile
->sFileName
;
577 git_wc_status2_t _status
;
578 git_wc_status2_t
*status
= &_status
;
580 if ((pFile
->nFlags
& WGFF_Directory
) && pFile
->nStatus
== WGFS_Unknown
)
581 status
->prop_status
= status
->text_status
= git_wc_status_incomplete
;
583 status
->prop_status
= status
->text_status
= GitStatusFromWingit(pFile
->nStatus
);
584 //if (pFile->nStatus > WGFS_Normal) {CStringA s; s.Format("==>%s %d\r\n",pFile->sFileName,pFile->nStatus); OutputDebugStringA(s);}
589 //if ((status->text_status != git_wc_status_none)&&(status->text_status != git_wc_status_missing))
590 svnPath
.SetFromGit(path
, pFile
->nFlags
& WGFF_Directory
);
592 svnPath.SetFromGit(path);*/
594 if (pFile
->nFlags
& WGFF_Directory
)
596 if ( !svnPath
.IsEquivalentToWithoutCase(pThis
->m_directoryPath
) )
598 if (pThis
->m_bRecursive
)
600 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
601 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
602 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
605 // Make sure we know about this child directory
606 // This initial status value is likely to be overwritten from below at some point
607 git_wc_status_kind s
= GitStatus::GetMoreImportant(status
->text_status
, status
->prop_status
);
608 CCachedDirectory
* cdir
= CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath
);
611 // This child directory is already in our cache!
612 // So ask this dir about its recursive status
613 git_wc_status_kind st
= GitStatus::GetMoreImportant(s
, cdir
->GetCurrentFullStatus());
614 AutoLocker
lock(pThis
->m_critSec
);
615 pThis
->m_childDirectories
[svnPath
] = st
;
619 // the child directory is not in the cache. Create a new entry for it in the cache which is
620 // initially 'unversioned'. But we added that directory to the crawling list above, which
621 // means the cache will be updated soon.
622 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
623 AutoLocker
lock(pThis
->m_critSec
);
624 pThis
->m_childDirectories
[svnPath
] = s
;
630 // Keep track of the most important status of all the files in this directory
631 // Don't include subdirectories in this figure, because they need to provide their
632 // own 'most important' value
633 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status
->text_status
);
634 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status
->prop_status
);
635 if (((status
->text_status
== git_wc_status_unversioned
)||(status
->text_status
== git_wc_status_none
))
636 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
638 // treat unversioned files as modified
639 if (pThis
->m_mostImportantFileStatus
!= git_wc_status_added
)
640 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
647 svnPath
.SetFromGit(path
);
648 // Subversion returns no 'entry' field for versioned folders if they're
649 // part of another working copy (nested layouts).
650 // So we have to make sure that such an 'unversioned' folder really
652 if (((status
->text_status
== git_wc_status_unversioned
)||(status
->text_status
== git_wc_status_missing
))&&(!svnPath
.IsEquivalentToWithoutCase(pThis
->m_directoryPath
))&&(svnPath
.IsDirectory()))
654 if (svnPath
.HasAdminDir())
656 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
657 // Mark the directory as 'versioned' (status 'normal' for now).
658 // This initial value will be overwritten from below some time later
660 AutoLocker
lock(pThis
->m_critSec
);
661 pThis
->m_childDirectories
[svnPath
] = git_wc_status_normal
;
663 // Make sure the entry is also in the cache
664 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
665 // also mark the status in the status object as normal
666 status
->text_status
= git_wc_status_normal
;
669 else if (status
->text_status
== git_wc_status_external
)
671 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
672 // Mark the directory as 'versioned' (status 'normal' for now).
673 // This initial value will be overwritten from below some time later
675 AutoLocker
lock(pThis
->m_critSec
);
676 pThis
->m_childDirectories
[svnPath
] = git_wc_status_normal
;
678 // we have added a directory to the child-directory list of this
679 // directory. We now must make sure that this directory also has
680 // an entry in the cache.
681 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
682 // also mark the status in the status object as normal
683 status
->text_status
= git_wc_status_normal
;
687 if (svnPath
.IsDirectory())
689 AutoLocker
lock(pThis
->m_critSec
);
690 pThis
->m_childDirectories
[svnPath
] = GitStatus::GetMoreImportant(status
->text_status
, status
->prop_status
);
692 else if ((CGitStatusCache::Instance().IsUnversionedAsModified())&&(status
->text_status
!= git_wc_status_missing
))
694 // make this unversioned item change the most important status of this
695 // folder to modified if it doesn't already have another status
696 if (pThis
->m_mostImportantFileStatus
!= git_wc_status_added
)
697 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
703 pThis
->AddEntry(svnPath
, status
);
709 git_error_t
* CCachedDirectory::GetStatusCallback(void *baton
, const char *path
, git_wc_status2_t
*status
)
711 CCachedDirectory
* pThis
= (CCachedDirectory
*)baton
;
720 if ((status
->text_status
!= git_wc_status_none
)&&(status
->text_status
!= git_wc_status_missing
))
721 svnPath
.SetFromSVN(path
, (status
->entry
->kind
== svn_node_dir
));
723 svnPath
.SetFromSVN(path
);
725 if(svnPath
.IsDirectory())
727 if(!svnPath
.IsEquivalentToWithoutCase(pThis
->m_directoryPath
))
729 if (pThis
->m_bRecursive
)
731 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
732 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
735 // Make sure we know about this child directory
736 // This initial status value is likely to be overwritten from below at some point
737 git_wc_status_kind s
= GitStatus::GetMoreImportant(status
->text_status
, status
->prop_status
);
738 CCachedDirectory
* cdir
= CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath
);
741 // This child directory is already in our cache!
742 // So ask this dir about its recursive status
743 git_wc_status_kind st
= GitStatus::GetMoreImportant(s
, cdir
->GetCurrentFullStatus());
744 AutoLocker
lock(pThis
->m_critSec
);
745 pThis
->m_childDirectories
[svnPath
] = st
;
749 // the child directory is not in the cache. Create a new entry for it in the cache which is
750 // initially 'unversioned'. But we added that directory to the crawling list above, which
751 // means the cache will be updated soon.
752 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
753 AutoLocker
lock(pThis
->m_critSec
);
754 pThis
->m_childDirectories
[svnPath
] = s
;
760 // Keep track of the most important status of all the files in this directory
761 // Don't include subdirectories in this figure, because they need to provide their
762 // own 'most important' value
763 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status
->text_status
);
764 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, status
->prop_status
);
765 if (((status
->text_status
== git_wc_status_unversioned
)||(status
->text_status
== git_wc_status_none
))
766 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
768 // treat unversioned files as modified
769 if (pThis
->m_mostImportantFileStatus
!= git_wc_status_added
)
770 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
776 svnPath
.SetFromSVN(path
);
777 // Subversion returns no 'entry' field for versioned folders if they're
778 // part of another working copy (nested layouts).
779 // So we have to make sure that such an 'unversioned' folder really
781 if (((status
->text_status
== git_wc_status_unversioned
)||(status
->text_status
== git_wc_status_missing
))&&(!svnPath
.IsEquivalentToWithoutCase(pThis
->m_directoryPath
))&&(svnPath
.IsDirectory()))
783 if (svnPath
.HasAdminDir())
785 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
786 // Mark the directory as 'versioned' (status 'normal' for now).
787 // This initial value will be overwritten from below some time later
789 AutoLocker
lock(pThis
->m_critSec
);
790 pThis
->m_childDirectories
[svnPath
] = git_wc_status_normal
;
792 // Make sure the entry is also in the cache
793 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
794 // also mark the status in the status object as normal
795 status
->text_status
= git_wc_status_normal
;
798 else if (status
->text_status
== git_wc_status_external
)
800 CGitStatusCache::Instance().AddFolderForCrawling(svnPath
);
801 // Mark the directory as 'versioned' (status 'normal' for now).
802 // This initial value will be overwritten from below some time later
804 AutoLocker
lock(pThis
->m_critSec
);
805 pThis
->m_childDirectories
[svnPath
] = git_wc_status_normal
;
807 // we have added a directory to the child-directory list of this
808 // directory. We now must make sure that this directory also has
809 // an entry in the cache.
810 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath
);
811 // also mark the status in the status object as normal
812 status
->text_status
= git_wc_status_normal
;
816 if (svnPath
.IsDirectory())
818 AutoLocker
lock(pThis
->m_critSec
);
819 pThis
->m_childDirectories
[svnPath
] = GitStatus::GetMoreImportant(status
->text_status
, status
->prop_status
);
821 else if ((CGitStatusCache::Instance().IsUnversionedAsModified())&&(status
->text_status
!= git_wc_status_missing
))
823 // make this unversioned item change the most important status of this
824 // folder to modified if it doesn't already have another status
825 if (pThis
->m_mostImportantFileStatus
!= git_wc_status_added
)
826 pThis
->m_mostImportantFileStatus
= GitStatus::GetMoreImportant(pThis
->m_mostImportantFileStatus
, git_wc_status_modified
);
831 pThis
->AddEntry(svnPath
, status
);
838 CCachedDirectory::IsOwnStatusValid() const
840 return m_ownStatus
.HasBeenSet() &&
841 !m_ownStatus
.HasExpired(GetTickCount()) &&
842 // 'external' isn't a valid status. That just
843 // means the folder is not part of the current working
844 // copy but it still has its own 'real' status
845 m_ownStatus
.GetEffectiveStatus()!=git_wc_status_external
&&
846 m_ownStatus
.IsKindKnown();
849 void CCachedDirectory::Invalidate()
851 m_ownStatus
.Invalidate();
854 git_wc_status_kind
CCachedDirectory::CalculateRecursiveStatus()
856 // Combine our OWN folder status with the most important of our *FILES'* status.
857 git_wc_status_kind retVal
= GitStatus::GetMoreImportant(m_mostImportantFileStatus
, m_ownStatus
.GetEffectiveStatus());
859 // NOTE: TSVN marks dir as modified if it contains added/deleted/missing files, but we prefer the most important
860 // status to propagate upward in its original state
861 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
863 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
864 retVal = git_wc_status_modified;
867 // Now combine all our child-directorie's status
869 AutoLocker
lock(m_critSec
);
870 ChildDirStatus::const_iterator it
;
871 for(it
= m_childDirectories
.begin(); it
!= m_childDirectories
.end(); ++it
)
873 retVal
= GitStatus::GetMoreImportant(retVal
, it
->second
);
874 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
876 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
877 retVal = git_wc_status_modified;
884 // Update our composite status and deal with things if it's changed
885 void CCachedDirectory::UpdateCurrentStatus()
887 git_wc_status_kind newStatus
= CalculateRecursiveStatus();
889 if ((newStatus
!= m_currentFullStatus
)&&(m_ownStatus
.IsVersioned()))
891 if ((m_currentFullStatus
!= git_wc_status_none
)&&(m_ownStatus
.GetEffectiveStatus() != git_wc_status_missing
))
893 // Our status has changed - tell the shell
894 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath
.GetWinPath(), m_currentFullStatus
, newStatus
);
895 CGitStatusCache::Instance().UpdateShell(m_directoryPath
);
897 if (m_ownStatus
.GetEffectiveStatus() != git_wc_status_missing
)
898 m_currentFullStatus
= newStatus
;
900 m_currentFullStatus
= git_wc_status_missing
;
902 // And tell our parent, if we've got one...
903 // we tell our parent *always* about our status, even if it hasn't
904 // changed. This is to make sure that the parent has really our current
905 // status - the parent can decide itself if our status has changed
907 CTGitPath parentPath
= m_directoryPath
.GetContainingDirectory();
908 if(!parentPath
.IsEmpty())
911 CCachedDirectory
* cachedDir
= CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath
);
913 cachedDir
->UpdateChildDirectoryStatus(m_directoryPath
, m_currentFullStatus
);
918 // Receive a notification from a child that its status has changed
919 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath
& childDir
, git_wc_status_kind childStatus
)
921 git_wc_status_kind currentStatus
= git_wc_status_none
;
923 AutoLocker
lock(m_critSec
);
924 currentStatus
= m_childDirectories
[childDir
];
926 if ((currentStatus
!= childStatus
)||(!IsOwnStatusValid()))
929 AutoLocker
lock(m_critSec
);
930 m_childDirectories
[childDir
] = childStatus
;
932 UpdateCurrentStatus();
936 CStatusCacheEntry
CCachedDirectory::GetOwnStatus(bool bRecursive
)
938 // Don't return recursive status if we're unversioned ourselves.
939 if(bRecursive
&& m_ownStatus
.GetEffectiveStatus() > git_wc_status_unversioned
)
941 CStatusCacheEntry
recursiveStatus(m_ownStatus
);
942 UpdateCurrentStatus();
943 recursiveStatus
.ForceStatus(m_currentFullStatus
);
944 return recursiveStatus
;
952 void CCachedDirectory::RefreshStatus(bool bRecursive
)
954 // Make sure that our own status is up-to-date
955 GetStatusForMember(m_directoryPath
,bRecursive
);
957 AutoLocker
lock(m_critSec
);
958 // We also need to check if all our file members have the right date on them
959 CacheEntryMap::iterator itMembers
;
960 std::set
<CTGitPath
> refreshedpaths
;
961 DWORD now
= GetTickCount();
962 if (m_entryCache
.size() == 0)
964 for (itMembers
= m_entryCache
.begin(); itMembers
!= m_entryCache
.end(); ++itMembers
)
966 if (itMembers
->first
)
968 CTGitPath
filePath(m_directoryPath
);
969 filePath
.AppendPathString(itMembers
->first
);
970 std::set
<CTGitPath
>::iterator refr_it
;
971 if ((!filePath
.IsEquivalentToWithoutCase(m_directoryPath
))&&
972 (((refr_it
= refreshedpaths
.lower_bound(filePath
)) == refreshedpaths
.end()) || !filePath
.IsEquivalentToWithoutCase(*refr_it
)))
974 if ((itMembers
->second
.HasExpired(now
))||(!itMembers
->second
.DoesFileTimeMatch(filePath
.GetLastWriteTime())))
977 // We need to request this item as well
978 GetStatusForMember(filePath
,bRecursive
);
979 // GetStatusForMember now has recreated the m_entryCache map.
980 // So start the loop again, but add this path to the refreshed paths set
981 // to make sure we don't refresh this path again. This is to make sure
982 // that we don't end up in an endless loop.
984 refreshedpaths
.insert(refr_it
, filePath
);
985 itMembers
= m_entryCache
.begin();
986 if (m_entryCache
.size()==0)
990 else if ((bRecursive
)&&(itMembers
->second
.IsDirectory()))
992 // crawl all sub folders too! Otherwise a change deep inside the
993 // tree which has changed won't get propagated up the tree.
994 CGitStatusCache::Instance().AddFolderForCrawling(filePath
);
1001 void CCachedDirectory::RefreshMostImportant()
1003 CacheEntryMap::iterator itMembers
;
1004 git_wc_status_kind newStatus
= m_ownStatus
.GetEffectiveStatus();
1005 for (itMembers
= m_entryCache
.begin(); itMembers
!= m_entryCache
.end(); ++itMembers
)
1007 newStatus
= GitStatus::GetMoreImportant(newStatus
, itMembers
->second
.GetEffectiveStatus());
1008 if (((itMembers
->second
.GetEffectiveStatus() == git_wc_status_unversioned
)||(itMembers
->second
.GetEffectiveStatus() == git_wc_status_none
))
1009 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
1011 // treat unversioned files as modified
1012 if (newStatus
!= git_wc_status_added
)
1013 newStatus
= GitStatus::GetMoreImportant(newStatus
, git_wc_status_modified
);
1016 if (newStatus
!= m_mostImportantFileStatus
)
1018 ATLTRACE(_T("status change of path %s\n"), m_directoryPath
.GetWinPath());
1019 CGitStatusCache::Instance().UpdateShell(m_directoryPath
);
1021 m_mostImportantFileStatus
= newStatus
;