refactored ignore file checking
[TortoiseGit.git] / src / Git / gitindex.h
blobe7fe88651d4083d878031a40234cc35792e1bb0c
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2012 - 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 "GitHash.h"
21 #include "gitdll.h"
22 #include "gitstatus.h"
23 #include "SharedMutex.h"
25 /* Copy from Git cache.h*/
26 #define FLEX_ARRAY 4
28 #pragma pack(push)
29 #pragma pack(1)
30 //#pragma pack(show)
31 #define CACHE_SIGNATURE 0x44495243 /* "DIRC" */
32 struct cache_header {
33 unsigned int hdr_signature;
34 unsigned int hdr_version;
35 unsigned int hdr_entries;
39 * The "cache_time" is just the low 32 bits of the
40 * time. It doesn't matter if it overflows - we only
41 * check it for equality in the 32 bits we save.
43 struct cache_time {
44 UINT32 sec;
45 UINT32 nsec;
49 * dev/ino/uid/gid/size are also just tracked to the low 32 bits
50 * Again - this is just a (very strong in practice) heuristic that
51 * the inode hasn't changed.
53 * We save the fields in big-endian order to allow using the
54 * index file over NFS transparently.
56 struct ondisk_cache_entry {
57 struct cache_time ctime;
58 struct cache_time mtime;
59 UINT32 dev;
60 UINT32 ino;
61 UINT32 mode;
62 UINT32 uid;
63 UINT32 gid;
64 UINT32 size;
65 BYTE sha1[20];
66 UINT16 flags;
67 char name[FLEX_ARRAY]; /* more */
71 * This struct is used when CE_EXTENDED bit is 1
72 * The struct must match ondisk_cache_entry exactly from
73 * ctime till flags
75 struct ondisk_cache_entry_extended {
76 struct cache_time ctime;
77 struct cache_time mtime;
78 UINT32 dev;
79 UINT32 ino;
80 UINT32 mode;
81 UINT32 uid;
82 UINT32 gid;
83 UINT32 size;
84 BYTE sha1[20];
85 UINT16 flags;
86 UINT16 flags2;
87 char name[FLEX_ARRAY]; /* more */
90 #pragma pack(pop)
92 #define CE_NAMEMASK (0x0fff)
93 #define CE_STAGEMASK (0x3000)
94 #define CE_EXTENDED (0x4000)
95 #define CE_VALID (0x8000)
96 #define CE_STAGESHIFT 12
98 * Range 0xFFFF0000 in ce_flags is divided into
99 * two parts: in-memory flags and on-disk ones.
100 * Flags in CE_EXTENDED_FLAGS will get saved on-disk
101 * if you want to save a new flag, add it in
102 * CE_EXTENDED_FLAGS
104 * In-memory only flags
106 #define CE_UPDATE (0x10000)
107 #define CE_REMOVE (0x20000)
108 #define CE_UPTODATE (0x40000)
109 #define CE_ADDED (0x80000)
111 #define CE_HASHED (0x100000)
112 #define CE_UNHASHED (0x200000)
115 * Extended on-disk flags
117 #define CE_INTENT_TO_ADD 0x20000000
118 /* CE_EXTENDED2 is for future extension */
119 #define CE_EXTENDED2 0x80000000
121 #define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD)
124 * Safeguard to avoid saving wrong flags:
125 * - CE_EXTENDED2 won't get saved until its semantic is known
126 * - Bits in 0x0000FFFF have been saved in ce_flags already
127 * - Bits in 0x003F0000 are currently in-memory flags
129 #if CE_EXTENDED_FLAGS & 0x803FFFFF
130 #error "CE_EXTENDED_FLAGS out of range"
131 #endif
134 * Copy the sha1 and stat state of a cache entry from one to
135 * another. But we never change the name, or the hash state!
137 #define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
139 template<class T>
140 T Big2lit(T data)
142 T ret;
143 BYTE *p1=(BYTE*)&data;
144 BYTE *p2=(BYTE*)&ret;
145 for(int i=0;i<sizeof(T);i++)
147 p2[sizeof(T)-i-1] = p1[i];
149 return ret;
152 template<class T>
153 static inline size_t ce_namelen(T *ce)
155 size_t len = Big2lit(ce->flags) & CE_NAMEMASK;
156 if (len < CE_NAMEMASK)
157 return len;
158 return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK;
161 #define flexible_size(STRUCT,len) ((offsetof(STRUCT,name) + (len) + 8) & ~7)
163 //#define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
164 //#define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
166 //#define ondisk_ce_size(ce) (((ce)->flags & CE_EXTENDED) ? \
167 // ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
168 // ondisk_cache_entry_size(ce_namelen(ce)))
170 template<class T>
171 static inline size_t ondisk_ce_size(T *ce)
173 return flexible_size(T,ce_namelen(ce));
176 class CGitIndex
178 public:
179 CString m_FileName;
180 __time64_t m_ModifyTime;
181 int m_Flags;
182 //int m_Status;
183 CGitHash m_IndexHash;
185 int FillData(ondisk_cache_entry* entry);
186 int FillData(ondisk_cache_entry_extended* entry);
187 int Print();
191 class CAutoReadLock
193 SharedMutex *m_Lock;
194 public:
195 CAutoReadLock(SharedMutex * lock)
197 m_Lock = lock;
198 lock->AcquireShared();
200 ~CAutoReadLock()
202 m_Lock->ReleaseShared();
206 class CAutoWriteLock
208 SharedMutex *m_Lock;
209 public:
210 CAutoWriteLock(SharedMutex * lock)
212 m_Lock = lock;
213 lock->AcquireExclusive();
215 ~CAutoWriteLock()
217 m_Lock->ReleaseExclusive();
221 class CGitIndexList:public std::vector<CGitIndex>
223 protected:
225 public:
226 __time64_t m_LastModifyTime;
228 #ifdef DEBUG
229 CString m_GitFile;
230 ~CGitIndexList()
232 //TRACE(_T("Free Index List 0x%x %s"),this, m_GitFile);
234 #endif
236 CGitIndexList();
238 int ReadIndex(CString file);
239 int GetStatus(const CString &gitdir,const CString &path,git_wc_status_kind * status,BOOL IsFull=false, BOOL IsRecursive=false,FIll_STATUS_CALLBACK callback=NULL,void *pData=NULL,CGitHash *pHash=NULL);
240 protected:
241 int GetFileStatus(const CString &gitdir,const CString &path, git_wc_status_kind * status,__int64 time,FIll_STATUS_CALLBACK callback=NULL,void *pData=NULL,CGitHash *pHash=NULL);
242 int GetDirStatus(const CString &gitdir,const CString &path, git_wc_status_kind * status,__int64 time,FIll_STATUS_CALLBACK callback=NULL,void *pData=NULL,CGitHash *pHash=NULL);
245 typedef std::tr1::shared_ptr<CGitIndexList> SHARED_INDEX_PTR;
246 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
248 class CGitIndexFileMap:public std::map<CString, SHARED_INDEX_PTR>
250 public:
251 CComCriticalSection m_critIndexSec;
253 CGitIndexFileMap() { m_critIndexSec.Init(); }
254 ~CGitIndexFileMap() { m_critIndexSec.Term(); }
256 SHARED_INDEX_PTR SafeGet(const CString &path)
258 CString thePath = path;
259 thePath.MakeLower();
260 CAutoLocker lock(m_critIndexSec);
261 if(this->find(thePath) == end())
262 return SHARED_INDEX_PTR();
263 else
264 return (*this)[thePath];
267 void SafeSet(const CString &path, SHARED_INDEX_PTR ptr)
269 CString thePath = path;
270 thePath.MakeLower();
271 CAutoLocker lock(m_critIndexSec);
272 (*this)[thePath] = ptr;
275 int Check(const CString &gitdir, bool *isChanged);
276 int LoadIndex(const CString &gitdir);
278 bool CheckAndUpdate(const CString &gitdir,bool isLoadUpdatedIndex)
280 bool isChanged=false;
281 if(isLoadUpdatedIndex && Check(gitdir,&isChanged))
282 return false;
284 if(isChanged && isLoadUpdatedIndex)
286 LoadIndex(gitdir);
287 return true;
290 return false;
292 int GetFileStatus(const CString &gitdir,const CString &path,git_wc_status_kind * status,
293 BOOL IsFull=false, BOOL IsRecursive=false,
294 FIll_STATUS_CALLBACK callback=NULL,
295 void *pData=NULL,CGitHash *pHash=NULL,
296 bool isLoadUpdatedIndex=true);
298 int IsUnderVersionControl(const CString &gitdir,
299 const CString &path,
300 bool isDir,
301 bool *isVersion,
302 bool isLoadUpdateIndex=true);
306 class CGitTreeItem
308 public:
309 CString m_FileName;
310 CGitHash m_Hash;
311 int m_Flags;
314 /* After object create, never change field agains
315 * that needn't lock to get field
317 class CGitHeadFileList:public std::vector<CGitTreeItem>
319 private:
321 int GetPackRef(const CString &gitdir);
323 public:
324 __time64_t m_LastModifyTimeHead;
325 __time64_t m_LastModifyTimeRef;
326 __time64_t m_LastModifyTimePackRef;
328 CString m_HeadRefFile;
329 CGitHash m_Head;
330 CString m_HeadFile;
331 CString m_Gitdir;
332 CString m_PackRefFile;
334 CGitHash m_TreeHash; /* buffered tree hash value */
336 std::map<CString,CGitHash> m_PackRefMap;
338 CGitHeadFileList()
340 m_LastModifyTimeHead=0;
341 m_LastModifyTimeRef=0;
342 m_LastModifyTimePackRef = 0;
345 #ifdef DEBUG
346 CString m_GitFile;
347 ~CGitHeadFileList()
349 //TRACE(_T("Free Index List 0x%x %s"),this, m_GitFile);
351 #endif
353 int ReadTree();
354 int ReadHeadHash(CString gitdir);
355 bool CheckHeadUpdate();
356 static int CallBack(const unsigned char *, const char *, int, const char *, unsigned int, int, void *);
357 //int ReadTree();
360 typedef std::tr1::shared_ptr<CGitHeadFileList> SHARED_TREE_PTR;
361 class CGitHeadFileMap:public std::map<CString,SHARED_TREE_PTR>
363 public:
365 CComCriticalSection m_critTreeSec;
367 CGitHeadFileMap() { m_critTreeSec.Init(); }
368 ~CGitHeadFileMap() { m_critTreeSec.Term(); }
370 SHARED_TREE_PTR SafeGet(const CString &path)
372 CString thePath = path;
373 thePath.MakeLower();
374 CAutoLocker lock(m_critTreeSec);
375 if(this->find(thePath) == end())
376 return SHARED_TREE_PTR();
377 else
378 return (*this)[thePath];
381 void SafeSet(const CString &path, SHARED_TREE_PTR ptr)
383 CString thePath = path;
384 thePath.MakeLower();
385 CAutoLocker lock(m_critTreeSec);
386 (*this)[thePath] = ptr;
389 int GetFileStatus(const CString &gitdir,const CString &path,git_wc_status_kind * status,BOOL IsFull=false, BOOL IsRecursive=false,
390 FIll_STATUS_CALLBACK callback=NULL,void *pData=NULL,
391 bool isLoaded=false);
392 bool CheckHeadUpdate(const CString &gitdir);
393 int GetHeadHash(const CString &gitdir, CGitHash &hash);
396 bool IsHashChanged(const CString &gitdir)
398 SHARED_TREE_PTR ptr = SafeGet(gitdir);
400 if( ptr.get() == NULL)
401 return false;
403 return ptr->m_Head != ptr->m_TreeHash;
408 class CGitFileName
410 public:
411 CString m_FileName;
412 CString m_CaseFileName;
415 class CGitIgnoreItem
417 public:
418 SharedMutex m_SharedMutex;
420 CGitIgnoreItem()
422 m_LastModifyTime =0;
423 m_pExcludeList =NULL;
425 ~CGitIgnoreItem()
427 if(m_pExcludeList)
428 git_free_exclude_list(m_pExcludeList);
429 m_pExcludeList=NULL;
431 __time64_t m_LastModifyTime;
432 CStringA m_BaseDir;
433 EXCLUDE_LIST m_pExcludeList;
434 int FetchIgnoreList(const CString &projectroot, const CString &file, bool isGlobal);
437 class CGitIgnoreList
439 private:
440 bool CheckFileChanged(const CString &path);
441 int FetchIgnoreFile(const CString &gitdir, const CString &gitignore, bool isGlobal);
443 int CheckIgnore(const CString &path,const CString &root);
444 int CheckFileAgainstIgnoreList(const CString &ignorefile, const CStringA &patha, const char * base, int &type);
446 public:
447 SharedMutex m_SharedMutex;
449 CGitIgnoreList(){ m_SharedMutex.Init(); }
450 ~CGitIgnoreList() { m_SharedMutex.Release(); }
452 std::map<CString, CGitIgnoreItem> m_Map;
454 int GetIgnoreFileChangeTimeList(const CString &dir, std::vector<__int64> &timelist);
455 bool CheckIgnoreChanged(const CString &gitdir,const CString &path);
456 int LoadAllIgnoreFile(const CString &gitdir,const CString &path);
457 bool IsIgnore(const CString &path,const CString &root);
460 template<class T>
461 int GetRangeInSortVector(T &vector,LPTSTR pstr,int len, int *start, int *end, int pos)
463 if( pos < 0)
465 return -1;
467 if(start == 0 || end == NULL)
468 return -1;
470 *start=*end=-1;
472 if(vector.size() ==0)
473 return -1;
475 if(pos >= vector.size())
476 return -1;
478 if( _tcsnccmp(vector[pos].m_FileName, pstr,len) != 0)
480 for(int i=0;i< vector.size();i++)
482 if( _tcsnccmp(vector[i].m_FileName, pstr,len) == 0 )
484 if(*start<0)
485 *start =i;
486 *end =i;
489 return -1;
491 else
493 *start =0;
494 *end = vector.size();
496 for(int i=pos;i<vector.size();i++)
498 if( _tcsnccmp(vector[i].m_FileName, pstr,len) == 0 )
500 *end=i;
502 else
504 break;
507 for(int i=pos;i>=0;i--)
509 if( _tcsnccmp(vector[i].m_FileName, pstr,len) == 0 )
511 *start=i;
513 else
515 break;
519 return 0;
522 template<class T>
523 int SearchInSortVector(T &vector, LPTSTR pstr, int len)
525 int end=vector.size()-1;
526 int start = 0;
527 int mid = (start+end)/2;
529 if(vector.size() == 0)
530 return -1;
532 while(!( start == end && start==mid))
534 int cmp;
535 if(len < 0)
536 cmp = _tcscmp(vector[mid].m_FileName,pstr);
537 else
538 cmp = _tcsnccmp( vector[mid].m_FileName,pstr,len );
540 if(cmp ==0)
541 return mid;
543 if(cmp < 0)
545 start = mid+1;
548 if(cmp > 0)
550 end=mid;
552 mid=(start +end ) /2;
555 if(len <0)
557 if(_tcscmp(vector[mid].m_FileName,pstr) == 0)
558 return mid;
560 else
562 if(_tcsnccmp( vector[mid].m_FileName,pstr,len ) == 0)
563 return mid;
565 return -1;
567 #if 0
569 class CGitStatus
571 protected:
572 int GetFileStatus(const CString &gitdir,const CString &path,git_wc_status_kind * status,BOOL IsFull=false, BOOL IsRecursive=false,FIll_STATUS_CALLBACK callback=NULL,void *pData=NULL);
573 public:
574 CGitIgnoreList m_IgnoreList;
575 CGitHeadFileMap m_HeadFilesMap;
576 CGitIndexFileMap m_IndexFilesMap;
578 int GetStatus(const CString &gitdir,const CString &path,git_wc_status_kind * status,BOOL IsFull=false, BOOL IsRecursive=false,FIll_STATUS_CALLBACK callback=NULL,void *pData=NULL);
581 #endif
583 class CGitAdminDirMap:public std::map<CString, CString>
585 public:
586 CComCriticalSection m_critIndexSec;
587 std::map<CString, CString> m_reverseLookup;
589 CGitAdminDirMap() { m_critIndexSec.Init(); }
590 ~CGitAdminDirMap() { m_critIndexSec.Term(); }
592 CString GetAdminDir(const CString &path)
594 CString thePath = path;
595 thePath.MakeLower();
596 CAutoLocker lock(m_critIndexSec);
597 if(this->find(thePath) == end())
599 if (PathIsDirectory(thePath + _T("\\.git")))
601 (*this)[thePath] = thePath + _T("\\.git\\");
602 m_reverseLookup[thePath + _T("\\.git")] = thePath;
603 return (*this)[thePath];
605 else
607 FILE * pFile = _tfsopen(thePath + _T("\\.git"), _T("r"), SH_DENYWR);
608 if (pFile)
610 int size = 65536;
611 auto_buffer<char> buffer(size);
612 if (fread(buffer, sizeof(char), size, pFile))
614 fclose(pFile);
615 CString str = CString(buffer);
616 if (str.Left(8) == _T("gitdir: "))
618 str = str.TrimRight().Mid(8);
619 str.Replace(_T("/"), _T("\\"));
620 str.TrimRight(_T("\\"));
621 if (str.GetLength() > 0 && str[0] == _T('.'))
623 str = thePath + _T("\\") + str;
624 CString newPath;
625 PathCanonicalize(newPath.GetBuffer(MAX_PATH), str.GetBuffer());
626 newPath.ReleaseBuffer();
627 str.ReleaseBuffer();
628 str = newPath;
630 (*this)[thePath] = str + _T("\\");
631 m_reverseLookup[str.MakeLower()] = path;
632 return (*this)[thePath];
635 fclose(pFile);
637 return thePath + _T("\\.git\\"); // in case of an error stick to old behavior
640 else
641 return (*this)[thePath];
644 CString GetWorkingCopy(const CString &gitDir)
646 CString path = gitDir;
647 path.MakeLower();
648 CAutoLocker lock(m_critIndexSec);
649 if (m_reverseLookup.find(path) == m_reverseLookup.end())
650 return gitDir;
651 else
652 return m_reverseLookup[path];