RebaseDlg: Prevent entering an invalid state when text transformations are performed...
[TortoiseGit.git] / src / Git / GitStatus.cpp
blob6dfcc19a1e47e5c00bed51811b89fe9be1a27e77
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.
20 #include "stdafx.h"
21 #include "registry.h"
22 #include "../TortoiseShell/resource.h"
23 #include "GitStatus.h"
24 #include "UnicodeUtils.h"
25 #include "gitindex.h"
26 #include "ShellCache.h"
27 #include "SysInfo.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()
39 // static method
40 #ifndef TGITCACHE
41 int GitStatus::GetAllStatus(const CTGitPath& path, bool bIsRecursive, git_wc_status2_t& status)
43 BOOL isDir;
44 CString sProjectRoot;
46 isDir = path.IsDirectory();
47 if (!path.HasAdminDir(&sProjectRoot))
48 return git_wc_status_none;
50 CString sSubPath;
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());
56 else
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);
62 if(isDir)
64 auto err = GetDirStatus(sProjectRoot, sSubPath, &status.status, isfull, bIsRecursive, isfull);
65 AdjustFolderStatus(status.status);
66 return err;
69 return GetFileStatus(sProjectRoot, sSubPath, status, isfull, isfull);
71 #endif
73 // static method
74 git_wc_status_kind GitStatus::GetMoreImportant(git_wc_status_kind status1, git_wc_status_kind status2)
76 if (GetStatusRanking(status1) >= GetStatusRanking(status2))
77 return status1;
78 return status2;
80 // static private method
81 int GitStatus::GetStatusRanking(git_wc_status_kind status)
83 switch (status)
85 case git_wc_status_none:
86 return 0;
87 case git_wc_status_unversioned:
88 return 1;
89 case git_wc_status_ignored:
90 return 2;
91 case git_wc_status_normal:
92 case git_wc_status_added:
93 return 6;
94 case git_wc_status_deleted:
95 return 8;
96 case git_wc_status_modified:
97 return 10;
98 case git_wc_status_conflicted:
99 return 12;
101 return 0;
104 #ifndef TGITCACHE
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) )
113 return;
115 const bool isfull = (static_cast<DWORD>(CRegStdDWORD(L"Software\\TortoiseGit\\CacheType", GetSystemMetrics(SM_REMOTESESSION)) ? ShellCache::dll : ShellCache::exe) == ShellCache::dllFull);
117 int err = 0;
119 LPCWSTR lpszSubPath = nullptr;
120 CString sSubPath;
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'\\')
128 ++lpszSubPath;
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);
140 else
141 err = GetFileStatus(sProjectRoot, lpszSubPath, m_status, isfull, !noignore);
143 // Error present if function is not under version control
144 if (err)
146 status = nullptr;
147 return;
150 status = &m_status;
152 #endif
154 using CAutoLocker = CComCritSecLock<CComCriticalSection>;
156 struct CGitRepoLists
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);
167 CGitHash hash;
168 if (repolists.pIndex->GetFileStatus(gitdir, path, status, &hash))
170 // an error occurred in GetFileStatus
171 status.status = git_wc_status_none;
172 return -1;
175 if (status.status == git_wc_status_conflicted)
176 return 0;
178 if (status.status == git_wc_status_unversioned)
180 if (IsFull)
182 if (!repolists.pTree)
184 if (update)
185 repolists.pTree = g_HeadFileMap.CheckHeadAndUpdate(gitdir, repolists.pIndex->IsIgnoreCase());
186 else
187 repolists.pTree = g_HeadFileMap.SafeGet(gitdir);
189 // broken HEAD
190 if (!repolists.pTree)
192 status.status = git_wc_status_none;
193 return -1;
196 // deleted only in index item?
197 if (SearchInSortVector(*repolists.pTree, path, -1, repolists.pIndex->IsIgnoreCase()) != NPOS)
199 status.status = git_wc_status_deleted;
200 return 0;
204 if (!IsIgnore)
206 status.status = git_wc_status_unversioned;
207 return 0;
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;
214 return 0;
217 if ((status.status == git_wc_status_normal || status.status == git_wc_status_modified) && IsFull)
219 if (!repolists.pTree)
221 if (update)
222 repolists.pTree = g_HeadFileMap.CheckHeadAndUpdate(gitdir, repolists.pIndex->IsIgnoreCase());
223 else
224 repolists.pTree = g_HeadFileMap.SafeGet(gitdir);
226 // broken HEAD
227 if (!repolists.pTree)
229 status.status = git_wc_status_none;
230 return -1;
233 //add item
234 size_t start = SearchInSortVector(*repolists.pTree, path, -1, repolists.pIndex->IsIgnoreCase());
235 if (start == NPOS)
237 status.status = git_wc_status_added;
238 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": File miss in head tree %s", static_cast<LPCWSTR>(path));
239 return 0;
242 // staged and not commit
243 if ((*repolists.pTree)[start].m_Hash != hash)
245 status = { git_wc_status_modified, false, false };
246 return 0;
250 return 0;
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;
260 if (update)
261 sharedRepoLists.pIndex = g_IndexFileMap.CheckAndUpdate(gitdir);
262 else
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;
268 return -1;
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'/'))
278 return false;
280 auto ptr = indexPath.GetString() + prefix;
281 int folderdepth = 0;
282 while (*ptr)
284 if (*ptr == L'/')
285 ++folderdepth;
286 ++ptr;
289 return folderdepth == 1;
292 #ifdef TGITCACHE
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);
297 if (!handle)
298 return -1;
301 if (wcscmp(data.cFileName, L".git") == 0)
303 isRepoRoot = true;
304 continue;
307 if (wcscmp(data.cFileName, L".") == 0)
308 continue;
310 if (wcscmp(data.cFileName, L"..") == 0)
311 continue;
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);
327 return 0;
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
340 if (!indexptr)
341 return -1;
343 SHARED_TREE_PTR treeptr = g_HeadFileMap.CheckHeadAndUpdate(gitdir, indexptr->IsIgnoreCase());
344 // there was an error loading the HEAD commit/tree
345 if (!treeptr)
346 return -1;
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;
359 if (isRepoRoot)
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;
378 bool bIsDir = false;
379 if (!onepath.IsEmpty() && onepath[onepath.GetLength() - 1] == L'/')
380 bIsDir = true;
382 int matchLength = -1;
383 if (bIsDir)
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;
394 else
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);
426 else
428 if (bIsDir)
430 status.status = git_wc_status_normal;
431 callback(CombinePath(gitdir, onepath), &status, bIsDir, fileentry.m_LastModified, pData);
433 else
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);
440 continue;
442 if ((*indexptr).GetFileStatus(repository, gitdir, indexentry, status, fileentry.m_LastModified, fileentry.m_Size, fileentry.m_bSymlink))
443 return -1;
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);
449 }/*End of For*/
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
458 CString oldstring;
459 for (auto it = indexptr->cbegin() + start, itlast = indexptr->cbegin() + end; it <= itlast; ++it)
461 auto& entry = *it;
462 const int commonPrefixLength = path.GetLength();
463 int index = entry.m_FileName.Find(L'/', commonPrefixLength);
464 if (index < 0)
465 index = entry.m_FileName.GetLength();
466 else
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())
484 continue;
486 alreadyReported.insert(filename);
487 callback(CombinePath(gitdir, subpath, filename), &status, isDir, 0, pData);
488 if (isDir)
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)
494 continue;
495 status.status = git_wc_status_deleted;
496 callback(CombinePath(gitdir, subpath, filename), &status, false, 0, pData);
503 start = end = 0;
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
507 CString oldstring;
508 for (auto it = treeptr->cbegin() + start, itlast = treeptr->cbegin() + end; it <= itlast; ++it)
510 auto& entry = *it;
511 const int commonPrefixLength = path.GetLength();
512 int index = entry.m_FileName.Find(L'/', commonPrefixLength);
513 if (index < 0)
514 index = entry.m_FileName.GetLength();
515 else
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);
532 return 0;
534 #endif
536 #ifndef TGITCACHE
537 int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_wc_status_kind* status, BOOL IsFul, BOOL IsRecursive, BOOL IsIgnore)
539 ATLASSERT(status);
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);
550 // broken index
551 if (!sharedRepoLists.pIndex)
553 *status = git_wc_status_none;
554 return -1;
557 size_t pos = SearchInSortVector(*sharedRepoLists.pIndex, path, path.GetLength(), sharedRepoLists.pIndex->IsIgnoreCase());
559 // Not In Version Contorl
560 if (pos == NPOS)
562 if (!IsIgnore)
564 // WC root is at least normal if there are no files added/deleted
565 if (subpath.IsEmpty())
567 *status = git_wc_status_normal;
568 return 0;
570 *status = git_wc_status_unversioned;
571 return 0;
574 sharedRepoLists.pTree = g_HeadFileMap.CheckHeadAndUpdate(gitdir, sharedRepoLists.pIndex->IsIgnoreCase());
575 // broken HEAD
576 if (!sharedRepoLists.pTree)
578 *status = git_wc_status_none;
579 return -1;
582 // check whether there files in head with are not in index
583 pos = SearchInSortVector(*sharedRepoLists.pTree, path, path.GetLength(), sharedRepoLists.pIndex->IsIgnoreCase());
584 if (pos != NPOS)
586 *status = git_wc_status_deleted;
587 return 0;
590 // WC root is at least normal if there are no files added/deleted
591 if (path.IsEmpty())
593 *status = git_wc_status_normal;
594 return 0;
597 // Check ignore
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;
601 else
602 *status = git_wc_status_unversioned;
604 return 0;
607 // In version control
608 *status = git_wc_status_normal;
610 size_t start = 0;
611 size_t end = 0;
613 GetRangeInSortVector(*sharedRepoLists.pIndex, path,path.GetLength(), sharedRepoLists.pIndex->IsIgnoreCase(), &start, &end, pos);
615 // Check Conflict;
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.
623 return 0;
627 if (IsFul)
629 // Check Add
631 // Check if new init repository
632 sharedRepoLists.pTree = g_HeadFileMap.CheckHeadAndUpdate(gitdir, sharedRepoLists.pIndex->IsIgnoreCase());
633 // broken HEAD
634 if (!sharedRepoLists.pTree)
636 *status = git_wc_status_none;
637 return -1;
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());
646 if (pos == NPOS)
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
651 break;
652 continue;
655 if ((*sharedRepoLists.pTree)[pos].m_Hash != indexentry.m_IndexHash)
657 *status = GetMoreImportant(git_wc_status_modified, *status); // modified file found
658 break;
662 // Check Delete
663 if (*status == git_wc_status_normal)
665 pos = SearchInSortVector(*sharedRepoLists.pTree, path, path.GetLength(), sharedRepoLists.pIndex->IsIgnoreCase());
666 if (pos == NPOS)
667 *status = GetMoreImportant(git_wc_status_added, *status); // added file found
668 else
670 size_t hstart, hend;
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
678 break;
684 } /* End lock*/
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)
691 return 0;
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()))
698 continue;
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)
711 return 0;
715 return 0;
717 #endif
719 #ifdef TGITCACHE
720 bool GitStatus::IsExistIndexLockFile(CString sDirName)
722 if (!PathIsDirectory(sDirName))
724 const int x = sDirName.ReverseFind(L'\\');
725 if (x < 2)
726 return false;
728 sDirName.Truncate(x);
731 for (;;)
733 if (PathFileExists(CombinePath(sDirName, L".git")))
735 if (PathFileExists(g_AdminDirMap.GetWorktreeAdminDirConcat(sDirName, L"index.lock")))
736 return true;
738 return false;
741 const int x = sDirName.ReverseFind(L'\\');
742 if (x < 2)
743 return false;
745 sDirName.Truncate(x);
748 #endif
750 bool GitStatus::ReleasePath(const CString &gitdir)
752 g_IndexFileMap.SafeClear(gitdir);
753 g_HeadFileMap.SafeClear(gitdir);
754 return true;
757 bool GitStatus::ReleasePathsRecursively(const CString &rootpath)
759 g_IndexFileMap.SafeClearRecursively(rootpath);
760 g_HeadFileMap.SafeClearRecursively(rootpath);
761 return true;
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;