Fix possible problems with "intend to add" staged files
[TortoiseGit.git] / src / Git / GitIndex.cpp
blobc558cab092818177ff1ec9ef31924fb05644689d
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 int CGitIndex::Print()
35 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
36 (int)this->m_ModifyTime,
37 this->m_Flags,
38 (LPCTSTR)this->m_IndexHash.ToString(),
39 (LPCTSTR)this->m_FileName);
41 return 0;
44 CGitIndexList::CGitIndexList()
45 : m_bHasConflicts(FALSE)
47 this->m_LastModifyTime = 0;
48 m_critRepoSec.Init();
49 m_iMaxCheckSize = (__int64)CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContentMaxSize"), 10 * 1024) * 1024; // stored in KiB
52 CGitIndexList::~CGitIndexList()
54 m_critRepoSec.Term();
57 static bool SortIndex(const CGitIndex &Item1, const CGitIndex &Item2)
59 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
62 static bool SortTree(const CGitTreeItem &Item1, const CGitTreeItem &Item2)
64 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
67 int CGitIndexList::ReadIndex(CString dgitdir)
69 this->clear();
71 CAutoLocker lock(m_critRepoSec);
72 if (repository.Open(dgitdir))
73 return -1;
75 // add config files
76 CAutoConfig config(true);
78 CString projectConfig = dgitdir + _T("config");
79 CString globalConfig = g_Git.GetGitGlobalConfig();
80 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
81 CString systemConfig(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
83 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
84 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
85 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
86 if (!systemConfig.IsEmpty())
87 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(systemConfig), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
89 git_repository_set_config(repository, config);
91 CAutoIndex index;
92 // load index in order to enumerate files
93 if (git_repository_index(index.GetPointer(), repository))
95 repository.Free();
96 return -1;
99 m_bHasConflicts = FALSE;
101 size_t ecount = git_index_entrycount(index);
102 resize(ecount);
103 for (size_t i = 0; i < ecount; ++i)
105 const git_index_entry *e = git_index_get_byindex(index, i);
107 this->at(i).m_FileName.Empty();
108 this->at(i).m_FileName = CUnicodeUtils::GetUnicode(e->path);
109 this->at(i).m_FileName.MakeLower();
110 this->at(i).m_ModifyTime = e->mtime.seconds;
111 this->at(i).m_Flags = e->flags;
112 this->at(i).m_FlagsExtended = e->flags_extended;
113 this->at(i).m_IndexHash = e->id.id;
114 this->at(i).m_Size = e->file_size;
115 m_bHasConflicts |= GIT_IDXENTRY_STAGE(e);
118 CGit::GetFileModifyTime(dgitdir + _T("index"), &this->m_LastModifyTime);
119 std::sort(this->begin(), this->end(), SortIndex);
121 return 0;
124 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)
126 if (!status)
127 return 0;
129 CString path = pathorg;
130 path.MakeLower();
132 int index = SearchInSortVector(*this, path, -1);
134 if (index < 0)
136 *status = git_wc_status_unversioned;
137 if (pHash)
138 pHash->Empty();
140 if (callback && assumeValid && skipWorktree)
141 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
143 return 0;
146 // skip-worktree has higher priority than assume-valid
147 if (at(index).m_FlagsExtended & GIT_IDXENTRY_SKIP_WORKTREE)
149 *status = git_wc_status_normal;
150 if (skipWorktree)
151 *skipWorktree = true;
153 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
155 *status = git_wc_status_normal;
156 if (assumeValid)
157 *assumeValid = true;
159 else if (filesize != at(index).m_Size)
160 *status = git_wc_status_modified;
161 else if (time == at(index).m_ModifyTime)
162 *status = git_wc_status_normal;
163 else if (repository && filesize < m_iMaxCheckSize)
165 git_oid actual;
166 CStringA fileA = CUnicodeUtils::GetMulti(pathorg, CP_UTF8);
167 m_critRepoSec.Lock(); // prevent concurrent access to repository instance and especially filter-lists
168 if (!git_repository_hashfile(&actual, repository, fileA, GIT_OBJ_BLOB, NULL) && !git_oid_cmp(&actual, (const git_oid*)at(index).m_IndexHash.m_hash))
170 at(index).m_ModifyTime = time;
171 *status = git_wc_status_normal;
173 else
174 *status = git_wc_status_modified;
175 m_critRepoSec.Unlock();
177 else
178 *status = git_wc_status_modified;
180 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
181 *status = git_wc_status_conflicted;
182 else if (at(index).m_FlagsExtended & GIT_IDXENTRY_INTENT_TO_ADD)
183 *status = git_wc_status_added;
185 if (pHash)
186 *pHash = at(index).m_IndexHash;
188 if (callback && assumeValid && skipWorktree)
189 callback(CombinePath(gitdir, pathorg), *status, false, pData, *assumeValid, *skipWorktree);
191 return 0;
194 int CGitIndexList::GetStatus(const CString& gitdir, CString path, git_wc_status_kind* status,
195 BOOL IsFull, BOOL /*IsRecursive*/,
196 FILL_STATUS_CALLBACK callback, void *pData,
197 CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
199 __int64 time, filesize = 0;
200 bool isDir = false;
202 if (!status)
203 return 0;
205 git_wc_status_kind dirstatus = git_wc_status_none;
206 int result;
207 if (path.IsEmpty())
208 result = CGit::GetFileModifyTime(gitdir, &time, &isDir);
209 else
210 result = CGit::GetFileModifyTime(CombinePath(gitdir, path), &time, &isDir, &filesize);
212 if (result)
214 *status = git_wc_status_deleted;
215 if (callback && assumeValid && skipWorktree)
216 callback(CombinePath(gitdir, path), git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
218 return 0;
221 if (!isDir)
223 GetFileStatus(gitdir, path, status, time, filesize, callback, pData, pHash, assumeValid, skipWorktree);
224 return 0;
227 if (!path.IsEmpty() && path.Right(1) != _T('\\'))
228 path += _T('\\');
230 int len = path.GetLength();
232 for (auto it = cbegin(), itend = cend(); it != itend; ++it)
234 if (!((*it).m_FileName.GetLength() > len && (*it).m_FileName.Left(len) == path))
235 continue;
237 if (!IsFull)
239 *status = git_wc_status_normal;
240 if (callback)
241 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);
243 return 0;
246 result = CGit::GetFileModifyTime(CombinePath(gitdir, (*it).m_FileName), &time, nullptr, &filesize);
247 if (result)
248 continue;
250 *status = git_wc_status_none;
251 if (assumeValid)
252 *assumeValid = false;
253 if (skipWorktree)
254 *skipWorktree = false;
256 GetFileStatus(gitdir, (*it).m_FileName, status, time, filesize, callback, pData, NULL, assumeValid, skipWorktree);
258 // 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
259 if (callback && assumeValid && skipWorktree && (*assumeValid || *skipWorktree))
260 callback(CombinePath(gitdir, path), *status, false, pData, *assumeValid, *skipWorktree);
261 if (*status != git_wc_status_none)
263 if (dirstatus == git_wc_status_none)
264 dirstatus = git_wc_status_normal;
265 if (*status != git_wc_status_normal)
266 dirstatus = git_wc_status_modified;
268 } /* End For */
270 if (dirstatus != git_wc_status_none)
271 *status = dirstatus;
272 else
273 *status = git_wc_status_unversioned;
275 if (callback)
276 callback(CombinePath(gitdir, path), *status, false, pData, false, false);
278 return 0;
281 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
283 __int64 time;
285 CString IndexFile = g_AdminDirMap.GetAdminDirConcat(gitdir, _T("index"));
287 if (CGit::GetFileModifyTime(IndexFile, &time))
288 return -1;
290 SHARED_INDEX_PTR pIndex;
291 pIndex = this->SafeGet(gitdir);
293 if (pIndex.get() == NULL)
295 if(isChanged)
296 *isChanged = true;
297 return 0;
300 if (pIndex->m_LastModifyTime == time)
302 if (isChanged)
303 *isChanged = false;
305 else
307 if (isChanged)
308 *isChanged = true;
310 return 0;
313 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
315 SHARED_INDEX_PTR pIndex(new CGitIndexList);
317 if (pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
318 return -1;
320 this->SafeSet(gitdir, pIndex);
322 return 0;
325 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
326 FILL_STATUS_CALLBACK callback, void *pData,
327 CGitHash *pHash,
328 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
330 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
332 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
333 if (pIndex.get())
334 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
335 else
337 // git working tree has not index
338 *status = git_wc_status_unversioned;
341 return 0;
344 int CGitIndexFileMap::IsUnderVersionControl(const CString& gitdir, CString subpath, bool isDir, bool* isVersion, bool isLoadUpdateIndex)
346 if (subpath.IsEmpty())
348 *isVersion = true;
349 return 0;
352 subpath.Replace(_T('\\'), _T('/'));
353 if (isDir)
354 subpath += _T('/');
356 subpath.MakeLower();
358 CheckAndUpdate(gitdir, isLoadUpdateIndex);
360 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
362 if (pIndex.get())
364 if (isDir)
365 *isVersion = (SearchInSortVector(*pIndex, subpath, subpath.GetLength()) >= 0);
366 else
367 *isVersion = (SearchInSortVector(*pIndex, subpath, -1) >= 0);
370 return 0;
373 // This method is assumed to be called with m_SharedMutex locked.
374 int CGitHeadFileList::GetPackRef(const CString &gitdir)
376 CString PackRef = g_AdminDirMap.GetAdminDirConcat(gitdir, _T("packed-refs"));
378 __int64 mtime;
379 if (CGit::GetFileModifyTime(PackRef, &mtime))
381 //packed refs is not existed
382 this->m_PackRefFile.Empty();
383 this->m_PackRefMap.clear();
384 return 0;
386 else if(mtime == m_LastModifyTimePackRef)
387 return 0;
388 else
390 this->m_PackRefFile = PackRef;
391 this->m_LastModifyTimePackRef = mtime;
394 m_PackRefMap.clear();
396 CAutoFile hfile = CreateFile(PackRef,
397 GENERIC_READ,
398 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
399 nullptr,
400 OPEN_EXISTING,
401 FILE_ATTRIBUTE_NORMAL,
402 nullptr);
404 if (!hfile)
405 return -1;
407 DWORD filesize = GetFileSize(hfile, nullptr);
408 if (filesize == 0)
409 return -1;
411 DWORD size = 0;
412 auto buff = std::make_unique<char[]>(filesize);
413 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
415 if (size != filesize)
416 return -1;
418 CString hash;
419 CString ref;
420 for (DWORD i = 0; i < filesize;)
422 hash.Empty();
423 ref.Empty();
424 if (buff[i] == '#' || buff[i] == '^')
426 while (buff[i] != '\n')
428 ++i;
429 if (i == filesize)
430 break;
432 ++i;
435 if (i >= filesize)
436 break;
438 while (buff[i] != ' ')
440 hash.AppendChar(buff[i]);
441 ++i;
442 if (i == filesize)
443 break;
446 ++i;
447 if (i >= filesize)
448 break;
450 while (buff[i] != '\n')
452 ref.AppendChar(buff[i]);
453 ++i;
454 if (i == filesize)
455 break;
458 if (!ref.IsEmpty())
459 m_PackRefMap[ref] = hash;
461 while (buff[i] == '\n')
463 ++i;
464 if (i == filesize)
465 break;
468 return 0;
470 int CGitHeadFileList::ReadHeadHash(const CString& gitdir)
472 CAutoWriteLock lock(m_SharedMutex);
473 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
475 m_HeadFile = m_Gitdir;
476 m_HeadFile += _T("HEAD");
478 if( CGit::GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
479 return -1;
481 CAutoFile hfile = CreateFile(m_HeadFile,
482 GENERIC_READ,
483 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
484 nullptr,
485 OPEN_EXISTING,
486 FILE_ATTRIBUTE_NORMAL,
487 nullptr);
489 if (!hfile)
490 return -1;
492 DWORD size = 0;
493 unsigned char buffer[40];
494 ReadFile(hfile, buffer, 4, &size, nullptr);
495 if (size != 4)
496 return -1;
497 buffer[4] = 0;
498 if (strcmp((const char*)buffer, "ref:") == 0)
500 m_HeadRefFile.Empty();
501 DWORD filesize = GetFileSize(hfile, nullptr);
502 if (filesize < 5)
503 return -1;
505 unsigned char *p = (unsigned char*)malloc(filesize - 4);
506 if (!p)
507 return -1;
509 ReadFile(hfile, p, filesize - 4, &size, nullptr);
510 CGit::StringAppend(&m_HeadRefFile, p, CP_UTF8, filesize - 4);
511 free(p);
513 CString ref = m_HeadRefFile.Trim();
514 int start = 0;
515 ref = ref.Tokenize(_T("\n"), start);
516 m_HeadRefFile = m_Gitdir + m_HeadRefFile;
517 m_HeadRefFile.Replace(_T('/'), _T('\\'));
519 __int64 time;
520 if (CGit::GetFileModifyTime(m_HeadRefFile, &time, nullptr))
522 m_HeadRefFile.Empty();
523 if (GetPackRef(gitdir))
524 return -1;
525 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
526 return -1;
528 m_Head = m_PackRefMap[ref];
529 return 0;
532 CAutoFile href = CreateFile(m_HeadRefFile,
533 GENERIC_READ,
534 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
535 nullptr,
536 OPEN_EXISTING,
537 FILE_ATTRIBUTE_NORMAL,
538 nullptr);
540 if (!href)
542 m_HeadRefFile.Empty();
544 if (GetPackRef(gitdir))
545 return -1;
547 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
548 return -1;
550 m_Head = m_PackRefMap[ref];
551 return 0;
554 ReadFile(href, buffer, 40, &size, nullptr);
555 if (size != 40)
556 return -1;
558 m_Head.ConvertFromStrA((char*)buffer);
560 m_LastModifyTimeRef = time;
562 return 0;
565 ReadFile(hfile, buffer + 4, 40 - 4, &size, nullptr);
566 if (size != 36)
567 return -1;
569 m_HeadRefFile.Empty();
571 m_Head.ConvertFromStrA((char*)buffer);
573 return 0;
576 bool CGitHeadFileList::CheckHeadUpdate()
578 CAutoReadLock lock(m_SharedMutex);
579 if (this->m_HeadFile.IsEmpty())
580 return true;
582 __int64 mtime=0;
584 if (CGit::GetFileModifyTime(m_HeadFile, &mtime))
585 return true;
587 if (mtime != this->m_LastModifyTimeHead)
588 return true;
590 if (!this->m_HeadRefFile.IsEmpty())
592 if (CGit::GetFileModifyTime(m_HeadRefFile, &mtime))
593 return true;
595 if (mtime != this->m_LastModifyTimeRef)
596 return true;
599 if(!this->m_PackRefFile.IsEmpty())
601 if (CGit::GetFileModifyTime(m_PackRefFile, &mtime))
602 return true;
604 if (mtime != this->m_LastModifyTimePackRef)
605 return true;
608 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
609 // So we need to retry again and again until the ref exists - otherwise we will never notice
610 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
611 return true;
613 return false;
616 bool CGitHeadFileList::HeadHashEqualsTreeHash()
618 CAutoReadLock lock(m_SharedMutex);
619 return (m_Head == m_TreeHash);
622 bool CGitHeadFileList::HeadFileIsEmpty()
624 CAutoReadLock lock(m_SharedMutex);
625 return m_HeadFile.IsEmpty();
628 bool CGitHeadFileList::HeadIsEmpty()
630 CAutoReadLock lock(m_SharedMutex);
631 return m_Head.IsEmpty();
634 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
635 const char *pathname, unsigned mode, int /*stage*/, void *context)
637 #define S_IFGITLINK 0160000
639 CGitHeadFileList *p = (CGitHeadFileList*)context;
641 if ((mode & S_IFDIR) && (mode & S_IFMT) != S_IFGITLINK)
642 return READ_TREE_RECURSIVE;
644 size_t cur = p->size();
645 p->resize(p->size() + 1);
646 p->at(cur).m_Hash = sha1;
647 p->at(cur).m_FileName.Empty();
649 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
650 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)pathname, CP_UTF8);
652 p->at(cur).m_FileName.MakeLower();
654 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
656 //p->m_Map[p->at(cur).m_FileName] = cur;
658 if( (mode&S_IFMT) == S_IFGITLINK)
659 return 0;
661 return READ_TREE_RECURSIVE;
664 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)
666 size_t count = git_tree_entrycount(tree);
667 for (size_t i = 0; i < count; ++i)
669 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
670 if (entry == NULL)
671 continue;
672 int mode = git_tree_entry_filemode(entry);
673 if( CallBack(git_tree_entry_id(entry)->id,
674 base,
675 base.GetLength(),
676 git_tree_entry_name(entry),
677 mode,
679 data) == READ_TREE_RECURSIVE
682 if(mode&S_IFDIR)
684 git_object *object = NULL;
685 git_tree_entry_to_object(&object, &repo, entry);
686 if (object == NULL)
687 continue;
688 CStringA parent = base;
689 parent += git_tree_entry_name(entry);
690 parent += "/";
691 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
692 git_object_free(object);
698 return 0;
701 // ReadTree is/must only be executed on an empty list
702 int CGitHeadFileList::ReadTree()
704 CAutoWriteLock lock(m_SharedMutex);
705 ATLASSERT(empty());
707 CAutoRepository repository(m_Gitdir);
708 CAutoCommit commit;
709 CAutoTree tree;
710 bool ret = repository;
711 ret = ret && !git_commit_lookup(commit.GetPointer(), repository, (const git_oid*)m_Head.m_hash);
712 ret = ret && !git_commit_tree(tree.GetPointer(), commit);
713 ret = ret && !ReadTreeRecursive(*repository, tree, "", CGitHeadFileList::CallBack, this);
714 if (!ret)
716 clear();
717 m_LastModifyTimeHead = 0;
718 return -1;
721 std::sort(this->begin(), this->end(), SortTree);
722 m_TreeHash = git_commit_id(commit)->id;
724 return 0;
726 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
728 if (this->m_pExcludeList)
730 git_free_exclude_list(m_pExcludeList);
731 m_pExcludeList=NULL;
733 free(m_buffer);
734 m_buffer = nullptr;
736 this->m_BaseDir.Empty();
737 if (!isGlobal)
739 CString base = file.Mid(projectroot.GetLength() + 1);
740 base.Replace(_T('\\'), _T('/'));
742 int start = base.ReverseFind(_T('/'));
743 if(start > 0)
745 base = base.Left(start);
746 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
750 if (CGit::GetFileModifyTime(file, &m_LastModifyTime))
751 return -1;
753 CAutoFile hfile = CreateFile(file,
754 GENERIC_READ,
755 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
756 nullptr,
757 OPEN_EXISTING,
758 FILE_ATTRIBUTE_NORMAL,
759 nullptr);
761 if (!hfile)
762 return -1 ;
764 DWORD filesize = GetFileSize(hfile, nullptr);
765 if (filesize == INVALID_FILE_SIZE)
766 return -1;
768 m_buffer = new BYTE[filesize + 1];
769 if (!m_buffer)
770 return -1;
772 DWORD size = 0;
773 if (!ReadFile(hfile, m_buffer, filesize, &size, NULL))
775 free(m_buffer);
776 m_buffer = nullptr;
777 return -1;
779 m_buffer[size] = 0;
781 if (git_create_exclude_list(&m_pExcludeList))
783 free(m_buffer);
784 m_buffer = nullptr;
785 return -1;
788 BYTE *p = m_buffer;
789 int line = 0;
790 for (DWORD i = 0; i < size; ++i)
792 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
794 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
795 m_buffer[i] = 0;
797 if (p[0] != '#' && p[0] != 0)
798 git_add_exclude((const char*)p, this->m_BaseDir, m_BaseDir.GetLength(), this->m_pExcludeList, ++line);
800 p = m_buffer + i + 1;
804 if (!line)
806 git_free_exclude_list(m_pExcludeList);
807 m_pExcludeList = nullptr;
808 free(m_buffer);
809 m_buffer = nullptr;
812 return 0;
815 #ifdef GTEST_INCLUDE_GTEST_GTEST_H_
816 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, int& type)
818 int pos = patha.ReverseFind('/');
819 const char* base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
821 return IsPathIgnored(patha, base, type);
823 #endif
825 int CGitIgnoreItem::IsPathIgnored(const CStringA& patha, const char* base, int& type)
827 if (!m_pExcludeList)
828 return -1; // error or undecided
830 return git_check_excluded_1(patha, patha.GetLength(), base, &type, m_pExcludeList);
833 bool CGitIgnoreList::CheckFileChanged(const CString &path)
835 __int64 time = 0;
837 int ret = CGit::GetFileModifyTime(path, &time);
839 bool cacheExist;
841 CAutoReadLock lock(m_SharedMutex);
842 cacheExist = (m_Map.find(path) != m_Map.end());
845 if (!cacheExist && ret == 0)
847 CAutoWriteLock lock(m_SharedMutex);
848 m_Map[path].m_LastModifyTime = 0;
850 // both cache and file is not exist
851 if ((ret != 0) && (!cacheExist))
852 return false;
854 // file exist but cache miss
855 if ((ret == 0) && (!cacheExist))
856 return true;
858 // file not exist but cache exist
859 if ((ret != 0) && (cacheExist))
861 return true;
863 // file exist and cache exist
866 CAutoReadLock lock(m_SharedMutex);
867 if (m_Map[path].m_LastModifyTime == time)
868 return false;
870 return true;
873 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir, const CString &path, bool isDir)
875 CString temp(gitdir);
876 temp += _T('\\');
877 temp += path;
879 temp.Replace(_T('/'), _T('\\'));
881 if (!isDir)
883 int x = temp.ReverseFind(_T('\\'));
884 if (x >= 2)
885 temp = temp.Left(x);
888 while(!temp.IsEmpty())
890 CString tempOrig = temp;
891 temp += _T("\\.git");
893 if (CGit::GitPathFileExists(temp))
895 CString gitignore=temp;
896 gitignore += _T("ignore");
897 if (CheckFileChanged(gitignore))
898 return true;
900 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
901 CString wcglobalgitignore = adminDir + _T("info\\exclude");
902 if (CheckFileChanged(wcglobalgitignore))
903 return true;
905 if (CheckAndUpdateCoreExcludefile(adminDir))
906 return true;
908 return false;
911 temp += _T("ignore");
912 if (CheckFileChanged(temp))
913 return true;
915 int found=0;
916 int i;
917 for (i = temp.GetLength() - 1; i >= 0; --i)
919 if(temp[i] == _T('\\'))
920 ++found;
922 if(found == 2)
923 break;
926 temp = temp.Left(i);
928 return true;
931 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
933 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
935 CAutoWriteLock lock(m_SharedMutex);
936 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
938 else
940 CAutoWriteLock lock(m_SharedMutex);
941 m_Map.erase(gitignore);
943 return 0;
946 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir, const CString &path, bool isDir)
948 CString temp(gitdir);
949 temp += _T('\\');
950 temp += path;
952 temp.Replace(_T('/'), _T('\\'));
954 if (!isDir)
956 int x = temp.ReverseFind(_T('\\'));
957 if (x >= 2)
958 temp = temp.Left(x);
961 while (!temp.IsEmpty())
963 CString tempOrig = temp;
964 temp += _T("\\.git");
966 if (CGit::GitPathFileExists(temp))
968 CString gitignore = temp;
969 gitignore += _T("ignore");
970 if (CheckFileChanged(gitignore))
971 FetchIgnoreFile(gitdir, gitignore, false);
973 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
974 CString wcglobalgitignore = adminDir;
975 wcglobalgitignore += _T("info\\exclude");
976 if (CheckFileChanged(wcglobalgitignore))
978 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
981 if (CheckAndUpdateCoreExcludefile(adminDir))
983 CString excludesFile;
985 CAutoReadLock lock(m_SharedMutex);
986 excludesFile = m_CoreExcludesfiles[adminDir];
988 if (!excludesFile.IsEmpty())
989 FetchIgnoreFile(gitdir, excludesFile, true);
992 return 0;
995 temp += _T("ignore");
996 if (CheckFileChanged(temp))
997 FetchIgnoreFile(gitdir, temp, false);
999 int found = 0;
1000 int i;
1001 for (i = temp.GetLength() - 1; i >= 0; --i)
1003 if(temp[i] == _T('\\'))
1004 ++found;
1006 if(found == 2)
1007 break;
1010 temp = temp.Left(i);
1012 return 0;
1014 bool CGitIgnoreList::CheckAndUpdateGitSystemConfigPath(bool force)
1016 // recheck every 30 seconds
1017 if (GetTickCount64() - m_dGitSystemConfigPathLastChecked > 30000UL || force)
1019 m_dGitSystemConfigPathLastChecked = GetTickCount64();
1020 CString gitSystemConfigPath(CRegString(REG_SYSTEM_GITCONFIGPATH, _T(""), FALSE));
1021 if (gitSystemConfigPath != m_sGitSystemConfigPath)
1023 m_sGitSystemConfigPath = gitSystemConfigPath;
1024 return true;
1027 return false;
1029 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1031 CString projectConfig(adminDir);
1032 projectConfig += _T("config");
1033 CString globalConfig = g_Git.GetGitGlobalConfig();
1034 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1036 CAutoWriteLock lock(m_coreExcludefilesSharedMutex);
1037 bool hasChanged = CheckAndUpdateGitSystemConfigPath();
1038 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1039 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1040 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1041 if (!m_sGitSystemConfigPath.IsEmpty())
1042 hasChanged = hasChanged || CheckFileChanged(m_sGitSystemConfigPath);
1044 CString excludesFile;
1046 CAutoReadLock lock2(m_SharedMutex);
1047 excludesFile = m_CoreExcludesfiles[adminDir];
1049 if (!excludesFile.IsEmpty())
1050 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1052 if (!hasChanged)
1053 return false;
1055 CAutoConfig config(true);
1056 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
1057 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
1058 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
1059 if (!m_sGitSystemConfigPath.IsEmpty())
1060 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(m_sGitSystemConfigPath), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
1061 config.GetString(_T("core.excludesfile"), excludesFile);
1062 if (excludesFile.IsEmpty())
1063 excludesFile = GetWindowsHome() + _T("\\.config\\git\\ignore");
1064 else if (excludesFile.Find(_T("~/")) == 0)
1065 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1067 CAutoWriteLock lockMap(m_SharedMutex);
1068 CGit::GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1069 CGit::GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1070 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1071 m_Map.erase(globalXDGConfig);
1072 CGit::GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1073 if (m_Map[globalConfig].m_LastModifyTime == 0)
1074 m_Map.erase(globalConfig);
1075 if (!m_sGitSystemConfigPath.IsEmpty())
1076 CGit::GetFileModifyTime(m_sGitSystemConfigPath, &m_Map[m_sGitSystemConfigPath].m_LastModifyTime);
1077 if (m_Map[m_sGitSystemConfigPath].m_LastModifyTime == 0 || m_sGitSystemConfigPath.IsEmpty())
1078 m_Map.erase(m_sGitSystemConfigPath);
1079 m_CoreExcludesfiles[adminDir] = excludesFile;
1081 return true;
1083 const CString CGitIgnoreList::GetWindowsHome()
1085 static CString sWindowsHome(g_Git.GetHomeDirectory());
1086 return sWindowsHome;
1088 bool CGitIgnoreList::IsIgnore(CString str, const CString& projectroot, bool isDir)
1090 str.Replace(_T('\\'),_T('/'));
1092 if (!str.IsEmpty() && str[str.GetLength() - 1] == _T('/'))
1093 str = str.Left(str.GetLength() - 1);
1095 int ret;
1096 ret = CheckIgnore(str, projectroot, isDir);
1097 while (ret < 0)
1099 int start = str.ReverseFind(_T('/'));
1100 if(start < 0)
1101 return (ret == 1);
1103 str = str.Left(start);
1104 ret = CheckIgnore(str, projectroot, isDir);
1107 return (ret == 1);
1109 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1111 if (m_Map.find(ignorefile) == m_Map.end())
1112 return -1; // error or undecided
1114 return (m_Map[ignorefile].IsPathIgnored(patha, base, type));
1116 int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, bool isDir)
1118 CString temp = CombinePath(projectroot, path);
1119 temp.Replace(_T('/'), _T('\\'));
1121 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1122 patha.Replace('\\', '/');
1124 int type = 0;
1125 if (isDir)
1127 type = DT_DIR;
1129 // strip directory name
1130 // we do not need to check for a .ignore file inside a directory we might ignore
1131 int i = temp.ReverseFind(_T('\\'));
1132 if (i >= 0)
1133 temp = temp.Left(i);
1135 else
1137 type = DT_REG;
1139 int x = temp.ReverseFind(_T('\\'));
1140 if (x >= 2)
1141 temp = temp.Left(x);
1144 int pos = patha.ReverseFind('/');
1145 const char * base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
1147 int ret = -1;
1149 CAutoReadLock lock(m_SharedMutex);
1150 while (!temp.IsEmpty())
1152 CString tempOrig = temp;
1153 temp += _T("\\.git");
1155 if (CGit::GitPathFileExists(temp))
1157 CString gitignore = temp;
1158 gitignore += _T("ignore");
1159 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1160 break;
1162 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1163 CString wcglobalgitignore = adminDir;
1164 wcglobalgitignore += _T("info\\exclude");
1165 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1166 break;
1168 CString excludesFile = m_CoreExcludesfiles[adminDir];
1169 if (!excludesFile.IsEmpty())
1170 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1172 break;
1175 temp += _T("ignore");
1176 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1177 break;
1179 int found = 0;
1180 int i;
1181 for (i = temp.GetLength() - 1; i >= 0; i--)
1183 if (temp[i] == _T('\\'))
1184 ++found;
1186 if (found == 2)
1187 break;
1190 temp = temp.Left(i);
1193 return ret;
1196 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir, bool readTree /* = true */)
1198 SHARED_TREE_PTR ptr = this->SafeGet(gitdir, true);
1200 if (ptr.get() && !ptr->CheckHeadUpdate() && (!readTree || ptr->HeadHashEqualsTreeHash()))
1201 return false;
1203 ptr = SHARED_TREE_PTR(new CGitHeadFileList);
1204 ptr->ReadHeadHash(gitdir);
1205 if (readTree)
1206 ptr->ReadTree();
1208 this->SafeSet(gitdir, ptr);
1210 return true;
1213 int CGitHeadFileMap::IsUnderVersionControl(const CString& gitdir, CString subpath, bool isDir, bool* isVersion)
1215 if (subpath.IsEmpty())
1217 *isVersion = true;
1218 return 0;
1221 subpath.Replace(_T('\\'), _T('/'));
1222 if (isDir)
1223 subpath += _T('/');
1225 subpath.MakeLower();
1227 CheckHeadAndUpdate(gitdir);
1229 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1231 // Init Repository
1232 if (treeptr->HeadFileIsEmpty())
1234 *isVersion = false;
1235 return 0;
1237 if (treeptr->empty())
1239 *isVersion = false;
1240 return 1;
1243 if (isDir)
1244 *isVersion = (SearchInSortVector(*treeptr, subpath, subpath.GetLength()) >= 0);
1245 else
1246 *isVersion = (SearchInSortVector(*treeptr, subpath, -1) >= 0);
1248 return 0;