1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - 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.
23 #include "UnicodeUtils.h"
26 #include <sys/types.h>
28 #include "SmartHandle.h"
29 #include "git2/sys/repository.h"
31 CGitAdminDirMap g_AdminDirMap
;
33 static CString
GetProgramDataGitConfig()
35 if (!((CRegDWORD(L
"Software\\TortoiseGit\\CygwinHack", FALSE
) == TRUE
) || (CRegDWORD(L
"Software\\TortoiseGit\\Msys2Hack", FALSE
) == TRUE
)))
37 CString programdataConfig
;
38 if (SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA
, NULL
, SHGFP_TYPE_CURRENT
, CStrBuf(programdataConfig
, MAX_PATH
)) == S_OK
&& programdataConfig
.GetLength() < MAX_PATH
- 11) /* 11 = len("\\Git\\config") */
39 return programdataConfig
+ L
"\\Git\\config";
44 int CGitIndex::Print()
46 wprintf(L
"0x%08X 0x%08X %s %s\n",
47 (int)this->m_ModifyTime
,
49 (LPCTSTR
)this->m_IndexHash
.ToString(),
50 (LPCTSTR
)this->m_FileName
);
55 CGitIndexList::CGitIndexList()
56 : m_bHasConflicts(FALSE
)
58 this->m_LastModifyTime
= 0;
60 m_iMaxCheckSize
= (__int64
)CRegDWORD(L
"Software\\TortoiseGit\\TGitCacheCheckContentMaxSize", 10 * 1024) * 1024; // stored in KiB
63 CGitIndexList::~CGitIndexList()
68 static bool SortIndex(const CGitIndex
&Item1
, const CGitIndex
&Item2
)
70 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
73 static bool SortTree(const CGitTreeItem
&Item1
, const CGitTreeItem
&Item2
)
75 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
78 int CGitIndexList::ReadIndex(CString dgitdir
)
82 CAutoLocker
lock(m_critRepoSec
);
83 if (repository
.Open(dgitdir
))
87 CAutoConfig
config(true);
89 CString projectConfig
= dgitdir
+ L
"config";
90 CString globalConfig
= g_Git
.GetGitGlobalConfig();
91 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
92 CString
systemConfig(CRegString(REG_SYSTEM_GITCONFIGPATH
, L
"", FALSE
));
93 CString
programDataConfig(GetProgramDataGitConfig());
95 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(projectConfig
), GIT_CONFIG_LEVEL_LOCAL
, FALSE
);
96 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalConfig
), GIT_CONFIG_LEVEL_GLOBAL
, FALSE
);
97 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalXDGConfig
), GIT_CONFIG_LEVEL_XDG
, FALSE
);
98 if (!systemConfig
.IsEmpty())
99 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(systemConfig
), GIT_CONFIG_LEVEL_SYSTEM
, FALSE
);
100 if (!programDataConfig
.IsEmpty())
101 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(programDataConfig
), GIT_CONFIG_LEVEL_PROGRAMDATA
, FALSE
);
103 git_repository_set_config(repository
, config
);
106 // load index in order to enumerate files
107 if (git_repository_index(index
.GetPointer(), repository
))
113 m_bHasConflicts
= FALSE
;
115 size_t ecount
= git_index_entrycount(index
);
120 catch (const std::bad_alloc
& ex
)
123 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Could not resize index-vector: %s\n", ex
.what());
126 for (size_t i
= 0; i
< ecount
; ++i
)
128 const git_index_entry
*e
= git_index_get_byindex(index
, i
);
130 auto& item
= (*this)[i
];
131 item
.m_FileName
= CUnicodeUtils::GetUnicode(e
->path
);
132 item
.m_FileName
.MakeLower();
133 item
.m_ModifyTime
= e
->mtime
.seconds
;
134 item
.m_Flags
= e
->flags
;
135 item
.m_FlagsExtended
= e
->flags_extended
;
136 item
.m_IndexHash
= e
->id
.id
;
137 item
.m_Size
= e
->file_size
;
138 m_bHasConflicts
|= GIT_IDXENTRY_STAGE(e
);
141 CGit::GetFileModifyTime(dgitdir
+ L
"index", &m_LastModifyTime
);
142 std::sort(this->begin(), this->end(), SortIndex
);
147 int CGitIndexList::GetFileStatus(const CString
&gitdir
, const CString
&pathorg
, git_wc_status_kind
*status
, __int64 time
, __int64 filesize
, FILL_STATUS_CALLBACK callback
, void *pData
, CGitHash
*pHash
, bool * assumeValid
, bool * skipWorktree
)
152 CString path
= pathorg
;
155 size_t index
= SearchInSortVector(*this, path
, -1);
159 *status
= git_wc_status_unversioned
;
163 if (callback
&& assumeValid
&& skipWorktree
)
164 callback(CombinePath(gitdir
, pathorg
), *status
, false, pData
, *assumeValid
, *skipWorktree
);
169 auto& entry
= (*this)[index
];
170 // skip-worktree has higher priority than assume-valid
171 if (entry
.m_FlagsExtended
& GIT_IDXENTRY_SKIP_WORKTREE
)
173 *status
= git_wc_status_normal
;
175 *skipWorktree
= true;
177 else if (entry
.m_Flags
& GIT_IDXENTRY_VALID
)
179 *status
= git_wc_status_normal
;
183 else if (filesize
!= entry
.m_Size
)
184 *status
= git_wc_status_modified
;
185 else if (time
== entry
.m_ModifyTime
)
186 *status
= git_wc_status_normal
;
187 else if (repository
&& filesize
< m_iMaxCheckSize
)
190 CStringA fileA
= CUnicodeUtils::GetMulti(pathorg
, CP_UTF8
);
191 m_critRepoSec
.Lock(); // prevent concurrent access to repository instance and especially filter-lists
192 if (!git_repository_hashfile(&actual
, repository
, fileA
, GIT_OBJ_BLOB
, nullptr) && !git_oid_cmp(&actual
, (const git_oid
*)entry
.m_IndexHash
.m_hash
))
194 entry
.m_ModifyTime
= time
;
195 *status
= git_wc_status_normal
;
198 *status
= git_wc_status_modified
;
199 m_critRepoSec
.Unlock();
202 *status
= git_wc_status_modified
;
204 if (entry
.m_Flags
& GIT_IDXENTRY_STAGEMASK
)
205 *status
= git_wc_status_conflicted
;
206 else if (entry
.m_FlagsExtended
& GIT_IDXENTRY_INTENT_TO_ADD
)
207 *status
= git_wc_status_added
;
210 *pHash
= entry
.m_IndexHash
;
212 if (callback
&& assumeValid
&& skipWorktree
)
213 callback(CombinePath(gitdir
, pathorg
), *status
, false, pData
, *assumeValid
, *skipWorktree
);
218 int CGitIndexList::GetStatus(const CString
& gitdir
, CString path
, git_wc_status_kind
* status
,
219 BOOL IsFull
, BOOL
/*IsRecursive*/,
220 FILL_STATUS_CALLBACK callback
, void *pData
,
221 CGitHash
*pHash
, bool * assumeValid
, bool * skipWorktree
)
223 __int64 time
, filesize
= 0;
229 git_wc_status_kind dirstatus
= git_wc_status_none
;
232 result
= CGit::GetFileModifyTime(gitdir
, &time
, &isDir
);
234 result
= CGit::GetFileModifyTime(CombinePath(gitdir
, path
), &time
, &isDir
, &filesize
);
238 *status
= git_wc_status_deleted
;
239 if (callback
&& assumeValid
&& skipWorktree
)
240 callback(CombinePath(gitdir
, path
), git_wc_status_deleted
, false, pData
, *assumeValid
, *skipWorktree
);
247 GetFileStatus(gitdir
, path
, status
, time
, filesize
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
251 if (!path
.IsEmpty() && !CStringUtils::EndsWith(path
, L
'\\'))
254 int len
= path
.GetLength();
256 for (auto it
= cbegin(), itend
= cend(); it
!= itend
; ++it
)
259 if (!(entry
.m_FileName
.GetLength() > len
&& wcsncmp(entry
.m_FileName
, path
, len
) == 0))
264 *status
= git_wc_status_normal
;
266 callback(CombinePath(gitdir
, path
), *status
, false, pData
, (entry
.m_Flags
& GIT_IDXENTRY_VALID
) && !(entry
.m_FlagsExtended
& GIT_IDXENTRY_SKIP_WORKTREE
), (entry
.m_FlagsExtended
& GIT_IDXENTRY_SKIP_WORKTREE
) != 0);
271 result
= CGit::GetFileModifyTime(CombinePath(gitdir
, entry
.m_FileName
), &time
, nullptr, &filesize
);
275 *status
= git_wc_status_none
;
277 *assumeValid
= false;
279 *skipWorktree
= false;
281 GetFileStatus(gitdir
, entry
.m_FileName
, status
, time
, filesize
, callback
, pData
, nullptr, assumeValid
, skipWorktree
);
283 // if a file is assumed valid, we need to inform the caller, otherwise the assumevalid flag might not get to the explorer on first open of a repository
284 if (callback
&& assumeValid
&& skipWorktree
&& (*assumeValid
|| *skipWorktree
))
285 callback(CombinePath(gitdir
, path
), *status
, false, pData
, *assumeValid
, *skipWorktree
);
286 if (*status
!= git_wc_status_none
)
288 if (dirstatus
== git_wc_status_none
)
289 dirstatus
= git_wc_status_normal
;
290 if (*status
!= git_wc_status_normal
)
291 dirstatus
= git_wc_status_modified
;
295 if (dirstatus
!= git_wc_status_none
)
298 *status
= git_wc_status_unversioned
;
301 callback(CombinePath(gitdir
, path
), *status
, false, pData
, false, false);
306 int CGitIndexFileMap::Check(const CString
&gitdir
, bool *isChanged
)
310 CString IndexFile
= g_AdminDirMap
.GetAdminDirConcat(gitdir
, L
"index");
312 if (CGit::GetFileModifyTime(IndexFile
, &time
))
315 SHARED_INDEX_PTR pIndex
;
316 pIndex
= this->SafeGet(gitdir
);
325 if (pIndex
->m_LastModifyTime
== time
)
338 int CGitIndexFileMap::LoadIndex(const CString
&gitdir
)
340 SHARED_INDEX_PTR pIndex
= std::make_shared
<CGitIndexList
>();
342 if (pIndex
->ReadIndex(g_AdminDirMap
.GetAdminDir(gitdir
)))
345 this->SafeSet(gitdir
, pIndex
);
350 int CGitIndexFileMap::GetFileStatus(const CString
&gitdir
, const CString
&path
, git_wc_status_kind
*status
,BOOL IsFull
, BOOL IsRecursive
,
351 FILL_STATUS_CALLBACK callback
, void *pData
,
353 bool isLoadUpdatedIndex
, bool * assumeValid
, bool * skipWorktree
)
355 CheckAndUpdate(gitdir
, isLoadUpdatedIndex
);
357 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
359 pIndex
->GetStatus(gitdir
, path
, status
, IsFull
, IsRecursive
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
362 // git working tree has not index
363 *status
= git_wc_status_unversioned
;
369 int CGitIndexFileMap::IsUnderVersionControl(const CString
& gitdir
, CString subpath
, bool isDir
, bool* isVersion
, bool isLoadUpdateIndex
)
371 if (subpath
.IsEmpty())
377 subpath
.Replace(L
'\\', L
'/');
383 CheckAndUpdate(gitdir
, isLoadUpdateIndex
);
385 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
390 *isVersion
= (SearchInSortVector(*pIndex
, subpath
, subpath
.GetLength()) != NPOS
);
392 *isVersion
= (SearchInSortVector(*pIndex
, subpath
, -1) != NPOS
);
398 // This method is assumed to be called with m_SharedMutex locked.
399 int CGitHeadFileList::GetPackRef(const CString
&gitdir
)
401 CString PackRef
= g_AdminDirMap
.GetAdminDirConcat(gitdir
, L
"packed-refs");
404 if (CGit::GetFileModifyTime(PackRef
, &mtime
))
406 //packed refs is not existed
407 this->m_PackRefFile
.Empty();
408 this->m_PackRefMap
.clear();
411 else if(mtime
== m_LastModifyTimePackRef
)
415 this->m_PackRefFile
= PackRef
;
416 this->m_LastModifyTimePackRef
= mtime
;
419 m_PackRefMap
.clear();
421 CAutoFile hfile
= CreateFile(PackRef
,
423 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
426 FILE_ATTRIBUTE_NORMAL
,
432 DWORD filesize
= GetFileSize(hfile
, nullptr);
437 auto buff
= std::make_unique
<char[]>(filesize
);
438 ReadFile(hfile
, buff
.get(), filesize
, &size
, nullptr);
440 if (size
!= filesize
)
443 for (DWORD i
= 0; i
< filesize
;)
447 if (buff
[i
] == '#' || buff
[i
] == '^')
449 while (buff
[i
] != '\n')
461 while (buff
[i
] != ' ')
463 hash
.AppendChar(buff
[i
]);
473 while (buff
[i
] != '\n')
475 ref
.AppendChar(buff
[i
]);
482 m_PackRefMap
[ref
] = hash
;
484 while (buff
[i
] == '\n')
493 int CGitHeadFileList::ReadHeadHash(const CString
& gitdir
)
495 CAutoWriteLock
lock(m_SharedMutex
);
496 m_Gitdir
= g_AdminDirMap
.GetAdminDir(gitdir
);
498 m_HeadFile
= m_Gitdir
;
499 m_HeadFile
+= L
"HEAD";
501 if( CGit::GetFileModifyTime(m_HeadFile
, &m_LastModifyTimeHead
))
504 CAutoFile hfile
= CreateFile(m_HeadFile
,
506 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
509 FILE_ATTRIBUTE_NORMAL
,
516 unsigned char buffer
[40];
517 ReadFile(hfile
, buffer
, 4, &size
, nullptr);
521 if (strcmp((const char*)buffer
, "ref:") == 0)
523 m_HeadRefFile
.Empty();
524 DWORD filesize
= GetFileSize(hfile
, nullptr);
528 unsigned char *p
= (unsigned char*)malloc(filesize
- 4);
532 ReadFile(hfile
, p
, filesize
- 4, &size
, nullptr);
533 CGit::StringAppend(&m_HeadRefFile
, p
, CP_UTF8
, filesize
- 4);
536 CString ref
= m_HeadRefFile
.Trim();
538 ref
= ref
.Tokenize(L
"\n", start
);
539 m_HeadRefFile
= m_Gitdir
+ m_HeadRefFile
;
540 m_HeadRefFile
.Replace(L
'/', L
'\\');
543 if (CGit::GetFileModifyTime(m_HeadRefFile
, &time
, nullptr))
545 m_HeadRefFile
.Empty();
546 if (GetPackRef(gitdir
))
548 if (m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
551 m_Head
= m_PackRefMap
[ref
];
555 CAutoFile href
= CreateFile(m_HeadRefFile
,
557 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
560 FILE_ATTRIBUTE_NORMAL
,
565 m_HeadRefFile
.Empty();
567 if (GetPackRef(gitdir
))
570 if (m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
573 m_Head
= m_PackRefMap
[ref
];
577 ReadFile(href
, buffer
, 40, &size
, nullptr);
581 m_Head
.ConvertFromStrA((char*)buffer
);
583 m_LastModifyTimeRef
= time
;
588 ReadFile(hfile
, buffer
+ 4, 40 - 4, &size
, nullptr);
592 m_HeadRefFile
.Empty();
594 m_Head
.ConvertFromStrA((char*)buffer
);
599 bool CGitHeadFileList::CheckHeadUpdate()
601 CAutoReadLock
lock(m_SharedMutex
);
602 if (this->m_HeadFile
.IsEmpty())
607 if (CGit::GetFileModifyTime(m_HeadFile
, &mtime
))
610 if (mtime
!= this->m_LastModifyTimeHead
)
613 if (!this->m_HeadRefFile
.IsEmpty())
615 if (CGit::GetFileModifyTime(m_HeadRefFile
, &mtime
))
618 if (mtime
!= this->m_LastModifyTimeRef
)
622 if(!this->m_PackRefFile
.IsEmpty())
624 if (CGit::GetFileModifyTime(m_PackRefFile
, &mtime
))
627 if (mtime
!= this->m_LastModifyTimePackRef
)
631 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
632 // So we need to retry again and again until the ref exists - otherwise we will never notice
633 if (this->m_Head
.IsEmpty() && this->m_HeadRefFile
.IsEmpty() && this->m_PackRefFile
.IsEmpty())
639 bool CGitHeadFileList::HeadHashEqualsTreeHash()
641 CAutoReadLock
lock(m_SharedMutex
);
642 return (m_Head
== m_TreeHash
);
645 bool CGitHeadFileList::HeadFileIsEmpty()
647 CAutoReadLock
lock(m_SharedMutex
);
648 return m_HeadFile
.IsEmpty();
651 bool CGitHeadFileList::HeadIsEmpty()
653 CAutoReadLock
lock(m_SharedMutex
);
654 return m_Head
.IsEmpty();
657 int CGitHeadFileList::CallBack(const unsigned char *sha1
, const char *base
, int baselen
,
658 const char *pathname
, unsigned mode
, int /*stage*/, void *context
)
660 #define S_IFGITLINK 0160000
662 CGitHeadFileList
* p
= reinterpret_cast<CGitHeadFileList
*>(context
);
664 if ((mode
& S_IFDIR
) && (mode
& S_IFMT
) != S_IFGITLINK
)
665 return READ_TREE_RECURSIVE
;
669 CGit::StringAppend(&item
.m_FileName
, (BYTE
*)base
, CP_UTF8
, baselen
);
670 CGit::StringAppend(&item
.m_FileName
, (BYTE
*)pathname
, CP_UTF8
);
672 item
.m_FileName
.MakeLower();
676 if( (mode
&S_IFMT
) == S_IFGITLINK
)
679 return READ_TREE_RECURSIVE
;
682 int ReadTreeRecursive(git_repository
&repo
, const git_tree
* tree
, const CStringA
& base
, int (*CallBack
) (const unsigned char *, const char *, int, const char *, unsigned int, int, void *), void *data
)
684 size_t count
= git_tree_entrycount(tree
);
685 for (size_t i
= 0; i
< count
; ++i
)
687 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
690 int mode
= git_tree_entry_filemode(entry
);
691 if( CallBack(git_tree_entry_id(entry
)->id
,
694 git_tree_entry_name(entry
),
697 data
) == READ_TREE_RECURSIVE
702 git_object
* object
= nullptr;
703 git_tree_entry_to_object(&object
, &repo
, entry
);
706 CStringA parent
= base
;
707 parent
+= git_tree_entry_name(entry
);
709 ReadTreeRecursive(repo
, (git_tree
*)object
, parent
, CallBack
, data
);
710 git_object_free(object
);
719 // ReadTree is/must only be executed on an empty list
720 int CGitHeadFileList::ReadTree()
722 CAutoWriteLock
lock(m_SharedMutex
);
725 CAutoRepository
repository(m_Gitdir
);
728 bool ret
= repository
;
729 ret
= ret
&& !git_commit_lookup(commit
.GetPointer(), repository
, (const git_oid
*)m_Head
.m_hash
);
730 ret
= ret
&& !git_commit_tree(tree
.GetPointer(), commit
);
733 ret
= ret
&& !ReadTreeRecursive(*repository
, tree
, "", CGitHeadFileList::CallBack
, this);
735 catch (const std::bad_alloc
& ex
)
737 CTraceToOutputDebugString::Instance()(__FUNCTION__
": Catched exception inside ReadTreeRecursive: %s\n", ex
.what());
743 m_LastModifyTimeHead
= 0;
747 std::sort(this->begin(), this->end(), SortTree
);
748 m_TreeHash
= git_commit_id(commit
)->id
;
752 int CGitIgnoreItem::FetchIgnoreList(const CString
&projectroot
, const CString
&file
, bool isGlobal
)
754 if (this->m_pExcludeList
)
756 git_free_exclude_list(m_pExcludeList
);
757 m_pExcludeList
= nullptr;
762 this->m_BaseDir
.Empty();
765 CString base
= file
.Mid(projectroot
.GetLength() + 1);
766 base
.Replace(L
'\\', L
'/');
768 int start
= base
.ReverseFind(L
'/');
771 base
.Truncate(start
);
772 this->m_BaseDir
= CUnicodeUtils::GetMulti(base
, CP_UTF8
) + "/";
776 if (CGit::GetFileModifyTime(file
, &m_LastModifyTime
))
779 CAutoFile hfile
= CreateFile(file
,
781 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
784 FILE_ATTRIBUTE_NORMAL
,
790 DWORD filesize
= GetFileSize(hfile
, nullptr);
791 if (filesize
== INVALID_FILE_SIZE
)
794 m_buffer
= new BYTE
[filesize
+ 1];
799 if (!ReadFile(hfile
, m_buffer
, filesize
, &size
, nullptr))
807 if (git_create_exclude_list(&m_pExcludeList
))
816 for (DWORD i
= 0; i
< size
; ++i
)
818 if (m_buffer
[i
] == '\n' || m_buffer
[i
] == '\r' || i
== (size
- 1))
820 if (m_buffer
[i
] == '\n' || m_buffer
[i
] == '\r')
823 if (p
[0] != '#' && p
[0] != 0)
824 git_add_exclude((const char*)p
, this->m_BaseDir
, m_BaseDir
.GetLength(), this->m_pExcludeList
, ++line
);
826 p
= m_buffer
+ i
+ 1;
832 git_free_exclude_list(m_pExcludeList
);
833 m_pExcludeList
= nullptr;
841 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
842 int CGitIgnoreItem::IsPathIgnored(const CStringA
& patha
, int& type
)
844 int pos
= patha
.ReverseFind('/');
845 const char* base
= (pos
>= 0) ? ((const char*)patha
+ pos
+ 1) : patha
;
847 return IsPathIgnored(patha
, base
, type
);
851 int CGitIgnoreItem::IsPathIgnored(const CStringA
& patha
, const char* base
, int& type
)
854 return -1; // error or undecided
856 return git_check_excluded_1(patha
, patha
.GetLength(), base
, &type
, m_pExcludeList
);
859 bool CGitIgnoreList::CheckFileChanged(const CString
&path
)
863 int ret
= CGit::GetFileModifyTime(path
, &time
);
867 CAutoReadLock
lock(m_SharedMutex
);
868 cacheExist
= (m_Map
.find(path
) != m_Map
.end());
871 if (!cacheExist
&& ret
== 0)
873 CAutoWriteLock
lock(m_SharedMutex
);
874 m_Map
[path
].m_LastModifyTime
= 0;
876 // both cache and file is not exist
877 if ((ret
!= 0) && (!cacheExist
))
880 // file exist but cache miss
881 if ((ret
== 0) && (!cacheExist
))
884 // file not exist but cache exist
885 if ((ret
!= 0) && (cacheExist
))
887 // file exist and cache exist
890 CAutoReadLock
lock(m_SharedMutex
);
891 if (m_Map
[path
].m_LastModifyTime
== time
)
897 int CGitIgnoreList::FetchIgnoreFile(const CString
&gitdir
, const CString
&gitignore
, bool isGlobal
)
899 if (CGit::GitPathFileExists(gitignore
)) //if .gitignore remove, we need remote cache
901 CAutoWriteLock
lock(m_SharedMutex
);
902 m_Map
[gitignore
].FetchIgnoreList(gitdir
, gitignore
, isGlobal
);
906 CAutoWriteLock
lock(m_SharedMutex
);
907 m_Map
.erase(gitignore
);
912 bool CGitIgnoreList::CheckAndUpdateIgnoreFiles(const CString
& gitdir
, const CString
& path
, bool isDir
)
914 CString
temp(gitdir
);
918 temp
.Replace(L
'/', L
'\\');
922 int x
= temp
.ReverseFind(L
'\\');
927 bool updated
= false;
928 while (!temp
.IsEmpty())
930 CString tempOrig
= temp
;
933 if (CGit::GitPathFileExists(temp
))
935 CString gitignore
= temp
;
936 gitignore
+= L
"ignore";
937 if (CheckFileChanged(gitignore
))
939 FetchIgnoreFile(gitdir
, gitignore
, false);
943 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
944 CString wcglobalgitignore
= adminDir
+ L
"info\\exclude";
945 if (CheckFileChanged(wcglobalgitignore
))
947 FetchIgnoreFile(gitdir
, wcglobalgitignore
, true);
951 if (CheckAndUpdateCoreExcludefile(adminDir
))
953 CString excludesFile
;
955 CAutoReadLock
lock(m_SharedMutex
);
956 excludesFile
= m_CoreExcludesfiles
[adminDir
];
958 if (!excludesFile
.IsEmpty())
960 FetchIgnoreFile(gitdir
, excludesFile
, true);
969 if (CheckFileChanged(temp
))
971 FetchIgnoreFile(gitdir
, temp
, false);
977 for (i
= temp
.GetLength() - 1; i
>= 0; --i
)
979 if (temp
[i
] == L
'\\')
986 temp
.Truncate(max(0, i
));
991 bool CGitIgnoreList::CheckAndUpdateGitSystemConfigPath(bool force
)
994 m_sGitProgramDataConfigPath
= GetProgramDataGitConfig();
995 // recheck every 30 seconds
996 if (GetTickCount64() - m_dGitSystemConfigPathLastChecked
> 30000UL || force
)
998 m_dGitSystemConfigPathLastChecked
= GetTickCount64();
999 CString
gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH
, L
"", FALSE
));
1000 if (gitSystemConfigPath
!= m_sGitSystemConfigPath
)
1002 m_sGitSystemConfigPath
= gitSystemConfigPath
;
1008 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString
&adminDir
)
1010 CString
projectConfig(adminDir
);
1011 projectConfig
+= L
"config";
1012 CString globalConfig
= g_Git
.GetGitGlobalConfig();
1013 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
1015 CAutoWriteLock
lock(m_coreExcludefilesSharedMutex
);
1016 bool hasChanged
= CheckAndUpdateGitSystemConfigPath();
1017 hasChanged
= hasChanged
|| CheckFileChanged(projectConfig
);
1018 hasChanged
= hasChanged
|| CheckFileChanged(globalConfig
);
1019 hasChanged
= hasChanged
|| CheckFileChanged(globalXDGConfig
);
1020 if (!m_sGitProgramDataConfigPath
.IsEmpty())
1021 hasChanged
= hasChanged
|| CheckFileChanged(m_sGitProgramDataConfigPath
);
1022 if (!m_sGitSystemConfigPath
.IsEmpty())
1023 hasChanged
= hasChanged
|| CheckFileChanged(m_sGitSystemConfigPath
);
1025 CString excludesFile
;
1027 CAutoReadLock
lock2(m_SharedMutex
);
1028 excludesFile
= m_CoreExcludesfiles
[adminDir
];
1030 if (!excludesFile
.IsEmpty())
1031 hasChanged
= hasChanged
|| CheckFileChanged(excludesFile
);
1036 CAutoConfig
config(true);
1037 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(projectConfig
), GIT_CONFIG_LEVEL_LOCAL
, FALSE
);
1038 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalConfig
), GIT_CONFIG_LEVEL_GLOBAL
, FALSE
);
1039 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalXDGConfig
), GIT_CONFIG_LEVEL_XDG
, FALSE
);
1040 if (!m_sGitSystemConfigPath
.IsEmpty())
1041 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(m_sGitSystemConfigPath
), GIT_CONFIG_LEVEL_SYSTEM
, FALSE
);
1042 if (!m_sGitProgramDataConfigPath
.IsEmpty())
1043 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(m_sGitProgramDataConfigPath
), GIT_CONFIG_LEVEL_PROGRAMDATA
, FALSE
);
1045 config
.GetString(L
"core.excludesfile", excludesFile
);
1046 if (excludesFile
.IsEmpty())
1047 excludesFile
= GetWindowsHome() + L
"\\.config\\git\\ignore";
1048 else if (CStringUtils::StartsWith(excludesFile
, L
"~/"))
1049 excludesFile
= GetWindowsHome() + excludesFile
.Mid(1);
1051 CAutoWriteLock
lockMap(m_SharedMutex
);
1052 CGit::GetFileModifyTime(projectConfig
, &m_Map
[projectConfig
].m_LastModifyTime
);
1053 CGit::GetFileModifyTime(globalXDGConfig
, &m_Map
[globalXDGConfig
].m_LastModifyTime
);
1054 if (m_Map
[globalXDGConfig
].m_LastModifyTime
== 0)
1055 m_Map
.erase(globalXDGConfig
);
1056 CGit::GetFileModifyTime(globalConfig
, &m_Map
[globalConfig
].m_LastModifyTime
);
1057 if (m_Map
[globalConfig
].m_LastModifyTime
== 0)
1058 m_Map
.erase(globalConfig
);
1059 if (!m_sGitSystemConfigPath
.IsEmpty())
1060 CGit::GetFileModifyTime(m_sGitSystemConfigPath
, &m_Map
[m_sGitSystemConfigPath
].m_LastModifyTime
);
1061 if (m_Map
[m_sGitSystemConfigPath
].m_LastModifyTime
== 0 || m_sGitSystemConfigPath
.IsEmpty())
1062 m_Map
.erase(m_sGitSystemConfigPath
);
1063 if (!m_sGitProgramDataConfigPath
.IsEmpty())
1064 CGit::GetFileModifyTime(m_sGitProgramDataConfigPath
, &m_Map
[m_sGitProgramDataConfigPath
].m_LastModifyTime
);
1065 if (m_Map
[m_sGitProgramDataConfigPath
].m_LastModifyTime
== 0 || m_sGitProgramDataConfigPath
.IsEmpty())
1066 m_Map
.erase(m_sGitProgramDataConfigPath
);
1067 m_CoreExcludesfiles
[adminDir
] = excludesFile
;
1071 const CString
CGitIgnoreList::GetWindowsHome()
1073 static CString
sWindowsHome(g_Git
.GetHomeDirectory());
1074 return sWindowsHome
;
1076 bool CGitIgnoreList::IsIgnore(CString str
, const CString
& projectroot
, bool isDir
)
1078 str
.Replace(L
'\\', L
'/');
1080 if (!str
.IsEmpty() && str
[str
.GetLength() - 1] == L
'/')
1081 str
.Truncate(str
.GetLength() - 1);
1084 ret
= CheckIgnore(str
, projectroot
, isDir
);
1087 int start
= str
.ReverseFind(L
'/');
1091 str
.Truncate(start
);
1092 ret
= CheckIgnore(str
, projectroot
, isDir
);
1097 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString
&ignorefile
, const CStringA
&patha
, const char * base
, int &type
)
1099 if (m_Map
.find(ignorefile
) == m_Map
.end())
1100 return -1; // error or undecided
1102 return (m_Map
[ignorefile
].IsPathIgnored(patha
, base
, type
));
1104 int CGitIgnoreList::CheckIgnore(const CString
&path
, const CString
&projectroot
, bool isDir
)
1106 CString temp
= CombinePath(projectroot
, path
);
1107 temp
.Replace(L
'/', L
'\\');
1109 CStringA patha
= CUnicodeUtils::GetMulti(path
, CP_UTF8
);
1110 patha
.Replace('\\', '/');
1117 // strip directory name
1118 // we do not need to check for a .ignore file inside a directory we might ignore
1119 int i
= temp
.ReverseFind(L
'\\');
1127 int x
= temp
.ReverseFind(L
'\\');
1132 int pos
= patha
.ReverseFind('/');
1133 const char * base
= (pos
>= 0) ? ((const char*)patha
+ pos
+ 1) : patha
;
1137 CAutoReadLock
lock(m_SharedMutex
);
1138 while (!temp
.IsEmpty())
1140 CString tempOrig
= temp
;
1143 if (CGit::GitPathFileExists(temp
))
1145 CString gitignore
= temp
;
1146 gitignore
+= L
"ignore";
1147 if ((ret
= CheckFileAgainstIgnoreList(gitignore
, patha
, base
, type
)) != -1)
1150 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1151 CString wcglobalgitignore
= adminDir
;
1152 wcglobalgitignore
+= L
"info\\exclude";
1153 if ((ret
= CheckFileAgainstIgnoreList(wcglobalgitignore
, patha
, base
, type
)) != -1)
1156 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1157 if (!excludesFile
.IsEmpty())
1158 ret
= CheckFileAgainstIgnoreList(excludesFile
, patha
, base
, type
);
1164 if ((ret
= CheckFileAgainstIgnoreList(temp
, patha
, base
, type
)) != -1)
1169 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1171 if (temp
[i
] == L
'\\')
1178 temp
.Truncate(max(0, i
));
1184 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString
&gitdir
, bool readTree
/* = true */)
1186 SHARED_TREE_PTR ptr
= this->SafeGet(gitdir
, true);
1188 if (ptr
.get() && !ptr
->CheckHeadUpdate() && (!readTree
|| ptr
->HeadHashEqualsTreeHash()))
1191 ptr
= std::make_shared
<CGitHeadFileList
>();
1192 ptr
->ReadHeadHash(gitdir
);
1196 this->SafeSet(gitdir
, ptr
);
1201 int CGitHeadFileMap::IsUnderVersionControl(const CString
& gitdir
, CString subpath
, bool isDir
, bool* isVersion
)
1203 if (subpath
.IsEmpty())
1209 subpath
.Replace(L
'\\', L
'/');
1213 subpath
.MakeLower();
1215 CheckHeadAndUpdate(gitdir
);
1217 SHARED_TREE_PTR treeptr
= SafeGet(gitdir
);
1220 if (treeptr
->HeadFileIsEmpty())
1225 if (treeptr
->empty())
1232 *isVersion
= (SearchInSortVector(*treeptr
, subpath
, subpath
.GetLength()) != NPOS
);
1234 *isVersion
= (SearchInSortVector(*treeptr
, subpath
, -1) != NPOS
);