Can also refresh Rebase dialog by double clicking to delete conflict file
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob0fc23912eccc0dd86b4c735ebe4926a28c17bdfd
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.
20 #include "StdAfx.h"
21 #include "Git.h"
22 #include "atlconv.h"
23 #include "GitRev.h"
24 #include "registry.h"
25 #include "GitConfig.h"
26 #include <map>
27 #include "UnicodeUtils.h"
28 #include "TGitPath.h"
29 #include "gitindex.h"
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include "SmartHandle.h"
34 class CAutoReadLock
36 SharedMutex *m_Lock;
37 public:
38 CAutoReadLock(SharedMutex * lock)
40 m_Lock = lock;
41 lock->AcquireShared();
43 ~CAutoReadLock()
45 m_Lock->ReleaseShared();
49 class CAutoWriteLock
51 SharedMutex *m_Lock;
52 public:
53 CAutoWriteLock(SharedMutex * lock)
55 m_Lock = lock;
56 lock->AcquireExclusive();
58 ~CAutoWriteLock()
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,
70 this->m_Flags,
71 this->m_IndexHash.ToString(),
72 this->m_FileName);
74 return 0;
77 CGitIndexList::CGitIndexList()
79 this->m_LastModifyTime = 0;
80 m_critRepoSec.Init();
81 repository = NULL;
82 m_bCheckContent = !!(CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContent"), TRUE) == TRUE);
85 CGitIndexList::~CGitIndexList()
87 if (repository != NULL)
89 git_repository_free(repository);
90 m_critRepoSec.Term();
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)
106 this->clear();
108 CStringA gitdir = CUnicodeUtils::GetMulti(dgitdir, CP_UTF8);
110 m_critRepoSec.Lock();
111 if (repository != NULL)
113 git_repository_free(repository);
114 repository = NULL;
116 git_index *index = NULL;
118 int ret = git_repository_open(&repository, gitdir.GetBuffer());
119 gitdir.ReleaseBuffer();
120 if (ret)
121 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 CStringA projectConfigA = CUnicodeUtils::GetMulti(projectConfig, CP_UTF8);
133 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 4);
134 projectConfigA.ReleaseBuffer();
135 CStringA globalConfigA = CUnicodeUtils::GetMulti(globalConfig, CP_UTF8);
136 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 3);
137 globalConfigA.ReleaseBuffer();
138 CStringA globalXDGConfigA = CUnicodeUtils::GetMulti(globalXDGConfig, CP_UTF8);
139 git_config_add_file_ondisk(config, globalXDGConfigA.GetBuffer(), 2);
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);
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))
154 repository = NULL;
155 m_critRepoSec.Unlock();
156 return -1;
159 unsigned int ecount = git_index_entrycount(index);
160 resize(ecount);
161 for (unsigned int i = 0; i < ecount; ++i)
163 git_index_entry *e = git_index_get(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();
180 return 0;
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)
185 if(status)
187 CString path = pathorg;
188 path.MakeLower();
190 int start = SearchInSortVector(*this, ((CString&)path).GetBuffer(), -1);
191 ((CString&)path).ReleaseBuffer();
193 if (start < 0)
195 *status = git_wc_status_unversioned;
196 if (pHash)
197 pHash->Empty();
200 else
202 int index = start;
203 if (index <0)
204 return -1;
205 if (index >= (int)size())
206 return -1;
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;
212 if (skipWorktree)
213 *skipWorktree = true;
215 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
217 *status = git_wc_status_normal;
218 if (assumeValid)
219 *assumeValid = true;
221 else if (time == at(index).m_ModifyTime)
223 *status = git_wc_status_normal;
225 else if (m_bCheckContent && repository)
227 git_oid actual;
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;
235 else
236 *status = git_wc_status_modified;
237 fileA.ReleaseBuffer();
238 m_critRepoSec.Unlock();
240 else
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;
248 if(pHash)
249 *pHash = at(index).m_IndexHash;
254 if(callback && status)
255 callback(gitdir + _T("\\") + pathorg, *status, false, pData, *assumeValid, *skipWorktree);
256 return 0;
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)
264 int result;
265 git_wc_status_kind dirstatus = git_wc_status_none;
266 __int64 time;
267 bool isDir = false;
268 CString path = pathParam;
270 if (status)
272 if (path.IsEmpty())
273 result = g_Git.GetFileModifyTime(gitdir, &time, &isDir);
274 else
275 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + path, &time, &isDir);
277 if (result)
279 *status = git_wc_status_deleted;
280 if (callback)
281 callback(gitdir + _T("\\") + path, git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
283 return 0;
285 if (isDir)
287 if (!path.IsEmpty())
289 if (path.Right(1) != _T("\\"))
290 path += _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)
300 if (!IsFull)
302 *status = git_wc_status_normal;
303 if (callback)
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);
305 return 0;
308 else
310 result = g_Git.GetFileModifyTime(gitdir+_T("\\") + at(i).m_FileName, &time);
311 if (result)
312 continue;
314 *status = git_wc_status_none;
315 if (assumeValid)
316 *assumeValid = false;
317 if (skipWorktree)
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;
338 } /* End For */
340 if (dirstatus != git_wc_status_none)
342 *status = dirstatus;
344 else
346 *status = git_wc_status_unversioned;
348 if(callback)
349 callback(gitdir + _T("\\") + path, *status, false, pData, false, false);
351 return 0;
354 else
356 GetFileStatus(gitdir, path, status, time, callback, pData, pHash, assumeValid, skipWorktree);
359 return 0;
362 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
364 __int64 time;
365 int result;
367 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
369 /* Get data associated with "crt_stat.c": */
370 result = g_Git.GetFileModifyTime(IndexFile, &time);
372 if (result)
373 return result;
375 SHARED_INDEX_PTR pIndex;
376 pIndex = this->SafeGet(gitdir);
378 if (pIndex.get() == NULL)
380 if(isChanged)
381 *isChanged = true;
382 return 0;
385 if (pIndex->m_LastModifyTime == time)
387 if (isChanged)
388 *isChanged = false;
390 else
392 if (isChanged)
393 *isChanged = true;
395 return 0;
398 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
402 SHARED_INDEX_PTR pIndex(new CGitIndexList);
404 if(pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
405 return -1;
407 this->SafeSet(gitdir, pIndex);
409 }catch(...)
411 return -1;
413 return 0;
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,
418 CGitHash *pHash,
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);
430 else
432 // git working tree has not index
433 *status = git_wc_status_unversioned;
436 catch(...)
438 return -1;
440 return 0;
443 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
447 if (path.IsEmpty())
449 *isVersion = true;
450 return 0;
453 CString subpath = path;
454 subpath.Replace(_T('\\'), _T('/'));
455 if(isDir)
456 subpath += _T('/');
458 subpath.MakeLower();
460 CheckAndUpdate(gitdir, isLoadUpdateIndex);
462 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
464 if(pIndex.get())
466 if(isDir)
467 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), subpath.GetLength()) >= 0);
468 else
469 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), -1) >= 0);
470 subpath.ReleaseBuffer();
473 }catch(...)
475 return -1;
477 return 0;
480 int CGitHeadFileList::GetPackRef(const CString &gitdir)
482 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
484 __int64 mtime;
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();
491 return 0;
493 else if(mtime == m_LastModifyTimePackRef)
495 return 0;
497 else
499 CAutoWriteLock lock(&this->m_SharedMutex);
500 this->m_PackRefFile = PackRef;
501 this->m_LastModifyTimePackRef = mtime;
504 int ret = 0;
506 CAutoWriteLock lock(&this->m_SharedMutex);
507 this->m_PackRefMap.clear();
509 CAutoFile hfile = CreateFile(PackRef,
510 GENERIC_READ,
511 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
512 NULL,
513 OPEN_EXISTING,
514 FILE_ATTRIBUTE_NORMAL,
515 NULL);
518 if (!hfile)
520 ret = -1;
521 break;
524 DWORD filesize = GetFileSize(hfile, NULL);
525 DWORD size =0;
526 char *buff;
527 buff = new char[filesize];
529 ReadFile(hfile, buff, filesize, &size, NULL);
531 if (size != filesize)
533 ret = -1;
534 break;
537 CString hash;
538 CString ref;
540 for(DWORD i=0;i<filesize;)
542 hash.Empty();
543 ref.Empty();
544 if (buff[i] == '#' || buff[i] == '^')
546 while (buff[i] != '\n')
548 i++;
549 if (i == filesize)
550 break;
552 i++;
555 if (i >= filesize)
556 break;
558 while (buff[i] != ' ')
560 hash.AppendChar(buff[i]);
561 i++;
562 if (i == filesize)
563 break;
566 i++;
567 if (i >= filesize)
568 break;
570 while (buff[i] != '\n')
572 ref.AppendChar(buff[i]);
573 i++;
574 if (i == filesize)
575 break;
578 if (!ref.IsEmpty() )
580 this->m_PackRefMap[ref] = hash;
583 while (buff[i] == '\n')
585 i++;
586 if (i == filesize)
587 break;
591 delete buff;
593 } while(0);
595 return ret;
598 int CGitHeadFileList::ReadHeadHash(CString gitdir)
600 int ret = 0;
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))
607 return -1;
613 CAutoFile hfile = CreateFile(m_HeadFile,
614 GENERIC_READ,
615 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
616 NULL,
617 OPEN_EXISTING,
618 FILE_ATTRIBUTE_NORMAL,
619 NULL);
621 if (!hfile)
623 ret = -1;
624 break;
627 DWORD size = 0,filesize = 0;
628 unsigned char buffer[40] ;
629 ReadFile(hfile, buffer, 4, &size, NULL);
630 if (size != 4)
632 ret = -1;
633 break;
635 buffer[4]=0;
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;
647 ref = ref.Trim();
648 int start = 0;
649 ref = ref.Tokenize(_T("\n"), start);
650 free(p);
651 m_HeadRefFile = m_Gitdir + m_HeadRefFile.Trim();
652 m_HeadRefFile.Replace(_T('/'),_T('\\'));
654 __int64 time;
655 if (g_Git.GetFileModifyTime(m_HeadRefFile, &time, NULL))
657 m_HeadRefFile.Empty();
658 if (GetPackRef(gitdir))
660 ret = -1;
661 break;
663 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
665 ret = -1;
666 break;
668 this ->m_Head = m_PackRefMap[ref];
669 ret = 0;
670 break;
673 CAutoFile href = CreateFile(m_HeadRefFile,
674 GENERIC_READ,
675 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
676 NULL,
677 OPEN_EXISTING,
678 FILE_ATTRIBUTE_NORMAL,
679 NULL);
681 if (!href)
683 m_HeadRefFile.Empty();
685 if (GetPackRef(gitdir))
687 ret = -1;
688 break;
691 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
693 ret = -1;
694 break;
696 this ->m_Head = m_PackRefMap[ref];
697 ret = 0;
698 break;
700 ReadFile(href, buffer, 40, &size, NULL);
701 if (size != 40)
703 ret = -1;
704 break;
706 this->m_Head.ConvertFromStrA((char*)buffer);
708 this->m_LastModifyTimeRef = time;
711 else
713 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
714 if(size != 36)
716 ret = -1;
717 break;
719 m_HeadRefFile.Empty();
721 this->m_Head.ConvertFromStrA((char*)buffer);
723 } while(0);
725 catch(...)
727 ret = -1;
730 return ret;
733 bool CGitHeadFileList::CheckHeadUpdate()
735 CAutoReadLock lock(&m_SharedMutex);
736 if (this->m_HeadFile.IsEmpty())
737 return true;
739 __int64 mtime=0;
741 if (g_Git.GetFileModifyTime(m_HeadFile, &mtime))
742 return true;
744 if (mtime != this->m_LastModifyTimeHead)
745 return true;
747 if (!this->m_HeadRefFile.IsEmpty())
749 if (g_Git.GetFileModifyTime(m_HeadRefFile, &mtime))
750 return true;
752 if (mtime != this->m_LastModifyTimeRef)
753 return true;
756 if(!this->m_PackRefFile.IsEmpty())
758 if (g_Git.GetFileModifyTime(m_PackRefFile, &mtime))
759 return true;
761 if (mtime != this->m_LastModifyTimePackRef)
762 return true;
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())
768 return true;
770 return false;
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;
779 if( mode&S_IFDIR )
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();
790 if(base)
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)
802 return 0;
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 unsigned int count = git_tree_entrycount(tree);
810 for (unsigned int i = 0; i < count; i++)
812 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
813 if (entry == NULL)
814 continue;
815 int mode = git_tree_entry_filemode(entry);
816 if( CallBack(git_tree_entry_id(entry)->id,
817 base,
818 base.GetLength(),
819 git_tree_entry_name(entry),
820 mode,
822 data) == READ_TREE_RECURSIVE
825 if(mode&S_IFDIR)
827 git_object *object = NULL;
828 git_tree_entry_to_object(&object, &repo, entry);
829 if (object == NULL)
830 continue;
831 CStringA parent = base;
832 parent += git_tree_entry_name(entry);
833 parent += "/";
834 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
835 git_object_free(object);
841 return 0;
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;
851 int ret = 0;
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();
857 if(ret)
858 break;
859 ret = git_commit_lookup(&commit, repository, (const git_oid*)m_Head.m_hash);
860 if(ret)
861 break;
863 ret = git_commit_tree(&tree, commit);
864 if(ret)
865 break;
867 ret = ReadTreeRecursive(*repository, tree,"", CGitHeadFileList::CallBack,this);
868 if(ret)
869 break;
871 std::sort(this->begin(), this->end(), SortTree);
872 this->m_TreeHash = (char*)(git_commit_id(commit)->id);
874 } while(0);
876 if (tree)
877 git_tree_free(tree);
879 if (commit)
880 git_commit_free(commit);
882 if (repository)
883 git_repository_free(repository);
885 return ret;
888 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
890 CAutoWriteLock lock(&this->m_SharedMutex);
892 if (this->m_pExcludeList)
894 free(m_pExcludeList);
895 m_pExcludeList=NULL;
898 this->m_BaseDir.Empty();
899 if (!isGlobal)
901 CString base = file.Mid(projectroot.GetLength() + 1);
902 base.Replace(_T('\\'), _T('/'));
904 int start = base.ReverseFind(_T('/'));
905 if(start > 0)
907 base = base.Left(start);
908 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
913 if(g_Git.GetFileModifyTime(file, &m_LastModifyTime))
914 return -1;
916 if(git_create_exclude_list(&this->m_pExcludeList))
917 return -1;
920 CAutoFile hfile = CreateFile(file,
921 GENERIC_READ,
922 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
923 NULL,
924 OPEN_EXISTING,
925 FILE_ATTRIBUTE_NORMAL,
926 NULL);
929 if (!hfile)
930 return -1 ;
932 DWORD size=0,filesize=0;
934 filesize=GetFileSize(hfile, NULL);
936 if(filesize == INVALID_FILE_SIZE)
937 return -1;
939 BYTE *buffer = new BYTE[filesize + 1];
941 if(buffer == NULL)
942 return -1;
944 if(! ReadFile(hfile, buffer,filesize,&size,NULL))
945 return GetLastError();
947 BYTE *p = buffer;
948 for (DWORD i = 0; i < size; i++)
950 if (buffer[i] == '\n' || buffer[i] == '\r' || i == (size - 1))
952 if (buffer[i] == '\n' || buffer[i] == '\r')
953 buffer[i] = 0;
954 if (i == size - 1)
955 buffer[size] = 0;
957 if(p[0] != '#' && p[0] != 0)
958 git_add_exclude((const char*)p,
959 this->m_BaseDir.GetBuffer(),
960 m_BaseDir.GetLength(),
961 this->m_pExcludeList);
963 p=buffer+i+1;
966 /* Can't free buffer, exluced list will use this buffer*/
967 //delete buffer;
968 //buffer = NULL;
970 return 0;
973 bool CGitIgnoreList::CheckFileChanged(const CString &path)
975 __int64 time = 0;
977 int ret = g_Git.GetFileModifyTime(path, &time);
979 this->m_SharedMutex.AcquireShared();
980 bool cacheExist = (m_Map.find(path) != m_Map.end());
981 this->m_SharedMutex.ReleaseShared();
983 if (!cacheExist && ret == 0)
985 CAutoWriteLock lock(&this->m_SharedMutex);
986 m_Map[path].m_LastModifyTime = 0;
987 m_Map[path].m_SharedMutex.Init();
989 // both cache and file is not exist
990 if ((ret != 0) && (!cacheExist))
991 return false;
993 // file exist but cache miss
994 if ((ret == 0) && (!cacheExist))
995 return true;
997 // file not exist but cache exist
998 if ((ret != 0) && (cacheExist))
1000 return true;
1002 // file exist and cache exist
1005 CAutoReadLock lock(&this->m_SharedMutex);
1006 if (m_Map[path].m_LastModifyTime == time)
1007 return false;
1009 return true;
1012 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir,const CString &path)
1014 CString temp;
1015 temp = gitdir;
1016 temp += _T("\\");
1017 temp += path;
1019 temp.Replace(_T('/'), _T('\\'));
1021 while(!temp.IsEmpty())
1023 CString tempOrig = temp;
1024 temp += _T("\\.git");
1026 if (CGit::GitPathFileExists(temp))
1028 CString gitignore=temp;
1029 gitignore += _T("ignore");
1030 if (CheckFileChanged(gitignore))
1031 return true;
1033 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1034 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1035 if (CheckFileChanged(wcglobalgitignore))
1036 return true;
1038 if (CheckAndUpdateCoreExcludefile(adminDir))
1039 return true;
1041 return false;
1043 else
1045 temp += _T("ignore");
1046 if (CheckFileChanged(temp))
1047 return true;
1050 int found=0;
1051 int i;
1052 for (i = temp.GetLength() - 1; i >= 0; i--)
1054 if(temp[i] == _T('\\'))
1055 found ++;
1057 if(found == 2)
1058 break;
1061 temp = temp.Left(i);
1063 return true;
1066 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
1068 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
1070 CAutoWriteLock lock(&this->m_SharedMutex);
1071 if (m_Map.find(gitignore) == m_Map.end())
1072 m_Map[gitignore].m_SharedMutex.Init();
1074 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
1076 else
1078 CAutoWriteLock lock(&this->m_SharedMutex);
1079 if (m_Map.find(gitignore) != m_Map.end())
1080 m_Map[gitignore].m_SharedMutex.Release();
1082 m_Map.erase(gitignore);
1084 return 0;
1087 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir,const CString &path)
1089 CString temp;
1091 temp = gitdir;
1092 temp += _T("\\");
1093 temp += path;
1095 temp.Replace(_T('/'), _T('\\'));
1097 while (!temp.IsEmpty())
1099 CString tempOrig = temp;
1100 temp += _T("\\.git");
1102 if (CGit::GitPathFileExists(temp))
1104 CString gitignore = temp;
1105 gitignore += _T("ignore");
1106 if (CheckFileChanged(gitignore))
1108 FetchIgnoreFile(gitdir, gitignore, false);
1111 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1112 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1113 if (CheckFileChanged(wcglobalgitignore))
1115 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1118 if (CheckAndUpdateCoreExcludefile(adminDir))
1120 m_SharedMutex.AcquireShared();
1121 CString excludesFile = m_CoreExcludesfiles[adminDir];
1122 m_SharedMutex.ReleaseShared();
1123 if (!excludesFile.IsEmpty())
1124 FetchIgnoreFile(gitdir, excludesFile, true);
1127 return 0;
1129 else
1131 temp += _T("ignore");
1132 if (CheckFileChanged(temp))
1134 FetchIgnoreFile(gitdir, temp, false);
1138 int found = 0;
1139 int i;
1140 for (i = temp.GetLength() - 1; i >= 0; i--)
1142 if(temp[i] == _T('\\'))
1143 found ++;
1145 if(found == 2)
1146 break;
1149 temp = temp.Left(i);
1151 return 0;
1153 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force)
1155 // recheck every 30 seconds
1156 if (GetTickCount() - m_dMsysGitBinPathLastChecked > 30000 || force)
1158 m_dMsysGitBinPathLastChecked = GetTickCount();
1159 CString msysGitBinPath(CRegString(REG_MSYSGIT_PATH, _T(""), FALSE));
1160 if (msysGitBinPath != m_sMsysGitBinPath)
1162 m_sMsysGitBinPath = msysGitBinPath;
1163 return true;
1166 return false;
1168 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1170 bool hasChanged = false;
1172 CString projectConfig = adminDir + _T("config");
1173 CString globalConfig = g_Git.GetGitGlobalConfig();
1174 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1176 CAutoWriteLock lock(&m_coreExcludefilesSharedMutex);
1177 hasChanged = CheckAndUpdateMsysGitBinpath();
1178 CString systemConfig = m_sMsysGitBinPath + _T("\\..\\etc\\gitconfig");
1180 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1181 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1182 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1183 if (!m_sMsysGitBinPath.IsEmpty())
1184 hasChanged = hasChanged || CheckFileChanged(systemConfig);
1186 m_SharedMutex.AcquireShared();
1187 CString excludesFile = m_CoreExcludesfiles[adminDir];
1188 m_SharedMutex.ReleaseShared();
1189 if (!excludesFile.IsEmpty())
1190 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1192 if (!hasChanged)
1193 return false;
1195 git_config * config;
1196 git_config_new(&config);
1197 CStringA projectConfigA = CUnicodeUtils::GetMulti(projectConfig, CP_UTF8);
1198 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 4);
1199 projectConfigA.ReleaseBuffer();
1200 CStringA globalConfigA = CUnicodeUtils::GetMulti(globalConfig, CP_UTF8);
1201 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 3);
1202 globalConfigA.ReleaseBuffer();
1203 CStringA globalXDGConfigA = CUnicodeUtils::GetMulti(globalXDGConfig, CP_UTF8);
1204 git_config_add_file_ondisk(config, globalXDGConfigA.GetBuffer(), 2);
1205 globalXDGConfigA.ReleaseBuffer();
1206 if (!m_sMsysGitBinPath.IsEmpty())
1208 CStringA systemConfigA = CUnicodeUtils::GetMulti(systemConfig, CP_UTF8);
1209 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1);
1210 systemConfigA.ReleaseBuffer();
1212 const char * out = NULL;
1213 CStringA name(_T("core.excludesfile"));
1214 git_config_get_string(&out, config, name.GetBuffer());
1215 name.ReleaseBuffer();
1216 CStringA excludesFileA(out);
1217 excludesFile = CUnicodeUtils::GetUnicode(excludesFileA);
1218 if (excludesFile.IsEmpty())
1219 excludesFile = GetWindowsHome() + _T("\\.config\\git\\ignore");
1220 else if (excludesFile.Find(_T("~/")) == 0)
1221 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1222 git_config_free(config);
1224 CAutoWriteLock lockMap(&m_SharedMutex);
1225 g_Git.GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1226 g_Git.GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1227 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1229 m_Map[globalXDGConfig].m_SharedMutex.Release();
1230 m_Map.erase(globalXDGConfig);
1232 g_Git.GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1233 if (m_Map[globalConfig].m_LastModifyTime == 0)
1235 m_Map[globalConfig].m_SharedMutex.Release();
1236 m_Map.erase(globalConfig);
1238 if (!m_sMsysGitBinPath.IsEmpty())
1239 g_Git.GetFileModifyTime(systemConfig, &m_Map[systemConfig].m_LastModifyTime);
1240 if (m_Map[systemConfig].m_LastModifyTime == 0 || m_sMsysGitBinPath.IsEmpty())
1242 m_Map[systemConfig].m_SharedMutex.Release();
1243 m_Map.erase(systemConfig);
1245 m_CoreExcludesfiles[adminDir] = excludesFile;
1247 return true;
1249 const CString CGitIgnoreList::GetWindowsHome()
1251 static CString sWindowsHome(g_Git.GetHomeDirectory());
1252 return sWindowsHome;
1254 bool CGitIgnoreList::IsIgnore(const CString &path,const CString &projectroot)
1256 CString str=path;
1258 str.Replace(_T('\\'),_T('/'));
1260 if (str.GetLength()>0)
1261 if (str[str.GetLength()-1] == _T('/'))
1262 str = str.Left(str.GetLength() - 1);
1264 int ret;
1265 ret = CheckIgnore(str, projectroot);
1266 while (ret < 0)
1268 int start = str.ReverseFind(_T('/'));
1269 if(start < 0)
1270 return (ret == 1);
1272 str = str.Left(start);
1273 ret = CheckIgnore(str, projectroot);
1276 return (ret == 1);
1278 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1280 if (m_Map.find(ignorefile) != m_Map.end())
1282 int ret = -1;
1283 if(m_Map[ignorefile].m_pExcludeList)
1284 ret = git_check_excluded_1(patha, patha.GetLength(), base, &type, m_Map[ignorefile].m_pExcludeList);
1285 if (ret == 0 || ret == 1)
1286 return ret;
1288 return -1;
1290 int CGitIgnoreList::CheckIgnore(const CString &path,const CString &projectroot)
1292 __int64 time = 0;
1293 bool dir = 0;
1294 CString temp = projectroot + _T("\\") + path;
1295 temp.Replace(_T('/'), _T('\\'));
1297 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1298 patha.Replace('\\', '/');
1300 if(g_Git.GetFileModifyTime(temp, &time, &dir))
1301 return -1;
1303 int type = 0;
1304 if (dir)
1306 type = DT_DIR;
1308 // strip directory name
1309 // we do not need to check for a .ignore file inside a directory we might ignore
1310 int i = temp.ReverseFind(_T('\\'));
1311 if (i >= 0)
1312 temp = temp.Left(i);
1314 else
1315 type = DT_REG;
1317 char * base = NULL;
1318 int pos = patha.ReverseFind('/');
1319 base = pos >= 0 ? patha.GetBuffer() + pos + 1 : patha.GetBuffer();
1321 int ret = -1;
1323 CAutoReadLock lock(&this->m_SharedMutex);
1324 while (!temp.IsEmpty())
1326 CString tempOrig = temp;
1327 temp += _T("\\.git");
1329 if (CGit::GitPathFileExists(temp))
1331 CString gitignore = temp;
1332 gitignore += _T("ignore");
1333 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1334 break;
1336 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1337 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1338 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1339 break;
1341 m_SharedMutex.AcquireShared();
1342 CString excludesFile = m_CoreExcludesfiles[adminDir];
1343 m_SharedMutex.ReleaseShared();
1344 if (!excludesFile.IsEmpty())
1345 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1347 break;
1349 else
1351 temp += _T("ignore");
1352 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1353 break;
1356 int found = 0;
1357 int i;
1358 for (i = temp.GetLength() - 1; i >= 0; i--)
1360 if (temp[i] == _T('\\'))
1361 found++;
1363 if (found == 2)
1364 break;
1367 temp = temp.Left(i);
1370 patha.ReleaseBuffer();
1372 return ret;
1375 bool CGitHeadFileMap::CheckHeadUpdate(const CString &gitdir)
1377 SHARED_TREE_PTR ptr;
1378 ptr = this->SafeGet(gitdir);
1380 if( ptr.get())
1382 return ptr->CheckHeadUpdate();
1384 else
1386 SHARED_TREE_PTR ptr1(new CGitHeadFileList);
1387 ptr1->ReadHeadHash(gitdir);
1389 this->SafeSet(gitdir, ptr1);
1390 return true;
1392 return false;
1395 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1399 if (path.IsEmpty())
1401 *isVersion = true;
1402 return 0;
1405 CString subpath = path;
1406 subpath.Replace(_T('\\'), _T('/'));
1407 if(isDir)
1408 subpath += _T('/');
1410 subpath.MakeLower();
1412 CheckHeadUpdate(gitdir);
1414 SHARED_TREE_PTR treeptr;
1415 treeptr = SafeGet(gitdir);
1417 if (treeptr->m_Head != treeptr->m_TreeHash)
1419 treeptr->ReadHeadHash(gitdir);
1421 // Init Repository
1422 if (treeptr->m_HeadFile.IsEmpty())
1424 *isVersion = false;
1425 return 0;
1427 else if (treeptr->ReadTree())
1429 treeptr->m_LastModifyTimeHead = 0;
1430 *isVersion = false;
1431 return 1;
1433 SafeSet(gitdir, treeptr);
1436 if(isDir)
1437 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), subpath.GetLength()) >= 0);
1438 else
1439 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), -1) >= 0);
1440 subpath.ReleaseBuffer();
1442 catch(...)
1444 return -1;
1447 return 0;
1450 int CGitHeadFileMap::GetHeadHash(const CString &gitdir, CGitHash &hash)
1452 SHARED_TREE_PTR ptr;
1453 ptr = this->SafeGet(gitdir);
1455 if(ptr.get() == NULL)
1457 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1458 ptr1->ReadHeadHash(gitdir);
1460 hash = ptr1->m_Head;
1462 this->SafeSet(gitdir, ptr1);
1465 else
1467 if(ptr->CheckHeadUpdate())
1469 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1470 ptr1->ReadHeadHash(gitdir);
1472 hash = ptr1->m_Head;
1473 this->SafeSet(gitdir, ptr1);
1476 hash = ptr->m_Head;
1478 return 0;