OSDN Git Service

initial icon overlay support using the wingit lib
[tortoisegit/TortoiseGitJp.git] / src / Git / GitFolderStatus.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2003-2008 - TortoiseSVN\r
4 \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
9 \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
14 \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
18 \r
19 #include "stdafx.h"\r
20 #include "ShellExt.h"\r
21 #include "GitFolderStatus.h"\r
22 #include "UnicodeUtils.h"\r
23 #include "..\TGitCache\CacheInterface.h"\r
24 //#include "GitGlobal.h"\r
25 \r
26 extern ShellCache g_ShellCache;\r
27 \r
28 \r
29 // get / auto-alloc a string "copy"\r
30 \r
31 const char* StringPool::GetString (const char* value)\r
32 {\r
33         // special case: NULL pointer\r
34         \r
35         if (value == NULL)\r
36         {\r
37                 return emptyString;\r
38         }\r
39 \r
40         // do we already have a string with the desired value?\r
41 \r
42         pool_type::const_iterator iter = pool.find (value);\r
43         if (iter != pool.end())\r
44         {\r
45                 // yes -> return it\r
46                 return *iter;\r
47         }\r
48         \r
49         // no -> add one        \r
50         \r
51         const char* newString =  _strdup (value);\r
52         if (newString)\r
53         {\r
54                 pool.insert (newString);\r
55         }\r
56         else\r
57                 return emptyString;\r
58         \r
59         // .. and return it\r
60         \r
61         return newString;\r
62 }\r
63 \r
64 // clear internal pool\r
65 \r
66 void StringPool::clear()\r
67 {\r
68         // delete all strings\r
69 \r
70         for (pool_type::iterator iter = pool.begin(), end = pool.end(); iter != end; ++iter)\r
71         {\r
72                 free((void*)*iter);\r
73         }\r
74                 \r
75         // remove pointers from pool\r
76                 \r
77         pool.clear();\r
78 }\r
79 \r
80 CTGitPath       GitFolderStatus::folderpath;\r
81 \r
82 \r
83 GitFolderStatus::GitFolderStatus(void)\r
84 {\r
85         m_TimeStamp = 0;\r
86         emptyString[0] = 0;\r
87         invalidstatus.author = emptyString;\r
88         invalidstatus.askedcounter = -1;\r
89         invalidstatus.status = git_wc_status_none;\r
90         invalidstatus.url = emptyString;\r
91 //      invalidstatus.rev = -1;\r
92         invalidstatus.owner = emptyString;\r
93         invalidstatus.needslock = false;\r
94         invalidstatus.tree_conflict = false;\r
95         m_nCounter = 0;\r
96         dirstatus = NULL;\r
97         sCacheKey.reserve(MAX_PATH);\r
98 \r
99         //rootpool = svn_pool_create (NULL);\r
100 \r
101         m_hInvalidationEvent = CreateEvent(NULL, FALSE, FALSE, _T("TortoiseGitCacheInvalidationEvent"));\r
102 }\r
103 \r
104 GitFolderStatus::~GitFolderStatus(void)\r
105 {\r
106         //svn_pool_destroy(rootpool);\r
107         CloseHandle(m_hInvalidationEvent);\r
108 }\r
109 \r
110 const FileStatusCacheEntry * GitFolderStatus::BuildCache(const CTGitPath& filepath, const CString& sProjectRoot, BOOL bIsFolder, BOOL bDirectFolder)\r
111 {\r
112 //      svn_client_ctx_t *                      localctx;\r
113 //      apr_hash_t *                            statushash;\r
114 //      apr_pool_t *                            pool;\r
115         //git_error_t *                         err = NULL; // If svn_client_status comes out through catch(...), err would else be unassigned\r
116         BOOL err = FALSE;\r
117 \r
118         //dont' build the cache if an instance of TortoiseProc is running\r
119         //since this could interfere with svn commands running (concurrent\r
120         //access of the .git directory).\r
121         if (g_ShellCache.BlockStatus())\r
122         {\r
123                 HANDLE TGitMutex = ::CreateMutex(NULL, FALSE, _T("TortoiseGitProc.exe"));       \r
124                 if (TGitMutex != NULL)\r
125                 {\r
126                         if (::GetLastError() == ERROR_ALREADY_EXISTS)\r
127                         {\r
128                                 ::CloseHandle(TGitMutex);\r
129                                 return &invalidstatus;\r
130                         }\r
131                 }\r
132                 ::CloseHandle(TGitMutex);\r
133         }\r
134 \r
135 //      pool = svn_pool_create (rootpool);                              // create the memory pool\r
136 \r
137         ClearCache();\r
138 //      svn_error_clear(svn_client_create_context(&localctx, pool));\r
139         // set up the configuration\r
140         // Note: I know this is an 'expensive' call, but without this, ignores\r
141         // done in the global ignore pattern won't show up.\r
142         if (g_ShellCache.ShowIgnoredOverlay())\r
143 ;//             svn_error_clear(svn_config_get_config (&(localctx->config), g_pConfigDir, pool));\r
144 \r
145         // strings pools are unused, now -> we may clear them\r
146         \r
147         authors.clear();\r
148         urls.clear();\r
149         owners.clear();\r
150         \r
151         ATLTRACE2(_T("building cache for %s\n"), filepath);\r
152         if (bIsFolder)\r
153         {\r
154                 if (bDirectFolder)\r
155                 {\r
156                         // NOTE: see not in GetFullStatus about project inside another project, we should only get here when\r
157                         //       that occurs, and this is not correctly handled yet\r
158 \r
159                         // initialize record members\r
160 //                      dirstat.rev = -1;\r
161                         dirstat.status = git_wc_status_none;\r
162                         dirstat.author = authors.GetString(NULL);\r
163                         dirstat.url = urls.GetString(NULL);\r
164                         dirstat.owner = owners.GetString(NULL);\r
165                         dirstat.askedcounter = GITFOLDERSTATUS_CACHETIMES;\r
166                         dirstat.needslock = false;\r
167                         dirstat.tree_conflict = false;\r
168 \r
169                         dirstatus = NULL;\r
170 //                      statushash = apr_hash_make(pool);\r
171 //                      git_revnum_t youngest = GIT_INVALID_REVNUM;\r
172 //                      git_opt_revision_t rev;\r
173 //                      rev.kind = git_opt_revision_unspecified;\r
174                         try\r
175                         {\r
176                                 folderpath = filepath;\r
177 \r
178                                 /*err = svn_client_status4 (&youngest,\r
179                                         filepath.GetDirectory().GetSVNApiPath(pool),\r
180                                         &rev,\r
181                                         findfolderstatus,\r
182                                         this,\r
183                                         svn_depth_empty,//depth\r
184                                         TRUE,           //getall\r
185                                         FALSE,          //update\r
186                                         TRUE,           //noignore\r
187                                         FALSE,          //ignore externals\r
188                                         NULL,\r
189                                         localctx,\r
190                                         pool);*/\r
191                         }\r
192                         catch ( ... )\r
193                         {\r
194                                 dirstatus = NULL;\r
195                         }\r
196 \r
197 \r
198                         if (dirstatus)\r
199                         {\r
200 /*                              if (dirstatus->entry)\r
201                                 {\r
202                                         dirstat.author = authors.GetString (dirstatus->entry->cmt_author);\r
203                                         dirstat.url = authors.GetString (dirstatus->entry->url);\r
204                                         dirstat.rev = dirstatus->entry->cmt_rev;\r
205                                         dirstat.owner = owners.GetString(dirstatus->entry->lock_owner);\r
206                                 }*/\r
207                                 dirstat.status = GitStatus::GetMoreImportant(dirstatus->text_status, dirstatus->prop_status);\r
208 //                              dirstat.tree_conflict = dirstatus->tree_conflict != NULL;\r
209                         }\r
210                         m_cache[filepath.GetWinPath()] = dirstat;\r
211                         m_TimeStamp = GetTickCount();\r
212 //                      svn_error_clear(err);\r
213 //                      svn_pool_destroy (pool);                                //free allocated memory\r
214                         return &dirstat;\r
215                 }\r
216         } // if (bIsFolder) \r
217         \r
218         m_nCounter = 0;\r
219         \r
220         //Fill in the cache with\r
221         //all files inside the same folder as the asked file/folder is\r
222         //since subversion can do this in one step\r
223 //      localctx->auth_baton = NULL;\r
224 \r
225 //      statushash = apr_hash_make(pool);\r
226 //      git_revnum_t youngest = GIT_INVALID_REVNUM;\r
227 //      git_opt_revision_t rev;\r
228 //      rev.kind = git_opt_revision_unspecified;\r
229         try\r
230         {\r
231                 // extract the sub-path (relative to project root)\r
232 //MessageBox(NULL, filepath.GetDirectory().GetWinPathString(), sProjectRoot, MB_OK);\r
233                 LPCSTR lpszSubPath = NULL;\r
234                 CStringA sSubPath;\r
235                 CString s = filepath.GetDirectory().GetWinPathString();\r
236                 if (s.GetLength() > sProjectRoot.GetLength())\r
237                 {\r
238                         sSubPath = CStringA(s.Right(s.GetLength() - sProjectRoot.GetLength() - 1/*otherwise it gets initial slash*/));\r
239                         lpszSubPath = sSubPath;\r
240                 }\r
241 \r
242 //if (lpszSubPath) MessageBoxA(NULL, lpszSubPath, "BuildCache", MB_OK);\r
243 //MessageBoxA(NULL, CStringA(sProjectRoot), sSubPath, MB_OK);\r
244                 err = !wgEnumFiles(CStringA(sProjectRoot), lpszSubPath, WGEFF_NoRecurse|WGEFF_FullPath|WGEFF_DirStatusDelta, &fillstatusmap, this);\r
245 \r
246                 /*err = svn_client_status4 (&youngest,\r
247                         filepath.GetDirectory().GetSVNApiPath(pool),\r
248                         &rev,\r
249                         fillstatusmap,\r
250                         this,\r
251                         svn_depth_immediates,           //depth\r
252                         TRUE,           //getall\r
253                         FALSE,          //update\r
254                         TRUE,           //noignore\r
255                         FALSE,          //ignore externals\r
256                         NULL,\r
257                         localctx,\r
258                         pool);*/\r
259         }\r
260         catch ( ... )\r
261         {\r
262         }\r
263 \r
264         // Error present if function is not under version control\r
265         if (err != NULL)\r
266         {\r
267 //              svn_error_clear(err);\r
268 //              svn_pool_destroy (pool);                                //free allocated memory\r
269                 return &invalidstatus;  \r
270         }\r
271 \r
272 //      svn_error_clear(err);\r
273 //      svn_pool_destroy (pool);                                //free allocated memory\r
274         m_TimeStamp = GetTickCount();\r
275         const FileStatusCacheEntry * ret = NULL;\r
276         FileStatusMap::const_iterator iter;\r
277         if ((iter = m_cache.find(filepath.GetWinPath())) != m_cache.end())\r
278         {\r
279                 ret = &iter->second;\r
280                 m_mostRecentPath = filepath;\r
281                 m_mostRecentStatus = ret;\r
282         }\r
283         else\r
284         {\r
285                 // for SUBST'ed drives, Subversion doesn't return a path with a backslash\r
286                 // e.g. G:\ but only G: when fetching the status. So search for that\r
287                 // path too before giving up.\r
288                 // This is especially true when right-clicking directly on a SUBST'ed\r
289                 // drive to get the context menu\r
290                 if (_tcslen(filepath.GetWinPath())==3)\r
291                 {\r
292                         if ((iter = m_cache.find((LPCTSTR)filepath.GetWinPathString().Left(2))) != m_cache.end())\r
293                         {\r
294                                 ret = &iter->second;\r
295                                 m_mostRecentPath = filepath;\r
296                                 m_mostRecentStatus = ret;\r
297                         }\r
298                 }               \r
299         }\r
300         if (ret)\r
301                 return ret;\r
302         return &invalidstatus;\r
303 }\r
304 \r
305 DWORD GitFolderStatus::GetTimeoutValue()\r
306 {\r
307         DWORD timeout = GITFOLDERSTATUS_CACHETIMEOUT;\r
308         DWORD factor = m_cache.size()/200;\r
309         if (factor==0)\r
310                 factor = 1;\r
311         return factor*timeout;\r
312 }\r
313 \r
314 const FileStatusCacheEntry * GitFolderStatus::GetFullStatus(const CTGitPath& filepath, BOOL bIsFolder, BOOL bColumnProvider)\r
315 {\r
316         const FileStatusCacheEntry * ret = NULL;\r
317 \r
318         CString sProjectRoot;\r
319         BOOL bHasAdminDir = g_ShellCache.HasSVNAdminDir(filepath.GetWinPath(), bIsFolder, &sProjectRoot);\r
320         \r
321         //no overlay for unversioned folders\r
322         if ((!bColumnProvider)&&(!bHasAdminDir))\r
323                 return &invalidstatus;\r
324         //for the SVNStatus column, we have to check the cache to see\r
325         //if it's not just unversioned but ignored\r
326         ret = GetCachedItem(filepath);\r
327         if ((ret)&&(ret->status == git_wc_status_unversioned)&&(bIsFolder)&&(bHasAdminDir))\r
328         {\r
329                 // an 'unversioned' folder, but with an ADMIN dir --> nested layout!\r
330                 // NOTE: this could be a sub-project in git, or just some standalone project inside of another, either way a TODO\r
331                 ret = BuildCache(filepath, sProjectRoot, bIsFolder, TRUE);\r
332                 if (ret)\r
333                         return ret;\r
334                 else\r
335                         return &invalidstatus;\r
336         }\r
337         if (ret)\r
338                 return ret;\r
339 \r
340         //if it's not in the cache and has no admin dir, then we assume\r
341         //it's not ignored too\r
342         if ((bColumnProvider)&&(!bHasAdminDir))\r
343                 return &invalidstatus;\r
344         ret = BuildCache(filepath, sProjectRoot, bIsFolder);\r
345         if (ret)\r
346                 return ret;\r
347         else\r
348                 return &invalidstatus;\r
349 }\r
350 \r
351 const FileStatusCacheEntry * GitFolderStatus::GetCachedItem(const CTGitPath& filepath)\r
352 {\r
353         sCacheKey.assign(filepath.GetWinPath());\r
354         FileStatusMap::const_iterator iter;\r
355         const FileStatusCacheEntry *retVal;\r
356 \r
357         if(m_mostRecentPath.IsEquivalentTo(CTGitPath(sCacheKey.c_str())))\r
358         {\r
359                 // We've hit the same result as we were asked for last time\r
360                 ATLTRACE2(_T("fast cache hit for %s\n"), filepath);\r
361                 retVal = m_mostRecentStatus;\r
362         }\r
363         else if ((iter = m_cache.find(sCacheKey)) != m_cache.end())\r
364         {\r
365                 ATLTRACE2(_T("cache found for %s\n"), filepath);\r
366                 retVal = &iter->second;\r
367                 m_mostRecentStatus = retVal;\r
368                 m_mostRecentPath = CTGitPath(sCacheKey.c_str());\r
369         }\r
370         else\r
371         {\r
372                 retVal = NULL;\r
373         }\r
374 \r
375         if(retVal != NULL)\r
376         {\r
377                 // We found something in a cache - check that the cache is not timed-out or force-invalidated\r
378                 DWORD now = GetTickCount();\r
379 \r
380                 if ((now >= m_TimeStamp)&&((now - m_TimeStamp) > GetTimeoutValue()))\r
381                 {\r
382                         // Cache is timed-out\r
383                         ATLTRACE("Cache timed-out\n");\r
384                         ClearCache();\r
385                         retVal = NULL;\r
386                 }\r
387                 else if(WaitForSingleObject(m_hInvalidationEvent, 0) == WAIT_OBJECT_0)\r
388                 {\r
389                         // TortoiseProc has just done something which has invalidated the cache\r
390                         ATLTRACE("Cache invalidated\n");\r
391                         ClearCache();\r
392                         retVal = NULL;\r
393                 }\r
394                 return retVal;\r
395         }\r
396         return NULL;\r
397 }\r
398 \r
399 BOOL GitFolderStatus::fillstatusmap(const struct wgFile_s *pFile, void *pUserData)\r
400 {\r
401         GitFolderStatus *Stat = (GitFolderStatus*)pUserData;\r
402 \r
403         FileStatusMap &cache = Stat->m_cache;\r
404         FileStatusCacheEntry s;\r
405         s.needslock = false;\r
406         s.tree_conflict = false;\r
407 \r
408         s.author = Stat->authors.GetString(NULL);\r
409         s.url = Stat->urls.GetString(NULL);\r
410 //      s.rev = -1;\r
411         s.owner = Stat->owners.GetString(NULL);\r
412 \r
413         s.status = git_wc_status_none;\r
414 \r
415         //s.status = GitStatus::GetMoreImportant(s.status, status->text_status);\r
416         //s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);\r
417         s.status = GitStatusFromWingit(pFile->nStatus);\r
418 \r
419         // TODO ?: s.blaha = pFile->nStage\r
420 \r
421         //s.lock = status->repos_lock;\r
422         //s.tree_conflict = (status->tree_conflict != NULL);\r
423 \r
424         s.askedcounter = GITFOLDERSTATUS_CACHETIMES;\r
425         stdstring str;\r
426         if (pFile->sFileName)\r
427         {\r
428                 str = CUnicodeUtils::StdGetUnicode(pFile->sFileName);\r
429                 std::replace(str.begin(), str.end(), '/', '\\');\r
430 //MessageBox(NULL, str.c_str(), _T(""), MB_OK);\r
431         }\r
432         else\r
433                 str = _T(" ");\r
434         cache[str] = s;\r
435 \r
436         return FALSE;\r
437 }\r
438 \r
439 #if 0\r
440 git_error_t* GitFolderStatus::fillstatusmap(void * baton, const char * path, git_wc_status2_t * status, apr_pool_t * /*pool*/)\r
441 {\r
442         GitFolderStatus * Stat = (GitFolderStatus *)baton;\r
443         FileStatusMap * cache = &Stat->m_cache;\r
444         FileStatusCacheEntry s;\r
445         s.needslock = false;\r
446         s.tree_conflict = false;\r
447         if ((status)&&(status->entry))\r
448         {\r
449                 s.author = Stat->authors.GetString(status->entry->cmt_author);\r
450                 s.url = Stat->urls.GetString(status->entry->url);\r
451                 s.rev = status->entry->cmt_rev;\r
452                 s.owner = Stat->owners.GetString(status->entry->lock_owner);\r
453                 if (status->entry->present_props)\r
454                         s.needslock = strstr(status->entry->present_props, "svn:needs-lock") ? true : false;\r
455         }\r
456         else\r
457         {\r
458                 s.author = Stat->authors.GetString(NULL);\r
459                 s.url = Stat->urls.GetString(NULL);\r
460                 s.rev = -1;\r
461                 s.owner = Stat->owners.GetString(NULL);\r
462         }\r
463         s.status = git_wc_status_none;\r
464         if (status)\r
465         {\r
466                 s.status = GitStatus::GetMoreImportant(s.status, status->text_status);\r
467                 s.status = GitStatus::GetMoreImportant(s.status, status->prop_status);\r
468                 s.lock = status->repos_lock;\r
469                 s.tree_conflict = (status->tree_conflict != NULL);\r
470         }\r
471         s.askedcounter = GITFOLDERSTATUS_CACHETIMES;\r
472         stdstring str;\r
473         if (path)\r
474         {\r
475                 str = CUnicodeUtils::StdGetUnicode(path);\r
476                 std::replace(str.begin(), str.end(), '/', '\\');\r
477         }\r
478         else\r
479                 str = _T(" ");\r
480         (*cache)[str] = s;\r
481 \r
482         return GIT_NO_ERROR;\r
483 }\r
484 \r
485 git_error_t* GitFolderStatus::findfolderstatus(void * baton, const char * path, git_wc_status2_t * status, apr_pool_t * /*pool*/)\r
486 {\r
487         GitFolderStatus * Stat = (GitFolderStatus *)baton;\r
488         if ((Stat)&&(Stat->folderpath.IsEquivalentTo(CTGitPath(CString(path)))))\r
489         {\r
490                 Stat->dirstatus = status;\r
491         }\r
492 \r
493         return GIT_NO_ERROR;\r
494 }\r
495 #endif\r
496 \r
497 void GitFolderStatus::ClearCache()\r
498 {\r
499         m_cache.clear();\r
500         m_mostRecentStatus = NULL;\r
501         m_mostRecentPath.Reset();\r
502         // If we're about to rebuild the cache, there's no point hanging on to \r
503         // an event which tells us that it's invalid\r
504         ResetEvent(m_hInvalidationEvent);\r
505 }\r