Use CUnicodeUtils::GetUnicode instead of CGit::StringAppend if possible
[TortoiseGit.git] / src / Git / GitIndex.cpp
blob06e68e2bb9b8bf7c07b4cd61250179341c99fdbb
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2014 - 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 <map>
24 #include "UnicodeUtils.h"
25 #include "TGitPath.h"
26 #include "gitindex.h"
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include "SmartHandle.h"
30 #include "git2/sys/repository.h"
32 CGitAdminDirMap g_AdminDirMap;
34 int CGitIndex::Print()
36 _tprintf(_T("0x%08X 0x%08X %s %s\n"),
37 (int)this->m_ModifyTime,
38 this->m_Flags,
39 this->m_IndexHash.ToString(),
40 this->m_FileName);
42 return 0;
45 CGitIndexList::CGitIndexList()
47 this->m_LastModifyTime = 0;
48 m_critRepoSec.Init();
49 m_bCheckContent = !!(CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContent"), TRUE) == TRUE);
50 m_iMaxCheckSize = (__int64)CRegDWORD(_T("Software\\TortoiseGit\\TGitCacheCheckContentMaxSize"), 10 * 1024) * 1024; // stored in KiB
53 CGitIndexList::~CGitIndexList()
55 m_critRepoSec.Term();
58 static bool SortIndex(const CGitIndex &Item1, const CGitIndex &Item2)
60 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
63 static bool SortTree(const CGitTreeItem &Item1, const CGitTreeItem &Item2)
65 return Item1.m_FileName.Compare(Item2.m_FileName) < 0;
68 int CGitIndexList::ReadIndex(CString dgitdir)
70 this->clear();
72 m_critRepoSec.Lock();
73 if (repository.Open(dgitdir))
75 m_critRepoSec.Unlock();
76 return -1;
79 // add config files
80 CAutoConfig config(true);
82 CString projectConfig = dgitdir + _T("config");
83 CString globalConfig = g_Git.GetGitGlobalConfig();
84 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
85 CString msysGitBinPath(CRegString(REG_MSYSGIT_PATH, _T(""), FALSE));
87 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
88 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
89 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
90 if (!msysGitBinPath.IsEmpty())
91 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(msysGitBinPath + _T("\\..\\etc\\gitconfig")), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
93 git_repository_set_config(repository, config);
95 CAutoIndex index;
96 // load index in order to enumerate files
97 if (git_repository_index(index.GetPointer(), repository))
99 repository.Free();
100 m_critRepoSec.Unlock();
101 return -1;
104 size_t ecount = git_index_entrycount(index);
105 resize(ecount);
106 for (size_t i = 0; i < ecount; ++i)
108 const git_index_entry *e = git_index_get_byindex(index, i);
110 this->at(i).m_FileName.Empty();
111 this->at(i).m_FileName = CUnicodeUtils::GetUnicode(e->path);
112 this->at(i).m_FileName.MakeLower();
113 this->at(i).m_ModifyTime = e->mtime.seconds;
114 this->at(i).m_Flags = e->flags | e->flags_extended;
115 this->at(i).m_IndexHash = e->id.id;
116 this->at(i).m_Size = e->file_size;
119 g_Git.GetFileModifyTime(dgitdir + _T("index"), &this->m_LastModifyTime);
120 std::sort(this->begin(), this->end(), SortIndex);
122 m_critRepoSec.Unlock();
124 return 0;
127 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)
129 if(status)
131 CString path = pathorg;
132 path.MakeLower();
134 int start = SearchInSortVector(*this, path, -1);
136 if (start < 0)
138 *status = git_wc_status_unversioned;
139 if (pHash)
140 pHash->Empty();
143 else
145 int index = start;
146 if (index >= (int)size())
147 return -1;
149 // skip-worktree has higher priority than assume-valid
150 if (at(index).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE)
152 *status = git_wc_status_normal;
153 if (skipWorktree)
154 *skipWorktree = true;
156 else if (at(index).m_Flags & GIT_IDXENTRY_VALID)
158 *status = git_wc_status_normal;
159 if (assumeValid)
160 *assumeValid = true;
162 else if (filesize != at(index).m_Size)
163 *status = git_wc_status_modified;
164 else if (time == at(index).m_ModifyTime)
166 *status = git_wc_status_normal;
168 else if (m_bCheckContent && repository && filesize < m_iMaxCheckSize)
170 git_oid actual;
171 CStringA fileA = CUnicodeUtils::GetMulti(pathorg, CP_UTF8);
172 m_critRepoSec.Lock(); // prevent concurrent access to repository instance and especially filter-lists
173 if (!git_repository_hashfile(&actual, repository, fileA, GIT_OBJ_BLOB, NULL) && !git_oid_cmp(&actual, (const git_oid*)at(index).m_IndexHash.m_hash))
175 at(index).m_ModifyTime = time;
176 *status = git_wc_status_normal;
178 else
179 *status = git_wc_status_modified;
180 m_critRepoSec.Unlock();
182 else
183 *status = git_wc_status_modified;
185 if (at(index).m_Flags & GIT_IDXENTRY_STAGEMASK)
186 *status = git_wc_status_conflicted;
187 else if (at(index).m_Flags & GIT_IDXENTRY_INTENT_TO_ADD)
188 *status = git_wc_status_added;
190 if(pHash)
191 *pHash = at(index).m_IndexHash;
196 if (callback && status && assumeValid && skipWorktree)
197 callback(gitdir + _T("\\") + pathorg, *status, false, pData, *assumeValid, *skipWorktree);
198 return 0;
201 int CGitIndexList::GetStatus(const CString &gitdir,const CString &pathParam, git_wc_status_kind *status,
202 BOOL IsFull, BOOL /*IsRecursive*/,
203 FILL_STATUS_CALLBACK callback, void *pData,
204 CGitHash *pHash, bool * assumeValid, bool * skipWorktree)
206 __int64 time, filesize = 0;
207 bool isDir = false;
208 CString path = pathParam;
210 if (status)
212 git_wc_status_kind dirstatus = git_wc_status_none;
213 int result;
214 if (path.IsEmpty())
215 result = g_Git.GetFileModifyTime(gitdir, &time, &isDir);
216 else
217 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + path, &time, &isDir, &filesize);
219 if (result)
221 *status = git_wc_status_deleted;
222 if (callback && assumeValid && skipWorktree)
223 callback(gitdir + _T("\\") + path, git_wc_status_deleted, false, pData, *assumeValid, *skipWorktree);
225 return 0;
227 if (isDir)
229 if (!path.IsEmpty())
231 if (path.Right(1) != _T("\\"))
232 path += _T("\\");
234 int len = path.GetLength();
236 for (size_t i = 0; i < size(); ++i)
238 if (at(i).m_FileName.GetLength() > len)
240 if (at(i).m_FileName.Left(len) == path)
242 if (!IsFull)
244 *status = git_wc_status_normal;
245 if (callback)
246 callback(gitdir + _T("\\") + path, *status, false, pData, (at(i).m_Flags & GIT_IDXENTRY_VALID) && !(at(i).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE), (at(i).m_Flags & GIT_IDXENTRY_SKIP_WORKTREE) != 0);
247 return 0;
250 else
252 result = g_Git.GetFileModifyTime(gitdir + _T("\\") + at(i).m_FileName, &time, nullptr, &filesize);
253 if (result)
254 continue;
256 *status = git_wc_status_none;
257 if (assumeValid)
258 *assumeValid = false;
259 if (skipWorktree)
260 *skipWorktree = false;
261 GetFileStatus(gitdir, at(i).m_FileName, status, time, filesize, callback, pData, NULL, assumeValid, skipWorktree);
262 // 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
263 if (callback && assumeValid && skipWorktree && (*assumeValid || *skipWorktree))
264 callback(gitdir + _T("\\") + path, *status, false, pData, *assumeValid, *skipWorktree);
265 if (*status != git_wc_status_none)
267 if (dirstatus == git_wc_status_none)
269 dirstatus = git_wc_status_normal;
271 if (*status != git_wc_status_normal)
273 dirstatus = git_wc_status_modified;
280 } /* End For */
282 if (dirstatus != git_wc_status_none)
284 *status = dirstatus;
286 else
288 *status = git_wc_status_unversioned;
290 if(callback)
291 callback(gitdir + _T("\\") + path, *status, false, pData, false, false);
293 return 0;
296 else
298 GetFileStatus(gitdir, path, status, time, filesize, callback, pData, pHash, assumeValid, skipWorktree);
301 return 0;
304 int CGitIndexFileMap::Check(const CString &gitdir, bool *isChanged)
306 __int64 time;
307 int result;
309 CString IndexFile = g_AdminDirMap.GetAdminDir(gitdir) + _T("index");
311 /* Get data associated with "crt_stat.c": */
312 result = g_Git.GetFileModifyTime(IndexFile, &time);
314 if (result)
315 return result;
317 SHARED_INDEX_PTR pIndex;
318 pIndex = this->SafeGet(gitdir);
320 if (pIndex.get() == NULL)
322 if(isChanged)
323 *isChanged = true;
324 return 0;
327 if (pIndex->m_LastModifyTime == time)
329 if (isChanged)
330 *isChanged = false;
332 else
334 if (isChanged)
335 *isChanged = true;
337 return 0;
340 int CGitIndexFileMap::LoadIndex(const CString &gitdir)
344 SHARED_INDEX_PTR pIndex(new CGitIndexList);
346 if(pIndex->ReadIndex(g_AdminDirMap.GetAdminDir(gitdir)))
347 return -1;
349 this->SafeSet(gitdir, pIndex);
351 }catch(...)
353 return -1;
355 return 0;
358 int CGitIndexFileMap::GetFileStatus(const CString &gitdir, const CString &path, git_wc_status_kind *status,BOOL IsFull, BOOL IsRecursive,
359 FILL_STATUS_CALLBACK callback, void *pData,
360 CGitHash *pHash,
361 bool isLoadUpdatedIndex, bool * assumeValid, bool * skipWorktree)
365 CheckAndUpdate(gitdir, isLoadUpdatedIndex);
367 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
368 if (pIndex.get() != NULL)
370 pIndex->GetStatus(gitdir, path, status, IsFull, IsRecursive, callback, pData, pHash, assumeValid, skipWorktree);
372 else
374 // git working tree has not index
375 *status = git_wc_status_unversioned;
378 catch(...)
380 return -1;
382 return 0;
385 int CGitIndexFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir,bool *isVersion, bool isLoadUpdateIndex)
389 if (path.IsEmpty())
391 *isVersion = true;
392 return 0;
395 CString subpath = path;
396 subpath.Replace(_T('\\'), _T('/'));
397 if(isDir)
398 subpath += _T('/');
400 subpath.MakeLower();
402 CheckAndUpdate(gitdir, isLoadUpdateIndex);
404 SHARED_INDEX_PTR pIndex = this->SafeGet(gitdir);
406 if(pIndex.get())
408 if(isDir)
409 *isVersion = (SearchInSortVector(*pIndex, subpath, subpath.GetLength()) >= 0);
410 else
411 *isVersion = (SearchInSortVector(*pIndex, subpath, -1) >= 0);
414 }catch(...)
416 return -1;
418 return 0;
421 // This method is assumed to be called with m_SharedMutex locked.
422 int CGitHeadFileList::GetPackRef(const CString &gitdir)
424 CString PackRef = g_AdminDirMap.GetAdminDir(gitdir) + _T("packed-refs");
426 __int64 mtime;
427 if (g_Git.GetFileModifyTime(PackRef, &mtime))
429 //packed refs is not existed
430 this->m_PackRefFile.Empty();
431 this->m_PackRefMap.clear();
432 return 0;
434 else if(mtime == m_LastModifyTimePackRef)
436 return 0;
438 else
440 this->m_PackRefFile = PackRef;
441 this->m_LastModifyTimePackRef = mtime;
444 m_PackRefMap.clear();
446 CAutoFile hfile = CreateFile(PackRef,
447 GENERIC_READ,
448 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
449 nullptr,
450 OPEN_EXISTING,
451 FILE_ATTRIBUTE_NORMAL,
452 nullptr);
454 if (!hfile)
455 return -1;
457 DWORD filesize = GetFileSize(hfile, nullptr);
458 if (filesize == 0)
459 return -1;
461 DWORD size = 0;
462 std::unique_ptr<char[]> buff(new char[filesize]);
463 ReadFile(hfile, buff.get(), filesize, &size, nullptr);
465 if (size != filesize)
466 return -1;
468 CString hash;
469 CString ref;
470 for (DWORD i = 0; i < filesize;)
472 hash.Empty();
473 ref.Empty();
474 if (buff[i] == '#' || buff[i] == '^')
476 while (buff[i] != '\n')
478 ++i;
479 if (i == filesize)
480 break;
482 ++i;
485 if (i >= filesize)
486 break;
488 while (buff[i] != ' ')
490 hash.AppendChar(buff[i]);
491 ++i;
492 if (i == filesize)
493 break;
496 ++i;
497 if (i >= filesize)
498 break;
500 while (buff[i] != '\n')
502 ref.AppendChar(buff[i]);
503 ++i;
504 if (i == filesize)
505 break;
508 if (!ref.IsEmpty())
509 m_PackRefMap[ref] = hash;
511 while (buff[i] == '\n')
513 ++i;
514 if (i == filesize)
515 break;
518 return 0;
520 int CGitHeadFileList::ReadHeadHash(CString gitdir)
522 CAutoWriteLock lock(m_SharedMutex);
523 m_Gitdir = g_AdminDirMap.GetAdminDir(gitdir);
525 m_HeadFile = m_Gitdir + _T("HEAD");
527 if( g_Git.GetFileModifyTime(m_HeadFile, &m_LastModifyTimeHead))
528 return -1;
530 CAutoFile hfile = CreateFile(m_HeadFile,
531 GENERIC_READ,
532 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
533 nullptr,
534 OPEN_EXISTING,
535 FILE_ATTRIBUTE_NORMAL,
536 nullptr);
538 if (!hfile)
539 return -1;
541 DWORD size = 0;
542 unsigned char buffer[40];
543 ReadFile(hfile, buffer, 4, &size, nullptr);
544 if (size != 4)
545 return -1;
546 buffer[4] = 0;
547 if (strcmp((const char*)buffer, "ref:") == 0)
549 m_HeadRefFile.Empty();
550 DWORD filesize = GetFileSize(hfile, nullptr);
551 if (filesize < 5)
552 return -1;
554 unsigned char *p = (unsigned char*)malloc(filesize - 4);
555 if (!p)
556 return -1;
558 ReadFile(hfile, p, filesize - 4, &size, nullptr);
559 CGit::StringAppend(&m_HeadRefFile, p, CP_UTF8, filesize - 4);
560 free(p);
562 CString ref = m_HeadRefFile.Trim();
563 int start = 0;
564 ref = ref.Tokenize(_T("\n"), start);
565 m_HeadRefFile = m_Gitdir + m_HeadRefFile;
566 m_HeadRefFile.Replace(_T('/'), _T('\\'));
568 __int64 time;
569 if (g_Git.GetFileModifyTime(m_HeadRefFile, &time, nullptr))
571 m_HeadRefFile.Empty();
572 if (GetPackRef(gitdir))
573 return -1;
574 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
575 return -1;
577 m_Head = m_PackRefMap[ref];
578 return 0;
581 CAutoFile href = CreateFile(m_HeadRefFile,
582 GENERIC_READ,
583 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
584 nullptr,
585 OPEN_EXISTING,
586 FILE_ATTRIBUTE_NORMAL,
587 nullptr);
589 if (!href)
591 m_HeadRefFile.Empty();
593 if (GetPackRef(gitdir))
594 return -1;
596 if (m_PackRefMap.find(ref) == m_PackRefMap.end())
597 return -1;
599 m_Head = m_PackRefMap[ref];
600 return 0;
603 ReadFile(href, buffer, 40, &size, nullptr);
604 if (size != 40)
605 return -1;
607 m_Head.ConvertFromStrA((char*)buffer);
609 m_LastModifyTimeRef = time;
611 else
613 ReadFile(hfile, buffer + 4, 40 - 4, &size, NULL);
614 if (size != 36)
615 return -1;
617 m_HeadRefFile.Empty();
619 m_Head.ConvertFromStrA((char*)buffer);
622 return 0;
625 bool CGitHeadFileList::CheckHeadUpdate()
627 CAutoReadLock lock(m_SharedMutex);
628 if (this->m_HeadFile.IsEmpty())
629 return true;
631 __int64 mtime=0;
633 if (g_Git.GetFileModifyTime(m_HeadFile, &mtime))
634 return true;
636 if (mtime != this->m_LastModifyTimeHead)
637 return true;
639 if (!this->m_HeadRefFile.IsEmpty())
641 if (g_Git.GetFileModifyTime(m_HeadRefFile, &mtime))
642 return true;
644 if (mtime != this->m_LastModifyTimeRef)
645 return true;
648 if(!this->m_PackRefFile.IsEmpty())
650 if (g_Git.GetFileModifyTime(m_PackRefFile, &mtime))
651 return true;
653 if (mtime != this->m_LastModifyTimePackRef)
654 return true;
657 // in an empty repo HEAD points to refs/heads/master, but this ref doesn't exist.
658 // So we need to retry again and again until the ref exists - otherwise we will never notice
659 if (this->m_Head.IsEmpty() && this->m_HeadRefFile.IsEmpty() && this->m_PackRefFile.IsEmpty())
660 return true;
662 return false;
665 bool CGitHeadFileList::HeadHashEqualsTreeHash()
667 CAutoReadLock lock(m_SharedMutex);
668 return (m_Head == m_TreeHash);
671 bool CGitHeadFileList::HeadFileIsEmpty()
673 CAutoReadLock lock(m_SharedMutex);
674 return m_HeadFile.IsEmpty();
677 bool CGitHeadFileList::HeadIsEmpty()
679 CAutoReadLock lock(m_SharedMutex);
680 return m_Head.IsEmpty();
683 int CGitHeadFileList::CallBack(const unsigned char *sha1, const char *base, int baselen,
684 const char *pathname, unsigned mode, int /*stage*/, void *context)
686 #define S_IFGITLINK 0160000
688 CGitHeadFileList *p = (CGitHeadFileList*)context;
689 if( mode&S_IFDIR )
691 if( (mode&S_IFMT) != S_IFGITLINK)
692 return READ_TREE_RECURSIVE;
695 size_t cur = p->size();
696 p->resize(p->size() + 1);
697 p->at(cur).m_Hash = sha1;
698 p->at(cur).m_FileName.Empty();
700 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)base, CP_UTF8, baselen);
701 CGit::StringAppend(&p->at(cur).m_FileName, (BYTE*)pathname, CP_UTF8);
703 p->at(cur).m_FileName.MakeLower();
705 //p->at(cur).m_FileName.Replace(_T('/'), _T('\\'));
707 //p->m_Map[p->at(cur).m_FileName] = cur;
709 if( (mode&S_IFMT) == S_IFGITLINK)
710 return 0;
712 return READ_TREE_RECURSIVE;
715 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)
717 size_t count = git_tree_entrycount(tree);
718 for (size_t i = 0; i < count; ++i)
720 const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
721 if (entry == NULL)
722 continue;
723 int mode = git_tree_entry_filemode(entry);
724 if( CallBack(git_tree_entry_id(entry)->id,
725 base,
726 base.GetLength(),
727 git_tree_entry_name(entry),
728 mode,
730 data) == READ_TREE_RECURSIVE
733 if(mode&S_IFDIR)
735 git_object *object = NULL;
736 git_tree_entry_to_object(&object, &repo, entry);
737 if (object == NULL)
738 continue;
739 CStringA parent = base;
740 parent += git_tree_entry_name(entry);
741 parent += "/";
742 ReadTreeRecursive(repo, (git_tree*)object, parent, CallBack, data);
743 git_object_free(object);
749 return 0;
752 // ReadTree is/must only be executed on an empty list
753 int CGitHeadFileList::ReadTree()
755 CAutoWriteLock lock(m_SharedMutex);
756 ATLASSERT(empty());
758 CAutoRepository repository(m_Gitdir);
759 CAutoCommit commit;
760 CAutoTree tree;
761 bool ret = repository;
762 ret = ret && !git_commit_lookup(commit.GetPointer(), repository, (const git_oid*)m_Head.m_hash);
763 ret = ret && !git_commit_tree(tree.GetPointer(), commit);
764 ret = ret && !ReadTreeRecursive(*repository, tree, "", CGitHeadFileList::CallBack, this);
765 if (!ret)
767 clear();
768 m_LastModifyTimeHead = 0;
769 return -1;
772 std::sort(this->begin(), this->end(), SortTree);
773 m_TreeHash = git_commit_id(commit)->id;
775 return 0;
777 int CGitIgnoreItem::FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal)
779 if (this->m_pExcludeList)
781 git_free_exclude_list(m_pExcludeList);
782 m_pExcludeList=NULL;
784 free(m_buffer);
785 m_buffer = nullptr;
787 this->m_BaseDir.Empty();
788 if (!isGlobal)
790 CString base = file.Mid(projectroot.GetLength() + 1);
791 base.Replace(_T('\\'), _T('/'));
793 int start = base.ReverseFind(_T('/'));
794 if(start > 0)
796 base = base.Left(start);
797 this->m_BaseDir = CUnicodeUtils::GetMulti(base, CP_UTF8) + "/";
802 if(g_Git.GetFileModifyTime(file, &m_LastModifyTime))
803 return -1;
805 if(git_create_exclude_list(&this->m_pExcludeList))
806 return -1;
809 CAutoFile hfile = CreateFile(file,
810 GENERIC_READ,
811 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
812 NULL,
813 OPEN_EXISTING,
814 FILE_ATTRIBUTE_NORMAL,
815 NULL);
818 if (!hfile)
819 return -1 ;
821 DWORD size=0,filesize=0;
823 filesize=GetFileSize(hfile, NULL);
825 if(filesize == INVALID_FILE_SIZE)
826 return -1;
828 m_buffer = new BYTE[filesize + 1];
830 if (m_buffer == NULL)
831 return -1;
833 if (!ReadFile(hfile, m_buffer, filesize, &size, NULL))
834 return GetLastError();
836 BYTE *p = m_buffer;
837 int line = 0;
838 for (DWORD i = 0; i < size; ++i)
840 if (m_buffer[i] == '\n' || m_buffer[i] == '\r' || i == (size - 1))
842 if (m_buffer[i] == '\n' || m_buffer[i] == '\r')
843 m_buffer[i] = 0;
844 if (i == size - 1)
845 m_buffer[size] = 0;
847 if(p[0] != '#' && p[0] != 0)
848 git_add_exclude((const char*)p,
849 this->m_BaseDir,
850 m_BaseDir.GetLength(),
851 this->m_pExcludeList, ++line);
853 p = m_buffer + i + 1;
857 return 0;
860 bool CGitIgnoreList::CheckFileChanged(const CString &path)
862 __int64 time = 0;
864 int ret = g_Git.GetFileModifyTime(path, &time);
866 bool cacheExist;
868 CAutoReadLock lock(m_SharedMutex);
869 cacheExist = (m_Map.find(path) != m_Map.end());
872 if (!cacheExist && ret == 0)
874 CAutoWriteLock lock(m_SharedMutex);
875 m_Map[path].m_LastModifyTime = 0;
877 // both cache and file is not exist
878 if ((ret != 0) && (!cacheExist))
879 return false;
881 // file exist but cache miss
882 if ((ret == 0) && (!cacheExist))
883 return true;
885 // file not exist but cache exist
886 if ((ret != 0) && (cacheExist))
888 return true;
890 // file exist and cache exist
893 CAutoReadLock lock(m_SharedMutex);
894 if (m_Map[path].m_LastModifyTime == time)
895 return false;
897 return true;
900 bool CGitIgnoreList::CheckIgnoreChanged(const CString &gitdir, const CString &path, bool isDir)
902 CString temp;
903 temp = gitdir;
904 temp += _T("\\");
905 temp += path;
907 temp.Replace(_T('/'), _T('\\'));
909 if (!isDir)
911 int x = temp.ReverseFind(_T('\\'));
912 if (x >= 2)
913 temp = temp.Left(x);
916 while(!temp.IsEmpty())
918 CString tempOrig = temp;
919 temp += _T("\\.git");
921 if (CGit::GitPathFileExists(temp))
923 CString gitignore=temp;
924 gitignore += _T("ignore");
925 if (CheckFileChanged(gitignore))
926 return true;
928 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
929 CString wcglobalgitignore = adminDir + _T("info\\exclude");
930 if (CheckFileChanged(wcglobalgitignore))
931 return true;
933 if (CheckAndUpdateCoreExcludefile(adminDir))
934 return true;
936 return false;
938 else
940 temp += _T("ignore");
941 if (CheckFileChanged(temp))
942 return true;
945 int found=0;
946 int i;
947 for (i = temp.GetLength() - 1; i >= 0; i--)
949 if(temp[i] == _T('\\'))
950 ++found;
952 if(found == 2)
953 break;
956 temp = temp.Left(i);
958 return true;
961 int CGitIgnoreList::FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal)
963 if (CGit::GitPathFileExists(gitignore)) //if .gitignore remove, we need remote cache
965 CAutoWriteLock lock(m_SharedMutex);
966 m_Map[gitignore].FetchIgnoreList(gitdir, gitignore, isGlobal);
968 else
970 CAutoWriteLock lock(m_SharedMutex);
971 m_Map.erase(gitignore);
973 return 0;
976 int CGitIgnoreList::LoadAllIgnoreFile(const CString &gitdir, const CString &path, bool isDir)
978 CString temp;
980 temp = gitdir;
981 temp += _T("\\");
982 temp += path;
984 temp.Replace(_T('/'), _T('\\'));
986 if (!isDir)
988 int x = temp.ReverseFind(_T('\\'));
989 if (x >= 2)
990 temp = temp.Left(x);
993 while (!temp.IsEmpty())
995 CString tempOrig = temp;
996 temp += _T("\\.git");
998 if (CGit::GitPathFileExists(temp))
1000 CString gitignore = temp;
1001 gitignore += _T("ignore");
1002 if (CheckFileChanged(gitignore))
1004 FetchIgnoreFile(gitdir, gitignore, false);
1007 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1008 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1009 if (CheckFileChanged(wcglobalgitignore))
1011 FetchIgnoreFile(gitdir, wcglobalgitignore, true);
1014 if (CheckAndUpdateCoreExcludefile(adminDir))
1016 CString excludesFile;
1018 CAutoReadLock lock(m_SharedMutex);
1019 excludesFile = m_CoreExcludesfiles[adminDir];
1021 if (!excludesFile.IsEmpty())
1022 FetchIgnoreFile(gitdir, excludesFile, true);
1025 return 0;
1027 else
1029 temp += _T("ignore");
1030 if (CheckFileChanged(temp))
1032 FetchIgnoreFile(gitdir, temp, false);
1036 int found = 0;
1037 int i;
1038 for (i = temp.GetLength() - 1; i >= 0; i--)
1040 if(temp[i] == _T('\\'))
1041 ++found;
1043 if(found == 2)
1044 break;
1047 temp = temp.Left(i);
1049 return 0;
1051 bool CGitIgnoreList::CheckAndUpdateMsysGitBinpath(bool force)
1053 // recheck every 30 seconds
1054 if (GetTickCount() - m_dMsysGitBinPathLastChecked > 30000 || force)
1056 m_dMsysGitBinPathLastChecked = GetTickCount();
1057 CString msysGitBinPath(CRegString(REG_MSYSGIT_PATH, _T(""), FALSE));
1058 if (msysGitBinPath != m_sMsysGitBinPath)
1060 m_sMsysGitBinPath = msysGitBinPath;
1061 return true;
1064 return false;
1066 bool CGitIgnoreList::CheckAndUpdateCoreExcludefile(const CString &adminDir)
1068 CString projectConfig = adminDir + _T("config");
1069 CString globalConfig = g_Git.GetGitGlobalConfig();
1070 CString globalXDGConfig = g_Git.GetGitGlobalXDGConfig();
1072 CAutoWriteLock lock(m_coreExcludefilesSharedMutex);
1073 bool hasChanged = CheckAndUpdateMsysGitBinpath();
1074 CString systemConfig = m_sMsysGitBinPath + _T("\\..\\etc\\gitconfig");
1076 hasChanged = hasChanged || CheckFileChanged(projectConfig);
1077 hasChanged = hasChanged || CheckFileChanged(globalConfig);
1078 hasChanged = hasChanged || CheckFileChanged(globalXDGConfig);
1079 if (!m_sMsysGitBinPath.IsEmpty())
1080 hasChanged = hasChanged || CheckFileChanged(systemConfig);
1082 CString excludesFile;
1084 CAutoReadLock lock(m_SharedMutex);
1085 excludesFile = m_CoreExcludesfiles[adminDir];
1087 if (!excludesFile.IsEmpty())
1088 hasChanged = hasChanged || CheckFileChanged(excludesFile);
1090 if (!hasChanged)
1091 return false;
1093 CAutoConfig config(true);
1094 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(projectConfig), GIT_CONFIG_LEVEL_LOCAL, FALSE);
1095 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalConfig), GIT_CONFIG_LEVEL_GLOBAL, FALSE);
1096 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(globalXDGConfig), GIT_CONFIG_LEVEL_XDG, FALSE);
1097 if (!m_sMsysGitBinPath.IsEmpty())
1098 git_config_add_file_ondisk(config, CGit::GetGitPathStringA(systemConfig), GIT_CONFIG_LEVEL_SYSTEM, FALSE);
1099 config.GetString(_T("core.excludesfile"), excludesFile);
1100 if (excludesFile.IsEmpty())
1101 excludesFile = GetWindowsHome() + _T("\\.config\\git\\ignore");
1102 else if (excludesFile.Find(_T("~/")) == 0)
1103 excludesFile = GetWindowsHome() + excludesFile.Mid(1);
1105 CAutoWriteLock lockMap(m_SharedMutex);
1106 g_Git.GetFileModifyTime(projectConfig, &m_Map[projectConfig].m_LastModifyTime);
1107 g_Git.GetFileModifyTime(globalXDGConfig, &m_Map[globalXDGConfig].m_LastModifyTime);
1108 if (m_Map[globalXDGConfig].m_LastModifyTime == 0)
1109 m_Map.erase(globalXDGConfig);
1110 g_Git.GetFileModifyTime(globalConfig, &m_Map[globalConfig].m_LastModifyTime);
1111 if (m_Map[globalConfig].m_LastModifyTime == 0)
1112 m_Map.erase(globalConfig);
1113 if (!m_sMsysGitBinPath.IsEmpty())
1114 g_Git.GetFileModifyTime(systemConfig, &m_Map[systemConfig].m_LastModifyTime);
1115 if (m_Map[systemConfig].m_LastModifyTime == 0 || m_sMsysGitBinPath.IsEmpty())
1116 m_Map.erase(systemConfig);
1117 m_CoreExcludesfiles[adminDir] = excludesFile;
1119 return true;
1121 const CString CGitIgnoreList::GetWindowsHome()
1123 static CString sWindowsHome(g_Git.GetHomeDirectory());
1124 return sWindowsHome;
1126 bool CGitIgnoreList::IsIgnore(const CString &path, const CString &projectroot, bool isDir)
1128 CString str=path;
1130 str.Replace(_T('\\'),_T('/'));
1132 if (str.GetLength()>0)
1133 if (str[str.GetLength()-1] == _T('/'))
1134 str = str.Left(str.GetLength() - 1);
1136 int ret;
1137 ret = CheckIgnore(str, projectroot, isDir);
1138 while (ret < 0)
1140 int start = str.ReverseFind(_T('/'));
1141 if(start < 0)
1142 return (ret == 1);
1144 str = str.Left(start);
1145 ret = CheckIgnore(str, projectroot, isDir);
1148 return (ret == 1);
1150 int CGitIgnoreList::CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type)
1152 if (m_Map.find(ignorefile) != m_Map.end())
1154 int ret = -1;
1155 if(m_Map[ignorefile].m_pExcludeList)
1156 ret = git_check_excluded_1(patha, patha.GetLength(), base, &type, m_Map[ignorefile].m_pExcludeList);
1157 if (ret == 0 || ret == 1)
1158 return ret;
1160 return -1;
1162 int CGitIgnoreList::CheckIgnore(const CString &path, const CString &projectroot, bool isDir)
1164 CString temp = projectroot + _T("\\") + path;
1165 temp.Replace(_T('/'), _T('\\'));
1167 CStringA patha = CUnicodeUtils::GetMulti(path, CP_UTF8);
1168 patha.Replace('\\', '/');
1170 int type = 0;
1171 if (isDir)
1173 type = DT_DIR;
1175 // strip directory name
1176 // we do not need to check for a .ignore file inside a directory we might ignore
1177 int i = temp.ReverseFind(_T('\\'));
1178 if (i >= 0)
1179 temp = temp.Left(i);
1181 else
1183 type = DT_REG;
1185 int x = temp.ReverseFind(_T('\\'));
1186 if (x >= 2)
1187 temp = temp.Left(x);
1190 int pos = patha.ReverseFind('/');
1191 const char * base = (pos >= 0) ? ((const char*)patha + pos + 1) : patha;
1193 int ret = -1;
1195 CAutoReadLock lock(m_SharedMutex);
1196 while (!temp.IsEmpty())
1198 CString tempOrig = temp;
1199 temp += _T("\\.git");
1201 if (CGit::GitPathFileExists(temp))
1203 CString gitignore = temp;
1204 gitignore += _T("ignore");
1205 if ((ret = CheckFileAgainstIgnoreList(gitignore, patha, base, type)) != -1)
1206 break;
1208 CString adminDir = g_AdminDirMap.GetAdminDir(tempOrig);
1209 CString wcglobalgitignore = adminDir + _T("info\\exclude");
1210 if ((ret = CheckFileAgainstIgnoreList(wcglobalgitignore, patha, base, type)) != -1)
1211 break;
1213 CString excludesFile = m_CoreExcludesfiles[adminDir];
1214 if (!excludesFile.IsEmpty())
1215 ret = CheckFileAgainstIgnoreList(excludesFile, patha, base, type);
1217 break;
1219 else
1221 temp += _T("ignore");
1222 if ((ret = CheckFileAgainstIgnoreList(temp, patha, base, type)) != -1)
1223 break;
1226 int found = 0;
1227 int i;
1228 for (i = temp.GetLength() - 1; i >= 0; i--)
1230 if (temp[i] == _T('\\'))
1231 ++found;
1233 if (found == 2)
1234 break;
1237 temp = temp.Left(i);
1240 return ret;
1243 bool CGitHeadFileMap::CheckHeadAndUpdate(const CString &gitdir, bool readTree /* = true */)
1245 SHARED_TREE_PTR ptr;
1246 ptr = this->SafeGet(gitdir);
1248 if (ptr.get() && !ptr->CheckHeadUpdate() && (!readTree || ptr->HeadHashEqualsTreeHash()))
1249 return false;
1251 ptr = SHARED_TREE_PTR(new CGitHeadFileList);
1252 ptr->ReadHeadHash(gitdir);
1253 if (readTree)
1254 ptr->ReadTree();
1256 this->SafeSet(gitdir, ptr);
1258 return true;
1261 int CGitHeadFileMap::IsUnderVersionControl(const CString &gitdir, const CString &path, bool isDir, bool *isVersion)
1265 if (path.IsEmpty())
1267 *isVersion = true;
1268 return 0;
1271 CString subpath = path;
1272 subpath.Replace(_T('\\'), _T('/'));
1273 if(isDir)
1274 subpath += _T('/');
1276 subpath.MakeLower();
1278 CheckHeadAndUpdate(gitdir);
1280 SHARED_TREE_PTR treeptr = SafeGet(gitdir);
1282 // Init Repository
1283 if (treeptr->HeadFileIsEmpty())
1285 *isVersion = false;
1286 return 0;
1288 else if (treeptr->empty())
1290 *isVersion = false;
1291 return 1;
1294 if(isDir)
1295 *isVersion = (SearchInSortVector(*treeptr, subpath, subpath.GetLength()) >= 0);
1296 else
1297 *isVersion = (SearchInSortVector(*treeptr, subpath, -1) >= 0);
1299 catch(...)
1301 return -1;
1304 return 0;