Merge branch 'scintilla-404'
[TortoiseGit.git] / src / Git / GitFolderStatus.cpp
blobd5773308ceb9e4278850fb9845f779613390db46
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008,2011, 2014 - TortoiseSVN
4 // Copyright (C) 2008-2017 - 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 "ShellCache.h"
22 #include "GitFolderStatus.h"
23 #include "UnicodeUtils.h"
24 #include "..\TGitCache\CacheInterface.h"
25 #include "Git.h"
26 #include "gitindex.h"
28 extern ShellCache g_ShellCache;
30 GitFolderStatus::GitFolderStatus(void)
32 m_TimeStamp = 0;
33 invalidstatus.askedcounter = -1;
34 invalidstatus.status = git_wc_status_none;
35 invalidstatus.assumeValid = FALSE;
36 invalidstatus.skipWorktree = FALSE;
37 dirstat.askedcounter = -1;
38 dirstat.assumeValid = dirstat.skipWorktree = false;
39 dirstat.status = git_wc_status_none;
40 dirstatus = nullptr;
41 m_mostRecentStatus = nullptr;
42 sCacheKey.reserve(MAX_PATH);
44 g_Git.SetCurrentDir(L"");
45 m_hInvalidationEvent = CreateEvent(nullptr, FALSE, FALSE, L"TortoiseGitCacheInvalidationEvent"); // no need to explicitly close m_hInvalidationEvent in ~GitFolderStatus as it is CAutoGeneralHandle
48 GitFolderStatus::~GitFolderStatus(void)
52 const FileStatusCacheEntry * GitFolderStatus::BuildCache(const CTGitPath& filepath, const CString& /*sProjectRoot*/, BOOL bIsFolder, BOOL bDirectFolder)
54 //dont' build the cache if an instance of TortoiseGitProc is running
55 //since this could interfere with svn commands running (concurrent
56 //access of the .git directory).
57 if (g_ShellCache.BlockStatus())
59 CAutoGeneralHandle TGitMutex = ::CreateMutex(nullptr, FALSE, L"TortoiseGitProc.exe");
60 if (TGitMutex != nullptr)
62 if (::GetLastError() == ERROR_ALREADY_EXISTS)
63 return &invalidstatus;
67 ClearCache();
69 if (bIsFolder)
71 if (bDirectFolder)
73 // NOTE: see not in GetFullStatus about project inside another project, we should only get here when
74 // that occurs, and this is not correctly handled yet
76 // initialize record members
77 dirstat.status = git_wc_status_none;
78 dirstat.askedcounter = GITFOLDERSTATUS_CACHETIMES;
79 dirstat.assumeValid = FALSE;
80 dirstat.skipWorktree = FALSE;
82 dirstatus = nullptr;
83 // rev.kind = git_opt_revision_unspecified;
86 if (dirstatus)
87 dirstat.status = dirstatus->status;
88 m_cache[filepath.GetWinPath()] = dirstat;
89 m_TimeStamp = GetTickCount64();
90 return &dirstat;
92 } // if (bIsFolder)
94 git_wc_status2_t status = { git_wc_status_none, false, false };
95 int t1,t2;
96 t2=t1=0;
97 try
99 t1 = ::GetCurrentTime();
100 if (m_GitStatus.GetAllStatus(filepath, g_ShellCache.GetCacheType() != ShellCache::dll, status))
101 status = { git_wc_status_none, false, false };
102 t2 = ::GetCurrentTime();
104 catch ( ... )
106 status = { git_wc_status_none, false, false };
109 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": building cache for %s - time %d\n", filepath.GetWinPath(), t2 - t1);
111 m_TimeStamp = GetTickCount64();
112 FileStatusCacheEntry* ret = nullptr;
114 if (wcslen(filepath.GetWinPath()) == 3)
115 ret = &m_cache[(LPCTSTR)filepath.GetWinPathString().Left(2)];
116 else
117 ret = &m_cache[filepath.GetWinPath()];
119 if (ret)
121 ret->status = status.status;
122 ret->assumeValid = status.assumeValid;
123 ret->skipWorktree = status.skipWorktree;
126 m_mostRecentPath = filepath;
127 m_mostRecentStatus = ret;
129 if (ret)
130 return ret;
131 return &invalidstatus;
134 ULONGLONG GitFolderStatus::GetTimeoutValue()
136 ULONGLONG timeout = GITFOLDERSTATUS_CACHETIMEOUT;
137 ULONGLONG factor = (ULONGLONG)m_cache.size() / 200UL;
138 if (factor==0)
139 factor = 1;
140 return factor*timeout;
143 const FileStatusCacheEntry * GitFolderStatus::GetFullStatus(const CTGitPath& filepath, BOOL bIsFolder)
145 CString sProjectRoot;
146 BOOL bHasAdminDir = g_ShellCache.HasGITAdminDir(filepath.GetWinPath(), bIsFolder, &sProjectRoot);
148 //no overlay for unversioned folders
149 if (!bHasAdminDir)
150 return &invalidstatus;
151 //for the SVNStatus column, we have to check the cache to see
152 //if it's not just unversioned but ignored
153 const FileStatusCacheEntry* ret = GetCachedItem(filepath);
154 if ((ret)&&(ret->status == git_wc_status_unversioned)&&(bIsFolder)&&(bHasAdminDir))
156 // an 'unversioned' folder, but with an ADMIN dir --> nested layout!
157 // NOTE: this could be a sub-project in git, or just some standalone project inside of another, either way a TODO
158 ret = BuildCache(filepath, sProjectRoot, bIsFolder, TRUE);
159 if (ret)
160 return ret;
161 else
162 return &invalidstatus;
164 if (ret)
165 return ret;
167 //if it's not in the cache and has no admin dir, then we assume
168 //it's not ignored too
169 ret = BuildCache(filepath, sProjectRoot, bIsFolder);
170 if (ret)
171 return ret;
172 else
173 return &invalidstatus;
176 const FileStatusCacheEntry * GitFolderStatus::GetCachedItem(const CTGitPath& filepath)
178 sCacheKey.assign(filepath.GetWinPath());
179 FileStatusMap::const_iterator iter;
180 const FileStatusCacheEntry* retVal = nullptr;
182 if(m_mostRecentPath.IsEquivalentTo(CTGitPath(sCacheKey.c_str())))
184 // We've hit the same result as we were asked for last time
185 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": fast cache hit for %s\n", filepath.GetWinPath());
186 retVal = m_mostRecentStatus;
188 else if ((iter = m_cache.find(sCacheKey)) != m_cache.end())
190 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": cache found for %s\n", filepath.GetWinPath());
191 retVal = &iter->second;
192 m_mostRecentStatus = retVal;
193 m_mostRecentPath = CTGitPath(sCacheKey.c_str());
196 if (!retVal)
197 return nullptr;
199 // We found something in a cache - check that the cache is not timed-out or force-invalidated
200 ULONGLONG now = GetTickCount64();
202 if ((now >= m_TimeStamp) && ((now - m_TimeStamp) > GetTimeoutValue()))
204 // Cache is timed-out
205 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Cache timed-out\n");
206 ClearCache();
207 return nullptr;
209 else if (WaitForSingleObject(m_hInvalidationEvent, 0) == WAIT_OBJECT_0)
211 // TortoiseGitProc has just done something which has invalidated the cache
212 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Cache invalidated\n");
213 ClearCache();
214 return nullptr;
216 return retVal;
219 void GitFolderStatus::ClearCache()
221 m_cache.clear();
222 m_mostRecentStatus = nullptr;
223 m_mostRecentPath.Reset();
224 // If we're about to rebuild the cache, there's no point hanging on to
225 // an event which tells us that it's invalid
226 ResetEvent(m_hInvalidationEvent);