Fixed issue #2979: gitignore patterns are always treated case sensitively for overlay...
[TortoiseGit.git] / src / Git / gitindex.h
bloba919c1573b178f81c596eb3201578d62ed4feed6
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 class CGitIndex
31 public:
32 CString m_FileName;
33 __time64_t m_ModifyTime;
34 uint16_t m_Flags;
35 uint16_t m_FlagsExtended;
36 CGitHash m_IndexHash;
37 __int64 m_Size;
39 int Print();
42 class CGitIndexList:public std::vector<CGitIndex>
44 public:
45 __time64_t m_LastModifyTime;
46 BOOL m_bHasConflicts;
48 CGitIndexList();
49 ~CGitIndexList();
51 int ReadIndex(CString dotgitdir);
52 int GetStatus(const CString& gitdir, CString path, git_wc_status_kind* status, BOOL IsFull = FALSE, BOOL IsRecursive = FALSE, FILL_STATUS_CALLBACK callback = nullptr, void* pData = nullptr, CGitHash* pHash = nullptr, bool* assumeValid = nullptr, bool* skipWorktree = nullptr);
53 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
54 FRIEND_TEST(GitIndexCBasicGitWithTestRepoFixture, GetFileStatus);
55 #endif
56 protected:
57 __int64 m_iMaxCheckSize;
58 CAutoConfig config;
59 int GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind * status, __int64 time, __int64 filesize, FILL_STATUS_CALLBACK callback = nullptr, void *pData = nullptr, CGitHash *pHash = nullptr, bool * assumeValid = nullptr, bool * skipWorktree = nullptr);
62 typedef std::shared_ptr<CGitIndexList> SHARED_INDEX_PTR;
63 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
65 class CGitIndexFileMap:public std::map<CString, SHARED_INDEX_PTR>
67 public:
68 CComCriticalSection m_critIndexSec;
70 CGitIndexFileMap() { m_critIndexSec.Init(); }
71 ~CGitIndexFileMap() { m_critIndexSec.Term(); }
73 SHARED_INDEX_PTR SafeGet(CString thePath)
75 thePath.MakeLower();
76 CAutoLocker lock(m_critIndexSec);
77 auto lookup = find(thePath);
78 if (lookup == cend())
79 return SHARED_INDEX_PTR();
80 return lookup->second;
83 void SafeSet(CString thePath, SHARED_INDEX_PTR ptr)
85 thePath.MakeLower();
86 CAutoLocker lock(m_critIndexSec);
87 (*this)[thePath] = ptr;
90 bool SafeClear(CString thePath)
92 thePath.MakeLower();
93 CAutoLocker lock(m_critIndexSec);
94 auto lookup = find(thePath);
95 if (lookup == cend())
96 return false;
97 erase(lookup);
98 return true;
101 bool SafeClearRecursively(CString thePath)
103 thePath.MakeLower();
104 CAutoLocker lock(m_critIndexSec);
105 std::vector<CString> toRemove;
106 for (auto it = this->cbegin(); it != this->cend(); ++it)
108 if (CStringUtils::StartsWith((*it).first, thePath))
109 toRemove.push_back((*it).first);
111 for (auto it = toRemove.cbegin(); it != toRemove.cend(); ++it)
112 this->erase(*it);
113 return !toRemove.empty();
116 int Check(const CString &gitdir, bool *isChanged);
117 int LoadIndex(const CString &gitdir);
119 bool CheckAndUpdate(const CString& gitdir)
121 bool isChanged=false;
122 if (Check(gitdir, &isChanged))
123 return false;
125 if (isChanged)
127 LoadIndex(gitdir);
128 return true;
131 return false;
133 int GetFileStatus(const CString &gitdir,const CString &path,git_wc_status_kind * status,
134 BOOL IsFull=false, BOOL IsRecursive=false,
135 FILL_STATUS_CALLBACK callback = nullptr,
136 void* pData = nullptr, CGitHash* pHash = nullptr,
137 bool* assumeValid = nullptr, bool* skipWorktree = nullptr);
139 int IsUnderVersionControl(const CString &gitdir,
140 CString path,
141 bool isDir,
142 bool *isVersion);
146 class CGitTreeItem
148 public:
149 CString m_FileName;
150 CGitHash m_Hash;
151 int m_Flags;
154 /* After object create, never change field agains
155 * that needn't lock to get field
157 class CGitHeadFileList:public std::vector<CGitTreeItem>
159 private:
160 int GetPackRef(const CString &gitdir);
162 __time64_t m_LastModifyTimeHead;
163 __time64_t m_LastModifyTimeRef;
164 __time64_t m_LastModifyTimePackRef;
166 CString m_HeadRefFile;
167 CGitHash m_Head;
168 CString m_HeadFile;
169 CString m_Gitdir;
170 CString m_PackRefFile;
172 CGitHash m_TreeHash; /* buffered tree hash value */
174 std::map<CString,CGitHash> m_PackRefMap;
176 public:
177 CGitHeadFileList()
178 : m_LastModifyTimeHead(0)
179 , m_LastModifyTimeRef(0)
180 , m_LastModifyTimePackRef(0)
184 int ReadTree();
185 int ReadHeadHash(const CString& gitdir);
186 bool CheckHeadUpdate();
187 bool HeadHashEqualsTreeHash();
188 bool HeadFileIsEmpty();
189 bool HeadIsEmpty();
190 static int CallBack(const unsigned char *, const char *, int, const char *, unsigned int, int, void *);
193 typedef std::shared_ptr<CGitHeadFileList> SHARED_TREE_PTR;
194 class CGitHeadFileMap:public std::map<CString,SHARED_TREE_PTR>
196 public:
198 CComCriticalSection m_critTreeSec;
200 CGitHeadFileMap() { m_critTreeSec.Init(); }
201 ~CGitHeadFileMap() { m_critTreeSec.Term(); }
203 SHARED_TREE_PTR SafeGet(CString thePath, bool allowEmpty = false)
205 thePath.MakeLower();
206 CAutoLocker lock(m_critTreeSec);
207 auto lookup = find(thePath);
208 if (lookup == cend())
210 if (allowEmpty)
211 return SHARED_TREE_PTR();
212 return std::make_shared<CGitHeadFileList>();
214 return lookup->second;
217 void SafeSet(CString thePath, SHARED_TREE_PTR ptr)
219 thePath.MakeLower();
220 CAutoLocker lock(m_critTreeSec);
221 (*this)[thePath] = ptr;
224 bool SafeClear(CString thePath)
226 thePath.MakeLower();
227 CAutoLocker lock(m_critTreeSec);
228 auto lookup = find(thePath);
229 if (lookup == cend())
230 return false;
231 erase(lookup);
232 return true;
235 bool SafeClearRecursively(CString thePath)
237 thePath.MakeLower();
238 CAutoLocker lock(m_critTreeSec);
239 std::vector<CString> toRemove;
240 for (auto it = this->cbegin(); it != this->cend(); ++it)
242 if (CStringUtils::StartsWith((*it).first, thePath))
243 toRemove.push_back((*it).first);
245 for (auto it = toRemove.cbegin(); it != toRemove.cend(); ++it)
246 this->erase(*it);
247 return !toRemove.empty();
250 int GetFileStatus(const CString &gitdir,const CString &path,git_wc_status_kind * status,BOOL IsFull=false, BOOL IsRecursive=false,
251 FILL_STATUS_CALLBACK callback = nullptr, void *pData = nullptr,
252 bool isLoaded=false);
253 bool CheckHeadAndUpdate(const CString& gitdir);
254 int IsUnderVersionControl(const CString& gitdir, CString path, bool isDir, bool* isVersion);
257 class CGitFileName
259 public:
260 CGitFileName() {}
261 CGitFileName(LPCTSTR filename)
262 : m_FileName(filename)
265 CString m_FileName;
268 static bool SortCGitFileName(const CGitFileName& item1, const CGitFileName& item2)
270 return item1.m_FileName.Compare(item2.m_FileName) < 0;
273 class CGitIgnoreItem
275 public:
276 CGitIgnoreItem()
277 : m_LastModifyTime(0)
278 , m_pExcludeList(nullptr)
279 , m_buffer(nullptr)
280 , m_iIgnoreCase(nullptr)
284 ~CGitIgnoreItem()
286 if(m_pExcludeList)
287 git_free_exclude_list(m_pExcludeList);
288 free(m_buffer);
291 __time64_t m_LastModifyTime;
292 CStringA m_BaseDir;
293 BYTE *m_buffer;
294 EXCLUDE_LIST m_pExcludeList;
295 int* m_iIgnoreCase;
297 int FetchIgnoreList(const CString& projectroot, const CString& file, bool isGlobal, int* ignoreCase);
300 * patha: the filename to be checked whether is is ignored or not
301 * base: must be a pointer to the beginning of the base filename WITHIN patha
302 * type: DT_DIR or DT_REG
304 int IsPathIgnored(const CStringA& patha, const char* base, int& type);
305 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
306 int IsPathIgnored(const CStringA& patha, int& type);
307 #endif
310 class CGitIgnoreList
312 private:
313 bool CheckFileChanged(const CString &path);
314 int FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal);
316 int CheckIgnore(const CString &path, const CString &root, bool isDir);
317 int CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type);
319 // core.excludesfile stuff
320 std::map<CString, CString> m_CoreExcludesfiles;
321 std::map<CString, int> m_IgnoreCase;
322 CString m_sGitSystemConfigPath;
323 CString m_sGitProgramDataConfigPath;
324 ULONGLONG m_dGitSystemConfigPathLastChecked;
325 CReaderWriterLock m_coreExcludefilesSharedMutex;
326 // checks if the msysgit path has changed and return true/false
327 // if the path changed, the cache is update
328 // force is only ised in constructor
329 bool CheckAndUpdateGitSystemConfigPath(bool force = true);
330 bool CheckAndUpdateCoreExcludefile(const CString &adminDir);
331 const CString GetWindowsHome();
333 public:
334 CReaderWriterLock m_SharedMutex;
336 CGitIgnoreList(){ CheckAndUpdateGitSystemConfigPath(true); }
338 std::map<CString, CGitIgnoreItem> m_Map;
340 bool CheckAndUpdateIgnoreFiles(const CString& gitdir, const CString& path, bool isDir);
341 bool IsIgnore(CString path, const CString& root, bool isDir);
344 static const size_t NPOS = (size_t)-1; // bad/missing length/position
345 static_assert(MAXSIZE_T == NPOS, "NPOS must equal MAXSIZE_T");
346 #pragma warning(push)
347 #pragma warning(disable: 4310)
348 static_assert(-1 == (int)NPOS, "NPOS must equal -1");
349 #pragma warning(pop)
351 template<class T>
352 int GetRangeInSortVector(const T& vector, LPCTSTR pstr, size_t len, size_t* start, size_t* end, size_t pos)
354 if (pos == NPOS)
355 return -1;
356 if (!start || !end)
357 return -1;
359 *start = *end = NPOS;
361 if (vector.empty())
362 return -1;
364 if (pos >= vector.size())
365 return -1;
367 if (wcsncmp(vector[pos].m_FileName, pstr, len) != 0)
368 return -1;
370 *start = 0;
371 *end = vector.size() - 1;
373 // shortcut, if all entries are going match
374 if (!len)
375 return 0;
377 for (size_t i = pos; i < vector.size(); ++i)
379 if (wcsncmp(vector[i].m_FileName, pstr, len) != 0)
380 break;
382 *end = i;
384 for (size_t i = pos + 1; i-- > 0;)
386 if (wcsncmp(vector[i].m_FileName, pstr, len) != 0)
387 break;
389 *start = i;
392 return 0;
395 template<class T>
396 size_t SearchInSortVector(const T& vector, LPCTSTR pstr, int len)
398 size_t end = vector.size() - 1;
399 size_t start = 0;
400 size_t mid = (start + end) / 2;
402 if (vector.empty())
403 return NPOS;
405 while(!( start == end && start==mid))
407 int cmp;
408 if(len < 0)
409 cmp = wcscmp(vector[mid].m_FileName, pstr);
410 else
411 cmp = wcsncmp(vector[mid].m_FileName, pstr, len);
413 if (cmp == 0)
414 return mid;
415 else if (cmp < 0)
416 start = mid + 1;
417 else // (cmp > 0)
418 end = mid;
420 mid=(start +end ) /2;
423 if(len <0)
425 if (wcscmp(vector[mid].m_FileName, pstr) == 0)
426 return mid;
428 else
430 if (wcsncmp(vector[mid].m_FileName, pstr, len) == 0)
431 return mid;
433 return NPOS;
436 class CGitAdminDirMap:public std::map<CString, CString>
438 public:
439 CComCriticalSection m_critIndexSec;
440 std::map<CString, CString> m_reverseLookup;
441 std::map<CString, CString> m_WorktreeAdminDirLookup;
443 CGitAdminDirMap() { m_critIndexSec.Init(); }
444 ~CGitAdminDirMap() { m_critIndexSec.Term(); }
446 CString GetAdminDir(const CString &path)
448 CString thePath(CPathUtils::NormalizePath(path));
449 CAutoLocker lock(m_critIndexSec);
450 auto lookup = find(thePath);
451 if (lookup == cend())
453 CString adminDir;
454 GitAdminDir::GetAdminDirPath(thePath, adminDir);
455 if (PathIsDirectory(adminDir))
457 adminDir = CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(adminDir));
458 (*this)[thePath] = adminDir;
459 m_reverseLookup[adminDir] = thePath;
460 return (*this)[thePath];
462 return thePath + L".git\\"; // in case of an error stick to old behavior
465 return lookup->second;
468 void ResetAdminDirCache(const CString& path)
470 CString thePath(path);
471 thePath.MakeLower();
472 CAutoLocker lock(m_critIndexSec);
473 auto lookup = find(thePath);
474 if (lookup == cend())
475 return;
476 m_reverseLookup.erase(lookup->second.MakeLower().TrimRight(L'\\'));
477 erase(lookup);
480 CString GetAdminDirConcat(const CString& path, const CString& subpath)
482 CString result(GetAdminDir(path));
483 result += subpath;
484 return result;
487 CString GetWorktreeAdminDir(const CString& path)
489 CString thePath(CPathUtils::NormalizePath(path));
490 CAutoLocker lock(m_critIndexSec);
491 auto lookup = m_WorktreeAdminDirLookup.find(thePath);
492 if (lookup == m_WorktreeAdminDirLookup.cend())
494 CString wtadmindir;
495 GitAdminDir::GetWorktreeAdminDirPath(thePath, wtadmindir);
496 if (PathIsDirectory(wtadmindir))
498 wtadmindir = CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(wtadmindir));
499 m_WorktreeAdminDirLookup[thePath] = wtadmindir;
500 m_reverseLookup[wtadmindir] = thePath;
501 return m_WorktreeAdminDirLookup[thePath];
503 ATLASSERT(false);
504 return thePath + L".git\\"; // we should never get here
506 return lookup->second;
509 CString GetWorktreeAdminDirConcat(const CString& path, const CString& subpath)
511 CString result(GetWorktreeAdminDir(path));
512 result += subpath;
513 return result;
516 CString GetWorkingCopy(const CString &gitDir)
518 CString path(CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(gitDir)));
519 CAutoLocker lock(m_critIndexSec);
520 auto lookup = m_reverseLookup.find(path);
521 if (lookup == m_reverseLookup.cend())
522 return gitDir;
523 return lookup->second;