Added CString wrapper for get_windows_home_directory()
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob07e5b9cdb5f07a8b5dd0b6fd78ee8288700ab39f
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 #define FILL_DATA() \
38 m_FileName.Empty();\
39 g_Git.StringAppend(&m_FileName, (BYTE*)entry->name, CP_UTF8, Big2lit(entry->flags)&CE_NAMEMASK);\
40 m_FileName.MakeLower(); \
41 this->m_Flags=Big2lit(entry->flags);\
42 this->m_ModifyTime=Big2lit(entry->mtime.sec);\
43 this->m_IndexHash=(char*)(entry->sha1);
45 int CGitIndex::FillData(ondisk_cache_entry * entry)
47 FILL_DATA();
48 return 0;
51 int CGitIndex::FillData(ondisk_cache_entry_extended * entry)
53 FILL_DATA();
54 this->m_Flags |= ((int)Big2lit(entry->flags2))<<16;
55 return 0;
58 int CGitIndex::Print()
60 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
61 (int)this->m_ModifyTime,
62 this->m_Flags,
63 this->m_IndexHash.ToString(),
64 this->m_FileName);
66 return 0;
69 CGitIndexList::CGitIndexList()
71 this->m_LastModifyTime = 0;
74 static bool SortIndex(CGitIndex &Item1, CGitIndex &Item2)
76 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
79 static bool SortTree(CGitTreeItem &Item1, CGitTreeItem &Item2)
81 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
84 int CGitIndexList::ReadIndex(CString IndexFile)
86 int ret=0;
87 BYTE *buffer = NULL, *p;
88 CGitIndex GitIndex;
90 #ifdef DEBUG
91 m_GitFile = IndexFile;
92 #endif
94 try
98 this->clear();
100 CAutoFile hfile = CreateFile(IndexFile,
101 GENERIC_READ,
102 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
103 NULL,
104 OPEN_EXISTING,
105 FILE_ATTRIBUTE_NORMAL,
106 NULL);
109 if (!hfile)
111 ret = -1 ;
112 break;
115 CAutoFile hmap = CreateFileMapping(hfile, NULL, PAGE_READONLY, 0, 0, NULL);
116 if (!hmap)
118 ret =-1;
119 break;
122 p = buffer = (BYTE*)MapViewOfFile(hmap, FILE_MAP_READ, 0, 0, 0);
123 if (buffer == NULL)
125 ret = -1;
126 break;
129 cache_header *header;
130 header = (cache_header *) buffer;
132 if (Big2lit(header->hdr_signature) != CACHE_SIGNATURE )
134 ret = -1;
135 break;
137 p += sizeof(cache_header);
139 int entries = Big2lit(header->hdr_entries);
140 resize(entries);
142 for(int i = 0; i < entries; i++)
144 ondisk_cache_entry *entry;
145 ondisk_cache_entry_extended *entryex;
146 entry = (ondisk_cache_entry*)p;
147 entryex = (ondisk_cache_entry_extended*)p;
148 int flags=Big2lit(entry->flags);
149 if (flags & CE_EXTENDED)
151 this->at(i).FillData(entryex);
152 p += ondisk_ce_size(entryex);
154 else
156 this->at(i).FillData(entry);
157 p += ondisk_ce_size(entry);
161 std::sort(this->begin(), this->end(), SortIndex);
162 g_Git.GetFileModifyTime(IndexFile, &this->m_LastModifyTime);
163 } while(0);
165 catch(...)
167 ret= -1;
170 if (buffer)
171 UnmapViewOfFile(buffer);
173 return ret;
176 int CGitIndexList::GetFileStatus(const CString &gitdir,const CString &pathorg,git_wc_status_kind *status,__int64 time,FIll_STATUS_CALLBACK callback,void *pData, CGitHash *pHash)
178 if(status)
180 CString path = pathorg;
181 path.MakeLower();
183 int start = SearchInSortVector(*this, ((CString&)path).GetBuffer(), -1);
184 ((CString&)path).ReleaseBuffer();
186 if (start < 0)
188 *status = git_wc_status_unversioned;
189 if (pHash)
190 pHash->Empty();
193 else
195 int index = start;
196 if (index <0)
197 return -1;
198 if (index >= size() )
199 return -1;
201 if (time == at(index).m_ModifyTime)
203 *status = git_wc_status_normal;
205 else
207 *status = git_wc_status_modified;
210 if (at(index).m_Flags & CE_STAGEMASK )
211 *status = git_wc_status_conflicted;
212 else if (at(index).m_Flags & CE_INTENT_TO_ADD)
213 *status = git_wc_status_added;
215 if(pHash)
216 *pHash = at(index).m_IndexHash;
221 if(callback && status)
222 callback(gitdir + _T("\\") + pathorg, *status, false, pData);
223 return 0;
226 int CGitIndexList::GetStatus(const CString &gitdir,const CString &pathParam, git_wc_status_kind *status,
227 BOOL IsFull, BOOL IsRecursive,
228 FIll_STATUS_CALLBACK callback,void *pData,
229 CGitHash *pHash)
231 int result;
232 git_wc_status_kind dirstatus = git_wc_status_none;
233 __int64 time;
234 bool isDir = false;
235 CString path = pathParam;
237 if (status)
239 if (path.IsEmpty())
240 result = g_Git.GetFileModifyTime(gitdir, &time, &isDir);
241 else
242 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + path, &time, &isDir);
244 if (result)
246 *status = git_wc_status_deleted;
247 if (callback)
248 callback(gitdir + _T("\\") + path, git_wc_status_deleted, false, pData);
250 return 0;
252 if (isDir)
254 if (!path.IsEmpty())
256 if (path.Right(1) != _T("\\"))
257 path += _T("\\");
259 int len = path.GetLength();
261 for (int i = 0; i < size(); i++)
263 if (at(i).m_FileName.GetLength() > len)
265 if (at(i).m_FileName.Left(len) == path)
267 if (!IsFull)
269 *status = git_wc_status_normal;
270 if (callback)
271 callback(gitdir + _T("\\") + path, *status, false, pData);
272 return 0;
275 else
277 result = g_Git.GetFileModifyTime(gitdir+_T("\\") + at(i).m_FileName, &time);
278 if (result)
279 continue;
281 *status = git_wc_status_none;
282 GetFileStatus(gitdir, at(i).m_FileName, status, time, callback, pData);
283 if (*status != git_wc_status_none)
285 if (dirstatus == git_wc_status_none)
287 dirstatus = git_wc_status_normal;
289 if (*status != git_wc_status_normal)
291 dirstatus = git_wc_status_modified;
298 } /* End For */
300 if (dirstatus != git_wc_status_none)
302 *status = dirstatus;
304 else
306 *status = git_wc_status_unversioned;
308 if(callback)
309 callback(gitdir + _T("\\") + path, *status, false, pData);
311 return 0;
314 else
316 GetFileStatus(gitdir, path, status, time, callback, pData, pHash);
319 return 0;
322 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
324 __int64 time;
325 int result;
327 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
329 /* Get data associated with "crt_stat.c": */
330 result = g_Git.GetFileModifyTime(IndexFile, &time);
332 if (result)
333 return result;
335 SHARED_INDEX_PTR pIndex;
336 pIndex = this->SafeGet(gitdir);
338 if (pIndex.get() == NULL)
340 if(isChanged)
341 *isChanged = true;
342 return 0;
345 if (pIndex->m_LastModifyTime == time)
347 if (isChanged)
348 *isChanged = false;
350 else
352 if (isChanged)
353 *isChanged = true;
355 return 0;
358 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
362 SHARED_INDEX_PTR pIndex(new CGitIndexList);
364 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
366 if(pIndex->ReadIndex(IndexFile))
367 return -1;
369 this->SafeSet(gitdir, pIndex);
371 }catch(...)
373 return -1;
375 return 0;
378 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
379 FIll_STATUS_CALLBACK callback,void *pData,
380 CGitHash *pHash,
381 bool isLoadUpdatedIndex)
385 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
387 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
388 if (pIndex.get() != NULL)
390 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash);
394 catch(...)
396 return -1;
398 return 0;
401 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
405 if (path.IsEmpty())
407 *isVersion = true;
408 return 0;
411 CString subpath = path;
412 subpath.Replace(_T('\\'), _T('/'));
413 if(isDir)
414 subpath += _T('/');
416 subpath.MakeLower();
418 CheckAndUpdate(gitdir, isLoadUpdateIndex);
420 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
422 if(pIndex.get())
424 if(isDir)
425 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), subpath.GetLength()) >= 0);
426 else
427 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), -1) >= 0);
428 subpath.ReleaseBuffer();
431 }catch(...)
433 return -1;
435 return 0;
438 int CGitHeadFileList::GetPackRef(const CString &gitdir)
440 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
442 __int64 mtime;
443 if (g_Git.GetFileModifyTime(PackRef, &mtime))
445 //packed refs is not existed
446 this->m_PackRefFile.Empty();
447 this->m_PackRefMap.clear();
448 return 0;
450 else if(mtime == m_LastModifyTimePackRef)
452 return 0;
454 else
456 this->m_PackRefFile = PackRef;
457 this->m_LastModifyTimePackRef = mtime;
460 int ret = 0;
462 this->m_PackRefMap.clear();
464 CAutoFile hfile = CreateFile(PackRef,
465 GENERIC_READ,
466 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
467 NULL,
468 OPEN_EXISTING,
469 FILE_ATTRIBUTE_NORMAL,
470 NULL);
473 if (!hfile)
475 ret = -1;
476 break;
479 DWORD filesize = GetFileSize(hfile, NULL);
480 DWORD size =0;
481 char *buff;
482 buff = new char[filesize];
484 ReadFile(hfile, buff, filesize, &size, NULL);
486 if (size != filesize)
488 ret = -1;
489 break;
492 CString hash;
493 CString ref;
495 for(DWORD i=0;i<filesize;)
497 hash.Empty();
498 ref.Empty();
499 if (buff[i] == '#' || buff[i] == '^')
501 while (buff[i] != '\n')
503 i++;
504 if (i == filesize)
505 break;
507 i++;
510 if (i >= filesize)
511 break;
513 while (buff[i] != ' ')
515 hash.AppendChar(buff[i]);
516 i++;
517 if (i == filesize)
518 break;
521 i++;
522 if (i >= filesize)
523 break;
525 while (buff[i] != '\n')
527 ref.AppendChar(buff[i]);
528 i++;
529 if (i == filesize)
530 break;
533 if (!ref.IsEmpty() )
535 this->m_PackRefMap[ref] = hash;
538 while (buff[i] == '\n')
540 i++;
541 if (i == filesize)
542 break;
546 delete buff;
548 } while(0);
550 return ret;
553 int CGitHeadFileList::ReadHeadHash(CString gitdir)
555 int ret = 0;
556 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
558 m_HeadFile = m_Gitdir + _T("HEAD");
560 if( g_Git.GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
561 return -1;
567 CAutoFile hfile = CreateFile(m_HeadFile,
568 GENERIC_READ,
569 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
570 NULL,
571 OPEN_EXISTING,
572 FILE_ATTRIBUTE_NORMAL,
573 NULL);
575 if (!hfile)
577 ret = -1;
578 break;
581 DWORD size = 0,filesize = 0;
582 unsigned char buffer[40] ;
583 ReadFile(hfile, buffer, 4, &size, NULL);
584 if (size != 4)
586 ret = -1;
587 break;
589 buffer[4]=0;
590 if (strcmp((const char*)buffer,"ref:") == 0)
592 filesize = GetFileSize(hfile, NULL);
594 unsigned char *p = (unsigned char*)malloc(filesize -4);
596 ReadFile(hfile, p, filesize - 4, &size, NULL);
598 m_HeadRefFile.Empty();
599 g_Git.StringAppend(&this->m_HeadRefFile, p, CP_UTF8, filesize - 4);
600 CString ref = this->m_HeadRefFile;
601 ref = ref.Trim();
602 int start = 0;
603 ref = ref.Tokenize(_T("\n"), start);
604 free(p);
605 m_HeadRefFile = m_Gitdir + m_HeadRefFile.Trim();
606 m_HeadRefFile.Replace(_T('/'),_T('\\'));
608 __int64 time;
609 if (g_Git.GetFileModifyTime(m_HeadRefFile, &time, NULL))
611 m_HeadRefFile.Empty();
612 if (GetPackRef(gitdir))
614 ret = -1;
615 break;
617 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
619 ret = -1;
620 break;
622 this ->m_Head = m_PackRefMap[ref];
623 ret = 0;
624 break;
627 CAutoFile href = CreateFile(m_HeadRefFile,
628 GENERIC_READ,
629 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
630 NULL,
631 OPEN_EXISTING,
632 FILE_ATTRIBUTE_NORMAL,
633 NULL);
635 if (!href)
637 m_HeadRefFile.Empty();
639 if (GetPackRef(gitdir))
641 ret = -1;
642 break;
645 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
647 ret = -1;
648 break;
650 this ->m_Head = m_PackRefMap[ref];
651 ret = 0;
652 break;
654 ReadFile(href, buffer, 40, &size, NULL);
655 if (size != 40)
657 ret = -1;
658 break;
660 this->m_Head.ConvertFromStrA((char*)buffer);
662 this->m_LastModifyTimeRef = time;
665 else
667 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
668 if(size != 36)
670 ret = -1;
671 break;
673 m_HeadRefFile.Empty();
675 this->m_Head.ConvertFromStrA((char*)buffer);
677 } while(0);
679 catch(...)
681 ret = -1;
684 return ret;
687 bool CGitHeadFileList::CheckHeadUpdate()
689 if (this->m_HeadFile.IsEmpty())
690 return true;
692 __int64 mtime=0;
694 if (g_Git.GetFileModifyTime(m_HeadFile, &mtime))
695 return true;
697 if (mtime != this->m_LastModifyTimeHead)
698 return true;
700 if (!this->m_HeadRefFile.IsEmpty())
702 if (g_Git.GetFileModifyTime(m_HeadRefFile, &mtime))
703 return true;
705 if (mtime != this->m_LastModifyTimeRef)
706 return true;
709 if(!this->m_PackRefFile.IsEmpty())
711 if (g_Git.GetFileModifyTime(m_PackRefFile, &mtime))
712 return true;
714 if (mtime != this->m_LastModifyTimePackRef)
715 return true;
718 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
719 // So we need to retry again and again until the ref exists - otherwise we will never notice
720 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
721 return true;
723 return false;
725 #if 0
726 int CGitHeadFileList::ReadTree()
728 int ret;
729 if (this->m_Head.IsEmpty())
730 return -1;
734 CAutoLocker lock(g_Git.m_critGitDllSec);
735 CAutoWriteLock lock1(&this->m_SharedMutex);
737 if (m_Gitdir != g_Git.m_CurrentDir)
739 g_Git.SetCurrentDir(m_Gitdir);
740 SetCurrentDirectory(g_Git.m_CurrentDir);
741 git_init();
744 this->m_Map.clear();
745 this->clear();
747 ret = git_read_tree(this->m_Head.m_hash, CGitHeadFileList::CallBack, this);
748 if (!ret)
749 m_TreeHash = m_Head;
751 } catch(...)
753 return -1;
755 return ret;
757 #endif;
759 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
760 const char *pathname, unsigned mode, int /*stage*/, void *context)
762 #define S_IFGITLINK 0160000
764 CGitHeadFileList *p = (CGitHeadFileList*)context;
765 if( mode&S_IFDIR )
767 if( (mode&S_IFMT) != S_IFGITLINK)
768 return READ_TREE_RECURSIVE;
771 unsigned int cur = p->size();
772 p->resize(p->size() + 1);
773 p->at(cur).m_Hash = (char*)sha1;
774 p->at(cur).m_FileName.Empty();
776 if(base)
777 g_Git.StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
779 g_Git.StringAppend(&p->at(cur).m_FileName,(BYTE*)pathname, CP_UTF8);
781 p->at(cur).m_FileName.MakeLower();
783 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
785 //p->m_Map[p->at(cur).m_FileName] = cur;
787 if( (mode&S_IFMT) == S_IFGITLINK)
788 return 0;
790 return READ_TREE_RECURSIVE;
793 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)
795 size_t count = git_tree_entrycount(tree);
796 for (int i = 0; i < count; i++)
798 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
799 if (entry == NULL)
800 continue;
801 int mode = git_tree_entry_attributes(entry);
802 if( CallBack(git_tree_entry_id(entry)->id,
803 base,
804 base.GetLength(),
805 git_tree_entry_name(entry),
806 mode,
808 data) == READ_TREE_RECURSIVE
811 if(mode&S_IFDIR)
813 git_object *object = NULL;
814 git_tree_entry_to_object(&object, &repo, entry);
815 if (object == NULL)
816 continue;
817 CStringA parent = base;
818 parent += git_tree_entry_name(entry);
819 parent += "/";
820 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
821 git_object_free(object);
827 return 0;
830 int CGitHeadFileList::ReadTree()
832 CStringA gitdir = CUnicodeUtils::GetMulti(m_Gitdir, CP_UTF8);
833 git_repository *repository = NULL;
834 git_commit *commit = NULL;
835 git_tree * tree = NULL;
836 int ret = 0;
837 this->clear(); // hack to avoid duplicates in the head list, which are introduced in GitStatus::GetFileStatus when this method is called
840 ret = git_repository_open(&repository, gitdir.GetBuffer());
841 gitdir.ReleaseBuffer();
842 if(ret)
843 break;
844 ret = git_commit_lookup(&commit, repository, (const git_oid*)m_Head.m_hash);
845 if(ret)
846 break;
848 ret = git_commit_tree(&tree, commit);
849 if(ret)
850 break;
852 ret = ReadTreeRecursive(*repository, tree,"", CGitHeadFileList::CallBack,this);
853 if(ret)
854 break;
856 std::sort(this->begin(), this->end(), SortTree);
857 this->m_TreeHash = (char*)(git_commit_id(commit)->id);
859 } while(0);
861 if (tree)
862 git_tree_free(tree);
864 if (commit)
865 git_commit_free(commit);
867 if (repository)
868 git_repository_free(repository);
870 return ret;
873 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
875 CAutoWriteLock lock(&this->m_SharedMutex);
877 if (this->m_pExcludeList)
879 free(m_pExcludeList);
880 m_pExcludeList=NULL;
883 this->m_BaseDir.Empty();
884 if (!isGlobal)
886 CString base = file.Mid(projectroot.GetLength() + 1);
887 base.Replace(_T('\\'), _T('/'));
889 int start = base.ReverseFind(_T('/'));
890 if(start > 0)
892 base = base.Left(start);
893 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
898 if(g_Git.GetFileModifyTime(file, &m_LastModifyTime))
899 return -1;
901 if(git_create_exclude_list(&this->m_pExcludeList))
902 return -1;
905 CAutoFile hfile = CreateFile(file,
906 GENERIC_READ,
907 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
908 NULL,
909 OPEN_EXISTING,
910 FILE_ATTRIBUTE_NORMAL,
911 NULL);
914 if (!hfile)
915 return -1 ;
917 DWORD size=0,filesize=0;
919 filesize=GetFileSize(hfile, NULL);
921 if(filesize == INVALID_FILE_SIZE)
922 return -1;
924 BYTE *buffer = new BYTE[filesize + 1];
926 if(buffer == NULL)
927 return -1;
929 if(! ReadFile(hfile, buffer,filesize,&size,NULL))
930 return GetLastError();
932 BYTE *p = buffer;
933 for (int i = 0; i < size; i++)
935 if (buffer[i] == '\n' || buffer[i] == '\r' || i == (size - 1))
937 if (buffer[i] == '\n' || buffer[i] == '\r')
938 buffer[i] = 0;
939 if (i == size - 1)
940 buffer[size] = 0;
942 if(p[0] != '#' && p[0] != 0)
943 git_add_exclude((const char*)p,
944 this->m_BaseDir.GetBuffer(),
945 m_BaseDir.GetLength(),
946 this->m_pExcludeList);
948 p=buffer+i+1;
951 /* Can't free buffer, exluced list will use this buffer*/
952 //delete buffer;
953 //buffer = NULL;
955 return 0;
958 bool CGitIgnoreList::CheckFileChanged(const CString &path)
960 __int64 time = 0;
962 int ret = g_Git.GetFileModifyTime(path, &time);
964 this->m_SharedMutex.AcquireShared();
965 bool cacheExist = (m_Map.find(path) != m_Map.end());
966 this->m_SharedMutex.ReleaseShared();
968 if (!cacheExist && ret == 0)
970 CAutoWriteLock lock(&this->m_SharedMutex);
971 m_Map[path].m_LastModifyTime = 0;
972 m_Map[path].m_SharedMutex.Init();
974 // both cache and file is not exist
975 if ((ret != 0) && (!cacheExist))
976 return false;
978 // file exist but cache miss
979 if ((ret == 0) && (!cacheExist))
980 return true;
982 // file not exist but cache exist
983 if ((ret != 0) && (cacheExist))
985 return true;
987 // file exist and cache exist
990 CAutoReadLock lock(&this->m_SharedMutex);
991 if (m_Map[path].m_LastModifyTime == time)
992 return false;
994 return true;
997 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir,const CString &path)
999 CString temp;
1000 temp = gitdir;
1001 temp += _T("\\");
1002 temp += path;
1004 temp.Replace(_T('/'), _T('\\'));
1006 while(!temp.IsEmpty())
1008 CString tempOrig = temp;
1009 temp += _T("\\.git");
1011 if (CGit::GitPathFileExists(temp))
1013 CString gitignore=temp;
1014 gitignore += _T("ignore");
1015 if (CheckFileChanged(gitignore))
1016 return true;
1018 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1019 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1020 if (CheckFileChanged(wcglobalgitignore))
1021 return true;
1023 if (CheckAndUpdateCoreExcludefile(adminDir))
1024 return true;
1026 return false;
1028 else
1030 temp += _T("ignore");
1031 if (CheckFileChanged(temp))
1032 return true;
1035 int found=0;
1036 int i;
1037 for (i = temp.GetLength() - 1; i >= 0; i--)
1039 if(temp[i] == _T('\\'))
1040 found ++;
1042 if(found == 2)
1043 break;
1046 temp = temp.Left(i);
1048 return true;
1051 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
1053 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
1055 CAutoWriteLock lock(&this->m_SharedMutex);
1056 if (m_Map.find(gitignore) == m_Map.end())
1057 m_Map[gitignore].m_SharedMutex.Init();
1059 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
1061 else
1063 CAutoWriteLock lock(&this->m_SharedMutex);
1064 if (m_Map.find(gitignore) != m_Map.end())
1065 m_Map[gitignore].m_SharedMutex.Release();
1067 m_Map.erase(gitignore);
1069 return 0;
1072 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir,const CString &path)
1074 CString temp;
1076 temp = gitdir;
1077 temp += _T("\\");
1078 temp += path;
1080 temp.Replace(_T('/'), _T('\\'));
1082 while (!temp.IsEmpty())
1084 CString tempOrig = temp;
1085 temp += _T("\\.git");
1087 if (CGit::GitPathFileExists(temp))
1089 CString gitignore = temp;
1090 gitignore += _T("ignore");
1091 if (CheckFileChanged(gitignore))
1093 FetchIgnoreFile(gitdir, gitignore, false);
1096 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1097 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1098 if (CheckFileChanged(wcglobalgitignore))
1100 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1103 if (CheckAndUpdateCoreExcludefile(adminDir))
1105 m_SharedMutex.AcquireShared();
1106 CString excludesFile = m_CoreExcludesfiles[adminDir];
1107 m_SharedMutex.ReleaseShared();
1108 if (!excludesFile.IsEmpty())
1109 FetchIgnoreFile(gitdir, excludesFile, true);
1112 return 0;
1114 else
1116 temp += _T("ignore");
1117 if (CheckFileChanged(temp))
1119 FetchIgnoreFile(gitdir, temp, false);
1123 int found = 0;
1124 int i;
1125 for (i = temp.GetLength() - 1; i >= 0; i--)
1127 if(temp[i] == _T('\\'))
1128 found ++;
1130 if(found == 2)
1131 break;
1134 temp = temp.Left(i);
1136 return 0;
1138 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force)
1140 // recheck every 30 seconds
1141 if (GetTickCount() - m_dMsysGitBinPathLastChecked > 30000 || force)
1143 m_dMsysGitBinPathLastChecked = GetTickCount();
1144 CString msysGitBinPath(CRegString(REG_MSYSGIT_PATH, _T(""), FALSE));
1145 if (msysGitBinPath != m_sMsysGitBinPath)
1147 m_sMsysGitBinPath = msysGitBinPath;
1148 return true;
1151 return false;
1153 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1155 bool hasChanged = false;
1157 CString projectConfig = adminDir + _T("config");
1158 CString globalConfig = GetWindowsHome() + _T("\\.gitconfig");
1160 CAutoWriteLock lock(&m_coreExcludefilesSharedMutex);
1161 hasChanged = CheckAndUpdateMsysGitBinpath();
1162 CString systemConfig = m_sMsysGitBinPath + _T("\\..\\etc\\gitconfig");
1164 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1165 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1166 if (!m_sMsysGitBinPath.IsEmpty())
1167 hasChanged = hasChanged || CheckFileChanged(systemConfig);
1169 m_SharedMutex.AcquireShared();
1170 CString excludesFile = m_CoreExcludesfiles[adminDir];
1171 m_SharedMutex.ReleaseShared();
1172 if (!excludesFile.IsEmpty())
1173 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1175 if (!hasChanged)
1176 return false;
1178 git_config * config;
1179 git_config_new(&config);
1180 CStringA projectConfigA = CUnicodeUtils::GetMulti(projectConfig, CP_UTF8);
1181 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 3);
1182 projectConfigA.ReleaseBuffer();
1183 CStringA globalConfigA = CUnicodeUtils::GetMulti(globalConfig, CP_UTF8);
1184 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 2);
1185 globalConfigA.ReleaseBuffer();
1186 if (!m_sMsysGitBinPath.IsEmpty())
1188 CStringA systemConfigA = CUnicodeUtils::GetMulti(systemConfig, CP_UTF8);
1189 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1);
1190 systemConfigA.ReleaseBuffer();
1192 const char * out = NULL;
1193 CStringA name(_T("core.excludesfile"));
1194 git_config_get_string(&out, config, name.GetBuffer());
1195 name.ReleaseBuffer();
1196 CStringA excludesFileA(out);
1197 excludesFile = CUnicodeUtils::GetUnicode(excludesFileA);
1198 if (excludesFile.Find(_T("~/")) == 0)
1199 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1200 git_config_free(config);
1202 CAutoWriteLock lockMap(&m_SharedMutex);
1203 g_Git.GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1204 g_Git.GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1205 if (m_Map[globalConfig].m_LastModifyTime == 0)
1207 m_Map[globalConfig].m_SharedMutex.Release();
1208 m_Map.erase(globalConfig);
1210 if (!m_sMsysGitBinPath.IsEmpty())
1211 g_Git.GetFileModifyTime(systemConfig, &m_Map[systemConfig].m_LastModifyTime);
1212 if (m_Map[systemConfig].m_LastModifyTime == 0 || m_sMsysGitBinPath.IsEmpty())
1214 m_Map[systemConfig].m_SharedMutex.Release();
1215 m_Map.erase(systemConfig);
1217 m_CoreExcludesfiles[adminDir] = excludesFile;
1219 return true;
1221 const CString CGitIgnoreList::GetWindowsHome()
1223 static CString sWindowsHome(g_Git.GetHomeDirectory());
1224 return sWindowsHome;
1226 bool CGitIgnoreList::IsIgnore(const CString &path,const CString &projectroot)
1228 CString str=path;
1230 str.Replace(_T('\\'),_T('/'));
1232 if (str.GetLength()>0)
1233 if (str[str.GetLength()-1] == _T('/'))
1234 str = str.Left(str.GetLength() - 1);
1236 int ret;
1237 ret = CheckIgnore(str, projectroot);
1238 while (ret < 0)
1240 int start = str.ReverseFind(_T('/'));
1241 if(start < 0)
1242 return (ret == 1);
1244 str = str.Left(start);
1245 ret = CheckIgnore(str, projectroot);
1248 return (ret == 1);
1250 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1252 if (m_Map.find(ignorefile) != m_Map.end())
1254 int ret = -1;
1255 if(m_Map[ignorefile].m_pExcludeList)
1256 ret = git_check_excluded_1(patha, patha.GetLength(), base, &type, m_Map[ignorefile].m_pExcludeList);
1257 if (ret == 0 || ret == 1)
1258 return ret;
1260 return -1;
1262 int CGitIgnoreList::CheckIgnore(const CString &path,const CString &projectroot)
1264 __int64 time = 0;
1265 bool dir = 0;
1266 CString temp = projectroot + _T("\\") + path;
1267 temp.Replace(_T('/'), _T('\\'));
1269 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1270 patha.Replace('\\', '/');
1272 if(g_Git.GetFileModifyTime(temp, &time, &dir))
1273 return -1;
1275 int type = 0;
1276 if (dir)
1278 type = DT_DIR;
1280 // strip directory name
1281 // we do not need to check for a .ignore file inside a directory we might ignore
1282 int i = temp.ReverseFind(_T('\\'));
1283 if (i >= 0)
1284 temp = temp.Left(i);
1286 else
1287 type = DT_REG;
1289 char * base = NULL;
1290 int pos = patha.ReverseFind('/');
1291 base = pos >= 0 ? patha.GetBuffer() + pos + 1 : patha.GetBuffer();
1293 int ret = -1;
1295 CAutoReadLock lock(&this->m_SharedMutex);
1296 while (!temp.IsEmpty())
1298 CString tempOrig = temp;
1299 temp += _T("\\.git");
1301 if (CGit::GitPathFileExists(temp))
1303 CString gitignore = temp;
1304 gitignore += _T("ignore");
1305 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1306 break;
1308 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1309 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1310 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1311 break;
1313 m_SharedMutex.AcquireShared();
1314 CString excludesFile = m_CoreExcludesfiles[adminDir];
1315 m_SharedMutex.ReleaseShared();
1316 if (!excludesFile.IsEmpty())
1317 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1319 break;
1321 else
1323 temp += _T("ignore");
1324 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1325 break;
1328 int found = 0;
1329 int i;
1330 for (i = temp.GetLength() - 1; i >= 0; i--)
1332 if (temp[i] == _T('\\'))
1333 found++;
1335 if (found == 2)
1336 break;
1339 temp = temp.Left(i);
1342 patha.ReleaseBuffer();
1344 return ret;
1347 bool CGitHeadFileMap::CheckHeadUpdate(const CString &gitdir)
1349 SHARED_TREE_PTR ptr;
1350 ptr = this->SafeGet(gitdir);
1352 if( ptr.get())
1354 return ptr->CheckHeadUpdate();
1356 else
1358 SHARED_TREE_PTR ptr1(new CGitHeadFileList);
1359 ptr1->ReadHeadHash(gitdir);
1361 this->SafeSet(gitdir, ptr1);
1362 return true;
1364 return false;
1367 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1371 if (path.IsEmpty())
1373 *isVersion = true;
1374 return 0;
1377 CString subpath = path;
1378 subpath.Replace(_T('\\'), _T('/'));
1379 if(isDir)
1380 subpath += _T('/');
1382 subpath.MakeLower();
1384 CheckHeadUpdate(gitdir);
1386 SHARED_TREE_PTR treeptr;
1387 treeptr = SafeGet(gitdir);
1389 if (treeptr->m_Head != treeptr->m_TreeHash)
1391 treeptr->ReadHeadHash(gitdir);
1393 // Init Repository
1394 if (treeptr->m_HeadFile.IsEmpty())
1396 *isVersion = false;
1397 return 0;
1399 else if (treeptr->ReadTree())
1401 treeptr->m_LastModifyTimeHead = 0;
1402 *isVersion = false;
1403 return 1;
1405 SafeSet(gitdir, treeptr);
1408 if(isDir)
1409 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), subpath.GetLength()) >= 0);
1410 else
1411 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), -1) >= 0);
1412 subpath.ReleaseBuffer();
1414 catch(...)
1416 return -1;
1419 return 0;
1422 int CGitHeadFileMap::GetHeadHash(const CString &gitdir, CGitHash &hash)
1424 SHARED_TREE_PTR ptr;
1425 ptr = this->SafeGet(gitdir);
1427 if(ptr.get() == NULL)
1429 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1430 ptr1->ReadHeadHash(gitdir);
1432 hash = ptr1->m_Head;
1434 this->SafeSet(gitdir, ptr1);
1437 else
1439 if(ptr->CheckHeadUpdate())
1441 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1442 ptr1->ReadHeadHash(gitdir);
1444 hash = ptr1->m_Head;
1445 this->SafeSet(gitdir, ptr1);
1448 hash = ptr->m_Head;
1450 return 0;