1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - 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.
24 #include "UnicodeUtils.h"
27 #include <sys/types.h>
29 #include "SmartHandle.h"
30 #include "git2/sys/repository.h"
32 CGitAdminDirMap g_AdminDirMap
;
34 int CGitIndex::Print()
36 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
37 (int)this->m_ModifyTime
,
39 this->m_IndexHash
.ToString(),
45 CGitIndexList::CGitIndexList()
47 this->m_LastModifyTime
= 0;
49 m_bCheckContent
= !!(CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContent"), TRUE
) == TRUE
);
50 m_iMaxCheckSize
= (__int64
)CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContentMaxSize"), 10 * 1024) * 1024; // stored in KiB
53 CGitIndexList::~CGitIndexList()
58 static bool SortIndex(const CGitIndex
&Item1
, const CGitIndex
&Item2
)
60 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
63 static bool SortTree(const CGitTreeItem
&Item1
, const CGitTreeItem
&Item2
)
65 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
68 int CGitIndexList::ReadIndex(CString dgitdir
)
73 if (repository
.Open(dgitdir
))
75 m_critRepoSec
.Unlock();
80 CAutoConfig
config(true);
82 CString projectConfig
= dgitdir
+ _T("config");
83 CString globalConfig
= g_Git
.GetGitGlobalConfig();
84 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
85 CString
msysGitBinPath(CRegString(REG_MSYSGIT_PATH
, _T(""), FALSE
));
87 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(projectConfig
), GIT_CONFIG_LEVEL_LOCAL
, FALSE
);
88 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalConfig
), GIT_CONFIG_LEVEL_GLOBAL
, FALSE
);
89 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalXDGConfig
), GIT_CONFIG_LEVEL_XDG
, FALSE
);
90 if (!msysGitBinPath
.IsEmpty())
91 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(msysGitBinPath
+ _T("\\..\\etc\\gitconfig")), GIT_CONFIG_LEVEL_SYSTEM
, FALSE
);
93 git_repository_set_config(repository
, config
);
96 // load index in order to enumerate files
97 if (git_repository_index(index
.GetPointer(), repository
))
100 m_critRepoSec
.Unlock();
104 size_t ecount
= git_index_entrycount(index
);
106 for (size_t i
= 0; i
< ecount
; ++i
)
108 const git_index_entry
*e
= git_index_get_byindex(index
, i
);
110 this->at(i
).m_FileName
.Empty();
111 this->at(i
).m_FileName
= CUnicodeUtils::GetUnicode(e
->path
);
112 this->at(i
).m_FileName
.MakeLower();
113 this->at(i
).m_ModifyTime
= e
->mtime
.seconds
;
114 this->at(i
).m_Flags
= e
->flags
| e
->flags_extended
;
115 this->at(i
).m_IndexHash
= e
->id
.id
;
116 this->at(i
).m_Size
= e
->file_size
;
119 g_Git
.GetFileModifyTime(dgitdir
+ _T("index"), &this->m_LastModifyTime
);
120 std::sort(this->begin(), this->end(), SortIndex
);
122 m_critRepoSec
.Unlock();
127 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
)
131 CString path
= pathorg
;
134 int start
= SearchInSortVector(*this, path
, -1);
138 *status
= git_wc_status_unversioned
;
146 if (index
>= (int)size())
149 // skip-worktree has higher priority than assume-valid
150 if (at(index
).m_Flags
& GIT_IDXENTRY_SKIP_WORKTREE
)
152 *status
= git_wc_status_normal
;
154 *skipWorktree
= true;
156 else if (at(index
).m_Flags
& GIT_IDXENTRY_VALID
)
158 *status
= git_wc_status_normal
;
162 else if (filesize
!= at(index
).m_Size
)
163 *status
= git_wc_status_modified
;
164 else if (time
== at(index
).m_ModifyTime
)
166 *status
= git_wc_status_normal
;
168 else if (m_bCheckContent
&& repository
&& filesize
< m_iMaxCheckSize
)
171 CStringA fileA
= CUnicodeUtils::GetMulti(pathorg
, CP_UTF8
);
172 m_critRepoSec
.Lock(); // prevent concurrent access to repository instance and especially filter-lists
173 if (!git_repository_hashfile(&actual
, repository
, fileA
, GIT_OBJ_BLOB
, NULL
) && !git_oid_cmp(&actual
, (const git_oid
*)at(index
).m_IndexHash
.m_hash
))
175 at(index
).m_ModifyTime
= time
;
176 *status
= git_wc_status_normal
;
179 *status
= git_wc_status_modified
;
180 m_critRepoSec
.Unlock();
183 *status
= git_wc_status_modified
;
185 if (at(index
).m_Flags
& GIT_IDXENTRY_STAGEMASK
)
186 *status
= git_wc_status_conflicted
;
187 else if (at(index
).m_Flags
& GIT_IDXENTRY_INTENT_TO_ADD
)
188 *status
= git_wc_status_added
;
191 *pHash
= at(index
).m_IndexHash
;
196 if (callback
&& status
&& assumeValid
&& skipWorktree
)
197 callback(gitdir
+ _T("\\") + pathorg
, *status
, false, pData
, *assumeValid
, *skipWorktree
);
201 int CGitIndexList::GetStatus(const CString
&gitdir
,const CString
&pathParam
, git_wc_status_kind
*status
,
202 BOOL IsFull
, BOOL
/*IsRecursive*/,
203 FILL_STATUS_CALLBACK callback
, void *pData
,
204 CGitHash
*pHash
, bool * assumeValid
, bool * skipWorktree
)
206 __int64 time
, filesize
= 0;
208 CString path
= pathParam
;
212 git_wc_status_kind dirstatus
= git_wc_status_none
;
215 result
= g_Git
.GetFileModifyTime(gitdir
, &time
, &isDir
);
217 result
= g_Git
.GetFileModifyTime(gitdir
+ _T("\\") + path
, &time
, &isDir
, &filesize
);
221 *status
= git_wc_status_deleted
;
222 if (callback
&& assumeValid
&& skipWorktree
)
223 callback(gitdir
+ _T("\\") + path
, git_wc_status_deleted
, false, pData
, *assumeValid
, *skipWorktree
);
231 if (path
.Right(1) != _T("\\"))
234 int len
= path
.GetLength();
236 for (size_t i
= 0; i
< size(); ++i
)
238 if (at(i
).m_FileName
.GetLength() > len
)
240 if (at(i
).m_FileName
.Left(len
) == path
)
244 *status
= git_wc_status_normal
;
246 callback(gitdir
+ _T("\\") + path
, *status
, false, pData
, (at(i
).m_Flags
& GIT_IDXENTRY_VALID
) && !(at(i
).m_Flags
& GIT_IDXENTRY_SKIP_WORKTREE
), (at(i
).m_Flags
& GIT_IDXENTRY_SKIP_WORKTREE
) != 0);
252 result
= g_Git
.GetFileModifyTime(gitdir
+ _T("\\") + at(i
).m_FileName
, &time
, nullptr, &filesize
);
256 *status
= git_wc_status_none
;
258 *assumeValid
= false;
260 *skipWorktree
= false;
261 GetFileStatus(gitdir
, at(i
).m_FileName
, status
, time
, filesize
, callback
, pData
, NULL
, assumeValid
, skipWorktree
);
262 // 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
263 if (callback
&& assumeValid
&& skipWorktree
&& (*assumeValid
|| *skipWorktree
))
264 callback(gitdir
+ _T("\\") + path
, *status
, false, pData
, *assumeValid
, *skipWorktree
);
265 if (*status
!= git_wc_status_none
)
267 if (dirstatus
== git_wc_status_none
)
269 dirstatus
= git_wc_status_normal
;
271 if (*status
!= git_wc_status_normal
)
273 dirstatus
= git_wc_status_modified
;
282 if (dirstatus
!= git_wc_status_none
)
288 *status
= git_wc_status_unversioned
;
291 callback(gitdir
+ _T("\\") + path
, *status
, false, pData
, false, false);
298 GetFileStatus(gitdir
, path
, status
, time
, filesize
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
304 int CGitIndexFileMap::Check(const CString
&gitdir
, bool *isChanged
)
309 CString IndexFile
= g_AdminDirMap
.GetAdminDir(gitdir
) + _T("index");
311 /* Get data associated with "crt_stat.c": */
312 result
= g_Git
.GetFileModifyTime(IndexFile
, &time
);
317 SHARED_INDEX_PTR pIndex
;
318 pIndex
= this->SafeGet(gitdir
);
320 if (pIndex
.get() == NULL
)
327 if (pIndex
->m_LastModifyTime
== time
)
340 int CGitIndexFileMap::LoadIndex(const CString
&gitdir
)
344 SHARED_INDEX_PTR
pIndex(new CGitIndexList
);
346 if(pIndex
->ReadIndex(g_AdminDirMap
.GetAdminDir(gitdir
)))
349 this->SafeSet(gitdir
, pIndex
);
358 int CGitIndexFileMap::GetFileStatus(const CString
&gitdir
, const CString
&path
, git_wc_status_kind
*status
,BOOL IsFull
, BOOL IsRecursive
,
359 FILL_STATUS_CALLBACK callback
, void *pData
,
361 bool isLoadUpdatedIndex
, bool * assumeValid
, bool * skipWorktree
)
365 CheckAndUpdate(gitdir
, isLoadUpdatedIndex
);
367 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
368 if (pIndex
.get() != NULL
)
370 pIndex
->GetStatus(gitdir
, path
, status
, IsFull
, IsRecursive
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
374 // git working tree has not index
375 *status
= git_wc_status_unversioned
;
385 int CGitIndexFileMap::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
,bool *isVersion
, bool isLoadUpdateIndex
)
395 CString subpath
= path
;
396 subpath
.Replace(_T('\\'), _T('/'));
402 CheckAndUpdate(gitdir
, isLoadUpdateIndex
);
404 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
409 *isVersion
= (SearchInSortVector(*pIndex
, subpath
, subpath
.GetLength()) >= 0);
411 *isVersion
= (SearchInSortVector(*pIndex
, subpath
, -1) >= 0);
421 // This method is assumed to be called with m_SharedMutex locked.
422 int CGitHeadFileList::GetPackRef(const CString
&gitdir
)
424 CString PackRef
= g_AdminDirMap
.GetAdminDir(gitdir
) + _T("packed-refs");
427 if (g_Git
.GetFileModifyTime(PackRef
, &mtime
))
429 //packed refs is not existed
430 this->m_PackRefFile
.Empty();
431 this->m_PackRefMap
.clear();
434 else if(mtime
== m_LastModifyTimePackRef
)
440 this->m_PackRefFile
= PackRef
;
441 this->m_LastModifyTimePackRef
= mtime
;
444 m_PackRefMap
.clear();
446 CAutoFile hfile
= CreateFile(PackRef
,
448 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
451 FILE_ATTRIBUTE_NORMAL
,
457 DWORD filesize
= GetFileSize(hfile
, nullptr);
462 std::unique_ptr
<char[]> buff(new char[filesize
]);
463 ReadFile(hfile
, buff
.get(), filesize
, &size
, nullptr);
465 if (size
!= filesize
)
470 for (DWORD i
= 0; i
< filesize
;)
474 if (buff
[i
] == '#' || buff
[i
] == '^')
476 while (buff
[i
] != '\n')
488 while (buff
[i
] != ' ')
490 hash
.AppendChar(buff
[i
]);
500 while (buff
[i
] != '\n')
502 ref
.AppendChar(buff
[i
]);
509 m_PackRefMap
[ref
] = hash
;
511 while (buff
[i
] == '\n')
520 int CGitHeadFileList::ReadHeadHash(CString gitdir
)
522 CAutoWriteLock
lock(m_SharedMutex
);
523 m_Gitdir
= g_AdminDirMap
.GetAdminDir(gitdir
);
525 m_HeadFile
= m_Gitdir
+ _T("HEAD");
527 if( g_Git
.GetFileModifyTime(m_HeadFile
, &m_LastModifyTimeHead
))
530 CAutoFile hfile
= CreateFile(m_HeadFile
,
532 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
535 FILE_ATTRIBUTE_NORMAL
,
542 unsigned char buffer
[40];
543 ReadFile(hfile
, buffer
, 4, &size
, nullptr);
547 if (strcmp((const char*)buffer
, "ref:") == 0)
549 m_HeadRefFile
.Empty();
550 DWORD filesize
= GetFileSize(hfile
, nullptr);
554 unsigned char *p
= (unsigned char*)malloc(filesize
- 4);
558 ReadFile(hfile
, p
, filesize
- 4, &size
, nullptr);
559 CGit::StringAppend(&m_HeadRefFile
, p
, CP_UTF8
, filesize
- 4);
562 CString ref
= m_HeadRefFile
.Trim();
564 ref
= ref
.Tokenize(_T("\n"), start
);
565 m_HeadRefFile
= m_Gitdir
+ m_HeadRefFile
;
566 m_HeadRefFile
.Replace(_T('/'), _T('\\'));
569 if (g_Git
.GetFileModifyTime(m_HeadRefFile
, &time
, nullptr))
571 m_HeadRefFile
.Empty();
572 if (GetPackRef(gitdir
))
574 if (m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
577 m_Head
= m_PackRefMap
[ref
];
581 CAutoFile href
= CreateFile(m_HeadRefFile
,
583 FILE_SHARE_READ
| FILE_SHARE_DELETE
| FILE_SHARE_WRITE
,
586 FILE_ATTRIBUTE_NORMAL
,
591 m_HeadRefFile
.Empty();
593 if (GetPackRef(gitdir
))
596 if (m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
599 m_Head
= m_PackRefMap
[ref
];
603 ReadFile(href
, buffer
, 40, &size
, nullptr);
607 m_Head
.ConvertFromStrA((char*)buffer
);
609 m_LastModifyTimeRef
= time
;
613 ReadFile(hfile
, buffer
+ 4, 40 - 4, &size
, NULL
);
617 m_HeadRefFile
.Empty();
619 m_Head
.ConvertFromStrA((char*)buffer
);
625 bool CGitHeadFileList::CheckHeadUpdate()
627 CAutoReadLock
lock(m_SharedMutex
);
628 if (this->m_HeadFile
.IsEmpty())
633 if (g_Git
.GetFileModifyTime(m_HeadFile
, &mtime
))
636 if (mtime
!= this->m_LastModifyTimeHead
)
639 if (!this->m_HeadRefFile
.IsEmpty())
641 if (g_Git
.GetFileModifyTime(m_HeadRefFile
, &mtime
))
644 if (mtime
!= this->m_LastModifyTimeRef
)
648 if(!this->m_PackRefFile
.IsEmpty())
650 if (g_Git
.GetFileModifyTime(m_PackRefFile
, &mtime
))
653 if (mtime
!= this->m_LastModifyTimePackRef
)
657 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
658 // So we need to retry again and again until the ref exists - otherwise we will never notice
659 if (this->m_Head
.IsEmpty() && this->m_HeadRefFile
.IsEmpty() && this->m_PackRefFile
.IsEmpty())
665 bool CGitHeadFileList::HeadHashEqualsTreeHash()
667 CAutoReadLock
lock(m_SharedMutex
);
668 return (m_Head
== m_TreeHash
);
671 bool CGitHeadFileList::HeadFileIsEmpty()
673 CAutoReadLock
lock(m_SharedMutex
);
674 return m_HeadFile
.IsEmpty();
677 bool CGitHeadFileList::HeadIsEmpty()
679 CAutoReadLock
lock(m_SharedMutex
);
680 return m_Head
.IsEmpty();
683 int CGitHeadFileList::CallBack(const unsigned char *sha1
, const char *base
, int baselen
,
684 const char *pathname
, unsigned mode
, int /*stage*/, void *context
)
686 #define S_IFGITLINK 0160000
688 CGitHeadFileList
*p
= (CGitHeadFileList
*)context
;
691 if( (mode
&S_IFMT
) != S_IFGITLINK
)
692 return READ_TREE_RECURSIVE
;
695 size_t cur
= p
->size();
696 p
->resize(p
->size() + 1);
697 p
->at(cur
).m_Hash
= sha1
;
698 p
->at(cur
).m_FileName
.Empty();
700 CGit::StringAppend(&p
->at(cur
).m_FileName
, (BYTE
*)base
, CP_UTF8
, baselen
);
701 CGit::StringAppend(&p
->at(cur
).m_FileName
, (BYTE
*)pathname
, CP_UTF8
);
703 p
->at(cur
).m_FileName
.MakeLower();
705 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
707 //p->m_Map[p->at(cur).m_FileName] = cur;
709 if( (mode
&S_IFMT
) == S_IFGITLINK
)
712 return READ_TREE_RECURSIVE
;
715 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
)
717 size_t count
= git_tree_entrycount(tree
);
718 for (size_t i
= 0; i
< count
; ++i
)
720 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
723 int mode
= git_tree_entry_filemode(entry
);
724 if( CallBack(git_tree_entry_id(entry
)->id
,
727 git_tree_entry_name(entry
),
730 data
) == READ_TREE_RECURSIVE
735 git_object
*object
= NULL
;
736 git_tree_entry_to_object(&object
, &repo
, entry
);
739 CStringA parent
= base
;
740 parent
+= git_tree_entry_name(entry
);
742 ReadTreeRecursive(repo
, (git_tree
*)object
, parent
, CallBack
, data
);
743 git_object_free(object
);
752 // ReadTree is/must only be executed on an empty list
753 int CGitHeadFileList::ReadTree()
755 CAutoWriteLock
lock(m_SharedMutex
);
758 CAutoRepository
repository(m_Gitdir
);
761 bool ret
= repository
;
762 ret
= ret
&& !git_commit_lookup(commit
.GetPointer(), repository
, (const git_oid
*)m_Head
.m_hash
);
763 ret
= ret
&& !git_commit_tree(tree
.GetPointer(), commit
);
764 ret
= ret
&& !ReadTreeRecursive(*repository
, tree
, "", CGitHeadFileList::CallBack
, this);
768 m_LastModifyTimeHead
= 0;
772 std::sort(this->begin(), this->end(), SortTree
);
773 m_TreeHash
= git_commit_id(commit
)->id
;
777 int CGitIgnoreItem::FetchIgnoreList(const CString
&projectroot
, const CString
&file
, bool isGlobal
)
779 if (this->m_pExcludeList
)
781 git_free_exclude_list(m_pExcludeList
);
787 this->m_BaseDir
.Empty();
790 CString base
= file
.Mid(projectroot
.GetLength() + 1);
791 base
.Replace(_T('\\'), _T('/'));
793 int start
= base
.ReverseFind(_T('/'));
796 base
= base
.Left(start
);
797 this->m_BaseDir
= CUnicodeUtils::GetMulti(base
, CP_UTF8
) + "/";
802 if(g_Git
.GetFileModifyTime(file
, &m_LastModifyTime
))
805 if(git_create_exclude_list(&this->m_pExcludeList
))
809 CAutoFile hfile
= CreateFile(file
,
811 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
814 FILE_ATTRIBUTE_NORMAL
,
821 DWORD size
=0,filesize
=0;
823 filesize
=GetFileSize(hfile
, NULL
);
825 if(filesize
== INVALID_FILE_SIZE
)
828 m_buffer
= new BYTE
[filesize
+ 1];
830 if (m_buffer
== NULL
)
833 if (!ReadFile(hfile
, m_buffer
, filesize
, &size
, NULL
))
834 return GetLastError();
838 for (DWORD i
= 0; i
< size
; ++i
)
840 if (m_buffer
[i
] == '\n' || m_buffer
[i
] == '\r' || i
== (size
- 1))
842 if (m_buffer
[i
] == '\n' || m_buffer
[i
] == '\r')
847 if(p
[0] != '#' && p
[0] != 0)
848 git_add_exclude((const char*)p
,
850 m_BaseDir
.GetLength(),
851 this->m_pExcludeList
, ++line
);
853 p
= m_buffer
+ i
+ 1;
860 bool CGitIgnoreList::CheckFileChanged(const CString
&path
)
864 int ret
= g_Git
.GetFileModifyTime(path
, &time
);
868 CAutoReadLock
lock(m_SharedMutex
);
869 cacheExist
= (m_Map
.find(path
) != m_Map
.end());
872 if (!cacheExist
&& ret
== 0)
874 CAutoWriteLock
lock(m_SharedMutex
);
875 m_Map
[path
].m_LastModifyTime
= 0;
877 // both cache and file is not exist
878 if ((ret
!= 0) && (!cacheExist
))
881 // file exist but cache miss
882 if ((ret
== 0) && (!cacheExist
))
885 // file not exist but cache exist
886 if ((ret
!= 0) && (cacheExist
))
890 // file exist and cache exist
893 CAutoReadLock
lock(m_SharedMutex
);
894 if (m_Map
[path
].m_LastModifyTime
== time
)
900 bool CGitIgnoreList::CheckIgnoreChanged(const CString
&gitdir
, const CString
&path
, bool isDir
)
907 temp
.Replace(_T('/'), _T('\\'));
911 int x
= temp
.ReverseFind(_T('\\'));
916 while(!temp
.IsEmpty())
918 CString tempOrig
= temp
;
919 temp
+= _T("\\.git");
921 if (CGit::GitPathFileExists(temp
))
923 CString gitignore
=temp
;
924 gitignore
+= _T("ignore");
925 if (CheckFileChanged(gitignore
))
928 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
929 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
930 if (CheckFileChanged(wcglobalgitignore
))
933 if (CheckAndUpdateCoreExcludefile(adminDir
))
940 temp
+= _T("ignore");
941 if (CheckFileChanged(temp
))
947 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
949 if(temp
[i
] == _T('\\'))
961 int CGitIgnoreList::FetchIgnoreFile(const CString
&gitdir
, const CString
&gitignore
, bool isGlobal
)
963 if (CGit::GitPathFileExists(gitignore
)) //if .gitignore remove, we need remote cache
965 CAutoWriteLock
lock(m_SharedMutex
);
966 m_Map
[gitignore
].FetchIgnoreList(gitdir
, gitignore
, isGlobal
);
970 CAutoWriteLock
lock(m_SharedMutex
);
971 m_Map
.erase(gitignore
);
976 int CGitIgnoreList::LoadAllIgnoreFile(const CString
&gitdir
, const CString
&path
, bool isDir
)
984 temp
.Replace(_T('/'), _T('\\'));
988 int x
= temp
.ReverseFind(_T('\\'));
993 while (!temp
.IsEmpty())
995 CString tempOrig
= temp
;
996 temp
+= _T("\\.git");
998 if (CGit::GitPathFileExists(temp
))
1000 CString gitignore
= temp
;
1001 gitignore
+= _T("ignore");
1002 if (CheckFileChanged(gitignore
))
1004 FetchIgnoreFile(gitdir
, gitignore
, false);
1007 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1008 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
1009 if (CheckFileChanged(wcglobalgitignore
))
1011 FetchIgnoreFile(gitdir
, wcglobalgitignore
, true);
1014 if (CheckAndUpdateCoreExcludefile(adminDir
))
1016 CString excludesFile
;
1018 CAutoReadLock
lock(m_SharedMutex
);
1019 excludesFile
= m_CoreExcludesfiles
[adminDir
];
1021 if (!excludesFile
.IsEmpty())
1022 FetchIgnoreFile(gitdir
, excludesFile
, true);
1029 temp
+= _T("ignore");
1030 if (CheckFileChanged(temp
))
1032 FetchIgnoreFile(gitdir
, temp
, false);
1038 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1040 if(temp
[i
] == _T('\\'))
1047 temp
= temp
.Left(i
);
1051 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force
)
1053 // recheck every 30 seconds
1054 if (GetTickCount() - m_dMsysGitBinPathLastChecked
> 30000 || force
)
1056 m_dMsysGitBinPathLastChecked
= GetTickCount();
1057 CString
msysGitBinPath(CRegString(REG_MSYSGIT_PATH
, _T(""), FALSE
));
1058 if (msysGitBinPath
!= m_sMsysGitBinPath
)
1060 m_sMsysGitBinPath
= msysGitBinPath
;
1066 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString
&adminDir
)
1068 CString projectConfig
= adminDir
+ _T("config");
1069 CString globalConfig
= g_Git
.GetGitGlobalConfig();
1070 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
1072 CAutoWriteLock
lock(m_coreExcludefilesSharedMutex
);
1073 bool hasChanged
= CheckAndUpdateMsysGitBinpath();
1074 CString systemConfig
= m_sMsysGitBinPath
+ _T("\\..\\etc\\gitconfig");
1076 hasChanged
= hasChanged
|| CheckFileChanged(projectConfig
);
1077 hasChanged
= hasChanged
|| CheckFileChanged(globalConfig
);
1078 hasChanged
= hasChanged
|| CheckFileChanged(globalXDGConfig
);
1079 if (!m_sMsysGitBinPath
.IsEmpty())
1080 hasChanged
= hasChanged
|| CheckFileChanged(systemConfig
);
1082 CString excludesFile
;
1084 CAutoReadLock
lock(m_SharedMutex
);
1085 excludesFile
= m_CoreExcludesfiles
[adminDir
];
1087 if (!excludesFile
.IsEmpty())
1088 hasChanged
= hasChanged
|| CheckFileChanged(excludesFile
);
1093 CAutoConfig
config(true);
1094 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(projectConfig
), GIT_CONFIG_LEVEL_LOCAL
, FALSE
);
1095 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalConfig
), GIT_CONFIG_LEVEL_GLOBAL
, FALSE
);
1096 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalXDGConfig
), GIT_CONFIG_LEVEL_XDG
, FALSE
);
1097 if (!m_sMsysGitBinPath
.IsEmpty())
1098 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(systemConfig
), GIT_CONFIG_LEVEL_SYSTEM
, FALSE
);
1099 config
.GetString(_T("core.excludesfile"), excludesFile
);
1100 if (excludesFile
.IsEmpty())
1101 excludesFile
= GetWindowsHome() + _T("\\.config\\git\\ignore");
1102 else if (excludesFile
.Find(_T("~/")) == 0)
1103 excludesFile
= GetWindowsHome() + excludesFile
.Mid(1);
1105 CAutoWriteLock
lockMap(m_SharedMutex
);
1106 g_Git
.GetFileModifyTime(projectConfig
, &m_Map
[projectConfig
].m_LastModifyTime
);
1107 g_Git
.GetFileModifyTime(globalXDGConfig
, &m_Map
[globalXDGConfig
].m_LastModifyTime
);
1108 if (m_Map
[globalXDGConfig
].m_LastModifyTime
== 0)
1109 m_Map
.erase(globalXDGConfig
);
1110 g_Git
.GetFileModifyTime(globalConfig
, &m_Map
[globalConfig
].m_LastModifyTime
);
1111 if (m_Map
[globalConfig
].m_LastModifyTime
== 0)
1112 m_Map
.erase(globalConfig
);
1113 if (!m_sMsysGitBinPath
.IsEmpty())
1114 g_Git
.GetFileModifyTime(systemConfig
, &m_Map
[systemConfig
].m_LastModifyTime
);
1115 if (m_Map
[systemConfig
].m_LastModifyTime
== 0 || m_sMsysGitBinPath
.IsEmpty())
1116 m_Map
.erase(systemConfig
);
1117 m_CoreExcludesfiles
[adminDir
] = excludesFile
;
1121 const CString
CGitIgnoreList::GetWindowsHome()
1123 static CString
sWindowsHome(g_Git
.GetHomeDirectory());
1124 return sWindowsHome
;
1126 bool CGitIgnoreList::IsIgnore(const CString
&path
, const CString
&projectroot
, bool isDir
)
1130 str
.Replace(_T('\\'),_T('/'));
1132 if (str
.GetLength()>0)
1133 if (str
[str
.GetLength()-1] == _T('/'))
1134 str
= str
.Left(str
.GetLength() - 1);
1137 ret
= CheckIgnore(str
, projectroot
, isDir
);
1140 int start
= str
.ReverseFind(_T('/'));
1144 str
= str
.Left(start
);
1145 ret
= CheckIgnore(str
, projectroot
, isDir
);
1150 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString
&ignorefile
, const CStringA
&patha
, const char * base
, int &type
)
1152 if (m_Map
.find(ignorefile
) != m_Map
.end())
1155 if(m_Map
[ignorefile
].m_pExcludeList
)
1156 ret
= git_check_excluded_1(patha
, patha
.GetLength(), base
, &type
, m_Map
[ignorefile
].m_pExcludeList
);
1157 if (ret
== 0 || ret
== 1)
1162 int CGitIgnoreList::CheckIgnore(const CString
&path
, const CString
&projectroot
, bool isDir
)
1164 CString temp
= projectroot
+ _T("\\") + path
;
1165 temp
.Replace(_T('/'), _T('\\'));
1167 CStringA patha
= CUnicodeUtils::GetMulti(path
, CP_UTF8
);
1168 patha
.Replace('\\', '/');
1175 // strip directory name
1176 // we do not need to check for a .ignore file inside a directory we might ignore
1177 int i
= temp
.ReverseFind(_T('\\'));
1179 temp
= temp
.Left(i
);
1185 int x
= temp
.ReverseFind(_T('\\'));
1187 temp
= temp
.Left(x
);
1190 int pos
= patha
.ReverseFind('/');
1191 const char * base
= (pos
>= 0) ? ((const char*)patha
+ pos
+ 1) : patha
;
1195 CAutoReadLock
lock(m_SharedMutex
);
1196 while (!temp
.IsEmpty())
1198 CString tempOrig
= temp
;
1199 temp
+= _T("\\.git");
1201 if (CGit::GitPathFileExists(temp
))
1203 CString gitignore
= temp
;
1204 gitignore
+= _T("ignore");
1205 if ((ret
= CheckFileAgainstIgnoreList(gitignore
, patha
, base
, type
)) != -1)
1208 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1209 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
1210 if ((ret
= CheckFileAgainstIgnoreList(wcglobalgitignore
, patha
, base
, type
)) != -1)
1213 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1214 if (!excludesFile
.IsEmpty())
1215 ret
= CheckFileAgainstIgnoreList(excludesFile
, patha
, base
, type
);
1221 temp
+= _T("ignore");
1222 if ((ret
= CheckFileAgainstIgnoreList(temp
, patha
, base
, type
)) != -1)
1228 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1230 if (temp
[i
] == _T('\\'))
1237 temp
= temp
.Left(i
);
1243 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString
&gitdir
, bool readTree
/* = true */)
1245 SHARED_TREE_PTR ptr
;
1246 ptr
= this->SafeGet(gitdir
);
1248 if (ptr
.get() && !ptr
->CheckHeadUpdate() && (!readTree
|| ptr
->HeadHashEqualsTreeHash()))
1251 ptr
= SHARED_TREE_PTR(new CGitHeadFileList
);
1252 ptr
->ReadHeadHash(gitdir
);
1256 this->SafeSet(gitdir
, ptr
);
1261 int CGitHeadFileMap::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
, bool *isVersion
)
1271 CString subpath
= path
;
1272 subpath
.Replace(_T('\\'), _T('/'));
1276 subpath
.MakeLower();
1278 CheckHeadAndUpdate(gitdir
);
1280 SHARED_TREE_PTR treeptr
= SafeGet(gitdir
);
1283 if (treeptr
->HeadFileIsEmpty())
1288 else if (treeptr
->empty())
1295 *isVersion
= (SearchInSortVector(*treeptr
, subpath
, subpath
.GetLength()) >= 0);
1297 *isVersion
= (SearchInSortVector(*treeptr
, subpath
, -1) >= 0);