1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - 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()
38 m_status
.assumeValid
= m_status
.skipWorktree
= false;
39 m_status
.status
= git_wc_status_none
;
44 int GitStatus::GetAllStatus(const CTGitPath
& path
, bool bIsRecursive
, git_wc_status2_t
& status
)
49 isDir
= path
.IsDirectory();
50 if (!path
.HasAdminDir(&sProjectRoot
))
51 return git_wc_status_none
;
54 CString s
= path
.GetWinPathString();
55 if (s
.GetLength() > sProjectRoot
.GetLength())
57 if (sProjectRoot
.GetLength() == 3 && sProjectRoot
[1] == L
':')
58 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
60 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength() - 1/*otherwise it gets initial slash*/);
63 bool isfull
= ((DWORD
)CRegStdDWORD(L
"Software\\TortoiseGit\\CacheType",
64 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
68 auto err
= GetDirStatus(sProjectRoot
, sSubPath
, &status
.status
, isfull
, bIsRecursive
, isfull
);
69 AdjustFolderStatus(status
.status
);
73 return GetFileStatus(sProjectRoot
, sSubPath
, status
, isfull
, isfull
);
78 git_wc_status_kind
GitStatus::GetMoreImportant(git_wc_status_kind status1
, git_wc_status_kind status2
)
80 if (GetStatusRanking(status1
) >= GetStatusRanking(status2
))
84 // static private method
85 int GitStatus::GetStatusRanking(git_wc_status_kind status
)
89 case git_wc_status_none
:
91 case git_wc_status_unversioned
:
93 case git_wc_status_ignored
:
95 case git_wc_status_normal
:
96 case git_wc_status_added
:
98 case git_wc_status_deleted
:
100 case git_wc_status_modified
:
102 case git_wc_status_conflicted
:
109 void GitStatus::GetStatus(const CTGitPath
& path
, bool /*update*/ /* = false */, bool noignore
/* = false */, bool /*noexternals*/ /* = false */)
111 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
112 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
113 // after the call again
115 CString sProjectRoot
;
116 if ( !path
.HasAdminDir(&sProjectRoot
) )
119 bool isfull
= ((DWORD
)CRegStdDWORD(L
"Software\\TortoiseGit\\CacheType",
120 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
124 LPCTSTR lpszSubPath
= nullptr;
126 CString s
= path
.GetWinPathString();
127 if (s
.GetLength() > sProjectRoot
.GetLength())
129 sSubPath
= s
.Right(s
.GetLength() - sProjectRoot
.GetLength());
130 lpszSubPath
= sSubPath
;
131 // skip initial slash if necessary
132 if (*lpszSubPath
== L
'\\')
136 m_status
.status
= git_wc_status_none
;
137 m_status
.assumeValid
= false;
138 m_status
.skipWorktree
= false;
140 if (path
.IsDirectory())
142 err
= GetDirStatus(sProjectRoot
, lpszSubPath
, &m_status
.status
, isfull
, false, !noignore
);
143 AdjustFolderStatus(m_status
.status
);
146 err
= GetFileStatus(sProjectRoot
, lpszSubPath
, m_status
, isfull
, !noignore
);
148 // Error present if function is not under version control
159 typedef CComCritSecLock
<CComCriticalSection
> CAutoLocker
;
161 typedef struct CGitRepoLists
163 SHARED_INDEX_PTR pIndex
;
164 SHARED_TREE_PTR pTree
;
167 static int GetFileStatus_int(const CString
& gitdir
, CGitRepoLists
& repolists
, const CString
& path
, git_wc_status2_t
& status
, BOOL IsFull
, BOOL IsIgnore
, BOOL update
)
169 ATLASSERT(repolists
.pIndex
);
170 ATLASSERT(!status
.assumeValid
&& !status
.skipWorktree
);
173 if (repolists
.pIndex
->GetFileStatus(gitdir
, path
, status
, &hash
))
175 // an error occurred in GetFileStatus
176 status
.status
= git_wc_status_none
;
180 if (status
.status
== git_wc_status_conflicted
)
183 if (status
.status
== git_wc_status_unversioned
)
187 if (!repolists
.pTree
)
190 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, repolists
.pIndex
->IsIgnoreCase());
192 // Check Head Tree Hash
193 repolists
.pTree
= g_HeadFileMap
.SafeGet(gitdir
);
196 if (!repolists
.pTree
)
198 status
.status
= git_wc_status_none
;
202 // deleted only in index item?
203 if (SearchInSortVector(*repolists
.pTree
, path
, -1, repolists
.pIndex
->IsIgnoreCase()) != NPOS
)
205 status
.status
= git_wc_status_deleted
;
212 status
.status
= git_wc_status_unversioned
;
216 g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, path
, false);
217 if (g_IgnoreList
.IsIgnore(path
, gitdir
, false))
218 status
.status
= git_wc_status_ignored
;
223 if ((status
.status
== git_wc_status_normal
|| status
.status
== git_wc_status_modified
) && IsFull
)
225 if (!repolists
.pTree
)
228 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, repolists
.pIndex
->IsIgnoreCase());
230 // Check Head Tree Hash
231 repolists
.pTree
= g_HeadFileMap
.SafeGet(gitdir
);
234 if (!repolists
.pTree
)
236 status
.status
= git_wc_status_none
;
241 size_t start
= SearchInSortVector(*repolists
.pTree
, path
, -1, repolists
.pIndex
->IsIgnoreCase());
244 status
.status
= git_wc_status_added
;
245 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) L
": File miss in head tree %s", (LPCTSTR
)path
);
249 // staged and not commit
250 if ((*repolists
.pTree
)[start
].m_Hash
!= hash
)
252 status
= { git_wc_status_modified
, false, false };
260 int GitStatus::GetFileStatus(const CString
& gitdir
, CString path
, git_wc_status2_t
& status
, BOOL IsFull
, BOOL IsIgnore
, bool update
)
262 ATLASSERT(!status
.assumeValid
&& !status
.skipWorktree
);
264 path
.Replace(L
'\\', L
'/');
266 CGitRepoLists sharedRepoLists
;
268 g_IndexFileMap
.CheckAndUpdate(gitdir
);
269 sharedRepoLists
.pIndex
= g_IndexFileMap
.SafeGet(gitdir
);
270 if (!sharedRepoLists
.pIndex
)
272 // git working tree has broken index
273 status
.status
= git_wc_status_none
;
277 return GetFileStatus_int(gitdir
, sharedRepoLists
, path
, status
, IsFull
, IsIgnore
, update
);
280 // checks whether indexPath is a direct submodule and not one in a subfolder
281 static bool IsDirectSubmodule(const CString
& indexPath
, int prefix
)
283 if (!CStringUtils::EndsWith(indexPath
, L
'/'))
286 auto ptr
= indexPath
.GetString() + prefix
;
295 return folderdepth
== 1;
299 bool GitStatus::CheckAndUpdateIgnoreFiles(const CString
& gitdir
, const CString
& subpaths
, bool isDir
)
301 return g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, subpaths
, isDir
);
304 bool GitStatus::IsIgnored(const CString
& gitdir
, const CString
& path
, bool isDir
)
306 return g_IgnoreList
.IsIgnore(path
, gitdir
, isDir
);
309 int GitStatus::GetFileList(const CString
& path
, std::vector
<CGitFileName
>& list
, bool& isRepoRoot
, bool ignoreCase
)
311 WIN32_FIND_DATA data
;
312 CAutoFindFile handle
= ::FindFirstFileEx(CombinePath(path
, L
"*.*"), FindExInfoBasic
, &data
, FindExSearchNameMatch
, nullptr, FIND_FIRST_EX_LARGE_FETCH
);
317 if (wcscmp(data
.cFileName
, L
".git") == 0)
323 if (wcscmp(data
.cFileName
, L
".") == 0)
326 if (wcscmp(data
.cFileName
, L
"..") == 0)
329 CGitFileName
filename(data
.cFileName
, ((__int64
)data
.nFileSizeHigh
<< 32) + data
.nFileSizeLow
, ((__int64
)data
.ftLastWriteTime
.dwHighDateTime
<< 32) + data
.ftLastWriteTime
.dwLowDateTime
);
330 if ((data
.dwFileAttributes
& FILE_ATTRIBUTE_REPARSE_POINT
) && !CPathUtils::ReadLink(CombinePath(path
, filename
.m_FileName
)))
331 filename
.m_bSymlink
= true;
332 else if (data
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
)
333 filename
.m_FileName
+= L
'/';
335 list
.push_back(filename
);
337 }while(::FindNextFile(handle
, &data
));
339 handle
.CloseHandle(); // manually close handle here in order to keep handles open as short as possible
341 DoSortFilenametSortVector(list
, ignoreCase
);
346 int GitStatus::EnumDirStatus(const CString
& gitdir
, const CString
& subpath
, git_wc_status_kind
* dirstatus
, FILL_STATUS_CALLBACK callback
, void* pData
)
348 CString path
= subpath
;
350 path
.Replace(L
'\\', L
'/');
351 if (!path
.IsEmpty() && path
[path
.GetLength() - 1] != L
'/')
352 path
+= L
'/'; // Add trail / to show it is directory, not file name.
354 g_IndexFileMap
.CheckAndUpdate(gitdir
);
356 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
357 // there was an error loading the index
361 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, indexptr
->IsIgnoreCase());
363 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
364 // there was an error loading the HEAD commit/tree
368 size_t indexpos
= SearchInSortVector(*indexptr
, path
, path
.GetLength(), indexptr
->IsIgnoreCase()); // match path prefix, (sub)folders end with slash
369 size_t treepos
= SearchInSortVector(*treeptr
, path
, path
.GetLength(), indexptr
->IsIgnoreCase()); // match path prefix, (sub)folders end with slash
371 std::vector
<CGitFileName
> filelist
;
372 int folderignoredchecked
= false;
373 bool isRepoRoot
= false;
374 GetFileList(CombinePath(gitdir
, subpath
), filelist
, isRepoRoot
, indexptr
->IsIgnoreCase());
375 *dirstatus
= git_wc_status_unknown
;
377 *dirstatus
= git_wc_status_normal
;
378 else if (indexpos
== NPOS
&& treepos
== NPOS
)
380 // if folder does not contain any versioned items, it might be ignored
381 g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, subpath
, true);
382 if (g_IgnoreList
.IsIgnore(subpath
, gitdir
, true))
383 *dirstatus
= git_wc_status_ignored
;
384 folderignoredchecked
= true;
387 CAutoRepository repository
;
388 for (auto it
= filelist
.cbegin(), itend
= filelist
.cend(); it
!= itend
; ++it
)
390 auto& fileentry
= *it
;
392 CString
onepath(path
);
393 onepath
+= fileentry
.m_FileName
;
396 if (!onepath
.IsEmpty() && onepath
[onepath
.GetLength() - 1] == L
'/')
399 int matchLength
= -1;
401 matchLength
= onepath
.GetLength();
402 size_t pos
= SearchInSortVector(*indexptr
, onepath
, matchLength
, indexptr
->IsIgnoreCase());
403 size_t posintree
= SearchInSortVector(*treeptr
, onepath
, matchLength
, indexptr
->IsIgnoreCase());
405 git_wc_status2_t status
= { git_wc_status_none
, false, false };
407 if (pos
== NPOS
&& posintree
== NPOS
)
409 if (*dirstatus
== git_wc_status_ignored
)
410 status
.status
= git_wc_status_ignored
;
413 status
.status
= git_wc_status_unversioned
;
415 g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, onepath
, bIsDir
);
416 // whole folder might be ignored, check this once if we are not the root folder in order to speed up all following ignored files
417 if (!folderignoredchecked
&& *dirstatus
!= git_wc_status_normal
)
419 if (g_IgnoreList
.IsIgnore(subpath
, gitdir
, true))
421 *dirstatus
= git_wc_status_ignored
;
422 status
.status
= git_wc_status_ignored
;
424 folderignoredchecked
= true;
426 if (status
.status
!= git_wc_status_ignored
&& g_IgnoreList
.IsIgnore(onepath
, gitdir
, bIsDir
))
427 status
.status
= git_wc_status_ignored
;
429 callback(CombinePath(gitdir
, onepath
), &status
, bIsDir
, fileentry
.m_LastModified
, pData
);
431 else if (pos
== NPOS
&& posintree
!= NPOS
) /* check if file delete in index */
433 status
.status
= git_wc_status_deleted
;
434 callback(CombinePath(gitdir
, onepath
), &status
, bIsDir
, fileentry
.m_LastModified
, pData
);
436 else if (pos
!= NPOS
&& posintree
== NPOS
) /* Check if file added */
438 status
.status
= git_wc_status_added
;
439 if ((*indexptr
)[pos
].m_Flags
& GIT_IDXENTRY_STAGEMASK
)
440 status
.status
= git_wc_status_conflicted
;
441 callback(CombinePath(gitdir
, onepath
), &status
, bIsDir
, fileentry
.m_LastModified
, pData
);
447 status
.status
= git_wc_status_normal
;
448 callback(CombinePath(gitdir
, onepath
), &status
, bIsDir
, fileentry
.m_LastModified
, pData
);
452 auto& indexentry
= (*indexptr
)[pos
];
453 if (indexentry
.m_Flags
& GIT_IDXENTRY_STAGEMASK
)
455 status
.status
= git_wc_status_conflicted
;
456 callback(CombinePath(gitdir
, onepath
), &status
, false, fileentry
.m_LastModified
, pData
);
459 if ((*indexptr
).GetFileStatus(repository
, gitdir
, indexentry
, status
, fileentry
.m_LastModified
, fileentry
.m_Size
, fileentry
.m_bSymlink
))
461 if (status
.status
== git_wc_status_normal
&& (*treeptr
)[posintree
].m_Hash
!= indexentry
.m_IndexHash
)
462 status
= { git_wc_status_modified
, false, false };
463 callback(CombinePath(gitdir
, onepath
), &status
, false, fileentry
.m_LastModified
, pData
);
467 repository
.Free(); // explicitly free the handle here in order to keep an open repository as short as possible
469 /* Check deleted file in system */
470 size_t start
= 0, end
= 0;
471 std::set
<CString
> alreadyReported
;
472 if (GetRangeInSortVector(*indexptr
, path
, path
.GetLength(), indexptr
->IsIgnoreCase(), &start
, &end
, indexpos
) == 0)
474 *dirstatus
= git_wc_status_normal
; // here we know that this folder has versioned entries
476 for (auto it
= indexptr
->cbegin() + start
, itlast
= indexptr
->cbegin() + end
; it
<= itlast
; ++it
)
479 int commonPrefixLength
= path
.GetLength();
480 int index
= entry
.m_FileName
.Find(L
'/', commonPrefixLength
);
482 index
= entry
.m_FileName
.GetLength();
484 ++index
; // include slash at the end for subfolders, so that we do not match files by mistake
486 CString filename
= entry
.m_FileName
.Mid(commonPrefixLength
, index
- commonPrefixLength
);
487 if (oldstring
!= filename
)
489 oldstring
= filename
;
490 int length
= filename
.GetLength();
491 bool isDir
= filename
[length
- 1] == L
'/';
492 if (SearchInSortVector(filelist
, filename
, isDir
? length
: -1, indexptr
->IsIgnoreCase()) == NPOS
) // do full match for filenames and only prefix-match ending with "/" for folders
494 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
495 if ((entry
.m_FlagsExtended
& GIT_IDXENTRY_SKIP_WORKTREE
) != 0)
497 status
.skipWorktree
= true;
498 status
.status
= git_wc_status_normal
;
499 oldstring
.Empty(); // without this a deleted folder which has two versioned files and only the first is skipwoktree flagged gets reported as normal
500 if (alreadyReported
.find(filename
) != alreadyReported
.cend())
503 alreadyReported
.insert(filename
);
504 callback(CombinePath(gitdir
, subpath
, filename
), &status
, isDir
, 0, pData
);
507 // folder might be replaced by symlink
508 filename
.TrimRight(L
'/');
509 auto filepos
= SearchInSortVector(filelist
, filename
, -1, indexptr
->IsIgnoreCase());
510 if (filepos
== NPOS
|| !filelist
[filepos
].m_bSymlink
)
512 status
.status
= git_wc_status_deleted
;
513 callback(CombinePath(gitdir
, subpath
, filename
), &status
, false, 0, pData
);
521 if (GetRangeInSortVector(*treeptr
, path
, path
.GetLength(), indexptr
->IsIgnoreCase(), &start
, &end
, treepos
) == 0)
523 *dirstatus
= git_wc_status_normal
; // here we know that this folder has versioned entries
525 for (auto it
= treeptr
->cbegin() + start
, itlast
= treeptr
->cbegin() + end
; it
<= itlast
; ++it
)
528 int commonPrefixLength
= path
.GetLength();
529 int index
= entry
.m_FileName
.Find(L
'/', commonPrefixLength
);
531 index
= entry
.m_FileName
.GetLength();
533 ++index
; // include slash at the end for subfolders, so that we do not match files by mistake
535 CString filename
= entry
.m_FileName
.Mid(commonPrefixLength
, index
- commonPrefixLength
);
536 if (oldstring
!= filename
&& alreadyReported
.find(filename
) == alreadyReported
.cend())
538 oldstring
= filename
;
539 int length
= filename
.GetLength();
540 bool isDir
= filename
[length
- 1] == L
'/';
541 if (SearchInSortVector(filelist
, filename
, isDir
? length
: -1, indexptr
->IsIgnoreCase()) == NPOS
) // do full match for filenames and only prefix-match ending with "/" for folders
543 git_wc_status2_t status
= { (!isDir
|| IsDirectSubmodule(entry
.m_FileName
, commonPrefixLength
)) ? git_wc_status_deleted
: git_wc_status_modified
, false, false };
544 callback(CombinePath(gitdir
, subpath
, filename
), &status
, isDir
, 0, pData
);
554 int GitStatus::GetDirStatus(const CString
& gitdir
, const CString
& subpath
, git_wc_status_kind
* status
, BOOL IsFul
, BOOL IsRecursive
, BOOL IsIgnore
)
558 CString path
= subpath
;
560 path
.Replace(L
'\\', L
'/');
561 if (!path
.IsEmpty() && path
[path
.GetLength() - 1] != L
'/')
562 path
+= L
'/'; //Add trail / to show it is directory, not file name.
564 g_IndexFileMap
.CheckAndUpdate(gitdir
);
566 CGitRepoLists sharedRepoLists
;
567 sharedRepoLists
.pIndex
= g_IndexFileMap
.SafeGet(gitdir
);
570 if (!sharedRepoLists
.pIndex
)
572 *status
= git_wc_status_none
;
576 size_t pos
= SearchInSortVector(*sharedRepoLists
.pIndex
, path
, path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase());
578 // Not In Version Contorl
583 // WC root is at least normal if there are no files added/deleted
584 if (subpath
.IsEmpty())
586 *status
= git_wc_status_normal
;
589 *status
= git_wc_status_unversioned
;
593 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, sharedRepoLists
.pIndex
->IsIgnoreCase());
595 sharedRepoLists
.pTree
= g_HeadFileMap
.SafeGet(gitdir
);
597 if (!sharedRepoLists
.pTree
)
599 *status
= git_wc_status_none
;
603 // check whether there files in head with are not in index
604 pos
= SearchInSortVector(*sharedRepoLists
.pTree
, path
, path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase());
607 *status
= git_wc_status_deleted
;
611 // WC root is at least normal if there are no files added/deleted
614 *status
= git_wc_status_normal
;
619 g_IgnoreList
.CheckAndUpdateIgnoreFiles(gitdir
, path
, true);
620 if (g_IgnoreList
.IsIgnore(path
, gitdir
, true))
621 *status
= git_wc_status_ignored
;
623 *status
= git_wc_status_unversioned
;
628 // In version control
629 *status
= git_wc_status_normal
;
634 GetRangeInSortVector(*sharedRepoLists
.pIndex
, path
,path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase(), &start
, &end
, pos
);
637 for (auto it
= sharedRepoLists
.pIndex
->cbegin() + start
, itlast
= sharedRepoLists
.pIndex
->cbegin() + end
; sharedRepoLists
.pIndex
->m_bHasConflicts
&& it
<= itlast
; ++it
)
639 if (((*it
).m_Flags
& GIT_IDXENTRY_STAGEMASK
) != 0)
641 *status
= git_wc_status_conflicted
;
642 // When status == git_wc_status_conflicted, we don't need to check each file status
643 // because git_wc_status_conflicted is the highest.
650 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, sharedRepoLists
.pIndex
->IsIgnoreCase());
654 // Check if new init repository
655 sharedRepoLists
.pTree
= g_HeadFileMap
.SafeGet(gitdir
);
657 if (!sharedRepoLists
.pTree
)
659 *status
= git_wc_status_none
;
664 for (auto it
= sharedRepoLists
.pIndex
->cbegin() + start
, itlast
= sharedRepoLists
.pIndex
->cbegin() + end
; it
<= itlast
; ++it
)
666 auto& indexentry
= *it
;
667 pos
= SearchInSortVector(*sharedRepoLists
.pTree
, indexentry
.m_FileName
, -1, sharedRepoLists
.pIndex
->IsIgnoreCase());
671 *status
= GetMoreImportant(git_wc_status_added
, *status
); // added file found
672 AdjustFolderStatus(*status
);
673 if (GetMoreImportant(*status
, git_wc_status_modified
) == *status
) // the only potential higher status which me might get in this loop
678 if ((*sharedRepoLists
.pTree
)[pos
].m_Hash
!= indexentry
.m_IndexHash
)
680 *status
= GetMoreImportant(git_wc_status_modified
, *status
); // modified file found
686 if (*status
== git_wc_status_normal
)
688 pos
= SearchInSortVector(*sharedRepoLists
.pTree
, path
, path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase());
690 *status
= GetMoreImportant(git_wc_status_added
, *status
); // added file found
694 // we know that pos exists in treeptr
695 GetRangeInSortVector(*sharedRepoLists
.pTree
, path
, path
.GetLength(), sharedRepoLists
.pIndex
->IsIgnoreCase(), &hstart
, &hend
, pos
);
696 for (auto hit
= sharedRepoLists
.pTree
->cbegin() + hstart
, lastElement
= sharedRepoLists
.pTree
->cbegin() + hend
; hit
<= lastElement
; ++hit
)
698 if (SearchInSortVector(*sharedRepoLists
.pIndex
, (*hit
).m_FileName
, -1, sharedRepoLists
.pIndex
->IsIgnoreCase()) == NPOS
)
700 *status
= GetMoreImportant(git_wc_status_deleted
, *status
); // deleted file found
710 auto mostImportantPossibleFolderStatus
= GetMoreImportant(git_wc_status_added
, GetMoreImportant(git_wc_status_modified
, git_wc_status_deleted
));
711 AdjustFolderStatus(mostImportantPossibleFolderStatus
);
712 // we can skip here when we already have the highest possible status
713 if (mostImportantPossibleFolderStatus
== *status
)
716 for (auto it
= sharedRepoLists
.pIndex
->cbegin() + start
, itlast
= sharedRepoLists
.pIndex
->cbegin() + end
; it
<= itlast
; ++it
)
718 auto& indexentry
= *it
;
719 // skip child directory, but handle submodules
720 if (!IsRecursive
&& indexentry
.m_FileName
.Find(L
'/', path
.GetLength()) > 0 && !IsDirectSubmodule(indexentry
.m_FileName
, path
.GetLength()))
723 git_wc_status2_t filestatus
= { git_wc_status_none
, false, false };
724 GetFileStatus_int(gitdir
, sharedRepoLists
, indexentry
.m_FileName
, filestatus
, IsFul
, IsIgnore
, false);
725 switch (filestatus
.status
)
727 case git_wc_status_added
:
728 case git_wc_status_modified
:
729 case git_wc_status_deleted
:
730 //case git_wc_status_conflicted: cannot happen, we exit as soon we found a conflict in subpath
731 *status
= GetMoreImportant(filestatus
.status
, *status
);
732 AdjustFolderStatus(*status
);
733 if (mostImportantPossibleFolderStatus
== *status
)
743 bool GitStatus::IsExistIndexLockFile(CString sDirName
)
745 if (!PathIsDirectory(sDirName
))
747 int x
= sDirName
.ReverseFind(L
'\\');
751 sDirName
.Truncate(x
);
756 if (PathFileExists(CombinePath(sDirName
, L
".git")))
758 if (PathFileExists(g_AdminDirMap
.GetWorktreeAdminDirConcat(sDirName
, L
"index.lock")))
764 int x
= sDirName
.ReverseFind(L
'\\');
768 sDirName
.Truncate(x
);
773 bool GitStatus::ReleasePath(const CString
&gitdir
)
775 g_IndexFileMap
.SafeClear(gitdir
);
776 g_HeadFileMap
.SafeClear(gitdir
);
780 bool GitStatus::ReleasePathsRecursively(const CString
&rootpath
)
782 g_IndexFileMap
.SafeClearRecursively(rootpath
);
783 g_HeadFileMap
.SafeClearRecursively(rootpath
);
787 void GitStatus::AdjustFolderStatus(git_wc_status_kind
& status
)
789 if (status
== git_wc_status_deleted
|| status
== git_wc_status_added
)
790 status
= git_wc_status_modified
;