Fixed issue #3108: Versioned file in ignored folder causes wrong overlays
[TortoiseGit.git] / src / Git / GitStatus.cpp
blob0aba892eb23845ebc842683dfb3021d19d7ad150
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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 "Git.h"
26 #include "gitindex.h"
27 #include "ShellCache.h"
28 #include "SysInfo.h"
29 #include "SmartHandle.h"
31 extern CGitAdminDirMap g_AdminDirMap;
32 CGitIndexFileMap g_IndexFileMap;
33 CGitHeadFileMap g_HeadFileMap;
34 CGitIgnoreList g_IgnoreList;
36 GitStatus::GitStatus()
37 : status(nullptr)
39 m_status.assumeValid = m_status.skipWorktree = false;
40 m_status.status = git_wc_status_none;
43 // static method
44 #ifndef TGITCACHE
45 int GitStatus::GetAllStatus(const CTGitPath& path, bool bIsRecursive, git_wc_status2_t& status)
47 BOOL isDir;
48 CString sProjectRoot;
50 isDir = path.IsDirectory();
51 if (!path.HasAdminDir(&sProjectRoot))
52 return git_wc_status_none;
54 CString sSubPath;
55 CString s = path.GetWinPathString();
56 if (s.GetLength() > sProjectRoot.GetLength())
58 if (sProjectRoot.GetLength() == 3 && sProjectRoot[1] == L':')
59 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength());
60 else
61 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() - 1/*otherwise it gets initial slash*/);
64 bool isfull = ((DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\CacheType",
65 GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) == ShellCache::dllFull);
67 if(isDir)
69 auto err = GetDirStatus(sProjectRoot, sSubPath, &status.status, isfull, bIsRecursive, isfull);
70 AdjustFolderStatus(status.status);
71 return err;
74 return GetFileStatus(sProjectRoot, sSubPath, status, isfull, isfull);
76 #endif
78 // static method
79 git_wc_status_kind GitStatus::GetMoreImportant(git_wc_status_kind status1, git_wc_status_kind status2)
81 if (GetStatusRanking(status1) >= GetStatusRanking(status2))
82 return status1;
83 return status2;
85 // static private method
86 int GitStatus::GetStatusRanking(git_wc_status_kind status)
88 switch (status)
90 case git_wc_status_none:
91 return 0;
92 case git_wc_status_unversioned:
93 return 1;
94 case git_wc_status_ignored:
95 return 2;
96 case git_wc_status_normal:
97 case git_wc_status_added:
98 return 6;
99 case git_wc_status_deleted:
100 return 8;
101 case git_wc_status_modified:
102 return 10;
103 case git_wc_status_conflicted:
104 return 12;
106 return 0;
109 #ifndef TGITCACHE
110 void GitStatus::GetStatus(const CTGitPath& path, bool /*update*/ /* = false */, bool noignore /* = false */, bool /*noexternals*/ /* = false */)
112 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
113 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
114 // after the call again
116 CString sProjectRoot;
117 if ( !path.HasAdminDir(&sProjectRoot) )
118 return;
120 bool isfull = ((DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\CacheType",
121 GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) == ShellCache::dllFull);
123 int err = 0;
125 LPCTSTR lpszSubPath = nullptr;
126 CString sSubPath;
127 CString s = path.GetWinPathString();
128 if (s.GetLength() > sProjectRoot.GetLength())
130 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength());
131 lpszSubPath = sSubPath;
132 // skip initial slash if necessary
133 if (*lpszSubPath == L'\\')
134 ++lpszSubPath;
137 m_status.status = git_wc_status_none;
138 m_status.assumeValid = false;
139 m_status.skipWorktree = false;
141 if (path.IsDirectory())
143 err = GetDirStatus(sProjectRoot, lpszSubPath, &m_status.status, isfull, false, !noignore);
144 AdjustFolderStatus(m_status.status);
146 else
147 err = GetFileStatus(sProjectRoot, lpszSubPath, m_status, isfull, !noignore);
149 // Error present if function is not under version control
150 if (err)
152 status = nullptr;
153 return;
156 status = &m_status;
158 #endif
160 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
162 int GitStatus::GetFileStatus(const CString& gitdir, CString path, git_wc_status2_t& status, BOOL IsFull, BOOL IsIgnore, bool update)
164 ATLASSERT(!status.assumeValid && !status.skipWorktree);
166 path.Replace(L'\\', L'/');
168 if (update)
169 g_IndexFileMap.CheckAndUpdate(gitdir);
170 auto pIndex = g_IndexFileMap.SafeGet(gitdir);
171 CGitHash hash;
172 if (!pIndex || pIndex->GetFileStatus(gitdir, path, status, &hash))
174 // git working tree has broken index or an error occurred in GetFileStatus
175 status.status = git_wc_status_none;
176 return -1;
179 if (status.status == git_wc_status_conflicted)
180 return 0;
182 if (status.status == git_wc_status_unversioned)
184 if (IsFull)
186 if (update)
187 g_HeadFileMap.CheckHeadAndUpdate(gitdir, pIndex->IsIgnoreCase());
189 // Check Head Tree Hash
190 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
191 // broken HEAD
192 if (!treeptr)
194 status.status = git_wc_status_none;
195 return -1;
198 // deleted only in index item?
199 if (SearchInSortVector(*treeptr, path, -1, pIndex->IsIgnoreCase()) != NPOS)
201 status.status = git_wc_status_deleted;
202 return 0;
206 if (!IsIgnore)
208 status.status = git_wc_status_unversioned;
209 return 0;
212 g_IgnoreList.CheckAndUpdateIgnoreFiles(gitdir, path, false);
213 if (g_IgnoreList.IsIgnore(path, gitdir, false))
214 status.status = git_wc_status_ignored;
216 return 0;
219 if ((status.status == git_wc_status_normal || status.status == git_wc_status_modified) && IsFull)
221 if (update)
222 g_HeadFileMap.CheckHeadAndUpdate(gitdir, pIndex->IsIgnoreCase());
224 // Check Head Tree Hash
225 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
226 // broken HEAD
227 if (!treeptr)
229 status.status = git_wc_status_none;
230 return -1;
233 //add item
234 size_t start = SearchInSortVector(*treeptr, path, -1, 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", (LPCTSTR)path);
239 return 0;
242 // staged and not commit
243 if ((*treeptr)[start].m_Hash != hash)
245 status = { git_wc_status_modified, false, false };
246 return 0;
250 return 0;
253 // checks whether indexPath is a direct submodule and not one in a subfolder
254 static bool IsDirectSubmodule(const CString& indexPath, int prefix)
256 if (!CStringUtils::EndsWith(indexPath, L'/'))
257 return false;
259 auto ptr = indexPath.GetString() + prefix;
260 int folderdepth = 0;
261 while (*ptr)
263 if (*ptr == L'/')
264 ++folderdepth;
265 ++ptr;
268 return folderdepth == 1;
271 #ifdef TGITCACHE
272 bool GitStatus::CheckAndUpdateIgnoreFiles(const CString& gitdir, const CString& subpaths, bool isDir)
274 return g_IgnoreList.CheckAndUpdateIgnoreFiles(gitdir, subpaths, isDir);
277 bool GitStatus::IsIgnored(const CString& gitdir, const CString& path, bool isDir)
279 return g_IgnoreList.IsIgnore(path, gitdir, isDir);
282 int GitStatus::GetFileList(const CString& path, std::vector<CGitFileName>& list, bool& isRepoRoot, bool ignoreCase)
284 WIN32_FIND_DATA data;
285 CAutoFindFile handle = ::FindFirstFileEx(CombinePath(path, L"*.*"), FindExInfoBasic, &data, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH);
286 if (!handle)
287 return -1;
290 if (wcscmp(data.cFileName, L".git") == 0)
292 isRepoRoot = true;
293 continue;
296 if (wcscmp(data.cFileName, L".") == 0)
297 continue;
299 if (wcscmp(data.cFileName, L"..") == 0)
300 continue;
302 CGitFileName filename(data.cFileName, ((__int64)data.nFileSizeHigh << 32) + data.nFileSizeLow, ((__int64)data.ftLastWriteTime.dwHighDateTime << 32) + data.ftLastWriteTime.dwLowDateTime);
303 if ((data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && !CPathUtils::ReadLink(CombinePath(path, filename.m_FileName)))
304 filename.m_bSymlink = true;
305 else if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
306 filename.m_FileName += L'/';
308 list.push_back(filename);
310 }while(::FindNextFile(handle, &data));
312 handle.CloseHandle(); // manually close handle here in order to keep handles open as short as possible
314 DoSortFilenametSortVector(list, ignoreCase);
316 return 0;
319 int GitStatus::EnumDirStatus(const CString& gitdir, const CString& subpath, git_wc_status_kind* dirstatus, FILL_STATUS_CALLBACK callback, void* pData)
321 CString path = subpath;
323 path.Replace(L'\\', L'/');
324 if (!path.IsEmpty() && path[path.GetLength() - 1] != L'/')
325 path += L'/'; // Add trail / to show it is directory, not file name.
327 g_IndexFileMap.CheckAndUpdate(gitdir);
329 SHARED_INDEX_PTR indexptr = g_IndexFileMap.SafeGet(gitdir);
330 // there was an error loading the index
331 if (!indexptr)
332 return -1;
334 g_HeadFileMap.CheckHeadAndUpdate(gitdir, indexptr->IsIgnoreCase());
336 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
337 // there was an error loading the HEAD commit/tree
338 if (!treeptr)
339 return -1;
341 size_t indexpos = SearchInSortVector(*indexptr, path, path.GetLength(), indexptr->IsIgnoreCase()); // match path prefix, (sub)folders end with slash
342 size_t treepos = SearchInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase()); // match path prefix, (sub)folders end with slash
344 std::vector<CGitFileName> filelist;
345 int folderignoredchecked = false;
346 bool isRepoRoot = false;
347 GetFileList(CombinePath(gitdir, subpath), filelist, isRepoRoot, indexptr->IsIgnoreCase());
348 *dirstatus = git_wc_status_unknown;
349 if (isRepoRoot)
350 *dirstatus = git_wc_status_normal;
351 else if (indexpos == NPOS && treepos == NPOS)
353 // if folder does not contain any versioned items, it might be ignored
354 g_IgnoreList.CheckAndUpdateIgnoreFiles(gitdir, subpath, true);
355 if (g_IgnoreList.IsIgnore(subpath, gitdir, true))
356 *dirstatus = git_wc_status_ignored;
357 folderignoredchecked = true;
360 CAutoRepository repository;
361 for (auto it = filelist.cbegin(), itend = filelist.cend(); it != itend; ++it)
363 auto& fileentry = *it;
365 CString onepath(path);
366 onepath += fileentry.m_FileName;
368 bool bIsDir = false;
369 if (!onepath.IsEmpty() && onepath[onepath.GetLength() - 1] == L'/')
370 bIsDir = true;
372 int matchLength = -1;
373 if (bIsDir)
374 matchLength = onepath.GetLength();
375 size_t pos = SearchInSortVector(*indexptr, onepath, matchLength, indexptr->IsIgnoreCase());
376 size_t posintree = SearchInSortVector(*treeptr, onepath, matchLength, indexptr->IsIgnoreCase());
378 git_wc_status2_t status = { git_wc_status_none, false, false };
380 if (pos == NPOS && posintree == NPOS)
382 if (*dirstatus == git_wc_status_ignored)
383 status.status = git_wc_status_ignored;
384 else
386 status.status = git_wc_status_unversioned;
388 g_IgnoreList.CheckAndUpdateIgnoreFiles(gitdir, onepath, bIsDir);
389 // whole folder might be ignored, check this once if we are not the root folder in order to speed up all following ignored files
390 if (!folderignoredchecked && *dirstatus != git_wc_status_normal)
392 if (g_IgnoreList.IsIgnore(subpath, gitdir, true))
394 *dirstatus = git_wc_status_ignored;
395 status.status = git_wc_status_ignored;
397 folderignoredchecked = true;
399 if (status.status != git_wc_status_ignored && g_IgnoreList.IsIgnore(onepath, gitdir, bIsDir))
400 status.status = git_wc_status_ignored;
402 callback(CombinePath(gitdir, onepath), &status, bIsDir, fileentry.m_LastModified, pData);
404 else if (pos == NPOS && posintree != NPOS) /* check if file delete in index */
406 status.status = git_wc_status_deleted;
407 callback(CombinePath(gitdir, onepath), &status, bIsDir, fileentry.m_LastModified, pData);
409 else if (pos != NPOS && posintree == NPOS) /* Check if file added */
411 status.status = git_wc_status_added;
412 if ((*indexptr)[pos].m_Flags & GIT_IDXENTRY_STAGEMASK)
413 status.status = git_wc_status_conflicted;
414 callback(CombinePath(gitdir, onepath), &status, bIsDir, fileentry.m_LastModified, pData);
416 else
418 if (bIsDir)
420 status.status = git_wc_status_normal;
421 callback(CombinePath(gitdir, onepath), &status, bIsDir, fileentry.m_LastModified, pData);
423 else
425 auto& indexentry = (*indexptr)[pos];
426 if (indexentry.m_Flags & GIT_IDXENTRY_STAGEMASK)
428 status.status = git_wc_status_conflicted;
429 callback(CombinePath(gitdir, onepath), &status, false, fileentry.m_LastModified, pData);
430 continue;
432 if ((*indexptr).GetFileStatus(repository, gitdir, indexentry, status, fileentry.m_LastModified, fileentry.m_Size, fileentry.m_bSymlink))
433 return -1;
434 if (status.status == git_wc_status_normal && (*treeptr)[posintree].m_Hash != indexentry.m_IndexHash)
435 status = { git_wc_status_modified, false, false };
436 callback(CombinePath(gitdir, onepath), &status, false, fileentry.m_LastModified, pData);
439 }/*End of For*/
440 repository.Free(); // explicitly free the handle here in order to keep an open repository as short as possible
442 /* Check deleted file in system */
443 size_t start = 0, end = 0;
444 std::set<CString> alreadyReported;
445 if (GetRangeInSortVector(*indexptr, path, path.GetLength(), indexptr->IsIgnoreCase(), &start, &end, indexpos) == 0)
447 *dirstatus = git_wc_status_normal; // here we know that this folder has versioned entries
448 CString oldstring;
449 for (auto it = indexptr->cbegin() + start, itlast = indexptr->cbegin() + end; it <= itlast; ++it)
451 auto& entry = *it;
452 int commonPrefixLength = path.GetLength();
453 int index = entry.m_FileName.Find(L'/', commonPrefixLength);
454 if (index < 0)
455 index = entry.m_FileName.GetLength();
456 else
457 ++index; // include slash at the end for subfolders, so that we do not match files by mistake
459 CString filename = entry.m_FileName.Mid(commonPrefixLength, index - commonPrefixLength);
460 if (oldstring != filename)
462 oldstring = filename;
463 int length = filename.GetLength();
464 bool isDir = filename[length - 1] == L'/';
465 if (SearchInSortVector(filelist, filename, isDir ? length : -1, indexptr->IsIgnoreCase()) == NPOS) // do full match for filenames and only prefix-match ending with "/" for folders
467 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
468 if ((entry.m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
470 status.skipWorktree = true;
471 status.status = git_wc_status_normal;
472 oldstring.Empty(); // without this a deleted folder which has two versioned files and only the first is skipwoktree flagged gets reported as normal
473 if (alreadyReported.find(filename) != alreadyReported.cend())
474 continue;
476 alreadyReported.insert(filename);
477 callback(CombinePath(gitdir, subpath, filename), &status, isDir, 0, pData);
478 if (isDir)
480 // folder might be replaced by symlink
481 filename.TrimRight(L'/');
482 auto filepos = SearchInSortVector(filelist, filename, -1, indexptr->IsIgnoreCase());
483 if (filepos == NPOS || !filelist[filepos].m_bSymlink)
484 continue;
485 status.status = git_wc_status_deleted;
486 callback(CombinePath(gitdir, subpath, filename), &status, false, 0, pData);
493 start = end = 0;
494 if (GetRangeInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase(), &start, &end, treepos) == 0)
496 *dirstatus = git_wc_status_normal; // here we know that this folder has versioned entries
497 CString oldstring;
498 for (auto it = treeptr->cbegin() + start, itlast = treeptr->cbegin() + end; it <= itlast; ++it)
500 auto& entry = *it;
501 int commonPrefixLength = path.GetLength();
502 int index = entry.m_FileName.Find(L'/', commonPrefixLength);
503 if (index < 0)
504 index = entry.m_FileName.GetLength();
505 else
506 ++index; // include slash at the end for subfolders, so that we do not match files by mistake
508 CString filename = entry.m_FileName.Mid(commonPrefixLength, index - commonPrefixLength);
509 if (oldstring != filename && alreadyReported.find(filename) == alreadyReported.cend())
511 oldstring = filename;
512 int length = filename.GetLength();
513 bool isDir = filename[length - 1] == L'/';
514 if (SearchInSortVector(filelist, filename, isDir ? length : -1, indexptr->IsIgnoreCase()) == NPOS) // do full match for filenames and only prefix-match ending with "/" for folders
516 git_wc_status2_t status = { (!isDir || IsDirectSubmodule(entry.m_FileName, commonPrefixLength)) ? git_wc_status_deleted : git_wc_status_modified, false, false };
517 callback(CombinePath(gitdir, subpath, filename), &status, isDir, 0, pData);
522 return 0;
524 #endif
526 #ifndef TGITCACHE
527 int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_wc_status_kind* status, BOOL IsFul, BOOL IsRecursive, BOOL IsIgnore)
529 ATLASSERT(status);
531 CString path = subpath;
533 path.Replace(L'\\', L'/');
534 if (!path.IsEmpty() && path[path.GetLength() - 1] != L'/')
535 path += L'/'; //Add trail / to show it is directory, not file name.
537 g_IndexFileMap.CheckAndUpdate(gitdir);
539 SHARED_INDEX_PTR indexptr = g_IndexFileMap.SafeGet(gitdir);
541 // broken index
542 if (!indexptr)
544 *status = git_wc_status_none;
545 return -1;
548 size_t pos = SearchInSortVector(*indexptr, path, path.GetLength(), indexptr->IsIgnoreCase());
550 // Not In Version Contorl
551 if (pos == NPOS)
553 if (!IsIgnore)
555 // WC root is at least normal if there are no files added/deleted
556 if (subpath.IsEmpty())
558 *status = git_wc_status_normal;
559 return 0;
561 *status = git_wc_status_unversioned;
562 return 0;
565 g_HeadFileMap.CheckHeadAndUpdate(gitdir, indexptr->IsIgnoreCase());
567 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
568 // broken HEAD
569 if (!treeptr)
571 *status = git_wc_status_none;
572 return -1;
575 // check whether there files in head with are not in index
576 pos = SearchInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase());
577 if (pos != NPOS)
579 *status = git_wc_status_deleted;
580 return 0;
583 // WC root is at least normal if there are no files added/deleted
584 if (path.IsEmpty())
586 *status = git_wc_status_normal;
587 return 0;
590 // Check ignore
591 g_IgnoreList.CheckAndUpdateIgnoreFiles(gitdir, path, true);
592 if (g_IgnoreList.IsIgnore(path, gitdir, true))
593 *status = git_wc_status_ignored;
594 else
595 *status = git_wc_status_unversioned;
597 return 0;
600 // In version control
601 *status = git_wc_status_normal;
603 size_t start = 0;
604 size_t end = 0;
606 GetRangeInSortVector(*indexptr, path,path.GetLength(), indexptr->IsIgnoreCase(), &start, &end, pos);
608 // Check Conflict;
609 for (auto it = indexptr->cbegin() + start, itlast = indexptr->cbegin() + end; indexptr->m_bHasConflicts && it <= itlast; ++it)
611 if (((*it).m_Flags & GIT_IDXENTRY_STAGEMASK) != 0)
613 *status = git_wc_status_conflicted;
614 // When status == git_wc_status_conflicted, we don't need to check each file status
615 // because git_wc_status_conflicted is the highest.
616 return 0;
620 if (IsFul)
622 g_HeadFileMap.CheckHeadAndUpdate(gitdir, indexptr->IsIgnoreCase());
624 // Check Add
626 // Check if new init repository
627 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
628 // broken HEAD
629 if (!treeptr)
631 *status = git_wc_status_none;
632 return -1;
636 for (auto it = indexptr->cbegin() + start, itlast = indexptr->cbegin() + end; it <= itlast; ++it)
638 auto& indexentry = *it;
639 pos = SearchInSortVector(*treeptr, indexentry.m_FileName, -1, indexptr->IsIgnoreCase());
641 if (pos == NPOS)
643 *status = GetMoreImportant(git_wc_status_added, *status); // added file found
644 AdjustFolderStatus(*status);
645 if (GetMoreImportant(*status, git_wc_status_modified) == *status) // the only potential higher status which me might get in this loop
646 break;
647 continue;
650 if ((*treeptr)[pos].m_Hash != indexentry.m_IndexHash)
652 *status = GetMoreImportant(git_wc_status_modified, *status); // modified file found
653 break;
657 // Check Delete
658 if (*status == git_wc_status_normal)
660 pos = SearchInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase());
661 if (pos == NPOS)
662 *status = GetMoreImportant(git_wc_status_added, *status); // added file found
663 else
665 size_t hstart, hend;
666 // we know that pos exists in treeptr
667 GetRangeInSortVector(*treeptr, path, path.GetLength(), indexptr->IsIgnoreCase(), &hstart, &hend, pos);
668 for (auto hit = treeptr->cbegin() + hstart, lastElement = treeptr->cbegin() + hend; hit <= lastElement; ++hit)
670 if (SearchInSortVector(*indexptr, (*hit).m_FileName, -1, indexptr->IsIgnoreCase()) == NPOS)
672 *status = GetMoreImportant(git_wc_status_deleted, *status); // deleted file found
673 break;
679 } /* End lock*/
682 auto mostImportantPossibleFolderStatus = GetMoreImportant(git_wc_status_added, GetMoreImportant(git_wc_status_modified, git_wc_status_deleted));
683 AdjustFolderStatus(mostImportantPossibleFolderStatus);
684 // we can skip here when we already have the highest possible status
685 if (mostImportantPossibleFolderStatus == *status)
686 return 0;
688 for (auto it = indexptr->cbegin() + start, itlast = indexptr->cbegin() + end; it <= itlast; ++it)
690 auto& indexentry = *it;
691 // skip child directory, but handle submodules
692 if (!IsRecursive && indexentry.m_FileName.Find(L'/', path.GetLength()) > 0 && !IsDirectSubmodule(indexentry.m_FileName, path.GetLength()))
693 continue;
695 git_wc_status2_t filestatus = { git_wc_status_none, false, false };
696 GetFileStatus(gitdir, indexentry.m_FileName, filestatus, IsFul, IsIgnore, false);
697 switch (filestatus.status)
699 case git_wc_status_added:
700 case git_wc_status_modified:
701 case git_wc_status_deleted:
702 //case git_wc_status_conflicted: cannot happen, we exit as soon we found a conflict in subpath
703 *status = GetMoreImportant(filestatus.status, *status);
704 AdjustFolderStatus(*status);
705 if (mostImportantPossibleFolderStatus == *status)
706 return 0;
710 return 0;
712 #endif
714 #ifdef TGITCACHE
715 bool GitStatus::IsExistIndexLockFile(CString sDirName)
717 if (!PathIsDirectory(sDirName))
719 int x = sDirName.ReverseFind(L'\\');
720 if (x < 2)
721 return false;
723 sDirName.Truncate(x);
726 for (;;)
728 if (PathFileExists(CombinePath(sDirName, L".git")))
730 if (PathFileExists(g_AdminDirMap.GetWorktreeAdminDirConcat(sDirName, L"index.lock")))
731 return true;
733 return false;
736 int x = sDirName.ReverseFind(L'\\');
737 if (x < 2)
738 return false;
740 sDirName.Truncate(x);
743 #endif
745 bool GitStatus::ReleasePath(const CString &gitdir)
747 g_IndexFileMap.SafeClear(gitdir);
748 g_HeadFileMap.SafeClear(gitdir);
749 return true;
752 bool GitStatus::ReleasePathsRecursively(const CString &rootpath)
754 g_IndexFileMap.SafeClearRecursively(rootpath);
755 g_HeadFileMap.SafeClearRecursively(rootpath);
756 return true;
759 void GitStatus::AdjustFolderStatus(git_wc_status_kind& status)
761 if (status == git_wc_status_deleted || status == git_wc_status_added)
762 status = git_wc_status_modified;