Use wcsncmp instead of Find for StartsWith checks
[TortoiseGit.git] / src / TortoiseProc / GitLogCache.cpp
blob7a70af73f188ab0776431b8b7308cb38b2a00a58
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.
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, GIT_HASH_SIZE);
28 CLogCache::CLogCache()
30 m_IndexFile = INVALID_HANDLE_VALUE;
31 m_IndexFileMap = INVALID_HANDLE_VALUE;
32 m_pCacheIndex = nullptr;
34 m_DataFile = INVALID_HANDLE_VALUE;
35 m_DataFileMap = INVALID_HANDLE_VALUE;
36 m_pCacheData = nullptr;
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 = nullptr;
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 = nullptr;
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;
82 CLogCache::~CLogCache()
84 CloseIndexHandles();
85 CloseDataHandles();
88 GitRevLoglist* CLogCache::GetCacheData(const CGitHash& hash)
90 m_HashMap[hash].m_CommitHash=hash;
91 return &m_HashMap[hash];
94 ULONGLONG CLogCache::GetOffset(const CGitHash& hash, SLogCacheIndexFile* pData)
96 if (!pData)
97 pData = m_pCacheIndex;
99 if (!pData)
100 return 0;
102 SLogCacheIndexItem *p=(SLogCacheIndexItem *)bsearch(hash.m_hash,pData->m_Item,
103 pData->m_Header.m_ItemCount,
104 sizeof(SLogCacheIndexItem),
105 Compare);
107 if(p)
108 return p->m_Offset;
109 else
110 return 0;
113 int CLogCache::FetchCacheIndex(CString GitDir)
115 if (!m_bEnabled)
116 return 0;
118 if (!GitAdminDir::GetAdminDirPath(GitDir, m_GitDir))
119 return -1;
121 int ret = -1;
124 if( m_IndexFile == INVALID_HANDLE_VALUE)
126 CString file = m_GitDir + INDEX_FILE_NAME;
127 m_IndexFile = CreateFile(file,
128 GENERIC_READ,
129 FILE_SHARE_READ | FILE_SHARE_DELETE,
130 nullptr,
131 OPEN_EXISTING,
132 FILE_ATTRIBUTE_NORMAL,
133 nullptr);
135 if( m_IndexFile == INVALID_HANDLE_VALUE)
136 break;
139 if( m_IndexFileMap == INVALID_HANDLE_VALUE)
141 m_IndexFileMap = CreateFileMapping(m_IndexFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
142 if( m_IndexFileMap == INVALID_HANDLE_VALUE)
143 break;
146 if (!m_pCacheIndex)
148 m_pCacheIndex = (SLogCacheIndexFile*)MapViewOfFile(m_IndexFileMap,FILE_MAP_READ,0,0,0);
149 if (!m_pCacheIndex)
150 break;
153 DWORD indexFileLength = GetFileSize(m_IndexFile, nullptr);
154 if (indexFileLength == INVALID_FILE_SIZE || indexFileLength < sizeof(SLogCacheIndexHeader))
155 break;
157 if( !CheckHeader(&m_pCacheIndex->m_Header))
158 break;
160 if (indexFileLength < sizeof(SLogCacheIndexHeader) + m_pCacheIndex->m_Header.m_ItemCount * sizeof(SLogCacheIndexItem))
161 break;
163 if( m_DataFile == INVALID_HANDLE_VALUE )
165 CString file = m_GitDir + DATA_FILE_NAME;
166 m_DataFile = CreateFile(file,
167 GENERIC_READ,
168 FILE_SHARE_READ | FILE_SHARE_DELETE,
169 nullptr,
170 OPEN_EXISTING,
171 FILE_ATTRIBUTE_NORMAL,
172 nullptr);
174 if(m_DataFile == INVALID_HANDLE_VALUE)
175 break;
178 if( m_DataFileMap == INVALID_HANDLE_VALUE)
180 m_DataFileMap = CreateFileMapping(m_DataFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
181 if( m_DataFileMap == INVALID_HANDLE_VALUE)
182 break;
184 m_DataFileLength = GetFileSize(m_DataFile, nullptr);
185 if (m_DataFileLength == INVALID_FILE_SIZE || m_DataFileLength < sizeof(SLogCacheDataFileHeader))
186 break;
188 if (!m_pCacheData)
190 m_pCacheData = (BYTE*)MapViewOfFile(m_DataFileMap,FILE_MAP_READ,0,0,0);
191 if (!m_pCacheData)
192 break;
195 if(!CheckHeader( (SLogCacheDataFileHeader*)m_pCacheData))
196 break;
198 if (m_DataFileLength < sizeof(SLogCacheDataFileHeader) + m_pCacheIndex->m_Header.m_ItemCount * sizeof(SLogCacheDataFileHeader))
199 break;
201 ret = 0;
202 }while(0);
204 if(ret)
206 CloseIndexHandles();
207 CloseDataHandles();
208 ::DeleteFile(m_GitDir + INDEX_FILE_NAME);
209 ::DeleteFile(m_GitDir + DATA_FILE_NAME);
211 return ret;
214 int CLogCache::SaveOneItem(const GitRevLoglist& Rev, LONG offset)
216 if(!Rev.m_IsDiffFiles)
217 return -1;
219 SetFilePointer(this->m_DataFile, offset,0, 0);
221 SLogCacheRevItemHeader header;
223 header.m_Magic=LOG_DATA_ITEM_MAGIC;
224 header.m_Version=LOG_INDEX_VERSION;
225 header.m_FileCount=Rev.m_Files.GetCount();
227 DWORD num;
229 if(!WriteFile(this->m_DataFile,&header, sizeof(header),&num,0))
230 return -1;
232 CString stat;
233 CString name,oldname;
234 for (int i = 0; i < Rev.m_Files.GetCount(); ++i)
236 SLogCacheRevFileHeader revfileheader;
237 revfileheader.m_Magic = LOG_DATA_FILE_MAGIC;
238 revfileheader.m_Version = LOG_INDEX_VERSION;
239 revfileheader.m_Action = Rev.m_Files[i].m_Action;
240 revfileheader.m_Stage = Rev.m_Files[i].m_Stage;
241 revfileheader.m_ParentNo = Rev.m_Files[i].m_ParentNo;
242 name = Rev.m_Files[i].GetGitPathString();
243 revfileheader.m_FileNameSize = name.GetLength();
244 oldname = Rev.m_Files[i].GetGitOldPathString();
245 revfileheader.m_OldFileNameSize = oldname.GetLength();
247 stat = Rev.m_Files[i].m_StatAdd;
248 revfileheader.m_Add = (stat == _T("-")) ? 0xFFFFFFFF : _tstol(stat);
249 stat = Rev.m_Files[i].m_StatDel;
250 revfileheader.m_Del = (stat == _T("-")) ? 0xFFFFFFFF : _tstol(stat);
252 if(!WriteFile(this->m_DataFile, &revfileheader, sizeof(revfileheader) - sizeof(TCHAR), &num, 0))
253 return -1;
255 if(!name.IsEmpty())
257 if (!WriteFile(this->m_DataFile, name, name.GetLength() * sizeof(TCHAR), &num, 0))
258 return -1;
260 if(!oldname.IsEmpty())
262 if (!WriteFile(this->m_DataFile, oldname, oldname.GetLength() * sizeof(TCHAR), &num, 0))
263 return -1;
267 return 0;
270 int CLogCache::LoadOneItem(GitRevLoglist& Rev,ULONGLONG offset)
272 if (!m_pCacheData)
273 return -1;
275 if (offset + sizeof(SLogCacheRevItemHeader) > m_DataFileLength)
276 return -2;
278 SLogCacheRevItemHeader *header;
279 header = (SLogCacheRevItemHeader *)(this->m_pCacheData + offset);
281 if( !CheckHeader(header))
282 return -2;
284 Rev.m_Action = 0;
285 SLogCacheRevFileHeader *fileheader;
287 offset += sizeof(SLogCacheRevItemHeader);
288 fileheader =(SLogCacheRevFileHeader *)(this->m_pCacheData + offset);
290 for (DWORD i = 0; i < header->m_FileCount; ++i)
292 CTGitPath path;
293 CString oldfile;
295 if (offset + sizeof(SLogCacheRevFileHeader) > m_DataFileLength)
297 Rev.m_Action = 0;
298 Rev.m_Files.Clear();
299 return -2;
302 if(!CheckHeader(fileheader))
304 Rev.m_Action = 0;
305 Rev.m_Files.Clear();
306 return -2;
309 CString file(fileheader->m_FileName, fileheader->m_FileNameSize);
310 if(fileheader->m_OldFileNameSize)
311 oldfile = CString(fileheader->m_FileName + fileheader->m_FileNameSize, fileheader->m_OldFileNameSize);
312 path.SetFromGit(file,&oldfile);
314 path.m_ParentNo = fileheader ->m_ParentNo;
315 path.m_Stage = fileheader ->m_Stage;
316 path.m_Action = fileheader->m_Action & ~(CTGitPath::LOGACTIONS_HIDE | CTGitPath::LOGACTIONS_GRAY);
317 Rev.m_Action |= path.m_Action;
319 if(fileheader->m_Add == 0xFFFFFFFF)
320 path.m_StatAdd=_T("-");
321 else
322 path.m_StatAdd.Format(_T("%d"),fileheader->m_Add);
324 if(fileheader->m_Del == 0xFFFFFFFF)
325 path.m_StatDel=_T("-");
326 else
327 path.m_StatDel.Format(_T("%d"), fileheader->m_Del);
329 Rev.m_Files.AddPath(path);
331 offset += sizeof(*fileheader) + fileheader->m_OldFileNameSize*sizeof(TCHAR) + fileheader->m_FileNameSize*sizeof(TCHAR) - sizeof(TCHAR);
332 fileheader = (SLogCacheRevFileHeader *) (this->m_pCacheData + offset);
334 return 0;
336 int CLogCache::RebuildCacheFile()
338 SLogCacheIndexHeader Indexheader;
340 Indexheader.m_Magic = LOG_INDEX_MAGIC;
341 Indexheader.m_Version = LOG_INDEX_VERSION;
342 Indexheader.m_ItemCount =0;
344 SLogCacheDataFileHeader dataheader;
346 dataheader.m_Magic = LOG_DATA_MAGIC;
347 dataheader.m_Version = LOG_INDEX_VERSION;
349 ::SetFilePointer(m_IndexFile,0,0,0);
350 ::SetFilePointer(m_DataFile,0,0,0);
351 SetEndOfFile(this->m_IndexFile);
352 SetEndOfFile(this->m_DataFile);
354 DWORD num;
355 WriteFile(this->m_IndexFile,&Indexheader,sizeof(SLogCacheIndexHeader),&num,0);
356 SetEndOfFile(this->m_IndexFile);
357 WriteFile(this->m_DataFile,&dataheader,sizeof(SLogCacheDataFileHeader),&num,0);
358 SetEndOfFile(this->m_DataFile);
359 return 0;
361 int CLogCache::SaveCache()
363 if (!m_bEnabled)
364 return 0;
366 BOOL bIsRebuild=false;
368 if (this->m_HashMap.empty()) // is not sufficient, because "working copy changes" are always included
369 return 0;
371 if( this->m_GitDir.IsEmpty())
372 return 0;
374 if (this->m_pCacheIndex && m_pCacheIndex->m_Header.m_ItemCount == 0) // check for empty log list (issue #915)
375 return 0;
377 SLogCacheIndexFile* pIndex = nullptr;
378 if(this->m_pCacheIndex)
380 pIndex = (SLogCacheIndexFile *)malloc(sizeof(SLogCacheIndexFile)
381 +sizeof(SLogCacheIndexItem) * (m_pCacheIndex->m_Header.m_ItemCount) );
382 if (!pIndex)
383 return -1;
385 memcpy(pIndex,this->m_pCacheIndex,
386 sizeof(SLogCacheIndexFile) + sizeof(SLogCacheIndexItem) *( m_pCacheIndex->m_Header.m_ItemCount-1)
390 this->CloseDataHandles();
391 this->CloseIndexHandles();
393 SLogCacheIndexHeader header;
394 CString file = this->m_GitDir + INDEX_FILE_NAME;
395 int ret = -1;
398 m_IndexFile = CreateFile(file,
399 GENERIC_READ|GENERIC_WRITE,
401 nullptr,
402 OPEN_ALWAYS,
403 FILE_ATTRIBUTE_NORMAL,
404 nullptr);
406 if(m_IndexFile == INVALID_HANDLE_VALUE)
407 break;
409 file = m_GitDir + DATA_FILE_NAME;
411 m_DataFile = CreateFile(file,
412 GENERIC_READ|GENERIC_WRITE,
414 nullptr,
415 OPEN_ALWAYS,
416 FILE_ATTRIBUTE_NORMAL,
417 nullptr);
419 if(m_DataFile == INVALID_HANDLE_VALUE)
420 break;
423 memset(&header,0,sizeof(SLogCacheIndexHeader));
424 DWORD num=0;
425 if ((!ReadFile(m_IndexFile, &header, sizeof(SLogCacheIndexHeader), &num, 0)) || num != sizeof(SLogCacheIndexHeader) ||
426 !CheckHeader(&header)
429 RebuildCacheFile();
430 bIsRebuild =true;
433 if(!bIsRebuild)
435 SLogCacheDataFileHeader datafileheader;
436 DWORD num=0;
437 if ((!ReadFile(m_DataFile, &datafileheader, sizeof(SLogCacheDataFileHeader), &num, 0) || num != sizeof(SLogCacheDataFileHeader) ||
438 !CheckHeader(&datafileheader)))
440 RebuildCacheFile();
441 bIsRebuild=true;
445 if(bIsRebuild)
446 header.m_ItemCount=0;
448 SetFilePointer(m_DataFile,0,0,2);
449 SetFilePointer(m_IndexFile,0,0,2);
451 for (auto i = m_HashMap.cbegin(); i != m_HashMap.cend(); ++i)
453 if(this->GetOffset((*i).second.m_CommitHash,pIndex) ==0 || bIsRebuild)
455 if((*i).second.m_IsDiffFiles && !(*i).second.m_CommitHash.IsEmpty())
457 LARGE_INTEGER offset;
458 offset.LowPart=0;
459 offset.HighPart=0;
460 LARGE_INTEGER start;
461 start.QuadPart = 0;
462 SetFilePointerEx(this->m_DataFile,start,&offset,1);
463 if (this->SaveOneItem((*i).second, (LONG)offset.QuadPart))
465 TRACE(_T("Save one item error"));
466 SetFilePointerEx(this->m_DataFile,offset, &offset,0);
467 continue;
470 SLogCacheIndexItem item;
471 item.m_Hash = (*i).second.m_CommitHash;
472 item.m_Offset=offset.QuadPart;
474 DWORD num;
475 WriteFile(m_IndexFile,&item,sizeof(SLogCacheIndexItem),&num,0);
476 ++header.m_ItemCount;
480 FlushFileBuffers(m_IndexFile);
482 m_IndexFileMap = CreateFileMapping(m_IndexFile, nullptr, PAGE_READWRITE, 0, 0, nullptr);
483 if(m_IndexFileMap == INVALID_HANDLE_VALUE)
484 break;
486 m_pCacheIndex = (SLogCacheIndexFile*)MapViewOfFile(m_IndexFileMap,FILE_MAP_WRITE,0,0,0);
487 if (!m_pCacheIndex)
488 break;
490 m_pCacheIndex->m_Header.m_ItemCount = header.m_ItemCount;
491 Sort();
492 FlushViewOfFile(m_pCacheIndex,0);
493 ret = 0;
494 }while(0);
496 this->CloseDataHandles();
497 this->CloseIndexHandles();
499 free(pIndex);
500 return ret;
503 void CLogCache::Sort()
505 if(this->m_pCacheIndex)
506 qsort(m_pCacheIndex->m_Item, m_pCacheIndex->m_Header.m_ItemCount,sizeof(SLogCacheIndexItem), Compare);
509 int CLogCache::ClearAllParent()
511 for (auto i = m_HashMap.begin(); i != m_HashMap.end(); ++i)
513 (*i).second.m_ParentHash.clear();
514 (*i).second.m_Lanes.clear();
516 return 0;
519 void CLogCache::ClearAllLanes()
521 for (auto i = m_HashMap.begin(); i != m_HashMap.end(); ++i)
522 (*i).second.m_Lanes.clear();