1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008,2011, 2014 - TortoiseSVN
4 // Copyright (C) 2008-2017, 2019, 2023 - 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 "ShellCache.h"
22 #include "GitFolderStatus.h"
23 #include "UnicodeUtils.h"
24 #include "..\TGitCache\CacheInterface.h"
28 extern ShellCache g_ShellCache
;
30 GitFolderStatus::GitFolderStatus()
32 sCacheKey
.reserve(MAX_PATH
);
34 g_Git
.SetCurrentDir(L
"");
35 m_hInvalidationEvent
= CreateEvent(nullptr, FALSE
, FALSE
, L
"TortoiseGitCacheInvalidationEvent"); // no need to explicitly close m_hInvalidationEvent in ~GitFolderStatus as it is CAutoGeneralHandle
38 GitFolderStatus::~GitFolderStatus()
42 const FileStatusCacheEntry
* GitFolderStatus::BuildCache(const CTGitPath
& filepath
, const CString
& /*sProjectRoot*/, BOOL bIsFolder
, BOOL bDirectFolder
)
44 //dont' build the cache if an instance of TortoiseGitProc is running
45 //since this could interfere with svn commands running (concurrent
46 //access of the .git directory).
47 if (g_ShellCache
.BlockStatus())
49 CAutoGeneralHandle TGitMutex
= ::CreateMutex(nullptr, FALSE
, L
"TortoiseGitProc.exe");
50 if (TGitMutex
!= nullptr)
52 if (::GetLastError() == ERROR_ALREADY_EXISTS
)
53 return &invalidstatus
;
63 // NOTE: see not in GetFullStatus about project inside another project, we should only get here when
64 // that occurs, and this is not correctly handled yet
66 // initialize record members
67 dirstat
.status
= git_wc_status_none
;
68 dirstat
.askedcounter
= GITFOLDERSTATUS_CACHETIMES
;
69 dirstat
.assumeValid
= FALSE
;
70 dirstat
.skipWorktree
= FALSE
;
73 // rev.kind = git_opt_revision_unspecified;
77 dirstat
.status
= dirstatus
->status
;
78 m_cache
[filepath
.GetWinPath()] = dirstat
;
79 m_TimeStamp
= GetTickCount64();
84 git_wc_status2_t status
= { git_wc_status_none
, false, false };
89 t1
= ::GetCurrentTime();
90 if (m_GitStatus
.GetAllStatus(filepath
, g_ShellCache
.GetCacheType() != ShellCache::dll
, status
))
91 status
= { git_wc_status_none
, false, false };
92 t2
= ::GetCurrentTime();
96 status
= { git_wc_status_none
, false, false };
99 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": building cache for %s - time %d\n", filepath
.GetWinPath(), t2
- t1
);
101 m_TimeStamp
= GetTickCount64();
102 FileStatusCacheEntry
* ret
= nullptr;
104 if (wcslen(filepath
.GetWinPath()) == 3)
105 ret
= &m_cache
[static_cast<LPCWSTR
>(filepath
.GetWinPathString().Left(2))];
107 ret
= &m_cache
[filepath
.GetWinPath()];
111 ret
->status
= status
.status
;
112 ret
->assumeValid
= status
.assumeValid
;
113 ret
->skipWorktree
= status
.skipWorktree
;
116 m_mostRecentPath
= filepath
;
117 m_mostRecentStatus
= ret
;
121 return &invalidstatus
;
124 ULONGLONG
GitFolderStatus::GetTimeoutValue()
126 ULONGLONG timeout
= GITFOLDERSTATUS_CACHETIMEOUT
;
127 ULONGLONG factor
= static_cast<ULONGLONG
>(m_cache
.size()) / 200UL;
130 return factor
*timeout
;
133 const FileStatusCacheEntry
* GitFolderStatus::GetFullStatus(const CTGitPath
& filepath
, BOOL bIsFolder
)
135 CString sProjectRoot
;
136 BOOL bHasAdminDir
= g_ShellCache
.HasGITAdminDir(filepath
.GetWinPath(), bIsFolder
, &sProjectRoot
);
138 //no overlay for unversioned folders
140 return &invalidstatus
;
141 //for the SVNStatus column, we have to check the cache to see
142 //if it's not just unversioned but ignored
143 const FileStatusCacheEntry
* ret
= GetCachedItem(filepath
);
144 if ((ret
)&&(ret
->status
== git_wc_status_unversioned
)&&(bIsFolder
)&&(bHasAdminDir
))
146 // an 'unversioned' folder, but with an ADMIN dir --> nested layout!
147 // NOTE: this could be a sub-project in git, or just some standalone project inside of another, either way a TODO
148 ret
= BuildCache(filepath
, sProjectRoot
, bIsFolder
, TRUE
);
152 return &invalidstatus
;
157 //if it's not in the cache and has no admin dir, then we assume
158 //it's not ignored too
159 ret
= BuildCache(filepath
, sProjectRoot
, bIsFolder
);
163 return &invalidstatus
;
166 const FileStatusCacheEntry
* GitFolderStatus::GetCachedItem(const CTGitPath
& filepath
)
168 sCacheKey
.assign(filepath
.GetWinPath());
169 FileStatusMap::const_iterator iter
;
170 const FileStatusCacheEntry
* retVal
= nullptr;
172 if(m_mostRecentPath
.IsEquivalentTo(CTGitPath(sCacheKey
.c_str())))
174 // We've hit the same result as we were asked for last time
175 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": fast cache hit for %s\n", filepath
.GetWinPath());
176 retVal
= m_mostRecentStatus
;
178 else if ((iter
= m_cache
.find(sCacheKey
)) != m_cache
.end())
180 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": cache found for %s\n", filepath
.GetWinPath());
181 retVal
= &iter
->second
;
182 m_mostRecentStatus
= retVal
;
183 m_mostRecentPath
= CTGitPath(sCacheKey
.c_str());
189 // We found something in a cache - check that the cache is not timed-out or force-invalidated
190 ULONGLONG now
= GetTickCount64();
192 if ((now
>= m_TimeStamp
) && ((now
- m_TimeStamp
) > GetTimeoutValue()))
194 // Cache is timed-out
195 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Cache timed-out\n");
199 else if (WaitForSingleObject(m_hInvalidationEvent
, 0) == WAIT_OBJECT_0
)
201 // TortoiseGitProc has just done something which has invalidated the cache
202 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Cache invalidated\n");
209 void GitFolderStatus::ClearCache()
212 m_mostRecentStatus
= nullptr;
213 m_mostRecentPath
.Reset();
214 // If we're about to rebuild the cache, there's no point hanging on to
215 // an event which tells us that it's invalid
216 ResetEvent(m_hInvalidationEvent
);