1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2017 - 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(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
;
61 if(!g_Git
.CheckCleanWorkTree())
62 CMessageBox::Show(nullptr, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
65 if (this->GetSelectedCount() > 1)
67 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT
)));
68 progress
.SetTime(true);
69 progress
.ShowModeless(this);
72 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
74 POSITION pos
= GetFirstSelectedItemPosition();
78 int index
= GetNextSelectedItem(pos
);
79 GitRev
* r1
= m_arShownList
.SafeGetAt(index
);
81 if (progress
.IsVisible())
83 progress
.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT
, (LPCTSTR
)r1
->m_CommitHash
.ToString());
84 progress
.FormatNonPathLine(2, L
"%s", (LPCTSTR
)r1
->GetSubject());
85 progress
.SetProgress(i
, this->GetSelectedCount());
89 if(r1
->m_CommitHash
.IsEmpty())
92 if (g_Git
.GitRevert(parent
, r1
->m_CommitHash
))
95 str
.LoadString(IDS_SVNACTION_FAILEDREVERT
);
96 str
= g_Git
.GetGitLastErr(str
, CGit::GIT_CMD_REVERT
);
97 if( GetSelectedCount() == 1)
98 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
99 else if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), str
, L
"TortoiseGit", 2, IDI_ERROR
, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
105 if (progress
.HasUserCancelled())
110 int CGitLogList::CherryPickFrom(CString from
, CString to
)
112 CLogDataVector
logs(&m_LogCache
);
114 range
.Format(L
"%s..%s", (LPCTSTR
)from
, (LPCTSTR
)to
);
115 if (logs
.ParserFromLog(nullptr, 0, 0, &range
))
121 CSysProgressDlg progress
;
122 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK
)));
123 progress
.SetTime(true);
124 progress
.ShowModeless(this);
126 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
128 for (int i
= (int)logs
.size() - 1; i
>= 0; i
--)
130 if (progress
.IsVisible())
132 progress
.FormatNonPathLine(1, IDS_PROC_PICK
, (LPCTSTR
)logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
133 progress
.FormatNonPathLine(2, L
"%s", (LPCTSTR
)logs
.GetGitRevAt(i
).GetSubject());
134 progress
.SetProgress64(logs
.size() - i
, logs
.size());
136 if (progress
.HasUserCancelled())
137 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED
))));
139 cmd
.Format(L
"git.exe cherry-pick %s", (LPCTSTR
)logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
140 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
141 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED
)) + L
":\r\n\r\n" + out
));
147 int CGitLogList::DeleteRef(const CString
& ref
)
150 if (CGit::GetShortName(ref
, shortname
, L
"refs/remotes/"))
153 msg
.Format(IDS_PROC_DELETEREMOTEBRANCH
, (LPCTSTR
)ref
);
154 int result
= CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), msg
, L
"TortoiseGit", 3, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCALREMOTE
)), CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCAL
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
)));
157 CString remoteName
= shortname
.Left(shortname
.Find(L
'/'));
158 shortname
= shortname
.Mid(shortname
.Find(L
'/') + 1);
159 if (CAppUtils::IsSSHPutty())
160 CAppUtils::LaunchPAgent(nullptr, &remoteName
);
162 CSysProgressDlg sysProgressDlg
;
163 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
164 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
165 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
166 sysProgressDlg
.SetShowProgressBar(false);
167 sysProgressDlg
.ShowModal(this, true);
169 list
.push_back(L
"refs/heads/" + shortname
);
170 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
171 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), g_Git
.GetGitLastErr(L
"Could not delete remote ref.", CGit::GIT_CMD_PUSH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
172 sysProgressDlg
.Stop();
175 else if (result
== 2)
177 if (g_Git
.DeleteRef(ref
))
179 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
186 else if (CGit::GetShortName(ref
, shortname
, L
"refs/stash"))
189 std::vector
<GitRevLoglist
> stashList
;
190 size_t count
= !GitRevLoglist::GetRefLog(ref
, stashList
, err
) ? stashList
.size() : 0;
192 msg
.Format(IDS_PROC_DELETEALLSTASH
, count
);
193 int choose
= CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), msg
, L
"TortoiseGit", 3, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_DROPONESTASH
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
)));
197 if (g_Git
.Run(L
"git.exe stash clear", &out
, CP_UTF8
))
198 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
201 else if (choose
== 2)
204 if (g_Git
.Run(L
"git.exe stash drop refs/stash@{0}", &out
, CP_UTF8
))
205 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
212 msg
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)ref
);
213 if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), msg
, L
"TortoiseGit", 2, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 1)
215 if (g_Git
.DeleteRef(ref
))
217 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
225 void CGitLogList::ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
*popmenu
)
227 POSITION pos
= GetFirstSelectedItemPosition();
228 int indexNext
= GetNextSelectedItem(pos
);
232 GitRevLoglist
* pSelLogEntry
= m_arShownList
.SafeGetAt(indexNext
);
234 bool bShiftPressed
= !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000);
236 theApp
.DoWaitCursor(1);
241 CTGitPathList pathlist
;
242 CTGitPathList selectedlist
;
243 pathlist
.AddPath(this->m_Path
);
244 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\SelectFilesForCommit", TRUE
));
246 CAppUtils::Commit(CString(),false,str
,
247 pathlist
,selectedlist
,bSelectFilesForCommit
);
249 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
,0);
254 if (CAppUtils::MergeAbort())
255 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
, 0);
258 case ID_GNUDIFF1
: // compare with WC, unified
260 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
261 bool bMerge
= false, bCombine
= false;
263 if(!r1
->m_CommitHash
.IsEmpty())
267 if( (cmd
&0xFFFF) == 0xFFFF)
269 else if((cmd
&0xFFFF) == 0xFFFE)
271 else if ((cmd
& 0xFFFF) == 0xFFFD)
273 CString tempfile
= GetTempFile();
274 CString gitcmd
= L
"git.exe diff-tree --cc " + r1
->m_CommitHash
.ToString();
276 if (g_Git
.RunLogFile(gitcmd
, tempfile
, &lastErr
))
278 MessageBox(lastErr
, L
"TortoiseGit", MB_ICONERROR
);
284 CStdioFile
file(tempfile
, CFile::typeText
| CFile::modeRead
| CFile::shareDenyWrite
);
286 bool isHash
= file
.ReadString(strLine
) && r1
->m_CommitHash
.ToString() == strLine
;
287 bool more
= isHash
&& file
.ReadString(strLine
) && !strLine
.IsEmpty();
290 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_NOCHANGEAFTERMERGE
, IDS_APPNAME
, MB_OK
);
294 catch (CFileException
* e
)
299 CAppUtils::StartUnifiedDiffViewer(tempfile
, r1
->m_CommitHash
.ToString());
306 if ((size_t)cmd
> r1
->m_ParentHash
.size())
309 str
.Format(IDS_PROC_NOPARENT
, cmd
);
310 MessageBox(str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
316 hash2
= r1
->m_ParentHash
[cmd
-1].ToString();
319 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2
, CTGitPath(), r1
->m_CommitHash
.ToString(), bShiftPressed
, false, false, bMerge
, bCombine
);
322 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), L
"HEAD", CTGitPath(), GitRev::GetWorkingCopy(), bShiftPressed
, false, false, bMerge
, bCombine
);
326 case ID_GNUDIFF2
: // compare two revisions, unified
328 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
329 GitRev
* r2
= m_arShownList
.SafeGetAt(LastSelect
);
330 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2
->m_CommitHash
.ToString(), CTGitPath(), r1
->m_CommitHash
.ToString(), bShiftPressed
);
334 case ID_COMPARETWO
: // compare two revisions
336 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
337 GitRev
* r2
= m_arShownList
.SafeGetAt(LastSelect
);
338 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
339 CGitDiff::DiffCommit(m_Path
, r1
, r2
, bShiftPressed
);
342 CString path1
= m_Path
.GetGitPathString();
343 // start with 1 (0 = working copy changes)
344 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
346 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
347 CTGitPathList list
= first
->GetFiles(nullptr);
348 const CTGitPath
* file
= list
.LookForGitPath(path1
);
349 if (file
&& !file
->GetGitOldPathString().IsEmpty())
350 path1
= file
->GetGitOldPathString();
352 CString path2
= path1
;
353 for (int i
= FirstSelect
; i
< LastSelect
; ++i
)
355 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
356 CTGitPathList list
= first
->GetFiles(nullptr);
357 const CTGitPath
* file
= list
.LookForGitPath(path2
);
358 if (file
&& !file
->GetGitOldPathString().IsEmpty())
359 path2
= file
->GetGitOldPathString();
361 CGitDiff::DiffCommit(CTGitPath(path1
), CTGitPath(path2
), r1
, r2
, bShiftPressed
);
367 case ID_COMPARE
: // compare revision with WC
369 GitRevLoglist
* r1
= &m_wcRev
;
370 GitRevLoglist
* r2
= pSelLogEntry
;
372 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
373 CGitDiff::DiffCommit(m_Path
, r1
, r2
, bShiftPressed
);
376 CString path1
= m_Path
.GetGitPathString();
377 // start with 1 (0 = working copy changes)
378 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
380 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
381 CTGitPathList list
= first
->GetFiles(nullptr);
382 const CTGitPath
* file
= list
.LookForGitPath(path1
);
383 if (file
&& !file
->GetGitOldPathString().IsEmpty())
384 path1
= file
->GetGitOldPathString();
386 CGitDiff::DiffCommit(m_Path
, CTGitPath(path1
), r1
, r2
, bShiftPressed
);
389 //user clicked on the menu item "compare with working copy"
392 // GitDiff diff(this, m_hWnd, true);
393 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
394 // diff.SetHEADPeg(m_LogRevision);
395 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
398 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
402 case ID_COMPAREWITHPREVIOUS
:
404 if (pSelLogEntry
->m_ParentHash
.empty())
406 if (pSelLogEntry
->GetParentFromHash(pSelLogEntry
->m_CommitHash
))
407 MessageBox(pSelLogEntry
->GetLastErr(), L
"TortoiseGit", MB_ICONERROR
);
410 if (!pSelLogEntry
->m_ParentHash
.empty())
411 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
419 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
420 CGitDiff::DiffCommit(m_Path
, pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString(), bShiftPressed
);
423 CString path1
= m_Path
.GetGitPathString();
424 // start with 1 (0 = working copy changes)
425 for (int i
= m_bShowWC
? 1 : 0; i
< indexNext
; ++i
)
427 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
428 CTGitPathList list
= first
->GetFiles(nullptr);
429 const CTGitPath
* file
= list
.LookForGitPath(path1
);
430 if (file
&& !file
->GetGitOldPathString().IsEmpty())
431 path1
= file
->GetGitOldPathString();
433 CString path2
= path1
;
434 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(indexNext
);
435 CTGitPathList list
= first
->GetFiles(nullptr);
436 const CTGitPath
* file
= list
.LookForGitPath(path2
);
437 if (file
&& !file
->GetGitOldPathString().IsEmpty())
438 path2
= file
->GetGitOldPathString();
440 CGitDiff::DiffCommit(CTGitPath(path1
), CTGitPath(path2
), pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString(), bShiftPressed
);
445 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_NOPREVIOUSVERSION
, IDS_APPNAME
, MB_OK
);
449 // GitDiff diff(this, m_hWnd, true);
450 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
451 // diff.SetHEADPeg(m_LogRevision);
452 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
455 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
458 case ID_COMPARETWOCOMMITCHANGES
:
460 auto pFirstEntry
= m_arShownList
.SafeGetAt(FirstSelect
);
461 auto pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
462 CString patch1
= GetTempFile();
463 CString patch2
= GetTempFile();
465 if (g_Git
.RunLogFile(L
"git.exe format-patch --stdout " + pFirstEntry
->m_CommitHash
.ToString() + L
"~1.." + pFirstEntry
->m_CommitHash
.ToString() + L
"", patch1
, &err
))
467 MessageBox(L
"Could not generate patch for commit " + pFirstEntry
->m_CommitHash
.ToString() + L
".\n" + err
, L
"TortoiseGit", MB_ICONERROR
);
470 if (g_Git
.RunLogFile(L
"git.exe format-patch --stdout " + pLastEntry
->m_CommitHash
.ToString() + L
"~1.." + pLastEntry
->m_CommitHash
.ToString() + L
"", patch2
, &err
))
472 MessageBox(L
"Could not generate patch for commit " + pLastEntry
->m_CommitHash
.ToString() + L
".\n" + err
, L
"TortoiseGit", MB_ICONERROR
);
475 CAppUtils::DiffFlags flags
;
476 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
.ToString(), pLastEntry
->m_CommitHash
.ToString(), flags
);
479 case ID_LOG_VIEWRANGE
:
480 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
:
482 GitRev
* pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
485 if ((cmd
& 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
)
489 cmdline
.Format(L
"/command:log /path:\"%s\" /range:\"%s%s%s\"",
490 (LPCTSTR
)g_Git
.CombinePath(m_Path
), (LPCTSTR
)pLastEntry
->m_CommitHash
.ToString(), (LPCTSTR
)sep
, (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString());
491 CAppUtils::RunTortoiseGitProc(cmdline
);
494 case ID_COPYCLIPBOARDFULL
:
495 case ID_COPYCLIPBOARDFULLNOPATHS
:
496 case ID_COPYCLIPBOARDHASH
:
497 case ID_COPYCLIPBOARDAUTHORSFULL
:
498 case ID_COPYCLIPBOARDAUTHORSNAME
:
499 case ID_COPYCLIPBOARDAUTHORSEMAIL
:
500 case ID_COPYCLIPBOARDSUBJECTS
:
501 case ID_COPYCLIPBOARDMESSAGES
:
503 CopySelectionToClipBoard(cmd
& 0xFFFF);
506 case ID_COPYCLIPBOARDBRANCHTAG
:
510 auto selectedBranch
= reinterpret_cast<const CString
*>(reinterpret_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
));
514 if (CStringUtils::StartsWith(*selectedBranch
, L
"refs/tags/"))
515 sClipboard
= selectedBranch
->Mid((int)wcslen(L
"refs/tags/")).TrimRight(L
"^{}");
517 sClipboard
= CGit::StripRefName(*selectedBranch
);
521 for (const auto& ref
: m_HashMap
[pSelLogEntry
->m_CommitHash
])
524 sClipboard
+= L
"\r\n";
527 CStringUtils::WriteAsciiStringToClipboard(sClipboard
, GetSafeHwnd());
532 CString str
=pSelLogEntry
->m_CommitHash
.ToString();
533 // try to get the tag
534 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/tags/", str
);
535 CAppUtils::Export(&str
, &m_Path
);
538 case ID_CREATE_BRANCH
:
541 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
542 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
545 else // try to guess remote branch in order to enable tracking
546 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/remotes/", str
);
548 CAppUtils::CreateBranchTag((cmd
&0xFFFF) == ID_CREATE_TAG
, &str
);
551 m_pFindDialog
->RefreshList();
553 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
558 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
559 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
562 else // try to guess remote branch in order to recommend good branch name and tracking
563 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/remotes/", str
);
565 CAppUtils::Switch(str
);
569 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
571 case ID_SWITCHBRANCH
:
574 const CString
* branch
= (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
577 CString name
= *branch
;
578 CGit::GetShortName(*branch
, name
, L
"refs/heads/");
579 CAppUtils::PerformSwitch(name
);
583 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
588 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
589 if (CAppUtils::GitReset(&str
))
598 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK
);
601 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT
);
603 case ID_REBASE_SQUASH
:
604 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH
);
607 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP
);
609 case ID_COMBINE_COMMIT
:
613 CGitHash hashFirst
,hashLast
;
615 int headindex
=GetHeadIndex();
616 if(headindex
>=0) //incase show all branch, head is not the first commits.
618 head
.Format(L
"HEAD~%d", FirstSelect
- headindex
);
619 if (g_Git
.GetHash(hashFirst
, head
))
621 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of first selected revision."), L
"TortoiseGit", MB_ICONERROR
);
625 head
.Format(L
"HEAD~%d", LastSelect
- headindex
);
626 if (g_Git
.GetHash(hashLast
, head
))
628 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of last selected revision."), L
"TortoiseGit", MB_ICONERROR
);
633 GitRev
* pFirstEntry
= m_arShownList
.SafeGetAt(FirstSelect
);
634 GitRev
* pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
635 if(pFirstEntry
->m_CommitHash
!= hashFirst
|| pLastEntry
->m_CommitHash
!= hashLast
)
637 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_CANNOTCOMBINE
, IDS_APPNAME
, MB_OK
| MB_ICONEXCLAMATION
);
642 if (lastRevision
.GetParentFromHash(hashLast
))
644 MessageBox(lastRevision
.GetLastErr(), L
"TortoiseGit", MB_ICONERROR
);
648 if (g_Git
.GetHash(headhash
, L
"HEAD"))
650 MessageBox(g_Git
.GetGitLastErr(L
"Could not get HEAD hash."), L
"TortoiseGit", MB_ICONERROR
);
654 if(!g_Git
.CheckCleanWorkTree())
656 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
| MB_ICONEXCLAMATION
);
661 //Use throw to abort this process (reset back to original HEAD)
664 sCmd
.Format(L
"git.exe reset --hard %s --", (LPCTSTR
)pFirstEntry
->m_CommitHash
.ToString());
665 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
667 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
668 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1
)) + L
"\r\n\r\n" + out
));
670 sCmd
.Format(L
"git.exe reset --soft %s --", (LPCTSTR
)hashLast
.ToString());
671 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
673 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
674 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2
)) + L
"\r\n\r\n"+out
));
678 for (int i
= FirstSelect
; i
<= LastSelect
; ++i
)
680 GitRev
* pRev
= m_arShownList
.SafeGetAt(i
);
681 dlg
.m_sLogMessage
+= pRev
->GetSubject() + L
'\n' + pRev
->GetBody();
682 dlg
.m_sLogMessage
+= L
'\n';
684 dlg
.m_bWholeProject
=true;
685 dlg
.m_bSelectFilesForCommit
= true;
686 dlg
.m_bForceCommitAmend
=true;
687 int squashDate
= (int)CRegDWORD(L
"Software\\TortoiseGit\\SquashDate", 0);
689 dlg
.SetTime(m_arShownList
.SafeGetAt(FirstSelect
)->GetAuthorDate());
690 else if (squashDate
== 2)
691 dlg
.SetTime(CTime::GetCurrentTime());
693 dlg
.SetTime(m_arShownList
.SafeGetAt(LastSelect
)->GetAuthorDate());
695 gpl
.AddPath(CTGitPath(g_Git
.m_CurrentDir
));
696 dlg
.m_pathList
= gpl
;
697 if (lastRevision
.ParentsCount() != 1)
699 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
);
700 dlg
.m_bAmendDiffToLastCommit
= TRUE
;
703 dlg
.m_bAmendDiffToLastCommit
= FALSE
;
704 dlg
.m_bNoPostActions
=true;
705 dlg
.m_AmendStr
=dlg
.m_sLogMessage
;
707 if (dlg
.DoModal() == IDOK
)
709 if(pFirstEntry
->m_CommitHash
!=headhash
)
711 if(CherryPickFrom(pFirstEntry
->m_CommitHash
.ToString(),headhash
))
714 msg
.Format(L
"Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n");
715 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
720 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED
))));
722 catch(std::exception
& e
)
724 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), CUnicodeUtils::GetUnicode(CStringA(e
.what())), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
725 sCmd
.Format(L
"git.exe reset --hard %s --", (LPCTSTR
)headhash
.ToString());
727 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
728 MessageBox(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD
)) + L
"\r\n\r\n" + out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
736 if (m_bThreadRunning
)
738 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
742 dlg
.m_IsCherryPick
= TRUE
;
743 dlg
.m_Upstream
= this->m_CurrentBranch
;
744 POSITION pos2
= GetFirstSelectedItemPosition();
747 int indexNext2
= GetNextSelectedItem(pos2
);
748 dlg
.m_CommitList
.m_logEntries
.push_back(m_arShownList
.SafeGetAt(indexNext2
)->m_CommitHash
);
749 dlg
.m_CommitList
.m_LogCache
.m_HashMap
[m_arShownList
.SafeGetAt(indexNext2
)->m_CommitHash
] = *m_arShownList
.SafeGetAt(indexNext2
);
750 dlg
.m_CommitList
.m_logEntries
.GetGitRevAt(dlg
.m_CommitList
.m_logEntries
.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK
;
753 if(dlg
.DoModal() == IDOK
)
759 case ID_REBASE_TO_VERSION
:
761 if (m_bThreadRunning
)
763 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
767 auto refList
= m_HashMap
[pSelLogEntry
->m_CommitHash
];
768 dlg
.m_Upstream
= refList
.empty() ? pSelLogEntry
->m_CommitHash
.ToString() : refList
.front();
769 for (const auto& ref
: refList
)
770 if (CGit::GetShortName(ref
, dlg
.m_Upstream
, L
"refs/heads/"))
773 if(dlg
.DoModal() == IDOK
)
782 if (CAppUtils::StashSave())
787 if (CAppUtils::StashPop())
792 CAppUtils::RunTortoiseGitProc(L
"/command:reflog /ref:refs/stash");
795 case ID_REFLOG_STASH_APPLY
:
796 CAppUtils::StashApply(pSelLogEntry
->m_Ref
);
802 if (GetSelectedCount() > 1)
803 str
.Format(IDS_PROC_DELETENREFS
, GetSelectedCount());
805 str
.Format(IDS_PROC_DELETEREF
, (LPCTSTR
)pSelLogEntry
->m_Ref
);
807 if (CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), str
, L
"TortoiseGit", 1, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
810 std::vector
<CString
> refsToDelete
;
811 POSITION pos2
= GetFirstSelectedItemPosition();
814 CString ref
= m_arShownList
.SafeGetAt(GetNextSelectedItem(pos2
))->m_Ref
;
815 if (CStringUtils::StartsWith(ref
, L
"refs/"))
817 int refpos
= ref
.ReverseFind(L
'{');
818 if (refpos
> 0 && ref
.Mid(refpos
- 1, 2) != L
"@{")
819 ref
= ref
.Left(refpos
) + L
'@'+ ref
.Mid(refpos
);
820 refsToDelete
.push_back(ref
);
823 for (auto revIt
= refsToDelete
.crbegin(); revIt
!= refsToDelete
.crend(); ++revIt
)
825 CString ref
= *revIt
;
827 if (CStringUtils::StartsWith(ref
, L
"stash"))
828 sCmd
.Format(L
"git.exe stash drop %s", (LPCTSTR
)ref
);
830 sCmd
.Format(L
"git.exe reflog delete %s", (LPCTSTR
)ref
);
832 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
833 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
835 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
841 CString sCmd
= L
"/command:log";
842 sCmd
+= L
" /path:\"" + g_Git
.m_CurrentDir
+ L
"\" ";
843 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
844 sCmd
+= L
" /endrev:" + r1
->m_CommitHash
.ToString();
845 CAppUtils::RunTortoiseGitProc(sCmd
);
848 case ID_CREATE_PATCH
:
850 int select
=this->GetSelectedCount();
851 CString sCmd
= L
"/command:formatpatch";
852 sCmd
+= L
" /path:\"" + g_Git
.m_CurrentDir
+ L
"\" ";
854 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
855 GitRev
* r2
= nullptr;
858 sCmd
+= L
" /startrev:" + r1
->m_CommitHash
.ToString();
862 r2
= m_arShownList
.SafeGetAt(LastSelect
);
863 if( this->m_IsOldFirst
)
865 sCmd
+= L
" /startrev:" + r1
->m_CommitHash
.ToString() + L
"~1";
866 sCmd
+= L
" /endrev:" + r2
->m_CommitHash
.ToString();
871 sCmd
+= L
" /startrev:" + r2
->m_CommitHash
.ToString() + L
"~1";
872 sCmd
+= L
" /endrev:" + r1
->m_CommitHash
.ToString();
877 CAppUtils::RunTortoiseGitProc(sCmd
);
882 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
883 GitRev
* last
= m_arShownList
.SafeGetAt(LastSelect
);
884 ASSERT(first
&& last
);
886 CString firstBad
= first
->m_CommitHash
.ToString();
887 if (!m_HashMap
[first
->m_CommitHash
].empty())
888 firstBad
= m_HashMap
[first
->m_CommitHash
].at(0);
889 CString lastGood
= last
->m_CommitHash
.ToString();
890 if (!m_HashMap
[last
->m_CommitHash
].empty())
891 lastGood
= m_HashMap
[last
->m_CommitHash
].at(0);
893 if (CAppUtils::BisectStart(lastGood
, firstBad
))
899 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
900 if (CAppUtils::BisectOperation(L
"good", !first
->m_CommitHash
.IsEmpty() ? first
->m_CommitHash
.ToString() : L
""))
906 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
907 if (CAppUtils::BisectOperation(L
"bad", !first
->m_CommitHash
.IsEmpty() ? first
->m_CommitHash
.ToString() : L
""))
914 POSITION pos2
= GetFirstSelectedItemPosition();
917 int indexNext2
= GetNextSelectedItem(pos2
);
918 auto rev
= m_arShownList
.SafeGetAt(indexNext2
);
919 if (!rev
->m_CommitHash
.IsEmpty())
920 refs
.AppendFormat(L
" %s", (LPCTSTR
)rev
->m_CommitHash
.ToString());
922 if (CAppUtils::BisectOperation(L
"skip", refs
))
928 if (CAppUtils::BisectOperation(L
"reset"))
935 sCmd
.Format(L
"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString());
936 CAppUtils::RunTortoiseGitProc(sCmd
);
941 CString guessAssociatedBranch
= pSelLogEntry
->m_CommitHash
;
942 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
) : nullptr;
944 guessAssociatedBranch
= *branch
;
945 else if (!GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/heads/", guessAssociatedBranch
))
946 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/tags/", guessAssociatedBranch
);
948 guessAssociatedBranch
.Replace(L
"^{}", L
"");
950 if (CAppUtils::Push(guessAssociatedBranch
))
956 if (CAppUtils::Pull())
962 if (CAppUtils::Fetch())
968 if (CAppUtils::SVNDCommit())
975 sCmd
.Format(L
"/command:cleanup /path:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
);
976 CAppUtils::RunTortoiseGitProc(sCmd
);
979 case ID_SUBMODULE_UPDATE
:
982 sCmd
.Format(L
"/command:subupdate /bkpath:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
);
983 CAppUtils::RunTortoiseGitProc(sCmd
);
986 case ID_SHOWBRANCHES
:
988 CCommitIsOnRefsDlg
* dlg
= new CCommitIsOnRefsDlg(this);
989 dlg
->m_Rev
= (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString();
991 // pointer won't leak as it is destroyed within PostNcDestroy()
996 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
) : nullptr;
999 CMessageBox::Show(GetSafeOwner()->GetSafeHwnd(), IDS_ERROR_NOREF
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
1003 if (branch
== (CString
*)MAKEINTRESOURCE(IDS_ALL
))
1005 CString currentBranch
= L
"refs/heads/" + m_CurrentBranch
;
1006 bool nothingDeleted
= true;
1007 for (const auto& ref
: m_HashMap
[pSelLogEntry
->m_CommitHash
])
1009 if (ref
== currentBranch
)
1011 if (!DeleteRef(ref
))
1013 nothingDeleted
= false;
1018 else if (!DeleteRef(*branch
))
1020 this->ReloadHashMap();
1022 m_pFindDialog
->RefreshList();
1024 this->GetItemRect(FirstSelect
,&rect
,LVIR_BOUNDS
);
1025 this->InvalidateRect(rect
);
1031 m_nSearchIndex
= GetSelectionMark();
1032 if (m_nSearchIndex
< 0)
1038 m_pFindDialog
= new CFindDlg();
1039 m_pFindDialog
->Create(this);
1045 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
1046 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
1049 else if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty())
1050 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
1051 // we need an URL to complete this command, so error out if we can't get an URL
1052 if(CAppUtils::Merge(&str
))
1061 if (GetSelectedCount() == 1)
1064 if ((size_t)parent
> pSelLogEntry
->m_ParentHash
.size())
1067 str
.Format(IDS_PROC_NOPARENT
, parent
);
1068 MessageBox(str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
1073 if (!this->RevertSelectedCommits(parent
))
1075 if (CMessageBox::Show(m_hWnd
, IDS_REVREVERTED
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_OKBUTTON
, IDS_COMMITBUTTON
) == 2)
1077 CTGitPathList pathlist
;
1078 CTGitPathList selectedlist
;
1079 pathlist
.AddPath(this->m_Path
);
1080 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\SelectFilesForCommit", TRUE
));
1082 CAppUtils::Commit(CString(), false, str
, pathlist
, selectedlist
, bSelectFilesForCommit
);
1090 CAppUtils::EditNote(pSelLogEntry
, &m_ProjectProperties
);
1091 this->SetItemState(FirstSelect
, 0, LVIS_SELECTED
);
1092 this->SetItemState(FirstSelect
, LVIS_SELECTED
, LVIS_SELECTED
);
1096 //CMessageBox::Show(nullptr, L"Have not implemented", L"TortoiseGit", MB_OK);
1100 case ID_BLAMECOMPARE
:
1102 //user clicked on the menu item "compare with working copy"
1103 //now first get the revision which is selected
1106 GitDiff
diff(this, this->m_hWnd
, true);
1107 diff
.SetHEADPeg(m_LogRevision
);
1108 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
1111 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
1114 case ID_BLAMEWITHPREVIOUS
:
1116 //user clicked on the menu item "Compare and Blame with previous revision"
1119 GitDiff
diff(this, this->m_hWnd
, true);
1120 diff
.SetHEADPeg(m_LogRevision
);
1121 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
1124 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
1132 CProgressDlg progDlg
;
1133 progDlg
.SetTitle(IDS_APPNAME
);
1134 progDlg
.SetAnimation(IDR_DOWNLOAD
);
1136 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), (LPCTSTR
)revSelected
.ToString());
1137 progDlg
.SetLine(1, sInfoLine
, true);
1138 SetAndClearProgressInfo(&progDlg
);
1139 progDlg
.ShowModeless(m_hWnd
);
1140 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
1141 bool bSuccess
= true;
1142 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
1145 // try again, but with the selected revision as the peg revision
1146 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
1149 SetAndClearProgressInfo(nullptr);
1150 CMessageBox::Show(GetSafeHwnd(), GetLastErrorMessage(), L
"TortoiseGit", MB_ICONERROR
);
1159 SetAndClearProgressInfo(nullptr);
1160 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
1162 CAppUtils::ShellOpen(tempfile
.GetWinPath(), GetSafeHwnd());
1164 CAppUtils::ShowOpenWithDialog(tempfile
.GetWinPathString(), GetSafeHwnd());
1171 dlg
.EndRev
= revSelected
;
1172 if (dlg
.DoModal() == IDOK
)
1177 tempfile
= blame
.BlameToTempFile(m_path
, dlg
.StartRev
, dlg
.EndRev
, dlg
.EndRev
, logfile
, L
"", dlg
.m_bIncludeMerge
, TRUE
, TRUE
);
1178 if (!tempfile
.IsEmpty())
1180 if (dlg
.m_bTextView
)
1182 //open the default text editor for the result file
1183 CAppUtils::StartTextViewer(tempfile
);
1187 CString sParams
= L
"/path:\"" + m_path
.GetGitPathString() + L
"\" ";
1188 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
1196 CMessageBox::Show(GetSafeHwnd(), blame
.GetLastErrorMessage(), L
"TortoiseGit", MB_ICONERROR
);
1204 sCmd
.Format(L
"%s /command:export /path:\"%s\" /revision:%ld",
1205 (LPCTSTR
)(CPathUtils::GetAppDirectory() + L
"TortoiseGitProc.exe"),
1206 (LPCTSTR
)pathURL
, (LONG
)revSelected
);
1207 CAppUtils::LaunchApplication(sCmd
, nullptr, false);
1212 CString url
= m_ProjectProperties
.sWebViewerRev
;
1213 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1214 url
.Replace(L
"%REVISION%", revSelected
.ToString());
1216 ShellExecute(GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
1219 case ID_VIEWPATHREV
:
1221 CString relurl
= pathURL
;
1222 CString sRoot
= GetRepositoryRoot(CTGitPath(relurl
));
1223 relurl
= relurl
.Mid(sRoot
.GetLength());
1224 CString url
= m_ProjectProperties
.sWebViewerPathRev
;
1225 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1226 url
.Replace(L
"%REVISION%", revSelected
.ToString());
1227 url
.Replace(L
"%PATH%", relurl
);
1229 ShellExecute(GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
1236 theApp
.DoWaitCursor(-1);
1239 void CGitLogList::SetSelectedRebaseAction(int action
)
1241 POSITION pos
= GetFirstSelectedItemPosition();
1246 index
= GetNextSelectedItem(pos
);
1247 if (m_arShownList
.SafeGetAt(index
)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
) || (index
== GetItemCount() - 1 && action
== LOGACTIONS_REBASE_SQUASH
))
1249 if (!m_bIsCherryPick
&& m_arShownList
.SafeGetAt(index
)->ParentsCount() > 1 && action
== LOGACTIONS_REBASE_SQUASH
)
1251 m_arShownList
.SafeGetAt(index
)->GetRebaseAction() = action
;
1253 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
1254 this->InvalidateRect(rect
);
1257 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1260 void CGitLogList::SetUnselectedRebaseAction(int action
)
1262 POSITION pos
= GetFirstSelectedItemPosition();
1263 int index
= pos
? GetNextSelectedItem(pos
) : -1;
1264 for (int i
= 0; i
< GetItemCount(); i
++)
1268 index
= pos
? GetNextSelectedItem(pos
) : -1;
1272 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))
1274 m_arShownList
.SafeGetAt(i
)->GetRebaseAction() = action
;
1276 this->GetItemRect(i
, &rect
, LVIR_BOUNDS
);
1277 this->InvalidateRect(rect
);
1280 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1283 void CGitLogList::ShiftSelectedRebaseAction()
1285 POSITION pos
= GetFirstSelectedItemPosition();
1289 index
= GetNextSelectedItem(pos
);
1290 int* action
= &(m_arShownList
.SafeGetAt(index
))->GetRebaseAction();
1293 case LOGACTIONS_REBASE_PICK
:
1294 *action
= LOGACTIONS_REBASE_SKIP
;
1296 case LOGACTIONS_REBASE_SKIP
:
1297 *action
= LOGACTIONS_REBASE_EDIT
;
1299 case LOGACTIONS_REBASE_EDIT
:
1300 *action
= LOGACTIONS_REBASE_SQUASH
;
1301 if (index
== GetItemCount() - 1 && (m_bIsCherryPick
|| m_arShownList
.SafeGetAt(index
)->m_ParentHash
.size() == 1))
1302 *action
= LOGACTIONS_REBASE_PICK
;
1304 case LOGACTIONS_REBASE_SQUASH
:
1305 *action
= LOGACTIONS_REBASE_PICK
;
1309 this->GetItemRect(index
, &rect
, LVIR_BOUNDS
);
1310 this->InvalidateRect(rect
);