1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - 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 "FileDiffDlg.h"
39 #include "CommitDlg.h"
40 #include "RebaseDlg.h"
42 #include "../TGitCache/CacheInterface.h"
44 IMPLEMENT_DYNAMIC(CGitLogList
, CHintListCtrl
)
46 static void GetFirstEntryStartingWith(STRING_VECTOR
& heystack
, const CString
& needle
, CString
& result
)
48 auto it
= std::find_if(heystack
.cbegin(), heystack
.cend(), [&needle
](const CString
& entry
) { return entry
.Find(needle
) == 0; });
49 if (it
== heystack
.cend())
54 int CGitLogList::RevertSelectedCommits(int parent
)
56 CSysProgressDlg progress
;
60 if(!g_Git
.CheckCleanWorkTree())
62 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
67 if (this->GetSelectedCount() > 1)
69 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_REVERTCOMMIT
)));
70 progress
.SetAnimation(IDR_MOVEANI
);
71 progress
.SetTime(true);
72 progress
.ShowModeless(this);
75 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
77 POSITION pos
= GetFirstSelectedItemPosition();
81 int index
= GetNextSelectedItem(pos
);
82 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(index
));
84 if (progress
.IsVisible())
86 progress
.FormatNonPathLine(1, IDS_PROC_REVERTCOMMIT
, r1
->m_CommitHash
.ToString());
87 progress
.FormatNonPathLine(2, _T("%s"), (LPCTSTR
)r1
->GetSubject());
88 progress
.SetProgress(i
, this->GetSelectedCount());
92 if(r1
->m_CommitHash
.IsEmpty())
95 if (g_Git
.GitRevert(parent
, r1
->m_CommitHash
))
98 str
.LoadString(IDS_SVNACTION_FAILEDREVERT
);
99 str
= g_Git
.GetGitLastErr(str
, CGit::GIT_CMD_REVERT
);
100 if( GetSelectedCount() == 1)
101 CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
104 if(CMessageBox::Show(NULL
, str
, _T("TortoiseGit"),2 , IDI_ERROR
, CString(MAKEINTRESOURCE(IDS_SKIPBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
115 if (progress
.HasUserCancelled())
120 int CGitLogList::CherryPickFrom(CString from
, CString to
)
122 CLogDataVector
logs(&m_LogCache
);
124 range
.Format(_T("%s..%s"), (LPCTSTR
)from
, (LPCTSTR
)to
);
125 if (logs
.ParserFromLog(nullptr, 0, 0, &range
))
131 CSysProgressDlg progress
;
132 progress
.SetTitle(CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK
)));
133 progress
.SetAnimation(IDR_MOVEANI
);
134 progress
.SetTime(true);
135 progress
.ShowModeless(this);
137 CBlockCacheForPath
cacheBlock(g_Git
.m_CurrentDir
);
139 for (int i
= (int)logs
.size() - 1; i
>= 0; i
--)
141 if (progress
.IsVisible())
143 progress
.FormatNonPathLine(1, IDS_PROC_PICK
, logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
144 progress
.FormatNonPathLine(2, _T("%s"), (LPCTSTR
)logs
.GetGitRevAt(i
).GetSubject());
145 progress
.SetProgress64(logs
.size() - i
, logs
.size());
147 if (progress
.HasUserCancelled())
149 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED
))));
152 cmd
.Format(_T("git.exe cherry-pick %s"), (LPCTSTR
)logs
.GetGitRevAt(i
).m_CommitHash
.ToString());
154 if(g_Git
.Run(cmd
,&out
,CP_UTF8
))
156 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_CHERRYPICKFAILED
)) + _T(":\r\n\r\n") + out
));
163 int CGitLogList::DeleteRef(const CString
& ref
)
166 if (CGit::GetShortName(ref
, shortname
, _T("refs/remotes/")))
169 msg
.Format(IDS_PROC_DELETEREMOTEBRANCH
, (LPCTSTR
)ref
);
170 int result
= CMessageBox::Show(nullptr, msg
, _T("TortoiseGit"), 3, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCALREMOTE
)), CString(MAKEINTRESOURCE(IDS_PROC_DELETEREMOTEBRANCH_LOCAL
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
)));
173 CString remoteName
= shortname
.Left(shortname
.Find(L
'/'));
174 shortname
= shortname
.Mid(shortname
.Find(L
'/') + 1);
175 if (CAppUtils::IsSSHPutty())
176 CAppUtils::LaunchPAgent(nullptr, &remoteName
);
178 CSysProgressDlg sysProgressDlg
;
179 sysProgressDlg
.SetTitle(CString(MAKEINTRESOURCE(IDS_APPNAME
)));
180 sysProgressDlg
.SetLine(1, CString(MAKEINTRESOURCE(IDS_DELETING_REMOTE_REFS
)));
181 sysProgressDlg
.SetLine(2, CString(MAKEINTRESOURCE(IDS_PROGRESSWAIT
)));
182 sysProgressDlg
.SetShowProgressBar(false);
183 sysProgressDlg
.ShowModal(this, true);
185 list
.push_back(_T("refs/heads/") + shortname
);
186 if (g_Git
.DeleteRemoteRefs(remoteName
, list
))
187 CMessageBox::Show(nullptr, g_Git
.GetGitLastErr(_T("Could not delete remote ref."), CGit::GIT_CMD_PUSH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
188 sysProgressDlg
.Stop();
191 else if (result
== 2)
193 if (g_Git
.DeleteRef(ref
))
195 CMessageBox::Show(nullptr, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
202 else if (CGit::GetShortName(ref
, shortname
, _T("refs/stash")))
205 std::vector
<GitRevLoglist
> stashList
;
206 size_t count
= !GitRevLoglist::GetRefLog(ref
, stashList
, err
) ? stashList
.size() : 0;
208 msg
.Format(IDS_PROC_DELETEALLSTASH
, count
);
209 if (CMessageBox::Show(nullptr, msg
, _T("TortoiseGit"), 2, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 1)
212 sCmd
.Format(_T("git.exe stash clear"));
214 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
215 CMessageBox::Show(nullptr, out
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
222 msg
.Format(IDS_PROC_DELETEBRANCHTAG
, (LPCTSTR
)ref
);
223 if (CMessageBox::Show(nullptr, msg
, _T("TortoiseGit"), 2, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 1)
225 if (g_Git
.DeleteRef(ref
))
227 CMessageBox::Show(nullptr, g_Git
.GetGitLastErr(L
"Could not delete reference.", CGit::GIT_CMD_DELETETAGBRANCH
), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
235 void CGitLogList::ContextMenuAction(int cmd
,int FirstSelect
, int LastSelect
, CMenu
*popmenu
)
237 POSITION pos
= GetFirstSelectedItemPosition();
238 int indexNext
= GetNextSelectedItem(pos
);
242 GitRevLoglist
* pSelLogEntry
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(indexNext
));
244 theApp
.DoWaitCursor(1);
249 CTGitPathList pathlist
;
250 CTGitPathList selectedlist
;
251 pathlist
.AddPath(this->m_Path
);
252 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
254 CAppUtils::Commit(CString(),false,str
,
255 pathlist
,selectedlist
,bSelectFilesForCommit
);
257 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
,0);
262 if (CAppUtils::MergeAbort())
263 this->GetParent()->PostMessage(WM_COMMAND
,ID_LOGDLG_REFRESH
, 0);
266 case ID_GNUDIFF1
: // compare with WC, unified
268 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
269 bool bMerge
= false, bCombine
= false;
271 if(!r1
->m_CommitHash
.IsEmpty())
275 if( (cmd
&0xFFFF) == 0xFFFF)
277 else if((cmd
&0xFFFF) == 0xFFFE)
279 else if ((cmd
& 0xFFFF) == 0xFFFD)
281 CString tempfile
= GetTempFile();
282 CString gitcmd
= _T("git.exe diff-tree --cc ") + r1
->m_CommitHash
.ToString();
284 if (g_Git
.RunLogFile(gitcmd
, tempfile
, &lastErr
))
286 MessageBox(lastErr
, _T("TortoiseGit"), MB_ICONERROR
);
292 CStdioFile
file(tempfile
, CFile::typeText
| CFile::modeRead
| CFile::shareDenyWrite
);
294 bool isHash
= file
.ReadString(strLine
) && r1
->m_CommitHash
.ToString() == strLine
;
295 bool more
= isHash
&& file
.ReadString(strLine
) && !strLine
.IsEmpty();
298 CMessageBox::Show(nullptr, IDS_NOCHANGEAFTERMERGE
, IDS_APPNAME
, MB_OK
);
302 catch (CFileException
* e
)
307 CAppUtils::StartUnifiedDiffViewer(tempfile
, _T("dd"));
312 if ((size_t)cmd
> r1
->m_ParentHash
.size())
315 str
.Format(IDS_PROC_NOPARENT
, cmd
);
316 MessageBox(str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
322 hash2
= r1
->m_ParentHash
[cmd
-1].ToString();
325 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), hash2
, CTGitPath(), r1
->m_CommitHash
.ToString(), false, false, false, bMerge
, bCombine
);
328 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), _T("HEAD"), CTGitPath(), GitRev::GetWorkingCopy(), false, false, false, bMerge
, bCombine
);
332 case ID_GNUDIFF2
: // compare two revisions, unified
334 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
335 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
336 CAppUtils::StartShowUnifiedDiff(nullptr, CTGitPath(), r2
->m_CommitHash
.ToString(), CTGitPath(), r1
->m_CommitHash
.ToString());
340 case ID_COMPARETWO
: // compare two revisions
342 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
343 GitRev
* r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
344 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
345 CGitDiff::DiffCommit(this->m_Path
, r1
,r2
);
348 CString path1
= m_Path
.GetGitPathString();
349 // start with 1 (0 = working copy changes)
350 for (int i
= 1; i
< FirstSelect
; ++i
)
352 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(i
));
353 CTGitPathList list
= first
->GetFiles(NULL
);
354 const CTGitPath
* file
= list
.LookForGitPath(path1
);
355 if (file
&& !file
->GetGitOldPathString().IsEmpty())
356 path1
= file
->GetGitOldPathString();
358 CString path2
= path1
;
359 for (int i
= FirstSelect
; i
< LastSelect
; ++i
)
361 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(i
));
362 CTGitPathList list
= first
->GetFiles(NULL
);
363 const CTGitPath
* file
= list
.LookForGitPath(path2
);
364 if (file
&& !file
->GetGitOldPathString().IsEmpty())
365 path2
= file
->GetGitOldPathString();
367 CGitDiff::DiffCommit(CTGitPath(path1
), CTGitPath(path2
), r1
, r2
);
373 case ID_COMPARE
: // compare revision with WC
375 GitRevLoglist
* r1
= &m_wcRev
;
376 GitRevLoglist
* r2
= pSelLogEntry
;
378 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
379 CGitDiff::DiffCommit(this->m_Path
, r1
,r2
);
382 CString path1
= m_Path
.GetGitPathString();
383 // start with 1 (0 = working copy changes)
384 for (int i
= 1; i
< FirstSelect
; ++i
)
386 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(i
));
387 CTGitPathList list
= first
->GetFiles(NULL
);
388 const CTGitPath
* file
= list
.LookForGitPath(path1
);
389 if (file
&& !file
->GetGitOldPathString().IsEmpty())
390 path1
= file
->GetGitOldPathString();
392 CGitDiff::DiffCommit(m_Path
, CTGitPath(path1
), r1
, r2
);
395 //user clicked on the menu item "compare with working copy"
398 // GitDiff diff(this, m_hWnd, true);
399 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
400 // diff.SetHEADPeg(m_LogRevision);
401 // diff.ShowCompare(m_path, GitRev::REV_WC, m_path, revSelected);
404 // CAppUtils::StartShowCompare(m_hWnd, m_path, GitRev::REV_WC, m_path, revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
408 case ID_COMPAREWITHPREVIOUS
:
412 if (pSelLogEntry
->m_ParentHash
.empty())
414 if (pSelLogEntry
->GetParentFromHash(pSelLogEntry
->m_CommitHash
))
415 MessageBox(pSelLogEntry
->GetLastErr(), _T("TortoiseGit"), MB_ICONERROR
);
418 if (!pSelLogEntry
->m_ParentHash
.empty())
419 //if(m_logEntries.m_HashMap[pSelLogEntry->m_ParentHash[0]]>=0)
427 if (m_Path
.IsDirectory() || !(m_ShowMask
& CGit::LOG_INFO_FOLLOW
))
428 CGitDiff::DiffCommit(m_Path
, pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString());
431 CString path1
= m_Path
.GetGitPathString();
432 // start with 1 (0 = working copy changes)
433 for (int i
= 1; i
< indexNext
; ++i
)
435 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(i
));
436 CTGitPathList list
= first
->GetFiles(NULL
);
437 const CTGitPath
* file
= list
.LookForGitPath(path1
);
438 if (file
&& !file
->GetGitOldPathString().IsEmpty())
439 path1
= file
->GetGitOldPathString();
441 CString path2
= path1
;
442 GitRevLoglist
* first
= reinterpret_cast<GitRevLoglist
*>(m_arShownList
.GetAt(indexNext
));
443 CTGitPathList list
= first
->GetFiles(NULL
);
444 const CTGitPath
* file
= list
.LookForGitPath(path2
);
445 if (file
&& !file
->GetGitOldPathString().IsEmpty())
446 path2
= file
->GetGitOldPathString();
448 CGitDiff::DiffCommit(CTGitPath(path1
), CTGitPath(path2
), pSelLogEntry
->m_CommitHash
.ToString(), pSelLogEntry
->m_ParentHash
[cmd
- 1].ToString());
453 CMessageBox::Show(NULL
, IDS_PROC_NOPREVIOUSVERSION
, IDS_APPNAME
, MB_OK
);
457 // GitDiff diff(this, m_hWnd, true);
458 // diff.SetAlternativeTool(!!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
459 // diff.SetHEADPeg(m_LogRevision);
460 // diff.ShowCompare(CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected);
463 // CAppUtils::StartShowCompare(m_hWnd, CTGitPath(pathURL), revPrevious, CTGitPath(pathURL), revSelected, GitRev(), m_LogRevision, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
466 case ID_LOG_VIEWRANGE
:
467 case ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
:
469 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.SafeGetAt(LastSelect
));
471 CString sep
= _T("..");
472 if ((cmd
& 0xFFFF) == ID_LOG_VIEWRANGE_REACHABLEFROMONLYONE
)
476 cmdline
.Format(_T("/command:log /path:\"%s\" /range:\"%s%s%s\""),
477 (LPCTSTR
)g_Git
.CombinePath(m_Path
), (LPCTSTR
)pLastEntry
->m_CommitHash
.ToString(), (LPCTSTR
)sep
, (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString());
478 CAppUtils::RunTortoiseGitProc(cmdline
);
481 case ID_COPYCLIPBOARD
:
483 CopySelectionToClipBoard();
486 case ID_COPYCLIPBOARDMESSAGES
:
488 if ((GetAsyncKeyState(VK_SHIFT
) & 0x8000) != 0)
489 CopySelectionToClipBoard(ID_COPY_SUBJECT
);
491 CopySelectionToClipBoard(ID_COPY_MESSAGE
);
496 CopySelectionToClipBoard(ID_COPY_HASH
);
501 CString str
=pSelLogEntry
->m_CommitHash
.ToString();
502 // try to get the tag
503 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], _T("refs/tags/"), str
);
504 CAppUtils::Export(&str
, &m_Path
);
507 case ID_CREATE_BRANCH
:
510 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
511 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
514 else // try to guess remote branch in order to enable tracking
515 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], _T("refs/remotes/"), str
);
517 CAppUtils::CreateBranchTag((cmd
&0xFFFF) == ID_CREATE_TAG
, &str
);
520 m_pFindDialog
->RefreshList();
522 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
527 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
528 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
531 else // try to guess remote branch in order to recommend good branch name and tracking
532 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], _T("refs/remotes/"), str
);
534 CAppUtils::Switch(str
);
538 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
540 case ID_SWITCHBRANCH
:
543 const CString
* branch
= (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
);
546 CString name
= *branch
;
547 CGit::GetShortName(*branch
, name
, L
"refs/heads/");
548 CAppUtils::PerformSwitch(name
);
552 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
557 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
558 if (CAppUtils::GitReset(&str
))
567 SetSelectedRebaseAction(LOGACTIONS_REBASE_PICK
);
570 SetSelectedRebaseAction(LOGACTIONS_REBASE_EDIT
);
572 case ID_REBASE_SQUASH
:
573 SetSelectedRebaseAction(LOGACTIONS_REBASE_SQUASH
);
576 SetSelectedRebaseAction(LOGACTIONS_REBASE_SKIP
);
578 case ID_COMBINE_COMMIT
:
582 CGitHash hashFirst
,hashLast
;
584 int headindex
=GetHeadIndex();
585 if(headindex
>=0) //incase show all branch, head is not the first commits.
587 head
.Format(_T("HEAD~%d"),FirstSelect
-headindex
);
588 if (g_Git
.GetHash(hashFirst
, head
))
590 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of first selected revision.")), _T("TortoiseGit"), MB_ICONERROR
);
594 head
.Format(_T("HEAD~%d"),LastSelect
-headindex
);
595 if (g_Git
.GetHash(hashLast
, head
))
597 MessageBox(g_Git
.GetGitLastErr(_T("Could not get hash of last selected revision.")), _T("TortoiseGit"), MB_ICONERROR
);
602 GitRev
* pFirstEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
603 GitRev
* pLastEntry
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
604 if(pFirstEntry
->m_CommitHash
!= hashFirst
|| pLastEntry
->m_CommitHash
!= hashLast
)
606 CMessageBox::Show(NULL
, IDS_PROC_CANNOTCOMBINE
, IDS_APPNAME
, MB_OK
);
611 if (lastRevision
.GetParentFromHash(hashLast
))
613 MessageBox(lastRevision
.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR
);
617 if (g_Git
.GetHash(headhash
, _T("HEAD")))
619 MessageBox(g_Git
.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR
);
623 if(!g_Git
.CheckCleanWorkTree())
625 CMessageBox::Show(NULL
, IDS_PROC_NOCLEAN
, IDS_APPNAME
, MB_OK
);
630 //Use throw to abort this process (reset back to original HEAD)
633 sCmd
.Format(_T("git.exe reset --hard %s --"), (LPCTSTR
)pFirstEntry
->m_CommitHash
.ToString());
634 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
636 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
637 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP1
)) + _T("\r\n\r\n") + out
));
639 sCmd
.Format(_T("git.exe reset --mixed %s --"), (LPCTSTR
)hashLast
.ToString());
640 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
642 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
643 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORSTEP2
)) + _T("\r\n\r\n")+out
));
646 CTGitPathList PathList
;
647 /* don't why must add --stat to get action status*/
648 /* first -z will be omitted by gitdll*/
649 if(g_Git
.GetDiffPath(&PathList
,&pFirstEntry
->m_CommitHash
,&hashLast
,"-z --stat -r"))
651 CMessageBox::Show(NULL
,_T("Get Diff file list error"),_T("TortoiseGit"),MB_OK
);
652 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not get changed file list aborting...\r\n\r\n")+out
));
655 for (int i
= 0; i
< PathList
.GetCount(); ++i
)
657 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_ADDED
)
659 sCmd
.Format(_T("git.exe add -- \"%s\""), (LPCTSTR
)PathList
[i
].GetGitPathString());
660 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
662 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
663 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not add new file aborting...\r\n\r\n")+out
));
667 if(PathList
[i
].m_Action
& CTGitPath::LOGACTIONS_DELETED
)
669 sCmd
.Format(_T("git.exe rm -- \"%s\""), (LPCTSTR
)PathList
[i
].GetGitPathString());
670 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
672 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
673 throw std::exception(CUnicodeUtils::GetUTF8(_T("Could not rm file aborting...\r\n\r\n")+out
));
679 for (int i
= FirstSelect
; i
<= LastSelect
; ++i
)
681 GitRev
* pRev
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(i
));
682 dlg
.m_sLogMessage
+=pRev
->GetSubject()+_T("\n")+pRev
->GetBody();
683 dlg
.m_sLogMessage
+=_T("\n");
685 dlg
.m_bWholeProject
=true;
686 dlg
.m_bSelectFilesForCommit
= true;
687 dlg
.m_bForceCommitAmend
=true;
689 gpl
.AddPath(CTGitPath(g_Git
.m_CurrentDir
));
690 dlg
.m_pathList
= gpl
;
691 if (lastRevision
.ParentsCount() != 1)
693 CMessageBox::Show(NULL
, _T("The following commit dialog can only show changes of oldest commit if it has exactly one parent. This is not the case right now."), _T("TortoiseGit"),MB_OK
);
694 dlg
.m_bAmendDiffToLastCommit
= TRUE
;
697 dlg
.m_bAmendDiffToLastCommit
= FALSE
;
698 dlg
.m_bNoPostActions
=true;
699 dlg
.m_AmendStr
=dlg
.m_sLogMessage
;
701 if (dlg
.DoModal() == IDOK
)
703 if(pFirstEntry
->m_CommitHash
!=headhash
)
705 if(CherryPickFrom(pFirstEntry
->m_CommitHash
.ToString(),headhash
))
708 msg
.Format(_T("Error while cherry pick commits on top of combined commits. Aborting.\r\n\r\n"));
709 throw std::exception(CUnicodeUtils::GetUTF8(msg
));
714 throw std::exception(CUnicodeUtils::GetUTF8(CString(MAKEINTRESOURCE(IDS_USERCANCELLED
))));
716 catch(std::exception
& e
)
718 CMessageBox::Show(NULL
, CUnicodeUtils::GetUnicode(CStringA(e
.what())), _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
719 sCmd
.Format(_T("git.exe reset --hard %s --"), (LPCTSTR
)headhash
.ToString());
721 if(g_Git
.Run(sCmd
, &out
, CP_UTF8
))
723 CMessageBox::Show(NULL
, CString(MAKEINTRESOURCE(IDS_PROC_COMBINE_ERRORRESETHEAD
)) + _T("\r\n\r\n") + out
, _T("TortoiseGit"), MB_OK
);
732 if (m_bThreadRunning
)
734 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
738 dlg
.m_IsCherryPick
= TRUE
;
739 dlg
.m_Upstream
= this->m_CurrentBranch
;
740 POSITION pos2
= GetFirstSelectedItemPosition();
743 int indexNext2
= GetNextSelectedItem(pos2
);
744 dlg
.m_CommitList
.m_logEntries
.push_back(((GitRevLoglist
*)m_arShownList
[indexNext2
])->m_CommitHash
);
745 dlg
.m_CommitList
.m_LogCache
.m_HashMap
[((GitRevLoglist
*)m_arShownList
[indexNext2
])->m_CommitHash
] = *(GitRevLoglist
*)m_arShownList
[indexNext2
];
746 dlg
.m_CommitList
.m_logEntries
.GetGitRevAt(dlg
.m_CommitList
.m_logEntries
.size() - 1).GetRebaseAction() |= LOGACTIONS_REBASE_PICK
;
749 if(dlg
.DoModal() == IDOK
)
755 case ID_REBASE_TO_VERSION
:
757 if (m_bThreadRunning
)
759 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
763 auto refList
= m_HashMap
[pSelLogEntry
->m_CommitHash
];
764 dlg
.m_Upstream
= refList
.empty() ? pSelLogEntry
->m_CommitHash
.ToString() : refList
.front();
765 for (const auto& ref
: refList
)
766 if (CGit::GetShortName(ref
, dlg
.m_Upstream
, L
"refs/heads/"))
769 if(dlg
.DoModal() == IDOK
)
778 if (CAppUtils::StashSave())
783 if (CAppUtils::StashPop())
788 CAppUtils::RunTortoiseGitProc(_T("/command:reflog /ref:refs/stash"));
791 case ID_REFLOG_STASH_APPLY
:
792 CAppUtils::StashApply(pSelLogEntry
->m_Ref
);
798 if (GetSelectedCount() > 1)
799 str
.Format(IDS_PROC_DELETENREFS
, GetSelectedCount());
801 str
.Format(IDS_PROC_DELETEREF
, (LPCTSTR
)pSelLogEntry
->m_Ref
);
803 if (CMessageBox::Show(NULL
, str
, _T("TortoiseGit"), 1, IDI_QUESTION
, CString(MAKEINTRESOURCE(IDS_DELETEBUTTON
)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON
))) == 2)
806 std::vector
<CString
> refsToDelete
;
807 POSITION pos2
= GetFirstSelectedItemPosition();
810 CString ref
= ((GitRevLoglist
*)m_arShownList
[GetNextSelectedItem(pos2
)])->m_Ref
;
811 if (ref
.Find(_T("refs/")) == 0)
813 int refpos
= ref
.ReverseFind('{');
814 if (refpos
> 0 && ref
.Mid(refpos
- 1, 2) != _T("@{"))
815 ref
= ref
.Left(refpos
) + _T("@")+ ref
.Mid(refpos
);
816 refsToDelete
.push_back(ref
);
819 for (auto revIt
= refsToDelete
.crbegin(); revIt
!= refsToDelete
.crend(); ++revIt
)
821 CString ref
= *revIt
;
823 if (ref
.Find(_T("stash")) == 0)
824 sCmd
.Format(_T("git.exe stash drop %s"), (LPCTSTR
)ref
);
826 sCmd
.Format(_T("git.exe reflog delete %s"), (LPCTSTR
)ref
);
828 if (g_Git
.Run(sCmd
, &out
, CP_UTF8
))
829 CMessageBox::Show(NULL
,out
,_T("TortoiseGit"),MB_OK
);
831 ::PostMessage(this->GetParent()->m_hWnd
,MSG_REFLOG_CHANGED
,0,0);
837 CString sCmd
= _T("/command:log");
838 sCmd
+= _T(" /path:\"") + g_Git
.m_CurrentDir
+ _T("\" ");
839 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
840 sCmd
+= _T(" /endrev:") + r1
->m_CommitHash
.ToString();
841 CAppUtils::RunTortoiseGitProc(sCmd
);
844 case ID_CREATE_PATCH
:
846 int select
=this->GetSelectedCount();
847 CString sCmd
= _T("/command:formatpatch");
848 sCmd
+= _T(" /path:\"") + g_Git
.m_CurrentDir
+ _T("\" ");
850 GitRev
* r1
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
854 sCmd
+= _T(" /startrev:") + r1
->m_CommitHash
.ToString();
858 r2
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
859 if( this->m_IsOldFirst
)
861 sCmd
+= _T(" /startrev:") + r1
->m_CommitHash
.ToString() + _T("~1");
862 sCmd
+= _T(" /endrev:") + r2
->m_CommitHash
.ToString();
867 sCmd
+= _T(" /startrev:") + r2
->m_CommitHash
.ToString() + _T("~1");
868 sCmd
+= _T(" /endrev:") + r1
->m_CommitHash
.ToString();
873 CAppUtils::RunTortoiseGitProc(sCmd
);
878 GitRev
* first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
879 GitRev
* last
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(LastSelect
));
880 ASSERT(first
!= NULL
&& last
!= NULL
);
882 CString firstBad
= first
->m_CommitHash
.ToString();
883 if (!m_HashMap
[first
->m_CommitHash
].empty())
884 firstBad
= m_HashMap
[first
->m_CommitHash
].at(0);
885 CString lastGood
= last
->m_CommitHash
.ToString();
886 if (!m_HashMap
[last
->m_CommitHash
].empty())
887 lastGood
= m_HashMap
[last
->m_CommitHash
].at(0);
889 if (CAppUtils::BisectStart(lastGood
, firstBad
))
895 GitRev
*first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
896 if (CAppUtils::BisectOperation(_T("good"), !first
->m_CommitHash
.IsEmpty() ? first
->m_CommitHash
.ToString() : _T("")))
902 GitRev
*first
= reinterpret_cast<GitRev
*>(m_arShownList
.GetAt(FirstSelect
));
903 if (CAppUtils::BisectOperation(_T("bad"), !first
->m_CommitHash
.IsEmpty() ? first
->m_CommitHash
.ToString() : _T("")))
909 if (CAppUtils::BisectOperation(_T("reset")))
916 sCmd
.Format(_T("/command:repobrowser /path:\"%s\" /rev:%s"), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString());
917 CAppUtils::RunTortoiseGitProc(sCmd
);
922 CString guessAssociatedBranch
= pSelLogEntry
->m_CommitHash
;
923 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
) : nullptr;
925 guessAssociatedBranch
= *branch
;
927 GetFirstEntryStartingWith(m_HashMap
[pSelLogEntry
->m_CommitHash
], _T("refs/heads/"), guessAssociatedBranch
);
929 if (CAppUtils::Push(guessAssociatedBranch
))
935 if (CAppUtils::Pull())
941 if (CAppUtils::Fetch())
948 sCmd
.Format(_T("/command:cleanup /path:\"%s\""), (LPCTSTR
)g_Git
.m_CurrentDir
);
949 CAppUtils::RunTortoiseGitProc(sCmd
);
952 case ID_SUBMODULE_UPDATE
:
955 sCmd
.Format(_T("/command:subupdate /bkpath:\"%s\""), (LPCTSTR
)g_Git
.m_CurrentDir
);
956 CAppUtils::RunTortoiseGitProc(sCmd
);
959 case ID_SHOWBRANCHES
:
962 sCmd
.Format(_T("git.exe branch -a --contains %s"), (LPCTSTR
)pSelLogEntry
->m_CommitHash
.ToString());
963 CProgressDlg progress
;
964 progress
.m_AutoClose
= AUTOCLOSE_NO
;
965 progress
.m_GitCmd
= sCmd
;
971 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
) : nullptr;
974 CMessageBox::Show(NULL
,IDS_ERROR_NOREF
,IDS_APPNAME
,MB_OK
|MB_ICONERROR
);
978 if (branch
== (CString
*)MAKEINTRESOURCE(IDS_ALL
))
980 CString currentBranch
= L
"refs/heads/" + m_CurrentBranch
;
981 bool nothingDeleted
= true;
982 for (const auto& ref
: m_HashMap
[pSelLogEntry
->m_CommitHash
])
984 if (ref
== currentBranch
)
988 nothingDeleted
= false;
993 else if (!DeleteRef(*branch
))
995 this->ReloadHashMap();
997 m_pFindDialog
->RefreshList();
999 this->GetItemRect(FirstSelect
,&rect
,LVIR_BOUNDS
);
1000 this->InvalidateRect(rect
);
1006 m_nSearchIndex
= GetSelectionMark();
1007 if (m_nSearchIndex
< 0)
1015 m_pFindDialog
= new CFindDlg();
1016 m_pFindDialog
->Create(this);
1022 CString str
= pSelLogEntry
->m_CommitHash
.ToString();
1023 const CString
* branch
= popmenu
? (const CString
*)((CIconMenu
*)popmenu
)->GetMenuItemData(cmd
& 0xFFFF) : nullptr;
1026 else if (!m_HashMap
[pSelLogEntry
->m_CommitHash
].empty())
1027 str
= m_HashMap
[pSelLogEntry
->m_CommitHash
].at(0);
1028 // we need an URL to complete this command, so error out if we can't get an URL
1029 if(CAppUtils::Merge(&str
))
1038 if (GetSelectedCount() == 1)
1041 if ((size_t)parent
> pSelLogEntry
->m_ParentHash
.size())
1044 str
.Format(IDS_PROC_NOPARENT
, parent
);
1045 MessageBox(str
, _T("TortoiseGit"), MB_OK
| MB_ICONERROR
);
1050 if (!this->RevertSelectedCommits(parent
))
1052 if (CMessageBox::Show(m_hWnd
, IDS_REVREVERTED
, IDS_APPNAME
, 1, IDI_QUESTION
, IDS_OKBUTTON
, IDS_COMMITBUTTON
) == 2)
1054 CTGitPathList pathlist
;
1055 CTGitPathList selectedlist
;
1056 pathlist
.AddPath(this->m_Path
);
1057 bool bSelectFilesForCommit
= !!DWORD(CRegStdDWORD(_T("Software\\TortoiseGit\\SelectFilesForCommit"), TRUE
));
1059 CAppUtils::Commit(CString(), false, str
, pathlist
, selectedlist
, bSelectFilesForCommit
);
1067 CAppUtils::EditNote(pSelLogEntry
);
1068 this->SetItemState(FirstSelect
, 0, LVIS_SELECTED
);
1069 this->SetItemState(FirstSelect
, LVIS_SELECTED
, LVIS_SELECTED
);
1073 //CMessageBox::Show(NULL,_T("Have not implemented"),_T("TortoiseGit"),MB_OK);
1077 case ID_BLAMECOMPARE
:
1079 //user clicked on the menu item "compare with working copy"
1080 //now first get the revision which is selected
1083 GitDiff
diff(this, this->m_hWnd
, true);
1084 diff
.SetHEADPeg(m_LogRevision
);
1085 diff
.ShowCompare(m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), false, true);
1088 CAppUtils::StartShowCompare(m_hWnd
, m_path
, GitRev::REV_BASE
, m_path
, revSelected
, GitRev(), m_LogRevision
, false, false, true);
1091 case ID_BLAMEWITHPREVIOUS
:
1093 //user clicked on the menu item "Compare and Blame with previous revision"
1096 GitDiff
diff(this, this->m_hWnd
, true);
1097 diff
.SetHEADPeg(m_LogRevision
);
1098 diff
.ShowCompare(CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), false, true);
1101 CAppUtils::StartShowCompare(m_hWnd
, CTGitPath(pathURL
), revPrevious
, CTGitPath(pathURL
), revSelected
, GitRev(), m_LogRevision
, false, false, true);
1109 CProgressDlg progDlg
;
1110 progDlg
.SetTitle(IDS_APPNAME
);
1111 progDlg
.SetAnimation(IDR_DOWNLOAD
);
1113 sInfoLine
.Format(IDS_PROGRESSGETFILEREVISION
, m_path
.GetWinPath(), (LPCTSTR
)revSelected
.ToString());
1114 progDlg
.SetLine(1, sInfoLine
, true);
1115 SetAndClearProgressInfo(&progDlg
);
1116 progDlg
.ShowModeless(m_hWnd
);
1117 CTGitPath tempfile
= CTempFiles::Instance().GetTempFilePath(false, m_path
, revSelected
);
1118 bool bSuccess
= true;
1119 if (!Cat(m_path
, GitRev(GitRev::REV_HEAD
), revSelected
, tempfile
))
1122 // try again, but with the selected revision as the peg revision
1123 if (!Cat(m_path
, revSelected
, revSelected
, tempfile
))
1126 SetAndClearProgressInfo((HWND
)NULL
);
1127 CMessageBox::Show(this->m_hWnd
, GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
1136 SetAndClearProgressInfo((HWND
)NULL
);
1137 SetFileAttributes(tempfile
.GetWinPath(), FILE_ATTRIBUTE_READONLY
);
1139 CAppUtils::ShellOpen(tempfile
.GetWinPath(), GetSafeHwnd());
1141 CAppUtils::ShowOpenWithDialog(tempfile
.GetWinPathString(), GetSafeHwnd());
1148 dlg
.EndRev
= revSelected
;
1149 if (dlg
.DoModal() == IDOK
)
1154 tempfile
= blame
.BlameToTempFile(m_path
, dlg
.StartRev
, dlg
.EndRev
, dlg
.EndRev
, logfile
, _T(""), dlg
.m_bIncludeMerge
, TRUE
, TRUE
);
1155 if (!tempfile
.IsEmpty())
1157 if (dlg
.m_bTextView
)
1159 //open the default text editor for the result file
1160 CAppUtils::StartTextViewer(tempfile
);
1164 CString sParams
= _T("/path:\"") + m_path
.GetGitPathString() + _T("\" ");
1165 if(!CAppUtils::LaunchTortoiseBlame(tempfile
, logfile
, CPathUtils::GetFileNameFromPath(m_path
.GetFileOrDirectoryName()),sParams
))
1173 CMessageBox::Show(this->m_hWnd
, blame
.GetLastErrorMessage(), _T("TortoiseGit"), MB_ICONERROR
);
1181 sCmd
.Format(_T("%s /command:export /path:\"%s\" /revision:%ld"),
1182 (LPCTSTR
)(CPathUtils::GetAppDirectory()+_T("TortoiseGitProc.exe")),
1183 (LPCTSTR
)pathURL
, (LONG
)revSelected
);
1184 CAppUtils::LaunchApplication(sCmd
, NULL
, false);
1189 CString url
= m_ProjectProperties
.sWebViewerRev
;
1190 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1191 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
1193 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
1196 case ID_VIEWPATHREV
:
1198 CString relurl
= pathURL
;
1199 CString sRoot
= GetRepositoryRoot(CTGitPath(relurl
));
1200 relurl
= relurl
.Mid(sRoot
.GetLength());
1201 CString url
= m_ProjectProperties
.sWebViewerPathRev
;
1202 url
= GetAbsoluteUrlFromRelativeUrl(url
);
1203 url
.Replace(_T("%REVISION%"), revSelected
.ToString());
1204 url
.Replace(_T("%PATH%"), relurl
);
1206 ShellExecute(this->m_hWnd
, _T("open"), url
, NULL
, NULL
, SW_SHOWDEFAULT
);
1213 theApp
.DoWaitCursor(-1);
1216 void CGitLogList::SetSelectedRebaseAction(int action
)
1218 POSITION pos
= GetFirstSelectedItemPosition();
1223 index
= GetNextSelectedItem(pos
);
1224 if (((GitRevLoglist
*)m_arShownList
[index
])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
) || (index
== GetItemCount() - 1 && action
== LOGACTIONS_REBASE_SQUASH
))
1226 if (!m_bIsCherryPick
&& ((GitRevLoglist
*)m_arShownList
[index
])->ParentsCount() > 1 && action
== LOGACTIONS_REBASE_SQUASH
)
1228 ((GitRevLoglist
*)m_arShownList
[index
])->GetRebaseAction() = action
;
1230 this->GetItemRect(index
,&rect
,LVIR_BOUNDS
);
1231 this->InvalidateRect(rect
);
1234 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1237 void CGitLogList::SetUnselectedRebaseAction(int action
)
1239 POSITION pos
= GetFirstSelectedItemPosition();
1240 int index
= pos
? GetNextSelectedItem(pos
) : -1;
1241 for (int i
= 0; i
< GetItemCount(); i
++)
1245 index
= pos
? GetNextSelectedItem(pos
) : -1;
1249 if (((GitRevLoglist
*)m_arShownList
[i
])->GetRebaseAction() & (LOGACTIONS_REBASE_CURRENT
| LOGACTIONS_REBASE_DONE
) || (i
== GetItemCount() - 1 && action
== LOGACTIONS_REBASE_SQUASH
) || (!m_bIsCherryPick
&& action
== LOGACTIONS_REBASE_SQUASH
&& ((GitRevLoglist
*)m_arShownList
[i
])->ParentsCount() != 1))
1251 ((GitRevLoglist
*)m_arShownList
[i
])->GetRebaseAction() = action
;
1253 this->GetItemRect(i
, &rect
, LVIR_BOUNDS
);
1254 this->InvalidateRect(rect
);
1257 GetParent()->PostMessage(CGitLogListBase::m_RebaseActionMessage
);
1260 void CGitLogList::ShiftSelectedRebaseAction()
1262 POSITION pos
= GetFirstSelectedItemPosition();
1266 index
= GetNextSelectedItem(pos
);
1267 int *action
= &((GitRevLoglist
*)m_arShownList
[index
])->GetRebaseAction();
1270 case LOGACTIONS_REBASE_PICK
:
1271 *action
= LOGACTIONS_REBASE_SKIP
;
1273 case LOGACTIONS_REBASE_SKIP
:
1274 *action
= LOGACTIONS_REBASE_EDIT
;
1276 case LOGACTIONS_REBASE_EDIT
:
1277 *action
= LOGACTIONS_REBASE_SQUASH
;
1278 if (index
== GetItemCount() - 1 && (m_bIsCherryPick
|| ((GitRevLoglist
*)m_arShownList
[index
])->m_ParentHash
.size() == 1))
1279 *action
= LOGACTIONS_REBASE_PICK
;
1281 case LOGACTIONS_REBASE_SQUASH
:
1282 *action
= LOGACTIONS_REBASE_PICK
;
1286 this->GetItemRect(index
, &rect
, LVIR_BOUNDS
);
1287 this->InvalidateRect(rect
);