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.
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
, (const git_oid
*)m_CommitHash
.m_hash
) < 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
.m_hash
))
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
)
147 git_root_diff(git
->GetGitSimpleListDiff(), commit
.m_hash
, &file
, &count
, 0);
149 git_do_diff(git
->GetGitSimpleListDiff(), parent
, commit
.m_hash
, &file
, &count
, 0);
158 for (int j
= 0; j
< count
; ++j
)
162 int status
, isBin
, inc
, dec
, isDir
;
166 git_get_diff_file(git
->GetGitSimpleListDiff(), file
, j
, &newname
, &oldname
, &isDir
, &status
, &isBin
, &inc
, &dec
);
173 m_SimpleFileList
.push_back(CUnicodeUtils::GetUnicode(newname
));
176 git_diff_flush(git
->GetGitSimpleListDiff());
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
);
191 int GitRevLoglist::SafeFetchFullInfo(CGit
* git
)
193 if (InterlockedExchange(&m_IsUpdateing
, TRUE
) == TRUE
)
197 if (git
->UsingLibGit2(CGit::GIT_CMD_LOGLISTDIFF
))
199 CAutoRepository
repo(git
->GetGitRepository());
203 if (git_commit_lookup(commit
.GetPointer(), repo
, (const git_oid
*)m_CommitHash
.m_hash
) < 0)
206 CAutoTree commitTree
;
207 if (git_commit_tree(commitTree
.GetPointer(), commit
) < 0)
210 bool isRoot
= git_commit_parentcount(commit
) == 0;
211 for (unsigned int parentId
= 0; isRoot
|| parentId
< git_commit_parentcount(commit
); ++parentId
)
213 CAutoTree parentTree
;
216 CAutoCommit parentCommit
;
217 if (git_commit_parent(parentCommit
.GetPointer(), commit
, parentId
) < 0)
220 if (git_commit_tree(parentTree
.GetPointer(), parentCommit
) < 0)
226 if (git_diff_tree_to_tree(diff
.GetPointer(), repo
, parentTree
, commitTree
, nullptr) < 0)
229 if (git_diff_find_similar(diff
, nullptr) < 0)
232 const git_diff_delta
* lastDelta
= nullptr;
234 size_t deltas
= git_diff_num_deltas(diff
);
235 for (size_t i
= 0; i
< deltas
; ++i
)
238 if (git_patch_from_diff(patch
.GetPointer(), diff
, i
) < 0)
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
))
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
);
264 CString newname
= CUnicodeUtils::GetUnicode(delta
->new_file
.path
);
266 if (delta
->new_file
.path
== delta
->old_file
.path
)
267 path
.SetFromGit(newname
, isDir
!= FALSE
);
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
"-";
285 if (git_patch_line_stats(nullptr, &adds
, &dels
, patch
) < 0)
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
);
299 git
->CheckAndInitDll();
300 GIT_COMMIT commit
= { 0 };
301 GIT_COMMIT_LIST list
;
304 CAutoLocker
lock(g_Git
.m_critGitDllSec
);
308 if (git_get_commit_from_hash(&commit
, m_CommitHash
.m_hash
))
317 git_get_commit_first_parent(&commit
, &list
);
318 bool isRoot
= (list
== nullptr);
320 while (git_get_commit_next_parent(&list
, parent
) == 0 || isRoot
)
328 git_root_diff(git
->GetGitDiff(), m_CommitHash
.m_hash
, &file
, &count
, 1);
330 git_do_diff(git
->GetGitDiff(), parent
, commit
.m_hash
, &file
, &count
, 1);
334 git_free_commit(&commit
);
343 for (int j
= 0; j
< count
; ++j
)
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
);
360 git
->StringAppend(&stroldname
, (BYTE
*)oldname
, CP_UTF8
);
361 path
.SetFromGit(strnewname
, &stroldname
, &isDir
);
363 path
.ParserAction((BYTE
)status
);
366 m_Action
|= path
.m_Action
;
370 path
.m_StatAdd
= L
"-";
371 path
.m_StatDel
= L
"-";
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());
384 InterlockedExchange(&m_IsUpdateing
, FALSE
);
385 InterlockedExchange(&m_IsFull
, TRUE
);
386 if (m_GitCommit
.m_pGitCommit
!= commit
.m_pGitCommit
)
387 git_free_commit(&commit
);
392 int GitRevLoglist::GetRefLog(const CString
& ref
, std::vector
<GitRevLoglist
>& refloglist
, CString
& error
)
395 if (g_Git
.m_IsUseLibGit2
)
397 CAutoRepository
repo(g_Git
.GetGitRepository());
400 error
= g_Git
.GetLibGit2LastErr();
405 if (git_reflog_read(reflog
.GetPointer(), repo
, CUnicodeUtils::GetUTF8(ref
)) < 0)
407 error
= g_Git
.GetLibGit2LastErr();
411 for (size_t i
= 0; i
< git_reflog_entrycount(reflog
); ++i
)
413 const git_reflog_entry
*entry
= git_reflog_entry_byindex(reflog
, i
);
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
": ");
427 rev
.m_RefAction
= one
.Left(message
);
428 rev
.GetSubject() = one
.Mid(message
+ 2);
431 rev
.m_RefAction
= one
;
433 refloglist
.push_back(rev
);
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
;
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
": ");
453 rev
.m_RefAction
= one
.Left(message
);
454 rev
.GetSubject() = one
.Mid(message
+ 2).TrimRight(L
'\n');
457 rev
.m_RefAction
= one
.TrimRight(L
'\n');
458 vector
->push_back(rev
);
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
);
473 if (!GitAdminDir::GetAdminDirPath(g_Git
.m_CurrentDir
, dotGitDir
))
475 error
= L
".git directory not found";
480 // git.exe would fail with "branch not known"
481 if (!PathFileExists(dotGitDir
+ ref
))
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
))
490 CString prefix
= ref
+ L
"@{";
494 CString one
= out
.Tokenize(L
"\n", pos
);
495 int refPos
= one
.Find(L
' ');
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)
506 int spacePos
= one
.Find(L
' ', prefixPos
+ prefix
.GetLength() + 1);
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);
516 int message
= one
.Find(L
": ", action
);
519 rev
.m_RefAction
= one
.Mid(action
+ 1, message
- action
- 1);
520 rev
.GetSubject() = one
.Right(one
.GetLength() - message
- 2);
523 rev
.m_RefAction
= one
.Mid(action
+ 1);
526 refloglist
.push_back(rev
);