Fixed issue #1899: Support cherry-picking merge revisions
[TortoiseGit.git] / src / TortoiseProc / RebaseDlg.cpp
blobce4ff9a660bd06785c87488ea7cc7c0dc135ef34
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 /*=NULL*/)
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(WM_TASKBARBTNCREATED, 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_LEFT);
134 AddAnchor(IDC_REBASE_COMBOXEX_BRANCH,TOP_LEFT);
135 AddAnchor(IDC_REBASE_STATIC_UPSTREAM,TOP_LEFT);
136 AddAnchor(IDC_REBASE_STATIC_BRANCH,TOP_LEFT);
137 AddAnchor(IDHELP, BOTTOM_RIGHT);
138 AddAnchor(IDC_REBASE_CHECK_FORCE,TOP_RIGHT);
139 AddAnchor(IDC_REBASE_CHECK_PRESERVEMERGES, TOP_LEFT);
140 AddAnchor(IDC_CHECK_CHERRYPICKED_FROM, TOP_RIGHT);
141 AddAnchor(IDC_REBASE_SPLIT_COMMIT, BOTTOM_RIGHT);
142 AddAnchor(IDC_REBASE_POST_BUTTON,BOTTOM_LEFT);
144 this->AddOthersToAnchor();
147 BOOL CRebaseDlg::OnInitDialog()
149 CResizableStandAloneDialog::OnInitDialog();
150 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
152 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
153 // do this, Explorer would be unable to send that message to our window if we
154 // were running elevated. It's OK to make the call all the time, since if we're
155 // not elevated, this is a no-op.
156 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
157 typedef BOOL STDAPICALLTYPE ChangeWindowMessageFilterExDFN(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
158 CAutoLibrary hUser = AtlLoadSystemLibraryUsingFullPath(_T("user32.dll"));
159 if (hUser)
161 ChangeWindowMessageFilterExDFN *pfnChangeWindowMessageFilterEx = (ChangeWindowMessageFilterExDFN*)GetProcAddress(hUser, "ChangeWindowMessageFilterEx");
162 if (pfnChangeWindowMessageFilterEx)
164 pfnChangeWindowMessageFilterEx(m_hWnd, WM_TASKBARBTNCREATED, MSGFLT_ALLOW, &cfs);
167 m_pTaskbarList.Release();
168 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
169 m_pTaskbarList = nullptr;
171 CRect rectDummy;
172 //IDC_REBASE_DUMY_TAB
174 GetClientRect(m_DlgOrigRect);
175 m_CommitList.GetClientRect(m_CommitListOrigRect);
177 CWnd *pwnd=this->GetDlgItem(IDC_REBASE_DUMY_TAB);
178 pwnd->GetWindowRect(&rectDummy);
179 this->ScreenToClient(rectDummy);
181 if (!m_ctrlTabCtrl.Create(CMFCTabCtrl::STYLE_FLAT, rectDummy, this, IDC_REBASE_TAB))
183 TRACE0("Failed to create output tab window\n");
184 return FALSE; // fail to create
186 m_ctrlTabCtrl.SetResizeMode(CMFCTabCtrl::RESIZE_NO);
187 // Create output panes:
188 //const DWORD dwStyle = LBS_NOINTEGRALHEIGHT | WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL;
189 DWORD dwStyle =LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP | WS_CHILD | WS_VISIBLE;
191 if (! this->m_FileListCtrl.Create(dwStyle,rectDummy,&this->m_ctrlTabCtrl,0) )
193 TRACE0("Failed to create output windows\n");
194 return FALSE; // fail to create
196 m_FileListCtrl.m_hwndLogicalParent = this;
198 if( ! this->m_LogMessageCtrl.Create(_T("Scintilla"),_T("source"),0,rectDummy,&m_ctrlTabCtrl,0,0) )
200 TRACE0("Failed to create log message control");
201 return FALSE;
203 m_ProjectProperties.ReadProps();
204 m_LogMessageCtrl.Init(m_ProjectProperties);
205 m_LogMessageCtrl.SetFont((CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8));
206 m_LogMessageCtrl.Call(SCI_SETREADONLY, TRUE);
208 dwStyle = LBS_NOINTEGRALHEIGHT | WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL;
210 if (!m_wndOutputRebase.Create(_T("Scintilla"),_T("source"),0,rectDummy, &m_ctrlTabCtrl, 0,0) )
212 TRACE0("Failed to create output windows\n");
213 return -1; // fail to create
215 m_wndOutputRebase.Init(-1);
216 m_wndOutputRebase.SetFont((CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8));
217 m_wndOutputRebase.Call(SCI_SETREADONLY, TRUE);
219 m_tooltips.AddTool(IDC_REBASE_CHECK_FORCE,IDS_REBASE_FORCE_TT);
220 m_tooltips.AddTool(IDC_REBASE_ABORT, IDS_REBASE_ABORT_TT);
221 m_tooltips.AddTool(IDC_REBASE_CHECK_PRESERVEMERGES, IDS_REBASE_PRESERVEMERGES_TT);
224 CString temp;
225 temp.LoadString(IDS_PROC_REBASE_SELECTALL_PICK);
226 m_SplitAllOptions.AddEntry(temp);
227 temp.LoadString(IDS_PROC_REBASE_SELECTALL_SQUASH);
228 m_SplitAllOptions.AddEntry(temp);
229 temp.LoadString(IDS_PROC_REBASE_SELECTALL_EDIT);
230 m_SplitAllOptions.AddEntry(temp);
231 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_SKIP);
232 m_SplitAllOptions.AddEntry(temp);
233 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_SQUASH);
234 m_SplitAllOptions.AddEntry(temp);
235 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_EDIT);
236 m_SplitAllOptions.AddEntry(temp);
239 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);
241 m_ctrlTabCtrl.AddTab(&m_FileListCtrl, CString(MAKEINTRESOURCE(IDS_PROC_REVISIONFILES)));
242 m_ctrlTabCtrl.AddTab(&m_LogMessageCtrl, CString(MAKEINTRESOURCE(IDS_PROC_COMMITMESSAGE)), 1);
243 AddRebaseAnchor();
245 AdjustControlSize(IDC_CHECK_CHERRYPICKED_FROM);
246 AdjustControlSize(IDC_REBASE_SPLIT_COMMIT);
248 CString sWindowTitle;
249 GetWindowText(sWindowTitle);
250 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, sWindowTitle);
252 EnableSaveRestore(_T("RebaseDlg"));
254 DWORD yPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RebaseDlgSizer"));
255 RECT rcDlg, rcLogMsg, rcFileList;
256 GetClientRect(&rcDlg);
257 m_CommitList.GetWindowRect(&rcLogMsg);
258 ScreenToClient(&rcLogMsg);
259 this->m_ctrlTabCtrl.GetWindowRect(&rcFileList);
260 ScreenToClient(&rcFileList);
261 if (yPos)
263 RECT rectSplitter;
264 m_wndSplitter.GetWindowRect(&rectSplitter);
265 ScreenToClient(&rectSplitter);
266 int delta = yPos - rectSplitter.top;
267 if ((rcLogMsg.bottom + delta > rcLogMsg.top)&&(rcLogMsg.bottom + delta < rcFileList.bottom - 30))
269 m_wndSplitter.SetWindowPos(NULL, 0, yPos, 0, 0, SWP_NOSIZE);
270 DoSize(delta);
274 if (this->m_RebaseStage == CHOOSE_BRANCH && !m_IsCherryPick)
276 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();
419 void CRebaseDlg::SetSplitterRange()
421 if ((m_CommitList)&&(m_ctrlTabCtrl))
423 CRect rcTop;
424 m_CommitList.GetWindowRect(rcTop);
425 ScreenToClient(rcTop);
426 CRect rcMiddle;
427 m_ctrlTabCtrl.GetWindowRect(rcMiddle);
428 ScreenToClient(rcMiddle);
429 if (rcMiddle.Height() && rcMiddle.Width())
430 m_wndSplitter.SetRange(rcTop.top+60, rcMiddle.bottom-80);
434 void CRebaseDlg::OnSize(UINT nType,int cx, int cy)
436 // first, let the resizing take place
437 __super::OnSize(nType, cx, cy);
439 //set range
440 SetSplitterRange();
443 void CRebaseDlg::SaveSplitterPos()
445 if (!IsIconic())
447 CRegDWORD regPos = CRegDWORD(_T("Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RebaseDlgSizer"));
448 RECT rectSplitter;
449 m_wndSplitter.GetWindowRect(&rectSplitter);
450 ScreenToClient(&rectSplitter);
451 regPos = rectSplitter.top;
455 void CRebaseDlg::LoadBranchInfo()
457 m_BranchCtrl.SetMaxHistoryItems(0x7FFFFFFF);
458 m_UpstreamCtrl.SetMaxHistoryItems(0x7FFFFFFF);
460 STRING_VECTOR list;
461 list.clear();
462 int current = -1;
463 g_Git.GetBranchList(list,&current,CGit::BRANCH_ALL);
464 m_BranchCtrl.SetList(list);
465 if (current >= 0)
466 m_BranchCtrl.SetCurSel(current);
467 else
468 m_BranchCtrl.AddString(g_Git.GetCurrentBranch(true));
469 list.clear();
470 g_Git.GetBranchList(list, NULL, CGit::BRANCH_ALL_F);
471 g_Git.GetTagList(list);
472 m_UpstreamCtrl.SetList(list);
474 AddBranchToolTips(&m_BranchCtrl);
476 if(!m_Upstream.IsEmpty())
478 m_UpstreamCtrl.AddString(m_Upstream);
480 else
482 //Select pull-remote from current branch
483 CString pullRemote, pullBranch;
484 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
486 CString defaultUpstream;
487 defaultUpstream.Format(L"remotes/%s/%s", (LPCTSTR)pullRemote, (LPCTSTR)pullBranch);
488 int found = m_UpstreamCtrl.FindStringExact(0, defaultUpstream);
489 if(found >= 0)
490 m_UpstreamCtrl.SetCurSel(found);
491 else
492 m_UpstreamCtrl.SetCurSel(-1);
494 AddBranchToolTips(&m_UpstreamCtrl);
497 void CRebaseDlg::OnCbnSelchangeBranch()
499 FetchLogList();
502 void CRebaseDlg::OnCbnSelchangeUpstream()
504 FetchLogList();
507 void CRebaseDlg::FetchLogList()
509 CGitHash base,hash,upstream;
510 m_IsFastForward=FALSE;
512 if (m_BranchCtrl.GetString().IsEmpty())
514 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_SELECTBRANCH)));
515 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
516 return;
519 if (g_Git.GetHash(hash, m_BranchCtrl.GetString()))
521 m_CommitList.ShowText(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_BranchCtrl.GetString() + _T("\".")));
522 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
523 return;
526 if (m_UpstreamCtrl.GetString().IsEmpty())
528 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_SELECTUPSTREAM)));
529 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
530 return;
533 if (g_Git.GetHash(upstream, m_UpstreamCtrl.GetString()))
535 m_CommitList.ShowText(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_UpstreamCtrl.GetString() + _T("\".")));
536 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
537 return;
540 if (hash == upstream)
542 m_CommitList.Clear();
543 CString text;
544 text.Format(IDS_REBASE_EQUAL_FMT, (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)this->m_UpstreamCtrl.GetString());
546 m_CommitList.ShowText(text);
547 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
548 if (m_bRebaseAutoStart)
549 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_ABORT, BN_CLICKED), (LPARAM)GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd());
550 return;
553 if (g_Git.IsFastForward(m_BranchCtrl.GetString(), m_UpstreamCtrl.GetString(), &base) && m_Onto.IsEmpty())
555 this->m_IsFastForward=TRUE;
557 m_CommitList.Clear();
558 CString text;
559 text.Format(IDS_REBASE_FASTFORWARD_FMT, (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)this->m_UpstreamCtrl.GetString(),
560 (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)this->m_UpstreamCtrl.GetString());
562 m_CommitList.ShowText(text);
563 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(true);
564 SetContinueButtonText();
566 return ;
569 if (!m_bForce && m_Onto.IsEmpty())
571 if (base == upstream)
573 m_CommitList.Clear();
574 CString text;
575 text.Format(IDS_REBASE_UPTODATE_FMT, (LPCTSTR)m_BranchCtrl.GetString());
576 m_CommitList.ShowText(text);
577 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(m_CommitList.GetItemCount());
578 SetContinueButtonText();
579 return;
583 m_CommitList.Clear();
584 CString refFrom = g_Git.FixBranchName(m_UpstreamCtrl.GetString());
585 CString refTo = g_Git.FixBranchName(m_BranchCtrl.GetString());
586 CString range;
587 range.Format(_T("%s..%s"), (LPCTSTR)refFrom, (LPCTSTR)refTo);
588 this->m_CommitList.FillGitLog(nullptr, &range, m_bPreserveMerges ? 0 : CGit::LOG_INFO_NO_MERGE);
590 if( m_CommitList.GetItemCount() == 0 )
591 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_PROC_NOTHINGTOREBASE)));
593 #if 0
594 if(m_CommitList.m_logEntries[m_CommitList.m_logEntries.size()-1].m_ParentHash.size() >=0 )
596 if(upstream == m_CommitList.m_logEntries[m_CommitList.m_logEntries.size()-1].m_ParentHash[0])
598 m_CommitList.Clear();
599 m_CommitList.ShowText(_T("Nothing Rebase"));
602 #endif
604 m_tooltips.Pop();
605 AddBranchToolTips(&this->m_BranchCtrl);
606 AddBranchToolTips(&this->m_UpstreamCtrl);
608 // Default all actions to 'pick'
609 std::map<CGitHash, size_t> revIxMap;
610 for (size_t i = 0; i < m_CommitList.m_logEntries.size(); ++i)
612 GitRevLoglist& rev = m_CommitList.m_logEntries.GetGitRevAt(i);
613 rev.GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_PICK;
614 revIxMap[rev.m_CommitHash] = i;
617 // Default to skip when already in upstream
618 if (!m_Onto.IsEmpty())
619 refFrom = g_Git.FixBranchName(m_Onto);
620 CString cherryCmd;
621 cherryCmd.Format(L"git.exe cherry \"%s\" \"%s\"", (LPCTSTR)refFrom, (LPCTSTR)refTo);
622 bool bHasSKip = false;
623 g_Git.Run(cherryCmd, [&](const CStringA& line)
625 if (line.GetLength() < 2)
626 return;
627 if (line[0] != '-')
628 return; // Don't skip (only skip commits starting with a '-')
629 CString hash = CUnicodeUtils::GetUnicode(line.Mid(1));
630 hash.Trim();
631 auto itIx = revIxMap.find(CGitHash(hash));
632 if (itIx == revIxMap.end())
633 return; // Not found?? Should not occur...
635 // Found. Skip it.
636 m_CommitList.m_logEntries.GetGitRevAt(itIx->second).GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_SKIP;
637 m_droppedCommitsMap[m_CommitList.m_logEntries.GetGitRevAt(itIx->second).m_CommitHash] = m_CommitList.m_logEntries.GetGitRevAt(itIx->second).m_ParentHash;
638 bHasSKip = true;
641 m_CommitList.Invalidate();
642 if (bHasSKip)
644 m_CtrlStatusText.SetWindowText(CString(MAKEINTRESOURCE(IDS_REBASE_AUTOSKIPPED)));
645 m_bStatusWarning = true;
647 else
649 m_CtrlStatusText.SetWindowText(m_sStatusText);
650 m_bStatusWarning = false;
652 m_CtrlStatusText.Invalidate();
654 if(m_CommitList.m_IsOldFirst)
655 this->m_CurrentRebaseIndex = -1;
656 else
657 this->m_CurrentRebaseIndex = (int)m_CommitList.m_logEntries.size();
659 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(m_CommitList.GetItemCount());
660 SetContinueButtonText();
663 void CRebaseDlg::AddBranchToolTips(CHistoryCombo *pBranch)
665 if(pBranch)
667 CString text=pBranch->GetString();
668 CString tooltip;
670 if (text.IsEmpty())
672 pBranch->DisableTooltip();
673 return;
676 GitRev rev;
677 if (rev.GetCommit(text))
679 MessageBox(rev.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
680 pBranch->DisableTooltip();
681 return;
684 tooltip.Format(_T("%s: %s\n%s: %s <%s>\n%s: %s\n%s:\n%s\n%s"),
685 (LPCTSTR)CString(MAKEINTRESOURCE(IDS_LOG_REVISION)),
686 (LPCTSTR)rev.m_CommitHash.ToString(),
687 (LPCTSTR)CString(MAKEINTRESOURCE(IDS_LOG_AUTHOR)),
688 (LPCTSTR)rev.GetAuthorName(),
689 (LPCTSTR)rev.GetAuthorEmail(),
690 (LPCTSTR)CString(MAKEINTRESOURCE(IDS_LOG_DATE)),
691 (LPCTSTR)CLoglistUtils::FormatDateAndTime(rev.GetAuthorDate(), DATE_LONGDATE),
692 (LPCTSTR)CString(MAKEINTRESOURCE(IDS_LOG_MESSAGE)),
693 (LPCTSTR)rev.GetSubject(),
694 (LPCTSTR)rev.GetBody());
696 pBranch->DisableTooltip();
697 this->m_tooltips.AddTool(pBranch->GetComboBoxCtrl(),tooltip);
701 BOOL CRebaseDlg::PreTranslateMessage(MSG*pMsg)
703 if (pMsg->message == WM_KEYDOWN)
705 switch (pMsg->wParam)
707 case ' ':
708 if (LogListHasFocus(pMsg->hwnd)
709 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_PICK)
710 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SQUASH)
711 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_EDIT)
712 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SKIP))
714 m_CommitList.ShiftSelectedRebaseAction();
715 return TRUE;
717 break;
718 case 'P':
719 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_PICK))
721 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_PICK);
722 return TRUE;
724 break;
725 case 'S':
726 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SKIP))
728 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SKIP);
729 return TRUE;
731 break;
732 case 'Q':
733 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SQUASH))
735 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
736 return TRUE;
738 break;
739 case 'E':
740 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_EDIT))
742 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
743 return TRUE;
745 break;
746 case 'A':
747 if(LogListHasFocus(pMsg->hwnd) && GetAsyncKeyState(VK_CONTROL) & 0x8000)
749 // select all entries
750 for (int i = 0; i < m_CommitList.GetItemCount(); ++i)
752 m_CommitList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
754 return TRUE;
756 break;
757 case VK_F5:
759 Refresh();
760 return TRUE;
762 break;
763 case VK_RETURN:
765 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
767 if (GetDlgItem(IDC_REBASE_CONTINUE)->IsWindowEnabled())
768 GetDlgItem(IDC_REBASE_CONTINUE)->SetFocus();
769 else if (GetDlgItem(IDC_REBASE_ABORT)->IsWindowEnabled())
770 GetDlgItem(IDC_REBASE_ABORT)->SetFocus();
771 else
772 GetDlgItem(IDHELP)->SetFocus();
773 return TRUE;
776 break;
777 /* Avoid TAB control destroy but dialog exist*/
778 case VK_ESCAPE:
779 case VK_CANCEL:
781 TCHAR buff[128] = { 0 };
782 ::GetClassName(pMsg->hwnd,buff,128);
785 /* Use MSFTEDIT_CLASS http://msdn.microsoft.com/en-us/library/bb531344.aspx */
786 if (_tcsnicmp(buff, MSFTEDIT_CLASS, 128) == 0 || //Unicode and MFC 2012 and later
787 _tcsnicmp(buff, RICHEDIT_CLASS, 128) == 0 || //ANSI or MFC 2010
788 _tcsnicmp(buff,_T("Scintilla"),128)==0 ||
789 _tcsnicmp(buff,_T("SysListView32"),128)==0||
790 ::GetParent(pMsg->hwnd) == this->m_ctrlTabCtrl.m_hWnd)
792 this->PostMessage(WM_KEYDOWN,VK_ESCAPE,0);
793 return TRUE;
798 else if (pMsg->message == WM_NEXTDLGCTL)
800 HWND hwnd = GetFocus()->GetSafeHwnd();
801 if (hwnd == m_LogMessageCtrl.GetSafeHwnd() || hwnd == m_wndOutputRebase.GetSafeHwnd())
803 if (GetDlgItem(IDC_REBASE_CONTINUE)->IsWindowEnabled())
804 GetDlgItem(IDC_REBASE_CONTINUE)->SetFocus();
805 else if (GetDlgItem(IDC_REBASE_ABORT)->IsWindowEnabled())
806 GetDlgItem(IDC_REBASE_ABORT)->SetFocus();
807 else
808 GetDlgItem(IDHELP)->SetFocus();
809 return TRUE;
812 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
815 bool CRebaseDlg::LogListHasFocus(HWND hwnd)
817 TCHAR buff[128] = { 0 };
818 ::GetClassName(hwnd, buff, 128);
820 if(_tcsnicmp(buff, _T("SysListView32"), 128) == 0)
821 return true;
822 return false;
825 bool CRebaseDlg::LogListHasMenuItem(int i)
827 return (m_CommitList.m_ContextMenuMask & m_CommitList.GetContextMenuBit(i)) != 0;
830 int CRebaseDlg::CheckRebaseCondition()
832 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
834 if( !g_Git.CheckCleanWorkTree() )
836 if ((!m_IsCherryPick && g_Git.GetConfigValueBool(L"rebase.autostash")) || CMessageBox::Show(NULL, IDS_ERROR_NOCLEAN_STASH, IDS_APPNAME, 1, IDI_QUESTION, IDS_STASHBUTTON, IDS_ABORTBUTTON) == 1)
838 CString cmd,out;
839 cmd=_T("git.exe stash");
840 this->AddLogString(cmd);
841 if (g_Git.Run(cmd, &out, CP_UTF8))
843 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK);
844 return -1;
846 m_bStashed = true;
848 else
849 return -1;
851 //Todo Check $REBASE_ROOT
852 //Todo Check $DOTEST
854 if (!CAppUtils::CheckUserData())
855 return -1;
857 //Todo call pre_rebase_hook
858 return 0;
861 void CRebaseDlg::CheckRestoreStash()
863 bool autoStash = !m_IsCherryPick && g_Git.GetConfigValueBool(L"rebase.autostash");
864 if (m_bStashed && (autoStash || CMessageBox::Show(nullptr, IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) == IDYES))
865 CAppUtils::StashPop(autoStash ? 0 : 1);
866 m_bStashed = false;
869 int CRebaseDlg::StartRebase()
871 CString cmd,out;
872 m_OrigHEADBranch = g_Git.GetCurrentBranch(true);
874 m_OrigHEADHash.Empty();
875 if (g_Git.GetHash(m_OrigHEADHash, _T("HEAD")))
877 AddLogString(CString(MAKEINTRESOURCE(IDS_PROC_NOHEAD)));
878 return -1;
880 //Todo
881 //git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
882 // echo "detached HEAD" > "$DOTEST"/head-name
884 cmd.Format(_T("git.exe update-ref ORIG_HEAD ") + m_OrigHEADHash.ToString());
885 if(g_Git.Run(cmd,&out,CP_UTF8))
887 AddLogString(_T("update ORIG_HEAD Fail"));
888 return -1;
891 m_OrigUpstreamHash.Empty();
892 if (g_Git.GetHash(m_OrigUpstreamHash, (m_IsCherryPick || m_Onto.IsEmpty()) ? m_UpstreamCtrl.GetString() : m_Onto))
894 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);
895 return -1;
898 if( !this->m_IsCherryPick )
900 cmd.Format(_T("git.exe checkout -f %s --"), (LPCTSTR)m_OrigUpstreamHash.ToString());
901 this->AddLogString(cmd);
902 if (RunGitCmdRetryOrAbort(cmd))
903 return -1;
906 CString log;
907 if( !this->m_IsCherryPick )
909 if (g_Git.GetHash(m_OrigBranchHash, m_BranchCtrl.GetString()))
911 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_BranchCtrl.GetString() + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
912 return -1;
914 log.Format(_T("%s\r\n"), (LPCTSTR)CString(MAKEINTRESOURCE(IDS_PROC_REBASE_STARTREBASE)));
916 else
917 log.Format(_T("%s\r\n"), (LPCTSTR)CString(MAKEINTRESOURCE(IDS_PROC_REBASE_STARTCHERRYPICK)));
919 this->AddLogString(log);
920 for (int i = 0; i < m_CommitList.GetItemCount(); ++i)
922 m_rewrittenCommitsMap[((GitRevLoglist*)m_CommitList.m_arShownList[i])->m_CommitHash] = CGitHash();
924 if (m_bPreserveMerges)
926 CString mergecmd;
927 mergecmd.Format(L"git merge-base --all %s %s", (LPCTSTR)m_OrigHEADHash.ToString(), (LPCTSTR)m_OrigUpstreamHash.ToString());
928 g_Git.Run(mergecmd, [&](const CStringA& line)
930 CGitHash hash;
931 hash.ConvertFromStrA(line);
932 if (hash.IsEmpty())
933 return;
934 m_rewrittenCommitsMap[hash] = m_OrigUpstreamHash;
937 return 0;
939 int CRebaseDlg::VerifyNoConflict()
941 int hasConflicts = g_Git.HasWorkingTreeConflicts();
942 if (hasConflicts < 0)
944 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
945 return -1;
947 if (hasConflicts)
949 CMessageBox::Show(NULL, IDS_PROGRS_CONFLICTSOCCURED, IDS_APPNAME, MB_OK);
950 return -1;
952 CleanUpRebaseActiveFolder();
953 return 0;
957 static bool IsLocalBranch(CString ref)
959 STRING_VECTOR list;
960 g_Git.GetBranchList(list, nullptr, CGit::BRANCH_LOCAL);
961 return std::find(list.cbegin(), list.cend(), ref) != list.cend();
964 int CRebaseDlg::FinishRebase()
966 if (m_bFinishedRebase)
967 return 0;
969 m_bFinishedRebase = true;
970 if(this->m_IsCherryPick) //cherry pick mode no "branch", working at upstream branch
972 m_sStatusText.LoadString(IDS_DONE);
973 m_CtrlStatusText.SetWindowText(m_sStatusText);
974 m_bStatusWarning = false;
975 m_CtrlStatusText.Invalidate();
976 return 0;
979 RewriteNotes();
981 CGitHash head;
982 if (g_Git.GetHash(head, _T("HEAD")))
984 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
985 return -1;
987 CString out,cmd;
989 if (IsLocalBranch(m_BranchCtrl.GetString()))
991 cmd.Format(_T("git.exe checkout -f -B %s %s --"), (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)head.ToString());
992 AddLogString(cmd);
993 if (RunGitCmdRetryOrAbort(cmd))
994 return -1;
995 AddLogString(out);
998 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)head.ToString());
999 AddLogString(cmd);
1000 if (RunGitCmdRetryOrAbort(cmd))
1001 return -1;
1002 AddLogString(out);
1004 while (m_ctrlTabCtrl.GetTabsNum() > 1)
1005 m_ctrlTabCtrl.RemoveTab(0);
1006 m_CtrlStatusText.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_REBASEFINISHED)));
1007 m_sStatusText.LoadString(IDS_PROC_REBASEFINISHED);
1008 m_bStatusWarning = false;
1009 m_CtrlStatusText.Invalidate();
1011 m_bRebaseAutoEnd = m_bRebaseAutoStart;
1013 return 0;
1016 void CRebaseDlg::RewriteNotes()
1018 CString rewrites;
1019 for (const auto& entry : m_rewrittenCommitsMap)
1021 if (entry.second.IsEmpty())
1022 continue;
1023 rewrites += entry.first.ToString();
1024 rewrites += L" ";
1025 rewrites += entry.second.ToString();
1026 rewrites += L"\n";
1028 if (rewrites.IsEmpty())
1029 return;
1030 CString tmpfile = GetTempFile();
1031 tmpfile.Replace(L"\\", L"/");
1032 if (!CStringUtils::WriteStringToTextFile((LPCTSTR)tmpfile, (LPCTSTR)rewrites))
1033 return;
1034 SCOPE_EXIT{ ::DeleteFile(tmpfile); };
1035 CString pipefile = GetTempFile();
1036 pipefile.Replace(L"\\", L"/");
1037 CString pipecmd;
1038 pipecmd.Format(L"git notes copy --for-rewrite=rebase < %s", (LPCTSTR)tmpfile);
1039 if (!CStringUtils::WriteStringToTextFile((LPCTSTR)pipefile, (LPCTSTR)pipecmd))
1040 return;
1041 SCOPE_EXIT{ ::DeleteFile(pipefile); };
1042 CString out;
1043 g_Git.Run(L"bash.exe " + pipefile, &out, CP_UTF8);
1046 void CRebaseDlg::OnBnClickedContinue()
1048 if( m_RebaseStage == REBASE_DONE)
1050 OnOK();
1051 CleanUpRebaseActiveFolder();
1052 CheckRestoreStash();
1053 return;
1056 if (m_RebaseStage == CHOOSE_BRANCH || m_RebaseStage == CHOOSE_COMMIT_PICK_MODE)
1058 if (CheckRebaseCondition())
1059 return;
1062 if( this->m_IsFastForward )
1064 CString cmd,out;
1065 if (g_Git.GetHash(m_OrigBranchHash, m_BranchCtrl.GetString()))
1067 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_BranchCtrl.GetString() + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
1068 return;
1070 if (g_Git.GetHash(m_OrigUpstreamHash, m_UpstreamCtrl.GetString()))
1072 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_UpstreamCtrl.GetString() + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
1073 return;
1076 if(!g_Git.IsFastForward(this->m_BranchCtrl.GetString(),this->m_UpstreamCtrl.GetString()))
1078 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1079 AddLogString(_T("No fast forward possible.\r\nMaybe repository changed"));
1080 return;
1083 if (IsLocalBranch(m_BranchCtrl.GetString()))
1085 cmd.Format(_T("git.exe checkout --no-track -f -B %s %s --"), (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)m_UpstreamCtrl.GetString());
1086 AddLogString(cmd);
1087 if (RunGitCmdRetryOrAbort(cmd))
1088 return;
1089 AddLogString(out);
1090 out.Empty();
1092 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)g_Git.FixBranchName(this->m_UpstreamCtrl.GetString()));
1093 CString log;
1094 log.Format(IDS_PROC_REBASE_FFTO, m_UpstreamCtrl.GetString());
1095 this->AddLogString(log);
1097 AddLogString(cmd);
1098 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1099 if (RunGitCmdRetryOrAbort(cmd))
1100 return;
1101 AddLogString(out);
1102 AddLogString(CString(MAKEINTRESOURCE(IDS_DONE)));
1103 m_RebaseStage = REBASE_DONE;
1104 UpdateCurrentStatus();
1106 if (m_bRebaseAutoStart)
1107 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_CONTINUE, BN_CLICKED), (LPARAM)GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd());
1109 return;
1112 if( m_RebaseStage == CHOOSE_BRANCH|| m_RebaseStage == CHOOSE_COMMIT_PICK_MODE )
1114 if(CheckRebaseCondition())
1115 return ;
1116 m_RebaseStage = REBASE_START;
1117 m_FileListCtrl.Clear();
1118 m_FileListCtrl.SetHasCheckboxes(false);
1119 m_FileListCtrl.m_CurrentVersion = L"";
1120 m_ctrlTabCtrl.SetTabLabel(REBASE_TAB_CONFLICT, CString(MAKEINTRESOURCE(IDS_PROC_CONFLICTFILES)));
1121 m_ctrlTabCtrl.AddTab(&m_wndOutputRebase, CString(MAKEINTRESOURCE(IDS_LOG)), 2);
1124 if( m_RebaseStage == REBASE_FINISH )
1126 if(FinishRebase())
1127 return ;
1129 OnOK();
1132 if( m_RebaseStage == REBASE_SQUASH_CONFLICT)
1134 if(VerifyNoConflict())
1135 return;
1136 GitRevLoglist* curRev = (GitRevLoglist*)m_CommitList.m_arShownList[m_CurrentRebaseIndex];
1137 if(this->CheckNextCommitIsSquash())
1138 {//next commit is not squash;
1139 m_RebaseStage = REBASE_SQUASH_EDIT;
1140 this->OnRebaseUpdateUI(0,0);
1141 this->UpdateCurrentStatus();
1142 return ;
1145 m_RebaseStage=REBASE_CONTINUE;
1146 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1147 m_forRewrite.push_back(curRev->m_CommitHash);
1148 this->UpdateCurrentStatus();
1152 if( m_RebaseStage == REBASE_CONFLICT )
1154 if(VerifyNoConflict())
1155 return;
1157 GitRevLoglist* curRev = (GitRevLoglist*)m_CommitList.m_arShownList[m_CurrentRebaseIndex];
1158 // ***************************************************
1159 // ATTENTION: Similar code in CommitDlg.cpp!!!
1160 // ***************************************************
1161 CMassiveGitTask mgtReAddAfterCommit(_T("add --ignore-errors -f"));
1162 CMassiveGitTask mgtReDelAfterCommit(_T("rm --cached --ignore-unmatch"));
1163 CMassiveGitTask mgtAdd(_T("add -f"));
1164 CMassiveGitTask mgtUpdateIndexForceRemove(_T("update-index --force-remove"));
1165 CMassiveGitTask mgtUpdateIndex(_T("update-index"));
1166 CMassiveGitTask mgtRm(_T("rm --ignore-unmatch"));
1167 CMassiveGitTask mgtRmFCache(_T("rm -f --cache"));
1168 CMassiveGitTask mgtReset(_T("reset"), TRUE, true);
1169 for (int i = 0; i < m_FileListCtrl.GetItemCount(); i++)
1171 CTGitPath *entry = (CTGitPath *)m_FileListCtrl.GetItemData(i);
1172 if (entry->m_Checked)
1174 if (entry->m_Action & CTGitPath::LOGACTIONS_UNVER)
1175 mgtAdd.AddFile(entry->GetGitPathString());
1176 else if (entry->m_Action & CTGitPath::LOGACTIONS_DELETED)
1177 mgtUpdateIndexForceRemove.AddFile(entry->GetGitPathString());
1178 else
1179 mgtUpdateIndex.AddFile(entry->GetGitPathString());
1181 if (entry->m_Action & CTGitPath::LOGACTIONS_REPLACED)
1182 mgtRm.AddFile(entry->GetGitOldPathString());
1184 else
1186 if (entry->m_Action & CTGitPath::LOGACTIONS_ADDED || entry->m_Action & CTGitPath::LOGACTIONS_REPLACED)
1188 mgtRmFCache.AddFile(entry->GetGitPathString());
1189 mgtReAddAfterCommit.AddFile(*entry);
1191 if (entry->m_Action & CTGitPath::LOGACTIONS_REPLACED && !entry->GetGitOldPathString().IsEmpty())
1193 mgtReset.AddFile(entry->GetGitOldPathString());
1194 mgtReDelAfterCommit.AddFile(entry->GetGitOldPathString());
1197 else if(!(entry->m_Action & CTGitPath::LOGACTIONS_UNVER))
1199 mgtReset.AddFile(entry->GetGitPathString());
1200 if (entry->m_Action & CTGitPath::LOGACTIONS_DELETED && !(entry->m_Action & CTGitPath::LOGACTIONS_MISSING))
1201 mgtReDelAfterCommit.AddFile(entry->GetGitPathString());
1206 BOOL cancel = FALSE;
1207 bool successful = true;
1208 successful = successful && mgtAdd.Execute(cancel);
1209 successful = successful && mgtUpdateIndexForceRemove.Execute(cancel);
1210 successful = successful && mgtUpdateIndex.Execute(cancel);
1211 successful = successful && mgtRm.Execute(cancel);
1212 successful = successful && mgtRmFCache.Execute(cancel);
1213 successful = successful && mgtReset.Execute(cancel);
1215 if (!successful)
1217 AddLogString(_T("An error occurred while updating the index."));
1218 return;
1221 CString out =_T("");
1222 CString cmd;
1223 cmd.Format(_T("git.exe commit -C %s"), (LPCTSTR)curRev->m_CommitHash.ToString());
1225 AddLogString(cmd);
1227 if(g_Git.Run(cmd,&out,CP_UTF8))
1229 AddLogString(out);
1230 if(!g_Git.CheckCleanWorkTree())
1232 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1233 return;
1237 AddLogString(out);
1239 // update commit message if needed
1240 CString str = m_LogMessageCtrl.GetText().Trim();
1241 if (str != (curRev->GetSubject() + _T("\n") + curRev->GetBody()).Trim())
1243 if (str.Trim().IsEmpty())
1245 CMessageBox::Show(NULL, IDS_PROC_COMMITMESSAGE_EMPTY,IDS_APPNAME, MB_OK | MB_ICONERROR);
1246 return;
1248 CString tempfile = ::GetTempFile();
1249 if (CAppUtils::SaveCommitUnicodeFile(tempfile, str))
1251 CMessageBox::Show(nullptr, _T("Could not save commit message"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1252 return;
1255 out.Empty();
1256 cmd.Format(_T("git.exe commit --amend -F \"%s\""), (LPCTSTR)tempfile);
1257 AddLogString(cmd);
1259 if (g_Git.Run(cmd, &out, CP_UTF8))
1261 AddLogString(out);
1262 if (!g_Git.CheckCleanWorkTree())
1264 CMessageBox::Show(NULL, out, _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1265 return;
1269 AddLogString(out);
1272 if (((DWORD)CRegStdDWORD(_T("Software\\TortoiseGit\\ReaddUnselectedAddedFilesAfterCommit"), TRUE)) == TRUE)
1274 BOOL cancel2 = FALSE;
1275 mgtReAddAfterCommit.Execute(cancel2);
1276 mgtReDelAfterCommit.Execute(cancel2);
1279 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1280 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_EDIT)
1282 m_RebaseStage=REBASE_EDIT;
1283 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
1284 this->UpdateCurrentStatus();
1285 return;
1287 else
1289 m_RebaseStage=REBASE_CONTINUE;
1290 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1291 this->UpdateCurrentStatus();
1293 if (CheckNextCommitIsSquash() == 0) // remember commit msg after edit if next commit if squash
1294 ResetParentForSquash(str);
1295 else
1297 m_SquashMessage.Empty();
1298 CGitHash head;
1299 if (g_Git.GetHash(head, _T("HEAD")))
1301 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1302 return;
1304 m_rewrittenCommitsMap[curRev->m_CommitHash] = head;
1309 if ((m_RebaseStage == REBASE_EDIT || m_RebaseStage == REBASE_CONTINUE || m_bSplitCommit) && CheckNextCommitIsSquash() && (m_bSplitCommit || !g_Git.CheckCleanWorkTree(true)))
1311 if (!m_bSplitCommit && CMessageBox::Show(nullptr, IDS_PROC_REBASE_CONTINUE_NOTCLEAN, IDS_APPNAME, 1, IDI_ERROR, IDS_MSGBOX_OK, IDS_ABORTBUTTON) == 2)
1312 return;
1313 BOOL isFirst = TRUE;
1316 CCommitDlg dlg;
1317 if (isFirst)
1318 dlg.m_sLogMessage = m_LogMessageCtrl.GetText();
1319 dlg.m_bWholeProject = true;
1320 dlg.m_bSelectFilesForCommit = true;
1321 dlg.m_bCommitAmend = isFirst && (m_RebaseStage != REBASE_SQUASH_EDIT); // do not amend on squash_edit stage, we need a normal commit there
1322 CTGitPathList gpl;
1323 gpl.AddPath(CTGitPath());
1324 dlg.m_pathList = gpl;
1325 dlg.m_bAmendDiffToLastCommit = !m_bSplitCommit;
1326 dlg.m_bNoPostActions = true;
1327 if (dlg.m_bCommitAmend)
1328 dlg.m_AmendStr = dlg.m_sLogMessage;
1329 dlg.m_bWarnDetachedHead = false;
1331 if (dlg.DoModal() != IDOK)
1332 return;
1334 isFirst = !m_bSplitCommit; // only select amend on second+ runs if not in split commit mode
1336 m_SquashMessage.Empty();
1337 } while (!g_Git.CheckCleanWorkTree() || (m_bSplitCommit && CMessageBox::Show(GetSafeHwnd(), IDS_REBASE_ADDANOTHERCOMMIT, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) == IDYES));
1339 m_bSplitCommit = FALSE;
1340 UpdateData(FALSE);
1342 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1343 m_RebaseStage = REBASE_CONTINUE;
1344 GitRevLoglist* curRev = (GitRevLoglist*)m_CommitList.m_arShownList[m_CurrentRebaseIndex];
1345 CGitHash head;
1346 if (g_Git.GetHash(head, _T("HEAD")))
1348 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1349 return;
1351 m_rewrittenCommitsMap[curRev->m_CommitHash] = head;
1352 for (const auto& hash : m_forRewrite)
1353 m_rewrittenCommitsMap[hash] = head;
1354 m_forRewrite.clear();
1355 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1356 this->UpdateCurrentStatus();
1359 if( m_RebaseStage == REBASE_EDIT || m_RebaseStage == REBASE_SQUASH_EDIT )
1361 CString str;
1362 GitRevLoglist* curRev = (GitRevLoglist*)m_CommitList.m_arShownList[m_CurrentRebaseIndex];
1364 str=this->m_LogMessageCtrl.GetText();
1365 if(str.Trim().IsEmpty())
1367 CMessageBox::Show(NULL, IDS_PROC_COMMITMESSAGE_EMPTY,IDS_APPNAME, MB_OK | MB_ICONERROR);
1368 return;
1371 CString tempfile=::GetTempFile();
1372 if (CAppUtils::SaveCommitUnicodeFile(tempfile, str))
1374 CMessageBox::Show(nullptr, _T("Could not save commit message"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1375 return;
1378 CString out,cmd;
1380 if( m_RebaseStage == REBASE_SQUASH_EDIT )
1381 cmd.Format(_T("git.exe commit %s-F \"%s\""), (LPCTSTR)m_SquashFirstMetaData, (LPCTSTR)tempfile);
1382 else
1384 CString options;
1385 int isEmpty = IsCommitEmpty(curRev->m_CommitHash);
1386 if (isEmpty == 1)
1387 options = _T("--allow-empty ");
1388 else if (isEmpty < 0)
1389 return;
1390 cmd.Format(_T("git.exe commit --amend %s-F \"%s\""), (LPCTSTR)options, (LPCTSTR)tempfile);
1393 if(g_Git.Run(cmd,&out,CP_UTF8))
1395 if(!g_Git.CheckCleanWorkTree())
1397 CMessageBox::Show(NULL,out,_T("TortoiseGit"),MB_OK|MB_ICONERROR);
1398 return;
1402 ::DeleteFile(tempfile);
1403 AddLogString(out);
1404 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
1406 ResetParentForSquash(str);
1408 else
1409 m_SquashMessage.Empty();
1410 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1411 m_RebaseStage=REBASE_CONTINUE;
1412 CGitHash head;
1413 if (g_Git.GetHash(head, _T("HEAD")))
1415 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1416 return;
1418 m_rewrittenCommitsMap[curRev->m_CommitHash] = head; // we had a reset to parent, so this is not the correct hash
1419 for (const auto& hash : m_forRewrite)
1420 m_rewrittenCommitsMap[hash] = head;
1421 m_forRewrite.clear();
1422 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1423 this->UpdateCurrentStatus();
1427 InterlockedExchange(&m_bThreadRunning, TRUE);
1428 SetControlEnable();
1430 if (AfxBeginThread(RebaseThreadEntry, this)==NULL)
1432 InterlockedExchange(&m_bThreadRunning, FALSE);
1433 CMessageBox::Show(NULL, _T("Create Rebase Thread Fail"), _T("TortoiseGit"), MB_OK | MB_ICONERROR);
1434 SetControlEnable();
1437 void CRebaseDlg::ResetParentForSquash(const CString& commitMessage)
1439 m_SquashMessage = commitMessage;
1440 // reset parent so that we can do "git cherry-pick --no-commit" w/o introducing an unwanted commit
1441 CString cmd = _T("git.exe reset --soft HEAD~1");
1442 m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1443 if (RunGitCmdRetryOrAbort(cmd))
1444 return;
1446 int CRebaseDlg::CheckNextCommitIsSquash()
1448 int index;
1449 if(m_CommitList.m_IsOldFirst)
1450 index=m_CurrentRebaseIndex+1;
1451 else
1452 index=m_CurrentRebaseIndex-1;
1454 GitRevLoglist* curRev;
1457 if(index<0)
1458 return -1;
1459 if(index>= m_CommitList.GetItemCount())
1460 return -1;
1462 curRev = (GitRevLoglist*)m_CommitList.m_arShownList[index];
1464 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1465 return 0;
1466 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SKIP)
1468 if(m_CommitList.m_IsOldFirst)
1469 ++index;
1470 else
1471 --index;
1473 else
1474 return -1;
1476 } while(curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SKIP);
1478 return -1;
1481 int CRebaseDlg::GoNext()
1483 if(m_CommitList.m_IsOldFirst)
1484 ++m_CurrentRebaseIndex;
1485 else
1486 --m_CurrentRebaseIndex;
1487 return 0;
1490 int CRebaseDlg::StateAction()
1492 switch(this->m_RebaseStage)
1494 case CHOOSE_BRANCH:
1495 case CHOOSE_COMMIT_PICK_MODE:
1496 if(StartRebase())
1497 return -1;
1498 m_RebaseStage = REBASE_START;
1499 GoNext();
1500 break;
1503 return 0;
1505 void CRebaseDlg::SetContinueButtonText()
1507 CString Text;
1508 switch(this->m_RebaseStage)
1510 case CHOOSE_BRANCH:
1511 case CHOOSE_COMMIT_PICK_MODE:
1512 if(this->m_IsFastForward)
1513 Text.LoadString(IDS_PROC_STARTREBASEFFBUTTON);
1514 else
1515 Text.LoadString(IDS_PROC_STARTREBASEBUTTON);
1516 break;
1518 case REBASE_START:
1519 case REBASE_ERROR:
1520 case REBASE_CONTINUE:
1521 case REBASE_SQUASH_CONFLICT:
1522 Text.LoadString(IDS_CONTINUEBUTTON);
1523 break;
1525 case REBASE_CONFLICT:
1526 Text.LoadString(IDS_COMMITBUTTON);
1527 break;
1528 case REBASE_EDIT:
1529 Text.LoadString(IDS_AMENDBUTTON);
1530 break;
1532 case REBASE_SQUASH_EDIT:
1533 Text.LoadString(IDS_COMMITBUTTON);
1534 break;
1536 case REBASE_ABORT:
1537 case REBASE_FINISH:
1538 Text.LoadString(IDS_FINISHBUTTON);
1539 break;
1541 case REBASE_DONE:
1542 Text.LoadString(IDS_DONE);
1543 break;
1545 this->GetDlgItem(IDC_REBASE_CONTINUE)->SetWindowText(Text);
1548 void CRebaseDlg::SetControlEnable()
1550 switch(this->m_RebaseStage)
1552 case CHOOSE_BRANCH:
1553 case CHOOSE_COMMIT_PICK_MODE:
1555 this->GetDlgItem(IDC_SPLITALLOPTIONS)->EnableWindow(TRUE);
1556 this->GetDlgItem(IDC_BUTTON_UP2)->EnableWindow(TRUE);
1557 this->GetDlgItem(IDC_BUTTON_DOWN2)->EnableWindow(TRUE);
1559 if(!m_IsCherryPick)
1561 this->GetDlgItem(IDC_REBASE_COMBOXEX_BRANCH)->EnableWindow(TRUE);
1562 this->GetDlgItem(IDC_REBASE_COMBOXEX_UPSTREAM)->EnableWindow(TRUE);
1563 this->GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(TRUE);
1564 this->GetDlgItem(IDC_REBASE_CHECK_FORCE)->EnableWindow(TRUE);
1565 this->GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->EnableWindow(TRUE);
1567 this->m_CommitList.m_ContextMenuMask |= m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_PICK)|
1568 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_SQUASH)|
1569 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_EDIT)|
1570 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_SKIP)|
1571 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_LOG);
1572 break;
1574 case REBASE_START:
1575 case REBASE_CONTINUE:
1576 case REBASE_ABORT:
1577 case REBASE_ERROR:
1578 case REBASE_FINISH:
1579 case REBASE_CONFLICT:
1580 case REBASE_EDIT:
1581 case REBASE_SQUASH_CONFLICT:
1582 case REBASE_DONE:
1583 this->GetDlgItem(IDC_SPLITALLOPTIONS)->EnableWindow(FALSE);
1584 this->GetDlgItem(IDC_REBASE_COMBOXEX_BRANCH)->EnableWindow(FALSE);
1585 this->GetDlgItem(IDC_REBASE_COMBOXEX_UPSTREAM)->EnableWindow(FALSE);
1586 this->GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(FALSE);
1587 this->GetDlgItem(IDC_REBASE_CHECK_FORCE)->EnableWindow(FALSE);
1588 this->GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->EnableWindow(FALSE);
1589 this->GetDlgItem(IDC_BUTTON_UP2)->EnableWindow(FALSE);
1590 this->GetDlgItem(IDC_BUTTON_DOWN2)->EnableWindow(FALSE);
1592 if( m_RebaseStage == REBASE_DONE && (this->m_PostButtonTexts.GetCount() != 0) )
1594 this->GetDlgItem(IDC_STATUS_STATIC)->ShowWindow(SW_HIDE);
1595 this->GetDlgItem(IDC_REBASE_POST_BUTTON)->ShowWindow(SW_SHOWNORMAL);
1596 this->m_PostButton.RemoveAll();
1597 this->m_PostButton.AddEntries(m_PostButtonTexts);
1598 //this->GetDlgItem(IDC_REBASE_POST_BUTTON)->SetWindowText(this->m_PostButtonText);
1600 break;
1603 GetDlgItem(IDC_REBASE_SPLIT_COMMIT)->ShowWindow((m_RebaseStage == REBASE_EDIT || m_RebaseStage == REBASE_SQUASH_EDIT) ? SW_SHOW : SW_HIDE);
1605 if(m_bThreadRunning)
1607 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(FALSE);
1610 else if (m_RebaseStage != REBASE_ERROR)
1612 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(TRUE);
1616 void CRebaseDlg::UpdateProgress()
1618 int index;
1619 CRect rect;
1621 if(m_CommitList.m_IsOldFirst)
1622 index = m_CurrentRebaseIndex+1;
1623 else
1624 index = m_CommitList.GetItemCount()-m_CurrentRebaseIndex;
1626 int finishedCommits = index - 1; // introduced an variable which shows the number handled revisions for the progress bars
1627 if (m_RebaseStage == REBASE_FINISH || finishedCommits == -1)
1628 finishedCommits = index;
1630 m_ProgressBar.SetRange32(0, m_CommitList.GetItemCount());
1631 m_ProgressBar.SetPos(finishedCommits);
1632 if (m_pTaskbarList)
1634 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
1635 m_pTaskbarList->SetProgressValue(m_hWnd, finishedCommits, m_CommitList.GetItemCount());
1638 if(m_CurrentRebaseIndex>=0 && m_CurrentRebaseIndex< m_CommitList.GetItemCount())
1640 CString text;
1641 text.Format(IDS_PROC_REBASING_PROGRESS, index, m_CommitList.GetItemCount());
1642 m_sStatusText = text;
1643 m_CtrlStatusText.SetWindowText(text);
1644 m_bStatusWarning = false;
1645 m_CtrlStatusText.Invalidate();
1648 GitRevLoglist* prevRev = nullptr, *curRev = nullptr;
1650 if( m_CurrentRebaseIndex >= 0 && m_CurrentRebaseIndex< m_CommitList.m_arShownList.GetSize())
1652 curRev = (GitRevLoglist*)m_CommitList.m_arShownList[m_CurrentRebaseIndex];
1655 for (int i = 0; i < m_CommitList.m_arShownList.GetSize(); ++i)
1657 prevRev = (GitRevLoglist*)m_CommitList.m_arShownList[i];
1658 if (prevRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_CURRENT)
1660 prevRev->GetRebaseAction() &= ~CGitLogListBase::LOGACTIONS_REBASE_CURRENT;
1661 m_CommitList.GetItemRect(i,&rect,LVIR_BOUNDS);
1662 m_CommitList.InvalidateRect(rect);
1666 if(curRev)
1668 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_CURRENT;
1669 m_CommitList.GetItemRect(m_CurrentRebaseIndex,&rect,LVIR_BOUNDS);
1670 m_CommitList.InvalidateRect(rect);
1672 m_CommitList.EnsureVisible(m_CurrentRebaseIndex,FALSE);
1675 void CRebaseDlg::UpdateCurrentStatus()
1677 SetContinueButtonText();
1678 SetControlEnable();
1679 UpdateProgress();
1680 if (m_RebaseStage == REBASE_DONE)
1681 GetDlgItem(IDC_REBASE_CONTINUE)->SetFocus();
1684 void CRebaseDlg::AddLogString(CString str)
1686 this->m_wndOutputRebase.SendMessage(SCI_SETREADONLY, FALSE);
1687 CStringA sTextA = m_wndOutputRebase.StringForControl(str);//CUnicodeUtils::GetUTF8(str);
1688 this->m_wndOutputRebase.SendMessage(SCI_DOCUMENTEND);
1689 this->m_wndOutputRebase.SendMessage(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)sTextA);
1690 this->m_wndOutputRebase.SendMessage(SCI_REPLACESEL, 0, (LPARAM)(LPCSTR)"\n");
1691 this->m_wndOutputRebase.SendMessage(SCI_SETREADONLY, TRUE);
1694 int CRebaseDlg::GetCurrentCommitID()
1696 if(m_CommitList.m_IsOldFirst)
1698 return this->m_CurrentRebaseIndex+1;
1701 else
1703 return m_CommitList.GetItemCount()-m_CurrentRebaseIndex;
1707 int CRebaseDlg::IsCommitEmpty(const CGitHash& hash)
1709 CString cmd, tree, ptree;
1710 cmd.Format(_T("git.exe rev-parse -q --verify %s^{tree}"), (LPCTSTR)hash.ToString());
1711 if (g_Git.Run(cmd, &tree, CP_UTF8))
1713 AddLogString(cmd);
1714 AddLogString(tree);
1715 return -1;
1717 cmd.Format(_T("git.exe rev-parse -q --verify %s^^{tree}"), (LPCTSTR)hash.ToString());
1718 if (g_Git.Run(cmd, &ptree, CP_UTF8))
1719 ptree = _T("4b825dc642cb6eb9a060e54bf8d69288fbee4904"); // empty tree
1720 return tree == ptree;
1723 int CRebaseDlg::DoRebase()
1725 CString cmd,out;
1726 if(m_CurrentRebaseIndex <0)
1727 return 0;
1728 if(m_CurrentRebaseIndex >= m_CommitList.GetItemCount() )
1729 return 0;
1731 GitRevLoglist* pRev = (GitRevLoglist*)m_CommitList.m_arShownList[m_CurrentRebaseIndex];
1732 int mode = pRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_MODE_MASK;
1733 CString nocommit;
1735 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SKIP)
1737 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1738 return 0;
1741 bool nextCommitIsSquash = (CheckNextCommitIsSquash() == 0);
1742 if (nextCommitIsSquash || mode != CGitLogListBase::LOGACTIONS_REBASE_PICK)
1743 { // next commit is squash or not pick
1744 if (!this->m_SquashMessage.IsEmpty())
1745 this->m_SquashMessage += _T("\n\n");
1746 this->m_SquashMessage += pRev->GetSubject();
1747 this->m_SquashMessage += _T("\n");
1748 this->m_SquashMessage += pRev->GetBody().TrimRight();
1749 if (m_bAddCherryPickedFrom)
1751 if (!pRev->GetBody().IsEmpty())
1752 m_SquashMessage += _T("\n");
1753 m_SquashMessage += _T("(cherry picked from commit ");
1754 m_SquashMessage += pRev->m_CommitHash.ToString();
1755 m_SquashMessage += _T(")");
1758 else
1760 this->m_SquashMessage.Empty();
1761 m_SquashFirstMetaData.Empty();
1764 if ((nextCommitIsSquash && mode != CGitLogListBase::LOGACTIONS_REBASE_EDIT) || mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1765 { // next or this commit is squash (don't do this on edit->squash sequence)
1766 nocommit=_T(" --no-commit ");
1769 if (nextCommitIsSquash && mode != CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1770 m_SquashFirstMetaData.Format(_T("--date=%s --author=\"%s <%s>\" "), (LPCTSTR)pRev->GetAuthorDate().Format(_T("%Y-%m-%dT%H:%M:%S")), (LPCTSTR)pRev->GetAuthorName(), (LPCTSTR)pRev->GetAuthorEmail());
1772 CString log;
1773 log.Format(_T("%s %d: %s"), (LPCTSTR)CGitLogListBase::GetRebaseActionName(mode), GetCurrentCommitID(), (LPCTSTR)pRev->m_CommitHash.ToString());
1774 AddLogString(log);
1775 AddLogString(pRev->GetSubject());
1776 if (pRev->GetSubject().IsEmpty())
1778 CMessageBox::Show(m_hWnd, IDS_PROC_REBASE_EMPTYCOMMITMSG, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
1779 mode = CGitLogListBase::LOGACTIONS_REBASE_EDIT;
1782 CString cherryPickedFrom;
1783 if (m_bAddCherryPickedFrom)
1784 cherryPickedFrom = _T("-x ");
1785 else if (!m_IsCherryPick && nocommit.IsEmpty())
1786 cherryPickedFrom = _T("--ff "); // for issue #1833: "If the current HEAD is the same as the parent of the cherry-pick’ed commit, then a fast forward to this commit will be performed."
1788 int isEmpty = IsCommitEmpty(pRev->m_CommitHash);
1789 if (isEmpty == 1)
1790 cherryPickedFrom += _T("--allow-empty ");
1791 else if (isEmpty < 0)
1792 return -1;
1794 if (m_IsCherryPick && pRev->m_ParentHash.size() > 1)
1796 CString msg;
1797 msg.Format(IDS_CHERRYPICK_MERGECOMMIT, (LPCTSTR)pRev->m_CommitHash.ToString(), (LPCTSTR)pRev->GetSubject());
1798 CString parent1;
1799 parent1.Format(IDS_PARENT, 1);
1800 CString parent2;
1801 parent2.Format(IDS_PARENT, 2);
1802 CString cancel;
1803 cancel.LoadString(IDS_MSGBOX_CANCEL);
1804 auto ret = CMessageBox::Show(m_hWnd, msg, _T("TortoiseGit"), 3, IDI_QUESTION, parent1, parent2, cancel);
1805 if (ret == 3)
1806 return - 1;
1808 CString mergeParam;
1809 mergeParam.Format(L"-m %d ", ret);
1810 cherryPickedFrom += mergeParam;
1813 while (true)
1815 cmd.Format(_T("git.exe cherry-pick %s%s %s"), (LPCTSTR)cherryPickedFrom, (LPCTSTR)nocommit, (LPCTSTR)pRev->m_CommitHash.ToString());
1816 if (m_bPreserveMerges)
1818 bool parentRewritten = false;
1819 CGitHash currentHeadHash;
1820 if (g_Git.GetHash(currentHeadHash, _T("HEAD")))
1822 m_RebaseStage = REBASE_ERROR;
1823 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
1824 return -1;
1826 if (!m_currentCommits.empty())
1828 for (const auto& commit : m_currentCommits)
1829 m_rewrittenCommitsMap[commit] = currentHeadHash;
1830 m_currentCommits.clear();
1832 m_currentCommits.push_back(pRev->m_CommitHash);
1833 GIT_REV_LIST possibleParents = pRev->m_ParentHash;
1834 GIT_REV_LIST newParents;
1835 for (auto it = possibleParents.cbegin(); it != possibleParents.cend(); it = possibleParents.begin())
1837 CGitHash parent = *it;
1838 possibleParents.erase(it);
1840 const auto rewrittenParent = m_rewrittenCommitsMap.find(parent);
1841 if (rewrittenParent == m_rewrittenCommitsMap.cend())
1843 // no part of the rebase process
1844 newParents.push_back(parent);
1845 continue;
1847 auto droppedCommitParents = m_droppedCommitsMap.find(parent);
1848 if (rewrittenParent->second.IsEmpty() && droppedCommitParents != m_droppedCommitsMap.cend())
1850 parentRewritten = true;
1851 for (const auto& droppedCommitParent : droppedCommitParents->second)
1852 possibleParents.push_back(droppedCommitParent);
1853 continue;
1855 if (rewrittenParent->second.IsEmpty() && parent == pRev->m_ParentHash[0] && pRev->ParentsCount() > 1)
1857 m_RebaseStage = REBASE_ERROR;
1858 AddLogString(_T(""));
1859 AddLogString(_T("Unrecoverable error: Merge commit parent missing."));
1860 return -1;
1863 CGitHash newParent = rewrittenParent->second;
1864 if (newParent.IsEmpty()) // use current HEAD as fallback
1865 newParent = currentHeadHash;
1867 if (newParent != parent)
1868 parentRewritten = true;
1870 if (std::find(newParents.begin(), newParents.end(), newParent) == newParents.end())
1871 newParents.push_back(newParent);
1873 if (pRev->ParentsCount() > 1)
1875 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1877 m_RebaseStage = REBASE_ERROR;
1878 AddLogString(_T("Cannot squash merge commit on rebase."));
1879 return -1;
1881 if (!parentRewritten && nocommit.IsEmpty())
1882 cmd.Format(_T("git.exe reset --hard %s"), (LPCTSTR)pRev->m_CommitHash.ToString());
1883 else
1885 CString parentString;
1886 for (const auto& parent : newParents)
1887 parentString += L" " + parent.ToString();
1888 cmd.Format(_T("git.exe checkout %s"), (LPCTSTR)newParents[0].ToString());
1889 if (RunGitCmdRetryOrAbort(cmd))
1891 m_RebaseStage = REBASE_ERROR;
1892 return -1;
1894 cmd.Format(_T("git.exe merge --no-ff%s %s"), (LPCTSTR)nocommit, (LPCTSTR)parentString);
1895 if (nocommit.IsEmpty())
1897 if (g_Git.Run(cmd, &out, CP_UTF8))
1899 AddLogString(cmd);
1900 AddLogString(out);
1901 int hasConflicts = g_Git.HasWorkingTreeConflicts();
1902 if (hasConflicts > 0)
1904 m_RebaseStage = REBASE_CONFLICT;
1905 return -1;
1907 else if (hasConflicts < 0)
1908 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
1909 AddLogString(_T("An unrecoverable error occurred."));
1910 m_RebaseStage = REBASE_ERROR;
1911 return -1;
1913 cmd.Format(_T("git.exe commit --amend -C %s"), (LPCTSTR)pRev->m_CommitHash.ToString());
1917 else
1919 if (mode != CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1921 cmd.Format(_T("git.exe checkout %s"), (LPCTSTR)newParents[0].ToString());
1922 if (RunGitCmdRetryOrAbort(cmd))
1924 m_RebaseStage = REBASE_ERROR;
1925 return -1;
1928 cmd.Format(_T("git.exe cherry-pick %s%s %s"), (LPCTSTR)cherryPickedFrom, (LPCTSTR)nocommit, (LPCTSTR)pRev->m_CommitHash.ToString());
1932 if(g_Git.Run(cmd,&out,CP_UTF8))
1934 AddLogString(out);
1935 int hasConflicts = g_Git.HasWorkingTreeConflicts();
1936 if (hasConflicts < 0)
1938 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
1939 return -1;
1941 if (!hasConflicts)
1943 if (mode == CGitLogListBase::LOGACTIONS_REBASE_PICK)
1945 if (m_pTaskbarList)
1946 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
1947 int choose = -1;
1948 if (!m_bAutoSkipFailedCommit)
1950 choose = CMessageBox::ShowCheck(m_hWnd, IDS_CHERRYPICKFAILEDSKIP, IDS_APPNAME, 1, IDI_QUESTION, IDS_SKIPBUTTON, IDS_MSGBOX_RETRY, IDS_MSGBOX_CANCEL, NULL, IDS_DO_SAME_FOR_REST, &m_bAutoSkipFailedCommit);
1951 if (choose == 2)
1953 m_bAutoSkipFailedCommit = FALSE;
1954 continue; // retry cherry pick
1957 if (m_bAutoSkipFailedCommit || choose == 1)
1959 if (!RunGitCmdRetryOrAbort(_T("git.exe reset --hard")))
1961 pRev->GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_SKIP;
1962 m_CommitList.Invalidate();
1963 return 0;
1967 m_RebaseStage = REBASE_ERROR;
1968 AddLogString(_T("An unrecoverable error occurred."));
1969 return -1;
1971 if (mode == CGitLogListBase::LOGACTIONS_REBASE_EDIT)
1973 this->m_RebaseStage = REBASE_EDIT ;
1974 return -1; // Edit return -1 to stop rebase.
1976 // Squash Case
1977 if(CheckNextCommitIsSquash())
1978 { // no squash
1979 // let user edit last commmit message
1980 this->m_RebaseStage = REBASE_SQUASH_EDIT;
1981 return -1;
1985 if (m_pTaskbarList)
1986 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
1987 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1988 m_RebaseStage = REBASE_SQUASH_CONFLICT;
1989 else
1990 m_RebaseStage = REBASE_CONFLICT;
1991 return -1;
1994 else
1996 AddLogString(out);
1997 if (mode == CGitLogListBase::LOGACTIONS_REBASE_PICK)
1999 if (nocommit.IsEmpty())
2001 CGitHash head;
2002 if (g_Git.GetHash(head, _T("HEAD")))
2004 MessageBox(g_Git.GetGitLastErr(_T("Could not get HEAD hash.")), _T("TortoiseGit"), MB_ICONERROR);
2005 m_RebaseStage = REBASE_ERROR;
2006 return -1;
2008 m_rewrittenCommitsMap[pRev->m_CommitHash] = head;
2010 else
2011 m_forRewrite.push_back(pRev->m_CommitHash);
2012 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
2013 return 0;
2015 if (mode == CGitLogListBase::LOGACTIONS_REBASE_EDIT)
2017 this->m_RebaseStage = REBASE_EDIT ;
2018 return -1; // Edit return -1 to stop rebase.
2021 // Squash Case
2022 if(CheckNextCommitIsSquash())
2023 { // no squash
2024 // let user edit last commmit message
2025 this->m_RebaseStage = REBASE_SQUASH_EDIT;
2026 return -1;
2028 else if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2030 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
2031 m_forRewrite.push_back(pRev->m_CommitHash);
2035 return 0;
2039 BOOL CRebaseDlg::IsEnd()
2041 if(m_CommitList.m_IsOldFirst)
2042 return m_CurrentRebaseIndex>= this->m_CommitList.GetItemCount();
2043 else
2044 return m_CurrentRebaseIndex<0;
2047 int CRebaseDlg::RebaseThread()
2049 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
2051 int ret=0;
2052 while(1)
2054 if( m_RebaseStage == REBASE_START )
2056 if( this->StartRebase() )
2058 ret = -1;
2059 break;
2061 m_RebaseStage = REBASE_CONTINUE;
2064 else if( m_RebaseStage == REBASE_CONTINUE )
2066 this->GoNext();
2067 SendMessage(MSG_REBASE_UPDATE_UI);
2068 if(IsEnd())
2070 ret = 0;
2071 m_RebaseStage = REBASE_FINISH;
2074 else
2076 ret = DoRebase();
2078 if( ret )
2080 break;
2085 else if( m_RebaseStage == REBASE_FINISH )
2087 SendMessage(MSG_REBASE_UPDATE_UI);
2088 m_RebaseStage = REBASE_DONE;
2089 break;
2092 else
2094 break;
2096 this->PostMessage(MSG_REBASE_UPDATE_UI);
2099 InterlockedExchange(&m_bThreadRunning, FALSE);
2100 this->PostMessage(MSG_REBASE_UPDATE_UI);
2101 return ret;
2104 void CRebaseDlg::ListConflictFile()
2106 this->m_FileListCtrl.Clear();
2107 m_FileListCtrl.SetHasCheckboxes(true);
2108 CTGitPathList list;
2109 CTGitPath path;
2110 list.AddPath(path);
2112 CString adminDir;
2113 if (GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
2114 CreateDirectory(adminDir + L"tgitrebase.active", nullptr);
2116 this->m_FileListCtrl.GetStatus(&list,true);
2117 this->m_FileListCtrl.Show(CTGitPath::LOGACTIONS_UNMERGED|CTGitPath::LOGACTIONS_MODIFIED|CTGitPath::LOGACTIONS_ADDED|CTGitPath::LOGACTIONS_DELETED,
2118 CTGitPath::LOGACTIONS_UNMERGED);
2120 m_FileListCtrl.Check(GITSLC_SHOWFILES);
2121 bool hasSubmoduleChange = false;
2122 for (int i = 0; i < m_FileListCtrl.GetItemCount(); i++)
2124 CTGitPath *entry = (CTGitPath *)m_FileListCtrl.GetItemData(i);
2125 if (entry->IsDirectory())
2127 hasSubmoduleChange = true;
2128 break;
2132 if (hasSubmoduleChange)
2134 m_CtrlStatusText.SetWindowText(m_sStatusText + _T(", ") + CString(MAKEINTRESOURCE(IDS_CARE_SUBMODULE_CHANGES)));
2135 m_bStatusWarning = true;
2136 m_CtrlStatusText.Invalidate();
2138 else
2140 m_CtrlStatusText.SetWindowText(m_sStatusText);
2141 m_bStatusWarning = false;
2142 m_CtrlStatusText.Invalidate();
2146 LRESULT CRebaseDlg::OnRebaseUpdateUI(WPARAM,LPARAM)
2148 if (m_RebaseStage == REBASE_FINISH)
2150 FinishRebase();
2151 return 0;
2153 UpdateCurrentStatus();
2155 if (m_RebaseStage == REBASE_DONE && m_bRebaseAutoEnd)
2157 m_bRebaseAutoEnd = false;
2158 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_CONTINUE, BN_CLICKED), (LPARAM)GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd());
2161 if (m_RebaseStage == REBASE_DONE && m_pTaskbarList)
2162 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS); // do not show progress on taskbar any more to show we finished
2163 if(m_CurrentRebaseIndex <0)
2164 return 0;
2165 if(m_CurrentRebaseIndex >= m_CommitList.GetItemCount() )
2166 return 0;
2167 GitRev *curRev=(GitRev*)m_CommitList.m_arShownList[m_CurrentRebaseIndex];
2169 switch(m_RebaseStage)
2171 case REBASE_CONFLICT:
2172 case REBASE_SQUASH_CONFLICT:
2174 ListConflictFile();
2175 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_CONFLICT);
2176 if (m_pTaskbarList)
2177 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
2178 this->m_LogMessageCtrl.Call(SCI_SETREADONLY, FALSE);
2179 CString logMessage;
2180 if (m_IsCherryPick)
2182 CString dotGitPath;
2183 GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, dotGitPath);
2184 // vanilla git also re-uses MERGE_MSG on conflict (listing all conflicted files)
2185 // and it's also needed for cherry-pick in order to get cherry-picked-from included on conflicts
2186 CGit::LoadTextFile(dotGitPath + _T("MERGE_MSG"), logMessage);
2188 if (logMessage.IsEmpty())
2189 logMessage = curRev->GetSubject() + _T("\n") + curRev->GetBody();
2190 this->m_LogMessageCtrl.SetText(logMessage);
2191 break;
2193 case REBASE_EDIT:
2194 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
2195 if (m_pTaskbarList)
2196 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
2197 this->m_LogMessageCtrl.Call(SCI_SETREADONLY, FALSE);
2198 if (m_bAddCherryPickedFrom)
2200 // Since the new commit is done and the HEAD points to it,
2201 // just using the new body modified by git self.
2202 GitRev headRevision;
2203 if (headRevision.GetCommit(_T("HEAD")))
2204 MessageBox(headRevision.GetLastErr(), _T("TortoiseGit"), MB_ICONERROR);
2206 m_LogMessageCtrl.SetText(headRevision.GetSubject() + _T("\n") + headRevision.GetBody());
2208 else
2209 m_LogMessageCtrl.SetText(curRev->GetSubject() + _T("\n") + curRev->GetBody());
2210 break;
2211 case REBASE_SQUASH_EDIT:
2212 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
2213 this->m_LogMessageCtrl.Call(SCI_SETREADONLY, FALSE);
2214 this->m_LogMessageCtrl.SetText(this->m_SquashMessage);
2215 if (m_pTaskbarList)
2216 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
2217 break;
2218 default:
2219 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
2221 return 0;
2223 void CRebaseDlg::OnCancel()
2225 OnBnClickedAbort();
2227 void CRebaseDlg::OnBnClickedAbort()
2229 if (m_pTaskbarList)
2230 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
2232 m_tooltips.Pop();
2234 if(m_OrigUpstreamHash.IsEmpty())
2236 __super::OnCancel();
2239 if(m_RebaseStage == CHOOSE_BRANCH || m_RebaseStage== CHOOSE_COMMIT_PICK_MODE)
2241 goto end;
2244 if(CMessageBox::Show(NULL, IDS_PROC_REBASE_ABORT, IDS_APPNAME, MB_YESNO) != IDYES)
2245 goto end;
2247 if(this->m_IsFastForward)
2249 CString cmd;
2250 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)this->m_OrigBranchHash.ToString());
2251 RunGitCmdRetryOrAbort(cmd);
2252 __super::OnCancel();
2253 goto end;
2256 if (m_IsCherryPick) // there are not "branch" at cherry pick mode
2258 CString cmd;
2259 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)m_OrigUpstreamHash.ToString());
2260 RunGitCmdRetryOrAbort(cmd);
2261 __super::OnCancel();
2262 goto end;
2265 if (m_OrigHEADBranch == m_BranchCtrl.GetString())
2267 CString cmd, out;
2268 if (IsLocalBranch(m_OrigHEADBranch))
2269 cmd.Format(_T("git.exe checkout -f -B %s %s --"), (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)m_OrigBranchHash.ToString());
2270 else
2271 cmd.Format(_T("git.exe checkout -f %s --"), (LPCTSTR)m_OrigBranchHash.ToString());
2272 if (g_Git.Run(cmd, &out, CP_UTF8))
2274 AddLogString(out);
2275 ::MessageBox(m_hWnd, _T("Unrecoverable error on cleanup:\n") + out, _T("TortoiseGit"), MB_ICONERROR);
2276 __super::OnCancel();
2277 goto end;
2280 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)m_OrigBranchHash.ToString());
2281 RunGitCmdRetryOrAbort(cmd);
2283 else
2285 CString cmd, out;
2286 if (m_OrigHEADBranch != g_Git.GetCurrentBranch(true))
2288 if (IsLocalBranch(m_OrigHEADBranch))
2289 cmd.Format(_T("git.exe checkout -f -B %s %s --"), (LPCTSTR)m_OrigHEADBranch, (LPCTSTR)m_OrigHEADHash.ToString());
2290 else
2291 cmd.Format(_T("git.exe checkout -f %s --"), (LPCTSTR)m_OrigHEADHash.ToString());
2292 if (g_Git.Run(cmd, &out, CP_UTF8))
2294 AddLogString(out);
2295 ::MessageBox(m_hWnd, _T("Unrecoverable error on cleanup:\n") + out, _T("TortoiseGit"), MB_ICONERROR);
2296 // continue to restore moved branch
2300 cmd.Format(_T("git.exe reset --hard %s --"), (LPCTSTR)m_OrigHEADHash.ToString());
2301 RunGitCmdRetryOrAbort(cmd);
2303 // restore moved branch
2304 if (IsLocalBranch(m_BranchCtrl.GetString()))
2306 cmd.Format(_T("git.exe branch -f %s %s --"), (LPCTSTR)m_BranchCtrl.GetString(), (LPCTSTR)m_OrigBranchHash.ToString());
2307 if (g_Git.Run(cmd, &out, CP_UTF8))
2309 AddLogString(out);
2310 ::MessageBox(m_hWnd, _T("Unrecoverable error on cleanup:\n") + out, _T("TortoiseGit"), MB_ICONERROR);
2311 __super::OnCancel();
2312 goto end;
2316 __super::OnCancel();
2317 end:
2318 CleanUpRebaseActiveFolder();
2319 CheckRestoreStash();
2322 void CRebaseDlg::OnBnClickedButtonReverse()
2324 CString temp = m_BranchCtrl.GetString();
2325 m_BranchCtrl.AddString(m_UpstreamCtrl.GetString());
2326 m_UpstreamCtrl.AddString(temp);
2327 OnCbnSelchangeUpstream();
2330 void CRebaseDlg::OnBnClickedButtonBrowse()
2332 if(CBrowseRefsDlg::PickRefForCombo(&m_UpstreamCtrl))
2333 OnCbnSelchangeUpstream();
2336 void CRebaseDlg::OnBnClickedRebaseCheckForce()
2338 this->UpdateData();
2339 this->FetchLogList();
2342 void CRebaseDlg::OnBnClickedRebasePostButton()
2344 this->m_Upstream=this->m_UpstreamCtrl.GetString();
2345 this->m_Branch=this->m_BranchCtrl.GetString();
2347 this->EndDialog((int)(IDC_REBASE_POST_BUTTON+this->m_PostButton.GetCurrentEntry()));
2350 LRESULT CRebaseDlg::OnGitStatusListCtrlNeedsRefresh(WPARAM, LPARAM)
2352 Refresh();
2353 return 0;
2356 void CRebaseDlg::Refresh()
2358 if (m_RebaseStage == REBASE_CONFLICT || m_RebaseStage == REBASE_SQUASH_CONFLICT)
2360 ListConflictFile();
2361 return;
2364 if(this->m_IsCherryPick)
2365 return ;
2367 if(this->m_RebaseStage == CHOOSE_BRANCH )
2369 this->UpdateData();
2370 this->FetchLogList();
2374 void CRebaseDlg::OnBnClickedButtonUp2()
2376 POSITION pos;
2377 pos = m_CommitList.GetFirstSelectedItemPosition();
2379 bool moveToTop = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
2380 // do nothing if the first selected item is the first item in the list
2381 if (!moveToTop && m_CommitList.GetNextSelectedItem(pos) == 0)
2382 return;
2384 pos = m_CommitList.GetFirstSelectedItemPosition();
2386 int count = 0;
2387 bool changed = false;
2388 while(pos)
2390 int index=m_CommitList.GetNextSelectedItem(pos);
2391 count = moveToTop ? count : (index - 1);
2392 while (index > count)
2394 CGitHash old = m_CommitList.m_logEntries[index - 1];
2395 m_CommitList.m_logEntries[index - 1] = m_CommitList.m_logEntries[index];
2396 m_CommitList.m_logEntries[index] = old;
2397 m_CommitList.RecalculateShownList(&m_CommitList.m_arShownList);
2398 m_CommitList.SetItemState(index - 1, LVIS_SELECTED, LVIS_SELECTED);
2399 m_CommitList.SetItemState(index, 0, LVIS_SELECTED);
2400 changed = true;
2401 index--;
2403 count++;
2405 if (changed)
2407 pos = m_CommitList.GetFirstSelectedItemPosition();
2408 m_CommitList.EnsureVisible(m_CommitList.GetNextSelectedItem(pos), false);
2409 m_CommitList.Invalidate();
2410 m_CommitList.SetFocus();
2414 void CRebaseDlg::OnBnClickedButtonDown2()
2416 if (m_CommitList.GetSelectedCount() == 0)
2417 return;
2419 bool moveToBottom = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
2420 POSITION pos;
2421 pos = m_CommitList.GetFirstSelectedItemPosition();
2422 bool changed = false;
2423 // use an array to store all selected item indexes; the user won't select too much items
2424 auto indexes = std::make_unique<int[]>(m_CommitList.GetSelectedCount());
2425 int i = 0;
2426 while(pos)
2428 indexes.get()[i++] = m_CommitList.GetNextSelectedItem(pos);
2430 // don't move any item if the last selected item is the last item in the m_CommitList
2431 // (that would change the order of the selected items)
2432 if (!moveToBottom && indexes.get()[m_CommitList.GetSelectedCount() - 1] >= m_CommitList.GetItemCount() - 1)
2433 return;
2434 int count = m_CommitList.GetItemCount() - 1;
2435 // iterate over the indexes backwards in order to correctly move multiselected items
2436 for (i = m_CommitList.GetSelectedCount() - 1; i >= 0; i--)
2438 int index = indexes.get()[i];
2439 count = moveToBottom ? count : (index + 1);
2440 while (index < count)
2442 CGitHash old = m_CommitList.m_logEntries[index + 1];
2443 m_CommitList.m_logEntries[index + 1] = m_CommitList.m_logEntries[index];
2444 m_CommitList.m_logEntries[index] = old;
2445 m_CommitList.RecalculateShownList(&m_CommitList.m_arShownList);
2446 m_CommitList.SetItemState(index, 0, LVIS_SELECTED);
2447 m_CommitList.SetItemState(index + 1, LVIS_SELECTED, LVIS_SELECTED);
2448 changed = true;
2449 index++;
2451 count--;
2453 m_CommitList.EnsureVisible(indexes.get()[m_CommitList.GetSelectedCount() - 1] + 1, false);
2454 if (changed)
2456 m_CommitList.Invalidate();
2457 m_CommitList.SetFocus();
2461 LRESULT CRebaseDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
2463 m_pTaskbarList.Release();
2464 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
2465 SetUUIDOverlayIcon(m_hWnd);
2466 return 0;
2469 void CRebaseDlg::OnLvnItemchangedLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2471 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2472 *pResult = 0;
2473 if(m_CommitList.m_bNoDispUpdates)
2474 return;
2475 if (pNMLV->iItem >= 0)
2477 this->m_CommitList.m_nSearchIndex = pNMLV->iItem;
2478 if (pNMLV->iSubItem != 0)
2479 return;
2480 if ((pNMLV->iItem == m_CommitList.m_arShownList.GetCount()))
2482 // remove the selected state
2483 if (pNMLV->uChanged & LVIF_STATE)
2485 m_CommitList.SetItemState(pNMLV->iItem, 0, LVIS_SELECTED);
2486 FillLogMessageCtrl();
2488 return;
2490 if (pNMLV->uChanged & LVIF_STATE)
2492 FillLogMessageCtrl();
2495 else
2497 FillLogMessageCtrl();
2501 void CRebaseDlg::FillLogMessageCtrl()
2503 int selCount = m_CommitList.GetSelectedCount();
2504 if (selCount == 1 && (m_RebaseStage == CHOOSE_BRANCH || m_RebaseStage == CHOOSE_COMMIT_PICK_MODE))
2506 POSITION pos = m_CommitList.GetFirstSelectedItemPosition();
2507 int selIndex = m_CommitList.GetNextSelectedItem(pos);
2508 GitRevLoglist* pLogEntry = reinterpret_cast<GitRevLoglist*>(m_CommitList.m_arShownList.SafeGetAt(selIndex));
2509 m_FileListCtrl.UpdateWithGitPathList(pLogEntry->GetFiles(&m_CommitList));
2510 m_FileListCtrl.m_CurrentVersion = pLogEntry->m_CommitHash;
2511 m_FileListCtrl.Show(GITSLC_SHOWVERSIONED);
2512 m_LogMessageCtrl.Call(SCI_SETREADONLY, FALSE);
2513 m_LogMessageCtrl.SetText(pLogEntry->GetSubject() + _T("\n") + pLogEntry->GetBody());
2514 m_LogMessageCtrl.Call(SCI_SETREADONLY, TRUE);
2517 void CRebaseDlg::OnBnClickedCheckCherryPickedFrom()
2519 UpdateData();
2522 LRESULT CRebaseDlg::OnRebaseActionMessage(WPARAM, LPARAM)
2524 if (m_RebaseStage == REBASE_ERROR || m_RebaseStage == REBASE_CONFLICT)
2526 GitRevLoglist* pRev = (GitRevLoglist*)m_CommitList.m_arShownList[m_CurrentRebaseIndex];
2527 int mode = pRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_MODE_MASK;
2528 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SKIP)
2530 if (!RunGitCmdRetryOrAbort(_T("git.exe reset --hard")))
2532 m_FileListCtrl.Clear();
2533 m_RebaseStage = REBASE_CONTINUE;
2534 UpdateCurrentStatus();
2538 return 0;
2542 void CRebaseDlg::OnBnClickedSplitAllOptions()
2544 switch (m_SplitAllOptions.GetCurrentEntry())
2546 case 0:
2547 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_PICK);
2548 break;
2549 case 1:
2550 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
2551 break;
2552 case 2:
2553 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
2554 break;
2555 case 3:
2556 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SKIP);
2557 break;
2558 case 4:
2559 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
2560 break;
2561 case 5:
2562 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
2563 break;
2564 default:
2565 ATLASSERT(false);
2569 void CRebaseDlg::OnBnClickedRebaseSplitCommit()
2571 UpdateData();
2574 static bool GetCompareHash(const CString& ref, const CGitHash& hash)
2576 CGitHash refHash;
2577 if (g_Git.GetHash(refHash, ref))
2578 MessageBox(nullptr, g_Git.GetGitLastErr(_T("Could not get hash of \"") + ref + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
2579 return refHash.IsEmpty() || (hash == refHash);
2582 void CRebaseDlg::OnBnClickedButtonOnto()
2584 m_Onto = CBrowseRefsDlg::PickRef(false, m_Onto);
2585 if (!m_Onto.IsEmpty())
2587 // make sure that the user did not select upstream, selected branch or HEAD
2588 CGitHash hash;
2589 if (g_Git.GetHash(hash, m_Onto))
2591 MessageBox(g_Git.GetGitLastErr(_T("Could not get hash of \"") + m_BranchCtrl.GetString() + _T("\".")), _T("TortoiseGit"), MB_ICONERROR);
2592 m_Onto.Empty();
2593 ((CButton*)GetDlgItem(IDC_BUTTON_ONTO))->SetCheck(m_Onto.IsEmpty() ? BST_UNCHECKED : BST_CHECKED);
2594 return;
2596 if (GetCompareHash(_T("HEAD"), hash) || GetCompareHash(m_UpstreamCtrl.GetString(), hash) || GetCompareHash(m_BranchCtrl.GetString(), hash))
2597 m_Onto.Empty();
2599 if (m_Onto.IsEmpty())
2600 m_tooltips.DelTool(IDC_BUTTON_ONTO);
2601 else
2602 m_tooltips.AddTool(IDC_BUTTON_ONTO, m_Onto);
2603 ((CButton*)GetDlgItem(IDC_BUTTON_ONTO))->SetCheck(m_Onto.IsEmpty() ? BST_UNCHECKED : BST_CHECKED);
2604 FetchLogList();
2607 void CRebaseDlg::OnHelp()
2609 HtmlHelp(0x20000 + (m_IsCherryPick ? IDD_REBASECHERRYPICK : IDD_REBASE));
2612 int CRebaseDlg::RunGitCmdRetryOrAbort(const CString& cmd)
2614 while (true)
2616 CString out;
2617 if (g_Git.Run(cmd, &out, CP_UTF8))
2619 AddLogString(cmd);
2620 AddLogString(CString(MAKEINTRESOURCE(IDS_FAIL)));
2621 AddLogString(out);
2622 CString msg;
2623 msg.Format(L"\"%s\" failed.\n%s", (LPCTSTR)cmd, (LPCTSTR)out);
2624 if (CMessageBox::Show(GetSafeHwnd(), msg, _T("TortoiseGit"), 1, IDI_ERROR, CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY)), CString(MAKEINTRESOURCE(IDS_MSGBOX_ABORT))) != 1)
2625 return -1;
2627 else
2628 return 0;