Pass const reference to path
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob2d2da4dfaf727645f6a7c2f77032c87bb6de59f6
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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 "PathUtils.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 - (int)wcslen(L"\\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)
57 , m_LastModifyTime(0)
59 m_iMaxCheckSize = (__int64)CRegDWORD(L"Software\\TortoiseGit\\TGitCacheCheckContentMaxSize", 10 * 1024) * 1024; // stored in KiB
62 CGitIndexList::~CGitIndexList()
66 static bool SortIndex(const CGitIndex &Item1, const CGitIndex &Item2)
68 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
71 static bool SortTree(const CGitTreeItem &Item1, const CGitTreeItem &Item2)
73 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
76 int CGitIndexList::ReadIndex(CString dgitdir)
78 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
79 clear(); // HACK to make tests work, until we use CGitIndexList
80 #endif
81 ATLASSERT(empty());
83 CAutoRepository repository(dgitdir);
84 if (!repository)
86 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Could not open git repository in %s: %s\n", (LPCTSTR)dgitdir, (LPCTSTR)CGit::GetLibGit2LastErr());
87 return -1;
90 // add config files
91 config.New();
93 CString projectConfig = g_AdminDirMap.GetAdminDir(dgitdir) + L"config";
94 CString globalConfig = g_Git.GetGitGlobalConfig();
95 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
96 CString systemConfig(CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE));
97 CString programDataConfig(GetProgramDataGitConfig());
99 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
100 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
101 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
102 if (!systemConfig.IsEmpty())
103 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(systemConfig), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
104 if (!programDataConfig.IsEmpty())
105 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(programDataConfig), GIT_CONFIG_LEVEL_PROGRAMDATA, FALSE);
107 git_repository_set_config(repository, config);
109 CAutoIndex index;
110 // load index in order to enumerate files
111 if (git_repository_index(index.GetPointer(), repository))
113 config.Free();
114 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Could not get index of git repository in %s: %s\n", (LPCTSTR)dgitdir, (LPCTSTR)CGit::GetLibGit2LastErr());
115 return -1;
118 m_bHasConflicts = FALSE;
120 size_t ecount = git_index_entrycount(index);
123 resize(ecount);
125 catch (const std::bad_alloc& ex)
127 config.Free();
128 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Could not resize index-vector: %s\n", ex.what());
129 return -1;
131 for (size_t i = 0; i < ecount; ++i)
133 const git_index_entry *e = git_index_get_byindex(index, i);
135 auto& item = (*this)[i];
136 item.m_FileName = CUnicodeUtils::GetUnicode(e->path);
137 if (e->mode & S_IFDIR)
138 item.m_FileName += L'/';
139 item.m_ModifyTime = e->mtime.seconds;
140 item.m_Flags = e->flags;
141 item.m_FlagsExtended = e->flags_extended;
142 item.m_IndexHash = e->id.id;
143 item.m_Size = e->file_size;
144 m_bHasConflicts |= GIT_IDXENTRY_STAGE(e);
147 CGit::GetFileModifyTime(g_AdminDirMap.GetWorktreeAdminDir(dgitdir) + L"index", &m_LastModifyTime);
148 std::sort(this->begin(), this->end(), SortIndex);
150 return 0;
153 int CGitIndexList::GetFileStatus(const CString& gitdir, const CString& pathorg, git_wc_status2_t& status, __int64 time, __int64 filesize, CGitHash* pHash)
155 size_t index = SearchInSortVector(*this, pathorg, -1);
157 if (index == NPOS)
159 status.status = git_wc_status_unversioned;
160 if (pHash)
161 pHash->Empty();
163 return 0;
166 auto& entry = (*this)[index];
167 if (pHash)
168 *pHash = entry.m_IndexHash;
169 ATLASSERT(pathorg == entry.m_FileName);
170 CAutoRepository repository;
171 return GetFileStatus(repository, gitdir, entry, status, time, filesize);
174 int CGitIndexList::GetFileStatus(CAutoRepository& repository, const CString& gitdir, CGitIndex& entry, git_wc_status2_t& status, __int64 time, __int64 filesize)
176 ATLASSERT(!status.assumeValid && !status.skipWorktree);
178 // skip-worktree has higher priority than assume-valid
179 if (entry.m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE)
181 status.status = git_wc_status_normal;
182 status.skipWorktree = true;
184 else if (entry.m_Flags & GIT_IDXENTRY_VALID)
186 status.status = git_wc_status_normal;
187 status.assumeValid = true;
189 else if (filesize == -1)
190 status.status = git_wc_status_deleted;
191 else if (filesize != entry.m_Size)
192 status.status = git_wc_status_modified;
193 else if (time == entry.m_ModifyTime)
194 status.status = git_wc_status_normal;
195 else if (config && filesize < m_iMaxCheckSize)
198 * Opening a new repository each time is not yet optimal, however, there is no API to clear the pack-cache
199 * When a shared repository is used, we might need a mutex to prevent concurrent access to repository instance and especially filter-lists
201 if (!repository)
203 if (repository.Open(gitdir))
205 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Could not open git repository in %s for checking file: %s\n", (LPCTSTR)gitdir, (LPCTSTR)CGit::GetLibGit2LastErr());
206 return -1;
208 git_repository_set_config(repository, config);
211 git_oid actual;
212 CStringA fileA = CUnicodeUtils::GetMulti(entry.m_FileName, CP_UTF8);
213 if (!git_repository_hashfile(&actual, repository, fileA, GIT_OBJ_BLOB, nullptr) && !git_oid_cmp(&actual, (const git_oid*)entry.m_IndexHash.m_hash))
215 entry.m_ModifyTime = time;
216 status.status = git_wc_status_normal;
218 else
219 status.status = git_wc_status_modified;
221 else
222 status.status = git_wc_status_modified;
224 if (entry.m_Flags & GIT_IDXENTRY_STAGEMASK)
225 status.status = git_wc_status_conflicted;
226 else if (entry.m_FlagsExtended & GIT_IDXENTRY_INTENT_TO_ADD)
227 status.status = git_wc_status_added;
229 return 0;
232 int CGitIndexList::GetFileStatus(const CString& gitdir, const CString& path, git_wc_status2_t& status, CGitHash* pHash)
234 ATLASSERT(!status.assumeValid && !status.skipWorktree);
236 __int64 time, filesize = 0;
237 bool isDir = false;
239 int result;
240 if (path.IsEmpty())
241 result = CGit::GetFileModifyTime(gitdir, &time, &isDir);
242 else
243 result = CGit::GetFileModifyTime(CombinePath(gitdir, path), &time, &isDir, &filesize);
245 if (result)
246 filesize = -1;
248 if (!isDir)
249 return GetFileStatus(gitdir, path, status, time, filesize, pHash);
251 if (CStringUtils::EndsWith(path, L'/'))
253 size_t index = SearchInSortVector(*this, path, -1);
254 if (index == NPOS)
256 status.status = git_wc_status_unversioned;
257 if (pHash)
258 pHash->Empty();
260 return 0;
263 if (pHash)
264 *pHash = (*this)[index].m_IndexHash;
266 if (!result)
267 status.status = git_wc_status_normal;
268 else
269 status.status = git_wc_status_deleted;
270 return 0;
273 // we should never get here
274 status.status = git_wc_status_unversioned;
276 return -1;
279 bool CGitIndexFileMap::HasIndexChangedOnDisk(const CString& gitdir)
281 __int64 time;
283 auto pIndex = SafeGet(gitdir);
285 if (!pIndex)
286 return true;
288 CString IndexFile = g_AdminDirMap.GetWorktreeAdminDirConcat(gitdir, L"index");
289 // no need to refresh if there is no index right now and the current index is empty, but otherwise or lastmodified time differs
290 return (CGit::GetFileModifyTime(IndexFile, &time) && !pIndex->empty()) || pIndex->m_LastModifyTime != time;
293 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
295 SHARED_INDEX_PTR pIndex = std::make_shared<CGitIndexList>();
297 if (pIndex->ReadIndex(gitdir))
299 SafeClear(gitdir);
300 return -1;
303 this->SafeSet(gitdir, pIndex);
305 return 0;
308 // This method is assumed to be called with m_SharedMutex locked.
309 int CGitHeadFileList::GetPackRef(const CString &gitdir)
311 CString PackRef = g_AdminDirMap.GetAdminDirConcat(gitdir, L"packed-refs");
313 __int64 mtime;
314 if (CGit::GetFileModifyTime(PackRef, &mtime))
316 //packed refs is not existed
317 this->m_PackRefFile.Empty();
318 this->m_PackRefMap.clear();
319 return 0;
321 else if(mtime == m_LastModifyTimePackRef)
322 return 0;
323 else
325 this->m_PackRefFile = PackRef;
326 this->m_LastModifyTimePackRef = mtime;
329 m_PackRefMap.clear();
331 CAutoFile hfile = CreateFile(PackRef,
332 GENERIC_READ,
333 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
334 nullptr,
335 OPEN_EXISTING,
336 FILE_ATTRIBUTE_NORMAL,
337 nullptr);
339 if (!hfile)
340 return -1;
342 DWORD filesize = GetFileSize(hfile, nullptr);
343 if (filesize == 0 || filesize == INVALID_FILE_SIZE)
344 return -1;
346 DWORD size = 0;
347 auto buff = std::make_unique<char[]>(filesize);
348 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
350 if (size != filesize)
351 return -1;
353 for (DWORD i = 0; i < filesize;)
355 CString hash;
356 CString ref;
357 if (buff[i] == '#' || buff[i] == '^')
359 while (buff[i] != '\n')
361 ++i;
362 if (i == filesize)
363 break;
365 ++i;
368 if (i >= filesize)
369 break;
371 while (buff[i] != ' ')
373 hash.AppendChar(buff[i]);
374 ++i;
375 if (i == filesize)
376 break;
379 ++i;
380 if (i >= filesize)
381 break;
383 while (buff[i] != '\n')
385 ref.AppendChar(buff[i]);
386 ++i;
387 if (i == filesize)
388 break;
391 if (!ref.IsEmpty())
392 m_PackRefMap[ref] = hash;
394 while (buff[i] == '\n')
396 ++i;
397 if (i == filesize)
398 break;
401 return 0;
403 int CGitHeadFileList::ReadHeadHash(const CString& gitdir)
405 ATLASSERT(m_Gitdir.IsEmpty() && m_HeadFile.IsEmpty() && m_Head.IsEmpty());
407 m_Gitdir = g_AdminDirMap.GetWorktreeAdminDir(gitdir);
409 m_HeadFile = m_Gitdir;
410 m_HeadFile += L"HEAD";
412 if( CGit::GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
413 return -1;
415 CAutoFile hfile = CreateFile(m_HeadFile,
416 GENERIC_READ,
417 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
418 nullptr,
419 OPEN_EXISTING,
420 FILE_ATTRIBUTE_NORMAL,
421 nullptr);
423 if (!hfile)
424 return -1;
426 DWORD size = 0;
427 unsigned char buffer[2 * GIT_HASH_SIZE];
428 ReadFile(hfile, buffer, (DWORD)strlen("ref:"), &size, nullptr);
429 if (size != strlen("ref:"))
430 return -1;
431 buffer[4] = '\0';
432 if (strcmp((const char*)buffer, "ref:") == 0)
434 m_HeadRefFile.Empty();
435 DWORD filesize = GetFileSize(hfile, nullptr);
436 if (filesize < 5 || filesize == INVALID_FILE_SIZE)
437 return -1;
439 unsigned char *p = (unsigned char*)malloc(filesize - strlen("ref:"));
440 if (!p)
441 return -1;
443 ReadFile(hfile, p, filesize - (DWORD)strlen("ref:"), &size, nullptr);
444 CGit::StringAppend(&m_HeadRefFile, p, CP_UTF8, filesize - (int)strlen("ref:"));
445 free(p);
447 CString ref = m_HeadRefFile.Trim();
448 int start = 0;
449 ref = ref.Tokenize(L"\n", start);
450 m_HeadRefFile = g_AdminDirMap.GetAdminDir(gitdir) + m_HeadRefFile;
451 m_HeadRefFile.Replace(L'/', L'\\');
453 __int64 time;
454 if (CGit::GetFileModifyTime(m_HeadRefFile, &time, nullptr))
456 m_HeadRefFile.Empty();
457 if (GetPackRef(gitdir))
458 return -1;
459 if (m_PackRefMap.find(ref) != m_PackRefMap.end())
461 m_Head = m_PackRefMap[ref];
462 return 0;
465 // unborn branch
466 m_Head.Empty();
468 return 0;
471 CAutoFile href = CreateFile(m_HeadRefFile,
472 GENERIC_READ,
473 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
474 nullptr,
475 OPEN_EXISTING,
476 FILE_ATTRIBUTE_NORMAL,
477 nullptr);
479 if (!href)
481 m_HeadRefFile.Empty();
483 if (GetPackRef(gitdir))
484 return -1;
486 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
487 return -1;
489 m_Head = m_PackRefMap[ref];
490 return 0;
493 ReadFile(href, buffer, 2 * GIT_HASH_SIZE, &size, nullptr);
494 if (size != 2 * GIT_HASH_SIZE)
495 return -1;
497 m_Head.ConvertFromStrA((char*)buffer);
499 m_LastModifyTimeRef = time;
501 return 0;
504 ReadFile(hfile, buffer + (DWORD)strlen("ref:"), 2 * GIT_HASH_SIZE - (DWORD)strlen("ref:"), &size, nullptr);
505 if (size != 2 * GIT_HASH_SIZE - (DWORD)strlen("ref:"))
506 return -1;
508 m_HeadRefFile.Empty();
510 m_Head.ConvertFromStrA((char*)buffer);
512 return 0;
515 bool CGitHeadFileList::CheckHeadUpdate()
517 if (this->m_HeadFile.IsEmpty())
518 return true;
520 __int64 mtime=0;
522 if (CGit::GetFileModifyTime(m_HeadFile, &mtime))
523 return true;
525 if (mtime != this->m_LastModifyTimeHead)
526 return true;
528 if (!this->m_HeadRefFile.IsEmpty())
530 if (CGit::GetFileModifyTime(m_HeadRefFile, &mtime))
531 return true;
533 if (mtime != this->m_LastModifyTimeRef)
534 return true;
537 if(!this->m_PackRefFile.IsEmpty())
539 if (CGit::GetFileModifyTime(m_PackRefFile, &mtime))
540 return true;
542 if (mtime != this->m_LastModifyTimePackRef)
543 return true;
546 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
547 // So we need to retry again and again until the ref exists - otherwise we will never notice
548 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
549 return true;
551 return false;
554 bool CGitHeadFileList::HeadHashEqualsTreeHash()
556 return (m_Head == m_TreeHash);
559 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
560 const char *pathname, unsigned mode, int /*stage*/, void *context)
562 #define S_IFGITLINK 0160000
564 CGitHeadFileList* p = reinterpret_cast<CGitHeadFileList*>(context);
566 if ((mode & S_IFDIR) && (mode & S_IFMT) != S_IFGITLINK)
567 return READ_TREE_RECURSIVE;
569 CGitTreeItem item;
570 item.m_Hash = sha1;
571 CGit::StringAppend(&item.m_FileName, (BYTE*)base, CP_UTF8, baselen);
572 CGit::StringAppend(&item.m_FileName, (BYTE*)pathname, CP_UTF8);
573 if ((mode & S_IFMT) == S_IFGITLINK)
574 item.m_FileName += L'/';
576 p->push_back(item);
578 if( (mode&S_IFMT) == S_IFGITLINK)
579 return 0;
581 return READ_TREE_RECURSIVE;
584 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)
586 size_t count = git_tree_entrycount(tree);
587 for (size_t i = 0; i < count; ++i)
589 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
590 if (!entry)
591 continue;
592 int mode = git_tree_entry_filemode(entry);
593 if( CallBack(git_tree_entry_id(entry)->id,
594 base,
595 base.GetLength(),
596 git_tree_entry_name(entry),
597 mode,
599 data) == READ_TREE_RECURSIVE
602 if(mode&S_IFDIR)
604 git_object* object = nullptr;
605 git_tree_entry_to_object(&object, &repo, entry);
606 if (!object)
607 continue;
608 CStringA parent = base;
609 parent += git_tree_entry_name(entry);
610 parent += "/";
611 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
612 git_object_free(object);
618 return 0;
621 // ReadTree is/must only be executed on an empty list
622 int CGitHeadFileList::ReadTree()
624 ATLASSERT(empty() && m_TreeHash.IsEmpty());
626 // unborn branch
627 if (m_Head.IsEmpty())
628 return 0;
630 CAutoRepository repository(m_Gitdir);
631 CAutoCommit commit;
632 CAutoTree tree;
633 bool ret = repository;
634 ret = ret && !git_commit_lookup(commit.GetPointer(), repository, (const git_oid*)m_Head.m_hash);
635 ret = ret && !git_commit_tree(tree.GetPointer(), commit);
638 ret = ret && !ReadTreeRecursive(*repository, tree, "", CGitHeadFileList::CallBack, this);
640 catch (const std::bad_alloc& ex)
642 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Catched exception inside ReadTreeRecursive: %s\n", ex.what());
643 return -1;
645 if (!ret)
647 clear();
648 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": Could not open git repository in %s and read HEAD commit %s: %s\n", (LPCTSTR)m_Gitdir, (LPCTSTR)m_Head.ToString(), (LPCTSTR)CGit::GetLibGit2LastErr());
649 m_LastModifyTimeHead = 0;
650 return -1;
653 std::sort(this->begin(), this->end(), SortTree);
654 m_TreeHash = git_commit_id(commit)->id;
656 return 0;
658 int CGitIgnoreItem::FetchIgnoreList(const CString& projectroot, const CString& file, bool isGlobal, int* ignoreCase)
660 if (this->m_pExcludeList)
662 git_free_exclude_list(m_pExcludeList);
663 m_pExcludeList = nullptr;
665 free(m_buffer);
666 m_buffer = nullptr;
668 this->m_BaseDir.Empty();
669 if (!isGlobal)
671 CString base = file.Mid(projectroot.GetLength() + 1);
672 base.Replace(L'\\', L'/');
674 int start = base.ReverseFind(L'/');
675 if(start > 0)
677 base.Truncate(start);
678 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
682 if (CGit::GetFileModifyTime(file, &m_LastModifyTime))
683 return -1;
685 CAutoFile hfile = CreateFile(file,
686 GENERIC_READ,
687 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
688 nullptr,
689 OPEN_EXISTING,
690 FILE_ATTRIBUTE_NORMAL,
691 nullptr);
693 if (!hfile)
694 return -1 ;
696 DWORD filesize = GetFileSize(hfile, nullptr);
697 if (filesize == INVALID_FILE_SIZE)
698 return -1;
700 m_buffer = new BYTE[filesize + 1];
701 if (!m_buffer)
702 return -1;
704 DWORD size = 0;
705 if (!ReadFile(hfile, m_buffer, filesize, &size, nullptr))
707 free(m_buffer);
708 m_buffer = nullptr;
709 return -1;
711 m_buffer[size] = '\0';
713 if (git_create_exclude_list(&m_pExcludeList))
715 free(m_buffer);
716 m_buffer = nullptr;
717 return -1;
720 m_iIgnoreCase = ignoreCase;
722 BYTE *p = m_buffer;
723 int line = 0;
724 for (DWORD i = 0; i < size; ++i)
726 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
728 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
729 m_buffer[i] = '\0';
731 if (p[0] != '#' && p[0])
732 git_add_exclude((const char*)p, this->m_BaseDir, m_BaseDir.GetLength(), this->m_pExcludeList, ++line);
734 p = m_buffer + i + 1;
738 if (!line)
740 git_free_exclude_list(m_pExcludeList);
741 m_pExcludeList = nullptr;
742 free(m_buffer);
743 m_buffer = nullptr;
746 return 0;
749 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
750 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, int& type)
752 int pos = patha.ReverseFind('/');
753 const char* base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
755 return IsPathIgnored(patha, base, type);
757 #endif
759 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, const char* base, int& type)
761 if (!m_pExcludeList)
762 return -1; // error or undecided
764 return git_check_excluded_1(patha, patha.GetLength(), base, &type, m_pExcludeList, m_iIgnoreCase ? *m_iIgnoreCase : 1);
767 bool CGitIgnoreList::CheckFileChanged(const CString &path)
769 __int64 time = 0;
771 int ret = CGit::GetFileModifyTime(path, &time);
773 bool cacheExist;
775 CAutoReadLock lock(m_SharedMutex);
776 cacheExist = (m_Map.find(path) != m_Map.end());
779 if (!cacheExist && ret == 0)
781 CAutoWriteLock lock(m_SharedMutex);
782 m_Map[path].m_LastModifyTime = 0;
784 // both cache and file is not exist
785 if ((ret != 0) && (!cacheExist))
786 return false;
788 // file exist but cache miss
789 if ((ret == 0) && (!cacheExist))
790 return true;
792 // file not exist but cache exist
793 if ((ret != 0) && (cacheExist))
794 return true;
795 // file exist and cache exist
798 CAutoReadLock lock(m_SharedMutex);
799 if (m_Map[path].m_LastModifyTime == time)
800 return false;
802 return true;
805 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
807 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
809 CAutoWriteLock lock(m_SharedMutex);
810 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal, &m_IgnoreCase[g_AdminDirMap.GetAdminDir(gitdir)]);
812 else
814 CAutoWriteLock lock(m_SharedMutex);
815 m_Map.erase(gitignore);
817 return 0;
820 bool CGitIgnoreList::CheckAndUpdateIgnoreFiles(const CString& gitdir, const CString& path, bool isDir)
822 CString temp(gitdir);
823 temp += L'\\';
824 temp += path;
826 temp.Replace(L'/', L'\\');
828 if (!isDir)
830 int x = temp.ReverseFind(L'\\');
831 if (x >= 2)
832 temp.Truncate(x);
835 bool updated = false;
836 while (!temp.IsEmpty())
838 temp += L"\\.gitignore";
840 if (CheckFileChanged(temp))
842 FetchIgnoreFile(gitdir, temp, false);
843 updated = true;
846 temp.Truncate(temp.GetLength() - (int)wcslen(L"\\.gitignore"));
847 if (CPathUtils::ArePathStringsEqual(temp, gitdir))
849 CString adminDir = g_AdminDirMap.GetAdminDir(temp);
850 CString wcglobalgitignore = adminDir + L"info\\exclude";
851 if (CheckFileChanged(wcglobalgitignore))
853 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
854 updated = true;
857 if (CheckAndUpdateCoreExcludefile(adminDir))
859 CString excludesFile;
861 CAutoReadLock lock(m_SharedMutex);
862 excludesFile = m_CoreExcludesfiles[adminDir];
864 if (!excludesFile.IsEmpty())
866 FetchIgnoreFile(gitdir, excludesFile, true);
867 updated = true;
871 return updated;
874 int i = temp.ReverseFind(L'\\');
875 temp.Truncate(max(0, i));
877 return updated;
880 bool CGitIgnoreList::CheckAndUpdateGitSystemConfigPath(bool force)
882 if (force)
883 m_sGitProgramDataConfigPath = GetProgramDataGitConfig();
884 // recheck every 30 seconds
885 if (GetTickCount64() - m_dGitSystemConfigPathLastChecked > 30000UL || force)
887 m_dGitSystemConfigPathLastChecked = GetTickCount64();
888 CString gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH, L"", FALSE));
889 if (gitSystemConfigPath != m_sGitSystemConfigPath)
891 m_sGitSystemConfigPath = gitSystemConfigPath;
892 return true;
895 return false;
897 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
899 CString projectConfig(adminDir);
900 projectConfig += L"config";
901 CString globalConfig = g_Git.GetGitGlobalConfig();
902 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
904 CAutoWriteLock lock(m_coreExcludefilesSharedMutex);
905 bool hasChanged = CheckAndUpdateGitSystemConfigPath();
906 hasChanged = hasChanged || CheckFileChanged(projectConfig);
907 hasChanged = hasChanged || CheckFileChanged(globalConfig);
908 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
909 if (!m_sGitProgramDataConfigPath.IsEmpty())
910 hasChanged = hasChanged || CheckFileChanged(m_sGitProgramDataConfigPath);
911 if (!m_sGitSystemConfigPath.IsEmpty())
912 hasChanged = hasChanged || CheckFileChanged(m_sGitSystemConfigPath);
914 CString excludesFile;
916 CAutoReadLock lock2(m_SharedMutex);
917 excludesFile = m_CoreExcludesfiles[adminDir];
919 if (!excludesFile.IsEmpty())
920 hasChanged = hasChanged || CheckFileChanged(excludesFile);
922 if (!hasChanged)
923 return false;
925 CAutoConfig config(true);
926 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
927 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
928 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
929 if (!m_sGitSystemConfigPath.IsEmpty())
930 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitSystemConfigPath), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
931 if (!m_sGitProgramDataConfigPath.IsEmpty())
932 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitProgramDataConfigPath), GIT_CONFIG_LEVEL_PROGRAMDATA, FALSE);
934 config.GetString(L"core.excludesfile", excludesFile);
935 if (excludesFile.IsEmpty())
936 excludesFile = GetWindowsHome() + L"\\.config\\git\\ignore";
937 else if (CStringUtils::StartsWith(excludesFile, L"~/"))
938 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
940 CAutoWriteLock lockMap(m_SharedMutex);
941 m_IgnoreCase[adminDir] = 1;
942 config.GetBOOL(L"core.ignorecase", m_IgnoreCase[adminDir]);
943 CGit::GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
944 CGit::GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
945 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
946 m_Map.erase(globalXDGConfig);
947 CGit::GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
948 if (m_Map[globalConfig].m_LastModifyTime == 0)
949 m_Map.erase(globalConfig);
950 if (!m_sGitSystemConfigPath.IsEmpty())
951 CGit::GetFileModifyTime(m_sGitSystemConfigPath, &m_Map[m_sGitSystemConfigPath].m_LastModifyTime);
952 if (m_Map[m_sGitSystemConfigPath].m_LastModifyTime == 0 || m_sGitSystemConfigPath.IsEmpty())
953 m_Map.erase(m_sGitSystemConfigPath);
954 if (!m_sGitProgramDataConfigPath.IsEmpty())
955 CGit::GetFileModifyTime(m_sGitProgramDataConfigPath, &m_Map[m_sGitProgramDataConfigPath].m_LastModifyTime);
956 if (m_Map[m_sGitProgramDataConfigPath].m_LastModifyTime == 0 || m_sGitProgramDataConfigPath.IsEmpty())
957 m_Map.erase(m_sGitProgramDataConfigPath);
958 m_CoreExcludesfiles[adminDir] = excludesFile;
960 return true;
962 const CString CGitIgnoreList::GetWindowsHome()
964 static CString sWindowsHome(g_Git.GetHomeDirectory());
965 return sWindowsHome;
967 bool CGitIgnoreList::IsIgnore(CString str, const CString& projectroot, bool isDir)
969 str.Replace(L'\\', L'/');
971 if (!str.IsEmpty() && str[str.GetLength() - 1] == L'/')
972 str.Truncate(str.GetLength() - 1);
974 int ret;
975 ret = CheckIgnore(str, projectroot, isDir);
976 while (ret < 0)
978 int start = str.ReverseFind(L'/');
979 if(start < 0)
980 return (ret == 1);
982 str.Truncate(start);
983 ret = CheckIgnore(str, projectroot, isDir);
986 return (ret == 1);
988 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
990 if (m_Map.find(ignorefile) == m_Map.end())
991 return -1; // error or undecided
993 return (m_Map[ignorefile].IsPathIgnored(patha, base, type));
995 int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, bool isDir)
997 CString temp = CombinePath(projectroot, path);
998 temp.Replace(L'/', L'\\');
1000 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1001 patha.Replace('\\', '/');
1003 int type = 0;
1004 if (isDir)
1006 type = DT_DIR;
1008 // strip directory name
1009 // we do not need to check for a .ignore file inside a directory we might ignore
1010 int i = temp.ReverseFind(L'\\');
1011 if (i >= 0)
1012 temp.Truncate(i);
1014 else
1016 type = DT_REG;
1018 int x = temp.ReverseFind(L'\\');
1019 if (x >= 2)
1020 temp.Truncate(x);
1023 int pos = patha.ReverseFind('/');
1024 const char * base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
1026 int ret = -1;
1028 CAutoReadLock lock(m_SharedMutex);
1029 while (!temp.IsEmpty())
1031 temp += L"\\.gitignore";
1033 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1034 return ret;
1036 temp.Truncate(temp.GetLength() - (int)wcslen(L"\\.gitignore"));
1038 if (CPathUtils::ArePathStringsEqual(temp, projectroot))
1040 CString adminDir = g_AdminDirMap.GetAdminDir(temp);
1041 CString wcglobalgitignore = adminDir;
1042 wcglobalgitignore += L"info\\exclude";
1043 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1044 return ret;
1046 CString excludesFile = m_CoreExcludesfiles[adminDir];
1047 if (!excludesFile.IsEmpty())
1048 return CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1050 return -1;
1053 int i = temp.ReverseFind(L'\\');
1054 temp.Truncate(max(0, i));
1057 return -1;
1060 void CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir)
1062 SHARED_TREE_PTR ptr = this->SafeGet(gitdir);
1064 if (ptr.get() && !ptr->CheckHeadUpdate() && ptr->HeadHashEqualsTreeHash())
1065 return;
1067 ptr = std::make_shared<CGitHeadFileList>();
1068 if (ptr->ReadHeadHash(gitdir) || ptr->ReadTree())
1070 SafeClear(gitdir);
1071 return;
1074 this->SafeSet(gitdir, ptr);
1076 return;