Merge branch 'scintilla-404'
[TortoiseGit.git] / src / Git / gitindex.h
blob02fb92045e945be80d1ecb4060fa618626c14999
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 "GitHash.h"
21 #include "gitdll.h"
22 #include "GitStatus.h"
23 #include "UnicodeUtils.h"
24 #include "ReaderWriterLock.h"
25 #include "GitAdminDir.h"
26 #include "StringUtils.h"
27 #include "PathUtils.h"
29 #ifndef S_IFLNK
30 #define S_IFLNK 0120000
31 #undef _S_IFLNK
32 #define _S_IFLNK S_IFLNK
33 #endif
34 #ifndef S_ISLNK
35 #define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK)
36 #endif
38 class CGitIndex
40 public:
41 CString m_FileName;
42 __time64_t m_ModifyTime;
43 uint16_t m_Flags;
44 uint16_t m_FlagsExtended;
45 CGitHash m_IndexHash;
46 __int64 m_Size;
47 uint32_t m_Mode;
49 int Print();
52 class CGitIndexList:public std::vector<CGitIndex>
54 public:
55 __time64_t m_LastModifyTime;
56 __int64 m_LastFileSize;
57 BOOL m_bHasConflicts;
58 inline bool IsIgnoreCase() { return m_iIndexCaps & GIT_INDEXCAP_IGNORE_CASE; }
60 CGitIndexList();
61 ~CGitIndexList();
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);
68 #endif
69 protected:
70 int m_iIndexCaps;
71 __int64 m_iMaxCheckSize;
72 CAutoConfig config;
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>
81 public:
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);
92 if (lookup == cend())
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())
110 return false;
111 erase(lookup);
112 return true;
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)
126 this->erase(*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))
136 LoadIndex(gitdir);
140 class CGitTreeItem
142 public:
143 CString m_FileName;
144 CGitHash m_Hash;
145 int m_Flags;
148 /* After object create, never change field against
149 * that needn't lock to get field
151 class CGitHeadFileList:public std::vector<CGitTreeItem>
153 private:
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;
164 CGitHash m_Head;
165 CString m_HeadFile;
166 CString m_Gitdir;
167 CString m_PackRefFile;
169 std::map<CString,CGitHash> m_PackRefMap;
171 public:
172 CGitHeadFileList()
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();
185 private:
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>
192 public:
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())
222 return false;
223 erase(lookup);
224 return true;
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)
238 this->erase(*it);
239 return !toRemove.empty();
241 void CheckHeadAndUpdate(const CString& gitdir, bool ignoreCase);
244 class CGitFileName
246 public:
247 CGitFileName() {}
248 CGitFileName(LPCTSTR filename, __int64 size, __int64 lastmodified)
249 : m_FileName(filename)
250 , m_Size(size)
251 , m_LastModified(lastmodified)
252 , m_bSymlink(false)
255 CString m_FileName;
256 __int64 m_Size;
257 __int64 m_LastModified;
258 bool m_bSymlink;
261 class CGitIgnoreItem
263 public:
264 CGitIgnoreItem()
265 : m_LastModifyTime(0)
266 , m_LastFileSize(-1)
267 , m_pExcludeList(nullptr)
268 , m_buffer(nullptr)
269 , m_iIgnoreCase(nullptr)
273 ~CGitIgnoreItem()
275 if(m_pExcludeList)
276 git_free_exclude_list(m_pExcludeList);
277 free(m_buffer);
280 __time64_t m_LastModifyTime;
281 __int64 m_LastFileSize;
282 CStringA m_BaseDir;
283 BYTE *m_buffer;
284 EXCLUDE_LIST m_pExcludeList;
285 int* m_iIgnoreCase;
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);
297 #endif
300 class CGitIgnoreList
302 private:
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();
323 public:
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);
334 template<class T>
335 inline void DoSortFilenametSortVector(T& vector, bool ignoreCase)
337 if (ignoreCase)
338 std::sort(vector.begin(), vector.end(), [](const auto& e1, const auto& e2) { return e1.m_FileName.CompareNoCase(e2.m_FileName) < 0; });
339 else
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");
348 #pragma warning(pop)
350 template<class T>
351 inline int GetRangeInSortVector(const T& vector, LPCTSTR pstr, size_t len, bool ignoreCase, size_t* start, size_t* end, size_t pos)
353 if (ignoreCase)
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)
362 if (pos == NPOS)
363 return -1;
364 if (!start || !end)
365 return -1;
367 *start = *end = NPOS;
369 if (vector.empty())
370 return -1;
372 if (pos >= vector.size())
373 return -1;
375 if (compare(vector[pos].m_FileName, pstr, len) != 0)
376 return -1;
378 *start = 0;
379 *end = vector.size() - 1;
381 // shortcut, if all entries are going match
382 if (!len)
383 return 0;
385 for (size_t i = pos; i < vector.size(); ++i)
387 if (compare(vector[i].m_FileName, pstr, len) != 0)
388 break;
390 *end = i;
392 for (size_t i = pos + 1; i-- > 0;)
394 if (compare(vector[i].m_FileName, pstr, len) != 0)
395 break;
397 *start = i;
400 return 0;
403 template<class T>
404 inline size_t SearchInSortVector(const T& vector, LPCTSTR pstr, int len, bool ignoreCase)
406 if (ignoreCase)
408 if (len < 0)
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); });
414 if (len < 0)
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;
424 size_t start = 0;
425 size_t mid = (start + end) / 2;
427 if (vector.empty())
428 return NPOS;
430 while(!( start == end && start==mid))
432 int cmp = compare(vector[mid].m_FileName, pstr);
433 if (cmp == 0)
434 return mid;
435 else if (cmp < 0)
436 start = mid + 1;
437 else // (cmp > 0)
438 end = mid;
440 mid=(start +end ) /2;
444 if (compare(vector[mid].m_FileName, pstr) == 0)
445 return mid;
447 return NPOS;
450 class CGitAdminDirMap:public std::map<CString, CString>
452 public:
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())
467 CString adminDir;
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));
487 result += subpath;
488 return result;
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())
498 CString wtadmindir;
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];
507 ATLASSERT(false);
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));
516 result += subpath;
517 return result;
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())
526 return gitDir;
527 return lookup->second;