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.
23 #include "GitConfig.h"
25 #include "UnicodeUtils.h"
28 #include <sys/types.h>
30 #include "SmartHandle.h"
31 #include "git2/sys/repository.h"
37 CAutoReadLock(SharedMutex
* lock
)
40 lock
->AcquireShared();
44 m_Lock
->ReleaseShared();
52 CAutoWriteLock(SharedMutex
* lock
)
55 lock
->AcquireExclusive();
59 m_Lock
->ReleaseExclusive();
63 CGitAdminDirMap g_AdminDirMap
;
65 int CGitIndex::Print()
67 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
68 (int)this->m_ModifyTime
,
70 this->m_IndexHash
.ToString(),
76 CGitIndexList::CGitIndexList()
78 this->m_LastModifyTime
= 0;
81 m_bCheckContent
= !!(CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContent"), TRUE
) == TRUE
);
84 CGitIndexList::~CGitIndexList()
86 if (repository
!= NULL
)
88 git_repository_free(repository
);
93 static bool SortIndex(const CGitIndex
&Item1
, const CGitIndex
&Item2
)
95 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
98 static bool SortTree(const CGitTreeItem
&Item1
, const CGitTreeItem
&Item2
)
100 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
103 int CGitIndexList::ReadIndex(CString dgitdir
)
107 CStringA gitdir
= CUnicodeUtils::GetMulti(dgitdir
, CP_UTF8
);
109 m_critRepoSec
.Lock();
110 if (repository
!= NULL
)
112 git_repository_free(repository
);
115 git_index
*index
= NULL
;
117 if (git_repository_open(&repository
, gitdir
))
119 m_critRepoSec
.Unlock();
125 git_config_new(&config
);
127 CString projectConfig
= dgitdir
+ _T("config");
128 CString globalConfig
= g_Git
.GetGitGlobalConfig();
129 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
130 CString
msysGitBinPath(CRegString(REG_MSYSGIT_PATH
, _T(""), FALSE
));
132 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(projectConfig
), GIT_CONFIG_LEVEL_LOCAL
, FALSE
);
133 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalConfig
), GIT_CONFIG_LEVEL_GLOBAL
, FALSE
);
134 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalXDGConfig
), GIT_CONFIG_LEVEL_XDG
, FALSE
);
135 if (!msysGitBinPath
.IsEmpty())
136 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(msysGitBinPath
+ _T("\\..\\etc\\gitconfig")), GIT_CONFIG_LEVEL_SYSTEM
, FALSE
);
138 git_repository_set_config(repository
, config
);
139 git_config_free(config
);
142 // load index in order to enumerate files
143 if (git_repository_index(&index
, repository
))
145 git_repository_free(repository
);
147 m_critRepoSec
.Unlock();
151 size_t ecount
= git_index_entrycount(index
);
153 for (size_t i
= 0; i
< ecount
; ++i
)
155 const git_index_entry
*e
= git_index_get_byindex(index
, i
);
157 this->at(i
).m_FileName
.Empty();
158 g_Git
.StringAppend(&this->at(i
).m_FileName
, (BYTE
*)e
->path
, CP_UTF8
);
159 this->at(i
).m_FileName
.MakeLower();
160 this->at(i
).m_ModifyTime
= e
->mtime
.seconds
;
161 this->at(i
).m_Flags
= e
->flags
| e
->flags_extended
;
162 this->at(i
).m_IndexHash
= (char *) e
->oid
.id
;
165 git_index_free(index
);
167 g_Git
.GetFileModifyTime(dgitdir
+ _T("index"), &this->m_LastModifyTime
);
168 std::sort(this->begin(), this->end(), SortIndex
);
170 m_critRepoSec
.Unlock();
175 int CGitIndexList::GetFileStatus(const CString
&gitdir
, const CString
&pathorg
, git_wc_status_kind
*status
, __int64 time
, FILL_STATUS_CALLBACK callback
, void *pData
, CGitHash
*pHash
, bool * assumeValid
, bool * skipWorktree
)
179 CString path
= pathorg
;
182 int start
= SearchInSortVector(*this, path
, -1);
186 *status
= git_wc_status_unversioned
;
194 if (index
>= (int)size())
197 // skip-worktree has higher priority than assume-valid
198 if (at(index
).m_Flags
& GIT_IDXENTRY_SKIP_WORKTREE
)
200 *status
= git_wc_status_normal
;
202 *skipWorktree
= true;
204 else if (at(index
).m_Flags
& GIT_IDXENTRY_VALID
)
206 *status
= git_wc_status_normal
;
210 else if (time
== at(index
).m_ModifyTime
)
212 *status
= git_wc_status_normal
;
214 else if (m_bCheckContent
&& repository
)
217 CStringA fileA
= CUnicodeUtils::GetMulti(pathorg
, CP_UTF8
);
218 m_critRepoSec
.Lock(); // prevent concurrent access to repository instance and especially filter-lists
219 if (!git_repository_hashfile(&actual
, repository
, fileA
, GIT_OBJ_BLOB
, NULL
) && !git_oid_cmp(&actual
, (const git_oid
*)at(index
).m_IndexHash
.m_hash
))
221 at(index
).m_ModifyTime
= time
;
222 *status
= git_wc_status_normal
;
225 *status
= git_wc_status_modified
;
226 m_critRepoSec
.Unlock();
229 *status
= git_wc_status_modified
;
231 if (at(index
).m_Flags
& GIT_IDXENTRY_STAGEMASK
)
232 *status
= git_wc_status_conflicted
;
233 else if (at(index
).m_Flags
& GIT_IDXENTRY_INTENT_TO_ADD
)
234 *status
= git_wc_status_added
;
237 *pHash
= at(index
).m_IndexHash
;
242 if (callback
&& status
&& assumeValid
&& skipWorktree
)
243 callback(gitdir
+ _T("\\") + pathorg
, *status
, false, pData
, *assumeValid
, *skipWorktree
);
247 int CGitIndexList::GetStatus(const CString
&gitdir
,const CString
&pathParam
, git_wc_status_kind
*status
,
248 BOOL IsFull
, BOOL
/*IsRecursive*/,
249 FILL_STATUS_CALLBACK callback
, void *pData
,
250 CGitHash
*pHash
, bool * assumeValid
, bool * skipWorktree
)
254 CString path
= pathParam
;
258 git_wc_status_kind dirstatus
= git_wc_status_none
;
261 result
= g_Git
.GetFileModifyTime(gitdir
, &time
, &isDir
);
263 result
= g_Git
.GetFileModifyTime(gitdir
+ _T("\\") + path
, &time
, &isDir
);
267 *status
= git_wc_status_deleted
;
268 if (callback
&& assumeValid
&& skipWorktree
)
269 callback(gitdir
+ _T("\\") + path
, git_wc_status_deleted
, false, pData
, *assumeValid
, *skipWorktree
);
277 if (path
.Right(1) != _T("\\"))
280 int len
= path
.GetLength();
282 for (size_t i
= 0; i
< size(); ++i
)
284 if (at(i
).m_FileName
.GetLength() > len
)
286 if (at(i
).m_FileName
.Left(len
) == path
)
290 *status
= git_wc_status_normal
;
292 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);
298 result
= g_Git
.GetFileModifyTime(gitdir
+_T("\\") + at(i
).m_FileName
, &time
);
302 *status
= git_wc_status_none
;
304 *assumeValid
= false;
306 *skipWorktree
= false;
307 GetFileStatus(gitdir
, at(i
).m_FileName
, status
, time
, callback
, pData
, NULL
, assumeValid
, skipWorktree
);
308 // 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
309 if (callback
&& assumeValid
&& skipWorktree
&& (*assumeValid
|| *skipWorktree
))
310 callback(gitdir
+ _T("\\") + path
, *status
, false, pData
, *assumeValid
, *skipWorktree
);
311 if (*status
!= git_wc_status_none
)
313 if (dirstatus
== git_wc_status_none
)
315 dirstatus
= git_wc_status_normal
;
317 if (*status
!= git_wc_status_normal
)
319 dirstatus
= git_wc_status_modified
;
328 if (dirstatus
!= git_wc_status_none
)
334 *status
= git_wc_status_unversioned
;
337 callback(gitdir
+ _T("\\") + path
, *status
, false, pData
, false, false);
344 GetFileStatus(gitdir
, path
, status
, time
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
350 int CGitIndexFileMap::Check(const CString
&gitdir
, bool *isChanged
)
355 CString IndexFile
= g_AdminDirMap
.GetAdminDir(gitdir
) + _T("index");
357 /* Get data associated with "crt_stat.c": */
358 result
= g_Git
.GetFileModifyTime(IndexFile
, &time
);
363 SHARED_INDEX_PTR pIndex
;
364 pIndex
= this->SafeGet(gitdir
);
366 if (pIndex
.get() == NULL
)
373 if (pIndex
->m_LastModifyTime
== time
)
386 int CGitIndexFileMap::LoadIndex(const CString
&gitdir
)
390 SHARED_INDEX_PTR
pIndex(new CGitIndexList
);
392 if(pIndex
->ReadIndex(g_AdminDirMap
.GetAdminDir(gitdir
)))
395 this->SafeSet(gitdir
, pIndex
);
404 int CGitIndexFileMap::GetFileStatus(const CString
&gitdir
, const CString
&path
, git_wc_status_kind
*status
,BOOL IsFull
, BOOL IsRecursive
,
405 FILL_STATUS_CALLBACK callback
, void *pData
,
407 bool isLoadUpdatedIndex
, bool * assumeValid
, bool * skipWorktree
)
411 CheckAndUpdate(gitdir
, isLoadUpdatedIndex
);
413 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
414 if (pIndex
.get() != NULL
)
416 pIndex
->GetStatus(gitdir
, path
, status
, IsFull
, IsRecursive
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
420 // git working tree has not index
421 *status
= git_wc_status_unversioned
;
431 int CGitIndexFileMap::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
,bool *isVersion
, bool isLoadUpdateIndex
)
441 CString subpath
= path
;
442 subpath
.Replace(_T('\\'), _T('/'));
448 CheckAndUpdate(gitdir
, isLoadUpdateIndex
);
450 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
455 *isVersion
= (SearchInSortVector(*pIndex
, subpath
, subpath
.GetLength()) >= 0);
457 *isVersion
= (SearchInSortVector(*pIndex
, subpath
, -1) >= 0);
467 // This method is assumed to be called with m_SharedMutex locked.
468 int CGitHeadFileList::GetPackRef(const CString
&gitdir
)
470 CString PackRef
= g_AdminDirMap
.GetAdminDir(gitdir
) + _T("packed-refs");
473 if (g_Git
.GetFileModifyTime(PackRef
, &mtime
))
475 //packed refs is not existed
476 this->m_PackRefFile
.Empty();
477 this->m_PackRefMap
.clear();
480 else if(mtime
== m_LastModifyTimePackRef
)
486 this->m_PackRefFile
= PackRef
;
487 this->m_LastModifyTimePackRef
= mtime
;
492 this->m_PackRefMap
.clear();
494 CAutoFile hfile
= CreateFile(PackRef
,
496 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
499 FILE_ATTRIBUTE_NORMAL
,
509 DWORD filesize
= GetFileSize(hfile
, NULL
);
517 buff
= new char[filesize
];
519 ReadFile(hfile
, buff
, filesize
, &size
, NULL
);
521 if (size
!= filesize
)
531 for(DWORD i
=0;i
<filesize
;)
535 if (buff
[i
] == '#' || buff
[i
] == '^')
537 while (buff
[i
] != '\n')
549 while (buff
[i
] != ' ')
551 hash
.AppendChar(buff
[i
]);
561 while (buff
[i
] != '\n')
563 ref
.AppendChar(buff
[i
]);
571 this->m_PackRefMap
[ref
] = hash
;
574 while (buff
[i
] == '\n')
589 int CGitHeadFileList::ReadHeadHash(CString gitdir
)
592 CAutoWriteLock
lock(&this->m_SharedMutex
);
593 m_Gitdir
= g_AdminDirMap
.GetAdminDir(gitdir
);
595 m_HeadFile
= m_Gitdir
+ _T("HEAD");
597 if( g_Git
.GetFileModifyTime(m_HeadFile
, &m_LastModifyTimeHead
))
604 CAutoFile hfile
= CreateFile(m_HeadFile
,
606 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
609 FILE_ATTRIBUTE_NORMAL
,
619 unsigned char buffer
[40] ;
620 ReadFile(hfile
, buffer
, 4, &size
, NULL
);
627 if (strcmp((const char*)buffer
,"ref:") == 0)
629 DWORD filesize
= GetFileSize(hfile
, NULL
);
632 m_HeadRefFile
.Empty();
637 unsigned char *p
= (unsigned char*)malloc(filesize
-4);
639 ReadFile(hfile
, p
, filesize
- 4, &size
, NULL
);
641 m_HeadRefFile
.Empty();
642 g_Git
.StringAppend(&this->m_HeadRefFile
, p
, CP_UTF8
, filesize
- 4);
643 CString ref
= this->m_HeadRefFile
;
646 ref
= ref
.Tokenize(_T("\n"), start
);
648 m_HeadRefFile
= m_Gitdir
+ m_HeadRefFile
.Trim();
649 m_HeadRefFile
.Replace(_T('/'),_T('\\'));
652 if (g_Git
.GetFileModifyTime(m_HeadRefFile
, &time
, NULL
))
654 m_HeadRefFile
.Empty();
655 if (GetPackRef(gitdir
))
660 if (this->m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
665 this ->m_Head
= m_PackRefMap
[ref
];
670 CAutoFile href
= CreateFile(m_HeadRefFile
,
672 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
675 FILE_ATTRIBUTE_NORMAL
,
680 m_HeadRefFile
.Empty();
682 if (GetPackRef(gitdir
))
688 if (this->m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
693 this ->m_Head
= m_PackRefMap
[ref
];
697 ReadFile(href
, buffer
, 40, &size
, NULL
);
703 this->m_Head
.ConvertFromStrA((char*)buffer
);
705 this->m_LastModifyTimeRef
= time
;
710 ReadFile(hfile
, buffer
+ 4, 40 - 4, &size
, NULL
);
716 m_HeadRefFile
.Empty();
718 this->m_Head
.ConvertFromStrA((char*)buffer
);
730 bool CGitHeadFileList::CheckHeadUpdate()
732 CAutoReadLock
lock(&m_SharedMutex
);
733 if (this->m_HeadFile
.IsEmpty())
738 if (g_Git
.GetFileModifyTime(m_HeadFile
, &mtime
))
741 if (mtime
!= this->m_LastModifyTimeHead
)
744 if (!this->m_HeadRefFile
.IsEmpty())
746 if (g_Git
.GetFileModifyTime(m_HeadRefFile
, &mtime
))
749 if (mtime
!= this->m_LastModifyTimeRef
)
753 if(!this->m_PackRefFile
.IsEmpty())
755 if (g_Git
.GetFileModifyTime(m_PackRefFile
, &mtime
))
758 if (mtime
!= this->m_LastModifyTimePackRef
)
762 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
763 // So we need to retry again and again until the ref exists - otherwise we will never notice
764 if (this->m_Head
.IsEmpty() && this->m_HeadRefFile
.IsEmpty() && this->m_PackRefFile
.IsEmpty())
770 bool CGitHeadFileList::HeadHashEqualsTreeHash()
772 CAutoReadLock
lock(&this->m_SharedMutex
);
773 return (m_Head
== m_TreeHash
);
776 bool CGitHeadFileList::HeadFileIsEmpty()
778 CAutoReadLock
lock(&this->m_SharedMutex
);
779 return m_HeadFile
.IsEmpty();
782 bool CGitHeadFileList::HeadIsEmpty()
784 CAutoReadLock
lock(&this->m_SharedMutex
);
785 return m_Head
.IsEmpty();
788 int CGitHeadFileList::CallBack(const unsigned char *sha1
, const char *base
, int baselen
,
789 const char *pathname
, unsigned mode
, int /*stage*/, void *context
)
791 #define S_IFGITLINK 0160000
793 CGitHeadFileList
*p
= (CGitHeadFileList
*)context
;
796 if( (mode
&S_IFMT
) != S_IFGITLINK
)
797 return READ_TREE_RECURSIVE
;
800 size_t cur
= p
->size();
801 p
->resize(p
->size() + 1);
802 p
->at(cur
).m_Hash
= (char*)sha1
;
803 p
->at(cur
).m_FileName
.Empty();
805 g_Git
.StringAppend(&p
->at(cur
).m_FileName
, (BYTE
*)base
, CP_UTF8
, baselen
);
806 g_Git
.StringAppend(&p
->at(cur
).m_FileName
,(BYTE
*)pathname
, CP_UTF8
);
808 p
->at(cur
).m_FileName
.MakeLower();
810 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
812 //p->m_Map[p->at(cur).m_FileName] = cur;
814 if( (mode
&S_IFMT
) == S_IFGITLINK
)
817 return READ_TREE_RECURSIVE
;
820 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
)
822 size_t count
= git_tree_entrycount(tree
);
823 for (size_t i
= 0; i
< count
; ++i
)
825 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
828 int mode
= git_tree_entry_filemode(entry
);
829 if( CallBack(git_tree_entry_id(entry
)->id
,
832 git_tree_entry_name(entry
),
835 data
) == READ_TREE_RECURSIVE
840 git_object
*object
= NULL
;
841 git_tree_entry_to_object(&object
, &repo
, entry
);
844 CStringA parent
= base
;
845 parent
+= git_tree_entry_name(entry
);
847 ReadTreeRecursive(repo
, (git_tree
*)object
, parent
, CallBack
, data
);
848 git_object_free(object
);
857 // ReadTree is/must only be executed on an empty list
858 int CGitHeadFileList::ReadTree()
860 CAutoWriteLock
lock(&m_SharedMutex
);
861 CStringA gitdir
= CUnicodeUtils::GetMulti(m_Gitdir
, CP_UTF8
);
862 git_repository
*repository
= NULL
;
863 git_commit
*commit
= NULL
;
864 git_tree
* tree
= NULL
;
866 ATLASSERT(this->empty());
869 ret
= git_repository_open(&repository
, gitdir
);
872 ret
= git_commit_lookup(&commit
, repository
, (const git_oid
*)m_Head
.m_hash
);
876 ret
= git_commit_tree(&tree
, commit
);
880 ret
= ReadTreeRecursive(*repository
, tree
,"", CGitHeadFileList::CallBack
,this);
884 std::sort(this->begin(), this->end(), SortTree
);
885 this->m_TreeHash
= (char*)(git_commit_id(commit
)->id
);
893 git_commit_free(commit
);
896 git_repository_free(repository
);
901 m_LastModifyTimeHead
= 0;
907 int CGitIgnoreItem::FetchIgnoreList(const CString
&projectroot
, const CString
&file
, bool isGlobal
)
909 CAutoWriteLock
lock(&this->m_SharedMutex
);
911 if (this->m_pExcludeList
)
913 git_free_exclude_list(m_pExcludeList
);
922 this->m_BaseDir
.Empty();
925 CString base
= file
.Mid(projectroot
.GetLength() + 1);
926 base
.Replace(_T('\\'), _T('/'));
928 int start
= base
.ReverseFind(_T('/'));
931 base
= base
.Left(start
);
932 this->m_BaseDir
= CUnicodeUtils::GetMulti(base
, CP_UTF8
) + "/";
937 if(g_Git
.GetFileModifyTime(file
, &m_LastModifyTime
))
940 if(git_create_exclude_list(&this->m_pExcludeList
))
944 CAutoFile hfile
= CreateFile(file
,
946 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
949 FILE_ATTRIBUTE_NORMAL
,
956 DWORD size
=0,filesize
=0;
958 filesize
=GetFileSize(hfile
, NULL
);
960 if(filesize
== INVALID_FILE_SIZE
)
963 m_buffer
= new BYTE
[filesize
+ 1];
965 if (m_buffer
== NULL
)
968 if (!ReadFile(hfile
, m_buffer
, filesize
, &size
, NULL
))
969 return GetLastError();
973 for (DWORD i
= 0; i
< size
; ++i
)
975 if (m_buffer
[i
] == '\n' || m_buffer
[i
] == '\r' || i
== (size
- 1))
977 if (m_buffer
[i
] == '\n' || m_buffer
[i
] == '\r')
982 if(p
[0] != '#' && p
[0] != 0)
983 git_add_exclude((const char*)p
,
985 m_BaseDir
.GetLength(),
986 this->m_pExcludeList
, ++line
);
988 p
= m_buffer
+ i
+ 1;
995 bool CGitIgnoreList::CheckFileChanged(const CString
&path
)
999 int ret
= g_Git
.GetFileModifyTime(path
, &time
);
1001 this->m_SharedMutex
.AcquireShared();
1002 bool cacheExist
= (m_Map
.find(path
) != m_Map
.end());
1003 this->m_SharedMutex
.ReleaseShared();
1005 if (!cacheExist
&& ret
== 0)
1007 CAutoWriteLock
lock(&this->m_SharedMutex
);
1008 m_Map
[path
].m_LastModifyTime
= 0;
1009 m_Map
[path
].m_SharedMutex
.Init();
1011 // both cache and file is not exist
1012 if ((ret
!= 0) && (!cacheExist
))
1015 // file exist but cache miss
1016 if ((ret
== 0) && (!cacheExist
))
1019 // file not exist but cache exist
1020 if ((ret
!= 0) && (cacheExist
))
1024 // file exist and cache exist
1027 CAutoReadLock
lock(&this->m_SharedMutex
);
1028 if (m_Map
[path
].m_LastModifyTime
== time
)
1034 bool CGitIgnoreList::CheckIgnoreChanged(const CString
&gitdir
,const CString
&path
)
1041 temp
.Replace(_T('/'), _T('\\'));
1043 while(!temp
.IsEmpty())
1045 CString tempOrig
= temp
;
1046 temp
+= _T("\\.git");
1048 if (CGit::GitPathFileExists(temp
))
1050 CString gitignore
=temp
;
1051 gitignore
+= _T("ignore");
1052 if (CheckFileChanged(gitignore
))
1055 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1056 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
1057 if (CheckFileChanged(wcglobalgitignore
))
1060 if (CheckAndUpdateCoreExcludefile(adminDir
))
1067 temp
+= _T("ignore");
1068 if (CheckFileChanged(temp
))
1074 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1076 if(temp
[i
] == _T('\\'))
1083 temp
= temp
.Left(i
);
1088 int CGitIgnoreList::FetchIgnoreFile(const CString
&gitdir
, const CString
&gitignore
, bool isGlobal
)
1090 if (CGit::GitPathFileExists(gitignore
)) //if .gitignore remove, we need remote cache
1092 CAutoWriteLock
lock(&this->m_SharedMutex
);
1093 if (m_Map
.find(gitignore
) == m_Map
.end())
1094 m_Map
[gitignore
].m_SharedMutex
.Init();
1096 m_Map
[gitignore
].FetchIgnoreList(gitdir
, gitignore
, isGlobal
);
1100 CAutoWriteLock
lock(&this->m_SharedMutex
);
1101 if (m_Map
.find(gitignore
) != m_Map
.end())
1102 m_Map
[gitignore
].m_SharedMutex
.Release();
1104 m_Map
.erase(gitignore
);
1109 int CGitIgnoreList::LoadAllIgnoreFile(const CString
&gitdir
,const CString
&path
)
1117 temp
.Replace(_T('/'), _T('\\'));
1119 while (!temp
.IsEmpty())
1121 CString tempOrig
= temp
;
1122 temp
+= _T("\\.git");
1124 if (CGit::GitPathFileExists(temp
))
1126 CString gitignore
= temp
;
1127 gitignore
+= _T("ignore");
1128 if (CheckFileChanged(gitignore
))
1130 FetchIgnoreFile(gitdir
, gitignore
, false);
1133 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1134 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
1135 if (CheckFileChanged(wcglobalgitignore
))
1137 FetchIgnoreFile(gitdir
, wcglobalgitignore
, true);
1140 if (CheckAndUpdateCoreExcludefile(adminDir
))
1142 m_SharedMutex
.AcquireShared();
1143 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1144 m_SharedMutex
.ReleaseShared();
1145 if (!excludesFile
.IsEmpty())
1146 FetchIgnoreFile(gitdir
, excludesFile
, true);
1153 temp
+= _T("ignore");
1154 if (CheckFileChanged(temp
))
1156 FetchIgnoreFile(gitdir
, temp
, false);
1162 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1164 if(temp
[i
] == _T('\\'))
1171 temp
= temp
.Left(i
);
1175 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force
)
1177 // recheck every 30 seconds
1178 if (GetTickCount() - m_dMsysGitBinPathLastChecked
> 30000 || force
)
1180 m_dMsysGitBinPathLastChecked
= GetTickCount();
1181 CString
msysGitBinPath(CRegString(REG_MSYSGIT_PATH
, _T(""), FALSE
));
1182 if (msysGitBinPath
!= m_sMsysGitBinPath
)
1184 m_sMsysGitBinPath
= msysGitBinPath
;
1190 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString
&adminDir
)
1192 CString projectConfig
= adminDir
+ _T("config");
1193 CString globalConfig
= g_Git
.GetGitGlobalConfig();
1194 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
1196 CAutoWriteLock
lock(&m_coreExcludefilesSharedMutex
);
1197 bool hasChanged
= CheckAndUpdateMsysGitBinpath();
1198 CString systemConfig
= m_sMsysGitBinPath
+ _T("\\..\\etc\\gitconfig");
1200 hasChanged
= hasChanged
|| CheckFileChanged(projectConfig
);
1201 hasChanged
= hasChanged
|| CheckFileChanged(globalConfig
);
1202 hasChanged
= hasChanged
|| CheckFileChanged(globalXDGConfig
);
1203 if (!m_sMsysGitBinPath
.IsEmpty())
1204 hasChanged
= hasChanged
|| CheckFileChanged(systemConfig
);
1206 m_SharedMutex
.AcquireShared();
1207 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1208 m_SharedMutex
.ReleaseShared();
1209 if (!excludesFile
.IsEmpty())
1210 hasChanged
= hasChanged
|| CheckFileChanged(excludesFile
);
1215 git_config
* config
;
1216 git_config_new(&config
);
1217 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(projectConfig
), GIT_CONFIG_LEVEL_LOCAL
, FALSE
);
1218 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalConfig
), GIT_CONFIG_LEVEL_GLOBAL
, FALSE
);
1219 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(globalXDGConfig
), GIT_CONFIG_LEVEL_XDG
, FALSE
);
1220 if (!m_sMsysGitBinPath
.IsEmpty())
1221 git_config_add_file_ondisk(config
, CGit::GetGitPathStringA(systemConfig
), GIT_CONFIG_LEVEL_SYSTEM
, FALSE
);
1222 const char * out
= NULL
;
1223 CStringA
name(_T("core.excludesfile"));
1224 git_config_get_string(&out
, config
, name
);
1225 CStringA
excludesFileA(out
);
1226 excludesFile
= CUnicodeUtils::GetUnicode(excludesFileA
);
1227 if (excludesFile
.IsEmpty())
1228 excludesFile
= GetWindowsHome() + _T("\\.config\\git\\ignore");
1229 else if (excludesFile
.Find(_T("~/")) == 0)
1230 excludesFile
= GetWindowsHome() + excludesFile
.Mid(1);
1231 git_config_free(config
);
1233 CAutoWriteLock
lockMap(&m_SharedMutex
);
1234 g_Git
.GetFileModifyTime(projectConfig
, &m_Map
[projectConfig
].m_LastModifyTime
);
1235 g_Git
.GetFileModifyTime(globalXDGConfig
, &m_Map
[globalXDGConfig
].m_LastModifyTime
);
1236 if (m_Map
[globalXDGConfig
].m_LastModifyTime
== 0)
1238 m_Map
[globalXDGConfig
].m_SharedMutex
.Release();
1239 m_Map
.erase(globalXDGConfig
);
1241 g_Git
.GetFileModifyTime(globalConfig
, &m_Map
[globalConfig
].m_LastModifyTime
);
1242 if (m_Map
[globalConfig
].m_LastModifyTime
== 0)
1244 m_Map
[globalConfig
].m_SharedMutex
.Release();
1245 m_Map
.erase(globalConfig
);
1247 if (!m_sMsysGitBinPath
.IsEmpty())
1248 g_Git
.GetFileModifyTime(systemConfig
, &m_Map
[systemConfig
].m_LastModifyTime
);
1249 if (m_Map
[systemConfig
].m_LastModifyTime
== 0 || m_sMsysGitBinPath
.IsEmpty())
1251 m_Map
[systemConfig
].m_SharedMutex
.Release();
1252 m_Map
.erase(systemConfig
);
1254 m_CoreExcludesfiles
[adminDir
] = excludesFile
;
1258 const CString
CGitIgnoreList::GetWindowsHome()
1260 static CString
sWindowsHome(g_Git
.GetHomeDirectory());
1261 return sWindowsHome
;
1263 bool CGitIgnoreList::IsIgnore(const CString
&path
,const CString
&projectroot
)
1267 str
.Replace(_T('\\'),_T('/'));
1269 if (str
.GetLength()>0)
1270 if (str
[str
.GetLength()-1] == _T('/'))
1271 str
= str
.Left(str
.GetLength() - 1);
1274 ret
= CheckIgnore(str
, projectroot
);
1277 int start
= str
.ReverseFind(_T('/'));
1281 str
= str
.Left(start
);
1282 ret
= CheckIgnore(str
, projectroot
);
1287 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString
&ignorefile
, const CStringA
&patha
, const char * base
, int &type
)
1289 if (m_Map
.find(ignorefile
) != m_Map
.end())
1292 if(m_Map
[ignorefile
].m_pExcludeList
)
1293 ret
= git_check_excluded_1(patha
, patha
.GetLength(), base
, &type
, m_Map
[ignorefile
].m_pExcludeList
);
1294 if (ret
== 0 || ret
== 1)
1299 int CGitIgnoreList::CheckIgnore(const CString
&path
,const CString
&projectroot
)
1303 CString temp
= projectroot
+ _T("\\") + path
;
1304 temp
.Replace(_T('/'), _T('\\'));
1306 CStringA patha
= CUnicodeUtils::GetMulti(path
, CP_UTF8
);
1307 patha
.Replace('\\', '/');
1309 if(g_Git
.GetFileModifyTime(temp
, &time
, &dir
))
1317 // strip directory name
1318 // we do not need to check for a .ignore file inside a directory we might ignore
1319 int i
= temp
.ReverseFind(_T('\\'));
1321 temp
= temp
.Left(i
);
1327 int pos
= patha
.ReverseFind('/');
1328 base
= pos
>= 0 ? patha
.GetBuffer() + pos
+ 1 : patha
.GetBuffer();
1332 CAutoReadLock
lock(&this->m_SharedMutex
);
1333 while (!temp
.IsEmpty())
1335 CString tempOrig
= temp
;
1336 temp
+= _T("\\.git");
1338 if (CGit::GitPathFileExists(temp
))
1340 CString gitignore
= temp
;
1341 gitignore
+= _T("ignore");
1342 if ((ret
= CheckFileAgainstIgnoreList(gitignore
, patha
, base
, type
)) != -1)
1345 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1346 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
1347 if ((ret
= CheckFileAgainstIgnoreList(wcglobalgitignore
, patha
, base
, type
)) != -1)
1350 m_SharedMutex
.AcquireShared();
1351 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1352 m_SharedMutex
.ReleaseShared();
1353 if (!excludesFile
.IsEmpty())
1354 ret
= CheckFileAgainstIgnoreList(excludesFile
, patha
, base
, type
);
1360 temp
+= _T("ignore");
1361 if ((ret
= CheckFileAgainstIgnoreList(temp
, patha
, base
, type
)) != -1)
1367 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1369 if (temp
[i
] == _T('\\'))
1376 temp
= temp
.Left(i
);
1379 patha
.ReleaseBuffer();
1384 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString
&gitdir
, bool readTree
/* = true */)
1386 SHARED_TREE_PTR ptr
;
1387 ptr
= this->SafeGet(gitdir
);
1389 if (ptr
.get() && !ptr
->CheckHeadUpdate() && (!readTree
|| ptr
->HeadHashEqualsTreeHash()))
1392 ptr
= SHARED_TREE_PTR(new CGitHeadFileList
);
1393 ptr
->ReadHeadHash(gitdir
);
1397 this->SafeSet(gitdir
, ptr
);
1402 int CGitHeadFileMap::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
, bool *isVersion
)
1412 CString subpath
= path
;
1413 subpath
.Replace(_T('\\'), _T('/'));
1417 subpath
.MakeLower();
1419 CheckHeadAndUpdate(gitdir
);
1421 SHARED_TREE_PTR treeptr
= SafeGet(gitdir
);
1424 if (treeptr
->HeadFileIsEmpty())
1429 else if (treeptr
->empty())
1436 *isVersion
= (SearchInSortVector(*treeptr
, subpath
, subpath
.GetLength()) >= 0);
1438 *isVersion
= (SearchInSortVector(*treeptr
, subpath
, -1) >= 0);