Refactor: Drop try catch
[TortoiseGit.git] / src / Git / GitIndex.cpp
bloba51f5266278bd713eaf157e9434168746207c4a5
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)
129 return 0;
131 CString path = pathorg;
132 path.MakeLower();
134 int index = SearchInSortVector(*this, path, -1);
136 if (index < 0)
138 *status = git_wc_status_unversioned;
139 if (pHash)
140 pHash->Empty();
142 if (callback && assumeValid && skipWorktree)
143 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
145 return 0;
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)
164 *status = git_wc_status_normal;
165 else if (m_bCheckContent && repository && filesize < m_iMaxCheckSize)
167 git_oid actual;
168 CStringA fileA = CUnicodeUtils::GetMulti(pathorg, CP_UTF8);
169 m_critRepoSec.Lock(); // prevent concurrent access to repository instance and especially filter-lists
170 if (!git_repository_hashfile(&actual, repository, fileA, GIT_OBJ_BLOB, NULL) && !git_oid_cmp(&actual, (const git_oid*)at(index).m_IndexHash.m_hash))
172 at(index).m_ModifyTime = time;
173 *status = git_wc_status_normal;
175 else
176 *status = git_wc_status_modified;
177 m_critRepoSec.Unlock();
179 else
180 *status = git_wc_status_modified;
182 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
183 *status = git_wc_status_conflicted;
184 else if (at(index).m_Flags & GIT_IDXENTRY_INTENT_TO_ADD)
185 *status = git_wc_status_added;
187 if (pHash)
188 *pHash = at(index).m_IndexHash;
190 if (callback && assumeValid && skipWorktree)
191 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
193 return 0;
196 int CGitIndexList::GetStatus(const CString &gitdir,const CString &pathParam, git_wc_status_kind *status,
197 BOOL IsFull, BOOL /*IsRecursive*/,
198 FILL_STATUS_CALLBACK callback, void *pData,
199 CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
201 __int64 time, filesize = 0;
202 bool isDir = false;
203 CString path = pathParam;
205 if (!status)
206 return 0;
208 git_wc_status_kind dirstatus = git_wc_status_none;
209 int result;
210 if (path.IsEmpty())
211 result = CGit::GetFileModifyTime(gitdir, &time, &isDir);
212 else
213 result = CGit::GetFileModifyTime(CombinePath(gitdir, path), &time, &isDir, &filesize);
215 if (result)
217 *status = git_wc_status_deleted;
218 if (callback && assumeValid && skipWorktree)
219 callback(CombinePath(gitdir, path), git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
221 return 0;
224 if (!isDir)
226 GetFileStatus(gitdir, path, status, time, filesize, callback, pData, pHash, assumeValid, skipWorktree);
227 return 0;
230 if (!path.IsEmpty() && 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 && (*it).m_FileName.Left(len) == path))
238 continue;
240 if (!IsFull)
242 *status = git_wc_status_normal;
243 if (callback)
244 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 result = CGit::GetFileModifyTime(CombinePath(gitdir, (*it).m_FileName), &time, nullptr, &filesize);
250 if (result)
251 continue;
253 *status = git_wc_status_none;
254 if (assumeValid)
255 *assumeValid = false;
256 if (skipWorktree)
257 *skipWorktree = false;
259 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)
267 dirstatus = git_wc_status_normal;
268 if (*status != git_wc_status_normal)
269 dirstatus = git_wc_status_modified;
271 } /* End For */
273 if (dirstatus != git_wc_status_none)
274 *status = dirstatus;
275 else
276 *status = git_wc_status_unversioned;
278 if (callback)
279 callback(CombinePath(gitdir, path), *status, false, pData, false, false);
281 return 0;
284 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
286 __int64 time;
288 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir);
289 IndexFile += _T("index");
291 if (CGit::GetFileModifyTime(IndexFile, &time))
292 return -1;
294 SHARED_INDEX_PTR pIndex;
295 pIndex = this->SafeGet(gitdir);
297 if (pIndex.get() == NULL)
299 if(isChanged)
300 *isChanged = true;
301 return 0;
304 if (pIndex->m_LastModifyTime == time)
306 if (isChanged)
307 *isChanged = false;
309 else
311 if (isChanged)
312 *isChanged = true;
314 return 0;
317 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
319 SHARED_INDEX_PTR pIndex(new CGitIndexList);
321 if (pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
322 return -1;
324 this->SafeSet(gitdir, pIndex);
326 return 0;
329 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
330 FILL_STATUS_CALLBACK callback, void *pData,
331 CGitHash *pHash,
332 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
334 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
336 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
337 if (pIndex.get())
338 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
339 else
341 // git working tree has not index
342 *status = git_wc_status_unversioned;
345 return 0;
348 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
350 if (path.IsEmpty())
352 *isVersion = true;
353 return 0;
356 CString subpath = path;
357 subpath.Replace(_T('\\'), _T('/'));
358 if (isDir)
359 subpath += _T('/');
361 subpath.MakeLower();
363 CheckAndUpdate(gitdir, isLoadUpdateIndex);
365 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
367 if (pIndex.get())
369 if (isDir)
370 *isVersion = (SearchInSortVector(*pIndex, subpath, subpath.GetLength()) >= 0);
371 else
372 *isVersion = (SearchInSortVector(*pIndex, subpath, -1) >= 0);
375 return 0;
378 // This method is assumed to be called with m_SharedMutex locked.
379 int CGitHeadFileList::GetPackRef(const CString &gitdir)
381 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
383 __int64 mtime;
384 if (CGit::GetFileModifyTime(PackRef, &mtime))
386 //packed refs is not existed
387 this->m_PackRefFile.Empty();
388 this->m_PackRefMap.clear();
389 return 0;
391 else if(mtime == m_LastModifyTimePackRef)
392 return 0;
393 else
395 this->m_PackRefFile = PackRef;
396 this->m_LastModifyTimePackRef = mtime;
399 m_PackRefMap.clear();
401 CAutoFile hfile = CreateFile(PackRef,
402 GENERIC_READ,
403 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
404 nullptr,
405 OPEN_EXISTING,
406 FILE_ATTRIBUTE_NORMAL,
407 nullptr);
409 if (!hfile)
410 return -1;
412 DWORD filesize = GetFileSize(hfile, nullptr);
413 if (filesize == 0)
414 return -1;
416 DWORD size = 0;
417 std::unique_ptr<char[]> buff(new char[filesize]);
418 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
420 if (size != filesize)
421 return -1;
423 CString hash;
424 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())
464 m_PackRefMap[ref] = hash;
466 while (buff[i] == '\n')
468 ++i;
469 if (i == filesize)
470 break;
473 return 0;
475 int CGitHeadFileList::ReadHeadHash(const CString& gitdir)
477 CAutoWriteLock lock(m_SharedMutex);
478 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
480 m_HeadFile = m_Gitdir;
481 m_HeadFile += _T("HEAD");
483 if( CGit::GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
484 return -1;
486 CAutoFile hfile = CreateFile(m_HeadFile,
487 GENERIC_READ,
488 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
489 nullptr,
490 OPEN_EXISTING,
491 FILE_ATTRIBUTE_NORMAL,
492 nullptr);
494 if (!hfile)
495 return -1;
497 DWORD size = 0;
498 unsigned char buffer[40];
499 ReadFile(hfile, buffer, 4, &size, nullptr);
500 if (size != 4)
501 return -1;
502 buffer[4] = 0;
503 if (strcmp((const char*)buffer, "ref:") == 0)
505 m_HeadRefFile.Empty();
506 DWORD filesize = GetFileSize(hfile, nullptr);
507 if (filesize < 5)
508 return -1;
510 unsigned char *p = (unsigned char*)malloc(filesize - 4);
511 if (!p)
512 return -1;
514 ReadFile(hfile, p, filesize - 4, &size, nullptr);
515 CGit::StringAppend(&m_HeadRefFile, p, CP_UTF8, filesize - 4);
516 free(p);
518 CString ref = m_HeadRefFile.Trim();
519 int start = 0;
520 ref = ref.Tokenize(_T("\n"), start);
521 m_HeadRefFile = m_Gitdir + m_HeadRefFile;
522 m_HeadRefFile.Replace(_T('/'), _T('\\'));
524 __int64 time;
525 if (CGit::GetFileModifyTime(m_HeadRefFile, &time, nullptr))
527 m_HeadRefFile.Empty();
528 if (GetPackRef(gitdir))
529 return -1;
530 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
531 return -1;
533 m_Head = m_PackRefMap[ref];
534 return 0;
537 CAutoFile href = CreateFile(m_HeadRefFile,
538 GENERIC_READ,
539 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
540 nullptr,
541 OPEN_EXISTING,
542 FILE_ATTRIBUTE_NORMAL,
543 nullptr);
545 if (!href)
547 m_HeadRefFile.Empty();
549 if (GetPackRef(gitdir))
550 return -1;
552 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
553 return -1;
555 m_Head = m_PackRefMap[ref];
556 return 0;
559 ReadFile(href, buffer, 40, &size, nullptr);
560 if (size != 40)
561 return -1;
563 m_Head.ConvertFromStrA((char*)buffer);
565 m_LastModifyTimeRef = time;
567 return 0;
570 ReadFile(hfile, buffer + 4, 40 - 4, &size, nullptr);
571 if (size != 36)
572 return -1;
574 m_HeadRefFile.Empty();
576 m_Head.ConvertFromStrA((char*)buffer);
578 return 0;
581 bool CGitHeadFileList::CheckHeadUpdate()
583 CAutoReadLock lock(m_SharedMutex);
584 if (this->m_HeadFile.IsEmpty())
585 return true;
587 __int64 mtime=0;
589 if (CGit::GetFileModifyTime(m_HeadFile, &mtime))
590 return true;
592 if (mtime != this->m_LastModifyTimeHead)
593 return true;
595 if (!this->m_HeadRefFile.IsEmpty())
597 if (CGit::GetFileModifyTime(m_HeadRefFile, &mtime))
598 return true;
600 if (mtime != this->m_LastModifyTimeRef)
601 return true;
604 if(!this->m_PackRefFile.IsEmpty())
606 if (CGit::GetFileModifyTime(m_PackRefFile, &mtime))
607 return true;
609 if (mtime != this->m_LastModifyTimePackRef)
610 return true;
613 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
614 // So we need to retry again and again until the ref exists - otherwise we will never notice
615 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
616 return true;
618 return false;
621 bool CGitHeadFileList::HeadHashEqualsTreeHash()
623 CAutoReadLock lock(m_SharedMutex);
624 return (m_Head == m_TreeHash);
627 bool CGitHeadFileList::HeadFileIsEmpty()
629 CAutoReadLock lock(m_SharedMutex);
630 return m_HeadFile.IsEmpty();
633 bool CGitHeadFileList::HeadIsEmpty()
635 CAutoReadLock lock(m_SharedMutex);
636 return m_Head.IsEmpty();
639 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
640 const char *pathname, unsigned mode, int /*stage*/, void *context)
642 #define S_IFGITLINK 0160000
644 CGitHeadFileList *p = (CGitHeadFileList*)context;
646 if ((mode & S_IFDIR) && (mode & S_IFMT) != S_IFGITLINK)
647 return READ_TREE_RECURSIVE;
649 size_t cur = p->size();
650 p->resize(p->size() + 1);
651 p->at(cur).m_Hash = sha1;
652 p->at(cur).m_FileName.Empty();
654 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
655 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)pathname, CP_UTF8);
657 p->at(cur).m_FileName.MakeLower();
659 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
661 //p->m_Map[p->at(cur).m_FileName] = cur;
663 if( (mode&S_IFMT) == S_IFGITLINK)
664 return 0;
666 return READ_TREE_RECURSIVE;
669 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)
671 size_t count = git_tree_entrycount(tree);
672 for (size_t i = 0; i < count; ++i)
674 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
675 if (entry == NULL)
676 continue;
677 int mode = git_tree_entry_filemode(entry);
678 if( CallBack(git_tree_entry_id(entry)->id,
679 base,
680 base.GetLength(),
681 git_tree_entry_name(entry),
682 mode,
684 data) == READ_TREE_RECURSIVE
687 if(mode&S_IFDIR)
689 git_object *object = NULL;
690 git_tree_entry_to_object(&object, &repo, entry);
691 if (object == NULL)
692 continue;
693 CStringA parent = base;
694 parent += git_tree_entry_name(entry);
695 parent += "/";
696 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
697 git_object_free(object);
703 return 0;
706 // ReadTree is/must only be executed on an empty list
707 int CGitHeadFileList::ReadTree()
709 CAutoWriteLock lock(m_SharedMutex);
710 ATLASSERT(empty());
712 CAutoRepository repository(m_Gitdir);
713 CAutoCommit commit;
714 CAutoTree tree;
715 bool ret = repository;
716 ret = ret && !git_commit_lookup(commit.GetPointer(), repository, (const git_oid*)m_Head.m_hash);
717 ret = ret && !git_commit_tree(tree.GetPointer(), commit);
718 ret = ret && !ReadTreeRecursive(*repository, tree, "", CGitHeadFileList::CallBack, this);
719 if (!ret)
721 clear();
722 m_LastModifyTimeHead = 0;
723 return -1;
726 std::sort(this->begin(), this->end(), SortTree);
727 m_TreeHash = git_commit_id(commit)->id;
729 return 0;
731 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
733 if (this->m_pExcludeList)
735 git_free_exclude_list(m_pExcludeList);
736 m_pExcludeList=NULL;
738 free(m_buffer);
739 m_buffer = nullptr;
741 this->m_BaseDir.Empty();
742 if (!isGlobal)
744 CString base = file.Mid(projectroot.GetLength() + 1);
745 base.Replace(_T('\\'), _T('/'));
747 int start = base.ReverseFind(_T('/'));
748 if(start > 0)
750 base = base.Left(start);
751 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
755 if (CGit::GetFileModifyTime(file, &m_LastModifyTime))
756 return -1;
758 CAutoFile hfile = CreateFile(file,
759 GENERIC_READ,
760 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
761 nullptr,
762 OPEN_EXISTING,
763 FILE_ATTRIBUTE_NORMAL,
764 nullptr);
766 if (!hfile)
767 return -1 ;
769 DWORD filesize = GetFileSize(hfile, nullptr);
770 if (filesize == INVALID_FILE_SIZE)
771 return -1;
773 m_buffer = new BYTE[filesize + 1];
774 if (!m_buffer)
775 return -1;
777 DWORD size = 0;
778 if (!ReadFile(hfile, m_buffer, filesize, &size, NULL))
780 free(m_buffer);
781 m_buffer = nullptr;
782 return -1;
784 m_buffer[size] = 0;
786 if (git_create_exclude_list(&m_pExcludeList))
788 free(m_buffer);
789 m_buffer = nullptr;
790 return -1;
793 BYTE *p = m_buffer;
794 int line = 0;
795 for (DWORD i = 0; i < size; ++i)
797 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
799 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
800 m_buffer[i] = 0;
802 if (p[0] != '#' && p[0] != 0)
803 git_add_exclude((const char*)p, this->m_BaseDir, m_BaseDir.GetLength(), this->m_pExcludeList, ++line);
805 p = m_buffer + i + 1;
809 if (!line)
811 git_free_exclude_list(m_pExcludeList);
812 m_pExcludeList = nullptr;
813 free(m_buffer);
814 m_buffer = nullptr;
817 return 0;
820 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
821 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, int& type)
823 int pos = patha.ReverseFind('/');
824 const char* base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
826 return IsPathIgnored(patha, base, type);
828 #endif
830 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, const char* base, int& type)
832 if (!m_pExcludeList)
833 return -1; // error or undecided
835 return git_check_excluded_1(patha, patha.GetLength(), base, &type, m_pExcludeList);
838 bool CGitIgnoreList::CheckFileChanged(const CString &path)
840 __int64 time = 0;
842 int ret = CGit::GetFileModifyTime(path, &time);
844 bool cacheExist;
846 CAutoReadLock lock(m_SharedMutex);
847 cacheExist = (m_Map.find(path) != m_Map.end());
850 if (!cacheExist && ret == 0)
852 CAutoWriteLock lock(m_SharedMutex);
853 m_Map[path].m_LastModifyTime = 0;
855 // both cache and file is not exist
856 if ((ret != 0) && (!cacheExist))
857 return false;
859 // file exist but cache miss
860 if ((ret == 0) && (!cacheExist))
861 return true;
863 // file not exist but cache exist
864 if ((ret != 0) && (cacheExist))
866 return true;
868 // file exist and cache exist
871 CAutoReadLock lock(m_SharedMutex);
872 if (m_Map[path].m_LastModifyTime == time)
873 return false;
875 return true;
878 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir, const CString &path, bool isDir)
880 CString temp;
881 temp = gitdir;
882 temp += _T("\\");
883 temp += path;
885 temp.Replace(_T('/'), _T('\\'));
887 if (!isDir)
889 int x = temp.ReverseFind(_T('\\'));
890 if (x >= 2)
891 temp = temp.Left(x);
894 while(!temp.IsEmpty())
896 CString tempOrig = temp;
897 temp += _T("\\.git");
899 if (CGit::GitPathFileExists(temp))
901 CString gitignore=temp;
902 gitignore += _T("ignore");
903 if (CheckFileChanged(gitignore))
904 return true;
906 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
907 CString wcglobalgitignore = adminDir + _T("info\\exclude");
908 if (CheckFileChanged(wcglobalgitignore))
909 return true;
911 if (CheckAndUpdateCoreExcludefile(adminDir))
912 return true;
914 return false;
917 temp += _T("ignore");
918 if (CheckFileChanged(temp))
919 return true;
921 int found=0;
922 int i;
923 for (i = temp.GetLength() - 1; i >= 0; i--)
925 if(temp[i] == _T('\\'))
926 ++found;
928 if(found == 2)
929 break;
932 temp = temp.Left(i);
934 return true;
937 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
939 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
941 CAutoWriteLock lock(m_SharedMutex);
942 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
944 else
946 CAutoWriteLock lock(m_SharedMutex);
947 m_Map.erase(gitignore);
949 return 0;
952 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir, const CString &path, bool isDir)
954 CString temp;
956 temp = gitdir;
957 temp += _T("\\");
958 temp += path;
960 temp.Replace(_T('/'), _T('\\'));
962 if (!isDir)
964 int x = temp.ReverseFind(_T('\\'));
965 if (x >= 2)
966 temp = temp.Left(x);
969 while (!temp.IsEmpty())
971 CString tempOrig = temp;
972 temp += _T("\\.git");
974 if (CGit::GitPathFileExists(temp))
976 CString gitignore = temp;
977 gitignore += _T("ignore");
978 if (CheckFileChanged(gitignore))
979 FetchIgnoreFile(gitdir, gitignore, false);
981 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
982 CString wcglobalgitignore = adminDir;
983 wcglobalgitignore += _T("info\\exclude");
984 if (CheckFileChanged(wcglobalgitignore))
986 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
989 if (CheckAndUpdateCoreExcludefile(adminDir))
991 CString excludesFile;
993 CAutoReadLock lock(m_SharedMutex);
994 excludesFile = m_CoreExcludesfiles[adminDir];
996 if (!excludesFile.IsEmpty())
997 FetchIgnoreFile(gitdir, excludesFile, true);
1000 return 0;
1003 temp += _T("ignore");
1004 if (CheckFileChanged(temp))
1005 FetchIgnoreFile(gitdir, temp, false);
1007 int found = 0;
1008 int i;
1009 for (i = temp.GetLength() - 1; i >= 0; i--)
1011 if(temp[i] == _T('\\'))
1012 ++found;
1014 if(found == 2)
1015 break;
1018 temp = temp.Left(i);
1020 return 0;
1022 bool CGitIgnoreList::CheckAndUpdateGitSystemConfigPath(bool force)
1024 // recheck every 30 seconds
1025 if (GetTickCount() - m_dGitSystemConfigPathLastChecked > 30000 || force)
1027 m_dGitSystemConfigPathLastChecked = GetTickCount();
1028 CString gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
1029 if (gitSystemConfigPath != m_sGitSystemConfigPath)
1031 m_sGitSystemConfigPath = gitSystemConfigPath;
1032 return true;
1035 return false;
1037 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1039 CString projectConfig = adminDir + _T("config");
1040 CString globalConfig = g_Git.GetGitGlobalConfig();
1041 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1043 CAutoWriteLock lock(m_coreExcludefilesSharedMutex);
1044 bool hasChanged = CheckAndUpdateGitSystemConfigPath();
1045 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1046 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1047 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1048 if (!m_sGitSystemConfigPath.IsEmpty())
1049 hasChanged = hasChanged || CheckFileChanged(m_sGitSystemConfigPath);
1051 CString excludesFile;
1053 CAutoReadLock lock2(m_SharedMutex);
1054 excludesFile = m_CoreExcludesfiles[adminDir];
1056 if (!excludesFile.IsEmpty())
1057 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1059 if (!hasChanged)
1060 return false;
1062 CAutoConfig config(true);
1063 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
1064 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
1065 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
1066 if (!m_sGitSystemConfigPath.IsEmpty())
1067 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitSystemConfigPath), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
1068 config.GetString(_T("core.excludesfile"), excludesFile);
1069 if (excludesFile.IsEmpty())
1070 excludesFile = GetWindowsHome() + _T("\\.config\\git\\ignore");
1071 else if (excludesFile.Find(_T("~/")) == 0)
1072 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1074 CAutoWriteLock lockMap(m_SharedMutex);
1075 CGit::GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1076 CGit::GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1077 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1078 m_Map.erase(globalXDGConfig);
1079 CGit::GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1080 if (m_Map[globalConfig].m_LastModifyTime == 0)
1081 m_Map.erase(globalConfig);
1082 if (!m_sGitSystemConfigPath.IsEmpty())
1083 CGit::GetFileModifyTime(m_sGitSystemConfigPath, &m_Map[m_sGitSystemConfigPath].m_LastModifyTime);
1084 if (m_Map[m_sGitSystemConfigPath].m_LastModifyTime == 0 || m_sGitSystemConfigPath.IsEmpty())
1085 m_Map.erase(m_sGitSystemConfigPath);
1086 m_CoreExcludesfiles[adminDir] = excludesFile;
1088 return true;
1090 const CString CGitIgnoreList::GetWindowsHome()
1092 static CString sWindowsHome(g_Git.GetHomeDirectory());
1093 return sWindowsHome;
1095 bool CGitIgnoreList::IsIgnore(const CString &path, const CString &projectroot, bool isDir)
1097 CString str=path;
1099 str.Replace(_T('\\'),_T('/'));
1101 if (str.GetLength()>0)
1102 if (str[str.GetLength()-1] == _T('/'))
1103 str = str.Left(str.GetLength() - 1);
1105 int ret;
1106 ret = CheckIgnore(str, projectroot, isDir);
1107 while (ret < 0)
1109 int start = str.ReverseFind(_T('/'));
1110 if(start < 0)
1111 return (ret == 1);
1113 str = str.Left(start);
1114 ret = CheckIgnore(str, projectroot, isDir);
1117 return (ret == 1);
1119 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1121 if (m_Map.find(ignorefile) == m_Map.end())
1122 return -1; // error or undecided
1124 return (m_Map[ignorefile].IsPathIgnored(patha, base, type));
1126 int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, bool isDir)
1128 CString temp = projectroot + _T("\\") + path;
1129 temp.Replace(_T('/'), _T('\\'));
1131 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1132 patha.Replace('\\', '/');
1134 int type = 0;
1135 if (isDir)
1137 type = DT_DIR;
1139 // strip directory name
1140 // we do not need to check for a .ignore file inside a directory we might ignore
1141 int i = temp.ReverseFind(_T('\\'));
1142 if (i >= 0)
1143 temp = temp.Left(i);
1145 else
1147 type = DT_REG;
1149 int x = temp.ReverseFind(_T('\\'));
1150 if (x >= 2)
1151 temp = temp.Left(x);
1154 int pos = patha.ReverseFind('/');
1155 const char * base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
1157 int ret = -1;
1159 CAutoReadLock lock(m_SharedMutex);
1160 while (!temp.IsEmpty())
1162 CString tempOrig = temp;
1163 temp += _T("\\.git");
1165 if (CGit::GitPathFileExists(temp))
1167 CString gitignore = temp;
1168 gitignore += _T("ignore");
1169 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1170 break;
1172 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1173 CString wcglobalgitignore = adminDir;
1174 wcglobalgitignore += _T("info\\exclude");
1175 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1176 break;
1178 CString excludesFile = m_CoreExcludesfiles[adminDir];
1179 if (!excludesFile.IsEmpty())
1180 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1182 break;
1185 temp += _T("ignore");
1186 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1187 break;
1189 int found = 0;
1190 int i;
1191 for (i = temp.GetLength() - 1; i >= 0; i--)
1193 if (temp[i] == _T('\\'))
1194 ++found;
1196 if (found == 2)
1197 break;
1200 temp = temp.Left(i);
1203 return ret;
1206 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir, bool readTree /* = true */)
1208 SHARED_TREE_PTR ptr;
1209 ptr = this->SafeGet(gitdir);
1211 if (ptr.get() && !ptr->CheckHeadUpdate() && (!readTree || ptr->HeadHashEqualsTreeHash()))
1212 return false;
1214 ptr = SHARED_TREE_PTR(new CGitHeadFileList);
1215 ptr->ReadHeadHash(gitdir);
1216 if (readTree)
1217 ptr->ReadTree();
1219 this->SafeSet(gitdir, ptr);
1221 return true;
1224 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1226 if (path.IsEmpty())
1228 *isVersion = true;
1229 return 0;
1232 CString subpath = path;
1233 subpath.Replace(_T('\\'), _T('/'));
1234 if (isDir)
1235 subpath += _T('/');
1237 subpath.MakeLower();
1239 CheckHeadAndUpdate(gitdir);
1241 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1243 // Init Repository
1244 if (treeptr->HeadFileIsEmpty())
1246 *isVersion = false;
1247 return 0;
1249 if (treeptr->empty())
1251 *isVersion = false;
1252 return 1;
1255 if (isDir)
1256 *isVersion = (SearchInSortVector(*treeptr, subpath, subpath.GetLength()) >= 0);
1257 else
1258 *isVersion = (SearchInSortVector(*treeptr, subpath, -1) >= 0);
1260 return 0;