fixed possible concurrent modifications of m_pathsToUpdate
[TortoiseGit.git] / src / TGitCache / CachedDirectory.cpp
bloba2051c9879f9ba4c9e2f06e8e0652a3df9c837c7
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.
20 #include "StdAfx.h"
21 #include ".\cacheddirectory.h"
22 //#include "SVNHelpers.h"
23 #include "GitStatusCache.h"
24 #include "GitStatus.h"
25 #include <set>
27 CCachedDirectory::CCachedDirectory(void)
29 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
30 m_bRecursive = true;
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;
44 m_bRecursive = true;
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);
62 if (value)
64 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
65 return false;
66 if (!I->second.SaveToDisk(pFile))
67 return false;
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);
77 if (value)
79 if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)
80 return false;
81 git_wc_status_kind status = I->second;
82 WRITEVALUETOFILE(status);
85 // WRITEVALUETOFILE(m_propsFileTime);
86 value = m_directoryPath.GetWinPathString().GetLength();
87 WRITEVALUETOFILE(value);
88 if (value)
90 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
91 return false;
93 if (!m_ownStatus.SaveToDisk(pFile))
94 return false;
95 WRITEVALUETOFILE(m_currentFullStatus);
96 WRITEVALUETOFILE(m_mostImportantFileStatus);
97 return true;
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
110 int mapsize = 0;
111 LOADVALUEFROMFILE(mapsize);
112 for (int i=0; i<mapsize; ++i)
114 LOADVALUEFROMFILE(value);
115 if (value > MAX_PATH)
116 return false;
117 if (value)
119 CString sKey;
120 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
122 sKey.ReleaseBuffer(0);
123 return false;
125 sKey.ReleaseBuffer(value);
126 CStatusCacheEntry entry;
127 if (!entry.LoadFromDisk(pFile))
128 return false;
129 m_entryCache[sKey] = entry;
132 LOADVALUEFROMFILE(mapsize);
133 for (int i=0; i<mapsize; ++i)
135 LOADVALUEFROMFILE(value);
136 if (value > MAX_PATH)
137 return false;
138 if (value)
140 CString sPath;
141 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)
143 sPath.ReleaseBuffer(0);
144 return false;
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)
154 return false;
155 if (value)
157 CString sPath;
158 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
160 sPath.ReleaseBuffer(0);
161 return false;
163 sPath.ReleaseBuffer(value);
164 m_directoryPath.SetFromWin(sPath);
166 if (!m_ownStatus.LoadFromDisk(pFile))
167 return false;
169 LOADVALUEFROMFILE(m_currentFullStatus);
170 LOADVALUEFROMFILE(m_mostImportantFileStatus);
172 catch ( CAtlException )
174 return false;
176 return true;
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);
188 if( dirEntry)
190 if (dirEntry->IsOwnStatusValid())
191 return dirEntry->GetOwnStatus(bRecursive);
192 else
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);
209 else
211 CGitStatusCache::Instance().AddFolderForCrawling(path);
213 return CStatusCacheEntry();
215 else
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;
238 else
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);
258 else
259 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength());
262 GitStatus *pGitStatus = &CGitStatusCache::Instance().m_GitStatus;
264 CGitHash head;
266 pGitStatus->GetHeadHash(sProjectRoot,head);
268 bool isVersion =true;
269 pGitStatus->IsUnderVersionControl(sProjectRoot, subpaths, path.IsDirectory(), &isVersion);
270 if(!isVersion)
271 { //untracked file
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*/
287 if(dirEntry)
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];
324 else
326 return itMap->second;
329 return CStatusCacheEntry();
332 else
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 */)
346 CString strCacheKey;
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();
377 if(bFetch)
379 return GetStatusFromGit(path, sProjectRoot);
381 else
383 return GetStatusFromCache(path, bRecursive);
387 CStatusCacheEntry CCachedDirectory::GetCacheStatusForMember(const CTGitPath& path)
389 // no disk access!
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;
401 if(path)
402 path->HasAdminDir(&sProjectRoot);
403 else
404 m_directoryPath.HasAdminDir(&sProjectRoot);
406 ATLTRACE(_T("EnumFiles %s\n"), path->GetWinPath());
408 ATLASSERT( !m_directoryPath.IsEmpty() );
410 CString sSubPath;
412 CString s;
413 if(path)
414 s=path->GetWinPath();
415 else
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);
423 else
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);
433 else
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);
443 return 0;
445 void
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);
452 if (childDir)
454 if ((childDir->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
456 if(pGitStatus)
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);
472 else
474 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path.GetContainingDirectory());
475 bool bNotified = false;
477 if(!childDir)
478 return ;
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)
484 if (pGitStatus)
486 if (entry_it->second.GetEffectiveStatus() > git_wc_status_none &&
487 entry_it->second.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status)
490 bNotified =true;
495 else
497 entry_it = childDir->m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
498 bNotified = true;
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);
505 if(bNotified)
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());
516 if(parent)
518 if ((parent->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
520 if(pGitStatus)
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);
535 CString
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();
543 CString
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;
556 CTGitPath gitPath;
558 CString lowcasepath = path;
559 lowcasepath.MakeLower();
560 gitPath.SetFromUnknown(lowcasepath);
562 CCachedDirectory *pThis = CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath.GetContainingDirectory());
564 if(pThis == NULL)
565 return FALSE;
567 // if(status->entry)
569 if (isDir)
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);
584 return FALSE;
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);
601 if (cdir)
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);
610 else
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);
633 else
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);
650 return FALSE;
653 #if 0
654 git_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, git_wc_status2_t *status)
656 CCachedDirectory* pThis = (CCachedDirectory*)baton;
658 if (path == NULL)
659 return 0;
661 CTGitPath svnPath;
663 if(status->entry)
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));
667 else
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);
684 if (cdir)
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;
692 else
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;
703 else
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);
719 else
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
725 // is unversioned.
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;
759 else
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);
778 return 0;
780 #endif
782 bool
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;
821 return retVal;
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;
852 else
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
859 // or not.
860 CTGitPath parentPath = m_directoryPath.GetContainingDirectory();
861 if(!parentPath.IsEmpty())
863 // We have a parent
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);
869 if (cachedDir)
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;
904 else
906 return m_ownStatus;
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)
921 return;
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())))
934 lock.Unlock();
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.
941 lock.Lock();
942 refreshedpaths.insert(refr_it, filePath);
943 itMembers = m_entryCache.begin();
944 if (m_entryCache.size()==0)
945 return;
946 continue;
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;