TGitCache: Show "assume-unchanged" files as clean
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob744e4abdd748124f536af251fde90a2f963219b2
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 CGitAdminDirMap g_AdminDirMap;
37 int CGitIndex::Print()
39 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
40 (int)this->m_ModifyTime,
41 this->m_Flags,
42 this->m_IndexHash.ToString(),
43 this->m_FileName);
45 return 0;
48 CGitIndexList::CGitIndexList()
50 this->m_LastModifyTime = 0;
53 static bool SortIndex(CGitIndex &Item1, CGitIndex &Item2)
55 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
58 static bool SortTree(CGitTreeItem &Item1, CGitTreeItem &Item2)
60 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
63 int CGitIndexList::ReadIndex(CString dgitdir)
65 this->clear();
67 CStringA gitdir = CUnicodeUtils::GetMulti(dgitdir, CP_UTF8);
68 git_repository *repository = NULL;
69 git_index *index = NULL;
71 int ret = git_repository_open(&repository, gitdir.GetBuffer());
72 gitdir.ReleaseBuffer();
73 if (ret)
74 return -1;
76 if (git_repository_index(&index, repository))
78 git_repository_free(repository);
79 return -1;
82 unsigned int ecount = git_index_entrycount(index);
83 resize(ecount);
84 for (unsigned int i = 0; i < ecount; ++i)
86 git_index_entry *e = git_index_get(index, i);
88 this->at(i).m_FileName.Empty();
89 g_Git.StringAppend(&this->at(i).m_FileName, (BYTE*)e->path, CP_UTF8);
90 this->at(i).m_FileName.MakeLower();
91 this->at(i).m_ModifyTime = e->mtime.seconds;
92 this->at(i).m_Flags = e->flags;
93 this->at(i).m_IndexHash = (char *) e->oid.id;
96 git_index_free(index);
97 git_repository_free(repository);
99 g_Git.GetFileModifyTime(dgitdir + _T("index"), &this->m_LastModifyTime);
100 std::sort(this->begin(), this->end(), SortIndex);
102 return 0;
105 int CGitIndexList::GetFileStatus(const CString &gitdir,const CString &pathorg,git_wc_status_kind *status,__int64 time,FIll_STATUS_CALLBACK callback,void *pData, CGitHash *pHash)
107 if(status)
109 CString path = pathorg;
110 path.MakeLower();
112 int start = SearchInSortVector(*this, ((CString&)path).GetBuffer(), -1);
113 ((CString&)path).ReleaseBuffer();
115 if (start < 0)
117 *status = git_wc_status_unversioned;
118 if (pHash)
119 pHash->Empty();
122 else
124 int index = start;
125 if (index <0)
126 return -1;
127 if (index >= size() )
128 return -1;
130 if (at(index).m_Flags & GIT_IDXENTRY_VALID)
132 *status = git_wc_status_normal;
134 else if (time == at(index).m_ModifyTime)
136 *status = git_wc_status_normal;
138 else
140 *status = git_wc_status_modified;
143 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
144 *status = git_wc_status_conflicted;
145 else if (at(index).m_Flags & GIT_IDXENTRY_INTENT_TO_ADD)
146 *status = git_wc_status_added;
148 if(pHash)
149 *pHash = at(index).m_IndexHash;
154 if(callback && status)
155 callback(gitdir + _T("\\") + pathorg, *status, false, pData);
156 return 0;
159 int CGitIndexList::GetStatus(const CString &gitdir,const CString &pathParam, git_wc_status_kind *status,
160 BOOL IsFull, BOOL IsRecursive,
161 FIll_STATUS_CALLBACK callback,void *pData,
162 CGitHash *pHash)
164 int result;
165 git_wc_status_kind dirstatus = git_wc_status_none;
166 __int64 time;
167 bool isDir = false;
168 CString path = pathParam;
170 if (status)
172 if (path.IsEmpty())
173 result = g_Git.GetFileModifyTime(gitdir, &time, &isDir);
174 else
175 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + path, &time, &isDir);
177 if (result)
179 *status = git_wc_status_deleted;
180 if (callback)
181 callback(gitdir + _T("\\") + path, git_wc_status_deleted, false, pData);
183 return 0;
185 if (isDir)
187 if (!path.IsEmpty())
189 if (path.Right(1) != _T("\\"))
190 path += _T("\\");
192 int len = path.GetLength();
194 for (int i = 0; i < size(); i++)
196 if (at(i).m_FileName.GetLength() > len)
198 if (at(i).m_FileName.Left(len) == path)
200 if (!IsFull)
202 *status = git_wc_status_normal;
203 if (callback)
204 callback(gitdir + _T("\\") + path, *status, false, pData);
205 return 0;
208 else
210 result = g_Git.GetFileModifyTime(gitdir+_T("\\") + at(i).m_FileName, &time);
211 if (result)
212 continue;
214 *status = git_wc_status_none;
215 GetFileStatus(gitdir, at(i).m_FileName, status, time, callback, pData);
216 if (*status != git_wc_status_none)
218 if (dirstatus == git_wc_status_none)
220 dirstatus = git_wc_status_normal;
222 if (*status != git_wc_status_normal)
224 dirstatus = git_wc_status_modified;
231 } /* End For */
233 if (dirstatus != git_wc_status_none)
235 *status = dirstatus;
237 else
239 *status = git_wc_status_unversioned;
241 if(callback)
242 callback(gitdir + _T("\\") + path, *status, false, pData);
244 return 0;
247 else
249 GetFileStatus(gitdir, path, status, time, callback, pData, pHash);
252 return 0;
255 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
257 __int64 time;
258 int result;
260 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
262 /* Get data associated with "crt_stat.c": */
263 result = g_Git.GetFileModifyTime(IndexFile, &time);
265 if (result)
266 return result;
268 SHARED_INDEX_PTR pIndex;
269 pIndex = this->SafeGet(gitdir);
271 if (pIndex.get() == NULL)
273 if(isChanged)
274 *isChanged = true;
275 return 0;
278 if (pIndex->m_LastModifyTime == time)
280 if (isChanged)
281 *isChanged = false;
283 else
285 if (isChanged)
286 *isChanged = true;
288 return 0;
291 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
295 SHARED_INDEX_PTR pIndex(new CGitIndexList);
297 if(pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
298 return -1;
300 this->SafeSet(gitdir, pIndex);
302 }catch(...)
304 return -1;
306 return 0;
309 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
310 FIll_STATUS_CALLBACK callback,void *pData,
311 CGitHash *pHash,
312 bool isLoadUpdatedIndex)
316 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
318 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
319 if (pIndex.get() != NULL)
321 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash);
325 catch(...)
327 return -1;
329 return 0;
332 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
336 if (path.IsEmpty())
338 *isVersion = true;
339 return 0;
342 CString subpath = path;
343 subpath.Replace(_T('\\'), _T('/'));
344 if(isDir)
345 subpath += _T('/');
347 subpath.MakeLower();
349 CheckAndUpdate(gitdir, isLoadUpdateIndex);
351 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
353 if(pIndex.get())
355 if(isDir)
356 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), subpath.GetLength()) >= 0);
357 else
358 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), -1) >= 0);
359 subpath.ReleaseBuffer();
362 }catch(...)
364 return -1;
366 return 0;
369 int CGitHeadFileList::GetPackRef(const CString &gitdir)
371 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
373 __int64 mtime;
374 if (g_Git.GetFileModifyTime(PackRef, &mtime))
376 CAutoWriteLock lock(&this->m_SharedMutex);
377 //packed refs is not existed
378 this->m_PackRefFile.Empty();
379 this->m_PackRefMap.clear();
380 return 0;
382 else if(mtime == m_LastModifyTimePackRef)
384 return 0;
386 else
388 CAutoWriteLock lock(&this->m_SharedMutex);
389 this->m_PackRefFile = PackRef;
390 this->m_LastModifyTimePackRef = mtime;
393 int ret = 0;
395 CAutoWriteLock lock(&this->m_SharedMutex);
396 this->m_PackRefMap.clear();
398 CAutoFile hfile = CreateFile(PackRef,
399 GENERIC_READ,
400 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
401 NULL,
402 OPEN_EXISTING,
403 FILE_ATTRIBUTE_NORMAL,
404 NULL);
407 if (!hfile)
409 ret = -1;
410 break;
413 DWORD filesize = GetFileSize(hfile, NULL);
414 DWORD size =0;
415 char *buff;
416 buff = new char[filesize];
418 ReadFile(hfile, buff, filesize, &size, NULL);
420 if (size != filesize)
422 ret = -1;
423 break;
426 CString hash;
427 CString ref;
429 for(DWORD i=0;i<filesize;)
431 hash.Empty();
432 ref.Empty();
433 if (buff[i] == '#' || buff[i] == '^')
435 while (buff[i] != '\n')
437 i++;
438 if (i == filesize)
439 break;
441 i++;
444 if (i >= filesize)
445 break;
447 while (buff[i] != ' ')
449 hash.AppendChar(buff[i]);
450 i++;
451 if (i == filesize)
452 break;
455 i++;
456 if (i >= filesize)
457 break;
459 while (buff[i] != '\n')
461 ref.AppendChar(buff[i]);
462 i++;
463 if (i == filesize)
464 break;
467 if (!ref.IsEmpty() )
469 this->m_PackRefMap[ref] = hash;
472 while (buff[i] == '\n')
474 i++;
475 if (i == filesize)
476 break;
480 delete buff;
482 } while(0);
484 return ret;
487 int CGitHeadFileList::ReadHeadHash(CString gitdir)
489 int ret = 0;
490 CAutoWriteLock lock(&this->m_SharedMutex);
491 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
493 m_HeadFile = m_Gitdir + _T("HEAD");
495 if( g_Git.GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
496 return -1;
502 CAutoFile hfile = CreateFile(m_HeadFile,
503 GENERIC_READ,
504 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
505 NULL,
506 OPEN_EXISTING,
507 FILE_ATTRIBUTE_NORMAL,
508 NULL);
510 if (!hfile)
512 ret = -1;
513 break;
516 DWORD size = 0,filesize = 0;
517 unsigned char buffer[40] ;
518 ReadFile(hfile, buffer, 4, &size, NULL);
519 if (size != 4)
521 ret = -1;
522 break;
524 buffer[4]=0;
525 if (strcmp((const char*)buffer,"ref:") == 0)
527 filesize = GetFileSize(hfile, NULL);
529 unsigned char *p = (unsigned char*)malloc(filesize -4);
531 ReadFile(hfile, p, filesize - 4, &size, NULL);
533 m_HeadRefFile.Empty();
534 g_Git.StringAppend(&this->m_HeadRefFile, p, CP_UTF8, filesize - 4);
535 CString ref = this->m_HeadRefFile;
536 ref = ref.Trim();
537 int start = 0;
538 ref = ref.Tokenize(_T("\n"), start);
539 free(p);
540 m_HeadRefFile = m_Gitdir + m_HeadRefFile.Trim();
541 m_HeadRefFile.Replace(_T('/'),_T('\\'));
543 __int64 time;
544 if (g_Git.GetFileModifyTime(m_HeadRefFile, &time, NULL))
546 m_HeadRefFile.Empty();
547 if (GetPackRef(gitdir))
549 ret = -1;
550 break;
552 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
554 ret = -1;
555 break;
557 this ->m_Head = m_PackRefMap[ref];
558 ret = 0;
559 break;
562 CAutoFile href = CreateFile(m_HeadRefFile,
563 GENERIC_READ,
564 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
565 NULL,
566 OPEN_EXISTING,
567 FILE_ATTRIBUTE_NORMAL,
568 NULL);
570 if (!href)
572 m_HeadRefFile.Empty();
574 if (GetPackRef(gitdir))
576 ret = -1;
577 break;
580 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
582 ret = -1;
583 break;
585 this ->m_Head = m_PackRefMap[ref];
586 ret = 0;
587 break;
589 ReadFile(href, buffer, 40, &size, NULL);
590 if (size != 40)
592 ret = -1;
593 break;
595 this->m_Head.ConvertFromStrA((char*)buffer);
597 this->m_LastModifyTimeRef = time;
600 else
602 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
603 if(size != 36)
605 ret = -1;
606 break;
608 m_HeadRefFile.Empty();
610 this->m_Head.ConvertFromStrA((char*)buffer);
612 } while(0);
614 catch(...)
616 ret = -1;
619 return ret;
622 bool CGitHeadFileList::CheckHeadUpdate()
624 CAutoReadLock lock(&m_SharedMutex);
625 if (this->m_HeadFile.IsEmpty())
626 return true;
628 __int64 mtime=0;
630 if (g_Git.GetFileModifyTime(m_HeadFile, &mtime))
631 return true;
633 if (mtime != this->m_LastModifyTimeHead)
634 return true;
636 if (!this->m_HeadRefFile.IsEmpty())
638 if (g_Git.GetFileModifyTime(m_HeadRefFile, &mtime))
639 return true;
641 if (mtime != this->m_LastModifyTimeRef)
642 return true;
645 if(!this->m_PackRefFile.IsEmpty())
647 if (g_Git.GetFileModifyTime(m_PackRefFile, &mtime))
648 return true;
650 if (mtime != this->m_LastModifyTimePackRef)
651 return true;
654 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
655 // So we need to retry again and again until the ref exists - otherwise we will never notice
656 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
657 return true;
659 return false;
661 #if 0
662 int CGitHeadFileList::ReadTree()
664 int ret;
665 if (this->m_Head.IsEmpty())
666 return -1;
670 CAutoLocker lock(g_Git.m_critGitDllSec);
671 CAutoWriteLock lock1(&this->m_SharedMutex);
673 if (m_Gitdir != g_Git.m_CurrentDir)
675 g_Git.SetCurrentDir(m_Gitdir);
676 SetCurrentDirectory(g_Git.m_CurrentDir);
677 git_init();
680 this->m_Map.clear();
681 this->clear();
683 ret = git_read_tree(this->m_Head.m_hash, CGitHeadFileList::CallBack, this);
684 if (!ret)
685 m_TreeHash = m_Head;
687 } catch(...)
689 return -1;
691 return ret;
693 #endif;
695 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
696 const char *pathname, unsigned mode, int /*stage*/, void *context)
698 #define S_IFGITLINK 0160000
700 CGitHeadFileList *p = (CGitHeadFileList*)context;
701 if( mode&S_IFDIR )
703 if( (mode&S_IFMT) != S_IFGITLINK)
704 return READ_TREE_RECURSIVE;
707 unsigned int cur = p->size();
708 p->resize(p->size() + 1);
709 p->at(cur).m_Hash = (char*)sha1;
710 p->at(cur).m_FileName.Empty();
712 if(base)
713 g_Git.StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
715 g_Git.StringAppend(&p->at(cur).m_FileName,(BYTE*)pathname, CP_UTF8);
717 p->at(cur).m_FileName.MakeLower();
719 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
721 //p->m_Map[p->at(cur).m_FileName] = cur;
723 if( (mode&S_IFMT) == S_IFGITLINK)
724 return 0;
726 return READ_TREE_RECURSIVE;
729 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)
731 size_t count = git_tree_entrycount(tree);
732 for (int i = 0; i < count; i++)
734 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
735 if (entry == NULL)
736 continue;
737 int mode = git_tree_entry_attributes(entry);
738 if( CallBack(git_tree_entry_id(entry)->id,
739 base,
740 base.GetLength(),
741 git_tree_entry_name(entry),
742 mode,
744 data) == READ_TREE_RECURSIVE
747 if(mode&S_IFDIR)
749 git_object *object = NULL;
750 git_tree_entry_to_object(&object, &repo, entry);
751 if (object == NULL)
752 continue;
753 CStringA parent = base;
754 parent += git_tree_entry_name(entry);
755 parent += "/";
756 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
757 git_object_free(object);
763 return 0;
766 int CGitHeadFileList::ReadTree()
768 CAutoWriteLock lock(&m_SharedMutex);
769 CStringA gitdir = CUnicodeUtils::GetMulti(m_Gitdir, CP_UTF8);
770 git_repository *repository = NULL;
771 git_commit *commit = NULL;
772 git_tree * tree = NULL;
773 int ret = 0;
774 this->clear(); // hack to avoid duplicates in the head list, which are introduced in GitStatus::GetFileStatus when this method is called
777 ret = git_repository_open(&repository, gitdir.GetBuffer());
778 gitdir.ReleaseBuffer();
779 if(ret)
780 break;
781 ret = git_commit_lookup(&commit, repository, (const git_oid*)m_Head.m_hash);
782 if(ret)
783 break;
785 ret = git_commit_tree(&tree, commit);
786 if(ret)
787 break;
789 ret = ReadTreeRecursive(*repository, tree,"", CGitHeadFileList::CallBack,this);
790 if(ret)
791 break;
793 std::sort(this->begin(), this->end(), SortTree);
794 this->m_TreeHash = (char*)(git_commit_id(commit)->id);
796 } while(0);
798 if (tree)
799 git_tree_free(tree);
801 if (commit)
802 git_commit_free(commit);
804 if (repository)
805 git_repository_free(repository);
807 return ret;
810 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
812 CAutoWriteLock lock(&this->m_SharedMutex);
814 if (this->m_pExcludeList)
816 free(m_pExcludeList);
817 m_pExcludeList=NULL;
820 this->m_BaseDir.Empty();
821 if (!isGlobal)
823 CString base = file.Mid(projectroot.GetLength() + 1);
824 base.Replace(_T('\\'), _T('/'));
826 int start = base.ReverseFind(_T('/'));
827 if(start > 0)
829 base = base.Left(start);
830 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
835 if(g_Git.GetFileModifyTime(file, &m_LastModifyTime))
836 return -1;
838 if(git_create_exclude_list(&this->m_pExcludeList))
839 return -1;
842 CAutoFile hfile = CreateFile(file,
843 GENERIC_READ,
844 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
845 NULL,
846 OPEN_EXISTING,
847 FILE_ATTRIBUTE_NORMAL,
848 NULL);
851 if (!hfile)
852 return -1 ;
854 DWORD size=0,filesize=0;
856 filesize=GetFileSize(hfile, NULL);
858 if(filesize == INVALID_FILE_SIZE)
859 return -1;
861 BYTE *buffer = new BYTE[filesize + 1];
863 if(buffer == NULL)
864 return -1;
866 if(! ReadFile(hfile, buffer,filesize,&size,NULL))
867 return GetLastError();
869 BYTE *p = buffer;
870 for (int i = 0; i < size; i++)
872 if (buffer[i] == '\n' || buffer[i] == '\r' || i == (size - 1))
874 if (buffer[i] == '\n' || buffer[i] == '\r')
875 buffer[i] = 0;
876 if (i == size - 1)
877 buffer[size] = 0;
879 if(p[0] != '#' && p[0] != 0)
880 git_add_exclude((const char*)p,
881 this->m_BaseDir.GetBuffer(),
882 m_BaseDir.GetLength(),
883 this->m_pExcludeList);
885 p=buffer+i+1;
888 /* Can't free buffer, exluced list will use this buffer*/
889 //delete buffer;
890 //buffer = NULL;
892 return 0;
895 bool CGitIgnoreList::CheckFileChanged(const CString &path)
897 __int64 time = 0;
899 int ret = g_Git.GetFileModifyTime(path, &time);
901 this->m_SharedMutex.AcquireShared();
902 bool cacheExist = (m_Map.find(path) != m_Map.end());
903 this->m_SharedMutex.ReleaseShared();
905 if (!cacheExist && ret == 0)
907 CAutoWriteLock lock(&this->m_SharedMutex);
908 m_Map[path].m_LastModifyTime = 0;
909 m_Map[path].m_SharedMutex.Init();
911 // both cache and file is not exist
912 if ((ret != 0) && (!cacheExist))
913 return false;
915 // file exist but cache miss
916 if ((ret == 0) && (!cacheExist))
917 return true;
919 // file not exist but cache exist
920 if ((ret != 0) && (cacheExist))
922 return true;
924 // file exist and cache exist
927 CAutoReadLock lock(&this->m_SharedMutex);
928 if (m_Map[path].m_LastModifyTime == time)
929 return false;
931 return true;
934 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir,const CString &path)
936 CString temp;
937 temp = gitdir;
938 temp += _T("\\");
939 temp += path;
941 temp.Replace(_T('/'), _T('\\'));
943 while(!temp.IsEmpty())
945 CString tempOrig = temp;
946 temp += _T("\\.git");
948 if (CGit::GitPathFileExists(temp))
950 CString gitignore=temp;
951 gitignore += _T("ignore");
952 if (CheckFileChanged(gitignore))
953 return true;
955 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
956 CString wcglobalgitignore = adminDir + _T("info\\exclude");
957 if (CheckFileChanged(wcglobalgitignore))
958 return true;
960 if (CheckAndUpdateCoreExcludefile(adminDir))
961 return true;
963 return false;
965 else
967 temp += _T("ignore");
968 if (CheckFileChanged(temp))
969 return true;
972 int found=0;
973 int i;
974 for (i = temp.GetLength() - 1; i >= 0; i--)
976 if(temp[i] == _T('\\'))
977 found ++;
979 if(found == 2)
980 break;
983 temp = temp.Left(i);
985 return true;
988 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
990 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
992 CAutoWriteLock lock(&this->m_SharedMutex);
993 if (m_Map.find(gitignore) == m_Map.end())
994 m_Map[gitignore].m_SharedMutex.Init();
996 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
998 else
1000 CAutoWriteLock lock(&this->m_SharedMutex);
1001 if (m_Map.find(gitignore) != m_Map.end())
1002 m_Map[gitignore].m_SharedMutex.Release();
1004 m_Map.erase(gitignore);
1006 return 0;
1009 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir,const CString &path)
1011 CString temp;
1013 temp = gitdir;
1014 temp += _T("\\");
1015 temp += path;
1017 temp.Replace(_T('/'), _T('\\'));
1019 while (!temp.IsEmpty())
1021 CString tempOrig = temp;
1022 temp += _T("\\.git");
1024 if (CGit::GitPathFileExists(temp))
1026 CString gitignore = temp;
1027 gitignore += _T("ignore");
1028 if (CheckFileChanged(gitignore))
1030 FetchIgnoreFile(gitdir, gitignore, false);
1033 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1034 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1035 if (CheckFileChanged(wcglobalgitignore))
1037 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1040 if (CheckAndUpdateCoreExcludefile(adminDir))
1042 m_SharedMutex.AcquireShared();
1043 CString excludesFile = m_CoreExcludesfiles[adminDir];
1044 m_SharedMutex.ReleaseShared();
1045 if (!excludesFile.IsEmpty())
1046 FetchIgnoreFile(gitdir, excludesFile, true);
1049 return 0;
1051 else
1053 temp += _T("ignore");
1054 if (CheckFileChanged(temp))
1056 FetchIgnoreFile(gitdir, temp, false);
1060 int found = 0;
1061 int i;
1062 for (i = temp.GetLength() - 1; i >= 0; i--)
1064 if(temp[i] == _T('\\'))
1065 found ++;
1067 if(found == 2)
1068 break;
1071 temp = temp.Left(i);
1073 return 0;
1075 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force)
1077 // recheck every 30 seconds
1078 if (GetTickCount() - m_dMsysGitBinPathLastChecked > 30000 || force)
1080 m_dMsysGitBinPathLastChecked = GetTickCount();
1081 CString msysGitBinPath(CRegString(REG_MSYSGIT_PATH, _T(""), FALSE));
1082 if (msysGitBinPath != m_sMsysGitBinPath)
1084 m_sMsysGitBinPath = msysGitBinPath;
1085 return true;
1088 return false;
1090 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1092 bool hasChanged = false;
1094 CString projectConfig = adminDir + _T("config");
1095 CString globalConfig = GetWindowsHome() + _T("\\.gitconfig");
1097 CAutoWriteLock lock(&m_coreExcludefilesSharedMutex);
1098 hasChanged = CheckAndUpdateMsysGitBinpath();
1099 CString systemConfig = m_sMsysGitBinPath + _T("\\..\\etc\\gitconfig");
1101 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1102 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1103 if (!m_sMsysGitBinPath.IsEmpty())
1104 hasChanged = hasChanged || CheckFileChanged(systemConfig);
1106 m_SharedMutex.AcquireShared();
1107 CString excludesFile = m_CoreExcludesfiles[adminDir];
1108 m_SharedMutex.ReleaseShared();
1109 if (!excludesFile.IsEmpty())
1110 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1112 if (!hasChanged)
1113 return false;
1115 git_config * config;
1116 git_config_new(&config);
1117 CStringA projectConfigA = CUnicodeUtils::GetMulti(projectConfig, CP_UTF8);
1118 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 3);
1119 projectConfigA.ReleaseBuffer();
1120 CStringA globalConfigA = CUnicodeUtils::GetMulti(globalConfig, CP_UTF8);
1121 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 2);
1122 globalConfigA.ReleaseBuffer();
1123 if (!m_sMsysGitBinPath.IsEmpty())
1125 CStringA systemConfigA = CUnicodeUtils::GetMulti(systemConfig, CP_UTF8);
1126 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1);
1127 systemConfigA.ReleaseBuffer();
1129 const char * out = NULL;
1130 CStringA name(_T("core.excludesfile"));
1131 git_config_get_string(&out, config, name.GetBuffer());
1132 name.ReleaseBuffer();
1133 CStringA excludesFileA(out);
1134 excludesFile = CUnicodeUtils::GetUnicode(excludesFileA);
1135 if (excludesFile.Find(_T("~/")) == 0)
1136 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1137 git_config_free(config);
1139 CAutoWriteLock lockMap(&m_SharedMutex);
1140 g_Git.GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1141 g_Git.GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1142 if (m_Map[globalConfig].m_LastModifyTime == 0)
1144 m_Map[globalConfig].m_SharedMutex.Release();
1145 m_Map.erase(globalConfig);
1147 if (!m_sMsysGitBinPath.IsEmpty())
1148 g_Git.GetFileModifyTime(systemConfig, &m_Map[systemConfig].m_LastModifyTime);
1149 if (m_Map[systemConfig].m_LastModifyTime == 0 || m_sMsysGitBinPath.IsEmpty())
1151 m_Map[systemConfig].m_SharedMutex.Release();
1152 m_Map.erase(systemConfig);
1154 m_CoreExcludesfiles[adminDir] = excludesFile;
1156 return true;
1158 const CString CGitIgnoreList::GetWindowsHome()
1160 static CString sWindowsHome(g_Git.GetHomeDirectory());
1161 return sWindowsHome;
1163 bool CGitIgnoreList::IsIgnore(const CString &path,const CString &projectroot)
1165 CString str=path;
1167 str.Replace(_T('\\'),_T('/'));
1169 if (str.GetLength()>0)
1170 if (str[str.GetLength()-1] == _T('/'))
1171 str = str.Left(str.GetLength() - 1);
1173 int ret;
1174 ret = CheckIgnore(str, projectroot);
1175 while (ret < 0)
1177 int start = str.ReverseFind(_T('/'));
1178 if(start < 0)
1179 return (ret == 1);
1181 str = str.Left(start);
1182 ret = CheckIgnore(str, projectroot);
1185 return (ret == 1);
1187 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1189 if (m_Map.find(ignorefile) != m_Map.end())
1191 int ret = -1;
1192 if(m_Map[ignorefile].m_pExcludeList)
1193 ret = git_check_excluded_1(patha, patha.GetLength(), base, &type, m_Map[ignorefile].m_pExcludeList);
1194 if (ret == 0 || ret == 1)
1195 return ret;
1197 return -1;
1199 int CGitIgnoreList::CheckIgnore(const CString &path,const CString &projectroot)
1201 __int64 time = 0;
1202 bool dir = 0;
1203 CString temp = projectroot + _T("\\") + path;
1204 temp.Replace(_T('/'), _T('\\'));
1206 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1207 patha.Replace('\\', '/');
1209 if(g_Git.GetFileModifyTime(temp, &time, &dir))
1210 return -1;
1212 int type = 0;
1213 if (dir)
1215 type = DT_DIR;
1217 // strip directory name
1218 // we do not need to check for a .ignore file inside a directory we might ignore
1219 int i = temp.ReverseFind(_T('\\'));
1220 if (i >= 0)
1221 temp = temp.Left(i);
1223 else
1224 type = DT_REG;
1226 char * base = NULL;
1227 int pos = patha.ReverseFind('/');
1228 base = pos >= 0 ? patha.GetBuffer() + pos + 1 : patha.GetBuffer();
1230 int ret = -1;
1232 CAutoReadLock lock(&this->m_SharedMutex);
1233 while (!temp.IsEmpty())
1235 CString tempOrig = temp;
1236 temp += _T("\\.git");
1238 if (CGit::GitPathFileExists(temp))
1240 CString gitignore = temp;
1241 gitignore += _T("ignore");
1242 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1243 break;
1245 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1246 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1247 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1248 break;
1250 m_SharedMutex.AcquireShared();
1251 CString excludesFile = m_CoreExcludesfiles[adminDir];
1252 m_SharedMutex.ReleaseShared();
1253 if (!excludesFile.IsEmpty())
1254 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1256 break;
1258 else
1260 temp += _T("ignore");
1261 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1262 break;
1265 int found = 0;
1266 int i;
1267 for (i = temp.GetLength() - 1; i >= 0; i--)
1269 if (temp[i] == _T('\\'))
1270 found++;
1272 if (found == 2)
1273 break;
1276 temp = temp.Left(i);
1279 patha.ReleaseBuffer();
1281 return ret;
1284 bool CGitHeadFileMap::CheckHeadUpdate(const CString &gitdir)
1286 SHARED_TREE_PTR ptr;
1287 ptr = this->SafeGet(gitdir);
1289 if( ptr.get())
1291 return ptr->CheckHeadUpdate();
1293 else
1295 SHARED_TREE_PTR ptr1(new CGitHeadFileList);
1296 ptr1->ReadHeadHash(gitdir);
1298 this->SafeSet(gitdir, ptr1);
1299 return true;
1301 return false;
1304 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1308 if (path.IsEmpty())
1310 *isVersion = true;
1311 return 0;
1314 CString subpath = path;
1315 subpath.Replace(_T('\\'), _T('/'));
1316 if(isDir)
1317 subpath += _T('/');
1319 subpath.MakeLower();
1321 CheckHeadUpdate(gitdir);
1323 SHARED_TREE_PTR treeptr;
1324 treeptr = SafeGet(gitdir);
1326 if (treeptr->m_Head != treeptr->m_TreeHash)
1328 treeptr->ReadHeadHash(gitdir);
1330 // Init Repository
1331 if (treeptr->m_HeadFile.IsEmpty())
1333 *isVersion = false;
1334 return 0;
1336 else if (treeptr->ReadTree())
1338 treeptr->m_LastModifyTimeHead = 0;
1339 *isVersion = false;
1340 return 1;
1342 SafeSet(gitdir, treeptr);
1345 if(isDir)
1346 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), subpath.GetLength()) >= 0);
1347 else
1348 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), -1) >= 0);
1349 subpath.ReleaseBuffer();
1351 catch(...)
1353 return -1;
1356 return 0;
1359 int CGitHeadFileMap::GetHeadHash(const CString &gitdir, CGitHash &hash)
1361 SHARED_TREE_PTR ptr;
1362 ptr = this->SafeGet(gitdir);
1364 if(ptr.get() == NULL)
1366 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1367 ptr1->ReadHeadHash(gitdir);
1369 hash = ptr1->m_Head;
1371 this->SafeSet(gitdir, ptr1);
1374 else
1376 if(ptr->CheckHeadUpdate())
1378 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1379 ptr1->ReadHeadHash(gitdir);
1381 hash = ptr1->m_Head;
1382 this->SafeSet(gitdir, ptr1);
1385 hash = ptr->m_Head;
1387 return 0;