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.
20 #include "GitLogCache.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
;
34 m_DataFile
= INVALID_HANDLE_VALUE
;
35 m_DataFileMap
= INVALID_HANDLE_VALUE
;
38 m_bEnabled
= CRegDWORD(_T("Software\\TortoiseGit\\EnableLogCache"), TRUE
);
41 void CLogCache::CloseDataHandles()
45 UnmapViewOfFile(m_pCacheData
);
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()
64 UnmapViewOfFile(m_pCacheIndex
);
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()
89 int CLogCache::AddCacheEntry(GitRev
&Rev
)
91 this->m_HashMap
[Rev
.m_CommitHash
] = Rev
;
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
)
105 pData
= m_pCacheIndex
;
110 SLogCacheIndexItem
*p
=(SLogCacheIndexItem
*)bsearch(hash
.m_hash
,pData
->m_Item
,
111 pData
->m_Header
.m_ItemCount
,
112 sizeof(SLogCacheIndexItem
),
121 int CLogCache::FetchCacheIndex(CString GitDir
)
127 if (!g_GitAdminDir
.GetAdminDirPath(GitDir
, m_GitDir
))
132 if( m_IndexFile
== INVALID_HANDLE_VALUE
)
134 CString file
= m_GitDir
+ INDEX_FILE_NAME
;
135 m_IndexFile
= CreateFile(file
,
137 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
140 FILE_ATTRIBUTE_NORMAL
,
143 if( m_IndexFile
== INVALID_HANDLE_VALUE
)
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
)
160 if( m_pCacheIndex
== NULL
)
162 m_pCacheIndex
= (SLogCacheIndexFile
*)MapViewOfFile(m_IndexFileMap
,FILE_MAP_READ
,0,0,0);
163 if( m_pCacheIndex
==NULL
)
170 if( !CheckHeader(&m_pCacheIndex
->m_Header
))
176 if( m_DataFile
== INVALID_HANDLE_VALUE
)
178 CString file
= m_GitDir
+ DATA_FILE_NAME
;
179 m_DataFile
= CreateFile(file
,
181 FILE_SHARE_READ
|FILE_SHARE_DELETE
|FILE_SHARE_WRITE
,
184 FILE_ATTRIBUTE_NORMAL
,
187 if(m_DataFile
== INVALID_HANDLE_VALUE
)
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
)
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
)
214 if(!CheckHeader( (SLogCacheDataFileHeader
*)m_pCacheData
))
225 ::DeleteFile(m_GitDir
+ INDEX_FILE_NAME
);
226 ::DeleteFile(m_GitDir
+ DATA_FILE_NAME
);
232 int CLogCache::SaveOneItem(GitRev
&Rev
, LONG offset
)
234 if(!Rev
.m_IsDiffFiles
)
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();
247 if(!WriteFile(this->m_DataFile
,&header
, sizeof(header
),&num
,0))
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))
275 if(!WriteFile(this->m_DataFile
,name
.GetBuffer(), name
.GetLength()*sizeof(TCHAR
),&num
,0))
278 if(!oldname
.IsEmpty())
280 if(!WriteFile(this->m_DataFile
,oldname
.GetBuffer(), oldname
.GetLength()*sizeof(TCHAR
),&num
,0))
288 int CLogCache::LoadOneItem(GitRev
&Rev
,ULONGLONG offset
)
290 if(m_pCacheData
== NULL
)
293 if (offset
+ sizeof(SLogCacheRevItemHeader
) > m_DataFileLength
)
296 SLogCacheRevItemHeader
*header
;
297 header
= (SLogCacheRevItemHeader
*)(this->m_pCacheData
+ offset
);
299 if( !CheckHeader(header
))
303 SLogCacheRevFileHeader
*fileheader
;
305 offset
+= sizeof(SLogCacheRevItemHeader
);
306 fileheader
=(SLogCacheRevFileHeader
*)(this->m_pCacheData
+ offset
);
308 for (DWORD i
= 0; i
< header
->m_FileCount
; ++i
)
314 if (offset
+ sizeof(SLogCacheRevFileHeader
) > m_DataFileLength
)
317 if(!CheckHeader(fileheader
))
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("-");
335 path
.m_StatAdd
.Format(_T("%d"),fileheader
->m_Add
);
337 if(fileheader
->m_Del
== 0xFFFFFFFF)
338 path
.m_StatDel
=_T("-");
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
);
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
);
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
);
374 int CLogCache::SaveCache()
380 BOOL bIsRebuild
=false;
382 if (this->m_HashMap
.empty()) // is not sufficient, because "working copy changes" are always included
385 if( this->m_GitDir
.IsEmpty())
388 if (this->m_pCacheIndex
&& m_pCacheIndex
->m_Header
.m_ItemCount
== 0) // check for empty log list (issue #915)
391 SLogCacheIndexFile
*pIndex
= NULL
;
392 if(this->m_pCacheIndex
)
394 pIndex
= (SLogCacheIndexFile
*)malloc(sizeof(SLogCacheIndexFile
)
395 +sizeof(SLogCacheIndexItem
) * (m_pCacheIndex
->m_Header
.m_ItemCount
) );
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
,
416 FILE_ATTRIBUTE_NORMAL
,
419 if(m_IndexFile
== INVALID_HANDLE_VALUE
)
425 file
= m_GitDir
+ DATA_FILE_NAME
;
427 m_DataFile
= CreateFile(file
,
428 GENERIC_READ
|GENERIC_WRITE
,
432 FILE_ATTRIBUTE_NORMAL
,
435 if(m_DataFile
== INVALID_HANDLE_VALUE
)
444 memset(&header
,0,sizeof(SLogCacheIndexHeader
));
446 if((!ReadFile(m_IndexFile
,&header
, sizeof(SLogCacheIndexHeader
),&num
,0)) ||
447 !CheckHeader(&header
)
456 SLogCacheDataFileHeader header
;
458 if((!ReadFile(m_DataFile
,&header
,sizeof(SLogCacheDataFileHeader
),&num
,0)||
459 !CheckHeader(&header
)))
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
;
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);
491 SLogCacheIndexItem item
;
492 item
.m_Hash
= (*i
).second
.m_CommitHash
;
493 item
.m_Offset
=offset
.QuadPart
;
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
)
510 m_pCacheIndex
= (SLogCacheIndexFile
*)MapViewOfFile(m_IndexFileMap
,FILE_MAP_WRITE
,0,0,0);
511 if(m_pCacheIndex
== NULL
)
517 m_pCacheIndex
->m_Header
.m_ItemCount
= header
.m_ItemCount
;
519 FlushViewOfFile(m_pCacheIndex
,0);
523 this->CloseDataHandles();
524 this->CloseIndexHandles();
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();
551 void CLogCache::ClearAllLanes()
553 for (auto i
= m_HashMap
.begin(); i
!= m_HashMap
.end(); ++i
)
555 (*i
).second
.m_Lanes
.clear();