Avoid creation of intermediates
[TortoiseGit.git] / src / Git / GitIndex.cpp
blobb9142a5323bfdcfcbb1086bd8b90d60cacc10a5b
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - 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 static CString GetProgramDataGitConfig()
35 if (!((CRegDWORD(L"Software\\TortoiseGit\\CygwinHack", FALSE) == TRUE) || (CRegDWORD(L"Software\\TortoiseGit\\Msys2Hack", FALSE) == TRUE)))
37 CString programdataConfig;
38 if (SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, NULL, SHGFP_TYPE_CURRENT, CStrBuf(programdataConfig, MAX_PATH)) == S_OK && programdataConfig.GetLength() < MAX_PATH - 11) /* 11 = len("\\Git\\config") */
39 return programdataConfig + L"\\Git\\config";
41 return L"";
44 int CGitIndex::Print()
46 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
47 (int)this->m_ModifyTime,
48 this->m_Flags,
49 (LPCTSTR)this->m_IndexHash.ToString(),
50 (LPCTSTR)this->m_FileName);
52 return 0;
55 CGitIndexList::CGitIndexList()
56 : m_bHasConflicts(FALSE)
58 this->m_LastModifyTime = 0;
59 m_critRepoSec.Init();
60 m_iMaxCheckSize = (__int64)CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContentMaxSize"), 10 * 1024) * 1024; // stored in KiB
63 CGitIndexList::~CGitIndexList()
65 m_critRepoSec.Term();
68 static bool SortIndex(const CGitIndex &Item1, const CGitIndex &Item2)
70 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
73 static bool SortTree(const CGitTreeItem &Item1, const CGitTreeItem &Item2)
75 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
78 int CGitIndexList::ReadIndex(CString dgitdir)
80 this->clear();
82 CAutoLocker lock(m_critRepoSec);
83 if (repository.Open(dgitdir))
84 return -1;
86 // add config files
87 CAutoConfig config(true);
89 CString projectConfig = dgitdir + _T("config");
90 CString globalConfig = g_Git.GetGitGlobalConfig();
91 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
92 CString systemConfig(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
93 CString programDataConfig(GetProgramDataGitConfig());
95 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
96 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
97 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
98 if (!systemConfig.IsEmpty())
99 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(systemConfig), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
100 if (!programDataConfig.IsEmpty())
101 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(programDataConfig), GIT_CONFIG_LEVEL_PROGRAMDATA, FALSE);
103 git_repository_set_config(repository, config);
105 CAutoIndex index;
106 // load index in order to enumerate files
107 if (git_repository_index(index.GetPointer(), repository))
109 repository.Free();
110 return -1;
113 m_bHasConflicts = FALSE;
115 size_t ecount = git_index_entrycount(index);
118 resize(ecount);
120 catch (std::bad_alloc ex)
122 repository.Free();
123 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Could not resize index-vector: %s\n", ex.what());
124 return -1;
126 for (size_t i = 0; i < ecount; ++i)
128 const git_index_entry *e = git_index_get_byindex(index, i);
130 this->at(i).m_FileName = CUnicodeUtils::GetUnicode(e->path);
131 this->at(i).m_FileName.MakeLower();
132 this->at(i).m_ModifyTime = e->mtime.seconds;
133 this->at(i).m_Flags = e->flags;
134 this->at(i).m_FlagsExtended = e->flags_extended;
135 this->at(i).m_IndexHash = e->id.id;
136 this->at(i).m_Size = e->file_size;
137 m_bHasConflicts |= GIT_IDXENTRY_STAGE(e);
140 CGit::GetFileModifyTime(dgitdir + _T("index"), &this->m_LastModifyTime);
141 std::sort(this->begin(), this->end(), SortIndex);
143 return 0;
146 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)
148 if (!status)
149 return 0;
151 CString path = pathorg;
152 path.MakeLower();
154 int index = SearchInSortVector(*this, path, -1);
156 if (index < 0)
158 *status = git_wc_status_unversioned;
159 if (pHash)
160 pHash->Empty();
162 if (callback && assumeValid && skipWorktree)
163 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
165 return 0;
168 // skip-worktree has higher priority than assume-valid
169 if (at(index).m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE)
171 *status = git_wc_status_normal;
172 if (skipWorktree)
173 *skipWorktree = true;
175 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
177 *status = git_wc_status_normal;
178 if (assumeValid)
179 *assumeValid = true;
181 else if (filesize != at(index).m_Size)
182 *status = git_wc_status_modified;
183 else if (time == at(index).m_ModifyTime)
184 *status = git_wc_status_normal;
185 else if (repository && filesize < m_iMaxCheckSize)
187 git_oid actual;
188 CStringA fileA = CUnicodeUtils::GetMulti(pathorg, CP_UTF8);
189 m_critRepoSec.Lock(); // prevent concurrent access to repository instance and especially filter-lists
190 if (!git_repository_hashfile(&actual, repository, fileA, GIT_OBJ_BLOB, nullptr) && !git_oid_cmp(&actual, (const git_oid*)at(index).m_IndexHash.m_hash))
192 at(index).m_ModifyTime = time;
193 *status = git_wc_status_normal;
195 else
196 *status = git_wc_status_modified;
197 m_critRepoSec.Unlock();
199 else
200 *status = git_wc_status_modified;
202 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
203 *status = git_wc_status_conflicted;
204 else if (at(index).m_FlagsExtended & GIT_IDXENTRY_INTENT_TO_ADD)
205 *status = git_wc_status_added;
207 if (pHash)
208 *pHash = at(index).m_IndexHash;
210 if (callback && assumeValid && skipWorktree)
211 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
213 return 0;
216 int CGitIndexList::GetStatus(const CString& gitdir, CString path, git_wc_status_kind* status,
217 BOOL IsFull, BOOL /*IsRecursive*/,
218 FILL_STATUS_CALLBACK callback, void *pData,
219 CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
221 __int64 time, filesize = 0;
222 bool isDir = false;
224 if (!status)
225 return 0;
227 git_wc_status_kind dirstatus = git_wc_status_none;
228 int result;
229 if (path.IsEmpty())
230 result = CGit::GetFileModifyTime(gitdir, &time, &isDir);
231 else
232 result = CGit::GetFileModifyTime(CombinePath(gitdir, path), &time, &isDir, &filesize);
234 if (result)
236 *status = git_wc_status_deleted;
237 if (callback && assumeValid && skipWorktree)
238 callback(CombinePath(gitdir, path), git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
240 return 0;
243 if (!isDir)
245 GetFileStatus(gitdir, path, status, time, filesize, callback, pData, pHash, assumeValid, skipWorktree);
246 return 0;
249 if (!path.IsEmpty() && path.Right(1) != _T('\\'))
250 path += _T('\\');
252 int len = path.GetLength();
254 for (auto it = cbegin(), itend = cend(); it != itend; ++it)
256 if (!((*it).m_FileName.GetLength() > len && wcsncmp((*it).m_FileName, path, len) == 0))
257 continue;
259 if (!IsFull)
261 *status = git_wc_status_normal;
262 if (callback)
263 callback(CombinePath(gitdir, path), *status, false, pData, ((*it).m_Flags & GIT_IDXENTRY_VALID) && !((*it).m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE), ((*it).m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE) != 0);
265 return 0;
268 result = CGit::GetFileModifyTime(CombinePath(gitdir, (*it).m_FileName), &time, nullptr, &filesize);
269 if (result)
270 continue;
272 *status = git_wc_status_none;
273 if (assumeValid)
274 *assumeValid = false;
275 if (skipWorktree)
276 *skipWorktree = false;
278 GetFileStatus(gitdir, (*it).m_FileName, status, time, filesize, callback, pData, nullptr, assumeValid, skipWorktree);
280 // 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
281 if (callback && assumeValid && skipWorktree && (*assumeValid || *skipWorktree))
282 callback(CombinePath(gitdir, path), *status, false, pData, *assumeValid, *skipWorktree);
283 if (*status != git_wc_status_none)
285 if (dirstatus == git_wc_status_none)
286 dirstatus = git_wc_status_normal;
287 if (*status != git_wc_status_normal)
288 dirstatus = git_wc_status_modified;
290 } /* End For */
292 if (dirstatus != git_wc_status_none)
293 *status = dirstatus;
294 else
295 *status = git_wc_status_unversioned;
297 if (callback)
298 callback(CombinePath(gitdir, path), *status, false, pData, false, false);
300 return 0;
303 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
305 __int64 time;
307 CString IndexFile = g_AdminDirMap.GetAdminDirConcat(gitdir, _T("index"));
309 if (CGit::GetFileModifyTime(IndexFile, &time))
310 return -1;
312 SHARED_INDEX_PTR pIndex;
313 pIndex = this->SafeGet(gitdir);
315 if (!pIndex)
317 if(isChanged)
318 *isChanged = true;
319 return 0;
322 if (pIndex->m_LastModifyTime == time)
324 if (isChanged)
325 *isChanged = false;
327 else
329 if (isChanged)
330 *isChanged = true;
332 return 0;
335 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
337 SHARED_INDEX_PTR pIndex(new CGitIndexList);
339 if (pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
340 return -1;
342 this->SafeSet(gitdir, pIndex);
344 return 0;
347 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
348 FILL_STATUS_CALLBACK callback, void *pData,
349 CGitHash *pHash,
350 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
352 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
354 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
355 if (pIndex)
356 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
357 else
359 // git working tree has not index
360 *status = git_wc_status_unversioned;
363 return 0;
366 int CGitIndexFileMap::IsUnderVersionControl(const CString& gitdir, CString subpath, bool isDir, bool* isVersion, bool isLoadUpdateIndex)
368 if (subpath.IsEmpty())
370 *isVersion = true;
371 return 0;
374 subpath.Replace(_T('\\'), _T('/'));
375 if (isDir)
376 subpath += _T('/');
378 subpath.MakeLower();
380 CheckAndUpdate(gitdir, isLoadUpdateIndex);
382 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
384 if (pIndex)
386 if (isDir)
387 *isVersion = (SearchInSortVector(*pIndex, subpath, subpath.GetLength()) >= 0);
388 else
389 *isVersion = (SearchInSortVector(*pIndex, subpath, -1) >= 0);
392 return 0;
395 // This method is assumed to be called with m_SharedMutex locked.
396 int CGitHeadFileList::GetPackRef(const CString &gitdir)
398 CString PackRef = g_AdminDirMap.GetAdminDirConcat(gitdir, _T("packed-refs"));
400 __int64 mtime;
401 if (CGit::GetFileModifyTime(PackRef, &mtime))
403 //packed refs is not existed
404 this->m_PackRefFile.Empty();
405 this->m_PackRefMap.clear();
406 return 0;
408 else if(mtime == m_LastModifyTimePackRef)
409 return 0;
410 else
412 this->m_PackRefFile = PackRef;
413 this->m_LastModifyTimePackRef = mtime;
416 m_PackRefMap.clear();
418 CAutoFile hfile = CreateFile(PackRef,
419 GENERIC_READ,
420 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
421 nullptr,
422 OPEN_EXISTING,
423 FILE_ATTRIBUTE_NORMAL,
424 nullptr);
426 if (!hfile)
427 return -1;
429 DWORD filesize = GetFileSize(hfile, nullptr);
430 if (filesize == 0)
431 return -1;
433 DWORD size = 0;
434 auto buff = std::make_unique<char[]>(filesize);
435 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
437 if (size != filesize)
438 return -1;
440 for (DWORD i = 0; i < filesize;)
442 CString hash;
443 CString ref;
444 if (buff[i] == '#' || buff[i] == '^')
446 while (buff[i] != '\n')
448 ++i;
449 if (i == filesize)
450 break;
452 ++i;
455 if (i >= filesize)
456 break;
458 while (buff[i] != ' ')
460 hash.AppendChar(buff[i]);
461 ++i;
462 if (i == filesize)
463 break;
466 ++i;
467 if (i >= filesize)
468 break;
470 while (buff[i] != '\n')
472 ref.AppendChar(buff[i]);
473 ++i;
474 if (i == filesize)
475 break;
478 if (!ref.IsEmpty())
479 m_PackRefMap[ref] = hash;
481 while (buff[i] == '\n')
483 ++i;
484 if (i == filesize)
485 break;
488 return 0;
490 int CGitHeadFileList::ReadHeadHash(const CString& gitdir)
492 CAutoWriteLock lock(m_SharedMutex);
493 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
495 m_HeadFile = m_Gitdir;
496 m_HeadFile += _T("HEAD");
498 if( CGit::GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
499 return -1;
501 CAutoFile hfile = CreateFile(m_HeadFile,
502 GENERIC_READ,
503 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
504 nullptr,
505 OPEN_EXISTING,
506 FILE_ATTRIBUTE_NORMAL,
507 nullptr);
509 if (!hfile)
510 return -1;
512 DWORD size = 0;
513 unsigned char buffer[40];
514 ReadFile(hfile, buffer, 4, &size, nullptr);
515 if (size != 4)
516 return -1;
517 buffer[4] = 0;
518 if (strcmp((const char*)buffer, "ref:") == 0)
520 m_HeadRefFile.Empty();
521 DWORD filesize = GetFileSize(hfile, nullptr);
522 if (filesize < 5)
523 return -1;
525 unsigned char *p = (unsigned char*)malloc(filesize - 4);
526 if (!p)
527 return -1;
529 ReadFile(hfile, p, filesize - 4, &size, nullptr);
530 CGit::StringAppend(&m_HeadRefFile, p, CP_UTF8, filesize - 4);
531 free(p);
533 CString ref = m_HeadRefFile.Trim();
534 int start = 0;
535 ref = ref.Tokenize(_T("\n"), start);
536 m_HeadRefFile = m_Gitdir + m_HeadRefFile;
537 m_HeadRefFile.Replace(_T('/'), _T('\\'));
539 __int64 time;
540 if (CGit::GetFileModifyTime(m_HeadRefFile, &time, nullptr))
542 m_HeadRefFile.Empty();
543 if (GetPackRef(gitdir))
544 return -1;
545 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
546 return -1;
548 m_Head = m_PackRefMap[ref];
549 return 0;
552 CAutoFile href = CreateFile(m_HeadRefFile,
553 GENERIC_READ,
554 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
555 nullptr,
556 OPEN_EXISTING,
557 FILE_ATTRIBUTE_NORMAL,
558 nullptr);
560 if (!href)
562 m_HeadRefFile.Empty();
564 if (GetPackRef(gitdir))
565 return -1;
567 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
568 return -1;
570 m_Head = m_PackRefMap[ref];
571 return 0;
574 ReadFile(href, buffer, 40, &size, nullptr);
575 if (size != 40)
576 return -1;
578 m_Head.ConvertFromStrA((char*)buffer);
580 m_LastModifyTimeRef = time;
582 return 0;
585 ReadFile(hfile, buffer + 4, 40 - 4, &size, nullptr);
586 if (size != 36)
587 return -1;
589 m_HeadRefFile.Empty();
591 m_Head.ConvertFromStrA((char*)buffer);
593 return 0;
596 bool CGitHeadFileList::CheckHeadUpdate()
598 CAutoReadLock lock(m_SharedMutex);
599 if (this->m_HeadFile.IsEmpty())
600 return true;
602 __int64 mtime=0;
604 if (CGit::GetFileModifyTime(m_HeadFile, &mtime))
605 return true;
607 if (mtime != this->m_LastModifyTimeHead)
608 return true;
610 if (!this->m_HeadRefFile.IsEmpty())
612 if (CGit::GetFileModifyTime(m_HeadRefFile, &mtime))
613 return true;
615 if (mtime != this->m_LastModifyTimeRef)
616 return true;
619 if(!this->m_PackRefFile.IsEmpty())
621 if (CGit::GetFileModifyTime(m_PackRefFile, &mtime))
622 return true;
624 if (mtime != this->m_LastModifyTimePackRef)
625 return true;
628 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
629 // So we need to retry again and again until the ref exists - otherwise we will never notice
630 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
631 return true;
633 return false;
636 bool CGitHeadFileList::HeadHashEqualsTreeHash()
638 CAutoReadLock lock(m_SharedMutex);
639 return (m_Head == m_TreeHash);
642 bool CGitHeadFileList::HeadFileIsEmpty()
644 CAutoReadLock lock(m_SharedMutex);
645 return m_HeadFile.IsEmpty();
648 bool CGitHeadFileList::HeadIsEmpty()
650 CAutoReadLock lock(m_SharedMutex);
651 return m_Head.IsEmpty();
654 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
655 const char *pathname, unsigned mode, int /*stage*/, void *context)
657 #define S_IFGITLINK 0160000
659 CGitHeadFileList *p = (CGitHeadFileList*)context;
661 if ((mode & S_IFDIR) && (mode & S_IFMT) != S_IFGITLINK)
662 return READ_TREE_RECURSIVE;
664 size_t cur = p->size();
665 p->resize(p->size() + 1);
666 p->at(cur).m_Hash = sha1;
668 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
669 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)pathname, CP_UTF8);
671 p->at(cur).m_FileName.MakeLower();
673 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
675 //p->m_Map[p->at(cur).m_FileName] = cur;
677 if( (mode&S_IFMT) == S_IFGITLINK)
678 return 0;
680 return READ_TREE_RECURSIVE;
683 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)
685 size_t count = git_tree_entrycount(tree);
686 for (size_t i = 0; i < count; ++i)
688 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
689 if (!entry)
690 continue;
691 int mode = git_tree_entry_filemode(entry);
692 if( CallBack(git_tree_entry_id(entry)->id,
693 base,
694 base.GetLength(),
695 git_tree_entry_name(entry),
696 mode,
698 data) == READ_TREE_RECURSIVE
701 if(mode&S_IFDIR)
703 git_object* object = nullptr;
704 git_tree_entry_to_object(&object, &repo, entry);
705 if (!object)
706 continue;
707 CStringA parent = base;
708 parent += git_tree_entry_name(entry);
709 parent += "/";
710 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
711 git_object_free(object);
717 return 0;
720 // ReadTree is/must only be executed on an empty list
721 int CGitHeadFileList::ReadTree()
723 CAutoWriteLock lock(m_SharedMutex);
724 ATLASSERT(empty());
726 CAutoRepository repository(m_Gitdir);
727 CAutoCommit commit;
728 CAutoTree tree;
729 bool ret = repository;
730 ret = ret && !git_commit_lookup(commit.GetPointer(), repository, (const git_oid*)m_Head.m_hash);
731 ret = ret && !git_commit_tree(tree.GetPointer(), commit);
734 ret = ret && !ReadTreeRecursive(*repository, tree, "", CGitHeadFileList::CallBack, this);
736 catch (std::bad_alloc ex)
738 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Catched exception inside ReadTreeRecursive: %s\n", ex.what());
739 return -1;
741 if (!ret)
743 clear();
744 m_LastModifyTimeHead = 0;
745 return -1;
748 std::sort(this->begin(), this->end(), SortTree);
749 m_TreeHash = git_commit_id(commit)->id;
751 return 0;
753 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
755 if (this->m_pExcludeList)
757 git_free_exclude_list(m_pExcludeList);
758 m_pExcludeList = nullptr;
760 free(m_buffer);
761 m_buffer = nullptr;
763 this->m_BaseDir.Empty();
764 if (!isGlobal)
766 CString base = file.Mid(projectroot.GetLength() + 1);
767 base.Replace(_T('\\'), _T('/'));
769 int start = base.ReverseFind(_T('/'));
770 if(start > 0)
772 base.Truncate(start);
773 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
777 if (CGit::GetFileModifyTime(file, &m_LastModifyTime))
778 return -1;
780 CAutoFile hfile = CreateFile(file,
781 GENERIC_READ,
782 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
783 nullptr,
784 OPEN_EXISTING,
785 FILE_ATTRIBUTE_NORMAL,
786 nullptr);
788 if (!hfile)
789 return -1 ;
791 DWORD filesize = GetFileSize(hfile, nullptr);
792 if (filesize == INVALID_FILE_SIZE)
793 return -1;
795 m_buffer = new BYTE[filesize + 1];
796 if (!m_buffer)
797 return -1;
799 DWORD size = 0;
800 if (!ReadFile(hfile, m_buffer, filesize, &size, nullptr))
802 free(m_buffer);
803 m_buffer = nullptr;
804 return -1;
806 m_buffer[size] = 0;
808 if (git_create_exclude_list(&m_pExcludeList))
810 free(m_buffer);
811 m_buffer = nullptr;
812 return -1;
815 BYTE *p = m_buffer;
816 int line = 0;
817 for (DWORD i = 0; i < size; ++i)
819 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
821 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
822 m_buffer[i] = 0;
824 if (p[0] != '#' && p[0] != 0)
825 git_add_exclude((const char*)p, this->m_BaseDir, m_BaseDir.GetLength(), this->m_pExcludeList, ++line);
827 p = m_buffer + i + 1;
831 if (!line)
833 git_free_exclude_list(m_pExcludeList);
834 m_pExcludeList = nullptr;
835 free(m_buffer);
836 m_buffer = nullptr;
839 return 0;
842 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
843 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, int& type)
845 int pos = patha.ReverseFind('/');
846 const char* base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
848 return IsPathIgnored(patha, base, type);
850 #endif
852 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, const char* base, int& type)
854 if (!m_pExcludeList)
855 return -1; // error or undecided
857 return git_check_excluded_1(patha, patha.GetLength(), base, &type, m_pExcludeList);
860 bool CGitIgnoreList::CheckFileChanged(const CString &path)
862 __int64 time = 0;
864 int ret = CGit::GetFileModifyTime(path, &time);
866 bool cacheExist;
868 CAutoReadLock lock(m_SharedMutex);
869 cacheExist = (m_Map.find(path) != m_Map.end());
872 if (!cacheExist && ret == 0)
874 CAutoWriteLock lock(m_SharedMutex);
875 m_Map[path].m_LastModifyTime = 0;
877 // both cache and file is not exist
878 if ((ret != 0) && (!cacheExist))
879 return false;
881 // file exist but cache miss
882 if ((ret == 0) && (!cacheExist))
883 return true;
885 // file not exist but cache exist
886 if ((ret != 0) && (cacheExist))
887 return true;
888 // file exist and cache exist
891 CAutoReadLock lock(m_SharedMutex);
892 if (m_Map[path].m_LastModifyTime == time)
893 return false;
895 return true;
898 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir, const CString &path, bool isDir)
900 CString temp(gitdir);
901 temp += _T('\\');
902 temp += path;
904 temp.Replace(_T('/'), _T('\\'));
906 if (!isDir)
908 int x = temp.ReverseFind(_T('\\'));
909 if (x >= 2)
910 temp.Truncate(x);
913 while(!temp.IsEmpty())
915 CString tempOrig = temp;
916 temp += _T("\\.git");
918 if (CGit::GitPathFileExists(temp))
920 CString gitignore=temp;
921 gitignore += _T("ignore");
922 if (CheckFileChanged(gitignore))
923 return true;
925 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
926 CString wcglobalgitignore = adminDir + _T("info\\exclude");
927 if (CheckFileChanged(wcglobalgitignore))
928 return true;
930 if (CheckAndUpdateCoreExcludefile(adminDir))
931 return true;
933 return false;
936 temp += _T("ignore");
937 if (CheckFileChanged(temp))
938 return true;
940 int found=0;
941 int i;
942 for (i = temp.GetLength() - 1; i >= 0; --i)
944 if(temp[i] == _T('\\'))
945 ++found;
947 if(found == 2)
948 break;
951 temp.Truncate(i);
953 return true;
956 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
958 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
960 CAutoWriteLock lock(m_SharedMutex);
961 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
963 else
965 CAutoWriteLock lock(m_SharedMutex);
966 m_Map.erase(gitignore);
968 return 0;
971 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir, const CString &path, bool isDir)
973 CString temp(gitdir);
974 temp += _T('\\');
975 temp += path;
977 temp.Replace(_T('/'), _T('\\'));
979 if (!isDir)
981 int x = temp.ReverseFind(_T('\\'));
982 if (x >= 2)
983 temp.Truncate(x);
986 while (!temp.IsEmpty())
988 CString tempOrig = temp;
989 temp += _T("\\.git");
991 if (CGit::GitPathFileExists(temp))
993 CString gitignore = temp;
994 gitignore += _T("ignore");
995 if (CheckFileChanged(gitignore))
996 FetchIgnoreFile(gitdir, gitignore, false);
998 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
999 CString wcglobalgitignore = adminDir;
1000 wcglobalgitignore += _T("info\\exclude");
1001 if (CheckFileChanged(wcglobalgitignore))
1003 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1006 if (CheckAndUpdateCoreExcludefile(adminDir))
1008 CString excludesFile;
1010 CAutoReadLock lock(m_SharedMutex);
1011 excludesFile = m_CoreExcludesfiles[adminDir];
1013 if (!excludesFile.IsEmpty())
1014 FetchIgnoreFile(gitdir, excludesFile, true);
1017 return 0;
1020 temp += _T("ignore");
1021 if (CheckFileChanged(temp))
1022 FetchIgnoreFile(gitdir, temp, false);
1024 int found = 0;
1025 int i;
1026 for (i = temp.GetLength() - 1; i >= 0; --i)
1028 if(temp[i] == _T('\\'))
1029 ++found;
1031 if(found == 2)
1032 break;
1035 temp.Truncate(i);
1037 return 0;
1039 bool CGitIgnoreList::CheckAndUpdateGitSystemConfigPath(bool force)
1041 if (force)
1042 m_sGitProgramDataConfigPath = GetProgramDataGitConfig();
1043 // recheck every 30 seconds
1044 if (GetTickCount64() - m_dGitSystemConfigPathLastChecked > 30000UL || force)
1046 m_dGitSystemConfigPathLastChecked = GetTickCount64();
1047 CString gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
1048 if (gitSystemConfigPath != m_sGitSystemConfigPath)
1050 m_sGitSystemConfigPath = gitSystemConfigPath;
1051 return true;
1054 return false;
1056 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1058 CString projectConfig(adminDir);
1059 projectConfig += _T("config");
1060 CString globalConfig = g_Git.GetGitGlobalConfig();
1061 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1063 CAutoWriteLock lock(m_coreExcludefilesSharedMutex);
1064 bool hasChanged = CheckAndUpdateGitSystemConfigPath();
1065 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1066 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1067 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1068 if (!m_sGitProgramDataConfigPath.IsEmpty())
1069 hasChanged = hasChanged || CheckFileChanged(m_sGitProgramDataConfigPath);
1070 if (!m_sGitSystemConfigPath.IsEmpty())
1071 hasChanged = hasChanged || CheckFileChanged(m_sGitSystemConfigPath);
1073 CString excludesFile;
1075 CAutoReadLock lock2(m_SharedMutex);
1076 excludesFile = m_CoreExcludesfiles[adminDir];
1078 if (!excludesFile.IsEmpty())
1079 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1081 if (!hasChanged)
1082 return false;
1084 CAutoConfig config(true);
1085 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
1086 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
1087 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
1088 if (!m_sGitSystemConfigPath.IsEmpty())
1089 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitSystemConfigPath), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
1090 if (!m_sGitProgramDataConfigPath.IsEmpty())
1091 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitProgramDataConfigPath), GIT_CONFIG_LEVEL_PROGRAMDATA, FALSE);
1093 config.GetString(_T("core.excludesfile"), excludesFile);
1094 if (excludesFile.IsEmpty())
1095 excludesFile = GetWindowsHome() + _T("\\.config\\git\\ignore");
1096 else if (CStringUtils::StartsWith(excludesFile, L"~/"))
1097 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1099 CAutoWriteLock lockMap(m_SharedMutex);
1100 CGit::GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1101 CGit::GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1102 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1103 m_Map.erase(globalXDGConfig);
1104 CGit::GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1105 if (m_Map[globalConfig].m_LastModifyTime == 0)
1106 m_Map.erase(globalConfig);
1107 if (!m_sGitSystemConfigPath.IsEmpty())
1108 CGit::GetFileModifyTime(m_sGitSystemConfigPath, &m_Map[m_sGitSystemConfigPath].m_LastModifyTime);
1109 if (m_Map[m_sGitSystemConfigPath].m_LastModifyTime == 0 || m_sGitSystemConfigPath.IsEmpty())
1110 m_Map.erase(m_sGitSystemConfigPath);
1111 if (!m_sGitProgramDataConfigPath.IsEmpty())
1112 CGit::GetFileModifyTime(m_sGitProgramDataConfigPath, &m_Map[m_sGitProgramDataConfigPath].m_LastModifyTime);
1113 if (m_Map[m_sGitProgramDataConfigPath].m_LastModifyTime == 0 || m_sGitProgramDataConfigPath.IsEmpty())
1114 m_Map.erase(m_sGitProgramDataConfigPath);
1115 m_CoreExcludesfiles[adminDir] = excludesFile;
1117 return true;
1119 const CString CGitIgnoreList::GetWindowsHome()
1121 static CString sWindowsHome(g_Git.GetHomeDirectory());
1122 return sWindowsHome;
1124 bool CGitIgnoreList::IsIgnore(CString str, const CString& projectroot, bool isDir)
1126 str.Replace(_T('\\'),_T('/'));
1128 if (!str.IsEmpty() && str[str.GetLength() - 1] == _T('/'))
1129 str.Truncate(str.GetLength() - 1);
1131 int ret;
1132 ret = CheckIgnore(str, projectroot, isDir);
1133 while (ret < 0)
1135 int start = str.ReverseFind(_T('/'));
1136 if(start < 0)
1137 return (ret == 1);
1139 str.Truncate(start);
1140 ret = CheckIgnore(str, projectroot, isDir);
1143 return (ret == 1);
1145 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1147 if (m_Map.find(ignorefile) == m_Map.end())
1148 return -1; // error or undecided
1150 return (m_Map[ignorefile].IsPathIgnored(patha, base, type));
1152 int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, bool isDir)
1154 CString temp = CombinePath(projectroot, path);
1155 temp.Replace(_T('/'), _T('\\'));
1157 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1158 patha.Replace('\\', '/');
1160 int type = 0;
1161 if (isDir)
1163 type = DT_DIR;
1165 // strip directory name
1166 // we do not need to check for a .ignore file inside a directory we might ignore
1167 int i = temp.ReverseFind(_T('\\'));
1168 if (i >= 0)
1169 temp.Truncate(i);
1171 else
1173 type = DT_REG;
1175 int x = temp.ReverseFind(_T('\\'));
1176 if (x >= 2)
1177 temp.Truncate(x);
1180 int pos = patha.ReverseFind('/');
1181 const char * base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
1183 int ret = -1;
1185 CAutoReadLock lock(m_SharedMutex);
1186 while (!temp.IsEmpty())
1188 CString tempOrig = temp;
1189 temp += _T("\\.git");
1191 if (CGit::GitPathFileExists(temp))
1193 CString gitignore = temp;
1194 gitignore += _T("ignore");
1195 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1196 break;
1198 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1199 CString wcglobalgitignore = adminDir;
1200 wcglobalgitignore += _T("info\\exclude");
1201 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1202 break;
1204 CString excludesFile = m_CoreExcludesfiles[adminDir];
1205 if (!excludesFile.IsEmpty())
1206 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1208 break;
1211 temp += _T("ignore");
1212 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1213 break;
1215 int found = 0;
1216 int i;
1217 for (i = temp.GetLength() - 1; i >= 0; i--)
1219 if (temp[i] == _T('\\'))
1220 ++found;
1222 if (found == 2)
1223 break;
1226 temp.Truncate(i);
1229 return ret;
1232 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir, bool readTree /* = true */)
1234 SHARED_TREE_PTR ptr = this->SafeGet(gitdir, true);
1236 if (ptr.get() && !ptr->CheckHeadUpdate() && (!readTree || ptr->HeadHashEqualsTreeHash()))
1237 return false;
1239 ptr = SHARED_TREE_PTR(new CGitHeadFileList);
1240 ptr->ReadHeadHash(gitdir);
1241 if (readTree)
1242 ptr->ReadTree();
1244 this->SafeSet(gitdir, ptr);
1246 return true;
1249 int CGitHeadFileMap::IsUnderVersionControl(const CString& gitdir, CString subpath, bool isDir, bool* isVersion)
1251 if (subpath.IsEmpty())
1253 *isVersion = true;
1254 return 0;
1257 subpath.Replace(_T('\\'), _T('/'));
1258 if (isDir)
1259 subpath += _T('/');
1261 subpath.MakeLower();
1263 CheckHeadAndUpdate(gitdir);
1265 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1267 // Init Repository
1268 if (treeptr->HeadFileIsEmpty())
1270 *isVersion = false;
1271 return 0;
1273 if (treeptr->empty())
1275 *isVersion = false;
1276 return 1;
1279 if (isDir)
1280 *isVersion = (SearchInSortVector(*treeptr, subpath, subpath.GetLength()) >= 0);
1281 else
1282 *isVersion = (SearchInSortVector(*treeptr, subpath, -1) >= 0);
1284 return 0;