1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - 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.
25 #include "GitConfig.h"
27 #include "UnicodeUtils.h"
30 #include <sys/types.h>
32 #include "SmartHandle.h"
38 CAutoReadLock(SharedMutex
* lock
)
41 lock
->AcquireShared();
45 m_Lock
->ReleaseShared();
53 CAutoWriteLock(SharedMutex
* lock
)
56 lock
->AcquireExclusive();
60 m_Lock
->ReleaseExclusive();
64 CGitAdminDirMap g_AdminDirMap
;
66 int CGitIndex::Print()
68 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
69 (int)this->m_ModifyTime
,
71 this->m_IndexHash
.ToString(),
77 CGitIndexList::CGitIndexList()
79 this->m_LastModifyTime
= 0;
82 m_bCheckContent
= !!(CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContent"), TRUE
) == TRUE
);
85 CGitIndexList::~CGitIndexList()
87 if (repository
!= NULL
)
89 git_repository_free(repository
);
94 static bool SortIndex(CGitIndex
&Item1
, CGitIndex
&Item2
)
96 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
99 static bool SortTree(CGitTreeItem
&Item1
, CGitTreeItem
&Item2
)
101 return Item1
.m_FileName
.Compare(Item2
.m_FileName
) < 0;
104 int CGitIndexList::ReadIndex(CString dgitdir
)
108 CStringA gitdir
= CUnicodeUtils::GetMulti(dgitdir
, CP_UTF8
);
110 m_critRepoSec
.Lock();
111 if (repository
!= NULL
)
113 git_repository_free(repository
);
116 git_index
*index
= NULL
;
118 int ret
= git_repository_open(&repository
, gitdir
.GetBuffer());
119 gitdir
.ReleaseBuffer();
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 CStringA projectConfigA
= CUnicodeUtils::GetMulti(projectConfig
, CP_UTF8
);
133 git_config_add_file_ondisk(config
, projectConfigA
.GetBuffer(), 4, FALSE
);
134 projectConfigA
.ReleaseBuffer();
135 CStringA globalConfigA
= CUnicodeUtils::GetMulti(globalConfig
, CP_UTF8
);
136 git_config_add_file_ondisk(config
, globalConfigA
.GetBuffer(), 3, FALSE
);
137 globalConfigA
.ReleaseBuffer();
138 CStringA globalXDGConfigA
= CUnicodeUtils::GetMulti(globalXDGConfig
, CP_UTF8
);
139 git_config_add_file_ondisk(config
, globalXDGConfigA
.GetBuffer(), 2, FALSE
);
140 globalXDGConfigA
.ReleaseBuffer();
141 if (!msysGitBinPath
.IsEmpty())
143 CString systemConfig
= msysGitBinPath
+ _T("\\..\\etc\\gitconfig");
144 CStringA systemConfigA
= CUnicodeUtils::GetMulti(systemConfig
, CP_UTF8
);
145 git_config_add_file_ondisk(config
, systemConfigA
.GetBuffer(), 1, FALSE
);
146 systemConfigA
.ReleaseBuffer();
149 git_repository_set_config(repository
, config
);
151 // load index in order to enumerate files
152 if (git_repository_index(&index
, repository
))
155 m_critRepoSec
.Unlock();
159 size_t ecount
= git_index_entrycount(index
);
161 for (size_t i
= 0; i
< ecount
; ++i
)
163 const git_index_entry
*e
= git_index_get_byindex(index
, i
);
165 this->at(i
).m_FileName
.Empty();
166 g_Git
.StringAppend(&this->at(i
).m_FileName
, (BYTE
*)e
->path
, CP_UTF8
);
167 this->at(i
).m_FileName
.MakeLower();
168 this->at(i
).m_ModifyTime
= e
->mtime
.seconds
;
169 this->at(i
).m_Flags
= e
->flags
| e
->flags_extended
;
170 this->at(i
).m_IndexHash
= (char *) e
->oid
.id
;
173 git_index_free(index
);
175 g_Git
.GetFileModifyTime(dgitdir
+ _T("index"), &this->m_LastModifyTime
);
176 std::sort(this->begin(), this->end(), SortIndex
);
178 m_critRepoSec
.Unlock();
183 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
)
187 CString path
= pathorg
;
190 int start
= SearchInSortVector(*this, ((CString
&)path
).GetBuffer(), -1);
191 ((CString
&)path
).ReleaseBuffer();
195 *status
= git_wc_status_unversioned
;
205 if (index
>= (int)size())
208 // skip-worktree has higher priority than assume-valid
209 if (at(index
).m_Flags
& GIT_IDXENTRY_SKIP_WORKTREE
)
211 *status
= git_wc_status_normal
;
213 *skipWorktree
= true;
215 else if (at(index
).m_Flags
& GIT_IDXENTRY_VALID
)
217 *status
= git_wc_status_normal
;
221 else if (time
== at(index
).m_ModifyTime
)
223 *status
= git_wc_status_normal
;
225 else if (m_bCheckContent
&& repository
)
228 CStringA fileA
= CUnicodeUtils::GetMulti(pathorg
, CP_UTF8
);
229 m_critRepoSec
.Lock(); // prevent concurrent access to repository instance and especially filter-lists
230 if (!git_repository_hashfile(&actual
, repository
, fileA
.GetBuffer(), GIT_OBJ_BLOB
, NULL
) && !git_oid_cmp(&actual
, (const git_oid
*)at(index
).m_IndexHash
.m_hash
))
232 at(index
).m_ModifyTime
= time
;
233 *status
= git_wc_status_normal
;
236 *status
= git_wc_status_modified
;
237 fileA
.ReleaseBuffer();
238 m_critRepoSec
.Unlock();
241 *status
= git_wc_status_modified
;
243 if (at(index
).m_Flags
& GIT_IDXENTRY_STAGEMASK
)
244 *status
= git_wc_status_conflicted
;
245 else if (at(index
).m_Flags
& GIT_IDXENTRY_INTENT_TO_ADD
)
246 *status
= git_wc_status_added
;
249 *pHash
= at(index
).m_IndexHash
;
254 if(callback
&& status
)
255 callback(gitdir
+ _T("\\") + pathorg
, *status
, false, pData
, *assumeValid
, *skipWorktree
);
259 int CGitIndexList::GetStatus(const CString
&gitdir
,const CString
&pathParam
, git_wc_status_kind
*status
,
260 BOOL IsFull
, BOOL
/*IsRecursive*/,
261 FIll_STATUS_CALLBACK callback
,void *pData
,
262 CGitHash
*pHash
, bool * assumeValid
, bool * skipWorktree
)
265 git_wc_status_kind dirstatus
= git_wc_status_none
;
268 CString path
= pathParam
;
273 result
= g_Git
.GetFileModifyTime(gitdir
, &time
, &isDir
);
275 result
= g_Git
.GetFileModifyTime(gitdir
+ _T("\\") + path
, &time
, &isDir
);
279 *status
= git_wc_status_deleted
;
281 callback(gitdir
+ _T("\\") + path
, git_wc_status_deleted
, false, pData
, *assumeValid
, *skipWorktree
);
289 if (path
.Right(1) != _T("\\"))
292 int len
= path
.GetLength();
294 for (size_t i
= 0; i
< size(); i
++)
296 if (at(i
).m_FileName
.GetLength() > len
)
298 if (at(i
).m_FileName
.Left(len
) == path
)
302 *status
= git_wc_status_normal
;
304 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);
310 result
= g_Git
.GetFileModifyTime(gitdir
+_T("\\") + at(i
).m_FileName
, &time
);
314 *status
= git_wc_status_none
;
316 *assumeValid
= false;
318 *skipWorktree
= false;
319 GetFileStatus(gitdir
, at(i
).m_FileName
, status
, time
, callback
, pData
, NULL
, assumeValid
, skipWorktree
);
320 // 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
321 if (callback
&& (assumeValid
|| skipWorktree
))
322 callback(gitdir
+ _T("\\") + path
, *status
, false, pData
, *assumeValid
, *skipWorktree
);
323 if (*status
!= git_wc_status_none
)
325 if (dirstatus
== git_wc_status_none
)
327 dirstatus
= git_wc_status_normal
;
329 if (*status
!= git_wc_status_normal
)
331 dirstatus
= git_wc_status_modified
;
340 if (dirstatus
!= git_wc_status_none
)
346 *status
= git_wc_status_unversioned
;
349 callback(gitdir
+ _T("\\") + path
, *status
, false, pData
, false, false);
356 GetFileStatus(gitdir
, path
, status
, time
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
362 int CGitIndexFileMap::Check(const CString
&gitdir
, bool *isChanged
)
367 CString IndexFile
= g_AdminDirMap
.GetAdminDir(gitdir
) + _T("index");
369 /* Get data associated with "crt_stat.c": */
370 result
= g_Git
.GetFileModifyTime(IndexFile
, &time
);
375 SHARED_INDEX_PTR pIndex
;
376 pIndex
= this->SafeGet(gitdir
);
378 if (pIndex
.get() == NULL
)
385 if (pIndex
->m_LastModifyTime
== time
)
398 int CGitIndexFileMap::LoadIndex(const CString
&gitdir
)
402 SHARED_INDEX_PTR
pIndex(new CGitIndexList
);
404 if(pIndex
->ReadIndex(g_AdminDirMap
.GetAdminDir(gitdir
)))
407 this->SafeSet(gitdir
, pIndex
);
416 int CGitIndexFileMap::GetFileStatus(const CString
&gitdir
, const CString
&path
, git_wc_status_kind
*status
,BOOL IsFull
, BOOL IsRecursive
,
417 FIll_STATUS_CALLBACK callback
,void *pData
,
419 bool isLoadUpdatedIndex
, bool * assumeValid
, bool * skipWorktree
)
423 CheckAndUpdate(gitdir
, isLoadUpdatedIndex
);
425 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
426 if (pIndex
.get() != NULL
)
428 pIndex
->GetStatus(gitdir
, path
, status
, IsFull
, IsRecursive
, callback
, pData
, pHash
, assumeValid
, skipWorktree
);
432 // git working tree has not index
433 *status
= git_wc_status_unversioned
;
443 int CGitIndexFileMap::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
,bool *isVersion
, bool isLoadUpdateIndex
)
453 CString subpath
= path
;
454 subpath
.Replace(_T('\\'), _T('/'));
460 CheckAndUpdate(gitdir
, isLoadUpdateIndex
);
462 SHARED_INDEX_PTR pIndex
= this->SafeGet(gitdir
);
467 *isVersion
= (SearchInSortVector(*pIndex
, subpath
.GetBuffer(), subpath
.GetLength()) >= 0);
469 *isVersion
= (SearchInSortVector(*pIndex
, subpath
.GetBuffer(), -1) >= 0);
470 subpath
.ReleaseBuffer();
480 int CGitHeadFileList::GetPackRef(const CString
&gitdir
)
482 CString PackRef
= g_AdminDirMap
.GetAdminDir(gitdir
) + _T("packed-refs");
485 if (g_Git
.GetFileModifyTime(PackRef
, &mtime
))
487 CAutoWriteLock
lock(&this->m_SharedMutex
);
488 //packed refs is not existed
489 this->m_PackRefFile
.Empty();
490 this->m_PackRefMap
.clear();
493 else if(mtime
== m_LastModifyTimePackRef
)
499 CAutoWriteLock
lock(&this->m_SharedMutex
);
500 this->m_PackRefFile
= PackRef
;
501 this->m_LastModifyTimePackRef
= mtime
;
506 CAutoWriteLock
lock(&this->m_SharedMutex
);
507 this->m_PackRefMap
.clear();
509 CAutoFile hfile
= CreateFile(PackRef
,
511 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
514 FILE_ATTRIBUTE_NORMAL
,
524 DWORD filesize
= GetFileSize(hfile
, NULL
);
527 buff
= new char[filesize
];
529 ReadFile(hfile
, buff
, filesize
, &size
, NULL
);
531 if (size
!= filesize
)
540 for(DWORD i
=0;i
<filesize
;)
544 if (buff
[i
] == '#' || buff
[i
] == '^')
546 while (buff
[i
] != '\n')
558 while (buff
[i
] != ' ')
560 hash
.AppendChar(buff
[i
]);
570 while (buff
[i
] != '\n')
572 ref
.AppendChar(buff
[i
]);
580 this->m_PackRefMap
[ref
] = hash
;
583 while (buff
[i
] == '\n')
598 int CGitHeadFileList::ReadHeadHash(CString gitdir
)
601 CAutoWriteLock
lock(&this->m_SharedMutex
);
602 m_Gitdir
= g_AdminDirMap
.GetAdminDir(gitdir
);
604 m_HeadFile
= m_Gitdir
+ _T("HEAD");
606 if( g_Git
.GetFileModifyTime(m_HeadFile
, &m_LastModifyTimeHead
))
613 CAutoFile hfile
= CreateFile(m_HeadFile
,
615 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
618 FILE_ATTRIBUTE_NORMAL
,
627 DWORD size
= 0,filesize
= 0;
628 unsigned char buffer
[40] ;
629 ReadFile(hfile
, buffer
, 4, &size
, NULL
);
636 if (strcmp((const char*)buffer
,"ref:") == 0)
638 filesize
= GetFileSize(hfile
, NULL
);
640 unsigned char *p
= (unsigned char*)malloc(filesize
-4);
642 ReadFile(hfile
, p
, filesize
- 4, &size
, NULL
);
644 m_HeadRefFile
.Empty();
645 g_Git
.StringAppend(&this->m_HeadRefFile
, p
, CP_UTF8
, filesize
- 4);
646 CString ref
= this->m_HeadRefFile
;
649 ref
= ref
.Tokenize(_T("\n"), start
);
651 m_HeadRefFile
= m_Gitdir
+ m_HeadRefFile
.Trim();
652 m_HeadRefFile
.Replace(_T('/'),_T('\\'));
655 if (g_Git
.GetFileModifyTime(m_HeadRefFile
, &time
, NULL
))
657 m_HeadRefFile
.Empty();
658 if (GetPackRef(gitdir
))
663 if (this->m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
668 this ->m_Head
= m_PackRefMap
[ref
];
673 CAutoFile href
= CreateFile(m_HeadRefFile
,
675 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
678 FILE_ATTRIBUTE_NORMAL
,
683 m_HeadRefFile
.Empty();
685 if (GetPackRef(gitdir
))
691 if (this->m_PackRefMap
.find(ref
) == m_PackRefMap
.end())
696 this ->m_Head
= m_PackRefMap
[ref
];
700 ReadFile(href
, buffer
, 40, &size
, NULL
);
706 this->m_Head
.ConvertFromStrA((char*)buffer
);
708 this->m_LastModifyTimeRef
= time
;
713 ReadFile(hfile
, buffer
+ 4, 40 - 4, &size
, NULL
);
719 m_HeadRefFile
.Empty();
721 this->m_Head
.ConvertFromStrA((char*)buffer
);
733 bool CGitHeadFileList::CheckHeadUpdate()
735 CAutoReadLock
lock(&m_SharedMutex
);
736 if (this->m_HeadFile
.IsEmpty())
741 if (g_Git
.GetFileModifyTime(m_HeadFile
, &mtime
))
744 if (mtime
!= this->m_LastModifyTimeHead
)
747 if (!this->m_HeadRefFile
.IsEmpty())
749 if (g_Git
.GetFileModifyTime(m_HeadRefFile
, &mtime
))
752 if (mtime
!= this->m_LastModifyTimeRef
)
756 if(!this->m_PackRefFile
.IsEmpty())
758 if (g_Git
.GetFileModifyTime(m_PackRefFile
, &mtime
))
761 if (mtime
!= this->m_LastModifyTimePackRef
)
765 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
766 // So we need to retry again and again until the ref exists - otherwise we will never notice
767 if (this->m_Head
.IsEmpty() && this->m_HeadRefFile
.IsEmpty() && this->m_PackRefFile
.IsEmpty())
773 int CGitHeadFileList::CallBack(const unsigned char *sha1
, const char *base
, int baselen
,
774 const char *pathname
, unsigned mode
, int /*stage*/, void *context
)
776 #define S_IFGITLINK 0160000
778 CGitHeadFileList
*p
= (CGitHeadFileList
*)context
;
781 if( (mode
&S_IFMT
) != S_IFGITLINK
)
782 return READ_TREE_RECURSIVE
;
785 size_t cur
= p
->size();
786 p
->resize(p
->size() + 1);
787 p
->at(cur
).m_Hash
= (char*)sha1
;
788 p
->at(cur
).m_FileName
.Empty();
791 g_Git
.StringAppend(&p
->at(cur
).m_FileName
, (BYTE
*)base
, CP_UTF8
, baselen
);
793 g_Git
.StringAppend(&p
->at(cur
).m_FileName
,(BYTE
*)pathname
, CP_UTF8
);
795 p
->at(cur
).m_FileName
.MakeLower();
797 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
799 //p->m_Map[p->at(cur).m_FileName] = cur;
801 if( (mode
&S_IFMT
) == S_IFGITLINK
)
804 return READ_TREE_RECURSIVE
;
807 int ReadTreeRecursive(git_repository
&repo
, git_tree
* tree
, CStringA base
, int (*CallBack
) (const unsigned char *, const char *, int, const char *, unsigned int, int, void *),void *data
)
809 size_t count
= git_tree_entrycount(tree
);
810 for (size_t i
= 0; i
< count
; i
++)
812 const git_tree_entry
*entry
= git_tree_entry_byindex(tree
, i
);
815 int mode
= git_tree_entry_filemode(entry
);
816 if( CallBack(git_tree_entry_id(entry
)->id
,
819 git_tree_entry_name(entry
),
822 data
) == READ_TREE_RECURSIVE
827 git_object
*object
= NULL
;
828 git_tree_entry_to_object(&object
, &repo
, entry
);
831 CStringA parent
= base
;
832 parent
+= git_tree_entry_name(entry
);
834 ReadTreeRecursive(repo
, (git_tree
*)object
, parent
, CallBack
, data
);
835 git_object_free(object
);
844 int CGitHeadFileList::ReadTree()
846 CAutoWriteLock
lock(&m_SharedMutex
);
847 CStringA gitdir
= CUnicodeUtils::GetMulti(m_Gitdir
, CP_UTF8
);
848 git_repository
*repository
= NULL
;
849 git_commit
*commit
= NULL
;
850 git_tree
* tree
= NULL
;
852 this->clear(); // hack to avoid duplicates in the head list, which are introduced in GitStatus::GetFileStatus when this method is called
855 ret
= git_repository_open(&repository
, gitdir
.GetBuffer());
856 gitdir
.ReleaseBuffer();
859 ret
= git_commit_lookup(&commit
, repository
, (const git_oid
*)m_Head
.m_hash
);
863 ret
= git_commit_tree(&tree
, commit
);
867 ret
= ReadTreeRecursive(*repository
, tree
,"", CGitHeadFileList::CallBack
,this);
871 std::sort(this->begin(), this->end(), SortTree
);
872 this->m_TreeHash
= (char*)(git_commit_id(commit
)->id
);
880 git_commit_free(commit
);
883 git_repository_free(repository
);
886 m_LastModifyTimeHead
= 0;
891 int CGitIgnoreItem::FetchIgnoreList(const CString
&projectroot
, const CString
&file
, bool isGlobal
)
893 CAutoWriteLock
lock(&this->m_SharedMutex
);
895 if (this->m_pExcludeList
)
897 free(m_pExcludeList
);
901 this->m_BaseDir
.Empty();
904 CString base
= file
.Mid(projectroot
.GetLength() + 1);
905 base
.Replace(_T('\\'), _T('/'));
907 int start
= base
.ReverseFind(_T('/'));
910 base
= base
.Left(start
);
911 this->m_BaseDir
= CUnicodeUtils::GetMulti(base
, CP_UTF8
) + "/";
916 if(g_Git
.GetFileModifyTime(file
, &m_LastModifyTime
))
919 if(git_create_exclude_list(&this->m_pExcludeList
))
923 CAutoFile hfile
= CreateFile(file
,
925 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
928 FILE_ATTRIBUTE_NORMAL
,
935 DWORD size
=0,filesize
=0;
937 filesize
=GetFileSize(hfile
, NULL
);
939 if(filesize
== INVALID_FILE_SIZE
)
942 BYTE
*buffer
= new BYTE
[filesize
+ 1];
947 if(! ReadFile(hfile
, buffer
,filesize
,&size
,NULL
))
948 return GetLastError();
951 for (DWORD i
= 0; i
< size
; i
++)
953 if (buffer
[i
] == '\n' || buffer
[i
] == '\r' || i
== (size
- 1))
955 if (buffer
[i
] == '\n' || buffer
[i
] == '\r')
960 if(p
[0] != '#' && p
[0] != 0)
961 git_add_exclude((const char*)p
,
962 this->m_BaseDir
.GetBuffer(),
963 m_BaseDir
.GetLength(),
964 this->m_pExcludeList
);
969 /* Can't free buffer, exluced list will use this buffer*/
976 bool CGitIgnoreList::CheckFileChanged(const CString
&path
)
980 int ret
= g_Git
.GetFileModifyTime(path
, &time
);
982 this->m_SharedMutex
.AcquireShared();
983 bool cacheExist
= (m_Map
.find(path
) != m_Map
.end());
984 this->m_SharedMutex
.ReleaseShared();
986 if (!cacheExist
&& ret
== 0)
988 CAutoWriteLock
lock(&this->m_SharedMutex
);
989 m_Map
[path
].m_LastModifyTime
= 0;
990 m_Map
[path
].m_SharedMutex
.Init();
992 // both cache and file is not exist
993 if ((ret
!= 0) && (!cacheExist
))
996 // file exist but cache miss
997 if ((ret
== 0) && (!cacheExist
))
1000 // file not exist but cache exist
1001 if ((ret
!= 0) && (cacheExist
))
1005 // file exist and cache exist
1008 CAutoReadLock
lock(&this->m_SharedMutex
);
1009 if (m_Map
[path
].m_LastModifyTime
== time
)
1015 bool CGitIgnoreList::CheckIgnoreChanged(const CString
&gitdir
,const CString
&path
)
1022 temp
.Replace(_T('/'), _T('\\'));
1024 while(!temp
.IsEmpty())
1026 CString tempOrig
= temp
;
1027 temp
+= _T("\\.git");
1029 if (CGit::GitPathFileExists(temp
))
1031 CString gitignore
=temp
;
1032 gitignore
+= _T("ignore");
1033 if (CheckFileChanged(gitignore
))
1036 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1037 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
1038 if (CheckFileChanged(wcglobalgitignore
))
1041 if (CheckAndUpdateCoreExcludefile(adminDir
))
1048 temp
+= _T("ignore");
1049 if (CheckFileChanged(temp
))
1055 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1057 if(temp
[i
] == _T('\\'))
1064 temp
= temp
.Left(i
);
1069 int CGitIgnoreList::FetchIgnoreFile(const CString
&gitdir
, const CString
&gitignore
, bool isGlobal
)
1071 if (CGit::GitPathFileExists(gitignore
)) //if .gitignore remove, we need remote cache
1073 CAutoWriteLock
lock(&this->m_SharedMutex
);
1074 if (m_Map
.find(gitignore
) == m_Map
.end())
1075 m_Map
[gitignore
].m_SharedMutex
.Init();
1077 m_Map
[gitignore
].FetchIgnoreList(gitdir
, gitignore
, isGlobal
);
1081 CAutoWriteLock
lock(&this->m_SharedMutex
);
1082 if (m_Map
.find(gitignore
) != m_Map
.end())
1083 m_Map
[gitignore
].m_SharedMutex
.Release();
1085 m_Map
.erase(gitignore
);
1090 int CGitIgnoreList::LoadAllIgnoreFile(const CString
&gitdir
,const CString
&path
)
1098 temp
.Replace(_T('/'), _T('\\'));
1100 while (!temp
.IsEmpty())
1102 CString tempOrig
= temp
;
1103 temp
+= _T("\\.git");
1105 if (CGit::GitPathFileExists(temp
))
1107 CString gitignore
= temp
;
1108 gitignore
+= _T("ignore");
1109 if (CheckFileChanged(gitignore
))
1111 FetchIgnoreFile(gitdir
, gitignore
, false);
1114 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1115 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
1116 if (CheckFileChanged(wcglobalgitignore
))
1118 FetchIgnoreFile(gitdir
, wcglobalgitignore
, true);
1121 if (CheckAndUpdateCoreExcludefile(adminDir
))
1123 m_SharedMutex
.AcquireShared();
1124 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1125 m_SharedMutex
.ReleaseShared();
1126 if (!excludesFile
.IsEmpty())
1127 FetchIgnoreFile(gitdir
, excludesFile
, true);
1134 temp
+= _T("ignore");
1135 if (CheckFileChanged(temp
))
1137 FetchIgnoreFile(gitdir
, temp
, false);
1143 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1145 if(temp
[i
] == _T('\\'))
1152 temp
= temp
.Left(i
);
1156 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force
)
1158 // recheck every 30 seconds
1159 if (GetTickCount() - m_dMsysGitBinPathLastChecked
> 30000 || force
)
1161 m_dMsysGitBinPathLastChecked
= GetTickCount();
1162 CString
msysGitBinPath(CRegString(REG_MSYSGIT_PATH
, _T(""), FALSE
));
1163 if (msysGitBinPath
!= m_sMsysGitBinPath
)
1165 m_sMsysGitBinPath
= msysGitBinPath
;
1171 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString
&adminDir
)
1173 bool hasChanged
= false;
1175 CString projectConfig
= adminDir
+ _T("config");
1176 CString globalConfig
= g_Git
.GetGitGlobalConfig();
1177 CString globalXDGConfig
= g_Git
.GetGitGlobalXDGConfig();
1179 CAutoWriteLock
lock(&m_coreExcludefilesSharedMutex
);
1180 hasChanged
= CheckAndUpdateMsysGitBinpath();
1181 CString systemConfig
= m_sMsysGitBinPath
+ _T("\\..\\etc\\gitconfig");
1183 hasChanged
= hasChanged
|| CheckFileChanged(projectConfig
);
1184 hasChanged
= hasChanged
|| CheckFileChanged(globalConfig
);
1185 hasChanged
= hasChanged
|| CheckFileChanged(globalXDGConfig
);
1186 if (!m_sMsysGitBinPath
.IsEmpty())
1187 hasChanged
= hasChanged
|| CheckFileChanged(systemConfig
);
1189 m_SharedMutex
.AcquireShared();
1190 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1191 m_SharedMutex
.ReleaseShared();
1192 if (!excludesFile
.IsEmpty())
1193 hasChanged
= hasChanged
|| CheckFileChanged(excludesFile
);
1198 git_config
* config
;
1199 git_config_new(&config
);
1200 CStringA projectConfigA
= CUnicodeUtils::GetMulti(projectConfig
, CP_UTF8
);
1201 git_config_add_file_ondisk(config
, projectConfigA
.GetBuffer(), 4, FALSE
);
1202 projectConfigA
.ReleaseBuffer();
1203 CStringA globalConfigA
= CUnicodeUtils::GetMulti(globalConfig
, CP_UTF8
);
1204 git_config_add_file_ondisk(config
, globalConfigA
.GetBuffer(), 3, FALSE
);
1205 globalConfigA
.ReleaseBuffer();
1206 CStringA globalXDGConfigA
= CUnicodeUtils::GetMulti(globalXDGConfig
, CP_UTF8
);
1207 git_config_add_file_ondisk(config
, globalXDGConfigA
.GetBuffer(), 2, FALSE
);
1208 globalXDGConfigA
.ReleaseBuffer();
1209 if (!m_sMsysGitBinPath
.IsEmpty())
1211 CStringA systemConfigA
= CUnicodeUtils::GetMulti(systemConfig
, CP_UTF8
);
1212 git_config_add_file_ondisk(config
, systemConfigA
.GetBuffer(), 1, FALSE
);
1213 systemConfigA
.ReleaseBuffer();
1215 const char * out
= NULL
;
1216 CStringA
name(_T("core.excludesfile"));
1217 git_config_get_string(&out
, config
, name
.GetBuffer());
1218 name
.ReleaseBuffer();
1219 CStringA
excludesFileA(out
);
1220 excludesFile
= CUnicodeUtils::GetUnicode(excludesFileA
);
1221 if (excludesFile
.IsEmpty())
1222 excludesFile
= GetWindowsHome() + _T("\\.config\\git\\ignore");
1223 else if (excludesFile
.Find(_T("~/")) == 0)
1224 excludesFile
= GetWindowsHome() + excludesFile
.Mid(1);
1225 git_config_free(config
);
1227 CAutoWriteLock
lockMap(&m_SharedMutex
);
1228 g_Git
.GetFileModifyTime(projectConfig
, &m_Map
[projectConfig
].m_LastModifyTime
);
1229 g_Git
.GetFileModifyTime(globalXDGConfig
, &m_Map
[globalXDGConfig
].m_LastModifyTime
);
1230 if (m_Map
[globalXDGConfig
].m_LastModifyTime
== 0)
1232 m_Map
[globalXDGConfig
].m_SharedMutex
.Release();
1233 m_Map
.erase(globalXDGConfig
);
1235 g_Git
.GetFileModifyTime(globalConfig
, &m_Map
[globalConfig
].m_LastModifyTime
);
1236 if (m_Map
[globalConfig
].m_LastModifyTime
== 0)
1238 m_Map
[globalConfig
].m_SharedMutex
.Release();
1239 m_Map
.erase(globalConfig
);
1241 if (!m_sMsysGitBinPath
.IsEmpty())
1242 g_Git
.GetFileModifyTime(systemConfig
, &m_Map
[systemConfig
].m_LastModifyTime
);
1243 if (m_Map
[systemConfig
].m_LastModifyTime
== 0 || m_sMsysGitBinPath
.IsEmpty())
1245 m_Map
[systemConfig
].m_SharedMutex
.Release();
1246 m_Map
.erase(systemConfig
);
1248 m_CoreExcludesfiles
[adminDir
] = excludesFile
;
1252 const CString
CGitIgnoreList::GetWindowsHome()
1254 static CString
sWindowsHome(g_Git
.GetHomeDirectory());
1255 return sWindowsHome
;
1257 bool CGitIgnoreList::IsIgnore(const CString
&path
,const CString
&projectroot
)
1261 str
.Replace(_T('\\'),_T('/'));
1263 if (str
.GetLength()>0)
1264 if (str
[str
.GetLength()-1] == _T('/'))
1265 str
= str
.Left(str
.GetLength() - 1);
1268 ret
= CheckIgnore(str
, projectroot
);
1271 int start
= str
.ReverseFind(_T('/'));
1275 str
= str
.Left(start
);
1276 ret
= CheckIgnore(str
, projectroot
);
1281 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString
&ignorefile
, const CStringA
&patha
, const char * base
, int &type
)
1283 if (m_Map
.find(ignorefile
) != m_Map
.end())
1286 if(m_Map
[ignorefile
].m_pExcludeList
)
1287 ret
= git_check_excluded_1(patha
, patha
.GetLength(), base
, &type
, m_Map
[ignorefile
].m_pExcludeList
);
1288 if (ret
== 0 || ret
== 1)
1293 int CGitIgnoreList::CheckIgnore(const CString
&path
,const CString
&projectroot
)
1297 CString temp
= projectroot
+ _T("\\") + path
;
1298 temp
.Replace(_T('/'), _T('\\'));
1300 CStringA patha
= CUnicodeUtils::GetMulti(path
, CP_UTF8
);
1301 patha
.Replace('\\', '/');
1303 if(g_Git
.GetFileModifyTime(temp
, &time
, &dir
))
1311 // strip directory name
1312 // we do not need to check for a .ignore file inside a directory we might ignore
1313 int i
= temp
.ReverseFind(_T('\\'));
1315 temp
= temp
.Left(i
);
1321 int pos
= patha
.ReverseFind('/');
1322 base
= pos
>= 0 ? patha
.GetBuffer() + pos
+ 1 : patha
.GetBuffer();
1326 CAutoReadLock
lock(&this->m_SharedMutex
);
1327 while (!temp
.IsEmpty())
1329 CString tempOrig
= temp
;
1330 temp
+= _T("\\.git");
1332 if (CGit::GitPathFileExists(temp
))
1334 CString gitignore
= temp
;
1335 gitignore
+= _T("ignore");
1336 if ((ret
= CheckFileAgainstIgnoreList(gitignore
, patha
, base
, type
)) != -1)
1339 CString adminDir
= g_AdminDirMap
.GetAdminDir(tempOrig
);
1340 CString wcglobalgitignore
= adminDir
+ _T("info\\exclude");
1341 if ((ret
= CheckFileAgainstIgnoreList(wcglobalgitignore
, patha
, base
, type
)) != -1)
1344 m_SharedMutex
.AcquireShared();
1345 CString excludesFile
= m_CoreExcludesfiles
[adminDir
];
1346 m_SharedMutex
.ReleaseShared();
1347 if (!excludesFile
.IsEmpty())
1348 ret
= CheckFileAgainstIgnoreList(excludesFile
, patha
, base
, type
);
1354 temp
+= _T("ignore");
1355 if ((ret
= CheckFileAgainstIgnoreList(temp
, patha
, base
, type
)) != -1)
1361 for (i
= temp
.GetLength() - 1; i
>= 0; i
--)
1363 if (temp
[i
] == _T('\\'))
1370 temp
= temp
.Left(i
);
1373 patha
.ReleaseBuffer();
1378 bool CGitHeadFileMap::CheckHeadUpdate(const CString
&gitdir
)
1380 SHARED_TREE_PTR ptr
;
1381 ptr
= this->SafeGet(gitdir
);
1385 return ptr
->CheckHeadUpdate();
1389 SHARED_TREE_PTR
ptr1(new CGitHeadFileList
);
1390 ptr1
->ReadHeadHash(gitdir
);
1392 this->SafeSet(gitdir
, ptr1
);
1397 int CGitHeadFileMap::IsUnderVersionControl(const CString
&gitdir
, const CString
&path
, bool isDir
, bool *isVersion
)
1407 CString subpath
= path
;
1408 subpath
.Replace(_T('\\'), _T('/'));
1412 subpath
.MakeLower();
1414 CheckHeadUpdate(gitdir
);
1416 SHARED_TREE_PTR treeptr
;
1417 treeptr
= SafeGet(gitdir
);
1419 if (treeptr
->m_Head
!= treeptr
->m_TreeHash
)
1421 treeptr
->ReadHeadHash(gitdir
);
1424 if (treeptr
->m_HeadFile
.IsEmpty())
1429 else if (treeptr
->ReadTree())
1434 SafeSet(gitdir
, treeptr
);
1438 *isVersion
= (SearchInSortVector(*treeptr
, subpath
.GetBuffer(), subpath
.GetLength()) >= 0);
1440 *isVersion
= (SearchInSortVector(*treeptr
, subpath
.GetBuffer(), -1) >= 0);
1441 subpath
.ReleaseBuffer();