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.
20 #include "GitLogCache.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;
38 m_bEnabled
= CRegDWORD(_T("Software\\TortoiseGit\\EnableLogCache"), TRUE
);
41 void CLogCache::CloseDataHandles()
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()
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()
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
)
97 pData
= m_pCacheIndex
;
102 SLogCacheIndexItem
*p
=(SLogCacheIndexItem
*)bsearch(hash
.m_hash
,pData
->m_Item
,
103 pData
->m_Header
.m_ItemCount
,
104 sizeof(SLogCacheIndexItem
),
113 int CLogCache::FetchCacheIndex(CString GitDir
)
118 if (!GitAdminDir::GetAdminDirPath(GitDir
, m_GitDir
))
124 if( m_IndexFile
== INVALID_HANDLE_VALUE
)
126 CString file
= m_GitDir
+ INDEX_FILE_NAME
;
127 m_IndexFile
= CreateFile(file
,
129 FILE_SHARE_READ
| FILE_SHARE_DELETE
,
132 FILE_ATTRIBUTE_NORMAL
,
135 if( m_IndexFile
== INVALID_HANDLE_VALUE
)
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
)
148 m_pCacheIndex
= (SLogCacheIndexFile
*)MapViewOfFile(m_IndexFileMap
,FILE_MAP_READ
,0,0,0);
153 DWORD indexFileLength
= GetFileSize(m_IndexFile
, nullptr);
154 if (indexFileLength
== INVALID_FILE_SIZE
|| indexFileLength
< sizeof(SLogCacheIndexHeader
))
157 if( !CheckHeader(&m_pCacheIndex
->m_Header
))
160 if (indexFileLength
< sizeof(SLogCacheIndexHeader
) + m_pCacheIndex
->m_Header
.m_ItemCount
* sizeof(SLogCacheIndexItem
))
163 if( m_DataFile
== INVALID_HANDLE_VALUE
)
165 CString file
= m_GitDir
+ DATA_FILE_NAME
;
166 m_DataFile
= CreateFile(file
,
168 FILE_SHARE_READ
| FILE_SHARE_DELETE
,
171 FILE_ATTRIBUTE_NORMAL
,
174 if(m_DataFile
== INVALID_HANDLE_VALUE
)
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
)
184 m_DataFileLength
= GetFileSize(m_DataFile
, nullptr);
185 if (m_DataFileLength
== INVALID_FILE_SIZE
|| m_DataFileLength
< sizeof(SLogCacheDataFileHeader
))
190 m_pCacheData
= (BYTE
*)MapViewOfFile(m_DataFileMap
,FILE_MAP_READ
,0,0,0);
195 if(!CheckHeader( (SLogCacheDataFileHeader
*)m_pCacheData
))
198 if (m_DataFileLength
< sizeof(SLogCacheDataFileHeader
) + m_pCacheIndex
->m_Header
.m_ItemCount
* sizeof(SLogCacheDataFileHeader
))
208 ::DeleteFile(m_GitDir
+ INDEX_FILE_NAME
);
209 ::DeleteFile(m_GitDir
+ DATA_FILE_NAME
);
214 int CLogCache::SaveOneItem(const GitRevLoglist
& Rev
, LONG offset
)
216 if(!Rev
.m_IsDiffFiles
)
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();
229 if(!WriteFile(this->m_DataFile
,&header
, sizeof(header
),&num
,0))
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))
257 if (!WriteFile(this->m_DataFile
, name
, name
.GetLength() * sizeof(TCHAR
), &num
, 0))
260 if(!oldname
.IsEmpty())
262 if (!WriteFile(this->m_DataFile
, oldname
, oldname
.GetLength() * sizeof(TCHAR
), &num
, 0))
270 int CLogCache::LoadOneItem(GitRevLoglist
& Rev
,ULONGLONG offset
)
275 if (offset
+ sizeof(SLogCacheRevItemHeader
) > m_DataFileLength
)
278 SLogCacheRevItemHeader
*header
;
279 header
= (SLogCacheRevItemHeader
*)(this->m_pCacheData
+ offset
);
281 if( !CheckHeader(header
))
285 SLogCacheRevFileHeader
*fileheader
;
287 offset
+= sizeof(SLogCacheRevItemHeader
);
288 fileheader
=(SLogCacheRevFileHeader
*)(this->m_pCacheData
+ offset
);
290 for (DWORD i
= 0; i
< header
->m_FileCount
; ++i
)
295 if (offset
+ sizeof(SLogCacheRevFileHeader
) > m_DataFileLength
)
302 if(!CheckHeader(fileheader
))
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("-");
322 path
.m_StatAdd
.Format(_T("%d"),fileheader
->m_Add
);
324 if(fileheader
->m_Del
== 0xFFFFFFFF)
325 path
.m_StatDel
=_T("-");
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
);
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
);
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
);
361 int CLogCache::SaveCache()
366 BOOL bIsRebuild
=false;
368 if (this->m_HashMap
.empty()) // is not sufficient, because "working copy changes" are always included
371 if( this->m_GitDir
.IsEmpty())
374 if (this->m_pCacheIndex
&& m_pCacheIndex
->m_Header
.m_ItemCount
== 0) // check for empty log list (issue #915)
377 SLogCacheIndexFile
* pIndex
= nullptr;
378 if(this->m_pCacheIndex
)
380 pIndex
= (SLogCacheIndexFile
*)malloc(sizeof(SLogCacheIndexFile
)
381 +sizeof(SLogCacheIndexItem
) * (m_pCacheIndex
->m_Header
.m_ItemCount
) );
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
;
398 m_IndexFile
= CreateFile(file
,
399 GENERIC_READ
|GENERIC_WRITE
,
403 FILE_ATTRIBUTE_NORMAL
,
406 if(m_IndexFile
== INVALID_HANDLE_VALUE
)
409 file
= m_GitDir
+ DATA_FILE_NAME
;
411 m_DataFile
= CreateFile(file
,
412 GENERIC_READ
|GENERIC_WRITE
,
416 FILE_ATTRIBUTE_NORMAL
,
419 if(m_DataFile
== INVALID_HANDLE_VALUE
)
423 memset(&header
,0,sizeof(SLogCacheIndexHeader
));
425 if ((!ReadFile(m_IndexFile
, &header
, sizeof(SLogCacheIndexHeader
), &num
, 0)) || num
!= sizeof(SLogCacheIndexHeader
) ||
426 !CheckHeader(&header
)
435 SLogCacheDataFileHeader datafileheader
;
437 if ((!ReadFile(m_DataFile
, &datafileheader
, sizeof(SLogCacheDataFileHeader
), &num
, 0) || num
!= sizeof(SLogCacheDataFileHeader
) ||
438 !CheckHeader(&datafileheader
)))
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
;
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);
470 SLogCacheIndexItem item
;
471 item
.m_Hash
= (*i
).second
.m_CommitHash
;
472 item
.m_Offset
=offset
.QuadPart
;
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
)
486 m_pCacheIndex
= (SLogCacheIndexFile
*)MapViewOfFile(m_IndexFileMap
,FILE_MAP_WRITE
,0,0,0);
490 m_pCacheIndex
->m_Header
.m_ItemCount
= header
.m_ItemCount
;
492 FlushViewOfFile(m_pCacheIndex
,0);
496 this->CloseDataHandles();
497 this->CloseIndexHandles();
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();
519 void CLogCache::ClearAllLanes()
521 for (auto i
= m_HashMap
.begin(); i
!= m_HashMap
.end(); ++i
)
522 (*i
).second
.m_Lanes
.clear();