1
// TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - 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(GetParentHWND(), str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
99 else if (CMessageBox::Show(GetParentHWND(), 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 void CGitLogList::ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
*popmenu
)
149 POSITION pos
= GetFirstSelectedItemPosition();
150 int indexNext
= GetNextSelectedItem(pos
);
154 GitRevLoglist
* pSelLogEntry
= m_arShownList
.SafeGetAt(indexNext
);
156 bool bShiftPressed
= !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000);
158 theApp
.DoWaitCursor(1);
163 CTGitPathList pathlist
;
164 pathlist
.AddPath(this->m_Path
);
165 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\SelectFilesForCommit", TRUE
));
167 CAppUtils::Commit(GetParentHWND(), CString(), false, str
,
168 pathlist
, bSelectFilesForCommit
);
170 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
,0);
175 if (CAppUtils::MergeAbort(GetParentHWND()))
176 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
, 0);
179 case ID_GNUDIFF1
: // compare with WC, unified
181 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
182 if(!r1
->m_CommitHash
.IsEmpty())
185 bool bMerge
= false, bCombine
= false;
188 if( (cmd
&0xFFFF) == 0xFFFF)
190 else if((cmd
&0xFFFF) == 0xFFFE)
192 else if ((cmd
& 0xFFFF) == 0xFFFD)
194 CString tempfile
= GetTempFile();
195 CString gitcmd
= L
"git.exe diff-tree --cc " + r1
->m_CommitHash
.ToString();
197 if (g_Git
.RunLogFile(gitcmd
, tempfile
, &lastErr
))
199 MessageBox(lastErr
, L
"TortoiseGit", MB_ICONERROR
);
205 CStdioFile
file(tempfile
, CFile::typeText
| CFile::modeRead
| CFile::shareDenyWrite
);
207 bool isHash
= file
.ReadString(strLine
) && r1
->m_CommitHash
.ToString() == strLine
;
208 bool more
= isHash
&& file
.ReadString(strLine
) && !strLine
.IsEmpty();
211 CMessageBox::Show(GetParentHWND(), IDS_NOCHANGEAFTERMERGE
, IDS_APPNAME
, MB_OK
);
215 catch (CFileException
* e
)
220 CAppUtils::StartUnifiedDiffViewer(tempfile
, r1
->m_CommitHash
.ToString());
227 if ((size_t)cmd
> r1
->m_ParentHash
.size())
230 str
.Format(IDS_PROC_NOPARENT
, cmd
);
231 MessageBox(str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
237 hash2
= r1
->m_ParentHash
[cmd
-1].ToString();
240 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
241 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path
, hash2
, m_Path
, r1
->m_CommitHash
.ToString(), bShiftPressed
, false, false, bMerge
, bCombine
);
244 CString path
= m_Path
.GetGitPathString();
245 // start with 1 (0 = working copy changes)
246 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
248 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
249 CTGitPathList list
= first
->GetFiles(nullptr);
250 const CTGitPath
* file
= list
.LookForGitPath(path
);
251 if (file
&& !file
->GetGitOldPathString().IsEmpty())
252 path
= file
->GetGitOldPathString();
254 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(path
), hash2
, CTGitPath(path
), r1
->m_CommitHash
.ToString(), bShiftPressed
, false, false, bMerge
, bCombine
);
258 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path
, L
"HEAD", m_Path
, GitRev::GetWorkingCopy(), bShiftPressed
);
262 case ID_GNUDIFF2
: // compare two revisions, unified
264 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
265 GitRev
* r2
= m_arShownList
.SafeGetAt(LastSelect
);
266 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
267 CAppUtils::StartShowUnifiedDiff(nullptr, m_Path
, r2
->m_CommitHash
.ToString(), m_Path
, r1
->m_CommitHash
.ToString(), bShiftPressed
);
270 CString path
= m_Path
.GetGitPathString();
271 // start with 1 (0 = working copy changes)
272 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
274 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
275 CTGitPathList list
= first
->GetFiles(nullptr);
276 const CTGitPath
* file
= list
.LookForGitPath(path
);
277 if (file
&& !file
->GetGitOldPathString().IsEmpty())
278 path
= file
->GetGitOldPathString();
280 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(path
), r2
->m_CommitHash
.ToString(), CTGitPath(path
), r1
->m_CommitHash
.ToString(), bShiftPressed
);
285 case ID_COMPARETWO
: // compare two revisions
287 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
288 GitRev
* r2
= m_arShownList
.SafeGetAt(LastSelect
);
289 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
290 CGitDiff::DiffCommit(GetParentHWND(), m_Path
, r1
, r2
, bShiftPressed
);
293 CString path1
= m_Path
.GetGitPathString();
294 // start with 1 (0 = working copy changes)
295 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
297 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
298 CTGitPathList list
= first
->GetFiles(nullptr);
299 const CTGitPath
* file
= list
.LookForGitPath(path1
);
300 if (file
&& !file
->GetGitOldPathString().IsEmpty())
301 path1
= file
->GetGitOldPathString();
303 CString path2
= path1
;
304 for (int i
= FirstSelect
; i
< LastSelect
; ++i
)
306 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
307 CTGitPathList list
= first
->GetFiles(nullptr);
308 const CTGitPath
* file
= list
.LookForGitPath(path2
);
309 if (file
&& !file
->GetGitOldPathString().IsEmpty())
310 path2
= file
->GetGitOldPathString();
312 CGitDiff::DiffCommit(GetParentHWND(), CTGitPath(path1
), CTGitPath(path2
), r1
, r2
, bShiftPressed
);
318 case ID_COMPARE
: // compare revision with WC
320 GitRevLoglist
* r1
= &m_wcRev
;
321 GitRevLoglist
* r2
= pSelLogEntry
;
323 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
324 CGitDiff::DiffCommit(GetParentHWND(), m_Path
, r1
, r2
, bShiftPressed
);
327 CString path1
= m_Path
.GetGitPathString();
328 // start with 1 (0 = working copy changes)
329 for (int i
= m_bShowWC
? 1 : 0; i
< FirstSelect
; ++i
)
331 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
332 CTGitPathList list
= first
->GetFiles(nullptr);
333 const CTGitPath
* file
= list
.LookForGitPath(path1
);
334 if (file
&& !file
->GetGitOldPathString().IsEmpty())
335 path1
= file
->GetGitOldPathString();
337 CGitDiff::DiffCommit(GetParentHWND(), m_Path
, CTGitPath(path1
), r1
, r2
, bShiftPressed
);
340 //user clicked on the menu item "compare with working copy"
343 // GitDiff diff(this, m_hWnd, true);
344 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
345 // diff.SetHEADPeg(m_LogRevision);
346 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
349 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
353 case ID_COMPAREWITHPREVIOUS
:
355 if (pSelLogEntry
->m_ParentHash
.empty())
357 if (pSelLogEntry
->GetParentFromHash(pSelLogEntry
->m_CommitHash
))
358 MessageBox(pSelLogEntry
->GetLastErr(), L
"TortoiseGit", MB_ICONERROR
);
361 if (!pSelLogEntry
->m_ParentHash
.empty())
362 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
370 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
371 CGitDiff::DiffCommit(GetParentHWND(), m_Path
, pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString(), bShiftPressed
);
374 CString path1
= m_Path
.GetGitPathString();
375 // start with 1 (0 = working copy changes)
376 for (int i
= m_bShowWC
? 1 : 0; i
< indexNext
; ++i
)
378 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(i
);
379 CTGitPathList list
= first
->GetFiles(nullptr);
380 const CTGitPath
* file
= list
.LookForGitPath(path1
);
381 if (file
&& !file
->GetGitOldPathString().IsEmpty())
382 path1
= file
->GetGitOldPathString();
384 CString path2
= path1
;
385 GitRevLoglist
* first
= m_arShownList
.SafeGetAt(indexNext
);
386 CTGitPathList list
= first
->GetFiles(nullptr);
387 const CTGitPath
* file
= list
.LookForGitPath(path2
);
388 if (file
&& !file
->GetGitOldPathString().IsEmpty())
389 path2
= file
->GetGitOldPathString();
391 CGitDiff::DiffCommit(GetParentHWND(), CTGitPath(path1
), CTGitPath(path2
), pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString(), bShiftPressed
);
396 CMessageBox::Show(GetParentHWND(), IDS_PROC_NOPREVIOUSVERSION
, IDS_APPNAME
, MB_OK
);
400 // GitDiff diff(this, m_hWnd, true);
401 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
402 // diff.SetHEADPeg(m_LogRevision);
403 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
406 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
409 case ID_COMPARETWOCOMMITCHANGES
:
411 auto pFirstEntry
= m_arShownList
.SafeGetAt(FirstSelect
);
412 auto pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
413 CString patch1
= GetTempFile();
414 CString patch2
= GetTempFile();
416 if (g_Git
.RunLogFile(L
"git.exe format-patch --stdout " + pFirstEntry
->m_CommitHash
.ToString() + L
"~1.." + pFirstEntry
->m_CommitHash
.ToString() + L
"", patch1
, &err
))
418 MessageBox(L
"Could not generate patch for commit " + pFirstEntry
->m_CommitHash
.ToString() + L
".\n" + err
, L
"TortoiseGit", MB_ICONERROR
);
421 if (g_Git
.RunLogFile(L
"git.exe format-patch --stdout " + pLastEntry
->m_CommitHash
.ToString() + L
"~1.." + pLastEntry
->m_CommitHash
.ToString() + L
"", patch2
, &err
))
423 MessageBox(L
"Could not generate patch for commit " + pLastEntry
->m_CommitHash
.ToString() + L
".\n" + err
, L
"TortoiseGit", MB_ICONERROR
);
426 CAppUtils::DiffFlags flags
;
427 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
);
430 case ID_LOG_VIEWRANGE
:
431 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
:
433 GitRev
* pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
436 if ((cmd
& 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
)
440 cmdline
.Format(L
"/command:log /path:\"%s\" /range:\"%s%s%s\"",
441 (LPCTSTR
)g_Git
.CombinePath(m_Path
), (LPCTSTR
)pLastEntry
->m_CommitHash
.ToString(), (LPCTSTR
)sep
, (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString());
442 CAppUtils::RunTortoiseGitProc(cmdline
);
445 case ID_COPYCLIPBOARDFULL
:
446 case ID_COPYCLIPBOARDFULLNOPATHS
:
447 case ID_COPYCLIPBOARDHASH
:
448 case ID_COPYCLIPBOARDAUTHORSFULL
:
449 case ID_COPYCLIPBOARDAUTHORSNAME
:
450 case ID_COPYCLIPBOARDAUTHORSEMAIL
:
451 case ID_COPYCLIPBOARDSUBJECTS
:
452 case ID_COPYCLIPBOARDMESSAGES
:
454 CopySelectionToClipBoard(cmd
& 0xFFFF);
457 case ID_COPYCLIPBOARDBRANCHTAG
:
461 auto selectedBranch
= reinterpret_cast<const CString
*>(reinterpret_cast<CIconMenu
*>(popmenu
)->GetMenuItemData(cmd
));
465 if (CStringUtils::StartsWith(*selectedBranch
, L
"refs/tags/"))
466 sClipboard
= selectedBranch
->Mid((int)wcslen(L
"refs/tags/")).TrimRight(L
"^{}");
468 sClipboard
= CGit::StripRefName(*selectedBranch
);
472 for (const auto& ref
: m_HashMap
[pSelLogEntry
->m_CommitHash
])
475 sClipboard
+= L
"\r\n";
478 CStringUtils::WriteAsciiStringToClipboard(sClipboard
, GetSafeHwnd());
483 CString str
=pSelLogEntry
->m_CommitHash
.ToString();
484 // try to get the tag
485 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/tags/", str
);
486 CAppUtils::Export(GetParentHWND(), &str
, &m_Path
);
489 case ID_CREATE_BRANCH
:
492 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
493 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
496 else // try to guess remote branch in order to enable tracking
497 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/remotes/", str
);
499 CAppUtils::CreateBranchTag(GetParentHWND(), (cmd
& 0xFFFF) == ID_CREATE_TAG
, &str
);
502 m_pFindDialog
->RefreshList();
504 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
509 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
510 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
513 else // try to guess remote branch in order to recommend good branch name and tracking
514 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/remotes/", str
);
516 CAppUtils::Switch(GetParentHWND(), str
);
520 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
522 case ID_SWITCHBRANCH
:
525 const CString
* branch
= (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
528 CString name
= *branch
;
529 CGit::GetShortName(*branch
, name
, L
"refs/heads/");
530 CAppUtils::PerformSwitch(GetParentHWND(), name
);
534 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
539 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
540 if (CAppUtils::GitReset(GetParentHWND(), &str
))
549 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK
);
552 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT
);
554 case ID_REBASE_SQUASH
:
555 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH
);
558 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP
);
560 case ID_COMBINE_COMMIT
:
564 CGitHash hashFirst
,hashLast
;
566 int headindex
=GetHeadIndex();
567 if(headindex
>=0) //incase show all branch, head is not the first commits.
569 head
.Format(L
"HEAD~%d", FirstSelect
- headindex
);
570 if (g_Git
.GetHash(hashFirst
, head
))
572 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of first selected revision."), L
"TortoiseGit", MB_ICONERROR
);
576 head
.Format(L
"HEAD~%d", LastSelect
- headindex
);
577 if (g_Git
.GetHash(hashLast
, head
))
579 MessageBox(g_Git
.GetGitLastErr(L
"Could not get hash of last selected revision."), L
"TortoiseGit", MB_ICONERROR
);
584 GitRev
* pFirstEntry
= m_arShownList
.SafeGetAt(FirstSelect
);
585 GitRev
* pLastEntry
= m_arShownList
.SafeGetAt(LastSelect
);
586 if(pFirstEntry
->m_CommitHash
!= hashFirst
|| pLastEntry
->m_CommitHash
!= hashLast
)
588 CMessageBox::Show(GetParentHWND(), IDS_PROC_CANNOTCOMBINE
, IDS_APPNAME
, MB_OK
| MB_ICONEXCLAMATION
);
593 if (lastRevision
.GetParentFromHash(hashLast
))
595 MessageBox(lastRevision
.GetLastErr(), L
"TortoiseGit", MB_ICONERROR
);
599 if (g_Git
.GetHash(headhash
, L
"HEAD"))
601 MessageBox(g_Git
.GetGitLastErr(L
"Could not get HEAD hash."), L
"TortoiseGit", MB_ICONERROR
);
605 if(!g_Git
.CheckCleanWorkTree())
607 CMessageBox::Show(GetParentHWND(), IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
| MB_ICONEXCLAMATION
);
612 //Use throw to abort this process (reset back to original HEAD)
615 sCmd
.Format(L
"git.exe reset --hard %s --", (LPCTSTR
)pFirstEntry
->m_CommitHash
.ToString());
616 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
618 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
619 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1
)) + L
"\r\n\r\n" + out
));
621 sCmd
.Format(L
"git.exe reset --soft %s --", (LPCTSTR
)hashLast
.ToString());
622 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
624 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
625 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2
)) + L
"\r\n\r\n"+out
));
629 for (int i
= FirstSelect
; i
<= LastSelect
; ++i
)
631 GitRev
* pRev
= m_arShownList
.SafeGetAt(i
);
632 dlg
.m_sLogMessage
+= pRev
->GetSubject() + L
'\n' + pRev
->GetBody();
633 dlg
.m_sLogMessage
+= L
'\n';
635 dlg
.m_bWholeProject
=true;
636 dlg
.m_bSelectFilesForCommit
= true;
637 dlg
.m_bForceCommitAmend
=true;
638 int squashDate
= (int)CRegDWORD(L
"Software\\TortoiseGit\\SquashDate", 0);
640 dlg
.SetTime(m_arShownList
.SafeGetAt(FirstSelect
)->GetAuthorDate());
641 else if (squashDate
== 2)
642 dlg
.SetTime(CTime::GetCurrentTime());
644 dlg
.SetTime(m_arShownList
.SafeGetAt(LastSelect
)->GetAuthorDate());
646 gpl
.AddPath(CTGitPath(g_Git
.m_CurrentDir
));
647 dlg
.m_pathList
= gpl
;
648 if (lastRevision
.ParentsCount() != 1)
650 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
);
651 dlg
.m_bAmendDiffToLastCommit
= TRUE
;
654 dlg
.m_bAmendDiffToLastCommit
= FALSE
;
655 dlg
.m_bNoPostActions
=true;
656 dlg
.m_AmendStr
=dlg
.m_sLogMessage
;
658 if (dlg
.DoModal() == IDOK
)
660 if(pFirstEntry
->m_CommitHash
!=headhash
)
662 if (CherryPickFrom(pFirstEntry
->m_CommitHash
.ToString(), headhash
.ToString()))
665 msg
.Format(L
"Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n");
666 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
671 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED
))));
673 catch(std::exception
& e
)
675 CMessageBox::Show(GetParentHWND(), CUnicodeUtils::GetUnicode(CStringA(e
.what())), L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
676 sCmd
.Format(L
"git.exe reset --hard %s --", (LPCTSTR
)headhash
.ToString());
678 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
679 MessageBox(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD
)) + L
"\r\n\r\n" + out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
687 if (m_bThreadRunning
)
689 CMessageBox::Show(GetParentHWND(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
693 dlg
.m_IsCherryPick
= TRUE
;
694 dlg
.m_Upstream
= this->m_CurrentBranch
;
695 POSITION pos2
= GetFirstSelectedItemPosition();
698 int indexNext2
= GetNextSelectedItem(pos2
);
699 dlg
.m_CommitList
.m_logEntries
.push_back(m_arShownList
.SafeGetAt(indexNext2
)->m_CommitHash
);
700 dlg
.m_CommitList
.m_LogCache
.m_HashMap
[m_arShownList
.SafeGetAt(indexNext2
)->m_CommitHash
] = *m_arShownList
.SafeGetAt(indexNext2
);
701 dlg
.m_CommitList
.m_logEntries
.GetGitRevAt(dlg
.m_CommitList
.m_logEntries
.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK
;
704 if(dlg
.DoModal() == IDOK
)
710 case ID_REBASE_TO_VERSION
:
712 if (m_bThreadRunning
)
714 CMessageBox::Show(GetParentHWND(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
718 auto refList
= m_HashMap
[pSelLogEntry
->m_CommitHash
];
719 dlg
.m_Upstream
= refList
.empty() ? pSelLogEntry
->m_CommitHash
.ToString() : refList
.front();
720 for (const auto& ref
: refList
)
721 if (CGit::GetShortName(ref
, dlg
.m_Upstream
, L
"refs/heads/"))
724 if(dlg
.DoModal() == IDOK
)
733 if (CAppUtils::StashSave(GetParentHWND()))
738 if (CAppUtils::StashPop(GetParentHWND()))
743 CAppUtils::RunTortoiseGitProc(L
"/command:reflog /ref:refs/stash");
746 case ID_REFLOG_STASH_APPLY
:
747 CAppUtils::StashApply(GetParentHWND(), pSelLogEntry
->m_Ref
);
753 if (GetSelectedCount() > 1)
754 str
.Format(IDS_PROC_DELETENREFS
, GetSelectedCount());
756 str
.Format(IDS_PROC_DELETEREF
, (LPCTSTR
)pSelLogEntry
->m_Ref
);
758 if (CMessageBox::Show(GetParentHWND(), str
, L
"TortoiseGit", 1, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
761 std::vector
<CString
> refsToDelete
;
762 POSITION pos2
= GetFirstSelectedItemPosition();
765 CString ref
= m_arShownList
.SafeGetAt(GetNextSelectedItem(pos2
))->m_Ref
;
766 if (CStringUtils::StartsWith(ref
, L
"refs/"))
767 ref
= ref
.Mid((int)wcslen(L
"refs/"));
768 int refpos
= ref
.ReverseFind(L
'{');
769 if (refpos
> 0 && ref
.Mid(refpos
- 1, 2) != L
"@{")
770 ref
= ref
.Left(refpos
) + L
'@'+ ref
.Mid(refpos
);
771 refsToDelete
.push_back(ref
);
774 for (auto revIt
= refsToDelete
.crbegin(); revIt
!= refsToDelete
.crend(); ++revIt
)
776 CString ref
= *revIt
;
778 if (CStringUtils::StartsWith(ref
, L
"stash"))
779 sCmd
.Format(L
"git.exe stash drop %s", (LPCTSTR
)ref
);
781 sCmd
.Format(L
"git.exe reflog delete %s", (LPCTSTR
)ref
);
783 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
784 MessageBox(out
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
786 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
792 CString sCmd
= L
"/command:log";
793 sCmd
+= L
" /path:\"" + g_Git
.m_CurrentDir
+ L
"\" ";
794 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
795 sCmd
+= L
" /endrev:" + r1
->m_CommitHash
.ToString();
796 CAppUtils::RunTortoiseGitProc(sCmd
);
799 case ID_CREATE_PATCH
:
801 int select
=this->GetSelectedCount();
802 CString sCmd
= L
"/command:formatpatch";
803 sCmd
+= L
" /path:\"" + g_Git
.m_CurrentDir
+ L
"\" ";
805 GitRev
* r1
= m_arShownList
.SafeGetAt(FirstSelect
);
806 GitRev
* r2
= nullptr;
809 sCmd
+= L
" /startrev:" + r1
->m_CommitHash
.ToString();
813 r2
= m_arShownList
.SafeGetAt(LastSelect
);
814 if( this->m_IsOldFirst
)
816 sCmd
+= L
" /startrev:" + r1
->m_CommitHash
.ToString() + L
"~1";
817 sCmd
+= L
" /endrev:" + r2
->m_CommitHash
.ToString();
822 sCmd
+= L
" /startrev:" + r2
->m_CommitHash
.ToString() + L
"~1";
823 sCmd
+= L
" /endrev:" + r1
->m_CommitHash
.ToString();
828 CAppUtils::RunTortoiseGitProc(sCmd
);
833 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
834 GitRev
* last
= m_arShownList
.SafeGetAt(LastSelect
);
835 ASSERT(first
&& last
);
837 CString firstBad
= first
->m_CommitHash
.ToString();
838 if (!m_HashMap
[first
->m_CommitHash
].empty())
839 firstBad
= m_HashMap
[first
->m_CommitHash
].at(0);
840 CString lastGood
= last
->m_CommitHash
.ToString();
841 if (!m_HashMap
[last
->m_CommitHash
].empty())
842 lastGood
= m_HashMap
[last
->m_CommitHash
].at(0);
844 if (CAppUtils::BisectStart(GetParentHWND(), lastGood
, firstBad
))
850 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
851 if (CAppUtils::BisectOperation(GetParentHWND(), L
"good", !first
->m_CommitHash
.IsEmpty() ? first
->m_CommitHash
.ToString() : L
""))
857 GitRev
* first
= m_arShownList
.SafeGetAt(FirstSelect
);
858 if (CAppUtils::BisectOperation(GetParentHWND(), L
"bad", !first
->m_CommitHash
.IsEmpty() ? first
->m_CommitHash
.ToString() : L
""))
865 POSITION pos2
= GetFirstSelectedItemPosition();
868 int indexNext2
= GetNextSelectedItem(pos2
);
869 auto rev
= m_arShownList
.SafeGetAt(indexNext2
);
870 if (!rev
->m_CommitHash
.IsEmpty())
871 refs
.AppendFormat(L
" %s", (LPCTSTR
)rev
->m_CommitHash
.ToString());
873 if (CAppUtils::BisectOperation(GetParentHWND(), L
"skip", refs
))
879 if (CAppUtils::BisectOperation(GetParentHWND(), L
"reset"))
886 sCmd
.Format(L
"/command:repobrowser /path:\"%s\" /rev:%s", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString());
887 CAppUtils::RunTortoiseGitProc(sCmd
);
892 CString guessAssociatedBranch
= pSelLogEntry
->m_CommitHash
.ToString();
893 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
) : nullptr;
894 if (branch
&& !CStringUtils::StartsWith(*branch
, L
"refs/remotes/"))
895 guessAssociatedBranch
= *branch
;
896 else if (!GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/heads/", guessAssociatedBranch
))
897 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], L
"refs/tags/", guessAssociatedBranch
);
899 guessAssociatedBranch
.Replace(L
"^{}", L
"");
901 if (CAppUtils::Push(GetParentHWND(), guessAssociatedBranch
))
907 if (CAppUtils::Pull(GetParentHWND()))
913 if (CAppUtils::Fetch(GetParentHWND()))
919 if (CAppUtils::SVNDCommit(GetParentHWND()))
926 sCmd
.Format(L
"/command:cleanup /path:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
);
927 CAppUtils::RunTortoiseGitProc(sCmd
);
930 case ID_SUBMODULE_UPDATE
:
933 sCmd
.Format(L
"/command:subupdate /bkpath:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
);
934 CAppUtils::RunTortoiseGitProc(sCmd
);
937 case ID_SHOWBRANCHES
:
939 CCommitIsOnRefsDlg
* dlg
= new CCommitIsOnRefsDlg(this);
940 dlg
->m_Rev
= (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString();
942 // pointer won't leak as it is destroyed within PostNcDestroy()
947 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
) : nullptr;
950 CMessageBox::Show(GetParentHWND(), IDS_ERROR_NOREF
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
954 if (branch
== (CString
*)MAKEINTRESOURCE(IDS_ALL
))
956 CString currentBranch
= L
"refs/heads/" + m_CurrentBranch
;
957 bool nothingDeleted
= true;
958 for (const auto& ref
: m_HashMap
[pSelLogEntry
->m_CommitHash
])
960 if (ref
== currentBranch
)
962 if (!CAppUtils::DeleteRef(this, ref
))
964 nothingDeleted
= false;
969 else if (!CAppUtils::DeleteRef(this, *branch
))
971 this->ReloadHashMap();
973 m_pFindDialog
->RefreshList();
975 this->GetItemRect(FirstSelect
,&rect
,LVIR_BOUNDS
);
976 this->InvalidateRect(rect
);
982 m_nSearchIndex
= GetSelectionMark();
983 if (m_nSearchIndex
< 0)
989 m_pFindDialog
= new CFindDlg();
990 m_pFindDialog
->Create(this);
996 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
997 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
1000 else if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty())
1001 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
1002 // we need an URL to complete this command, so error out if we can't get an URL
1003 if (CAppUtils::Merge(GetParentHWND(), &str
))
1012 if (GetSelectedCount() == 1)
1015 if ((size_t)parent
> pSelLogEntry
->m_ParentHash
.size())
1018 str
.Format(IDS_PROC_NOPARENT
, parent
);
1019 MessageBox(str
, L
"TortoiseGit", MB_OK
| MB_ICONERROR
);
1024 if (!this->RevertSelectedCommits(parent
))
1026 if (CMessageBox::Show(m_hWnd
, IDS_REVREVERTED
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_OKBUTTON
, IDS_COMMITBUTTON
) == 2)
1028 CTGitPathList pathlist
;
1029 pathlist
.AddPath(this->m_Path
);
1030 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(L
"Software\\TortoiseGit\\SelectFilesForCommit", TRUE
));
1032 CAppUtils::Commit(GetParentHWND(), CString(), false, str
, pathlist
, bSelectFilesForCommit
);
1040 CAppUtils::EditNote(GetParentHWND(), pSelLogEntry
, &m_ProjectProperties
);
1041 this->SetItemState(FirstSelect
, 0, LVIS_SELECTED
);
1042 this->SetItemState(FirstSelect
, LVIS_SELECTED
, LVIS_SELECTED
);
1046 //CMessageBox::Show(nullptr, L"Have not implemented", L"TortoiseGit", MB_OK);
1050 case ID_BLAMECOMPARE
:
1052 //user clicked on the menu item "compare with working copy"
1053 //now first get the revision which is selected
1056 GitDiff
diff(this, this->m_hWnd
, true);
1057 diff
.SetHEADPeg(m_LogRevision
);
1058 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
1061 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
1064 case ID_BLAMEWITHPREVIOUS
:
1066 //user clicked on the menu item "Compare and Blame with previous revision"
1069 GitDiff
diff(this, this->m_hWnd
, true);
1070 diff
.SetHEADPeg(m_LogRevision
);
1071 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
1074 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
1082 CProgressDlg progDlg
;
1083 progDlg
.SetTitle(IDS_APPNAME
);
1084 progDlg
.SetAnimation(IDR_DOWNLOAD
);
1086 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), (LPCTSTR
)revSelected
.ToString());
1087 progDlg
.SetLine(1, sInfoLine
, true);
1088 SetAndClearProgressInfo(&progDlg
);
1089 progDlg
.ShowModeless(m_hWnd
);
1090 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
1091 bool bSuccess
= true;
1092 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
1095 // try again, but with the selected revision as the peg revision
1096 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
1099 SetAndClearProgressInfo(nullptr);
1100 CMessageBox::Show(GetSafeHwnd(), GetLastErrorMessage(), L
"TortoiseGit", MB_ICONERROR
);
1109 SetAndClearProgressInfo(nullptr);
1110 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
1112 CAppUtils::ShellOpen(tempfile
.GetWinPath(), GetSafeHwnd());
1114 CAppUtils::ShowOpenWithDialog(tempfile
.GetWinPathString(), GetSafeHwnd());
1121 dlg
.EndRev
= revSelected
;
1122 if (dlg
.DoModal() == IDOK
)
1127 tempfile
= blame
.BlameToTempFile(m_path
, dlg
.StartRev
, dlg
.EndRev
, dlg
.EndRev
, logfile
, L
"", dlg
.m_bIncludeMerge
, TRUE
, TRUE
);
1128 if (!tempfile
.IsEmpty())
1130 if (dlg
.m_bTextView
)
1132 //open the default text editor for the result file
1133 CAppUtils::StartTextViewer(tempfile
);
1137 CString sParams
= L
"/path:\"" + m_path
.GetGitPathString() + L
"\" ";
1138 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
1146 CMessageBox::Show(GetSafeHwnd(), blame
.GetLastErrorMessage(), L
"TortoiseGit", MB_ICONERROR
);
1154 sCmd
.Format(L
"%s /command:export /path:\"%s\" /revision:%ld",
1155 (LPCTSTR
)(CPathUtils::GetAppDirectory() + L
"TortoiseGitProc.exe"),
1156 (LPCTSTR
)pathURL
, (LONG
)revSelected
);
1157 CAppUtils::LaunchApplication(sCmd
, nullptr, false);
1162 CString url
= m_ProjectProperties
.sWebViewerRev
;
1163 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1164 url
.Replace(L
"%REVISION%", revSelected
.ToString());
1166 ShellExecute(GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
1169 case ID_VIEWPATHREV
:
1171 CString relurl
= pathURL
;
1172 CString sRoot
= GetRepositoryRoot(CTGitPath(relurl
));
1173 relurl
= relurl
.Mid(sRoot
.GetLength());
1174 CString url
= m_ProjectProperties
.sWebViewerPathRev
;
1175 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1176 url
.Replace(L
"%REVISION%", revSelected
.ToString());
1177 url
.Replace(L
"%PATH%", relurl
);
1179 ShellExecute(GetSafeHwnd(), L
"open", url
, nullptr, nullptr, SW_SHOWDEFAULT
);
1186 theApp
.DoWaitCursor(-1);
1189 void CGitLogList::SetSelectedRebaseAction(int action
)
1191 POSITION pos
= GetFirstSelectedItemPosition();
1196 index
= GetNextSelectedItem(pos
);
1197 if (m_arShownList
.SafeGetAt(index
)->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
) || (index
== GetItemCount() - 1 && action
== LOGACTIONS_REBASE_SQUASH
))
1199 if (!m_bIsCherryPick
&& m_arShownList
.SafeGetAt(index
)->ParentsCount() > 1 && action
== LOGACTIONS_REBASE_SQUASH
)
1201 m_arShownList
.SafeGetAt(index
)->GetRebaseAction() = action
;
1203 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
1204 this->InvalidateRect(rect
);
1207 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1210 void CGitLogList::SetUnselectedRebaseAction(int action
)
1212 POSITION pos
= GetFirstSelectedItemPosition();
1213 int index
= pos
? GetNextSelectedItem(pos
) : -1;
1214 for (int i
= 0; i
< GetItemCount(); i
++)
1218 index
= pos
? GetNextSelectedItem(pos
) : -1;
1222 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))
1224 m_arShownList
.SafeGetAt(i
)->GetRebaseAction() = action
;
1226 this->GetItemRect(i
, &rect
, LVIR_BOUNDS
);
1227 this->InvalidateRect(rect
);
1230 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1233 void CGitLogList::ShiftSelectedRebaseAction()
1235 POSITION pos
= GetFirstSelectedItemPosition();
1239 index
= GetNextSelectedItem(pos
);
1240 int* action
= &(m_arShownList
.SafeGetAt(index
))->GetRebaseAction();
1243 case LOGACTIONS_REBASE_PICK
:
1244 *action
= LOGACTIONS_REBASE_SKIP
;
1246 case LOGACTIONS_REBASE_SKIP
:
1247 *action
= LOGACTIONS_REBASE_EDIT
;
1249 case LOGACTIONS_REBASE_EDIT
:
1250 *action
= LOGACTIONS_REBASE_SQUASH
;
1251 if (index
== GetItemCount() - 1 && (m_bIsCherryPick
|| m_arShownList
.SafeGetAt(index
)->m_ParentHash
.size() == 1))
1252 *action
= LOGACTIONS_REBASE_PICK
;
1254 case LOGACTIONS_REBASE_SQUASH
:
1255 *action
= LOGACTIONS_REBASE_PICK
;
1259 this->GetItemRect(index
, &rect
, LVIR_BOUNDS
);
1260 this->InvalidateRect(rect
);