1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2008 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
20 #include "ShellExt.h"
\r
21 #include "GitFolderStatus.h"
\r
22 #include "UnicodeUtils.h"
\r
23 #include "..\TGitCache\CacheInterface.h"
\r
25 //#include "GitGlobal.h"
\r
26 #include "gitindex.h"
\r
28 extern ShellCache g_ShellCache;
\r
30 extern CGitIndexFileMap g_IndexFileMap;
\r
32 // get / auto-alloc a string "copy"
\r
34 const char* StringPool::GetString (const char* value)
\r
36 // special case: NULL pointer
\r
43 // do we already have a string with the desired value?
\r
45 pool_type::const_iterator iter = pool.find (value);
\r
46 if (iter != pool.end())
\r
54 const char* newString = _strdup (value);
\r
57 pool.insert (newString);
\r
67 // clear internal pool
\r
69 void StringPool::clear()
\r
71 // delete all strings
\r
73 for (pool_type::iterator iter = pool.begin(), end = pool.end(); iter != end; ++iter)
\r
78 // remove pointers from pool
\r
83 CTGitPath GitFolderStatus::folderpath;
\r
86 GitFolderStatus::GitFolderStatus(void)
\r
90 invalidstatus.author = emptyString;
\r
91 invalidstatus.askedcounter = -1;
\r
92 invalidstatus.status = git_wc_status_none;
\r
93 invalidstatus.url = emptyString;
\r
94 // invalidstatus.rev = -1;
\r
95 invalidstatus.owner = emptyString;
\r
96 invalidstatus.needslock = false;
\r
97 invalidstatus.tree_conflict = false;
\r
100 sCacheKey.reserve(MAX_PATH);
\r
102 //rootpool = svn_pool_create (NULL);
\r
104 m_hInvalidationEvent = CreateEvent(NULL, FALSE, FALSE, _T("TortoiseGitCacheInvalidationEvent"));
\r
107 GitFolderStatus::~GitFolderStatus(void)
\r
109 //svn_pool_destroy(rootpool);
\r
110 CloseHandle(m_hInvalidationEvent);
\r
113 const FileStatusCacheEntry * GitFolderStatus::BuildCache(const CTGitPath& filepath, const CString& sProjectRoot, BOOL bIsFolder, BOOL bDirectFolder)
\r
115 // svn_client_ctx_t * localctx;
\r
116 // apr_hash_t * statushash;
\r
117 // apr_pool_t * pool;
\r
118 //git_error_t * err = NULL; // If svn_client_status comes out through catch(...), err would else be unassigned
\r
119 git_error_t err = 0;
\r
121 //dont' build the cache if an instance of TortoiseProc is running
\r
122 //since this could interfere with svn commands running (concurrent
\r
123 //access of the .git directory).
\r
124 if (g_ShellCache.BlockStatus())
\r
126 HANDLE TGitMutex = ::CreateMutex(NULL, FALSE, _T("TortoiseGitProc.exe"));
\r
127 if (TGitMutex != NULL)
\r
129 if (::GetLastError() == ERROR_ALREADY_EXISTS)
\r
131 ::CloseHandle(TGitMutex);
\r
132 return &invalidstatus;
\r
135 ::CloseHandle(TGitMutex);
\r
138 // pool = svn_pool_create (rootpool); // create the memory pool
\r
141 // svn_error_clear(svn_client_create_context(&localctx, pool));
\r
142 // set up the configuration
\r
143 // Note: I know this is an 'expensive' call, but without this, ignores
\r
144 // done in the global ignore pattern won't show up.
\r
145 if (g_ShellCache.ShowIgnoredOverlay())
\r
146 ;// svn_error_clear(svn_config_get_config (&(localctx->config), g_pConfigDir, pool));
\r
148 // strings pools are unused, now -> we may clear them
\r
154 ATLTRACE2(_T("building cache for %s\n"), filepath);
\r
159 // NOTE: see not in GetFullStatus about project inside another project, we should only get here when
\r
160 // that occurs, and this is not correctly handled yet
\r
162 // initialize record members
\r
163 // dirstat.rev = -1;
\r
164 dirstat.status = git_wc_status_none;
\r
165 dirstat.author = authors.GetString(NULL);
\r
166 dirstat.url = urls.GetString(NULL);
\r
167 dirstat.owner = owners.GetString(NULL);
\r
168 dirstat.askedcounter = GITFOLDERSTATUS_CACHETIMES;
\r
169 dirstat.needslock = false;
\r
170 dirstat.tree_conflict = false;
\r
173 // statushash = apr_hash_make(pool);
\r
174 // git_revnum_t youngest = GIT_INVALID_REVNUM;
\r
175 // git_opt_revision_t rev;
\r
176 // rev.kind = git_opt_revision_unspecified;
\r
179 folderpath = filepath;
\r
181 /*err = svn_client_status4 (&youngest,
\r
182 filepath.GetDirectory().GetSVNApiPath(pool),
\r
186 svn_depth_empty,//depth
\r
190 FALSE, //ignore externals
\r
203 /* if (dirstatus->entry)
\r
205 dirstat.author = authors.GetString (dirstatus->entry->cmt_author);
\r
206 dirstat.url = authors.GetString (dirstatus->entry->url);
\r
207 dirstat.rev = dirstatus->entry->cmt_rev;
\r
208 dirstat.owner = owners.GetString(dirstatus->entry->lock_owner);
\r
210 dirstat.status = GitStatus::GetMoreImportant(dirstatus->text_status, dirstatus->prop_status);
\r
211 // dirstat.tree_conflict = dirstatus->tree_conflict != NULL;
\r
213 m_cache[filepath.GetWinPath()] = dirstat;
\r
214 m_TimeStamp = GetTickCount();
\r
215 // svn_error_clear(err);
\r
216 // svn_pool_destroy (pool); //free allocated memory
\r
219 } // if (bIsFolder)
\r
223 //Fill in the cache with
\r
224 //all files inside the same folder as the asked file/folder is
\r
225 //since subversion can do this in one step
\r
226 // localctx->auth_baton = NULL;
\r
228 // statushash = apr_hash_make(pool);
\r
229 // git_revnum_t youngest = GIT_INVALID_REVNUM;
\r
230 // git_opt_revision_t rev;
\r
231 // rev.kind = git_opt_revision_unspecified;
\r
234 if (g_ShellCache.GetCacheType() == ShellCache::dll)
\r
236 // gitindex.h based status
\r
238 // extract the sub-path (relative to project root)
\r
240 CString s = filepath.GetWinPathString();
\r
241 if (s.GetLength() > sProjectRoot.GetLength())
\r
243 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() - 1/*otherwise it gets initial slash*/);
\r
246 git_wc_status_kind status;
\r
248 err = g_IndexFileMap.GetFileStatus((CString&)sProjectRoot,sSubPath,&status,true,true,fillstatusmap_idx,this);
\r
252 // extract the sub-path (relative to project root)
\r
253 //MessageBox(NULL, filepath.GetDirectory().GetWinPathString(), sProjectRoot, MB_OK);
\r
254 LPCSTR lpszSubPath = NULL;
\r
256 CString s = filepath.GetDirectory().GetWinPathString();
\r
257 if (s.GetLength() > sProjectRoot.GetLength())
\r
259 sSubPath = CStringA(s.Right(s.GetLength() - sProjectRoot.GetLength() - 1/*otherwise it gets initial slash*/));
\r
260 lpszSubPath = sSubPath;
\r
263 //if (lpszSubPath) MessageBoxA(NULL, lpszSubPath, "BuildCache", MB_OK);
\r
264 //MessageBoxA(NULL, CStringA(sProjectRoot), sSubPath, MB_OK);
\r
265 err = !wgEnumFiles(CStringA(sProjectRoot), lpszSubPath, WGEFF_NoRecurse|WGEFF_FullPath|WGEFF_DirStatusAll, &fillstatusmap, this);
\r
267 /*err = svn_client_status4 (&youngest,
\r
268 filepath.GetDirectory().GetSVNApiPath(pool),
\r
272 svn_depth_immediates, //depth
\r
276 FALSE, //ignore externals
\r
286 // Error present if function is not under version control
\r
289 // svn_error_clear(err);
\r
290 // svn_pool_destroy (pool); //free allocated memory
\r
291 return &invalidstatus;
\r
294 // svn_error_clear(err);
\r
295 // svn_pool_destroy (pool); //free allocated memory
\r
296 m_TimeStamp = GetTickCount();
\r
297 const FileStatusCacheEntry * ret = NULL;
\r
298 FileStatusMap::const_iterator iter;
\r
299 if ((iter = m_cache.find(filepath.GetWinPath())) != m_cache.end())
\r
301 ret = &iter->second;
\r
302 m_mostRecentPath = filepath;
\r
303 m_mostRecentStatus = ret;
\r
307 // for SUBST'ed drives, Subversion doesn't return a path with a backslash
\r
308 // e.g. G:\ but only G: when fetching the status. So search for that
\r
309 // path too before giving up.
\r
310 // This is especially true when right-clicking directly on a SUBST'ed
\r
311 // drive to get the context menu
\r
312 if (_tcslen(filepath.GetWinPath())==3)
\r
314 if ((iter = m_cache.find((LPCTSTR)filepath.GetWinPathString().Left(2))) != m_cache.end())
\r
316 ret = &iter->second;
\r
317 m_mostRecentPath = filepath;
\r
318 m_mostRecentStatus = ret;
\r
324 return &invalidstatus;
\r
327 DWORD GitFolderStatus::GetTimeoutValue()
\r
329 DWORD timeout = GITFOLDERSTATUS_CACHETIMEOUT;
\r
330 DWORD factor = m_cache.size()/200;
\r
333 return factor*timeout;
\r
336 const FileStatusCacheEntry * GitFolderStatus::GetFullStatus(const CTGitPath& filepath, BOOL bIsFolder, BOOL bColumnProvider)
\r
338 const FileStatusCacheEntry * ret = NULL;
\r
340 CString sProjectRoot;
\r
341 BOOL bHasAdminDir = g_ShellCache.HasSVNAdminDir(filepath.GetWinPath(), bIsFolder, &sProjectRoot);
\r
343 //no overlay for unversioned folders
\r
344 if ((!bColumnProvider)&&(!bHasAdminDir))
\r
345 return &invalidstatus;
\r
346 //for the SVNStatus column, we have to check the cache to see
\r
347 //if it's not just unversioned but ignored
\r
348 ret = GetCachedItem(filepath);
\r
349 if ((ret)&&(ret->status == git_wc_status_unversioned)&&(bIsFolder)&&(bHasAdminDir))
\r
351 // an 'unversioned' folder, but with an ADMIN dir --> nested layout!
\r
352 // NOTE: this could be a sub-project in git, or just some standalone project inside of another, either way a TODO
\r
353 ret = BuildCache(filepath, sProjectRoot, bIsFolder, TRUE);
\r
357 return &invalidstatus;
\r
362 //if it's not in the cache and has no admin dir, then we assume
\r
363 //it's not ignored too
\r
364 if ((bColumnProvider)&&(!bHasAdminDir))
\r
365 return &invalidstatus;
\r
366 ret = BuildCache(filepath, sProjectRoot, bIsFolder);
\r
370 return &invalidstatus;
\r
373 const FileStatusCacheEntry * GitFolderStatus::GetCachedItem(const CTGitPath& filepath)
\r
375 sCacheKey.assign(filepath.GetWinPath());
\r
376 FileStatusMap::const_iterator iter;
\r
377 const FileStatusCacheEntry *retVal;
\r
379 if(m_mostRecentPath.IsEquivalentTo(CTGitPath(sCacheKey.c_str())))
\r
381 // We've hit the same result as we were asked for last time
\r
382 ATLTRACE2(_T("fast cache hit for %s\n"), filepath);
\r
383 retVal = m_mostRecentStatus;
\r
385 else if ((iter = m_cache.find(sCacheKey)) != m_cache.end())
\r
387 ATLTRACE2(_T("cache found for %s\n"), filepath);
\r
388 retVal = &iter->second;
\r
389 m_mostRecentStatus = retVal;
\r
390 m_mostRecentPath = CTGitPath(sCacheKey.c_str());
\r
399 // We found something in a cache - check that the cache is not timed-out or force-invalidated
\r
400 DWORD now = GetTickCount();
\r
402 if ((now >= m_TimeStamp)&&((now - m_TimeStamp) > GetTimeoutValue()))
\r
404 // Cache is timed-out
\r
405 ATLTRACE("Cache timed-out\n");
\r
409 else if(WaitForSingleObject(m_hInvalidationEvent, 0) == WAIT_OBJECT_0)
\r
411 // TortoiseProc has just done something which has invalidated the cache
\r
412 ATLTRACE("Cache invalidated\n");
\r
421 BOOL GitFolderStatus::fillstatusmap(const struct wgFile_s *pFile, void *pUserData)
\r
423 GitFolderStatus *Stat = (GitFolderStatus*)pUserData;
\r
425 FileStatusMap &cache = Stat->m_cache;
\r
426 FileStatusCacheEntry s;
\r
427 s.needslock = false;
\r
428 s.tree_conflict = false;
\r
430 s.author = Stat->authors.GetString(NULL);
\r
431 s.url = Stat->urls.GetString(NULL);
\r
433 s.rev = ConvertHashToRevnum(pFile->sha1);
\r
434 s.owner = Stat->owners.GetString(NULL);
\r
436 s.status = git_wc_status_none;
\r
438 //s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
\r
439 //s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
\r
440 s.status = GitStatusFromWingit(pFile->nStatus);
\r
442 // TODO ?: s.blaha = pFile->nStage
\r
444 //s.lock = status->repos_lock;
\r
445 //s.tree_conflict = (status->tree_conflict != NULL);
\r
447 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
\r
449 if (pFile->sFileName)
\r
451 str = CUnicodeUtils::StdGetUnicode(pFile->sFileName);
\r
452 std::replace(str.begin(), str.end(), '/', '\\');
\r
453 //MessageBox(NULL, str.c_str(), _T(""), MB_OK);
\r
462 void GitFolderStatus::fillstatusmap_idx(CString &path,git_wc_status_kind status,void *pUserData)
\r
464 GitFolderStatus *Stat = (GitFolderStatus*)pUserData;
\r
466 FileStatusMap &cache = Stat->m_cache;
\r
467 FileStatusCacheEntry s;
\r
468 s.needslock = false;
\r
469 s.tree_conflict = false;
\r
471 s.author = Stat->authors.GetString(NULL);
\r
472 s.url = Stat->urls.GetString(NULL);
\r
474 s.owner = Stat->owners.GetString(NULL);
\r
478 //s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
\r
479 //s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
\r
480 //s.status = GitStatusFromWingit(pFile->nStatus);
\r
482 // TODO ?: s.blaha = pFile->nStage
\r
484 //s.lock = status->repos_lock;
\r
485 //s.tree_conflict = (status->tree_conflict != NULL);
\r
487 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
\r
489 //if (pFile->sFileName)
\r
491 // str = CUnicodeUtils::StdGetUnicode(pFile->sFileName);
\r
492 // std::replace(str.begin(), str.end(), '/', '\\');
\r
493 //MessageBox(NULL, str.c_str(), _T(""), MB_OK);
\r
497 if( path.Right(1) == _T("\\"))
\r
499 path=path.Left(path.GetLength()-1);
\r
509 git_error_t* GitFolderStatus::fillstatusmap(void * baton, const char * path, git_wc_status2_t * status, apr_pool_t * /*pool*/)
\r
511 GitFolderStatus * Stat = (GitFolderStatus *)baton;
\r
512 FileStatusMap * cache = &Stat->m_cache;
\r
513 FileStatusCacheEntry s;
\r
514 s.needslock = false;
\r
515 s.tree_conflict = false;
\r
516 if ((status)&&(status->entry))
\r
518 s.author = Stat->authors.GetString(status->entry->cmt_author);
\r
519 s.url = Stat->urls.GetString(status->entry->url);
\r
520 s.rev = status->entry->cmt_rev;
\r
521 s.owner = Stat->owners.GetString(status->entry->lock_owner);
\r
522 if (status->entry->present_props)
\r
523 s.needslock = strstr(status->entry->present_props, "svn:needs-lock") ? true : false;
\r
527 s.author = Stat->authors.GetString(NULL);
\r
528 s.url = Stat->urls.GetString(NULL);
\r
530 s.owner = Stat->owners.GetString(NULL);
\r
532 s.status = git_wc_status_none;
\r
535 s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
\r
536 s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
\r
537 s.lock = status->repos_lock;
\r
538 s.tree_conflict = (status->tree_conflict != NULL);
\r
540 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
\r
544 str = CUnicodeUtils::StdGetUnicode(path);
\r
545 std::replace(str.begin(), str.end(), '/', '\\');
\r
551 return GIT_NO_ERROR;
\r
554 git_error_t* GitFolderStatus::findfolderstatus(void * baton, const char * path, git_wc_status2_t * status, apr_pool_t * /*pool*/)
\r
556 GitFolderStatus * Stat = (GitFolderStatus *)baton;
\r
557 if ((Stat)&&(Stat->folderpath.IsEquivalentTo(CTGitPath(CString(path)))))
\r
559 Stat->dirstatus = status;
\r
562 return GIT_NO_ERROR;
\r
566 void GitFolderStatus::ClearCache()
\r
569 m_mostRecentStatus = NULL;
\r
570 m_mostRecentPath.Reset();
\r
571 // If we're about to rebuild the cache, there's no point hanging on to
\r
572 // an event which tells us that it's invalid
\r
573 ResetEvent(m_hInvalidationEvent);
\r