OSDN Git Service

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