Make CGitHash "operator const unsigned char*" explicit
[TortoiseGit.git] / src / Git / GitRevLoglist.cpp
blobfbaa2ee37b5a0333cd4ea2d4363ba37eece0dcb7
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.
20 #include "stdafx.h"
21 #include "GitRevLoglist.h"
22 #include "Git.h"
23 #include "gitdll.h"
24 #include "UnicodeUtils.h"
25 #include <sys/stat.h>
27 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
29 GitRevLoglist::GitRevLoglist(void) : GitRev()
30 , m_Action(0)
31 , m_RebaseAction(0)
32 , m_IsFull(FALSE)
33 , m_IsUpdateing(FALSE)
34 , m_IsCommitParsed(FALSE)
35 , m_IsDiffFiles(FALSE)
36 , m_CallDiffAsync(nullptr)
37 , m_IsSimpleListReady(FALSE)
38 , m_Mark(0)
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()
51 GitRev::Clear();
52 m_Action = 0;
53 m_Files.Clear();
54 m_UnRevFiles.Clear();
55 m_Ref.Empty();
56 m_RefAction.Empty();
57 m_Mark = 0;
58 m_IsFull = FALSE;
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)
70 return 0;
72 m_SimpleFileList.clear();
73 if (git->UsingLibGit2(CGit::GIT_CMD_LOGLISTDIFF))
75 CAutoRepository repo(git->GetGitRepository());
76 if (!repo)
77 return -1;
78 CAutoCommit commit;
79 if (git_commit_lookup(commit.GetPointer(), repo, m_CommitHash) < 0)
80 return -1;
82 CAutoTree commitTree;
83 if (git_commit_tree(commitTree.GetPointer(), commit) < 0)
84 return -1;
86 bool isRoot = git_commit_parentcount(commit) == 0;
87 for (unsigned int parentId = 0; isRoot || parentId < git_commit_parentcount(commit); ++parentId)
89 CAutoTree parentTree;
90 if (!isRoot)
92 CAutoCommit parentCommit;
93 if (git_commit_parent(parentCommit.GetPointer(), commit, parentId) < 0)
94 return -1;
96 if (git_commit_tree(parentTree.GetPointer(), parentCommit) < 0)
97 return -1;
99 isRoot = false;
101 CAutoDiff diff;
102 if (git_diff_tree_to_tree(diff.GetPointer(), repo, parentTree, commitTree, nullptr) < 0)
103 return -1;
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);
118 return 0;
120 git->CheckAndInitDll();
121 GIT_COMMIT commit = { 0 };
122 GIT_COMMIT_LIST list;
123 GIT_HASH parent;
125 CAutoLocker lock(g_Git.m_critGitDllSec);
129 if (git_get_commit_from_hash(&commit, static_cast<const unsigned char*>(m_CommitHash)))
130 return -1;
132 catch (char *)
134 return -1;
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)
141 GIT_FILE file = 0;
142 int count = 0;
145 if (isRoot)
146 git_root_diff(git->GetGitSimpleListDiff(), commit.m_hash, &file, &count, 0);
147 else
148 git_do_diff(git->GetGitSimpleListDiff(), parent, commit.m_hash, &file, &count, 0);
150 catch (char *)
152 return -1;
155 isRoot = false;
157 for (int j = 0; j < count; ++j)
159 char* newname;
160 char* oldname;
161 int status, isBin, inc, dec, isDir;
165 git_get_diff_file(git->GetGitSimpleListDiff(), file, j, &newname, &oldname, &isDir, &status, &isBin, &inc, &dec);
167 catch (char *)
169 return -1;
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);
186 return 0;
189 int GitRevLoglist::SafeFetchFullInfo(CGit* git)
191 if (InterlockedExchange(&m_IsUpdateing, TRUE) == TRUE)
192 return 0;
194 m_Files.Clear();
195 if (git->UsingLibGit2(CGit::GIT_CMD_LOGLISTDIFF))
197 CAutoRepository repo(git->GetGitRepository());
198 if (!repo)
199 return -1;
200 CAutoCommit commit;
201 if (git_commit_lookup(commit.GetPointer(), repo, m_CommitHash) < 0)
202 return -1;
204 CAutoTree commitTree;
205 if (git_commit_tree(commitTree.GetPointer(), commit) < 0)
206 return -1;
208 bool isRoot = git_commit_parentcount(commit) == 0;
209 for (unsigned int parentId = 0; isRoot || parentId < git_commit_parentcount(commit); ++parentId)
211 CAutoTree parentTree;
212 if (!isRoot)
214 CAutoCommit parentCommit;
215 if (git_commit_parent(parentCommit.GetPointer(), commit, parentId) < 0)
216 return -1;
218 if (git_commit_tree(parentTree.GetPointer(), parentCommit) < 0)
219 return -1;
221 isRoot = false;
223 CAutoDiff diff;
224 if (git_diff_tree_to_tree(diff.GetPointer(), repo, parentTree, commitTree, nullptr) < 0)
225 return -1;
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)
234 return-1;
237 const git_diff_delta* lastDelta = nullptr;
238 int oldAction = 0;
239 size_t deltas = git_diff_num_deltas(diff);
240 for (size_t i = 0; i < deltas; ++i)
242 CAutoPatch patch;
243 if (git_patch_from_diff(patch.GetPointer(), diff, i) < 0)
244 return -1;
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))
254 // rename handling
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);
264 lastDelta = nullptr;
265 continue;
267 lastDelta = delta;
269 CString newname = CUnicodeUtils::GetUnicode(delta->new_file.path);
270 CTGitPath path;
271 if (delta->new_file.path == delta->old_file.path)
272 path.SetFromGit(newname, isDir != FALSE);
273 else
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"-";
287 else
289 size_t adds, dels;
290 if (git_patch_line_stats(nullptr, &adds, &dels, patch) < 0)
291 return -1;
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);
301 return 0;
304 git->CheckAndInitDll();
305 GIT_COMMIT commit = { 0 };
306 GIT_COMMIT_LIST list;
307 GIT_HASH parent;
309 CAutoLocker lock(g_Git.m_critGitDllSec);
313 if (git_get_commit_from_hash(&commit, static_cast<const unsigned char*>(m_CommitHash)))
314 return -1;
316 catch (char *)
318 return -1;
321 int i = 0;
322 git_get_commit_first_parent(&commit, &list);
323 bool isRoot = (list == nullptr);
325 while (git_get_commit_next_parent(&list, parent) == 0 || isRoot)
327 GIT_FILE file = 0;
328 int count = 0;
332 if (isRoot)
333 git_root_diff(git->GetGitDiff(), static_cast<const unsigned char*>(m_CommitHash), &file, &count, 1);
334 else
335 git_do_diff(git->GetGitDiff(), parent, commit.m_hash, &file, &count, 1);
337 catch (char*)
339 git_free_commit(&commit);
340 return -1;
342 isRoot = false;
344 CTGitPath path;
345 CString strnewname;
346 CString stroldname;
348 for (int j = 0; j < count; ++j)
350 path.Reset();
351 char* newname;
352 char* oldname;
354 strnewname.Empty();
355 stroldname.Empty();
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);
363 else
365 CGit::StringAppend(&stroldname, (BYTE*)oldname, CP_UTF8);
366 path.SetFromGit(strnewname, &stroldname, &isDir);
368 path.ParserAction((BYTE)status);
369 path.m_ParentNo = i;
371 m_Action |= path.m_Action;
373 if (isBin)
375 path.m_StatAdd = L"-";
376 path.m_StatDel = L"-";
378 else
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());
386 ++i;
389 InterlockedExchange(&m_IsUpdateing, FALSE);
390 InterlockedExchange(&m_IsFull, TRUE);
391 if (m_GitCommit.m_pGitCommit != commit.m_pGitCommit)
392 git_free_commit(&commit);
394 return 0;
397 int GitRevLoglist::GetRefLog(const CString& ref, std::vector<GitRevLoglist>& refloglist, CString& error)
399 refloglist.clear();
400 if (g_Git.m_IsUseLibGit2)
402 CAutoRepository repo(g_Git.GetGitRepository());
403 if (!repo)
405 error = g_Git.GetLibGit2LastErr();
406 return -1;
409 CAutoReflog reflog;
410 if (git_reflog_read(reflog.GetPointer(), repo, CUnicodeUtils::GetUTF8(ref)) < 0)
412 error = g_Git.GetLibGit2LastErr();
413 return -1;
416 for (size_t i = 0; i < git_reflog_entrycount(reflog); ++i)
418 const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i);
419 if (!entry)
420 continue;
422 GitRevLoglist rev;
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": ");
430 if (message > 0)
432 rev.m_RefAction = one.Left(message);
433 rev.GetSubject() = one.Mid(message + 2);
435 else
436 rev.m_RefAction = one;
438 refloglist.push_back(rev);
440 return 0;
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;
450 GitRevLoglist rev;
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": ");
456 if (message > 0)
458 rev.m_RefAction = one.Left(message);
459 rev.GetSubject() = one.Mid(message + 2).TrimRight(L'\n');
461 else
462 rev.m_RefAction = one.TrimRight(L'\n');
463 vector->push_back(rev);
465 return 0;
466 }, &tmp);
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);
474 return 0;
477 CString dotGitDir;
478 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, dotGitDir))
480 error = L".git directory not found";
481 return -1;
484 error.Empty();
485 // git.exe would fail with "branch not known"
486 if (!PathFileExists(dotGitDir + ref))
487 return 0;
489 CString cmd, out;
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))
492 return -1;
494 int i = 0;
495 CString prefix = ref + L"@{";
496 int pos = 0;
497 while (pos >= 0)
499 CString one = out.Tokenize(L"\n", pos);
500 int refPos = one.Find(L' ');
501 if (refPos < 0)
502 continue;
504 GitRevLoglist rev;
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)
509 continue;
511 int spacePos = one.Find(L' ', prefixPos + prefix.GetLength() + 1);
512 if (spacePos < 0)
513 continue;
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);
518 if (action > 0)
520 action += 2;
521 int message = one.Find(L": ", action);
522 if (message > 0)
524 rev.m_RefAction = one.Mid(action + 1, message - action - 1);
525 rev.GetSubject() = one.Right(one.GetLength() - message - 2);
527 else
528 rev.m_RefAction = one.Mid(action + 1);
531 refloglist.push_back(rev);
533 return 0;