Ask what file to save as, defaults to last view
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob9b9408b2289b446869f94fb5badafef956ab8f9b
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - 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, 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);
150 git_config_free(config);
151 config = nullptr;
153 // load index in order to enumerate files
154 if (git_repository_index(&index, repository))
156 repository = NULL;
157 m_critRepoSec.Unlock();
158 return -1;
161 size_t ecount = git_index_entrycount(index);
162 resize(ecount);
163 for (size_t i = 0; i < ecount; ++i)
165 const git_index_entry *e = git_index_get_byindex(index, i);
167 this->at(i).m_FileName.Empty();
168 g_Git.StringAppend(&this->at(i).m_FileName, (BYTE*)e->path, CP_UTF8);
169 this->at(i).m_FileName.MakeLower();
170 this->at(i).m_ModifyTime = e->mtime.seconds;
171 this->at(i).m_Flags = e->flags | e->flags_extended;
172 this->at(i).m_IndexHash = (char *) e->oid.id;
175 git_index_free(index);
177 g_Git.GetFileModifyTime(dgitdir + _T("index"), &this->m_LastModifyTime);
178 std::sort(this->begin(), this->end(), SortIndex);
180 m_critRepoSec.Unlock();
182 return 0;
185 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 if(status)
189 CString path = pathorg;
190 path.MakeLower();
192 int start = SearchInSortVector(*this, ((CString&)path).GetBuffer(), -1);
193 ((CString&)path).ReleaseBuffer();
195 if (start < 0)
197 *status = git_wc_status_unversioned;
198 if (pHash)
199 pHash->Empty();
202 else
204 int index = start;
205 if (index <0)
206 return -1;
207 if (index >= (int)size())
208 return -1;
210 // skip-worktree has higher priority than assume-valid
211 if (at(index).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE)
213 *status = git_wc_status_normal;
214 if (skipWorktree)
215 *skipWorktree = true;
217 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
219 *status = git_wc_status_normal;
220 if (assumeValid)
221 *assumeValid = true;
223 else if (time == at(index).m_ModifyTime)
225 *status = git_wc_status_normal;
227 else if (m_bCheckContent && repository)
229 git_oid actual;
230 CStringA fileA = CUnicodeUtils::GetMulti(pathorg, CP_UTF8);
231 m_critRepoSec.Lock(); // prevent concurrent access to repository instance and especially filter-lists
232 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))
234 at(index).m_ModifyTime = time;
235 *status = git_wc_status_normal;
237 else
238 *status = git_wc_status_modified;
239 fileA.ReleaseBuffer();
240 m_critRepoSec.Unlock();
242 else
243 *status = git_wc_status_modified;
245 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
246 *status = git_wc_status_conflicted;
247 else if (at(index).m_Flags & GIT_IDXENTRY_INTENT_TO_ADD)
248 *status = git_wc_status_added;
250 if(pHash)
251 *pHash = at(index).m_IndexHash;
256 if(callback && status)
257 callback(gitdir + _T("\\") + pathorg, *status, false, pData, *assumeValid, *skipWorktree);
258 return 0;
261 int CGitIndexList::GetStatus(const CString &gitdir,const CString &pathParam, git_wc_status_kind *status,
262 BOOL IsFull, BOOL /*IsRecursive*/,
263 FIll_STATUS_CALLBACK callback,void *pData,
264 CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
266 int result;
267 git_wc_status_kind dirstatus = git_wc_status_none;
268 __int64 time;
269 bool isDir = false;
270 CString path = pathParam;
272 if (status)
274 if (path.IsEmpty())
275 result = g_Git.GetFileModifyTime(gitdir, &time, &isDir);
276 else
277 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + path, &time, &isDir);
279 if (result)
281 *status = git_wc_status_deleted;
282 if (callback)
283 callback(gitdir + _T("\\") + path, git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
285 return 0;
287 if (isDir)
289 if (!path.IsEmpty())
291 if (path.Right(1) != _T("\\"))
292 path += _T("\\");
294 int len = path.GetLength();
296 for (size_t i = 0; i < size(); ++i)
298 if (at(i).m_FileName.GetLength() > len)
300 if (at(i).m_FileName.Left(len) == path)
302 if (!IsFull)
304 *status = git_wc_status_normal;
305 if (callback)
306 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);
307 return 0;
310 else
312 result = g_Git.GetFileModifyTime(gitdir+_T("\\") + at(i).m_FileName, &time);
313 if (result)
314 continue;
316 *status = git_wc_status_none;
317 if (assumeValid)
318 *assumeValid = false;
319 if (skipWorktree)
320 *skipWorktree = false;
321 GetFileStatus(gitdir, at(i).m_FileName, status, time, callback, pData, NULL, assumeValid, skipWorktree);
322 // 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
323 if (callback && (assumeValid || skipWorktree))
324 callback(gitdir + _T("\\") + path, *status, false, pData, *assumeValid, *skipWorktree);
325 if (*status != git_wc_status_none)
327 if (dirstatus == git_wc_status_none)
329 dirstatus = git_wc_status_normal;
331 if (*status != git_wc_status_normal)
333 dirstatus = git_wc_status_modified;
340 } /* End For */
342 if (dirstatus != git_wc_status_none)
344 *status = dirstatus;
346 else
348 *status = git_wc_status_unversioned;
350 if(callback)
351 callback(gitdir + _T("\\") + path, *status, false, pData, false, false);
353 return 0;
356 else
358 GetFileStatus(gitdir, path, status, time, callback, pData, pHash, assumeValid, skipWorktree);
361 return 0;
364 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
366 __int64 time;
367 int result;
369 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
371 /* Get data associated with "crt_stat.c": */
372 result = g_Git.GetFileModifyTime(IndexFile, &time);
374 if (result)
375 return result;
377 SHARED_INDEX_PTR pIndex;
378 pIndex = this->SafeGet(gitdir);
380 if (pIndex.get() == NULL)
382 if(isChanged)
383 *isChanged = true;
384 return 0;
387 if (pIndex->m_LastModifyTime == time)
389 if (isChanged)
390 *isChanged = false;
392 else
394 if (isChanged)
395 *isChanged = true;
397 return 0;
400 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
404 SHARED_INDEX_PTR pIndex(new CGitIndexList);
406 if(pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
407 return -1;
409 this->SafeSet(gitdir, pIndex);
411 }catch(...)
413 return -1;
415 return 0;
418 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
419 FIll_STATUS_CALLBACK callback,void *pData,
420 CGitHash *pHash,
421 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
425 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
427 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
428 if (pIndex.get() != NULL)
430 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
432 else
434 // git working tree has not index
435 *status = git_wc_status_unversioned;
438 catch(...)
440 return -1;
442 return 0;
445 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
449 if (path.IsEmpty())
451 *isVersion = true;
452 return 0;
455 CString subpath = path;
456 subpath.Replace(_T('\\'), _T('/'));
457 if(isDir)
458 subpath += _T('/');
460 subpath.MakeLower();
462 CheckAndUpdate(gitdir, isLoadUpdateIndex);
464 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
466 if(pIndex.get())
468 if(isDir)
469 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), subpath.GetLength()) >= 0);
470 else
471 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), -1) >= 0);
472 subpath.ReleaseBuffer();
475 }catch(...)
477 return -1;
479 return 0;
482 // This method is assumed to be called with m_SharedMutex locked.
483 int CGitHeadFileList::GetPackRef(const CString &gitdir)
485 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
487 __int64 mtime;
488 if (g_Git.GetFileModifyTime(PackRef, &mtime))
490 //packed refs is not existed
491 this->m_PackRefFile.Empty();
492 this->m_PackRefMap.clear();
493 return 0;
495 else if(mtime == m_LastModifyTimePackRef)
497 return 0;
499 else
501 this->m_PackRefFile = PackRef;
502 this->m_LastModifyTimePackRef = mtime;
505 int ret = 0;
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 if (filesize == 0)
527 ret = -1;
528 break;
530 DWORD size =0;
531 char *buff;
532 buff = new char[filesize];
534 ReadFile(hfile, buff, filesize, &size, NULL);
536 if (size != filesize)
538 delete[] buff;
539 ret = -1;
540 break;
543 CString hash;
544 CString ref;
546 for(DWORD i=0;i<filesize;)
548 hash.Empty();
549 ref.Empty();
550 if (buff[i] == '#' || buff[i] == '^')
552 while (buff[i] != '\n')
554 ++i;
555 if (i == filesize)
556 break;
558 ++i;
561 if (i >= filesize)
562 break;
564 while (buff[i] != ' ')
566 hash.AppendChar(buff[i]);
567 ++i;
568 if (i == filesize)
569 break;
572 ++i;
573 if (i >= filesize)
574 break;
576 while (buff[i] != '\n')
578 ref.AppendChar(buff[i]);
579 ++i;
580 if (i == filesize)
581 break;
584 if (!ref.IsEmpty() )
586 this->m_PackRefMap[ref] = hash;
589 while (buff[i] == '\n')
591 ++i;
592 if (i == filesize)
593 break;
597 delete[] buff;
599 } while(0);
601 return ret;
604 int CGitHeadFileList::ReadHeadHash(CString gitdir)
606 int ret = 0;
607 CAutoWriteLock lock(&this->m_SharedMutex);
608 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
610 m_HeadFile = m_Gitdir + _T("HEAD");
612 if( g_Git.GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
613 return -1;
619 CAutoFile hfile = CreateFile(m_HeadFile,
620 GENERIC_READ,
621 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
622 NULL,
623 OPEN_EXISTING,
624 FILE_ATTRIBUTE_NORMAL,
625 NULL);
627 if (!hfile)
629 ret = -1;
630 break;
633 DWORD size = 0,filesize = 0;
634 unsigned char buffer[40] ;
635 ReadFile(hfile, buffer, 4, &size, NULL);
636 if (size != 4)
638 ret = -1;
639 break;
641 buffer[4]=0;
642 if (strcmp((const char*)buffer,"ref:") == 0)
644 filesize = GetFileSize(hfile, NULL);
645 if (filesize < 5)
647 m_HeadRefFile.Empty();
648 ret = -1;
649 break;
652 unsigned char *p = (unsigned char*)malloc(filesize -4);
654 ReadFile(hfile, p, filesize - 4, &size, NULL);
656 m_HeadRefFile.Empty();
657 g_Git.StringAppend(&this->m_HeadRefFile, p, CP_UTF8, filesize - 4);
658 CString ref = this->m_HeadRefFile;
659 ref = ref.Trim();
660 int start = 0;
661 ref = ref.Tokenize(_T("\n"), start);
662 free(p);
663 m_HeadRefFile = m_Gitdir + m_HeadRefFile.Trim();
664 m_HeadRefFile.Replace(_T('/'),_T('\\'));
666 __int64 time;
667 if (g_Git.GetFileModifyTime(m_HeadRefFile, &time, NULL))
669 m_HeadRefFile.Empty();
670 if (GetPackRef(gitdir))
672 ret = -1;
673 break;
675 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
677 ret = -1;
678 break;
680 this ->m_Head = m_PackRefMap[ref];
681 ret = 0;
682 break;
685 CAutoFile href = CreateFile(m_HeadRefFile,
686 GENERIC_READ,
687 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
688 NULL,
689 OPEN_EXISTING,
690 FILE_ATTRIBUTE_NORMAL,
691 NULL);
693 if (!href)
695 m_HeadRefFile.Empty();
697 if (GetPackRef(gitdir))
699 ret = -1;
700 break;
703 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
705 ret = -1;
706 break;
708 this ->m_Head = m_PackRefMap[ref];
709 ret = 0;
710 break;
712 ReadFile(href, buffer, 40, &size, NULL);
713 if (size != 40)
715 ret = -1;
716 break;
718 this->m_Head.ConvertFromStrA((char*)buffer);
720 this->m_LastModifyTimeRef = time;
723 else
725 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
726 if(size != 36)
728 ret = -1;
729 break;
731 m_HeadRefFile.Empty();
733 this->m_Head.ConvertFromStrA((char*)buffer);
735 } while(0);
737 catch(...)
739 ret = -1;
742 return ret;
745 bool CGitHeadFileList::CheckHeadUpdate()
747 CAutoReadLock lock(&m_SharedMutex);
748 if (this->m_HeadFile.IsEmpty())
749 return true;
751 __int64 mtime=0;
753 if (g_Git.GetFileModifyTime(m_HeadFile, &mtime))
754 return true;
756 if (mtime != this->m_LastModifyTimeHead)
757 return true;
759 if (!this->m_HeadRefFile.IsEmpty())
761 if (g_Git.GetFileModifyTime(m_HeadRefFile, &mtime))
762 return true;
764 if (mtime != this->m_LastModifyTimeRef)
765 return true;
768 if(!this->m_PackRefFile.IsEmpty())
770 if (g_Git.GetFileModifyTime(m_PackRefFile, &mtime))
771 return true;
773 if (mtime != this->m_LastModifyTimePackRef)
774 return true;
777 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
778 // So we need to retry again and again until the ref exists - otherwise we will never notice
779 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
780 return true;
782 return false;
785 bool CGitHeadFileList::HeadHashEqualsTreeHash()
787 CAutoReadLock lock(&this->m_SharedMutex);
788 return (m_Head == m_TreeHash);
791 bool CGitHeadFileList::HeadFileIsEmpty()
793 CAutoReadLock lock(&this->m_SharedMutex);
794 return m_HeadFile.IsEmpty();
797 bool CGitHeadFileList::HeadIsEmpty()
799 CAutoReadLock lock(&this->m_SharedMutex);
800 return m_Head.IsEmpty();
803 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
804 const char *pathname, unsigned mode, int /*stage*/, void *context)
806 #define S_IFGITLINK 0160000
808 CGitHeadFileList *p = (CGitHeadFileList*)context;
809 if( mode&S_IFDIR )
811 if( (mode&S_IFMT) != S_IFGITLINK)
812 return READ_TREE_RECURSIVE;
815 size_t cur = p->size();
816 p->resize(p->size() + 1);
817 p->at(cur).m_Hash = (char*)sha1;
818 p->at(cur).m_FileName.Empty();
820 if(base)
821 g_Git.StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
823 g_Git.StringAppend(&p->at(cur).m_FileName,(BYTE*)pathname, CP_UTF8);
825 p->at(cur).m_FileName.MakeLower();
827 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
829 //p->m_Map[p->at(cur).m_FileName] = cur;
831 if( (mode&S_IFMT) == S_IFGITLINK)
832 return 0;
834 return READ_TREE_RECURSIVE;
837 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)
839 size_t count = git_tree_entrycount(tree);
840 for (size_t i = 0; i < count; ++i)
842 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
843 if (entry == NULL)
844 continue;
845 int mode = git_tree_entry_filemode(entry);
846 if( CallBack(git_tree_entry_id(entry)->id,
847 base,
848 base.GetLength(),
849 git_tree_entry_name(entry),
850 mode,
852 data) == READ_TREE_RECURSIVE
855 if(mode&S_IFDIR)
857 git_object *object = NULL;
858 git_tree_entry_to_object(&object, &repo, entry);
859 if (object == NULL)
860 continue;
861 CStringA parent = base;
862 parent += git_tree_entry_name(entry);
863 parent += "/";
864 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
865 git_object_free(object);
871 return 0;
874 // ReadTree is/must only be executed on an empty list
875 int CGitHeadFileList::ReadTree()
877 CAutoWriteLock lock(&m_SharedMutex);
878 CStringA gitdir = CUnicodeUtils::GetMulti(m_Gitdir, CP_UTF8);
879 git_repository *repository = NULL;
880 git_commit *commit = NULL;
881 git_tree * tree = NULL;
882 int ret = 0;
883 ATLASSERT(this->empty());
886 ret = git_repository_open(&repository, gitdir.GetBuffer());
887 gitdir.ReleaseBuffer();
888 if(ret)
889 break;
890 ret = git_commit_lookup(&commit, repository, (const git_oid*)m_Head.m_hash);
891 if(ret)
892 break;
894 ret = git_commit_tree(&tree, commit);
895 if(ret)
896 break;
898 ret = ReadTreeRecursive(*repository, tree,"", CGitHeadFileList::CallBack,this);
899 if(ret)
900 break;
902 std::sort(this->begin(), this->end(), SortTree);
903 this->m_TreeHash = (char*)(git_commit_id(commit)->id);
905 } while(0);
907 if (tree)
908 git_tree_free(tree);
910 if (commit)
911 git_commit_free(commit);
913 if (repository)
914 git_repository_free(repository);
916 if (ret)
918 clear();
919 m_LastModifyTimeHead = 0;
922 return ret;
925 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
927 CAutoWriteLock lock(&this->m_SharedMutex);
929 if (this->m_pExcludeList)
931 git_free_exclude_list(m_pExcludeList);
932 m_pExcludeList=NULL;
934 if (m_buffer)
936 free(m_buffer);
937 m_buffer = NULL;
940 this->m_BaseDir.Empty();
941 if (!isGlobal)
943 CString base = file.Mid(projectroot.GetLength() + 1);
944 base.Replace(_T('\\'), _T('/'));
946 int start = base.ReverseFind(_T('/'));
947 if(start > 0)
949 base = base.Left(start);
950 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
955 if(g_Git.GetFileModifyTime(file, &m_LastModifyTime))
956 return -1;
958 if(git_create_exclude_list(&this->m_pExcludeList))
959 return -1;
962 CAutoFile hfile = CreateFile(file,
963 GENERIC_READ,
964 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
965 NULL,
966 OPEN_EXISTING,
967 FILE_ATTRIBUTE_NORMAL,
968 NULL);
971 if (!hfile)
972 return -1 ;
974 DWORD size=0,filesize=0;
976 filesize=GetFileSize(hfile, NULL);
978 if(filesize == INVALID_FILE_SIZE)
979 return -1;
981 m_buffer = new BYTE[filesize + 1];
983 if (m_buffer == NULL)
984 return -1;
986 if (!ReadFile(hfile, m_buffer, filesize, &size, NULL))
987 return GetLastError();
989 BYTE *p = m_buffer;
990 for (DWORD i = 0; i < size; ++i)
992 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
994 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
995 m_buffer[i] = 0;
996 if (i == size - 1)
997 m_buffer[size] = 0;
999 if(p[0] != '#' && p[0] != 0)
1000 git_add_exclude((const char*)p,
1001 this->m_BaseDir.GetBuffer(),
1002 m_BaseDir.GetLength(),
1003 this->m_pExcludeList);
1005 p = m_buffer + i + 1;
1009 return 0;
1012 bool CGitIgnoreList::CheckFileChanged(const CString &path)
1014 __int64 time = 0;
1016 int ret = g_Git.GetFileModifyTime(path, &time);
1018 this->m_SharedMutex.AcquireShared();
1019 bool cacheExist = (m_Map.find(path) != m_Map.end());
1020 this->m_SharedMutex.ReleaseShared();
1022 if (!cacheExist && ret == 0)
1024 CAutoWriteLock lock(&this->m_SharedMutex);
1025 m_Map[path].m_LastModifyTime = 0;
1026 m_Map[path].m_SharedMutex.Init();
1028 // both cache and file is not exist
1029 if ((ret != 0) && (!cacheExist))
1030 return false;
1032 // file exist but cache miss
1033 if ((ret == 0) && (!cacheExist))
1034 return true;
1036 // file not exist but cache exist
1037 if ((ret != 0) && (cacheExist))
1039 return true;
1041 // file exist and cache exist
1044 CAutoReadLock lock(&this->m_SharedMutex);
1045 if (m_Map[path].m_LastModifyTime == time)
1046 return false;
1048 return true;
1051 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir,const CString &path)
1053 CString temp;
1054 temp = gitdir;
1055 temp += _T("\\");
1056 temp += path;
1058 temp.Replace(_T('/'), _T('\\'));
1060 while(!temp.IsEmpty())
1062 CString tempOrig = temp;
1063 temp += _T("\\.git");
1065 if (CGit::GitPathFileExists(temp))
1067 CString gitignore=temp;
1068 gitignore += _T("ignore");
1069 if (CheckFileChanged(gitignore))
1070 return true;
1072 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1073 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1074 if (CheckFileChanged(wcglobalgitignore))
1075 return true;
1077 if (CheckAndUpdateCoreExcludefile(adminDir))
1078 return true;
1080 return false;
1082 else
1084 temp += _T("ignore");
1085 if (CheckFileChanged(temp))
1086 return true;
1089 int found=0;
1090 int i;
1091 for (i = temp.GetLength() - 1; i >= 0; i--)
1093 if(temp[i] == _T('\\'))
1094 ++found;
1096 if(found == 2)
1097 break;
1100 temp = temp.Left(i);
1102 return true;
1105 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
1107 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
1109 CAutoWriteLock lock(&this->m_SharedMutex);
1110 if (m_Map.find(gitignore) == m_Map.end())
1111 m_Map[gitignore].m_SharedMutex.Init();
1113 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
1115 else
1117 CAutoWriteLock lock(&this->m_SharedMutex);
1118 if (m_Map.find(gitignore) != m_Map.end())
1119 m_Map[gitignore].m_SharedMutex.Release();
1121 m_Map.erase(gitignore);
1123 return 0;
1126 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir,const CString &path)
1128 CString temp;
1130 temp = gitdir;
1131 temp += _T("\\");
1132 temp += path;
1134 temp.Replace(_T('/'), _T('\\'));
1136 while (!temp.IsEmpty())
1138 CString tempOrig = temp;
1139 temp += _T("\\.git");
1141 if (CGit::GitPathFileExists(temp))
1143 CString gitignore = temp;
1144 gitignore += _T("ignore");
1145 if (CheckFileChanged(gitignore))
1147 FetchIgnoreFile(gitdir, gitignore, false);
1150 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1151 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1152 if (CheckFileChanged(wcglobalgitignore))
1154 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1157 if (CheckAndUpdateCoreExcludefile(adminDir))
1159 m_SharedMutex.AcquireShared();
1160 CString excludesFile = m_CoreExcludesfiles[adminDir];
1161 m_SharedMutex.ReleaseShared();
1162 if (!excludesFile.IsEmpty())
1163 FetchIgnoreFile(gitdir, excludesFile, true);
1166 return 0;
1168 else
1170 temp += _T("ignore");
1171 if (CheckFileChanged(temp))
1173 FetchIgnoreFile(gitdir, temp, false);
1177 int found = 0;
1178 int i;
1179 for (i = temp.GetLength() - 1; i >= 0; i--)
1181 if(temp[i] == _T('\\'))
1182 ++found;
1184 if(found == 2)
1185 break;
1188 temp = temp.Left(i);
1190 return 0;
1192 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force)
1194 // recheck every 30 seconds
1195 if (GetTickCount() - m_dMsysGitBinPathLastChecked > 30000 || force)
1197 m_dMsysGitBinPathLastChecked = GetTickCount();
1198 CString msysGitBinPath(CRegString(REG_MSYSGIT_PATH, _T(""), FALSE));
1199 if (msysGitBinPath != m_sMsysGitBinPath)
1201 m_sMsysGitBinPath = msysGitBinPath;
1202 return true;
1205 return false;
1207 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1209 bool hasChanged = false;
1211 CString projectConfig = adminDir + _T("config");
1212 CString globalConfig = g_Git.GetGitGlobalConfig();
1213 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1215 CAutoWriteLock lock(&m_coreExcludefilesSharedMutex);
1216 hasChanged = CheckAndUpdateMsysGitBinpath();
1217 CString systemConfig = m_sMsysGitBinPath + _T("\\..\\etc\\gitconfig");
1219 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1220 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1221 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1222 if (!m_sMsysGitBinPath.IsEmpty())
1223 hasChanged = hasChanged || CheckFileChanged(systemConfig);
1225 m_SharedMutex.AcquireShared();
1226 CString excludesFile = m_CoreExcludesfiles[adminDir];
1227 m_SharedMutex.ReleaseShared();
1228 if (!excludesFile.IsEmpty())
1229 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1231 if (!hasChanged)
1232 return false;
1234 git_config * config;
1235 git_config_new(&config);
1236 CStringA projectConfigA = CUnicodeUtils::GetMulti(projectConfig, CP_UTF8);
1237 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 4, FALSE);
1238 projectConfigA.ReleaseBuffer();
1239 CStringA globalConfigA = CUnicodeUtils::GetMulti(globalConfig, CP_UTF8);
1240 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 3, FALSE);
1241 globalConfigA.ReleaseBuffer();
1242 CStringA globalXDGConfigA = CUnicodeUtils::GetMulti(globalXDGConfig, CP_UTF8);
1243 git_config_add_file_ondisk(config, globalXDGConfigA.GetBuffer(), 2, FALSE);
1244 globalXDGConfigA.ReleaseBuffer();
1245 if (!m_sMsysGitBinPath.IsEmpty())
1247 CStringA systemConfigA = CUnicodeUtils::GetMulti(systemConfig, CP_UTF8);
1248 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1, FALSE);
1249 systemConfigA.ReleaseBuffer();
1251 const char * out = NULL;
1252 CStringA name(_T("core.excludesfile"));
1253 git_config_get_string(&out, config, name.GetBuffer());
1254 name.ReleaseBuffer();
1255 CStringA excludesFileA(out);
1256 excludesFile = CUnicodeUtils::GetUnicode(excludesFileA);
1257 if (excludesFile.IsEmpty())
1258 excludesFile = GetWindowsHome() + _T("\\.config\\git\\ignore");
1259 else if (excludesFile.Find(_T("~/")) == 0)
1260 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1261 git_config_free(config);
1263 CAutoWriteLock lockMap(&m_SharedMutex);
1264 g_Git.GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1265 g_Git.GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1266 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1268 m_Map[globalXDGConfig].m_SharedMutex.Release();
1269 m_Map.erase(globalXDGConfig);
1271 g_Git.GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1272 if (m_Map[globalConfig].m_LastModifyTime == 0)
1274 m_Map[globalConfig].m_SharedMutex.Release();
1275 m_Map.erase(globalConfig);
1277 if (!m_sMsysGitBinPath.IsEmpty())
1278 g_Git.GetFileModifyTime(systemConfig, &m_Map[systemConfig].m_LastModifyTime);
1279 if (m_Map[systemConfig].m_LastModifyTime == 0 || m_sMsysGitBinPath.IsEmpty())
1281 m_Map[systemConfig].m_SharedMutex.Release();
1282 m_Map.erase(systemConfig);
1284 m_CoreExcludesfiles[adminDir] = excludesFile;
1286 return true;
1288 const CString CGitIgnoreList::GetWindowsHome()
1290 static CString sWindowsHome(g_Git.GetHomeDirectory());
1291 return sWindowsHome;
1293 bool CGitIgnoreList::IsIgnore(const CString &path,const CString &projectroot)
1295 CString str=path;
1297 str.Replace(_T('\\'),_T('/'));
1299 if (str.GetLength()>0)
1300 if (str[str.GetLength()-1] == _T('/'))
1301 str = str.Left(str.GetLength() - 1);
1303 int ret;
1304 ret = CheckIgnore(str, projectroot);
1305 while (ret < 0)
1307 int start = str.ReverseFind(_T('/'));
1308 if(start < 0)
1309 return (ret == 1);
1311 str = str.Left(start);
1312 ret = CheckIgnore(str, projectroot);
1315 return (ret == 1);
1317 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1319 if (m_Map.find(ignorefile) != m_Map.end())
1321 int ret = -1;
1322 if(m_Map[ignorefile].m_pExcludeList)
1323 ret = git_check_excluded_1(patha, patha.GetLength(), base, &type, m_Map[ignorefile].m_pExcludeList);
1324 if (ret == 0 || ret == 1)
1325 return ret;
1327 return -1;
1329 int CGitIgnoreList::CheckIgnore(const CString &path,const CString &projectroot)
1331 __int64 time = 0;
1332 bool dir = 0;
1333 CString temp = projectroot + _T("\\") + path;
1334 temp.Replace(_T('/'), _T('\\'));
1336 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1337 patha.Replace('\\', '/');
1339 if(g_Git.GetFileModifyTime(temp, &time, &dir))
1340 return -1;
1342 int type = 0;
1343 if (dir)
1345 type = DT_DIR;
1347 // strip directory name
1348 // we do not need to check for a .ignore file inside a directory we might ignore
1349 int i = temp.ReverseFind(_T('\\'));
1350 if (i >= 0)
1351 temp = temp.Left(i);
1353 else
1354 type = DT_REG;
1356 char * base = NULL;
1357 int pos = patha.ReverseFind('/');
1358 base = pos >= 0 ? patha.GetBuffer() + pos + 1 : patha.GetBuffer();
1360 int ret = -1;
1362 CAutoReadLock lock(&this->m_SharedMutex);
1363 while (!temp.IsEmpty())
1365 CString tempOrig = temp;
1366 temp += _T("\\.git");
1368 if (CGit::GitPathFileExists(temp))
1370 CString gitignore = temp;
1371 gitignore += _T("ignore");
1372 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1373 break;
1375 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1376 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1377 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1378 break;
1380 m_SharedMutex.AcquireShared();
1381 CString excludesFile = m_CoreExcludesfiles[adminDir];
1382 m_SharedMutex.ReleaseShared();
1383 if (!excludesFile.IsEmpty())
1384 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1386 break;
1388 else
1390 temp += _T("ignore");
1391 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1392 break;
1395 int found = 0;
1396 int i;
1397 for (i = temp.GetLength() - 1; i >= 0; i--)
1399 if (temp[i] == _T('\\'))
1400 ++found;
1402 if (found == 2)
1403 break;
1406 temp = temp.Left(i);
1409 patha.ReleaseBuffer();
1411 return ret;
1414 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir, bool readTree /* = true */)
1416 SHARED_TREE_PTR ptr;
1417 ptr = this->SafeGet(gitdir);
1419 if (ptr.get() && !ptr->CheckHeadUpdate() && (!readTree || ptr->HeadHashEqualsTreeHash()))
1420 return false;
1422 ptr = SHARED_TREE_PTR(new CGitHeadFileList);
1423 ptr->ReadHeadHash(gitdir);
1424 if (readTree)
1425 ptr->ReadTree();
1427 this->SafeSet(gitdir, ptr);
1429 return true;
1432 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1436 if (path.IsEmpty())
1438 *isVersion = true;
1439 return 0;
1442 CString subpath = path;
1443 subpath.Replace(_T('\\'), _T('/'));
1444 if(isDir)
1445 subpath += _T('/');
1447 subpath.MakeLower();
1449 CheckHeadAndUpdate(gitdir);
1451 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1453 // Init Repository
1454 if (treeptr->HeadFileIsEmpty())
1456 *isVersion = false;
1457 return 0;
1459 else if (treeptr->empty())
1461 *isVersion = false;
1462 return 1;
1465 if(isDir)
1466 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), subpath.GetLength()) >= 0);
1467 else
1468 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), -1) >= 0);
1469 subpath.ReleaseBuffer();
1471 catch(...)
1473 return -1;
1476 return 0;