Prefer to use VS2013 for compiling and testing on AppVeyor
[TortoiseGit.git] / src / Git / GitRevLoglist.cpp
blob4ef031124b932d50f23089c12a4ed3779b80d348
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2015 - 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"
26 typedef CComCritSecLock<CComCriticalSection> CAutoLocker;
28 GitRevLoglist::GitRevLoglist(void)
30 GitRev();
31 m_Action = 0;
32 m_RebaseAction = 0;
33 m_IsFull = FALSE;
34 m_IsUpdateing = FALSE;
35 m_IsCommitParsed = FALSE;
36 m_IsDiffFiles = FALSE;
37 m_CallDiffAsync = nullptr;
38 m_IsSimpleListReady = FALSE;
39 m_Mark = 0;
41 SecureZeroMemory(&m_GitCommit, sizeof(GIT_COMMIT));
44 GitRevLoglist::~GitRevLoglist(void)
46 if (!m_IsCommitParsed && m_GitCommit.m_pGitCommit)
47 git_free_commit(&m_GitCommit);
50 void GitRevLoglist::Clear()
52 GitRev::Clear();
53 m_Action = 0;
54 m_Files.Clear();
55 m_UnRevFiles.Clear();
56 m_Ref.Empty();
57 m_RefAction.Empty();
58 m_Mark = 0;
59 m_IsFull = FALSE;
60 m_IsUpdateing = FALSE;
61 m_IsCommitParsed = FALSE;
62 m_IsDiffFiles = FALSE;
63 m_CallDiffAsync = nullptr;
64 m_IsSimpleListReady = FALSE;
68 int GitRevLoglist::SafeGetSimpleList(CGit* git)
70 if (InterlockedExchange(&m_IsUpdateing, TRUE) == TRUE)
71 return 0;
73 m_SimpleFileList.clear();
74 if (git->UsingLibGit2(CGit::GIT_CMD_LOGLISTDIFF))
76 CAutoRepository repo(git->GetGitRepository());
77 if (!repo)
78 return -1;
79 CAutoCommit commit;
80 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid*)m_CommitHash.m_hash) < 0)
81 return -1;
83 CAutoTree commitTree;
84 if (git_commit_tree(commitTree.GetPointer(), commit) < 0)
85 return -1;
87 bool isRoot = git_commit_parentcount(commit) == 0;
88 for (unsigned int parentId = 0; isRoot || parentId < git_commit_parentcount(commit); ++parentId)
90 CAutoTree parentTree;
91 if (!isRoot)
93 CAutoCommit parentCommit;
94 if (git_commit_parent(parentCommit.GetPointer(), commit, parentId) < 0)
95 return -1;
97 if (git_commit_tree(parentTree.GetPointer(), parentCommit) < 0)
98 return -1;
100 isRoot = false;
102 CAutoDiff diff;
103 if (git_diff_tree_to_tree(diff.GetPointer(), repo, parentTree, commitTree, nullptr) < 0)
104 return -1;
106 size_t deltas = git_diff_num_deltas(diff);
107 for (size_t i = 0; i < deltas; ++i)
109 const git_diff_delta* delta = git_diff_get_delta(diff, i);
110 m_SimpleFileList.push_back(CUnicodeUtils::GetUnicode(delta->new_file.path));
114 InterlockedExchange(&m_IsUpdateing, FALSE);
115 InterlockedExchange(&m_IsSimpleListReady, TRUE);
116 return 0;
118 git->CheckAndInitDll();
119 GIT_COMMIT commit = { 0 };
120 GIT_COMMIT_LIST list;
121 GIT_HASH parent;
123 CAutoLocker lock(g_Git.m_critGitDllSec);
127 if (git_get_commit_from_hash(&commit, m_CommitHash.m_hash))
128 return -1;
130 catch (char *)
132 return -1;
135 int i = 0;
136 bool isRoot = m_ParentHash.empty();
137 git_get_commit_first_parent(&commit, &list);
138 while (git_get_commit_next_parent(&list, parent) == 0 || isRoot)
140 GIT_FILE file = 0;
141 int count = 0;
144 if (isRoot)
145 git_root_diff(git->GetGitSimpleListDiff(), commit.m_hash, &file, &count, 0);
146 else
147 git_do_diff(git->GetGitSimpleListDiff(), parent, commit.m_hash, &file, &count, 0);
149 catch (char *)
151 return -1;
154 isRoot = false;
156 for (int j = 0; j < count; ++j)
158 char* newname;
159 char* oldname;
160 int mode, IsBin, inc, dec;
164 git_get_diff_file(git->GetGitSimpleListDiff(), file, j, &newname, &oldname, &mode, &IsBin, &inc, &dec);
166 catch (char *)
168 return -1;
171 m_SimpleFileList.push_back(CUnicodeUtils::GetUnicode(newname));
174 git_diff_flush(git->GetGitSimpleListDiff());
175 ++i;
178 InterlockedExchange(&m_IsUpdateing, FALSE);
179 InterlockedExchange(&m_IsSimpleListReady, TRUE);
180 if (m_GitCommit.m_pGitCommit != commit.m_pGitCommit)
181 git_free_commit(&commit);
183 return 0;
186 int GitRevLoglist::SafeFetchFullInfo(CGit* git)
188 if (InterlockedExchange(&m_IsUpdateing, TRUE) == TRUE)
189 return 0;
191 m_Files.Clear();
192 if (git->UsingLibGit2(CGit::GIT_CMD_LOGLISTDIFF))
194 CAutoRepository repo(git->GetGitRepository());
195 if (!repo)
196 return -1;
197 CAutoCommit commit;
198 if (git_commit_lookup(commit.GetPointer(), repo, (const git_oid*)m_CommitHash.m_hash) < 0)
199 return -1;
201 CAutoTree commitTree;
202 if (git_commit_tree(commitTree.GetPointer(), commit) < 0)
203 return -1;
205 bool isRoot = git_commit_parentcount(commit) == 0;
206 for (unsigned int parentId = 0; isRoot || parentId < git_commit_parentcount(commit); ++parentId)
208 CAutoTree parentTree;
209 if (!isRoot)
211 CAutoCommit parentCommit;
212 if (git_commit_parent(parentCommit.GetPointer(), commit, parentId) < 0)
213 return -1;
215 if (git_commit_tree(parentTree.GetPointer(), parentCommit) < 0)
216 return -1;
218 isRoot = false;
220 CAutoDiff diff;
221 if (git_diff_tree_to_tree(diff.GetPointer(), repo, parentTree, commitTree, nullptr) < 0)
222 return -1;
224 if (git_diff_find_similar(diff, nullptr) < 0)
225 return-1;
227 const git_diff_delta* lastDelta = nullptr;
228 int oldAction = 0;
229 size_t deltas = git_diff_num_deltas(diff);
230 for (size_t i = 0; i < deltas; ++i)
232 CAutoPatch patch;
233 if (git_patch_from_diff(patch.GetPointer(), diff, i) < 0)
234 return -1;
236 const git_diff_delta* delta = git_patch_get_delta(patch);
238 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))
240 CTGitPath path = m_Files[m_Files.GetCount() - 1];
241 m_Files.RemoveItem(path);
242 path.m_StatAdd = _T("-");
243 path.m_StatDel = _T("-");
244 path.m_Action = CTGitPath::LOGACTIONS_MODIFIED;
245 m_Action = oldAction | CTGitPath::LOGACTIONS_MODIFIED;
246 m_Files.AddPath(path);
247 lastDelta = nullptr;
248 continue;
250 lastDelta = delta;
252 CString newname = CUnicodeUtils::GetUnicode(delta->new_file.path);
253 CTGitPath path;
254 if (delta->new_file.path == delta->old_file.path)
255 path.SetFromGit(newname);
256 else
258 CString oldname = CUnicodeUtils::GetUnicode(delta->old_file.path);
259 path.SetFromGit(newname, &oldname);
261 oldAction = m_Action;
262 m_Action |= path.ParserAction(delta->status);
263 path.m_ParentNo = parentId;
265 if (delta->flags & GIT_DIFF_FLAG_BINARY)
267 path.m_StatAdd = _T("-");
268 path.m_StatDel = _T("-");
270 else
272 size_t adds, dels;
273 if (git_patch_line_stats(nullptr, &adds, &dels, patch) < 0)
274 return -1;
275 path.m_StatAdd.Format(_T("%d"), adds);
276 path.m_StatDel.Format(_T("%d"), dels);
278 m_Files.AddPath(path);
282 InterlockedExchange(&m_IsUpdateing, FALSE);
283 InterlockedExchange(&m_IsFull, TRUE);
284 return 0;
287 git->CheckAndInitDll();
288 GIT_COMMIT commit = { 0 };
289 GIT_COMMIT_LIST list;
290 GIT_HASH parent;
292 CAutoLocker lock(g_Git.m_critGitDllSec);
296 if (git_get_commit_from_hash(&commit, m_CommitHash.m_hash))
297 return -1;
299 catch (char *)
301 return -1;
304 int i = 0;
305 git_get_commit_first_parent(&commit, &list);
306 bool isRoot = (list == nullptr);
308 while (git_get_commit_next_parent(&list, parent) == 0 || isRoot)
310 GIT_FILE file = 0;
311 int count = 0;
315 if (isRoot)
316 git_root_diff(git->GetGitDiff(), m_CommitHash.m_hash, &file, &count, 1);
317 else
318 git_do_diff(git->GetGitDiff(), parent, commit.m_hash, &file, &count, 1);
320 catch (char*)
322 git_free_commit(&commit);
323 return -1;
325 isRoot = false;
327 CTGitPath path;
328 CString strnewname;
329 CString stroldname;
331 for (int j = 0; j < count; ++j)
333 path.Reset();
334 char* newname;
335 char* oldname;
337 strnewname.Empty();
338 stroldname.Empty();
340 int mode, IsBin, inc, dec;
341 git_get_diff_file(git->GetGitDiff(), file, j, &newname, &oldname, &mode, &IsBin, &inc, &dec);
343 git->StringAppend(&strnewname, (BYTE*)newname, CP_UTF8);
344 if (newname == oldname)
345 path.SetFromGit(strnewname);
346 else
348 git->StringAppend(&stroldname, (BYTE*)oldname, CP_UTF8);
349 path.SetFromGit(strnewname, &stroldname);
351 path.ParserAction((BYTE)mode);
352 path.m_ParentNo = i;
354 m_Action |= path.m_Action;
356 if (IsBin)
358 path.m_StatAdd = _T("-");
359 path.m_StatDel = _T("-");
361 else
363 path.m_StatAdd.Format(_T("%d"), inc);
364 path.m_StatDel.Format(_T("%d"), dec);
366 m_Files.AddPath(path);
368 git_diff_flush(git->GetGitDiff());
369 ++i;
372 InterlockedExchange(&m_IsUpdateing, FALSE);
373 InterlockedExchange(&m_IsFull, TRUE);
374 if (m_GitCommit.m_pGitCommit != commit.m_pGitCommit)
375 git_free_commit(&commit);
377 return 0;
380 int GitRevLoglist::GetRefLog(const CString& ref, std::vector<GitRevLoglist>& refloglist, CString& error)
382 refloglist.clear();
383 if (g_Git.m_IsUseLibGit2)
385 CAutoRepository repo(g_Git.GetGitRepository());
386 if (!repo)
388 error = g_Git.GetLibGit2LastErr();
389 return -1;
392 CAutoReflog reflog;
393 if (git_reflog_read(reflog.GetPointer(), repo, CUnicodeUtils::GetUTF8(ref)) < 0)
395 error = g_Git.GetLibGit2LastErr();
396 return -1;
399 for (size_t i = 0; i < git_reflog_entrycount(reflog); ++i)
401 const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i);
402 if (!entry)
403 continue;
405 GitRevLoglist rev;
406 rev.m_CommitHash = (const unsigned char*)git_reflog_entry_id_new(entry)->id;
407 rev.m_Ref.Format(_T("%s@{%d}"), (LPCTSTR)ref, i);
408 rev.GetCommitterDate() = CTime(git_reflog_entry_committer(entry)->when.time);
409 if (git_reflog_entry_message(entry) != nullptr)
411 CString one = CUnicodeUtils::GetUnicode(git_reflog_entry_message(entry));
412 int message = one.Find(_T(": "), 0);
413 if (message > 0)
415 rev.m_RefAction = one.Left(message);
416 rev.GetSubject() = one.Mid(message + 2);
418 else
419 rev.m_RefAction = one;
421 refloglist.push_back(rev);
423 return 0;
425 else if (g_Git.m_IsUseGitDLL)
427 g_Git.CheckAndInitDll();
428 std::vector<GitRevLoglist> tmp;
429 // no error checking, because the only error which could occour is file not found
430 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)
432 std::vector<GitRevLoglist>* vector = (std::vector<GitRevLoglist>*)data;
433 GitRevLoglist rev;
434 rev.m_CommitHash = (const unsigned char*)nsha1;
435 rev.GetCommitterDate() = CTime(time);
437 CString one = CUnicodeUtils::GetUnicode(msg);
438 int message = one.Find(_T(": "), 0);
439 if (message > 0)
441 rev.m_RefAction = one.Left(message);
442 rev.GetSubject() = one.Mid(message + 2).TrimRight(L'\n');
444 else
445 rev.m_RefAction = one.TrimRight(L'\n');
446 vector->push_back(rev);
448 return 0;
449 }, &tmp);
451 for (size_t i = tmp.size(), id = 0; i > 0; --i, ++id)
453 GitRevLoglist rev = tmp[i - 1];
454 rev.m_Ref.Format(_T("%s@{%ld}"), (LPCTSTR)ref, id);
455 refloglist.push_back(rev);
457 return 0;
460 CString dotGitDir;
461 if (!GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, dotGitDir))
463 error = _T(".git directory not found");
464 return -1;
467 error.Empty();
468 // git.exe would fail with "branch not known"
469 if (!PathFileExists(dotGitDir + ref))
470 return 0;
472 CString cmd, out;
473 cmd.Format(_T("git.exe reflog show --pretty=\"%%H %%gD: %%gs\" --date=raw %s"), (LPCTSTR)ref);
474 if (g_Git.Run(cmd, &out, &error, CP_UTF8))
475 return -1;
477 int i = 0;
478 CString prefix = ref + _T("@{");
479 int pos = 0;
480 while (pos >= 0)
482 CString one = out.Tokenize(_T("\n"), pos);
483 int refPos = one.Find(_T(' '), 0);
484 if (refPos < 0)
485 continue;
487 GitRevLoglist rev;
488 rev.m_CommitHash = one.Left(refPos);
489 rev.m_Ref.Format(_T("%s@{%d}"), (LPCTSTR)ref, i++);
490 int prefixPos = one.Find(prefix, refPos + 1);
491 if (prefixPos != refPos + 1)
492 continue;
494 int spacePos = one.Find(_T(' '), prefixPos + prefix.GetLength() + 1);
495 if (spacePos < 0)
496 continue;
498 CString timeStr = one.Mid(prefixPos + prefix.GetLength(), spacePos - prefixPos - prefix.GetLength());
499 rev.GetCommitterDate() = CTime(_ttoi(timeStr));
500 int action = one.Find(_T("}: "), spacePos + 1);
501 if (action > 0)
503 action += 2;
504 int message = one.Find(_T(": "), action);
505 if (message > 0)
507 rev.m_RefAction = one.Mid(action + 1, message - action - 1);
508 rev.GetSubject() = one.Right(one.GetLength() - message - 2);
510 else
511 rev.m_RefAction = one.Mid(action + 1);
514 refloglist.push_back(rev);
516 return 0;