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 int CGitIndex::Print()
35 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
36 (int)this->m_ModifyTime
,
38 (LPCTSTR
)this->m_IndexHash
.ToString(),
39 (LPCTSTR
)this->m_FileName
);
44 CGitIndexList::CGitIndexList()
45 : m_bHasConflicts(FALSE
)
47 this->m_LastModifyTime
= 0;
49 m_iMaxCheckSize
= (__int64
)CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContentMaxSize"), 10 * 1024) * 1024; // stored in KiB
52 CGitIndexList::~CGitIndexList()
57 static bool SortIndex(const CGitIndex
&Item1
, const CGitIndex
&Item2
)
59 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
62 static bool SortTree(const CGitTreeItem
&Item1
, const CGitTreeItem
&Item2
)
64 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
67 int CGitIndexList::ReadIndex(CString dgitdir
)
71 CAutoLocker
lock(m_critRepoSec
);
72 if (repository
.Open(dgitdir
))
76 CAutoConfig
config(true);
78 CString projectConfig
= dgitdir
+ _T("config");
79 CString globalConfig
= g_Git
.GetGitGlobalConfig();
80 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
81 CString
systemConfig(CRegString(REG_SYSTEM_GITCONFIGPATH
, _T(""), FALSE
));
83 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(projectConfig
), GIT_CONFIG_LEVEL_LOCAL
, FALSE
);
84 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalConfig
), GIT_CONFIG_LEVEL_GLOBAL
, FALSE
);
85 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalXDGConfig
), GIT_CONFIG_LEVEL_XDG
, FALSE
);
86 if (!systemConfig
.IsEmpty())
87 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(systemConfig
), GIT_CONFIG_LEVEL_SYSTEM
, FALSE
);
89 git_repository_set_config(repository
, config
);
92 // load index in order to enumerate files
93 if (git_repository_index(index
.GetPointer(), repository
))
99 m_bHasConflicts
= FALSE
;
101 size_t ecount
= git_index_entrycount(index
);
103 for (size_t i
= 0; i
< ecount
; ++i
)
105 const git_index_entry
*e
= git_index_get_byindex(index
, i
);
107 this->at(i
).m_FileName
.Empty();
108 this->at(i
).m_FileName
= CUnicodeUtils::GetUnicode(e
->path
);
109 this->at(i
).m_FileName
.MakeLower();
110 this->at(i
).m_ModifyTime
= e
->mtime
.seconds
;
111 this->at(i
).m_Flags
= e
->flags
;
112 this->at(i
).m_FlagsExtended
= e
->flags_extended
;
113 this->at(i
).m_IndexHash
= e
->id
.id
;
114 this->at(i
).m_Size
= e
->file_size
;
115 m_bHasConflicts
|= GIT_IDXENTRY_STAGE(e
);
118 CGit::GetFileModifyTime(dgitdir
+ _T("index"), &this->m_LastModifyTime
);
119 std::sort(this->begin(), this->end(), SortIndex
);
124 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
)
129 CString path
= pathorg
;
132 int index
= SearchInSortVector(*this, path
, -1);
136 *status
= git_wc_status_unversioned
;
140 if (callback
&& assumeValid
&& skipWorktree
)
141 callback(CombinePath(gitdir
, pathorg
), *status
, false, pData
, *assumeValid
, *skipWorktree
);
146 // skip-worktree has higher priority than assume-valid
147 if (at(index
).m_FlagsExtended
& GIT_IDXENTRY_SKIP_WORKTREE
)
149 *status
= git_wc_status_normal
;
151 *skipWorktree
= true;
153 else if (at(index
).m_Flags
& GIT_IDXENTRY_VALID
)
155 *status
= git_wc_status_normal
;
159 else if (filesize
!= at(index
).m_Size
)
160 *status
= git_wc_status_modified
;
161 else if (time
== at(index
).m_ModifyTime
)
162 *status
= git_wc_status_normal
;
163 else if (repository
&& filesize
< m_iMaxCheckSize
)
166 CStringA fileA
= CUnicodeUtils::GetMulti(pathorg
, CP_UTF8
);
167 m_critRepoSec
.Lock(); // prevent concurrent access to repository instance and especially filter-lists
168 if (!git_repository_hashfile(&actual
, repository
, fileA
, GIT_OBJ_BLOB
, NULL
) && !git_oid_cmp(&actual
, (const git_oid
*)at(index
).m_IndexHash
.m_hash
))
170 at(index
).m_ModifyTime
= time
;
171 *status
= git_wc_status_normal
;
174 *status
= git_wc_status_modified
;
175 m_critRepoSec
.Unlock();
178 *status
= git_wc_status_modified
;
180 if (at(index
).m_Flags
& GIT_IDXENTRY_STAGEMASK
)
181 *status
= git_wc_status_conflicted
;
182 else if (at(index
).m_FlagsExtended
& GIT_IDXENTRY_INTENT_TO_ADD
)
183 *status
= git_wc_status_added
;
186 *pHash
= at(index
).m_IndexHash
;
188 if (callback
&& assumeValid
&& skipWorktree
)
189 callback(CombinePath(gitdir
, pathorg
), *status
, false, pData
, *assumeValid
, *skipWorktree
);
194 int CGitIndexList::GetStatus(const CString
& gitdir
, CString path
, git_wc_status_kind
* status
,
195 BOOL IsFull
, BOOL
/*IsRecursive*/,
196 FILL_STATUS_CALLBACK callback
, void *pData
,
197 CGitHash
*pHash
, bool * assumeValid
, bool * skipWorktree
)
199 __int64 time
, filesize
= 0;
205 git_wc_status_kind dirstatus
= git_wc_status_none
;
208 result
= CGit::GetFileModifyTime(gitdir
, &time
, &isDir
);
210 result
= CGit::GetFileModifyTime(CombinePath(gitdir
, path
), &time
, &isDir
, &filesize
);
214 *status
= git_wc_status_deleted
;
215 if (callback
&& assumeValid
&& skipWorktree
)
216 callback(CombinePath(gitdir
, path
), git_wc_status_deleted
, false, pData
, *assumeValid
, *skipWorktree
);
223 GetFileStatus(gitdir
, path
, status
, time
, filesize
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
227 if (!path
.IsEmpty() && path
.Right(1) != _T('\\'))
230 int len
= path
.GetLength();
232 for (auto it
= cbegin(), itend
= cend(); it
!= itend
; ++it
)
234 if (!((*it
).m_FileName
.GetLength() > len
&& (*it
).m_FileName
.Left(len
) == path
))
239 *status
= git_wc_status_normal
;
241 callback(CombinePath(gitdir
, path
), *status
, false, pData
, ((*it
).m_Flags
& GIT_IDXENTRY_VALID
) && !((*it
).m_FlagsExtended
& GIT_IDXENTRY_SKIP_WORKTREE
), ((*it
).m_FlagsExtended
& GIT_IDXENTRY_SKIP_WORKTREE
) != 0);
246 result
= CGit::GetFileModifyTime(CombinePath(gitdir
, (*it
).m_FileName
), &time
, nullptr, &filesize
);
250 *status
= git_wc_status_none
;
252 *assumeValid
= false;
254 *skipWorktree
= false;
256 GetFileStatus(gitdir
, (*it
).m_FileName
, status
, time
, filesize
, callback
, pData
, NULL
, assumeValid
, skipWorktree
);
258 // 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
259 if (callback
&& assumeValid
&& skipWorktree
&& (*assumeValid
|| *skipWorktree
))
260 callback(CombinePath(gitdir
, path
), *status
, false, pData
, *assumeValid
, *skipWorktree
);
261 if (*status
!= git_wc_status_none
)
263 if (dirstatus
== git_wc_status_none
)
264 dirstatus
= git_wc_status_normal
;
265 if (*status
!= git_wc_status_normal
)
266 dirstatus
= git_wc_status_modified
;
270 if (dirstatus
!= git_wc_status_none
)
273 *status
= git_wc_status_unversioned
;
276 callback(CombinePath(gitdir
, path
), *status
, false, pData
, false, false);
281 int CGitIndexFileMap::Check(const CString
&gitdir
, bool *isChanged
)
285 CString IndexFile
= g_AdminDirMap
.GetAdminDirConcat(gitdir
, _T("index"));
287 if (CGit::GetFileModifyTime(IndexFile
, &time
))
290 SHARED_INDEX_PTR pIndex
;
291 pIndex
= this->SafeGet(gitdir
);
293 if (pIndex
.get() == NULL
)
300 if (pIndex
->m_LastModifyTime
== time
)
313 int CGitIndexFileMap::LoadIndex(const CString
&gitdir
)
315 SHARED_INDEX_PTR
pIndex(new CGitIndexList
);
317 if (pIndex
->ReadIndex(g_AdminDirMap
.GetAdminDir(gitdir
)))
320 this->SafeSet(gitdir
, pIndex
);
325 int CGitIndexFileMap::GetFileStatus(const CString
&gitdir
, const CString
&path
, git_wc_status_kind
*status
,BOOL IsFull
, BOOL IsRecursive
,
326 FILL_STATUS_CALLBACK callback
, void *pData
,
328 bool isLoadUpdatedIndex
, bool * assumeValid
, bool * skipWorktree
)
330 CheckAndUpdate(gitdir
, isLoadUpdatedIndex
);
332 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
334 pIndex
->GetStatus(gitdir
, path
, status
, IsFull
, IsRecursive
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
337 // git working tree has not index
338 *status
= git_wc_status_unversioned
;
344 int CGitIndexFileMap::IsUnderVersionControl(const CString
& gitdir
, CString subpath
, bool isDir
, bool* isVersion
, bool isLoadUpdateIndex
)
346 if (subpath
.IsEmpty())
352 subpath
.Replace(_T('\\'), _T('/'));
358 CheckAndUpdate(gitdir
, isLoadUpdateIndex
);
360 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
365 *isVersion
= (SearchInSortVector(*pIndex
, subpath
, subpath
.GetLength()) >= 0);
367 *isVersion
= (SearchInSortVector(*pIndex
, subpath
, -1) >= 0);
373 // This method is assumed to be called with m_SharedMutex locked.
374 int CGitHeadFileList::GetPackRef(const CString
&gitdir
)
376 CString PackRef
= g_AdminDirMap
.GetAdminDirConcat(gitdir
, _T("packed-refs"));
379 if (CGit::GetFileModifyTime(PackRef
, &mtime
))
381 //packed refs is not existed
382 this->m_PackRefFile
.Empty();
383 this->m_PackRefMap
.clear();
386 else if(mtime
== m_LastModifyTimePackRef
)
390 this->m_PackRefFile
= PackRef
;
391 this->m_LastModifyTimePackRef
= mtime
;
394 m_PackRefMap
.clear();
396 CAutoFile hfile
= CreateFile(PackRef
,
398 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
401 FILE_ATTRIBUTE_NORMAL
,
407 DWORD filesize
= GetFileSize(hfile
, nullptr);
412 auto buff
= std::make_unique
<char[]>(filesize
);
413 ReadFile(hfile
, buff
.get(), filesize
, &size
, nullptr);
415 if (size
!= filesize
)
420 for (DWORD i
= 0; i
< filesize
;)
424 if (buff
[i
] == '#' || buff
[i
] == '^')
426 while (buff
[i
] != '\n')
438 while (buff
[i
] != ' ')
440 hash
.AppendChar(buff
[i
]);
450 while (buff
[i
] != '\n')
452 ref
.AppendChar(buff
[i
]);
459 m_PackRefMap
[ref
] = hash
;
461 while (buff
[i
] == '\n')
470 int CGitHeadFileList::ReadHeadHash(const CString
& gitdir
)
472 CAutoWriteLock
lock(m_SharedMutex
);
473 m_Gitdir
= g_AdminDirMap
.GetAdminDir(gitdir
);
475 m_HeadFile
= m_Gitdir
;
476 m_HeadFile
+= _T("HEAD");
478 if( CGit::GetFileModifyTime(m_HeadFile
, &m_LastModifyTimeHead
))
481 CAutoFile hfile
= CreateFile(m_HeadFile
,
483 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
486 FILE_ATTRIBUTE_NORMAL
,
493 unsigned char buffer
[40];
494 ReadFile(hfile
, buffer
, 4, &size
, nullptr);
498 if (strcmp((const char*)buffer
, "ref:") == 0)
500 m_HeadRefFile
.Empty();
501 DWORD filesize
= GetFileSize(hfile
, nullptr);
505 unsigned char *p
= (unsigned char*)malloc(filesize
- 4);
509 ReadFile(hfile
, p
, filesize
- 4, &size
, nullptr);
510 CGit::StringAppend(&m_HeadRefFile
, p
, CP_UTF8
, filesize
- 4);
513 CString ref
= m_HeadRefFile
.Trim();
515 ref
= ref
.Tokenize(_T("\n"), start
);
516 m_HeadRefFile
= m_Gitdir
+ m_HeadRefFile
;
517 m_HeadRefFile
.Replace(_T('/'), _T('\\'));
520 if (CGit::GetFileModifyTime(m_HeadRefFile
, &time
, nullptr))
522 m_HeadRefFile
.Empty();
523 if (GetPackRef(gitdir
))
525 if (m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
528 m_Head
= m_PackRefMap
[ref
];
532 CAutoFile href
= CreateFile(m_HeadRefFile
,
534 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
537 FILE_ATTRIBUTE_NORMAL
,
542 m_HeadRefFile
.Empty();
544 if (GetPackRef(gitdir
))
547 if (m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
550 m_Head
= m_PackRefMap
[ref
];
554 ReadFile(href
, buffer
, 40, &size
, nullptr);
558 m_Head
.ConvertFromStrA((char*)buffer
);
560 m_LastModifyTimeRef
= time
;
565 ReadFile(hfile
, buffer
+ 4, 40 - 4, &size
, nullptr);
569 m_HeadRefFile
.Empty();
571 m_Head
.ConvertFromStrA((char*)buffer
);
576 bool CGitHeadFileList::CheckHeadUpdate()
578 CAutoReadLock
lock(m_SharedMutex
);
579 if (this->m_HeadFile
.IsEmpty())
584 if (CGit::GetFileModifyTime(m_HeadFile
, &mtime
))
587 if (mtime
!= this->m_LastModifyTimeHead
)
590 if (!this->m_HeadRefFile
.IsEmpty())
592 if (CGit::GetFileModifyTime(m_HeadRefFile
, &mtime
))
595 if (mtime
!= this->m_LastModifyTimeRef
)
599 if(!this->m_PackRefFile
.IsEmpty())
601 if (CGit::GetFileModifyTime(m_PackRefFile
, &mtime
))
604 if (mtime
!= this->m_LastModifyTimePackRef
)
608 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
609 // So we need to retry again and again until the ref exists - otherwise we will never notice
610 if (this->m_Head
.IsEmpty() && this->m_HeadRefFile
.IsEmpty() && this->m_PackRefFile
.IsEmpty())
616 bool CGitHeadFileList::HeadHashEqualsTreeHash()
618 CAutoReadLock
lock(m_SharedMutex
);
619 return (m_Head
== m_TreeHash
);
622 bool CGitHeadFileList::HeadFileIsEmpty()
624 CAutoReadLock
lock(m_SharedMutex
);
625 return m_HeadFile
.IsEmpty();
628 bool CGitHeadFileList::HeadIsEmpty()
630 CAutoReadLock
lock(m_SharedMutex
);
631 return m_Head
.IsEmpty();
634 int CGitHeadFileList::CallBack(const unsigned char *sha1
, const char *base
, int baselen
,
635 const char *pathname
, unsigned mode
, int /*stage*/, void *context
)
637 #define S_IFGITLINK 0160000
639 CGitHeadFileList
*p
= (CGitHeadFileList
*)context
;
641 if ((mode
& S_IFDIR
) && (mode
& S_IFMT
) != S_IFGITLINK
)
642 return READ_TREE_RECURSIVE
;
644 size_t cur
= p
->size();
645 p
->resize(p
->size() + 1);
646 p
->at(cur
).m_Hash
= sha1
;
647 p
->at(cur
).m_FileName
.Empty();
649 CGit::StringAppend(&p
->at(cur
).m_FileName
, (BYTE
*)base
, CP_UTF8
, baselen
);
650 CGit::StringAppend(&p
->at(cur
).m_FileName
, (BYTE
*)pathname
, CP_UTF8
);
652 p
->at(cur
).m_FileName
.MakeLower();
654 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
656 //p->m_Map[p->at(cur).m_FileName] = cur;
658 if( (mode
&S_IFMT
) == S_IFGITLINK
)
661 return READ_TREE_RECURSIVE
;
664 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
)
666 size_t count
= git_tree_entrycount(tree
);
667 for (size_t i
= 0; i
< count
; ++i
)
669 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
672 int mode
= git_tree_entry_filemode(entry
);
673 if( CallBack(git_tree_entry_id(entry
)->id
,
676 git_tree_entry_name(entry
),
679 data
) == READ_TREE_RECURSIVE
684 git_object
*object
= NULL
;
685 git_tree_entry_to_object(&object
, &repo
, entry
);
688 CStringA parent
= base
;
689 parent
+= git_tree_entry_name(entry
);
691 ReadTreeRecursive(repo
, (git_tree
*)object
, parent
, CallBack
, data
);
692 git_object_free(object
);
701 // ReadTree is/must only be executed on an empty list
702 int CGitHeadFileList::ReadTree()
704 CAutoWriteLock
lock(m_SharedMutex
);
707 CAutoRepository
repository(m_Gitdir
);
710 bool ret
= repository
;
711 ret
= ret
&& !git_commit_lookup(commit
.GetPointer(), repository
, (const git_oid
*)m_Head
.m_hash
);
712 ret
= ret
&& !git_commit_tree(tree
.GetPointer(), commit
);
713 ret
= ret
&& !ReadTreeRecursive(*repository
, tree
, "", CGitHeadFileList::CallBack
, this);
717 m_LastModifyTimeHead
= 0;
721 std::sort(this->begin(), this->end(), SortTree
);
722 m_TreeHash
= git_commit_id(commit
)->id
;
726 int CGitIgnoreItem::FetchIgnoreList(const CString
&projectroot
, const CString
&file
, bool isGlobal
)
728 if (this->m_pExcludeList
)
730 git_free_exclude_list(m_pExcludeList
);
736 this->m_BaseDir
.Empty();
739 CString base
= file
.Mid(projectroot
.GetLength() + 1);
740 base
.Replace(_T('\\'), _T('/'));
742 int start
= base
.ReverseFind(_T('/'));
745 base
= base
.Left(start
);
746 this->m_BaseDir
= CUnicodeUtils::GetMulti(base
, CP_UTF8
) + "/";
750 if (CGit::GetFileModifyTime(file
, &m_LastModifyTime
))
753 CAutoFile hfile
= CreateFile(file
,
755 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
758 FILE_ATTRIBUTE_NORMAL
,
764 DWORD filesize
= GetFileSize(hfile
, nullptr);
765 if (filesize
== INVALID_FILE_SIZE
)
768 m_buffer
= new BYTE
[filesize
+ 1];
773 if (!ReadFile(hfile
, m_buffer
, filesize
, &size
, NULL
))
781 if (git_create_exclude_list(&m_pExcludeList
))
790 for (DWORD i
= 0; i
< size
; ++i
)
792 if (m_buffer
[i
] == '\n' || m_buffer
[i
] == '\r' || i
== (size
- 1))
794 if (m_buffer
[i
] == '\n' || m_buffer
[i
] == '\r')
797 if (p
[0] != '#' && p
[0] != 0)
798 git_add_exclude((const char*)p
, this->m_BaseDir
, m_BaseDir
.GetLength(), this->m_pExcludeList
, ++line
);
800 p
= m_buffer
+ i
+ 1;
806 git_free_exclude_list(m_pExcludeList
);
807 m_pExcludeList
= nullptr;
815 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
816 int CGitIgnoreItem::IsPathIgnored(const CStringA
& patha
, int& type
)
818 int pos
= patha
.ReverseFind('/');
819 const char* base
= (pos
>= 0) ? ((const char*)patha
+ pos
+ 1) : patha
;
821 return IsPathIgnored(patha
, base
, type
);
825 int CGitIgnoreItem::IsPathIgnored(const CStringA
& patha
, const char* base
, int& type
)
828 return -1; // error or undecided
830 return git_check_excluded_1(patha
, patha
.GetLength(), base
, &type
, m_pExcludeList
);
833 bool CGitIgnoreList::CheckFileChanged(const CString
&path
)
837 int ret
= CGit::GetFileModifyTime(path
, &time
);
841 CAutoReadLock
lock(m_SharedMutex
);
842 cacheExist
= (m_Map
.find(path
) != m_Map
.end());
845 if (!cacheExist
&& ret
== 0)
847 CAutoWriteLock
lock(m_SharedMutex
);
848 m_Map
[path
].m_LastModifyTime
= 0;
850 // both cache and file is not exist
851 if ((ret
!= 0) && (!cacheExist
))
854 // file exist but cache miss
855 if ((ret
== 0) && (!cacheExist
))
858 // file not exist but cache exist
859 if ((ret
!= 0) && (cacheExist
))
863 // file exist and cache exist
866 CAutoReadLock
lock(m_SharedMutex
);
867 if (m_Map
[path
].m_LastModifyTime
== time
)
873 bool CGitIgnoreList::CheckIgnoreChanged(const CString
&gitdir
, const CString
&path
, bool isDir
)
875 CString
temp(gitdir
);
879 temp
.Replace(_T('/'), _T('\\'));
883 int x
= temp
.ReverseFind(_T('\\'));
888 while(!temp
.IsEmpty())
890 CString tempOrig
= temp
;
891 temp
+= _T("\\.git");
893 if (CGit::GitPathFileExists(temp
))
895 CString gitignore
=temp
;
896 gitignore
+= _T("ignore");
897 if (CheckFileChanged(gitignore
))
900 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
901 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
902 if (CheckFileChanged(wcglobalgitignore
))
905 if (CheckAndUpdateCoreExcludefile(adminDir
))
911 temp
+= _T("ignore");
912 if (CheckFileChanged(temp
))
917 for (i
= temp
.GetLength() - 1; i
>= 0; --i
)
919 if(temp
[i
] == _T('\\'))
931 int CGitIgnoreList::FetchIgnoreFile(const CString
&gitdir
, const CString
&gitignore
, bool isGlobal
)
933 if (CGit::GitPathFileExists(gitignore
)) //if .gitignore remove, we need remote cache
935 CAutoWriteLock
lock(m_SharedMutex
);
936 m_Map
[gitignore
].FetchIgnoreList(gitdir
, gitignore
, isGlobal
);
940 CAutoWriteLock
lock(m_SharedMutex
);
941 m_Map
.erase(gitignore
);
946 int CGitIgnoreList::LoadAllIgnoreFile(const CString
&gitdir
, const CString
&path
, bool isDir
)
948 CString
temp(gitdir
);
952 temp
.Replace(_T('/'), _T('\\'));
956 int x
= temp
.ReverseFind(_T('\\'));
961 while (!temp
.IsEmpty())
963 CString tempOrig
= temp
;
964 temp
+= _T("\\.git");
966 if (CGit::GitPathFileExists(temp
))
968 CString gitignore
= temp
;
969 gitignore
+= _T("ignore");
970 if (CheckFileChanged(gitignore
))
971 FetchIgnoreFile(gitdir
, gitignore
, false);
973 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
974 CString wcglobalgitignore
= adminDir
;
975 wcglobalgitignore
+= _T("info\\exclude");
976 if (CheckFileChanged(wcglobalgitignore
))
978 FetchIgnoreFile(gitdir
, wcglobalgitignore
, true);
981 if (CheckAndUpdateCoreExcludefile(adminDir
))
983 CString excludesFile
;
985 CAutoReadLock
lock(m_SharedMutex
);
986 excludesFile
= m_CoreExcludesfiles
[adminDir
];
988 if (!excludesFile
.IsEmpty())
989 FetchIgnoreFile(gitdir
, excludesFile
, true);
995 temp
+= _T("ignore");
996 if (CheckFileChanged(temp
))
997 FetchIgnoreFile(gitdir
, temp
, false);
1001 for (i
= temp
.GetLength() - 1; i
>= 0; --i
)
1003 if(temp
[i
] == _T('\\'))
1010 temp
= temp
.Left(i
);
1014 bool CGitIgnoreList::CheckAndUpdateGitSystemConfigPath(bool force
)
1016 // recheck every 30 seconds
1017 if (GetTickCount64() - m_dGitSystemConfigPathLastChecked
> 30000UL || force
)
1019 m_dGitSystemConfigPathLastChecked
= GetTickCount64();
1020 CString
gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH
, _T(""), FALSE
));
1021 if (gitSystemConfigPath
!= m_sGitSystemConfigPath
)
1023 m_sGitSystemConfigPath
= gitSystemConfigPath
;
1029 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString
&adminDir
)
1031 CString
projectConfig(adminDir
);
1032 projectConfig
+= _T("config");
1033 CString globalConfig
= g_Git
.GetGitGlobalConfig();
1034 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
1036 CAutoWriteLock
lock(m_coreExcludefilesSharedMutex
);
1037 bool hasChanged
= CheckAndUpdateGitSystemConfigPath();
1038 hasChanged
= hasChanged
|| CheckFileChanged(projectConfig
);
1039 hasChanged
= hasChanged
|| CheckFileChanged(globalConfig
);
1040 hasChanged
= hasChanged
|| CheckFileChanged(globalXDGConfig
);
1041 if (!m_sGitSystemConfigPath
.IsEmpty())
1042 hasChanged
= hasChanged
|| CheckFileChanged(m_sGitSystemConfigPath
);
1044 CString excludesFile
;
1046 CAutoReadLock
lock2(m_SharedMutex
);
1047 excludesFile
= m_CoreExcludesfiles
[adminDir
];
1049 if (!excludesFile
.IsEmpty())
1050 hasChanged
= hasChanged
|| CheckFileChanged(excludesFile
);
1055 CAutoConfig
config(true);
1056 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(projectConfig
), GIT_CONFIG_LEVEL_LOCAL
, FALSE
);
1057 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalConfig
), GIT_CONFIG_LEVEL_GLOBAL
, FALSE
);
1058 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalXDGConfig
), GIT_CONFIG_LEVEL_XDG
, FALSE
);
1059 if (!m_sGitSystemConfigPath
.IsEmpty())
1060 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(m_sGitSystemConfigPath
), GIT_CONFIG_LEVEL_SYSTEM
, FALSE
);
1061 config
.GetString(_T("core.excludesfile"), excludesFile
);
1062 if (excludesFile
.IsEmpty())
1063 excludesFile
= GetWindowsHome() + _T("\\.config\\git\\ignore");
1064 else if (excludesFile
.Find(_T("~/")) == 0)
1065 excludesFile
= GetWindowsHome() + excludesFile
.Mid(1);
1067 CAutoWriteLock
lockMap(m_SharedMutex
);
1068 CGit::GetFileModifyTime(projectConfig
, &m_Map
[projectConfig
].m_LastModifyTime
);
1069 CGit::GetFileModifyTime(globalXDGConfig
, &m_Map
[globalXDGConfig
].m_LastModifyTime
);
1070 if (m_Map
[globalXDGConfig
].m_LastModifyTime
== 0)
1071 m_Map
.erase(globalXDGConfig
);
1072 CGit::GetFileModifyTime(globalConfig
, &m_Map
[globalConfig
].m_LastModifyTime
);
1073 if (m_Map
[globalConfig
].m_LastModifyTime
== 0)
1074 m_Map
.erase(globalConfig
);
1075 if (!m_sGitSystemConfigPath
.IsEmpty())
1076 CGit::GetFileModifyTime(m_sGitSystemConfigPath
, &m_Map
[m_sGitSystemConfigPath
].m_LastModifyTime
);
1077 if (m_Map
[m_sGitSystemConfigPath
].m_LastModifyTime
== 0 || m_sGitSystemConfigPath
.IsEmpty())
1078 m_Map
.erase(m_sGitSystemConfigPath
);
1079 m_CoreExcludesfiles
[adminDir
] = excludesFile
;
1083 const CString
CGitIgnoreList::GetWindowsHome()
1085 static CString
sWindowsHome(g_Git
.GetHomeDirectory());
1086 return sWindowsHome
;
1088 bool CGitIgnoreList::IsIgnore(CString str
, const CString
& projectroot
, bool isDir
)
1090 str
.Replace(_T('\\'),_T('/'));
1092 if (!str
.IsEmpty() && str
[str
.GetLength() - 1] == _T('/'))
1093 str
= str
.Left(str
.GetLength() - 1);
1096 ret
= CheckIgnore(str
, projectroot
, isDir
);
1099 int start
= str
.ReverseFind(_T('/'));
1103 str
= str
.Left(start
);
1104 ret
= CheckIgnore(str
, projectroot
, isDir
);
1109 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString
&ignorefile
, const CStringA
&patha
, const char * base
, int &type
)
1111 if (m_Map
.find(ignorefile
) == m_Map
.end())
1112 return -1; // error or undecided
1114 return (m_Map
[ignorefile
].IsPathIgnored(patha
, base
, type
));
1116 int CGitIgnoreList::CheckIgnore(const CString
&path
, const CString
&projectroot
, bool isDir
)
1118 CString temp
= CombinePath(projectroot
, path
);
1119 temp
.Replace(_T('/'), _T('\\'));
1121 CStringA patha
= CUnicodeUtils::GetMulti(path
, CP_UTF8
);
1122 patha
.Replace('\\', '/');
1129 // strip directory name
1130 // we do not need to check for a .ignore file inside a directory we might ignore
1131 int i
= temp
.ReverseFind(_T('\\'));
1133 temp
= temp
.Left(i
);
1139 int x
= temp
.ReverseFind(_T('\\'));
1141 temp
= temp
.Left(x
);
1144 int pos
= patha
.ReverseFind('/');
1145 const char * base
= (pos
>= 0) ? ((const char*)patha
+ pos
+ 1) : patha
;
1149 CAutoReadLock
lock(m_SharedMutex
);
1150 while (!temp
.IsEmpty())
1152 CString tempOrig
= temp
;
1153 temp
+= _T("\\.git");
1155 if (CGit::GitPathFileExists(temp
))
1157 CString gitignore
= temp
;
1158 gitignore
+= _T("ignore");
1159 if ((ret
= CheckFileAgainstIgnoreList(gitignore
, patha
, base
, type
)) != -1)
1162 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1163 CString wcglobalgitignore
= adminDir
;
1164 wcglobalgitignore
+= _T("info\\exclude");
1165 if ((ret
= CheckFileAgainstIgnoreList(wcglobalgitignore
, patha
, base
, type
)) != -1)
1168 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1169 if (!excludesFile
.IsEmpty())
1170 ret
= CheckFileAgainstIgnoreList(excludesFile
, patha
, base
, type
);
1175 temp
+= _T("ignore");
1176 if ((ret
= CheckFileAgainstIgnoreList(temp
, patha
, base
, type
)) != -1)
1181 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1183 if (temp
[i
] == _T('\\'))
1190 temp
= temp
.Left(i
);
1196 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString
&gitdir
, bool readTree
/* = true */)
1198 SHARED_TREE_PTR ptr
= this->SafeGet(gitdir
, true);
1200 if (ptr
.get() && !ptr
->CheckHeadUpdate() && (!readTree
|| ptr
->HeadHashEqualsTreeHash()))
1203 ptr
= SHARED_TREE_PTR(new CGitHeadFileList
);
1204 ptr
->ReadHeadHash(gitdir
);
1208 this->SafeSet(gitdir
, ptr
);
1213 int CGitHeadFileMap::IsUnderVersionControl(const CString
& gitdir
, CString subpath
, bool isDir
, bool* isVersion
)
1215 if (subpath
.IsEmpty())
1221 subpath
.Replace(_T('\\'), _T('/'));
1225 subpath
.MakeLower();
1227 CheckHeadAndUpdate(gitdir
);
1229 SHARED_TREE_PTR treeptr
= SafeGet(gitdir
);
1232 if (treeptr
->HeadFileIsEmpty())
1237 if (treeptr
->empty())
1244 *isVersion
= (SearchInSortVector(*treeptr
, subpath
, subpath
.GetLength()) >= 0);
1246 *isVersion
= (SearchInSortVector(*treeptr
, subpath
, -1) >= 0);