some spaces-tabs code cleanup
[TortoiseGit.git] / src / TGitCache / CachedDirectory.cpp
blobd3331301ebd693420d5298a41043f17cb45f46c5
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
4 // Copyright (C) 2008-2011 - 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;
31 m_indexFileTime = 0;
34 CCachedDirectory::~CCachedDirectory(void)
38 CCachedDirectory::CCachedDirectory(const CTGitPath& directoryPath)
40 ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));
42 m_directoryPath = directoryPath;
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_indexFileTime);
87 WRITEVALUETOFILE(m_Head.m_hash);
88 // WRITEVALUETOFILE(m_propsFileTime);
89 value = m_directoryPath.GetWinPathString().GetLength();
90 WRITEVALUETOFILE(value);
91 if (value)
93 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
94 return false;
96 if (!m_ownStatus.SaveToDisk(pFile))
97 return false;
98 WRITEVALUETOFILE(m_currentFullStatus);
99 WRITEVALUETOFILE(m_mostImportantFileStatus);
100 return true;
103 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)
105 AutoLocker lock(m_critSec);
106 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
109 unsigned int value = 0;
110 LOADVALUEFROMFILE(value);
111 if (value != GIT_CACHE_VERSION)
112 return false; // not the correct version
113 int mapsize = 0;
114 LOADVALUEFROMFILE(mapsize);
115 for (int i=0; i<mapsize; ++i)
117 LOADVALUEFROMFILE(value);
118 if (value > MAX_PATH)
119 return false;
120 if (value)
122 CString sKey;
123 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
125 sKey.ReleaseBuffer(0);
126 return false;
128 sKey.ReleaseBuffer(value);
129 CStatusCacheEntry entry;
130 if (!entry.LoadFromDisk(pFile))
131 return false;
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(m_indexFileTime);
156 LOADVALUEFROMFILE(m_Head.m_hash);
157 // LOADVALUEFROMFILE(m_propsFileTime);
158 LOADVALUEFROMFILE(value);
159 if (value > MAX_PATH)
160 return false;
161 if (value)
163 CString sPath;
164 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
166 sPath.ReleaseBuffer(0);
167 return false;
169 sPath.ReleaseBuffer(value);
170 m_directoryPath.SetFromWin(sPath);
172 if (!m_ownStatus.LoadFromDisk(pFile))
173 return false;
175 LOADVALUEFROMFILE(m_currentFullStatus);
176 LOADVALUEFROMFILE(m_mostImportantFileStatus);
178 catch ( CAtlException )
180 return false;
182 return true;
187 CStatusCacheEntry CCachedDirectory::GetStatusFromCache(const CTGitPath& path, bool bRecursive)
189 if(path.IsDirectory())
191 // We don't have directory status in our cache
192 // Ask the directory if it knows its own status
193 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
194 if( dirEntry)
196 if (dirEntry->IsOwnStatusValid())
197 return dirEntry->GetOwnStatus(bRecursive);
198 else
200 /* cache have outof date, need crawl again*/
202 /*AutoLocker lock(dirEntry->m_critSec);
203 ChildDirStatus::const_iterator it;
204 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
206 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
209 CGitStatusCache::Instance().AddFolderForCrawling(path);
211 /*Return old status during crawling*/
212 return dirEntry->GetOwnStatus(bRecursive);
215 else
217 CGitStatusCache::Instance().AddFolderForCrawling(path);
219 return CStatusCacheEntry();
221 else
223 // Look up a file in our own cache
224 AutoLocker lock(m_critSec);
225 CString strCacheKey = GetCacheKey(path);
226 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
227 if(itMap != m_entryCache.end())
229 // We've hit the cache - check for timeout
230 if(!itMap->second.HasExpired((long)GetTickCount()))
232 if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))
234 if ((itMap->second.GetEffectiveStatus()!=git_wc_status_missing)||(!PathFileExists(path.GetWinPath())))
236 // Note: the filetime matches after a modified has been committed too.
237 // So in that case, we would return a wrong status (e.g. 'modified' instead
238 // of 'normal') here.
239 return itMap->second;
243 }else
245 //All file ignored if under ignore directory
246 if( m_currentFullStatus == git_wc_status_ignored)
247 return CStatusCacheEntry(git_wc_status_ignored);
250 CGitStatusCache::Instance().AddFolderForCrawling(path);
251 return CStatusCacheEntry();
256 CStatusCacheEntry CCachedDirectory::GetStatusFromGit(const CTGitPath &path, CString sProjectRoot)
258 CString subpaths = path.GetGitPathString();
259 if(subpaths.GetLength() >= sProjectRoot.GetLength())
261 if(subpaths[sProjectRoot.GetLength()] == _T('/'))
262 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength()-1);
263 else
264 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength());
267 GitStatus *pGitStatus = &CGitStatusCache::Instance().m_GitStatus;
269 CGitHash head;
271 pGitStatus->GetHeadHash(sProjectRoot,head);
273 bool isVersion =true;
274 pGitStatus->IsUnderVersionControl(sProjectRoot, subpaths, path.IsDirectory(), &isVersion);
275 if(!isVersion)
276 { //untracked file
277 bool isIgnoreFileChanged=false;
279 isIgnoreFileChanged = pGitStatus->IsGitReposChanged(sProjectRoot, subpaths, GIT_MODE_IGNORE);
281 if( isIgnoreFileChanged)
283 pGitStatus->LoadIgnoreFile(sProjectRoot, subpaths);
286 if(path.IsDirectory())
289 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path,
290 false); /* we needn't watch untracked directory*/
292 if(dirEntry)
294 AutoLocker lock(dirEntry->m_critSec);
296 git_wc_status_kind dirstatus = dirEntry->GetCurrentFullStatus() ;
297 if( dirstatus == git_wc_status_none || dirstatus >= git_wc_status_normal || isIgnoreFileChanged )
298 {/* status have not initialized*/
299 git_wc_status2_t status2;
300 bool isignore = false;
301 pGitStatus->IsIgnore(sProjectRoot,subpaths,&isignore);
302 status2.text_status = status2.prop_status =
303 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
305 dirEntry->m_ownStatus.SetStatus(&status2);
306 dirEntry->m_ownStatus.SetKind(git_node_dir);
309 return dirEntry->m_ownStatus;
312 }else /* path is file */
314 AutoLocker lock(m_critSec);
315 CString strCacheKey = GetCacheKey(path);
317 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
318 if(itMap == m_entryCache.end() || isIgnoreFileChanged)
320 git_wc_status2_t status2;
321 bool isignore = false;
322 pGitStatus->IsIgnore(sProjectRoot,subpaths,&isignore);
323 status2.text_status = status2.prop_status =
324 (isignore? git_wc_status_ignored:git_wc_status_unversioned);
325 AddEntry(path, &status2);
326 return m_entryCache[strCacheKey];
327 }else
329 return itMap->second;
332 return CStatusCacheEntry();
334 }else
336 EnumFiles((CTGitPath*)&path, TRUE);
337 return CStatusCacheEntry(git_wc_status_normal);
342 /// bFetch is true, fetch all status, call by crawl.
343 /// bFetch is false, get cache status, return quickly.
345 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */)
347 CString strCacheKey;
348 bool bRequestForSelf = false;
349 if(path.IsEquivalentToWithoutCase(m_directoryPath))
351 bRequestForSelf = true;
353 //OutputDebugStringA("GetStatusForMember: ");OutputDebugStringW(path.GetWinPathString());OutputDebugStringA("\r\n");
354 // In all most circumstances, we ask for the status of a member of this directory.
355 ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);
357 CString sProjectRoot;
358 const BOOL bIsVersionedPath = path.HasAdminDir(&sProjectRoot);
360 //If is not version control path
361 if( !bIsVersionedPath)
363 ATLTRACE(_T("%s is not underversion control\n"), path.GetWinPath());
364 return CStatusCacheEntry();
367 // We've not got this item in the cache - let's add it
368 // We never bother asking SVN for the status of just one file, always for its containing directory
370 if (g_GitAdminDir.IsAdminDirPath(path.GetWinPathString()))
372 // We're being asked for the status of an .git directory
373 // It's not worth asking for this
374 return CStatusCacheEntry();
378 if(bFetch)
380 GetStatusFromGit(path, sProjectRoot);
381 return CStatusCacheEntry();
382 }else
384 return GetStatusFromCache(path, bRecursive);
388 int CCachedDirectory::EnumFiles(CTGitPath *path , bool IsFull)
390 CString sProjectRoot;
391 if(path)
392 path->HasAdminDir(&sProjectRoot);
393 else
394 m_directoryPath.HasAdminDir(&sProjectRoot);
396 ATLTRACE(_T("EnumFiles %s\n"), path->GetWinPath());
398 ATLASSERT( !m_directoryPath.IsEmpty() );
400 CString sSubPath;
402 CString s;
403 if(path)
404 s=path->GetWinPath();
405 else
406 s=m_directoryPath.GetDirectory().GetWinPathString();
408 if (s.GetLength() > sProjectRoot.GetLength())
410 // skip initial slash if necessary
411 if(s[sProjectRoot.GetLength()] == _T('\\'))
412 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() -1);
413 else
414 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() );
417 GitStatus *pStatus = &CGitStatusCache::Instance().m_GitStatus;
418 UNREFERENCED_PARAMETER(pStatus);
419 git_wc_status_kind status;
421 if(!path->IsDirectory())
422 pStatus->GetFileStatus(sProjectRoot, sSubPath, &status, IsFull, false,true, GetStatusCallback,this);
423 else
425 m_mostImportantFileStatus = git_wc_status_unknown;
426 pStatus->EnumDirStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback,this);
429 m_mostImportantFileStatus = GitStatus::GetMoreImportant(m_mostImportantFileStatus, status);
431 return 0;
433 void
434 CCachedDirectory::AddEntry(const CTGitPath& path, const git_wc_status2_t* pGitStatus, DWORD validuntil /* = 0*/)
436 AutoLocker lock(m_critSec);
437 if(path.IsDirectory())
439 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
440 if (childDir)
442 if ((childDir->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
444 if(pGitStatus)
446 if(childDir->GetCurrentFullStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
448 CGitStatusCache::Instance().UpdateShell(path);
449 //ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
450 childDir->m_ownStatus.SetStatus(pGitStatus);
451 childDir->m_ownStatus.SetKind(git_node_dir);
455 childDir->m_ownStatus.SetKind(git_node_dir);
460 else
462 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path.GetContainingDirectory());
463 bool bNotified = false;
465 if(!childDir)
466 return ;
468 CString cachekey = GetCacheKey(path);
469 CacheEntryMap::iterator entry_it = childDir->m_entryCache.lower_bound(cachekey);
470 if (entry_it != childDir->m_entryCache.end() && entry_it->first == cachekey)
472 if (pGitStatus)
474 if (entry_it->second.GetEffectiveStatus() > git_wc_status_none &&
475 entry_it->second.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status)
478 bNotified =true;
483 else
485 entry_it = childDir->m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
486 bNotified = true;
489 entry_it->second = CStatusCacheEntry(pGitStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);
490 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
491 entry_it->second.SetKind(git_node_file);
493 if(bNotified)
495 CGitStatusCache::Instance().UpdateShell(path);
496 //ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
499 //ATLTRACE(_T("Path Entry Add %s %s %s %d\n"), path.GetWinPath(), cachekey, m_directoryPath.GetWinPath(), pGitStatus->text_status);
502 CCachedDirectory * parent = CGitStatusCache::Instance().GetDirectoryCacheEntry(path.GetContainingDirectory());
504 if(parent)
506 if ((parent->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
508 if(pGitStatus)
510 if(parent->GetCurrentFullStatus() < GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
512 CGitStatusCache::Instance().UpdateShell(parent->m_directoryPath);
513 //ATLTRACE(_T("shell update for %s\n"), parent->m_directoryPath.GetWinPathString());
514 parent->m_ownStatus.SetStatus(pGitStatus);
515 parent->m_ownStatus.SetKind(git_node_dir);
523 CString
524 CCachedDirectory::GetCacheKey(const CTGitPath& path)
526 // All we put into the cache as a key is just the end portion of the pathname
527 // There's no point storing the path of the containing directory for every item
528 return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength()).MakeLower();
531 CString
532 CCachedDirectory::GetFullPathString(const CString& cacheKey)
534 return m_directoryPath.GetWinPathString() + _T("\\") + cacheKey;
537 BOOL CCachedDirectory::GetStatusCallback(const CString & path, git_wc_status_kind status,bool isDir, void *pUserData)
539 git_wc_status2_t _status;
540 git_wc_status2_t *status2 = &_status;
542 status2->prop_status = status2->text_status = status;
544 CTGitPath gitPath;
546 CString lowcasepath = path;
547 lowcasepath.MakeLower();
548 gitPath.SetFromUnknown(lowcasepath);
550 CCachedDirectory *pThis = CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath.GetContainingDirectory());
552 if(pThis == NULL)
553 return FALSE;
555 // if(status->entry)
557 if (isDir)
558 { /*gitpath is directory*/
559 //if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
561 if (!gitPath.Exists())
563 ATLTRACE(_T("Miss dir %s \n"), gitPath.GetWinPath());
564 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_deleted);
567 if ( status < git_wc_status_normal)
569 if( ::PathFileExists(path+_T("\\.git")))
570 { // this is submodule
571 ATLTRACE(_T("skip submodule %s\n"), path);
572 return FALSE;
575 if (pThis->m_bRecursive)
577 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
578 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
579 if(status >= git_wc_status_normal)
580 if(status != git_wc_status_missing)
581 if(status != git_wc_status_deleted)
582 CGitStatusCache::Instance().AddFolderForCrawling(gitPath);
585 // Make sure we know about this child directory
586 // This initial status value is likely to be overwritten from below at some point
587 git_wc_status_kind s = GitStatus::GetMoreImportant(status2->text_status, status2->prop_status);
588 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(gitPath);
589 if (cdir)
591 // This child directory is already in our cache!
592 // So ask this dir about its recursive status
593 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
594 AutoLocker lock(pThis->m_critSec);
595 pThis->m_childDirectories[gitPath] = st;
596 ATLTRACE(_T("call 1 Update dir %s %d\n"), gitPath.GetWinPath(), st);
598 else
600 AutoLocker lock(pThis->m_critSec);
601 // the child directory is not in the cache. Create a new entry for it in the cache which is
602 // initially 'unversioned'. But we added that directory to the crawling list above, which
603 // means the cache will be updated soon.
604 CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath);
606 pThis->m_childDirectories[gitPath] = s;
607 ATLTRACE(_T("call 2 Update dir %s %d\n"), gitPath.GetWinPath(), s);
611 else /* gitpath is file*/
613 // Keep track of the most important status of all the files in this directory
614 // Don't include subdirectories in this figure, because they need to provide their
615 // own 'most important' value
616 if (status2->text_status == git_wc_status_deleted || status2->text_status == git_wc_status_added)
618 // if just a file in a folder is deleted or added report back that the folder is modified and not deleted or added
619 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
621 else
623 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->text_status);
624 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->prop_status);
625 if (((status2->text_status == git_wc_status_unversioned)||(status2->text_status == git_wc_status_none))
626 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
628 // treat unversioned files as modified
629 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
630 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
636 pThis->AddEntry(gitPath, status2);
638 return FALSE;
641 #if 0
642 git_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, git_wc_status2_t *status)
644 CCachedDirectory* pThis = (CCachedDirectory*)baton;
646 if (path == NULL)
647 return 0;
649 CTGitPath svnPath;
651 if(status->entry)
653 if ((status->text_status != git_wc_status_none)&&(status->text_status != git_wc_status_missing))
654 svnPath.SetFromSVN(path, (status->entry->kind == svn_node_dir));
655 else
656 svnPath.SetFromSVN(path);
658 if(svnPath.IsDirectory())
660 if(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))
662 if (pThis->m_bRecursive)
664 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
665 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
668 // Make sure we know about this child directory
669 // This initial status value is likely to be overwritten from below at some point
670 git_wc_status_kind s = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
671 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath);
672 if (cdir)
674 // This child directory is already in our cache!
675 // So ask this dir about its recursive status
676 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
677 AutoLocker lock(pThis->m_critSec);
678 pThis->m_childDirectories[svnPath] = st;
680 else
682 // the child directory is not in the cache. Create a new entry for it in the cache which is
683 // initially 'unversioned'. But we added that directory to the crawling list above, which
684 // means the cache will be updated soon.
685 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
686 AutoLocker lock(pThis->m_critSec);
687 pThis->m_childDirectories[svnPath] = s;
691 else
693 // Keep track of the most important status of all the files in this directory
694 // Don't include subdirectories in this figure, because they need to provide their
695 // own 'most important' value
696 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->text_status);
697 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->prop_status);
698 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_none))
699 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
701 // treat unversioned files as modified
702 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
703 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
707 else
709 svnPath.SetFromSVN(path);
710 // Subversion returns no 'entry' field for versioned folders if they're
711 // part of another working copy (nested layouts).
712 // So we have to make sure that such an 'unversioned' folder really
713 // is unversioned.
714 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_missing))&&(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))&&(svnPath.IsDirectory()))
716 if (svnPath.HasAdminDir())
718 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
719 // Mark the directory as 'versioned' (status 'normal' for now).
720 // This initial value will be overwritten from below some time later
722 AutoLocker lock(pThis->m_critSec);
723 pThis->m_childDirectories[svnPath] = git_wc_status_normal;
725 // Make sure the entry is also in the cache
726 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
727 // also mark the status in the status object as normal
728 status->text_status = git_wc_status_normal;
731 else if (status->text_status == git_wc_status_external)
733 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
734 // Mark the directory as 'versioned' (status 'normal' for now).
735 // This initial value will be overwritten from below some time later
737 AutoLocker lock(pThis->m_critSec);
738 pThis->m_childDirectories[svnPath] = git_wc_status_normal;
740 // we have added a directory to the child-directory list of this
741 // directory. We now must make sure that this directory also has
742 // an entry in the cache.
743 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
744 // also mark the status in the status object as normal
745 status->text_status = git_wc_status_normal;
747 else
749 if (svnPath.IsDirectory())
751 AutoLocker lock(pThis->m_critSec);
752 pThis->m_childDirectories[svnPath] = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
754 else if ((CGitStatusCache::Instance().IsUnversionedAsModified())&&(status->text_status != git_wc_status_missing))
756 // make this unversioned item change the most important status of this
757 // folder to modified if it doesn't already have another status
758 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
759 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
764 pThis->AddEntry(svnPath, status);
766 return 0;
768 #endif
770 bool
771 CCachedDirectory::IsOwnStatusValid() const
773 return m_ownStatus.HasBeenSet() &&
774 !m_ownStatus.HasExpired(GetTickCount()) &&
775 // 'external' isn't a valid status. That just
776 // means the folder is not part of the current working
777 // copy but it still has its own 'real' status
778 m_ownStatus.GetEffectiveStatus()!=git_wc_status_external &&
779 m_ownStatus.IsKindKnown();
782 void CCachedDirectory::Invalidate()
784 m_ownStatus.Invalidate();
787 git_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()
789 // Combine our OWN folder status with the most important of our *FILES'* status.
790 git_wc_status_kind retVal = GitStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());
792 // NOTE: TSVN marks dir as modified if it contains added/deleted/missing files, but we prefer the most important
793 // status to propagate upward in its original state
794 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
796 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
797 retVal = git_wc_status_modified;
800 // Now combine all our child-directorie's status
802 AutoLocker lock(m_critSec);
803 ChildDirStatus::const_iterator it;
804 for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)
806 retVal = GitStatus::GetMoreImportant(retVal, it->second);
807 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
809 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
810 retVal = git_wc_status_modified;
814 return retVal;
817 // Update our composite status and deal with things if it's changed
818 void CCachedDirectory::UpdateCurrentStatus()
820 git_wc_status_kind newStatus = CalculateRecursiveStatus();
821 ATLTRACE(_T("UpdateCurrentStatus %s new:%d old: %d\n"),
822 m_directoryPath.GetWinPath(),
823 newStatus, m_currentFullStatus);
825 if ( this->m_ownStatus.GetEffectiveStatus() < git_wc_status_normal )
827 if (::PathFileExists(this->m_directoryPath.GetWinPathString()+_T("\\.git")))
829 //project root must be normal status at least.
830 ATLTRACE(_T("force update project root directory as normal status\n"));
831 this->m_ownStatus.ForceStatus(git_wc_status_normal);
835 if ((newStatus != m_currentFullStatus) && m_ownStatus.IsVersioned())
837 if ((m_currentFullStatus != git_wc_status_none)&&(m_ownStatus.GetEffectiveStatus() != git_wc_status_missing))
839 // Our status has changed - tell the shell
840 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);
841 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
843 if (m_ownStatus.GetEffectiveStatus() != git_wc_status_missing)
844 m_currentFullStatus = newStatus;
845 else
846 m_currentFullStatus = git_wc_status_missing;
848 // And tell our parent, if we've got one...
849 // we tell our parent *always* about our status, even if it hasn't
850 // changed. This is to make sure that the parent has really our current
851 // status - the parent can decide itself if our status has changed
852 // or not.
853 CTGitPath parentPath = m_directoryPath.GetContainingDirectory();
854 if(!parentPath.IsEmpty())
856 // We have a parent
857 // just version controled directory need to cache.
858 CString root1, root2;
859 if(parentPath.HasAdminDir(&root1) && m_directoryPath.HasAdminDir(&root2) && root1 == root2)
861 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath);
862 if (cachedDir)
863 cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);
869 // Receive a notification from a child that its status has changed
870 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath& childDir, git_wc_status_kind childStatus)
872 git_wc_status_kind currentStatus = git_wc_status_none;
874 AutoLocker lock(m_critSec);
875 currentStatus = m_childDirectories[childDir];
877 if ((currentStatus != childStatus)||(!IsOwnStatusValid()))
880 AutoLocker lock(m_critSec);
881 m_childDirectories[childDir] = childStatus;
883 UpdateCurrentStatus();
887 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)
889 // Don't return recursive status if we're unversioned ourselves.
890 if(bRecursive && m_ownStatus.GetEffectiveStatus() > git_wc_status_unversioned)
892 CStatusCacheEntry recursiveStatus(m_ownStatus);
893 UpdateCurrentStatus();
894 recursiveStatus.ForceStatus(m_currentFullStatus);
895 return recursiveStatus;
897 else
899 return m_ownStatus;
903 void CCachedDirectory::RefreshStatus(bool bRecursive)
905 // Make sure that our own status is up-to-date
906 GetStatusForMember(m_directoryPath,bRecursive);
908 AutoLocker lock(m_critSec);
909 // We also need to check if all our file members have the right date on them
910 CacheEntryMap::iterator itMembers;
911 std::set<CTGitPath> refreshedpaths;
912 DWORD now = GetTickCount();
913 if (m_entryCache.size() == 0)
914 return;
915 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
917 if (itMembers->first)
919 CTGitPath filePath(m_directoryPath);
920 filePath.AppendPathString(itMembers->first);
921 std::set<CTGitPath>::iterator refr_it;
922 if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&
923 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))
925 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))
927 lock.Unlock();
928 // We need to request this item as well
929 GetStatusForMember(filePath,bRecursive);
930 // GetStatusForMember now has recreated the m_entryCache map.
931 // So start the loop again, but add this path to the refreshed paths set
932 // to make sure we don't refresh this path again. This is to make sure
933 // that we don't end up in an endless loop.
934 lock.Lock();
935 refreshedpaths.insert(refr_it, filePath);
936 itMembers = m_entryCache.begin();
937 if (m_entryCache.size()==0)
938 return;
939 continue;
941 else if ((bRecursive)&&(itMembers->second.IsDirectory()))
943 // crawl all sub folders too! Otherwise a change deep inside the
944 // tree which has changed won't get propagated up the tree.
945 CGitStatusCache::Instance().AddFolderForCrawling(filePath);
952 void CCachedDirectory::UpdateParentsStatus(const CTGitPath& path, git_wc_status_kind childStatus)
954 return ;
957 void CCachedDirectory::RefreshMostImportant()
959 CacheEntryMap::iterator itMembers;
960 git_wc_status_kind newStatus = m_ownStatus.GetEffectiveStatus();
961 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
963 newStatus = GitStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());
964 if (((itMembers->second.GetEffectiveStatus() == git_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == git_wc_status_none))
965 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
967 // treat unversioned files as modified
968 if (newStatus != git_wc_status_added)
969 newStatus = GitStatus::GetMoreImportant(newStatus, git_wc_status_modified);
972 if (newStatus != m_mostImportantFileStatus)
974 ATLTRACE(_T("status change of path %s\n"), m_directoryPath.GetWinPath());
975 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
977 m_mostImportantFileStatus = newStatus;