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.
22 #include "..\TortoiseShell\resource.h"
23 #include "GitStatus.h"
24 #include "UnicodeUtils.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()
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)
47 git_wc_status_kind
GitStatus::GetAllStatus(const CTGitPath
& path
, git_depth_t depth
, bool * assumeValid
, bool * skipWorktree
)
49 git_wc_status_kind statuskind
;
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
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());
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
);
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
;
87 err
= GetFileStatus(sProjectRoot
, sSubPath
, &statuskind
, isfull
, false, isfull
, NULL
, NULL
, assumeValid
, skipWorktree
);
94 git_wc_status_kind
GitStatus::GetAllStatusRecursive(const CTGitPath
& path
)
96 return GetAllStatus(path
, git_depth_infinity
);
100 git_wc_status_kind
GitStatus::GetMoreImportant(git_wc_status_kind status1
, git_wc_status_kind status2
)
102 if (GetStatusRanking(status1
) >= GetStatusRanking(status2
))
106 // static private method
107 int GitStatus::GetStatusRanking(git_wc_status_kind status
)
111 case git_wc_status_none
:
113 case git_wc_status_unversioned
:
115 case git_wc_status_ignored
:
117 case git_wc_status_incomplete
:
119 case git_wc_status_normal
:
120 case git_wc_status_external
:
122 case git_wc_status_added
:
124 case git_wc_status_missing
:
126 case git_wc_status_deleted
:
128 case git_wc_status_replaced
:
130 case git_wc_status_modified
:
132 case git_wc_status_merged
:
134 case git_wc_status_conflicted
:
136 case git_wc_status_obstructed
:
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
) )
152 bool isfull
= ((DWORD
)CRegStdDWORD(_T("Software\\TortoiseGit\\CacheType"),
153 GetSystemMetrics(SM_REMOTESESSION
) ? ShellCache::dll
: ShellCache::exe
) == ShellCache::dllFull
);
158 LPCTSTR lpszSubPath
= NULL
;
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('\\'))
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
,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
;
182 err
= GetFileStatus(sProjectRoot
, 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
196 void GitStatus::GetStatusString(git_wc_status_kind status
, size_t buflen
, TCHAR
* string
)
201 case git_wc_status_none
:
204 case git_wc_status_unversioned
:
205 buf
= _T("unversioned\0");
207 case git_wc_status_normal
:
208 buf
= _T("normal\0");
210 case git_wc_status_added
:
213 case git_wc_status_missing
:
214 buf
= _T("missing\0");
216 case git_wc_status_deleted
:
217 buf
= _T("deleted\0");
219 case git_wc_status_replaced
:
220 buf
= _T("replaced\0");
222 case git_wc_status_modified
:
223 buf
= _T("modified\0");
225 case git_wc_status_merged
:
226 buf
= _T("merged\0");
228 case git_wc_status_conflicted
:
229 buf
= _T("conflicted\0");
231 case git_wc_status_obstructed
:
232 buf
= _T("obstructed\0");
234 case git_wc_status_ignored
:
237 case git_wc_status_external
:
238 buf
= _T("external");
240 case git_wc_status_incomplete
:
241 buf
= _T("incomplete\0");
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
)
254 case git_wc_status_none
:
255 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
257 case git_wc_status_unversioned
:
258 LoadStringEx(hInst
, IDS_STATUSUNVERSIONED
, string
, size
, lang
);
260 case git_wc_status_normal
:
261 LoadStringEx(hInst
, IDS_STATUSNORMAL
, string
, size
, lang
);
263 case git_wc_status_added
:
264 LoadStringEx(hInst
, IDS_STATUSADDED
, string
, size
, lang
);
266 case git_wc_status_missing
:
267 LoadStringEx(hInst
, IDS_STATUSABSENT
, string
, size
, lang
);
269 case git_wc_status_deleted
:
270 LoadStringEx(hInst
, IDS_STATUSDELETED
, string
, size
, lang
);
272 case git_wc_status_replaced
:
273 LoadStringEx(hInst
, IDS_STATUSREPLACED
, string
, size
, lang
);
275 case git_wc_status_modified
:
276 LoadStringEx(hInst
, IDS_STATUSMODIFIED
, string
, size
, lang
);
278 case git_wc_status_merged
:
279 LoadStringEx(hInst
, IDS_STATUSMERGED
, string
, size
, lang
);
281 case git_wc_status_conflicted
:
282 LoadStringEx(hInst
, IDS_STATUSCONFLICTED
, string
, size
, lang
);
284 case git_wc_status_ignored
:
285 LoadStringEx(hInst
, IDS_STATUSIGNORED
, string
, size
, lang
);
287 case git_wc_status_obstructed
:
288 LoadStringEx(hInst
, IDS_STATUSOBSTRUCTED
, string
, size
, lang
);
290 case git_wc_status_external
:
291 LoadStringEx(hInst
, IDS_STATUSEXTERNAL
, string
, size
, lang
);
293 case git_wc_status_incomplete
:
294 LoadStringEx(hInst
, IDS_STATUSINCOMPLETE
, string
, size
, lang
);
297 LoadStringEx(hInst
, IDS_STATUSNONE
, string
, size
, lang
);
302 int GitStatus::LoadStringEx(HINSTANCE hInstance
, UINT uID
, LPTSTR lpBuffer
, int nBufferMax
, WORD wLanguage
)
304 const STRINGRESOURCEIMAGE
* pImage
;
305 const STRINGRESOURCEIMAGE
* pImageEnd
;
311 HRSRC hResource
= FindResourceEx(hInstance
, RT_STRING
, MAKEINTRESOURCE(((uID
>>4)+1)), wLanguage
);
314 // try the default language before giving up!
315 hResource
= FindResource(hInstance
, MAKEINTRESOURCE(((uID
>>4)+1)), RT_STRING
);
319 hGlobal
= LoadResource(hInstance
, hResource
);
322 pImage
= (const STRINGRESOURCEIMAGE
*)::LockResource(hGlobal
);
326 nResourceSize
= ::SizeofResource(hInstance
, hResource
);
327 pImageEnd
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+nResourceSize
);
330 while ((iIndex
> 0) && (pImage
< pImageEnd
))
332 pImage
= (const STRINGRESOURCEIMAGE
*)(LPBYTE(pImage
)+(sizeof(STRINGRESOURCEIMAGE
)+(pImage
->nLength
*sizeof(WCHAR
))));
335 if (pImage
>= pImageEnd
)
337 if (pImage
->nLength
== 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;
348 wcsncpy_s(lpBuffer
, nBufferMax
, pImage
->achString
, pImage
->nLength
);
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();
369 git_wc_status_kind st
= git_wc_status_none
;
372 g_IndexFileMap
.GetFileStatus(gitdir
, path
, &st
, IsFull
, false, callback
, pData
, &hash
, true, assumeValid
, skipWorktree
);
374 if( st
== git_wc_status_conflicted
)
377 if (callback
&& assumeValid
&& skipWorktree
)
378 callback(gitdir
+ _T("/") + path
, st
, false, pData
, *assumeValid
, *skipWorktree
);
382 if( st
== git_wc_status_unversioned
)
386 *status
= git_wc_status_unversioned
;
387 if (callback
&& assumeValid
&& skipWorktree
)
388 callback(gitdir
+ _T("/") + path
, *status
, false, pData
, *assumeValid
, *skipWorktree
);
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
;
401 if (callback
&& assumeValid
&& skipWorktree
)
402 callback(gitdir
+ _T("/") + path
, st
, false, pData
, *assumeValid
, *skipWorktree
);
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;
418 int start
= SearchInSortVector(*treeptr
, lowcasepath
, -1);
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
);
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
);
441 if (callback
&& assumeValid
&& skipWorktree
)
442 callback(gitdir
+ _T("/") + path
, st
, false, pData
, *assumeValid
, *skipWorktree
);
449 *status
= git_wc_status_none
;
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
))
471 return g_HeadFileMap
.IsUnderVersionControl(gitdir
, path
, isDir
, isVersion
);
475 __int64
GitStatus::GetIndexFileTime(const CString
&gitdir
)
477 SHARED_INDEX_PTR ptr
=g_IndexFileMap
.SafeGet(gitdir
);
478 if(ptr
.get() == NULL
)
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
);
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)
508 if(_tcscmp(data
.cFileName
, _T(".")) == 0)
511 if(_tcscmp(data
.cFileName
, _T("..")) == 0)
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
));
530 std::sort(list
.begin(), list
.end(), SortFileName
);
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('/'));
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
);
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
;
570 if (it
->m_FileName
.GetLength() > 0 && it
->m_FileName
[it
->m_FileName
.GetLength() - 1] == _T('/'))
575 if (g_IgnoreList
.CheckIgnoreChanged(gitdir
, casepath
, bIsDir
))
576 g_IgnoreList
.LoadAllIgnoreFile(gitdir
, casepath
, bIsDir
);
578 if (g_IgnoreList
.IsIgnore(casepath
, gitdir
, bIsDir
))
579 *status
= git_wc_status_ignored
;
583 *status
= git_wc_status_unversioned
;
588 *status
= git_wc_status_unversioned
;
591 callback(gitdir
+ _T("/") + casepath
, *status
, bIsDir
, pData
, false, false);
598 for (it
= filelist
.begin(); it
< filelist
.end(); ++it
)
600 casepath
=onepath
= path
;
602 onepath
+= it
->m_FileName
;
603 casepath
+= it
->m_CaseFileName
;
606 if (!onepath
.IsEmpty() && onepath
[onepath
.GetLength() - 1] == _T('/'))
609 int matchLength
= -1;
611 matchLength
= onepath
.GetLength();
612 int pos
= SearchInSortVector(*indexptr
, onepath
, matchLength
);
613 int posintree
= SearchInSortVector(*treeptr
, onepath
, matchLength
);
615 if(pos
<0 && posintree
<0)
617 if (onepath
.IsEmpty())
622 *status
= git_wc_status_unversioned
;
624 callback(gitdir
+ _T("/") + casepath
, *status
, bIsDir
, pData
, false, false);
628 if (g_IgnoreList
.CheckIgnoreChanged(gitdir
, casepath
, bIsDir
))
629 g_IgnoreList
.LoadAllIgnoreFile(gitdir
, casepath
, bIsDir
);
631 if (g_IgnoreList
.IsIgnore(casepath
, gitdir
, bIsDir
))
632 *status
= git_wc_status_ignored
;
634 *status
= git_wc_status_unversioned
;
637 callback(gitdir
+ _T("/") + casepath
, *status
, bIsDir
, pData
, false, false);
640 else if(pos
<0 && posintree
>=0) /* check if file delete in index */
642 *status
= git_wc_status_deleted
;
644 callback(gitdir
+ _T("/") + casepath
, *status
, bIsDir
, pData
, false, false);
647 else if(pos
>=0 && posintree
<0) /* Check if file added */
649 *status
= git_wc_status_added
;
651 callback(gitdir
+ _T("/") + casepath
, *status
, bIsDir
, pData
, false, false);
655 if (onepath
.IsEmpty())
660 *status
= git_wc_status_normal
;
662 callback(gitdir
+ _T("/") + casepath
, *status
, bIsDir
, pData
, false, false);
666 bool assumeValid
= false;
667 bool skipWorktree
= false;
668 git_wc_status_kind filestatus
;
669 GetFileStatus(gitdir
, casepath
, &filestatus
, IsFul
, IsRecursive
, IsIgnore
, callback
, pData
, &assumeValid
, &skipWorktree
);
675 /* Check deleted file in system */
677 int pos
= SearchInSortVector(*indexptr
, lowcasepath
, lowcasepath
.GetLength()); // match path prefix, (sub)folders end with slash
678 std::map
<CString
, bool> skipWorktreeMap
;
680 if (GetRangeInSortVector(*indexptr
, lowcasepath
, lowcasepath
.GetLength(), &start
, &end
, pos
) == 0)
682 CGitIndexList::iterator it
;
685 for (it
= indexptr
->begin() + start
; it
<= indexptr
->begin() + end
; ++it
)
687 int commonPrefixLength
= lowcasepath
.GetLength();
688 int index
= (*it
).m_FileName
.Find(_T('/'), commonPrefixLength
);
690 index
= (*it
).m_FileName
.GetLength();
692 ++index
; // include slash at the end for subfolders, so that we do not match files by mistake
694 CString filename
= (*it
).m_FileName
.Mid(commonPrefixLength
, index
- commonPrefixLength
);
695 if(oldstring
!= filename
)
697 oldstring
= filename
;
698 if (SearchInSortVector(filelist
, filename
, filename
.GetLength()) < 0)
700 bool skipWorktree
= false;
701 *status
= git_wc_status_deleted
;
702 if (((*it
).m_Flags
& GIT_IDXENTRY_SKIP_WORKTREE
) != 0)
704 skipWorktreeMap
[filename
] = true;
706 *status
= git_wc_status_normal
;
709 callback(gitdir
+ _T("/") + (*it
).m_FileName
, *status
, false, pData
, false, skipWorktree
);
716 pos
= SearchInSortVector(*treeptr
, lowcasepath
, lowcasepath
.GetLength()); // match path prefix, (sub)folders end with slash
717 if (GetRangeInSortVector(*treeptr
, lowcasepath
, lowcasepath
.GetLength(), &start
, &end
, pos
) == 0)
719 CGitHeadFileList::iterator it
;
722 for (it
= treeptr
->begin() + start
; it
<= treeptr
->begin() + end
; ++it
)
724 int commonPrefixLength
= lowcasepath
.GetLength();
725 int index
= (*it
).m_FileName
.Find(_T('/'), commonPrefixLength
);
727 index
= (*it
).m_FileName
.GetLength();
729 ++index
; // include slash at the end for subfolders, so that we do not match files by mistake
731 CString filename
= (*it
).m_FileName
.Mid(commonPrefixLength
, index
- commonPrefixLength
);
732 if (oldstring
!= filename
&& skipWorktreeMap
[filename
] != true)
734 oldstring
= filename
;
735 if (SearchInSortVector(filelist
, filename
, filename
.GetLength()) < 0)
737 *status
= git_wc_status_deleted
;
739 callback(gitdir
+ _T("/") + (*it
).m_FileName
, *status
, false, pData
, false, false);
745 }/*End of if status*/
753 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
)
757 CString path
=subpath
;
759 path
.Replace(_T('\\'),_T('/'));
761 if(path
[path
.GetLength()-1] != _T('/'))
762 path
+= _T('/'); //Add trail / to show it is directory, not file name.
764 CString lowcasepath
= path
;
765 lowcasepath
.MakeLower();
769 g_IndexFileMap
.CheckAndUpdate(gitdir
, true);
771 SHARED_INDEX_PTR indexptr
= g_IndexFileMap
.SafeGet(gitdir
);
773 if (indexptr
== NULL
)
775 *status
= git_wc_status_unversioned
;
779 int pos
= SearchInSortVector(*indexptr
, lowcasepath
, lowcasepath
.GetLength());
781 //Not In Version Contorl
786 *status
= git_wc_status_unversioned
;
788 callback(gitdir
+ _T("/") + path
, *status
, false, pData
, false, false);
791 //Check ignore always.
793 if (g_IgnoreList
.CheckIgnoreChanged(gitdir
, path
, true))
794 g_IgnoreList
.LoadAllIgnoreFile(gitdir
, path
, true);
796 if (g_IgnoreList
.IsIgnore(path
, gitdir
, true))
797 *status
= git_wc_status_ignored
;
799 *status
= git_wc_status_unversioned
;
801 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
, false);
803 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
804 //Check init repository
805 if (treeptr
->HeadIsEmpty() && path
.IsEmpty())
806 *status
= git_wc_status_normal
;
810 else // In version control
812 *status
= git_wc_status_normal
;
819 end
= (int)indexptr
->size() - 1;
821 GetRangeInSortVector(*indexptr
, lowcasepath
, lowcasepath
.GetLength(), &start
, &end
, pos
);
822 CGitIndexList::iterator it
;
824 it
= indexptr
->begin()+start
;
827 for (int i
= start
; i
<= end
; ++i
)
829 if (((*it
).m_Flags
& GIT_IDXENTRY_STAGEMASK
) !=0)
831 *status
= git_wc_status_conflicted
;
834 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
835 if(dirpos
<0 || IsRecursive
)
836 callback(gitdir
+ _T("\\") + it
->m_FileName
, git_wc_status_conflicted
, false, pData
, false, false);
844 if( IsFul
&& (*status
!= git_wc_status_conflicted
))
846 *status
= git_wc_status_normal
;
848 g_HeadFileMap
.CheckHeadAndUpdate(gitdir
);
851 it
= indexptr
->begin()+start
;
855 //Check if new init repository
856 SHARED_TREE_PTR treeptr
= g_HeadFileMap
.SafeGet(gitdir
);
858 if (!treeptr
->empty() || treeptr
->HeadIsEmpty())
860 for (int i
= start
; i
<= end
; ++i
)
862 pos
= SearchInSortVector(*treeptr
, (*it
).m_FileName
, -1);
866 *status
= max(git_wc_status_added
, *status
); // added file found
869 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
870 if(dirpos
<0 || IsRecursive
)
871 callback(gitdir
+ _T("\\") + it
->m_FileName
, git_wc_status_added
, false, pData
, false, false);
878 if( pos
>=0 && treeptr
->at(pos
).m_Hash
!= (*it
).m_IndexHash
)
880 *status
= max(git_wc_status_modified
, *status
); // modified file found
883 int dirpos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
884 if(dirpos
<0 || IsRecursive
)
885 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);
896 if( *status
== git_wc_status_normal
)
898 pos
= SearchInSortVector(*treeptr
, lowcasepath
, lowcasepath
.GetLength());
901 *status
= max(git_wc_status_added
, *status
); // added file found
907 GetRangeInSortVector(*treeptr
, lowcasepath
, lowcasepath
.GetLength(), &hstart
, &hend
, pos
);
908 CGitHeadFileList::iterator hit
;
909 hit
= treeptr
->begin() + hstart
;
910 CGitHeadFileList::iterator lastElement
= treeptr
->end();
911 for (int i
= hstart
; i
<= hend
&& hit
!= lastElement
; ++i
)
913 if (SearchInSortVector(*indexptr
, (*hit
).m_FileName
, -1) < 0)
915 *status
= max(git_wc_status_deleted
, *status
); // deleted file found
925 // If define callback, it need update each file status.
926 // If not define callback, status == git_wc_status_conflicted, needn't check each file status
927 // because git_wc_status_conflicted is highest.s
928 if(callback
|| (*status
!= git_wc_status_conflicted
))
933 CString sub
, currentPath
;
934 it
= indexptr
->begin()+start
;
935 for (int i
= start
; i
<= end
; ++i
, ++it
)
939 //skip child directory
940 int pos
= (*it
).m_FileName
.Find(_T('/'), path
.GetLength());
944 currentPath
= (*it
).m_FileName
.Left(pos
);
945 if( callback
&& (sub
!= currentPath
) )
948 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__
) _T(": index subdir %s\n"),sub
);
949 if(callback
) callback(gitdir
+ _T("\\") + sub
,
950 git_wc_status_normal
, true, pData
, false, false);
956 git_wc_status_kind filestatus
= git_wc_status_none
;
957 bool assumeValid
= false;
958 bool skipWorktree
= false;
959 GetFileStatus(gitdir
, (*it
).m_FileName
, &filestatus
, IsFul
, IsRecursive
, IsIgnore
, callback
, pData
, &assumeValid
, &skipWorktree
);
965 if(callback
) callback(gitdir
+ _T("/") + subpath
, *status
, true, pData
, false, false);
971 *status
= git_wc_status_none
;
978 bool GitStatus::IsExistIndexLockFile(const CString
&gitdir
)
980 CString sDirName
= gitdir
;
982 if (!PathIsDirectory(sDirName
))
984 int x
= sDirName
.ReverseFind(_T('\\'));
988 sDirName
= sDirName
.Left(x
);
993 if(PathFileExists(sDirName
+ _T("\\.git")))
995 if(PathFileExists(g_AdminDirMap
.GetAdminDir(sDirName
) + _T("index.lock")))
1001 int x
= sDirName
.ReverseFind(_T('\\'));
1005 sDirName
= sDirName
.Left(x
);
1009 bool GitStatus::ReleasePath(const CString
&gitdir
)
1011 g_IndexFileMap
.SafeClear(gitdir
);
1015 bool GitStatus::ReleasePathsRecursively(const CString
&rootpath
)
1017 g_IndexFileMap
.SafeClearRecursively(rootpath
);