1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2023 - TortoiseGit
4 // Copyright (C) 2005-2007 Marco Costalba
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // GitLogList.cpp : implementation file
23 #include "TortoiseProc.h"
24 #include "GitLogList.h"
28 #include "GitProgressDlg.h"
29 #include "ProgressDlg.h"
30 #include "SysProgressDlg.h"
32 #include "MessageBox.h"
35 #include "StringUtils.h"
36 #include "UnicodeUtils.h"
38 #include "CommitDlg.h"
39 #include "RebaseDlg.h"
40 #include "CommitIsOnRefsDlg.h"
42 #include "../TGitCache/CacheInterface.h"
44 IMPLEMENT_DYNAMIC(CGitLogList
, CHintCtrl
<CListCtrl
>)
46 static bool GetFirstEntryStartingWith(const STRING_VECTOR
& heystack
, const CString
& needle
, CString
& result
)
48 auto it
= std::find_if(heystack
.cbegin(), heystack
.cend(), [&needle
](const CString
& entry
) { return CStringUtils::StartsWith(entry
, needle
); });
49 if (it
== heystack
.cend())
55 int CGitLogList::RevertSelectedCommits(int parent
)
57 CSysProgressDlg progress
;
60 if (CMessageBox::Show(GetParentHWND(), IDS_REVERTCOMMITS
, IDS_APPNAME
, MB_YESNO
| MB_ICONQUESTION
| MB_DEFBUTTON2
) == IDNO
)
64 if(!g_Git
.CheckCleanWorkTree())
65 CMessageBox::Show(nullptr, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
68 if (this->GetSelectedCount() > 1)
70 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT
)));
71 progress
.SetTime(true);
72 progress
.ShowModeless(this);
75 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
77 POSITION pos
= GetFirstSelectedItemPosition();
81 GitAdminDir::GetWorktreeAdminDirPath(g_Git
.m_CurrentDir
, dotGitPath
);
84 const int index
= GetNextSelectedItem(pos
);
85 GitRev
* r1
= m_arShownList
.SafeGetAt(index
);
87 if (progress
.IsVisible())
89 progress
.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT
, static_cast<LPCWSTR
>(r1
->m_CommitHash
.ToString()));
90 progress
.FormatNonPathLine(2, L
"%s", static_cast<LPCWSTR
>(r1
->GetSubject()));
91 progress
.SetProgress(i
, this->GetSelectedCount());
95 if(r1
->m_CommitHash
.IsEmpty())
98 if (g_Git
.GitRevert(parent
, r1
->m_CommitHash
))
101 str
.LoadString(IDS_SVNACTION_FAILEDREVERT
);
102 str
= g_Git
.GetGitLastErr(str
, CGit::GIT_CMD_REVERT
);
103 if( GetSelectedCount() == 1)
104 CMessageBox::Show(GetParentHWND(), str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
105 else if (CMessageBox::Show(GetParentHWND(), str
, L
"TortoiseGit", 2, IDI_ERROR
, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
110 if (!mergeMsg
.IsEmpty())
112 CGit::LoadTextFile(dotGitPath
+ L
"MERGE_MSG", mergeMsg
);
116 if (progress
.HasUserCancelled())
119 if (!mergeMsg
.IsEmpty())
120 CStringUtils::WriteStringToTextFile(dotGitPath
+ L
"MERGE_MSG", mergeMsg
);
123 int CGitLogList::CherryPickFrom(CString from
, CString to
)
125 CLogDataVector
logs(&m_LogCache
);
127 range
.Format(L
"%s..%s", static_cast<LPCWSTR
>(from
), static_cast<LPCWSTR
>(to
));
128 if (logs
.ParserFromLog(nullptr, 0, 0, &range
))
134 CSysProgressDlg progress
;
135 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK
)));
136 progress
.SetTime(true);
137 progress
.ShowModeless(this);
139 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
141 for (int i
= static_cast<int>(logs
.size()) - 1; i
>= 0; --i
)
143 if (progress
.IsVisible())
145 progress
.FormatNonPathLine(1, IDS_PROC_PICK
, static_cast<LPCWSTR
>(logs
.GetGitRevAt(i
).m_CommitHash
.ToString()));
146 progress
.FormatNonPathLine(2, L
"%s", static_cast<LPCWSTR
>(logs
.GetGitRevAt(i
).GetSubject()));
147 progress
.SetProgress64(logs
.size() - i
, logs
.size());
149 if (progress
.HasUserCancelled())
150 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED
))));
152 cmd
.Format(L
"git.exe cherry-pick %s", static_cast<LPCWSTR
>(logs
.GetGitRevAt(i
).m_CommitHash
.ToString()));
153 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
154 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED
)) + L
":\r\n\r\n" + out
));
160 void CGitLogList::ContextMenuAction(int cmd
, int FirstSelect
, int LastSelect
, CMenu
* popmenu
, const MAP_HASH_NAME
& hashMap
)
162 POSITION pos
= GetFirstSelectedItemPosition();
163 const int indexNext
= GetNextSelectedItem(pos
);
167 GitRevLoglist
* pSelLogEntry
= m_arShownList
.SafeGetAt(indexNext
);
169 bool bShiftPressed
= !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000);
171 theApp
.DoWaitCursor(1);
176 CTGitPathList pathlist
;
177 pathlist
.AddPath(this->m_Path
);
178 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\SelectFilesForCommit", TRUE
));
180 CAppUtils::Commit(GetParentHWND(), CString(), false, str
,
181 pathlist
, bSelectFilesForCommit
);
183 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
,0);
188 if (CAppUtils::MergeAbort(GetParentHWND()))
189 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
, 0);
192 case ID_GNUDIFF1
: // compare with WC, unified
194 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
195 if(!r1
->m_CommitHash
.IsEmpty())
198 bool bMerge
= false, bCombine
= false;
201 if( (cmd
&0xFFFF) == 0xFFFF)
203 else if((cmd
&0xFFFF) == 0xFFFE)
205 else if ((cmd
& 0xFFFF) == 0xFFFD)
207 CString tempfile
= GetTempFile();
208 if (tempfile
.IsEmpty())
210 MessageBox(L
"Could not create temp file.", L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
213 CString gitcmd
= L
"git.exe diff-tree --cc " + r1
->m_CommitHash
.ToString();
215 if (g_Git
.RunLogFile(gitcmd
, tempfile
, &lastErr
))
217 MessageBox(lastErr
, L
"TortoiseGit", MB_ICONERROR
);
223 CStdioFile
file(tempfile
, CFile::typeText
| CFile::modeRead
| CFile::shareDenyWrite
);
225 bool isHash
= file
.ReadString(strLine
) && r1
->m_CommitHash
.ToString() == strLine
;
226 bool more
= isHash
&& file
.ReadString(strLine
) && !strLine
.IsEmpty();
229 CMessageBox::Show(GetParentHWND(), IDS_NOCHANGEAFTERMERGE
, IDS_APPNAME
, MB_OK
);
233 catch (CFileException
* e
)
238 CAppUtils::StartUnifiedDiffViewer(tempfile
, r1
->m_CommitHash
.ToString());
245 if (static_cast<size_t>(cmd
) > r1
->m_ParentHash
.size())
248 str
.Format(IDS_PROC_NOPARENT
, cmd
);
249 MessageBox(str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
255 hash2
= r1
->m_ParentHash
[cmd
-1].ToString();
258 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
259 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path
, hash2
, m_Path
, r1
->m_CommitHash
.ToString(), bShiftPressed
, false, false, bMerge
, bCombine
);
262 CString path
= m_Path
.GetGitPathString();
263 // start with 1 (0 = working copy changes)
264 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
266 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
267 auto filesWrapper
= first
->GetFiles(nullptr);
268 auto& list
= filesWrapper
.m_files
;
269 const CTGitPath
* file
= list
.LookForGitPath(path
);
270 if (file
&& !file
->GetGitOldPathString().IsEmpty())
271 path
= file
->GetGitOldPathString();
273 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(path
), hash2
, CTGitPath(path
), r1
->m_CommitHash
.ToString(), bShiftPressed
, false, false, bMerge
, bCombine
);
277 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path
, L
"HEAD", m_Path
, GitRev::GetWorkingCopy(), bShiftPressed
);
281 case ID_GNUDIFF2
: // compare two revisions, unified
283 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
284 GitRev
* r2
= m_arShownList
.SafeGetAt(LastSelect
);
285 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
286 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path
, r2
->m_CommitHash
.ToString(), m_Path
, r1
->m_CommitHash
.ToString(), bShiftPressed
);
289 CString path
= m_Path
.GetGitPathString();
290 // start with 1 (0 = working copy changes)
291 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
293 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
294 auto filesWrapper
= first
->GetFiles(nullptr);
295 auto& list
= filesWrapper
.m_files
;
296 const CTGitPath
* file
= list
.LookForGitPath(path
);
297 if (file
&& !file
->GetGitOldPathString().IsEmpty())
298 path
= file
->GetGitOldPathString();
300 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(path
), r2
->m_CommitHash
.ToString(), CTGitPath(path
), r1
->m_CommitHash
.ToString(), bShiftPressed
);
305 case ID_COMPARETWO
: // compare two revisions
307 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
308 GitRev
* r2
= m_arShownList
.SafeGetAt(LastSelect
);
309 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
310 CGitDiff::DiffCommit(GetParentHWND(), m_Path
, r1
, r2
, bShiftPressed
);
313 CString path1
= m_Path
.GetGitPathString();
314 // start with 1 (0 = working copy changes)
315 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
317 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
318 auto filesWrapper
= first
->GetFiles(nullptr);
319 auto& list
= filesWrapper
.m_files
;
320 const CTGitPath
* file
= list
.LookForGitPath(path1
);
321 if (file
&& !file
->GetGitOldPathString().IsEmpty())
322 path1
= file
->GetGitOldPathString();
324 CString path2
= path1
;
325 for (int i
= FirstSelect
; i
< LastSelect
; ++i
)
327 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
328 auto filesWrapper
= first
->GetFiles(nullptr);
329 auto& list
= filesWrapper
.m_files
;
330 const CTGitPath
* file
= list
.LookForGitPath(path2
);
331 if (file
&& !file
->GetGitOldPathString().IsEmpty())
332 path2
= file
->GetGitOldPathString();
334 CGitDiff::DiffCommit(GetParentHWND(), CTGitPath(path1
), CTGitPath(path2
), r1
, r2
, bShiftPressed
);
340 case ID_COMPARE
: // compare revision with WC
342 GitRevLoglist
* r1
= &m_wcRev
;
343 GitRevLoglist
* r2
= pSelLogEntry
;
345 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
346 CGitDiff::DiffCommit(GetParentHWND(), m_Path
, r1
, r2
, bShiftPressed
);
349 CString path1
= m_Path
.GetGitPathString();
350 // start with 1 (0 = working copy changes)
351 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
353 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
354 auto filesWrapper
= first
->GetFiles(nullptr);
355 auto& list
= filesWrapper
.m_files
;
356 const CTGitPath
* file
= list
.LookForGitPath(path1
);
357 if (file
&& !file
->GetGitOldPathString().IsEmpty())
358 path1
= file
->GetGitOldPathString();
360 CGitDiff::DiffCommit(GetParentHWND(), m_Path
, CTGitPath(path1
), r1
, r2
, bShiftPressed
);
363 //user clicked on the menu item "compare with working copy"
366 // GitDiff diff(this, m_hWnd, true);
367 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
368 // diff.SetHEADPeg(m_LogRevision);
369 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
372 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
376 case ID_COMPAREWITHPREVIOUS
:
378 if (pSelLogEntry
->m_ParentHash
.empty())
380 if (pSelLogEntry
->GetParentFromHash(pSelLogEntry
->m_CommitHash
))
381 MessageBox(pSelLogEntry
->GetLastErr(), L
"TortoiseGit", MB_ICONERROR
);
384 if (!pSelLogEntry
->m_ParentHash
.empty())
385 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
393 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
394 CGitDiff::DiffCommit(GetParentHWND(), m_Path
, pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString(), bShiftPressed
);
397 CString path1
= m_Path
.GetGitPathString();
398 // start with 1 (0 = working copy changes)
399 for (int i
= m_bShowWC
? 1 : 0; i
< indexNext
; ++i
)
401 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
402 auto filesWrapper
= first
->GetFiles(nullptr);
403 auto& list
= filesWrapper
.m_files
;
404 const CTGitPath
* file
= list
.LookForGitPath(path1
);
405 if (file
&& !file
->GetGitOldPathString().IsEmpty())
406 path1
= file
->GetGitOldPathString();
408 CString path2
= path1
;
409 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(indexNext
);
410 auto filesWrapper
= first
->GetFiles(nullptr);
411 auto& list
= filesWrapper
.m_files
;
412 const CTGitPath
* file
= list
.LookForGitPath(path2
);
413 if (file
&& !file
->GetGitOldPathString().IsEmpty())
414 path2
= file
->GetGitOldPathString();
416 CGitDiff::DiffCommit(GetParentHWND(), CTGitPath(path1
), CTGitPath(path2
), pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString(), bShiftPressed
);
421 CMessageBox::Show(GetParentHWND(), IDS_PROC_NOPREVIOUSVERSION
, IDS_APPNAME
, MB_OK
);
425 // GitDiff diff(this, m_hWnd, true);
426 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
427 // diff.SetHEADPeg(m_LogRevision);
428 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
431 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
434 case ID_COMPARETWOCOMMITCHANGES
:
436 auto pFirstEntry
= m_arShownList
.SafeGetAt(FirstSelect
);
437 auto pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
438 CString patch1
= GetTempFile();
439 CString patch2
= GetTempFile();
440 if (patch1
.IsEmpty() || patch2
.IsEmpty())
442 MessageBox(L
"Could not create temp file.", L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
445 if (CString err
; g_Git
.RunLogFile(L
"git.exe format-patch --stdout " + pFirstEntry
->m_CommitHash
.ToString() + L
"~1.." + pFirstEntry
->m_CommitHash
.ToString() + L
"", patch1
, &err
))
447 MessageBox(L
"Could not generate patch for commit " + pFirstEntry
->m_CommitHash
.ToString() + L
".\n" + err
, L
"TortoiseGit", MB_ICONERROR
);
450 if (CString err
; g_Git
.RunLogFile(L
"git.exe format-patch --stdout " + pLastEntry
->m_CommitHash
.ToString() + L
"~1.." + pLastEntry
->m_CommitHash
.ToString() + L
"", patch2
, &err
))
452 MessageBox(L
"Could not generate patch for commit " + pLastEntry
->m_CommitHash
.ToString() + L
".\n" + err
, L
"TortoiseGit", MB_ICONERROR
);
455 CAppUtils::StartExtDiff(patch1
, patch2
, pFirstEntry
->m_CommitHash
.ToString(), pLastEntry
->m_CommitHash
.ToString(), pFirstEntry
->m_CommitHash
.ToString() + L
".patch", pLastEntry
->m_CommitHash
.ToString() + L
".patch", pFirstEntry
->m_CommitHash
, pLastEntry
->m_CommitHash
, CAppUtils::DiffFlags());
458 case ID_LOG_VIEWRANGE
:
459 case ID_LOG_VIEWRANGE_REVERSE
:
460 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
:
462 GitRev
* pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
465 if ((cmd
& 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
)
469 if ((cmd
& 0xFFFF) == ID_LOG_VIEWRANGE_REVERSE
)
470 cmdline
.Format(L
"/command:log /path:\"%s\" /range:\"%s%s%s\"", static_cast<LPCWSTR
>(g_Git
.CombinePath(m_Path
)), static_cast<LPCWSTR
>(pSelLogEntry
->m_CommitHash
.ToString()), static_cast<LPCWSTR
>(sep
), static_cast<LPCWSTR
>(pLastEntry
->m_CommitHash
.ToString()));
472 cmdline
.Format(L
"/command:log /path:\"%s\" /range:\"%s%s%s\"", static_cast<LPCWSTR
>(g_Git
.CombinePath(m_Path
)), static_cast<LPCWSTR
>(pLastEntry
->m_CommitHash
.ToString()), static_cast<LPCWSTR
>(sep
), static_cast<LPCWSTR
>(pSelLogEntry
->m_CommitHash
.ToString()));
473 CAppUtils::RunTortoiseGitProc(cmdline
);
476 case ID_COPYCLIPBOARDFULL
:
477 case ID_COPYCLIPBOARDFULLNOPATHS
:
478 case ID_COPYCLIPBOARDHASH
:
479 case ID_COPYCLIPBOARDAUTHORSFULL
:
480 case ID_COPYCLIPBOARDAUTHORSNAME
:
481 case ID_COPYCLIPBOARDAUTHORSEMAIL
:
482 case ID_COPYCLIPBOARDSUBJECTS
:
483 case ID_COPYCLIPBOARDMESSAGES
:
485 CopySelectionToClipBoard(cmd
& 0xFFFF);
488 case ID_COPYCLIPBOARDBRANCHTAG
:
492 auto selectedBranch
= reinterpret_cast<const CString
*>(reinterpret_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
));
496 if (CStringUtils::StartsWith(*selectedBranch
, L
"refs/tags/"))
498 if (CStringUtils::EndsWith(*selectedBranch
, L
"^{}"))
499 sClipboard
= selectedBranch
->Mid(static_cast<int>(wcslen(L
"refs/tags/")), selectedBranch
->GetLength() - static_cast<int>(wcslen(L
"refs/tags/")) - static_cast<int>(wcslen(L
"^{}")));
501 sClipboard
= selectedBranch
->Mid(static_cast<int>(wcslen(L
"refs/tags/")));
504 sClipboard
= CGit::StripRefName(*selectedBranch
);
506 else if (auto refList
= hashMap
.find(pSelLogEntry
->m_CommitHash
); refList
!= hashMap
.cend())
508 for (const auto& ref
: refList
->second
)
510 if (CStringUtils::StartsWith(ref
, L
"refs/tags/") && CStringUtils::EndsWith(ref
, L
"^{}"))
511 sClipboard
+= ref
.Left(ref
.GetLength() - static_cast<int>(wcslen(L
"^{}")));
514 sClipboard
+= L
"\r\n";
517 CStringUtils::WriteAsciiStringToClipboard(sClipboard
, GetSafeHwnd());
522 CString str
=pSelLogEntry
->m_CommitHash
.ToString();
523 // try to get the tag
524 if (auto refList
= hashMap
.find(pSelLogEntry
->m_CommitHash
); refList
!= hashMap
.cend())
525 GetFirstEntryStartingWith(refList
->second
, L
"refs/tags/", str
);
526 CAppUtils::Export(GetParentHWND(), &str
, &m_Path
);
529 case ID_CREATE_BRANCH
:
532 auto branch
= popmenu
? reinterpret_cast<const CString
*>(static_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
& 0xFFFF)) : nullptr;
533 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
536 else if (auto refList
= hashMap
.find(pSelLogEntry
->m_CommitHash
); refList
!= hashMap
.cend()) // try to guess remote branch in order to enable tracking
537 GetFirstEntryStartingWith(refList
->second
, L
"refs/remotes/", str
);
539 CAppUtils::CreateBranchTag(GetParentHWND(), (cmd
& 0xFFFF) == ID_CREATE_TAG
, &str
);
542 m_pFindDialog
->RefreshList();
544 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
549 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
550 auto branch
= popmenu
? reinterpret_cast<const CString
*>(static_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
& 0xFFFF)) : nullptr;
553 else if (auto refList
= hashMap
.find(pSelLogEntry
->m_CommitHash
); refList
!= hashMap
.cend()) // try to guess remote branch in order to recommend good branch name and tracking
554 GetFirstEntryStartingWith(refList
->second
, L
"refs/remotes/", str
);
556 CAppUtils::Switch(GetParentHWND(), str
);
560 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
562 case ID_SWITCHBRANCH
:
565 auto branch
= popmenu
? reinterpret_cast<const CString
*>(static_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
)) : nullptr;
568 CString name
= *branch
;
569 CGit::GetShortName(*branch
, name
, L
"refs/heads/");
570 CAppUtils::PerformSwitch(GetParentHWND(), name
);
574 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
579 if (CAppUtils::GitReset(GetParentHWND(), pSelLogEntry
->m_CommitHash
.ToString()))
588 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK
);
591 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT
);
593 case ID_REBASE_SQUASH
:
594 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH
);
597 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP
);
599 case ID_COMBINE_COMMIT
:
603 CGitHash hashFirst
,hashLast
;
605 int headindex
=GetHeadIndex();
606 if(headindex
>=0) //incase show all branch, head is not the first commits.
608 head
.Format(L
"HEAD~%d", FirstSelect
- headindex
);
609 if (g_Git
.GetHash(hashFirst
, head
))
611 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of first selected revision."), L
"TortoiseGit", MB_ICONERROR
);
615 head
.Format(L
"HEAD~%d", LastSelect
- headindex
);
616 if (g_Git
.GetHash(hashLast
, head
))
618 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of last selected revision."), L
"TortoiseGit", MB_ICONERROR
);
623 GitRev
* pFirstEntry
= m_arShownList
.SafeGetAt(FirstSelect
);
624 GitRev
* pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
625 if(pFirstEntry
->m_CommitHash
!= hashFirst
|| pLastEntry
->m_CommitHash
!= hashLast
)
627 CMessageBox::Show(GetParentHWND(), IDS_PROC_CANNOTCOMBINE
, IDS_APPNAME
, MB_OK
| MB_ICONEXCLAMATION
);
632 if (lastRevision
.GetParentFromHash(hashLast
))
634 MessageBox(lastRevision
.GetLastErr(), L
"TortoiseGit", MB_ICONERROR
);
638 if (g_Git
.GetHash(headhash
, L
"HEAD"))
640 MessageBox(g_Git
.GetGitLastErr(L
"Could not get HEAD hash."), L
"TortoiseGit", MB_ICONERROR
);
644 if(!g_Git
.CheckCleanWorkTree())
646 CMessageBox::Show(GetParentHWND(), IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
| MB_ICONEXCLAMATION
);
651 //Use throw to abort this process (reset back to original HEAD)
654 sCmd
.Format(L
"git.exe reset --hard %s --", static_cast<LPCWSTR
>(pFirstEntry
->m_CommitHash
.ToString()));
655 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
657 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
658 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1
)) + L
"\r\n\r\n" + out
));
660 sCmd
.Format(L
"git.exe reset --soft %s --", static_cast<LPCWSTR
>(hashLast
.ToString()));
661 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
663 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
664 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2
)) + L
"\r\n\r\n"+out
));
668 for (int i
= FirstSelect
; i
<= LastSelect
; ++i
)
670 GitRev
* pRev
= m_arShownList
.SafeGetAt(i
);
671 dlg
.m_sLogMessage
+= pRev
->GetSubject() + L
'\n' + pRev
->GetBody();
672 dlg
.m_sLogMessage
+= L
'\n';
674 dlg
.m_bWholeProject
=true;
675 dlg
.m_bSelectFilesForCommit
= true;
676 dlg
.m_bForceCommitAmend
=true;
677 dlg
.m_bCommitAmend
= TRUE
;
678 const int squashDate
= CRegDWORD(L
"Software\\TortoiseGit\\SquashDate", 0);
680 dlg
.SetTime(m_arShownList
.SafeGetAt(FirstSelect
)->GetAuthorDate());
681 else if (squashDate
== 2)
682 dlg
.SetTime(CTime::GetCurrentTime());
684 dlg
.SetTime(m_arShownList
.SafeGetAt(LastSelect
)->GetAuthorDate());
686 gpl
.AddPath(CTGitPath(g_Git
.m_CurrentDir
));
687 dlg
.m_pathList
= gpl
;
688 if (lastRevision
.ParentsCount() != 1)
690 MessageBox(L
"The following commit dialog can only show changes of oldest commit if it has exactly one parent. This is not the case right now.", L
"TortoiseGit", MB_OK
| MB_ICONINFORMATION
);
691 dlg
.m_bAmendDiffToLastCommit
= TRUE
;
694 dlg
.m_bAmendDiffToLastCommit
= FALSE
;
695 dlg
.m_bNoPostActions
=true;
696 dlg
.m_AmendStr
=dlg
.m_sLogMessage
;
698 if (dlg
.DoModal() == IDOK
)
700 if(pFirstEntry
->m_CommitHash
!=headhash
)
702 if (CherryPickFrom(pFirstEntry
->m_CommitHash
.ToString(), headhash
.ToString()))
705 msg
.Format(L
"Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n");
706 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
711 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED
))));
713 catch(std::exception
& e
)
715 CMessageBox::Show(GetParentHWND(), CUnicodeUtils::GetUnicode(e
.what()), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
716 sCmd
.Format(L
"git.exe reset --hard %s --", static_cast<LPCWSTR
>(headhash
.ToString()));
718 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
719 MessageBox(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD
)) + L
"\r\n\r\n" + out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
727 if (s_bThreadRunning
)
729 CMessageBox::Show(GetParentHWND(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
733 dlg
.m_IsCherryPick
= TRUE
;
734 dlg
.m_Upstream
= this->m_CurrentBranch
;
735 POSITION pos2
= GetFirstSelectedItemPosition();
738 const int indexNext2
= GetNextSelectedItem(pos2
);
739 dlg
.m_CommitList
.m_logEntries
.push_back(m_arShownList
.SafeGetAt(indexNext2
)->m_CommitHash
);
740 dlg
.m_CommitList
.m_LogCache
.m_HashMap
[m_arShownList
.SafeGetAt(indexNext2
)->m_CommitHash
] = *m_arShownList
.SafeGetAt(indexNext2
);
741 dlg
.m_CommitList
.m_logEntries
.GetGitRevAt(dlg
.m_CommitList
.m_logEntries
.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK
;
744 SafeTerminateAsyncDiffThread();
745 if(dlg
.DoModal() == IDOK
)
750 StartAsyncDiffThread();
753 case ID_REBASE_TO_VERSION
:
755 if (s_bThreadRunning
)
757 CMessageBox::Show(GetParentHWND(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
761 dlg
.m_Upstream
= pSelLogEntry
->m_CommitHash
.ToString();
762 // try to guess a branch, optimally a local branch
763 if (auto refList
= hashMap
.find(pSelLogEntry
->m_CommitHash
); refList
!= hashMap
.cend())
765 if (!refList
->second
.empty())
766 dlg
.m_Upstream
= refList
->second
.front();
767 for (const auto& ref
: refList
->second
)
769 if (CGit::GetShortName(ref
, dlg
.m_Upstream
, L
"refs/heads/"))
774 SafeTerminateAsyncDiffThread();
775 if(dlg
.DoModal() == IDOK
)
780 StartAsyncDiffThread();
786 if (CAppUtils::StashSave(GetParentHWND()))
791 if (CAppUtils::StashPop(GetParentHWND()))
796 CAppUtils::RunTortoiseGitProc(L
"/command:reflog /ref:refs/stash");
799 case ID_REFLOG_STASH_APPLY
:
800 CAppUtils::StashApply(GetParentHWND(), pSelLogEntry
->m_Ref
);
805 // security measure, make sure the view is still up2date before deleting any refs, see issue #3782
806 std::vector
<GitRevLoglist
> cache
;
807 if (CString err
; GitRevLoglist::GetRefLog(m_CurrentBranch
, cache
, err
))
809 MessageBox(L
"Error while loading reflog.\n" + err
, L
"TortoiseGit", MB_ICONERROR
);
812 if (cache
.size() != m_arShownList
.size() || !cache
.empty() && (cache
.at(0).GetCommitterDate() != m_arShownList
.SafeGetAt(0)->GetCommitterDate() || cache
.at(0).m_CommitHash
!= m_arShownList
.SafeGetAt(0)->m_CommitHash
|| cache
.at(cache
.size() - 1).m_CommitHash
!= m_arShownList
.SafeGetAt(m_arShownList
.size() - 1)->m_CommitHash
))
814 MessageBox(L
"The current view seems to be out of date. Please refresh, e.g. by pressing F5 and recheck the selection.", L
"TortoiseGit", MB_ICONERROR
);
819 if (GetSelectedCount() > 1)
820 str
.Format(IDS_PROC_DELETENREFS
, GetSelectedCount());
822 str
.Format(IDS_PROC_DELETEREF
, static_cast<LPCWSTR
>(pSelLogEntry
->m_Ref
));
824 if (CMessageBox::Show(GetParentHWND(), str
, L
"TortoiseGit", 1, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
827 std::vector
<CString
> refsToDelete
;
828 POSITION pos2
= GetFirstSelectedItemPosition();
831 CString ref
= m_arShownList
.SafeGetAt(GetNextSelectedItem(pos2
))->m_Ref
;
832 if (CStringUtils::StartsWith(ref
, L
"refs/"))
833 ref
= ref
.Mid(static_cast<int>(wcslen(L
"refs/")));
834 const int refpos
= ref
.ReverseFind(L
'{');
835 if (refpos
> 0 && ref
.Mid(refpos
- 1, 2) != L
"@{")
836 ref
= ref
.Left(refpos
) + L
'@'+ ref
.Mid(refpos
);
837 refsToDelete
.push_back(ref
);
840 for (auto revIt
= refsToDelete
.crbegin(); revIt
!= refsToDelete
.crend(); ++revIt
)
842 CString ref
= *revIt
;
844 if (CStringUtils::StartsWith(ref
, L
"stash"))
845 sCmd
.Format(L
"git.exe stash drop %s", static_cast<LPCWSTR
>(ref
));
847 sCmd
.Format(L
"git.exe reflog delete %s", static_cast<LPCWSTR
>(ref
));
849 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
850 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
852 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
858 CString sCmd
= L
"/command:log";
859 sCmd
+= L
" /path:\"" + g_Git
.m_CurrentDir
+ L
"\" ";
860 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
861 sCmd
+= L
" /endrev:" + r1
->m_CommitHash
.ToString();
862 CAppUtils::RunTortoiseGitProc(sCmd
);
865 case ID_CREATE_PATCH
:
867 const int select
= this->GetSelectedCount();
868 CString sCmd
= L
"/command:formatpatch";
869 sCmd
+= L
" /path:\"" + g_Git
.m_CurrentDir
+ L
"\" ";
871 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
872 GitRev
* r2
= nullptr;
875 sCmd
+= L
" /startrev:" + r1
->m_CommitHash
.ToString();
879 r2
= m_arShownList
.SafeGetAt(LastSelect
);
880 if( this->m_IsOldFirst
)
882 sCmd
+= L
" /startrev:" + r1
->m_CommitHash
.ToString() + L
"~1";
883 sCmd
+= L
" /endrev:" + r2
->m_CommitHash
.ToString();
888 sCmd
+= L
" /startrev:" + r2
->m_CommitHash
.ToString() + L
"~1";
889 sCmd
+= L
" /endrev:" + r1
->m_CommitHash
.ToString();
894 CAppUtils::RunTortoiseGitProc(sCmd
);
899 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
900 GitRev
* last
= m_arShownList
.SafeGetAt(LastSelect
);
901 ASSERT(first
&& last
);
903 CString firstBad
= first
->m_CommitHash
.ToString();
904 if (auto refList
= hashMap
.find(first
->m_CommitHash
); refList
!= hashMap
.cend() && !refList
->second
.empty())
905 firstBad
= refList
->second
.at(0);
906 CString lastGood
= last
->m_CommitHash
.ToString();
907 if (auto refList
= hashMap
.find(last
->m_CommitHash
); refList
!= hashMap
.cend() && !refList
->second
.empty())
908 lastGood
= refList
->second
.at(0);
910 if (CAppUtils::BisectStart(GetParentHWND(), lastGood
, firstBad
))
916 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
917 if (CAppUtils::BisectOperation(GetParentHWND(), L
"good", !first
->m_CommitHash
.IsEmpty() ? first
->m_CommitHash
.ToString() : CString()))
923 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
924 if (CAppUtils::BisectOperation(GetParentHWND(), L
"bad", !first
->m_CommitHash
.IsEmpty() ? first
->m_CommitHash
.ToString() : CString()))
931 POSITION pos2
= GetFirstSelectedItemPosition();
934 const int indexNext2
= GetNextSelectedItem(pos2
);
935 auto rev
= m_arShownList
.SafeGetAt(indexNext2
);
936 if (!rev
->m_CommitHash
.IsEmpty())
937 refs
.AppendFormat(L
" %s", static_cast<LPCWSTR
>(rev
->m_CommitHash
.ToString()));
939 if (CAppUtils::BisectOperation(GetParentHWND(), L
"skip", refs
))
945 if (CAppUtils::BisectOperation(GetParentHWND(), L
"reset"))
952 sCmd
.Format(L
"/command:repobrowser /path:\"%s\" /rev:%s", static_cast<LPCWSTR
>(g_Git
.m_CurrentDir
), static_cast<LPCWSTR
>(pSelLogEntry
->m_CommitHash
.ToString()));
953 CAppUtils::RunTortoiseGitProc(sCmd
);
958 CString workingDir
= g_Git
.m_CurrentDir
;
959 workingDir
.Replace(L
':', L
'_');
960 bool pushAllBranches
= CRegDWORD(L
"Software\\TortoiseGit\\TortoiseProc\\Push\\" + workingDir
+ L
"\\AllBranches", FALSE
) == TRUE
;
962 CString guessAssociatedBranch
= pSelLogEntry
->m_CommitHash
.ToString();
963 auto branch
= popmenu
? reinterpret_cast<const CString
*>(static_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
)) : nullptr;
964 if (branch
&& !CStringUtils::StartsWith(*branch
, L
"refs/remotes/"))
966 guessAssociatedBranch
= *branch
;
967 pushAllBranches
= false;
969 else if (auto refList
= hashMap
.find(pSelLogEntry
->m_CommitHash
); refList
!= hashMap
.cend())
971 if (!GetFirstEntryStartingWith(refList
->second
, L
"refs/heads/", guessAssociatedBranch
))
972 GetFirstEntryStartingWith(refList
->second
, L
"refs/tags/", guessAssociatedBranch
);
975 if (CStringUtils::EndsWith(guessAssociatedBranch
, L
"^{}"))
976 guessAssociatedBranch
.Truncate(guessAssociatedBranch
.GetLength() - static_cast<int>(wcslen(L
"^{}")));
978 if (CAppUtils::Push(GetParentHWND(), guessAssociatedBranch
, pushAllBranches
))
984 if (CAppUtils::Pull(GetParentHWND()))
990 if (CAppUtils::Fetch(GetParentHWND()))
996 if (CAppUtils::SVNDCommit(GetParentHWND()))
1003 sCmd
.Format(L
"/command:cleanup /path:\"%s\"", static_cast<LPCWSTR
>(g_Git
.m_CurrentDir
));
1004 CAppUtils::RunTortoiseGitProc(sCmd
);
1007 case ID_SUBMODULE_UPDATE
:
1010 sCmd
.Format(L
"/command:subupdate /bkpath:\"%s\"", static_cast<LPCWSTR
>(g_Git
.m_CurrentDir
));
1011 CAppUtils::RunTortoiseGitProc(sCmd
);
1014 case ID_SHOWBRANCHES
:
1016 CCommitIsOnRefsDlg
* dlg
= new CCommitIsOnRefsDlg(this);
1017 dlg
->m_Rev
= static_cast<LPCWSTR
>(pSelLogEntry
->m_CommitHash
.ToString());
1019 // pointer won't leak as it is destroyed within PostNcDestroy()
1024 auto branch
= popmenu
? reinterpret_cast<const CString
*>(static_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
)) : nullptr;
1027 CMessageBox::Show(GetParentHWND(), IDS_ERROR_NOREF
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1031 if (branch
== reinterpret_cast<CString
*>(MAKEINTRESOURCE(IDS_ALL
)))
1033 auto refList
= hashMap
.find(pSelLogEntry
->m_CommitHash
);
1034 if (refList
== hashMap
.cend())
1036 CString currentBranch
= L
"refs/heads/" + m_CurrentBranch
;
1037 bool nothingDeleted
= true;
1038 for (const auto& ref
: refList
->second
)
1040 if (ref
== currentBranch
)
1042 if (!CAppUtils::DeleteRef(this, ref
))
1044 nothingDeleted
= false;
1049 else if (!CAppUtils::DeleteRef(this, *branch
))
1051 this->ReloadHashMap();
1053 m_pFindDialog
->RefreshList();
1055 this->GetItemRect(FirstSelect
,&rect
,LVIR_BOUNDS
);
1056 this->InvalidateRect(rect
);
1060 case ID_TOGGLE_ROLLUP
:
1062 auto newRollUpStates
= std::make_shared
<RollUpStateMap
>(*m_RollUpStates
.load());
1063 RollUpState newNodeState
= pSelLogEntry
->m_RolledUp
? RollUpState::Expand
: RollUpState::Collapse
;
1064 if (auto it
= newRollUpStates
->find(pSelLogEntry
->m_CommitHash
); it
!= newRollUpStates
->end())
1066 if (pSelLogEntry
->m_RolledUpIsForced
)
1067 newRollUpStates
->erase(it
);
1069 it
->second
= newNodeState
;
1072 newRollUpStates
->emplace(pSelLogEntry
->m_CommitHash
, newNodeState
);
1073 m_RollUpStates
.store(newRollUpStates
);
1080 m_nSearchIndex
= GetSelectionMark();
1081 if (m_nSearchIndex
< 0)
1085 m_pFindDialog
->SetFocus();
1090 m_pFindDialog
= new CFindDlg();
1091 m_pFindDialog
->Create(this);
1097 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
1098 auto branch
= popmenu
? reinterpret_cast<const CString
*>(static_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
& 0xFFFF)) : nullptr;
1101 else if (auto refList
= hashMap
.find(pSelLogEntry
->m_CommitHash
); refList
!= hashMap
.cend() && !refList
->second
.empty())
1102 str
= refList
->second
.at(0);
1103 // we need an URL to complete this command, so error out if we can't get an URL
1104 if (CAppUtils::Merge(GetParentHWND(), &str
))
1113 if (GetSelectedCount() == 1)
1116 if (static_cast<size_t>(parent
) > pSelLogEntry
->m_ParentHash
.size())
1119 str
.Format(IDS_PROC_NOPARENT
, parent
);
1120 MessageBox(str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
1125 if (!this->RevertSelectedCommits(parent
))
1127 if (CMessageBox::Show(m_hWnd
, IDS_REVREVERTED
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_OKBUTTON
, IDS_COMMITBUTTON
) == 2)
1129 CTGitPathList pathlist
;
1130 pathlist
.AddPath(this->m_Path
);
1131 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\SelectFilesForCommit", TRUE
));
1133 CAppUtils::Commit(GetParentHWND(), CString(), false, str
, pathlist
, bSelectFilesForCommit
);
1141 CAppUtils::EditNote(GetParentHWND(), pSelLogEntry
, &m_ProjectProperties
);
1142 this->SetItemState(FirstSelect
, 0, LVIS_SELECTED
);
1143 this->SetItemState(FirstSelect
, LVIS_SELECTED
, LVIS_SELECTED
);
1147 //CMessageBox::Show(nullptr, L"Have not implemented", L"TortoiseGit", MB_OK);
1151 case ID_BLAMECOMPARE
:
1153 //user clicked on the menu item "compare with working copy"
1154 //now first get the revision which is selected
1157 GitDiff
diff(this, this->m_hWnd
, true);
1158 diff
.SetHEADPeg(m_LogRevision
);
1159 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
1162 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
1165 case ID_BLAMEWITHPREVIOUS
:
1167 //user clicked on the menu item "Compare and Blame with previous revision"
1170 GitDiff
diff(this, this->m_hWnd
, true);
1171 diff
.SetHEADPeg(m_LogRevision
);
1172 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
1175 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
1184 CProgressDlg progDlg
;
1185 progDlg
.SetTitle(IDS_APPNAME
);
1186 progDlg
.SetAnimation(IDR_DOWNLOAD
);
1188 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), static_cast<LPCWSTR
>(revSelected
.ToString()));
1189 progDlg
.SetLine(1, sInfoLine
, true);
1190 SetAndClearProgressInfo(&progDlg
);
1191 progDlg
.ShowModeless(m_hWnd
);
1192 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
1193 bool bSuccess
= true;
1194 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
1197 // try again, but with the selected revision as the peg revision
1198 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
1201 SetAndClearProgressInfo(nullptr);
1202 CMessageBox::Show(GetSafeHwnd(), GetLastErrorMessage(), L
"TortoiseGit", MB_ICONERROR
);
1211 SetAndClearProgressInfo(nullptr);
1212 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
1214 CAppUtils::ShellOpen(tempfile
.GetWinPath(), GetSafeHwnd());
1216 CAppUtils::ShowOpenWithDialog(tempfile
.GetWinPathString(), GetSafeHwnd());
1223 dlg
.EndRev
= revSelected
;
1224 if (dlg
.DoModal() == IDOK
)
1229 tempfile
= blame
.BlameToTempFile(m_path
, dlg
.StartRev
, dlg
.EndRev
, dlg
.EndRev
, logfile
, L
"", dlg
.m_bIncludeMerge
, TRUE
, TRUE
);
1230 if (!tempfile
.IsEmpty())
1232 if (dlg
.m_bTextView
)
1234 //open the default text editor for the result file
1235 CAppUtils::StartTextViewer(tempfile
);
1239 CString sParams
= L
"/path:\"" + m_path
.GetGitPathString() + L
"\" ";
1240 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
1248 CMessageBox::Show(GetSafeHwnd(), blame
.GetLastErrorMessage(), L
"TortoiseGit", MB_ICONERROR
);
1256 sCmd
.Format(L
"%s /command:export /path:\"%s\" /revision:%ld",
1257 static_cast<LPCWSTR
>(CPathUtils::GetAppDirectory() + L
"TortoiseGitProc.exe"),
1258 static_cast<LPCWSTR
>(pathURL
), static_cast<LONG
>(revSelected
));
1259 CAppUtils::LaunchApplication(sCmd
, nullptr, false);
1264 CString url
= m_ProjectProperties
.sWebViewerRev
;
1265 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1266 url
.Replace(L
"%REVISION%", revSelected
.ToString());
1268 ShellExecute(GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
1271 case ID_VIEWPATHREV
:
1273 CString relurl
= pathURL
;
1274 CString sRoot
= GetRepositoryRoot(CTGitPath(relurl
));
1275 relurl
= relurl
.Mid(sRoot
.GetLength());
1276 CString url
= m_ProjectProperties
.sWebViewerPathRev
;
1277 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1278 url
.Replace(L
"%REVISION%", revSelected
.ToString());
1279 url
.Replace(L
"%PATH%", relurl
);
1281 ShellExecute(GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
1288 theApp
.DoWaitCursor(-1);
1291 void CGitLogList::SetSelectedRebaseAction(int action
)
1293 POSITION pos
= GetFirstSelectedItemPosition();
1297 auto index
= GetNextSelectedItem(pos
);
1298 if (m_arShownList
.SafeGetAt(index
)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
) || (index
== GetItemCount() - 1 && action
== LOGACTIONS_REBASE_SQUASH
))
1300 if (!m_bIsCherryPick
&& m_arShownList
.SafeGetAt(index
)->ParentsCount() > 1 && action
== LOGACTIONS_REBASE_SQUASH
)
1302 m_arShownList
.SafeGetAt(index
)->GetRebaseAction() = action
;
1304 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
1305 this->InvalidateRect(rect
);
1308 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1311 void CGitLogList::SetUnselectedRebaseAction(int action
)
1313 POSITION pos
= GetFirstSelectedItemPosition();
1314 int index
= pos
? GetNextSelectedItem(pos
) : -1;
1315 for (int i
= 0; i
< GetItemCount(); i
++)
1319 index
= pos
? GetNextSelectedItem(pos
) : -1;
1323 if (m_arShownList
.SafeGetAt(i
)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
) || (i
== GetItemCount() - 1 && action
== LOGACTIONS_REBASE_SQUASH
) || (!m_bIsCherryPick
&& action
== LOGACTIONS_REBASE_SQUASH
&& m_arShownList
.SafeGetAt(i
)->ParentsCount() != 1))
1325 m_arShownList
.SafeGetAt(i
)->GetRebaseAction() = action
;
1327 this->GetItemRect(i
, &rect
, LVIR_BOUNDS
);
1328 this->InvalidateRect(rect
);
1331 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1334 void CGitLogList::ShiftSelectedRebaseAction()
1336 POSITION pos
= GetFirstSelectedItemPosition();
1339 auto index
= GetNextSelectedItem(pos
);
1340 int* action
= &(m_arShownList
.SafeGetAt(index
))->GetRebaseAction();
1343 case LOGACTIONS_REBASE_PICK
:
1344 *action
= LOGACTIONS_REBASE_SKIP
;
1346 case LOGACTIONS_REBASE_SKIP
:
1347 *action
= LOGACTIONS_REBASE_EDIT
;
1349 case LOGACTIONS_REBASE_EDIT
:
1350 *action
= LOGACTIONS_REBASE_SQUASH
;
1351 if (index
== GetItemCount() - 1 && (m_bIsCherryPick
|| m_arShownList
.SafeGetAt(index
)->m_ParentHash
.size() == 1))
1352 *action
= LOGACTIONS_REBASE_PICK
;
1354 case LOGACTIONS_REBASE_SQUASH
:
1355 *action
= LOGACTIONS_REBASE_PICK
;
1359 this->GetItemRect(index
, &rect
, LVIR_BOUNDS
);
1360 this->InvalidateRect(rect
);