OSDN Git Service

Fix 'Explore To' in context menu in Commit Dialog
[tortoisegit/TortoiseGitJp.git] / src / TGitCache / FolderCrawler.cpp
1 // TortoiseSVN - a Windows shell extension for easy version control\r
2 \r
3 // External Cache Copyright (C) 2005-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 \r
20 #include "StdAfx.h"\r
21 #include ".\foldercrawler.h"\r
22 #include "GitStatusCache.h"\r
23 #include "registry.h"\r
24 #include "TSVNCache.h"\r
25 #include "shlobj.h"\r
26 \r
27 \r
28 CFolderCrawler::CFolderCrawler(void)\r
29 {\r
30         m_hWakeEvent = CreateEvent(NULL,FALSE,FALSE,NULL);\r
31         m_hTerminationEvent = CreateEvent(NULL,TRUE,FALSE,NULL);\r
32         m_hThread = INVALID_HANDLE_VALUE;\r
33         m_lCrawlInhibitSet = 0;\r
34         m_crawlHoldoffReleasesAt = (long)GetTickCount();\r
35         m_bRun = false;\r
36         m_bPathsAddedSinceLastCrawl = false;\r
37         m_bItemsAddedSinceLastCrawl = false;\r
38 }\r
39 \r
40 CFolderCrawler::~CFolderCrawler(void)\r
41 {\r
42         Stop();\r
43 }\r
44 \r
45 void CFolderCrawler::Stop()\r
46 {\r
47         m_bRun = false;\r
48         if (m_hTerminationEvent != INVALID_HANDLE_VALUE)\r
49         {\r
50                 SetEvent(m_hTerminationEvent);\r
51                 if(WaitForSingleObject(m_hThread, 4000) != WAIT_OBJECT_0)\r
52                 {\r
53                         ATLTRACE("Error terminating crawler thread\n");\r
54                 }\r
55                 CloseHandle(m_hThread);\r
56                 m_hThread = INVALID_HANDLE_VALUE;\r
57                 CloseHandle(m_hTerminationEvent);\r
58                 m_hTerminationEvent = INVALID_HANDLE_VALUE;\r
59                 CloseHandle(m_hWakeEvent);\r
60                 m_hWakeEvent = INVALID_HANDLE_VALUE;\r
61         }\r
62 }\r
63 \r
64 void CFolderCrawler::Initialise()\r
65 {\r
66         // Don't call Initialize more than once\r
67         ATLASSERT(m_hThread == INVALID_HANDLE_VALUE);\r
68 \r
69         // Just start the worker thread. \r
70         // It will wait for event being signaled.\r
71         // If m_hWakeEvent is already signaled the worker thread \r
72         // will behave properly (with normal priority at worst).\r
73 \r
74         m_bRun = true;\r
75         unsigned int threadId;\r
76         m_hThread = (HANDLE)_beginthreadex(NULL,0,ThreadEntry,this,0,&threadId);\r
77         SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);\r
78 }\r
79 \r
80 void CFolderCrawler::AddDirectoryForUpdate(const CTGitPath& path)\r
81 {\r
82         if (!CGitStatusCache::Instance().IsPathGood(path))\r
83                 return;\r
84         {\r
85                 AutoLocker lock(m_critSec);\r
86                 m_foldersToUpdate.push_back(path);\r
87                 m_foldersToUpdate.back().SetCustomData(GetTickCount()+10);\r
88                 ATLASSERT(path.IsDirectory() || !path.Exists());\r
89                 // set this flag while we are sync'ed \r
90                 // with the worker thread\r
91                 m_bItemsAddedSinceLastCrawl = true;\r
92         }\r
93         if (SetHoldoff())\r
94                 SetEvent(m_hWakeEvent);\r
95 }\r
96 \r
97 void CFolderCrawler::AddPathForUpdate(const CTGitPath& path)\r
98 {\r
99         if (!CGitStatusCache::Instance().IsPathGood(path))\r
100                 return;\r
101         {\r
102                 AutoLocker lock(m_critSec);\r
103                 m_pathsToUpdate.push_back(path);\r
104                 m_pathsToUpdate.back().SetCustomData(GetTickCount()+1000);\r
105                 m_bPathsAddedSinceLastCrawl = true;\r
106         }\r
107         if (SetHoldoff())\r
108                 SetEvent(m_hWakeEvent);\r
109 }\r
110 \r
111 unsigned int CFolderCrawler::ThreadEntry(void* pContext)\r
112 {\r
113         ((CFolderCrawler*)pContext)->WorkerThread();\r
114         return 0;\r
115 }\r
116 \r
117 void CFolderCrawler::WorkerThread()\r
118 {\r
119         HANDLE hWaitHandles[2];\r
120         hWaitHandles[0] = m_hTerminationEvent;  \r
121         hWaitHandles[1] = m_hWakeEvent;\r
122         CTGitPath workingPath;\r
123         bool bFirstRunAfterWakeup = false;\r
124         DWORD currentTicks = 0;\r
125 \r
126         // Quick check if we're on Vista\r
127         OSVERSIONINFOEX inf;\r
128         SecureZeroMemory(&inf, sizeof(OSVERSIONINFOEX));\r
129         inf.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);\r
130         GetVersionEx((OSVERSIONINFO *)&inf);\r
131         WORD fullver = MAKEWORD(inf.dwMinorVersion, inf.dwMajorVersion);\r
132 \r
133         for(;;)\r
134         {\r
135                 bool bRecursive = !!(DWORD)CRegStdWORD(_T("Software\\TortoiseGit\\RecursiveOverlay"), TRUE);\r
136 \r
137                 if (fullver >= 0x0600)\r
138                 {\r
139                         SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END);\r
140                 }\r
141                 DWORD waitResult = WaitForMultipleObjects(sizeof(hWaitHandles)/sizeof(hWaitHandles[0]), hWaitHandles, FALSE, INFINITE);\r
142                 \r
143                 // exit event/working loop if the first event (m_hTerminationEvent)\r
144                 // has been signaled or if one of the events has been abandoned\r
145                 // (i.e. ~CFolderCrawler() is being executed)\r
146                 if(m_bRun == false || waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED_0 || waitResult == WAIT_ABANDONED_0+1)\r
147                 {\r
148                         // Termination event\r
149                         break;\r
150                 }\r
151 \r
152                 if (fullver >= 0x0600)\r
153                 {\r
154                         SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN);\r
155                 }\r
156 \r
157                 // If we get here, we've been woken up by something being added to the queue.\r
158                 // However, it's important that we don't do our crawling while\r
159                 // the shell is still asking for items\r
160                 // \r
161                 bFirstRunAfterWakeup = true;\r
162                 for(;;)\r
163                 {\r
164                         if (!m_bRun)\r
165                                 break;\r
166                         // Any locks today?\r
167                         if (CGitStatusCache::Instance().m_bClearMemory)\r
168                         {\r
169                                 CGitStatusCache::Instance().WaitToWrite();\r
170                                 CGitStatusCache::Instance().ClearCache();\r
171                                 CGitStatusCache::Instance().Done();\r
172                                 CGitStatusCache::Instance().m_bClearMemory = false;\r
173                         }\r
174                         if(m_lCrawlInhibitSet > 0)\r
175                         {\r
176                                 // We're in crawl hold-off \r
177                                 ATLTRACE("Crawl hold-off\n");\r
178                                 Sleep(50);\r
179                                 continue;\r
180                         }\r
181                         if (bFirstRunAfterWakeup)\r
182                         {\r
183                                 Sleep(2000);\r
184                                 bFirstRunAfterWakeup = false;\r
185                                 continue;\r
186                         }\r
187                         if ((m_blockReleasesAt < GetTickCount())&&(!m_blockedPath.IsEmpty()))\r
188                         {\r
189                                 ATLTRACE(_T("stop blocking path %s\n"), m_blockedPath.GetWinPath());\r
190                                 m_blockedPath.Reset();\r
191                         }\r
192         \r
193                         if ((m_foldersToUpdate.empty())&&(m_pathsToUpdate.empty()))\r
194                         {\r
195                                 // Nothing left to do \r
196                                 break;\r
197                         }\r
198                         currentTicks = GetTickCount();\r
199                         if (!m_pathsToUpdate.empty())\r
200                         {\r
201                                 {\r
202                                         AutoLocker lock(m_critSec);\r
203 \r
204                                         if (m_bPathsAddedSinceLastCrawl)\r
205                                         {\r
206                                                 // The queue has changed - it's worth sorting and de-duping\r
207                                                 std::sort(m_pathsToUpdate.begin(), m_pathsToUpdate.end());\r
208                                                 m_pathsToUpdate.erase(std::unique(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), &CTGitPath::PredLeftSameWCPathAsRight), m_pathsToUpdate.end());\r
209                                                 m_bPathsAddedSinceLastCrawl = false;\r
210                                         }\r
211                                         workingPath = m_pathsToUpdate.front();\r
212                                         if ((DWORD(workingPath.GetCustomData()) < currentTicks)||(DWORD(workingPath.GetCustomData()) > (currentTicks + 200000)))\r
213                                                 m_pathsToUpdate.pop_front();\r
214                                         else\r
215                                         {\r
216                                                 // since we always sort the whole list, we risk adding tons of new paths to m_pathsToUpdate\r
217                                                 // until the last one in the sorted list finally times out.\r
218                                                 // to avoid that, we go through the list again and crawl the first one which is valid\r
219                                                 // to crawl. That way, we still reduce the size of the list.\r
220                                                 if (m_pathsToUpdate.size() > 10)\r
221                                                         ATLTRACE("attention: the list of paths to update is now %ld big!\n", m_pathsToUpdate.size());\r
222                                                 for (std::deque<CTGitPath>::iterator it = m_pathsToUpdate.begin(); it != m_pathsToUpdate.end(); ++it)\r
223                                                 {\r
224                                                         workingPath = *it;\r
225                                                         if ((DWORD(workingPath.GetCustomData()) < currentTicks)||(DWORD(workingPath.GetCustomData()) > (currentTicks + 200000)))\r
226                                                         {\r
227                                                                 m_pathsToUpdate.erase(it);\r
228                                                                 break;\r
229                                                         }\r
230                                                 }\r
231                                         }\r
232                                 }\r
233                                 if (DWORD(workingPath.GetCustomData()) >= currentTicks)\r
234                                 {\r
235                                         Sleep(50);\r
236                                         continue;\r
237                                 }\r
238                                 if ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath)))\r
239                                         continue;\r
240                                 // don't crawl paths that are excluded\r
241                                 if (!CGitStatusCache::Instance().IsPathAllowed(workingPath))\r
242                                         continue;\r
243                                 // check if the changed path is inside an .svn folder\r
244                                 if ((workingPath.HasAdminDir()&&workingPath.IsDirectory())||workingPath.IsAdminDir())\r
245                                 {\r
246                                         // we don't crawl for paths changed in a tmp folder inside an .svn folder.\r
247                                         // Because we also get notifications for those even if we just ask for the status!\r
248                                         // And changes there don't affect the file status at all, so it's safe\r
249                                         // to ignore notifications on those paths.\r
250                                         if (workingPath.IsAdminDir())\r
251                                         {\r
252                                                 CString lowerpath = workingPath.GetWinPathString();\r
253                                                 lowerpath.MakeLower();\r
254                                                 if (lowerpath.Find(_T("\\tmp\\"))>0)\r
255                                                         continue;\r
256                                                 if (lowerpath.Find(_T("\\tmp")) == (lowerpath.GetLength()-4))\r
257                                                         continue;\r
258                                                 if (lowerpath.Find(_T("\\log"))>0)\r
259                                                         continue;\r
260                                                 // Here's a little problem:\r
261                                                 // the lock file is also created for fetching the status\r
262                                                 // and not just when committing.\r
263                                                 // If we could find out why the lock file was changed\r
264                                                 // we could decide to crawl the folder again or not.\r
265                                                 // But for now, we have to crawl the parent folder\r
266                                                 // no matter what.\r
267 \r
268                                                 //if (lowerpath.Find(_T("\\lock"))>0)\r
269                                                 //      continue;\r
270                                         }\r
271                                         else if (!workingPath.Exists())\r
272                                         {\r
273                                                 CGitStatusCache::Instance().WaitToWrite();\r
274                                                 CGitStatusCache::Instance().RemoveCacheForPath(workingPath);\r
275                                                 CGitStatusCache::Instance().Done();\r
276                                                 continue;\r
277                                         }\r
278 \r
279                                         do \r
280                                         {\r
281                                                 workingPath = workingPath.GetContainingDirectory();     \r
282                                         } while(workingPath.IsAdminDir());\r
283 \r
284                                         ATLTRACE(_T("Invalidating and refreshing folder: %s\n"), workingPath.GetWinPath());\r
285                                         {\r
286                                                 AutoLocker print(critSec);\r
287                                                 _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Invalidating and refreshing folder: %s"), workingPath.GetWinPath());\r
288                                                 nCurrentCrawledpathIndex++;\r
289                                                 if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS)\r
290                                                         nCurrentCrawledpathIndex = 0;\r
291                                         }\r
292                                         InvalidateRect(hWnd, NULL, FALSE);\r
293                                         CGitStatusCache::Instance().WaitToRead();\r
294                                         // Invalidate the cache of this folder, to make sure its status is fetched again.\r
295                                         CCachedDirectory * pCachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath);\r
296                                         if (pCachedDir)\r
297                                         {\r
298                                                 git_wc_status_kind status = pCachedDir->GetCurrentFullStatus();\r
299                                                 pCachedDir->Invalidate();\r
300                                                 if (workingPath.Exists())\r
301                                                 {\r
302                                                         pCachedDir->RefreshStatus(bRecursive);\r
303                                                         // if the previous status wasn't normal and now it is, then\r
304                                                         // send a notification too.\r
305                                                         // We do this here because GetCurrentFullStatus() doesn't send\r
306                                                         // notifications for 'normal' status - if it would, we'd get tons\r
307                                                         // of notifications when crawling a working copy not yet in the cache.\r
308                                                         if ((status != git_wc_status_normal)&&(pCachedDir->GetCurrentFullStatus() != status))\r
309                                                         {\r
310                                                                 CGitStatusCache::Instance().UpdateShell(workingPath);\r
311                                                                 ATLTRACE(_T("shell update in crawler for %s\n"), workingPath.GetWinPath());\r
312                                                         }\r
313                                                 }\r
314                                                 else\r
315                                                 {\r
316                                                         CGitStatusCache::Instance().Done();\r
317                                                         CGitStatusCache::Instance().WaitToWrite();\r
318                                                         CGitStatusCache::Instance().RemoveCacheForPath(workingPath);\r
319                                                 }\r
320                                         }\r
321                                         CGitStatusCache::Instance().Done();\r
322                                         //In case that svn_client_stat() modified a file and we got\r
323                                         //a notification about that in the directory watcher,\r
324                                         //remove that here again - this is to prevent an endless loop\r
325                                         AutoLocker lock(m_critSec);\r
326                                         m_pathsToUpdate.erase(std::remove(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), workingPath), m_pathsToUpdate.end());\r
327                                 }\r
328                                 else if (workingPath.HasAdminDir())\r
329                                 {\r
330                                         if (!workingPath.Exists())\r
331                                         {\r
332                                                 CGitStatusCache::Instance().WaitToWrite();\r
333                                                 CGitStatusCache::Instance().RemoveCacheForPath(workingPath);\r
334                                                 CGitStatusCache::Instance().Done();\r
335                                                 continue;\r
336                                         }\r
337                                         if (!workingPath.Exists())\r
338                                                 continue;\r
339                                         ATLTRACE(_T("Updating path: %s\n"), workingPath.GetWinPath());\r
340                                         {\r
341                                                 AutoLocker print(critSec);\r
342                                                 _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Updating path: %s"), workingPath.GetWinPath());\r
343                                                 nCurrentCrawledpathIndex++;\r
344                                                 if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS)\r
345                                                         nCurrentCrawledpathIndex = 0;\r
346                                         }\r
347                                         InvalidateRect(hWnd, NULL, FALSE);\r
348                                         // HasAdminDir() already checks if the path points to a dir\r
349                                         DWORD flags = TSVNCACHE_FLAGS_FOLDERISKNOWN;\r
350                                         flags |= (workingPath.IsDirectory() ? TSVNCACHE_FLAGS_ISFOLDER : 0);\r
351                                         flags |= (bRecursive ? TSVNCACHE_FLAGS_RECUSIVE_STATUS : 0);\r
352                                         CGitStatusCache::Instance().WaitToRead();\r
353                                         // Invalidate the cache of folders manually. The cache of files is invalidated\r
354                                         // automatically if the status is asked for it and the file times don't match\r
355                                         // anymore, so we don't need to manually invalidate those.\r
356                                         if (workingPath.IsDirectory())\r
357                                         {\r
358                                                 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath);\r
359                                                 if (cachedDir)\r
360                                                         cachedDir->Invalidate();\r
361                                         }\r
362                                         CStatusCacheEntry ce = CGitStatusCache::Instance().GetStatusForPath(workingPath, flags);\r
363                                         if (ce.GetEffectiveStatus() > git_wc_status_unversioned)\r
364                                         {\r
365                                                 CGitStatusCache::Instance().UpdateShell(workingPath);\r
366                                                 ATLTRACE(_T("shell update in folder crawler for %s\n"), workingPath.GetWinPath());\r
367                                         }\r
368                                         CGitStatusCache::Instance().Done();\r
369                                         AutoLocker lock(m_critSec);\r
370                                         m_pathsToUpdate.erase(std::remove(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), workingPath), m_pathsToUpdate.end());\r
371                                 }\r
372                                 else\r
373                                 {\r
374                                         if (!workingPath.Exists())\r
375                                         {\r
376                                                 CGitStatusCache::Instance().WaitToWrite();\r
377                                                 CGitStatusCache::Instance().RemoveCacheForPath(workingPath);\r
378                                                 CGitStatusCache::Instance().Done();\r
379                                         }\r
380                                 }\r
381                         }\r
382                         else if (!m_foldersToUpdate.empty())\r
383                         {\r
384                                 {\r
385                                         AutoLocker lock(m_critSec);\r
386 \r
387                                         if (m_bItemsAddedSinceLastCrawl)\r
388                                         {\r
389                                                 // The queue has changed - it's worth sorting and de-duping\r
390                                                 std::sort(m_foldersToUpdate.begin(), m_foldersToUpdate.end());\r
391                                                 m_foldersToUpdate.erase(std::unique(m_foldersToUpdate.begin(), m_foldersToUpdate.end(), &CTGitPath::PredLeftEquivalentToRight), m_foldersToUpdate.end());\r
392                                                 m_bItemsAddedSinceLastCrawl = false;\r
393                                         }\r
394                                         // create a new CTGitPath object to make sure the cached flags are requested again.\r
395                                         // without this, a missing file/folder is still treated as missing even if it is available\r
396                                         // now when crawling.\r
397                                         workingPath = CTGitPath(m_foldersToUpdate.front().GetWinPath());\r
398                                         workingPath.SetCustomData(m_foldersToUpdate.front().GetCustomData());\r
399                                         if ((DWORD(workingPath.GetCustomData()) < currentTicks)||(DWORD(workingPath.GetCustomData()) > (currentTicks + 200000)))\r
400                                                 m_foldersToUpdate.pop_front();\r
401                                         else\r
402                                         {\r
403                                                 // since we always sort the whole list, we risk adding tons of new paths to m_pathsToUpdate\r
404                                                 // until the last one in the sorted list finally times out.\r
405                                                 // to avoid that, we go through the list again and crawl the first one which is valid\r
406                                                 // to crawl. That way, we still reduce the size of the list.\r
407                                                 if (m_foldersToUpdate.size() > 10)\r
408                                                         ATLTRACE("attention: the list of folders to update is now %ld big!\n", m_foldersToUpdate.size());\r
409                                                 for (std::deque<CTGitPath>::iterator it = m_foldersToUpdate.begin(); it != m_foldersToUpdate.end(); ++it)\r
410                                                 {\r
411                                                         workingPath = *it;\r
412                                                         if ((DWORD(workingPath.GetCustomData()) < currentTicks)||(DWORD(workingPath.GetCustomData()) > (currentTicks + 200000)))\r
413                                                         {\r
414                                                                 m_foldersToUpdate.erase(it);\r
415                                                                 break;\r
416                                                         }\r
417                                                 }\r
418                                         }\r
419                                 }\r
420                                 if (DWORD(workingPath.GetCustomData()) >= currentTicks)\r
421                                 {\r
422                                         Sleep(50);\r
423                                         continue;\r
424                                 }\r
425                                 if ((!m_blockedPath.IsEmpty())&&(m_blockedPath.IsAncestorOf(workingPath)))\r
426                                         continue;\r
427                                 if (!CGitStatusCache::Instance().IsPathAllowed(workingPath))\r
428                                         continue;\r
429 \r
430                                 ATLTRACE(_T("Crawling folder: %s\n"), workingPath.GetWinPath());\r
431                                 {\r
432                                         AutoLocker print(critSec);\r
433                                         _stprintf_s(szCurrentCrawledPath[nCurrentCrawledpathIndex], MAX_CRAWLEDPATHSLEN, _T("Crawling folder: %s"), workingPath.GetWinPath());\r
434                                         nCurrentCrawledpathIndex++;\r
435                                         if (nCurrentCrawledpathIndex >= MAX_CRAWLEDPATHS)\r
436                                                 nCurrentCrawledpathIndex = 0;\r
437                                 }\r
438                                 InvalidateRect(hWnd, NULL, FALSE);\r
439                                 CGitStatusCache::Instance().WaitToRead();\r
440                                 // Now, we need to visit this folder, to make sure that we know its 'most important' status\r
441                                 CCachedDirectory * cachedDir = CGitStatusCache::Instance().GetDirectoryCacheEntry(workingPath.GetDirectory());\r
442                                 // check if the path is monitored by the watcher. If it isn't, then we have to invalidate the cache\r
443                                 // for that path and add it to the watcher.\r
444                                 if (!CGitStatusCache::Instance().IsPathWatched(workingPath))\r
445                                 {\r
446                                         if (workingPath.HasAdminDir())\r
447                                                 CGitStatusCache::Instance().AddPathToWatch(workingPath);\r
448                                         if (cachedDir)\r
449                                                 cachedDir->Invalidate();\r
450                                         else\r
451                                         {\r
452                                                 CGitStatusCache::Instance().Done();\r
453                                                 CGitStatusCache::Instance().WaitToWrite();\r
454                                                 CGitStatusCache::Instance().RemoveCacheForPath(workingPath);\r
455                                         }\r
456                                 }\r
457                                 if (cachedDir)\r
458                                         cachedDir->RefreshStatus(bRecursive);\r
459 \r
460                                 // While refreshing the status, we could get another crawl request for the same folder.\r
461                                 // This can happen if the crawled folder has a lower status than one of the child folders\r
462                                 // (recursively). To avoid double crawlings, remove such a crawl request here\r
463                                 AutoLocker lock(m_critSec);\r
464                                 if (m_bItemsAddedSinceLastCrawl)\r
465                                 {\r
466                                         if (m_foldersToUpdate.back().IsEquivalentToWithoutCase(workingPath))\r
467                                         {\r
468                                                 m_foldersToUpdate.pop_back();\r
469                                                 m_bItemsAddedSinceLastCrawl = false;\r
470                                         }\r
471                                 }\r
472                                 CGitStatusCache::Instance().Done();\r
473                         }\r
474                 }\r
475         }\r
476         _endthread();\r
477 }\r
478 \r
479 bool CFolderCrawler::SetHoldoff(DWORD milliseconds /* = 100*/)\r
480 {\r
481         long tick = (long)GetTickCount();\r
482         bool ret = ((tick - m_crawlHoldoffReleasesAt) > 0);\r
483         m_crawlHoldoffReleasesAt = tick + milliseconds;\r
484         return ret;\r
485 }\r
486 \r
487 void CFolderCrawler::BlockPath(const CTGitPath& path, DWORD ticks)\r
488 {\r
489         ATLTRACE(_T("block path %s from being crawled\n"), path.GetWinPath());\r
490         m_blockedPath = path;\r
491         if (ticks == 0)\r
492                 m_blockReleasesAt = GetTickCount()+10000;\r
493         else\r
494                 m_blockReleasesAt = GetTickCount()+ticks;\r
495 }\r