Do not check for FILENAME\.git folders
[TortoiseGit.git] / src / Git / GitStatus.cpp
blob186663fe2b095a3737db2126b109f1069d113b18
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - 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"
29 extern CGitAdminDirMap g_AdminDirMap;
30 CGitIndexFileMap g_IndexFileMap;
31 CGitHeadFileMap g_HeadFileMap;
32 CGitIgnoreList g_IgnoreList;
34 GitStatus::GitStatus()
35 : status(NULL)
36 , m_allstatus(git_wc_status_none)
38 m_status.assumeValid = m_status.skipWorktree = false;
39 m_status.prop_status = m_status.text_status = git_wc_status_none;
42 GitStatus::~GitStatus(void)
46 // static method
47 git_wc_status_kind GitStatus::GetAllStatus(const CTGitPath& path, git_depth_t depth, bool * assumeValid, bool * skipWorktree)
49 git_wc_status_kind statuskind;
50 BOOL err;
51 BOOL isDir;
52 CString sProjectRoot;
54 isDir = path.IsDirectory();
55 if (!path.HasAdminDir(&sProjectRoot))
56 return git_wc_status_none;
58 // rev.kind = git_opt_revision_unspecified;
59 statuskind = git_wc_status_none;
61 const BOOL bIsRecursive = (depth == git_depth_infinity || depth == git_depth_unknown); // taken from SVN source
63 CString sSubPath;
64 CString s = path.GetWinPathString();
65 if (s.GetLength() > sProjectRoot.GetLength())
67 if (sProjectRoot.GetLength() == 3 && sProjectRoot[1] == _T(':'))
68 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength());
69 else
70 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength() - 1/*otherwise it gets initial slash*/);
73 bool isfull = ((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"),
74 GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) == ShellCache::dllFull);
76 if(isDir)
78 err = GetDirStatus(sProjectRoot,sSubPath,&statuskind, isfull,bIsRecursive,isfull,NULL, NULL);
79 // folders must not be displayed as added or deleted only as modified (this is for Shell Overlay-Modes)
80 if (statuskind == git_wc_status_unversioned && sSubPath.IsEmpty())
81 statuskind = git_wc_status_normal;
82 else if (statuskind == git_wc_status_deleted || statuskind == git_wc_status_added)
83 statuskind = git_wc_status_modified;
85 else
87 err = GetFileStatus(sProjectRoot, sSubPath, &statuskind, isfull, false, isfull, NULL, NULL, assumeValid, skipWorktree);
90 return statuskind;
93 // static method
94 git_wc_status_kind GitStatus::GetAllStatusRecursive(const CTGitPath& path)
96 return GetAllStatus(path, git_depth_infinity);
99 // static method
100 git_wc_status_kind GitStatus::GetMoreImportant(git_wc_status_kind status1, git_wc_status_kind status2)
102 if (GetStatusRanking(status1) >= GetStatusRanking(status2))
103 return status1;
104 return status2;
106 // static private method
107 int GitStatus::GetStatusRanking(git_wc_status_kind status)
109 switch (status)
111 case git_wc_status_none:
112 return 0;
113 case git_wc_status_unversioned:
114 return 1;
115 case git_wc_status_ignored:
116 return 2;
117 case git_wc_status_incomplete:
118 return 4;
119 case git_wc_status_normal:
120 case git_wc_status_external:
121 return 5;
122 case git_wc_status_added:
123 return 6;
124 case git_wc_status_missing:
125 return 7;
126 case git_wc_status_deleted:
127 return 8;
128 case git_wc_status_replaced:
129 return 9;
130 case git_wc_status_modified:
131 return 10;
132 case git_wc_status_merged:
133 return 11;
134 case git_wc_status_conflicted:
135 return 12;
136 case git_wc_status_obstructed:
137 return 13;
139 return 0;
142 void GitStatus::GetStatus(const CTGitPath& path, bool /*update*/ /* = false */, bool noignore /* = false */, bool /*noexternals*/ /* = false */)
144 // NOTE: unlike the SVN version this one does not cache the enumerated files, because in practice no code in all of
145 // Tortoise uses this, all places that call GetStatus create a temp GitStatus object which gets destroyed right
146 // after the call again
148 CString sProjectRoot;
149 if ( !path.HasAdminDir(&sProjectRoot) )
150 return;
152 bool isfull = ((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"),
153 GetSystemMetrics(SM_REMOTESESSION) ? ShellCache::dll : ShellCache::exe) == ShellCache::dllFull);
155 int err = 0;
158 LPCTSTR lpszSubPath = NULL;
159 CString sSubPath;
160 CString s = path.GetWinPathString();
161 if (s.GetLength() > sProjectRoot.GetLength())
163 sSubPath = s.Right(s.GetLength() - sProjectRoot.GetLength());
164 lpszSubPath = sSubPath;
165 // skip initial slash if necessary
166 if (*lpszSubPath == _T('\\'))
167 ++lpszSubPath;
170 m_status.prop_status = m_status.text_status = git_wc_status_none;
171 m_status.assumeValid = false;
172 m_status.skipWorktree = false;
174 if(path.IsDirectory())
176 err = GetDirStatus(sProjectRoot,CString(lpszSubPath),&m_status.text_status , isfull, false,!noignore, NULL, NULL);
177 if (m_status.text_status == git_wc_status_added || m_status.text_status == git_wc_status_deleted) // fix for issue #1769; a folder is either modified, conflicted or normal
178 m_status.text_status = git_wc_status_modified;
180 else
182 err = GetFileStatus(sProjectRoot, CString(lpszSubPath), &m_status.text_status ,isfull, false,!noignore, NULL,NULL, &m_status.assumeValid, &m_status.skipWorktree);
186 // Error present if function is not under version control
187 if (err)
189 status = NULL;
190 return;
193 status = &m_status;
196 void GitStatus::GetStatusString(git_wc_status_kind status, size_t buflen, TCHAR * string)
198 TCHAR * buf;
199 switch (status)
201 case git_wc_status_none:
202 buf = _T("none\0");
203 break;
204 case git_wc_status_unversioned:
205 buf = _T("unversioned\0");
206 break;
207 case git_wc_status_normal:
208 buf = _T("normal\0");
209 break;
210 case git_wc_status_added:
211 buf = _T("added\0");
212 break;
213 case git_wc_status_missing:
214 buf = _T("missing\0");
215 break;
216 case git_wc_status_deleted:
217 buf = _T("deleted\0");
218 break;
219 case git_wc_status_replaced:
220 buf = _T("replaced\0");
221 break;
222 case git_wc_status_modified:
223 buf = _T("modified\0");
224 break;
225 case git_wc_status_merged:
226 buf = _T("merged\0");
227 break;
228 case git_wc_status_conflicted:
229 buf = _T("conflicted\0");
230 break;
231 case git_wc_status_obstructed:
232 buf = _T("obstructed\0");
233 break;
234 case git_wc_status_ignored:
235 buf = _T("ignored");
236 break;
237 case git_wc_status_external:
238 buf = _T("external");
239 break;
240 case git_wc_status_incomplete:
241 buf = _T("incomplete\0");
242 break;
243 default:
244 buf = _T("\0");
245 break;
247 _stprintf_s(string, buflen, _T("%s"), buf);
250 void GitStatus::GetStatusString(HINSTANCE hInst, git_wc_status_kind status, TCHAR * string, int size, WORD lang)
252 switch (status)
254 case git_wc_status_none:
255 LoadStringEx(hInst, IDS_STATUSNONE, string, size, lang);
256 break;
257 case git_wc_status_unversioned:
258 LoadStringEx(hInst, IDS_STATUSUNVERSIONED, string, size, lang);
259 break;
260 case git_wc_status_normal:
261 LoadStringEx(hInst, IDS_STATUSNORMAL, string, size, lang);
262 break;
263 case git_wc_status_added:
264 LoadStringEx(hInst, IDS_STATUSADDED, string, size, lang);
265 break;
266 case git_wc_status_missing:
267 LoadStringEx(hInst, IDS_STATUSABSENT, string, size, lang);
268 break;
269 case git_wc_status_deleted:
270 LoadStringEx(hInst, IDS_STATUSDELETED, string, size, lang);
271 break;
272 case git_wc_status_replaced:
273 LoadStringEx(hInst, IDS_STATUSREPLACED, string, size, lang);
274 break;
275 case git_wc_status_modified:
276 LoadStringEx(hInst, IDS_STATUSMODIFIED, string, size, lang);
277 break;
278 case git_wc_status_merged:
279 LoadStringEx(hInst, IDS_STATUSMERGED, string, size, lang);
280 break;
281 case git_wc_status_conflicted:
282 LoadStringEx(hInst, IDS_STATUSCONFLICTED, string, size, lang);
283 break;
284 case git_wc_status_ignored:
285 LoadStringEx(hInst, IDS_STATUSIGNORED, string, size, lang);
286 break;
287 case git_wc_status_obstructed:
288 LoadStringEx(hInst, IDS_STATUSOBSTRUCTED, string, size, lang);
289 break;
290 case git_wc_status_external:
291 LoadStringEx(hInst, IDS_STATUSEXTERNAL, string, size, lang);
292 break;
293 case git_wc_status_incomplete:
294 LoadStringEx(hInst, IDS_STATUSINCOMPLETE, string, size, lang);
295 break;
296 default:
297 LoadStringEx(hInst, IDS_STATUSNONE, string, size, lang);
298 break;
302 int GitStatus::LoadStringEx(HINSTANCE hInstance, UINT uID, LPTSTR lpBuffer, int nBufferMax, WORD wLanguage)
304 const STRINGRESOURCEIMAGE* pImage;
305 const STRINGRESOURCEIMAGE* pImageEnd;
306 ULONG nResourceSize;
307 HGLOBAL hGlobal;
308 UINT iIndex;
309 int ret;
311 HRSRC hResource = FindResourceEx(hInstance, RT_STRING, MAKEINTRESOURCE(((uID>>4)+1)), wLanguage);
312 if (!hResource)
314 // try the default language before giving up!
315 hResource = FindResource(hInstance, MAKEINTRESOURCE(((uID>>4)+1)), RT_STRING);
316 if (!hResource)
317 return 0;
319 hGlobal = LoadResource(hInstance, hResource);
320 if (!hGlobal)
321 return 0;
322 pImage = (const STRINGRESOURCEIMAGE*)::LockResource(hGlobal);
323 if(!pImage)
324 return 0;
326 nResourceSize = ::SizeofResource(hInstance, hResource);
327 pImageEnd = (const STRINGRESOURCEIMAGE*)(LPBYTE(pImage)+nResourceSize);
328 iIndex = uID&0x000f;
330 while ((iIndex > 0) && (pImage < pImageEnd))
332 pImage = (const STRINGRESOURCEIMAGE*)(LPBYTE(pImage)+(sizeof(STRINGRESOURCEIMAGE)+(pImage->nLength*sizeof(WCHAR))));
333 iIndex--;
335 if (pImage >= pImageEnd)
336 return 0;
337 if (pImage->nLength == 0)
338 return 0;
340 ret = pImage->nLength;
341 if (pImage->nLength > nBufferMax)
343 wcsncpy_s(lpBuffer, nBufferMax, pImage->achString, pImage->nLength-1);
344 lpBuffer[nBufferMax-1] = 0;
346 else
348 wcsncpy_s(lpBuffer, nBufferMax, pImage->achString, pImage->nLength);
349 lpBuffer[ret] = 0;
351 return ret;
354 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
356 int GitStatus::GetFileStatus(const CString &gitdir, const CString &pathParam, git_wc_status_kind * status,BOOL IsFull, BOOL /*IsRecursive*/,BOOL IsIgnore, FILL_STATUS_CALLBACK callback, void *pData, bool * assumeValid, bool * skipWorktree)
360 CString path = pathParam;
362 path.Replace(_T('\\'),_T('/'));
364 CString lowcasepath =path;
365 lowcasepath.MakeLower();
367 if(status)
369 git_wc_status_kind st = git_wc_status_none;
370 CGitHash hash;
372 g_IndexFileMap.GetFileStatus(gitdir, path, &st, IsFull, false, callback, pData, &hash, true, assumeValid, skipWorktree);
374 if( st == git_wc_status_conflicted )
376 *status =st;
377 if (callback && assumeValid && skipWorktree)
378 callback(gitdir + _T("/") + path, st, false, pData, *assumeValid, *skipWorktree);
379 return 0;
382 if( st == git_wc_status_unversioned )
384 if(!IsIgnore)
386 *status = git_wc_status_unversioned;
387 if (callback && assumeValid && skipWorktree)
388 callback(gitdir + _T("/") + path, *status, false, pData, *assumeValid, *skipWorktree);
389 return 0;
392 if (g_IgnoreList.CheckIgnoreChanged(gitdir, path, false))
394 g_IgnoreList.LoadAllIgnoreFile(gitdir, path, false);
396 if (g_IgnoreList.IsIgnore(path, gitdir, false))
398 st = git_wc_status_ignored;
400 *status = st;
401 if (callback && assumeValid && skipWorktree)
402 callback(gitdir + _T("/") + path, st, false, pData, *assumeValid, *skipWorktree);
404 return 0;
407 if ((st == git_wc_status_normal || st == git_wc_status_modified) && IsFull)
410 g_HeadFileMap.CheckHeadAndUpdate(gitdir);
412 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
414 // Check Head Tree Hash;
416 //add item
418 int start = SearchInSortVector(*treeptr, lowcasepath, -1);
420 if(start<0)
422 *status =st=git_wc_status_added;
423 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": File miss in head tree %s"), path);
424 if (callback && assumeValid && skipWorktree)
425 callback(gitdir + _T("/") + path, st, false, pData, *assumeValid, *skipWorktree);
426 return 0;
429 //staged and not commit
430 if( treeptr->at(start).m_Hash != hash )
432 *status =st=git_wc_status_modified;
433 if (callback && assumeValid && skipWorktree)
434 callback(gitdir + _T("/") + path, st, false, pData, *assumeValid, *skipWorktree);
435 return 0;
440 *status =st;
441 if (callback && assumeValid && skipWorktree)
442 callback(gitdir + _T("/") + path, st, false, pData, *assumeValid, *skipWorktree);
443 return 0;
446 catch(...)
448 if(status)
449 *status = git_wc_status_none;
450 return -1;
453 return 0;
457 bool GitStatus::HasIgnoreFilesChanged(const CString &gitdir, const CString &subpaths, bool isDir)
459 return g_IgnoreList.CheckIgnoreChanged(gitdir, subpaths, isDir);
462 int GitStatus::LoadIgnoreFile(const CString &gitdir, const CString &subpaths, bool isDir)
464 return g_IgnoreList.LoadAllIgnoreFile(gitdir, subpaths, isDir);
466 int GitStatus::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion)
468 if (g_IndexFileMap.IsUnderVersionControl(gitdir, path, isDir, isVersion))
469 return 1;
470 if (!*isVersion)
471 return g_HeadFileMap.IsUnderVersionControl(gitdir, path, isDir, isVersion);
472 return 0;
475 __int64 GitStatus::GetIndexFileTime(const CString &gitdir)
477 SHARED_INDEX_PTR ptr=g_IndexFileMap.SafeGet(gitdir);
478 if(ptr.get() == NULL)
479 return 0;
481 return ptr->m_LastModifyTime;
484 int GitStatus::IsIgnore(const CString &gitdir, const CString &path, bool *isIgnore, bool isDir)
486 if (g_IgnoreList.CheckIgnoreChanged(gitdir, path, isDir))
487 g_IgnoreList.LoadAllIgnoreFile(gitdir, path, isDir);
489 *isIgnore = g_IgnoreList.IsIgnore(path, gitdir, isDir);
491 return 0;
494 static bool SortFileName(CGitFileName &Item1, CGitFileName &Item2)
496 return Item1.m_FileName.Compare(Item2.m_FileName)<0;
499 int GitStatus::GetFileList(const CString &gitdir, const CString &subpath, std::vector<CGitFileName> &list)
501 WIN32_FIND_DATA data;
502 HANDLE handle=::FindFirstFile(gitdir+_T("\\")+subpath+_T("\\*.*"), &data);
505 if(_tcscmp(data.cFileName, _T(".git")) == 0)
506 continue;
508 if(_tcscmp(data.cFileName, _T(".")) == 0)
509 continue;
511 if(_tcscmp(data.cFileName, _T("..")) == 0)
512 continue;
514 CGitFileName filename;
516 filename.m_CaseFileName = filename.m_FileName = data.cFileName;
517 filename.m_FileName.MakeLower();
519 if(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
521 filename.m_FileName += _T('/');
524 list.push_back(filename);
526 }while(::FindNextFile(handle, &data));
528 FindClose(handle);
530 std::sort(list.begin(), list.end(), SortFileName);
531 return 0;
534 int GitStatus::EnumDirStatus(const CString &gitdir, const CString &subpath, git_wc_status_kind * status,BOOL IsFul, BOOL IsRecursive, BOOL IsIgnore, FILL_STATUS_CALLBACK callback, void *pData)
538 CString path =subpath;
540 path.Replace(_T('\\'),_T('/'));
541 if(!path.IsEmpty())
542 if(path[path.GetLength()-1] != _T('/'))
543 path += _T('/'); //Add trail / to show it is directory, not file name.
545 CString lowcasepath = path;
546 lowcasepath.MakeLower();
548 std::vector<CGitFileName> filelist;
549 GetFileList(gitdir, subpath, filelist);
551 if(status)
553 g_IndexFileMap.CheckAndUpdate(gitdir,true);
555 g_HeadFileMap.CheckHeadAndUpdate(gitdir);
557 SHARED_INDEX_PTR indexptr = g_IndexFileMap.SafeGet(gitdir);
558 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
560 std::vector<CGitFileName>::iterator it;
562 // new git working tree has no index file
563 if (indexptr.get() == NULL)
565 for (it = filelist.begin(); it < filelist.end(); ++it)
567 CString casepath = path + it->m_CaseFileName;
569 bool bIsDir = false;
570 if (casepath.GetLength() > 0 && casepath[casepath.GetLength() - 1] == _T('/'))
571 bIsDir = true;
573 if (bIsDir) /*check if it is directory*/
575 if (::PathFileExists(gitdir + casepath + _T("\\.git")))
576 { /* That is git submodule */
577 *status = git_wc_status_unknown;
578 if (callback)
579 callback(gitdir + _T("/") + casepath, *status, bIsDir, pData, false, false);
580 continue;
584 if (IsIgnore)
586 if (g_IgnoreList.CheckIgnoreChanged(gitdir, casepath, bIsDir))
587 g_IgnoreList.LoadAllIgnoreFile(gitdir, casepath, bIsDir);
589 if (g_IgnoreList.IsIgnore(casepath, gitdir, bIsDir))
590 *status = git_wc_status_ignored;
591 else if (bIsDir)
592 continue;
593 else
594 *status = git_wc_status_unversioned;
596 else if (bIsDir)
597 continue;
598 else
599 *status = git_wc_status_unversioned;
601 if(callback)
602 callback(gitdir + _T("/") + casepath, *status, bIsDir, pData, false, false);
604 return 0;
607 CString onepath;
608 CString casepath;
609 for (it = filelist.begin(); it < filelist.end(); ++it)
611 casepath=onepath = path;
612 onepath.MakeLower();
613 onepath += it->m_FileName;
614 casepath += it->m_CaseFileName;
616 bool bIsDir = false;
617 if (!onepath.IsEmpty() && onepath[onepath.GetLength() - 1] == _T('/'))
618 bIsDir = true;
620 int matchLength = -1;
621 if (bIsDir)
622 matchLength = onepath.GetLength();
623 int pos = SearchInSortVector(*indexptr, onepath, matchLength);
624 int posintree = SearchInSortVector(*treeptr, onepath, matchLength);
626 if(pos <0 && posintree<0)
628 if (onepath.IsEmpty())
629 continue;
631 if(bIsDir) /*check if it is directory*/
633 if(::PathFileExists(gitdir+onepath+_T("/.git")))
634 { /* That is git submodule */
635 *status = git_wc_status_unknown;
636 if(callback)
637 callback(gitdir + _T("/") + casepath, *status, bIsDir, pData, false, false);
638 continue;
642 if(!IsIgnore)
644 *status = git_wc_status_unversioned;
645 if(callback)
646 callback(gitdir + _T("/") + casepath, *status, bIsDir, pData, false, false);
647 continue;
650 if (g_IgnoreList.CheckIgnoreChanged(gitdir, casepath, bIsDir))
651 g_IgnoreList.LoadAllIgnoreFile(gitdir, casepath, bIsDir);
653 if (g_IgnoreList.IsIgnore(casepath, gitdir, bIsDir))
654 *status = git_wc_status_ignored;
655 else
656 *status = git_wc_status_unversioned;
658 if(callback)
659 callback(gitdir + _T("/") + casepath, *status, bIsDir, pData, false, false);
662 else if(pos <0 && posintree>=0) /* check if file delete in index */
664 *status = git_wc_status_deleted;
665 if(callback)
666 callback(gitdir + _T("/") + casepath, *status, bIsDir, pData, false, false);
669 else if(pos >=0 && posintree <0) /* Check if file added */
671 *status = git_wc_status_added;
672 if(callback)
673 callback(gitdir + _T("/") + casepath, *status, bIsDir, pData, false, false);
675 else
677 if (onepath.IsEmpty())
678 continue;
680 if (bIsDir)
682 *status = git_wc_status_normal;
683 if(callback)
684 callback(gitdir + _T("/") + casepath, *status, bIsDir, pData, false, false);
686 else
688 bool assumeValid = false;
689 bool skipWorktree = false;
690 git_wc_status_kind filestatus;
691 GetFileStatus(gitdir, casepath, &filestatus, IsFul, IsRecursive, IsIgnore, callback, pData, &assumeValid, &skipWorktree);
695 }/*End of For*/
697 /* Check deleted file in system */
698 int start=0, end=0;
699 int pos = SearchInSortVector(*indexptr, lowcasepath, lowcasepath.GetLength()); // match path prefix, (sub)folders end with slash
700 std::map<CString, bool> skipWorktreeMap;
702 if (GetRangeInSortVector(*indexptr, lowcasepath, lowcasepath.GetLength(), &start, &end, pos) == 0)
704 CGitIndexList::iterator it;
705 CString oldstring;
707 for (it = indexptr->begin() + start; it <= indexptr->begin() + end; ++it)
709 int commonPrefixLength = lowcasepath.GetLength();
710 int index = (*it).m_FileName.Find(_T('/'), commonPrefixLength);
711 if(index<0)
712 index = (*it).m_FileName.GetLength();
713 else
714 ++index; // include slash at the end for subfolders, so that we do not match files by mistake
716 CString filename = (*it).m_FileName.Mid(commonPrefixLength, index - commonPrefixLength);
717 if(oldstring != filename)
719 oldstring = filename;
720 if (SearchInSortVector(filelist, filename, filename.GetLength()) < 0)
722 bool skipWorktree = false;
723 *status = git_wc_status_deleted;
724 if (((*it).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
726 skipWorktreeMap[filename] = true;
727 skipWorktree = true;
728 *status = git_wc_status_normal;
730 if(callback)
731 callback(gitdir + _T("/") + (*it).m_FileName, *status, false, pData, false, skipWorktree);
737 start = end =0;
738 pos = SearchInSortVector(*treeptr, lowcasepath, lowcasepath.GetLength()); // match path prefix, (sub)folders end with slash
739 if (GetRangeInSortVector(*treeptr, lowcasepath, lowcasepath.GetLength(), &start, &end, pos) == 0)
741 CGitHeadFileList::iterator it;
742 CString oldstring;
744 for (it = treeptr->begin() + start; it <= treeptr->begin() + end; ++it)
746 int commonPrefixLength = lowcasepath.GetLength();
747 int index = (*it).m_FileName.Find(_T('/'), commonPrefixLength);
748 if(index<0)
749 index = (*it).m_FileName.GetLength();
750 else
751 ++index; // include slash at the end for subfolders, so that we do not match files by mistake
753 CString filename = (*it).m_FileName.Mid(commonPrefixLength, index - commonPrefixLength);
754 if (oldstring != filename && skipWorktreeMap[filename] != true)
756 oldstring = filename;
757 if (SearchInSortVector(filelist, filename, filename.GetLength()) < 0)
759 *status = git_wc_status_deleted;
760 if(callback)
761 callback(gitdir + _T("/") + (*it).m_FileName, *status, false, pData, false, false);
767 }/*End of if status*/
768 }catch(...)
770 return -1;
772 return 0;
775 int GitStatus::GetDirStatus(const CString &gitdir, const CString &subpath, git_wc_status_kind * status, BOOL IsFul, BOOL IsRecursive, BOOL IsIgnore, FILL_STATUS_CALLBACK callback, void *pData)
779 CString path =subpath;
781 path.Replace(_T('\\'),_T('/'));
782 if(!path.IsEmpty())
783 if(path[path.GetLength()-1] != _T('/'))
784 path += _T('/'); //Add trail / to show it is directory, not file name.
786 CString lowcasepath = path;
787 lowcasepath.MakeLower();
789 if(status)
791 g_IndexFileMap.CheckAndUpdate(gitdir, true);
793 SHARED_INDEX_PTR indexptr = g_IndexFileMap.SafeGet(gitdir);
795 if (indexptr == NULL)
797 *status = git_wc_status_unversioned;
798 return 0;
801 int pos = SearchInSortVector(*indexptr, lowcasepath, lowcasepath.GetLength());
803 //Not In Version Contorl
804 if(pos<0)
806 if(!IsIgnore)
808 *status = git_wc_status_unversioned;
809 if(callback)
810 callback(gitdir + _T("/") + path, *status, false, pData, false, false);
811 return 0;
813 //Check ignore always.
815 if (g_IgnoreList.CheckIgnoreChanged(gitdir, path, true))
816 g_IgnoreList.LoadAllIgnoreFile(gitdir, path, true);
818 if (g_IgnoreList.IsIgnore(path, gitdir, true))
819 *status = git_wc_status_ignored;
820 else
821 *status = git_wc_status_unversioned;
823 g_HeadFileMap.CheckHeadAndUpdate(gitdir, false);
825 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
826 //Check init repository
827 if (treeptr->HeadIsEmpty() && path.IsEmpty())
828 *status = git_wc_status_normal;
832 else // In version control
834 *status = git_wc_status_normal;
836 int start=0;
837 int end=0;
838 if(path.IsEmpty())
840 start=0;
841 end = (int)indexptr->size() - 1;
843 GetRangeInSortVector(*indexptr, lowcasepath, lowcasepath.GetLength(), &start, &end, pos);
844 CGitIndexList::iterator it;
846 it = indexptr->begin()+start;
848 // Check Conflict;
849 for (int i = start; i <= end; ++i)
851 if (((*it).m_Flags & GIT_IDXENTRY_STAGEMASK) !=0)
853 *status = git_wc_status_conflicted;
854 if(callback)
856 int dirpos = (*it).m_FileName.Find(_T('/'), path.GetLength());
857 if(dirpos<0 || IsRecursive)
858 callback(gitdir + _T("\\") + it->m_FileName, git_wc_status_conflicted, false, pData, false, false);
860 else
861 break;
863 ++it;
866 if( IsFul && (*status != git_wc_status_conflicted))
868 *status = git_wc_status_normal;
870 g_HeadFileMap.CheckHeadAndUpdate(gitdir);
872 //Check Add
873 it = indexptr->begin()+start;
877 //Check if new init repository
878 SHARED_TREE_PTR treeptr = g_HeadFileMap.SafeGet(gitdir);
880 if (!treeptr->empty() || treeptr->HeadIsEmpty())
882 for (int i = start; i<= end; ++i)
884 pos = SearchInSortVector(*treeptr, (*it).m_FileName, -1);
886 if(pos < 0)
888 *status = max(git_wc_status_added, *status); // added file found
889 if(callback)
891 int dirpos = (*it).m_FileName.Find(_T('/'), path.GetLength());
892 if(dirpos<0 || IsRecursive)
893 callback(gitdir + _T("\\") + it->m_FileName, git_wc_status_added, false, pData, false, false);
896 else
897 break;
900 if( pos>=0 && treeptr->at(pos).m_Hash != (*it).m_IndexHash)
902 *status = max(git_wc_status_modified, *status); // modified file found
903 if(callback)
905 int dirpos = (*it).m_FileName.Find(_T('/'), path.GetLength());
906 if(dirpos<0 || IsRecursive)
907 callback(gitdir + _T("\\") + it->m_FileName, git_wc_status_modified, false, pData, ((*it).m_Flags & GIT_IDXENTRY_VALID) && !((*it).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE), ((*it).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE) != 0);
910 else
911 break;
914 ++it;
917 //Check Delete
918 if( *status == git_wc_status_normal )
920 pos = SearchInSortVector(*treeptr, lowcasepath, lowcasepath.GetLength());
921 if(pos <0)
923 *status = max(git_wc_status_added, *status); // added file found
926 else
928 int hstart,hend;
929 GetRangeInSortVector(*treeptr, lowcasepath, lowcasepath.GetLength(), &hstart, &hend, pos);
930 CGitHeadFileList::iterator hit;
931 hit = treeptr->begin() + hstart;
932 CGitHeadFileList::iterator lastElement = treeptr->end();
933 for (int i = hstart; i <= hend && hit != lastElement; ++i)
935 if (SearchInSortVector(*indexptr, (*hit).m_FileName, -1) < 0)
937 *status = max(git_wc_status_deleted, *status); // deleted file found
938 break;
940 ++hit;
945 }/* End lock*/
947 // If define callback, it need update each file status.
948 // If not define callback, status == git_wc_status_conflicted, needn't check each file status
949 // because git_wc_status_conflicted is highest.s
950 if(callback || (*status != git_wc_status_conflicted))
952 //Check File Time;
953 //if(IsRecursive)
955 CString sub, currentPath;
956 it = indexptr->begin()+start;
957 for (int i = start; i <= end; ++i, ++it)
959 if( !IsRecursive )
961 //skip child directory
962 int pos = (*it).m_FileName.Find(_T('/'), path.GetLength());
964 if( pos > 0)
966 currentPath = (*it).m_FileName.Left(pos);
967 if( callback && (sub != currentPath) )
969 sub = currentPath;
970 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": index subdir %s\n"),sub);
971 if(callback) callback(gitdir + _T("\\") + sub,
972 git_wc_status_normal, true, pData, false, false);
974 continue;
978 git_wc_status_kind filestatus = git_wc_status_none;
979 bool assumeValid = false;
980 bool skipWorktree = false;
981 GetFileStatus(gitdir, (*it).m_FileName, &filestatus, IsFul, IsRecursive, IsIgnore, callback, pData, &assumeValid, &skipWorktree);
987 if(callback) callback(gitdir + _T("/") + subpath, *status, true, pData, false, false);
990 }catch(...)
992 if(status)
993 *status = git_wc_status_none;
994 return -1;
997 return 0;
1000 bool GitStatus::IsExistIndexLockFile(const CString &gitdir)
1002 CString sDirName= gitdir;
1004 if (!PathIsDirectory(sDirName))
1006 int x = sDirName.ReverseFind(_T('\\'));
1007 if (x < 2)
1008 return false;
1010 sDirName = sDirName.Left(x);
1013 for (;;)
1015 if(PathFileExists(sDirName + _T("\\.git")))
1017 if(PathFileExists(g_AdminDirMap.GetAdminDir(sDirName) + _T("index.lock")))
1018 return true;
1019 else
1020 return false;
1023 int x = sDirName.ReverseFind(_T('\\'));
1024 if (x < 2)
1025 return false;
1027 sDirName = sDirName.Left(x);
1031 bool GitStatus::ReleasePath(const CString &gitdir)
1033 g_IndexFileMap.SafeClear(gitdir);
1034 return true;
1037 bool GitStatus::ReleasePathsRecursively(const CString &rootpath)
1039 g_IndexFileMap.SafeClearRecursively(rootpath);
1040 return true;