Constify and staticify methods
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob7156ae71fa2edad42e06516a4d414d5f53660eee
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.
20 #include "stdafx.h"
21 #include "Git.h"
22 #include "registry.h"
23 #include "GitConfig.h"
24 #include <map>
25 #include "UnicodeUtils.h"
26 #include "TGitPath.h"
27 #include "gitindex.h"
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include "SmartHandle.h"
31 #include "git2/sys/repository.h"
33 class CAutoReadLock
35 SharedMutex *m_Lock;
36 public:
37 CAutoReadLock(SharedMutex * lock)
39 m_Lock = lock;
40 lock->AcquireShared();
42 ~CAutoReadLock()
44 m_Lock->ReleaseShared();
48 class CAutoWriteLock
50 SharedMutex *m_Lock;
51 public:
52 CAutoWriteLock(SharedMutex * lock)
54 m_Lock = lock;
55 lock->AcquireExclusive();
57 ~CAutoWriteLock()
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,
69 this->m_Flags,
70 this->m_IndexHash.ToString(),
71 this->m_FileName);
73 return 0;
76 CGitIndexList::CGitIndexList()
78 this->m_LastModifyTime = 0;
79 m_critRepoSec.Init();
80 repository = NULL;
81 m_bCheckContent = !!(CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContent"), TRUE) == TRUE);
84 CGitIndexList::~CGitIndexList()
86 if (repository != NULL)
88 git_repository_free(repository);
90 m_critRepoSec.Term();
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)
105 this->clear();
107 CStringA gitdir = CUnicodeUtils::GetMulti(dgitdir, CP_UTF8);
109 m_critRepoSec.Lock();
110 if (repository != NULL)
112 git_repository_free(repository);
113 repository = NULL;
115 git_index *index = NULL;
117 if (git_repository_open(&repository, gitdir))
119 m_critRepoSec.Unlock();
120 return -1;
123 // add config files
124 git_config * config;
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);
140 config = nullptr;
142 // load index in order to enumerate files
143 if (git_repository_index(&index, repository))
145 git_repository_free(repository);
146 repository = NULL;
147 m_critRepoSec.Unlock();
148 return -1;
151 size_t ecount = git_index_entrycount(index);
152 resize(ecount);
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 = e->id.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();
172 return 0;
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)
177 if(status)
179 CString path = pathorg;
180 path.MakeLower();
182 int start = SearchInSortVector(*this, path, -1);
184 if (start < 0)
186 *status = git_wc_status_unversioned;
187 if (pHash)
188 pHash->Empty();
191 else
193 int index = start;
194 if (index >= (int)size())
195 return -1;
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;
201 if (skipWorktree)
202 *skipWorktree = true;
204 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
206 *status = git_wc_status_normal;
207 if (assumeValid)
208 *assumeValid = true;
210 else if (time == at(index).m_ModifyTime)
212 *status = git_wc_status_normal;
214 else if (m_bCheckContent && repository)
216 git_oid actual;
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;
224 else
225 *status = git_wc_status_modified;
226 m_critRepoSec.Unlock();
228 else
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;
236 if(pHash)
237 *pHash = at(index).m_IndexHash;
242 if (callback && status && assumeValid && skipWorktree)
243 callback(gitdir + _T("\\") + pathorg, *status, false, pData, *assumeValid, *skipWorktree);
244 return 0;
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)
252 __int64 time;
253 bool isDir = false;
254 CString path = pathParam;
256 if (status)
258 git_wc_status_kind dirstatus = git_wc_status_none;
259 int result;
260 if (path.IsEmpty())
261 result = g_Git.GetFileModifyTime(gitdir, &time, &isDir);
262 else
263 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + path, &time, &isDir);
265 if (result)
267 *status = git_wc_status_deleted;
268 if (callback && assumeValid && skipWorktree)
269 callback(gitdir + _T("\\") + path, git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
271 return 0;
273 if (isDir)
275 if (!path.IsEmpty())
277 if (path.Right(1) != _T("\\"))
278 path += _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)
288 if (!IsFull)
290 *status = git_wc_status_normal;
291 if (callback)
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);
293 return 0;
296 else
298 result = g_Git.GetFileModifyTime(gitdir+_T("\\") + at(i).m_FileName, &time);
299 if (result)
300 continue;
302 *status = git_wc_status_none;
303 if (assumeValid)
304 *assumeValid = false;
305 if (skipWorktree)
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;
326 } /* End For */
328 if (dirstatus != git_wc_status_none)
330 *status = dirstatus;
332 else
334 *status = git_wc_status_unversioned;
336 if(callback)
337 callback(gitdir + _T("\\") + path, *status, false, pData, false, false);
339 return 0;
342 else
344 GetFileStatus(gitdir, path, status, time, callback, pData, pHash, assumeValid, skipWorktree);
347 return 0;
350 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
352 __int64 time;
353 int result;
355 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
357 /* Get data associated with "crt_stat.c": */
358 result = g_Git.GetFileModifyTime(IndexFile, &time);
360 if (result)
361 return result;
363 SHARED_INDEX_PTR pIndex;
364 pIndex = this->SafeGet(gitdir);
366 if (pIndex.get() == NULL)
368 if(isChanged)
369 *isChanged = true;
370 return 0;
373 if (pIndex->m_LastModifyTime == time)
375 if (isChanged)
376 *isChanged = false;
378 else
380 if (isChanged)
381 *isChanged = true;
383 return 0;
386 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
390 SHARED_INDEX_PTR pIndex(new CGitIndexList);
392 if(pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
393 return -1;
395 this->SafeSet(gitdir, pIndex);
397 }catch(...)
399 return -1;
401 return 0;
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,
406 CGitHash *pHash,
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);
418 else
420 // git working tree has not index
421 *status = git_wc_status_unversioned;
424 catch(...)
426 return -1;
428 return 0;
431 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
435 if (path.IsEmpty())
437 *isVersion = true;
438 return 0;
441 CString subpath = path;
442 subpath.Replace(_T('\\'), _T('/'));
443 if(isDir)
444 subpath += _T('/');
446 subpath.MakeLower();
448 CheckAndUpdate(gitdir, isLoadUpdateIndex);
450 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
452 if(pIndex.get())
454 if(isDir)
455 *isVersion = (SearchInSortVector(*pIndex, subpath, subpath.GetLength()) >= 0);
456 else
457 *isVersion = (SearchInSortVector(*pIndex, subpath, -1) >= 0);
460 }catch(...)
462 return -1;
464 return 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");
472 __int64 mtime;
473 if (g_Git.GetFileModifyTime(PackRef, &mtime))
475 //packed refs is not existed
476 this->m_PackRefFile.Empty();
477 this->m_PackRefMap.clear();
478 return 0;
480 else if(mtime == m_LastModifyTimePackRef)
482 return 0;
484 else
486 this->m_PackRefFile = PackRef;
487 this->m_LastModifyTimePackRef = mtime;
490 int ret = 0;
492 this->m_PackRefMap.clear();
494 CAutoFile hfile = CreateFile(PackRef,
495 GENERIC_READ,
496 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
497 NULL,
498 OPEN_EXISTING,
499 FILE_ATTRIBUTE_NORMAL,
500 NULL);
503 if (!hfile)
505 ret = -1;
506 break;
509 DWORD filesize = GetFileSize(hfile, NULL);
510 if (filesize == 0)
512 ret = -1;
513 break;
515 DWORD size =0;
516 char *buff;
517 buff = new char[filesize];
519 ReadFile(hfile, buff, filesize, &size, NULL);
521 if (size != filesize)
523 delete[] buff;
524 ret = -1;
525 break;
528 CString hash;
529 CString ref;
531 for(DWORD i=0;i<filesize;)
533 hash.Empty();
534 ref.Empty();
535 if (buff[i] == '#' || buff[i] == '^')
537 while (buff[i] != '\n')
539 ++i;
540 if (i == filesize)
541 break;
543 ++i;
546 if (i >= filesize)
547 break;
549 while (buff[i] != ' ')
551 hash.AppendChar(buff[i]);
552 ++i;
553 if (i == filesize)
554 break;
557 ++i;
558 if (i >= filesize)
559 break;
561 while (buff[i] != '\n')
563 ref.AppendChar(buff[i]);
564 ++i;
565 if (i == filesize)
566 break;
569 if (!ref.IsEmpty() )
571 this->m_PackRefMap[ref] = hash;
574 while (buff[i] == '\n')
576 ++i;
577 if (i == filesize)
578 break;
582 delete[] buff;
584 } while(0);
586 return ret;
589 int CGitHeadFileList::ReadHeadHash(CString gitdir)
591 int ret = 0;
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))
598 return -1;
604 CAutoFile hfile = CreateFile(m_HeadFile,
605 GENERIC_READ,
606 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
607 NULL,
608 OPEN_EXISTING,
609 FILE_ATTRIBUTE_NORMAL,
610 NULL);
612 if (!hfile)
614 ret = -1;
615 break;
618 DWORD size = 0;
619 unsigned char buffer[40] ;
620 ReadFile(hfile, buffer, 4, &size, NULL);
621 if (size != 4)
623 ret = -1;
624 break;
626 buffer[4]=0;
627 if (strcmp((const char*)buffer,"ref:") == 0)
629 DWORD filesize = GetFileSize(hfile, NULL);
630 if (filesize < 5)
632 m_HeadRefFile.Empty();
633 ret = -1;
634 break;
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;
644 ref = ref.Trim();
645 int start = 0;
646 ref = ref.Tokenize(_T("\n"), start);
647 free(p);
648 m_HeadRefFile = m_Gitdir + m_HeadRefFile.Trim();
649 m_HeadRefFile.Replace(_T('/'),_T('\\'));
651 __int64 time;
652 if (g_Git.GetFileModifyTime(m_HeadRefFile, &time, NULL))
654 m_HeadRefFile.Empty();
655 if (GetPackRef(gitdir))
657 ret = -1;
658 break;
660 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
662 ret = -1;
663 break;
665 this ->m_Head = m_PackRefMap[ref];
666 ret = 0;
667 break;
670 CAutoFile href = CreateFile(m_HeadRefFile,
671 GENERIC_READ,
672 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
673 NULL,
674 OPEN_EXISTING,
675 FILE_ATTRIBUTE_NORMAL,
676 NULL);
678 if (!href)
680 m_HeadRefFile.Empty();
682 if (GetPackRef(gitdir))
684 ret = -1;
685 break;
688 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
690 ret = -1;
691 break;
693 this ->m_Head = m_PackRefMap[ref];
694 ret = 0;
695 break;
697 ReadFile(href, buffer, 40, &size, NULL);
698 if (size != 40)
700 ret = -1;
701 break;
703 this->m_Head.ConvertFromStrA((char*)buffer);
705 this->m_LastModifyTimeRef = time;
708 else
710 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
711 if(size != 36)
713 ret = -1;
714 break;
716 m_HeadRefFile.Empty();
718 this->m_Head.ConvertFromStrA((char*)buffer);
720 } while(0);
722 catch(...)
724 ret = -1;
727 return ret;
730 bool CGitHeadFileList::CheckHeadUpdate()
732 CAutoReadLock lock(&m_SharedMutex);
733 if (this->m_HeadFile.IsEmpty())
734 return true;
736 __int64 mtime=0;
738 if (g_Git.GetFileModifyTime(m_HeadFile, &mtime))
739 return true;
741 if (mtime != this->m_LastModifyTimeHead)
742 return true;
744 if (!this->m_HeadRefFile.IsEmpty())
746 if (g_Git.GetFileModifyTime(m_HeadRefFile, &mtime))
747 return true;
749 if (mtime != this->m_LastModifyTimeRef)
750 return true;
753 if(!this->m_PackRefFile.IsEmpty())
755 if (g_Git.GetFileModifyTime(m_PackRefFile, &mtime))
756 return true;
758 if (mtime != this->m_LastModifyTimePackRef)
759 return true;
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())
765 return true;
767 return false;
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;
794 if( mode&S_IFDIR )
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 = 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)
815 return 0;
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);
826 if (entry == NULL)
827 continue;
828 int mode = git_tree_entry_filemode(entry);
829 if( CallBack(git_tree_entry_id(entry)->id,
830 base,
831 base.GetLength(),
832 git_tree_entry_name(entry),
833 mode,
835 data) == READ_TREE_RECURSIVE
838 if(mode&S_IFDIR)
840 git_object *object = NULL;
841 git_tree_entry_to_object(&object, &repo, entry);
842 if (object == NULL)
843 continue;
844 CStringA parent = base;
845 parent += git_tree_entry_name(entry);
846 parent += "/";
847 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
848 git_object_free(object);
854 return 0;
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;
865 int ret = 0;
866 ATLASSERT(this->empty());
869 ret = git_repository_open(&repository, gitdir);
870 if(ret)
871 break;
872 ret = git_commit_lookup(&commit, repository, (const git_oid*)m_Head.m_hash);
873 if(ret)
874 break;
876 ret = git_commit_tree(&tree, commit);
877 if(ret)
878 break;
880 ret = ReadTreeRecursive(*repository, tree,"", CGitHeadFileList::CallBack,this);
881 if(ret)
882 break;
884 std::sort(this->begin(), this->end(), SortTree);
885 this->m_TreeHash = git_commit_id(commit)->id;
887 } while(0);
889 if (tree)
890 git_tree_free(tree);
892 if (commit)
893 git_commit_free(commit);
895 if (repository)
896 git_repository_free(repository);
898 if (ret)
900 clear();
901 m_LastModifyTimeHead = 0;
904 return ret;
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);
914 m_pExcludeList=NULL;
916 if (m_buffer)
918 free(m_buffer);
919 m_buffer = NULL;
922 this->m_BaseDir.Empty();
923 if (!isGlobal)
925 CString base = file.Mid(projectroot.GetLength() + 1);
926 base.Replace(_T('\\'), _T('/'));
928 int start = base.ReverseFind(_T('/'));
929 if(start > 0)
931 base = base.Left(start);
932 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
937 if(g_Git.GetFileModifyTime(file, &m_LastModifyTime))
938 return -1;
940 if(git_create_exclude_list(&this->m_pExcludeList))
941 return -1;
944 CAutoFile hfile = CreateFile(file,
945 GENERIC_READ,
946 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
947 NULL,
948 OPEN_EXISTING,
949 FILE_ATTRIBUTE_NORMAL,
950 NULL);
953 if (!hfile)
954 return -1 ;
956 DWORD size=0,filesize=0;
958 filesize=GetFileSize(hfile, NULL);
960 if(filesize == INVALID_FILE_SIZE)
961 return -1;
963 m_buffer = new BYTE[filesize + 1];
965 if (m_buffer == NULL)
966 return -1;
968 if (!ReadFile(hfile, m_buffer, filesize, &size, NULL))
969 return GetLastError();
971 BYTE *p = m_buffer;
972 int line = 0;
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')
978 m_buffer[i] = 0;
979 if (i == size - 1)
980 m_buffer[size] = 0;
982 if(p[0] != '#' && p[0] != 0)
983 git_add_exclude((const char*)p,
984 this->m_BaseDir,
985 m_BaseDir.GetLength(),
986 this->m_pExcludeList, ++line);
988 p = m_buffer + i + 1;
992 return 0;
995 bool CGitIgnoreList::CheckFileChanged(const CString &path)
997 __int64 time = 0;
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))
1013 return false;
1015 // file exist but cache miss
1016 if ((ret == 0) && (!cacheExist))
1017 return true;
1019 // file not exist but cache exist
1020 if ((ret != 0) && (cacheExist))
1022 return true;
1024 // file exist and cache exist
1027 CAutoReadLock lock(&this->m_SharedMutex);
1028 if (m_Map[path].m_LastModifyTime == time)
1029 return false;
1031 return true;
1034 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir,const CString &path)
1036 CString temp;
1037 temp = gitdir;
1038 temp += _T("\\");
1039 temp += 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))
1053 return true;
1055 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1056 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1057 if (CheckFileChanged(wcglobalgitignore))
1058 return true;
1060 if (CheckAndUpdateCoreExcludefile(adminDir))
1061 return true;
1063 return false;
1065 else
1067 temp += _T("ignore");
1068 if (CheckFileChanged(temp))
1069 return true;
1072 int found=0;
1073 int i;
1074 for (i = temp.GetLength() - 1; i >= 0; i--)
1076 if(temp[i] == _T('\\'))
1077 ++found;
1079 if(found == 2)
1080 break;
1083 temp = temp.Left(i);
1085 return true;
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);
1098 else
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);
1106 return 0;
1109 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir,const CString &path)
1111 CString temp;
1113 temp = gitdir;
1114 temp += _T("\\");
1115 temp += 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);
1149 return 0;
1151 else
1153 temp += _T("ignore");
1154 if (CheckFileChanged(temp))
1156 FetchIgnoreFile(gitdir, temp, false);
1160 int found = 0;
1161 int i;
1162 for (i = temp.GetLength() - 1; i >= 0; i--)
1164 if(temp[i] == _T('\\'))
1165 ++found;
1167 if(found == 2)
1168 break;
1171 temp = temp.Left(i);
1173 return 0;
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;
1185 return true;
1188 return false;
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);
1212 if (!hasChanged)
1213 return false;
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;
1256 return true;
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)
1265 CString str=path;
1267 str.Replace(_T('\\'),_T('/'));
1269 if (str.GetLength()>0)
1270 if (str[str.GetLength()-1] == _T('/'))
1271 str = str.Left(str.GetLength() - 1);
1273 int ret;
1274 ret = CheckIgnore(str, projectroot);
1275 while (ret < 0)
1277 int start = str.ReverseFind(_T('/'));
1278 if(start < 0)
1279 return (ret == 1);
1281 str = str.Left(start);
1282 ret = CheckIgnore(str, projectroot);
1285 return (ret == 1);
1287 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1289 if (m_Map.find(ignorefile) != m_Map.end())
1291 int ret = -1;
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)
1295 return ret;
1297 return -1;
1299 int CGitIgnoreList::CheckIgnore(const CString &path,const CString &projectroot)
1301 __int64 time = 0;
1302 bool dir = 0;
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))
1310 return -1;
1312 int type = 0;
1313 if (dir)
1315 type = DT_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('\\'));
1320 if (i >= 0)
1321 temp = temp.Left(i);
1323 else
1324 type = DT_REG;
1326 char * base = NULL;
1327 int pos = patha.ReverseFind('/');
1328 base = pos >= 0 ? patha.GetBuffer() + pos + 1 : patha.GetBuffer();
1330 int ret = -1;
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)
1343 break;
1345 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1346 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1347 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1348 break;
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);
1356 break;
1358 else
1360 temp += _T("ignore");
1361 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1362 break;
1365 int found = 0;
1366 int i;
1367 for (i = temp.GetLength() - 1; i >= 0; i--)
1369 if (temp[i] == _T('\\'))
1370 ++found;
1372 if (found == 2)
1373 break;
1376 temp = temp.Left(i);
1379 patha.ReleaseBuffer();
1381 return ret;
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()))
1390 return false;
1392 ptr = SHARED_TREE_PTR(new CGitHeadFileList);
1393 ptr->ReadHeadHash(gitdir);
1394 if (readTree)
1395 ptr->ReadTree();
1397 this->SafeSet(gitdir, ptr);
1399 return true;
1402 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1406 if (path.IsEmpty())
1408 *isVersion = true;
1409 return 0;
1412 CString subpath = path;
1413 subpath.Replace(_T('\\'), _T('/'));
1414 if(isDir)
1415 subpath += _T('/');
1417 subpath.MakeLower();
1419 CheckHeadAndUpdate(gitdir);
1421 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1423 // Init Repository
1424 if (treeptr->HeadFileIsEmpty())
1426 *isVersion = false;
1427 return 0;
1429 else if (treeptr->empty())
1431 *isVersion = false;
1432 return 1;
1435 if(isDir)
1436 *isVersion = (SearchInSortVector(*treeptr, subpath, subpath.GetLength()) >= 0);
1437 else
1438 *isVersion = (SearchInSortVector(*treeptr, subpath, -1) >= 0);
1440 catch(...)
1442 return -1;
1445 return 0;