CPatch: New memory management
[TortoiseGit.git] / src / Git / GitStatus.cpp
blob6ba85d8aef288195c431041ad4e05f50a089484a
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.
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()
36 : status(nullptr)
38 m_status.assumeValid = m_status.skipWorktree = false;
39 m_status.status = git_wc_status_none;
42 // static method
43 #ifndef TGITCACHE
44 int GitStatus::GetAllStatus(const CTGitPath& path, bool bIsRecursive, git_wc_status2_t& status)
46 BOOL isDir;
47 CString sProjectRoot;
49 isDir = path.IsDirectory();
50 if (!path.HasAdminDir(&sProjectRoot))
51 return git_wc_status_none;
53 CString sSubPath;
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());
59 else
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);
66 if(isDir)
68 auto err = GetDirStatus(sProjectRoot, sSubPath, &status.status, isfull, bIsRecursive, isfull);
69 AdjustFolderStatus(status.status);
70 return err;
73 return GetFileStatus(sProjectRoot, sSubPath, status, isfull, isfull);
75 #endif
77 // static method
78 git_wc_status_kind GitStatus::GetMoreImportant(git_wc_status_kind status1, git_wc_status_kind status2)
80 if (GetStatusRanking(status1) >= GetStatusRanking(status2))
81 return status1;
82 return status2;
84 // static private method
85 int GitStatus::GetStatusRanking(git_wc_status_kind status)
87 switch (status)
89 case git_wc_status_none:
90 return 0;
91 case git_wc_status_unversioned:
92 return 1;
93 case git_wc_status_ignored:
94 return 2;
95 case git_wc_status_normal:
96 case git_wc_status_added:
97 return 6;
98 case git_wc_status_deleted:
99 return 8;
100 case git_wc_status_modified:
101 return 10;
102 case git_wc_status_conflicted:
103 return 12;
105 return 0;
108 #ifndef TGITCACHE
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) )
117 return;
119 bool isfull = ((DWORD)CRegStdDWORD(L"Software\\TortoiseGit\\CacheType",
120 GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) == ShellCache::dllFull);
122 int err = 0;
124 LPCTSTR lpszSubPath = nullptr;
125 CString sSubPath;
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'\\')
133 ++lpszSubPath;
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);
145 else
146 err = GetFileStatus(sProjectRoot, lpszSubPath, m_status, isfull, !noignore);
148 // Error present if function is not under version control
149 if (err)
151 status = nullptr;
152 return;
155 status = &m_status;
157 #endif
159 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
161 typedef struct CGitRepoLists
163 SHARED_INDEX_PTR pIndex;
164 SHARED_TREE_PTR pTree;
165 } CGitRepoLists;
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);
172 CGitHash hash;
173 if (repolists.pIndex->GetFileStatus(gitdir, path, status, &hash))
175 // an error occurred in GetFileStatus
176 status.status = git_wc_status_none;
177 return -1;
180 if (status.status == git_wc_status_conflicted)
181 return 0;
183 if (status.status == git_wc_status_unversioned)
185 if (IsFull)
187 if (!repolists.pTree)
189 if (update)
190 g_HeadFileMap.CheckHeadAndUpdate(gitdir, repolists.pIndex->IsIgnoreCase());
192 // Check Head Tree Hash
193 repolists.pTree = g_HeadFileMap.SafeGet(gitdir);
195 // broken HEAD
196 if (!repolists.pTree)
198 status.status = git_wc_status_none;
199 return -1;
202 // deleted only in index item?
203 if (SearchInSortVector(*repolists.pTree, path, -1, repolists.pIndex->IsIgnoreCase()) != NPOS)
205 status.status = git_wc_status_deleted;
206 return 0;
210 if (!IsIgnore)
212 status.status = git_wc_status_unversioned;
213 return 0;
216 g_IgnoreList.CheckAndUpdateIgnoreFiles(gitdir, path, false);
217 if (g_IgnoreList.IsIgnore(path, gitdir, false))
218 status.status = git_wc_status_ignored;
220 return 0;
223 if ((status.status == git_wc_status_normal || status.status == git_wc_status_modified) && IsFull)
225 if (!repolists.pTree)
227 if (update)
228 g_HeadFileMap.CheckHeadAndUpdate(gitdir, repolists.pIndex->IsIgnoreCase());
230 // Check Head Tree Hash
231 repolists.pTree = g_HeadFileMap.SafeGet(gitdir);
233 // broken HEAD
234 if (!repolists.pTree)
236 status.status = git_wc_status_none;
237 return -1;
240 //add item
241 size_t start = SearchInSortVector(*repolists.pTree, path, -1, repolists.pIndex->IsIgnoreCase());
242 if (start == NPOS)
244 status.status = git_wc_status_added;
245 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": File miss in head tree %s", (LPCTSTR)path);
246 return 0;
249 // staged and not commit
250 if ((*repolists.pTree)[start].m_Hash != hash)
252 status = { git_wc_status_modified, false, false };
253 return 0;
257 return 0;
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;
267 if (update)
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;
274 return -1;
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'/'))
284 return false;
286 auto ptr = indexPath.GetString() + prefix;
287 int folderdepth = 0;
288 while (*ptr)
290 if (*ptr == L'/')
291 ++folderdepth;
292 ++ptr;
295 return folderdepth == 1;
298 #ifdef TGITCACHE
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);
313 if (!handle)
314 return -1;
317 if (wcscmp(data.cFileName, L".git") == 0)
319 isRepoRoot = true;
320 continue;
323 if (wcscmp(data.cFileName, L".") == 0)
324 continue;
326 if (wcscmp(data.cFileName, L"..") == 0)
327 continue;
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);
343 return 0;
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
358 if (!indexptr)
359 return -1;
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
365 if (!treeptr)
366 return -1;
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;
376 if (isRepoRoot)
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;
395 bool bIsDir = false;
396 if (!onepath.IsEmpty() && onepath[onepath.GetLength() - 1] == L'/')
397 bIsDir = true;
399 int matchLength = -1;
400 if (bIsDir)
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;
411 else
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);
443 else
445 if (bIsDir)
447 status.status = git_wc_status_normal;
448 callback(CombinePath(gitdir, onepath), &status, bIsDir, fileentry.m_LastModified, pData);
450 else
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);
457 continue;
459 if ((*indexptr).GetFileStatus(repository, gitdir, indexentry, status, fileentry.m_LastModified, fileentry.m_Size, fileentry.m_bSymlink))
460 return -1;
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);
466 }/*End of For*/
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
475 CString oldstring;
476 for (auto it = indexptr->cbegin() + start, itlast = indexptr->cbegin() + end; it <= itlast; ++it)
478 auto& entry = *it;
479 int commonPrefixLength = path.GetLength();
480 int index = entry.m_FileName.Find(L'/', commonPrefixLength);
481 if (index < 0)
482 index = entry.m_FileName.GetLength();
483 else
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())
501 continue;
503 alreadyReported.insert(filename);
504 callback(CombinePath(gitdir, subpath, filename), &status, isDir, 0, pData);
505 if (isDir)
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)
511 continue;
512 status.status = git_wc_status_deleted;
513 callback(CombinePath(gitdir, subpath, filename), &status, false, 0, pData);
520 start = end = 0;
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
524 CString oldstring;
525 for (auto it = treeptr->cbegin() + start, itlast = treeptr->cbegin() + end; it <= itlast; ++it)
527 auto& entry = *it;
528 int commonPrefixLength = path.GetLength();
529 int index = entry.m_FileName.Find(L'/', commonPrefixLength);
530 if (index < 0)
531 index = entry.m_FileName.GetLength();
532 else
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);
549 return 0;
551 #endif
553 #ifndef TGITCACHE
554 int GitStatus::GetDirStatus(const CString& gitdir, const CString& subpath, git_wc_status_kind* status, BOOL IsFul, BOOL IsRecursive, BOOL IsIgnore)
556 ATLASSERT(status);
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);
569 // broken index
570 if (!sharedRepoLists.pIndex)
572 *status = git_wc_status_none;
573 return -1;
576 size_t pos = SearchInSortVector(*sharedRepoLists.pIndex, path, path.GetLength(), sharedRepoLists.pIndex->IsIgnoreCase());
578 // Not In Version Contorl
579 if (pos == NPOS)
581 if (!IsIgnore)
583 // WC root is at least normal if there are no files added/deleted
584 if (subpath.IsEmpty())
586 *status = git_wc_status_normal;
587 return 0;
589 *status = git_wc_status_unversioned;
590 return 0;
593 g_HeadFileMap.CheckHeadAndUpdate(gitdir, sharedRepoLists.pIndex->IsIgnoreCase());
595 sharedRepoLists.pTree = g_HeadFileMap.SafeGet(gitdir);
596 // broken HEAD
597 if (!sharedRepoLists.pTree)
599 *status = git_wc_status_none;
600 return -1;
603 // check whether there files in head with are not in index
604 pos = SearchInSortVector(*sharedRepoLists.pTree, path, path.GetLength(), sharedRepoLists.pIndex->IsIgnoreCase());
605 if (pos != NPOS)
607 *status = git_wc_status_deleted;
608 return 0;
611 // WC root is at least normal if there are no files added/deleted
612 if (path.IsEmpty())
614 *status = git_wc_status_normal;
615 return 0;
618 // Check ignore
619 g_IgnoreList.CheckAndUpdateIgnoreFiles(gitdir, path, true);
620 if (g_IgnoreList.IsIgnore(path, gitdir, true))
621 *status = git_wc_status_ignored;
622 else
623 *status = git_wc_status_unversioned;
625 return 0;
628 // In version control
629 *status = git_wc_status_normal;
631 size_t start = 0;
632 size_t end = 0;
634 GetRangeInSortVector(*sharedRepoLists.pIndex, path,path.GetLength(), sharedRepoLists.pIndex->IsIgnoreCase(), &start, &end, pos);
636 // Check Conflict;
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.
644 return 0;
648 if (IsFul)
650 g_HeadFileMap.CheckHeadAndUpdate(gitdir, sharedRepoLists.pIndex->IsIgnoreCase());
652 // Check Add
654 // Check if new init repository
655 sharedRepoLists.pTree = g_HeadFileMap.SafeGet(gitdir);
656 // broken HEAD
657 if (!sharedRepoLists.pTree)
659 *status = git_wc_status_none;
660 return -1;
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());
669 if (pos == NPOS)
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
674 break;
675 continue;
678 if ((*sharedRepoLists.pTree)[pos].m_Hash != indexentry.m_IndexHash)
680 *status = GetMoreImportant(git_wc_status_modified, *status); // modified file found
681 break;
685 // Check Delete
686 if (*status == git_wc_status_normal)
688 pos = SearchInSortVector(*sharedRepoLists.pTree, path, path.GetLength(), sharedRepoLists.pIndex->IsIgnoreCase());
689 if (pos == NPOS)
690 *status = GetMoreImportant(git_wc_status_added, *status); // added file found
691 else
693 size_t hstart, hend;
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
701 break;
707 } /* End lock*/
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)
714 return 0;
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()))
721 continue;
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)
734 return 0;
738 return 0;
740 #endif
742 #ifdef TGITCACHE
743 bool GitStatus::IsExistIndexLockFile(CString sDirName)
745 if (!PathIsDirectory(sDirName))
747 int x = sDirName.ReverseFind(L'\\');
748 if (x < 2)
749 return false;
751 sDirName.Truncate(x);
754 for (;;)
756 if (PathFileExists(CombinePath(sDirName, L".git")))
758 if (PathFileExists(g_AdminDirMap.GetWorktreeAdminDirConcat(sDirName, L"index.lock")))
759 return true;
761 return false;
764 int x = sDirName.ReverseFind(L'\\');
765 if (x < 2)
766 return false;
768 sDirName.Truncate(x);
771 #endif
773 bool GitStatus::ReleasePath(const CString &gitdir)
775 g_IndexFileMap.SafeClear(gitdir);
776 g_HeadFileMap.SafeClear(gitdir);
777 return true;
780 bool GitStatus::ReleasePathsRecursively(const CString &rootpath)
782 g_IndexFileMap.SafeClearRecursively(rootpath);
783 g_HeadFileMap.SafeClearRecursively(rootpath);
784 return true;
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;