Use IsEmpty()/empty() where possible
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob076079420a164e8f8e740d6358f4f75d501be2fa
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()
45 : m_bHasConflicts(FALSE)
47 this->m_LastModifyTime = 0;
48 m_critRepoSec.Init();
49 m_bCheckContent = !!(CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContent"), TRUE) == TRUE);
50 m_iMaxCheckSize = (__int64)CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContentMaxSize"), 10 * 1024) * 1024; // stored in KiB
53 CGitIndexList::~CGitIndexList()
55 m_critRepoSec.Term();
58 static bool SortIndex(const CGitIndex &Item1, const CGitIndex &Item2)
60 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
63 static bool SortTree(const CGitTreeItem &Item1, const CGitTreeItem &Item2)
65 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
68 int CGitIndexList::ReadIndex(CString dgitdir)
70 this->clear();
72 m_critRepoSec.Lock();
73 if (repository.Open(dgitdir))
75 m_critRepoSec.Unlock();
76 return -1;
79 // add config files
80 CAutoConfig config(true);
82 CString projectConfig = dgitdir + _T("config");
83 CString globalConfig = g_Git.GetGitGlobalConfig();
84 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
85 CString systemConfig(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
87 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
88 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
89 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
90 if (!systemConfig.IsEmpty())
91 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(systemConfig), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
93 git_repository_set_config(repository, config);
95 CAutoIndex index;
96 // load index in order to enumerate files
97 if (git_repository_index(index.GetPointer(), repository))
99 repository.Free();
100 m_critRepoSec.Unlock();
101 return -1;
104 m_bHasConflicts = FALSE;
106 size_t ecount = git_index_entrycount(index);
107 resize(ecount);
108 for (size_t i = 0; i < ecount; ++i)
110 const git_index_entry *e = git_index_get_byindex(index, i);
112 this->at(i).m_FileName.Empty();
113 this->at(i).m_FileName = CUnicodeUtils::GetUnicode(e->path);
114 this->at(i).m_FileName.MakeLower();
115 this->at(i).m_ModifyTime = e->mtime.seconds;
116 this->at(i).m_Flags = e->flags | e->flags_extended;
117 this->at(i).m_IndexHash = e->id.id;
118 this->at(i).m_Size = e->file_size;
119 m_bHasConflicts |= GIT_IDXENTRY_STAGE(e);
122 CGit::GetFileModifyTime(dgitdir + _T("index"), &this->m_LastModifyTime);
123 std::sort(this->begin(), this->end(), SortIndex);
125 m_critRepoSec.Unlock();
127 return 0;
130 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)
132 if (!status)
133 return 0;
135 CString path = pathorg;
136 path.MakeLower();
138 int index = SearchInSortVector(*this, path, -1);
140 if (index < 0)
142 *status = git_wc_status_unversioned;
143 if (pHash)
144 pHash->Empty();
146 if (callback && assumeValid && skipWorktree)
147 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
149 return 0;
152 // skip-worktree has higher priority than assume-valid
153 if (at(index).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE)
155 *status = git_wc_status_normal;
156 if (skipWorktree)
157 *skipWorktree = true;
159 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
161 *status = git_wc_status_normal;
162 if (assumeValid)
163 *assumeValid = true;
165 else if (filesize != at(index).m_Size)
166 *status = git_wc_status_modified;
167 else if (time == at(index).m_ModifyTime)
168 *status = git_wc_status_normal;
169 else if (m_bCheckContent && repository && filesize < m_iMaxCheckSize)
171 git_oid actual;
172 CStringA fileA = CUnicodeUtils::GetMulti(pathorg, CP_UTF8);
173 m_critRepoSec.Lock(); // prevent concurrent access to repository instance and especially filter-lists
174 if (!git_repository_hashfile(&actual, repository, fileA, GIT_OBJ_BLOB, NULL) && !git_oid_cmp(&actual, (const git_oid*)at(index).m_IndexHash.m_hash))
176 at(index).m_ModifyTime = time;
177 *status = git_wc_status_normal;
179 else
180 *status = git_wc_status_modified;
181 m_critRepoSec.Unlock();
183 else
184 *status = git_wc_status_modified;
186 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
187 *status = git_wc_status_conflicted;
188 else if (at(index).m_Flags & GIT_IDXENTRY_INTENT_TO_ADD)
189 *status = git_wc_status_added;
191 if (pHash)
192 *pHash = at(index).m_IndexHash;
194 if (callback && assumeValid && skipWorktree)
195 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
197 return 0;
200 int CGitIndexList::GetStatus(const CString& gitdir, CString path, 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;
208 if (!status)
209 return 0;
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;
227 if (!isDir)
229 GetFileStatus(gitdir, path, status, time, filesize, callback, pData, pHash, assumeValid, skipWorktree);
230 return 0;
233 if (!path.IsEmpty() && path.Right(1) != _T('\\'))
234 path += _T('\\');
236 int len = path.GetLength();
238 for (auto it = cbegin(), itend = cend(); it != itend; ++it)
240 if (!((*it).m_FileName.GetLength() > len && (*it).m_FileName.Left(len) == path))
241 continue;
243 if (!IsFull)
245 *status = git_wc_status_normal;
246 if (callback)
247 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);
249 return 0;
252 result = CGit::GetFileModifyTime(CombinePath(gitdir, (*it).m_FileName), &time, nullptr, &filesize);
253 if (result)
254 continue;
256 *status = git_wc_status_none;
257 if (assumeValid)
258 *assumeValid = false;
259 if (skipWorktree)
260 *skipWorktree = false;
262 GetFileStatus(gitdir, (*it).m_FileName, status, time, filesize, callback, pData, NULL, assumeValid, skipWorktree);
264 // 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
265 if (callback && assumeValid && skipWorktree && (*assumeValid || *skipWorktree))
266 callback(CombinePath(gitdir, path), *status, false, pData, *assumeValid, *skipWorktree);
267 if (*status != git_wc_status_none)
269 if (dirstatus == git_wc_status_none)
270 dirstatus = git_wc_status_normal;
271 if (*status != git_wc_status_normal)
272 dirstatus = git_wc_status_modified;
274 } /* End For */
276 if (dirstatus != git_wc_status_none)
277 *status = dirstatus;
278 else
279 *status = git_wc_status_unversioned;
281 if (callback)
282 callback(CombinePath(gitdir, path), *status, false, pData, false, false);
284 return 0;
287 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
289 __int64 time;
291 CString IndexFile = g_AdminDirMap.GetAdminDirConcat(gitdir, _T("index"));
293 if (CGit::GetFileModifyTime(IndexFile, &time))
294 return -1;
296 SHARED_INDEX_PTR pIndex;
297 pIndex = this->SafeGet(gitdir);
299 if (pIndex.get() == NULL)
301 if(isChanged)
302 *isChanged = true;
303 return 0;
306 if (pIndex->m_LastModifyTime == time)
308 if (isChanged)
309 *isChanged = false;
311 else
313 if (isChanged)
314 *isChanged = true;
316 return 0;
319 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
321 SHARED_INDEX_PTR pIndex(new CGitIndexList);
323 if (pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
324 return -1;
326 this->SafeSet(gitdir, pIndex);
328 return 0;
331 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
332 FILL_STATUS_CALLBACK callback, void *pData,
333 CGitHash *pHash,
334 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
336 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
338 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
339 if (pIndex.get())
340 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
341 else
343 // git working tree has not index
344 *status = git_wc_status_unversioned;
347 return 0;
350 int CGitIndexFileMap::IsUnderVersionControl(const CString& gitdir, CString subpath, bool isDir, bool* isVersion, bool isLoadUpdateIndex)
352 if (subpath.IsEmpty())
354 *isVersion = true;
355 return 0;
358 subpath.Replace(_T('\\'), _T('/'));
359 if (isDir)
360 subpath += _T('/');
362 subpath.MakeLower();
364 CheckAndUpdate(gitdir, isLoadUpdateIndex);
366 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
368 if (pIndex.get())
370 if (isDir)
371 *isVersion = (SearchInSortVector(*pIndex, subpath, subpath.GetLength()) >= 0);
372 else
373 *isVersion = (SearchInSortVector(*pIndex, subpath, -1) >= 0);
376 return 0;
379 // This method is assumed to be called with m_SharedMutex locked.
380 int CGitHeadFileList::GetPackRef(const CString &gitdir)
382 CString PackRef = g_AdminDirMap.GetAdminDirConcat(gitdir, _T("packed-refs"));
384 __int64 mtime;
385 if (CGit::GetFileModifyTime(PackRef, &mtime))
387 //packed refs is not existed
388 this->m_PackRefFile.Empty();
389 this->m_PackRefMap.clear();
390 return 0;
392 else if(mtime == m_LastModifyTimePackRef)
393 return 0;
394 else
396 this->m_PackRefFile = PackRef;
397 this->m_LastModifyTimePackRef = mtime;
400 m_PackRefMap.clear();
402 CAutoFile hfile = CreateFile(PackRef,
403 GENERIC_READ,
404 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
405 nullptr,
406 OPEN_EXISTING,
407 FILE_ATTRIBUTE_NORMAL,
408 nullptr);
410 if (!hfile)
411 return -1;
413 DWORD filesize = GetFileSize(hfile, nullptr);
414 if (filesize == 0)
415 return -1;
417 DWORD size = 0;
418 std::unique_ptr<char[]> buff(new char[filesize]);
419 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
421 if (size != filesize)
422 return -1;
424 CString hash;
425 CString ref;
426 for (DWORD i = 0; i < filesize;)
428 hash.Empty();
429 ref.Empty();
430 if (buff[i] == '#' || buff[i] == '^')
432 while (buff[i] != '\n')
434 ++i;
435 if (i == filesize)
436 break;
438 ++i;
441 if (i >= filesize)
442 break;
444 while (buff[i] != ' ')
446 hash.AppendChar(buff[i]);
447 ++i;
448 if (i == filesize)
449 break;
452 ++i;
453 if (i >= filesize)
454 break;
456 while (buff[i] != '\n')
458 ref.AppendChar(buff[i]);
459 ++i;
460 if (i == filesize)
461 break;
464 if (!ref.IsEmpty())
465 m_PackRefMap[ref] = hash;
467 while (buff[i] == '\n')
469 ++i;
470 if (i == filesize)
471 break;
474 return 0;
476 int CGitHeadFileList::ReadHeadHash(const CString& gitdir)
478 CAutoWriteLock lock(m_SharedMutex);
479 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
481 m_HeadFile = m_Gitdir;
482 m_HeadFile += _T("HEAD");
484 if( CGit::GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
485 return -1;
487 CAutoFile hfile = CreateFile(m_HeadFile,
488 GENERIC_READ,
489 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
490 nullptr,
491 OPEN_EXISTING,
492 FILE_ATTRIBUTE_NORMAL,
493 nullptr);
495 if (!hfile)
496 return -1;
498 DWORD size = 0;
499 unsigned char buffer[40];
500 ReadFile(hfile, buffer, 4, &size, nullptr);
501 if (size != 4)
502 return -1;
503 buffer[4] = 0;
504 if (strcmp((const char*)buffer, "ref:") == 0)
506 m_HeadRefFile.Empty();
507 DWORD filesize = GetFileSize(hfile, nullptr);
508 if (filesize < 5)
509 return -1;
511 unsigned char *p = (unsigned char*)malloc(filesize - 4);
512 if (!p)
513 return -1;
515 ReadFile(hfile, p, filesize - 4, &size, nullptr);
516 CGit::StringAppend(&m_HeadRefFile, p, CP_UTF8, filesize - 4);
517 free(p);
519 CString ref = m_HeadRefFile.Trim();
520 int start = 0;
521 ref = ref.Tokenize(_T("\n"), start);
522 m_HeadRefFile = m_Gitdir + m_HeadRefFile;
523 m_HeadRefFile.Replace(_T('/'), _T('\\'));
525 __int64 time;
526 if (CGit::GetFileModifyTime(m_HeadRefFile, &time, nullptr))
528 m_HeadRefFile.Empty();
529 if (GetPackRef(gitdir))
530 return -1;
531 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
532 return -1;
534 m_Head = m_PackRefMap[ref];
535 return 0;
538 CAutoFile href = CreateFile(m_HeadRefFile,
539 GENERIC_READ,
540 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
541 nullptr,
542 OPEN_EXISTING,
543 FILE_ATTRIBUTE_NORMAL,
544 nullptr);
546 if (!href)
548 m_HeadRefFile.Empty();
550 if (GetPackRef(gitdir))
551 return -1;
553 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
554 return -1;
556 m_Head = m_PackRefMap[ref];
557 return 0;
560 ReadFile(href, buffer, 40, &size, nullptr);
561 if (size != 40)
562 return -1;
564 m_Head.ConvertFromStrA((char*)buffer);
566 m_LastModifyTimeRef = time;
568 return 0;
571 ReadFile(hfile, buffer + 4, 40 - 4, &size, nullptr);
572 if (size != 36)
573 return -1;
575 m_HeadRefFile.Empty();
577 m_Head.ConvertFromStrA((char*)buffer);
579 return 0;
582 bool CGitHeadFileList::CheckHeadUpdate()
584 CAutoReadLock lock(m_SharedMutex);
585 if (this->m_HeadFile.IsEmpty())
586 return true;
588 __int64 mtime=0;
590 if (CGit::GetFileModifyTime(m_HeadFile, &mtime))
591 return true;
593 if (mtime != this->m_LastModifyTimeHead)
594 return true;
596 if (!this->m_HeadRefFile.IsEmpty())
598 if (CGit::GetFileModifyTime(m_HeadRefFile, &mtime))
599 return true;
601 if (mtime != this->m_LastModifyTimeRef)
602 return true;
605 if(!this->m_PackRefFile.IsEmpty())
607 if (CGit::GetFileModifyTime(m_PackRefFile, &mtime))
608 return true;
610 if (mtime != this->m_LastModifyTimePackRef)
611 return true;
614 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
615 // So we need to retry again and again until the ref exists - otherwise we will never notice
616 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
617 return true;
619 return false;
622 bool CGitHeadFileList::HeadHashEqualsTreeHash()
624 CAutoReadLock lock(m_SharedMutex);
625 return (m_Head == m_TreeHash);
628 bool CGitHeadFileList::HeadFileIsEmpty()
630 CAutoReadLock lock(m_SharedMutex);
631 return m_HeadFile.IsEmpty();
634 bool CGitHeadFileList::HeadIsEmpty()
636 CAutoReadLock lock(m_SharedMutex);
637 return m_Head.IsEmpty();
640 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
641 const char *pathname, unsigned mode, int /*stage*/, void *context)
643 #define S_IFGITLINK 0160000
645 CGitHeadFileList *p = (CGitHeadFileList*)context;
647 if ((mode & S_IFDIR) && (mode & S_IFMT) != S_IFGITLINK)
648 return READ_TREE_RECURSIVE;
650 size_t cur = p->size();
651 p->resize(p->size() + 1);
652 p->at(cur).m_Hash = sha1;
653 p->at(cur).m_FileName.Empty();
655 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
656 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)pathname, CP_UTF8);
658 p->at(cur).m_FileName.MakeLower();
660 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
662 //p->m_Map[p->at(cur).m_FileName] = cur;
664 if( (mode&S_IFMT) == S_IFGITLINK)
665 return 0;
667 return READ_TREE_RECURSIVE;
670 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)
672 size_t count = git_tree_entrycount(tree);
673 for (size_t i = 0; i < count; ++i)
675 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
676 if (entry == NULL)
677 continue;
678 int mode = git_tree_entry_filemode(entry);
679 if( CallBack(git_tree_entry_id(entry)->id,
680 base,
681 base.GetLength(),
682 git_tree_entry_name(entry),
683 mode,
685 data) == READ_TREE_RECURSIVE
688 if(mode&S_IFDIR)
690 git_object *object = NULL;
691 git_tree_entry_to_object(&object, &repo, entry);
692 if (object == NULL)
693 continue;
694 CStringA parent = base;
695 parent += git_tree_entry_name(entry);
696 parent += "/";
697 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
698 git_object_free(object);
704 return 0;
707 // ReadTree is/must only be executed on an empty list
708 int CGitHeadFileList::ReadTree()
710 CAutoWriteLock lock(m_SharedMutex);
711 ATLASSERT(empty());
713 CAutoRepository repository(m_Gitdir);
714 CAutoCommit commit;
715 CAutoTree tree;
716 bool ret = repository;
717 ret = ret && !git_commit_lookup(commit.GetPointer(), repository, (const git_oid*)m_Head.m_hash);
718 ret = ret && !git_commit_tree(tree.GetPointer(), commit);
719 ret = ret && !ReadTreeRecursive(*repository, tree, "", CGitHeadFileList::CallBack, this);
720 if (!ret)
722 clear();
723 m_LastModifyTimeHead = 0;
724 return -1;
727 std::sort(this->begin(), this->end(), SortTree);
728 m_TreeHash = git_commit_id(commit)->id;
730 return 0;
732 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
734 if (this->m_pExcludeList)
736 git_free_exclude_list(m_pExcludeList);
737 m_pExcludeList=NULL;
739 free(m_buffer);
740 m_buffer = nullptr;
742 this->m_BaseDir.Empty();
743 if (!isGlobal)
745 CString base = file.Mid(projectroot.GetLength() + 1);
746 base.Replace(_T('\\'), _T('/'));
748 int start = base.ReverseFind(_T('/'));
749 if(start > 0)
751 base = base.Left(start);
752 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
756 if (CGit::GetFileModifyTime(file, &m_LastModifyTime))
757 return -1;
759 CAutoFile hfile = CreateFile(file,
760 GENERIC_READ,
761 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
762 nullptr,
763 OPEN_EXISTING,
764 FILE_ATTRIBUTE_NORMAL,
765 nullptr);
767 if (!hfile)
768 return -1 ;
770 DWORD filesize = GetFileSize(hfile, nullptr);
771 if (filesize == INVALID_FILE_SIZE)
772 return -1;
774 m_buffer = new BYTE[filesize + 1];
775 if (!m_buffer)
776 return -1;
778 DWORD size = 0;
779 if (!ReadFile(hfile, m_buffer, filesize, &size, NULL))
781 free(m_buffer);
782 m_buffer = nullptr;
783 return -1;
785 m_buffer[size] = 0;
787 if (git_create_exclude_list(&m_pExcludeList))
789 free(m_buffer);
790 m_buffer = nullptr;
791 return -1;
794 BYTE *p = m_buffer;
795 int line = 0;
796 for (DWORD i = 0; i < size; ++i)
798 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
800 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
801 m_buffer[i] = 0;
803 if (p[0] != '#' && p[0] != 0)
804 git_add_exclude((const char*)p, this->m_BaseDir, m_BaseDir.GetLength(), this->m_pExcludeList, ++line);
806 p = m_buffer + i + 1;
810 if (!line)
812 git_free_exclude_list(m_pExcludeList);
813 m_pExcludeList = nullptr;
814 free(m_buffer);
815 m_buffer = nullptr;
818 return 0;
821 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
822 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, int& type)
824 int pos = patha.ReverseFind('/');
825 const char* base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
827 return IsPathIgnored(patha, base, type);
829 #endif
831 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, const char* base, int& type)
833 if (!m_pExcludeList)
834 return -1; // error or undecided
836 return git_check_excluded_1(patha, patha.GetLength(), base, &type, m_pExcludeList);
839 bool CGitIgnoreList::CheckFileChanged(const CString &path)
841 __int64 time = 0;
843 int ret = CGit::GetFileModifyTime(path, &time);
845 bool cacheExist;
847 CAutoReadLock lock(m_SharedMutex);
848 cacheExist = (m_Map.find(path) != m_Map.end());
851 if (!cacheExist && ret == 0)
853 CAutoWriteLock lock(m_SharedMutex);
854 m_Map[path].m_LastModifyTime = 0;
856 // both cache and file is not exist
857 if ((ret != 0) && (!cacheExist))
858 return false;
860 // file exist but cache miss
861 if ((ret == 0) && (!cacheExist))
862 return true;
864 // file not exist but cache exist
865 if ((ret != 0) && (cacheExist))
867 return true;
869 // file exist and cache exist
872 CAutoReadLock lock(m_SharedMutex);
873 if (m_Map[path].m_LastModifyTime == time)
874 return false;
876 return true;
879 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir, const CString &path, bool isDir)
881 CString 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(gitdir);
955 temp += _T('\\');
956 temp += path;
958 temp.Replace(_T('/'), _T('\\'));
960 if (!isDir)
962 int x = temp.ReverseFind(_T('\\'));
963 if (x >= 2)
964 temp = temp.Left(x);
967 while (!temp.IsEmpty())
969 CString tempOrig = temp;
970 temp += _T("\\.git");
972 if (CGit::GitPathFileExists(temp))
974 CString gitignore = temp;
975 gitignore += _T("ignore");
976 if (CheckFileChanged(gitignore))
977 FetchIgnoreFile(gitdir, gitignore, false);
979 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
980 CString wcglobalgitignore = adminDir;
981 wcglobalgitignore += _T("info\\exclude");
982 if (CheckFileChanged(wcglobalgitignore))
984 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
987 if (CheckAndUpdateCoreExcludefile(adminDir))
989 CString excludesFile;
991 CAutoReadLock lock(m_SharedMutex);
992 excludesFile = m_CoreExcludesfiles[adminDir];
994 if (!excludesFile.IsEmpty())
995 FetchIgnoreFile(gitdir, excludesFile, true);
998 return 0;
1001 temp += _T("ignore");
1002 if (CheckFileChanged(temp))
1003 FetchIgnoreFile(gitdir, temp, false);
1005 int found = 0;
1006 int i;
1007 for (i = temp.GetLength() - 1; i >= 0; --i)
1009 if(temp[i] == _T('\\'))
1010 ++found;
1012 if(found == 2)
1013 break;
1016 temp = temp.Left(i);
1018 return 0;
1020 bool CGitIgnoreList::CheckAndUpdateGitSystemConfigPath(bool force)
1022 // recheck every 30 seconds
1023 if (GetTickCount() - m_dGitSystemConfigPathLastChecked > 30000 || force)
1025 m_dGitSystemConfigPathLastChecked = GetTickCount();
1026 CString gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
1027 if (gitSystemConfigPath != m_sGitSystemConfigPath)
1029 m_sGitSystemConfigPath = gitSystemConfigPath;
1030 return true;
1033 return false;
1035 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1037 CString projectConfig(adminDir);
1038 projectConfig += _T("config");
1039 CString globalConfig = g_Git.GetGitGlobalConfig();
1040 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1042 CAutoWriteLock lock(m_coreExcludefilesSharedMutex);
1043 bool hasChanged = CheckAndUpdateGitSystemConfigPath();
1044 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1045 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1046 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1047 if (!m_sGitSystemConfigPath.IsEmpty())
1048 hasChanged = hasChanged || CheckFileChanged(m_sGitSystemConfigPath);
1050 CString excludesFile;
1052 CAutoReadLock lock2(m_SharedMutex);
1053 excludesFile = m_CoreExcludesfiles[adminDir];
1055 if (!excludesFile.IsEmpty())
1056 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1058 if (!hasChanged)
1059 return false;
1061 CAutoConfig config(true);
1062 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
1063 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
1064 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
1065 if (!m_sGitSystemConfigPath.IsEmpty())
1066 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitSystemConfigPath), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
1067 config.GetString(_T("core.excludesfile"), excludesFile);
1068 if (excludesFile.IsEmpty())
1069 excludesFile = GetWindowsHome() + _T("\\.config\\git\\ignore");
1070 else if (excludesFile.Find(_T("~/")) == 0)
1071 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1073 CAutoWriteLock lockMap(m_SharedMutex);
1074 CGit::GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1075 CGit::GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1076 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1077 m_Map.erase(globalXDGConfig);
1078 CGit::GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1079 if (m_Map[globalConfig].m_LastModifyTime == 0)
1080 m_Map.erase(globalConfig);
1081 if (!m_sGitSystemConfigPath.IsEmpty())
1082 CGit::GetFileModifyTime(m_sGitSystemConfigPath, &m_Map[m_sGitSystemConfigPath].m_LastModifyTime);
1083 if (m_Map[m_sGitSystemConfigPath].m_LastModifyTime == 0 || m_sGitSystemConfigPath.IsEmpty())
1084 m_Map.erase(m_sGitSystemConfigPath);
1085 m_CoreExcludesfiles[adminDir] = excludesFile;
1087 return true;
1089 const CString CGitIgnoreList::GetWindowsHome()
1091 static CString sWindowsHome(g_Git.GetHomeDirectory());
1092 return sWindowsHome;
1094 bool CGitIgnoreList::IsIgnore(CString str, const CString& projectroot, bool isDir)
1096 str.Replace(_T('\\'),_T('/'));
1098 if (!str.IsEmpty() && str[str.GetLength() - 1] == _T('/'))
1099 str = str.Left(str.GetLength() - 1);
1101 int ret;
1102 ret = CheckIgnore(str, projectroot, isDir);
1103 while (ret < 0)
1105 int start = str.ReverseFind(_T('/'));
1106 if(start < 0)
1107 return (ret == 1);
1109 str = str.Left(start);
1110 ret = CheckIgnore(str, projectroot, isDir);
1113 return (ret == 1);
1115 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1117 if (m_Map.find(ignorefile) == m_Map.end())
1118 return -1; // error or undecided
1120 return (m_Map[ignorefile].IsPathIgnored(patha, base, type));
1122 int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, bool isDir)
1124 CString temp = CombinePath(projectroot, path);
1125 temp.Replace(_T('/'), _T('\\'));
1127 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1128 patha.Replace('\\', '/');
1130 int type = 0;
1131 if (isDir)
1133 type = DT_DIR;
1135 // strip directory name
1136 // we do not need to check for a .ignore file inside a directory we might ignore
1137 int i = temp.ReverseFind(_T('\\'));
1138 if (i >= 0)
1139 temp = temp.Left(i);
1141 else
1143 type = DT_REG;
1145 int x = temp.ReverseFind(_T('\\'));
1146 if (x >= 2)
1147 temp = temp.Left(x);
1150 int pos = patha.ReverseFind('/');
1151 const char * base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
1153 int ret = -1;
1155 CAutoReadLock lock(m_SharedMutex);
1156 while (!temp.IsEmpty())
1158 CString tempOrig = temp;
1159 temp += _T("\\.git");
1161 if (CGit::GitPathFileExists(temp))
1163 CString gitignore = temp;
1164 gitignore += _T("ignore");
1165 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1166 break;
1168 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1169 CString wcglobalgitignore = adminDir;
1170 wcglobalgitignore += _T("info\\exclude");
1171 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1172 break;
1174 CString excludesFile = m_CoreExcludesfiles[adminDir];
1175 if (!excludesFile.IsEmpty())
1176 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1178 break;
1181 temp += _T("ignore");
1182 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1183 break;
1185 int found = 0;
1186 int i;
1187 for (i = temp.GetLength() - 1; i >= 0; i--)
1189 if (temp[i] == _T('\\'))
1190 ++found;
1192 if (found == 2)
1193 break;
1196 temp = temp.Left(i);
1199 return ret;
1202 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir, bool readTree /* = true */)
1204 SHARED_TREE_PTR ptr = this->SafeGet(gitdir, true);
1206 if (ptr.get() && !ptr->CheckHeadUpdate() && (!readTree || ptr->HeadHashEqualsTreeHash()))
1207 return false;
1209 ptr = SHARED_TREE_PTR(new CGitHeadFileList);
1210 ptr->ReadHeadHash(gitdir);
1211 if (readTree)
1212 ptr->ReadTree();
1214 this->SafeSet(gitdir, ptr);
1216 return true;
1219 int CGitHeadFileMap::IsUnderVersionControl(const CString& gitdir, CString subpath, bool isDir, bool* isVersion)
1221 if (subpath.IsEmpty())
1223 *isVersion = true;
1224 return 0;
1227 subpath.Replace(_T('\\'), _T('/'));
1228 if (isDir)
1229 subpath += _T('/');
1231 subpath.MakeLower();
1233 CheckHeadAndUpdate(gitdir);
1235 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1237 // Init Repository
1238 if (treeptr->HeadFileIsEmpty())
1240 *isVersion = false;
1241 return 0;
1243 if (treeptr->empty())
1245 *isVersion = false;
1246 return 1;
1249 if (isDir)
1250 *isVersion = (SearchInSortVector(*treeptr, subpath, subpath.GetLength()) >= 0);
1251 else
1252 *isVersion = (SearchInSortVector(*treeptr, subpath, -1) >= 0);
1254 return 0;