1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - 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.
21 #include "GitRevLoglist.h"
24 #include "UnicodeUtils.h"
27 typedef CComCritSecLock
<CComCriticalSection
> CAutoLocker
;
29 GitRevLoglist::GitRevLoglist(void) : GitRev()
33 , m_IsUpdateing(FALSE
)
34 , m_IsCommitParsed(FALSE
)
35 , m_IsDiffFiles(FALSE
)
36 , m_CallDiffAsync(nullptr)
37 , m_IsSimpleListReady(FALSE
)
40 SecureZeroMemory(&m_GitCommit
, sizeof(GIT_COMMIT
));
43 GitRevLoglist::~GitRevLoglist(void)
45 if (!m_IsCommitParsed
&& m_GitCommit
.m_pGitCommit
)
46 git_free_commit(&m_GitCommit
);
49 void GitRevLoglist::Clear()
59 m_IsUpdateing
= FALSE
;
60 m_IsCommitParsed
= FALSE
;
61 m_IsDiffFiles
= FALSE
;
62 m_CallDiffAsync
= nullptr;
63 m_IsSimpleListReady
= FALSE
;
67 int GitRevLoglist::SafeGetSimpleList(CGit
* git
)
69 if (InterlockedExchange(&m_IsUpdateing
, TRUE
) == TRUE
)
72 m_SimpleFileList
.clear();
73 if (git
->UsingLibGit2(CGit::GIT_CMD_LOGLISTDIFF
))
75 CAutoRepository
repo(git
->GetGitRepository());
79 if (git_commit_lookup(commit
.GetPointer(), repo
, m_CommitHash
) < 0)
83 if (git_commit_tree(commitTree
.GetPointer(), commit
) < 0)
86 bool isRoot
= git_commit_parentcount(commit
) == 0;
87 for (unsigned int parentId
= 0; isRoot
|| parentId
< git_commit_parentcount(commit
); ++parentId
)
92 CAutoCommit parentCommit
;
93 if (git_commit_parent(parentCommit
.GetPointer(), commit
, parentId
) < 0)
96 if (git_commit_tree(parentTree
.GetPointer(), parentCommit
) < 0)
102 if (git_diff_tree_to_tree(diff
.GetPointer(), repo
, parentTree
, commitTree
, nullptr) < 0)
105 size_t deltas
= git_diff_num_deltas(diff
);
106 for (size_t i
= 0; i
< deltas
; ++i
)
108 const git_diff_delta
* delta
= git_diff_get_delta(diff
, i
);
109 m_SimpleFileList
.push_back(CUnicodeUtils::GetUnicode(delta
->new_file
.path
));
113 std::sort(m_SimpleFileList
.begin(), m_SimpleFileList
.end());
114 m_SimpleFileList
.erase(std::unique(m_SimpleFileList
.begin(), m_SimpleFileList
.end()), m_SimpleFileList
.end());
116 InterlockedExchange(&m_IsUpdateing
, FALSE
);
117 InterlockedExchange(&m_IsSimpleListReady
, TRUE
);
120 git
->CheckAndInitDll();
121 GIT_COMMIT commit
= { 0 };
122 GIT_COMMIT_LIST list
;
125 CAutoLocker
lock(g_Git
.m_critGitDllSec
);
129 if (git_get_commit_from_hash(&commit
, m_CommitHash
))
137 git_get_commit_first_parent(&commit
, &list
);
138 bool isRoot
= git_commit_is_root(&commit
) == 0;
139 while (git_get_commit_next_parent(&list
, parent
) == 0 || isRoot
)
146 git_root_diff(git
->GetGitSimpleListDiff(), commit
.m_hash
, &file
, &count
, 0);
148 git_do_diff(git
->GetGitSimpleListDiff(), parent
, commit
.m_hash
, &file
, &count
, 0);
157 for (int j
= 0; j
< count
; ++j
)
161 int status
, isBin
, inc
, dec
, isDir
;
165 git_get_diff_file(git
->GetGitSimpleListDiff(), file
, j
, &newname
, &oldname
, &isDir
, &status
, &isBin
, &inc
, &dec
);
172 m_SimpleFileList
.push_back(CUnicodeUtils::GetUnicode(newname
));
175 git_diff_flush(git
->GetGitSimpleListDiff());
178 std::sort(m_SimpleFileList
.begin(), m_SimpleFileList
.end());
179 m_SimpleFileList
.erase(std::unique(m_SimpleFileList
.begin(), m_SimpleFileList
.end()), m_SimpleFileList
.end());
181 InterlockedExchange(&m_IsUpdateing
, FALSE
);
182 InterlockedExchange(&m_IsSimpleListReady
, TRUE
);
183 if (m_GitCommit
.m_pGitCommit
!= commit
.m_pGitCommit
)
184 git_free_commit(&commit
);
189 int GitRevLoglist::SafeFetchFullInfo(CGit
* git
)
191 if (InterlockedExchange(&m_IsUpdateing
, TRUE
) == TRUE
)
195 if (git
->UsingLibGit2(CGit::GIT_CMD_LOGLISTDIFF
))
197 CAutoRepository
repo(git
->GetGitRepository());
201 if (git_commit_lookup(commit
.GetPointer(), repo
, m_CommitHash
) < 0)
204 CAutoTree commitTree
;
205 if (git_commit_tree(commitTree
.GetPointer(), commit
) < 0)
208 bool isRoot
= git_commit_parentcount(commit
) == 0;
209 for (unsigned int parentId
= 0; isRoot
|| parentId
< git_commit_parentcount(commit
); ++parentId
)
211 CAutoTree parentTree
;
214 CAutoCommit parentCommit
;
215 if (git_commit_parent(parentCommit
.GetPointer(), commit
, parentId
) < 0)
218 if (git_commit_tree(parentTree
.GetPointer(), parentCommit
) < 0)
224 if (git_diff_tree_to_tree(diff
.GetPointer(), repo
, parentTree
, commitTree
, nullptr) < 0)
227 // cf. CGit::GetGitDiff()
228 if (CGit::ms_iSimilarityIndexThreshold
> 0)
230 git_diff_find_options diffopts
= GIT_DIFF_FIND_OPTIONS_INIT
;
231 diffopts
.flags
= GIT_DIFF_FIND_COPIES
| GIT_DIFF_FIND_RENAMES
;
232 diffopts
.rename_threshold
= diffopts
.copy_threshold
= (uint16_t)CGit::ms_iSimilarityIndexThreshold
;
233 if (git_diff_find_similar(diff
, &diffopts
) < 0)
237 const git_diff_delta
* lastDelta
= nullptr;
239 size_t deltas
= git_diff_num_deltas(diff
);
240 for (size_t i
= 0; i
< deltas
; ++i
)
243 if (git_patch_from_diff(patch
.GetPointer(), diff
, i
) < 0)
246 const git_diff_delta
* delta
= git_patch_get_delta(patch
);
248 int isDir
= (delta
->new_file
.mode
& S_IFDIR
) == S_IFDIR
;
249 if (delta
->status
== GIT_DELTA_DELETED
)
250 isDir
= (delta
->old_file
.mode
& S_IFDIR
) == S_IFDIR
;
252 if (lastDelta
&& strcmp(lastDelta
->new_file
.path
, delta
->new_file
.path
) == 0 && (lastDelta
->status
== GIT_DELTA_DELETED
&& delta
->status
== GIT_DELTA_ADDED
|| delta
->status
== GIT_DELTA_DELETED
&& lastDelta
->status
== GIT_DELTA_ADDED
))
255 CTGitPath path
= m_Files
[m_Files
.GetCount() - 1];
256 m_Files
.RemoveItem(path
);
257 if (path
.IsDirectory() && !isDir
)
258 path
.UnsetDirectoryStatus();
259 path
.m_StatAdd
= L
"-";
260 path
.m_StatDel
= L
"-";
261 path
.m_Action
= CTGitPath::LOGACTIONS_MODIFIED
;
262 m_Action
= oldAction
| CTGitPath::LOGACTIONS_MODIFIED
;
263 m_Files
.AddPath(path
);
269 CString newname
= CUnicodeUtils::GetUnicode(delta
->new_file
.path
);
271 if (delta
->new_file
.path
== delta
->old_file
.path
)
272 path
.SetFromGit(newname
, isDir
!= FALSE
);
275 CString oldname
= CUnicodeUtils::GetUnicode(delta
->old_file
.path
);
276 path
.SetFromGit(newname
, &oldname
, &isDir
);
278 oldAction
= m_Action
;
279 m_Action
|= path
.ParserAction(delta
->status
);
280 path
.m_ParentNo
= parentId
;
282 if (delta
->flags
& GIT_DIFF_FLAG_BINARY
)
284 path
.m_StatAdd
= L
"-";
285 path
.m_StatDel
= L
"-";
290 if (git_patch_line_stats(nullptr, &adds
, &dels
, patch
) < 0)
292 path
.m_StatAdd
.Format(L
"%zu", adds
);
293 path
.m_StatDel
.Format(L
"%zu", dels
);
295 m_Files
.AddPath(path
);
299 InterlockedExchange(&m_IsUpdateing
, FALSE
);
300 InterlockedExchange(&m_IsFull
, TRUE
);
304 git
->CheckAndInitDll();
305 GIT_COMMIT commit
= { 0 };
306 GIT_COMMIT_LIST list
;
309 CAutoLocker
lock(g_Git
.m_critGitDllSec
);
313 if (git_get_commit_from_hash(&commit
, m_CommitHash
))
322 git_get_commit_first_parent(&commit
, &list
);
323 bool isRoot
= (list
== nullptr);
325 while (git_get_commit_next_parent(&list
, parent
) == 0 || isRoot
)
333 git_root_diff(git
->GetGitDiff(), m_CommitHash
, &file
, &count
, 1);
335 git_do_diff(git
->GetGitDiff(), parent
, commit
.m_hash
, &file
, &count
, 1);
339 git_free_commit(&commit
);
348 for (int j
= 0; j
< count
; ++j
)
357 int status
, isBin
, inc
, dec
, isDir
;
358 git_get_diff_file(git
->GetGitDiff(), file
, j
, &newname
, &oldname
, &isDir
, &status
, &isBin
, &inc
, &dec
);
360 CGit::StringAppend(&strnewname
, (BYTE
*)newname
, CP_UTF8
);
361 if (strcmp(newname
, oldname
) == 0)
362 path
.SetFromGit(strnewname
, isDir
!= FALSE
);
365 CGit::StringAppend(&stroldname
, (BYTE
*)oldname
, CP_UTF8
);
366 path
.SetFromGit(strnewname
, &stroldname
, &isDir
);
368 path
.ParserAction((BYTE
)status
);
371 m_Action
|= path
.m_Action
;
375 path
.m_StatAdd
= L
"-";
376 path
.m_StatDel
= L
"-";
380 path
.m_StatAdd
.Format(L
"%d", inc
);
381 path
.m_StatDel
.Format(L
"%d", dec
);
383 m_Files
.AddPath(path
);
385 git_diff_flush(git
->GetGitDiff());
389 InterlockedExchange(&m_IsUpdateing
, FALSE
);
390 InterlockedExchange(&m_IsFull
, TRUE
);
391 if (m_GitCommit
.m_pGitCommit
!= commit
.m_pGitCommit
)
392 git_free_commit(&commit
);
397 int GitRevLoglist::GetRefLog(const CString
& ref
, std::vector
<GitRevLoglist
>& refloglist
, CString
& error
)
400 if (g_Git
.m_IsUseLibGit2
)
402 CAutoRepository
repo(g_Git
.GetGitRepository());
405 error
= g_Git
.GetLibGit2LastErr();
410 if (git_reflog_read(reflog
.GetPointer(), repo
, CUnicodeUtils::GetUTF8(ref
)) < 0)
412 error
= g_Git
.GetLibGit2LastErr();
416 for (size_t i
= 0; i
< git_reflog_entrycount(reflog
); ++i
)
418 const git_reflog_entry
*entry
= git_reflog_entry_byindex(reflog
, i
);
423 rev
.m_CommitHash
= git_reflog_entry_id_new(entry
);
424 rev
.m_Ref
.Format(L
"%s@{%zu}", (LPCTSTR
)ref
, i
);
425 rev
.GetCommitterDate() = CTime(git_reflog_entry_committer(entry
)->when
.time
);
426 if (git_reflog_entry_message(entry
) != nullptr)
428 CString one
= CUnicodeUtils::GetUnicode(git_reflog_entry_message(entry
));
429 int message
= one
.Find(L
": ");
432 rev
.m_RefAction
= one
.Left(message
);
433 rev
.GetSubject() = one
.Mid(message
+ 2);
436 rev
.m_RefAction
= one
;
438 refloglist
.push_back(rev
);
442 else if (g_Git
.m_IsUseGitDLL
)
444 g_Git
.CheckAndInitDll();
445 std::vector
<GitRevLoglist
> tmp
;
446 // no error checking, because the only error which could occur is file not found
447 git_for_each_reflog_ent(CUnicodeUtils::GetUTF8(ref
), [](struct GIT_OBJECT_OID
* /*old_oid*/, struct GIT_OBJECT_OID
* new_oid
, const char* /*committer*/, unsigned long long time
, int /*sz*/, const char* msg
, void* data
)
449 std::vector
<GitRevLoglist
>* vector
= (std::vector
<GitRevLoglist
>*)data
;
451 rev
.m_CommitHash
= (const unsigned char*)new_oid
->hash
;
452 rev
.GetCommitterDate() = CTime(time
);
454 CString one
= CUnicodeUtils::GetUnicode(msg
);
455 int message
= one
.Find(L
": ");
458 rev
.m_RefAction
= one
.Left(message
);
459 rev
.GetSubject() = one
.Mid(message
+ 2).TrimRight(L
'\n');
462 rev
.m_RefAction
= one
.TrimRight(L
'\n');
463 vector
->push_back(rev
);
468 for (size_t i
= tmp
.size(), id
= 0; i
> 0; --i
, ++id
)
470 GitRevLoglist rev
= tmp
[i
- 1];
471 rev
.m_Ref
.Format(L
"%s@{%zu}", (LPCTSTR
)ref
, id
);
472 refloglist
.push_back(rev
);
478 if (!GitAdminDir::GetAdminDirPath(g_Git
.m_CurrentDir
, dotGitDir
))
480 error
= L
".git directory not found";
485 // git.exe would fail with "branch not known"
486 if (!PathFileExists(dotGitDir
+ ref
))
490 cmd
.Format(L
"git.exe reflog show --pretty=\"%%H %%gD: %%gs\" --date=raw %s", (LPCTSTR
)ref
);
491 if (g_Git
.Run(cmd
, &out
, &error
, CP_UTF8
))
495 CString prefix
= ref
+ L
"@{";
499 CString one
= out
.Tokenize(L
"\n", pos
);
500 int refPos
= one
.Find(L
' ');
505 rev
.m_CommitHash
= one
.Left(refPos
);
506 rev
.m_Ref
.Format(L
"%s@{%d}", (LPCTSTR
)ref
, i
++);
507 int prefixPos
= one
.Find(prefix
, refPos
+ 1);
508 if (prefixPos
!= refPos
+ 1)
511 int spacePos
= one
.Find(L
' ', prefixPos
+ prefix
.GetLength() + 1);
515 CString timeStr
= one
.Mid(prefixPos
+ prefix
.GetLength(), spacePos
- prefixPos
- prefix
.GetLength());
516 rev
.GetCommitterDate() = CTime(_wtoi(timeStr
));
517 int action
= one
.Find(L
"}: ", spacePos
+ 1);
521 int message
= one
.Find(L
": ", action
);
524 rev
.m_RefAction
= one
.Mid(action
+ 1, message
- action
- 1);
525 rev
.GetSubject() = one
.Right(one
.GetLength() - message
- 2);
528 rev
.m_RefAction
= one
.Mid(action
+ 1);
531 refloglist
.push_back(rev
);