Fixed typo introduced in last commit
[TortoiseGit.git] / src / TGitCache / CachedDirectory.cpp
blob967af8d92868f224edaee311c9873c2a49478325
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
4 // Copyright (C) 2008-2013 - 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;
42 m_directoryPath.GetGitPathString(); // make sure git path string is set
44 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
45 m_bRecursive = true;
48 BOOL CCachedDirectory::SaveToDisk(FILE * pFile)
50 AutoLocker lock(m_critSec);
51 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;
53 unsigned int value = GIT_CACHE_VERSION;
54 WRITEVALUETOFILE(value); // 'version' of this save-format
55 value = (int)m_entryCache.size();
56 WRITEVALUETOFILE(value); // size of the cache map
57 // now iterate through the maps and save every entry.
58 for (CacheEntryMap::iterator I = m_entryCache.begin(); I != m_entryCache.end(); ++I)
60 const CString& key = I->first;
61 value = key.GetLength();
62 WRITEVALUETOFILE(value);
63 if (value)
65 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
66 return false;
67 if (!I->second.SaveToDisk(pFile))
68 return false;
71 value = (int)m_childDirectories.size();
72 WRITEVALUETOFILE(value);
73 for (ChildDirStatus::iterator I = m_childDirectories.begin(); I != m_childDirectories.end(); ++I)
75 const CString& path = I->first.GetWinPathString();
76 value = path.GetLength();
77 WRITEVALUETOFILE(value);
78 if (value)
80 if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)
81 return false;
82 git_wc_status_kind status = I->second;
83 WRITEVALUETOFILE(status);
86 // WRITEVALUETOFILE(m_propsFileTime);
87 value = m_directoryPath.GetWinPathString().GetLength();
88 WRITEVALUETOFILE(value);
89 if (value)
91 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
92 return false;
94 if (!m_ownStatus.SaveToDisk(pFile))
95 return false;
96 WRITEVALUETOFILE(m_currentFullStatus);
97 WRITEVALUETOFILE(m_mostImportantFileStatus);
98 return true;
101 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)
103 AutoLocker lock(m_critSec);
104 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
107 unsigned int value = 0;
108 LOADVALUEFROMFILE(value);
109 if (value != GIT_CACHE_VERSION)
110 return false; // not the correct version
111 int mapsize = 0;
112 LOADVALUEFROMFILE(mapsize);
113 for (int i=0; i<mapsize; ++i)
115 LOADVALUEFROMFILE(value);
116 if (value > MAX_PATH)
117 return false;
118 if (value)
120 CString sKey;
121 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
123 sKey.ReleaseBuffer(0);
124 return false;
126 sKey.ReleaseBuffer(value);
127 CStatusCacheEntry entry;
128 if (!entry.LoadFromDisk(pFile))
129 return false;
130 // only read non empty keys (just needed for transition from old TGit clients)
131 if (!sKey.IsEmpty())
132 m_entryCache[sKey] = entry;
135 LOADVALUEFROMFILE(mapsize);
136 for (int i=0; i<mapsize; ++i)
138 LOADVALUEFROMFILE(value);
139 if (value > MAX_PATH)
140 return false;
141 if (value)
143 CString sPath;
144 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)
146 sPath.ReleaseBuffer(0);
147 return false;
149 sPath.ReleaseBuffer(value);
150 git_wc_status_kind status;
151 LOADVALUEFROMFILE(status);
152 m_childDirectories[CTGitPath(sPath)] = status;
155 LOADVALUEFROMFILE(value);
156 if (value > MAX_PATH)
157 return false;
158 if (value)
160 CString sPath;
161 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
163 sPath.ReleaseBuffer(0);
164 return false;
166 sPath.ReleaseBuffer(value);
167 // make sure paths do not end with backslash (just needed for transition from old TGit clients)
168 if (sPath.GetLength() > 3 && sPath[sPath.GetLength() - 1] == _T('\\'))
169 sPath.TrimRight(_T("\\"));
170 m_directoryPath.SetFromWin(sPath);
171 m_directoryPath.GetGitPathString(); // make sure git path string is set
173 if (!m_ownStatus.LoadFromDisk(pFile))
174 return false;
176 LOADVALUEFROMFILE(m_currentFullStatus);
177 LOADVALUEFROMFILE(m_mostImportantFileStatus);
179 catch ( CAtlException )
181 return false;
183 return true;
188 CStatusCacheEntry CCachedDirectory::GetStatusFromCache(const CTGitPath& path, bool bRecursive)
190 if(path.IsDirectory())
192 // We don't have directory status in our cache
193 // Ask the directory if it knows its own status
194 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
195 if( dirEntry)
197 if (dirEntry->IsOwnStatusValid())
198 return dirEntry->GetOwnStatus(bRecursive);
199 else
201 /* cache have outof date, need crawl again*/
203 /*AutoLocker lock(dirEntry->m_critSec);
204 ChildDirStatus::const_iterator it;
205 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
207 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
210 CGitStatusCache::Instance().AddFolderForCrawling(path);
212 /*Return old status during crawling*/
213 return dirEntry->GetOwnStatus(bRecursive);
216 else
218 CGitStatusCache::Instance().AddFolderForCrawling(path);
220 return CStatusCacheEntry();
222 else
224 //All file ignored if under ignore directory
225 if (m_ownStatus.GetEffectiveStatus() == git_wc_status_ignored)
226 return CStatusCacheEntry(git_wc_status_ignored);
227 if (m_ownStatus.GetEffectiveStatus() == git_wc_status_unversioned)
228 return CStatusCacheEntry(git_wc_status_unversioned);
230 // Look up a file in our own cache
231 AutoLocker lock(m_critSec);
232 CString strCacheKey = GetCacheKey(path);
233 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
234 if(itMap != m_entryCache.end())
236 // We've hit the cache - check for timeout
237 if(!itMap->second.HasExpired((long)GetTickCount()))
239 if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))
241 if ((itMap->second.GetEffectiveStatus()!=git_wc_status_missing)||(!PathFileExists(path.GetWinPath())))
243 // Note: the filetime matches after a modified has been committed too.
244 // So in that case, we would return a wrong status (e.g. 'modified' instead
245 // of 'normal') here.
246 return itMap->second;
252 CGitStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory());
253 return CStatusCacheEntry();
258 CStatusCacheEntry CCachedDirectory::GetStatusFromGit(const CTGitPath &path, CString sProjectRoot)
260 CString subpaths = path.GetGitPathString();
261 if(subpaths.GetLength() >= sProjectRoot.GetLength())
263 if(subpaths[sProjectRoot.GetLength()] == _T('/'))
264 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength()-1);
265 else
266 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength());
269 GitStatus *pGitStatus = &CGitStatusCache::Instance().m_GitStatus;
270 UNREFERENCED_PARAMETER(pGitStatus);
272 bool isVersion =true;
273 pGitStatus->IsUnderVersionControl(sProjectRoot, subpaths, path.IsDirectory(), &isVersion);
274 if(!isVersion)
275 { //untracked file
276 bool isIgnoreFileChanged = pGitStatus->HasIgnoreFilesChanged(sProjectRoot, subpaths);
278 if( isIgnoreFileChanged)
280 pGitStatus->LoadIgnoreFile(sProjectRoot, subpaths);
283 if(path.IsDirectory())
286 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path,
287 false); /* we needn't watch untracked directory*/
289 if(dirEntry)
291 AutoLocker lock(dirEntry->m_critSec);
293 git_wc_status_kind dirstatus = dirEntry->GetCurrentFullStatus() ;
294 if( dirstatus == git_wc_status_none || dirstatus >= git_wc_status_normal || isIgnoreFileChanged )
295 {/* status have not initialized*/
296 git_wc_status2_t status2;
297 bool isignore = false;
298 pGitStatus->IsIgnore(sProjectRoot,subpaths,&isignore);
299 status2.text_status = status2.prop_status =
300 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
302 dirEntry->m_ownStatus.SetKind(git_node_dir);
303 dirEntry->m_ownStatus.SetStatus(&status2);
306 return dirEntry->m_ownStatus;
310 else /* path is file */
312 AutoLocker lock(m_critSec);
313 CString strCacheKey = GetCacheKey(path);
315 if (strCacheKey.IsEmpty())
316 return CStatusCacheEntry();
318 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
319 if(itMap == m_entryCache.end() || isIgnoreFileChanged)
321 git_wc_status2_t status2;
322 bool isignore = false;
323 pGitStatus->IsIgnore(sProjectRoot,subpaths,&isignore);
324 status2.text_status = status2.prop_status =
325 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
326 AddEntry(path, &status2);
327 return m_entryCache[strCacheKey];
329 else
331 return itMap->second;
334 return CStatusCacheEntry();
337 else
339 EnumFiles((CTGitPath*)&path, TRUE);
340 UpdateCurrentStatus();
341 return CStatusCacheEntry(m_ownStatus);
346 /// bFetch is true, fetch all status, call by crawl.
347 /// bFetch is false, get cache status, return quickly.
349 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */)
351 CString sProjectRoot;
352 bool bIsVersionedPath;
354 bool bRequestForSelf = false;
355 if(path.IsEquivalentToWithoutCase(m_directoryPath))
357 bRequestForSelf = true;
358 AutoLocker lock(m_critSec);
359 // HasAdminDir might modify m_directoryPath, so we need to do it synchronized
360 bIsVersionedPath = m_directoryPath.HasAdminDir(&sProjectRoot);
362 else
363 bIsVersionedPath = path.HasAdminDir(&sProjectRoot);
365 // In all most circumstances, we ask for the status of a member of this directory.
366 ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);
368 //If is not version control path
369 if( !bIsVersionedPath)
371 ATLTRACE(_T("%s is not underversion control\n"), path.GetWinPath());
372 return CStatusCacheEntry();
375 // We've not got this item in the cache - let's add it
376 // We never bother asking SVN for the status of just one file, always for its containing directory
378 if (g_GitAdminDir.IsAdminDirPath(path.GetWinPathString()))
380 // We're being asked for the status of an .git directory
381 // It's not worth asking for this
382 return CStatusCacheEntry();
386 if(bFetch)
388 return GetStatusFromGit(path, sProjectRoot);
390 else
392 return GetStatusFromCache(path, bRecursive);
396 CStatusCacheEntry CCachedDirectory::GetCacheStatusForMember(const CTGitPath& path)
398 // no disk access!
399 AutoLocker lock(m_critSec);
400 CacheEntryMap::iterator itMap = m_entryCache.find(GetCacheKey(path));
401 if(itMap != m_entryCache.end())
402 return itMap->second;
404 return CStatusCacheEntry();
407 int CCachedDirectory::EnumFiles(CTGitPath *path , bool IsFull)
409 CString sProjectRoot;
410 if(path)
411 path->HasAdminDir(&sProjectRoot);
412 else
413 m_directoryPath.HasAdminDir(&sProjectRoot);
415 ATLTRACE(_T("EnumFiles %s\n"), path->GetWinPath());
417 ATLASSERT( !m_directoryPath.IsEmpty() );
419 CString sSubPath;
421 CString s;
422 if(path)
423 s=path->GetWinPath();
424 else
425 s=m_directoryPath.GetDirectory().GetWinPathString();
427 if (s.GetLength() > sProjectRoot.GetLength())
429 // skip initial slash if necessary
430 if(s[sProjectRoot.GetLength()] == _T('\\'))
431 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() -1);
432 else
433 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() );
436 // strip "\" at the end, otherwise cache lookups for drives do not work correctly
437 sProjectRoot.TrimRight(_T("\\"));
439 GitStatus *pStatus = &CGitStatusCache::Instance().m_GitStatus;
440 UNREFERENCED_PARAMETER(pStatus);
441 git_wc_status_kind status = git_wc_status_none;
443 if(!path->IsDirectory())
445 bool assumeValid = false;
446 bool skipWorktree = false;
447 pStatus->GetFileStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback, this, &assumeValid, &skipWorktree);
449 else
452 AutoLocker lock(m_critSec);
453 // clear subdirectory status cache
454 m_childDirectories.clear();
457 m_mostImportantFileStatus = git_wc_status_none;
458 pStatus->EnumDirStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback,this);
460 // folders must not be displayed as added or deleted only as modified
461 if (status == git_wc_status_deleted || status == git_wc_status_added)
462 status = git_wc_status_modified;
463 // this is necessary, even if the m_mostImportantFileStatus is set in GetStatusCallback,
464 // however, if it's missing, deleted subdirectories won't mark parents as modified
465 m_mostImportantFileStatus = GitStatus::GetMoreImportant(m_mostImportantFileStatus, status);
467 if (sSubPath.IsEmpty())
469 // request a shell notification for working trees which have not been examined before
470 if (m_currentFullStatus == git_wc_status_none)
472 m_currentFullStatus = git_wc_status_normal;
473 CGitStatusCache::Instance().UpdateShell(sProjectRoot);
475 m_ownStatus = git_wc_status_normal;
477 // otherwise folders which contain at least one ignored item gets displayed as ignored
478 // or: folders which were displayed as conflicted never recover from that state
479 else if (m_ownStatus.GetEffectiveStatus() == git_wc_status_ignored || m_ownStatus.GetEffectiveStatus() == git_wc_status_conflicted)
480 m_ownStatus = git_wc_status_normal;
483 return 0;
485 void
486 CCachedDirectory::AddEntry(const CTGitPath& path, const git_wc_status2_t* pGitStatus, DWORD validuntil /* = 0*/)
488 AutoLocker lock(m_critSec);
489 if(path.IsDirectory())
491 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
492 if (childDir)
494 if ((childDir->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
496 if(pGitStatus)
498 if(childDir->GetCurrentFullStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
500 CGitStatusCache::Instance().UpdateShell(path);
501 //ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
502 childDir->m_ownStatus.SetKind(git_node_dir);
503 childDir->m_ownStatus.SetStatus(pGitStatus);
507 childDir->m_ownStatus.SetKind(git_node_dir);
512 else
514 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path.GetContainingDirectory());
515 bool bNotified = false;
517 if(!childDir)
518 return ;
520 AutoLocker lock(childDir->m_critSec);
521 CString cachekey = GetCacheKey(path);
522 CacheEntryMap::iterator entry_it = childDir->m_entryCache.lower_bound(cachekey);
523 if (entry_it != childDir->m_entryCache.end() && entry_it->first == cachekey)
525 if (pGitStatus)
527 if (entry_it->second.GetEffectiveStatus() > git_wc_status_none &&
528 entry_it->second.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status)
531 bNotified =true;
536 else
538 entry_it = childDir->m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
539 bNotified = true;
542 entry_it->second = CStatusCacheEntry(pGitStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);
543 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
544 entry_it->second.SetKind(git_node_file);
546 if(bNotified)
548 CGitStatusCache::Instance().UpdateShell(path);
549 //ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
552 //ATLTRACE(_T("Path Entry Add %s %s %s %d\n"), path.GetWinPath(), cachekey, m_directoryPath.GetWinPath(), pGitStatus->text_status);
555 CCachedDirectory * parent = CGitStatusCache::Instance().GetDirectoryCacheEntry(path.GetContainingDirectory());
557 if(parent)
559 if ((parent->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
561 if(pGitStatus)
563 if(parent->GetCurrentFullStatus() < GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
565 CGitStatusCache::Instance().UpdateShell(parent->m_directoryPath);
566 //ATLTRACE(_T("shell update for %s\n"), parent->m_directoryPath.GetWinPathString());
567 parent->m_ownStatus.SetKind(git_node_dir);
568 parent->m_ownStatus.SetStatus(pGitStatus);
576 CString
577 CCachedDirectory::GetCacheKey(const CTGitPath& path)
579 // All we put into the cache as a key is just the end portion of the pathname
580 // There's no point storing the path of the containing directory for every item
581 return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength()).MakeLower().TrimLeft(_T("\\"));
584 CString
585 CCachedDirectory::GetFullPathString(const CString& cacheKey)
587 return m_directoryPath.GetWinPathString() + _T("\\") + cacheKey;
590 BOOL CCachedDirectory::GetStatusCallback(const CString & path, git_wc_status_kind status,bool isDir, void *, bool assumeValid, bool skipWorktree)
592 git_wc_status2_t _status;
593 git_wc_status2_t *status2 = &_status;
595 status2->prop_status = status2->text_status = status;
596 status2->assumeValid = assumeValid;
597 status2->skipWorktree = skipWorktree;
599 CTGitPath gitPath(path);
601 CCachedDirectory *pThis = CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath.GetContainingDirectory());
603 if(pThis == NULL)
604 return FALSE;
606 // if(status->entry)
608 if (isDir)
609 { /*gitpath is directory*/
610 //if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
612 if (!gitPath.Exists())
614 ATLTRACE(_T("Miss dir %s \n"), gitPath.GetWinPath());
615 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_deleted);
618 if ( status < git_wc_status_normal)
620 if( ::PathFileExists(path+_T("\\.git")))
621 { // this is submodule
622 ATLTRACE(_T("skip submodule %s\n"), path);
623 return FALSE;
626 if (pThis->m_bRecursive)
628 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
629 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
630 if(status >= git_wc_status_normal)
631 CGitStatusCache::Instance().AddFolderForCrawling(gitPath);
634 // Make sure we know about this child directory
635 // This initial status value is likely to be overwritten from below at some point
636 git_wc_status_kind s = GitStatus::GetMoreImportant(status2->text_status, status2->prop_status);
638 // folders must not be displayed as added or deleted only as modified
639 if (s == git_wc_status_deleted || s == git_wc_status_added)
640 s = git_wc_status_modified;
642 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(gitPath);
643 if (cdir)
645 // This child directory is already in our cache!
646 // So ask this dir about its recursive status
647 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
648 AutoLocker lock(pThis->m_critSec);
649 pThis->m_childDirectories[gitPath] = st;
650 ATLTRACE(_T("call 1 Update dir %s %d\n"), gitPath.GetWinPath(), st);
652 else
654 AutoLocker lock(pThis->m_critSec);
655 // the child directory is not in the cache. Create a new entry for it in the cache which is
656 // initially 'unversioned'. But we added that directory to the crawling list above, which
657 // means the cache will be updated soon.
658 CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath);
660 pThis->m_childDirectories[gitPath] = s;
661 ATLTRACE(_T("call 2 Update dir %s %d\n"), gitPath.GetWinPath(), s);
665 else /* gitpath is file*/
667 // Keep track of the most important status of all the files in this directory
668 // Don't include subdirectories in this figure, because they need to provide their
669 // own 'most important' value
670 if (status2->text_status == git_wc_status_deleted || status2->text_status == git_wc_status_added)
672 // if just a file in a folder is deleted or added report back that the folder is modified and not deleted or added
673 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
675 else
677 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->text_status);
678 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->prop_status);
679 if ((status2->text_status == git_wc_status_unversioned)
680 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
682 // treat unversioned files as modified
683 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
684 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
690 pThis->AddEntry(gitPath, status2);
692 return FALSE;
695 bool
696 CCachedDirectory::IsOwnStatusValid() const
698 return m_ownStatus.HasBeenSet() &&
699 !m_ownStatus.HasExpired(GetTickCount());
702 void CCachedDirectory::Invalidate()
704 m_ownStatus.Invalidate();
707 git_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()
709 // Combine our OWN folder status with the most important of our *FILES'* status.
710 git_wc_status_kind retVal = GitStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());
712 // Now combine all our child-directorie's status
713 AutoLocker lock(m_critSec);
714 ChildDirStatus::const_iterator it;
715 for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)
717 retVal = GitStatus::GetMoreImportant(retVal, it->second);
720 return retVal;
723 // Update our composite status and deal with things if it's changed
724 void CCachedDirectory::UpdateCurrentStatus()
726 git_wc_status_kind newStatus = CalculateRecursiveStatus();
727 ATLTRACE(_T("UpdateCurrentStatus %s new:%d old: %d\n"),
728 m_directoryPath.GetWinPath(),
729 newStatus, m_currentFullStatus);
731 if ( this->m_ownStatus.GetEffectiveStatus() < git_wc_status_normal )
733 if (::PathFileExists(this->m_directoryPath.GetWinPathString()+_T("\\.git")))
735 //project root must be normal status at least.
736 ATLTRACE(_T("force update project root directory as normal status\n"));
737 this->m_ownStatus.ForceStatus(git_wc_status_normal);
741 if ((newStatus != m_currentFullStatus) && m_ownStatus.IsVersioned())
743 if ((m_currentFullStatus != git_wc_status_none)&&(m_ownStatus.GetEffectiveStatus() != git_wc_status_missing))
745 // Our status has changed - tell the shell
746 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);
747 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
749 if (m_ownStatus.GetEffectiveStatus() != git_wc_status_missing)
750 m_currentFullStatus = newStatus;
751 else
752 m_currentFullStatus = git_wc_status_missing;
754 // And tell our parent, if we've got one...
755 // we tell our parent *always* about our status, even if it hasn't
756 // changed. This is to make sure that the parent has really our current
757 // status - the parent can decide itself if our status has changed
758 // or not.
759 CTGitPath parentPath = m_directoryPath.GetContainingDirectory();
760 if(!parentPath.IsEmpty())
762 // We have a parent
763 // just version controled directory need to cache.
764 CString root1, root2;
765 if(parentPath.HasAdminDir(&root1) && m_directoryPath.HasAdminDir(&root2) && root1 == root2)
767 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath);
768 if (cachedDir)
769 cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);
775 // Receive a notification from a child that its status has changed
776 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath& childDir, git_wc_status_kind childStatus)
778 git_wc_status_kind currentStatus = git_wc_status_none;
780 AutoLocker lock(m_critSec);
781 currentStatus = m_childDirectories[childDir];
783 if ((currentStatus != childStatus)||(!IsOwnStatusValid()))
786 AutoLocker lock(m_critSec);
787 m_childDirectories[childDir] = childStatus;
789 UpdateCurrentStatus();
793 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)
795 // Don't return recursive status if we're unversioned ourselves.
796 if(bRecursive && m_ownStatus.GetEffectiveStatus() > git_wc_status_unversioned && m_ownStatus.GetEffectiveStatus() != git_wc_status_ignored)
798 CStatusCacheEntry recursiveStatus(m_ownStatus);
799 UpdateCurrentStatus();
800 if (m_currentFullStatus == git_wc_status_deleted || m_currentFullStatus == git_wc_status_added)
801 m_currentFullStatus = git_wc_status_modified;
802 recursiveStatus.ForceStatus(m_currentFullStatus);
803 return recursiveStatus;
805 else
807 return m_ownStatus;
811 void CCachedDirectory::RefreshStatus(bool bRecursive)
813 // Make sure that our own status is up-to-date
814 GetStatusForMember(m_directoryPath,bRecursive);
816 AutoLocker lock(m_critSec);
817 // We also need to check if all our file members have the right date on them
818 CacheEntryMap::iterator itMembers;
819 std::set<CTGitPath> refreshedpaths;
820 DWORD now = GetTickCount();
821 if (m_entryCache.empty())
822 return;
823 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
825 if (itMembers->first)
827 CTGitPath filePath(m_directoryPath);
828 filePath.AppendPathString(itMembers->first);
829 std::set<CTGitPath>::iterator refr_it;
830 if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&
831 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))
833 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))
835 lock.Unlock();
836 // We need to request this item as well
837 GetStatusForMember(filePath,bRecursive);
838 // GetStatusForMember now has recreated the m_entryCache map.
839 // So start the loop again, but add this path to the refreshed paths set
840 // to make sure we don't refresh this path again. This is to make sure
841 // that we don't end up in an endless loop.
842 lock.Lock();
843 refreshedpaths.insert(refr_it, filePath);
844 itMembers = m_entryCache.begin();
845 if (m_entryCache.empty())
846 return;
847 continue;
849 else if ((bRecursive)&&(itMembers->second.IsDirectory()))
851 // crawl all sub folders too! Otherwise a change deep inside the
852 // tree which has changed won't get propagated up the tree.
853 CGitStatusCache::Instance().AddFolderForCrawling(filePath);
860 void CCachedDirectory::RefreshMostImportant()
862 AutoLocker lock(m_critSec);
863 CacheEntryMap::iterator itMembers;
864 git_wc_status_kind newStatus = m_ownStatus.GetEffectiveStatus();
865 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
867 newStatus = GitStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());
868 if (((itMembers->second.GetEffectiveStatus() == git_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == git_wc_status_none))
869 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
871 // treat unversioned files as modified
872 if (newStatus != git_wc_status_added)
873 newStatus = GitStatus::GetMoreImportant(newStatus, git_wc_status_modified);
876 if (newStatus != m_mostImportantFileStatus)
878 ATLTRACE(_T("status change of path %s\n"), m_directoryPath.GetWinPath());
879 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
881 m_mostImportantFileStatus = newStatus;