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.
22 #include "GitFolderStatus.h"
23 #include "UnicodeUtils.h"
24 #include "..\TGitCache\CacheInterface.h"
26 //#include "GitGlobal.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
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())
55 const char* newString
= _strdup (value
);
58 pool
.insert (newString
);
68 // clear internal pool
70 void StringPool::clear()
74 for (pool_type::iterator iter
= pool
.begin(), end
= pool
.end(); iter
!= end
; ++iter
)
79 // remove pointers from pool
84 CTGitPath
GitFolderStatus::folderpath
;
87 GitFolderStatus::GitFolderStatus(void)
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;
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
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
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
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
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;
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),
186 svn_depth_empty,//depth
190 FALSE, //ignore externals
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
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
;
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();
253 ATLTRACE2(_T("building cache for %s - time %d\n"), filepath
.GetWinPath(), t2
-t1
);
255 // Error present if function is not under version control
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)];
271 ret
= &m_cache
[filepath
.GetWinPath()];
273 //memset(ret, 0, sizeof(FileStatusCacheEntry));
274 ret
->status
= status
;
276 m_mostRecentPath
= filepath
;
277 m_mostRecentStatus
= ret
;
280 FileStatusMap::const_iterator iter
;
281 if ((iter
= m_cache
.find(filepath
.GetWinPath())) != m_cache
.end())
284 m_mostRecentPath
= filepath
;
285 m_mostRecentStatus
= ret
;
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())
299 m_mostRecentPath
= filepath
;
300 m_mostRecentStatus
= ret
;
307 return &invalidstatus
;
310 DWORD
GitFolderStatus::GetTimeoutValue()
312 DWORD timeout
= GITFOLDERSTATUS_CACHETIMEOUT
;
313 DWORD factor
= m_cache
.size()/200;
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
);
340 return &invalidstatus
;
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
);
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());
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");
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");
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
;
411 s
.tree_conflict
= false;
413 s
.author
= Stat
->authors
.GetString(NULL
);
414 s
.url
= Stat
->urls
.GetString(NULL
);
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
;
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);
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
;
452 s
.tree_conflict
= false;
454 s
.author
= Stat
->authors
.GetString(NULL
);
455 s
.url
= Stat
->urls
.GetString(NULL
);
457 s
.owner
= Stat
->owners
.GetString(NULL
);
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
;
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);
480 if( path
.Right(1) == _T("\\"))
482 path
=path
.Left(path
.GetLength()-1);
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
;
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;
510 s
.author
= Stat
->authors
.GetString(NULL
);
511 s
.url
= Stat
->urls
.GetString(NULL
);
513 s
.owner
= Stat
->owners
.GetString(NULL
);
515 s
.status
= git_wc_status_none
;
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
;
527 str
= CUnicodeUtils::StdGetUnicode(path
);
528 std::replace(str
.begin(), str
.end(), '/', '\\');
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
;
549 void GitFolderStatus::ClearCache()
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
);