Allow to move item past last item
[TortoiseGit.git] / src / TortoiseProc / GitLogCache.cpp
blobdccd5022edaaa13e067857002f8f01b61d1457c5
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(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 != 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=reinterpret_cast<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 = reinterpret_cast<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(reinterpret_cast<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_IsSubmodule = Rev.m_Files[i].IsDirectory() ? 1 : 0;
240 revfileheader.m_Action = Rev.m_Files[i].m_Action;
241 revfileheader.m_Stage = Rev.m_Files[i].m_Stage;
242 revfileheader.m_ParentNo = Rev.m_Files[i].m_ParentNo;
243 name = Rev.m_Files[i].GetGitPathString();
244 revfileheader.m_FileNameSize = name.GetLength();
245 oldname = Rev.m_Files[i].GetGitOldPathString();
246 revfileheader.m_OldFileNameSize = oldname.GetLength();
248 stat = Rev.m_Files[i].m_StatAdd;
249 revfileheader.m_Add = (stat == L"-") ? 0xFFFFFFFF : _wtol(stat);
250 stat = Rev.m_Files[i].m_StatDel;
251 revfileheader.m_Del = (stat == L"-") ? 0xFFFFFFFF : _wtol(stat);
253 if(!WriteFile(this->m_DataFile, &revfileheader, sizeof(revfileheader) - sizeof(TCHAR), &num, 0))
254 return -1;
256 if(!name.IsEmpty())
258 if (!WriteFile(this->m_DataFile, name, name.GetLength() * sizeof(TCHAR), &num, 0))
259 return -1;
261 if(!oldname.IsEmpty())
263 if (!WriteFile(this->m_DataFile, oldname, oldname.GetLength() * sizeof(TCHAR), &num, 0))
264 return -1;
268 return 0;
271 int CLogCache::LoadOneItem(GitRevLoglist& Rev,ULONGLONG offset)
273 if (!m_pCacheData)
274 return -1;
276 if (offset + sizeof(SLogCacheRevItemHeader) > m_DataFileLength)
277 return -2;
279 SLogCacheRevItemHeader* header = reinterpret_cast<SLogCacheRevItemHeader*>(m_pCacheData + offset);
281 if( !CheckHeader(header))
282 return -2;
284 Rev.m_Action = 0;
285 SLogCacheRevFileHeader *fileheader;
287 offset += sizeof(SLogCacheRevItemHeader);
288 fileheader = reinterpret_cast<SLogCacheRevFileHeader*>(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, (int*)&fileheader->m_IsSubmodule);
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 = L"-";
321 else
322 path.m_StatAdd.Format(L"%d", fileheader->m_Add);
324 if(fileheader->m_Del == 0xFFFFFFFF)
325 path.m_StatDel = L"-";
326 else
327 path.m_StatDel.Format(L"%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 = reinterpret_cast<SLogCacheRevFileHeader*>(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 = reinterpret_cast<SLogCacheIndexFile*>(malloc(sizeof(SLogCacheIndexFile) + sizeof(SLogCacheIndexItem) * (m_pCacheIndex->m_Header.m_ItemCount)));
381 if (!pIndex)
382 return -1;
384 memcpy(pIndex,this->m_pCacheIndex,
385 sizeof(SLogCacheIndexFile) + sizeof(SLogCacheIndexItem) *( m_pCacheIndex->m_Header.m_ItemCount-1)
389 this->CloseDataHandles();
390 this->CloseIndexHandles();
392 SLogCacheIndexHeader header;
393 CString file = this->m_GitDir + INDEX_FILE_NAME;
394 int ret = -1;
397 m_IndexFile = CreateFile(file,
398 GENERIC_READ|GENERIC_WRITE,
400 nullptr,
401 OPEN_ALWAYS,
402 FILE_ATTRIBUTE_NORMAL,
403 nullptr);
405 if(m_IndexFile == INVALID_HANDLE_VALUE)
406 break;
408 file = m_GitDir + DATA_FILE_NAME;
410 m_DataFile = CreateFile(file,
411 GENERIC_READ|GENERIC_WRITE,
413 nullptr,
414 OPEN_ALWAYS,
415 FILE_ATTRIBUTE_NORMAL,
416 nullptr);
418 if(m_DataFile == INVALID_HANDLE_VALUE)
419 break;
422 memset(&header,0,sizeof(SLogCacheIndexHeader));
423 DWORD num=0;
424 if ((!ReadFile(m_IndexFile, &header, sizeof(SLogCacheIndexHeader), &num, 0)) || num != sizeof(SLogCacheIndexHeader) ||
425 !CheckHeader(&header)
428 RebuildCacheFile();
429 bIsRebuild =true;
432 if(!bIsRebuild)
434 SLogCacheDataFileHeader datafileheader;
435 DWORD num=0;
436 if ((!ReadFile(m_DataFile, &datafileheader, sizeof(SLogCacheDataFileHeader), &num, 0) || num != sizeof(SLogCacheDataFileHeader) ||
437 !CheckHeader(&datafileheader)))
439 RebuildCacheFile();
440 bIsRebuild=true;
444 if(bIsRebuild)
445 header.m_ItemCount=0;
447 SetFilePointer(m_DataFile,0,0,2);
448 SetFilePointer(m_IndexFile,0,0,2);
450 for (auto i = m_HashMap.cbegin(); i != m_HashMap.cend(); ++i)
452 if(this->GetOffset((*i).second.m_CommitHash,pIndex) ==0 || bIsRebuild)
454 if((*i).second.m_IsDiffFiles && !(*i).second.m_CommitHash.IsEmpty())
456 LARGE_INTEGER offset;
457 offset.LowPart=0;
458 offset.HighPart=0;
459 LARGE_INTEGER start;
460 start.QuadPart = 0;
461 SetFilePointerEx(this->m_DataFile,start,&offset,1);
462 if (this->SaveOneItem((*i).second, (LONG)offset.QuadPart))
464 TRACE(L"Save one item error");
465 SetFilePointerEx(this->m_DataFile,offset, &offset,0);
466 continue;
469 SLogCacheIndexItem item;
470 item.m_Hash = (*i).second.m_CommitHash;
471 item.m_Offset=offset.QuadPart;
473 DWORD num;
474 WriteFile(m_IndexFile,&item,sizeof(SLogCacheIndexItem),&num,0);
475 ++header.m_ItemCount;
479 FlushFileBuffers(m_IndexFile);
481 m_IndexFileMap = CreateFileMapping(m_IndexFile, nullptr, PAGE_READWRITE, 0, 0, nullptr);
482 if(m_IndexFileMap == INVALID_HANDLE_VALUE)
483 break;
485 m_pCacheIndex = reinterpret_cast<SLogCacheIndexFile*>(MapViewOfFile(m_IndexFileMap, FILE_MAP_WRITE, 0, 0, 0));
486 if (!m_pCacheIndex)
487 break;
489 m_pCacheIndex->m_Header.m_ItemCount = header.m_ItemCount;
490 Sort();
491 FlushViewOfFile(m_pCacheIndex,0);
492 ret = 0;
493 }while(0);
495 this->CloseDataHandles();
496 this->CloseIndexHandles();
498 free(pIndex);
499 return ret;
502 void CLogCache::Sort()
504 if (this->m_pCacheIndex)
505 qsort(m_pCacheIndex->m_Item, m_pCacheIndex->m_Header.m_ItemCount, sizeof(SLogCacheIndexItem), Compare);
508 int CLogCache::ClearAllParent()
510 for (auto i = m_HashMap.begin(); i != m_HashMap.end(); ++i)
512 (*i).second.m_ParentHash.clear();
513 (*i).second.m_Lanes.clear();
515 return 0;
518 void CLogCache::ClearAllLanes()
520 for (auto i = m_HashMap.begin(); i != m_HashMap.end(); ++i)
521 (*i).second.m_Lanes.clear();