Fixed issue #1294: TortoiseGit might crash on concurrent access on CGitHeadFileList
[TortoiseGit.git] / src / Git / GitIndex.cpp
bloba8b4e4a4367ae452168b86c78e1a5b13a8b341bd
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 (time == at(index).m_ModifyTime)
132 *status = git_wc_status_normal;
134 else
136 *status = git_wc_status_modified;
139 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
140 *status = git_wc_status_conflicted;
141 else if (at(index).m_Flags & GIT_IDXENTRY_INTENT_TO_ADD)
142 *status = git_wc_status_added;
144 if(pHash)
145 *pHash = at(index).m_IndexHash;
150 if(callback && status)
151 callback(gitdir + _T("\\") + pathorg, *status, false, pData);
152 return 0;
155 int CGitIndexList::GetStatus(const CString &gitdir,const CString &pathParam, git_wc_status_kind *status,
156 BOOL IsFull, BOOL IsRecursive,
157 FIll_STATUS_CALLBACK callback,void *pData,
158 CGitHash *pHash)
160 int result;
161 git_wc_status_kind dirstatus = git_wc_status_none;
162 __int64 time;
163 bool isDir = false;
164 CString path = pathParam;
166 if (status)
168 if (path.IsEmpty())
169 result = g_Git.GetFileModifyTime(gitdir, &time, &isDir);
170 else
171 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + path, &time, &isDir);
173 if (result)
175 *status = git_wc_status_deleted;
176 if (callback)
177 callback(gitdir + _T("\\") + path, git_wc_status_deleted, false, pData);
179 return 0;
181 if (isDir)
183 if (!path.IsEmpty())
185 if (path.Right(1) != _T("\\"))
186 path += _T("\\");
188 int len = path.GetLength();
190 for (int i = 0; i < size(); i++)
192 if (at(i).m_FileName.GetLength() > len)
194 if (at(i).m_FileName.Left(len) == path)
196 if (!IsFull)
198 *status = git_wc_status_normal;
199 if (callback)
200 callback(gitdir + _T("\\") + path, *status, false, pData);
201 return 0;
204 else
206 result = g_Git.GetFileModifyTime(gitdir+_T("\\") + at(i).m_FileName, &time);
207 if (result)
208 continue;
210 *status = git_wc_status_none;
211 GetFileStatus(gitdir, at(i).m_FileName, status, time, callback, pData);
212 if (*status != git_wc_status_none)
214 if (dirstatus == git_wc_status_none)
216 dirstatus = git_wc_status_normal;
218 if (*status != git_wc_status_normal)
220 dirstatus = git_wc_status_modified;
227 } /* End For */
229 if (dirstatus != git_wc_status_none)
231 *status = dirstatus;
233 else
235 *status = git_wc_status_unversioned;
237 if(callback)
238 callback(gitdir + _T("\\") + path, *status, false, pData);
240 return 0;
243 else
245 GetFileStatus(gitdir, path, status, time, callback, pData, pHash);
248 return 0;
251 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
253 __int64 time;
254 int result;
256 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
258 /* Get data associated with "crt_stat.c": */
259 result = g_Git.GetFileModifyTime(IndexFile, &time);
261 if (result)
262 return result;
264 SHARED_INDEX_PTR pIndex;
265 pIndex = this->SafeGet(gitdir);
267 if (pIndex.get() == NULL)
269 if(isChanged)
270 *isChanged = true;
271 return 0;
274 if (pIndex->m_LastModifyTime == time)
276 if (isChanged)
277 *isChanged = false;
279 else
281 if (isChanged)
282 *isChanged = true;
284 return 0;
287 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
291 SHARED_INDEX_PTR pIndex(new CGitIndexList);
293 if(pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
294 return -1;
296 this->SafeSet(gitdir, pIndex);
298 }catch(...)
300 return -1;
302 return 0;
305 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
306 FIll_STATUS_CALLBACK callback,void *pData,
307 CGitHash *pHash,
308 bool isLoadUpdatedIndex)
312 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
314 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
315 if (pIndex.get() != NULL)
317 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash);
321 catch(...)
323 return -1;
325 return 0;
328 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
332 if (path.IsEmpty())
334 *isVersion = true;
335 return 0;
338 CString subpath = path;
339 subpath.Replace(_T('\\'), _T('/'));
340 if(isDir)
341 subpath += _T('/');
343 subpath.MakeLower();
345 CheckAndUpdate(gitdir, isLoadUpdateIndex);
347 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
349 if(pIndex.get())
351 if(isDir)
352 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), subpath.GetLength()) >= 0);
353 else
354 *isVersion = (SearchInSortVector(*pIndex, subpath.GetBuffer(), -1) >= 0);
355 subpath.ReleaseBuffer();
358 }catch(...)
360 return -1;
362 return 0;
365 int CGitHeadFileList::GetPackRef(const CString &gitdir)
367 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
369 __int64 mtime;
370 if (g_Git.GetFileModifyTime(PackRef, &mtime))
372 CAutoWriteLock lock(&this->m_SharedMutex);
373 //packed refs is not existed
374 this->m_PackRefFile.Empty();
375 this->m_PackRefMap.clear();
376 return 0;
378 else if(mtime == m_LastModifyTimePackRef)
380 return 0;
382 else
384 CAutoWriteLock lock(&this->m_SharedMutex);
385 this->m_PackRefFile = PackRef;
386 this->m_LastModifyTimePackRef = mtime;
389 int ret = 0;
391 CAutoWriteLock lock(&this->m_SharedMutex);
392 this->m_PackRefMap.clear();
394 CAutoFile hfile = CreateFile(PackRef,
395 GENERIC_READ,
396 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
397 NULL,
398 OPEN_EXISTING,
399 FILE_ATTRIBUTE_NORMAL,
400 NULL);
403 if (!hfile)
405 ret = -1;
406 break;
409 DWORD filesize = GetFileSize(hfile, NULL);
410 DWORD size =0;
411 char *buff;
412 buff = new char[filesize];
414 ReadFile(hfile, buff, filesize, &size, NULL);
416 if (size != filesize)
418 ret = -1;
419 break;
422 CString hash;
423 CString ref;
425 for(DWORD i=0;i<filesize;)
427 hash.Empty();
428 ref.Empty();
429 if (buff[i] == '#' || buff[i] == '^')
431 while (buff[i] != '\n')
433 i++;
434 if (i == filesize)
435 break;
437 i++;
440 if (i >= filesize)
441 break;
443 while (buff[i] != ' ')
445 hash.AppendChar(buff[i]);
446 i++;
447 if (i == filesize)
448 break;
451 i++;
452 if (i >= filesize)
453 break;
455 while (buff[i] != '\n')
457 ref.AppendChar(buff[i]);
458 i++;
459 if (i == filesize)
460 break;
463 if (!ref.IsEmpty() )
465 this->m_PackRefMap[ref] = hash;
468 while (buff[i] == '\n')
470 i++;
471 if (i == filesize)
472 break;
476 delete buff;
478 } while(0);
480 return ret;
483 int CGitHeadFileList::ReadHeadHash(CString gitdir)
485 int ret = 0;
486 CAutoWriteLock lock(&this->m_SharedMutex);
487 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
489 m_HeadFile = m_Gitdir + _T("HEAD");
491 if( g_Git.GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
492 return -1;
498 CAutoFile hfile = CreateFile(m_HeadFile,
499 GENERIC_READ,
500 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
501 NULL,
502 OPEN_EXISTING,
503 FILE_ATTRIBUTE_NORMAL,
504 NULL);
506 if (!hfile)
508 ret = -1;
509 break;
512 DWORD size = 0,filesize = 0;
513 unsigned char buffer[40] ;
514 ReadFile(hfile, buffer, 4, &size, NULL);
515 if (size != 4)
517 ret = -1;
518 break;
520 buffer[4]=0;
521 if (strcmp((const char*)buffer,"ref:") == 0)
523 filesize = GetFileSize(hfile, NULL);
525 unsigned char *p = (unsigned char*)malloc(filesize -4);
527 ReadFile(hfile, p, filesize - 4, &size, NULL);
529 m_HeadRefFile.Empty();
530 g_Git.StringAppend(&this->m_HeadRefFile, p, CP_UTF8, filesize - 4);
531 CString ref = this->m_HeadRefFile;
532 ref = ref.Trim();
533 int start = 0;
534 ref = ref.Tokenize(_T("\n"), start);
535 free(p);
536 m_HeadRefFile = m_Gitdir + m_HeadRefFile.Trim();
537 m_HeadRefFile.Replace(_T('/'),_T('\\'));
539 __int64 time;
540 if (g_Git.GetFileModifyTime(m_HeadRefFile, &time, NULL))
542 m_HeadRefFile.Empty();
543 if (GetPackRef(gitdir))
545 ret = -1;
546 break;
548 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
550 ret = -1;
551 break;
553 this ->m_Head = m_PackRefMap[ref];
554 ret = 0;
555 break;
558 CAutoFile href = CreateFile(m_HeadRefFile,
559 GENERIC_READ,
560 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
561 NULL,
562 OPEN_EXISTING,
563 FILE_ATTRIBUTE_NORMAL,
564 NULL);
566 if (!href)
568 m_HeadRefFile.Empty();
570 if (GetPackRef(gitdir))
572 ret = -1;
573 break;
576 if (this->m_PackRefMap.find(ref) == m_PackRefMap.end())
578 ret = -1;
579 break;
581 this ->m_Head = m_PackRefMap[ref];
582 ret = 0;
583 break;
585 ReadFile(href, buffer, 40, &size, NULL);
586 if (size != 40)
588 ret = -1;
589 break;
591 this->m_Head.ConvertFromStrA((char*)buffer);
593 this->m_LastModifyTimeRef = time;
596 else
598 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
599 if(size != 36)
601 ret = -1;
602 break;
604 m_HeadRefFile.Empty();
606 this->m_Head.ConvertFromStrA((char*)buffer);
608 } while(0);
610 catch(...)
612 ret = -1;
615 return ret;
618 bool CGitHeadFileList::CheckHeadUpdate()
620 CAutoReadLock lock(&m_SharedMutex);
621 if (this->m_HeadFile.IsEmpty())
622 return true;
624 __int64 mtime=0;
626 if (g_Git.GetFileModifyTime(m_HeadFile, &mtime))
627 return true;
629 if (mtime != this->m_LastModifyTimeHead)
630 return true;
632 if (!this->m_HeadRefFile.IsEmpty())
634 if (g_Git.GetFileModifyTime(m_HeadRefFile, &mtime))
635 return true;
637 if (mtime != this->m_LastModifyTimeRef)
638 return true;
641 if(!this->m_PackRefFile.IsEmpty())
643 if (g_Git.GetFileModifyTime(m_PackRefFile, &mtime))
644 return true;
646 if (mtime != this->m_LastModifyTimePackRef)
647 return true;
650 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
651 // So we need to retry again and again until the ref exists - otherwise we will never notice
652 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
653 return true;
655 return false;
657 #if 0
658 int CGitHeadFileList::ReadTree()
660 int ret;
661 if (this->m_Head.IsEmpty())
662 return -1;
666 CAutoLocker lock(g_Git.m_critGitDllSec);
667 CAutoWriteLock lock1(&this->m_SharedMutex);
669 if (m_Gitdir != g_Git.m_CurrentDir)
671 g_Git.SetCurrentDir(m_Gitdir);
672 SetCurrentDirectory(g_Git.m_CurrentDir);
673 git_init();
676 this->m_Map.clear();
677 this->clear();
679 ret = git_read_tree(this->m_Head.m_hash, CGitHeadFileList::CallBack, this);
680 if (!ret)
681 m_TreeHash = m_Head;
683 } catch(...)
685 return -1;
687 return ret;
689 #endif;
691 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
692 const char *pathname, unsigned mode, int /*stage*/, void *context)
694 #define S_IFGITLINK 0160000
696 CGitHeadFileList *p = (CGitHeadFileList*)context;
697 if( mode&S_IFDIR )
699 if( (mode&S_IFMT) != S_IFGITLINK)
700 return READ_TREE_RECURSIVE;
703 unsigned int cur = p->size();
704 p->resize(p->size() + 1);
705 p->at(cur).m_Hash = (char*)sha1;
706 p->at(cur).m_FileName.Empty();
708 if(base)
709 g_Git.StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
711 g_Git.StringAppend(&p->at(cur).m_FileName,(BYTE*)pathname, CP_UTF8);
713 p->at(cur).m_FileName.MakeLower();
715 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
717 //p->m_Map[p->at(cur).m_FileName] = cur;
719 if( (mode&S_IFMT) == S_IFGITLINK)
720 return 0;
722 return READ_TREE_RECURSIVE;
725 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)
727 size_t count = git_tree_entrycount(tree);
728 for (int i = 0; i < count; i++)
730 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
731 if (entry == NULL)
732 continue;
733 int mode = git_tree_entry_attributes(entry);
734 if( CallBack(git_tree_entry_id(entry)->id,
735 base,
736 base.GetLength(),
737 git_tree_entry_name(entry),
738 mode,
740 data) == READ_TREE_RECURSIVE
743 if(mode&S_IFDIR)
745 git_object *object = NULL;
746 git_tree_entry_to_object(&object, &repo, entry);
747 if (object == NULL)
748 continue;
749 CStringA parent = base;
750 parent += git_tree_entry_name(entry);
751 parent += "/";
752 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
753 git_object_free(object);
759 return 0;
762 int CGitHeadFileList::ReadTree()
764 CAutoWriteLock lock(&m_SharedMutex);
765 CStringA gitdir = CUnicodeUtils::GetMulti(m_Gitdir, CP_UTF8);
766 git_repository *repository = NULL;
767 git_commit *commit = NULL;
768 git_tree * tree = NULL;
769 int ret = 0;
770 this->clear(); // hack to avoid duplicates in the head list, which are introduced in GitStatus::GetFileStatus when this method is called
773 ret = git_repository_open(&repository, gitdir.GetBuffer());
774 gitdir.ReleaseBuffer();
775 if(ret)
776 break;
777 ret = git_commit_lookup(&commit, repository, (const git_oid*)m_Head.m_hash);
778 if(ret)
779 break;
781 ret = git_commit_tree(&tree, commit);
782 if(ret)
783 break;
785 ret = ReadTreeRecursive(*repository, tree,"", CGitHeadFileList::CallBack,this);
786 if(ret)
787 break;
789 std::sort(this->begin(), this->end(), SortTree);
790 this->m_TreeHash = (char*)(git_commit_id(commit)->id);
792 } while(0);
794 if (tree)
795 git_tree_free(tree);
797 if (commit)
798 git_commit_free(commit);
800 if (repository)
801 git_repository_free(repository);
803 return ret;
806 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
808 CAutoWriteLock lock(&this->m_SharedMutex);
810 if (this->m_pExcludeList)
812 free(m_pExcludeList);
813 m_pExcludeList=NULL;
816 this->m_BaseDir.Empty();
817 if (!isGlobal)
819 CString base = file.Mid(projectroot.GetLength() + 1);
820 base.Replace(_T('\\'), _T('/'));
822 int start = base.ReverseFind(_T('/'));
823 if(start > 0)
825 base = base.Left(start);
826 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
831 if(g_Git.GetFileModifyTime(file, &m_LastModifyTime))
832 return -1;
834 if(git_create_exclude_list(&this->m_pExcludeList))
835 return -1;
838 CAutoFile hfile = CreateFile(file,
839 GENERIC_READ,
840 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
841 NULL,
842 OPEN_EXISTING,
843 FILE_ATTRIBUTE_NORMAL,
844 NULL);
847 if (!hfile)
848 return -1 ;
850 DWORD size=0,filesize=0;
852 filesize=GetFileSize(hfile, NULL);
854 if(filesize == INVALID_FILE_SIZE)
855 return -1;
857 BYTE *buffer = new BYTE[filesize + 1];
859 if(buffer == NULL)
860 return -1;
862 if(! ReadFile(hfile, buffer,filesize,&size,NULL))
863 return GetLastError();
865 BYTE *p = buffer;
866 for (int i = 0; i < size; i++)
868 if (buffer[i] == '\n' || buffer[i] == '\r' || i == (size - 1))
870 if (buffer[i] == '\n' || buffer[i] == '\r')
871 buffer[i] = 0;
872 if (i == size - 1)
873 buffer[size] = 0;
875 if(p[0] != '#' && p[0] != 0)
876 git_add_exclude((const char*)p,
877 this->m_BaseDir.GetBuffer(),
878 m_BaseDir.GetLength(),
879 this->m_pExcludeList);
881 p=buffer+i+1;
884 /* Can't free buffer, exluced list will use this buffer*/
885 //delete buffer;
886 //buffer = NULL;
888 return 0;
891 bool CGitIgnoreList::CheckFileChanged(const CString &path)
893 __int64 time = 0;
895 int ret = g_Git.GetFileModifyTime(path, &time);
897 this->m_SharedMutex.AcquireShared();
898 bool cacheExist = (m_Map.find(path) != m_Map.end());
899 this->m_SharedMutex.ReleaseShared();
901 if (!cacheExist && ret == 0)
903 CAutoWriteLock lock(&this->m_SharedMutex);
904 m_Map[path].m_LastModifyTime = 0;
905 m_Map[path].m_SharedMutex.Init();
907 // both cache and file is not exist
908 if ((ret != 0) && (!cacheExist))
909 return false;
911 // file exist but cache miss
912 if ((ret == 0) && (!cacheExist))
913 return true;
915 // file not exist but cache exist
916 if ((ret != 0) && (cacheExist))
918 return true;
920 // file exist and cache exist
923 CAutoReadLock lock(&this->m_SharedMutex);
924 if (m_Map[path].m_LastModifyTime == time)
925 return false;
927 return true;
930 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir,const CString &path)
932 CString temp;
933 temp = gitdir;
934 temp += _T("\\");
935 temp += path;
937 temp.Replace(_T('/'), _T('\\'));
939 while(!temp.IsEmpty())
941 CString tempOrig = temp;
942 temp += _T("\\.git");
944 if (CGit::GitPathFileExists(temp))
946 CString gitignore=temp;
947 gitignore += _T("ignore");
948 if (CheckFileChanged(gitignore))
949 return true;
951 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
952 CString wcglobalgitignore = adminDir + _T("info\\exclude");
953 if (CheckFileChanged(wcglobalgitignore))
954 return true;
956 if (CheckAndUpdateCoreExcludefile(adminDir))
957 return true;
959 return false;
961 else
963 temp += _T("ignore");
964 if (CheckFileChanged(temp))
965 return true;
968 int found=0;
969 int i;
970 for (i = temp.GetLength() - 1; i >= 0; i--)
972 if(temp[i] == _T('\\'))
973 found ++;
975 if(found == 2)
976 break;
979 temp = temp.Left(i);
981 return true;
984 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
986 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
988 CAutoWriteLock lock(&this->m_SharedMutex);
989 if (m_Map.find(gitignore) == m_Map.end())
990 m_Map[gitignore].m_SharedMutex.Init();
992 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
994 else
996 CAutoWriteLock lock(&this->m_SharedMutex);
997 if (m_Map.find(gitignore) != m_Map.end())
998 m_Map[gitignore].m_SharedMutex.Release();
1000 m_Map.erase(gitignore);
1002 return 0;
1005 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir,const CString &path)
1007 CString temp;
1009 temp = gitdir;
1010 temp += _T("\\");
1011 temp += path;
1013 temp.Replace(_T('/'), _T('\\'));
1015 while (!temp.IsEmpty())
1017 CString tempOrig = temp;
1018 temp += _T("\\.git");
1020 if (CGit::GitPathFileExists(temp))
1022 CString gitignore = temp;
1023 gitignore += _T("ignore");
1024 if (CheckFileChanged(gitignore))
1026 FetchIgnoreFile(gitdir, gitignore, false);
1029 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1030 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1031 if (CheckFileChanged(wcglobalgitignore))
1033 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1036 if (CheckAndUpdateCoreExcludefile(adminDir))
1038 m_SharedMutex.AcquireShared();
1039 CString excludesFile = m_CoreExcludesfiles[adminDir];
1040 m_SharedMutex.ReleaseShared();
1041 if (!excludesFile.IsEmpty())
1042 FetchIgnoreFile(gitdir, excludesFile, true);
1045 return 0;
1047 else
1049 temp += _T("ignore");
1050 if (CheckFileChanged(temp))
1052 FetchIgnoreFile(gitdir, temp, false);
1056 int found = 0;
1057 int i;
1058 for (i = temp.GetLength() - 1; i >= 0; i--)
1060 if(temp[i] == _T('\\'))
1061 found ++;
1063 if(found == 2)
1064 break;
1067 temp = temp.Left(i);
1069 return 0;
1071 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force)
1073 // recheck every 30 seconds
1074 if (GetTickCount() - m_dMsysGitBinPathLastChecked > 30000 || force)
1076 m_dMsysGitBinPathLastChecked = GetTickCount();
1077 CString msysGitBinPath(CRegString(REG_MSYSGIT_PATH, _T(""), FALSE));
1078 if (msysGitBinPath != m_sMsysGitBinPath)
1080 m_sMsysGitBinPath = msysGitBinPath;
1081 return true;
1084 return false;
1086 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1088 bool hasChanged = false;
1090 CString projectConfig = adminDir + _T("config");
1091 CString globalConfig = GetWindowsHome() + _T("\\.gitconfig");
1093 CAutoWriteLock lock(&m_coreExcludefilesSharedMutex);
1094 hasChanged = CheckAndUpdateMsysGitBinpath();
1095 CString systemConfig = m_sMsysGitBinPath + _T("\\..\\etc\\gitconfig");
1097 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1098 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1099 if (!m_sMsysGitBinPath.IsEmpty())
1100 hasChanged = hasChanged || CheckFileChanged(systemConfig);
1102 m_SharedMutex.AcquireShared();
1103 CString excludesFile = m_CoreExcludesfiles[adminDir];
1104 m_SharedMutex.ReleaseShared();
1105 if (!excludesFile.IsEmpty())
1106 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1108 if (!hasChanged)
1109 return false;
1111 git_config * config;
1112 git_config_new(&config);
1113 CStringA projectConfigA = CUnicodeUtils::GetMulti(projectConfig, CP_UTF8);
1114 git_config_add_file_ondisk(config, projectConfigA.GetBuffer(), 3);
1115 projectConfigA.ReleaseBuffer();
1116 CStringA globalConfigA = CUnicodeUtils::GetMulti(globalConfig, CP_UTF8);
1117 git_config_add_file_ondisk(config, globalConfigA.GetBuffer(), 2);
1118 globalConfigA.ReleaseBuffer();
1119 if (!m_sMsysGitBinPath.IsEmpty())
1121 CStringA systemConfigA = CUnicodeUtils::GetMulti(systemConfig, CP_UTF8);
1122 git_config_add_file_ondisk(config, systemConfigA.GetBuffer(), 1);
1123 systemConfigA.ReleaseBuffer();
1125 const char * out = NULL;
1126 CStringA name(_T("core.excludesfile"));
1127 git_config_get_string(&out, config, name.GetBuffer());
1128 name.ReleaseBuffer();
1129 CStringA excludesFileA(out);
1130 excludesFile = CUnicodeUtils::GetUnicode(excludesFileA);
1131 if (excludesFile.Find(_T("~/")) == 0)
1132 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1133 git_config_free(config);
1135 CAutoWriteLock lockMap(&m_SharedMutex);
1136 g_Git.GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1137 g_Git.GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1138 if (m_Map[globalConfig].m_LastModifyTime == 0)
1140 m_Map[globalConfig].m_SharedMutex.Release();
1141 m_Map.erase(globalConfig);
1143 if (!m_sMsysGitBinPath.IsEmpty())
1144 g_Git.GetFileModifyTime(systemConfig, &m_Map[systemConfig].m_LastModifyTime);
1145 if (m_Map[systemConfig].m_LastModifyTime == 0 || m_sMsysGitBinPath.IsEmpty())
1147 m_Map[systemConfig].m_SharedMutex.Release();
1148 m_Map.erase(systemConfig);
1150 m_CoreExcludesfiles[adminDir] = excludesFile;
1152 return true;
1154 const CString CGitIgnoreList::GetWindowsHome()
1156 static CString sWindowsHome(g_Git.GetHomeDirectory());
1157 return sWindowsHome;
1159 bool CGitIgnoreList::IsIgnore(const CString &path,const CString &projectroot)
1161 CString str=path;
1163 str.Replace(_T('\\'),_T('/'));
1165 if (str.GetLength()>0)
1166 if (str[str.GetLength()-1] == _T('/'))
1167 str = str.Left(str.GetLength() - 1);
1169 int ret;
1170 ret = CheckIgnore(str, projectroot);
1171 while (ret < 0)
1173 int start = str.ReverseFind(_T('/'));
1174 if(start < 0)
1175 return (ret == 1);
1177 str = str.Left(start);
1178 ret = CheckIgnore(str, projectroot);
1181 return (ret == 1);
1183 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1185 if (m_Map.find(ignorefile) != m_Map.end())
1187 int ret = -1;
1188 if(m_Map[ignorefile].m_pExcludeList)
1189 ret = git_check_excluded_1(patha, patha.GetLength(), base, &type, m_Map[ignorefile].m_pExcludeList);
1190 if (ret == 0 || ret == 1)
1191 return ret;
1193 return -1;
1195 int CGitIgnoreList::CheckIgnore(const CString &path,const CString &projectroot)
1197 __int64 time = 0;
1198 bool dir = 0;
1199 CString temp = projectroot + _T("\\") + path;
1200 temp.Replace(_T('/'), _T('\\'));
1202 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1203 patha.Replace('\\', '/');
1205 if(g_Git.GetFileModifyTime(temp, &time, &dir))
1206 return -1;
1208 int type = 0;
1209 if (dir)
1211 type = DT_DIR;
1213 // strip directory name
1214 // we do not need to check for a .ignore file inside a directory we might ignore
1215 int i = temp.ReverseFind(_T('\\'));
1216 if (i >= 0)
1217 temp = temp.Left(i);
1219 else
1220 type = DT_REG;
1222 char * base = NULL;
1223 int pos = patha.ReverseFind('/');
1224 base = pos >= 0 ? patha.GetBuffer() + pos + 1 : patha.GetBuffer();
1226 int ret = -1;
1228 CAutoReadLock lock(&this->m_SharedMutex);
1229 while (!temp.IsEmpty())
1231 CString tempOrig = temp;
1232 temp += _T("\\.git");
1234 if (CGit::GitPathFileExists(temp))
1236 CString gitignore = temp;
1237 gitignore += _T("ignore");
1238 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1239 break;
1241 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1242 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1243 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1244 break;
1246 m_SharedMutex.AcquireShared();
1247 CString excludesFile = m_CoreExcludesfiles[adminDir];
1248 m_SharedMutex.ReleaseShared();
1249 if (!excludesFile.IsEmpty())
1250 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1252 break;
1254 else
1256 temp += _T("ignore");
1257 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1258 break;
1261 int found = 0;
1262 int i;
1263 for (i = temp.GetLength() - 1; i >= 0; i--)
1265 if (temp[i] == _T('\\'))
1266 found++;
1268 if (found == 2)
1269 break;
1272 temp = temp.Left(i);
1275 patha.ReleaseBuffer();
1277 return ret;
1280 bool CGitHeadFileMap::CheckHeadUpdate(const CString &gitdir)
1282 SHARED_TREE_PTR ptr;
1283 ptr = this->SafeGet(gitdir);
1285 if( ptr.get())
1287 return ptr->CheckHeadUpdate();
1289 else
1291 SHARED_TREE_PTR ptr1(new CGitHeadFileList);
1292 ptr1->ReadHeadHash(gitdir);
1294 this->SafeSet(gitdir, ptr1);
1295 return true;
1297 return false;
1300 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1304 if (path.IsEmpty())
1306 *isVersion = true;
1307 return 0;
1310 CString subpath = path;
1311 subpath.Replace(_T('\\'), _T('/'));
1312 if(isDir)
1313 subpath += _T('/');
1315 subpath.MakeLower();
1317 CheckHeadUpdate(gitdir);
1319 SHARED_TREE_PTR treeptr;
1320 treeptr = SafeGet(gitdir);
1322 if (treeptr->m_Head != treeptr->m_TreeHash)
1324 treeptr->ReadHeadHash(gitdir);
1326 // Init Repository
1327 if (treeptr->m_HeadFile.IsEmpty())
1329 *isVersion = false;
1330 return 0;
1332 else if (treeptr->ReadTree())
1334 treeptr->m_LastModifyTimeHead = 0;
1335 *isVersion = false;
1336 return 1;
1338 SafeSet(gitdir, treeptr);
1341 if(isDir)
1342 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), subpath.GetLength()) >= 0);
1343 else
1344 *isVersion = (SearchInSortVector(*treeptr, subpath.GetBuffer(), -1) >= 0);
1345 subpath.ReleaseBuffer();
1347 catch(...)
1349 return -1;
1352 return 0;
1355 int CGitHeadFileMap::GetHeadHash(const CString &gitdir, CGitHash &hash)
1357 SHARED_TREE_PTR ptr;
1358 ptr = this->SafeGet(gitdir);
1360 if(ptr.get() == NULL)
1362 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1363 ptr1->ReadHeadHash(gitdir);
1365 hash = ptr1->m_Head;
1367 this->SafeSet(gitdir, ptr1);
1370 else
1372 if(ptr->CheckHeadUpdate())
1374 SHARED_TREE_PTR ptr1(new CGitHeadFileList());
1375 ptr1->ReadHeadHash(gitdir);
1377 hash = ptr1->m_Head;
1378 this->SafeSet(gitdir, ptr1);
1381 hash = ptr->m_Head;
1383 return 0;