1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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 "GitAdminDir.h"
21 #include "GitLogCache.h"
24 int static Compare(const void *p1
, const void*p2
)
26 return memcmp(p1
, p2
, GIT_HASH_SIZE
);
29 CLogCache::CLogCache()
30 : m_IndexFile(INVALID_HANDLE_VALUE
)
31 , m_IndexFileMap(nullptr)
32 , m_pCacheIndex(nullptr)
33 , m_DataFile(INVALID_HANDLE_VALUE
)
34 , m_DataFileMap(nullptr)
35 , m_pCacheData(nullptr)
38 m_bEnabled
= CRegDWORD(L
"Software\\TortoiseGit\\EnableLogCache", TRUE
);
41 void CLogCache::CloseDataHandles()
45 UnmapViewOfFile(m_pCacheData
);
46 m_pCacheData
= nullptr;
50 CloseHandle(m_DataFileMap
);
51 m_DataFileMap
= nullptr;
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;
70 CloseHandle(m_IndexFileMap
);
71 m_IndexFileMap
= nullptr;
74 if (m_IndexFile
!= INVALID_HANDLE_VALUE
)
76 CloseHandle(m_IndexFile
);
77 m_IndexFile
=INVALID_HANDLE_VALUE
;
80 CLogCache::~CLogCache()
86 GitRevLoglist
* CLogCache::GetCacheData(const CGitHash
& hash
)
88 m_HashMap
[hash
].m_CommitHash
=hash
;
89 return &m_HashMap
[hash
];
92 ULONGLONG
CLogCache::GetOffset(const CGitHash
& hash
, SLogCacheIndexFile
* pData
)
95 pData
= m_pCacheIndex
;
100 SLogCacheIndexItem
* p
=reinterpret_cast<SLogCacheIndexItem
*>(bsearch(hash
.m_hash
, pData
->m_Item
,
101 pData
->m_Header
.m_ItemCount
,
102 sizeof(SLogCacheIndexItem
),
111 int CLogCache::FetchCacheIndex(CString GitDir
)
116 if (!GitAdminDir::GetWorktreeAdminDirPath(GitDir
, m_GitDir
))
122 if( m_IndexFile
== INVALID_HANDLE_VALUE
)
124 CString file
= m_GitDir
+ INDEX_FILE_NAME
;
125 m_IndexFile
= CreateFile(file
,
127 FILE_SHARE_READ
| FILE_SHARE_DELETE
,
130 FILE_ATTRIBUTE_NORMAL
,
133 if( m_IndexFile
== INVALID_HANDLE_VALUE
)
139 m_IndexFileMap
= CreateFileMapping(m_IndexFile
, nullptr, PAGE_READONLY
, 0, 0, nullptr);
146 m_pCacheIndex
= reinterpret_cast<SLogCacheIndexFile
*>(MapViewOfFile(m_IndexFileMap
, FILE_MAP_READ
, 0, 0, 0));
151 DWORD indexFileLength
= GetFileSize(m_IndexFile
, nullptr);
152 if (indexFileLength
== INVALID_FILE_SIZE
|| indexFileLength
< sizeof(SLogCacheIndexHeader
))
155 if( !CheckHeader(&m_pCacheIndex
->m_Header
))
158 if (indexFileLength
!= sizeof(SLogCacheIndexHeader
) + m_pCacheIndex
->m_Header
.m_ItemCount
* sizeof(SLogCacheIndexItem
))
161 if( m_DataFile
== INVALID_HANDLE_VALUE
)
163 CString file
= m_GitDir
+ DATA_FILE_NAME
;
164 m_DataFile
= CreateFile(file
,
166 FILE_SHARE_READ
| FILE_SHARE_DELETE
,
169 FILE_ATTRIBUTE_NORMAL
,
172 if(m_DataFile
== INVALID_HANDLE_VALUE
)
178 m_DataFileMap
= CreateFileMapping(m_DataFile
, nullptr, PAGE_READONLY
, 0, 0, nullptr);
182 m_DataFileLength
= GetFileSize(m_DataFile
, nullptr);
183 if (m_DataFileLength
== INVALID_FILE_SIZE
|| m_DataFileLength
< sizeof(SLogCacheDataFileHeader
))
188 m_pCacheData
= (BYTE
*)MapViewOfFile(m_DataFileMap
,FILE_MAP_READ
,0,0,0);
193 if (!CheckHeader(reinterpret_cast<SLogCacheDataFileHeader
*>(m_pCacheData
)))
196 if (m_DataFileLength
< sizeof(SLogCacheDataFileHeader
) + m_pCacheIndex
->m_Header
.m_ItemCount
* sizeof(SLogCacheDataFileHeader
))
206 ::DeleteFile(m_GitDir
+ INDEX_FILE_NAME
);
207 ::DeleteFile(m_GitDir
+ DATA_FILE_NAME
);
212 int CLogCache::SaveOneItem(const GitRevLoglist
& Rev
, LONG offset
)
214 if(!Rev
.m_IsDiffFiles
)
217 SetFilePointer(m_DataFile
, offset
, 0, FILE_BEGIN
);
219 SLogCacheRevItemHeader header
;
221 header
.m_Magic
=LOG_DATA_ITEM_MAGIC
;
222 header
.m_FileCount
=Rev
.m_Files
.GetCount();
226 if(!WriteFile(this->m_DataFile
,&header
, sizeof(header
),&num
,0))
230 CString name
,oldname
;
231 for (int i
= 0; i
< Rev
.m_Files
.GetCount(); ++i
)
233 SLogCacheRevFileHeader revfileheader
;
234 revfileheader
.m_Magic
= LOG_DATA_FILE_MAGIC
;
235 revfileheader
.m_IsSubmodule
= Rev
.m_Files
[i
].IsDirectory() ? 1 : 0;
236 revfileheader
.m_Action
= Rev
.m_Files
[i
].m_Action
;
237 revfileheader
.m_Stage
= Rev
.m_Files
[i
].m_Stage
;
238 revfileheader
.m_ParentNo
= Rev
.m_Files
[i
].m_ParentNo
;
239 name
= Rev
.m_Files
[i
].GetGitPathString();
240 revfileheader
.m_FileNameSize
= name
.GetLength();
241 oldname
= Rev
.m_Files
[i
].GetGitOldPathString();
242 revfileheader
.m_OldFileNameSize
= oldname
.GetLength();
244 stat
= Rev
.m_Files
[i
].m_StatAdd
;
245 revfileheader
.m_Add
= (stat
== L
"-") ? 0xFFFFFFFF : _wtol(stat
);
246 stat
= Rev
.m_Files
[i
].m_StatDel
;
247 revfileheader
.m_Del
= (stat
== L
"-") ? 0xFFFFFFFF : _wtol(stat
);
249 if(!WriteFile(this->m_DataFile
, &revfileheader
, sizeof(revfileheader
) - sizeof(TCHAR
), &num
, 0))
254 if (!WriteFile(this->m_DataFile
, name
, name
.GetLength() * sizeof(TCHAR
), &num
, 0))
257 if(!oldname
.IsEmpty())
259 if (!WriteFile(this->m_DataFile
, oldname
, oldname
.GetLength() * sizeof(TCHAR
), &num
, 0))
267 int CLogCache::LoadOneItem(GitRevLoglist
& Rev
,ULONGLONG offset
)
272 if (offset
+ sizeof(SLogCacheRevItemHeader
) > m_DataFileLength
)
275 SLogCacheRevItemHeader
* header
= reinterpret_cast<SLogCacheRevItemHeader
*>(m_pCacheData
+ offset
);
277 if( !CheckHeader(header
))
282 offset
+= sizeof(SLogCacheRevItemHeader
);
284 for (DWORD i
= 0; i
< header
->m_FileCount
; ++i
)
286 if (offset
+ sizeof(SLogCacheRevFileHeader
) >= m_DataFileLength
)
293 auto fileheader
= reinterpret_cast<SLogCacheRevFileHeader
*>(m_pCacheData
+ offset
);
295 if(!CheckHeader(fileheader
))
302 auto recordLength
= sizeof(SLogCacheRevFileHeader
) + fileheader
->m_FileNameSize
* sizeof(TCHAR
) + fileheader
->m_OldFileNameSize
* sizeof(TCHAR
) - sizeof(TCHAR
);
303 if (!fileheader
->m_FileNameSize
|| offset
+ recordLength
> m_DataFileLength
)
311 CString
file(fileheader
->m_FileName
, fileheader
->m_FileNameSize
);
312 if(fileheader
->m_OldFileNameSize
)
313 oldfile
= CString(fileheader
->m_FileName
+ fileheader
->m_FileNameSize
, fileheader
->m_OldFileNameSize
);
315 int isSubmodule
= fileheader
->m_IsSubmodule
;
316 path
.SetFromGit(file
, oldfile
.IsEmpty() ? nullptr : &oldfile
, (int*)&isSubmodule
);
318 path
.m_ParentNo
= fileheader
->m_ParentNo
;
319 path
.m_Stage
= fileheader
->m_Stage
;
320 path
.m_Action
= fileheader
->m_Action
& ~(CTGitPath::LOGACTIONS_HIDE
| CTGitPath::LOGACTIONS_GRAY
);
321 Rev
.m_Action
|= path
.m_Action
;
323 if(fileheader
->m_Add
== 0xFFFFFFFF)
324 path
.m_StatAdd
= L
"-";
326 path
.m_StatAdd
.Format(L
"%d", fileheader
->m_Add
);
328 if(fileheader
->m_Del
== 0xFFFFFFFF)
329 path
.m_StatDel
= L
"-";
331 path
.m_StatDel
.Format(L
"%d", fileheader
->m_Del
);
333 Rev
.m_Files
.AddPath(path
);
335 offset
+= recordLength
;
339 int CLogCache::RebuildCacheFile()
341 SLogCacheIndexHeader Indexheader
;
343 Indexheader
.m_Magic
= LOG_INDEX_MAGIC
;
344 Indexheader
.m_Version
= LOG_INDEX_VERSION
;
345 Indexheader
.m_ItemCount
=0;
347 SLogCacheDataFileHeader dataheader
;
349 dataheader
.m_Magic
= LOG_DATA_MAGIC
;
350 dataheader
.m_Version
= LOG_INDEX_VERSION
;
352 SetFilePointer(m_IndexFile
, 0, 0, FILE_BEGIN
);
353 SetFilePointer(m_DataFile
, 0, 0, FILE_BEGIN
);
354 SetEndOfFile(this->m_IndexFile
);
355 SetEndOfFile(this->m_DataFile
);
358 WriteFile(this->m_IndexFile
,&Indexheader
,sizeof(SLogCacheIndexHeader
),&num
,0);
359 SetEndOfFile(this->m_IndexFile
);
360 WriteFile(this->m_DataFile
,&dataheader
,sizeof(SLogCacheDataFileHeader
),&num
,0);
361 SetEndOfFile(this->m_DataFile
);
364 int CLogCache::SaveCache()
369 BOOL bIsRebuild
=false;
371 if (m_HashMap
.empty() || (m_HashMap
.size() == 1 && m_HashMap
.find(CGitHash()) != m_HashMap
.cend()))
374 if( this->m_GitDir
.IsEmpty())
377 SLogCacheIndexFile
* pIndex
= nullptr;
378 if (m_pCacheIndex
&& m_pCacheIndex
->m_Header
.m_ItemCount
> 0)
380 auto len
= sizeof(SLogCacheIndexFile
) + sizeof(SLogCacheIndexItem
) * (m_pCacheIndex
->m_Header
.m_ItemCount
- 1);
381 pIndex
= reinterpret_cast<SLogCacheIndexFile
*>(malloc(len
));
385 memcpy(pIndex
, m_pCacheIndex
, len
);
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, FILE_END
);
447 SetFilePointer(m_IndexFile
, 0, 0, FILE_END
);
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(m_DataFile
, start
, &offset
, FILE_CURRENT
);
461 if (this->SaveOneItem((*i
).second
, (LONG
)offset
.QuadPart
))
463 TRACE(L
"Save one item error");
464 SetFilePointerEx(m_DataFile
, offset
, &offset
, FILE_BEGIN
);
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);
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 std::qsort(m_pCacheIndex
->m_Item
, m_pCacheIndex
->m_Header
.m_ItemCount
, sizeof(SLogCacheIndexItem
), Compare
);
492 FlushViewOfFile(m_pCacheIndex
,0);
496 this->CloseDataHandles();
497 this->CloseIndexHandles();
503 int CLogCache::ClearAllParent()
505 for (auto i
= m_HashMap
.begin(); i
!= m_HashMap
.end(); ++i
)
507 (*i
).second
.m_ParentHash
.clear();
508 (*i
).second
.m_Lanes
.clear();
513 void CLogCache::ClearAllLanes()
515 for (auto i
= m_HashMap
.begin(); i
!= m_HashMap
.end(); ++i
)
516 (*i
).second
.m_Lanes
.clear();