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.
22 #include "GitStatus.h"
23 #include "UnicodeUtils.h"
24 #include "ReaderWriterLock.h"
25 #include "GitAdminDir.h"
26 #include "StringUtils.h"
27 #include "PathUtils.h"
30 #define S_IFLNK 0120000
32 #define _S_IFLNK S_IFLNK
35 #define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
42 __time64_t m_ModifyTime
;
44 uint16_t m_FlagsExtended
;
52 class CGitIndexList
:public std::vector
<CGitIndex
>
55 __time64_t m_LastModifyTime
;
56 __int64 m_LastFileSize
;
58 inline bool IsIgnoreCase() { return m_iIndexCaps
& GIT_INDEXCAP_IGNORE_CASE
; }
63 int ReadIndex(CString dotgitdir
);
64 int GetFileStatus(const CString
& gitdir
, const CString
& path
, git_wc_status2_t
& status
, CGitHash
* pHash
= nullptr);
65 int GetFileStatus(CAutoRepository
& repository
, const CString
& gitdir
, CGitIndex
& entry
, git_wc_status2_t
& status
, __int64 time
, __int64 filesize
, bool isSymlink
);
66 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
67 FRIEND_TEST(GitIndexCBasicGitWithTestRepoFixture
, GetFileStatus
);
71 __int64 m_iMaxCheckSize
;
73 int GetFileStatus(const CString
& gitdir
, const CString
& path
, git_wc_status2_t
& status
, __int64 time
, __int64 filesize
, bool isSymlink
, CGitHash
* pHash
= nullptr);
76 typedef std::shared_ptr
<CGitIndexList
> SHARED_INDEX_PTR
;
77 typedef CComCritSecLock
<CComCriticalSection
> CAutoLocker
;
79 class CGitIndexFileMap
:public std::map
<CString
, SHARED_INDEX_PTR
>
82 CComCriticalSection m_critIndexSec
;
84 CGitIndexFileMap() { m_critIndexSec
.Init(); }
85 ~CGitIndexFileMap() { m_critIndexSec
.Term(); }
87 SHARED_INDEX_PTR
SafeGet(const CString
& path
)
89 CString
thePath(CPathUtils::NormalizePath(path
));
90 CAutoLocker
lock(m_critIndexSec
);
91 auto lookup
= find(thePath
);
93 return SHARED_INDEX_PTR();
94 return lookup
->second
;
97 void SafeSet(const CString
& path
, SHARED_INDEX_PTR ptr
)
99 CString
thePath(CPathUtils::NormalizePath(path
));
100 CAutoLocker
lock(m_critIndexSec
);
101 (*this)[thePath
] = ptr
;
104 bool SafeClear(const CString
& path
)
106 CString
thePath(CPathUtils::NormalizePath(path
));
107 CAutoLocker
lock(m_critIndexSec
);
108 auto lookup
= find(thePath
);
109 if (lookup
== cend())
115 bool SafeClearRecursively(const CString
& path
)
117 CString
thePath(CPathUtils::NormalizePath(path
));
118 CAutoLocker
lock(m_critIndexSec
);
119 std::vector
<CString
> toRemove
;
120 for (auto it
= this->cbegin(); it
!= this->cend(); ++it
)
122 if (CStringUtils::StartsWith((*it
).first
, thePath
))
123 toRemove
.push_back((*it
).first
);
125 for (auto it
= toRemove
.cbegin(); it
!= toRemove
.cend(); ++it
)
127 return !toRemove
.empty();
130 bool HasIndexChangedOnDisk(const CString
& gitdir
);
131 int LoadIndex(const CString
&gitdir
);
133 void CheckAndUpdate(const CString
& gitdir
)
135 if (HasIndexChangedOnDisk(gitdir
))
148 /* After object create, never change field against
149 * that needn't lock to get field
151 class CGitHeadFileList
:public std::vector
<CGitTreeItem
>
154 int GetPackRef(const CString
&gitdir
);
156 __time64_t m_LastModifyTimeHead
;
157 __time64_t m_LastModifyTimeRef
;
158 __time64_t m_LastModifyTimePackRef
;
160 __int64 m_LastFileSizeHead
;
161 __int64 m_LastFileSizePackRef
;
163 CString m_HeadRefFile
;
167 CString m_PackRefFile
;
169 std::map
<CString
,CGitHash
> m_PackRefMap
;
173 : m_LastModifyTimeHead(0)
174 , m_LastModifyTimeRef(0)
175 , m_LastModifyTimePackRef(0)
176 , m_LastFileSizeHead(-1)
177 , m_LastFileSizePackRef(-1)
181 int ReadTree(bool ignoreCase
);
182 int ReadHeadHash(const CString
& gitdir
);
183 bool CheckHeadUpdate();
186 int ReadTreeRecursive(git_repository
& repo
, const git_tree
* tree
, const CStringA
& base
);
189 typedef std::shared_ptr
<CGitHeadFileList
> SHARED_TREE_PTR
;
190 class CGitHeadFileMap
:public std::map
<CString
,SHARED_TREE_PTR
>
194 CComCriticalSection m_critTreeSec
;
196 CGitHeadFileMap() { m_critTreeSec
.Init(); }
197 ~CGitHeadFileMap() { m_critTreeSec
.Term(); }
199 SHARED_TREE_PTR
SafeGet(const CString
& path
)
201 CString
thePath(CPathUtils::NormalizePath(path
));
202 CAutoLocker
lock(m_critTreeSec
);
203 auto lookup
= find(thePath
);
204 if (lookup
== cend())
205 return SHARED_TREE_PTR();
206 return lookup
->second
;
209 void SafeSet(const CString
& path
, SHARED_TREE_PTR ptr
)
211 CString
thePath(CPathUtils::NormalizePath(path
));
212 CAutoLocker
lock(m_critTreeSec
);
213 (*this)[thePath
] = ptr
;
216 bool SafeClear(const CString
& path
)
218 CString
thePath(CPathUtils::NormalizePath(path
));
219 CAutoLocker
lock(m_critTreeSec
);
220 auto lookup
= find(thePath
);
221 if (lookup
== cend())
227 bool SafeClearRecursively(const CString
& path
)
229 CString
thePath(CPathUtils::NormalizePath(path
));
230 CAutoLocker
lock(m_critTreeSec
);
231 std::vector
<CString
> toRemove
;
232 for (auto it
= this->cbegin(); it
!= this->cend(); ++it
)
234 if (CStringUtils::StartsWith((*it
).first
, thePath
))
235 toRemove
.push_back((*it
).first
);
237 for (auto it
= toRemove
.cbegin(); it
!= toRemove
.cend(); ++it
)
239 return !toRemove
.empty();
241 void CheckHeadAndUpdate(const CString
& gitdir
, bool ignoreCase
);
248 CGitFileName(LPCTSTR filename
, __int64 size
, __int64 lastmodified
)
249 : m_FileName(filename
)
251 , m_LastModified(lastmodified
)
257 __int64 m_LastModified
;
265 : m_LastModifyTime(0)
267 , m_pExcludeList(nullptr)
269 , m_iIgnoreCase(nullptr)
276 git_free_exclude_list(m_pExcludeList
);
280 __time64_t m_LastModifyTime
;
281 __int64 m_LastFileSize
;
284 EXCLUDE_LIST m_pExcludeList
;
287 int FetchIgnoreList(const CString
& projectroot
, const CString
& file
, bool isGlobal
, int* ignoreCase
);
290 * patha: the filename to be checked whether is is ignored or not
291 * base: must be a pointer to the beginning of the base filename WITHIN patha
292 * type: DT_DIR or DT_REG
294 int IsPathIgnored(const CStringA
& patha
, const char* base
, int& type
);
295 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
296 int IsPathIgnored(const CStringA
& patha
, int& type
);
303 bool CheckFileChanged(const CString
&path
);
304 int FetchIgnoreFile(const CString
&gitdir
, const CString
&gitignore
, bool isGlobal
);
306 int CheckIgnore(const CString
&path
, const CString
&root
, bool isDir
);
307 int CheckFileAgainstIgnoreList(const CString
&ignorefile
, const CStringA
&patha
, const char * base
, int &type
);
309 // core.excludesfile stuff
310 std::map
<CString
, CString
> m_CoreExcludesfiles
;
311 std::map
<CString
, int> m_IgnoreCase
;
312 CString m_sGitSystemConfigPath
;
313 CString m_sGitProgramDataConfigPath
;
314 ULONGLONG m_dGitSystemConfigPathLastChecked
;
315 CReaderWriterLock m_coreExcludefilesSharedMutex
;
316 // checks if the msysgit path has changed and return true/false
317 // if the path changed, the cache is update
318 // force is only ised in constructor
319 bool CheckAndUpdateGitSystemConfigPath(bool force
= true);
320 bool CheckAndUpdateCoreExcludefile(const CString
&adminDir
);
321 const CString
GetWindowsHome();
324 CReaderWriterLock m_SharedMutex
;
326 CGitIgnoreList(){ CheckAndUpdateGitSystemConfigPath(true); }
328 std::map
<CString
, CGitIgnoreItem
> m_Map
;
330 bool CheckAndUpdateIgnoreFiles(const CString
& gitdir
, const CString
& path
, bool isDir
);
331 bool IsIgnore(CString path
, const CString
& root
, bool isDir
);
335 inline void DoSortFilenametSortVector(T
& vector
, bool ignoreCase
)
338 std::sort(vector
.begin(), vector
.end(), [](const auto& e1
, const auto& e2
) { return e1
.m_FileName
.CompareNoCase(e2
.m_FileName
) < 0; });
340 std::sort(vector
.begin(), vector
.end(), [](const auto& e1
, const auto& e2
) { return e1
.m_FileName
.Compare(e2
.m_FileName
) < 0; });
343 static const size_t NPOS
= (size_t)-1; // bad/missing length/position
344 static_assert(MAXSIZE_T
== NPOS
, "NPOS must equal MAXSIZE_T");
345 #pragma warning(push)
346 #pragma warning(disable: 4310)
347 static_assert(-1 == (int)NPOS
, "NPOS must equal -1");
351 inline int GetRangeInSortVector(const T
& vector
, LPCTSTR pstr
, size_t len
, bool ignoreCase
, size_t* start
, size_t* end
, size_t pos
)
354 return GetRangeInSortVector_int(vector
, pstr
, len
, _wcsnicmp
, start
, end
, pos
);
356 return GetRangeInSortVector_int(vector
, pstr
, len
, wcsncmp
, start
, end
, pos
);
359 template<class T
, class V
>
360 int GetRangeInSortVector_int(const T
& vector
, LPCTSTR pstr
, size_t len
, V compare
, size_t* start
, size_t* end
, size_t pos
)
367 *start
= *end
= NPOS
;
372 if (pos
>= vector
.size())
375 if (compare(vector
[pos
].m_FileName
, pstr
, len
) != 0)
379 *end
= vector
.size() - 1;
381 // shortcut, if all entries are going match
385 for (size_t i
= pos
; i
< vector
.size(); ++i
)
387 if (compare(vector
[i
].m_FileName
, pstr
, len
) != 0)
392 for (size_t i
= pos
+ 1; i
-- > 0;)
394 if (compare(vector
[i
].m_FileName
, pstr
, len
) != 0)
404 inline size_t SearchInSortVector(const T
& vector
, LPCTSTR pstr
, int len
, bool ignoreCase
)
409 return SearchInSortVector_int(vector
, pstr
, _wcsicmp
);
411 return SearchInSortVector_int(vector
, pstr
, [len
](const auto& s1
, const auto& s2
) { return _wcsnicmp(s1
, s2
, len
); });
415 return SearchInSortVector_int(vector
, pstr
, wcscmp
);
417 return SearchInSortVector_int(vector
, pstr
, [len
](const auto& s1
, const auto& s2
) { return wcsncmp(s1
, s2
, len
); });
420 template<class T
, class V
>
421 size_t SearchInSortVector_int(const T
& vector
, LPCTSTR pstr
, V compare
)
423 size_t end
= vector
.size() - 1;
425 size_t mid
= (start
+ end
) / 2;
430 while(!( start
== end
&& start
==mid
))
432 int cmp
= compare(vector
[mid
].m_FileName
, pstr
);
440 mid
=(start
+end
) /2;
444 if (compare(vector
[mid
].m_FileName
, pstr
) == 0)
450 class CGitAdminDirMap
:public std::map
<CString
, CString
>
453 CComCriticalSection m_critIndexSec
;
454 std::map
<CString
, CString
> m_reverseLookup
;
455 std::map
<CString
, CString
> m_WorktreeAdminDirLookup
;
457 CGitAdminDirMap() { m_critIndexSec
.Init(); }
458 ~CGitAdminDirMap() { m_critIndexSec
.Term(); }
460 CString
GetAdminDir(const CString
&path
)
462 CString
thePath(CPathUtils::NormalizePath(path
));
463 CAutoLocker
lock(m_critIndexSec
);
464 auto lookup
= find(thePath
);
465 if (lookup
== cend())
468 bool isWorktree
= false;
469 GitAdminDir::GetAdminDirPath(thePath
, adminDir
, &isWorktree
);
470 if (PathIsDirectory(adminDir
))
472 adminDir
= CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(adminDir
));
473 (*this)[thePath
] = adminDir
;
474 if (!isWorktree
) // GitAdminDir::GetAdminDirPath returns the commongit dir ("parent/.git") and this would override the lookup path for the main repo
475 m_reverseLookup
[adminDir
] = thePath
;
476 return (*this)[thePath
];
478 return thePath
+ L
".git\\"; // in case of an error stick to old behavior
481 return lookup
->second
;
484 CString
GetAdminDirConcat(const CString
& path
, const CString
& subpath
)
486 CString
result(GetAdminDir(path
));
491 CString
GetWorktreeAdminDir(const CString
& path
)
493 CString
thePath(CPathUtils::NormalizePath(path
));
494 CAutoLocker
lock(m_critIndexSec
);
495 auto lookup
= m_WorktreeAdminDirLookup
.find(thePath
);
496 if (lookup
== m_WorktreeAdminDirLookup
.cend())
499 GitAdminDir::GetWorktreeAdminDirPath(thePath
, wtadmindir
);
500 if (PathIsDirectory(wtadmindir
))
502 wtadmindir
= CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(wtadmindir
));
503 m_WorktreeAdminDirLookup
[thePath
] = wtadmindir
;
504 m_reverseLookup
[wtadmindir
] = thePath
;
505 return m_WorktreeAdminDirLookup
[thePath
];
508 return thePath
+ L
".git\\"; // we should never get here
510 return lookup
->second
;
513 CString
GetWorktreeAdminDirConcat(const CString
& path
, const CString
& subpath
)
515 CString
result(GetWorktreeAdminDir(path
));
520 CString
GetWorkingCopy(const CString
&gitDir
)
522 CString
path(CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(gitDir
)));
523 CAutoLocker
lock(m_critIndexSec
);
524 auto lookup
= m_reverseLookup
.find(path
);
525 if (lookup
== m_reverseLookup
.cend())
527 return lookup
->second
;