Improve overlay status for symlinks
[TortoiseGit.git] / src / Git / gitindex.h
blob3d607e3784542dced9be6a7d2be2db7a42982c99
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 int m_iIndexCaps;
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 __int64 m_iMaxCheckSize;
71 CAutoConfig config;
72 int GetFileStatus(const CString& gitdir, const CString& path, git_wc_status2_t& status, __int64 time, __int64 filesize, bool isSymlink, CGitHash* pHash = nullptr);
75 typedef std::shared_ptr<CGitIndexList> SHARED_INDEX_PTR;
76 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
78 class CGitIndexFileMap:public std::map<CString, SHARED_INDEX_PTR>
80 public:
81 CComCriticalSection m_critIndexSec;
83 CGitIndexFileMap() { m_critIndexSec.Init(); }
84 ~CGitIndexFileMap() { m_critIndexSec.Term(); }
86 SHARED_INDEX_PTR SafeGet(const CString& path)
88 CString thePath(CPathUtils::NormalizePath(path));
89 CAutoLocker lock(m_critIndexSec);
90 auto lookup = find(thePath);
91 if (lookup == cend())
92 return SHARED_INDEX_PTR();
93 return lookup->second;
96 void SafeSet(const CString& path, SHARED_INDEX_PTR ptr)
98 CString thePath(CPathUtils::NormalizePath(path));
99 CAutoLocker lock(m_critIndexSec);
100 (*this)[thePath] = ptr;
103 bool SafeClear(const CString& path)
105 CString thePath(CPathUtils::NormalizePath(path));
106 CAutoLocker lock(m_critIndexSec);
107 auto lookup = find(thePath);
108 if (lookup == cend())
109 return false;
110 erase(lookup);
111 return true;
114 bool SafeClearRecursively(const CString& path)
116 CString thePath(CPathUtils::NormalizePath(path));
117 CAutoLocker lock(m_critIndexSec);
118 std::vector<CString> toRemove;
119 for (auto it = this->cbegin(); it != this->cend(); ++it)
121 if (CStringUtils::StartsWith((*it).first, thePath))
122 toRemove.push_back((*it).first);
124 for (auto it = toRemove.cbegin(); it != toRemove.cend(); ++it)
125 this->erase(*it);
126 return !toRemove.empty();
129 bool HasIndexChangedOnDisk(const CString& gitdir);
130 int LoadIndex(const CString &gitdir);
132 void CheckAndUpdate(const CString& gitdir)
134 if (HasIndexChangedOnDisk(gitdir))
135 LoadIndex(gitdir);
139 class CGitTreeItem
141 public:
142 CString m_FileName;
143 CGitHash m_Hash;
144 int m_Flags;
147 /* After object create, never change field agains
148 * that needn't lock to get field
150 class CGitHeadFileList:public std::vector<CGitTreeItem>
152 private:
153 int GetPackRef(const CString &gitdir);
155 __time64_t m_LastModifyTimeHead;
156 __time64_t m_LastModifyTimeRef;
157 __time64_t m_LastModifyTimePackRef;
159 __int64 m_LastFileSizeHead;
160 __int64 m_LastFileSizePackRef;
162 CString m_HeadRefFile;
163 CGitHash m_Head;
164 CString m_HeadFile;
165 CString m_Gitdir;
166 CString m_PackRefFile;
168 std::map<CString,CGitHash> m_PackRefMap;
170 public:
171 CGitHeadFileList()
172 : m_LastModifyTimeHead(0)
173 , m_LastModifyTimeRef(0)
174 , m_LastModifyTimePackRef(0)
175 , m_LastFileSizeHead(-1)
176 , m_LastFileSizePackRef(-1)
180 int ReadTree();
181 int ReadHeadHash(const CString& gitdir);
182 bool CheckHeadUpdate();
183 static int CallBack(const unsigned char *, const char *, int, const char *, unsigned int, int, void *);
186 typedef std::shared_ptr<CGitHeadFileList> SHARED_TREE_PTR;
187 class CGitHeadFileMap:public std::map<CString,SHARED_TREE_PTR>
189 public:
191 CComCriticalSection m_critTreeSec;
193 CGitHeadFileMap() { m_critTreeSec.Init(); }
194 ~CGitHeadFileMap() { m_critTreeSec.Term(); }
196 SHARED_TREE_PTR SafeGet(const CString& path)
198 CString thePath(CPathUtils::NormalizePath(path));
199 CAutoLocker lock(m_critTreeSec);
200 auto lookup = find(thePath);
201 if (lookup == cend())
202 return SHARED_TREE_PTR();
203 return lookup->second;
206 void SafeSet(const CString& path, SHARED_TREE_PTR ptr)
208 CString thePath(CPathUtils::NormalizePath(path));
209 CAutoLocker lock(m_critTreeSec);
210 (*this)[thePath] = ptr;
213 bool SafeClear(const CString& path)
215 CString thePath(CPathUtils::NormalizePath(path));
216 CAutoLocker lock(m_critTreeSec);
217 auto lookup = find(thePath);
218 if (lookup == cend())
219 return false;
220 erase(lookup);
221 return true;
224 bool SafeClearRecursively(const CString& path)
226 CString thePath(CPathUtils::NormalizePath(path));
227 CAutoLocker lock(m_critTreeSec);
228 std::vector<CString> toRemove;
229 for (auto it = this->cbegin(); it != this->cend(); ++it)
231 if (CStringUtils::StartsWith((*it).first, thePath))
232 toRemove.push_back((*it).first);
234 for (auto it = toRemove.cbegin(); it != toRemove.cend(); ++it)
235 this->erase(*it);
236 return !toRemove.empty();
238 void CheckHeadAndUpdate(const CString& gitdir);
241 class CGitFileName
243 public:
244 CGitFileName() {}
245 CGitFileName(LPCTSTR filename, __int64 size, __int64 lastmodified)
246 : m_FileName(filename)
247 , m_Size(size)
248 , m_LastModified(lastmodified)
249 , m_bSymlink(false)
252 CString m_FileName;
253 __int64 m_Size;
254 __int64 m_LastModified;
255 bool m_bSymlink;
258 static bool SortCGitFileName(const CGitFileName& item1, const CGitFileName& item2)
260 return item1.m_FileName.Compare(item2.m_FileName) < 0;
263 class CGitIgnoreItem
265 public:
266 CGitIgnoreItem()
267 : m_LastModifyTime(0)
268 , m_LastFileSize(-1)
269 , m_pExcludeList(nullptr)
270 , m_buffer(nullptr)
271 , m_iIgnoreCase(nullptr)
275 ~CGitIgnoreItem()
277 if(m_pExcludeList)
278 git_free_exclude_list(m_pExcludeList);
279 free(m_buffer);
282 __time64_t m_LastModifyTime;
283 __int64 m_LastFileSize;
284 CStringA m_BaseDir;
285 BYTE *m_buffer;
286 EXCLUDE_LIST m_pExcludeList;
287 int* m_iIgnoreCase;
289 int FetchIgnoreList(const CString& projectroot, const CString& file, bool isGlobal, int* ignoreCase);
292 * patha: the filename to be checked whether is is ignored or not
293 * base: must be a pointer to the beginning of the base filename WITHIN patha
294 * type: DT_DIR or DT_REG
296 int IsPathIgnored(const CStringA& patha, const char* base, int& type);
297 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
298 int IsPathIgnored(const CStringA& patha, int& type);
299 #endif
302 class CGitIgnoreList
304 private:
305 bool CheckFileChanged(const CString &path);
306 int FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal);
308 int CheckIgnore(const CString &path, const CString &root, bool isDir);
309 int CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type);
311 // core.excludesfile stuff
312 std::map<CString, CString> m_CoreExcludesfiles;
313 std::map<CString, int> m_IgnoreCase;
314 CString m_sGitSystemConfigPath;
315 CString m_sGitProgramDataConfigPath;
316 ULONGLONG m_dGitSystemConfigPathLastChecked;
317 CReaderWriterLock m_coreExcludefilesSharedMutex;
318 // checks if the msysgit path has changed and return true/false
319 // if the path changed, the cache is update
320 // force is only ised in constructor
321 bool CheckAndUpdateGitSystemConfigPath(bool force = true);
322 bool CheckAndUpdateCoreExcludefile(const CString &adminDir);
323 const CString GetWindowsHome();
325 public:
326 CReaderWriterLock m_SharedMutex;
328 CGitIgnoreList(){ CheckAndUpdateGitSystemConfigPath(true); }
330 std::map<CString, CGitIgnoreItem> m_Map;
332 bool CheckAndUpdateIgnoreFiles(const CString& gitdir, const CString& path, bool isDir);
333 bool IsIgnore(CString path, const CString& root, bool isDir);
336 static const size_t NPOS = (size_t)-1; // bad/missing length/position
337 static_assert(MAXSIZE_T == NPOS, "NPOS must equal MAXSIZE_T");
338 #pragma warning(push)
339 #pragma warning(disable: 4310)
340 static_assert(-1 == (int)NPOS, "NPOS must equal -1");
341 #pragma warning(pop)
343 template<class T>
344 int GetRangeInSortVector(const T& vector, LPCTSTR pstr, size_t len, size_t* start, size_t* end, size_t pos)
346 if (pos == NPOS)
347 return -1;
348 if (!start || !end)
349 return -1;
351 *start = *end = NPOS;
353 if (vector.empty())
354 return -1;
356 if (pos >= vector.size())
357 return -1;
359 if (wcsncmp(vector[pos].m_FileName, pstr, len) != 0)
360 return -1;
362 *start = 0;
363 *end = vector.size() - 1;
365 // shortcut, if all entries are going match
366 if (!len)
367 return 0;
369 for (size_t i = pos; i < vector.size(); ++i)
371 if (wcsncmp(vector[i].m_FileName, pstr, len) != 0)
372 break;
374 *end = i;
376 for (size_t i = pos + 1; i-- > 0;)
378 if (wcsncmp(vector[i].m_FileName, pstr, len) != 0)
379 break;
381 *start = i;
384 return 0;
387 template<class T>
388 size_t SearchInSortVector(const T& vector, LPCTSTR pstr, int len)
390 size_t end = vector.size() - 1;
391 size_t start = 0;
392 size_t mid = (start + end) / 2;
394 if (vector.empty())
395 return NPOS;
397 while(!( start == end && start==mid))
399 int cmp;
400 if(len < 0)
401 cmp = wcscmp(vector[mid].m_FileName, pstr);
402 else
403 cmp = wcsncmp(vector[mid].m_FileName, pstr, len);
405 if (cmp == 0)
406 return mid;
407 else if (cmp < 0)
408 start = mid + 1;
409 else // (cmp > 0)
410 end = mid;
412 mid=(start +end ) /2;
415 if(len <0)
417 if (wcscmp(vector[mid].m_FileName, pstr) == 0)
418 return mid;
420 else
422 if (wcsncmp(vector[mid].m_FileName, pstr, len) == 0)
423 return mid;
425 return NPOS;
428 class CGitAdminDirMap:public std::map<CString, CString>
430 public:
431 CComCriticalSection m_critIndexSec;
432 std::map<CString, CString> m_reverseLookup;
433 std::map<CString, CString> m_WorktreeAdminDirLookup;
435 CGitAdminDirMap() { m_critIndexSec.Init(); }
436 ~CGitAdminDirMap() { m_critIndexSec.Term(); }
438 CString GetAdminDir(const CString &path)
440 CString thePath(CPathUtils::NormalizePath(path));
441 CAutoLocker lock(m_critIndexSec);
442 auto lookup = find(thePath);
443 if (lookup == cend())
445 CString adminDir;
446 bool isWorktree = false;
447 GitAdminDir::GetAdminDirPath(thePath, adminDir, &isWorktree);
448 if (PathIsDirectory(adminDir))
450 adminDir = CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(adminDir));
451 (*this)[thePath] = adminDir;
452 if (!isWorktree) // GitAdminDir::GetAdminDirPath returns the commongit dir ("parent/.git") and this would override the lookup path for the main repo
453 m_reverseLookup[adminDir] = thePath;
454 return (*this)[thePath];
456 return thePath + L".git\\"; // in case of an error stick to old behavior
459 return lookup->second;
462 CString GetAdminDirConcat(const CString& path, const CString& subpath)
464 CString result(GetAdminDir(path));
465 result += subpath;
466 return result;
469 CString GetWorktreeAdminDir(const CString& path)
471 CString thePath(CPathUtils::NormalizePath(path));
472 CAutoLocker lock(m_critIndexSec);
473 auto lookup = m_WorktreeAdminDirLookup.find(thePath);
474 if (lookup == m_WorktreeAdminDirLookup.cend())
476 CString wtadmindir;
477 GitAdminDir::GetWorktreeAdminDirPath(thePath, wtadmindir);
478 if (PathIsDirectory(wtadmindir))
480 wtadmindir = CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(wtadmindir));
481 m_WorktreeAdminDirLookup[thePath] = wtadmindir;
482 m_reverseLookup[wtadmindir] = thePath;
483 return m_WorktreeAdminDirLookup[thePath];
485 ATLASSERT(false);
486 return thePath + L".git\\"; // we should never get here
488 return lookup->second;
491 CString GetWorktreeAdminDirConcat(const CString& path, const CString& subpath)
493 CString result(GetWorktreeAdminDir(path));
494 result += subpath;
495 return result;
498 CString GetWorkingCopy(const CString &gitDir)
500 CString path(CPathUtils::BuildPathWithPathDelimiter(CPathUtils::NormalizePath(gitDir)));
501 CAutoLocker lock(m_critIndexSec);
502 auto lookup = m_reverseLookup.find(path);
503 if (lookup == m_reverseLookup.cend())
504 return gitDir;
505 return lookup->second;