1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2006,2008,2010,2014 - TortoiseSVN
4 // Copyright (C) 2008-2014 - 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.
22 #include "GitStatus.h"
23 #include "GitStatusCache.h"
24 #include "CacheInterface.h"
27 #include "PathUtils.h"
29 //////////////////////////////////////////////////////////////////////////
31 #define BLOCK_PATH_DEFAULT_TIMEOUT 600 // 10 minutes
32 #define BLOCK_PATH_MAX_TIMEOUT 1200 // 20 minutes
34 #define CACHEDISKVERSION 2
37 #define STATUSCACHEFILENAME L"cache64"
39 #define STATUSCACHEFILENAME L"cache"
42 CGitStatusCache
* CGitStatusCache::m_pInstance
;
44 CGitStatusCache
& CGitStatusCache::Instance()
46 ATLASSERT(m_pInstance
!= NULL
);
50 void CGitStatusCache::Create()
52 ATLASSERT(m_pInstance
== NULL
);
53 m_pInstance
= new CGitStatusCache
;
55 m_pInstance
->watcher
.SetFolderCrawler(&m_pInstance
->m_folderCrawler
);
57 if (!CRegStdDWORD(_T("Software\\TortoiseGit\\CacheSave"), TRUE
))
60 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto exit;
61 #define LOADVALUEFROMFILE2(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto error;
62 unsigned int value
= (unsigned int)-1;
64 // find the location of the cache
65 CString path
= CPathUtils::GetLocalAppDataDirectory();
69 path
+= STATUSCACHEFILENAME
;
70 // in case the cache file is corrupt, we could crash while
71 // reading it! To prevent crashing every time once that happens,
72 // we make a copy of the cache file and use that copy to read from.
73 // if that copy is corrupt, the original file won't exist anymore
74 // and the second time we start up and try to read the file,
75 // it's not there anymore and we start from scratch without a crash.
79 CopyFile(path
, path2
, FALSE
);
81 pFile
= _tfsopen(path2
, _T("rb"), _SH_DENYNO
);
86 LOADVALUEFROMFILE(value
);
87 if (value
!= CACHEDISKVERSION
)
92 LOADVALUEFROMFILE(mapsize
);
93 for (int i
=0; i
<mapsize
; ++i
)
95 LOADVALUEFROMFILE2(value
);
101 if (fread(sKey
.GetBuffer(value
+1), sizeof(TCHAR
), value
, pFile
)!=value
)
103 sKey
.ReleaseBuffer(0);
106 sKey
.ReleaseBuffer(value
);
107 std::unique_ptr
<CCachedDirectory
> cacheddir (new CCachedDirectory());
108 if (!cacheddir
.get() || !cacheddir
->LoadFromDisk(pFile
))
113 CTGitPath KeyPath
= CTGitPath(sKey
);
114 if (m_pInstance
->IsPathAllowed(KeyPath
))
116 // only add the path to the watch list if it is versioned
117 if ((cacheddir
->GetCurrentFullStatus() != git_wc_status_unversioned
)&&(cacheddir
->GetCurrentFullStatus() != git_wc_status_none
))
118 m_pInstance
->watcher
.AddPath(KeyPath
, false);
120 m_pInstance
->m_directoryCache
[KeyPath
] = cacheddir
.release();
122 // do *not* add the paths for crawling!
123 // because crawled paths will trigger a shell
124 // notification, which makes the desktop flash constantly
125 // until the whole first time crawling is over
126 // m_pInstance->AddFolderForCrawling(KeyPath);
131 catch (CAtlException
)
141 m_pInstance
->watcher
.ClearInfoMap();
142 CTraceToOutputDebugString::Instance()(__FUNCTION__
": cache loaded from disk successfully!\n");
148 m_pInstance
->watcher
.ClearInfoMap();
150 m_pInstance
= new CGitStatusCache
;
151 CTraceToOutputDebugString::Instance()(__FUNCTION__
": cache not loaded from disk\n");
154 bool CGitStatusCache::SaveCache()
156 if (!CRegStdDWORD(_T("Software\\TortoiseGit\\CacheSave"), TRUE
))
159 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) goto error;
160 unsigned int value
= 0;
161 // save the cache to disk
163 // find a location to write the cache to
164 CString path
= CPathUtils::GetLocalAppDataDirectory();
167 path
+= STATUSCACHEFILENAME
;
168 _tfopen_s(&pFile
, path
, _T("wb"));
171 value
= CACHEDISKVERSION
;
172 WRITEVALUETOFILE(value
);
173 value
= (int)m_pInstance
->m_directoryCache
.size();
174 WRITEVALUETOFILE(value
);
175 for (CCachedDirectory::CachedDirMap::iterator I
= m_pInstance
->m_directoryCache
.begin(); I
!= m_pInstance
->m_directoryCache
.end(); ++I
)
177 if (I
->second
== NULL
)
180 WRITEVALUETOFILE(value
);
183 const CString
& key
= I
->first
.GetWinPathString();
184 value
= key
.GetLength();
185 WRITEVALUETOFILE(value
);
188 if (fwrite((LPCTSTR
)key
, sizeof(TCHAR
), value
, pFile
)!=value
)
190 if (!I
->second
->SaveToDisk(pFile
))
197 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": cache saved to disk at %s\n"), path
);
206 void CGitStatusCache::Destroy()
217 void CGitStatusCache::Stop()
219 // m_svnHelp.Cancel(true);
221 m_folderCrawler
.Stop();
222 m_shellUpdater
.Stop();
225 void CGitStatusCache::Init()
227 m_folderCrawler
.Initialise();
228 m_shellUpdater
.Initialise();
231 CGitStatusCache::CGitStatusCache(void)
233 #define forever DWORD(-1)
234 AutoLocker
lock(m_NoWatchPathCritSec
);
235 TCHAR path
[MAX_PATH
] = { 0 };
236 SHGetFolderPath(NULL
, CSIDL_COOKIES
, NULL
, 0, path
);
237 m_NoWatchPaths
[CTGitPath(CString(path
))] = forever
;
238 SecureZeroMemory(path
, sizeof(path
));
239 SHGetFolderPath(NULL
, CSIDL_HISTORY
, NULL
, 0, path
);
240 m_NoWatchPaths
[CTGitPath(CString(path
))] = forever
;
241 SecureZeroMemory(path
, sizeof(path
));
242 SHGetFolderPath(NULL
, CSIDL_INTERNET_CACHE
, NULL
, 0, path
);
243 m_NoWatchPaths
[CTGitPath(CString(path
))] = forever
;
244 SecureZeroMemory(path
, sizeof(path
));
245 SHGetFolderPath(NULL
, CSIDL_SYSTEM
, NULL
, 0, path
);
246 m_NoWatchPaths
[CTGitPath(CString(path
))] = forever
;
247 SecureZeroMemory(path
, sizeof(path
));
248 SHGetFolderPath(NULL
, CSIDL_WINDOWS
, NULL
, 0, path
);
249 m_NoWatchPaths
[CTGitPath(CString(path
))] = forever
;
250 if (SysInfo::Instance().IsVistaOrLater())
252 CAutoLibrary hShell
= AtlLoadSystemLibraryUsingFullPath(_T("shell32.dll"));
255 typedef HRESULT STDAPICALLTYPE
SHGetKnownFolderPathFN(__in REFKNOWNFOLDERID rfid
, __in DWORD dwFlags
, __in_opt HANDLE hToken
, __deref_out PWSTR
*ppszPath
);
256 SHGetKnownFolderPathFN
*pfnSHGetKnownFolderPath
= (SHGetKnownFolderPathFN
*)GetProcAddress(hShell
, "SHGetKnownFolderPath");
257 if (pfnSHGetKnownFolderPath
)
259 KNOWNFOLDERID folderids
[] = { FOLDERID_Cookies
, FOLDERID_History
, FOLDERID_InternetCache
, FOLDERID_Windows
, FOLDERID_CDBurning
, FOLDERID_Fonts
, FOLDERID_RecycleBinFolder
}; //FOLDERID_SearchHistory
260 for (KNOWNFOLDERID folderid
: folderids
)
262 PWSTR pszPath
= NULL
;
263 if (SHGetKnownFolderPath(folderid
, KF_FLAG_CREATE
, NULL
, &pszPath
) != S_OK
)
266 m_NoWatchPaths
[CTGitPath(CString(pszPath
))] = forever
;
267 CoTaskMemFree(pszPath
);
272 m_bClearMemory
= false;
273 m_mostRecentExpiresAt
= 0;
276 CGitStatusCache::~CGitStatusCache(void)
278 CAutoWriteLock
writeLock(m_guard
);
282 void CGitStatusCache::Refresh()
284 m_shellCache
.ForceRefresh();
285 // m_pInstance->m_svnHelp.ReloadConfig();
286 if (!m_pInstance
->m_directoryCache
.empty())
288 CCachedDirectory::CachedDirMap::iterator I
= m_pInstance
->m_directoryCache
.begin();
289 for (/* no init */; I
!= m_pInstance
->m_directoryCache
.end(); ++I
)
291 if (m_shellCache
.IsPathAllowed(I
->first
.GetWinPath()))
292 I
->second
->RefreshMostImportant();
295 CGitStatusCache::Instance().RemoveCacheForPath(I
->first
);
296 I
= m_pInstance
->m_directoryCache
.begin();
297 if (I
== m_pInstance
->m_directoryCache
.end())
304 bool CGitStatusCache::IsPathGood(const CTGitPath
& path
)
306 AutoLocker
lock(m_NoWatchPathCritSec
);
307 for (std::map
<CTGitPath
, DWORD
>::const_iterator it
= m_NoWatchPaths
.begin(); it
!= m_NoWatchPaths
.end(); ++it
)
309 // the ticks check is necessary here, because RemoveTimedoutBlocks is only called within the FolderCrawler loop
310 // and we might miss update calls
311 // TODO: maybe we also need to check if index.lock and HEAD.lock still exists after a specific timeout (if we miss update notifications for these files too often)
312 if (GetTickCount() < it
->second
&& it
->first
.IsAncestorOf(path
))
314 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": path not good: %s\n"), it
->first
.GetWinPath());
321 bool CGitStatusCache::BlockPath(const CTGitPath
& path
, DWORD timeout
/* = 0 */)
324 timeout
= BLOCK_PATH_DEFAULT_TIMEOUT
;
326 if (timeout
> BLOCK_PATH_MAX_TIMEOUT
)
327 timeout
= BLOCK_PATH_MAX_TIMEOUT
;
329 timeout
= GetTickCount() + (timeout
* 1000); // timeout is in seconds, but we need the milliseconds
331 AutoLocker
lock(m_NoWatchPathCritSec
);
332 m_NoWatchPaths
[path
.GetDirectory()] = timeout
;
337 bool CGitStatusCache::UnBlockPath(const CTGitPath
& path
)
340 AutoLocker
lock(m_NoWatchPathCritSec
);
341 std::map
<CTGitPath
, DWORD
>::iterator it
= m_NoWatchPaths
.find(path
.GetDirectory());
342 if (it
!= m_NoWatchPaths
.end())
344 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": path removed from no good: %s\n"), it
->first
.GetWinPath());
345 m_NoWatchPaths
.erase(it
);
348 AddFolderForCrawling(path
.GetDirectory());
353 bool CGitStatusCache::RemoveTimedoutBlocks()
356 DWORD currentTicks
= GetTickCount();
357 AutoLocker
lock(m_NoWatchPathCritSec
);
358 std::vector
<CTGitPath
> toRemove
;
359 for (std::map
<CTGitPath
, DWORD
>::const_iterator it
= m_NoWatchPaths
.begin(); it
!= m_NoWatchPaths
.end(); ++it
)
361 if (currentTicks
> it
->second
)
363 toRemove
.push_back(it
->first
);
366 if (!toRemove
.empty())
368 for (std::vector
<CTGitPath
>::const_iterator it
= toRemove
.begin(); it
!= toRemove
.end(); ++it
)
370 ret
= ret
|| UnBlockPath(*it
);
377 void CGitStatusCache::UpdateShell(const CTGitPath
& path
)
379 m_shellUpdater
.AddPathForUpdate(path
);
382 void CGitStatusCache::ClearCache()
384 CAutoWriteLock
writeLock(m_guard
);
385 for (CCachedDirectory::CachedDirMap::iterator I
= m_directoryCache
.begin(); I
!= m_directoryCache
.end(); ++I
)
390 m_directoryCache
.clear();
393 bool CGitStatusCache::RemoveCacheForDirectory(CCachedDirectory
* cdir
)
398 typedef std::map
<CTGitPath
, git_wc_status_kind
> ChildDirStatus
;
399 CAutoWriteLock
writeLock(m_guard
);
400 if (!cdir
->m_childDirectories
.empty())
402 ChildDirStatus::iterator it
= cdir
->m_childDirectories
.begin();
403 for (; it
!= cdir
->m_childDirectories
.end(); )
405 CCachedDirectory
* childdir
= CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(it
->first
);
406 if ((childdir
) && (!cdir
->m_directoryPath
.IsEquivalentTo(childdir
->m_directoryPath
)) && (cdir
->m_directoryPath
.GetFileOrDirectoryName() != L
".."))
407 RemoveCacheForDirectory(childdir
);
408 cdir
->m_childDirectories
.erase(it
->first
);
409 it
= cdir
->m_childDirectories
.begin();
412 cdir
->m_childDirectories
.clear();
413 m_directoryCache
.erase(cdir
->m_directoryPath
);
415 // we could have entries versioned and/or stored in our cache which are
416 // children of the specified directory, but not in the m_childDirectories
418 CCachedDirectory::ItDir itMap
= m_directoryCache
.lower_bound(cdir
->m_directoryPath
);
421 if (itMap
!= m_directoryCache
.end())
423 if (cdir
->m_directoryPath
.IsAncestorOf(itMap
->first
))
425 // just in case (see TortoiseSVN issue #255)
426 if (itMap
->second
== cdir
)
428 m_directoryCache
.erase(itMap
);
431 RemoveCacheForDirectory(itMap
->second
);
434 itMap
= m_directoryCache
.lower_bound(cdir
->m_directoryPath
);
435 } while (itMap
!= m_directoryCache
.end() && cdir
->m_directoryPath
.IsAncestorOf(itMap
->first
));
437 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": removed from cache %s\n"), cdir
->m_directoryPath
.GetWinPathString());
442 void CGitStatusCache::RemoveCacheForPath(const CTGitPath
& path
)
444 // Stop the crawler starting on a new folder
445 CCrawlInhibitor
crawlInhibit(&m_folderCrawler
);
446 CCachedDirectory::ItDir itMap
;
447 CCachedDirectory
* dirtoremove
= NULL
;
449 itMap
= m_directoryCache
.find(path
);
450 if ((itMap
!= m_directoryCache
.end())&&(itMap
->second
))
451 dirtoremove
= itMap
->second
;
452 if (dirtoremove
== NULL
)
454 ATLASSERT(path
.IsEquivalentToWithoutCase(dirtoremove
->m_directoryPath
));
455 RemoveCacheForDirectory(dirtoremove
);
458 CCachedDirectory
* CGitStatusCache::GetDirectoryCacheEntry(const CTGitPath
& path
, bool isAddToWatch
)
460 ATLASSERT(path
.IsDirectory() || !PathFileExists(path
.GetWinPath()));
463 CCachedDirectory::ItDir itMap
;
464 itMap
= m_directoryCache
.find(path
);
465 if ((itMap
!= m_directoryCache
.end())&&(itMap
->second
))
467 // We've found this directory in the cache
468 return itMap
->second
;
472 // if the CCachedDirectory is NULL but the path is in our cache,
473 // that means that path got invalidated and needs to be treated
474 // as if it never was in our cache. So we remove the last remains
475 // from the cache and start from scratch.
477 CAutoWriteLock
writeLock(m_guard
);
478 // Since above there's a small chance that before we can upgrade to
479 // writer state some other thread gained writer state and changed
480 // the data, we have to recreate the iterator here again.
481 itMap
= m_directoryCache
.find(path
);
482 if (itMap
!=m_directoryCache
.end())
484 delete itMap
->second
;
485 m_directoryCache
.erase(itMap
);
487 // We don't know anything about this directory yet - lets add it to our cache
488 // but only if it exists!
489 if (path
.Exists() && m_shellCache
.IsPathAllowed(path
.GetWinPath()) && !g_GitAdminDir
.IsAdminDirPath(path
.GetWinPath()))
491 // some notifications are for files which got removed/moved around.
492 // In such cases, the CTGitPath::IsDirectory() will return true (it assumes a directory if
493 // the path doesn't exist). Which means we can get here with a path to a file
494 // instead of a directory.
495 // Since we're here most likely called from the crawler thread, the file could exist
496 // again. If that's the case, just do nothing
497 if (path
.IsDirectory()||(!path
.Exists()))
499 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": adding %s to our cache\n"), path
.GetWinPath());
500 CCachedDirectory
* newcdir
= new CCachedDirectory(path
);
503 CCachedDirectory
* cdir
= m_directoryCache
.insert(m_directoryCache
.lower_bound(path
), std::make_pair(path
, newcdir
))->second
;
505 if ((!path
.IsEmpty())&&(path
.HasAdminDir(&gitdir
))&&isAddToWatch
)
507 /* Just watch version path */
508 watcher
.AddPath(gitdir
);
509 watcher
.AddPath(path
);
513 m_bClearMemory
= true;
520 CCachedDirectory
* CGitStatusCache::GetDirectoryCacheEntryNoCreate(const CTGitPath
& path
)
522 ATLASSERT(path
.IsDirectory() || !PathFileExists(path
.GetWinPath()));
524 CCachedDirectory::ItDir itMap
;
525 itMap
= m_directoryCache
.find(path
);
526 if(itMap
!= m_directoryCache
.end())
528 // We've found this directory in the cache
529 return itMap
->second
;
534 /* Fetch is true, means fetch status from */
535 /* Fetch is false, means fetch status from cache */
536 CStatusCacheEntry
CGitStatusCache::GetStatusForPath(const CTGitPath
& path
, DWORD flags
, bool bFetch
/* = true */)
538 bool bRecursive
= !!(flags
& TGITCACHE_FLAGS_RECUSIVE_STATUS
);
540 // Check a very short-lived 'mini-cache' of the last thing we were asked for.
541 long now
= (long)GetTickCount();
542 if(now
-m_mostRecentExpiresAt
< 0)
544 if(path
.IsEquivalentToWithoutCase(m_mostRecentPath
))
546 return m_mostRecentStatus
;
550 AutoLocker
lock(m_critSec
);
551 m_mostRecentPath
= path
;
552 m_mostRecentExpiresAt
= now
+ 1000;
555 if (IsPathGood(path
))
557 // Stop the crawler starting on a new folder while we're doing this much more important task...
558 // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath().
559 CCrawlInhibitor
crawlInhibit(&m_folderCrawler
);
561 CTGitPath dirpath
= path
.GetContainingDirectory();
562 if ((dirpath
.IsEmpty()) || (!m_shellCache
.IsPathAllowed(dirpath
.GetWinPath())))
563 dirpath
= path
.GetDirectory();
564 CCachedDirectory
* cachedDir
= GetDirectoryCacheEntry(dirpath
);
565 if (cachedDir
!= NULL
)
567 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": GetStatusForMember %d\n"), bFetch);
568 CStatusCacheEntry entry
= cachedDir
->GetStatusForMember(path
, bRecursive
, bFetch
);
570 AutoLocker
lock(m_critSec
);
571 m_mostRecentStatus
= entry
;
572 return m_mostRecentStatus
;
578 // path is blocked for some reason: return the cached status if we have one
579 // we do here only a cache search, absolutely no disk access is allowed!
580 CCachedDirectory::ItDir itMap
= m_directoryCache
.find(path
.GetDirectory());
581 if ((itMap
!= m_directoryCache
.end())&&(itMap
->second
))
583 if (path
.IsDirectory())
585 CStatusCacheEntry entry
= itMap
->second
->GetOwnStatus(false);
586 AutoLocker
lock(m_critSec
);
587 m_mostRecentStatus
= entry
;
588 return m_mostRecentStatus
;
592 // We've found this directory in the cache
593 CCachedDirectory
* cachedDir
= itMap
->second
;
594 CStatusCacheEntry entry
= cachedDir
->GetCacheStatusForMember(path
);
596 AutoLocker
lock(m_critSec
);
597 m_mostRecentStatus
= entry
;
598 return m_mostRecentStatus
;
603 AutoLocker
lock(m_critSec
);
604 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": ignored no good path %s\n"), path
.GetWinPath());
605 m_mostRecentStatus
= CStatusCacheEntry();
606 if (m_shellCache
.ShowExcludedAsNormal() && path
.IsDirectory() && m_shellCache
.HasGITAdminDir(path
.GetWinPath(), true))
608 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": force status %s\n"), path
.GetWinPath());
609 m_mostRecentStatus
.ForceStatus(git_wc_status_normal
);
611 return m_mostRecentStatus
;
614 void CGitStatusCache::AddFolderForCrawling(const CTGitPath
& path
)
616 m_folderCrawler
.AddDirectoryForUpdate(path
);
619 void CGitStatusCache::CloseWatcherHandles(HANDLE hFile
)
621 CTGitPath path
= watcher
.CloseInfoMap(hFile
);
623 m_folderCrawler
.BlockPath(path
);
624 CGitStatusCache::Instance().m_GitStatus
.ReleasePathsRecursively(path
.GetWinPathString());
627 void CGitStatusCache::CloseWatcherHandles(const CTGitPath
& path
)
629 watcher
.CloseHandlesForPath(path
);
630 m_folderCrawler
.ReleasePathForUpdate(path
);
631 CGitStatusCache::Instance().m_GitStatus
.ReleasePathsRecursively(path
.GetWinPathString());