TGitCache: Fixed typo, use value instead of reference
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob9e4992667d17e765a5f520ff1478092a8cb4b2aa
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 "git2.h"
33 #include "SmartHandle.h"
35 class CAutoReadLock
37 SharedMutex *m_Lock;
38 public:
39 CAutoReadLock(SharedMutex * lock)
41 m_Lock = lock;
42 lock->AcquireShared();
44 ~CAutoReadLock()
46 m_Lock->ReleaseShared();
50 class CAutoWriteLock
52 SharedMutex *m_Lock;
53 public:
54 CAutoWriteLock(SharedMutex * lock)
56 m_Lock = lock;
57 lock->AcquireExclusive();
59 ~CAutoWriteLock()
61 m_Lock->ReleaseExclusive();
65 CGitAdminDirMap g_AdminDirMap;
67 int CGitIndex::Print()
69 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
70 (int)this->m_ModifyTime,
71 this->m_Flags,
72 this->m_IndexHash.ToString(),
73 this->m_FileName);
75 return 0;
78 CGitIndexList::CGitIndexList()
80 this->m_LastModifyTime = 0;
83 static bool SortIndex(CGitIndex &Item1, CGitIndex &Item2)
85 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
88 static bool SortTree(CGitTreeItem &Item1, CGitTreeItem &Item2)
90 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
93 int CGitIndexList::ReadIndex(CString dgitdir)
95 this->clear();
97 CStringA gitdir = CUnicodeUtils::GetMulti(dgitdir, CP_UTF8);
98 git_repository *repository = NULL;
99 git_index *index = NULL;
101 int ret = git_repository_open(&repository, gitdir.GetBuffer());
102 gitdir.ReleaseBuffer();
103 if (ret)
104 return -1;
106 if (git_repository_index(&index, repository))
108 git_repository_free(repository);
109 return -1;
112 unsigned int ecount = git_index_entrycount(index);
113 resize(ecount);
114 for (unsigned int i = 0; i < ecount; ++i)
116 git_index_entry *e = git_index_get(index, i);
118 this->at(i).m_FileName.Empty();
119 g_Git.StringAppend(&this->at(i).m_FileName, (BYTE*)e->path, CP_UTF8);
120 this->at(i).m_FileName.MakeLower();
121 this->at(i).m_ModifyTime = e->mtime.seconds;
122 this->at(i).m_Flags = e->flags | e->flags_extended;
123 this->at(i).m_IndexHash = (char *) e->oid.id;
126 git_index_free(index);
127 git_repository_free(repository);
129 g_Git.GetFileModifyTime(dgitdir + _T("index"), &this->m_LastModifyTime);
130 std::sort(this->begin(), this->end(), SortIndex);
132 return 0;
135 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)
137 if(status)
139 CString path = pathorg;
140 path.MakeLower();
142 int start = SearchInSortVector(*this, ((CString&)path).GetBuffer(), -1);
143 ((CString&)path).ReleaseBuffer();
145 if (start < 0)
147 *status = git_wc_status_unversioned;
148 if (pHash)
149 pHash->Empty();
152 else
154 int index = start;
155 if (index <0)
156 return -1;
157 if (index >= size() )
158 return -1;
160 // skip-worktree has higher priority than assume-valid
161 if (at(index).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE)
163 *status = git_wc_status_normal;
164 if (skipWorktree)
165 *skipWorktree = true;
167 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
169 *status = git_wc_status_normal;
170 if (assumeValid)
171 *assumeValid = true;
173 else if (time == at(index).m_ModifyTime)
175 *status = git_wc_status_normal;
177 else
179 *status = git_wc_status_modified;
182 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
183 *status = git_wc_status_conflicted;
184 else if (at(index).m_Flags & GIT_IDXENTRY_INTENT_TO_ADD)
185 *status = git_wc_status_added;
187 if(pHash)
188 *pHash = at(index).m_IndexHash;
193 if(callback && status)
194 callback(gitdir + _T("\\") + pathorg, *status, false, pData, *assumeValid, *skipWorktree);
195 return 0;
198 int CGitIndexList::GetStatus(const CString &gitdir,const CString &pathParam, git_wc_status_kind *status,
199 BOOL IsFull, BOOL IsRecursive,
200 FIll_STATUS_CALLBACK callback,void *pData,
201 CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
203 int result;
204 git_wc_status_kind dirstatus = git_wc_status_none;
205 __int64 time;
206 bool isDir = false;
207 CString path = pathParam;
209 if (status)
211 if (path.IsEmpty())
212 result = g_Git.GetFileModifyTime(gitdir, &time, &isDir);
213 else
214 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + path, &time, &isDir);
216 if (result)
218 *status = git_wc_status_deleted;
219 if (callback)
220 callback(gitdir + _T("\\") + path, git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
222 return 0;
224 if (isDir)
226 if (!path.IsEmpty())
228 if (path.Right(1) != _T("\\"))
229 path += _T("\\");
231 int len = path.GetLength();
233 for (int i = 0; i < size(); i++)
235 if (at(i).m_FileName.GetLength() > len)
237 if (at(i).m_FileName.Left(len) == path)
239 if (!IsFull)
241 *status = git_wc_status_normal;
242 if (callback)
243 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);
244 return 0;
247 else
249 result = g_Git.GetFileModifyTime(gitdir+_T("\\") + at(i).m_FileName, &time);
250 if (result)
251 continue;
253 *status = git_wc_status_none;
254 if (assumeValid)
255 *assumeValid = false;
256 if (skipWorktree)
257 *skipWorktree = false;
258 GetFileStatus(gitdir, at(i).m_FileName, status, time, callback, pData, NULL, assumeValid, skipWorktree);
259 // 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
260 if (callback && (assumeValid || skipWorktree))
261 callback(gitdir + _T("\\") + path, *status, false, pData, *assumeValid, *skipWorktree);
262 if (*status != git_wc_status_none)
264 if (dirstatus == git_wc_status_none)
266 dirstatus = git_wc_status_normal;
268 if (*status != git_wc_status_normal)
270 dirstatus = git_wc_status_modified;
277 } /* End For */
279 if (dirstatus != git_wc_status_none)
281 *status = dirstatus;
283 else
285 *status = git_wc_status_unversioned;
287 if(callback)
288 callback(gitdir + _T("\\") + path, *status, false, pData, false, false);
290 return 0;
293 else
295 GetFileStatus(gitdir, path, status, time, callback, pData, pHash, assumeValid, skipWorktree);
298 return 0;
301 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
303 __int64 time;
304 int result;
306 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
308 /* Get data associated with "crt_stat.c": */
309 result = g_Git.GetFileModifyTime(IndexFile, &time);
311 if (result)
312 return result;
314 SHARED_INDEX_PTR pIndex;
315 pIndex = this->SafeGet(gitdir);
317 if (pIndex.get() == NULL)
319 if(isChanged)
320 *isChanged = true;
321 return 0;
324 if (pIndex->m_LastModifyTime == time)
326 if (isChanged)
327 *isChanged = false;
329 else
331 if (isChanged)
332 *isChanged = true;
334 return 0;
337 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
341 SHARED_INDEX_PTR pIndex(new CGitIndexList);
343 if(pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
344 return -1;
346 this->SafeSet(gitdir, pIndex);
348 }catch(...)
350 return -1;
352 return 0;
355 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
356 FIll_STATUS_CALLBACK callback,void *pData,
357 CGitHash *pHash,
358 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
362 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
364 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
365 if (pIndex.get() != NULL)
367 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
369 else
371 // git working tree has not index
372 *status = git_wc_status_unversioned;
375 catch(...)
377 return -1;
379 return 0;
382 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
386 if (path.IsEmpty())
388 *isVersion = true;
389 return 0;
392 CString subpath = path;
393 subpath.Replace(_T('\\'), _T('/'));
394 if(isDir)
395 subpath += _T('/');
397 subpath.MakeLower();
399 CheckAndUpdate(gitdir, isLoadUpdateIndex);
401 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
403 if(pIndex.get())
405 if(isDir)
406 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), subpath.GetLength()) >= 0);
407 else
408 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), -1) >= 0);
409 subpath.ReleaseBuffer();
412 }catch(...)
414 return -1;
416 return 0;
419 int CGitHeadFileList::GetPackRef(const CString &gitdir)
421 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
423 __int64 mtime;
424 if (g_Git.GetFileModifyTime(PackRef, &mtime))
426 CAutoWriteLock lock(&this->m_SharedMutex);
427 //packed refs is not existed
428 this->m_PackRefFile.Empty();
429 this->m_PackRefMap.clear();
430 return 0;
432 else if(mtime == m_LastModifyTimePackRef)
434 return 0;
436 else
438 CAutoWriteLock lock(&this->m_SharedMutex);
439 this->m_PackRefFile = PackRef;
440 this->m_LastModifyTimePackRef = mtime;
443 int ret = 0;
445 CAutoWriteLock lock(&this->m_SharedMutex);
446 this->m_PackRefMap.clear();
448 CAutoFile hfile = CreateFile(PackRef,
449 GENERIC_READ,
450 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
451 NULL,
452 OPEN_EXISTING,
453 FILE_ATTRIBUTE_NORMAL,
454 NULL);
457 if (!hfile)
459 ret = -1;
460 break;
463 DWORD filesize = GetFileSize(hfile, NULL);
464 DWORD size =0;
465 char *buff;
466 buff = new char[filesize];
468 ReadFile(hfile, buff, filesize, &size, NULL);
470 if (size != filesize)
472 ret = -1;
473 break;
476 CString hash;
477 CString ref;
479 for(DWORD i=0;i<filesize;)
481 hash.Empty();
482 ref.Empty();
483 if (buff[i] == '#' || buff[i] == '^')
485 while (buff[i] != '\n')
487 i++;
488 if (i == filesize)
489 break;
491 i++;
494 if (i >= filesize)
495 break;
497 while (buff[i] != ' ')
499 hash.AppendChar(buff[i]);
500 i++;
501 if (i == filesize)
502 break;
505 i++;
506 if (i >= filesize)
507 break;
509 while (buff[i] != '\n')
511 ref.AppendChar(buff[i]);
512 i++;
513 if (i == filesize)
514 break;
517 if (!ref.IsEmpty() )
519 this->m_PackRefMap[ref] = hash;
522 while (buff[i] == '\n')
524 i++;
525 if (i == filesize)
526 break;
530 delete buff;
532 } while(0);
534 return ret;
537 int CGitHeadFileList::ReadHeadHash(CString gitdir)
539 int ret = 0;
540 CAutoWriteLock lock(&this->m_SharedMutex);
541 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
543 m_HeadFile = m_Gitdir + _T("HEAD");
545 if( g_Git.GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
546 return -1;
552 CAutoFile hfile = CreateFile(m_HeadFile,
553 GENERIC_READ,
554 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
555 NULL,
556 OPEN_EXISTING,
557 FILE_ATTRIBUTE_NORMAL,
558 NULL);
560 if (!hfile)
562 ret = -1;
563 break;
566 DWORD size = 0,filesize = 0;
567 unsigned char buffer[40] ;
568 ReadFile(hfile, buffer, 4, &size, NULL);
569 if (size != 4)
571 ret = -1;
572 break;
574 buffer[4]=0;
575 if (strcmp((const char*)buffer,"ref:") == 0)
577 filesize = GetFileSize(hfile, NULL);
579 unsigned char *p = (unsigned char*)malloc(filesize -4);
581 ReadFile(hfile, p, filesize - 4, &size, NULL);
583 m_HeadRefFile.Empty();
584 g_Git.StringAppend(&this->m_HeadRefFile, p, CP_UTF8, filesize - 4);
585 CString ref = this->m_HeadRefFile;
586 ref = ref.Trim();
587 int start = 0;
588 ref = ref.Tokenize(_T("\n"), start);
589 free(p);
590 m_HeadRefFile = m_Gitdir + m_HeadRefFile.Trim();
591 m_HeadRefFile.Replace(_T('/'),_T('\\'));
593 __int64 time;
594 if (g_Git.GetFileModifyTime(m_HeadRefFile, &time, NULL))
596 m_HeadRefFile.Empty();
597 if (GetPackRef(gitdir))
599 ret = -1;
600 break;
602 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
604 ret = -1;
605 break;
607 this ->m_Head = m_PackRefMap[ref];
608 ret = 0;
609 break;
612 CAutoFile href = CreateFile(m_HeadRefFile,
613 GENERIC_READ,
614 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
615 NULL,
616 OPEN_EXISTING,
617 FILE_ATTRIBUTE_NORMAL,
618 NULL);
620 if (!href)
622 m_HeadRefFile.Empty();
624 if (GetPackRef(gitdir))
626 ret = -1;
627 break;
630 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
632 ret = -1;
633 break;
635 this ->m_Head = m_PackRefMap[ref];
636 ret = 0;
637 break;
639 ReadFile(href, buffer, 40, &size, NULL);
640 if (size != 40)
642 ret = -1;
643 break;
645 this->m_Head.ConvertFromStrA((char*)buffer);
647 this->m_LastModifyTimeRef = time;
650 else
652 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
653 if(size != 36)
655 ret = -1;
656 break;
658 m_HeadRefFile.Empty();
660 this->m_Head.ConvertFromStrA((char*)buffer);
662 } while(0);
664 catch(...)
666 ret = -1;
669 return ret;
672 bool CGitHeadFileList::CheckHeadUpdate()
674 CAutoReadLock lock(&m_SharedMutex);
675 if (this->m_HeadFile.IsEmpty())
676 return true;
678 __int64 mtime=0;
680 if (g_Git.GetFileModifyTime(m_HeadFile, &mtime))
681 return true;
683 if (mtime != this->m_LastModifyTimeHead)
684 return true;
686 if (!this->m_HeadRefFile.IsEmpty())
688 if (g_Git.GetFileModifyTime(m_HeadRefFile, &mtime))
689 return true;
691 if (mtime != this->m_LastModifyTimeRef)
692 return true;
695 if(!this->m_PackRefFile.IsEmpty())
697 if (g_Git.GetFileModifyTime(m_PackRefFile, &mtime))
698 return true;
700 if (mtime != this->m_LastModifyTimePackRef)
701 return true;
704 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
705 // So we need to retry again and again until the ref exists - otherwise we will never notice
706 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
707 return true;
709 return false;
712 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
713 const char *pathname, unsigned mode, int /*stage*/, void *context)
715 #define S_IFGITLINK 0160000
717 CGitHeadFileList *p = (CGitHeadFileList*)context;
718 if( mode&S_IFDIR )
720 if( (mode&S_IFMT) != S_IFGITLINK)
721 return READ_TREE_RECURSIVE;
724 unsigned int cur = p->size();
725 p->resize(p->size() + 1);
726 p->at(cur).m_Hash = (char*)sha1;
727 p->at(cur).m_FileName.Empty();
729 if(base)
730 g_Git.StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
732 g_Git.StringAppend(&p->at(cur).m_FileName,(BYTE*)pathname, CP_UTF8);
734 p->at(cur).m_FileName.MakeLower();
736 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
738 //p->m_Map[p->at(cur).m_FileName] = cur;
740 if( (mode&S_IFMT) == S_IFGITLINK)
741 return 0;
743 return READ_TREE_RECURSIVE;
746 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)
748 size_t count = git_tree_entrycount(tree);
749 for (int i = 0; i < count; i++)
751 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
752 if (entry == NULL)
753 continue;
754 int mode = git_tree_entry_attributes(entry);
755 if( CallBack(git_tree_entry_id(entry)->id,
756 base,
757 base.GetLength(),
758 git_tree_entry_name(entry),
759 mode,
761 data) == READ_TREE_RECURSIVE
764 if(mode&S_IFDIR)
766 git_object *object = NULL;
767 git_tree_entry_to_object(&object, &repo, entry);
768 if (object == NULL)
769 continue;
770 CStringA parent = base;
771 parent += git_tree_entry_name(entry);
772 parent += "/";
773 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
774 git_object_free(object);
780 return 0;
783 int CGitHeadFileList::ReadTree()
785 CAutoWriteLock lock(&m_SharedMutex);
786 CStringA gitdir = CUnicodeUtils::GetMulti(m_Gitdir, CP_UTF8);
787 git_repository *repository = NULL;
788 git_commit *commit = NULL;
789 git_tree * tree = NULL;
790 int ret = 0;
791 this->clear(); // hack to avoid duplicates in the head list, which are introduced in GitStatus::GetFileStatus when this method is called
794 ret = git_repository_open(&repository, gitdir.GetBuffer());
795 gitdir.ReleaseBuffer();
796 if(ret)
797 break;
798 ret = git_commit_lookup(&commit, repository, (const git_oid*)m_Head.m_hash);
799 if(ret)
800 break;
802 ret = git_commit_tree(&tree, commit);
803 if(ret)
804 break;
806 ret = ReadTreeRecursive(*repository, tree,"", CGitHeadFileList::CallBack,this);
807 if(ret)
808 break;
810 std::sort(this->begin(), this->end(), SortTree);
811 this->m_TreeHash = (char*)(git_commit_id(commit)->id);
813 } while(0);
815 if (tree)
816 git_tree_free(tree);
818 if (commit)
819 git_commit_free(commit);
821 if (repository)
822 git_repository_free(repository);
824 return ret;
827 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
829 CAutoWriteLock lock(&this->m_SharedMutex);
831 if (this->m_pExcludeList)
833 free(m_pExcludeList);
834 m_pExcludeList=NULL;
837 this->m_BaseDir.Empty();
838 if (!isGlobal)
840 CString base = file.Mid(projectroot.GetLength() + 1);
841 base.Replace(_T('\\'), _T('/'));
843 int start = base.ReverseFind(_T('/'));
844 if(start > 0)
846 base = base.Left(start);
847 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
852 if(g_Git.GetFileModifyTime(file, &m_LastModifyTime))
853 return -1;
855 if(git_create_exclude_list(&this->m_pExcludeList))
856 return -1;
859 CAutoFile hfile = CreateFile(file,
860 GENERIC_READ,
861 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
862 NULL,
863 OPEN_EXISTING,
864 FILE_ATTRIBUTE_NORMAL,
865 NULL);
868 if (!hfile)
869 return -1 ;
871 DWORD size=0,filesize=0;
873 filesize=GetFileSize(hfile, NULL);
875 if(filesize == INVALID_FILE_SIZE)
876 return -1;
878 BYTE *buffer = new BYTE[filesize + 1];
880 if(buffer == NULL)
881 return -1;
883 if(! ReadFile(hfile, buffer,filesize,&size,NULL))
884 return GetLastError();
886 BYTE *p = buffer;
887 for (int i = 0; i < size; i++)
889 if (buffer[i] == '\n' || buffer[i] == '\r' || i == (size - 1))
891 if (buffer[i] == '\n' || buffer[i] == '\r')
892 buffer[i] = 0;
893 if (i == size - 1)
894 buffer[size] = 0;
896 if(p[0] != '#' && p[0] != 0)
897 git_add_exclude((const char*)p,
898 this->m_BaseDir.GetBuffer(),
899 m_BaseDir.GetLength(),
900 this->m_pExcludeList);
902 p=buffer+i+1;
905 /* Can't free buffer, exluced list will use this buffer*/
906 //delete buffer;
907 //buffer = NULL;
909 return 0;
912 bool CGitIgnoreList::CheckFileChanged(const CString &path)
914 __int64 time = 0;
916 int ret = g_Git.GetFileModifyTime(path, &time);
918 this->m_SharedMutex.AcquireShared();
919 bool cacheExist = (m_Map.find(path) != m_Map.end());
920 this->m_SharedMutex.ReleaseShared();
922 if (!cacheExist && ret == 0)
924 CAutoWriteLock lock(&this->m_SharedMutex);
925 m_Map[path].m_LastModifyTime = 0;
926 m_Map[path].m_SharedMutex.Init();
928 // both cache and file is not exist
929 if ((ret != 0) && (!cacheExist))
930 return false;
932 // file exist but cache miss
933 if ((ret == 0) && (!cacheExist))
934 return true;
936 // file not exist but cache exist
937 if ((ret != 0) && (cacheExist))
939 return true;
941 // file exist and cache exist
944 CAutoReadLock lock(&this->m_SharedMutex);
945 if (m_Map[path].m_LastModifyTime == time)
946 return false;
948 return true;
951 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir,const CString &path)
953 CString temp;
954 temp = gitdir;
955 temp += _T("\\");
956 temp += path;
958 temp.Replace(_T('/'), _T('\\'));
960 while(!temp.IsEmpty())
962 CString tempOrig = temp;
963 temp += _T("\\.git");
965 if (CGit::GitPathFileExists(temp))
967 CString gitignore=temp;
968 gitignore += _T("ignore");
969 if (CheckFileChanged(gitignore))
970 return true;
972 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
973 CString wcglobalgitignore = adminDir + _T("info\\exclude");
974 if (CheckFileChanged(wcglobalgitignore))
975 return true;
977 if (CheckAndUpdateCoreExcludefile(adminDir))
978 return true;
980 return false;
982 else
984 temp += _T("ignore");
985 if (CheckFileChanged(temp))
986 return true;
989 int found=0;
990 int i;
991 for (i = temp.GetLength() - 1; i >= 0; i--)
993 if(temp[i] == _T('\\'))
994 found ++;
996 if(found == 2)
997 break;
1000 temp = temp.Left(i);
1002 return true;
1005 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
1007 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
1009 CAutoWriteLock lock(&this->m_SharedMutex);
1010 if (m_Map.find(gitignore) == m_Map.end())
1011 m_Map[gitignore].m_SharedMutex.Init();
1013 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
1015 else
1017 CAutoWriteLock lock(&this->m_SharedMutex);
1018 if (m_Map.find(gitignore) != m_Map.end())
1019 m_Map[gitignore].m_SharedMutex.Release();
1021 m_Map.erase(gitignore);
1023 return 0;
1026 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir,const CString &path)
1028 CString temp;
1030 temp = gitdir;
1031 temp += _T("\\");
1032 temp += path;
1034 temp.Replace(_T('/'), _T('\\'));
1036 while (!temp.IsEmpty())
1038 CString tempOrig = temp;
1039 temp += _T("\\.git");
1041 if (CGit::GitPathFileExists(temp))
1043 CString gitignore = temp;
1044 gitignore += _T("ignore");
1045 if (CheckFileChanged(gitignore))
1047 FetchIgnoreFile(gitdir, gitignore, false);
1050 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1051 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1052 if (CheckFileChanged(wcglobalgitignore))
1054 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1057 if (CheckAndUpdateCoreExcludefile(adminDir))
1059 m_SharedMutex.AcquireShared();
1060 CString excludesFile = m_CoreExcludesfiles[adminDir];
1061 m_SharedMutex.ReleaseShared();
1062 if (!excludesFile.IsEmpty())
1063 FetchIgnoreFile(gitdir, excludesFile, true);
1066 return 0;
1068 else
1070 temp += _T("ignore");
1071 if (CheckFileChanged(temp))
1073 FetchIgnoreFile(gitdir, temp, false);
1077 int found = 0;
1078 int i;
1079 for (i = temp.GetLength() - 1; i >= 0; i--)
1081 if(temp[i] == _T('\\'))
1082 found ++;
1084 if(found == 2)
1085 break;
1088 temp = temp.Left(i);
1090 return 0;
1092 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force)
1094 // recheck every 30 seconds
1095 if (GetTickCount() - m_dMsysGitBinPathLastChecked > 30000 || force)
1097 m_dMsysGitBinPathLastChecked = GetTickCount();
1098 CString msysGitBinPath(CRegString(REG_MSYSGIT_PATH, _T(""), FALSE));
1099 if (msysGitBinPath != m_sMsysGitBinPath)
1101 m_sMsysGitBinPath = msysGitBinPath;
1102 return true;
1105 return false;
1107 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1109 bool hasChanged = false;
1111 CString projectConfig = adminDir + _T("config");
1112 CString globalConfig = GetWindowsHome() + _T("\\.gitconfig");
1114 CAutoWriteLock lock(&m_coreExcludefilesSharedMutex);
1115 hasChanged = CheckAndUpdateMsysGitBinpath();
1116 CString systemConfig = m_sMsysGitBinPath + _T("\\..\\etc\\gitconfig");
1118 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1119 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1120 if (!m_sMsysGitBinPath.IsEmpty())
1121 hasChanged = hasChanged || CheckFileChanged(systemConfig);
1123 m_SharedMutex.AcquireShared();
1124 CString excludesFile = m_CoreExcludesfiles[adminDir];
1125 m_SharedMutex.ReleaseShared();
1126 if (!excludesFile.IsEmpty())
1127 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1129 if (!hasChanged)
1130 return false;
1132 git_config * config;
1133 git_config_new(&config);
1134 CStringA projectConfigA = CUnicodeUtils::GetMulti(projectConfig, CP_UTF8);
1135 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 3);
1136 projectConfigA.ReleaseBuffer();
1137 CStringA globalConfigA = CUnicodeUtils::GetMulti(globalConfig, CP_UTF8);
1138 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 2);
1139 globalConfigA.ReleaseBuffer();
1140 if (!m_sMsysGitBinPath.IsEmpty())
1142 CStringA systemConfigA = CUnicodeUtils::GetMulti(systemConfig, CP_UTF8);
1143 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1);
1144 systemConfigA.ReleaseBuffer();
1146 const char * out = NULL;
1147 CStringA name(_T("core.excludesfile"));
1148 git_config_get_string(&out, config, name.GetBuffer());
1149 name.ReleaseBuffer();
1150 CStringA excludesFileA(out);
1151 excludesFile = CUnicodeUtils::GetUnicode(excludesFileA);
1152 if (excludesFile.Find(_T("~/")) == 0)
1153 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1154 git_config_free(config);
1156 CAutoWriteLock lockMap(&m_SharedMutex);
1157 g_Git.GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1158 g_Git.GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1159 if (m_Map[globalConfig].m_LastModifyTime == 0)
1161 m_Map[globalConfig].m_SharedMutex.Release();
1162 m_Map.erase(globalConfig);
1164 if (!m_sMsysGitBinPath.IsEmpty())
1165 g_Git.GetFileModifyTime(systemConfig, &m_Map[systemConfig].m_LastModifyTime);
1166 if (m_Map[systemConfig].m_LastModifyTime == 0 || m_sMsysGitBinPath.IsEmpty())
1168 m_Map[systemConfig].m_SharedMutex.Release();
1169 m_Map.erase(systemConfig);
1171 m_CoreExcludesfiles[adminDir] = excludesFile;
1173 return true;
1175 const CString CGitIgnoreList::GetWindowsHome()
1177 static CString sWindowsHome(g_Git.GetHomeDirectory());
1178 return sWindowsHome;
1180 bool CGitIgnoreList::IsIgnore(const CString &path,const CString &projectroot)
1182 CString str=path;
1184 str.Replace(_T('\\'),_T('/'));
1186 if (str.GetLength()>0)
1187 if (str[str.GetLength()-1] == _T('/'))
1188 str = str.Left(str.GetLength() - 1);
1190 int ret;
1191 ret = CheckIgnore(str, projectroot);
1192 while (ret < 0)
1194 int start = str.ReverseFind(_T('/'));
1195 if(start < 0)
1196 return (ret == 1);
1198 str = str.Left(start);
1199 ret = CheckIgnore(str, projectroot);
1202 return (ret == 1);
1204 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1206 if (m_Map.find(ignorefile) != m_Map.end())
1208 int ret = -1;
1209 if(m_Map[ignorefile].m_pExcludeList)
1210 ret = git_check_excluded_1(patha, patha.GetLength(), base, &type, m_Map[ignorefile].m_pExcludeList);
1211 if (ret == 0 || ret == 1)
1212 return ret;
1214 return -1;
1216 int CGitIgnoreList::CheckIgnore(const CString &path,const CString &projectroot)
1218 __int64 time = 0;
1219 bool dir = 0;
1220 CString temp = projectroot + _T("\\") + path;
1221 temp.Replace(_T('/'), _T('\\'));
1223 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1224 patha.Replace('\\', '/');
1226 if(g_Git.GetFileModifyTime(temp, &time, &dir))
1227 return -1;
1229 int type = 0;
1230 if (dir)
1232 type = DT_DIR;
1234 // strip directory name
1235 // we do not need to check for a .ignore file inside a directory we might ignore
1236 int i = temp.ReverseFind(_T('\\'));
1237 if (i >= 0)
1238 temp = temp.Left(i);
1240 else
1241 type = DT_REG;
1243 char * base = NULL;
1244 int pos = patha.ReverseFind('/');
1245 base = pos >= 0 ? patha.GetBuffer() + pos + 1 : patha.GetBuffer();
1247 int ret = -1;
1249 CAutoReadLock lock(&this->m_SharedMutex);
1250 while (!temp.IsEmpty())
1252 CString tempOrig = temp;
1253 temp += _T("\\.git");
1255 if (CGit::GitPathFileExists(temp))
1257 CString gitignore = temp;
1258 gitignore += _T("ignore");
1259 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1260 break;
1262 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1263 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1264 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1265 break;
1267 m_SharedMutex.AcquireShared();
1268 CString excludesFile = m_CoreExcludesfiles[adminDir];
1269 m_SharedMutex.ReleaseShared();
1270 if (!excludesFile.IsEmpty())
1271 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1273 break;
1275 else
1277 temp += _T("ignore");
1278 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1279 break;
1282 int found = 0;
1283 int i;
1284 for (i = temp.GetLength() - 1; i >= 0; i--)
1286 if (temp[i] == _T('\\'))
1287 found++;
1289 if (found == 2)
1290 break;
1293 temp = temp.Left(i);
1296 patha.ReleaseBuffer();
1298 return ret;
1301 bool CGitHeadFileMap::CheckHeadUpdate(const CString &gitdir)
1303 SHARED_TREE_PTR ptr;
1304 ptr = this->SafeGet(gitdir);
1306 if( ptr.get())
1308 return ptr->CheckHeadUpdate();
1310 else
1312 SHARED_TREE_PTR ptr1(new CGitHeadFileList);
1313 ptr1->ReadHeadHash(gitdir);
1315 this->SafeSet(gitdir, ptr1);
1316 return true;
1318 return false;
1321 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1325 if (path.IsEmpty())
1327 *isVersion = true;
1328 return 0;
1331 CString subpath = path;
1332 subpath.Replace(_T('\\'), _T('/'));
1333 if(isDir)
1334 subpath += _T('/');
1336 subpath.MakeLower();
1338 CheckHeadUpdate(gitdir);
1340 SHARED_TREE_PTR treeptr;
1341 treeptr = SafeGet(gitdir);
1343 if (treeptr->m_Head != treeptr->m_TreeHash)
1345 treeptr->ReadHeadHash(gitdir);
1347 // Init Repository
1348 if (treeptr->m_HeadFile.IsEmpty())
1350 *isVersion = false;
1351 return 0;
1353 else if (treeptr->ReadTree())
1355 treeptr->m_LastModifyTimeHead = 0;
1356 *isVersion = false;
1357 return 1;
1359 SafeSet(gitdir, treeptr);
1362 if(isDir)
1363 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), subpath.GetLength()) >= 0);
1364 else
1365 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), -1) >= 0);
1366 subpath.ReleaseBuffer();
1368 catch(...)
1370 return -1;
1373 return 0;
1376 int CGitHeadFileMap::GetHeadHash(const CString &gitdir, CGitHash &hash)
1378 SHARED_TREE_PTR ptr;
1379 ptr = this->SafeGet(gitdir);
1381 if(ptr.get() == NULL)
1383 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1384 ptr1->ReadHeadHash(gitdir);
1386 hash = ptr1->m_Head;
1388 this->SafeSet(gitdir, ptr1);
1391 else
1393 if(ptr->CheckHeadUpdate())
1395 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1396 ptr1->ReadHeadHash(gitdir);
1398 hash = ptr1->m_Head;
1399 this->SafeSet(gitdir, ptr1);
1402 hash = ptr->m_Head;
1404 return 0;