Update some images for the documentation
[TortoiseGit.git] / src / Git / GitRevLoglist.cpp
blob11fb4d7f51078e2e305634d6aac23974b6508add
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 "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, (const git_oid*)m_CommitHash.m_hash) < 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, m_CommitHash.m_hash))
130 return -1;
132 catch (char *)
134 return -1;
137 int i = 0;
138 git_get_commit_first_parent(&commit, &list);
139 bool isRoot = git_commit_is_root(&commit) == 0;
140 while (git_get_commit_next_parent(&list, parent) == 0 || isRoot)
142 GIT_FILE file = 0;
143 int count = 0;
146 if (isRoot)
147 git_root_diff(git->GetGitSimpleListDiff(), commit.m_hash, &file, &count, 0);
148 else
149 git_do_diff(git->GetGitSimpleListDiff(), parent, commit.m_hash, &file, &count, 0);
151 catch (char *)
153 return -1;
156 isRoot = false;
158 for (int j = 0; j < count; ++j)
160 char* newname;
161 char* oldname;
162 int status, isBin, inc, dec, isDir;
166 git_get_diff_file(git->GetGitSimpleListDiff(), file, j, &newname, &oldname, &isDir, &status, &isBin, &inc, &dec);
168 catch (char *)
170 return -1;
173 m_SimpleFileList.push_back(CUnicodeUtils::GetUnicode(newname));
176 git_diff_flush(git->GetGitSimpleListDiff());
177 ++i;
180 std::sort(m_SimpleFileList.begin(), m_SimpleFileList.end());
181 m_SimpleFileList.erase(std::unique(m_SimpleFileList.begin(), m_SimpleFileList.end()), m_SimpleFileList.end());
183 InterlockedExchange(&m_IsUpdateing, FALSE);
184 InterlockedExchange(&m_IsSimpleListReady, TRUE);
185 if (m_GitCommit.m_pGitCommit != commit.m_pGitCommit)
186 git_free_commit(&commit);
188 return 0;
191 int GitRevLoglist::SafeFetchFullInfo(CGit* git)
193 if (InterlockedExchange(&m_IsUpdateing, TRUE) == TRUE)
194 return 0;
196 m_Files.Clear();
197 if (git->UsingLibGit2(CGit::GIT_CMD_LOGLISTDIFF))
199 CAutoRepository repo(git->GetGitRepository());
200 if (!repo)
201 return -1;
202 CAutoCommit commit;
203 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid*)m_CommitHash.m_hash) < 0)
204 return -1;
206 CAutoTree commitTree;
207 if (git_commit_tree(commitTree.GetPointer(), commit) < 0)
208 return -1;
210 bool isRoot = git_commit_parentcount(commit) == 0;
211 for (unsigned int parentId = 0; isRoot || parentId < git_commit_parentcount(commit); ++parentId)
213 CAutoTree parentTree;
214 if (!isRoot)
216 CAutoCommit parentCommit;
217 if (git_commit_parent(parentCommit.GetPointer(), commit, parentId) < 0)
218 return -1;
220 if (git_commit_tree(parentTree.GetPointer(), parentCommit) < 0)
221 return -1;
223 isRoot = false;
225 CAutoDiff diff;
226 if (git_diff_tree_to_tree(diff.GetPointer(), repo, parentTree, commitTree, nullptr) < 0)
227 return -1;
229 if (git_diff_find_similar(diff, nullptr) < 0)
230 return-1;
232 const git_diff_delta* lastDelta = nullptr;
233 int oldAction = 0;
234 size_t deltas = git_diff_num_deltas(diff);
235 for (size_t i = 0; i < deltas; ++i)
237 CAutoPatch patch;
238 if (git_patch_from_diff(patch.GetPointer(), diff, i) < 0)
239 return -1;
241 const git_diff_delta* delta = git_patch_get_delta(patch);
243 int isDir = (delta->new_file.mode & S_IFDIR) == S_IFDIR;
244 if (delta->status == GIT_DELTA_DELETED)
245 isDir = (delta->old_file.mode & S_IFDIR) == S_IFDIR;
247 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))
249 // rename handling
250 CTGitPath path = m_Files[m_Files.GetCount() - 1];
251 m_Files.RemoveItem(path);
252 if (path.IsDirectory() && !isDir)
253 path.UnsetDirectoryStatus();
254 path.m_StatAdd = L"-";
255 path.m_StatDel = L"-";
256 path.m_Action = CTGitPath::LOGACTIONS_MODIFIED;
257 m_Action = oldAction | CTGitPath::LOGACTIONS_MODIFIED;
258 m_Files.AddPath(path);
259 lastDelta = nullptr;
260 continue;
262 lastDelta = delta;
264 CString newname = CUnicodeUtils::GetUnicode(delta->new_file.path);
265 CTGitPath path;
266 if (delta->new_file.path == delta->old_file.path)
267 path.SetFromGit(newname, isDir != FALSE);
268 else
270 CString oldname = CUnicodeUtils::GetUnicode(delta->old_file.path);
271 path.SetFromGit(newname, &oldname, &isDir);
273 oldAction = m_Action;
274 m_Action |= path.ParserAction(delta->status);
275 path.m_ParentNo = parentId;
277 if (delta->flags & GIT_DIFF_FLAG_BINARY)
279 path.m_StatAdd = L"-";
280 path.m_StatDel = L"-";
282 else
284 size_t adds, dels;
285 if (git_patch_line_stats(nullptr, &adds, &dels, patch) < 0)
286 return -1;
287 path.m_StatAdd.Format(L"%d", adds);
288 path.m_StatDel.Format(L"%d", dels);
290 m_Files.AddPath(path);
294 InterlockedExchange(&m_IsUpdateing, FALSE);
295 InterlockedExchange(&m_IsFull, TRUE);
296 return 0;
299 git->CheckAndInitDll();
300 GIT_COMMIT commit = { 0 };
301 GIT_COMMIT_LIST list;
302 GIT_HASH parent;
304 CAutoLocker lock(g_Git.m_critGitDllSec);
308 if (git_get_commit_from_hash(&commit, m_CommitHash.m_hash))
309 return -1;
311 catch (char *)
313 return -1;
316 int i = 0;
317 git_get_commit_first_parent(&commit, &list);
318 bool isRoot = (list == nullptr);
320 while (git_get_commit_next_parent(&list, parent) == 0 || isRoot)
322 GIT_FILE file = 0;
323 int count = 0;
327 if (isRoot)
328 git_root_diff(git->GetGitDiff(), m_CommitHash.m_hash, &file, &count, 1);
329 else
330 git_do_diff(git->GetGitDiff(), parent, commit.m_hash, &file, &count, 1);
332 catch (char*)
334 git_free_commit(&commit);
335 return -1;
337 isRoot = false;
339 CTGitPath path;
340 CString strnewname;
341 CString stroldname;
343 for (int j = 0; j < count; ++j)
345 path.Reset();
346 char* newname;
347 char* oldname;
349 strnewname.Empty();
350 stroldname.Empty();
352 int status, isBin, inc, dec, isDir;
353 git_get_diff_file(git->GetGitDiff(), file, j, &newname, &oldname, &isDir, &status, &isBin, &inc, &dec);
355 git->StringAppend(&strnewname, (BYTE*)newname, CP_UTF8);
356 if (strcmp(newname, oldname) == 0)
357 path.SetFromGit(strnewname, isDir != FALSE);
358 else
360 git->StringAppend(&stroldname, (BYTE*)oldname, CP_UTF8);
361 path.SetFromGit(strnewname, &stroldname, &isDir);
363 path.ParserAction((BYTE)status);
364 path.m_ParentNo = i;
366 m_Action |= path.m_Action;
368 if (isBin)
370 path.m_StatAdd = L"-";
371 path.m_StatDel = L"-";
373 else
375 path.m_StatAdd.Format(L"%d", inc);
376 path.m_StatDel.Format(L"%d", dec);
378 m_Files.AddPath(path);
380 git_diff_flush(git->GetGitDiff());
381 ++i;
384 InterlockedExchange(&m_IsUpdateing, FALSE);
385 InterlockedExchange(&m_IsFull, TRUE);
386 if (m_GitCommit.m_pGitCommit != commit.m_pGitCommit)
387 git_free_commit(&commit);
389 return 0;
392 int GitRevLoglist::GetRefLog(const CString& ref, std::vector<GitRevLoglist>& refloglist, CString& error)
394 refloglist.clear();
395 if (g_Git.m_IsUseLibGit2)
397 CAutoRepository repo(g_Git.GetGitRepository());
398 if (!repo)
400 error = g_Git.GetLibGit2LastErr();
401 return -1;
404 CAutoReflog reflog;
405 if (git_reflog_read(reflog.GetPointer(), repo, CUnicodeUtils::GetUTF8(ref)) < 0)
407 error = g_Git.GetLibGit2LastErr();
408 return -1;
411 for (size_t i = 0; i < git_reflog_entrycount(reflog); ++i)
413 const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i);
414 if (!entry)
415 continue;
417 GitRevLoglist rev;
418 rev.m_CommitHash = git_reflog_entry_id_new(entry)->id;
419 rev.m_Ref.Format(L"%s@{%d}", (LPCTSTR)ref, i);
420 rev.GetCommitterDate() = CTime(git_reflog_entry_committer(entry)->when.time);
421 if (git_reflog_entry_message(entry) != nullptr)
423 CString one = CUnicodeUtils::GetUnicode(git_reflog_entry_message(entry));
424 int message = one.Find(L": ");
425 if (message > 0)
427 rev.m_RefAction = one.Left(message);
428 rev.GetSubject() = one.Mid(message + 2);
430 else
431 rev.m_RefAction = one;
433 refloglist.push_back(rev);
435 return 0;
437 else if (g_Git.m_IsUseGitDLL)
439 g_Git.CheckAndInitDll();
440 std::vector<GitRevLoglist> tmp;
441 // no error checking, because the only error which could occour is file not found
442 git_for_each_reflog_ent(CUnicodeUtils::GetUTF8(ref), [](unsigned char * /*osha1*/, unsigned char *nsha1, const char * /*name*/, unsigned long time, int /*sz*/, const char *msg, void *data)
444 std::vector<GitRevLoglist>* vector = (std::vector<GitRevLoglist>*)data;
445 GitRevLoglist rev;
446 rev.m_CommitHash = (const unsigned char*)nsha1;
447 rev.GetCommitterDate() = CTime(time);
449 CString one = CUnicodeUtils::GetUnicode(msg);
450 int message = one.Find(L": ");
451 if (message > 0)
453 rev.m_RefAction = one.Left(message);
454 rev.GetSubject() = one.Mid(message + 2).TrimRight(L'\n');
456 else
457 rev.m_RefAction = one.TrimRight(L'\n');
458 vector->push_back(rev);
460 return 0;
461 }, &tmp);
463 for (size_t i = tmp.size(), id = 0; i > 0; --i, ++id)
465 GitRevLoglist rev = tmp[i - 1];
466 rev.m_Ref.Format(L"%s@{%ld}", (LPCTSTR)ref, id);
467 refloglist.push_back(rev);
469 return 0;
472 CString dotGitDir;
473 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, dotGitDir))
475 error = L".git directory not found";
476 return -1;
479 error.Empty();
480 // git.exe would fail with "branch not known"
481 if (!PathFileExists(dotGitDir + ref))
482 return 0;
484 CString cmd, out;
485 cmd.Format(L"git.exe reflog show --pretty=\"%%H %%gD: %%gs\" --date=raw %s", (LPCTSTR)ref);
486 if (g_Git.Run(cmd, &out, &error, CP_UTF8))
487 return -1;
489 int i = 0;
490 CString prefix = ref + L"@{";
491 int pos = 0;
492 while (pos >= 0)
494 CString one = out.Tokenize(L"\n", pos);
495 int refPos = one.Find(L' ');
496 if (refPos < 0)
497 continue;
499 GitRevLoglist rev;
500 rev.m_CommitHash = one.Left(refPos);
501 rev.m_Ref.Format(L"%s@{%d}", (LPCTSTR)ref, i++);
502 int prefixPos = one.Find(prefix, refPos + 1);
503 if (prefixPos != refPos + 1)
504 continue;
506 int spacePos = one.Find(L' ', prefixPos + prefix.GetLength() + 1);
507 if (spacePos < 0)
508 continue;
510 CString timeStr = one.Mid(prefixPos + prefix.GetLength(), spacePos - prefixPos - prefix.GetLength());
511 rev.GetCommitterDate() = CTime(_wtoi(timeStr));
512 int action = one.Find(L"}: ", spacePos + 1);
513 if (action > 0)
515 action += 2;
516 int message = one.Find(L": ", action);
517 if (message > 0)
519 rev.m_RefAction = one.Mid(action + 1, message - action - 1);
520 rev.GetSubject() = one.Right(one.GetLength() - message - 2);
522 else
523 rev.m_RefAction = one.Mid(action + 1);
526 refloglist.push_back(rev);
528 return 0;