OSDN Git Service

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