Fixed issue #401: TGitCache.exe keeps open pack-xxx.idx on git repo
[TortoiseGit.git] / src / TGitCache / CachedDirectory.cpp
blobd5f5aa70004d1c91b68eca4d9db4ec23102f3be0
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "StdAfx.h"
20 #include ".\cacheddirectory.h"
21 //#include "SVNHelpers.h"
22 #include "GitStatusCache.h"
23 #include "GitStatus.h"
24 #include <set>
26 CCachedDirectory::CCachedDirectory(void)
28 m_currentStatusFetchingPathTicks = 0;
29 m_bCurrentFullStatusValid = false;
30 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
31 m_bRecursive = true;
32 m_indexFileTime = 0;
33 m_FullStatusFetched = false;
36 CCachedDirectory::~CCachedDirectory(void)
40 CCachedDirectory::CCachedDirectory(const CTGitPath& directoryPath)
42 ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath()));
44 m_directoryPath = directoryPath;
46 m_currentStatusFetchingPathTicks = 0;
47 m_bCurrentFullStatusValid = false;
48 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_none;
49 m_bRecursive = true;
52 BOOL CCachedDirectory::SaveToDisk(FILE * pFile)
54 AutoLocker lock(m_critSec);
55 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) return false;
57 unsigned int value = GIT_CACHE_VERSION;
58 WRITEVALUETOFILE(value); // 'version' of this save-format
59 value = (int)m_entryCache.size();
60 WRITEVALUETOFILE(value); // size of the cache map
61 // now iterate through the maps and save every entry.
62 for (CacheEntryMap::iterator I = m_entryCache.begin(); I != m_entryCache.end(); ++I)
64 const CString& key = I->first;
65 value = key.GetLength();
66 WRITEVALUETOFILE(value);
67 if (value)
69 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
70 return false;
71 if (!I->second.SaveToDisk(pFile))
72 return false;
75 value = (int)m_childDirectories.size();
76 WRITEVALUETOFILE(value);
77 for (ChildDirStatus::iterator I = m_childDirectories.begin(); I != m_childDirectories.end(); ++I)
79 const CString& path = I->first.GetWinPathString();
80 value = path.GetLength();
81 WRITEVALUETOFILE(value);
82 if (value)
84 if (fwrite((LPCTSTR)path, sizeof(TCHAR), value, pFile)!=value)
85 return false;
86 git_wc_status_kind status = I->second;
87 WRITEVALUETOFILE(status);
90 WRITEVALUETOFILE(m_indexFileTime);
91 WRITEVALUETOFILE(m_Head.m_hash);
92 // WRITEVALUETOFILE(m_propsFileTime);
93 value = m_directoryPath.GetWinPathString().GetLength();
94 WRITEVALUETOFILE(value);
95 if (value)
97 if (fwrite(m_directoryPath.GetWinPath(), sizeof(TCHAR), value, pFile)!=value)
98 return false;
100 if (!m_ownStatus.SaveToDisk(pFile))
101 return false;
102 WRITEVALUETOFILE(m_currentFullStatus);
103 WRITEVALUETOFILE(m_mostImportantFileStatus);
104 return true;
107 BOOL CCachedDirectory::LoadFromDisk(FILE * pFile)
109 AutoLocker lock(m_critSec);
110 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) return false;
113 unsigned int value = 0;
114 LOADVALUEFROMFILE(value);
115 if (value != GIT_CACHE_VERSION)
116 return false; // not the correct version
117 int mapsize = 0;
118 LOADVALUEFROMFILE(mapsize);
119 for (int i=0; i<mapsize; ++i)
121 LOADVALUEFROMFILE(value);
122 if (value > MAX_PATH)
123 return false;
124 if (value)
126 CString sKey;
127 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
129 sKey.ReleaseBuffer(0);
130 return false;
132 sKey.ReleaseBuffer(value);
133 CStatusCacheEntry entry;
134 if (!entry.LoadFromDisk(pFile))
135 return false;
136 m_entryCache[sKey] = entry;
139 LOADVALUEFROMFILE(mapsize);
140 for (int i=0; i<mapsize; ++i)
142 LOADVALUEFROMFILE(value);
143 if (value > MAX_PATH)
144 return false;
145 if (value)
147 CString sPath;
148 if (fread(sPath.GetBuffer(value), sizeof(TCHAR), value, pFile)!=value)
150 sPath.ReleaseBuffer(0);
151 return false;
153 sPath.ReleaseBuffer(value);
154 git_wc_status_kind status;
155 LOADVALUEFROMFILE(status);
156 m_childDirectories[CTGitPath(sPath)] = status;
159 LOADVALUEFROMFILE(m_indexFileTime);
160 LOADVALUEFROMFILE(m_Head.m_hash);
161 // LOADVALUEFROMFILE(m_propsFileTime);
162 LOADVALUEFROMFILE(value);
163 if (value > MAX_PATH)
164 return false;
165 if (value)
167 CString sPath;
168 if (fread(sPath.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
170 sPath.ReleaseBuffer(0);
171 return false;
173 sPath.ReleaseBuffer(value);
174 m_directoryPath.SetFromWin(sPath);
176 if (!m_ownStatus.LoadFromDisk(pFile))
177 return false;
179 LOADVALUEFROMFILE(m_currentFullStatus);
180 LOADVALUEFROMFILE(m_mostImportantFileStatus);
182 catch ( CAtlException )
184 return false;
186 return true;
189 /// bFetch is true, fetch all status, call by crawl.
190 /// bFetch is false, get cache status, return quickly.
192 CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTGitPath& path, bool bRecursive, bool bFetch /* = true */)
194 CString strCacheKey;
195 bool bThisDirectoryIsUnversioned = false;
196 bool bRequestForSelf = false;
197 if(path.IsEquivalentToWithoutCase(m_directoryPath))
199 bRequestForSelf = true;
201 //OutputDebugStringA("GetStatusForMember: ");OutputDebugStringW(path.GetWinPathString());OutputDebugStringA("\r\n");
202 // In all most circumstances, we ask for the status of a member of this directory.
203 ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf);
205 CString sProjectRoot;
206 const BOOL bIsVersionedPath = path.HasAdminDir(&sProjectRoot);
208 ATLTRACE(_T("%d GetStatusForMember for %s\n"),bFetch, path.GetWinPath());
210 //If is not version control path
211 if( !bIsVersionedPath)
213 ATLTRACE(_T("%s is not underversion control\n"), path.GetWinPath());
214 return CStatusCacheEntry();
217 // We've not got this item in the cache - let's add it
218 // We never bother asking SVN for the status of just one file, always for its containing directory
220 if (g_GitAdminDir.IsAdminDirPath(path.GetWinPathString()))
222 // We're being asked for the status of an .git directory
223 // It's not worth asking for this
224 return CStatusCacheEntry();
227 CString subpaths = path.GetGitPathString();
228 if(subpaths.GetLength() >= sProjectRoot.GetLength())
230 if(subpaths[sProjectRoot.GetLength()] == _T('/'))
231 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength()-1);
232 else
233 subpaths=subpaths.Right(subpaths.GetLength() - sProjectRoot.GetLength());
237 GitStatus *pGitStatus = &CGitStatusCache::Instance().m_GitStatus;
238 bool IsIndexHeadChanged = pGitStatus->IsGitReposChanged(sProjectRoot, subpaths, GIT_MODE_INDEX);
240 CGitHash head;
242 pGitStatus->GetHeadHash(sProjectRoot,head);
244 bool isVersion =true;
245 pGitStatus->IsUnderVersionControl(sProjectRoot, subpaths, path.IsDirectory(), &isVersion);
247 // Handle Untrack file and directory
248 //===================================
249 if(!isVersion)
251 /* if path is not under version control */
252 /* Just need check if it is ignore, it is very quick */
253 bool isIgnoreFileChanged=false;
255 isIgnoreFileChanged = pGitStatus->IsGitReposChanged(sProjectRoot, subpaths, GIT_MODE_IGNORE);
257 if( isIgnoreFileChanged)
259 pGitStatus->LoadIgnoreFile(sProjectRoot, subpaths);
262 std::vector<__int64> timelist;
263 pGitStatus->GetIgnoreFileChangeTimeList((CString &)this->m_directoryPath.GetWinPathString(), timelist);
265 if(timelist.size() != this->m_IgnoreFileTimeList.size())
266 isIgnoreFileChanged =true;
268 if(!isIgnoreFileChanged)
269 for(int i=0;i<timelist.size();i++)
270 if(timelist[i] != m_IgnoreFileTimeList[i])
272 isIgnoreFileChanged =true;
273 break;
276 m_IgnoreFileTimeList = timelist;
278 if(isIgnoreFileChanged)
279 m_entryCache.clear();
281 ATLTRACE(_T("Ignore Changed %d for %s\n"), isIgnoreFileChanged, path.GetWinPath());
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.SetStatus(&status2);
305 return dirEntry->m_ownStatus;
308 }else /* path is file */
310 AutoLocker lock(m_critSec);
311 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];
323 }else
325 return itMap->second;
328 return CStatusCacheEntry();
332 /// Handle track file and directory
333 //==================================
334 /* Get Status from git */
335 if( (head == m_Head) && (m_indexFileTime==pGitStatus->GetIndexFileTime(sProjectRoot)))
337 if(bFetch && !m_FullStatusFetched)
339 /* If crawl, fetch new updated status any way*/
340 EnumFiles();
341 m_FullStatusFetched = true;
344 if(path.IsDirectory())
346 // We don't have directory status in our cache
347 // Ask the directory if it knows its own status
349 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
350 if( dirEntry)
352 if (dirEntry->IsOwnStatusValid())
353 return dirEntry->GetOwnStatus(bRecursive);
354 else
356 /* cache have outof date, need crawl again*/
357 AutoLocker lock(dirEntry->m_critSec);
358 ChildDirStatus::const_iterator it;
359 for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it)
361 CGitStatusCache::Instance().AddFolderForCrawling(it->first);
366 if(bFetch)
368 ATLTRACE(_T("Clear Dir %s mostImportantFileStatus\n"), path.GetWinPath());
369 AutoLocker lock(m_critSec);
370 m_mostImportantFileStatus = git_wc_status_normal; /* unverstion file have handle at begin */
371 m_childDirectories.clear();
372 m_entryCache.clear();
373 m_ownStatus.SetStatus(NULL);
374 m_bRecursive = bRecursive;
376 EnumFiles();
377 m_FullStatusFetched = true;
378 }else
380 CGitStatusCache::Instance().AddFolderForCrawling(path);
382 return CStatusCacheEntry(this->m_ownStatus);
384 else
386 // Get File status;
387 // because all untrack file have handle at first, only tracked file status got to here
388 git_wc_status_kind status = git_wc_status_normal;
390 // if we currently are fetching the status of the directory
391 // we want the status for, we just return an empty entry here
392 // and don't wait for that fetching to finish.
393 // That's because fetching the status can take a *really* long
394 // time (e.g. if a commit is also in progress on that same
395 // directory), and we don't want to make the explorer appear
396 // to hang.
397 AutoLocker pathlock(m_critSecPath);
398 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
400 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
402 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
403 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_normal;
404 return CStatusCacheEntry(git_wc_status_normal);
408 // Look up a file in our own cache
409 AutoLocker lock(m_critSec);
410 strCacheKey = GetCacheKey(path);
411 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
412 if(itMap != m_entryCache.end())
414 // We've hit the cache - check for timeout
415 if(!itMap->second.HasExpired((long)GetTickCount()))
417 if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime()))
419 if ((itMap->second.GetEffectiveStatus()!=git_wc_status_missing)||(!PathFileExists(path.GetWinPath())))
421 // Note: the filetime matches after a modified has been committed too.
422 // So in that case, we would return a wrong status (e.g. 'modified' instead
423 // of 'normal') here.
424 return itMap->second;
430 if(bFetch)
432 //update single full file status
433 ATLTRACE(_T("Fetch file status %s\n"), path.GetWinPathString());
434 pGitStatus->GetFileStatus(sProjectRoot, subpaths, &status, true, false,true, GetStatusCallback,this);
435 }else
437 /* get status very quickly
438 Full status may load Head Tree.
440 pGitStatus->GetFileStatus(sProjectRoot, subpaths, &status, false, false,true, NULL,NULL);
441 CGitStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory());
442 m_FullStatusFetched = false;
444 return CStatusCacheEntry(status);
447 else /* Index or Head file changed*/
449 /* Clear all cache status */
451 AutoLocker pathlock(m_critSecPath);
452 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
454 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
456 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
457 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_normal;
458 return CStatusCacheEntry(git_wc_status_normal);
462 // if we're fetching the status for the explorer,
463 // we don't refresh the status but use the one
464 // we already have (to save time and make the explorer
465 // more responsive in stress conditions).
466 // We leave the refreshing to the crawler.
467 if ((!bFetch))
469 CGitStatusCache::Instance().AddFolderForCrawling(path.GetDirectory());
473 m_indexFileTime = pGitStatus->GetIndexFileTime(sProjectRoot);
474 m_Head = head;
475 m_FullStatusFetched = false;
477 AutoLocker lock(m_critSec);
478 // m_indexFileTime = indexFilePath.GetLastWriteTime();
479 // m_propsFileTime = propsDirPath.GetLastWriteTime();
480 m_entryCache.clear();
481 strCacheKey = GetCacheKey(path);
483 git_wc_status2_t status2;
484 status2.prop_status = status2.text_status = git_wc_status_normal;
485 this->m_mostImportantFileStatus = this->m_currentFullStatus = git_wc_status_normal;
486 m_ownStatus.SetStatus(&status2);
491 // Cache status have been invalidate, head or index changed.
492 // =====================================================
495 AutoLocker pathlock(m_critSecPath);
496 if ((!bFetch)&&(!m_currentStatusFetchingPath.IsEmpty()))
498 if ((m_currentStatusFetchingPath.IsAncestorOf(path))&&((m_currentStatusFetchingPathTicks + 1000)<GetTickCount()))
500 ATLTRACE(_T("returning empty status (status fetch in progress) for %s\n"), path.GetWinPath());
501 m_currentFullStatus = m_mostImportantFileStatus = git_wc_status_normal;
502 return CStatusCacheEntry(git_wc_status_normal);
508 /* If bFetch false, means get basic status as soon as possible.
509 If bFetch false, means get full status from crawl thread
510 when bfetch false, just fetch status according to index file.
511 can't fetch HEAD tree. fetch HEAD tree will take some long time
513 BOOL pErr = EnumFiles(NULL, bFetch);
516 AutoLocker pathlock(m_critSecPath);
517 m_currentStatusFetchingPath.Reset();
520 ATLTRACE(_T("git_enum_files finished for '%s'\n"), m_directoryPath.GetWinPath(), path.GetWinPath());
523 // Now that we've refreshed our SVN status, we can see if it's
524 // changed the 'most important' status value for this directory.
525 // If it has, then we should tell our parent
526 if(bFetch)
527 UpdateCurrentStatus();
529 if (path.IsDirectory())
531 CCachedDirectory * dirEntry = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
532 if ((dirEntry)&&(dirEntry->IsOwnStatusValid()))
534 /// status should be valid, needn't add to crawl.
535 // CGitStatusCache::Instance().AddFolderForCrawling(path);
536 return dirEntry->GetOwnStatus(bRecursive);
539 // If the status *still* isn't valid here, it means that
540 // the current directory is unversioned, and we shall need to ask its children for info about themselves
541 //if (dirEntry)
542 // return dirEntry->GetStatusForMember(path,bRecursive);
544 CGitStatusCache::Instance().AddFolderForCrawling(path);
545 return CStatusCacheEntry(git_wc_status_normal);;
547 else
549 CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey);
550 if(itMap != m_entryCache.end())
552 return itMap->second;
556 return CStatusCacheEntry(git_wc_status_normal);
559 int CCachedDirectory::EnumFiles(CTGitPath *path , bool IsFull)
561 CString sProjectRoot;
562 if(path)
563 path->HasAdminDir(&sProjectRoot);
564 else
565 m_directoryPath.HasAdminDir(&sProjectRoot);
567 ATLASSERT( !m_directoryPath.IsEmpty() );
569 LPCTSTR lpszSubPath = NULL;
570 CString sSubPath;
572 CString s;
573 if(path)
574 s=path->GetWinPath();
575 else
576 s=m_directoryPath.GetDirectory().GetWinPathString();
578 if (s.GetLength() > sProjectRoot.GetLength())
580 // skip initial slash if necessary
581 if(s[sProjectRoot.GetLength()] == _T('\\'))
582 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() -1);
583 else
584 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() );
587 GitStatus *pStatus = &CGitStatusCache::Instance().m_GitStatus;
588 git_wc_status_kind status;
590 if(path)
591 pStatus->GetFileStatus(sProjectRoot, sSubPath, &status, IsFull, false,true, GetStatusCallback,this);
592 else
593 pStatus->EnumDirStatus(sProjectRoot, sSubPath, &status, IsFull, false, true, GetStatusCallback,this);
595 m_mostImportantFileStatus = GitStatus::GetMoreImportant(m_mostImportantFileStatus, status);
597 return 0;
599 void
600 CCachedDirectory::AddEntry(const CTGitPath& path, const git_wc_status2_t* pGitStatus, DWORD validuntil /* = 0*/)
602 AutoLocker lock(m_critSec);
603 if(path.IsDirectory())
605 CCachedDirectory * childDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(path);
606 if (childDir)
608 if ((childDir->GetCurrentFullStatus() != git_wc_status_missing)||(pGitStatus==NULL)||(pGitStatus->text_status != git_wc_status_unversioned))
610 if(pGitStatus)
612 if(childDir->GetCurrentFullStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
614 CGitStatusCache::Instance().UpdateShell(path);
615 ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
618 childDir->m_ownStatus.SetStatus(pGitStatus);
620 childDir->m_ownStatus.SetKind(git_node_dir);
625 else
627 if(!(this->m_directoryPath == path.GetContainingDirectory()))
629 ATLTRACE(_T("parent path miss\n"));
631 CString cachekey = GetCacheKey(path);
632 CacheEntryMap::iterator entry_it = m_entryCache.lower_bound(cachekey);
633 if (entry_it != m_entryCache.end() && entry_it->first == cachekey)
635 if (pGitStatus)
637 if (entry_it->second.GetEffectiveStatus() > git_wc_status_none &&
638 entry_it->second.GetEffectiveStatus() != GitStatus::GetMoreImportant(pGitStatus->prop_status, pGitStatus->text_status))
640 CGitStatusCache::Instance().UpdateShell(path);
641 ATLTRACE(_T("shell update for %s\n"), path.GetWinPath());
645 else
647 entry_it = m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry()));
649 entry_it->second = CStatusCacheEntry(pGitStatus, path.GetLastWriteTime(), path.IsReadOnly(), validuntil);
650 // TEMP(?): git status doesn't not have "entry" that contains node type, so manually set as file
651 entry_it->second.SetKind(git_node_file);
653 ATLTRACE(_T("Path Entry Add %s %s %s %d\n"), path.GetWinPath(), cachekey, m_directoryPath.GetWinPath(), pGitStatus->text_status);
658 CString
659 CCachedDirectory::GetCacheKey(const CTGitPath& path)
661 // All we put into the cache as a key is just the end portion of the pathname
662 // There's no point storing the path of the containing directory for every item
663 return path.GetWinPathString().Mid(m_directoryPath.GetWinPathString().GetLength());
666 CString
667 CCachedDirectory::GetFullPathString(const CString& cacheKey)
669 return m_directoryPath.GetWinPathString() + _T("\\") + cacheKey;
672 BOOL CCachedDirectory::GetStatusCallback(CString & path, git_wc_status_kind status,bool isDir, void *pUserData)
674 CCachedDirectory* pThis = (CCachedDirectory*)pUserData;
676 git_wc_status2_t _status;
677 git_wc_status2_t *status2 = &_status;
679 status2->prop_status = status2->text_status = status;
681 CTGitPath gitPath;
684 // if(status->entry)
686 //if ((status->text_status != git_wc_status_none)&&(status->text_status != git_wc_status_missing))
687 gitPath.SetFromUnknown(path);
688 //status.SetFromGit(path, pFile->nFlags & WGFF_Directory);
689 /*else
690 svnPath.SetFromGit(path);*/
692 if (isDir)
693 { /*gitpath is directory*/
694 if ( !gitPath.IsEquivalentToWithoutCase(pThis->m_directoryPath) )
696 if (!gitPath.Exists())
698 ATLTRACE(_T("Miss dir %s \n"), gitPath.GetWinPath());
699 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_deleted);
702 if (pThis->m_bRecursive)
704 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
705 //OutputDebugStringA("AddFolderCrawl: ");OutputDebugStringW(svnPath.GetWinPathString());OutputDebugStringA("\r\n");
706 if(status >= git_wc_status_normal)
707 if(status != git_wc_status_missing)
708 if(status != git_wc_status_deleted)
709 CGitStatusCache::Instance().AddFolderForCrawling(gitPath);
712 // Make sure we know about this child directory
713 // This initial status value is likely to be overwritten from below at some point
714 git_wc_status_kind s = GitStatus::GetMoreImportant(status2->text_status, status2->prop_status);
715 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(gitPath);
716 if (cdir)
718 // This child directory is already in our cache!
719 // So ask this dir about its recursive status
720 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
721 AutoLocker lock(pThis->m_critSec);
722 pThis->m_childDirectories[gitPath] = st;
723 ATLTRACE(_T("call 1 Update dir %s %d\n"), gitPath.GetWinPath(), st);
725 else
727 AutoLocker lock(pThis->m_critSec);
728 // the child directory is not in the cache. Create a new entry for it in the cache which is
729 // initially 'unversioned'. But we added that directory to the crawling list above, which
730 // means the cache will be updated soon.
731 CGitStatusCache::Instance().GetDirectoryCacheEntry(gitPath);
733 pThis->m_childDirectories[gitPath] = s;
734 ATLTRACE(_T("call 2 Update dir %s %d\n"), gitPath.GetWinPath(), s);
738 else /* gitpath is file*/
740 // Keep track of the most important status of all the files in this directory
741 // Don't include subdirectories in this figure, because they need to provide their
742 // own 'most important' value
743 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->text_status);
744 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status2->prop_status);
745 if (((status2->text_status == git_wc_status_unversioned)||(status2->text_status == git_wc_status_none))
746 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
748 // treat unversioned files as modified
749 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
750 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
755 pThis->AddEntry(gitPath, status2);
757 return FALSE;
760 #if 0
761 git_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, git_wc_status2_t *status)
763 CCachedDirectory* pThis = (CCachedDirectory*)baton;
765 if (path == NULL)
766 return 0;
768 CTGitPath svnPath;
770 if(status->entry)
772 if ((status->text_status != git_wc_status_none)&&(status->text_status != git_wc_status_missing))
773 svnPath.SetFromSVN(path, (status->entry->kind == svn_node_dir));
774 else
775 svnPath.SetFromSVN(path);
777 if(svnPath.IsDirectory())
779 if(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))
781 if (pThis->m_bRecursive)
783 // Add any versioned directory, which is not our 'self' entry, to the list for having its status updated
784 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
787 // Make sure we know about this child directory
788 // This initial status value is likely to be overwritten from below at some point
789 git_wc_status_kind s = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
790 CCachedDirectory * cdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath);
791 if (cdir)
793 // This child directory is already in our cache!
794 // So ask this dir about its recursive status
795 git_wc_status_kind st = GitStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus());
796 AutoLocker lock(pThis->m_critSec);
797 pThis->m_childDirectories[svnPath] = st;
799 else
801 // the child directory is not in the cache. Create a new entry for it in the cache which is
802 // initially 'unversioned'. But we added that directory to the crawling list above, which
803 // means the cache will be updated soon.
804 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
805 AutoLocker lock(pThis->m_critSec);
806 pThis->m_childDirectories[svnPath] = s;
810 else
812 // Keep track of the most important status of all the files in this directory
813 // Don't include subdirectories in this figure, because they need to provide their
814 // own 'most important' value
815 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->text_status);
816 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, status->prop_status);
817 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_none))
818 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
820 // treat unversioned files as modified
821 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
822 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
826 else
828 svnPath.SetFromSVN(path);
829 // Subversion returns no 'entry' field for versioned folders if they're
830 // part of another working copy (nested layouts).
831 // So we have to make sure that such an 'unversioned' folder really
832 // is unversioned.
833 if (((status->text_status == git_wc_status_unversioned)||(status->text_status == git_wc_status_missing))&&(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))&&(svnPath.IsDirectory()))
835 if (svnPath.HasAdminDir())
837 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
838 // Mark the directory as 'versioned' (status 'normal' for now).
839 // This initial value will be overwritten from below some time later
841 AutoLocker lock(pThis->m_critSec);
842 pThis->m_childDirectories[svnPath] = git_wc_status_normal;
844 // Make sure the entry is also in the cache
845 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
846 // also mark the status in the status object as normal
847 status->text_status = git_wc_status_normal;
850 else if (status->text_status == git_wc_status_external)
852 CGitStatusCache::Instance().AddFolderForCrawling(svnPath);
853 // Mark the directory as 'versioned' (status 'normal' for now).
854 // This initial value will be overwritten from below some time later
856 AutoLocker lock(pThis->m_critSec);
857 pThis->m_childDirectories[svnPath] = git_wc_status_normal;
859 // we have added a directory to the child-directory list of this
860 // directory. We now must make sure that this directory also has
861 // an entry in the cache.
862 CGitStatusCache::Instance().GetDirectoryCacheEntry(svnPath);
863 // also mark the status in the status object as normal
864 status->text_status = git_wc_status_normal;
866 else
868 if (svnPath.IsDirectory())
870 AutoLocker lock(pThis->m_critSec);
871 pThis->m_childDirectories[svnPath] = GitStatus::GetMoreImportant(status->text_status, status->prop_status);
873 else if ((CGitStatusCache::Instance().IsUnversionedAsModified())&&(status->text_status != git_wc_status_missing))
875 // make this unversioned item change the most important status of this
876 // folder to modified if it doesn't already have another status
877 if (pThis->m_mostImportantFileStatus != git_wc_status_added)
878 pThis->m_mostImportantFileStatus = GitStatus::GetMoreImportant(pThis->m_mostImportantFileStatus, git_wc_status_modified);
883 pThis->AddEntry(svnPath, status);
885 return 0;
887 #endif
889 bool
890 CCachedDirectory::IsOwnStatusValid() const
892 return m_ownStatus.HasBeenSet() &&
893 !m_ownStatus.HasExpired(GetTickCount()) &&
894 // 'external' isn't a valid status. That just
895 // means the folder is not part of the current working
896 // copy but it still has its own 'real' status
897 m_ownStatus.GetEffectiveStatus()!=git_wc_status_external &&
898 m_ownStatus.IsKindKnown();
901 void CCachedDirectory::Invalidate()
903 m_ownStatus.Invalidate();
906 git_wc_status_kind CCachedDirectory::CalculateRecursiveStatus()
908 // Combine our OWN folder status with the most important of our *FILES'* status.
909 git_wc_status_kind retVal = GitStatus::GetMoreImportant(m_mostImportantFileStatus, m_ownStatus.GetEffectiveStatus());
911 // NOTE: TSVN marks dir as modified if it contains added/deleted/missing files, but we prefer the most important
912 // status to propagate upward in its original state
913 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
915 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
916 retVal = git_wc_status_modified;
919 // Now combine all our child-directorie's status
921 AutoLocker lock(m_critSec);
922 ChildDirStatus::const_iterator it;
923 for(it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it)
925 retVal = GitStatus::GetMoreImportant(retVal, it->second);
926 /*if ((retVal != git_wc_status_modified)&&(retVal != m_ownStatus.GetEffectiveStatus()))
928 if ((retVal == git_wc_status_added)||(retVal == git_wc_status_deleted)||(retVal == git_wc_status_missing))
929 retVal = git_wc_status_modified;
933 return retVal;
936 // Update our composite status and deal with things if it's changed
937 void CCachedDirectory::UpdateCurrentStatus()
939 git_wc_status_kind newStatus = CalculateRecursiveStatus();
940 ATLTRACE(_T("UpdateCurrentStatus %s new:%d old: %d\n"),
941 m_directoryPath.GetWinPath(),
942 newStatus, m_currentFullStatus);
943 if ((newStatus != m_currentFullStatus)&&(m_ownStatus.IsVersioned()))
945 if ((m_currentFullStatus != git_wc_status_none)&&(m_ownStatus.GetEffectiveStatus() != git_wc_status_missing))
947 // Our status has changed - tell the shell
948 ATLTRACE(_T("Dir %s, status change from %d to %d, send shell notification\n"), m_directoryPath.GetWinPath(), m_currentFullStatus, newStatus);
949 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
951 if (m_ownStatus.GetEffectiveStatus() != git_wc_status_missing)
952 m_currentFullStatus = newStatus;
953 else
954 m_currentFullStatus = git_wc_status_missing;
956 // And tell our parent, if we've got one...
957 // we tell our parent *always* about our status, even if it hasn't
958 // changed. This is to make sure that the parent has really our current
959 // status - the parent can decide itself if our status has changed
960 // or not.
961 CTGitPath parentPath = m_directoryPath.GetContainingDirectory();
962 if(!parentPath.IsEmpty())
964 // We have a parent
965 // just version controled directory need to cache.
966 if(parentPath.HasAdminDir())
968 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(parentPath);
969 if (cachedDir)
970 cachedDir->UpdateChildDirectoryStatus(m_directoryPath, m_currentFullStatus);
976 // Receive a notification from a child that its status has changed
977 void CCachedDirectory::UpdateChildDirectoryStatus(const CTGitPath& childDir, git_wc_status_kind childStatus)
979 git_wc_status_kind currentStatus = git_wc_status_none;
981 AutoLocker lock(m_critSec);
982 currentStatus = m_childDirectories[childDir];
984 if ((currentStatus != childStatus)||(!IsOwnStatusValid()))
987 AutoLocker lock(m_critSec);
988 m_childDirectories[childDir] = childStatus;
990 UpdateCurrentStatus();
994 CStatusCacheEntry CCachedDirectory::GetOwnStatus(bool bRecursive)
996 // Don't return recursive status if we're unversioned ourselves.
997 if(bRecursive && m_ownStatus.GetEffectiveStatus() > git_wc_status_unversioned)
999 CStatusCacheEntry recursiveStatus(m_ownStatus);
1000 UpdateCurrentStatus();
1001 recursiveStatus.ForceStatus(m_currentFullStatus);
1002 return recursiveStatus;
1004 else
1006 return m_ownStatus;
1010 void CCachedDirectory::RefreshStatus(bool bRecursive)
1012 // Make sure that our own status is up-to-date
1013 GetStatusForMember(m_directoryPath,bRecursive);
1015 AutoLocker lock(m_critSec);
1016 // We also need to check if all our file members have the right date on them
1017 CacheEntryMap::iterator itMembers;
1018 std::set<CTGitPath> refreshedpaths;
1019 DWORD now = GetTickCount();
1020 if (m_entryCache.size() == 0)
1021 return;
1022 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
1024 if (itMembers->first)
1026 CTGitPath filePath(m_directoryPath);
1027 filePath.AppendPathString(itMembers->first);
1028 std::set<CTGitPath>::iterator refr_it;
1029 if ((!filePath.IsEquivalentToWithoutCase(m_directoryPath))&&
1030 (((refr_it = refreshedpaths.lower_bound(filePath)) == refreshedpaths.end()) || !filePath.IsEquivalentToWithoutCase(*refr_it)))
1032 if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(filePath.GetLastWriteTime())))
1034 lock.Unlock();
1035 // We need to request this item as well
1036 GetStatusForMember(filePath,bRecursive);
1037 // GetStatusForMember now has recreated the m_entryCache map.
1038 // So start the loop again, but add this path to the refreshed paths set
1039 // to make sure we don't refresh this path again. This is to make sure
1040 // that we don't end up in an endless loop.
1041 lock.Lock();
1042 refreshedpaths.insert(refr_it, filePath);
1043 itMembers = m_entryCache.begin();
1044 if (m_entryCache.size()==0)
1045 return;
1046 continue;
1048 else if ((bRecursive)&&(itMembers->second.IsDirectory()))
1050 // crawl all sub folders too! Otherwise a change deep inside the
1051 // tree which has changed won't get propagated up the tree.
1052 CGitStatusCache::Instance().AddFolderForCrawling(filePath);
1059 void CCachedDirectory::RefreshMostImportant()
1061 CacheEntryMap::iterator itMembers;
1062 git_wc_status_kind newStatus = m_ownStatus.GetEffectiveStatus();
1063 for (itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers)
1065 newStatus = GitStatus::GetMoreImportant(newStatus, itMembers->second.GetEffectiveStatus());
1066 if (((itMembers->second.GetEffectiveStatus() == git_wc_status_unversioned)||(itMembers->second.GetEffectiveStatus() == git_wc_status_none))
1067 &&(CGitStatusCache::Instance().IsUnversionedAsModified()))
1069 // treat unversioned files as modified
1070 if (newStatus != git_wc_status_added)
1071 newStatus = GitStatus::GetMoreImportant(newStatus, git_wc_status_modified);
1074 if (newStatus != m_mostImportantFileStatus)
1076 ATLTRACE(_T("status change of path %s\n"), m_directoryPath.GetWinPath());
1077 CGitStatusCache::Instance().UpdateShell(m_directoryPath);
1079 m_mostImportantFileStatus = newStatus;