Fixed issue #915: TortoiseProc memory corruption on empty repo
[TortoiseGit.git] / src / TortoiseProc / GitLogCache.cpp
blob943585cd7d05b45a9349381f800447437074f975
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2011 - 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"
22 int static Compare(const void *p1, const void*p2)
24 return memcmp(p1,p2,20);
27 CLogCache::CLogCache()
29 m_IndexFile = INVALID_HANDLE_VALUE;
30 m_IndexFileMap = INVALID_HANDLE_VALUE;
31 m_pCacheIndex = NULL;
33 m_DataFile = INVALID_HANDLE_VALUE;
34 m_DataFileMap = INVALID_HANDLE_VALUE;
35 m_pCacheData = NULL;
38 void CLogCache::CloseDataHandles()
40 if(m_pCacheData)
42 UnmapViewOfFile(m_pCacheData);
43 m_pCacheData=NULL;
45 if(m_DataFileMap)
47 CloseHandle(m_DataFileMap);
48 m_DataFileMap=INVALID_HANDLE_VALUE;
50 if(m_DataFile)
52 CloseHandle(m_DataFile);
53 m_DataFile = INVALID_HANDLE_VALUE;
57 void CLogCache::CloseIndexHandles()
59 if(m_pCacheIndex)
61 UnmapViewOfFile(m_pCacheIndex);
62 m_pCacheIndex = NULL;
65 if(m_IndexFileMap)
67 CloseHandle(m_IndexFileMap);
68 m_IndexFileMap = INVALID_HANDLE_VALUE;
71 //this->m_IndexFile.Close();
72 //this->m_DataFile.Close();
73 if(m_IndexFile)
75 CloseHandle(m_IndexFile);
76 m_IndexFile=INVALID_HANDLE_VALUE;
80 CLogCache::~CLogCache()
82 CloseIndexHandles();
83 CloseDataHandles();
86 int CLogCache::AddCacheEntry(GitRev &Rev)
88 this->m_HashMap[Rev.m_CommitHash] = Rev;
89 return 0;
92 GitRev * CLogCache::GetCacheData(CGitHash &hash)
94 m_HashMap[hash].m_CommitHash=hash;
95 return &m_HashMap[hash];
98 ULONGLONG CLogCache::GetOffset(CGitHash &hash,SLogCacheIndexFile *pData)
101 if(pData==NULL)
102 pData = m_pCacheIndex;
104 if(pData == 0)
105 return 0;
107 SLogCacheIndexItem *p=(SLogCacheIndexItem *)bsearch(hash.m_hash,pData->m_Item,
108 pData->m_Header.m_ItemCount,
109 sizeof(SLogCacheIndexItem),
110 Compare);
112 if(p)
113 return p->m_Offset;
114 else
115 return 0;
118 int CLogCache::FetchCacheIndex(CString GitDir)
120 int ret=0;
121 this->m_GitDir = GitDir;
124 if( m_IndexFile == INVALID_HANDLE_VALUE)
126 CString file = GitDir+_T("\\.git\\")+INDEX_FILE_NAME;
127 m_IndexFile = CreateFile(file,
128 GENERIC_READ,
129 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
130 NULL,
131 OPEN_EXISTING,
132 FILE_ATTRIBUTE_NORMAL,
133 NULL);
135 if( m_IndexFile == INVALID_HANDLE_VALUE)
137 ret = -1;
138 break;;
142 if( m_IndexFileMap == INVALID_HANDLE_VALUE)
144 m_IndexFileMap = CreateFileMapping(m_IndexFile, NULL, PAGE_READONLY,0,0,NULL);
145 if( m_IndexFileMap == INVALID_HANDLE_VALUE)
147 ret = -1;
148 break;
152 if( m_pCacheIndex == NULL )
154 m_pCacheIndex = (SLogCacheIndexFile*)MapViewOfFile(m_IndexFileMap,FILE_MAP_READ,0,0,0);
155 if( m_pCacheIndex ==NULL )
157 ret = -1;
158 break;
162 if( !CheckHeader(&m_pCacheIndex->m_Header))
164 ret =-1;
165 break;
168 if( m_DataFile == INVALID_HANDLE_VALUE )
170 CString file = GitDir+_T("\\.git\\")+DATA_FILE_NAME;
171 m_DataFile = CreateFile(file,
172 GENERIC_READ,
173 FILE_SHARE_READ|FILE_SHARE_DELETE|FILE_SHARE_WRITE,
174 NULL,
175 OPEN_EXISTING,
176 FILE_ATTRIBUTE_NORMAL,
177 NULL);
179 if(m_DataFile == INVALID_HANDLE_VALUE)
181 ret =-1;
182 break;
186 if( m_DataFileMap == INVALID_HANDLE_VALUE)
188 m_DataFileMap = CreateFileMapping(m_DataFile, NULL, PAGE_READONLY,0,0,NULL);
189 if( m_DataFileMap == INVALID_HANDLE_VALUE)
191 ret = -1;
192 break;
196 if( m_pCacheData == NULL)
198 m_pCacheData = (BYTE*)MapViewOfFile(m_DataFileMap,FILE_MAP_READ,0,0,0);
199 if( m_pCacheData ==NULL )
201 ret = -1;
202 break;
206 if(!CheckHeader( (SLogCacheDataFileHeader*)m_pCacheData))
208 ret = -1;
209 break;
212 }while(0);
214 if(ret)
216 CloseIndexHandles();
217 ::DeleteFile(GitDir+_T("\\.git\\")+INDEX_FILE_NAME);
218 ::DeleteFile(GitDir+_T("\\.git\\")+DATA_FILE_NAME);
220 return ret;
224 int CLogCache::SaveOneItem(GitRev &Rev,ULONGLONG offset)
226 if(!Rev.m_IsDiffFiles)
227 return -1;
229 SetFilePointer(this->m_DataFile, offset,0, 0);
231 SLogCacheRevItemHeader header;
233 header.m_Magic=LOG_DATA_ITEM_MAGIC;
234 header.m_Version=LOG_INDEX_VERSION;
235 header.m_FileCount=Rev.m_Files.GetCount();
237 DWORD num;
239 if(!WriteFile(this->m_DataFile,&header, sizeof(header),&num,0))
240 return -1;
242 CString stat;
243 CString name,oldname;
244 for(int i=0;i<Rev.m_Files.GetCount();i++)
246 SLogCacheRevFileHeader header;
247 header.m_Magic=LOG_DATA_FILE_MAGIC;
248 header.m_Version=LOG_INDEX_VERSION;
249 header.m_Action = Rev.m_Files[i].m_Action;
250 header.m_Stage = Rev.m_Files[i].m_Stage;
251 header.m_ParentNo = Rev.m_Files[i].m_ParentNo;
252 name = Rev.m_Files[i].GetGitPathString();
253 header.m_FileNameSize = name.GetLength();
254 oldname = Rev.m_Files[i].GetGitOldPathString();
255 header.m_OldFileNameSize = oldname.GetLength();
257 stat = Rev.m_Files[i].m_StatAdd;
258 header.m_Add = (stat == _T("-"))? 0xFFFFFFFF:_tstol(stat);
259 stat = Rev.m_Files[i].m_StatDel;
260 header.m_Del = (stat == _T("-"))? 0xFFFFFFFF:_tstol(stat);
262 if(!WriteFile(this->m_DataFile,&header, sizeof(header)-sizeof(TCHAR),&num,0))
263 return -1;
265 if(!name.IsEmpty())
267 if(!WriteFile(this->m_DataFile,name.GetBuffer(), name.GetLength()*sizeof(TCHAR),&num,0))
268 return -1;
270 if(!oldname.IsEmpty())
272 if(!WriteFile(this->m_DataFile,oldname.GetBuffer(), oldname.GetLength()*sizeof(TCHAR),&num,0))
273 return -1;
277 return 0;
280 int CLogCache::LoadOneItem(GitRev &Rev,ULONGLONG offset)
282 if(m_pCacheData == NULL)
283 return -1;
285 SLogCacheRevItemHeader *header;
286 header = (SLogCacheRevItemHeader *)(this->m_pCacheData + offset);
288 if( !CheckHeader(header))
289 return -2;
291 Rev.m_Action = 0;
292 SLogCacheRevFileHeader *fileheader;
294 offset += sizeof(SLogCacheRevItemHeader);
295 fileheader =(SLogCacheRevFileHeader *)(this->m_pCacheData + offset);
297 for(int i=0;i<header->m_FileCount;i++)
299 CTGitPath path;
300 CString file,oldfile;
301 path.Reset();
303 if(!CheckHeader(fileheader))
304 return -1;
306 _tcsncpy(file.GetBufferSetLength(fileheader->m_FileNameSize), fileheader->m_FileName, fileheader->m_FileNameSize);
307 if(fileheader->m_OldFileNameSize)
309 _tcsncpy(oldfile.GetBufferSetLength(fileheader->m_OldFileNameSize),
310 fileheader->m_FileName+fileheader->m_FileNameSize,
311 fileheader->m_OldFileNameSize);
313 path.SetFromGit(file,&oldfile);
315 path.m_ParentNo = fileheader ->m_ParentNo;
316 path.m_Stage = fileheader ->m_Stage;
317 path.m_Action = fileheader ->m_Action;
318 Rev.m_Action |= path.m_Action;
320 if(fileheader->m_Add == 0xFFFFFFFF)
321 path.m_StatAdd=_T("-");
322 else
323 path.m_StatAdd.Format(_T("%d"),fileheader->m_Add);
325 if(fileheader->m_Del == 0xFFFFFFFF)
326 path.m_StatDel=_T("-");
327 else
328 path.m_StatDel.Format(_T("%d"), fileheader->m_Del);
330 Rev.m_Files.AddPath(path);
332 offset += sizeof(*fileheader) + fileheader->m_OldFileNameSize*sizeof(TCHAR) + fileheader->m_FileNameSize*sizeof(TCHAR) - sizeof(TCHAR);
333 fileheader = (SLogCacheRevFileHeader *) (this->m_pCacheData + offset);
335 return 0;
337 int CLogCache::RebuildCacheFile()
339 SLogCacheIndexHeader Indexheader;
341 Indexheader.m_Magic = LOG_INDEX_MAGIC;
342 Indexheader.m_Version = LOG_INDEX_VERSION;
343 Indexheader.m_ItemCount =0;
345 SLogCacheDataFileHeader dataheader;
347 dataheader.m_Magic = LOG_DATA_MAGIC;
348 dataheader.m_Version = LOG_INDEX_VERSION;
350 ::SetFilePointer(m_IndexFile,0,0,0);
351 ::SetFilePointer(m_DataFile,0,0,0);
352 SetEndOfFile(this->m_IndexFile);
353 SetEndOfFile(this->m_DataFile);
355 DWORD num;
356 WriteFile(this->m_IndexFile,&Indexheader,sizeof(SLogCacheIndexHeader),&num,0);
357 SetEndOfFile(this->m_IndexFile);
358 WriteFile(this->m_DataFile,&dataheader,sizeof(SLogCacheDataFileHeader),&num,0);
359 SetEndOfFile(this->m_DataFile);
360 return 0;
362 int CLogCache::SaveCache()
364 int ret =0;
365 BOOL bIsRebuild=false;
367 if( this->m_HashMap.size() == 0 ) // is not sufficient, because "working copy changes" are always included
368 return 0;
370 if( this->m_GitDir.IsEmpty())
371 return 0;
373 if (this->m_pCacheIndex && m_pCacheIndex->m_Header.m_ItemCount == 0) // check for empty log list (issue #915)
374 return 0;
376 SLogCacheIndexFile *pIndex = NULL;
377 if(this->m_pCacheIndex)
379 pIndex = (SLogCacheIndexFile *)malloc(sizeof(SLogCacheIndexFile)
380 +sizeof(SLogCacheIndexItem) * (m_pCacheIndex->m_Header.m_ItemCount) );
381 if(pIndex ==NULL)
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 +_T("\\.git\\")+INDEX_FILE_NAME;
396 m_IndexFile = CreateFile(file,
397 GENERIC_READ|GENERIC_WRITE,
399 NULL,
400 OPEN_ALWAYS,
401 FILE_ATTRIBUTE_NORMAL,
402 NULL);
404 if(m_IndexFile == INVALID_HANDLE_VALUE)
406 ret = -1;
407 break;
410 file = m_GitDir+_T("\\.git\\")+DATA_FILE_NAME;
412 m_DataFile = CreateFile(file,
413 GENERIC_READ|GENERIC_WRITE,
415 NULL,
416 OPEN_ALWAYS,
417 FILE_ATTRIBUTE_NORMAL,
418 NULL);
420 if(m_DataFile == INVALID_HANDLE_VALUE)
422 ret = -1;
423 break;
429 memset(&header,0,sizeof(SLogCacheIndexHeader));
430 DWORD num=0;
431 if((!ReadFile(m_IndexFile,&header, sizeof(SLogCacheIndexHeader),&num,0)) ||
432 !CheckHeader(&header)
435 RebuildCacheFile();
436 bIsRebuild =true;
439 if(!bIsRebuild)
441 SLogCacheDataFileHeader header;
442 DWORD num=0;
443 if((!ReadFile(m_DataFile,&header,sizeof(SLogCacheDataFileHeader),&num,0)||
444 !CheckHeader(&header)))
446 RebuildCacheFile();
447 bIsRebuild=true;
451 if(bIsRebuild)
452 header.m_ItemCount=0;
454 SetFilePointer(m_DataFile,0,0,2);
455 SetFilePointer(m_IndexFile,0,0,2);
457 CGitHashMap::iterator i;
458 for(i=m_HashMap.begin();i!=m_HashMap.end();i++)
460 if(this->GetOffset((*i).second.m_CommitHash,pIndex) ==0 || bIsRebuild)
462 if((*i).second.m_IsDiffFiles && !(*i).second.m_CommitHash.IsEmpty())
464 LARGE_INTEGER offset;
465 offset.LowPart=0;
466 offset.HighPart=0;
467 LARGE_INTEGER start;
468 start.QuadPart = 0;
469 SetFilePointerEx(this->m_DataFile,start,&offset,1);
470 if(this->SaveOneItem((*i).second,offset.QuadPart))
472 TRACE(_T("Save one item error"));
473 SetFilePointerEx(this->m_DataFile,offset, &offset,0);
474 continue;
477 SLogCacheIndexItem item;
478 item.m_Hash = (*i).second.m_CommitHash;
479 item.m_Offset=offset.QuadPart;
481 DWORD num;
482 WriteFile(m_IndexFile,&item,sizeof(SLogCacheIndexItem),&num,0);
483 header.m_ItemCount ++;
487 FlushFileBuffers(m_IndexFile);
489 m_IndexFileMap = CreateFileMapping(m_IndexFile, NULL, PAGE_READWRITE,0,0,NULL);
490 if(m_IndexFileMap == INVALID_HANDLE_VALUE)
492 ret =-1;
493 break;
496 m_pCacheIndex = (SLogCacheIndexFile*)MapViewOfFile(m_IndexFileMap,FILE_MAP_WRITE,0,0,0);
497 if(m_pCacheIndex == NULL)
499 ret = -1;
500 break;
503 m_pCacheIndex->m_Header.m_ItemCount = header.m_ItemCount;
504 Sort();
505 FlushViewOfFile(m_pCacheIndex,0);
507 }while(0);
509 this->CloseDataHandles();
510 this->CloseIndexHandles();
512 if(pIndex)
513 free(pIndex);
514 return ret;
519 void CLogCache::Sort()
521 if(this->m_pCacheIndex)
523 qsort(m_pCacheIndex->m_Item, m_pCacheIndex->m_Header.m_ItemCount,sizeof(SLogCacheIndexItem), Compare);
527 int CLogCache::ClearAllParent()
529 CGitHashMap::iterator i;
530 for(i=m_HashMap.begin();i!=m_HashMap.end();i++)
532 (*i).second.m_ParentHash.clear();
533 (*i).second.m_Lanes.clear();
535 return 0;