1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "..\TortoiseShell\resource.h"
23 #include "GitStatus.h"
24 #include "UnicodeUtils.h"
27 #include "ShellCache.h"
30 extern CGitAdminDirMap g_AdminDirMap
;
31 extern CGitIndexFileMap g_IndexFileMap
;
32 CGitHeadFileMap g_HeadFileMap
;
33 CGitIgnoreList g_IgnoreList
;
35 GitStatus::GitStatus()
38 m_status
.assumeValid
= m_status
.skipWorktree
= false;
39 m_status
.prop_status
= m_status
.text_status
= git_wc_status_none
;
42 GitStatus::~GitStatus(void)
48 git_wc_status_kind
GitStatus::GetAllStatus(const CTGitPath
& path
, git_depth_t depth
, bool * assumeValid
, bool * skipWorktree
)
50 git_wc_status_kind statuskind
;
55 isDir
= path
.IsDirectory();
56 if (!path
.HasAdminDir(&sProjectRoot
))
57 return git_wc_status_none
;
59 // rev.kind = git_opt_revision_unspecified;
60 statuskind
= git_wc_status_none
;
62 const BOOL bIsRecursive
= (depth
== git_depth_infinity
|| depth
== git_depth_unknown
); // taken from SVN source
65 CString s
= path
.GetWinPathString();
66 if (s
.GetLength() > sProjectRoot
.GetLength())
68 if (sProjectRoot
.GetLength() == 3 && sProjectRoot
[1] == _T(':'))
69 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
71 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() - 1/*otherwise it gets initial slash*/);
74 bool isfull
= ((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"),
75 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
79 err
= GetDirStatus(sProjectRoot
, sSubPath
, &statuskind
, isfull
, bIsRecursive
, isfull
);
80 // folders must not be displayed as added or deleted only as modified (this is for Shell Overlay-Modes)
81 if (statuskind
== git_wc_status_unversioned
&& sSubPath
.IsEmpty())
82 statuskind
= git_wc_status_normal
;
83 else if (statuskind
== git_wc_status_deleted
|| statuskind
== git_wc_status_added
)
84 statuskind
= git_wc_status_modified
;
88 err
= GetFileStatus(sProjectRoot
, sSubPath
, &statuskind
, isfull
, false, isfull
, NULL
, NULL
, assumeValid
, skipWorktree
);
96 git_wc_status_kind
GitStatus::GetMoreImportant(git_wc_status_kind status1
, git_wc_status_kind status2
)
98 if (GetStatusRanking(status1
) >= GetStatusRanking(status2
))
102 // static private method
103 int GitStatus::GetStatusRanking(git_wc_status_kind status
)
107 case git_wc_status_none
:
109 case git_wc_status_unversioned
:
111 case git_wc_status_ignored
:
113 case git_wc_status_incomplete
:
115 case git_wc_status_normal
:
116 case git_wc_status_external
:
118 case git_wc_status_added
:
120 case git_wc_status_missing
:
122 case git_wc_status_deleted
:
124 case git_wc_status_replaced
:
126 case git_wc_status_modified
:
128 case git_wc_status_merged
:
130 case git_wc_status_conflicted
:
132 case git_wc_status_obstructed
:
139 void GitStatus::GetStatus(const CTGitPath
& path
, bool /*update*/ /* = false */, bool noignore
/* = false */, bool /*noexternals*/ /* = false */)
141 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
142 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
143 // after the call again
145 CString sProjectRoot
;
146 if ( !path
.HasAdminDir(&sProjectRoot
) )
149 bool isfull
= ((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"),
150 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
154 LPCTSTR lpszSubPath
= NULL
;
156 CString s
= path
.GetWinPathString();
157 if (s
.GetLength() > sProjectRoot
.GetLength())
159 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
160 lpszSubPath
= sSubPath
;
161 // skip initial slash if necessary
162 if (*lpszSubPath
== _T('\\'))
166 m_status
.prop_status
= m_status
.text_status
= git_wc_status_none
;
167 m_status
.assumeValid
= false;
168 m_status
.skipWorktree
= false;
170 if (path
.IsDirectory())
172 err
= GetDirStatus(sProjectRoot
, lpszSubPath
, &m_status
.text_status
, isfull
, false, !noignore
);
173 if (m_status
.text_status
== git_wc_status_added
|| m_status
.text_status
== git_wc_status_deleted
) // fix for issue #1769; a folder is either modified, conflicted or normal
174 m_status
.text_status
= git_wc_status_modified
;
177 err
= GetFileStatus(sProjectRoot
, lpszSubPath
, &m_status
.text_status
, isfull
, false, !noignore
, nullptr, nullptr, &m_status
.assumeValid
, &m_status
.skipWorktree
);
179 // Error present if function is not under version control
190 typedef CComCritSecLock
<CComCriticalSection
> CAutoLocker
;
192 int GitStatus::GetFileStatus(const CString
&gitdir
, const CString
&pathParam
, git_wc_status_kind
* status
,BOOL IsFull
, BOOL
/*IsRecursive*/,BOOL IsIgnore
, FILL_STATUS_CALLBACK callback
, void *pData
, bool * assumeValid
, bool * skipWorktree
)
197 CString path
= pathParam
;
199 path
.Replace(_T('\\'), _T('/'));
201 CString lowcasepath
= path
;
202 lowcasepath
.MakeLower();
204 git_wc_status_kind st
= git_wc_status_none
;
207 g_IndexFileMap
.GetFileStatus(gitdir
, path
, &st
, IsFull
, false, callback
, pData
, &hash
, true, assumeValid
, skipWorktree
);
209 if (st
== git_wc_status_conflicted
)
212 if (callback
&& assumeValid
&& skipWorktree
)
213 callback(CombinePath(gitdir
, path
), st
, false, pData
, *assumeValid
, *skipWorktree
);
217 if (st
== git_wc_status_unversioned
)
221 *status
= git_wc_status_unversioned
;
222 if (callback
&& assumeValid
&& skipWorktree
)
223 callback(CombinePath(gitdir
, path
), *status
, false, pData
, *assumeValid
, *skipWorktree
);
227 if (g_IgnoreList
.CheckIgnoreChanged(gitdir
, path
, false))
228 g_IgnoreList
.LoadAllIgnoreFile(gitdir
, path
, false);
229 if (g_IgnoreList
.IsIgnore(path
, gitdir
, false))
230 st
= git_wc_status_ignored
;
233 if (callback
&& assumeValid
&& skipWorktree
)
234 callback(CombinePath(gitdir
, path
), st
, false, pData
, *assumeValid
, *skipWorktree
);
239 if ((st
== git_wc_status_normal
|| st
== git_wc_status_modified
) && IsFull
)
241 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
);
243 // Check Head Tree Hash
244 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
247 int start
= SearchInSortVector(*treeptr
, lowcasepath
, -1);
250 *status
= st
= git_wc_status_added
;
251 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": File miss in head tree %s"), (LPCTSTR
)path
);
252 if (callback
&& assumeValid
&& skipWorktree
)
253 callback(CombinePath(gitdir
, path
), st
, false, pData
, *assumeValid
, *skipWorktree
);
257 // staged and not commit
258 if (treeptr
->at(start
).m_Hash
!= hash
)
260 *status
= st
= git_wc_status_modified
;
261 if (callback
&& assumeValid
&& skipWorktree
)
262 callback(CombinePath(gitdir
, path
), st
, false, pData
, *assumeValid
, *skipWorktree
);
267 if (callback
&& assumeValid
&& skipWorktree
)
268 callback(CombinePath(gitdir
, path
), st
, false, pData
, *assumeValid
, *skipWorktree
);
273 bool GitStatus::HasIgnoreFilesChanged(const CString
&gitdir
, const CString
&subpaths
, bool isDir
)
275 return g_IgnoreList
.CheckIgnoreChanged(gitdir
, subpaths
, isDir
);
278 int GitStatus::LoadIgnoreFile(const CString
&gitdir
, const CString
&subpaths
, bool isDir
)
280 return g_IgnoreList
.LoadAllIgnoreFile(gitdir
, subpaths
, isDir
);
282 int GitStatus::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
,bool *isVersion
)
284 if (g_IndexFileMap
.IsUnderVersionControl(gitdir
, path
, isDir
, isVersion
))
287 return g_HeadFileMap
.IsUnderVersionControl(gitdir
, path
, isDir
, isVersion
);
291 int GitStatus::IsIgnore(const CString
&gitdir
, const CString
&path
, bool *isIgnore
, bool isDir
)
293 if (g_IgnoreList
.CheckIgnoreChanged(gitdir
, path
, isDir
))
294 g_IgnoreList
.LoadAllIgnoreFile(gitdir
, path
, isDir
);
296 *isIgnore
= g_IgnoreList
.IsIgnore(path
, gitdir
, isDir
);
301 int GitStatus::GetFileList(CString path
, std::vector
<CGitFileName
> &list
)
304 WIN32_FIND_DATA data
;
305 HANDLE handle
= ::FindFirstFileEx(path
, SysInfo::Instance().IsWin7OrLater() ? FindExInfoBasic
: FindExInfoStandard
, &data
, FindExSearchNameMatch
, nullptr, SysInfo::Instance().IsWin7OrLater() ? FIND_FIRST_EX_LARGE_FETCH
: 0);
308 if(_tcscmp(data
.cFileName
, _T(".git")) == 0)
311 if(_tcscmp(data
.cFileName
, _T(".")) == 0)
314 if(_tcscmp(data
.cFileName
, _T("..")) == 0)
317 CGitFileName filename
;
319 filename
.m_CaseFileName
= filename
.m_FileName
= data
.cFileName
;
320 filename
.m_FileName
.MakeLower();
322 if(data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
324 filename
.m_FileName
+= _T('/');
327 list
.push_back(filename
);
329 }while(::FindNextFile(handle
, &data
));
333 std::sort(list
.begin(), list
.end(), SortCGitFileName
);
337 int GitStatus::EnumDirStatus(const CString
&gitdir
, const CString
&subpath
, git_wc_status_kind
* status
,BOOL IsFul
, BOOL IsRecursive
, BOOL IsIgnore
, FILL_STATUS_CALLBACK callback
, void *pData
)
342 CString path
= subpath
;
344 path
.Replace(_T('\\'), _T('/'));
345 if (!path
.IsEmpty() && path
[path
.GetLength() - 1] != _T('/'))
346 path
+= _T('/'); // Add trail / to show it is directory, not file name.
348 std::vector
<CGitFileName
> filelist
;
349 GetFileList(CombinePath(gitdir
, subpath
), filelist
);
351 g_IndexFileMap
.CheckAndUpdate(gitdir
,true);
353 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
);
355 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
356 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
358 // new git working tree has no index file
361 for (auto it
= filelist
.cbegin(); it
!= filelist
.cend(); ++it
)
363 CString casepath
= path
;
364 casepath
+= it
->m_CaseFileName
;
367 if (it
->m_FileName
.GetLength() > 0 && it
->m_FileName
[it
->m_FileName
.GetLength() - 1] == _T('/'))
372 if (g_IgnoreList
.CheckIgnoreChanged(gitdir
, casepath
, bIsDir
))
373 g_IgnoreList
.LoadAllIgnoreFile(gitdir
, casepath
, bIsDir
);
375 if (g_IgnoreList
.IsIgnore(casepath
, gitdir
, bIsDir
))
376 *status
= git_wc_status_ignored
;
380 *status
= git_wc_status_unversioned
;
385 *status
= git_wc_status_unversioned
;
388 callback(CombinePath(gitdir
, casepath
), *status
, bIsDir
, pData
, false, false);
393 CString lowcasepath
= path
;
394 lowcasepath
.MakeLower();
398 for (auto it
= filelist
.cbegin(), itend
= filelist
.cend(); it
!= itend
; ++it
)
400 casepath
= onepath
= lowcasepath
;
401 onepath
+= it
->m_FileName
;
402 casepath
+= it
->m_CaseFileName
;
405 if (!onepath
.IsEmpty() && onepath
[onepath
.GetLength() - 1] == _T('/'))
408 int matchLength
= -1;
410 matchLength
= onepath
.GetLength();
411 int pos
= SearchInSortVector(*indexptr
, onepath
, matchLength
);
412 int posintree
= SearchInSortVector(*treeptr
, onepath
, matchLength
);
414 if (pos
< 0 && posintree
< 0)
416 if (onepath
.IsEmpty())
421 *status
= git_wc_status_unversioned
;
423 callback(CombinePath(gitdir
, casepath
), *status
, bIsDir
, pData
, false, false);
427 if (g_IgnoreList
.CheckIgnoreChanged(gitdir
, casepath
, bIsDir
))
428 g_IgnoreList
.LoadAllIgnoreFile(gitdir
, casepath
, bIsDir
);
430 if (g_IgnoreList
.IsIgnore(casepath
, gitdir
, bIsDir
))
431 *status
= git_wc_status_ignored
;
433 *status
= git_wc_status_unversioned
;
436 callback(CombinePath(gitdir
, casepath
), *status
, bIsDir
, pData
, false, false);
438 else if (pos
< 0 && posintree
>= 0) /* check if file delete in index */
440 *status
= git_wc_status_deleted
;
442 callback(CombinePath(gitdir
, casepath
), *status
, bIsDir
, pData
, false, false);
444 else if (pos
>= 0 && posintree
< 0) /* Check if file added */
446 *status
= git_wc_status_added
;
447 if (indexptr
->at(pos
).m_Flags
& GIT_IDXENTRY_STAGEMASK
)
448 *status
= git_wc_status_conflicted
;
450 callback(CombinePath(gitdir
, casepath
), *status
, bIsDir
, pData
, false, false);
454 if (onepath
.IsEmpty())
459 *status
= git_wc_status_normal
;
461 callback(CombinePath(gitdir
, casepath
), *status
, bIsDir
, pData
, false, false);
465 bool assumeValid
= false;
466 bool skipWorktree
= false;
467 git_wc_status_kind filestatus
;
468 GetFileStatus(gitdir
, casepath
, &filestatus
, IsFul
, IsRecursive
, IsIgnore
, callback
, pData
, &assumeValid
, &skipWorktree
);
473 /* Check deleted file in system */
474 int start
= 0, end
= 0;
475 int pos
= SearchInSortVector(*indexptr
, lowcasepath
, lowcasepath
.GetLength()); // match path prefix, (sub)folders end with slash
476 std::map
<CString
, bool> skipWorktreeMap
;
478 if (GetRangeInSortVector(*indexptr
, lowcasepath
, lowcasepath
.GetLength(), &start
, &end
, pos
) == 0)
481 for (auto it
= indexptr
->cbegin() + start
, itlast
= indexptr
->cbegin() + end
; it
<= itlast
; ++it
)
483 int commonPrefixLength
= lowcasepath
.GetLength();
484 int index
= (*it
).m_FileName
.Find(_T('/'), commonPrefixLength
);
486 index
= (*it
).m_FileName
.GetLength();
488 ++index
; // include slash at the end for subfolders, so that we do not match files by mistake
490 CString filename
= (*it
).m_FileName
.Mid(commonPrefixLength
, index
- commonPrefixLength
);
491 if (oldstring
!= filename
)
493 oldstring
= filename
;
494 if (SearchInSortVector(filelist
, filename
, filename
.GetLength()) < 0)
496 bool skipWorktree
= false;
497 *status
= git_wc_status_deleted
;
498 if (((*it
).m_Flags
& GIT_IDXENTRY_SKIP_WORKTREE
) != 0)
500 skipWorktreeMap
[filename
] = true;
502 *status
= git_wc_status_normal
;
505 callback(CombinePath(gitdir
, (*it
).m_FileName
), *status
, false, pData
, false, skipWorktree
);
512 pos
= SearchInSortVector(*treeptr
, lowcasepath
, lowcasepath
.GetLength()); // match path prefix, (sub)folders end with slash
513 if (GetRangeInSortVector(*treeptr
, lowcasepath
, lowcasepath
.GetLength(), &start
, &end
, pos
) == 0)
516 for (auto it
= treeptr
->cbegin() + start
, itlast
= treeptr
->cbegin() + end
; it
<= itlast
; ++it
)
518 int commonPrefixLength
= lowcasepath
.GetLength();
519 int index
= (*it
).m_FileName
.Find(_T('/'), commonPrefixLength
);
521 index
= (*it
).m_FileName
.GetLength();
523 ++index
; // include slash at the end for subfolders, so that we do not match files by mistake
525 CString filename
= (*it
).m_FileName
.Mid(commonPrefixLength
, index
- commonPrefixLength
);
526 if (oldstring
!= filename
&& skipWorktreeMap
[filename
] != true)
528 oldstring
= filename
;
529 if (SearchInSortVector(filelist
, filename
, filename
.GetLength()) < 0)
531 *status
= git_wc_status_deleted
;
533 callback(CombinePath(gitdir
, (*it
).m_FileName
), *status
, false, pData
, false, false);
543 int GitStatus::GetDirStatus(const CString
& gitdir
, const CString
& subpath
, git_wc_status_kind
* status
, BOOL IsFul
, BOOL IsRecursive
, BOOL IsIgnore
)
548 CString path
= subpath
;
550 path
.Replace(_T('\\'), _T('/'));
551 if (!path
.IsEmpty() && path
[path
.GetLength() - 1] != _T('/'))
552 path
+= _T('/'); //Add trail / to show it is directory, not file name.
554 g_IndexFileMap
.CheckAndUpdate(gitdir
, true);
556 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
560 *status
= git_wc_status_unversioned
;
564 CString lowcasepath
= path
;
565 lowcasepath
.MakeLower();
567 int pos
= SearchInSortVector(*indexptr
, lowcasepath
, lowcasepath
.GetLength());
569 // Not In Version Contorl
574 *status
= git_wc_status_unversioned
;
578 // Check ignore always.
579 if (g_IgnoreList
.CheckIgnoreChanged(gitdir
, path
, true))
580 g_IgnoreList
.LoadAllIgnoreFile(gitdir
, path
, true);
582 if (g_IgnoreList
.IsIgnore(path
, gitdir
, true))
583 *status
= git_wc_status_ignored
;
585 *status
= git_wc_status_unversioned
;
587 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
);
589 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
590 // Check init repository
591 if (treeptr
->HeadIsEmpty() && path
.IsEmpty())
592 *status
= git_wc_status_normal
;
593 // check if only one file in repository is deleted in index
594 else if (path
.IsEmpty() && !treeptr
->empty())
595 *status
= git_wc_status_deleted
;
600 // In version control
601 *status
= git_wc_status_normal
;
606 end
= (int)indexptr
->size() - 1;
608 GetRangeInSortVector(*indexptr
, lowcasepath
, lowcasepath
.GetLength(), &start
, &end
, pos
);
611 for (auto it
= indexptr
->cbegin() + start
, itlast
= indexptr
->cbegin() + end
; it
<= itlast
; ++it
)
613 if (((*it
).m_Flags
& GIT_IDXENTRY_STAGEMASK
) != 0)
615 *status
= git_wc_status_conflicted
;
620 if (IsFul
&& (*status
!= git_wc_status_conflicted
))
622 *status
= git_wc_status_normal
;
624 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
);
628 // Check if new init repository
629 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
631 if (!treeptr
->empty() || treeptr
->HeadIsEmpty())
633 for (auto it
= indexptr
->cbegin() + start
, itlast
= indexptr
->cbegin() + end
; it
<= itlast
; ++it
)
635 pos
= SearchInSortVector(*treeptr
, (*it
).m_FileName
, -1);
639 *status
= max(git_wc_status_added
, *status
); // added file found
643 if (pos
>= 0 && treeptr
->at(pos
).m_Hash
!= (*it
).m_IndexHash
)
645 *status
= max(git_wc_status_modified
, *status
); // modified file found
651 if (*status
== git_wc_status_normal
)
653 pos
= SearchInSortVector(*treeptr
, lowcasepath
, lowcasepath
.GetLength());
655 *status
= max(git_wc_status_added
, *status
); // added file found
659 GetRangeInSortVector(*treeptr
, lowcasepath
, lowcasepath
.GetLength(), &hstart
, &hend
, pos
);
660 for (auto hit
= treeptr
->cbegin() + hstart
, lastElement
= treeptr
->cbegin() + hend
; hit
<= lastElement
; ++hit
)
662 if (SearchInSortVector(*indexptr
, (*hit
).m_FileName
, -1) < 0)
664 *status
= max(git_wc_status_deleted
, *status
); // deleted file found
674 // When status == git_wc_status_conflicted, needn't check each file status
675 // because git_wc_status_conflicted is highest.s
676 if (*status
== git_wc_status_conflicted
)
679 for (auto it
= indexptr
->cbegin() + start
, itlast
= indexptr
->cbegin() + end
; it
<= itlast
; ++it
)
681 //skip child directory
682 if (!IsRecursive
&& (*it
).m_FileName
.Find(_T('/'), path
.GetLength()) > 0)
685 git_wc_status_kind filestatus
= git_wc_status_none
;
686 bool assumeValid
= false;
687 bool skipWorktree
= false;
688 GetFileStatus(gitdir
, (*it
).m_FileName
, &filestatus
, IsFul
, IsRecursive
, IsIgnore
, nullptr, nullptr, &assumeValid
, &skipWorktree
);
691 case git_wc_status_added
:
692 case git_wc_status_modified
:
693 case git_wc_status_deleted
:
694 case git_wc_status_conflicted
:
695 *status
= GetMoreImportant(filestatus
, *status
);
704 bool GitStatus::IsExistIndexLockFile(const CString
&gitdir
)
706 CString sDirName
= gitdir
;
708 if (!PathIsDirectory(sDirName
))
710 int x
= sDirName
.ReverseFind(_T('\\'));
714 sDirName
= sDirName
.Left(x
);
719 if(PathFileExists(sDirName
+ _T("\\.git")))
721 if(PathFileExists(g_AdminDirMap
.GetAdminDir(sDirName
) + _T("index.lock")))
727 int x
= sDirName
.ReverseFind(_T('\\'));
731 sDirName
= sDirName
.Left(x
);
736 bool GitStatus::ReleasePath(const CString
&gitdir
)
738 g_IndexFileMap
.SafeClear(gitdir
);
742 bool GitStatus::ReleasePathsRecursively(const CString
&rootpath
)
744 g_IndexFileMap
.SafeClearRecursively(rootpath
);