Add support for "project" config level
[TortoiseGit.git] / src / TortoiseProc / GitLogCache.cpp
blob107c2598ace5869c10323240e243da67acae32b1
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2013 - 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.
19 #include "stdafx.h"
20 #include "GitLogCache.h"
21 #include "registry.h"
23 int static Compare(const void *p1, const void*p2)
25 return memcmp(p1,p2,20);
28 CLogCache::CLogCache()
30 m_IndexFile = INVALID_HANDLE_VALUE;
31 m_IndexFileMap = INVALID_HANDLE_VALUE;
32 m_pCacheIndex = NULL;
34 m_DataFile = INVALID_HANDLE_VALUE;
35 m_DataFileMap = INVALID_HANDLE_VALUE;
36 m_pCacheData = NULL;
37 m_DataFileLength = 0;
38 m_bEnabled = CRegDWORD(_T("Software\\TortoiseGit\\EnableLogCache"), TRUE);
41 void CLogCache::CloseDataHandles()
43 if(m_pCacheData)
45 UnmapViewOfFile(m_pCacheData);
46 m_pCacheData=NULL;
48 if (m_DataFileMap != INVALID_HANDLE_VALUE)
50 CloseHandle(m_DataFileMap);
51 m_DataFileMap=INVALID_HANDLE_VALUE;
53 if (m_DataFile != INVALID_HANDLE_VALUE)
55 CloseHandle(m_DataFile);
56 m_DataFile = INVALID_HANDLE_VALUE;
60 void CLogCache::CloseIndexHandles()
62 if(m_pCacheIndex)
64 UnmapViewOfFile(m_pCacheIndex);
65 m_pCacheIndex = NULL;
68 if (m_IndexFileMap != INVALID_HANDLE_VALUE)
70 CloseHandle(m_IndexFileMap);
71 m_IndexFileMap = INVALID_HANDLE_VALUE;
74 //this->m_IndexFile.Close();
75 //this->m_DataFile.Close();
76 if (m_IndexFile != INVALID_HANDLE_VALUE)
78 CloseHandle(m_IndexFile);
79 m_IndexFile=INVALID_HANDLE_VALUE;
83 CLogCache::~CLogCache()
85 CloseIndexHandles();
86 CloseDataHandles();
89 int CLogCache::AddCacheEntry(GitRev &Rev)
91 this->m_HashMap[Rev.m_CommitHash] = Rev;
92 return 0;
95 GitRev * CLogCache::GetCacheData(CGitHash &hash)
97 m_HashMap[hash].m_CommitHash=hash;
98 return &m_HashMap[hash];
101 ULONGLONG CLogCache::GetOffset(CGitHash &hash,SLogCacheIndexFile *pData)
104 if(pData==NULL)
105 pData = m_pCacheIndex;
107 if(pData == 0)
108 return 0;
110 SLogCacheIndexItem *p=(SLogCacheIndexItem *)bsearch(hash.m_hash,pData->m_Item,
111 pData->m_Header.m_ItemCount,
112 sizeof(SLogCacheIndexItem),
113 Compare);
115 if(p)
116 return p->m_Offset;
117 else
118 return 0;
121 int CLogCache::FetchCacheIndex(CString GitDir)
123 if (!m_bEnabled)
124 return 0;
126 int ret=0;
127 if (!g_GitAdminDir.GetAdminDirPath(GitDir, m_GitDir))
128 return -1;
132 if( m_IndexFile == INVALID_HANDLE_VALUE)
134 CString file = m_GitDir + INDEX_FILE_NAME;
135 m_IndexFile = CreateFile(file,
136 GENERIC_READ,
137 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
138 NULL,
139 OPEN_EXISTING,
140 FILE_ATTRIBUTE_NORMAL,
141 NULL);
143 if( m_IndexFile == INVALID_HANDLE_VALUE)
145 ret = -1;
146 break;;
150 if( m_IndexFileMap == INVALID_HANDLE_VALUE)
152 m_IndexFileMap = CreateFileMapping(m_IndexFile, NULL, PAGE_READONLY,0,0,NULL);
153 if( m_IndexFileMap == INVALID_HANDLE_VALUE)
155 ret = -1;
156 break;
160 if( m_pCacheIndex == NULL )
162 m_pCacheIndex = (SLogCacheIndexFile*)MapViewOfFile(m_IndexFileMap,FILE_MAP_READ,0,0,0);
163 if( m_pCacheIndex ==NULL )
165 ret = -1;
166 break;
170 if( !CheckHeader(&m_pCacheIndex->m_Header))
172 ret =-1;
173 break;
176 if( m_DataFile == INVALID_HANDLE_VALUE )
178 CString file = m_GitDir + DATA_FILE_NAME;
179 m_DataFile = CreateFile(file,
180 GENERIC_READ,
181 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
182 NULL,
183 OPEN_EXISTING,
184 FILE_ATTRIBUTE_NORMAL,
185 NULL);
187 if(m_DataFile == INVALID_HANDLE_VALUE)
189 ret =-1;
190 break;
194 if( m_DataFileMap == INVALID_HANDLE_VALUE)
196 m_DataFileMap = CreateFileMapping(m_DataFile, NULL, PAGE_READONLY,0,0,NULL);
197 if( m_DataFileMap == INVALID_HANDLE_VALUE)
199 ret = -1;
200 break;
203 m_DataFileLength = GetFileSize(m_DataFile, NULL);
204 if( m_pCacheData == NULL)
206 m_pCacheData = (BYTE*)MapViewOfFile(m_DataFileMap,FILE_MAP_READ,0,0,0);
207 if( m_pCacheData ==NULL )
209 ret = -1;
210 break;
214 if(!CheckHeader( (SLogCacheDataFileHeader*)m_pCacheData))
216 ret = -1;
217 break;
220 }while(0);
222 if(ret)
224 CloseIndexHandles();
225 ::DeleteFile(m_GitDir + INDEX_FILE_NAME);
226 ::DeleteFile(m_GitDir + DATA_FILE_NAME);
228 return ret;
232 int CLogCache::SaveOneItem(GitRev &Rev, LONG offset)
234 if(!Rev.m_IsDiffFiles)
235 return -1;
237 SetFilePointer(this->m_DataFile, offset,0, 0);
239 SLogCacheRevItemHeader header;
241 header.m_Magic=LOG_DATA_ITEM_MAGIC;
242 header.m_Version=LOG_INDEX_VERSION;
243 header.m_FileCount=Rev.m_Files.GetCount();
245 DWORD num;
247 if(!WriteFile(this->m_DataFile,&header, sizeof(header),&num,0))
248 return -1;
250 CString stat;
251 CString name,oldname;
252 for (int i = 0; i < Rev.m_Files.GetCount(); ++i)
254 SLogCacheRevFileHeader header;
255 header.m_Magic=LOG_DATA_FILE_MAGIC;
256 header.m_Version=LOG_INDEX_VERSION;
257 header.m_Action = Rev.m_Files[i].m_Action;
258 header.m_Stage = Rev.m_Files[i].m_Stage;
259 header.m_ParentNo = Rev.m_Files[i].m_ParentNo;
260 name = Rev.m_Files[i].GetGitPathString();
261 header.m_FileNameSize = name.GetLength();
262 oldname = Rev.m_Files[i].GetGitOldPathString();
263 header.m_OldFileNameSize = oldname.GetLength();
265 stat = Rev.m_Files[i].m_StatAdd;
266 header.m_Add = (stat == _T("-"))? 0xFFFFFFFF:_tstol(stat);
267 stat = Rev.m_Files[i].m_StatDel;
268 header.m_Del = (stat == _T("-"))? 0xFFFFFFFF:_tstol(stat);
270 if(!WriteFile(this->m_DataFile,&header, sizeof(header)-sizeof(TCHAR),&num,0))
271 return -1;
273 if(!name.IsEmpty())
275 if(!WriteFile(this->m_DataFile,name.GetBuffer(), name.GetLength()*sizeof(TCHAR),&num,0))
276 return -1;
278 if(!oldname.IsEmpty())
280 if(!WriteFile(this->m_DataFile,oldname.GetBuffer(), oldname.GetLength()*sizeof(TCHAR),&num,0))
281 return -1;
285 return 0;
288 int CLogCache::LoadOneItem(GitRev &Rev,ULONGLONG offset)
290 if(m_pCacheData == NULL)
291 return -1;
293 if (offset + sizeof(SLogCacheRevItemHeader) > m_DataFileLength)
294 return -2;
296 SLogCacheRevItemHeader *header;
297 header = (SLogCacheRevItemHeader *)(this->m_pCacheData + offset);
299 if( !CheckHeader(header))
300 return -2;
302 Rev.m_Action = 0;
303 SLogCacheRevFileHeader *fileheader;
305 offset += sizeof(SLogCacheRevItemHeader);
306 fileheader =(SLogCacheRevFileHeader *)(this->m_pCacheData + offset);
308 for (DWORD i = 0; i < header->m_FileCount; ++i)
310 CTGitPath path;
311 CString oldfile;
312 path.Reset();
314 if (offset + sizeof(SLogCacheRevFileHeader) > m_DataFileLength)
315 return -2;
317 if(!CheckHeader(fileheader))
318 return -2;
320 CString file(fileheader->m_FileName, fileheader->m_FileNameSize);
321 if(fileheader->m_OldFileNameSize)
323 oldfile = CString(fileheader->m_FileName + fileheader->m_FileNameSize, fileheader->m_OldFileNameSize);
325 path.SetFromGit(file,&oldfile);
327 path.m_ParentNo = fileheader ->m_ParentNo;
328 path.m_Stage = fileheader ->m_Stage;
329 path.m_Action = fileheader ->m_Action;
330 Rev.m_Action |= path.m_Action;
332 if(fileheader->m_Add == 0xFFFFFFFF)
333 path.m_StatAdd=_T("-");
334 else
335 path.m_StatAdd.Format(_T("%d"),fileheader->m_Add);
337 if(fileheader->m_Del == 0xFFFFFFFF)
338 path.m_StatDel=_T("-");
339 else
340 path.m_StatDel.Format(_T("%d"), fileheader->m_Del);
342 Rev.m_Files.AddPath(path);
344 offset += sizeof(*fileheader) + fileheader->m_OldFileNameSize*sizeof(TCHAR) + fileheader->m_FileNameSize*sizeof(TCHAR) - sizeof(TCHAR);
345 fileheader = (SLogCacheRevFileHeader *) (this->m_pCacheData + offset);
347 return 0;
349 int CLogCache::RebuildCacheFile()
351 SLogCacheIndexHeader Indexheader;
353 Indexheader.m_Magic = LOG_INDEX_MAGIC;
354 Indexheader.m_Version = LOG_INDEX_VERSION;
355 Indexheader.m_ItemCount =0;
357 SLogCacheDataFileHeader dataheader;
359 dataheader.m_Magic = LOG_DATA_MAGIC;
360 dataheader.m_Version = LOG_INDEX_VERSION;
362 ::SetFilePointer(m_IndexFile,0,0,0);
363 ::SetFilePointer(m_DataFile,0,0,0);
364 SetEndOfFile(this->m_IndexFile);
365 SetEndOfFile(this->m_DataFile);
367 DWORD num;
368 WriteFile(this->m_IndexFile,&Indexheader,sizeof(SLogCacheIndexHeader),&num,0);
369 SetEndOfFile(this->m_IndexFile);
370 WriteFile(this->m_DataFile,&dataheader,sizeof(SLogCacheDataFileHeader),&num,0);
371 SetEndOfFile(this->m_DataFile);
372 return 0;
374 int CLogCache::SaveCache()
376 if (!m_bEnabled)
377 return 0;
379 int ret =0;
380 BOOL bIsRebuild=false;
382 if (this->m_HashMap.empty()) // is not sufficient, because "working copy changes" are always included
383 return 0;
385 if( this->m_GitDir.IsEmpty())
386 return 0;
388 if (this->m_pCacheIndex && m_pCacheIndex->m_Header.m_ItemCount == 0) // check for empty log list (issue #915)
389 return 0;
391 SLogCacheIndexFile *pIndex = NULL;
392 if(this->m_pCacheIndex)
394 pIndex = (SLogCacheIndexFile *)malloc(sizeof(SLogCacheIndexFile)
395 +sizeof(SLogCacheIndexItem) * (m_pCacheIndex->m_Header.m_ItemCount) );
396 if(pIndex ==NULL)
397 return -1;
399 memcpy(pIndex,this->m_pCacheIndex,
400 sizeof(SLogCacheIndexFile) + sizeof(SLogCacheIndexItem) *( m_pCacheIndex->m_Header.m_ItemCount-1)
404 this->CloseDataHandles();
405 this->CloseIndexHandles();
407 SLogCacheIndexHeader header;
408 CString file = this->m_GitDir + INDEX_FILE_NAME;
411 m_IndexFile = CreateFile(file,
412 GENERIC_READ|GENERIC_WRITE,
414 NULL,
415 OPEN_ALWAYS,
416 FILE_ATTRIBUTE_NORMAL,
417 NULL);
419 if(m_IndexFile == INVALID_HANDLE_VALUE)
421 ret = -1;
422 break;
425 file = m_GitDir + DATA_FILE_NAME;
427 m_DataFile = CreateFile(file,
428 GENERIC_READ|GENERIC_WRITE,
430 NULL,
431 OPEN_ALWAYS,
432 FILE_ATTRIBUTE_NORMAL,
433 NULL);
435 if(m_DataFile == INVALID_HANDLE_VALUE)
437 ret = -1;
438 break;
444 memset(&header,0,sizeof(SLogCacheIndexHeader));
445 DWORD num=0;
446 if((!ReadFile(m_IndexFile,&header, sizeof(SLogCacheIndexHeader),&num,0)) ||
447 !CheckHeader(&header)
450 RebuildCacheFile();
451 bIsRebuild =true;
454 if(!bIsRebuild)
456 SLogCacheDataFileHeader header;
457 DWORD num=0;
458 if((!ReadFile(m_DataFile,&header,sizeof(SLogCacheDataFileHeader),&num,0)||
459 !CheckHeader(&header)))
461 RebuildCacheFile();
462 bIsRebuild=true;
466 if(bIsRebuild)
467 header.m_ItemCount=0;
469 SetFilePointer(m_DataFile,0,0,2);
470 SetFilePointer(m_IndexFile,0,0,2);
472 for (auto i = m_HashMap.begin(); i != m_HashMap.end(); ++i)
474 if(this->GetOffset((*i).second.m_CommitHash,pIndex) ==0 || bIsRebuild)
476 if((*i).second.m_IsDiffFiles && !(*i).second.m_CommitHash.IsEmpty())
478 LARGE_INTEGER offset;
479 offset.LowPart=0;
480 offset.HighPart=0;
481 LARGE_INTEGER start;
482 start.QuadPart = 0;
483 SetFilePointerEx(this->m_DataFile,start,&offset,1);
484 if (this->SaveOneItem((*i).second, (LONG)offset.QuadPart))
486 TRACE(_T("Save one item error"));
487 SetFilePointerEx(this->m_DataFile,offset, &offset,0);
488 continue;
491 SLogCacheIndexItem item;
492 item.m_Hash = (*i).second.m_CommitHash;
493 item.m_Offset=offset.QuadPart;
495 DWORD num;
496 WriteFile(m_IndexFile,&item,sizeof(SLogCacheIndexItem),&num,0);
497 ++header.m_ItemCount;
501 FlushFileBuffers(m_IndexFile);
503 m_IndexFileMap = CreateFileMapping(m_IndexFile, NULL, PAGE_READWRITE,0,0,NULL);
504 if(m_IndexFileMap == INVALID_HANDLE_VALUE)
506 ret =-1;
507 break;
510 m_pCacheIndex = (SLogCacheIndexFile*)MapViewOfFile(m_IndexFileMap,FILE_MAP_WRITE,0,0,0);
511 if(m_pCacheIndex == NULL)
513 ret = -1;
514 break;
517 m_pCacheIndex->m_Header.m_ItemCount = header.m_ItemCount;
518 Sort();
519 FlushViewOfFile(m_pCacheIndex,0);
521 }while(0);
523 this->CloseDataHandles();
524 this->CloseIndexHandles();
526 if(pIndex)
527 free(pIndex);
528 return ret;
533 void CLogCache::Sort()
535 if(this->m_pCacheIndex)
537 qsort(m_pCacheIndex->m_Item, m_pCacheIndex->m_Header.m_ItemCount,sizeof(SLogCacheIndexItem), Compare);
541 int CLogCache::ClearAllParent()
543 for (auto i = m_HashMap.begin(); i != m_HashMap.end(); ++i)
545 (*i).second.m_ParentHash.clear();
546 (*i).second.m_Lanes.clear();
548 return 0;