CommitDlg: make sure branch name is valid
[TortoiseGit.git] / src / Git / GitFolderStatus.cpp
blob2b3eb88592a5ccbd76f9cd2933b79f364b532fbb
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-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.
20 #include "stdafx.h"
21 #include "ShellExt.h"
22 #include "GitFolderStatus.h"
23 #include "UnicodeUtils.h"
24 #include "..\TGitCache\CacheInterface.h"
25 #include "Git.h"
26 //#include "GitGlobal.h"
27 #include "gitindex.h"
29 extern ShellCache g_ShellCache;
31 //extern CGitIndexFileMap g_IndexFileMap;
33 // get / auto-alloc a string "copy"
35 const char* StringPool::GetString (const char* value)
37 // special case: NULL pointer
39 if (value == NULL)
41 return emptyString;
44 // do we already have a string with the desired value?
46 pool_type::const_iterator iter = pool.find (value);
47 if (iter != pool.end())
49 // yes -> return it
50 return *iter;
53 // no -> add one
55 const char* newString = _strdup (value);
56 if (newString)
58 pool.insert (newString);
60 else
61 return emptyString;
63 // .. and return it
65 return newString;
68 // clear internal pool
70 void StringPool::clear()
72 // delete all strings
74 for (pool_type::iterator iter = pool.begin(), end = pool.end(); iter != end; ++iter)
76 free((void*)*iter);
79 // remove pointers from pool
81 pool.clear();
84 CTGitPath GitFolderStatus::folderpath;
87 GitFolderStatus::GitFolderStatus(void)
89 m_TimeStamp = 0;
90 emptyString[0] = 0;
91 invalidstatus.author = emptyString;
92 invalidstatus.askedcounter = -1;
93 invalidstatus.status = git_wc_status_none;
94 invalidstatus.url = emptyString;
95 // invalidstatus.rev = -1;
96 invalidstatus.owner = emptyString;
97 invalidstatus.needslock = false;
98 invalidstatus.tree_conflict = false;
99 m_nCounter = 0;
100 dirstatus = NULL;
101 sCacheKey.reserve(MAX_PATH);
103 //rootpool = svn_pool_create (NULL);
104 g_Git.SetCurrentDir(_T(""));
105 m_hInvalidationEvent = CreateEvent(NULL, FALSE, FALSE, _T("TortoiseGitCacheInvalidationEvent"));
108 GitFolderStatus::~GitFolderStatus(void)
110 //svn_pool_destroy(rootpool);
111 CloseHandle(m_hInvalidationEvent);
114 const FileStatusCacheEntry * GitFolderStatus::BuildCache(const CTGitPath& filepath, const CString& sProjectRoot, BOOL bIsFolder, BOOL bDirectFolder)
116 // svn_client_ctx_t * localctx;
117 // apr_hash_t * statushash;
118 // apr_pool_t * pool;
119 //git_error_t * err = NULL; // If svn_client_status comes out through catch(...), err would else be unassigned
120 git_error_t err = 0;
122 //dont' build the cache if an instance of TortoiseProc is running
123 //since this could interfere with svn commands running (concurrent
124 //access of the .git directory).
125 if (g_ShellCache.BlockStatus())
127 HANDLE TGitMutex = ::CreateMutex(NULL, FALSE, _T("TortoiseGitProc.exe"));
128 if (TGitMutex != NULL)
130 if (::GetLastError() == ERROR_ALREADY_EXISTS)
132 ::CloseHandle(TGitMutex);
133 return &invalidstatus;
136 ::CloseHandle(TGitMutex);
139 // pool = svn_pool_create (rootpool); // create the memory pool
141 ClearCache();
142 // svn_error_clear(svn_client_create_context(&localctx, pool));
143 // set up the configuration
144 // Note: I know this is an 'expensive' call, but without this, ignores
145 // done in the global ignore pattern won't show up.
146 // if (g_ShellCache.ShowIgnoredOverlay())
147 //;// svn_error_clear(svn_config_get_config (&(localctx->config), g_pConfigDir, pool));
149 // strings pools are unused, now -> we may clear them
151 authors.clear();
152 urls.clear();
153 owners.clear();
155 if (bIsFolder)
157 if (bDirectFolder)
159 // NOTE: see not in GetFullStatus about project inside another project, we should only get here when
160 // that occurs, and this is not correctly handled yet
162 // initialize record members
163 // dirstat.rev = -1;
164 dirstat.status = git_wc_status_none;
165 dirstat.author = authors.GetString(NULL);
166 dirstat.url = urls.GetString(NULL);
167 dirstat.owner = owners.GetString(NULL);
168 dirstat.askedcounter = GITFOLDERSTATUS_CACHETIMES;
169 dirstat.needslock = false;
170 dirstat.tree_conflict = false;
172 dirstatus = NULL;
173 // statushash = apr_hash_make(pool);
174 // git_revnum_t youngest = GIT_INVALID_REVNUM;
175 // git_opt_revision_t rev;
176 // rev.kind = git_opt_revision_unspecified;
179 folderpath = filepath;
181 /*err = svn_client_status4 (&youngest,
182 filepath.GetDirectory().GetSVNApiPath(pool),
183 &rev,
184 findfolderstatus,
185 this,
186 svn_depth_empty,//depth
187 TRUE, //getall
188 FALSE, //update
189 TRUE, //noignore
190 FALSE, //ignore externals
191 NULL,
192 localctx,
193 pool);*/
195 catch ( ... )
197 dirstatus = NULL;
201 if (dirstatus)
203 /* if (dirstatus->entry)
205 dirstat.author = authors.GetString (dirstatus->entry->cmt_author);
206 dirstat.url = authors.GetString (dirstatus->entry->url);
207 dirstat.rev = dirstatus->entry->cmt_rev;
208 dirstat.owner = owners.GetString(dirstatus->entry->lock_owner);
210 dirstat.status = GitStatus::GetMoreImportant(dirstatus->text_status, dirstatus->prop_status);
211 // dirstat.tree_conflict = dirstatus->tree_conflict != NULL;
213 m_cache[filepath.GetWinPath()] = dirstat;
214 m_TimeStamp = GetTickCount();
215 // svn_error_clear(err);
216 // svn_pool_destroy (pool); //free allocated memory
217 return &dirstat;
219 } // if (bIsFolder)
221 m_nCounter = 0;
223 //Fill in the cache with
224 //all files inside the same folder as the asked file/folder is
225 //since subversion can do this in one step
226 // localctx->auth_baton = NULL;
228 // statushash = apr_hash_make(pool);
229 // git_revnum_t youngest = GIT_INVALID_REVNUM;
230 // git_opt_revision_t rev;
231 // rev.kind = git_opt_revision_unspecified;
233 git_wc_status_kind status;
234 int t1,t2;
235 t2=t1=0;
238 git_depth_t depth = git_depth_infinity;
240 if (g_ShellCache.GetCacheType() == ShellCache::dll)
242 depth = git_depth_empty;
245 t1 = ::GetCurrentTime();
246 status = m_GitStatus.GetAllStatus(filepath, depth);
247 t2 = ::GetCurrentTime();
249 catch ( ... )
253 ATLTRACE2(_T("building cache for %s - time %d\n"), filepath.GetWinPath(), t2 -t1);
255 // Error present if function is not under version control
256 if (err != NULL)
258 // svn_error_clear(err);
259 // svn_pool_destroy (pool); //free allocated memory
260 return &invalidstatus;
263 // svn_error_clear(err);
264 // svn_pool_destroy (pool); //free allocated memory
265 m_TimeStamp = GetTickCount();
266 FileStatusCacheEntry * ret = NULL;
268 if (_tcslen(filepath.GetWinPath())==3)
269 ret = &m_cache[(LPCTSTR)filepath.GetWinPathString().Left(2)];
270 else
271 ret = &m_cache[filepath.GetWinPath()];
273 //memset(ret, 0, sizeof(FileStatusCacheEntry));
274 ret->status = status;
276 m_mostRecentPath = filepath;
277 m_mostRecentStatus = ret;
279 #if 0
280 FileStatusMap::const_iterator iter;
281 if ((iter = m_cache.find(filepath.GetWinPath())) != m_cache.end())
283 ret = &iter->second;
284 m_mostRecentPath = filepath;
285 m_mostRecentStatus = ret;
287 else
289 // for SUBST'ed drives, Subversion doesn't return a path with a backslash
290 // e.g. G:\ but only G: when fetching the status. So search for that
291 // path too before giving up.
292 // This is especially true when right-clicking directly on a SUBST'ed
293 // drive to get the context menu
294 if (_tcslen(filepath.GetWinPath())==3)
296 if ((iter = m_cache.find((LPCTSTR)filepath.GetWinPathString().Left(2))) != m_cache.end())
298 ret = &iter->second;
299 m_mostRecentPath = filepath;
300 m_mostRecentStatus = ret;
304 #endif
305 if (ret)
306 return ret;
307 return &invalidstatus;
310 DWORD GitFolderStatus::GetTimeoutValue()
312 DWORD timeout = GITFOLDERSTATUS_CACHETIMEOUT;
313 DWORD factor = m_cache.size()/200;
314 if (factor==0)
315 factor = 1;
316 return factor*timeout;
319 const FileStatusCacheEntry * GitFolderStatus::GetFullStatus(const CTGitPath& filepath, BOOL bIsFolder, BOOL bColumnProvider)
321 const FileStatusCacheEntry * ret = NULL;
323 CString sProjectRoot;
324 BOOL bHasAdminDir = g_ShellCache.HasGITAdminDir(filepath.GetWinPath(), bIsFolder, &sProjectRoot);
326 //no overlay for unversioned folders
327 if ((!bColumnProvider)&&(!bHasAdminDir))
328 return &invalidstatus;
329 //for the SVNStatus column, we have to check the cache to see
330 //if it's not just unversioned but ignored
331 ret = GetCachedItem(filepath);
332 if ((ret)&&(ret->status == git_wc_status_unversioned)&&(bIsFolder)&&(bHasAdminDir))
334 // an 'unversioned' folder, but with an ADMIN dir --> nested layout!
335 // NOTE: this could be a sub-project in git, or just some standalone project inside of another, either way a TODO
336 ret = BuildCache(filepath, sProjectRoot, bIsFolder, TRUE);
337 if (ret)
338 return ret;
339 else
340 return &invalidstatus;
342 if (ret)
343 return ret;
345 //if it's not in the cache and has no admin dir, then we assume
346 //it's not ignored too
347 if ((bColumnProvider)&&(!bHasAdminDir))
348 return &invalidstatus;
349 ret = BuildCache(filepath, sProjectRoot, bIsFolder);
350 if (ret)
351 return ret;
352 else
353 return &invalidstatus;
356 const FileStatusCacheEntry * GitFolderStatus::GetCachedItem(const CTGitPath& filepath)
358 sCacheKey.assign(filepath.GetWinPath());
359 FileStatusMap::const_iterator iter;
360 const FileStatusCacheEntry *retVal;
362 if(m_mostRecentPath.IsEquivalentTo(CTGitPath(sCacheKey.c_str())))
364 // We've hit the same result as we were asked for last time
365 ATLTRACE2(_T("fast cache hit for %s\n"), filepath);
366 retVal = m_mostRecentStatus;
368 else if ((iter = m_cache.find(sCacheKey)) != m_cache.end())
370 ATLTRACE2(_T("cache found for %s\n"), filepath);
371 retVal = &iter->second;
372 m_mostRecentStatus = retVal;
373 m_mostRecentPath = CTGitPath(sCacheKey.c_str());
375 else
377 retVal = NULL;
380 if(retVal != NULL)
382 // We found something in a cache - check that the cache is not timed-out or force-invalidated
383 DWORD now = GetTickCount();
385 if ((now >= m_TimeStamp)&&((now - m_TimeStamp) > GetTimeoutValue()))
387 // Cache is timed-out
388 ATLTRACE("Cache timed-out\n");
389 ClearCache();
390 retVal = NULL;
392 else if(WaitForSingleObject(m_hInvalidationEvent, 0) == WAIT_OBJECT_0)
394 // TortoiseProc has just done something which has invalidated the cache
395 ATLTRACE("Cache invalidated\n");
396 ClearCache();
397 retVal = NULL;
399 return retVal;
401 return NULL;
404 BOOL GitFolderStatus::fillstatusmap(const struct wgFile_s *pFile, void *pUserData)
406 GitFolderStatus *Stat = (GitFolderStatus*)pUserData;
408 FileStatusMap &cache = Stat->m_cache;
409 FileStatusCacheEntry s;
410 s.needslock = false;
411 s.tree_conflict = false;
413 s.author = Stat->authors.GetString(NULL);
414 s.url = Stat->urls.GetString(NULL);
415 if (pFile->sha1)
416 s.rev = ConvertHashToRevnum(pFile->sha1);
417 s.owner = Stat->owners.GetString(NULL);
419 s.status = git_wc_status_none;
421 //s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
422 //s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
423 s.status = GitStatusFromWingit(pFile->nStatus);
425 // TODO ?: s.blaha = pFile->nStage
427 //s.lock = status->repos_lock;
428 //s.tree_conflict = (status->tree_conflict != NULL);
430 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
431 stdstring str;
432 if (pFile->sFileName)
434 str = pFile->sFileName;//CUnicodeUtils::StdGetUnicode(pFile->sFileName);
435 std::replace(str.begin(), str.end(), '/', '\\');
436 //MessageBox(NULL, str.c_str(), _T(""), MB_OK);
438 else
439 str = _T(" ");
440 cache[str] = s;
442 return FALSE;
445 void GitFolderStatus::fillstatusmap_idx(CString &path,git_wc_status_kind status,void *pUserData)
447 GitFolderStatus *Stat = (GitFolderStatus*)pUserData;
449 FileStatusMap &cache = Stat->m_cache;
450 FileStatusCacheEntry s;
451 s.needslock = false;
452 s.tree_conflict = false;
454 s.author = Stat->authors.GetString(NULL);
455 s.url = Stat->urls.GetString(NULL);
456 // s.rev = -1;
457 s.owner = Stat->owners.GetString(NULL);
459 s.status = status;
461 //s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
462 //s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
463 //s.status = GitStatusFromWingit(pFile->nStatus);
465 // TODO ?: s.blaha = pFile->nStage
467 //s.lock = status->repos_lock;
468 //s.tree_conflict = (status->tree_conflict != NULL);
470 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
471 //stdstring str;
472 //if (pFile->sFileName)
474 // str = CUnicodeUtils::StdGetUnicode(pFile->sFileName);
475 // std::replace(str.begin(), str.end(), '/', '\\');
476 //MessageBox(NULL, str.c_str(), _T(""), MB_OK);
478 //else
479 // str = _T(" ");
480 if( path.Right(1) == _T("\\"))
482 path=path.Left(path.GetLength()-1);
484 stdstring str;
485 str=path;
486 cache[str] = s;
488 return;
491 #if 0
492 git_error_t* GitFolderStatus::fillstatusmap(void * baton, const char * path, git_wc_status2_t * status, apr_pool_t * /*pool*/)
494 GitFolderStatus * Stat = (GitFolderStatus *)baton;
495 FileStatusMap * cache = &Stat->m_cache;
496 FileStatusCacheEntry s;
497 s.needslock = false;
498 s.tree_conflict = false;
499 if ((status)&&(status->entry))
501 s.author = Stat->authors.GetString(status->entry->cmt_author);
502 s.url = Stat->urls.GetString(status->entry->url);
503 s.rev = status->entry->cmt_rev;
504 s.owner = Stat->owners.GetString(status->entry->lock_owner);
505 if (status->entry->present_props)
506 s.needslock = strstr(status->entry->present_props, "svn:needs-lock") ? true : false;
508 else
510 s.author = Stat->authors.GetString(NULL);
511 s.url = Stat->urls.GetString(NULL);
512 s.rev = -1;
513 s.owner = Stat->owners.GetString(NULL);
515 s.status = git_wc_status_none;
516 if (status)
518 s.status = GitStatus::GetMoreImportant(s.status, status->text_status);
519 s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);
520 s.lock = status->repos_lock;
521 s.tree_conflict = (status->tree_conflict != NULL);
523 s.askedcounter = GITFOLDERSTATUS_CACHETIMES;
524 stdstring str;
525 if (path)
527 str = CUnicodeUtils::StdGetUnicode(path);
528 std::replace(str.begin(), str.end(), '/', '\\');
530 else
531 str = _T(" ");
532 (*cache)[str] = s;
534 return GIT_NO_ERROR;
537 git_error_t* GitFolderStatus::findfolderstatus(void * baton, const char * path, git_wc_status2_t * status, apr_pool_t * /*pool*/)
539 GitFolderStatus * Stat = (GitFolderStatus *)baton;
540 if ((Stat)&&(Stat->folderpath.IsEquivalentTo(CTGitPath(CString(path)))))
542 Stat->dirstatus = status;
545 return GIT_NO_ERROR;
547 #endif
549 void GitFolderStatus::ClearCache()
551 m_cache.clear();
552 m_mostRecentStatus = NULL;
553 m_mostRecentPath.Reset();
554 // If we're about to rebuild the cache, there's no point hanging on to
555 // an event which tells us that it's invalid
556 ResetEvent(m_hInvalidationEvent);