Add tests for CGitIgnoreItem
[TortoiseGit.git] / src / Git / GitIndex.cpp
blobfa1e7b15199fc7994c3d9f7aecb7596e4cfb8754
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - 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 "registry.h"
23 #include "UnicodeUtils.h"
24 #include "TGitPath.h"
25 #include "gitindex.h"
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include "SmartHandle.h"
29 #include "git2/sys/repository.h"
31 CGitAdminDirMap g_AdminDirMap;
33 int CGitIndex::Print()
35 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
36 (int)this->m_ModifyTime,
37 this->m_Flags,
38 (LPCTSTR)this->m_IndexHash.ToString(),
39 (LPCTSTR)this->m_FileName);
41 return 0;
44 CGitIndexList::CGitIndexList()
46 this->m_LastModifyTime = 0;
47 m_critRepoSec.Init();
48 m_bCheckContent = !!(CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContent"), TRUE) == TRUE);
49 m_iMaxCheckSize = (__int64)CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContentMaxSize"), 10 * 1024) * 1024; // stored in KiB
52 CGitIndexList::~CGitIndexList()
54 m_critRepoSec.Term();
57 static bool SortIndex(const CGitIndex &Item1, const CGitIndex &Item2)
59 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
62 static bool SortTree(const CGitTreeItem &Item1, const CGitTreeItem &Item2)
64 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
67 int CGitIndexList::ReadIndex(CString dgitdir)
69 this->clear();
71 m_critRepoSec.Lock();
72 if (repository.Open(dgitdir))
74 m_critRepoSec.Unlock();
75 return -1;
78 // add config files
79 CAutoConfig config(true);
81 CString projectConfig = dgitdir + _T("config");
82 CString globalConfig = g_Git.GetGitGlobalConfig();
83 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
84 CString systemConfig(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
86 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
87 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
88 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
89 if (!systemConfig.IsEmpty())
90 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(systemConfig), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
92 git_repository_set_config(repository, config);
94 CAutoIndex index;
95 // load index in order to enumerate files
96 if (git_repository_index(index.GetPointer(), repository))
98 repository.Free();
99 m_critRepoSec.Unlock();
100 return -1;
103 size_t ecount = git_index_entrycount(index);
104 resize(ecount);
105 for (size_t i = 0; i < ecount; ++i)
107 const git_index_entry *e = git_index_get_byindex(index, i);
109 this->at(i).m_FileName.Empty();
110 this->at(i).m_FileName = CUnicodeUtils::GetUnicode(e->path);
111 this->at(i).m_FileName.MakeLower();
112 this->at(i).m_ModifyTime = e->mtime.seconds;
113 this->at(i).m_Flags = e->flags | e->flags_extended;
114 this->at(i).m_IndexHash = e->id.id;
115 this->at(i).m_Size = e->file_size;
118 CGit::GetFileModifyTime(dgitdir + _T("index"), &this->m_LastModifyTime);
119 std::sort(this->begin(), this->end(), SortIndex);
121 m_critRepoSec.Unlock();
123 return 0;
126 int CGitIndexList::GetFileStatus(const CString &gitdir, const CString &pathorg, git_wc_status_kind *status, __int64 time, __int64 filesize, FILL_STATUS_CALLBACK callback, void *pData, CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
128 if(status)
130 CString path = pathorg;
131 path.MakeLower();
133 int start = SearchInSortVector(*this, path, -1);
135 if (start < 0)
137 *status = git_wc_status_unversioned;
138 if (pHash)
139 pHash->Empty();
142 else
144 int index = start;
145 if (index >= (int)size())
146 return -1;
148 // skip-worktree has higher priority than assume-valid
149 if (at(index).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE)
151 *status = git_wc_status_normal;
152 if (skipWorktree)
153 *skipWorktree = true;
155 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
157 *status = git_wc_status_normal;
158 if (assumeValid)
159 *assumeValid = true;
161 else if (filesize != at(index).m_Size)
162 *status = git_wc_status_modified;
163 else if (time == at(index).m_ModifyTime)
165 *status = git_wc_status_normal;
167 else if (m_bCheckContent && repository && filesize < m_iMaxCheckSize)
169 git_oid actual;
170 CStringA fileA = CUnicodeUtils::GetMulti(pathorg, CP_UTF8);
171 m_critRepoSec.Lock(); // prevent concurrent access to repository instance and especially filter-lists
172 if (!git_repository_hashfile(&actual, repository, fileA, GIT_OBJ_BLOB, NULL) && !git_oid_cmp(&actual, (const git_oid*)at(index).m_IndexHash.m_hash))
174 at(index).m_ModifyTime = time;
175 *status = git_wc_status_normal;
177 else
178 *status = git_wc_status_modified;
179 m_critRepoSec.Unlock();
181 else
182 *status = git_wc_status_modified;
184 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
185 *status = git_wc_status_conflicted;
186 else if (at(index).m_Flags & GIT_IDXENTRY_INTENT_TO_ADD)
187 *status = git_wc_status_added;
189 if(pHash)
190 *pHash = at(index).m_IndexHash;
195 if (callback && status && assumeValid && skipWorktree)
196 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
197 return 0;
200 int CGitIndexList::GetStatus(const CString &gitdir,const CString &pathParam, git_wc_status_kind *status,
201 BOOL IsFull, BOOL /*IsRecursive*/,
202 FILL_STATUS_CALLBACK callback, void *pData,
203 CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
205 __int64 time, filesize = 0;
206 bool isDir = false;
207 CString path = pathParam;
209 if (status)
211 git_wc_status_kind dirstatus = git_wc_status_none;
212 int result;
213 if (path.IsEmpty())
214 result = CGit::GetFileModifyTime(gitdir, &time, &isDir);
215 else
216 result = CGit::GetFileModifyTime(CombinePath(gitdir, path), &time, &isDir, &filesize);
218 if (result)
220 *status = git_wc_status_deleted;
221 if (callback && assumeValid && skipWorktree)
222 callback(CombinePath(gitdir, path), git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
224 return 0;
226 if (isDir)
228 if (!path.IsEmpty())
230 if (path.Right(1) != _T("\\"))
231 path += _T("\\");
233 int len = path.GetLength();
235 for (auto it = cbegin(), itend = cend(); it != itend; ++it)
237 if ((*it).m_FileName.GetLength() > len)
239 if ((*it).m_FileName.Left(len) == path)
241 if (!IsFull)
243 *status = git_wc_status_normal;
244 if (callback)
245 callback(CombinePath(gitdir, path), *status, false, pData, ((*it).m_Flags & GIT_IDXENTRY_VALID) && !((*it).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE), ((*it).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE) != 0);
246 return 0;
249 else
251 result = CGit::GetFileModifyTime(CombinePath(gitdir, (*it).m_FileName), &time, nullptr, &filesize);
252 if (result)
253 continue;
255 *status = git_wc_status_none;
256 if (assumeValid)
257 *assumeValid = false;
258 if (skipWorktree)
259 *skipWorktree = false;
260 GetFileStatus(gitdir, (*it).m_FileName, status, time, filesize, callback, pData, NULL, assumeValid, skipWorktree);
261 // 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
262 if (callback && assumeValid && skipWorktree && (*assumeValid || *skipWorktree))
263 callback(CombinePath(gitdir, path), *status, false, pData, *assumeValid, *skipWorktree);
264 if (*status != git_wc_status_none)
266 if (dirstatus == git_wc_status_none)
268 dirstatus = git_wc_status_normal;
270 if (*status != git_wc_status_normal)
272 dirstatus = git_wc_status_modified;
279 } /* End For */
281 if (dirstatus != git_wc_status_none)
283 *status = dirstatus;
285 else
287 *status = git_wc_status_unversioned;
289 if(callback)
290 callback(CombinePath(gitdir, path), *status, false, pData, false, false);
292 return 0;
295 else
297 GetFileStatus(gitdir, path, status, time, filesize, callback, pData, pHash, assumeValid, skipWorktree);
300 return 0;
303 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
305 __int64 time;
306 int result;
308 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir);
309 IndexFile += _T("index");
311 /* Get data associated with "crt_stat.c": */
312 result = CGit::GetFileModifyTime(IndexFile, &time);
314 if (result)
315 return result;
317 SHARED_INDEX_PTR pIndex;
318 pIndex = this->SafeGet(gitdir);
320 if (pIndex.get() == NULL)
322 if(isChanged)
323 *isChanged = true;
324 return 0;
327 if (pIndex->m_LastModifyTime == time)
329 if (isChanged)
330 *isChanged = false;
332 else
334 if (isChanged)
335 *isChanged = true;
337 return 0;
340 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
344 SHARED_INDEX_PTR pIndex(new CGitIndexList);
346 if(pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
347 return -1;
349 this->SafeSet(gitdir, pIndex);
351 }catch(...)
353 return -1;
355 return 0;
358 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
359 FILL_STATUS_CALLBACK callback, void *pData,
360 CGitHash *pHash,
361 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
365 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
367 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
368 if (pIndex.get() != NULL)
370 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
372 else
374 // git working tree has not index
375 *status = git_wc_status_unversioned;
378 catch(...)
380 return -1;
382 return 0;
385 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
389 if (path.IsEmpty())
391 *isVersion = true;
392 return 0;
395 CString subpath = path;
396 subpath.Replace(_T('\\'), _T('/'));
397 if(isDir)
398 subpath += _T('/');
400 subpath.MakeLower();
402 CheckAndUpdate(gitdir, isLoadUpdateIndex);
404 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
406 if(pIndex.get())
408 if(isDir)
409 *isVersion = (SearchInSortVector(*pIndex, subpath, subpath.GetLength()) >= 0);
410 else
411 *isVersion = (SearchInSortVector(*pIndex, subpath, -1) >= 0);
414 }catch(...)
416 return -1;
418 return 0;
421 // This method is assumed to be called with m_SharedMutex locked.
422 int CGitHeadFileList::GetPackRef(const CString &gitdir)
424 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
426 __int64 mtime;
427 if (CGit::GetFileModifyTime(PackRef, &mtime))
429 //packed refs is not existed
430 this->m_PackRefFile.Empty();
431 this->m_PackRefMap.clear();
432 return 0;
434 else if(mtime == m_LastModifyTimePackRef)
436 return 0;
438 else
440 this->m_PackRefFile = PackRef;
441 this->m_LastModifyTimePackRef = mtime;
444 m_PackRefMap.clear();
446 CAutoFile hfile = CreateFile(PackRef,
447 GENERIC_READ,
448 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
449 nullptr,
450 OPEN_EXISTING,
451 FILE_ATTRIBUTE_NORMAL,
452 nullptr);
454 if (!hfile)
455 return -1;
457 DWORD filesize = GetFileSize(hfile, nullptr);
458 if (filesize == 0)
459 return -1;
461 DWORD size = 0;
462 std::unique_ptr<char[]> buff(new char[filesize]);
463 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
465 if (size != filesize)
466 return -1;
468 CString hash;
469 CString ref;
470 for (DWORD i = 0; i < filesize;)
472 hash.Empty();
473 ref.Empty();
474 if (buff[i] == '#' || buff[i] == '^')
476 while (buff[i] != '\n')
478 ++i;
479 if (i == filesize)
480 break;
482 ++i;
485 if (i >= filesize)
486 break;
488 while (buff[i] != ' ')
490 hash.AppendChar(buff[i]);
491 ++i;
492 if (i == filesize)
493 break;
496 ++i;
497 if (i >= filesize)
498 break;
500 while (buff[i] != '\n')
502 ref.AppendChar(buff[i]);
503 ++i;
504 if (i == filesize)
505 break;
508 if (!ref.IsEmpty())
509 m_PackRefMap[ref] = hash;
511 while (buff[i] == '\n')
513 ++i;
514 if (i == filesize)
515 break;
518 return 0;
520 int CGitHeadFileList::ReadHeadHash(const CString& gitdir)
522 CAutoWriteLock lock(m_SharedMutex);
523 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
525 m_HeadFile = m_Gitdir;
526 m_HeadFile += _T("HEAD");
528 if( CGit::GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
529 return -1;
531 CAutoFile hfile = CreateFile(m_HeadFile,
532 GENERIC_READ,
533 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
534 nullptr,
535 OPEN_EXISTING,
536 FILE_ATTRIBUTE_NORMAL,
537 nullptr);
539 if (!hfile)
540 return -1;
542 DWORD size = 0;
543 unsigned char buffer[40];
544 ReadFile(hfile, buffer, 4, &size, nullptr);
545 if (size != 4)
546 return -1;
547 buffer[4] = 0;
548 if (strcmp((const char*)buffer, "ref:") == 0)
550 m_HeadRefFile.Empty();
551 DWORD filesize = GetFileSize(hfile, nullptr);
552 if (filesize < 5)
553 return -1;
555 unsigned char *p = (unsigned char*)malloc(filesize - 4);
556 if (!p)
557 return -1;
559 ReadFile(hfile, p, filesize - 4, &size, nullptr);
560 CGit::StringAppend(&m_HeadRefFile, p, CP_UTF8, filesize - 4);
561 free(p);
563 CString ref = m_HeadRefFile.Trim();
564 int start = 0;
565 ref = ref.Tokenize(_T("\n"), start);
566 m_HeadRefFile = m_Gitdir + m_HeadRefFile;
567 m_HeadRefFile.Replace(_T('/'), _T('\\'));
569 __int64 time;
570 if (CGit::GetFileModifyTime(m_HeadRefFile, &time, nullptr))
572 m_HeadRefFile.Empty();
573 if (GetPackRef(gitdir))
574 return -1;
575 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
576 return -1;
578 m_Head = m_PackRefMap[ref];
579 return 0;
582 CAutoFile href = CreateFile(m_HeadRefFile,
583 GENERIC_READ,
584 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
585 nullptr,
586 OPEN_EXISTING,
587 FILE_ATTRIBUTE_NORMAL,
588 nullptr);
590 if (!href)
592 m_HeadRefFile.Empty();
594 if (GetPackRef(gitdir))
595 return -1;
597 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
598 return -1;
600 m_Head = m_PackRefMap[ref];
601 return 0;
604 ReadFile(href, buffer, 40, &size, nullptr);
605 if (size != 40)
606 return -1;
608 m_Head.ConvertFromStrA((char*)buffer);
610 m_LastModifyTimeRef = time;
612 else
614 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
615 if (size != 36)
616 return -1;
618 m_HeadRefFile.Empty();
620 m_Head.ConvertFromStrA((char*)buffer);
623 return 0;
626 bool CGitHeadFileList::CheckHeadUpdate()
628 CAutoReadLock lock(m_SharedMutex);
629 if (this->m_HeadFile.IsEmpty())
630 return true;
632 __int64 mtime=0;
634 if (CGit::GetFileModifyTime(m_HeadFile, &mtime))
635 return true;
637 if (mtime != this->m_LastModifyTimeHead)
638 return true;
640 if (!this->m_HeadRefFile.IsEmpty())
642 if (CGit::GetFileModifyTime(m_HeadRefFile, &mtime))
643 return true;
645 if (mtime != this->m_LastModifyTimeRef)
646 return true;
649 if(!this->m_PackRefFile.IsEmpty())
651 if (CGit::GetFileModifyTime(m_PackRefFile, &mtime))
652 return true;
654 if (mtime != this->m_LastModifyTimePackRef)
655 return true;
658 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
659 // So we need to retry again and again until the ref exists - otherwise we will never notice
660 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
661 return true;
663 return false;
666 bool CGitHeadFileList::HeadHashEqualsTreeHash()
668 CAutoReadLock lock(m_SharedMutex);
669 return (m_Head == m_TreeHash);
672 bool CGitHeadFileList::HeadFileIsEmpty()
674 CAutoReadLock lock(m_SharedMutex);
675 return m_HeadFile.IsEmpty();
678 bool CGitHeadFileList::HeadIsEmpty()
680 CAutoReadLock lock(m_SharedMutex);
681 return m_Head.IsEmpty();
684 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
685 const char *pathname, unsigned mode, int /*stage*/, void *context)
687 #define S_IFGITLINK 0160000
689 CGitHeadFileList *p = (CGitHeadFileList*)context;
690 if( mode&S_IFDIR )
692 if( (mode&S_IFMT) != S_IFGITLINK)
693 return READ_TREE_RECURSIVE;
696 size_t cur = p->size();
697 p->resize(p->size() + 1);
698 p->at(cur).m_Hash = sha1;
699 p->at(cur).m_FileName.Empty();
701 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
702 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)pathname, CP_UTF8);
704 p->at(cur).m_FileName.MakeLower();
706 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
708 //p->m_Map[p->at(cur).m_FileName] = cur;
710 if( (mode&S_IFMT) == S_IFGITLINK)
711 return 0;
713 return READ_TREE_RECURSIVE;
716 int ReadTreeRecursive(git_repository &repo, const git_tree * tree, const CStringA& base, int (*CallBack) (const unsigned char *, const char *, int, const char *, unsigned int, int, void *), void *data)
718 size_t count = git_tree_entrycount(tree);
719 for (size_t i = 0; i < count; ++i)
721 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
722 if (entry == NULL)
723 continue;
724 int mode = git_tree_entry_filemode(entry);
725 if( CallBack(git_tree_entry_id(entry)->id,
726 base,
727 base.GetLength(),
728 git_tree_entry_name(entry),
729 mode,
731 data) == READ_TREE_RECURSIVE
734 if(mode&S_IFDIR)
736 git_object *object = NULL;
737 git_tree_entry_to_object(&object, &repo, entry);
738 if (object == NULL)
739 continue;
740 CStringA parent = base;
741 parent += git_tree_entry_name(entry);
742 parent += "/";
743 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
744 git_object_free(object);
750 return 0;
753 // ReadTree is/must only be executed on an empty list
754 int CGitHeadFileList::ReadTree()
756 CAutoWriteLock lock(m_SharedMutex);
757 ATLASSERT(empty());
759 CAutoRepository repository(m_Gitdir);
760 CAutoCommit commit;
761 CAutoTree tree;
762 bool ret = repository;
763 ret = ret && !git_commit_lookup(commit.GetPointer(), repository, (const git_oid*)m_Head.m_hash);
764 ret = ret && !git_commit_tree(tree.GetPointer(), commit);
765 ret = ret && !ReadTreeRecursive(*repository, tree, "", CGitHeadFileList::CallBack, this);
766 if (!ret)
768 clear();
769 m_LastModifyTimeHead = 0;
770 return -1;
773 std::sort(this->begin(), this->end(), SortTree);
774 m_TreeHash = git_commit_id(commit)->id;
776 return 0;
778 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
780 if (this->m_pExcludeList)
782 git_free_exclude_list(m_pExcludeList);
783 m_pExcludeList=NULL;
785 free(m_buffer);
786 m_buffer = nullptr;
788 this->m_BaseDir.Empty();
789 if (!isGlobal)
791 CString base = file.Mid(projectroot.GetLength() + 1);
792 base.Replace(_T('\\'), _T('/'));
794 int start = base.ReverseFind(_T('/'));
795 if(start > 0)
797 base = base.Left(start);
798 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
803 if(CGit::GetFileModifyTime(file, &m_LastModifyTime))
804 return -1;
806 if(git_create_exclude_list(&this->m_pExcludeList))
807 return -1;
810 CAutoFile hfile = CreateFile(file,
811 GENERIC_READ,
812 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
813 NULL,
814 OPEN_EXISTING,
815 FILE_ATTRIBUTE_NORMAL,
816 NULL);
819 if (!hfile)
820 return -1 ;
822 DWORD size=0,filesize=0;
824 filesize=GetFileSize(hfile, NULL);
826 if(filesize == INVALID_FILE_SIZE)
827 return -1;
829 m_buffer = new BYTE[filesize + 1];
831 if (m_buffer == NULL)
832 return -1;
834 if (!ReadFile(hfile, m_buffer, filesize, &size, NULL))
835 return GetLastError();
837 BYTE *p = m_buffer;
838 int line = 0;
839 for (DWORD i = 0; i < size; ++i)
841 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
843 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
844 m_buffer[i] = 0;
845 if (i == size - 1)
846 m_buffer[size] = 0;
848 if(p[0] != '#' && p[0] != 0)
849 git_add_exclude((const char*)p,
850 this->m_BaseDir,
851 m_BaseDir.GetLength(),
852 this->m_pExcludeList, ++line);
854 p = m_buffer + i + 1;
858 return 0;
861 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
862 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, int& type)
864 int pos = patha.ReverseFind('/');
865 const char* base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
867 return IsPathIgnored(patha, base, type);
869 #endif
871 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, const char* base, int& type)
873 if (!m_pExcludeList)
874 return -1; // error or undecided
876 return git_check_excluded_1(patha, patha.GetLength(), base, &type, m_pExcludeList);
879 bool CGitIgnoreList::CheckFileChanged(const CString &path)
881 __int64 time = 0;
883 int ret = CGit::GetFileModifyTime(path, &time);
885 bool cacheExist;
887 CAutoReadLock lock(m_SharedMutex);
888 cacheExist = (m_Map.find(path) != m_Map.end());
891 if (!cacheExist && ret == 0)
893 CAutoWriteLock lock(m_SharedMutex);
894 m_Map[path].m_LastModifyTime = 0;
896 // both cache and file is not exist
897 if ((ret != 0) && (!cacheExist))
898 return false;
900 // file exist but cache miss
901 if ((ret == 0) && (!cacheExist))
902 return true;
904 // file not exist but cache exist
905 if ((ret != 0) && (cacheExist))
907 return true;
909 // file exist and cache exist
912 CAutoReadLock lock(m_SharedMutex);
913 if (m_Map[path].m_LastModifyTime == time)
914 return false;
916 return true;
919 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir, const CString &path, bool isDir)
921 CString temp;
922 temp = gitdir;
923 temp += _T("\\");
924 temp += path;
926 temp.Replace(_T('/'), _T('\\'));
928 if (!isDir)
930 int x = temp.ReverseFind(_T('\\'));
931 if (x >= 2)
932 temp = temp.Left(x);
935 while(!temp.IsEmpty())
937 CString tempOrig = temp;
938 temp += _T("\\.git");
940 if (CGit::GitPathFileExists(temp))
942 CString gitignore=temp;
943 gitignore += _T("ignore");
944 if (CheckFileChanged(gitignore))
945 return true;
947 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
948 CString wcglobalgitignore = adminDir + _T("info\\exclude");
949 if (CheckFileChanged(wcglobalgitignore))
950 return true;
952 if (CheckAndUpdateCoreExcludefile(adminDir))
953 return true;
955 return false;
957 else
959 temp += _T("ignore");
960 if (CheckFileChanged(temp))
961 return true;
964 int found=0;
965 int i;
966 for (i = temp.GetLength() - 1; i >= 0; i--)
968 if(temp[i] == _T('\\'))
969 ++found;
971 if(found == 2)
972 break;
975 temp = temp.Left(i);
977 return true;
980 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
982 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
984 CAutoWriteLock lock(m_SharedMutex);
985 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
987 else
989 CAutoWriteLock lock(m_SharedMutex);
990 m_Map.erase(gitignore);
992 return 0;
995 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir, const CString &path, bool isDir)
997 CString temp;
999 temp = gitdir;
1000 temp += _T("\\");
1001 temp += path;
1003 temp.Replace(_T('/'), _T('\\'));
1005 if (!isDir)
1007 int x = temp.ReverseFind(_T('\\'));
1008 if (x >= 2)
1009 temp = temp.Left(x);
1012 while (!temp.IsEmpty())
1014 CString tempOrig = temp;
1015 temp += _T("\\.git");
1017 if (CGit::GitPathFileExists(temp))
1019 CString gitignore = temp;
1020 gitignore += _T("ignore");
1021 if (CheckFileChanged(gitignore))
1023 FetchIgnoreFile(gitdir, gitignore, false);
1026 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1027 CString wcglobalgitignore = adminDir;
1028 wcglobalgitignore += _T("info\\exclude");
1029 if (CheckFileChanged(wcglobalgitignore))
1031 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1034 if (CheckAndUpdateCoreExcludefile(adminDir))
1036 CString excludesFile;
1038 CAutoReadLock lock(m_SharedMutex);
1039 excludesFile = m_CoreExcludesfiles[adminDir];
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::CheckAndUpdateGitSystemConfigPath(bool force)
1073 // recheck every 30 seconds
1074 if (GetTickCount() - m_dGitSystemConfigPathLastChecked > 30000 || force)
1076 m_dGitSystemConfigPathLastChecked = GetTickCount();
1077 CString gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
1078 if (gitSystemConfigPath != m_sGitSystemConfigPath)
1080 m_sGitSystemConfigPath = gitSystemConfigPath;
1081 return true;
1084 return false;
1086 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1088 CString projectConfig = adminDir + _T("config");
1089 CString globalConfig = g_Git.GetGitGlobalConfig();
1090 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1092 CAutoWriteLock lock(m_coreExcludefilesSharedMutex);
1093 bool hasChanged = CheckAndUpdateGitSystemConfigPath();
1094 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1095 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1096 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1097 if (!m_sGitSystemConfigPath.IsEmpty())
1098 hasChanged = hasChanged || CheckFileChanged(m_sGitSystemConfigPath);
1100 CString excludesFile;
1102 CAutoReadLock lock2(m_SharedMutex);
1103 excludesFile = m_CoreExcludesfiles[adminDir];
1105 if (!excludesFile.IsEmpty())
1106 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1108 if (!hasChanged)
1109 return false;
1111 CAutoConfig config(true);
1112 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
1113 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
1114 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
1115 if (!m_sGitSystemConfigPath.IsEmpty())
1116 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitSystemConfigPath), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
1117 config.GetString(_T("core.excludesfile"), excludesFile);
1118 if (excludesFile.IsEmpty())
1119 excludesFile = GetWindowsHome() + _T("\\.config\\git\\ignore");
1120 else if (excludesFile.Find(_T("~/")) == 0)
1121 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1123 CAutoWriteLock lockMap(m_SharedMutex);
1124 CGit::GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1125 CGit::GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1126 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1127 m_Map.erase(globalXDGConfig);
1128 CGit::GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1129 if (m_Map[globalConfig].m_LastModifyTime == 0)
1130 m_Map.erase(globalConfig);
1131 if (!m_sGitSystemConfigPath.IsEmpty())
1132 CGit::GetFileModifyTime(m_sGitSystemConfigPath, &m_Map[m_sGitSystemConfigPath].m_LastModifyTime);
1133 if (m_Map[m_sGitSystemConfigPath].m_LastModifyTime == 0 || m_sGitSystemConfigPath.IsEmpty())
1134 m_Map.erase(m_sGitSystemConfigPath);
1135 m_CoreExcludesfiles[adminDir] = excludesFile;
1137 return true;
1139 const CString CGitIgnoreList::GetWindowsHome()
1141 static CString sWindowsHome(g_Git.GetHomeDirectory());
1142 return sWindowsHome;
1144 bool CGitIgnoreList::IsIgnore(const CString &path, const CString &projectroot, bool isDir)
1146 CString str=path;
1148 str.Replace(_T('\\'),_T('/'));
1150 if (str.GetLength()>0)
1151 if (str[str.GetLength()-1] == _T('/'))
1152 str = str.Left(str.GetLength() - 1);
1154 int ret;
1155 ret = CheckIgnore(str, projectroot, isDir);
1156 while (ret < 0)
1158 int start = str.ReverseFind(_T('/'));
1159 if(start < 0)
1160 return (ret == 1);
1162 str = str.Left(start);
1163 ret = CheckIgnore(str, projectroot, isDir);
1166 return (ret == 1);
1168 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1170 if (m_Map.find(ignorefile) == m_Map.end())
1171 return -1; // error or undecided
1173 return (m_Map[ignorefile].IsPathIgnored(patha, base, type));
1175 int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, bool isDir)
1177 CString temp = projectroot + _T("\\") + path;
1178 temp.Replace(_T('/'), _T('\\'));
1180 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1181 patha.Replace('\\', '/');
1183 int type = 0;
1184 if (isDir)
1186 type = DT_DIR;
1188 // strip directory name
1189 // we do not need to check for a .ignore file inside a directory we might ignore
1190 int i = temp.ReverseFind(_T('\\'));
1191 if (i >= 0)
1192 temp = temp.Left(i);
1194 else
1196 type = DT_REG;
1198 int x = temp.ReverseFind(_T('\\'));
1199 if (x >= 2)
1200 temp = temp.Left(x);
1203 int pos = patha.ReverseFind('/');
1204 const char * base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
1206 int ret = -1;
1208 CAutoReadLock lock(m_SharedMutex);
1209 while (!temp.IsEmpty())
1211 CString tempOrig = temp;
1212 temp += _T("\\.git");
1214 if (CGit::GitPathFileExists(temp))
1216 CString gitignore = temp;
1217 gitignore += _T("ignore");
1218 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1219 break;
1221 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1222 CString wcglobalgitignore = adminDir;
1223 wcglobalgitignore += _T("info\\exclude");
1224 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1225 break;
1227 CString excludesFile = m_CoreExcludesfiles[adminDir];
1228 if (!excludesFile.IsEmpty())
1229 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1231 break;
1233 else
1235 temp += _T("ignore");
1236 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1237 break;
1240 int found = 0;
1241 int i;
1242 for (i = temp.GetLength() - 1; i >= 0; i--)
1244 if (temp[i] == _T('\\'))
1245 ++found;
1247 if (found == 2)
1248 break;
1251 temp = temp.Left(i);
1254 return ret;
1257 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir, bool readTree /* = true */)
1259 SHARED_TREE_PTR ptr;
1260 ptr = this->SafeGet(gitdir);
1262 if (ptr.get() && !ptr->CheckHeadUpdate() && (!readTree || ptr->HeadHashEqualsTreeHash()))
1263 return false;
1265 ptr = SHARED_TREE_PTR(new CGitHeadFileList);
1266 ptr->ReadHeadHash(gitdir);
1267 if (readTree)
1268 ptr->ReadTree();
1270 this->SafeSet(gitdir, ptr);
1272 return true;
1275 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1279 if (path.IsEmpty())
1281 *isVersion = true;
1282 return 0;
1285 CString subpath = path;
1286 subpath.Replace(_T('\\'), _T('/'));
1287 if(isDir)
1288 subpath += _T('/');
1290 subpath.MakeLower();
1292 CheckHeadAndUpdate(gitdir);
1294 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1296 // Init Repository
1297 if (treeptr->HeadFileIsEmpty())
1299 *isVersion = false;
1300 return 0;
1302 else if (treeptr->empty())
1304 *isVersion = false;
1305 return 1;
1308 if(isDir)
1309 *isVersion = (SearchInSortVector(*treeptr, subpath, subpath.GetLength()) >= 0);
1310 else
1311 *isVersion = (SearchInSortVector(*treeptr, subpath, -1) >= 0);
1313 catch(...)
1315 return -1;
1318 return 0;