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"
33 __time64_t m_ModifyTime
;
35 uint16_t m_FlagsExtended
;
42 class CGitIndexList
:public std::vector
<CGitIndex
>
45 __time64_t m_LastModifyTime
;
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
);
57 __int64 m_iMaxCheckSize
;
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
>
68 CComCriticalSection m_critIndexSec
;
70 CGitIndexFileMap() { m_critIndexSec
.Init(); }
71 ~CGitIndexFileMap() { m_critIndexSec
.Term(); }
73 SHARED_INDEX_PTR
SafeGet(CString thePath
)
76 CAutoLocker
lock(m_critIndexSec
);
77 auto lookup
= find(thePath
);
79 return SHARED_INDEX_PTR();
80 return lookup
->second
;
83 void SafeSet(CString thePath
, SHARED_INDEX_PTR ptr
)
86 CAutoLocker
lock(m_critIndexSec
);
87 (*this)[thePath
] = ptr
;
90 bool SafeClear(CString thePath
)
93 CAutoLocker
lock(m_critIndexSec
);
94 auto lookup
= find(thePath
);
101 bool SafeClearRecursively(CString thePath
)
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
)
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
))
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
,
154 /* After object create, never change field agains
155 * that needn't lock to get field
157 class CGitHeadFileList
:public std::vector
<CGitTreeItem
>
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
;
170 CString m_PackRefFile
;
172 CGitHash m_TreeHash
; /* buffered tree hash value */
174 std::map
<CString
,CGitHash
> m_PackRefMap
;
178 : m_LastModifyTimeHead(0)
179 , m_LastModifyTimeRef(0)
180 , m_LastModifyTimePackRef(0)
185 int ReadHeadHash(const CString
& gitdir
);
186 bool CheckHeadUpdate();
187 bool HeadHashEqualsTreeHash();
188 bool HeadFileIsEmpty();
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
>
198 CComCriticalSection m_critTreeSec
;
200 CGitHeadFileMap() { m_critTreeSec
.Init(); }
201 ~CGitHeadFileMap() { m_critTreeSec
.Term(); }
203 SHARED_TREE_PTR
SafeGet(CString thePath
, bool allowEmpty
= false)
206 CAutoLocker
lock(m_critTreeSec
);
207 auto lookup
= find(thePath
);
208 if (lookup
== cend())
211 return SHARED_TREE_PTR();
212 return std::make_shared
<CGitHeadFileList
>();
214 return lookup
->second
;
217 void SafeSet(CString thePath
, SHARED_TREE_PTR ptr
)
220 CAutoLocker
lock(m_critTreeSec
);
221 (*this)[thePath
] = ptr
;
224 bool SafeClear(CString thePath
)
227 CAutoLocker
lock(m_critTreeSec
);
228 auto lookup
= find(thePath
);
229 if (lookup
== cend())
235 bool SafeClearRecursively(CString thePath
)
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
)
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
);
261 CGitFileName(LPCTSTR filename
)
262 : m_FileName(filename
)
268 static bool SortCGitFileName(const CGitFileName
& item1
, const CGitFileName
& item2
)
270 return item1
.m_FileName
.Compare(item2
.m_FileName
) < 0;
277 : m_LastModifyTime(0)
278 , m_pExcludeList(nullptr)
280 , m_iIgnoreCase(nullptr)
287 git_free_exclude_list(m_pExcludeList
);
291 __time64_t m_LastModifyTime
;
294 EXCLUDE_LIST m_pExcludeList
;
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
);
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();
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");
352 int GetRangeInSortVector(const T
& vector
, LPCTSTR pstr
, size_t len
, size_t* start
, size_t* end
, size_t pos
)
359 *start
= *end
= NPOS
;
364 if (pos
>= vector
.size())
367 if (wcsncmp(vector
[pos
].m_FileName
, pstr
, len
) != 0)
371 *end
= vector
.size() - 1;
373 // shortcut, if all entries are going match
377 for (size_t i
= pos
; i
< vector
.size(); ++i
)
379 if (wcsncmp(vector
[i
].m_FileName
, pstr
, len
) != 0)
384 for (size_t i
= pos
+ 1; i
-- > 0;)
386 if (wcsncmp(vector
[i
].m_FileName
, pstr
, len
) != 0)
396 size_t SearchInSortVector(const T
& vector
, LPCTSTR pstr
, int len
)
398 size_t end
= vector
.size() - 1;
400 size_t mid
= (start
+ end
) / 2;
405 while(!( start
== end
&& start
==mid
))
409 cmp
= wcscmp(vector
[mid
].m_FileName
, pstr
);
411 cmp
= wcsncmp(vector
[mid
].m_FileName
, pstr
, len
);
420 mid
=(start
+end
) /2;
425 if (wcscmp(vector
[mid
].m_FileName
, pstr
) == 0)
430 if (wcsncmp(vector
[mid
].m_FileName
, pstr
, len
) == 0)
436 class CGitAdminDirMap
:public std::map
<CString
, CString
>
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())
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
);
472 CAutoLocker
lock(m_critIndexSec
);
473 auto lookup
= find(thePath
);
474 if (lookup
== cend())
476 m_reverseLookup
.erase(lookup
->second
.MakeLower().TrimRight(L
'\\'));
480 CString
GetAdminDirConcat(const CString
& path
, const CString
& subpath
)
482 CString
result(GetAdminDir(path
));
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())
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
];
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
));
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())
523 return lookup
->second
;