For git > 2.1, pass --date=now when amending commit with author date reset
[TortoiseGit.git] / src / TGitCache / GITStatusCache.cpp
blob0f82940508ae05d44581a647aeadf43ffc408ea9
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-2015 - 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.
21 #include "stdafx.h"
22 #include "GitStatus.h"
23 #include "GitStatusCache.h"
24 #include "CacheInterface.h"
25 #include <ShlObj.h>
26 #include "SysInfo.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
36 #ifdef _WIN64
37 #define STATUSCACHEFILENAME L"cache64"
38 #else
39 #define STATUSCACHEFILENAME L"cache"
40 #endif
42 CGitStatusCache* CGitStatusCache::m_pInstance;
44 CGitStatusCache& CGitStatusCache::Instance()
46 ATLASSERT(m_pInstance != NULL);
47 return *m_pInstance;
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))
58 return;
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;
63 FILE * pFile = NULL;
64 // find the location of the cache
65 CString path = CPathUtils::GetLocalAppDataDirectory();
66 CString path2;
67 if (!path.IsEmpty())
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.
76 path2 = path;
77 path2 += _T("2");
78 DeleteFile(path2);
79 CopyFile(path, path2, FALSE);
80 DeleteFile(path);
81 pFile = _tfsopen(path2, _T("rb"), _SH_DENYNO);
82 if (pFile)
84 try
86 LOADVALUEFROMFILE(value);
87 if (value != CACHEDISKVERSION)
89 goto error;
91 int mapsize = 0;
92 LOADVALUEFROMFILE(mapsize);
93 for (int i=0; i<mapsize; ++i)
95 LOADVALUEFROMFILE2(value);
96 if (value > MAX_PATH)
97 goto error;
98 if (value)
100 CString sKey;
101 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
103 sKey.ReleaseBuffer(0);
104 goto error;
106 sKey.ReleaseBuffer(value);
107 std::unique_ptr<CCachedDirectory> cacheddir (new CCachedDirectory());
108 if (!cacheddir.get() || !cacheddir->LoadFromDisk(pFile))
110 cacheddir.reset();
111 goto error;
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)
133 goto error;
137 exit:
138 if (pFile)
139 fclose(pFile);
140 DeleteFile(path2);
141 m_pInstance->watcher.ClearInfoMap();
142 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": cache loaded from disk successfully!\n");
143 return;
144 error:
145 if (pFile)
146 fclose(pFile);
147 DeleteFile(path2);
148 m_pInstance->watcher.ClearInfoMap();
149 Destroy();
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))
157 return false;
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
162 FILE * pFile = NULL;
163 // find a location to write the cache to
164 CString path = CPathUtils::GetLocalAppDataDirectory();
165 if (!path.IsEmpty())
167 path += STATUSCACHEFILENAME;
168 _tfopen_s(&pFile, path, _T("wb"));
169 if (pFile)
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)
179 value = 0;
180 WRITEVALUETOFILE(value);
181 continue;
183 const CString& key = I->first.GetWinPathString();
184 value = key.GetLength();
185 WRITEVALUETOFILE(value);
186 if (value)
188 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
189 goto error;
190 if (!I->second->SaveToDisk(pFile))
191 goto error;
194 fclose(pFile);
197 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": cache saved to disk at %s\n"), path);
198 return true;
199 error:
200 fclose(pFile);
201 Destroy();
202 DeleteFile(path);
203 return false;
206 void CGitStatusCache::Destroy()
208 if (m_pInstance)
210 m_pInstance->Stop();
211 Sleep(100);
212 delete m_pInstance;
213 m_pInstance = NULL;
217 void CGitStatusCache::Stop()
219 // m_svnHelp.Cancel(true);
220 watcher.Stop();
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"));
253 if (hShell)
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 (pfnSHGetKnownFolderPath(folderid, KF_FLAG_CREATE, NULL, &pszPath) != S_OK)
264 continue;
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);
279 ClearCache();
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();
293 else
295 CGitStatusCache::Instance().RemoveCacheForPath(I->first);
296 I = m_pInstance->m_directoryCache.begin();
297 if (I == m_pInstance->m_directoryCache.end())
298 break;
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());
315 return false;
318 return true;
321 bool CGitStatusCache::BlockPath(const CTGitPath& path, DWORD timeout /* = 0 */)
323 if (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;
334 return true;
337 bool CGitStatusCache::UnBlockPath(const CTGitPath& path)
339 bool ret = false;
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);
346 ret = true;
348 AddFolderForCrawling(path.GetDirectory());
350 return ret;
353 bool CGitStatusCache::RemoveTimedoutBlocks()
355 bool ret = false;
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);
374 return ret;
377 void CGitStatusCache::UpdateShell(const CTGitPath& path)
379 if (path.IsEquivalentToWithoutCase(m_mostRecentPath))
380 m_mostRecentExpiresAt = 0;
381 m_shellUpdater.AddPathForUpdate(path);
384 void CGitStatusCache::ClearCache()
386 CAutoWriteLock writeLock(m_guard);
387 for (CCachedDirectory::CachedDirMap::iterator I = m_directoryCache.begin(); I != m_directoryCache.end(); ++I)
389 delete I->second;
390 I->second = NULL;
392 m_directoryCache.clear();
395 bool CGitStatusCache::RemoveCacheForDirectory(CCachedDirectory * cdir)
397 if (cdir == NULL)
398 return false;
400 typedef std::map<CTGitPath, git_wc_status_kind> ChildDirStatus;
401 CAutoWriteLock writeLock(m_guard);
402 if (!cdir->m_childDirectories.empty())
404 ChildDirStatus::iterator it = cdir->m_childDirectories.begin();
405 for (; it != cdir->m_childDirectories.end(); )
407 CCachedDirectory * childdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(it->first);
408 if ((childdir) && (!cdir->m_directoryPath.IsEquivalentTo(childdir->m_directoryPath)) && (cdir->m_directoryPath.GetFileOrDirectoryName() != L".."))
409 RemoveCacheForDirectory(childdir);
410 cdir->m_childDirectories.erase(it->first);
411 it = cdir->m_childDirectories.begin();
414 cdir->m_childDirectories.clear();
415 m_directoryCache.erase(cdir->m_directoryPath);
417 // we could have entries versioned and/or stored in our cache which are
418 // children of the specified directory, but not in the m_childDirectories
419 // member
420 CCachedDirectory::ItDir itMap = m_directoryCache.lower_bound(cdir->m_directoryPath);
423 if (itMap != m_directoryCache.end())
425 if (cdir->m_directoryPath.IsAncestorOf(itMap->first))
427 // just in case (see TortoiseSVN issue #255)
428 if (itMap->second == cdir)
430 m_directoryCache.erase(itMap);
432 else
433 RemoveCacheForDirectory(itMap->second);
436 itMap = m_directoryCache.lower_bound(cdir->m_directoryPath);
437 } while (itMap != m_directoryCache.end() && cdir->m_directoryPath.IsAncestorOf(itMap->first));
439 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": removed from cache %s\n"), cdir->m_directoryPath.GetWinPathString());
440 delete cdir;
441 return true;
444 void CGitStatusCache::RemoveCacheForPath(const CTGitPath& path)
446 // Stop the crawler starting on a new folder
447 CCrawlInhibitor crawlInhibit(&m_folderCrawler);
448 CCachedDirectory::ItDir itMap;
449 CCachedDirectory * dirtoremove = NULL;
451 itMap = m_directoryCache.find(path);
452 if ((itMap != m_directoryCache.end())&&(itMap->second))
453 dirtoremove = itMap->second;
454 if (dirtoremove == NULL)
455 return;
456 ATLASSERT(path.IsEquivalentToWithoutCase(dirtoremove->m_directoryPath));
457 RemoveCacheForDirectory(dirtoremove);
460 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntry(const CTGitPath& path, bool isAddToWatch)
462 ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));
465 CCachedDirectory::ItDir itMap;
466 itMap = m_directoryCache.find(path);
467 if ((itMap != m_directoryCache.end())&&(itMap->second))
469 // We've found this directory in the cache
470 return itMap->second;
472 else
474 // if the CCachedDirectory is NULL but the path is in our cache,
475 // that means that path got invalidated and needs to be treated
476 // as if it never was in our cache. So we remove the last remains
477 // from the cache and start from scratch.
479 CAutoWriteLock writeLock(m_guard);
480 // Since above there's a small chance that before we can upgrade to
481 // writer state some other thread gained writer state and changed
482 // the data, we have to recreate the iterator here again.
483 itMap = m_directoryCache.find(path);
484 if (itMap!=m_directoryCache.end())
486 delete itMap->second;
487 m_directoryCache.erase(itMap);
489 // We don't know anything about this directory yet - lets add it to our cache
490 // but only if it exists!
491 if (path.Exists() && m_shellCache.IsPathAllowed(path.GetWinPath()) && !GitAdminDir::IsAdminDirPath(path.GetWinPath()))
493 // some notifications are for files which got removed/moved around.
494 // In such cases, the CTGitPath::IsDirectory() will return true (it assumes a directory if
495 // the path doesn't exist). Which means we can get here with a path to a file
496 // instead of a directory.
497 // Since we're here most likely called from the crawler thread, the file could exist
498 // again. If that's the case, just do nothing
499 if (path.IsDirectory()||(!path.Exists()))
501 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": adding %s to our cache\n"), path.GetWinPath());
502 CCachedDirectory * newcdir = new CCachedDirectory(path);
503 if (newcdir)
505 CCachedDirectory * cdir = m_directoryCache.insert(m_directoryCache.lower_bound(path), std::make_pair(path, newcdir))->second;
506 CString gitdir;
507 if ((!path.IsEmpty())&&(path.HasAdminDir(&gitdir))&&isAddToWatch)
509 /* Just watch version path */
510 watcher.AddPath(gitdir);
511 watcher.AddPath(path);
513 return cdir;
515 m_bClearMemory = true;
518 return NULL;
522 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntryNoCreate(const CTGitPath& path)
524 ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));
526 CCachedDirectory::ItDir itMap;
527 itMap = m_directoryCache.find(path);
528 if(itMap != m_directoryCache.end())
530 // We've found this directory in the cache
531 return itMap->second;
533 return NULL;
536 /* Fetch is true, means fetch status from */
537 /* Fetch is false, means fetch status from cache */
538 CStatusCacheEntry CGitStatusCache::GetStatusForPath(const CTGitPath& path, DWORD flags, bool bFetch /* = true */)
540 bool bRecursive = !!(flags & TGITCACHE_FLAGS_RECUSIVE_STATUS);
542 // Check a very short-lived 'mini-cache' of the last thing we were asked for.
543 long now = (long)GetTickCount();
544 if(now-m_mostRecentExpiresAt < 0)
546 if(path.IsEquivalentToWithoutCase(m_mostRecentPath))
548 return m_mostRecentStatus;
552 AutoLocker lock(m_critSec);
553 m_mostRecentPath = path;
554 m_mostRecentExpiresAt = now + 1000;
557 if (IsPathGood(path))
559 // Stop the crawler starting on a new folder while we're doing this much more important task...
560 // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath().
561 CCrawlInhibitor crawlInhibit(&m_folderCrawler);
563 CTGitPath dirpath = path.GetContainingDirectory();
564 if ((dirpath.IsEmpty()) || (!m_shellCache.IsPathAllowed(dirpath.GetWinPath())))
565 dirpath = path.GetDirectory();
566 CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath);
567 if (cachedDir != NULL)
569 //CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": GetStatusForMember %d\n"), bFetch);
570 CStatusCacheEntry entry = cachedDir->GetStatusForMember(path, bRecursive, bFetch);
572 AutoLocker lock(m_critSec);
573 m_mostRecentStatus = entry;
574 return m_mostRecentStatus;
578 else
580 // path is blocked for some reason: return the cached status if we have one
581 // we do here only a cache search, absolutely no disk access is allowed!
582 CCachedDirectory::ItDir itMap = m_directoryCache.find(path.GetDirectory());
583 if ((itMap != m_directoryCache.end())&&(itMap->second))
585 if (path.IsDirectory())
587 CStatusCacheEntry entry = itMap->second->GetOwnStatus(false);
588 AutoLocker lock(m_critSec);
589 m_mostRecentStatus = entry;
590 return m_mostRecentStatus;
592 else
594 // We've found this directory in the cache
595 CCachedDirectory * cachedDir = itMap->second;
596 CStatusCacheEntry entry = cachedDir->GetCacheStatusForMember(path);
598 AutoLocker lock(m_critSec);
599 m_mostRecentStatus = entry;
600 return m_mostRecentStatus;
605 AutoLocker lock(m_critSec);
606 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": ignored no good path %s\n"), path.GetWinPath());
607 m_mostRecentStatus = CStatusCacheEntry();
608 if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.HasGITAdminDir(path.GetWinPath(), true))
610 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": force status %s\n"), path.GetWinPath());
611 m_mostRecentStatus.ForceStatus(git_wc_status_normal);
613 return m_mostRecentStatus;
616 void CGitStatusCache::AddFolderForCrawling(const CTGitPath& path)
618 m_folderCrawler.AddDirectoryForUpdate(path);
621 void CGitStatusCache::CloseWatcherHandles(HANDLE hFile)
623 CTGitPath path = watcher.CloseInfoMap(hFile);
624 if (!path.IsEmpty())
625 m_folderCrawler.BlockPath(path);
626 CGitStatusCache::Instance().m_GitStatus.ReleasePathsRecursively(path.GetWinPathString());
629 void CGitStatusCache::CloseWatcherHandles(const CTGitPath& path)
631 watcher.CloseHandlesForPath(path);
632 m_folderCrawler.ReleasePathForUpdate(path);
633 CGitStatusCache::Instance().m_GitStatus.ReleasePathsRecursively(path.GetWinPathString());