CGitIgnoreList: Deduplicate code for check ignore files changed and reload ignore...
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob039ce2ab58766cd6a89c6717acd0a4964b55100f
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 wprintf(L"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(L"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 + L"config";
90 CString globalConfig = g_Git.GetGitGlobalConfig();
91 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
92 CString systemConfig(CRegString(REG_SYSTEM_GITCONFIGPATH, L"", 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 (const 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 auto& item = (*this)[i];
131 item.m_FileName = CUnicodeUtils::GetUnicode(e->path);
132 item.m_FileName.MakeLower();
133 item.m_ModifyTime = e->mtime.seconds;
134 item.m_Flags = e->flags;
135 item.m_FlagsExtended = e->flags_extended;
136 item.m_IndexHash = e->id.id;
137 item.m_Size = e->file_size;
138 m_bHasConflicts |= GIT_IDXENTRY_STAGE(e);
141 CGit::GetFileModifyTime(dgitdir + L"index", &m_LastModifyTime);
142 std::sort(this->begin(), this->end(), SortIndex);
144 return 0;
147 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)
149 if (!status)
150 return 0;
152 CString path = pathorg;
153 path.MakeLower();
155 size_t index = SearchInSortVector(*this, path, -1);
157 if (index == NPOS)
159 *status = git_wc_status_unversioned;
160 if (pHash)
161 pHash->Empty();
163 if (callback && assumeValid && skipWorktree)
164 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
166 return 0;
169 auto& entry = (*this)[index];
170 // skip-worktree has higher priority than assume-valid
171 if (entry.m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE)
173 *status = git_wc_status_normal;
174 if (skipWorktree)
175 *skipWorktree = true;
177 else if (entry.m_Flags & GIT_IDXENTRY_VALID)
179 *status = git_wc_status_normal;
180 if (assumeValid)
181 *assumeValid = true;
183 else if (filesize != entry.m_Size)
184 *status = git_wc_status_modified;
185 else if (time == entry.m_ModifyTime)
186 *status = git_wc_status_normal;
187 else if (repository && filesize < m_iMaxCheckSize)
189 git_oid actual;
190 CStringA fileA = CUnicodeUtils::GetMulti(pathorg, CP_UTF8);
191 m_critRepoSec.Lock(); // prevent concurrent access to repository instance and especially filter-lists
192 if (!git_repository_hashfile(&actual, repository, fileA, GIT_OBJ_BLOB, nullptr) && !git_oid_cmp(&actual, (const git_oid*)entry.m_IndexHash.m_hash))
194 entry.m_ModifyTime = time;
195 *status = git_wc_status_normal;
197 else
198 *status = git_wc_status_modified;
199 m_critRepoSec.Unlock();
201 else
202 *status = git_wc_status_modified;
204 if (entry.m_Flags & GIT_IDXENTRY_STAGEMASK)
205 *status = git_wc_status_conflicted;
206 else if (entry.m_FlagsExtended & GIT_IDXENTRY_INTENT_TO_ADD)
207 *status = git_wc_status_added;
209 if (pHash)
210 *pHash = entry.m_IndexHash;
212 if (callback && assumeValid && skipWorktree)
213 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
215 return 0;
218 int CGitIndexList::GetStatus(const CString& gitdir, CString path, git_wc_status_kind* status,
219 BOOL IsFull, BOOL /*IsRecursive*/,
220 FILL_STATUS_CALLBACK callback, void *pData,
221 CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
223 __int64 time, filesize = 0;
224 bool isDir = false;
226 if (!status)
227 return 0;
229 git_wc_status_kind dirstatus = git_wc_status_none;
230 int result;
231 if (path.IsEmpty())
232 result = CGit::GetFileModifyTime(gitdir, &time, &isDir);
233 else
234 result = CGit::GetFileModifyTime(CombinePath(gitdir, path), &time, &isDir, &filesize);
236 if (result)
238 *status = git_wc_status_deleted;
239 if (callback && assumeValid && skipWorktree)
240 callback(CombinePath(gitdir, path), git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
242 return 0;
245 if (!isDir)
247 GetFileStatus(gitdir, path, status, time, filesize, callback, pData, pHash, assumeValid, skipWorktree);
248 return 0;
251 if (!path.IsEmpty() && !CStringUtils::EndsWith(path, L'\\'))
252 path += L'\\';
254 int len = path.GetLength();
256 for (auto it = cbegin(), itend = cend(); it != itend; ++it)
258 auto& entry = *it;
259 if (!(entry.m_FileName.GetLength() > len && wcsncmp(entry.m_FileName, path, len) == 0))
260 continue;
262 if (!IsFull)
264 *status = git_wc_status_normal;
265 if (callback)
266 callback(CombinePath(gitdir, path), *status, false, pData, (entry.m_Flags & GIT_IDXENTRY_VALID) && !(entry.m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE), (entry.m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE) != 0);
268 return 0;
271 result = CGit::GetFileModifyTime(CombinePath(gitdir, entry.m_FileName), &time, nullptr, &filesize);
272 if (result)
273 continue;
275 *status = git_wc_status_none;
276 if (assumeValid)
277 *assumeValid = false;
278 if (skipWorktree)
279 *skipWorktree = false;
281 GetFileStatus(gitdir, entry.m_FileName, status, time, filesize, callback, pData, nullptr, assumeValid, skipWorktree);
283 // 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
284 if (callback && assumeValid && skipWorktree && (*assumeValid || *skipWorktree))
285 callback(CombinePath(gitdir, path), *status, false, pData, *assumeValid, *skipWorktree);
286 if (*status != git_wc_status_none)
288 if (dirstatus == git_wc_status_none)
289 dirstatus = git_wc_status_normal;
290 if (*status != git_wc_status_normal)
291 dirstatus = git_wc_status_modified;
293 } /* End For */
295 if (dirstatus != git_wc_status_none)
296 *status = dirstatus;
297 else
298 *status = git_wc_status_unversioned;
300 if (callback)
301 callback(CombinePath(gitdir, path), *status, false, pData, false, false);
303 return 0;
306 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
308 __int64 time;
310 CString IndexFile = g_AdminDirMap.GetAdminDirConcat(gitdir, L"index");
312 if (CGit::GetFileModifyTime(IndexFile, &time))
313 return -1;
315 SHARED_INDEX_PTR pIndex;
316 pIndex = this->SafeGet(gitdir);
318 if (!pIndex)
320 if(isChanged)
321 *isChanged = true;
322 return 0;
325 if (pIndex->m_LastModifyTime == time)
327 if (isChanged)
328 *isChanged = false;
330 else
332 if (isChanged)
333 *isChanged = true;
335 return 0;
338 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
340 SHARED_INDEX_PTR pIndex = std::make_shared<CGitIndexList>();
342 if (pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
343 return -1;
345 this->SafeSet(gitdir, pIndex);
347 return 0;
350 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
351 FILL_STATUS_CALLBACK callback, void *pData,
352 CGitHash *pHash,
353 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
355 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
357 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
358 if (pIndex)
359 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
360 else
362 // git working tree has not index
363 *status = git_wc_status_unversioned;
366 return 0;
369 int CGitIndexFileMap::IsUnderVersionControl(const CString& gitdir, CString subpath, bool isDir, bool* isVersion, bool isLoadUpdateIndex)
371 if (subpath.IsEmpty())
373 *isVersion = true;
374 return 0;
377 subpath.Replace(L'\\', L'/');
378 if (isDir)
379 subpath += L'/';
381 subpath.MakeLower();
383 CheckAndUpdate(gitdir, isLoadUpdateIndex);
385 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
387 if (pIndex)
389 if (isDir)
390 *isVersion = (SearchInSortVector(*pIndex, subpath, subpath.GetLength()) != NPOS);
391 else
392 *isVersion = (SearchInSortVector(*pIndex, subpath, -1) != NPOS);
395 return 0;
398 // This method is assumed to be called with m_SharedMutex locked.
399 int CGitHeadFileList::GetPackRef(const CString &gitdir)
401 CString PackRef = g_AdminDirMap.GetAdminDirConcat(gitdir, L"packed-refs");
403 __int64 mtime;
404 if (CGit::GetFileModifyTime(PackRef, &mtime))
406 //packed refs is not existed
407 this->m_PackRefFile.Empty();
408 this->m_PackRefMap.clear();
409 return 0;
411 else if(mtime == m_LastModifyTimePackRef)
412 return 0;
413 else
415 this->m_PackRefFile = PackRef;
416 this->m_LastModifyTimePackRef = mtime;
419 m_PackRefMap.clear();
421 CAutoFile hfile = CreateFile(PackRef,
422 GENERIC_READ,
423 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
424 nullptr,
425 OPEN_EXISTING,
426 FILE_ATTRIBUTE_NORMAL,
427 nullptr);
429 if (!hfile)
430 return -1;
432 DWORD filesize = GetFileSize(hfile, nullptr);
433 if (filesize == 0)
434 return -1;
436 DWORD size = 0;
437 auto buff = std::make_unique<char[]>(filesize);
438 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
440 if (size != filesize)
441 return -1;
443 for (DWORD i = 0; i < filesize;)
445 CString hash;
446 CString ref;
447 if (buff[i] == '#' || buff[i] == '^')
449 while (buff[i] != '\n')
451 ++i;
452 if (i == filesize)
453 break;
455 ++i;
458 if (i >= filesize)
459 break;
461 while (buff[i] != ' ')
463 hash.AppendChar(buff[i]);
464 ++i;
465 if (i == filesize)
466 break;
469 ++i;
470 if (i >= filesize)
471 break;
473 while (buff[i] != '\n')
475 ref.AppendChar(buff[i]);
476 ++i;
477 if (i == filesize)
478 break;
481 if (!ref.IsEmpty())
482 m_PackRefMap[ref] = hash;
484 while (buff[i] == '\n')
486 ++i;
487 if (i == filesize)
488 break;
491 return 0;
493 int CGitHeadFileList::ReadHeadHash(const CString& gitdir)
495 CAutoWriteLock lock(m_SharedMutex);
496 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
498 m_HeadFile = m_Gitdir;
499 m_HeadFile += L"HEAD";
501 if( CGit::GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
502 return -1;
504 CAutoFile hfile = CreateFile(m_HeadFile,
505 GENERIC_READ,
506 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
507 nullptr,
508 OPEN_EXISTING,
509 FILE_ATTRIBUTE_NORMAL,
510 nullptr);
512 if (!hfile)
513 return -1;
515 DWORD size = 0;
516 unsigned char buffer[40];
517 ReadFile(hfile, buffer, 4, &size, nullptr);
518 if (size != 4)
519 return -1;
520 buffer[4] = 0;
521 if (strcmp((const char*)buffer, "ref:") == 0)
523 m_HeadRefFile.Empty();
524 DWORD filesize = GetFileSize(hfile, nullptr);
525 if (filesize < 5)
526 return -1;
528 unsigned char *p = (unsigned char*)malloc(filesize - 4);
529 if (!p)
530 return -1;
532 ReadFile(hfile, p, filesize - 4, &size, nullptr);
533 CGit::StringAppend(&m_HeadRefFile, p, CP_UTF8, filesize - 4);
534 free(p);
536 CString ref = m_HeadRefFile.Trim();
537 int start = 0;
538 ref = ref.Tokenize(L"\n", start);
539 m_HeadRefFile = m_Gitdir + m_HeadRefFile;
540 m_HeadRefFile.Replace(L'/', L'\\');
542 __int64 time;
543 if (CGit::GetFileModifyTime(m_HeadRefFile, &time, nullptr))
545 m_HeadRefFile.Empty();
546 if (GetPackRef(gitdir))
547 return -1;
548 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
549 return -1;
551 m_Head = m_PackRefMap[ref];
552 return 0;
555 CAutoFile href = CreateFile(m_HeadRefFile,
556 GENERIC_READ,
557 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
558 nullptr,
559 OPEN_EXISTING,
560 FILE_ATTRIBUTE_NORMAL,
561 nullptr);
563 if (!href)
565 m_HeadRefFile.Empty();
567 if (GetPackRef(gitdir))
568 return -1;
570 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
571 return -1;
573 m_Head = m_PackRefMap[ref];
574 return 0;
577 ReadFile(href, buffer, 40, &size, nullptr);
578 if (size != 40)
579 return -1;
581 m_Head.ConvertFromStrA((char*)buffer);
583 m_LastModifyTimeRef = time;
585 return 0;
588 ReadFile(hfile, buffer + 4, 40 - 4, &size, nullptr);
589 if (size != 36)
590 return -1;
592 m_HeadRefFile.Empty();
594 m_Head.ConvertFromStrA((char*)buffer);
596 return 0;
599 bool CGitHeadFileList::CheckHeadUpdate()
601 CAutoReadLock lock(m_SharedMutex);
602 if (this->m_HeadFile.IsEmpty())
603 return true;
605 __int64 mtime=0;
607 if (CGit::GetFileModifyTime(m_HeadFile, &mtime))
608 return true;
610 if (mtime != this->m_LastModifyTimeHead)
611 return true;
613 if (!this->m_HeadRefFile.IsEmpty())
615 if (CGit::GetFileModifyTime(m_HeadRefFile, &mtime))
616 return true;
618 if (mtime != this->m_LastModifyTimeRef)
619 return true;
622 if(!this->m_PackRefFile.IsEmpty())
624 if (CGit::GetFileModifyTime(m_PackRefFile, &mtime))
625 return true;
627 if (mtime != this->m_LastModifyTimePackRef)
628 return true;
631 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
632 // So we need to retry again and again until the ref exists - otherwise we will never notice
633 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
634 return true;
636 return false;
639 bool CGitHeadFileList::HeadHashEqualsTreeHash()
641 CAutoReadLock lock(m_SharedMutex);
642 return (m_Head == m_TreeHash);
645 bool CGitHeadFileList::HeadFileIsEmpty()
647 CAutoReadLock lock(m_SharedMutex);
648 return m_HeadFile.IsEmpty();
651 bool CGitHeadFileList::HeadIsEmpty()
653 CAutoReadLock lock(m_SharedMutex);
654 return m_Head.IsEmpty();
657 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
658 const char *pathname, unsigned mode, int /*stage*/, void *context)
660 #define S_IFGITLINK 0160000
662 CGitHeadFileList* p = reinterpret_cast<CGitHeadFileList*>(context);
664 if ((mode & S_IFDIR) && (mode & S_IFMT) != S_IFGITLINK)
665 return READ_TREE_RECURSIVE;
667 CGitTreeItem item;
668 item.m_Hash = sha1;
669 CGit::StringAppend(&item.m_FileName, (BYTE*)base, CP_UTF8, baselen);
670 CGit::StringAppend(&item.m_FileName, (BYTE*)pathname, CP_UTF8);
672 item.m_FileName.MakeLower();
674 p->push_back(item);
676 if( (mode&S_IFMT) == S_IFGITLINK)
677 return 0;
679 return READ_TREE_RECURSIVE;
682 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)
684 size_t count = git_tree_entrycount(tree);
685 for (size_t i = 0; i < count; ++i)
687 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
688 if (!entry)
689 continue;
690 int mode = git_tree_entry_filemode(entry);
691 if( CallBack(git_tree_entry_id(entry)->id,
692 base,
693 base.GetLength(),
694 git_tree_entry_name(entry),
695 mode,
697 data) == READ_TREE_RECURSIVE
700 if(mode&S_IFDIR)
702 git_object* object = nullptr;
703 git_tree_entry_to_object(&object, &repo, entry);
704 if (!object)
705 continue;
706 CStringA parent = base;
707 parent += git_tree_entry_name(entry);
708 parent += "/";
709 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
710 git_object_free(object);
716 return 0;
719 // ReadTree is/must only be executed on an empty list
720 int CGitHeadFileList::ReadTree()
722 CAutoWriteLock lock(m_SharedMutex);
723 ATLASSERT(empty());
725 CAutoRepository repository(m_Gitdir);
726 CAutoCommit commit;
727 CAutoTree tree;
728 bool ret = repository;
729 ret = ret && !git_commit_lookup(commit.GetPointer(), repository, (const git_oid*)m_Head.m_hash);
730 ret = ret && !git_commit_tree(tree.GetPointer(), commit);
733 ret = ret && !ReadTreeRecursive(*repository, tree, "", CGitHeadFileList::CallBack, this);
735 catch (const std::bad_alloc& ex)
737 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Catched exception inside ReadTreeRecursive: %s\n", ex.what());
738 return -1;
740 if (!ret)
742 clear();
743 m_LastModifyTimeHead = 0;
744 return -1;
747 std::sort(this->begin(), this->end(), SortTree);
748 m_TreeHash = git_commit_id(commit)->id;
750 return 0;
752 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
754 if (this->m_pExcludeList)
756 git_free_exclude_list(m_pExcludeList);
757 m_pExcludeList = nullptr;
759 free(m_buffer);
760 m_buffer = nullptr;
762 this->m_BaseDir.Empty();
763 if (!isGlobal)
765 CString base = file.Mid(projectroot.GetLength() + 1);
766 base.Replace(L'\\', L'/');
768 int start = base.ReverseFind(L'/');
769 if(start > 0)
771 base.Truncate(start);
772 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
776 if (CGit::GetFileModifyTime(file, &m_LastModifyTime))
777 return -1;
779 CAutoFile hfile = CreateFile(file,
780 GENERIC_READ,
781 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
782 nullptr,
783 OPEN_EXISTING,
784 FILE_ATTRIBUTE_NORMAL,
785 nullptr);
787 if (!hfile)
788 return -1 ;
790 DWORD filesize = GetFileSize(hfile, nullptr);
791 if (filesize == INVALID_FILE_SIZE)
792 return -1;
794 m_buffer = new BYTE[filesize + 1];
795 if (!m_buffer)
796 return -1;
798 DWORD size = 0;
799 if (!ReadFile(hfile, m_buffer, filesize, &size, nullptr))
801 free(m_buffer);
802 m_buffer = nullptr;
803 return -1;
805 m_buffer[size] = 0;
807 if (git_create_exclude_list(&m_pExcludeList))
809 free(m_buffer);
810 m_buffer = nullptr;
811 return -1;
814 BYTE *p = m_buffer;
815 int line = 0;
816 for (DWORD i = 0; i < size; ++i)
818 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
820 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
821 m_buffer[i] = 0;
823 if (p[0] != '#' && p[0] != 0)
824 git_add_exclude((const char*)p, this->m_BaseDir, m_BaseDir.GetLength(), this->m_pExcludeList, ++line);
826 p = m_buffer + i + 1;
830 if (!line)
832 git_free_exclude_list(m_pExcludeList);
833 m_pExcludeList = nullptr;
834 free(m_buffer);
835 m_buffer = nullptr;
838 return 0;
841 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
842 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, int& type)
844 int pos = patha.ReverseFind('/');
845 const char* base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
847 return IsPathIgnored(patha, base, type);
849 #endif
851 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, const char* base, int& type)
853 if (!m_pExcludeList)
854 return -1; // error or undecided
856 return git_check_excluded_1(patha, patha.GetLength(), base, &type, m_pExcludeList);
859 bool CGitIgnoreList::CheckFileChanged(const CString &path)
861 __int64 time = 0;
863 int ret = CGit::GetFileModifyTime(path, &time);
865 bool cacheExist;
867 CAutoReadLock lock(m_SharedMutex);
868 cacheExist = (m_Map.find(path) != m_Map.end());
871 if (!cacheExist && ret == 0)
873 CAutoWriteLock lock(m_SharedMutex);
874 m_Map[path].m_LastModifyTime = 0;
876 // both cache and file is not exist
877 if ((ret != 0) && (!cacheExist))
878 return false;
880 // file exist but cache miss
881 if ((ret == 0) && (!cacheExist))
882 return true;
884 // file not exist but cache exist
885 if ((ret != 0) && (cacheExist))
886 return true;
887 // file exist and cache exist
890 CAutoReadLock lock(m_SharedMutex);
891 if (m_Map[path].m_LastModifyTime == time)
892 return false;
894 return true;
897 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
899 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
901 CAutoWriteLock lock(m_SharedMutex);
902 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
904 else
906 CAutoWriteLock lock(m_SharedMutex);
907 m_Map.erase(gitignore);
909 return 0;
912 bool CGitIgnoreList::CheckAndUpdateIgnoreFiles(const CString& gitdir, const CString& path, bool isDir)
914 CString temp(gitdir);
915 temp += L'\\';
916 temp += path;
918 temp.Replace(L'/', L'\\');
920 if (!isDir)
922 int x = temp.ReverseFind(L'\\');
923 if (x >= 2)
924 temp.Truncate(x);
927 bool updated = false;
928 while (!temp.IsEmpty())
930 CString tempOrig = temp;
931 temp += L"\\.git";
933 if (CGit::GitPathFileExists(temp))
935 CString gitignore = temp;
936 gitignore += L"ignore";
937 if (CheckFileChanged(gitignore))
939 FetchIgnoreFile(gitdir, gitignore, false);
940 updated = true;
943 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
944 CString wcglobalgitignore = adminDir + L"info\\exclude";
945 if (CheckFileChanged(wcglobalgitignore))
947 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
948 updated = true;
951 if (CheckAndUpdateCoreExcludefile(adminDir))
953 CString excludesFile;
955 CAutoReadLock lock(m_SharedMutex);
956 excludesFile = m_CoreExcludesfiles[adminDir];
958 if (!excludesFile.IsEmpty())
960 FetchIgnoreFile(gitdir, excludesFile, true);
961 updated = true;
965 return updated;
968 temp += L"ignore";
969 if (CheckFileChanged(temp))
971 FetchIgnoreFile(gitdir, temp, false);
972 updated = true;
975 int found = 0;
976 int i;
977 for (i = temp.GetLength() - 1; i >= 0; --i)
979 if (temp[i] == L'\\')
980 ++found;
982 if (found == 2)
983 break;
986 temp.Truncate(max(0, i));
988 return updated;
991 bool CGitIgnoreList::CheckAndUpdateGitSystemConfigPath(bool force)
993 if (force)
994 m_sGitProgramDataConfigPath = GetProgramDataGitConfig();
995 // recheck every 30 seconds
996 if (GetTickCount64() - m_dGitSystemConfigPathLastChecked > 30000UL || force)
998 m_dGitSystemConfigPathLastChecked = GetTickCount64();
999 CString gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE));
1000 if (gitSystemConfigPath != m_sGitSystemConfigPath)
1002 m_sGitSystemConfigPath = gitSystemConfigPath;
1003 return true;
1006 return false;
1008 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1010 CString projectConfig(adminDir);
1011 projectConfig += L"config";
1012 CString globalConfig = g_Git.GetGitGlobalConfig();
1013 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1015 CAutoWriteLock lock(m_coreExcludefilesSharedMutex);
1016 bool hasChanged = CheckAndUpdateGitSystemConfigPath();
1017 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1018 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1019 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1020 if (!m_sGitProgramDataConfigPath.IsEmpty())
1021 hasChanged = hasChanged || CheckFileChanged(m_sGitProgramDataConfigPath);
1022 if (!m_sGitSystemConfigPath.IsEmpty())
1023 hasChanged = hasChanged || CheckFileChanged(m_sGitSystemConfigPath);
1025 CString excludesFile;
1027 CAutoReadLock lock2(m_SharedMutex);
1028 excludesFile = m_CoreExcludesfiles[adminDir];
1030 if (!excludesFile.IsEmpty())
1031 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1033 if (!hasChanged)
1034 return false;
1036 CAutoConfig config(true);
1037 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
1038 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
1039 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
1040 if (!m_sGitSystemConfigPath.IsEmpty())
1041 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitSystemConfigPath), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
1042 if (!m_sGitProgramDataConfigPath.IsEmpty())
1043 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitProgramDataConfigPath), GIT_CONFIG_LEVEL_PROGRAMDATA, FALSE);
1045 config.GetString(L"core.excludesfile", excludesFile);
1046 if (excludesFile.IsEmpty())
1047 excludesFile = GetWindowsHome() + L"\\.config\\git\\ignore";
1048 else if (CStringUtils::StartsWith(excludesFile, L"~/"))
1049 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1051 CAutoWriteLock lockMap(m_SharedMutex);
1052 CGit::GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1053 CGit::GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1054 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1055 m_Map.erase(globalXDGConfig);
1056 CGit::GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1057 if (m_Map[globalConfig].m_LastModifyTime == 0)
1058 m_Map.erase(globalConfig);
1059 if (!m_sGitSystemConfigPath.IsEmpty())
1060 CGit::GetFileModifyTime(m_sGitSystemConfigPath, &m_Map[m_sGitSystemConfigPath].m_LastModifyTime);
1061 if (m_Map[m_sGitSystemConfigPath].m_LastModifyTime == 0 || m_sGitSystemConfigPath.IsEmpty())
1062 m_Map.erase(m_sGitSystemConfigPath);
1063 if (!m_sGitProgramDataConfigPath.IsEmpty())
1064 CGit::GetFileModifyTime(m_sGitProgramDataConfigPath, &m_Map[m_sGitProgramDataConfigPath].m_LastModifyTime);
1065 if (m_Map[m_sGitProgramDataConfigPath].m_LastModifyTime == 0 || m_sGitProgramDataConfigPath.IsEmpty())
1066 m_Map.erase(m_sGitProgramDataConfigPath);
1067 m_CoreExcludesfiles[adminDir] = excludesFile;
1069 return true;
1071 const CString CGitIgnoreList::GetWindowsHome()
1073 static CString sWindowsHome(g_Git.GetHomeDirectory());
1074 return sWindowsHome;
1076 bool CGitIgnoreList::IsIgnore(CString str, const CString& projectroot, bool isDir)
1078 str.Replace(L'\\', L'/');
1080 if (!str.IsEmpty() && str[str.GetLength() - 1] == L'/')
1081 str.Truncate(str.GetLength() - 1);
1083 int ret;
1084 ret = CheckIgnore(str, projectroot, isDir);
1085 while (ret < 0)
1087 int start = str.ReverseFind(L'/');
1088 if(start < 0)
1089 return (ret == 1);
1091 str.Truncate(start);
1092 ret = CheckIgnore(str, projectroot, isDir);
1095 return (ret == 1);
1097 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1099 if (m_Map.find(ignorefile) == m_Map.end())
1100 return -1; // error or undecided
1102 return (m_Map[ignorefile].IsPathIgnored(patha, base, type));
1104 int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, bool isDir)
1106 CString temp = CombinePath(projectroot, path);
1107 temp.Replace(L'/', L'\\');
1109 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1110 patha.Replace('\\', '/');
1112 int type = 0;
1113 if (isDir)
1115 type = DT_DIR;
1117 // strip directory name
1118 // we do not need to check for a .ignore file inside a directory we might ignore
1119 int i = temp.ReverseFind(L'\\');
1120 if (i >= 0)
1121 temp.Truncate(i);
1123 else
1125 type = DT_REG;
1127 int x = temp.ReverseFind(L'\\');
1128 if (x >= 2)
1129 temp.Truncate(x);
1132 int pos = patha.ReverseFind('/');
1133 const char * base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
1135 int ret = -1;
1137 CAutoReadLock lock(m_SharedMutex);
1138 while (!temp.IsEmpty())
1140 CString tempOrig = temp;
1141 temp += L"\\.git";
1143 if (CGit::GitPathFileExists(temp))
1145 CString gitignore = temp;
1146 gitignore += L"ignore";
1147 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1148 break;
1150 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1151 CString wcglobalgitignore = adminDir;
1152 wcglobalgitignore += L"info\\exclude";
1153 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1154 break;
1156 CString excludesFile = m_CoreExcludesfiles[adminDir];
1157 if (!excludesFile.IsEmpty())
1158 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1160 break;
1163 temp += L"ignore";
1164 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1165 break;
1167 int found = 0;
1168 int i;
1169 for (i = temp.GetLength() - 1; i >= 0; i--)
1171 if (temp[i] == L'\\')
1172 ++found;
1174 if (found == 2)
1175 break;
1178 temp.Truncate(max(0, i));
1181 return ret;
1184 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir, bool readTree /* = true */)
1186 SHARED_TREE_PTR ptr = this->SafeGet(gitdir, true);
1188 if (ptr.get() && !ptr->CheckHeadUpdate() && (!readTree || ptr->HeadHashEqualsTreeHash()))
1189 return false;
1191 ptr = std::make_shared<CGitHeadFileList>();
1192 ptr->ReadHeadHash(gitdir);
1193 if (readTree)
1194 ptr->ReadTree();
1196 this->SafeSet(gitdir, ptr);
1198 return true;
1201 int CGitHeadFileMap::IsUnderVersionControl(const CString& gitdir, CString subpath, bool isDir, bool* isVersion)
1203 if (subpath.IsEmpty())
1205 *isVersion = true;
1206 return 0;
1209 subpath.Replace(L'\\', L'/');
1210 if (isDir)
1211 subpath += L'/';
1213 subpath.MakeLower();
1215 CheckHeadAndUpdate(gitdir);
1217 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1219 // Init Repository
1220 if (treeptr->HeadFileIsEmpty())
1222 *isVersion = false;
1223 return 0;
1225 if (treeptr->empty())
1227 *isVersion = false;
1228 return 1;
1231 if (isDir)
1232 *isVersion = (SearchInSortVector(*treeptr, subpath, subpath.GetLength()) != NPOS);
1233 else
1234 *isVersion = (SearchInSortVector(*treeptr, subpath, -1) != NPOS);
1236 return 0;