Fixed issue #914: unifiled diff always show wrong changes (base files compare with...
[TortoiseGit.git] / src / TGitCache / SVNStatusCache.cpp
blob93abec38b84c65fd3c608410c641fb29481cc9bb
1 // TortoiseGit - a Windows shell extension for easy version control
3 // External Cache Copyright (C) 2005-2006,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.
21 #include "StdAfx.h"
22 #include "GitStatus.h"
23 #include "GitStatuscache.h"
24 #include "CacheInterface.h"
25 #include "shlobj.h"
27 //////////////////////////////////////////////////////////////////////////
29 CGitStatusCache* CGitStatusCache::m_pInstance;
31 CGitStatusCache& CGitStatusCache::Instance()
33 ATLASSERT(m_pInstance != NULL);
34 return *m_pInstance;
37 void CGitStatusCache::Create()
39 ATLASSERT(m_pInstance == NULL);
40 m_pInstance = new CGitStatusCache;
42 m_pInstance->watcher.SetFolderCrawler(&m_pInstance->m_folderCrawler);
43 #define LOADVALUEFROMFILE(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto exit;
44 #define LOADVALUEFROMFILE2(x) if (fread(&x, sizeof(x), 1, pFile)!=1) goto error;
45 unsigned int value = (unsigned int)-1;
46 FILE * pFile = NULL;
47 // find the location of the cache
48 TCHAR path[MAX_PATH]; //MAX_PATH ok here.
49 TCHAR path2[MAX_PATH];
50 if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)==S_OK)
52 _tcscat_s(path, MAX_PATH, _T("\\TGitCache"));
53 if (!PathIsDirectory(path))
55 if (CreateDirectory(path, NULL)==0)
56 goto error;
58 _tcscat_s(path, MAX_PATH, _T("\\cache"));
59 // in case the cache file is corrupt, we could crash while
60 // reading it! To prevent crashing every time once that happens,
61 // we make a copy of the cache file and use that copy to read from.
62 // if that copy is corrupt, the original file won't exist anymore
63 // and the second time we start up and try to read the file,
64 // it's not there anymore and we start from scratch without a crash.
65 _tcscpy_s(path2, MAX_PATH, path);
66 _tcscat_s(path2, MAX_PATH, _T("2"));
67 DeleteFile(path2);
68 CopyFile(path, path2, FALSE);
69 DeleteFile(path);
70 pFile = _tfsopen(path2, _T("rb"), _SH_DENYNO);
71 if (pFile)
73 try
75 LOADVALUEFROMFILE(value);
76 if (value != 2)
78 goto error;
80 int mapsize = 0;
81 LOADVALUEFROMFILE(mapsize);
82 for (int i=0; i<mapsize; ++i)
84 LOADVALUEFROMFILE2(value);
85 if (value > MAX_PATH)
86 goto error;
87 if (value)
89 CString sKey;
90 if (fread(sKey.GetBuffer(value+1), sizeof(TCHAR), value, pFile)!=value)
92 sKey.ReleaseBuffer(0);
93 goto error;
95 sKey.ReleaseBuffer(value);
96 CCachedDirectory * cacheddir = new CCachedDirectory();
97 if (cacheddir == NULL)
98 goto error;
99 if (!cacheddir->LoadFromDisk(pFile))
100 goto error;
101 CTGitPath KeyPath = CTGitPath(sKey);
102 if (m_pInstance->IsPathAllowed(KeyPath))
104 m_pInstance->m_directoryCache[KeyPath] = cacheddir;
105 // only add the path to the watch list if it is versioned
106 if ((cacheddir->GetCurrentFullStatus() != git_wc_status_unversioned)&&(cacheddir->GetCurrentFullStatus() != git_wc_status_none))
107 m_pInstance->watcher.AddPath(KeyPath);
108 // do *not* add the paths for crawling!
109 // because crawled paths will trigger a shell
110 // notification, which makes the desktop flash constantly
111 // until the whole first time crawling is over
112 // m_pInstance->AddFolderForCrawling(KeyPath);
117 catch (CAtlException)
119 goto error;
123 exit:
124 if (pFile)
125 fclose(pFile);
126 DeleteFile(path2);
127 ATLTRACE("cache loaded from disk successfully!\n");
128 return;
129 error:
130 fclose(pFile);
131 DeleteFile(path2);
132 if (m_pInstance)
134 m_pInstance->Stop();
135 Sleep(100);
137 delete m_pInstance;
138 m_pInstance = new CGitStatusCache;
139 ATLTRACE("cache not loaded from disk\n");
142 bool CGitStatusCache::SaveCache()
144 #define WRITEVALUETOFILE(x) if (fwrite(&x, sizeof(x), 1, pFile)!=1) goto error;
145 unsigned int value = 0;
146 // save the cache to disk
147 FILE * pFile = NULL;
148 // find a location to write the cache to
149 TCHAR path[MAX_PATH]; //MAX_PATH ok here.
150 if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path)==S_OK)
152 _tcscat_s(path, MAX_PATH, _T("\\TGitCache"));
153 if (!PathIsDirectory(path))
154 CreateDirectory(path, NULL);
155 _tcscat_s(path, MAX_PATH, _T("\\cache"));
156 _tfopen_s(&pFile, path, _T("wb"));
157 if (pFile)
159 value = 2; // 'version'
160 WRITEVALUETOFILE(value);
161 value = (int)m_pInstance->m_directoryCache.size();
162 WRITEVALUETOFILE(value);
163 for (CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin(); I != m_pInstance->m_directoryCache.end(); ++I)
165 if (I->second == NULL)
167 value = 0;
168 WRITEVALUETOFILE(value);
169 continue;
171 const CString& key = I->first.GetWinPathString();
172 value = key.GetLength();
173 WRITEVALUETOFILE(value);
174 if (value)
176 if (fwrite((LPCTSTR)key, sizeof(TCHAR), value, pFile)!=value)
177 goto error;
178 if (!I->second->SaveToDisk(pFile))
179 goto error;
182 fclose(pFile);
185 ATLTRACE(_T("cache saved to disk at %s\n"), path);
186 return true;
187 error:
188 fclose(pFile);
189 if (m_pInstance)
191 m_pInstance->Stop();
192 Sleep(100);
194 delete m_pInstance;
195 m_pInstance = NULL;
196 DeleteFile(path);
197 return false;
200 void CGitStatusCache::Destroy()
202 if (m_pInstance)
204 m_pInstance->Stop();
205 Sleep(100);
207 delete m_pInstance;
208 m_pInstance = NULL;
211 void CGitStatusCache::Stop()
213 // m_svnHelp.Cancel(true);
214 watcher.Stop();
215 m_folderCrawler.Stop();
216 m_shellUpdater.Stop();
219 void CGitStatusCache::Init()
221 m_folderCrawler.Initialise();
222 m_shellUpdater.Initialise();
225 CGitStatusCache::CGitStatusCache(void)
227 TCHAR path[MAX_PATH];
228 SHGetFolderPath(NULL, CSIDL_COOKIES, NULL, 0, path);
229 m_NoWatchPaths.insert(CTGitPath(CString(path)));
230 SHGetFolderPath(NULL, CSIDL_HISTORY, NULL, 0, path);
231 m_NoWatchPaths.insert(CTGitPath(CString(path)));
232 SHGetFolderPath(NULL, CSIDL_INTERNET_CACHE, NULL, 0, path);
233 m_NoWatchPaths.insert(CTGitPath(CString(path)));
234 SHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, 0, path);
235 m_NoWatchPaths.insert(CTGitPath(CString(path)));
236 SHGetFolderPath(NULL, CSIDL_WINDOWS, NULL, 0, path);
237 m_NoWatchPaths.insert(CTGitPath(CString(path)));
238 m_bClearMemory = false;
239 m_mostRecentExpiresAt = 0;
242 CGitStatusCache::~CGitStatusCache(void)
244 for (CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin(); I != m_pInstance->m_directoryCache.end(); ++I)
246 delete I->second;
247 I->second = NULL;
251 void CGitStatusCache::Refresh()
253 m_shellCache.ForceRefresh();
254 // m_pInstance->m_svnHelp.ReloadConfig();
255 if (m_pInstance->m_directoryCache.size())
257 CCachedDirectory::CachedDirMap::iterator I = m_pInstance->m_directoryCache.begin();
258 for (/* no init */; I != m_pInstance->m_directoryCache.end(); ++I)
260 if (m_shellCache.IsPathAllowed(I->first.GetWinPath()))
261 I->second->RefreshMostImportant();
262 else
264 CGitStatusCache::Instance().RemoveCacheForPath(I->first);
265 I = m_pInstance->m_directoryCache.begin();
266 if (I == m_pInstance->m_directoryCache.end())
267 break;
273 bool CGitStatusCache::IsPathGood(const CTGitPath& path)
275 for (std::set<CTGitPath>::iterator it = m_NoWatchPaths.begin(); it != m_NoWatchPaths.end(); ++it)
277 if (it->IsAncestorOf(path))
278 return false;
280 return true;
283 void CGitStatusCache::UpdateShell(const CTGitPath& path)
285 m_shellUpdater.AddPathForUpdate(path);
288 void CGitStatusCache::ClearCache()
290 for (CCachedDirectory::CachedDirMap::iterator I = m_directoryCache.begin(); I != m_directoryCache.end(); ++I)
292 delete I->second;
293 I->second = NULL;
295 m_directoryCache.clear();
298 bool CGitStatusCache::RemoveCacheForDirectory(CCachedDirectory * cdir)
300 if (cdir == NULL)
301 return false;
302 AssertWriting();
303 typedef std::map<CTGitPath, git_wc_status_kind> ChildDirStatus;
304 if (cdir->m_childDirectories.size())
306 ChildDirStatus::iterator it = cdir->m_childDirectories.begin();
307 for (; it != cdir->m_childDirectories.end(); )
309 CCachedDirectory * childdir = CGitStatusCache::Instance().GetDirectoryCacheEntryNoCreate(it->first);
310 if ((childdir)&&(!cdir->m_directoryPath.IsEquivalentTo(childdir->m_directoryPath)))
311 RemoveCacheForDirectory(childdir);
312 cdir->m_childDirectories.erase(it->first);
313 it = cdir->m_childDirectories.begin();
316 cdir->m_childDirectories.clear();
317 m_directoryCache.erase(cdir->m_directoryPath);
318 ATLTRACE(_T("removed path %s from cache\n"), cdir->m_directoryPath.GetWinPathString());
319 delete cdir;
320 cdir = NULL;
321 return true;
324 void CGitStatusCache::RemoveCacheForPath(const CTGitPath& path)
326 // Stop the crawler starting on a new folder
327 CCrawlInhibitor crawlInhibit(&m_folderCrawler);
328 CCachedDirectory::ItDir itMap;
329 CCachedDirectory * dirtoremove = NULL;
331 AssertWriting();
332 itMap = m_directoryCache.find(path);
333 if ((itMap != m_directoryCache.end())&&(itMap->second))
334 dirtoremove = itMap->second;
335 if (dirtoremove == NULL)
336 return;
337 ATLASSERT(path.IsEquivalentToWithoutCase(dirtoremove->m_directoryPath));
338 RemoveCacheForDirectory(dirtoremove);
341 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntry(const CTGitPath& path, bool isAddToWatch)
343 ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));
346 CCachedDirectory::ItDir itMap;
347 itMap = m_directoryCache.find(path);
348 if ((itMap != m_directoryCache.end())&&(itMap->second))
350 // We've found this directory in the cache
351 return itMap->second;
353 else
355 // if the CCachedDirectory is NULL but the path is in our cache,
356 // that means that path got invalidated and needs to be treated
357 // as if it never was in our cache. So we remove the last remains
358 // from the cache and start from scratch.
359 AssertLock();
360 if (!IsWriter())
362 // upgrading our state to writer
363 ATLTRACE("trying to upgrade the state to \"Writer\"\n");
364 Done();
365 ATLTRACE("Returned \"Reader\" state\n");
366 WaitToWrite();
367 ATLTRACE("Got \"Writer\" state now\n");
369 // Since above there's a small chance that before we can upgrade to
370 // writer state some other thread gained writer state and changed
371 // the data, we have to recreate the iterator here again.
372 itMap = m_directoryCache.find(path);
373 if (itMap!=m_directoryCache.end())
374 m_directoryCache.erase(itMap);
375 // We don't know anything about this directory yet - lets add it to our cache
376 // but only if it exists!
377 if (path.Exists() && m_shellCache.IsPathAllowed(path.GetWinPath()) && !g_GitAdminDir.IsAdminDirPath(path.GetWinPath()))
379 // some notifications are for files which got removed/moved around.
380 // In such cases, the CTGitPath::IsDirectory() will return true (it assumes a directory if
381 // the path doesn't exist). Which means we can get here with a path to a file
382 // instead of a directory.
383 // Since we're here most likely called from the crawler thread, the file could exist
384 // again. If that's the case, just do nothing
385 if (path.IsDirectory()||(!path.Exists()))
387 ATLTRACE(_T("adding %s to our cache\n"), path.GetWinPath());
388 CCachedDirectory * newcdir = new CCachedDirectory(path);
389 if (newcdir)
391 CCachedDirectory * cdir = m_directoryCache.insert(m_directoryCache.lower_bound(path), std::make_pair(path, newcdir))->second;
392 CString gitdir;
393 if ((!path.IsEmpty())&&(path.HasAdminDir(&gitdir))&&isAddToWatch)
395 bool isVersion = true;
396 CString subpaths;
397 if(subpaths.GetLength() > gitdir.GetLength())
399 if(subpaths[gitdir.GetLength()] == _T('\\'))
400 subpaths=subpaths.Right(subpaths.GetLength() - gitdir.GetLength()-1);
401 else
402 subpaths=subpaths.Right(subpaths.GetLength() - gitdir.GetLength());
404 CGitStatusCache::Instance().m_GitStatus.IsUnderVersionControl(gitdir, subpaths, true, &isVersion);
406 /* Just watch version path */
407 if(isVersion)
409 watcher.AddPath(gitdir);
410 watcher.AddPath(path);
413 return cdir;
415 m_bClearMemory = true;
418 return NULL;
422 CCachedDirectory * CGitStatusCache::GetDirectoryCacheEntryNoCreate(const CTGitPath& path)
424 ATLASSERT(path.IsDirectory() || !PathFileExists(path.GetWinPath()));
426 CCachedDirectory::ItDir itMap;
427 itMap = m_directoryCache.find(path);
428 if(itMap != m_directoryCache.end())
430 // We've found this directory in the cache
431 return itMap->second;
433 return NULL;
436 /* Fetch is true, means fetch status from */
437 /* Fetch is false, means fetch status from cache */
438 CStatusCacheEntry CGitStatusCache::GetStatusForPath(const CTGitPath& path, DWORD flags, bool bFetch /* = true */)
440 bool bRecursive = !!(flags & TSVNCACHE_FLAGS_RECUSIVE_STATUS);
442 // Check a very short-lived 'mini-cache' of the last thing we were asked for.
443 long now = (long)GetTickCount();
444 if(now-m_mostRecentExpiresAt < 0)
446 if(path.IsEquivalentToWithoutCase(m_mostRecentPath))
448 return m_mostRecentStatus;
451 m_mostRecentPath = path;
452 m_mostRecentExpiresAt = now+1000;
454 if (IsPathGood(path))
456 // Stop the crawler starting on a new folder while we're doing this much more important task...
457 // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath().
458 CCrawlInhibitor crawlInhibit(&m_folderCrawler);
460 CTGitPath dirpath = path.GetContainingDirectory();
461 if ((dirpath.IsEmpty()) || (!m_shellCache.IsPathAllowed(dirpath.GetWinPath())))
462 dirpath = path.GetDirectory();
463 CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath);
464 if (cachedDir != NULL)
466 //ATLTRACE(_T("GetStatusForMember %d\n"), bFetch);
467 m_mostRecentStatus = cachedDir->GetStatusForMember(path, bRecursive, bFetch);
468 return m_mostRecentStatus;
471 ATLTRACE(_T("ignored no good path %s\n"), path.GetWinPath());
472 m_mostRecentStatus = CStatusCacheEntry();
473 if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.HasSVNAdminDir(path.GetWinPath(), true))
475 ATLTRACE(_T("force status %s\n"), path.GetWinPath());
476 m_mostRecentStatus.ForceStatus(git_wc_status_normal);
478 return m_mostRecentStatus;
481 void CGitStatusCache::AddFolderForCrawling(const CTGitPath& path)
483 m_folderCrawler.AddDirectoryForUpdate(path);
486 void CGitStatusCache::CloseWatcherHandles(HDEVNOTIFY hdev)
488 CTGitPath path = watcher.CloseInfoMap(hdev);
489 m_folderCrawler.BlockPath(path);
492 void CGitStatusCache::CloseWatcherHandles(const CTGitPath& path)
494 watcher.CloseHandlesForPath(path);
495 m_folderCrawler.BlockPath(path);