fixed "warning C4390: ';' : empty controlled statement found; is this the intent...
[TortoiseGit.git] / src / Git / GitFolderStatus.cpp
blob154197a7c26fd4de755abae0780c13124077e821
1 // TortoiseSVN - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "ShellExt.h"
21 #include "GitFolderStatus.h"
22 #include "UnicodeUtils.h"
23 #include "..\TGitCache\CacheInterface.h"
24 #include "Git.h"
25 //#include "GitGlobal.h"
26 #include "gitindex.h"
28 extern ShellCache g_ShellCache;
30 //extern CGitIndexFileMap g_IndexFileMap;
32 // get / auto-alloc a string "copy"
34 const char* StringPool::GetString (const char* value)
36 // special case: NULL pointer
38 if (value == NULL)
40 return emptyString;
43 // do we already have a string with the desired value?
45 pool_type::const_iterator iter = pool.find (value);
46 if (iter != pool.end())
48 // yes -> return it
49 return *iter;
52 // no -> add one
54 const char* newString = _strdup (value);
55 if (newString)
57 pool.insert (newString);
59 else
60 return emptyString;
62 // .. and return it
64 return newString;
67 // clear internal pool
69 void StringPool::clear()
71 // delete all strings
73 for (pool_type::iterator iter = pool.begin(), end = pool.end(); iter != end; ++iter)
75 free((void*)*iter);
78 // remove pointers from pool
80 pool.clear();
83 CTGitPath GitFolderStatus::folderpath;
86 GitFolderStatus::GitFolderStatus(void)
88 m_TimeStamp = 0;
89 emptyString[0] = 0;
90 invalidstatus.author = emptyString;
91 invalidstatus.askedcounter = -1;
92 invalidstatus.status = git_wc_status_none;
93 invalidstatus.url = emptyString;
94 // invalidstatus.rev = -1;
95 invalidstatus.owner = emptyString;
96 invalidstatus.needslock = false;
97 invalidstatus.tree_conflict = false;
98 m_nCounter = 0;
99 dirstatus = NULL;
100 sCacheKey.reserve(MAX_PATH);
102 //rootpool = svn_pool_create (NULL);
103 g_Git.SetCurrentDir(_T(""));
104 m_hInvalidationEvent = CreateEvent(NULL, FALSE, FALSE, _T("TortoiseGitCacheInvalidationEvent"));
107 GitFolderStatus::~GitFolderStatus(void)
109 //svn_pool_destroy(rootpool);
110 CloseHandle(m_hInvalidationEvent);
113 const FileStatusCacheEntry * GitFolderStatus::BuildCache(const CTGitPath& filepath, const CString& sProjectRoot, BOOL bIsFolder, BOOL bDirectFolder)
115 // svn_client_ctx_t * localctx;
116 // apr_hash_t * statushash;
117 // apr_pool_t * pool;
118 //git_error_t * err = NULL; // If svn_client_status comes out through catch(...), err would else be unassigned
119 git_error_t err = 0;
121 //dont' build the cache if an instance of TortoiseProc is running
122 //since this could interfere with svn commands running (concurrent
123 //access of the .git directory).
124 if (g_ShellCache.BlockStatus())
126 HANDLE TGitMutex = ::CreateMutex(NULL, FALSE, _T("TortoiseGitProc.exe"));
127 if (TGitMutex != NULL)
129 if (::GetLastError() == ERROR_ALREADY_EXISTS)
131 ::CloseHandle(TGitMutex);
132 return &invalidstatus;
135 ::CloseHandle(TGitMutex);
138 // pool = svn_pool_create (rootpool); // create the memory pool
140 ClearCache();
141 // svn_error_clear(svn_client_create_context(&localctx, pool));
142 // set up the configuration
143 // Note: I know this is an 'expensive' call, but without this, ignores
144 // done in the global ignore pattern won't show up.
145 // if (g_ShellCache.ShowIgnoredOverlay())
146 //;// svn_error_clear(svn_config_get_config (&(localctx->config), g_pConfigDir, pool));
148 // strings pools are unused, now -> we may clear them
150 authors.clear();
151 urls.clear();
152 owners.clear();
154 if (bIsFolder)
156 if (bDirectFolder)
158 // NOTE: see not in GetFullStatus about project inside another project, we should only get here when
159 // that occurs, and this is not correctly handled yet
161 // initialize record members
162 // dirstat.rev = -1;
163 dirstat.status = git_wc_status_none;
164 dirstat.author = authors.GetString(NULL);
165 dirstat.url = urls.GetString(NULL);
166 dirstat.owner = owners.GetString(NULL);
167 dirstat.askedcounter = GITFOLDERSTATUS_CACHETIMES;
168 dirstat.needslock = false;
169 dirstat.tree_conflict = false;
171 dirstatus = NULL;
172 // statushash = apr_hash_make(pool);
173 // git_revnum_t youngest = GIT_INVALID_REVNUM;
174 // git_opt_revision_t rev;
175 // rev.kind = git_opt_revision_unspecified;
178 folderpath = filepath;
180 /*err = svn_client_status4 (&youngest,
181 filepath.GetDirectory().GetSVNApiPath(pool),
182 &rev,
183 findfolderstatus,
184 this,
185 svn_depth_empty,//depth
186 TRUE, //getall
187 FALSE, //update
188 TRUE, //noignore
189 FALSE, //ignore externals
190 NULL,
191 localctx,
192 pool);*/
194 catch ( ... )
196 dirstatus = NULL;
200 if (dirstatus)
202 /* if (dirstatus->entry)
204 dirstat.author = authors.GetString (dirstatus->entry->cmt_author);
205 dirstat.url = authors.GetString (dirstatus->entry->url);
206 dirstat.rev = dirstatus->entry->cmt_rev;
207 dirstat.owner = owners.GetString(dirstatus->entry->lock_owner);
209 dirstat.status = GitStatus::GetMoreImportant(dirstatus->text_status, dirstatus->prop_status);
210 // dirstat.tree_conflict = dirstatus->tree_conflict != NULL;
212 m_cache[filepath.GetWinPath()] = dirstat;
213 m_TimeStamp = GetTickCount();
214 // svn_error_clear(err);
215 // svn_pool_destroy (pool); //free allocated memory
216 return &dirstat;
218 } // if (bIsFolder)
220 m_nCounter = 0;
222 //Fill in the cache with
223 //all files inside the same folder as the asked file/folder is
224 //since subversion can do this in one step
225 // localctx->auth_baton = NULL;
227 // statushash = apr_hash_make(pool);
228 // git_revnum_t youngest = GIT_INVALID_REVNUM;
229 // git_opt_revision_t rev;
230 // rev.kind = git_opt_revision_unspecified;
232 git_wc_status_kind status;
233 int t1,t2;
234 t2=t1=0;
237 git_depth_t depth = git_depth_infinity;
239 if (g_ShellCache.GetCacheType() == ShellCache::dll)
241 depth = git_depth_empty;
244 t1 = ::GetCurrentTime();
245 status = m_GitStatus.GetAllStatus(filepath, depth);
246 t2 = ::GetCurrentTime();
248 catch ( ... )
252 ATLTRACE2(_T("building cache for %s - time %d\n"), filepath.GetWinPath(), t2 -t1);
254 // Error present if function is not under version control
255 if (err != NULL)
257 // svn_error_clear(err);
258 // svn_pool_destroy (pool); //free allocated memory
259 return &invalidstatus;
262 // svn_error_clear(err);
263 // svn_pool_destroy (pool); //free allocated memory
264 m_TimeStamp = GetTickCount();
265 FileStatusCacheEntry * ret = NULL;
267 if (_tcslen(filepath.GetWinPath())==3)
268 ret = &m_cache[(LPCTSTR)filepath.GetWinPathString().Left(2)];
269 else
270 ret = &m_cache[filepath.GetWinPath()];
272 //memset(ret, 0, sizeof(FileStatusCacheEntry));
273 ret->status = status;
275 m_mostRecentPath = filepath;
276 m_mostRecentStatus = ret;
278 #if 0
279 FileStatusMap::const_iterator iter;
280 if ((iter = m_cache.find(filepath.GetWinPath())) != m_cache.end())
282 ret = &iter->second;
283 m_mostRecentPath = filepath;
284 m_mostRecentStatus = ret;
286 else
288 // for SUBST'ed drives, Subversion doesn't return a path with a backslash
289 // e.g. G:\ but only G: when fetching the status. So search for that
290 // path too before giving up.
291 // This is especially true when right-clicking directly on a SUBST'ed
292 // drive to get the context menu
293 if (_tcslen(filepath.GetWinPath())==3)
295 if ((iter = m_cache.find((LPCTSTR)filepath.GetWinPathString().Left(2))) != m_cache.end())
297 ret = &iter->second;
298 m_mostRecentPath = filepath;
299 m_mostRecentStatus = ret;
303 #endif
304 if (ret)
305 return ret;
306 return &invalidstatus;
309 DWORD GitFolderStatus::GetTimeoutValue()
311 DWORD timeout = GITFOLDERSTATUS_CACHETIMEOUT;
312 DWORD factor = m_cache.size()/200;
313 if (factor==0)
314 factor = 1;
315 return factor*timeout;
318 const FileStatusCacheEntry * GitFolderStatus::GetFullStatus(const CTGitPath& filepath, BOOL bIsFolder, BOOL bColumnProvider)
320 const FileStatusCacheEntry * ret = NULL;
322 CString sProjectRoot;
323 BOOL bHasAdminDir = g_ShellCache.HasSVNAdminDir(filepath.GetWinPath(), bIsFolder, &sProjectRoot);
325 //no overlay for unversioned folders
326 if ((!bColumnProvider)&&(!bHasAdminDir))
327 return &invalidstatus;
328 //for the SVNStatus column, we have to check the cache to see
329 //if it's not just unversioned but ignored
330 ret = GetCachedItem(filepath);
331 if ((ret)&&(ret->status == git_wc_status_unversioned)&&(bIsFolder)&&(bHasAdminDir))
333 // an 'unversioned' folder, but with an ADMIN dir --> nested layout!
334 // NOTE: this could be a sub-project in git, or just some standalone project inside of another, either way a TODO
335 ret = BuildCache(filepath, sProjectRoot, bIsFolder, TRUE);
336 if (ret)
337 return ret;
338 else
339 return &invalidstatus;
341 if (ret)
342 return ret;
344 //if it's not in the cache and has no admin dir, then we assume
345 //it's not ignored too
346 if ((bColumnProvider)&&(!bHasAdminDir))
347 return &invalidstatus;
348 ret = BuildCache(filepath, sProjectRoot, bIsFolder);
349 if (ret)
350 return ret;
351 else
352 return &invalidstatus;
355 const FileStatusCacheEntry * GitFolderStatus::GetCachedItem(const CTGitPath& filepath)
357 sCacheKey.assign(filepath.GetWinPath());
358 FileStatusMap::const_iterator iter;
359 const FileStatusCacheEntry *retVal;
361 if(m_mostRecentPath.IsEquivalentTo(CTGitPath(sCacheKey.c_str())))
363 // We've hit the same result as we were asked for last time
364 ATLTRACE2(_T("fast cache hit for %s\n"), filepath);
365 retVal = m_mostRecentStatus;
367 else if ((iter = m_cache.find(sCacheKey)) != m_cache.end())
369 ATLTRACE2(_T("cache found for %s\n"), filepath);
370 retVal = &iter->second;
371 m_mostRecentStatus = retVal;
372 m_mostRecentPath = CTGitPath(sCacheKey.c_str());
374 else
376 retVal = NULL;
379 if(retVal != NULL)
381 // We found something in a cache - check that the cache is not timed-out or force-invalidated
382 DWORD now = GetTickCount();
384 if ((now >= m_TimeStamp)&&((now - m_TimeStamp) > GetTimeoutValue()))
386 // Cache is timed-out
387 ATLTRACE("Cache timed-out\n");
388 ClearCache();
389 retVal = NULL;
391 else if(WaitForSingleObject(m_hInvalidationEvent, 0) == WAIT_OBJECT_0)
393 // TortoiseProc has just done something which has invalidated the cache
394 ATLTRACE("Cache invalidated\n");
395 ClearCache();
396 retVal = NULL;
398 return retVal;
400 return NULL;
403 BOOL GitFolderStatus::fillstatusmap(const struct wgFile_s *pFile, void *pUserData)
405 GitFolderStatus *Stat = (GitFolderStatus*)pUserData;
407 FileStatusMap &cache = Stat->m_cache;
408 FileStatusCacheEntry s;
409 s.needslock = false;
410 s.tree_conflict = false;
412 s.author = Stat->authors.GetString(NULL);
413 s.url = Stat->urls.GetString(NULL);
414 if (pFile->sha1)
415 s.rev = ConvertHashToRevnum(pFile->sha1);
416 s.owner = Stat->owners.GetString(NULL);
418 s.status = git_wc_status_none;
420 //s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
421 //s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
422 s.status = GitStatusFromWingit(pFile->nStatus);
424 // TODO ?: s.blaha = pFile->nStage
426 //s.lock = status->repos_lock;
427 //s.tree_conflict = (status->tree_conflict != NULL);
429 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
430 stdstring str;
431 if (pFile->sFileName)
433 str = pFile->sFileName;//CUnicodeUtils::StdGetUnicode(pFile->sFileName);
434 std::replace(str.begin(), str.end(), '/', '\\');
435 //MessageBox(NULL, str.c_str(), _T(""), MB_OK);
437 else
438 str = _T(" ");
439 cache[str] = s;
441 return FALSE;
444 void GitFolderStatus::fillstatusmap_idx(CString &path,git_wc_status_kind status,void *pUserData)
446 GitFolderStatus *Stat = (GitFolderStatus*)pUserData;
448 FileStatusMap &cache = Stat->m_cache;
449 FileStatusCacheEntry s;
450 s.needslock = false;
451 s.tree_conflict = false;
453 s.author = Stat->authors.GetString(NULL);
454 s.url = Stat->urls.GetString(NULL);
455 // s.rev = -1;
456 s.owner = Stat->owners.GetString(NULL);
458 s.status = status;
460 //s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
461 //s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
462 //s.status = GitStatusFromWingit(pFile->nStatus);
464 // TODO ?: s.blaha = pFile->nStage
466 //s.lock = status->repos_lock;
467 //s.tree_conflict = (status->tree_conflict != NULL);
469 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
470 //stdstring str;
471 //if (pFile->sFileName)
473 // str = CUnicodeUtils::StdGetUnicode(pFile->sFileName);
474 // std::replace(str.begin(), str.end(), '/', '\\');
475 //MessageBox(NULL, str.c_str(), _T(""), MB_OK);
477 //else
478 // str = _T(" ");
479 if( path.Right(1) == _T("\\"))
481 path=path.Left(path.GetLength()-1);
483 stdstring str;
484 str=path;
485 cache[str] = s;
487 return;
490 #if 0
491 git_error_t* GitFolderStatus::fillstatusmap(void * baton, const char * path, git_wc_status2_t * status, apr_pool_t * /*pool*/)
493 GitFolderStatus * Stat = (GitFolderStatus *)baton;
494 FileStatusMap * cache = &Stat->m_cache;
495 FileStatusCacheEntry s;
496 s.needslock = false;
497 s.tree_conflict = false;
498 if ((status)&&(status->entry))
500 s.author = Stat->authors.GetString(status->entry->cmt_author);
501 s.url = Stat->urls.GetString(status->entry->url);
502 s.rev = status->entry->cmt_rev;
503 s.owner = Stat->owners.GetString(status->entry->lock_owner);
504 if (status->entry->present_props)
505 s.needslock = strstr(status->entry->present_props, "svn:needs-lock") ? true : false;
507 else
509 s.author = Stat->authors.GetString(NULL);
510 s.url = Stat->urls.GetString(NULL);
511 s.rev = -1;
512 s.owner = Stat->owners.GetString(NULL);
514 s.status = git_wc_status_none;
515 if (status)
517 s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
518 s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
519 s.lock = status->repos_lock;
520 s.tree_conflict = (status->tree_conflict != NULL);
522 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
523 stdstring str;
524 if (path)
526 str = CUnicodeUtils::StdGetUnicode(path);
527 std::replace(str.begin(), str.end(), '/', '\\');
529 else
530 str = _T(" ");
531 (*cache)[str] = s;
533 return GIT_NO_ERROR;
536 git_error_t* GitFolderStatus::findfolderstatus(void * baton, const char * path, git_wc_status2_t * status, apr_pool_t * /*pool*/)
538 GitFolderStatus * Stat = (GitFolderStatus *)baton;
539 if ((Stat)&&(Stat->folderpath.IsEquivalentTo(CTGitPath(CString(path)))))
541 Stat->dirstatus = status;
544 return GIT_NO_ERROR;
546 #endif
548 void GitFolderStatus::ClearCache()
550 m_cache.clear();
551 m_mostRecentStatus = NULL;
552 m_mostRecentPath.Reset();
553 // If we're about to rebuild the cache, there's no point hanging on to
554 // an event which tells us that it's invalid
555 ResetEvent(m_hInvalidationEvent);