Refactor rebase commit list: Don't fill list again and again when reordering commits
[TortoiseGit.git] / src / TortoiseProc / GitLogCache.cpp
blob21a05cd3b0f11da9086ec444675ec5dbb55fb426
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - 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 "GitAdminDir.h"
21 #include "GitLogCache.h"
22 #include "registry.h"
24 int static Compare(const void *p1, const void*p2)
26 return memcmp(p1, p2, GIT_HASH_SIZE);
29 CLogCache::CLogCache()
30 : m_IndexFile(INVALID_HANDLE_VALUE)
31 , m_IndexFileMap(nullptr)
32 , m_pCacheIndex(nullptr)
33 , m_DataFile(INVALID_HANDLE_VALUE)
34 , m_DataFileMap(nullptr)
35 , m_pCacheData(nullptr)
36 , m_DataFileLength(0)
38 m_bEnabled = CRegDWORD(L"Software\\TortoiseGit\\EnableLogCache", TRUE);
41 void CLogCache::CloseDataHandles()
43 if(m_pCacheData)
45 UnmapViewOfFile(m_pCacheData);
46 m_pCacheData = nullptr;
48 if (m_DataFileMap)
50 CloseHandle(m_DataFileMap);
51 m_DataFileMap = nullptr;
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)
70 CloseHandle(m_IndexFileMap);
71 m_IndexFileMap = nullptr;
74 if (m_IndexFile != INVALID_HANDLE_VALUE)
76 CloseHandle(m_IndexFile);
77 m_IndexFile=INVALID_HANDLE_VALUE;
80 CLogCache::~CLogCache()
82 CloseIndexHandles();
83 CloseDataHandles();
86 GitRevLoglist* CLogCache::GetCacheData(const CGitHash& hash)
88 m_HashMap[hash].m_CommitHash=hash;
89 return &m_HashMap[hash];
92 ULONGLONG CLogCache::GetOffset(const CGitHash& hash, SLogCacheIndexFile* pData)
94 if (!pData)
95 pData = m_pCacheIndex;
97 if (!pData)
98 return 0;
100 SLogCacheIndexItem* p = reinterpret_cast<SLogCacheIndexItem*>(bsearch(static_cast<const unsigned char*>(hash), pData->m_Item,
101 pData->m_Header.m_ItemCount,
102 sizeof(SLogCacheIndexItem),
103 Compare));
105 if(p)
106 return p->m_Offset;
107 else
108 return 0;
111 int CLogCache::FetchCacheIndex(CString GitDir)
113 if (!m_bEnabled)
114 return 0;
116 if (!GitAdminDir::GetWorktreeAdminDirPath(GitDir, m_GitDir))
117 return -1;
119 int ret = -1;
122 if( m_IndexFile == INVALID_HANDLE_VALUE)
124 CString file = m_GitDir + INDEX_FILE_NAME;
125 m_IndexFile = CreateFile(file,
126 GENERIC_READ,
127 FILE_SHARE_READ | FILE_SHARE_DELETE,
128 nullptr,
129 OPEN_EXISTING,
130 FILE_ATTRIBUTE_NORMAL,
131 nullptr);
133 if( m_IndexFile == INVALID_HANDLE_VALUE)
134 break;
137 if (!m_IndexFileMap)
139 m_IndexFileMap = CreateFileMapping(m_IndexFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
140 if (!m_IndexFileMap)
141 break;
144 if (!m_pCacheIndex)
146 m_pCacheIndex = reinterpret_cast<SLogCacheIndexFile*>(MapViewOfFile(m_IndexFileMap, FILE_MAP_READ, 0, 0, 0));
147 if (!m_pCacheIndex)
148 break;
151 DWORD indexFileLength = GetFileSize(m_IndexFile, nullptr);
152 if (indexFileLength == INVALID_FILE_SIZE || indexFileLength < sizeof(SLogCacheIndexHeader))
153 break;
155 if( !CheckHeader(&m_pCacheIndex->m_Header))
156 break;
158 if (indexFileLength != sizeof(SLogCacheIndexHeader) + m_pCacheIndex->m_Header.m_ItemCount * sizeof(SLogCacheIndexItem))
159 break;
161 if( m_DataFile == INVALID_HANDLE_VALUE )
163 CString file = m_GitDir + DATA_FILE_NAME;
164 m_DataFile = CreateFile(file,
165 GENERIC_READ,
166 FILE_SHARE_READ | FILE_SHARE_DELETE,
167 nullptr,
168 OPEN_EXISTING,
169 FILE_ATTRIBUTE_NORMAL,
170 nullptr);
172 if(m_DataFile == INVALID_HANDLE_VALUE)
173 break;
176 if (!m_DataFileMap)
178 m_DataFileMap = CreateFileMapping(m_DataFile, nullptr, PAGE_READONLY, 0, 0, nullptr);
179 if (!m_DataFileMap)
180 break;
182 m_DataFileLength = GetFileSize(m_DataFile, nullptr);
183 if (m_DataFileLength == INVALID_FILE_SIZE || m_DataFileLength < sizeof(SLogCacheDataFileHeader))
184 break;
186 if (!m_pCacheData)
188 m_pCacheData = (BYTE*)MapViewOfFile(m_DataFileMap,FILE_MAP_READ,0,0,0);
189 if (!m_pCacheData)
190 break;
193 if (!CheckHeader(reinterpret_cast<SLogCacheDataFileHeader*>(m_pCacheData)))
194 break;
196 if (m_DataFileLength < sizeof(SLogCacheDataFileHeader) + m_pCacheIndex->m_Header.m_ItemCount * sizeof(SLogCacheDataFileHeader))
197 break;
199 ret = 0;
200 }while(0);
202 if(ret)
204 CloseIndexHandles();
205 CloseDataHandles();
206 ::DeleteFile(m_GitDir + INDEX_FILE_NAME);
207 ::DeleteFile(m_GitDir + DATA_FILE_NAME);
209 return ret;
212 int CLogCache::SaveOneItem(const GitRevLoglist& Rev, LONG offset)
214 if(!Rev.m_IsDiffFiles)
215 return -1;
217 SetFilePointer(m_DataFile, offset, 0, FILE_BEGIN);
219 SLogCacheRevItemHeader header;
221 header.m_Magic=LOG_DATA_ITEM_MAGIC;
222 header.m_FileCount=Rev.m_Files.GetCount();
224 DWORD num;
226 if(!WriteFile(this->m_DataFile,&header, sizeof(header),&num,0))
227 return -1;
229 CString stat;
230 CString name,oldname;
231 for (int i = 0; i < Rev.m_Files.GetCount(); ++i)
233 SLogCacheRevFileHeader revfileheader;
234 revfileheader.m_Magic = LOG_DATA_FILE_MAGIC;
235 revfileheader.m_IsSubmodule = Rev.m_Files[i].IsDirectory() ? 1 : 0;
236 revfileheader.m_Action = Rev.m_Files[i].m_Action;
237 revfileheader.m_Stage = Rev.m_Files[i].m_Stage;
238 revfileheader.m_ParentNo = Rev.m_Files[i].m_ParentNo;
239 name = Rev.m_Files[i].GetGitPathString();
240 revfileheader.m_FileNameSize = name.GetLength();
241 oldname = Rev.m_Files[i].GetGitOldPathString();
242 revfileheader.m_OldFileNameSize = oldname.GetLength();
244 stat = Rev.m_Files[i].m_StatAdd;
245 revfileheader.m_Add = (stat == L"-") ? 0xFFFFFFFF : _wtol(stat);
246 stat = Rev.m_Files[i].m_StatDel;
247 revfileheader.m_Del = (stat == L"-") ? 0xFFFFFFFF : _wtol(stat);
249 if(!WriteFile(this->m_DataFile, &revfileheader, sizeof(revfileheader) - sizeof(TCHAR), &num, 0))
250 return -1;
252 if(!name.IsEmpty())
254 if (!WriteFile(this->m_DataFile, name, name.GetLength() * sizeof(TCHAR), &num, 0))
255 return -1;
257 if(!oldname.IsEmpty())
259 if (!WriteFile(this->m_DataFile, oldname, oldname.GetLength() * sizeof(TCHAR), &num, 0))
260 return -1;
264 return 0;
267 int CLogCache::LoadOneItem(GitRevLoglist& Rev,ULONGLONG offset)
269 if (!m_pCacheData)
270 return -1;
272 if (offset + sizeof(SLogCacheRevItemHeader) > m_DataFileLength)
273 return -2;
275 SLogCacheRevItemHeader* header = reinterpret_cast<SLogCacheRevItemHeader*>(m_pCacheData + offset);
277 if( !CheckHeader(header))
278 return -2;
280 Rev.m_Action = 0;
282 offset += sizeof(SLogCacheRevItemHeader);
284 for (DWORD i = 0; i < header->m_FileCount; ++i)
286 if (offset + sizeof(SLogCacheRevFileHeader) >= m_DataFileLength)
288 Rev.m_Action = 0;
289 Rev.m_Files.Clear();
290 return -2;
293 auto fileheader = reinterpret_cast<SLogCacheRevFileHeader*>(m_pCacheData + offset);
295 if(!CheckHeader(fileheader))
297 Rev.m_Action = 0;
298 Rev.m_Files.Clear();
299 return -2;
302 auto recordLength = sizeof(SLogCacheRevFileHeader) + fileheader->m_FileNameSize * sizeof(TCHAR) + fileheader->m_OldFileNameSize * sizeof(TCHAR) - sizeof(TCHAR);
303 if (!fileheader->m_FileNameSize || offset + recordLength > m_DataFileLength)
305 Rev.m_Action = 0;
306 Rev.m_Files.Clear();
307 return -2;
310 CString oldfile;
311 CString file(fileheader->m_FileName, fileheader->m_FileNameSize);
312 if(fileheader->m_OldFileNameSize)
313 oldfile = CString(fileheader->m_FileName + fileheader->m_FileNameSize, fileheader->m_OldFileNameSize);
314 CTGitPath path;
315 int isSubmodule = fileheader->m_IsSubmodule;
316 path.SetFromGit(file, oldfile.IsEmpty() ? nullptr : &oldfile, (int*)&isSubmodule);
318 path.m_ParentNo = fileheader ->m_ParentNo;
319 path.m_Stage = fileheader ->m_Stage;
320 path.m_Action = fileheader->m_Action & ~(CTGitPath::LOGACTIONS_HIDE | CTGitPath::LOGACTIONS_GRAY);
321 Rev.m_Action |= path.m_Action;
323 if(fileheader->m_Add == 0xFFFFFFFF)
324 path.m_StatAdd = L"-";
325 else
326 path.m_StatAdd.Format(L"%d", fileheader->m_Add);
328 if(fileheader->m_Del == 0xFFFFFFFF)
329 path.m_StatDel = L"-";
330 else
331 path.m_StatDel.Format(L"%d", fileheader->m_Del);
333 Rev.m_Files.AddPath(path);
335 offset += recordLength;
337 return 0;
339 int CLogCache::RebuildCacheFile()
341 SLogCacheIndexHeader Indexheader;
343 Indexheader.m_Magic = LOG_INDEX_MAGIC;
344 Indexheader.m_Version = LOG_INDEX_VERSION;
345 Indexheader.m_ItemCount =0;
347 SLogCacheDataFileHeader dataheader;
349 dataheader.m_Magic = LOG_DATA_MAGIC;
350 dataheader.m_Version = LOG_INDEX_VERSION;
352 SetFilePointer(m_IndexFile, 0, 0, FILE_BEGIN);
353 SetFilePointer(m_DataFile, 0, 0, FILE_BEGIN);
354 SetEndOfFile(this->m_IndexFile);
355 SetEndOfFile(this->m_DataFile);
357 DWORD num;
358 WriteFile(this->m_IndexFile,&Indexheader,sizeof(SLogCacheIndexHeader),&num,0);
359 SetEndOfFile(this->m_IndexFile);
360 WriteFile(this->m_DataFile,&dataheader,sizeof(SLogCacheDataFileHeader),&num,0);
361 SetEndOfFile(this->m_DataFile);
362 return 0;
364 int CLogCache::SaveCache()
366 if (!m_bEnabled)
367 return 0;
369 BOOL bIsRebuild=false;
371 if (m_HashMap.empty() || (m_HashMap.size() == 1 && m_HashMap.find(CGitHash()) != m_HashMap.cend()))
372 return 0;
374 if( this->m_GitDir.IsEmpty())
375 return 0;
377 SLogCacheIndexFile* pIndex = nullptr;
378 if (m_pCacheIndex && m_pCacheIndex->m_Header.m_ItemCount > 0)
380 auto len = sizeof(SLogCacheIndexFile) + sizeof(SLogCacheIndexItem) * (m_pCacheIndex->m_Header.m_ItemCount - 1);
381 pIndex = reinterpret_cast<SLogCacheIndexFile*>(malloc(len));
382 if (!pIndex)
383 return -1;
385 memcpy(pIndex, m_pCacheIndex, len);
388 this->CloseDataHandles();
389 this->CloseIndexHandles();
391 SLogCacheIndexHeader header;
392 CString file = this->m_GitDir + INDEX_FILE_NAME;
393 int ret = -1;
396 m_IndexFile = CreateFile(file,
397 GENERIC_READ|GENERIC_WRITE,
399 nullptr,
400 OPEN_ALWAYS,
401 FILE_ATTRIBUTE_NORMAL,
402 nullptr);
404 if(m_IndexFile == INVALID_HANDLE_VALUE)
405 break;
407 file = m_GitDir + DATA_FILE_NAME;
409 m_DataFile = CreateFile(file,
410 GENERIC_READ|GENERIC_WRITE,
412 nullptr,
413 OPEN_ALWAYS,
414 FILE_ATTRIBUTE_NORMAL,
415 nullptr);
417 if(m_DataFile == INVALID_HANDLE_VALUE)
418 break;
421 memset(&header,0,sizeof(SLogCacheIndexHeader));
422 DWORD num=0;
423 if ((!ReadFile(m_IndexFile, &header, sizeof(SLogCacheIndexHeader), &num, 0)) || num != sizeof(SLogCacheIndexHeader) ||
424 !CheckHeader(&header)
427 RebuildCacheFile();
428 bIsRebuild =true;
431 if(!bIsRebuild)
433 SLogCacheDataFileHeader datafileheader;
434 DWORD num=0;
435 if ((!ReadFile(m_DataFile, &datafileheader, sizeof(SLogCacheDataFileHeader), &num, 0) || num != sizeof(SLogCacheDataFileHeader) ||
436 !CheckHeader(&datafileheader)))
438 RebuildCacheFile();
439 bIsRebuild=true;
443 if(bIsRebuild)
444 header.m_ItemCount=0;
446 SetFilePointer(m_DataFile, 0, 0, FILE_END);
447 SetFilePointer(m_IndexFile, 0, 0, FILE_END);
449 for (auto i = m_HashMap.cbegin(); i != m_HashMap.cend(); ++i)
451 if(this->GetOffset((*i).second.m_CommitHash,pIndex) ==0 || bIsRebuild)
453 if((*i).second.m_IsDiffFiles && !(*i).second.m_CommitHash.IsEmpty())
455 LARGE_INTEGER offset;
456 offset.LowPart=0;
457 offset.HighPart=0;
458 LARGE_INTEGER start;
459 start.QuadPart = 0;
460 SetFilePointerEx(m_DataFile, start, &offset, FILE_CURRENT);
461 if (this->SaveOneItem((*i).second, (LONG)offset.QuadPart))
463 TRACE(L"Save one item error");
464 SetFilePointerEx(m_DataFile, offset, &offset, FILE_BEGIN);
465 continue;
468 SLogCacheIndexItem item;
469 item.m_Hash = (*i).second.m_CommitHash;
470 item.m_Offset=offset.QuadPart;
472 DWORD num;
473 WriteFile(m_IndexFile,&item,sizeof(SLogCacheIndexItem),&num,0);
474 ++header.m_ItemCount;
478 FlushFileBuffers(m_IndexFile);
480 m_IndexFileMap = CreateFileMapping(m_IndexFile, nullptr, PAGE_READWRITE, 0, 0, nullptr);
481 if (!m_IndexFileMap)
482 break;
484 m_pCacheIndex = reinterpret_cast<SLogCacheIndexFile*>(MapViewOfFile(m_IndexFileMap, FILE_MAP_WRITE, 0, 0, 0));
485 if (!m_pCacheIndex)
486 break;
488 m_pCacheIndex->m_Header.m_ItemCount = header.m_ItemCount;
490 std::qsort(m_pCacheIndex->m_Item, m_pCacheIndex->m_Header.m_ItemCount, sizeof(SLogCacheIndexItem), Compare);
492 FlushViewOfFile(m_pCacheIndex,0);
493 ret = 0;
494 }while(0);
496 this->CloseDataHandles();
497 this->CloseIndexHandles();
499 free(pIndex);
500 return ret;
503 int CLogCache::ClearAllParent()
505 for (auto i = m_HashMap.begin(); i != m_HashMap.end(); ++i)
507 (*i).second.m_ParentHash.clear();
508 (*i).second.m_Lanes.clear();
510 return 0;
513 void CLogCache::ClearAllLanes()
515 for (auto i = m_HashMap.begin(); i != m_HashMap.end(); ++i)
516 (*i).second.m_Lanes.clear();