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
=reinterpret_cast<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
= reinterpret_cast<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(reinterpret_cast<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
= reinterpret_cast<SLogCacheRevItemHeader
*>(m_pCacheData
+ offset
);
280 if( !CheckHeader(header
))
284 SLogCacheRevFileHeader
*fileheader
;
286 offset
+= sizeof(SLogCacheRevItemHeader
);
287 fileheader
= reinterpret_cast<SLogCacheRevFileHeader
*>(m_pCacheData
+ offset
);
289 for (DWORD i
= 0; i
< header
->m_FileCount
; ++i
)
294 if (offset
+ sizeof(SLogCacheRevFileHeader
) > m_DataFileLength
)
301 if(!CheckHeader(fileheader
))
308 CString
file(fileheader
->m_FileName
, fileheader
->m_FileNameSize
);
309 if(fileheader
->m_OldFileNameSize
)
310 oldfile
= CString(fileheader
->m_FileName
+ fileheader
->m_FileNameSize
, fileheader
->m_OldFileNameSize
);
311 path
.SetFromGit(file
,&oldfile
);
313 path
.m_ParentNo
= fileheader
->m_ParentNo
;
314 path
.m_Stage
= fileheader
->m_Stage
;
315 path
.m_Action
= fileheader
->m_Action
& ~(CTGitPath::LOGACTIONS_HIDE
| CTGitPath::LOGACTIONS_GRAY
);
316 Rev
.m_Action
|= path
.m_Action
;
318 if(fileheader
->m_Add
== 0xFFFFFFFF)
319 path
.m_StatAdd
=_T("-");
321 path
.m_StatAdd
.Format(_T("%d"),fileheader
->m_Add
);
323 if(fileheader
->m_Del
== 0xFFFFFFFF)
324 path
.m_StatDel
=_T("-");
326 path
.m_StatDel
.Format(_T("%d"), fileheader
->m_Del
);
328 Rev
.m_Files
.AddPath(path
);
330 offset
+= sizeof(*fileheader
) + fileheader
->m_OldFileNameSize
*sizeof(TCHAR
) + fileheader
->m_FileNameSize
*sizeof(TCHAR
) - sizeof(TCHAR
);
331 fileheader
= reinterpret_cast<SLogCacheRevFileHeader
*>(m_pCacheData
+ offset
);
335 int CLogCache::RebuildCacheFile()
337 SLogCacheIndexHeader Indexheader
;
339 Indexheader
.m_Magic
= LOG_INDEX_MAGIC
;
340 Indexheader
.m_Version
= LOG_INDEX_VERSION
;
341 Indexheader
.m_ItemCount
=0;
343 SLogCacheDataFileHeader dataheader
;
345 dataheader
.m_Magic
= LOG_DATA_MAGIC
;
346 dataheader
.m_Version
= LOG_INDEX_VERSION
;
348 ::SetFilePointer(m_IndexFile
,0,0,0);
349 ::SetFilePointer(m_DataFile
,0,0,0);
350 SetEndOfFile(this->m_IndexFile
);
351 SetEndOfFile(this->m_DataFile
);
354 WriteFile(this->m_IndexFile
,&Indexheader
,sizeof(SLogCacheIndexHeader
),&num
,0);
355 SetEndOfFile(this->m_IndexFile
);
356 WriteFile(this->m_DataFile
,&dataheader
,sizeof(SLogCacheDataFileHeader
),&num
,0);
357 SetEndOfFile(this->m_DataFile
);
360 int CLogCache::SaveCache()
365 BOOL bIsRebuild
=false;
367 if (this->m_HashMap
.empty()) // is not sufficient, because "working copy changes" are always included
370 if( this->m_GitDir
.IsEmpty())
373 if (this->m_pCacheIndex
&& m_pCacheIndex
->m_Header
.m_ItemCount
== 0) // check for empty log list (issue #915)
376 SLogCacheIndexFile
* pIndex
= nullptr;
377 if(this->m_pCacheIndex
)
379 pIndex
= reinterpret_cast<SLogCacheIndexFile
*>(malloc(sizeof(SLogCacheIndexFile
) + sizeof(SLogCacheIndexItem
) * (m_pCacheIndex
->m_Header
.m_ItemCount
)));
383 memcpy(pIndex
,this->m_pCacheIndex
,
384 sizeof(SLogCacheIndexFile
) + sizeof(SLogCacheIndexItem
) *( m_pCacheIndex
->m_Header
.m_ItemCount
-1)
388 this->CloseDataHandles();
389 this->CloseIndexHandles();
391 SLogCacheIndexHeader header
;
392 CString file
= this->m_GitDir
+ INDEX_FILE_NAME
;
396 m_IndexFile
= CreateFile(file
,
397 GENERIC_READ
|GENERIC_WRITE
,
401 FILE_ATTRIBUTE_NORMAL
,
404 if(m_IndexFile
== INVALID_HANDLE_VALUE
)
407 file
= m_GitDir
+ DATA_FILE_NAME
;
409 m_DataFile
= CreateFile(file
,
410 GENERIC_READ
|GENERIC_WRITE
,
414 FILE_ATTRIBUTE_NORMAL
,
417 if(m_DataFile
== INVALID_HANDLE_VALUE
)
421 memset(&header
,0,sizeof(SLogCacheIndexHeader
));
423 if ((!ReadFile(m_IndexFile
, &header
, sizeof(SLogCacheIndexHeader
), &num
, 0)) || num
!= sizeof(SLogCacheIndexHeader
) ||
424 !CheckHeader(&header
)
433 SLogCacheDataFileHeader datafileheader
;
435 if ((!ReadFile(m_DataFile
, &datafileheader
, sizeof(SLogCacheDataFileHeader
), &num
, 0) || num
!= sizeof(SLogCacheDataFileHeader
) ||
436 !CheckHeader(&datafileheader
)))
444 header
.m_ItemCount
=0;
446 SetFilePointer(m_DataFile
,0,0,2);
447 SetFilePointer(m_IndexFile
,0,0,2);
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
;
460 SetFilePointerEx(this->m_DataFile
,start
,&offset
,1);
461 if (this->SaveOneItem((*i
).second
, (LONG
)offset
.QuadPart
))
463 TRACE(_T("Save one item error"));
464 SetFilePointerEx(this->m_DataFile
,offset
, &offset
,0);
468 SLogCacheIndexItem item
;
469 item
.m_Hash
= (*i
).second
.m_CommitHash
;
470 item
.m_Offset
=offset
.QuadPart
;
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
== INVALID_HANDLE_VALUE
)
484 m_pCacheIndex
= reinterpret_cast<SLogCacheIndexFile
*>(MapViewOfFile(m_IndexFileMap
, FILE_MAP_WRITE
, 0, 0, 0));
488 m_pCacheIndex
->m_Header
.m_ItemCount
= header
.m_ItemCount
;
490 FlushViewOfFile(m_pCacheIndex
,0);
494 this->CloseDataHandles();
495 this->CloseIndexHandles();
501 void CLogCache::Sort()
503 if(this->m_pCacheIndex
)
504 qsort(m_pCacheIndex
->m_Item
, m_pCacheIndex
->m_Header
.m_ItemCount
,sizeof(SLogCacheIndexItem
), Compare
);
507 int CLogCache::ClearAllParent()
509 for (auto i
= m_HashMap
.begin(); i
!= m_HashMap
.end(); ++i
)
511 (*i
).second
.m_ParentHash
.clear();
512 (*i
).second
.m_Lanes
.clear();
517 void CLogCache::ClearAllLanes()
519 for (auto i
= m_HashMap
.begin(); i
!= m_HashMap
.end(); ++i
)
520 (*i
).second
.m_Lanes
.clear();