Add U and D keyboard shortcuts in rebase dialog
[TortoiseGit.git] / src / TortoiseProc / RebaseDlg.cpp
blobae440756b2cc026ae1a63de25f216b58d386cab9
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2016 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // RebaseDlg.cpp : implementation file
23 #include "stdafx.h"
24 #include "TortoiseProc.h"
25 #include "RebaseDlg.h"
26 #include "AppUtils.h"
27 #include "LoglistUtils.h"
28 #include "MessageBox.h"
29 #include "UnicodeUtils.h"
30 #include "BrowseRefsDlg.h"
31 #include "ProgressDlg.h"
32 #include "SmartHandle.h"
33 #include "../TGitCache/CacheInterface.h"
34 #include "Settings\Settings.h"
35 #include "MassiveGitTask.h"
36 #include "CommitDlg.h"
37 #include "StringUtils.h"
39 // CRebaseDlg dialog
41 IMPLEMENT_DYNAMIC(CRebaseDlg, CResizableStandAloneDialog)
43 CRebaseDlg::CRebaseDlg(CWnd* pParent /*=nullptr*/)
44 : CResizableStandAloneDialog(CRebaseDlg::IDD, pParent)
45 , m_bAddCherryPickedFrom(FALSE)
46 , m_bStatusWarning(false)
47 , m_bAutoSkipFailedCommit(FALSE)
48 , m_bFinishedRebase(false)
49 , m_bStashed(false)
50 , m_bSplitCommit(FALSE)
51 , m_bPreserveMerges(FALSE)
52 , m_bRebaseAutoStart(false)
53 , m_bRebaseAutoEnd(false)
55 m_RebaseStage=CHOOSE_BRANCH;
56 m_CurrentRebaseIndex=-1;
57 m_bThreadRunning =FALSE;
58 this->m_IsCherryPick = FALSE;
59 m_bForce=FALSE;
60 m_IsFastForward=FALSE;
63 CRebaseDlg::~CRebaseDlg()
67 void CRebaseDlg::DoDataExchange(CDataExchange* pDX)
69 CDialog::DoDataExchange(pDX);
70 DDX_Control(pDX, IDC_REBASE_PROGRESS, m_ProgressBar);
71 DDX_Control(pDX, IDC_STATUS_STATIC, m_CtrlStatusText);
72 DDX_Control(pDX, IDC_REBASE_SPLIT, m_wndSplitter);
73 DDX_Control(pDX,IDC_COMMIT_LIST,m_CommitList);
74 DDX_Control(pDX,IDC_REBASE_COMBOXEX_BRANCH, this->m_BranchCtrl);
75 DDX_Control(pDX,IDC_REBASE_COMBOXEX_UPSTREAM, this->m_UpstreamCtrl);
76 DDX_Check(pDX, IDC_REBASE_CHECK_FORCE,m_bForce);
77 DDX_Check(pDX, IDC_REBASE_CHECK_PRESERVEMERGES, m_bPreserveMerges);
78 DDX_Check(pDX, IDC_CHECK_CHERRYPICKED_FROM, m_bAddCherryPickedFrom);
79 DDX_Control(pDX,IDC_REBASE_POST_BUTTON,m_PostButton);
80 DDX_Control(pDX, IDC_SPLITALLOPTIONS, m_SplitAllOptions);
81 DDX_Check(pDX, IDC_REBASE_SPLIT_COMMIT, m_bSplitCommit);
85 BEGIN_MESSAGE_MAP(CRebaseDlg, CResizableStandAloneDialog)
86 ON_BN_CLICKED(IDC_REBASE_SPLIT, &CRebaseDlg::OnBnClickedRebaseSplit)
87 ON_BN_CLICKED(IDC_REBASE_CONTINUE,OnBnClickedContinue)
88 ON_BN_CLICKED(IDC_REBASE_ABORT, OnBnClickedAbort)
89 ON_WM_SIZE()
90 ON_CBN_SELCHANGE(IDC_REBASE_COMBOXEX_BRANCH, &CRebaseDlg::OnCbnSelchangeBranch)
91 ON_CBN_SELCHANGE(IDC_REBASE_COMBOXEX_UPSTREAM, &CRebaseDlg::OnCbnSelchangeUpstream)
92 ON_MESSAGE(MSG_REBASE_UPDATE_UI, OnRebaseUpdateUI)
93 ON_REGISTERED_MESSAGE(CGitStatusListCtrl::GITSLNM_NEEDSREFRESH, OnGitStatusListCtrlNeedsRefresh)
94 ON_BN_CLICKED(IDC_BUTTON_REVERSE, OnBnClickedButtonReverse)
95 ON_BN_CLICKED(IDC_BUTTON_BROWSE, &CRebaseDlg::OnBnClickedButtonBrowse)
96 ON_BN_CLICKED(IDC_REBASE_CHECK_FORCE, &CRebaseDlg::OnBnClickedRebaseCheckForce)
97 ON_BN_CLICKED(IDC_REBASE_CHECK_PRESERVEMERGES, &CRebaseDlg::OnBnClickedRebaseCheckForce)
98 ON_BN_CLICKED(IDC_CHECK_CHERRYPICKED_FROM, &CRebaseDlg::OnBnClickedCheckCherryPickedFrom)
99 ON_BN_CLICKED(IDC_REBASE_POST_BUTTON, &CRebaseDlg::OnBnClickedRebasePostButton)
100 ON_BN_CLICKED(IDC_BUTTON_UP2, &CRebaseDlg::OnBnClickedButtonUp2)
101 ON_BN_CLICKED(IDC_BUTTON_DOWN2, &CRebaseDlg::OnBnClickedButtonDown2)
102 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
103 ON_NOTIFY(LVN_ITEMCHANGED, IDC_COMMIT_LIST, OnLvnItemchangedLoglist)
104 ON_REGISTERED_MESSAGE(CGitLogListBase::m_RebaseActionMessage, OnRebaseActionMessage)
105 ON_WM_CTLCOLOR()
106 ON_BN_CLICKED(IDC_SPLITALLOPTIONS, &CRebaseDlg::OnBnClickedSplitAllOptions)
107 ON_BN_CLICKED(IDC_REBASE_SPLIT_COMMIT, &CRebaseDlg::OnBnClickedRebaseSplitCommit)
108 ON_BN_CLICKED(IDC_BUTTON_ONTO, &CRebaseDlg::OnBnClickedButtonOnto)
109 ON_BN_CLICKED(IDHELP, OnHelp)
110 END_MESSAGE_MAP()
112 void CRebaseDlg::CleanUpRebaseActiveFolder()
114 if (m_IsCherryPick)
115 return;
116 CString adminDir;
117 if (GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
118 RemoveDirectory(adminDir + L"tgitrebase.active");
121 void CRebaseDlg::AddRebaseAnchor()
123 AddAnchor(IDC_REBASE_TAB,TOP_LEFT,BOTTOM_RIGHT);
124 AddAnchor(IDC_COMMIT_LIST,TOP_LEFT, TOP_RIGHT);
125 AddAnchor(IDC_REBASE_SPLIT,TOP_LEFT, TOP_RIGHT);
126 AddAnchor(IDC_STATUS_STATIC, BOTTOM_LEFT,BOTTOM_RIGHT);
127 AddAnchor(IDC_REBASE_CONTINUE,BOTTOM_RIGHT);
128 AddAnchor(IDC_REBASE_ABORT, BOTTOM_RIGHT);
129 AddAnchor(IDC_REBASE_PROGRESS,BOTTOM_LEFT, BOTTOM_RIGHT);
130 AddAnchor(IDC_SPLITALLOPTIONS, TOP_LEFT);
131 AddAnchor(IDC_BUTTON_UP2,TOP_LEFT);
132 AddAnchor(IDC_BUTTON_DOWN2,TOP_LEFT);
133 AddAnchor(IDC_REBASE_COMBOXEX_UPSTREAM, TOP_CENTER, TOP_RIGHT);
134 AddAnchor(IDC_REBASE_COMBOXEX_BRANCH, TOP_LEFT, TOP_CENTER);
135 AddAnchor(IDC_BUTTON_REVERSE, TOP_CENTER);
136 AddAnchor(IDC_BUTTON_BROWSE, TOP_RIGHT);
137 AddAnchor(IDC_BUTTON_ONTO, TOP_RIGHT);
138 AddAnchor(IDC_REBASE_STATIC_UPSTREAM, TOP_CENTER);
139 AddAnchor(IDC_REBASE_STATIC_BRANCH,TOP_LEFT);
140 AddAnchor(IDHELP, BOTTOM_RIGHT);
141 AddAnchor(IDC_REBASE_CHECK_FORCE,TOP_RIGHT);
142 AddAnchor(IDC_REBASE_CHECK_PRESERVEMERGES, TOP_LEFT);
143 AddAnchor(IDC_CHECK_CHERRYPICKED_FROM, TOP_RIGHT);
144 AddAnchor(IDC_REBASE_SPLIT_COMMIT, BOTTOM_RIGHT);
145 AddAnchor(IDC_REBASE_POST_BUTTON,BOTTOM_LEFT);
147 this->AddOthersToAnchor();
150 BOOL CRebaseDlg::OnInitDialog()
152 CResizableStandAloneDialog::OnInitDialog();
153 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
155 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
156 // do this, Explorer would be unable to send that message to our window if we
157 // were running elevated. It's OK to make the call all the time, since if we're
158 // not elevated, this is a no-op.
159 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
160 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
161 CAutoLibrary hUser = AtlLoadSystemLibraryUsingFullPath(_T("user32.dll"));
162 if (hUser)
164 ChangeWindowMessageFilterExDFN *pfnChangeWindowMessageFilterEx = (ChangeWindowMessageFilterExDFN*)GetProcAddress(hUser, "ChangeWindowMessageFilterEx");
165 if (pfnChangeWindowMessageFilterEx)
167 pfnChangeWindowMessageFilterEx(m_hWnd, TaskBarButtonCreated, MSGFLT_ALLOW, &cfs);
170 m_pTaskbarList.Release();
171 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
172 m_pTaskbarList = nullptr;
174 CRect rectDummy;
175 //IDC_REBASE_DUMY_TAB
177 GetClientRect(m_DlgOrigRect);
178 m_CommitList.GetClientRect(m_CommitListOrigRect);
180 CWnd *pwnd=this->GetDlgItem(IDC_REBASE_DUMY_TAB);
181 pwnd->GetWindowRect(&rectDummy);
182 this->ScreenToClient(rectDummy);
184 if (!m_ctrlTabCtrl.Create(CMFCTabCtrl::STYLE_FLAT, rectDummy, this, IDC_REBASE_TAB))
186 TRACE0("Failed to create output tab window\n");
187 return FALSE; // fail to create
189 m_ctrlTabCtrl.SetResizeMode(CMFCTabCtrl::RESIZE_NO);
190 // Create output panes:
191 //const DWORD dwStyle = LBS_NOINTEGRALHEIGHT | WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL;
192 DWORD dwStyle =LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP | WS_CHILD | WS_VISIBLE;
194 if (! this->m_FileListCtrl.Create(dwStyle,rectDummy,&this->m_ctrlTabCtrl,0) )
196 TRACE0("Failed to create output windows\n");
197 return FALSE; // fail to create
199 m_FileListCtrl.m_hwndLogicalParent = this;
201 if( ! this->m_LogMessageCtrl.Create(_T("Scintilla"),_T("source"),0,rectDummy,&m_ctrlTabCtrl,0,0) )
203 TRACE0("Failed to create log message control");
204 return FALSE;
206 m_ProjectProperties.ReadProps();
207 m_LogMessageCtrl.Init(m_ProjectProperties);
208 m_LogMessageCtrl.SetFont((CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8));
209 m_LogMessageCtrl.Call(SCI_SETREADONLY, TRUE);
211 dwStyle = LBS_NOINTEGRALHEIGHT | WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL;
213 if (!m_wndOutputRebase.Create(_T("Scintilla"),_T("source"),0,rectDummy, &m_ctrlTabCtrl, 0,0) )
215 TRACE0("Failed to create output windows\n");
216 return -1; // fail to create
218 m_wndOutputRebase.Init(-1);
219 m_wndOutputRebase.SetFont((CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8));
220 m_wndOutputRebase.Call(SCI_SETREADONLY, TRUE);
222 m_tooltips.AddTool(IDC_REBASE_CHECK_FORCE,IDS_REBASE_FORCE_TT);
223 m_tooltips.AddTool(IDC_REBASE_ABORT, IDS_REBASE_ABORT_TT);
224 m_tooltips.AddTool(IDC_REBASE_CHECK_PRESERVEMERGES, IDS_REBASE_PRESERVEMERGES_TT);
227 CString temp;
228 temp.LoadString(IDS_PROC_REBASE_SELECTALL_PICK);
229 m_SplitAllOptions.AddEntry(temp);
230 temp.LoadString(IDS_PROC_REBASE_SELECTALL_SQUASH);
231 m_SplitAllOptions.AddEntry(temp);
232 temp.LoadString(IDS_PROC_REBASE_SELECTALL_EDIT);
233 m_SplitAllOptions.AddEntry(temp);
234 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_SKIP);
235 m_SplitAllOptions.AddEntry(temp);
236 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_SQUASH);
237 m_SplitAllOptions.AddEntry(temp);
238 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_EDIT);
239 m_SplitAllOptions.AddEntry(temp);
242 m_FileListCtrl.Init(GITSLC_COLEXT | GITSLC_COLSTATUS |GITSLC_COLADD|GITSLC_COLDEL , _T("RebaseDlg"),(GITSLC_POPALL ^ (GITSLC_POPCOMMIT|GITSLC_POPRESTORE)), false, true, GITSLC_COLEXT | GITSLC_COLSTATUS | GITSLC_COLADD| GITSLC_COLDEL);
244 m_ctrlTabCtrl.AddTab(&m_FileListCtrl, CString(MAKEINTRESOURCE(IDS_PROC_REVISIONFILES)));
245 m_ctrlTabCtrl.AddTab(&m_LogMessageCtrl, CString(MAKEINTRESOURCE(IDS_PROC_COMMITMESSAGE)), 1);
246 AddRebaseAnchor();
248 AdjustControlSize(IDC_CHECK_CHERRYPICKED_FROM);
249 AdjustControlSize(IDC_REBASE_SPLIT_COMMIT);
251 CString sWindowTitle;
252 GetWindowText(sWindowTitle);
253 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
255 EnableSaveRestore(_T("RebaseDlg"));
257 DWORD yPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RebaseDlgSizer"));
258 RECT rcDlg, rcLogMsg, rcFileList;
259 GetClientRect(&rcDlg);
260 m_CommitList.GetWindowRect(&rcLogMsg);
261 ScreenToClient(&rcLogMsg);
262 this->m_ctrlTabCtrl.GetWindowRect(&rcFileList);
263 ScreenToClient(&rcFileList);
264 if (yPos)
266 RECT rectSplitter;
267 m_wndSplitter.GetWindowRect(&rectSplitter);
268 ScreenToClient(&rectSplitter);
269 int delta = yPos - rectSplitter.top;
270 if ((rcLogMsg.bottom + delta > rcLogMsg.top)&&(rcLogMsg.bottom + delta < rcFileList.bottom - 30))
272 m_wndSplitter.SetWindowPos(nullptr, 0, yPos, 0, 0, SWP_NOSIZE);
273 DoSize(delta);
277 if (this->m_RebaseStage == CHOOSE_BRANCH && !m_IsCherryPick)
278 this->LoadBranchInfo();
279 else
281 this->m_BranchCtrl.EnableWindow(FALSE);
282 this->m_UpstreamCtrl.EnableWindow(FALSE);
283 GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(FALSE);
286 m_CommitList.m_ColumnRegKey = _T("Rebase");
287 m_CommitList.m_IsIDReplaceAction = TRUE;
288 // m_CommitList.m_IsOldFirst = TRUE;
289 m_CommitList.m_IsRebaseReplaceGraph = TRUE;
290 m_CommitList.m_bNoHightlightHead = TRUE;
291 m_CommitList.m_bIsCherryPick = !!m_IsCherryPick;
293 m_CommitList.InsertGitColumn();
295 this->SetControlEnable();
297 if(m_IsCherryPick)
299 this->m_BranchCtrl.SetCurSel(-1);
300 this->m_BranchCtrl.EnableWindow(FALSE);
301 GetDlgItem(IDC_REBASE_CHECK_FORCE)->ShowWindow(SW_HIDE);
302 GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->ShowWindow(SW_HIDE);
303 GetDlgItem(IDC_BUTTON_BROWSE)->EnableWindow(FALSE);
304 GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(FALSE);
305 GetDlgItem(IDC_BUTTON_ONTO)->EnableWindow(FALSE);
306 this->m_UpstreamCtrl.AddString(_T("HEAD"));
307 this->m_UpstreamCtrl.EnableWindow(FALSE);
308 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
309 this->m_CommitList.StartFilter();
311 else
313 ((CButton*)GetDlgItem(IDC_BUTTON_ONTO))->SetCheck(m_Onto.IsEmpty() ? BST_UNCHECKED : BST_CHECKED);
314 GetDlgItem(IDC_CHECK_CHERRYPICKED_FROM)->ShowWindow(SW_HIDE);
315 ((CButton *)GetDlgItem(IDC_BUTTON_REVERSE))->SetIcon((HICON)LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_SWITCHLEFTRIGHT), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR));
316 SetContinueButtonText();
317 m_CommitList.DeleteAllItems();
318 FetchLogList();
321 m_CommitList.m_ContextMenuMask &= ~(m_CommitList.GetContextMenuBit(CGitLogListBase::ID_CHERRY_PICK)|
322 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_SWITCHTOREV)|
323 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_RESET)|
324 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REVERTREV)|
325 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_MERGEREV) |
326 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_TO_VERSION)|
327 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REVERTTOREV)|
328 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_COMBINE_COMMIT));
330 if(m_CommitList.m_IsOldFirst)
331 this->m_CurrentRebaseIndex = -1;
332 else
333 this->m_CurrentRebaseIndex = (int)m_CommitList.m_logEntries.size();
335 if (GetDlgItem(IDC_REBASE_CONTINUE)->IsWindowEnabled() && m_bRebaseAutoStart)
336 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_CONTINUE, BN_CLICKED), (LPARAM)GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd());
338 return TRUE;
340 // CRebaseDlg message handlers
342 HBRUSH CRebaseDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
344 if (pWnd->GetDlgCtrlID() == IDC_STATUS_STATIC && nCtlColor == CTLCOLOR_STATIC && m_bStatusWarning)
346 pDC->SetBkColor(RGB(255, 0, 0));
347 pDC->SetTextColor(RGB(255, 255, 255));
348 return CreateSolidBrush(RGB(255, 0, 0));
351 return CResizableStandAloneDialog::OnCtlColor(pDC, pWnd, nCtlColor);
354 void CRebaseDlg::SetAllRebaseAction(int action)
356 for (size_t i = 0; i < this->m_CommitList.m_logEntries.size(); ++i)
358 if (action == CGitLogListBase::LOGACTIONS_REBASE_SQUASH && (i == this->m_CommitList.m_logEntries.size() - 1 || (!m_IsCherryPick && m_CommitList.m_logEntries.GetGitRevAt(i).ParentsCount() != 1)))
359 continue;
360 m_CommitList.m_logEntries.GetGitRevAt(i).GetRebaseAction() = action;
362 m_CommitList.Invalidate();
365 void CRebaseDlg::OnBnClickedRebaseSplit()
367 this->UpdateData();
370 LRESULT CRebaseDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
372 switch (message) {
373 case WM_NOTIFY:
374 if (wParam == IDC_REBASE_SPLIT)
376 SPC_NMHDR* pHdr = (SPC_NMHDR*) lParam;
377 DoSize(pHdr->delta);
379 break;
382 return __super::DefWindowProc(message, wParam, lParam);
385 void CRebaseDlg::DoSize(int delta)
387 this->RemoveAllAnchors();
389 CSplitterControl::ChangeHeight(GetDlgItem(IDC_COMMIT_LIST), delta, CW_TOPALIGN);
390 //CSplitterControl::ChangeHeight(GetDlgItem(), delta, CW_TOPALIGN);
391 CSplitterControl::ChangeHeight(GetDlgItem(IDC_REBASE_TAB), -delta, CW_BOTTOMALIGN);
392 //CSplitterControl::ChangeHeight(GetDlgItem(), -delta, CW_BOTTOMALIGN);
393 CSplitterControl::ChangePos(GetDlgItem(IDC_SPLITALLOPTIONS), 0, delta);
394 CSplitterControl::ChangePos(GetDlgItem(IDC_BUTTON_UP2),0,delta);
395 CSplitterControl::ChangePos(GetDlgItem(IDC_BUTTON_DOWN2),0,delta);
396 CSplitterControl::ChangePos(GetDlgItem(IDC_REBASE_CHECK_FORCE),0,delta);
397 CSplitterControl::ChangePos(GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES), 0, delta);
398 CSplitterControl::ChangePos(GetDlgItem(IDC_CHECK_CHERRYPICKED_FROM), 0, delta);
400 this->AddRebaseAnchor();
401 // adjust the minimum size of the dialog to prevent the resizing from
402 // moving the list control too far down.
403 CRect rcLogMsg;
404 m_CommitList.GetClientRect(rcLogMsg);
405 SetMinTrackSize(CSize(m_DlgOrigRect.Width(), m_DlgOrigRect.Height()-m_CommitListOrigRect.Height()+rcLogMsg.Height()));
407 SetSplitterRange();
408 // m_CommitList.Invalidate();
410 // GetDlgItem(IDC_LOGMESSAGE)->Invalidate();
412 this->m_ctrlTabCtrl.Invalidate();
413 this->m_CommitList.Invalidate();
414 this->m_FileListCtrl.Invalidate();
415 this->m_LogMessageCtrl.Invalidate();
418 void CRebaseDlg::SetSplitterRange()
420 if ((m_CommitList)&&(m_ctrlTabCtrl))
422 CRect rcTop;
423 m_CommitList.GetWindowRect(rcTop);
424 ScreenToClient(rcTop);
425 CRect rcMiddle;
426 m_ctrlTabCtrl.GetWindowRect(rcMiddle);
427 ScreenToClient(rcMiddle);
428 if (rcMiddle.Height() && rcMiddle.Width())
429 m_wndSplitter.SetRange(rcTop.top+60, rcMiddle.bottom-80);
433 void CRebaseDlg::OnSize(UINT nType,int cx, int cy)
435 // first, let the resizing take place
436 __super::OnSize(nType, cx, cy);
438 //set range
439 SetSplitterRange();
442 void CRebaseDlg::SaveSplitterPos()
444 if (!IsIconic())
446 CRegDWORD regPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RebaseDlgSizer"));
447 RECT rectSplitter;
448 m_wndSplitter.GetWindowRect(&rectSplitter);
449 ScreenToClient(&rectSplitter);
450 regPos = rectSplitter.top;
454 void CRebaseDlg::LoadBranchInfo()
456 m_BranchCtrl.SetMaxHistoryItems(0x7FFFFFFF);
457 m_UpstreamCtrl.SetMaxHistoryItems(0x7FFFFFFF);
459 STRING_VECTOR list;
460 list.clear();
461 int current = -1;
462 g_Git.GetBranchList(list,&current,CGit::BRANCH_ALL);
463 m_BranchCtrl.SetList(list);
464 if (current >= 0)
465 m_BranchCtrl.SetCurSel(current);
466 else
467 m_BranchCtrl.AddString(g_Git.GetCurrentBranch(true));
468 list.clear();
469 g_Git.GetBranchList(list, nullptr, CGit::BRANCH_ALL_F);
470 g_Git.GetTagList(list);
471 m_UpstreamCtrl.SetList(list);
473 AddBranchToolTips(&m_BranchCtrl);
475 if(!m_Upstream.IsEmpty())
476 m_UpstreamCtrl.AddString(m_Upstream);
477 else
479 //Select pull-remote from current branch
480 CString pullRemote, pullBranch;
481 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
483 CString defaultUpstream;
484 defaultUpstream.Format(L"remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
485 int found = m_UpstreamCtrl.FindStringExact(0, defaultUpstream);
486 if(found >= 0)
487 m_UpstreamCtrl.SetCurSel(found);
488 else
489 m_UpstreamCtrl.SetCurSel(-1);
491 AddBranchToolTips(&m_UpstreamCtrl);
494 void CRebaseDlg::OnCbnSelchangeBranch()
496 FetchLogList();
499 void CRebaseDlg::OnCbnSelchangeUpstream()
501 FetchLogList();
504 void CRebaseDlg::FetchLogList()
506 CGitHash base,hash,upstream;
507 m_IsFastForward=FALSE;
509 if (m_BranchCtrl.GetString().IsEmpty())
511 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_SELECTBRANCH)));
512 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
513 return;
516 if (g_Git.GetHash(hash, m_BranchCtrl.GetString()))
518 m_CommitList.ShowText(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_BranchCtrl.GetString() + _T("\".")));
519 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
520 return;
523 if (m_UpstreamCtrl.GetString().IsEmpty())
525 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_SELECTUPSTREAM)));
526 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
527 return;
530 if (g_Git.GetHash(upstream, m_UpstreamCtrl.GetString()))
532 m_CommitList.ShowText(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_UpstreamCtrl.GetString() + _T("\".")));
533 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
534 return;
537 if (hash == upstream)
539 m_CommitList.Clear();
540 CString text;
541 text.Format(IDS_REBASE_EQUAL_FMT, (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)this->m_UpstreamCtrl.GetString());
543 m_CommitList.ShowText(text);
544 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
545 if (m_bRebaseAutoStart)
546 PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_ABORT, BN_CLICKED), (LPARAM)GetDlgItem(IDC_REBASE_ABORT)->GetSafeHwnd());
547 return;
550 if (g_Git.IsFastForward(m_BranchCtrl.GetString(), m_UpstreamCtrl.GetString(), &base) && m_Onto.IsEmpty())
552 this->m_IsFastForward=TRUE;
554 m_CommitList.Clear();
555 CString text;
556 text.Format(IDS_REBASE_FASTFORWARD_FMT, (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)this->m_UpstreamCtrl.GetString(),
557 (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)this->m_UpstreamCtrl.GetString());
559 m_CommitList.ShowText(text);
560 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(true);
561 SetContinueButtonText();
563 return ;
566 if (!m_bForce && m_Onto.IsEmpty())
568 if (base == upstream)
570 m_CommitList.Clear();
571 CString text;
572 text.Format(IDS_REBASE_UPTODATE_FMT, (LPCTSTR)m_BranchCtrl.GetString());
573 m_CommitList.ShowText(text);
574 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(FALSE);
575 SetContinueButtonText();
576 if (m_bRebaseAutoStart)
577 PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_ABORT, BN_CLICKED), (LPARAM)GetDlgItem(IDC_REBASE_ABORT)->GetSafeHwnd());
578 return;
582 m_CommitList.Clear();
583 CString refFrom = g_Git.FixBranchName(m_UpstreamCtrl.GetString());
584 CString refTo = g_Git.FixBranchName(m_BranchCtrl.GetString());
585 CString range;
586 range.Format(_T("%s..%s"), (LPCTSTR)refFrom, (LPCTSTR)refTo);
587 this->m_CommitList.FillGitLog(nullptr, &range, (m_bPreserveMerges ? 0 : CGit::LOG_INFO_NO_MERGE) | CGit::LOG_ORDER_TOPOORDER);
589 if( m_CommitList.GetItemCount() == 0 )
590 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_PROC_NOTHINGTOREBASE)));
592 m_rewrittenCommitsMap.clear();
593 if (m_bPreserveMerges)
595 CGitHash head;
596 if (g_Git.GetHash(head, _T("HEAD")))
598 AddLogString(CString(MAKEINTRESOURCE(IDS_PROC_NOHEAD)));
599 return;
601 CGitHash upstreamHash;
602 if (g_Git.GetHash(upstreamHash, m_Onto.IsEmpty() ? m_UpstreamCtrl.GetString() : m_Onto))
604 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + (m_Onto.IsEmpty() ? m_UpstreamCtrl.GetString() : m_Onto) + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
605 return;
607 CString mergecmd;
608 mergecmd.Format(L"git merge-base --all %s %s", (LPCTSTR)head.ToString(), (LPCTSTR)upstreamHash.ToString());
609 g_Git.Run(mergecmd, [&](const CStringA& line)
611 CGitHash hash;
612 hash.ConvertFromStrA(line);
613 if (hash.IsEmpty())
614 return;
615 m_rewrittenCommitsMap[hash] = upstreamHash;
618 std::vector<size_t> toDrop;
619 for (size_t i = m_CommitList.m_arShownList.size(); i-- > 0;)
621 bool preserve = false;
622 GitRevLoglist* pRev = m_CommitList.m_arShownList.SafeGetAt(i);
623 for (const auto& parent : pRev->m_ParentHash)
625 const auto rewrittenParent = m_rewrittenCommitsMap.find(parent);
626 if (rewrittenParent != m_rewrittenCommitsMap.cend())
628 preserve = true;
629 break;
632 if (preserve)
633 m_rewrittenCommitsMap[pRev->m_CommitHash] = CGitHash();
634 else
635 toDrop.push_back(i);
638 // Drop already included commits
639 std::vector<CGitHash> nonCherryPicked;
640 CString cherryCmd;
641 cherryCmd.Format(L"git rev-list \"%s...%s\" --left-right --cherry-pick", (LPCTSTR)refFrom, (LPCTSTR)refTo);
642 g_Git.Run(cherryCmd, [&](const CStringA& line)
644 if (line.GetLength() < 2)
645 return;
646 if (line[0] != '>')
647 return;
648 CString hash = CUnicodeUtils::GetUnicode(line.Mid(1));
649 hash.Trim();
650 nonCherryPicked.emplace_back(hash);
652 for (size_t i = m_CommitList.m_arShownList.size(); i-- > 0;)
654 GitRevLoglist* pRev = m_CommitList.m_arShownList.SafeGetAt(i);
655 pRev->GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_PICK;
656 if (m_rewrittenCommitsMap.find(pRev->m_CommitHash) != m_rewrittenCommitsMap.cend() && std::find(nonCherryPicked.cbegin(), nonCherryPicked.cend(), pRev->m_CommitHash) == nonCherryPicked.cend())
658 m_droppedCommitsMap[pRev->m_CommitHash].clear();
659 m_droppedCommitsMap[pRev->m_CommitHash].push_back(pRev->m_ParentHash[0]);
660 toDrop.push_back(i);
661 m_rewrittenCommitsMap.erase(pRev->m_CommitHash);
664 std::sort(toDrop.begin(), toDrop.end());
665 toDrop.erase(unique(toDrop.begin(), toDrop.end()), toDrop.end());
666 for (auto it = toDrop.crbegin(); it != toDrop.crend(); ++it)
668 m_CommitList.m_arShownList.SafeRemoveAt(*it);
669 m_CommitList.m_logEntries.erase(m_CommitList.m_logEntries.begin() + *it);
671 m_CommitList.SetItemCountEx((int)m_CommitList.m_logEntries.size());
674 #if 0
675 if(m_CommitList.m_logEntries[m_CommitList.m_logEntries.size()-1].m_ParentHash.size() >=0 )
677 if(upstream == m_CommitList.m_logEntries[m_CommitList.m_logEntries.size()-1].m_ParentHash[0])
679 m_CommitList.Clear();
680 m_CommitList.ShowText(_T("Nothing Rebase"));
683 #endif
685 m_tooltips.Pop();
686 AddBranchToolTips(&this->m_BranchCtrl);
687 AddBranchToolTips(&this->m_UpstreamCtrl);
689 bool bHasSKip = false;
690 if (!m_bPreserveMerges)
692 // Default all actions to 'pick'
693 std::map<CGitHash, size_t> revIxMap;
694 for (size_t i = 0; i < m_CommitList.m_logEntries.size(); ++i)
696 GitRevLoglist& rev = m_CommitList.m_logEntries.GetGitRevAt(i);
697 rev.GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_PICK;
698 revIxMap[rev.m_CommitHash] = i;
701 // Default to skip when already in upstream
702 if (!m_Onto.IsEmpty())
703 refFrom = g_Git.FixBranchName(m_Onto);
704 CString cherryCmd;
705 cherryCmd.Format(L"git.exe cherry \"%s\" \"%s\"", (LPCTSTR)refFrom, (LPCTSTR)refTo);
706 g_Git.Run(cherryCmd, [&](const CStringA& line)
708 if (line.GetLength() < 2)
709 return;
710 if (line[0] != '-')
711 return; // Don't skip (only skip commits starting with a '-')
712 CString hash = CUnicodeUtils::GetUnicode(line.Mid(1));
713 hash.Trim();
714 auto itIx = revIxMap.find(CGitHash(hash));
715 if (itIx == revIxMap.end())
716 return; // Not found?? Should not occur...
718 // Found. Skip it.
719 m_CommitList.m_logEntries.GetGitRevAt(itIx->second).GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_SKIP;
720 bHasSKip = true;
723 m_CommitList.Invalidate();
724 if (bHasSKip)
726 m_CtrlStatusText.SetWindowText(CString(MAKEINTRESOURCE(IDS_REBASE_AUTOSKIPPED)));
727 m_bStatusWarning = true;
729 else
731 m_CtrlStatusText.SetWindowText(m_sStatusText);
732 m_bStatusWarning = false;
734 m_CtrlStatusText.Invalidate();
736 if(m_CommitList.m_IsOldFirst)
737 this->m_CurrentRebaseIndex = -1;
738 else
739 this->m_CurrentRebaseIndex = (int)m_CommitList.m_logEntries.size();
741 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(m_bPreserveMerges || m_CommitList.GetItemCount());
742 SetContinueButtonText();
745 void CRebaseDlg::AddBranchToolTips(CHistoryCombo *pBranch)
747 if(pBranch)
749 CString text=pBranch->GetString();
750 CString tooltip;
752 if (text.IsEmpty())
754 pBranch->DisableTooltip();
755 return;
758 GitRev rev;
759 if (rev.GetCommit(text))
761 MessageBox(rev.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
762 pBranch->DisableTooltip();
763 return;
766 tooltip.Format(_T("%s: %s\n%s: %s <%s>\n%s: %s\n%s:\n%s\n%s"),
767 (LPCTSTR)CString(MAKEINTRESOURCE(IDS_LOG_REVISION)),
768 (LPCTSTR)rev.m_CommitHash.ToString(),
769 (LPCTSTR)CString(MAKEINTRESOURCE(IDS_LOG_AUTHOR)),
770 (LPCTSTR)rev.GetAuthorName(),
771 (LPCTSTR)rev.GetAuthorEmail(),
772 (LPCTSTR)CString(MAKEINTRESOURCE(IDS_LOG_DATE)),
773 (LPCTSTR)CLoglistUtils::FormatDateAndTime(rev.GetAuthorDate(), DATE_LONGDATE),
774 (LPCTSTR)CString(MAKEINTRESOURCE(IDS_LOG_MESSAGE)),
775 (LPCTSTR)rev.GetSubject(),
776 (LPCTSTR)rev.GetBody());
778 pBranch->DisableTooltip();
779 this->m_tooltips.AddTool(pBranch->GetComboBoxCtrl(),tooltip);
783 BOOL CRebaseDlg::PreTranslateMessage(MSG*pMsg)
785 if (pMsg->message == WM_KEYDOWN)
787 switch (pMsg->wParam)
789 case ' ':
790 if (LogListHasFocus(pMsg->hwnd)
791 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_PICK)
792 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SQUASH)
793 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_EDIT)
794 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SKIP))
796 m_CommitList.ShiftSelectedRebaseAction();
797 return TRUE;
799 break;
800 case 'P':
801 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_PICK))
803 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_PICK);
804 return TRUE;
806 break;
807 case 'S':
808 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SKIP))
810 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SKIP);
811 return TRUE;
813 break;
814 case 'Q':
815 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SQUASH))
817 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
818 return TRUE;
820 break;
821 case 'E':
822 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_EDIT))
824 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
825 return TRUE;
827 break;
828 case 'U':
829 if (LogListHasFocus(pMsg->hwnd))
831 OnBnClickedButtonDown2();
832 return TRUE;
834 break;
835 case 'D':
836 if (LogListHasFocus(pMsg->hwnd))
838 OnBnClickedButtonUp2();
839 return TRUE;
841 break;
842 case 'A':
843 if(LogListHasFocus(pMsg->hwnd) && GetAsyncKeyState(VK_CONTROL) & 0x8000)
845 // select all entries
846 for (int i = 0; i < m_CommitList.GetItemCount(); ++i)
847 m_CommitList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
848 return TRUE;
850 break;
851 case VK_F5:
853 Refresh();
854 return TRUE;
856 break;
857 case VK_RETURN:
859 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
861 if (GetDlgItem(IDC_REBASE_CONTINUE)->IsWindowEnabled())
862 GetDlgItem(IDC_REBASE_CONTINUE)->SetFocus();
863 else if (GetDlgItem(IDC_REBASE_ABORT)->IsWindowEnabled())
864 GetDlgItem(IDC_REBASE_ABORT)->SetFocus();
865 else
866 GetDlgItem(IDHELP)->SetFocus();
867 return TRUE;
870 break;
871 /* Avoid TAB control destroy but dialog exist*/
872 case VK_ESCAPE:
873 case VK_CANCEL:
875 TCHAR buff[128] = { 0 };
876 ::GetClassName(pMsg->hwnd,buff,128);
879 /* Use MSFTEDIT_CLASS http://msdn.microsoft.com/en-us/library/bb531344.aspx */
880 if (_tcsnicmp(buff, MSFTEDIT_CLASS, 128) == 0 || //Unicode and MFC 2012 and later
881 _tcsnicmp(buff, RICHEDIT_CLASS, 128) == 0 || //ANSI or MFC 2010
882 _tcsnicmp(buff,_T("Scintilla"),128)==0 ||
883 _tcsnicmp(buff,_T("SysListView32"),128)==0||
884 ::GetParent(pMsg->hwnd) == this->m_ctrlTabCtrl.m_hWnd)
886 this->PostMessage(WM_KEYDOWN,VK_ESCAPE,0);
887 return TRUE;
892 else if (pMsg->message == WM_NEXTDLGCTL)
894 HWND hwnd = GetFocus()->GetSafeHwnd();
895 if (hwnd == m_LogMessageCtrl.GetSafeHwnd() || hwnd == m_wndOutputRebase.GetSafeHwnd())
897 if (GetDlgItem(IDC_REBASE_CONTINUE)->IsWindowEnabled())
898 GetDlgItem(IDC_REBASE_CONTINUE)->SetFocus();
899 else if (GetDlgItem(IDC_REBASE_ABORT)->IsWindowEnabled())
900 GetDlgItem(IDC_REBASE_ABORT)->SetFocus();
901 else
902 GetDlgItem(IDHELP)->SetFocus();
903 return TRUE;
906 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
909 bool CRebaseDlg::LogListHasFocus(HWND hwnd)
911 TCHAR buff[128] = { 0 };
912 ::GetClassName(hwnd, buff, 128);
914 if(_tcsnicmp(buff, _T("SysListView32"), 128) == 0)
915 return true;
916 return false;
919 bool CRebaseDlg::LogListHasMenuItem(int i)
921 return (m_CommitList.m_ContextMenuMask & m_CommitList.GetContextMenuBit(i)) != 0;
924 int CRebaseDlg::CheckRebaseCondition()
926 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
928 if( !g_Git.CheckCleanWorkTree() )
930 if ((!m_IsCherryPick && g_Git.GetConfigValueBool(L"rebase.autostash")) || CMessageBox::Show(GetSafeHwnd(), IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
932 CString cmd,out;
933 cmd=_T("git.exe stash");
934 this->AddLogString(cmd);
935 if (g_Git.Run(cmd, &out, CP_UTF8))
937 MessageBox(out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
938 return -1;
940 m_bStashed = true;
942 else
943 return -1;
945 //Todo Check $REBASE_ROOT
946 //Todo Check $DOTEST
948 if (!CAppUtils::CheckUserData())
949 return -1;
951 //Todo call pre_rebase_hook
952 return 0;
955 void CRebaseDlg::CheckRestoreStash()
957 bool autoStash = !m_IsCherryPick && g_Git.GetConfigValueBool(L"rebase.autostash");
958 if (m_bStashed && (autoStash || CMessageBox::Show(GetSafeHwnd(), IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) == IDYES))
959 CAppUtils::StashPop(autoStash ? 0 : 1);
960 m_bStashed = false;
963 int CRebaseDlg::StartRebase()
965 CString cmd,out;
966 m_OrigHEADBranch = g_Git.GetCurrentBranch(true);
968 m_OrigHEADHash.Empty();
969 if (g_Git.GetHash(m_OrigHEADHash, _T("HEAD")))
971 AddLogString(CString(MAKEINTRESOURCE(IDS_PROC_NOHEAD)));
972 return -1;
974 //Todo
975 //git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
976 // echo "detached HEAD" > "$DOTEST"/head-name
978 cmd.Format(_T("git.exe update-ref ORIG_HEAD ") + m_OrigHEADHash.ToString());
979 if(g_Git.Run(cmd,&out,CP_UTF8))
981 AddLogString(_T("update ORIG_HEAD Fail"));
982 return -1;
985 m_OrigUpstreamHash.Empty();
986 if (g_Git.GetHash(m_OrigUpstreamHash, (m_IsCherryPick || m_Onto.IsEmpty()) ? m_UpstreamCtrl.GetString() : m_Onto))
988 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + (m_IsCherryPick || m_Onto.IsEmpty()) ? m_UpstreamCtrl.GetString() : m_Onto + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
989 return -1;
992 if( !this->m_IsCherryPick )
994 cmd.Format(_T("git.exe checkout -f %s --"), (LPCTSTR)m_OrigUpstreamHash.ToString());
995 this->AddLogString(cmd);
996 if (RunGitCmdRetryOrAbort(cmd))
997 return -1;
1000 CString log;
1001 if( !this->m_IsCherryPick )
1003 if (g_Git.GetHash(m_OrigBranchHash, m_BranchCtrl.GetString()))
1005 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_BranchCtrl.GetString() + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
1006 return -1;
1008 log.Format(_T("%s\r\n"), (LPCTSTR)CString(MAKEINTRESOURCE(IDS_PROC_REBASE_STARTREBASE)));
1010 else
1011 log.Format(_T("%s\r\n"), (LPCTSTR)CString(MAKEINTRESOURCE(IDS_PROC_REBASE_STARTCHERRYPICK)));
1013 this->AddLogString(log);
1014 return 0;
1016 int CRebaseDlg::VerifyNoConflict()
1018 int hasConflicts = g_Git.HasWorkingTreeConflicts();
1019 if (hasConflicts < 0)
1021 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
1022 return -1;
1024 if (hasConflicts)
1026 CMessageBox::Show(GetSafeHwnd(), IDS_PROGRS_CONFLICTSOCCURED, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
1027 auto pos = m_FileListCtrl.GetFirstSelectedItemPosition();
1028 while (pos)
1029 m_FileListCtrl.SetItemState(m_FileListCtrl.GetNextSelectedItem(pos), 0, LVIS_SELECTED);
1030 int nListItems = m_FileListCtrl.GetItemCount();
1031 for (int i = 0; i < nListItems; ++i)
1033 auto entry = m_FileListCtrl.GetListEntry(i);
1034 if (entry->m_Action & CTGitPath::LOGACTIONS_UNMERGED)
1036 m_FileListCtrl.EnsureVisible(i, FALSE);
1037 m_FileListCtrl.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1038 m_FileListCtrl.SetFocus();
1039 return -1;
1042 return -1;
1044 CleanUpRebaseActiveFolder();
1045 return 0;
1048 static bool IsLocalBranch(CString ref)
1050 STRING_VECTOR list;
1051 g_Git.GetBranchList(list, nullptr, CGit::BRANCH_LOCAL);
1052 return std::find(list.cbegin(), list.cend(), ref) != list.cend();
1055 int CRebaseDlg::FinishRebase()
1057 if (m_bFinishedRebase)
1058 return 0;
1060 m_bFinishedRebase = true;
1061 if(this->m_IsCherryPick) //cherry pick mode no "branch", working at upstream branch
1063 m_sStatusText.LoadString(IDS_DONE);
1064 m_CtrlStatusText.SetWindowText(m_sStatusText);
1065 m_bStatusWarning = false;
1066 m_CtrlStatusText.Invalidate();
1067 return 0;
1070 RewriteNotes();
1072 CGitHash head;
1073 if (g_Git.GetHash(head, _T("HEAD")))
1075 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1076 return -1;
1078 CString out,cmd;
1080 if (IsLocalBranch(m_BranchCtrl.GetString()))
1082 cmd.Format(_T("git.exe checkout -f -B %s %s --"), (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)head.ToString());
1083 AddLogString(cmd);
1084 if (RunGitCmdRetryOrAbort(cmd))
1085 return -1;
1086 AddLogString(out);
1089 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)head.ToString());
1090 AddLogString(cmd);
1091 if (RunGitCmdRetryOrAbort(cmd))
1092 return -1;
1093 AddLogString(out);
1095 while (m_ctrlTabCtrl.GetTabsNum() > 1)
1096 m_ctrlTabCtrl.RemoveTab(0);
1097 m_CtrlStatusText.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_REBASEFINISHED)));
1098 m_sStatusText.LoadString(IDS_PROC_REBASEFINISHED);
1099 m_bStatusWarning = false;
1100 m_CtrlStatusText.Invalidate();
1102 m_bRebaseAutoEnd = m_bRebaseAutoStart;
1104 return 0;
1107 void CRebaseDlg::RewriteNotes()
1109 CString rewrites;
1110 for (const auto& entry : m_rewrittenCommitsMap)
1112 if (entry.second.IsEmpty())
1113 continue;
1114 rewrites += entry.first.ToString();
1115 rewrites += L" ";
1116 rewrites += entry.second.ToString();
1117 rewrites += L"\n";
1119 if (rewrites.IsEmpty())
1120 return;
1121 CString tmpfile = GetTempFile();
1122 tmpfile.Replace(L"\\", L"/");
1123 if (!CStringUtils::WriteStringToTextFile(tmpfile, rewrites))
1124 return;
1125 SCOPE_EXIT{ ::DeleteFile(tmpfile); };
1126 CString pipefile = GetTempFile();
1127 pipefile.Replace(L"\\", L"/");
1128 CString pipecmd;
1129 pipecmd.Format(L"git notes copy --for-rewrite=rebase < %s", (LPCTSTR)tmpfile);
1130 if (!CStringUtils::WriteStringToTextFile(pipefile, pipecmd))
1131 return;
1132 SCOPE_EXIT{ ::DeleteFile(pipefile); };
1133 CString out;
1134 g_Git.Run(L"bash.exe " + pipefile, &out, CP_UTF8);
1137 void CRebaseDlg::OnBnClickedContinue()
1139 if( m_RebaseStage == REBASE_DONE)
1141 OnOK();
1142 CleanUpRebaseActiveFolder();
1143 CheckRestoreStash();
1144 return;
1147 if (m_RebaseStage == CHOOSE_BRANCH || m_RebaseStage == CHOOSE_COMMIT_PICK_MODE)
1149 if (CheckRebaseCondition())
1150 return;
1151 if (CAppUtils::IsTGitRebaseActive())
1152 return;
1155 if( this->m_IsFastForward )
1157 CString cmd,out;
1158 if (g_Git.GetHash(m_OrigBranchHash, m_BranchCtrl.GetString()))
1160 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_BranchCtrl.GetString() + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
1161 return;
1163 if (g_Git.GetHash(m_OrigUpstreamHash, m_UpstreamCtrl.GetString()))
1165 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_UpstreamCtrl.GetString() + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
1166 return;
1169 if(!g_Git.IsFastForward(this->m_BranchCtrl.GetString(),this->m_UpstreamCtrl.GetString()))
1171 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1172 AddLogString(_T("No fast forward possible.\r\nMaybe repository changed"));
1173 return;
1176 if (IsLocalBranch(m_BranchCtrl.GetString()))
1178 cmd.Format(_T("git.exe checkout --no-track -f -B %s %s --"), (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)m_UpstreamCtrl.GetString());
1179 AddLogString(cmd);
1180 if (RunGitCmdRetryOrAbort(cmd))
1181 return;
1182 AddLogString(out);
1183 out.Empty();
1185 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)g_Git.FixBranchName(this->m_UpstreamCtrl.GetString()));
1186 CString log;
1187 log.Format(IDS_PROC_REBASE_FFTO, m_UpstreamCtrl.GetString());
1188 this->AddLogString(log);
1190 AddLogString(cmd);
1191 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1192 if (RunGitCmdRetryOrAbort(cmd))
1193 return;
1194 AddLogString(out);
1195 AddLogString(CString(MAKEINTRESOURCE(IDS_DONE)));
1196 m_RebaseStage = REBASE_DONE;
1197 UpdateCurrentStatus();
1199 if (m_bRebaseAutoStart)
1200 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_CONTINUE, BN_CLICKED), (LPARAM)GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd());
1202 return;
1205 if( m_RebaseStage == CHOOSE_BRANCH|| m_RebaseStage == CHOOSE_COMMIT_PICK_MODE )
1207 if(CheckRebaseCondition())
1208 return ;
1209 m_RebaseStage = REBASE_START;
1210 m_FileListCtrl.Clear();
1211 m_FileListCtrl.SetHasCheckboxes(false);
1212 m_FileListCtrl.m_CurrentVersion = L"";
1213 m_ctrlTabCtrl.SetTabLabel(REBASE_TAB_CONFLICT, CString(MAKEINTRESOURCE(IDS_PROC_CONFLICTFILES)));
1214 m_ctrlTabCtrl.AddTab(&m_wndOutputRebase, CString(MAKEINTRESOURCE(IDS_LOG)), 2);
1217 if( m_RebaseStage == REBASE_FINISH )
1219 if(FinishRebase())
1220 return ;
1222 OnOK();
1225 if( m_RebaseStage == REBASE_SQUASH_CONFLICT)
1227 if(VerifyNoConflict())
1228 return;
1229 GitRevLoglist* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1230 if(this->CheckNextCommitIsSquash())
1231 {//next commit is not squash;
1232 m_RebaseStage = REBASE_SQUASH_EDIT;
1233 this->OnRebaseUpdateUI(0,0);
1234 this->UpdateCurrentStatus();
1235 return ;
1237 m_RebaseStage=REBASE_CONTINUE;
1238 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1239 m_forRewrite.push_back(curRev->m_CommitHash);
1240 this->UpdateCurrentStatus();
1243 if( m_RebaseStage == REBASE_CONFLICT )
1245 if(VerifyNoConflict())
1246 return;
1248 GitRevLoglist* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1249 // ***************************************************
1250 // ATTENTION: Similar code in CommitDlg.cpp!!!
1251 // ***************************************************
1252 CMassiveGitTask mgtReAddAfterCommit(_T("add --ignore-errors -f"));
1253 CMassiveGitTask mgtReDelAfterCommit(_T("rm --cached --ignore-unmatch"));
1254 CMassiveGitTask mgtAdd(_T("add -f"));
1255 CMassiveGitTask mgtUpdateIndexForceRemove(_T("update-index --force-remove"));
1256 CMassiveGitTask mgtUpdateIndex(_T("update-index"));
1257 CMassiveGitTask mgtRm(_T("rm --ignore-unmatch"));
1258 CMassiveGitTask mgtRmFCache(_T("rm -f --cache"));
1259 CMassiveGitTask mgtReset(_T("reset"), TRUE, true);
1260 for (int i = 0; i < m_FileListCtrl.GetItemCount(); i++)
1262 auto entry = m_FileListCtrl.GetListEntry(i);
1263 if (entry->m_Checked)
1265 if (entry->m_Action & CTGitPath::LOGACTIONS_UNVER)
1266 mgtAdd.AddFile(entry->GetGitPathString());
1267 else if (entry->m_Action & CTGitPath::LOGACTIONS_DELETED)
1268 mgtUpdateIndexForceRemove.AddFile(entry->GetGitPathString());
1269 else
1270 mgtUpdateIndex.AddFile(entry->GetGitPathString());
1272 if ((entry->m_Action & CTGitPath::LOGACTIONS_REPLACED) && !entry->GetGitOldPathString().IsEmpty())
1273 mgtRm.AddFile(entry->GetGitOldPathString());
1275 else
1277 if (entry->m_Action & CTGitPath::LOGACTIONS_ADDED || entry->m_Action & CTGitPath::LOGACTIONS_REPLACED)
1279 mgtRmFCache.AddFile(entry->GetGitPathString());
1280 mgtReAddAfterCommit.AddFile(*entry);
1282 if (entry->m_Action & CTGitPath::LOGACTIONS_REPLACED && !entry->GetGitOldPathString().IsEmpty())
1284 mgtReset.AddFile(entry->GetGitOldPathString());
1285 mgtReDelAfterCommit.AddFile(entry->GetGitOldPathString());
1288 else if(!(entry->m_Action & CTGitPath::LOGACTIONS_UNVER))
1290 mgtReset.AddFile(entry->GetGitPathString());
1291 if (entry->m_Action & CTGitPath::LOGACTIONS_DELETED && !(entry->m_Action & CTGitPath::LOGACTIONS_MISSING))
1292 mgtReDelAfterCommit.AddFile(entry->GetGitPathString());
1297 BOOL cancel = FALSE;
1298 bool successful = true;
1299 successful = successful && mgtAdd.Execute(cancel);
1300 successful = successful && mgtUpdateIndexForceRemove.Execute(cancel);
1301 successful = successful && mgtUpdateIndex.Execute(cancel);
1302 successful = successful && mgtRm.Execute(cancel);
1303 successful = successful && mgtRmFCache.Execute(cancel);
1304 successful = successful && mgtReset.Execute(cancel);
1306 if (!successful)
1308 AddLogString(_T("An error occurred while updating the index."));
1309 return;
1312 CString out =_T("");
1313 CString cmd;
1314 cmd.Format(_T("git.exe commit --allow-empty-message -C %s"), (LPCTSTR)curRev->m_CommitHash.ToString());
1316 AddLogString(cmd);
1318 if(g_Git.Run(cmd,&out,CP_UTF8))
1320 AddLogString(out);
1321 if(!g_Git.CheckCleanWorkTree())
1323 CMessageBox::Show(GetSafeHwnd(), out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1324 return;
1328 AddLogString(out);
1330 // update commit message if needed
1331 CString str = m_LogMessageCtrl.GetText().Trim();
1332 if (str != (curRev->GetSubject() + _T("\n") + curRev->GetBody()).Trim())
1334 if (str.Trim().IsEmpty())
1336 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_COMMITMESSAGE_EMPTY,IDS_APPNAME, MB_OK | MB_ICONERROR);
1337 return;
1339 CString tempfile = ::GetTempFile();
1340 if (CAppUtils::SaveCommitUnicodeFile(tempfile, str))
1342 CMessageBox::Show(GetSafeHwnd(), _T("Could not save commit message"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1343 return;
1346 out.Empty();
1347 cmd.Format(_T("git.exe commit --amend -F \"%s\""), (LPCTSTR)tempfile);
1348 AddLogString(cmd);
1350 if (g_Git.Run(cmd, &out, CP_UTF8))
1352 AddLogString(out);
1353 if (!g_Git.CheckCleanWorkTree())
1355 CMessageBox::Show(GetSafeHwnd(), out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1356 return;
1360 AddLogString(out);
1363 if (((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\ReaddUnselectedAddedFilesAfterCommit"), TRUE)) == TRUE)
1365 BOOL cancel2 = FALSE;
1366 mgtReAddAfterCommit.Execute(cancel2);
1367 mgtReDelAfterCommit.Execute(cancel2);
1370 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1371 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_EDIT)
1373 m_RebaseStage=REBASE_EDIT;
1374 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
1375 this->UpdateCurrentStatus();
1376 return;
1378 else
1380 m_RebaseStage=REBASE_CONTINUE;
1381 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1382 this->UpdateCurrentStatus();
1384 if (CheckNextCommitIsSquash() == 0) // remember commit msg after edit if next commit if squash
1385 ResetParentForSquash(str);
1386 else
1388 m_SquashMessage.Empty();
1389 CGitHash head;
1390 if (g_Git.GetHash(head, _T("HEAD")))
1392 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1393 return;
1395 m_rewrittenCommitsMap[curRev->m_CommitHash] = head;
1400 if ((m_RebaseStage == REBASE_EDIT || m_RebaseStage == REBASE_CONTINUE || m_bSplitCommit || m_RebaseStage == REBASE_SQUASH_EDIT) && CheckNextCommitIsSquash() && (m_bSplitCommit || !g_Git.CheckCleanWorkTree(true)))
1402 if (!m_bSplitCommit && CMessageBox::Show(GetSafeHwnd(), IDS_PROC_REBASE_CONTINUE_NOTCLEAN, IDS_APPNAME, 1, IDI_ERROR, IDS_MSGBOX_OK, IDS_ABORTBUTTON) == 2)
1403 return;
1404 BOOL isFirst = TRUE;
1407 CCommitDlg dlg;
1408 if (isFirst)
1409 dlg.m_sLogMessage = m_LogMessageCtrl.GetText();
1410 dlg.m_bWholeProject = true;
1411 dlg.m_bSelectFilesForCommit = true;
1412 dlg.m_bCommitAmend = isFirst && (m_RebaseStage != REBASE_SQUASH_EDIT); // do not amend on squash_edit stage, we need a normal commit there
1413 if (isFirst && m_RebaseStage == REBASE_SQUASH_EDIT)
1415 dlg.SetTime(m_SquashFirstMetaData.time);
1416 dlg.SetAuthor(m_SquashFirstMetaData.GetAuthor());
1418 CTGitPathList gpl;
1419 gpl.AddPath(CTGitPath());
1420 dlg.m_pathList = gpl;
1421 dlg.m_bAmendDiffToLastCommit = !m_bSplitCommit;
1422 dlg.m_bNoPostActions = true;
1423 if (dlg.m_bCommitAmend)
1424 dlg.m_AmendStr = dlg.m_sLogMessage;
1425 dlg.m_bWarnDetachedHead = false;
1427 if (dlg.DoModal() != IDOK)
1428 return;
1430 isFirst = !m_bSplitCommit; // only select amend on second+ runs if not in split commit mode
1432 m_SquashMessage.Empty();
1433 } while (!g_Git.CheckCleanWorkTree() || (m_bSplitCommit && CMessageBox::Show(GetSafeHwnd(), IDS_REBASE_ADDANOTHERCOMMIT, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) == IDYES));
1435 m_bSplitCommit = FALSE;
1436 UpdateData(FALSE);
1438 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1439 m_RebaseStage = REBASE_CONTINUE;
1440 GitRevLoglist* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1441 CGitHash head;
1442 if (g_Git.GetHash(head, _T("HEAD")))
1444 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1445 return;
1447 m_rewrittenCommitsMap[curRev->m_CommitHash] = head;
1448 for (const auto& hash : m_forRewrite)
1449 m_rewrittenCommitsMap[hash] = head;
1450 m_forRewrite.clear();
1451 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1452 this->UpdateCurrentStatus();
1455 if( m_RebaseStage == REBASE_EDIT || m_RebaseStage == REBASE_SQUASH_EDIT )
1457 CString str;
1458 GitRevLoglist* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1460 str=this->m_LogMessageCtrl.GetText();
1461 if(str.Trim().IsEmpty())
1463 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_COMMITMESSAGE_EMPTY,IDS_APPNAME, MB_OK | MB_ICONERROR);
1464 return;
1467 CString tempfile=::GetTempFile();
1468 if (CAppUtils::SaveCommitUnicodeFile(tempfile, str))
1470 CMessageBox::Show(GetSafeHwnd(), _T("Could not save commit message"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1471 return;
1474 CString out,cmd;
1476 if( m_RebaseStage == REBASE_SQUASH_EDIT )
1477 cmd.Format(_T("git.exe commit %s-F \"%s\""), (LPCTSTR)m_SquashFirstMetaData.GetAsParam(), (LPCTSTR)tempfile);
1478 else
1480 CString options;
1481 int isEmpty = IsCommitEmpty(curRev->m_CommitHash);
1482 if (isEmpty == 1)
1483 options = _T("--allow-empty ");
1484 else if (isEmpty < 0)
1485 return;
1486 cmd.Format(_T("git.exe commit --amend %s-F \"%s\""), (LPCTSTR)options, (LPCTSTR)tempfile);
1489 if(g_Git.Run(cmd,&out,CP_UTF8))
1491 if(!g_Git.CheckCleanWorkTree())
1493 CMessageBox::Show(GetSafeHwnd(), out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1494 return;
1498 ::DeleteFile(tempfile);
1499 AddLogString(out);
1500 if (CheckNextCommitIsSquash() == 0 && m_RebaseStage != REBASE_SQUASH_EDIT) // remember commit msg after edit if next commit if squash; but don't do this if ...->squash(reset here)->pick->squash
1502 ResetParentForSquash(str);
1504 else
1505 m_SquashMessage.Empty();
1506 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1507 m_RebaseStage=REBASE_CONTINUE;
1508 CGitHash head;
1509 if (g_Git.GetHash(head, _T("HEAD")))
1511 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1512 return;
1514 m_rewrittenCommitsMap[curRev->m_CommitHash] = head; // we had a reset to parent, so this is not the correct hash
1515 for (const auto& hash : m_forRewrite)
1516 m_rewrittenCommitsMap[hash] = head;
1517 m_forRewrite.clear();
1518 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1519 this->UpdateCurrentStatus();
1523 InterlockedExchange(&m_bThreadRunning, TRUE);
1524 SetControlEnable();
1526 if (!AfxBeginThread(RebaseThreadEntry, this))
1528 InterlockedExchange(&m_bThreadRunning, FALSE);
1529 CMessageBox::Show(GetSafeHwnd(), _T("Create Rebase Thread Fail"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1530 SetControlEnable();
1534 void CRebaseDlg::ResetParentForSquash(const CString& commitMessage)
1536 m_SquashMessage = commitMessage;
1537 // reset parent so that we can do "git cherry-pick --no-commit" w/o introducing an unwanted commit
1538 CString cmd = _T("git.exe reset --soft HEAD~1");
1539 m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1540 if (RunGitCmdRetryOrAbort(cmd))
1541 return;
1544 int CRebaseDlg::CheckNextCommitIsSquash()
1546 int index;
1547 if(m_CommitList.m_IsOldFirst)
1548 index=m_CurrentRebaseIndex+1;
1549 else
1550 index=m_CurrentRebaseIndex-1;
1552 GitRevLoglist* curRev;
1555 if(index<0)
1556 return -1;
1557 if(index>= m_CommitList.GetItemCount())
1558 return -1;
1560 curRev = m_CommitList.m_arShownList.SafeGetAt(index);
1562 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1563 return 0;
1564 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SKIP)
1566 if(m_CommitList.m_IsOldFirst)
1567 ++index;
1568 else
1569 --index;
1571 else
1572 return -1;
1574 } while(curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SKIP);
1576 return -1;
1579 int CRebaseDlg::GoNext()
1581 if(m_CommitList.m_IsOldFirst)
1582 ++m_CurrentRebaseIndex;
1583 else
1584 --m_CurrentRebaseIndex;
1585 return 0;
1588 int CRebaseDlg::StateAction()
1590 switch(this->m_RebaseStage)
1592 case CHOOSE_BRANCH:
1593 case CHOOSE_COMMIT_PICK_MODE:
1594 if(StartRebase())
1595 return -1;
1596 m_RebaseStage = REBASE_START;
1597 GoNext();
1598 break;
1601 return 0;
1603 void CRebaseDlg::SetContinueButtonText()
1605 CString Text;
1606 switch(this->m_RebaseStage)
1608 case CHOOSE_BRANCH:
1609 case CHOOSE_COMMIT_PICK_MODE:
1610 if(this->m_IsFastForward)
1611 Text.LoadString(IDS_PROC_STARTREBASEFFBUTTON);
1612 else
1613 Text.LoadString(IDS_PROC_STARTREBASEBUTTON);
1614 break;
1616 case REBASE_START:
1617 case REBASE_ERROR:
1618 case REBASE_CONTINUE:
1619 case REBASE_SQUASH_CONFLICT:
1620 Text.LoadString(IDS_CONTINUEBUTTON);
1621 break;
1623 case REBASE_CONFLICT:
1624 Text.LoadString(IDS_COMMITBUTTON);
1625 break;
1626 case REBASE_EDIT:
1627 Text.LoadString(IDS_AMENDBUTTON);
1628 break;
1630 case REBASE_SQUASH_EDIT:
1631 Text.LoadString(IDS_COMMITBUTTON);
1632 break;
1634 case REBASE_ABORT:
1635 case REBASE_FINISH:
1636 Text.LoadString(IDS_FINISHBUTTON);
1637 break;
1639 case REBASE_DONE:
1640 Text.LoadString(IDS_DONE);
1641 break;
1643 this->GetDlgItem(IDC_REBASE_CONTINUE)->SetWindowText(Text);
1646 void CRebaseDlg::SetControlEnable()
1648 switch(this->m_RebaseStage)
1650 case CHOOSE_BRANCH:
1651 case CHOOSE_COMMIT_PICK_MODE:
1653 this->GetDlgItem(IDC_SPLITALLOPTIONS)->EnableWindow(TRUE);
1654 this->GetDlgItem(IDC_BUTTON_UP2)->EnableWindow(TRUE);
1655 this->GetDlgItem(IDC_BUTTON_DOWN2)->EnableWindow(TRUE);
1657 if(!m_IsCherryPick)
1659 this->GetDlgItem(IDC_REBASE_COMBOXEX_BRANCH)->EnableWindow(TRUE);
1660 this->GetDlgItem(IDC_REBASE_COMBOXEX_UPSTREAM)->EnableWindow(TRUE);
1661 this->GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(TRUE);
1662 this->GetDlgItem(IDC_REBASE_CHECK_FORCE)->EnableWindow(TRUE);
1663 this->GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->EnableWindow(TRUE);
1665 this->m_CommitList.m_ContextMenuMask |= m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_PICK)|
1666 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_SQUASH)|
1667 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_EDIT)|
1668 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_SKIP)|
1669 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_LOG);
1670 break;
1672 case REBASE_START:
1673 case REBASE_CONTINUE:
1674 case REBASE_ABORT:
1675 case REBASE_ERROR:
1676 case REBASE_FINISH:
1677 case REBASE_CONFLICT:
1678 case REBASE_EDIT:
1679 case REBASE_SQUASH_CONFLICT:
1680 case REBASE_DONE:
1681 this->GetDlgItem(IDC_SPLITALLOPTIONS)->EnableWindow(FALSE);
1682 this->GetDlgItem(IDC_REBASE_COMBOXEX_BRANCH)->EnableWindow(FALSE);
1683 this->GetDlgItem(IDC_REBASE_COMBOXEX_UPSTREAM)->EnableWindow(FALSE);
1684 this->GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(FALSE);
1685 this->GetDlgItem(IDC_REBASE_CHECK_FORCE)->EnableWindow(FALSE);
1686 this->GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->EnableWindow(FALSE);
1687 this->GetDlgItem(IDC_BUTTON_UP2)->EnableWindow(FALSE);
1688 this->GetDlgItem(IDC_BUTTON_DOWN2)->EnableWindow(FALSE);
1690 if( m_RebaseStage == REBASE_DONE && (this->m_PostButtonTexts.GetCount() != 0) )
1692 this->GetDlgItem(IDC_STATUS_STATIC)->ShowWindow(SW_HIDE);
1693 this->GetDlgItem(IDC_REBASE_POST_BUTTON)->ShowWindow(SW_SHOWNORMAL);
1694 this->m_PostButton.RemoveAll();
1695 this->m_PostButton.AddEntries(m_PostButtonTexts);
1696 //this->GetDlgItem(IDC_REBASE_POST_BUTTON)->SetWindowText(this->m_PostButtonText);
1698 break;
1701 GetDlgItem(IDC_REBASE_SPLIT_COMMIT)->ShowWindow((m_RebaseStage == REBASE_EDIT || m_RebaseStage == REBASE_SQUASH_EDIT) ? SW_SHOW : SW_HIDE);
1703 if(m_bThreadRunning)
1705 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(FALSE);
1708 else if (m_RebaseStage != REBASE_ERROR)
1710 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(TRUE);
1714 void CRebaseDlg::UpdateProgress()
1716 int index;
1717 CRect rect;
1719 if(m_CommitList.m_IsOldFirst)
1720 index = m_CurrentRebaseIndex+1;
1721 else
1722 index = m_CommitList.GetItemCount()-m_CurrentRebaseIndex;
1724 int finishedCommits = index - 1; // introduced an variable which shows the number handled revisions for the progress bars
1725 if (m_RebaseStage == REBASE_FINISH || finishedCommits == -1)
1726 finishedCommits = index;
1728 m_ProgressBar.SetRange32(0, m_CommitList.GetItemCount());
1729 m_ProgressBar.SetPos(finishedCommits);
1730 if (m_pTaskbarList)
1732 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
1733 m_pTaskbarList->SetProgressValue(m_hWnd, finishedCommits, m_CommitList.GetItemCount());
1736 if(m_CurrentRebaseIndex>=0 && m_CurrentRebaseIndex< m_CommitList.GetItemCount())
1738 CString text;
1739 text.Format(IDS_PROC_REBASING_PROGRESS, index, m_CommitList.GetItemCount());
1740 m_sStatusText = text;
1741 m_CtrlStatusText.SetWindowText(text);
1742 m_bStatusWarning = false;
1743 m_CtrlStatusText.Invalidate();
1746 GitRevLoglist* prevRev = nullptr, *curRev = nullptr;
1748 if (m_CurrentRebaseIndex >= 0 && m_CurrentRebaseIndex < (int)m_CommitList.m_arShownList.size())
1749 curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1751 for (int i = 0; i < (int)m_CommitList.m_arShownList.size(); ++i)
1753 prevRev = m_CommitList.m_arShownList.SafeGetAt(i);
1754 if (prevRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_CURRENT)
1756 prevRev->GetRebaseAction() &= ~CGitLogListBase::LOGACTIONS_REBASE_CURRENT;
1757 m_CommitList.GetItemRect(i,&rect,LVIR_BOUNDS);
1758 m_CommitList.InvalidateRect(rect);
1762 if(curRev)
1764 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_CURRENT;
1765 m_CommitList.GetItemRect(m_CurrentRebaseIndex,&rect,LVIR_BOUNDS);
1766 m_CommitList.InvalidateRect(rect);
1768 m_CommitList.EnsureVisible(m_CurrentRebaseIndex,FALSE);
1771 void CRebaseDlg::UpdateCurrentStatus()
1773 SetContinueButtonText();
1774 SetControlEnable();
1775 UpdateProgress();
1776 if (m_RebaseStage == REBASE_DONE)
1777 GetDlgItem(IDC_REBASE_CONTINUE)->SetFocus();
1780 void CRebaseDlg::AddLogString(CString str)
1782 this->m_wndOutputRebase.SendMessage(SCI_SETREADONLY, FALSE);
1783 CStringA sTextA = m_wndOutputRebase.StringForControl(str);//CUnicodeUtils::GetUTF8(str);
1784 this->m_wndOutputRebase.SendMessage(SCI_DOCUMENTEND);
1785 this->m_wndOutputRebase.SendMessage(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)sTextA);
1786 this->m_wndOutputRebase.SendMessage(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)"\n");
1787 this->m_wndOutputRebase.SendMessage(SCI_SETREADONLY, TRUE);
1790 int CRebaseDlg::GetCurrentCommitID()
1792 if(m_CommitList.m_IsOldFirst)
1793 return this->m_CurrentRebaseIndex+1;
1794 else
1795 return m_CommitList.GetItemCount()-m_CurrentRebaseIndex;
1798 int CRebaseDlg::IsCommitEmpty(const CGitHash& hash)
1800 CString cmd, tree, ptree;
1801 cmd.Format(_T("git.exe rev-parse -q --verify %s^{tree}"), (LPCTSTR)hash.ToString());
1802 if (g_Git.Run(cmd, &tree, CP_UTF8))
1804 AddLogString(cmd);
1805 AddLogString(tree);
1806 return -1;
1808 cmd.Format(_T("git.exe rev-parse -q --verify %s^^{tree}"), (LPCTSTR)hash.ToString());
1809 if (g_Git.Run(cmd, &ptree, CP_UTF8))
1810 ptree = _T("4b825dc642cb6eb9a060e54bf8d69288fbee4904"); // empty tree
1811 return tree == ptree;
1814 int CRebaseDlg::DoRebase()
1816 CString cmd,out;
1817 if(m_CurrentRebaseIndex <0)
1818 return 0;
1819 if(m_CurrentRebaseIndex >= m_CommitList.GetItemCount() )
1820 return 0;
1822 GitRevLoglist* pRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1823 int mode = pRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_MODE_MASK;
1824 CString nocommit;
1826 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SKIP)
1828 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1829 return 0;
1832 bool nextCommitIsSquash = (CheckNextCommitIsSquash() == 0);
1833 if (nextCommitIsSquash || mode != CGitLogListBase::LOGACTIONS_REBASE_PICK)
1834 { // next commit is squash or not pick
1835 if (!this->m_SquashMessage.IsEmpty())
1836 this->m_SquashMessage += _T("\n\n");
1837 this->m_SquashMessage += pRev->GetSubject();
1838 this->m_SquashMessage += _T("\n");
1839 this->m_SquashMessage += pRev->GetBody().TrimRight();
1840 if (m_bAddCherryPickedFrom)
1842 if (!pRev->GetBody().IsEmpty())
1843 m_SquashMessage += _T("\n");
1844 m_SquashMessage += _T("(cherry picked from commit ");
1845 m_SquashMessage += pRev->m_CommitHash.ToString();
1846 m_SquashMessage += _T(")");
1849 else
1851 this->m_SquashMessage.Empty();
1852 m_SquashFirstMetaData.Empty();
1855 if ((nextCommitIsSquash && mode != CGitLogListBase::LOGACTIONS_REBASE_EDIT) || mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1856 { // next or this commit is squash (don't do this on edit->squash sequence)
1857 nocommit=_T(" --no-commit ");
1860 if (nextCommitIsSquash && mode != CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1861 m_SquashFirstMetaData = SquashFirstMetaData(pRev);
1863 CString log;
1864 log.Format(_T("%s %d: %s"), (LPCTSTR)CGitLogListBase::GetRebaseActionName(mode), GetCurrentCommitID(), (LPCTSTR)pRev->m_CommitHash.ToString());
1865 AddLogString(log);
1866 AddLogString(pRev->GetSubject());
1867 if (pRev->GetSubject().IsEmpty())
1869 CMessageBox::Show(m_hWnd, IDS_PROC_REBASE_EMPTYCOMMITMSG, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
1870 mode = CGitLogListBase::LOGACTIONS_REBASE_EDIT;
1873 CString cherryPickedFrom;
1874 if (m_bAddCherryPickedFrom)
1875 cherryPickedFrom = _T("-x ");
1876 else if (!m_IsCherryPick && nocommit.IsEmpty())
1877 cherryPickedFrom = _T("--ff "); // for issue #1833: "If the current HEAD is the same as the parent of the cherry-picked commit, then a fast forward to this commit will be performed."
1879 int isEmpty = IsCommitEmpty(pRev->m_CommitHash);
1880 if (isEmpty == 1)
1881 cherryPickedFrom += _T("--allow-empty ");
1882 else if (isEmpty < 0)
1883 return -1;
1885 if (m_IsCherryPick && pRev->m_ParentHash.size() > 1)
1887 CString msg;
1888 msg.Format(IDS_CHERRYPICK_MERGECOMMIT, (LPCTSTR)pRev->m_CommitHash.ToString(), (LPCTSTR)pRev->GetSubject());
1889 CString parent1;
1890 parent1.Format(IDS_PARENT, 1);
1891 CString parent2;
1892 parent2.Format(IDS_PARENT, 2);
1893 CString cancel;
1894 cancel.LoadString(IDS_MSGBOX_CANCEL);
1895 auto ret = CMessageBox::Show(m_hWnd, msg, _T("TortoiseGit"), 3, IDI_QUESTION, parent1, parent2, cancel);
1896 if (ret == 3)
1897 return - 1;
1899 cherryPickedFrom.AppendFormat(L"-m %d ", ret);
1902 while (true)
1904 cmd.Format(_T("git.exe cherry-pick %s%s %s"), (LPCTSTR)cherryPickedFrom, (LPCTSTR)nocommit, (LPCTSTR)pRev->m_CommitHash.ToString());
1905 if (m_bPreserveMerges)
1907 bool parentRewritten = false;
1908 CGitHash currentHeadHash;
1909 if (g_Git.GetHash(currentHeadHash, _T("HEAD")))
1911 m_RebaseStage = REBASE_ERROR;
1912 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1913 return -1;
1915 if (!m_currentCommits.empty())
1917 for (const auto& commit : m_currentCommits)
1918 m_rewrittenCommitsMap[commit] = currentHeadHash;
1919 m_currentCommits.clear();
1921 m_currentCommits.push_back(pRev->m_CommitHash);
1922 GIT_REV_LIST possibleParents = pRev->m_ParentHash;
1923 GIT_REV_LIST newParents;
1924 for (auto it = possibleParents.cbegin(); it != possibleParents.cend(); it = possibleParents.begin())
1926 CGitHash parent = *it;
1927 possibleParents.erase(it);
1929 const auto rewrittenParent = m_rewrittenCommitsMap.find(parent);
1930 if (rewrittenParent == m_rewrittenCommitsMap.cend())
1932 auto droppedCommitParents = m_droppedCommitsMap.find(parent);
1933 if (droppedCommitParents != m_droppedCommitsMap.cend())
1935 parentRewritten = true;
1936 for (auto droppedIt = droppedCommitParents->second.crbegin(); droppedIt != droppedCommitParents->second.crend(); ++droppedIt)
1937 possibleParents.insert(possibleParents.begin(), *droppedIt);
1938 continue;
1941 newParents.push_back(parent);
1942 continue;
1945 if (rewrittenParent->second.IsEmpty() && parent == pRev->m_ParentHash[0] && pRev->ParentsCount() > 1)
1947 m_RebaseStage = REBASE_ERROR;
1948 AddLogString(_T(""));
1949 AddLogString(_T("Unrecoverable error: Merge commit parent missing."));
1950 return -1;
1953 CGitHash newParent = rewrittenParent->second;
1954 if (newParent.IsEmpty()) // use current HEAD as fallback
1955 newParent = currentHeadHash;
1957 if (newParent != parent)
1958 parentRewritten = true;
1960 if (std::find(newParents.begin(), newParents.end(), newParent) == newParents.end())
1961 newParents.push_back(newParent);
1963 if (pRev->ParentsCount() > 1)
1965 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1967 m_RebaseStage = REBASE_ERROR;
1968 AddLogString(_T("Cannot squash merge commit on rebase."));
1969 return -1;
1971 if (!parentRewritten && nocommit.IsEmpty())
1972 cmd.Format(_T("git.exe reset --hard %s"), (LPCTSTR)pRev->m_CommitHash.ToString());
1973 else
1975 CString parentString;
1976 for (const auto& parent : newParents)
1977 parentString += L" " + parent.ToString();
1978 cmd.Format(_T("git.exe checkout %s"), (LPCTSTR)newParents[0].ToString());
1979 if (RunGitCmdRetryOrAbort(cmd))
1981 m_RebaseStage = REBASE_ERROR;
1982 return -1;
1984 cmd.Format(_T("git.exe merge --no-ff%s %s"), (LPCTSTR)nocommit, (LPCTSTR)parentString);
1985 if (nocommit.IsEmpty())
1987 if (g_Git.Run(cmd, &out, CP_UTF8))
1989 AddLogString(cmd);
1990 AddLogString(out);
1991 int hasConflicts = g_Git.HasWorkingTreeConflicts();
1992 if (hasConflicts > 0)
1994 m_RebaseStage = REBASE_CONFLICT;
1995 return -1;
1997 else if (hasConflicts < 0)
1998 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
1999 AddLogString(_T("An unrecoverable error occurred."));
2000 m_RebaseStage = REBASE_ERROR;
2001 return -1;
2003 CGitHash newHeadHash;
2004 if (g_Git.GetHash(newHeadHash, _T("HEAD")))
2006 m_RebaseStage = REBASE_ERROR;
2007 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
2008 return -1;
2010 // do nothing if already up2date
2011 if (currentHeadHash != newHeadHash)
2012 cmd.Format(_T("git.exe commit --amend -C %s"), (LPCTSTR)pRev->m_CommitHash.ToString());
2016 else
2018 if (mode != CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2020 cmd.Format(_T("git.exe checkout %s"), (LPCTSTR)newParents[0].ToString());
2021 if (RunGitCmdRetryOrAbort(cmd))
2023 m_RebaseStage = REBASE_ERROR;
2024 return -1;
2027 cmd.Format(_T("git.exe cherry-pick %s%s %s"), (LPCTSTR)cherryPickedFrom, (LPCTSTR)nocommit, (LPCTSTR)pRev->m_CommitHash.ToString());
2031 if(g_Git.Run(cmd,&out,CP_UTF8))
2033 AddLogString(out);
2034 int hasConflicts = g_Git.HasWorkingTreeConflicts();
2035 if (hasConflicts < 0)
2037 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
2038 return -1;
2040 if (!hasConflicts)
2042 if (mode == CGitLogListBase::LOGACTIONS_REBASE_PICK)
2044 if (m_pTaskbarList)
2045 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
2046 int choose = -1;
2047 if (!m_bAutoSkipFailedCommit)
2049 choose = CMessageBox::ShowCheck(GetSafeHwnd(), IDS_CHERRYPICKFAILEDSKIP, IDS_APPNAME, 1, IDI_QUESTION, IDS_SKIPBUTTON, IDS_MSGBOX_RETRY, IDS_MSGBOX_CANCEL, nullptr, IDS_DO_SAME_FOR_REST, &m_bAutoSkipFailedCommit);
2050 if (choose == 2)
2052 m_bAutoSkipFailedCommit = FALSE;
2053 continue; // retry cherry pick
2056 if (m_bAutoSkipFailedCommit || choose == 1)
2058 if (!RunGitCmdRetryOrAbort(_T("git.exe reset --hard")))
2060 pRev->GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_SKIP;
2061 m_CommitList.Invalidate();
2062 return 0;
2066 m_RebaseStage = REBASE_ERROR;
2067 AddLogString(_T("An unrecoverable error occurred."));
2068 return -1;
2070 if (mode == CGitLogListBase::LOGACTIONS_REBASE_EDIT)
2072 this->m_RebaseStage = REBASE_EDIT ;
2073 return -1; // Edit return -1 to stop rebase.
2075 // Squash Case
2076 if(CheckNextCommitIsSquash())
2077 { // no squash
2078 // let user edit last commmit message
2079 this->m_RebaseStage = REBASE_SQUASH_EDIT;
2080 return -1;
2084 if (m_pTaskbarList)
2085 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
2086 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2087 m_RebaseStage = REBASE_SQUASH_CONFLICT;
2088 else
2089 m_RebaseStage = REBASE_CONFLICT;
2090 return -1;
2093 else
2095 AddLogString(out);
2096 if (mode == CGitLogListBase::LOGACTIONS_REBASE_PICK)
2098 if (nocommit.IsEmpty())
2100 CGitHash head;
2101 if (g_Git.GetHash(head, _T("HEAD")))
2103 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
2104 m_RebaseStage = REBASE_ERROR;
2105 return -1;
2107 m_rewrittenCommitsMap[pRev->m_CommitHash] = head;
2109 else
2110 m_forRewrite.push_back(pRev->m_CommitHash);
2111 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
2112 return 0;
2114 if (mode == CGitLogListBase::LOGACTIONS_REBASE_EDIT)
2116 this->m_RebaseStage = REBASE_EDIT ;
2117 return -1; // Edit return -1 to stop rebase.
2120 // Squash Case
2121 if(CheckNextCommitIsSquash())
2122 { // no squash
2123 // let user edit last commmit message
2124 this->m_RebaseStage = REBASE_SQUASH_EDIT;
2125 return -1;
2127 else if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2129 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
2130 m_forRewrite.push_back(pRev->m_CommitHash);
2134 return 0;
2138 BOOL CRebaseDlg::IsEnd()
2140 if(m_CommitList.m_IsOldFirst)
2141 return m_CurrentRebaseIndex>= this->m_CommitList.GetItemCount();
2142 else
2143 return m_CurrentRebaseIndex<0;
2146 int CRebaseDlg::RebaseThread()
2148 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
2150 int ret=0;
2151 while(1)
2153 if( m_RebaseStage == REBASE_START )
2155 if( this->StartRebase() )
2157 ret = -1;
2158 break;
2160 m_RebaseStage = REBASE_CONTINUE;
2162 else if( m_RebaseStage == REBASE_CONTINUE )
2164 this->GoNext();
2165 SendMessage(MSG_REBASE_UPDATE_UI);
2166 if(IsEnd())
2168 ret = 0;
2169 m_RebaseStage = REBASE_FINISH;
2171 else
2173 ret = DoRebase();
2174 if( ret )
2175 break;
2178 else if( m_RebaseStage == REBASE_FINISH )
2180 SendMessage(MSG_REBASE_UPDATE_UI);
2181 m_RebaseStage = REBASE_DONE;
2182 break;
2184 else
2185 break;
2186 this->PostMessage(MSG_REBASE_UPDATE_UI);
2189 InterlockedExchange(&m_bThreadRunning, FALSE);
2190 this->PostMessage(MSG_REBASE_UPDATE_UI);
2191 return ret;
2194 void CRebaseDlg::ListConflictFile(bool noStoreScrollPosition)
2196 if (!noStoreScrollPosition)
2197 m_FileListCtrl.StoreScrollPos();
2198 this->m_FileListCtrl.Clear();
2199 m_FileListCtrl.SetHasCheckboxes(true);
2200 CTGitPathList list;
2201 CTGitPath path;
2202 list.AddPath(path);
2204 if (!m_IsCherryPick)
2206 CString adminDir;
2207 if (GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
2208 CreateDirectory(adminDir + L"tgitrebase.active", nullptr);
2211 this->m_FileListCtrl.GetStatus(&list,true);
2212 m_FileListCtrl.Show(CTGitPath::LOGACTIONS_UNMERGED | CTGitPath::LOGACTIONS_MODIFIED | CTGitPath::LOGACTIONS_ADDED | CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_REPLACED, CTGitPath::LOGACTIONS_UNMERGED);
2214 m_FileListCtrl.Check(GITSLC_SHOWFILES);
2215 bool hasSubmoduleChange = false;
2216 for (int i = 0; i < m_FileListCtrl.GetItemCount(); i++)
2218 auto entry = m_FileListCtrl.GetListEntry(i);
2219 if (entry->IsDirectory())
2221 hasSubmoduleChange = true;
2222 break;
2226 if (hasSubmoduleChange)
2228 m_CtrlStatusText.SetWindowText(m_sStatusText + _T(", ") + CString(MAKEINTRESOURCE(IDS_CARE_SUBMODULE_CHANGES)));
2229 m_bStatusWarning = true;
2230 m_CtrlStatusText.Invalidate();
2232 else
2234 m_CtrlStatusText.SetWindowText(m_sStatusText);
2235 m_bStatusWarning = false;
2236 m_CtrlStatusText.Invalidate();
2240 LRESULT CRebaseDlg::OnRebaseUpdateUI(WPARAM,LPARAM)
2242 if (m_RebaseStage == REBASE_FINISH)
2244 FinishRebase();
2245 return 0;
2247 UpdateCurrentStatus();
2249 if (m_RebaseStage == REBASE_DONE && m_bRebaseAutoEnd)
2251 m_bRebaseAutoEnd = false;
2252 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_CONTINUE, BN_CLICKED), (LPARAM)GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd());
2255 if (m_RebaseStage == REBASE_DONE && m_pTaskbarList)
2256 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS); // do not show progress on taskbar any more to show we finished
2257 if(m_CurrentRebaseIndex <0)
2258 return 0;
2259 if(m_CurrentRebaseIndex >= m_CommitList.GetItemCount() )
2260 return 0;
2261 GitRev* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
2263 switch(m_RebaseStage)
2265 case REBASE_CONFLICT:
2266 case REBASE_SQUASH_CONFLICT:
2268 ListConflictFile(true);
2269 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_CONFLICT);
2270 if (m_pTaskbarList)
2271 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
2272 this->m_LogMessageCtrl.Call(SCI_SETREADONLY, FALSE);
2273 CString logMessage;
2274 if (m_IsCherryPick)
2276 CString dotGitPath;
2277 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, dotGitPath);
2278 // vanilla git also re-uses MERGE_MSG on conflict (listing all conflicted files)
2279 // and it's also needed for cherry-pick in order to get cherry-picked-from included on conflicts
2280 CGit::LoadTextFile(dotGitPath + _T("MERGE_MSG"), logMessage);
2282 if (logMessage.IsEmpty())
2283 logMessage = curRev->GetSubject() + _T("\n") + curRev->GetBody();
2284 this->m_LogMessageCtrl.SetText(logMessage);
2285 break;
2287 case REBASE_EDIT:
2288 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
2289 if (m_pTaskbarList)
2290 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
2291 this->m_LogMessageCtrl.Call(SCI_SETREADONLY, FALSE);
2292 if (m_bAddCherryPickedFrom)
2294 // Since the new commit is done and the HEAD points to it,
2295 // just using the new body modified by git self.
2296 GitRev headRevision;
2297 if (headRevision.GetCommit(_T("HEAD")))
2298 MessageBox(headRevision.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
2300 m_LogMessageCtrl.SetText(headRevision.GetSubject() + _T("\n") + headRevision.GetBody());
2302 else
2303 m_LogMessageCtrl.SetText(curRev->GetSubject() + _T("\n") + curRev->GetBody());
2304 break;
2305 case REBASE_SQUASH_EDIT:
2306 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
2307 this->m_LogMessageCtrl.Call(SCI_SETREADONLY, FALSE);
2308 this->m_LogMessageCtrl.SetText(this->m_SquashMessage);
2309 if (m_pTaskbarList)
2310 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
2311 break;
2312 default:
2313 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
2315 return 0;
2318 void CRebaseDlg::OnCancel()
2320 OnBnClickedAbort();
2323 void CRebaseDlg::OnBnClickedAbort()
2325 if (m_pTaskbarList)
2326 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
2328 m_tooltips.Pop();
2330 if(m_OrigUpstreamHash.IsEmpty())
2332 __super::OnCancel();
2335 if(m_RebaseStage == CHOOSE_BRANCH || m_RebaseStage== CHOOSE_COMMIT_PICK_MODE)
2337 goto end;
2340 if (CMessageBox::Show(GetSafeHwnd(), IDS_PROC_REBASE_ABORT, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) != IDYES)
2341 goto end;
2343 if(this->m_IsFastForward)
2345 CString cmd;
2346 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)this->m_OrigBranchHash.ToString());
2347 RunGitCmdRetryOrAbort(cmd);
2348 __super::OnCancel();
2349 goto end;
2352 if (m_IsCherryPick) // there are not "branch" at cherry pick mode
2354 CString cmd;
2355 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)m_OrigUpstreamHash.ToString());
2356 RunGitCmdRetryOrAbort(cmd);
2357 __super::OnCancel();
2358 goto end;
2361 if (m_OrigHEADBranch == m_BranchCtrl.GetString())
2363 CString cmd, out;
2364 if (IsLocalBranch(m_OrigHEADBranch))
2365 cmd.Format(_T("git.exe checkout -f -B %s %s --"), (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)m_OrigBranchHash.ToString());
2366 else
2367 cmd.Format(_T("git.exe checkout -f %s --"), (LPCTSTR)m_OrigBranchHash.ToString());
2368 if (g_Git.Run(cmd, &out, CP_UTF8))
2370 AddLogString(out);
2371 ::MessageBox(m_hWnd, _T("Unrecoverable error on cleanup:\n") + out, _T("TortoiseGit"), MB_ICONERROR);
2372 __super::OnCancel();
2373 goto end;
2376 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)m_OrigBranchHash.ToString());
2377 RunGitCmdRetryOrAbort(cmd);
2379 else
2381 CString cmd, out;
2382 if (m_OrigHEADBranch != g_Git.GetCurrentBranch(true))
2384 if (IsLocalBranch(m_OrigHEADBranch))
2385 cmd.Format(_T("git.exe checkout -f -B %s %s --"), (LPCTSTR)m_OrigHEADBranch, (LPCTSTR)m_OrigHEADHash.ToString());
2386 else
2387 cmd.Format(_T("git.exe checkout -f %s --"), (LPCTSTR)m_OrigHEADHash.ToString());
2388 if (g_Git.Run(cmd, &out, CP_UTF8))
2390 AddLogString(out);
2391 ::MessageBox(m_hWnd, _T("Unrecoverable error on cleanup:\n") + out, _T("TortoiseGit"), MB_ICONERROR);
2392 // continue to restore moved branch
2396 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)m_OrigHEADHash.ToString());
2397 RunGitCmdRetryOrAbort(cmd);
2399 // restore moved branch
2400 if (IsLocalBranch(m_BranchCtrl.GetString()))
2402 cmd.Format(_T("git.exe branch -f %s %s --"), (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)m_OrigBranchHash.ToString());
2403 if (g_Git.Run(cmd, &out, CP_UTF8))
2405 AddLogString(out);
2406 ::MessageBox(m_hWnd, _T("Unrecoverable error on cleanup:\n") + out, _T("TortoiseGit"), MB_ICONERROR);
2407 __super::OnCancel();
2408 goto end;
2412 __super::OnCancel();
2413 end:
2414 CleanUpRebaseActiveFolder();
2415 CheckRestoreStash();
2418 void CRebaseDlg::OnBnClickedButtonReverse()
2420 CString temp = m_BranchCtrl.GetString();
2421 m_BranchCtrl.AddString(m_UpstreamCtrl.GetString());
2422 m_UpstreamCtrl.AddString(temp);
2423 OnCbnSelchangeUpstream();
2426 void CRebaseDlg::OnBnClickedButtonBrowse()
2428 if(CBrowseRefsDlg::PickRefForCombo(&m_UpstreamCtrl))
2429 OnCbnSelchangeUpstream();
2432 void CRebaseDlg::OnBnClickedRebaseCheckForce()
2434 this->UpdateData();
2435 this->FetchLogList();
2438 void CRebaseDlg::OnBnClickedRebasePostButton()
2440 this->m_Upstream=this->m_UpstreamCtrl.GetString();
2441 this->m_Branch=this->m_BranchCtrl.GetString();
2443 this->EndDialog((int)(IDC_REBASE_POST_BUTTON+this->m_PostButton.GetCurrentEntry()));
2446 LRESULT CRebaseDlg::OnGitStatusListCtrlNeedsRefresh(WPARAM, LPARAM)
2448 Refresh();
2449 return 0;
2452 void CRebaseDlg::Refresh()
2454 if (m_RebaseStage == REBASE_CONFLICT || m_RebaseStage == REBASE_SQUASH_CONFLICT)
2456 ListConflictFile(false);
2457 return;
2460 if(this->m_IsCherryPick)
2461 return ;
2463 if(this->m_RebaseStage == CHOOSE_BRANCH )
2465 this->UpdateData();
2466 this->FetchLogList();
2470 void CRebaseDlg::OnBnClickedButtonUp2()
2472 POSITION pos;
2473 pos = m_CommitList.GetFirstSelectedItemPosition();
2475 bool moveToTop = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
2476 // do nothing if the first selected item is the first item in the list
2477 if (!moveToTop && m_CommitList.GetNextSelectedItem(pos) == 0)
2478 return;
2480 pos = m_CommitList.GetFirstSelectedItemPosition();
2482 int count = 0;
2483 bool changed = false;
2484 while(pos)
2486 int index=m_CommitList.GetNextSelectedItem(pos);
2487 count = moveToTop ? count : (index - 1);
2488 while (index > count)
2490 CGitHash old = m_CommitList.m_logEntries[index - 1];
2491 m_CommitList.m_logEntries[index - 1] = m_CommitList.m_logEntries[index];
2492 m_CommitList.m_logEntries[index] = old;
2493 m_CommitList.RecalculateShownList(&m_CommitList.m_arShownList);
2494 m_CommitList.SetItemState(index - 1, LVIS_SELECTED, LVIS_SELECTED);
2495 m_CommitList.SetItemState(index, 0, LVIS_SELECTED);
2496 changed = true;
2497 index--;
2499 count++;
2501 if (changed)
2503 pos = m_CommitList.GetFirstSelectedItemPosition();
2504 m_CommitList.EnsureVisible(m_CommitList.GetNextSelectedItem(pos), false);
2505 m_CommitList.Invalidate();
2506 m_CommitList.SetFocus();
2510 void CRebaseDlg::OnBnClickedButtonDown2()
2512 if (m_CommitList.GetSelectedCount() == 0)
2513 return;
2515 bool moveToBottom = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
2516 POSITION pos;
2517 pos = m_CommitList.GetFirstSelectedItemPosition();
2518 bool changed = false;
2519 // use an array to store all selected item indexes; the user won't select too much items
2520 auto indexes = std::make_unique<int[]>(m_CommitList.GetSelectedCount());
2521 int i = 0;
2522 while(pos)
2523 indexes.get()[i++] = m_CommitList.GetNextSelectedItem(pos);
2524 // don't move any item if the last selected item is the last item in the m_CommitList
2525 // (that would change the order of the selected items)
2526 if (!moveToBottom && indexes.get()[m_CommitList.GetSelectedCount() - 1] >= m_CommitList.GetItemCount() - 1)
2527 return;
2528 int count = m_CommitList.GetItemCount() - 1;
2529 // iterate over the indexes backwards in order to correctly move multiselected items
2530 for (i = m_CommitList.GetSelectedCount() - 1; i >= 0; i--)
2532 int index = indexes.get()[i];
2533 count = moveToBottom ? count : (index + 1);
2534 while (index < count)
2536 CGitHash old = m_CommitList.m_logEntries[index + 1];
2537 m_CommitList.m_logEntries[index + 1] = m_CommitList.m_logEntries[index];
2538 m_CommitList.m_logEntries[index] = old;
2539 m_CommitList.RecalculateShownList(&m_CommitList.m_arShownList);
2540 m_CommitList.SetItemState(index, 0, LVIS_SELECTED);
2541 m_CommitList.SetItemState(index + 1, LVIS_SELECTED, LVIS_SELECTED);
2542 changed = true;
2543 index++;
2545 count--;
2547 m_CommitList.EnsureVisible(indexes.get()[m_CommitList.GetSelectedCount() - 1] + 1, false);
2548 if (changed)
2550 m_CommitList.Invalidate();
2551 m_CommitList.SetFocus();
2555 LRESULT CRebaseDlg::OnTaskbarBtnCreated(WPARAM wParam, LPARAM lParam)
2557 m_pTaskbarList.Release();
2558 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
2559 return __super::OnTaskbarButtonCreated(wParam, lParam);
2562 void CRebaseDlg::OnLvnItemchangedLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2564 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2565 *pResult = 0;
2566 if(m_CommitList.m_bNoDispUpdates)
2567 return;
2568 if (pNMLV->iItem >= 0)
2570 this->m_CommitList.m_nSearchIndex = pNMLV->iItem;
2571 if (pNMLV->iSubItem != 0)
2572 return;
2573 if (pNMLV->iItem == (int)m_CommitList.m_arShownList.size())
2575 // remove the selected state
2576 if (pNMLV->uChanged & LVIF_STATE)
2578 m_CommitList.SetItemState(pNMLV->iItem, 0, LVIS_SELECTED);
2579 FillLogMessageCtrl();
2581 return;
2583 if (pNMLV->uChanged & LVIF_STATE)
2584 FillLogMessageCtrl();
2586 else
2587 FillLogMessageCtrl();
2590 void CRebaseDlg::FillLogMessageCtrl()
2592 int selCount = m_CommitList.GetSelectedCount();
2593 if (selCount == 1 && (m_RebaseStage == CHOOSE_BRANCH || m_RebaseStage == CHOOSE_COMMIT_PICK_MODE))
2595 POSITION pos = m_CommitList.GetFirstSelectedItemPosition();
2596 int selIndex = m_CommitList.GetNextSelectedItem(pos);
2597 GitRevLoglist* pLogEntry = m_CommitList.m_arShownList.SafeGetAt(selIndex);
2598 m_FileListCtrl.UpdateWithGitPathList(pLogEntry->GetFiles(&m_CommitList));
2599 m_FileListCtrl.m_CurrentVersion = pLogEntry->m_CommitHash;
2600 m_FileListCtrl.Show(GITSLC_SHOWVERSIONED);
2601 m_LogMessageCtrl.Call(SCI_SETREADONLY, FALSE);
2602 m_LogMessageCtrl.SetText(pLogEntry->GetSubject() + _T("\n") + pLogEntry->GetBody());
2603 m_LogMessageCtrl.Call(SCI_SETREADONLY, TRUE);
2606 void CRebaseDlg::OnBnClickedCheckCherryPickedFrom()
2608 UpdateData();
2611 LRESULT CRebaseDlg::OnRebaseActionMessage(WPARAM, LPARAM)
2613 if (m_RebaseStage == REBASE_ERROR || m_RebaseStage == REBASE_CONFLICT)
2615 GitRevLoglist* pRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
2616 int mode = pRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_MODE_MASK;
2617 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SKIP)
2619 if (!RunGitCmdRetryOrAbort(_T("git.exe reset --hard")))
2621 m_FileListCtrl.Clear();
2622 m_RebaseStage = REBASE_CONTINUE;
2623 UpdateCurrentStatus();
2627 return 0;
2631 void CRebaseDlg::OnBnClickedSplitAllOptions()
2633 switch (m_SplitAllOptions.GetCurrentEntry())
2635 case 0:
2636 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_PICK);
2637 break;
2638 case 1:
2639 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
2640 break;
2641 case 2:
2642 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
2643 break;
2644 case 3:
2645 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SKIP);
2646 break;
2647 case 4:
2648 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
2649 break;
2650 case 5:
2651 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
2652 break;
2653 default:
2654 ATLASSERT(false);
2658 void CRebaseDlg::OnBnClickedRebaseSplitCommit()
2660 UpdateData();
2663 static bool GetCompareHash(const CString& ref, const CGitHash& hash)
2665 CGitHash refHash;
2666 if (g_Git.GetHash(refHash, ref))
2667 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get hash of \"") + ref + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
2668 return refHash.IsEmpty() || (hash == refHash);
2671 void CRebaseDlg::OnBnClickedButtonOnto()
2673 m_Onto = CBrowseRefsDlg::PickRef(false, m_Onto);
2674 if (!m_Onto.IsEmpty())
2676 // make sure that the user did not select upstream, selected branch or HEAD
2677 CGitHash hash;
2678 if (g_Git.GetHash(hash, m_Onto))
2680 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_BranchCtrl.GetString() + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
2681 m_Onto.Empty();
2682 ((CButton*)GetDlgItem(IDC_BUTTON_ONTO))->SetCheck(m_Onto.IsEmpty() ? BST_UNCHECKED : BST_CHECKED);
2683 return;
2685 if (GetCompareHash(_T("HEAD"), hash) || GetCompareHash(m_UpstreamCtrl.GetString(), hash) || GetCompareHash(m_BranchCtrl.GetString(), hash))
2686 m_Onto.Empty();
2688 if (m_Onto.IsEmpty())
2689 m_tooltips.DelTool(IDC_BUTTON_ONTO);
2690 else
2691 m_tooltips.AddTool(IDC_BUTTON_ONTO, m_Onto);
2692 ((CButton*)GetDlgItem(IDC_BUTTON_ONTO))->SetCheck(m_Onto.IsEmpty() ? BST_UNCHECKED : BST_CHECKED);
2693 FetchLogList();
2696 void CRebaseDlg::OnHelp()
2698 HtmlHelp(0x20000 + (m_IsCherryPick ? IDD_REBASECHERRYPICK : IDD_REBASE));
2701 int CRebaseDlg::RunGitCmdRetryOrAbort(const CString& cmd)
2703 while (true)
2705 CString out;
2706 if (g_Git.Run(cmd, &out, CP_UTF8))
2708 AddLogString(cmd);
2709 AddLogString(CString(MAKEINTRESOURCE(IDS_FAIL)));
2710 AddLogString(out);
2711 CString msg;
2712 msg.Format(L"\"%s\" failed.\n%s", (LPCTSTR)cmd, (LPCTSTR)out);
2713 if (CMessageBox::Show(GetSafeHwnd(), msg, _T("TortoiseGit"), 1, IDI_ERROR, CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY)), CString(MAKEINTRESOURCE(IDS_MSGBOX_ABORT))) != 1)
2714 return -1;
2716 else
2717 return 0;