1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2019, 2021-2023 - 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"
26 #include "ShellCache.h"
28 #include "SmartHandle.h"
30 extern CGitAdminDirMap g_AdminDirMap
;
31 CGitIndexFileMap g_IndexFileMap
;
32 CGitHeadFileMap g_HeadFileMap
;
33 CGitIgnoreList g_IgnoreList
;
35 GitStatus::GitStatus()
41 int GitStatus::GetAllStatus(const CTGitPath
& path
, bool bIsRecursive
, git_wc_status2_t
& status
)
46 isDir
= path
.IsDirectory();
47 if (!path
.HasAdminDir(&sProjectRoot
))
48 return git_wc_status_none
;
51 CString s
= path
.GetWinPathString();
52 if (s
.GetLength() > sProjectRoot
.GetLength())
54 if (sProjectRoot
.GetLength() == 3 && sProjectRoot
[1] == L
':')
55 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
57 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() - 1/*otherwise it gets initial slash*/);
60 const bool isfull
= (static_cast<DWORD
>(CRegStdDWORD(L
"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION
)) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
64 auto err
= GetDirStatus(sProjectRoot
, sSubPath
, &status
.status
, isfull
, bIsRecursive
, isfull
);
65 AdjustFolderStatus(status
.status
);
69 return GetFileStatus(sProjectRoot
, sSubPath
, status
, isfull
, isfull
);
74 git_wc_status_kind
GitStatus::GetMoreImportant(git_wc_status_kind status1
, git_wc_status_kind status2
)
76 if (GetStatusRanking(status1
) >= GetStatusRanking(status2
))
80 // static private method
81 int GitStatus::GetStatusRanking(git_wc_status_kind status
)
85 case git_wc_status_none
:
87 case git_wc_status_unversioned
:
89 case git_wc_status_ignored
:
91 case git_wc_status_normal
:
92 case git_wc_status_added
:
94 case git_wc_status_deleted
:
96 case git_wc_status_modified
:
98 case git_wc_status_conflicted
:
105 void GitStatus::GetStatus(const CTGitPath
& path
, bool /*update*/ /* = false */, bool noignore
/* = false */, bool /*noexternals*/ /* = false */)
107 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
108 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
109 // after the call again
111 CString sProjectRoot
;
112 if ( !path
.HasAdminDir(&sProjectRoot
) )
115 const bool isfull
= (static_cast<DWORD
>(CRegStdDWORD(L
"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION
)) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
119 LPCWSTR lpszSubPath
= nullptr;
121 CString s
= path
.GetWinPathString();
122 if (s
.GetLength() > sProjectRoot
.GetLength())
124 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
125 lpszSubPath
= sSubPath
;
126 // skip initial slash if necessary
127 if (*lpszSubPath
== L
'\\')
131 m_status
.status
= git_wc_status_none
;
132 m_status
.assumeValid
= false;
133 m_status
.skipWorktree
= false;
135 if (path
.IsDirectory())
137 err
= GetDirStatus(sProjectRoot
, lpszSubPath
, &m_status
.status
, isfull
, false, !noignore
);
138 AdjustFolderStatus(m_status
.status
);
141 err
= GetFileStatus(sProjectRoot
, lpszSubPath
, m_status
, isfull
, !noignore
);
143 // Error present if function is not under version control
154 using CAutoLocker
= CComCritSecLock
<CComCriticalSection
>;
158 SHARED_INDEX_PTR pIndex
;
159 SHARED_TREE_PTR pTree
;
162 static int GetFileStatus_int(const CString
& gitdir
, CGitRepoLists
& repolists
, const CString
& path
, git_wc_status2_t
& status
, BOOL IsFull
, BOOL IsIgnore
, BOOL update
)
164 ATLASSERT(repolists
.pIndex
);
165 ATLASSERT(!status
.assumeValid
&& !status
.skipWorktree
);
168 if (repolists
.pIndex
->GetFileStatus(gitdir
, path
, status
, &hash
))
170 // an error occurred in GetFileStatus
171 status
.status
= git_wc_status_none
;
175 if (status
.status
== git_wc_status_conflicted
)
178 if (status
.status
== git_wc_status_unversioned
)
182 if (!repolists
.pTree
)
185 repolists
.pTree
= g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, repolists
.pIndex
->IsIgnoreCase());
187 repolists
.pTree
= g_HeadFileMap
.SafeGet(gitdir
);
190 if (!repolists
.pTree
)
192 status
.status
= git_wc_status_none
;
196 // deleted only in index item?
197 if (SearchInSortVector(*repolists
.pTree
, path
, -1, repolists
.pIndex
->IsIgnoreCase()) != NPOS
)
199 status
.status
= git_wc_status_deleted
;
206 status
.status
= git_wc_status_unversioned
;
210 g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, path
, false);
211 if (g_IgnoreList
.IsIgnore(path
, gitdir
, false, g_AdminDirMap
.GetAdminDir(gitdir
)))
212 status
.status
= git_wc_status_ignored
;
217 if ((status
.status
== git_wc_status_normal
|| status
.status
== git_wc_status_modified
) && IsFull
)
219 if (!repolists
.pTree
)
222 repolists
.pTree
= g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, repolists
.pIndex
->IsIgnoreCase());
224 repolists
.pTree
= g_HeadFileMap
.SafeGet(gitdir
);
227 if (!repolists
.pTree
)
229 status
.status
= git_wc_status_none
;
234 size_t start
= SearchInSortVector(*repolists
.pTree
, path
, -1, repolists
.pIndex
->IsIgnoreCase());
237 status
.status
= git_wc_status_added
;
238 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": File miss in head tree %s", static_cast<LPCWSTR
>(path
));
242 // staged and not commit
243 if ((*repolists
.pTree
)[start
].m_Hash
!= hash
)
245 status
= { git_wc_status_modified
, false, false };
253 int GitStatus::GetFileStatus(const CString
& gitdir
, CString path
, git_wc_status2_t
& status
, BOOL IsFull
, BOOL IsIgnore
, bool update
)
255 ATLASSERT(!status
.assumeValid
&& !status
.skipWorktree
);
257 path
.Replace(L
'\\', L
'/');
259 CGitRepoLists sharedRepoLists
;
261 sharedRepoLists
.pIndex
= g_IndexFileMap
.CheckAndUpdate(gitdir
);
263 sharedRepoLists
.pIndex
= g_IndexFileMap
.SafeGet(gitdir
);
264 if (!sharedRepoLists
.pIndex
)
266 // git working tree has broken index
267 status
.status
= git_wc_status_none
;
271 return GetFileStatus_int(gitdir
, sharedRepoLists
, path
, status
, IsFull
, IsIgnore
, update
);
274 // checks whether indexPath is a direct submodule and not one in a subfolder
275 static bool IsDirectSubmodule(const CString
& indexPath
, int prefix
)
277 if (!CStringUtils::EndsWith(indexPath
, L
'/'))
280 auto ptr
= indexPath
.GetString() + prefix
;
289 return folderdepth
== 1;
293 int GitStatus::GetFileList(const CString
& path
, std::vector
<CGitFileName
>& list
, bool& isRepoRoot
, bool ignoreCase
)
295 WIN32_FIND_DATA data
;
296 CAutoFindFile handle
= ::FindFirstFileEx(CombinePath(path
, L
"*.*"), FindExInfoBasic
, &data
, FindExSearchNameMatch
, nullptr, FIND_FIRST_EX_LARGE_FETCH
);
301 if (wcscmp(data
.cFileName
, L
".git") == 0)
307 if (wcscmp(data
.cFileName
, L
".") == 0)
310 if (wcscmp(data
.cFileName
, L
"..") == 0)
313 CGitFileName
filename(data
.cFileName
, static_cast<__int64
>(data
.nFileSizeHigh
) << 32 | data
.nFileSizeLow
, static_cast<__int64
>(data
.ftLastWriteTime
.dwHighDateTime
) << 32 | data
.ftLastWriteTime
.dwLowDateTime
);
314 if ((data
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) && !CPathUtils::ReadLink(CombinePath(path
, filename
.m_FileName
)))
315 filename
.m_bSymlink
= true;
316 else if (data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
317 filename
.m_FileName
+= L
'/';
319 list
.push_back(filename
);
321 }while(::FindNextFile(handle
, &data
));
323 handle
.CloseHandle(); // manually close handle here in order to keep handles open as short as possible
325 DoSortFilenametSortVector(list
, ignoreCase
);
330 int GitStatus::EnumDirStatus(const CString
& gitdir
, const CString
& subpath
, git_wc_status_kind
* dirstatus
, FILL_STATUS_CALLBACK callback
, void* pData
)
332 CString path
= subpath
;
334 path
.Replace(L
'\\', L
'/');
335 if (!path
.IsEmpty() && path
[path
.GetLength() - 1] != L
'/')
336 path
+= L
'/'; // Add trail / to show it is directory, not file name.
338 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.CheckAndUpdate(gitdir
);
339 // there was an error loading the index
343 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, indexptr
->IsIgnoreCase());
344 // there was an error loading the HEAD commit/tree
348 size_t indexpos
= SearchInSortVector(*indexptr
, path
, path
.GetLength(), indexptr
->IsIgnoreCase()); // match path prefix, (sub)folders end with slash
349 size_t treepos
= SearchInSortVector(*treeptr
, path
, path
.GetLength(), indexptr
->IsIgnoreCase()); // match path prefix, (sub)folders end with slash
351 std::set
<CString
> localLastCheckCache
;
352 CString adminDir
= g_AdminDirMap
.GetAdminDir(gitdir
);
354 std::vector
<CGitFileName
> filelist
;
355 int folderignoredchecked
= false;
356 bool isRepoRoot
= false;
357 GetFileList(CombinePath(gitdir
, subpath
), filelist
, isRepoRoot
, indexptr
->IsIgnoreCase());
358 *dirstatus
= git_wc_status_unknown
;
360 *dirstatus
= git_wc_status_normal
;
361 else if (indexpos
== NPOS
&& treepos
== NPOS
)
363 // if folder does not contain any versioned items, it might be ignored
364 g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, subpath
, true, &localLastCheckCache
);
365 if (g_IgnoreList
.IsIgnore(subpath
, gitdir
, true, adminDir
))
366 *dirstatus
= git_wc_status_ignored
;
367 folderignoredchecked
= true;
370 CAutoRepository repository
;
371 for (auto it
= filelist
.cbegin(), itend
= filelist
.cend(); it
!= itend
; ++it
)
373 auto& fileentry
= *it
;
375 CString
onepath(path
);
376 onepath
+= fileentry
.m_FileName
;
379 if (!onepath
.IsEmpty() && onepath
[onepath
.GetLength() - 1] == L
'/')
382 int matchLength
= -1;
384 matchLength
= onepath
.GetLength();
385 size_t pos
= SearchInSortVector(*indexptr
, onepath
, matchLength
, indexptr
->IsIgnoreCase());
386 size_t posintree
= SearchInSortVector(*treeptr
, onepath
, matchLength
, indexptr
->IsIgnoreCase());
388 git_wc_status2_t status
= { git_wc_status_none
, false, false };
390 if (pos
== NPOS
&& posintree
== NPOS
)
392 if (*dirstatus
== git_wc_status_ignored
)
393 status
.status
= git_wc_status_ignored
;
396 status
.status
= git_wc_status_unversioned
;
398 g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, onepath
, bIsDir
, &localLastCheckCache
);
399 // whole folder might be ignored, check this once if we are not the root folder in order to speed up all following ignored files
400 if (!folderignoredchecked
&& *dirstatus
!= git_wc_status_normal
)
402 if (g_IgnoreList
.IsIgnore(subpath
, gitdir
, true, adminDir
))
404 *dirstatus
= git_wc_status_ignored
;
405 status
.status
= git_wc_status_ignored
;
407 folderignoredchecked
= true;
409 if (status
.status
!= git_wc_status_ignored
&& g_IgnoreList
.IsIgnore(onepath
, gitdir
, bIsDir
, adminDir
))
410 status
.status
= git_wc_status_ignored
;
412 callback(CombinePath(gitdir
, onepath
), &status
, bIsDir
, fileentry
.m_LastModified
, pData
);
414 else if (pos
== NPOS
&& posintree
!= NPOS
) /* check if file delete in index */
416 status
.status
= git_wc_status_deleted
;
417 callback(CombinePath(gitdir
, onepath
), &status
, bIsDir
, fileentry
.m_LastModified
, pData
);
419 else if (pos
!= NPOS
&& posintree
== NPOS
) /* Check if file added */
421 status
.status
= git_wc_status_added
;
422 if ((*indexptr
)[pos
].m_Flags
& GIT_INDEX_ENTRY_STAGEMASK
)
423 status
.status
= git_wc_status_conflicted
;
424 callback(CombinePath(gitdir
, onepath
), &status
, bIsDir
, fileentry
.m_LastModified
, pData
);
430 status
.status
= git_wc_status_normal
;
431 callback(CombinePath(gitdir
, onepath
), &status
, bIsDir
, fileentry
.m_LastModified
, pData
);
435 auto& indexentry
= (*indexptr
)[pos
];
436 if (indexentry
.m_Flags
& GIT_INDEX_ENTRY_STAGEMASK
)
438 status
.status
= git_wc_status_conflicted
;
439 callback(CombinePath(gitdir
, onepath
), &status
, false, fileentry
.m_LastModified
, pData
);
442 if ((*indexptr
).GetFileStatus(repository
, gitdir
, indexentry
, status
, fileentry
.m_LastModified
, fileentry
.m_Size
, fileentry
.m_bSymlink
))
444 if (status
.status
== git_wc_status_normal
&& (*treeptr
)[posintree
].m_Hash
!= indexentry
.m_IndexHash
)
445 status
= { git_wc_status_modified
, false, false };
446 callback(CombinePath(gitdir
, onepath
), &status
, false, fileentry
.m_LastModified
, pData
);
450 repository
.Free(); // explicitly free the handle here in order to keep an open repository as short as possible
452 /* Check deleted file in system */
453 size_t start
= 0, end
= 0;
454 std::set
<CString
> alreadyReported
;
455 if (GetRangeInSortVector(*indexptr
, path
, path
.GetLength(), indexptr
->IsIgnoreCase(), &start
, &end
, indexpos
) == 0)
457 *dirstatus
= git_wc_status_normal
; // here we know that this folder has versioned entries
459 for (auto it
= indexptr
->cbegin() + start
, itlast
= indexptr
->cbegin() + end
; it
<= itlast
; ++it
)
462 const int commonPrefixLength
= path
.GetLength();
463 int index
= entry
.m_FileName
.Find(L
'/', commonPrefixLength
);
465 index
= entry
.m_FileName
.GetLength();
467 ++index
; // include slash at the end for subfolders, so that we do not match files by mistake
469 CString filename
= entry
.m_FileName
.Mid(commonPrefixLength
, index
- commonPrefixLength
);
470 if (oldstring
!= filename
)
472 oldstring
= filename
;
473 const int length
= filename
.GetLength();
474 const bool isDir
= filename
[length
- 1] == L
'/';
475 if (SearchInSortVector(filelist
, filename
, isDir
? length
: -1, indexptr
->IsIgnoreCase()) == NPOS
) // do full match for filenames and only prefix-match ending with "/" for folders
477 git_wc_status2_t status
= { (!isDir
|| IsDirectSubmodule(entry
.m_FileName
, commonPrefixLength
)) ? git_wc_status_deleted
: git_wc_status_modified
, false, false }; // only report deleted submodules and files as deletedy
478 if ((entry
.m_FlagsExtended
& GIT_INDEX_ENTRY_SKIP_WORKTREE
) != 0)
480 status
.skipWorktree
= true;
481 status
.status
= git_wc_status_normal
;
482 oldstring
.Empty(); // without this a deleted folder which has two versioned files and only the first is skipwoktree flagged gets reported as normal
483 if (alreadyReported
.find(filename
) != alreadyReported
.cend())
486 alreadyReported
.insert(filename
);
487 callback(CombinePath(gitdir
, subpath
, filename
), &status
, isDir
, 0, pData
);
490 // folder might be replaced by symlink
491 filename
.TrimRight(L
'/');
492 auto filepos
= SearchInSortVector(filelist
, filename
, -1, indexptr
->IsIgnoreCase());
493 if (filepos
== NPOS
|| !filelist
[filepos
].m_bSymlink
)
495 status
.status
= git_wc_status_deleted
;
496 callback(CombinePath(gitdir
, subpath
, filename
), &status
, false, 0, pData
);
504 if (GetRangeInSortVector(*treeptr
, path
, path
.GetLength(), indexptr
->IsIgnoreCase(), &start
, &end
, treepos
) == 0)
506 *dirstatus
= git_wc_status_normal
; // here we know that this folder has versioned entries
508 for (auto it
= treeptr
->cbegin() + start
, itlast
= treeptr
->cbegin() + end
; it
<= itlast
; ++it
)
511 const int commonPrefixLength
= path
.GetLength();
512 int index
= entry
.m_FileName
.Find(L
'/', commonPrefixLength
);
514 index
= entry
.m_FileName
.GetLength();
516 ++index
; // include slash at the end for subfolders, so that we do not match files by mistake
518 CString filename
= entry
.m_FileName
.Mid(commonPrefixLength
, index
- commonPrefixLength
);
519 if (oldstring
!= filename
&& alreadyReported
.find(filename
) == alreadyReported
.cend())
521 oldstring
= filename
;
522 const int length
= filename
.GetLength();
523 const bool isDir
= filename
[length
- 1] == L
'/';
524 if (SearchInSortVector(filelist
, filename
, isDir
? length
: -1, indexptr
->IsIgnoreCase()) == NPOS
) // do full match for filenames and only prefix-match ending with "/" for folders
526 git_wc_status2_t status
= { (!isDir
|| IsDirectSubmodule(entry
.m_FileName
, commonPrefixLength
)) ? git_wc_status_deleted
: git_wc_status_modified
, false, false };
527 callback(CombinePath(gitdir
, subpath
, filename
), &status
, isDir
, 0, pData
);
537 int GitStatus::GetDirStatus(const CString
& gitdir
, const CString
& subpath
, git_wc_status_kind
* status
, BOOL IsFul
, BOOL IsRecursive
, BOOL IsIgnore
)
541 CString path
= subpath
;
543 path
.Replace(L
'\\', L
'/');
544 if (!path
.IsEmpty() && path
[path
.GetLength() - 1] != L
'/')
545 path
+= L
'/'; //Add trail / to show it is directory, not file name.
547 CGitRepoLists sharedRepoLists
;
548 sharedRepoLists
.pIndex
= g_IndexFileMap
.CheckAndUpdate(gitdir
);
551 if (!sharedRepoLists
.pIndex
)
553 *status
= git_wc_status_none
;
557 size_t pos
= SearchInSortVector(*sharedRepoLists
.pIndex
, path
, path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase());
559 // Not In Version Contorl
564 // WC root is at least normal if there are no files added/deleted
565 if (subpath
.IsEmpty())
567 *status
= git_wc_status_normal
;
570 *status
= git_wc_status_unversioned
;
574 sharedRepoLists
.pTree
= g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, sharedRepoLists
.pIndex
->IsIgnoreCase());
576 if (!sharedRepoLists
.pTree
)
578 *status
= git_wc_status_none
;
582 // check whether there files in head with are not in index
583 pos
= SearchInSortVector(*sharedRepoLists
.pTree
, path
, path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase());
586 *status
= git_wc_status_deleted
;
590 // WC root is at least normal if there are no files added/deleted
593 *status
= git_wc_status_normal
;
598 g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, path
, true);
599 if (g_IgnoreList
.IsIgnore(path
, gitdir
, true, g_AdminDirMap
.GetAdminDir(gitdir
)))
600 *status
= git_wc_status_ignored
;
602 *status
= git_wc_status_unversioned
;
607 // In version control
608 *status
= git_wc_status_normal
;
613 GetRangeInSortVector(*sharedRepoLists
.pIndex
, path
,path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase(), &start
, &end
, pos
);
616 for (auto it
= sharedRepoLists
.pIndex
->cbegin() + start
, itlast
= sharedRepoLists
.pIndex
->cbegin() + end
; sharedRepoLists
.pIndex
->m_bHasConflicts
&& it
<= itlast
; ++it
)
618 if (((*it
).m_Flags
& GIT_INDEX_ENTRY_STAGEMASK
) != 0)
620 *status
= git_wc_status_conflicted
;
621 // When status == git_wc_status_conflicted, we don't need to check each file status
622 // because git_wc_status_conflicted is the highest.
631 // Check if new init repository
632 sharedRepoLists
.pTree
= g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, sharedRepoLists
.pIndex
->IsIgnoreCase());
634 if (!sharedRepoLists
.pTree
)
636 *status
= git_wc_status_none
;
641 for (auto it
= sharedRepoLists
.pIndex
->cbegin() + start
, itlast
= sharedRepoLists
.pIndex
->cbegin() + end
; it
<= itlast
; ++it
)
643 auto& indexentry
= *it
;
644 pos
= SearchInSortVector(*sharedRepoLists
.pTree
, indexentry
.m_FileName
, -1, sharedRepoLists
.pIndex
->IsIgnoreCase());
648 *status
= GetMoreImportant(git_wc_status_added
, *status
); // added file found
649 AdjustFolderStatus(*status
);
650 if (GetMoreImportant(*status
, git_wc_status_modified
) == *status
) // the only potential higher status which me might get in this loop
655 if ((*sharedRepoLists
.pTree
)[pos
].m_Hash
!= indexentry
.m_IndexHash
)
657 *status
= GetMoreImportant(git_wc_status_modified
, *status
); // modified file found
663 if (*status
== git_wc_status_normal
)
665 pos
= SearchInSortVector(*sharedRepoLists
.pTree
, path
, path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase());
667 *status
= GetMoreImportant(git_wc_status_added
, *status
); // added file found
671 // we know that pos exists in treeptr
672 GetRangeInSortVector(*sharedRepoLists
.pTree
, path
, path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase(), &hstart
, &hend
, pos
);
673 for (auto hit
= sharedRepoLists
.pTree
->cbegin() + hstart
, lastElement
= sharedRepoLists
.pTree
->cbegin() + hend
; hit
<= lastElement
; ++hit
)
675 if (SearchInSortVector(*sharedRepoLists
.pIndex
, (*hit
).m_FileName
, -1, sharedRepoLists
.pIndex
->IsIgnoreCase()) == NPOS
)
677 *status
= GetMoreImportant(git_wc_status_deleted
, *status
); // deleted file found
687 auto mostImportantPossibleFolderStatus
= GetMoreImportant(git_wc_status_added
, GetMoreImportant(git_wc_status_modified
, git_wc_status_deleted
));
688 AdjustFolderStatus(mostImportantPossibleFolderStatus
);
689 // we can skip here when we already have the highest possible status
690 if (mostImportantPossibleFolderStatus
== *status
)
693 for (auto it
= sharedRepoLists
.pIndex
->cbegin() + start
, itlast
= sharedRepoLists
.pIndex
->cbegin() + end
; it
<= itlast
; ++it
)
695 auto& indexentry
= *it
;
696 // skip child directory, but handle submodules
697 if (!IsRecursive
&& indexentry
.m_FileName
.Find(L
'/', path
.GetLength()) > 0 && !IsDirectSubmodule(indexentry
.m_FileName
, path
.GetLength()))
700 git_wc_status2_t filestatus
= { git_wc_status_none
, false, false };
701 GetFileStatus_int(gitdir
, sharedRepoLists
, indexentry
.m_FileName
, filestatus
, IsFul
, IsIgnore
, false);
702 switch (filestatus
.status
)
704 case git_wc_status_added
:
705 case git_wc_status_modified
:
706 case git_wc_status_deleted
:
707 //case git_wc_status_conflicted: cannot happen, we exit as soon we found a conflict in subpath
708 *status
= GetMoreImportant(filestatus
.status
, *status
);
709 AdjustFolderStatus(*status
);
710 if (mostImportantPossibleFolderStatus
== *status
)
720 bool GitStatus::IsExistIndexLockFile(CString sDirName
)
722 if (!PathIsDirectory(sDirName
))
724 const int x
= sDirName
.ReverseFind(L
'\\');
728 sDirName
.Truncate(x
);
733 if (PathFileExists(CombinePath(sDirName
, L
".git")))
735 if (PathFileExists(g_AdminDirMap
.GetWorktreeAdminDirConcat(sDirName
, L
"index.lock")))
741 const int x
= sDirName
.ReverseFind(L
'\\');
745 sDirName
.Truncate(x
);
750 bool GitStatus::ReleasePath(const CString
&gitdir
)
752 g_IndexFileMap
.SafeClear(gitdir
);
753 g_HeadFileMap
.SafeClear(gitdir
);
757 bool GitStatus::ReleasePathsRecursively(const CString
&rootpath
)
759 g_IndexFileMap
.SafeClearRecursively(rootpath
);
760 g_HeadFileMap
.SafeClearRecursively(rootpath
);
764 void GitStatus::AdjustFolderStatus(git_wc_status_kind
& status
)
766 if (status
== git_wc_status_deleted
|| status
== git_wc_status_added
)
767 status
= git_wc_status_modified
;