Fixed issue #4133: libgit2 returned: failed to parse revision specifier (ref ending...
[TortoiseGit.git] / src / TortoiseProc / RebaseDlg.cpp
blob03161f2cf7419f8266cc065d731d224a87de7b7f
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2024 - 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"
38 #include "Hooks.h"
39 #include "LogDlg.h"
40 #include "ThemeMFCVisualManager.h"
42 // CRebaseDlg dialog
44 IMPLEMENT_DYNAMIC(CRebaseDlg, CResizableStandAloneDialog)
46 CRebaseDlg::CRebaseDlg(CWnd* pParent /*=nullptr*/)
47 : CResizableStandAloneDialog(CRebaseDlg::IDD, pParent)
48 , m_bAddCherryPickedFrom(FALSE)
49 , m_bSplitCommit(FALSE)
50 , m_bPreserveMerges(FALSE)
51 , m_bForce(BST_UNCHECKED)
52 , m_iSquashdate(CRegDWORD(L"Software\\TortoiseGit\\SquashDate", 0))
56 CRebaseDlg::~CRebaseDlg()
60 void CRebaseDlg::DoDataExchange(CDataExchange* pDX)
62 CDialog::DoDataExchange(pDX);
63 DDX_Control(pDX, IDC_REBASE_PROGRESS, m_ProgressBar);
64 DDX_Control(pDX, IDC_STATUS_STATIC, m_CtrlStatusText);
65 DDX_Control(pDX, IDC_REBASE_SPLIT, m_wndSplitter);
66 DDX_Control(pDX,IDC_COMMIT_LIST,m_CommitList);
67 DDX_Control(pDX,IDC_REBASE_COMBOXEX_BRANCH, this->m_BranchCtrl);
68 DDX_Control(pDX,IDC_REBASE_COMBOXEX_UPSTREAM, this->m_UpstreamCtrl);
69 DDX_Check(pDX, IDC_REBASE_CHECK_FORCE,m_bForce);
70 DDX_Check(pDX, IDC_REBASE_CHECK_PRESERVEMERGES, m_bPreserveMerges);
71 DDX_Check(pDX, IDC_CHECK_CHERRYPICKED_FROM, m_bAddCherryPickedFrom);
72 DDX_Control(pDX,IDC_REBASE_POST_BUTTON,m_PostButton);
73 DDX_Control(pDX, IDC_SPLITALLOPTIONS, m_SplitAllOptions);
74 DDX_Check(pDX, IDC_REBASE_SPLIT_COMMIT, m_bSplitCommit);
78 BEGIN_MESSAGE_MAP(CRebaseDlg, CResizableStandAloneDialog)
79 ON_BN_CLICKED(IDC_REBASE_SPLIT, &CRebaseDlg::OnBnClickedRebaseSplit)
80 ON_BN_CLICKED(IDC_REBASE_CONTINUE,OnBnClickedContinue)
81 ON_BN_CLICKED(IDC_REBASE_ABORT, OnBnClickedAbort)
82 ON_WM_SIZE()
83 ON_CBN_SELCHANGE(IDC_REBASE_COMBOXEX_BRANCH, &CRebaseDlg::OnCbnSelchangeBranch)
84 ON_CBN_SELCHANGE(IDC_REBASE_COMBOXEX_UPSTREAM, &CRebaseDlg::OnCbnSelchangeUpstream)
85 ON_MESSAGE(MSG_REBASE_UPDATE_UI, OnRebaseUpdateUI)
86 ON_REGISTERED_MESSAGE(CGitStatusListCtrl::GITSLNM_NEEDSREFRESH, OnGitStatusListCtrlNeedsRefresh)
87 ON_BN_CLICKED(IDC_BUTTON_REVERSE, OnBnClickedButtonReverse)
88 ON_BN_CLICKED(IDC_BUTTON_BROWSE, &CRebaseDlg::OnBnClickedButtonBrowse)
89 ON_BN_CLICKED(IDC_REBASE_CHECK_FORCE, &CRebaseDlg::OnBnClickedRebaseCheckForce)
90 ON_BN_CLICKED(IDC_REBASE_CHECK_PRESERVEMERGES, &CRebaseDlg::OnBnClickedRebaseCheckForce)
91 ON_BN_CLICKED(IDC_CHECK_CHERRYPICKED_FROM, &CRebaseDlg::OnBnClickedCheckCherryPickedFrom)
92 ON_BN_CLICKED(IDC_REBASE_POST_BUTTON, &CRebaseDlg::OnBnClickedRebasePostButton)
93 ON_BN_CLICKED(IDC_BUTTON_UP, &CRebaseDlg::OnBnClickedButtonUp)
94 ON_BN_CLICKED(IDC_BUTTON_DOWN, &CRebaseDlg::OnBnClickedButtonDown)
95 ON_REGISTERED_MESSAGE(TaskBarButtonCreated, OnTaskbarBtnCreated)
96 ON_NOTIFY(LVN_ITEMCHANGED, IDC_COMMIT_LIST, OnLvnItemchangedLoglist)
97 ON_REGISTERED_MESSAGE(CGitLogListBase::m_RebaseActionMessage, OnRebaseActionMessage)
98 ON_WM_CTLCOLOR()
99 ON_BN_CLICKED(IDC_SPLITALLOPTIONS, &CRebaseDlg::OnBnClickedSplitAllOptions)
100 ON_BN_CLICKED(IDC_REBASE_SPLIT_COMMIT, &CRebaseDlg::OnBnClickedRebaseSplitCommit)
101 ON_BN_CLICKED(IDC_BUTTON_ONTO, &CRebaseDlg::OnBnClickedButtonOnto)
102 ON_BN_CLICKED(IDHELP, OnHelp)
103 ON_BN_CLICKED(IDC_BUTTON_ADD, &CRebaseDlg::OnBnClickedButtonAdd)
104 ON_MESSAGE(MSG_COMMITS_REORDERED, OnCommitsReordered)
105 ON_COMMAND(MSG_FETCHED_DIFF, OnRefreshFilelist)
106 END_MESSAGE_MAP()
108 void CRebaseDlg::CleanUpRebaseActiveFolder()
110 if (m_IsCherryPick)
111 return;
112 CString adminDir;
113 if (GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
115 CString dir(adminDir + L"tgitrebase.active");
116 ::DeleteFile(dir + L"\\head-name");
117 ::DeleteFile(dir + L"\\onto");
118 ::RemoveDirectory(dir);
122 void CRebaseDlg::AddRebaseAnchor()
124 AdjustControlSize(IDC_CHECK_CHERRYPICKED_FROM);
125 AdjustControlSize(IDC_REBASE_SPLIT_COMMIT);
126 AdjustControlSize(IDC_REBASE_CHECK_FORCE);
127 AdjustControlSize(IDC_REBASE_CHECK_PRESERVEMERGES);
129 AddAnchor(IDC_REBASE_TAB,TOP_LEFT,BOTTOM_RIGHT);
130 AddAnchor(IDC_COMMIT_LIST,TOP_LEFT, TOP_RIGHT);
131 AddAnchor(IDC_REBASE_SPLIT,TOP_LEFT, TOP_RIGHT);
132 AddAnchor(IDC_STATUS_STATIC, BOTTOM_LEFT,BOTTOM_RIGHT);
133 AddAnchor(IDC_REBASE_CONTINUE,BOTTOM_RIGHT);
134 AddAnchor(IDC_REBASE_ABORT, BOTTOM_RIGHT);
135 AddAnchor(IDC_REBASE_PROGRESS,BOTTOM_LEFT, BOTTOM_RIGHT);
136 AddAnchor(IDC_SPLITALLOPTIONS, TOP_LEFT);
137 AddAnchor(IDC_BUTTON_UP, TOP_LEFT);
138 AddAnchor(IDC_BUTTON_DOWN, TOP_LEFT);
139 AddAnchor(IDC_BUTTON_ADD, TOP_LEFT);
140 AddAnchor(IDC_REBASE_COMBOXEX_UPSTREAM, TOP_CENTER, TOP_RIGHT);
141 AddAnchor(IDC_REBASE_COMBOXEX_BRANCH, TOP_LEFT, TOP_CENTER);
142 AddAnchor(IDC_BUTTON_REVERSE, TOP_CENTER);
143 AddAnchor(IDC_BUTTON_BROWSE, TOP_RIGHT);
144 AddAnchor(IDC_BUTTON_ONTO, TOP_RIGHT);
145 AddAnchor(IDC_REBASE_STATIC_UPSTREAM, TOP_CENTER);
146 AddAnchor(IDC_REBASE_STATIC_BRANCH,TOP_LEFT);
147 AddAnchor(IDHELP, BOTTOM_RIGHT);
148 AddAnchor(IDC_REBASE_CHECK_FORCE, TOP_CENTER);
149 AddAnchor(IDC_REBASE_CHECK_PRESERVEMERGES, TOP_LEFT);
150 AddAnchor(IDC_CHECK_CHERRYPICKED_FROM, TOP_RIGHT);
151 AddAnchor(IDC_REBASE_SPLIT_COMMIT, BOTTOM_RIGHT);
152 AddAnchor(IDC_REBASE_POST_BUTTON,BOTTOM_LEFT);
154 this->AddOthersToAnchor();
157 BOOL CRebaseDlg::OnInitDialog()
159 CResizableStandAloneDialog::OnInitDialog();
160 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
162 // Let the TaskbarButtonCreated message through the UIPI filter. If we don't
163 // do this, Explorer would be unable to send that message to our window if we
164 // were running elevated. It's OK to make the call all the time, since if we're
165 // not elevated, this is a no-op.
166 CHANGEFILTERSTRUCT cfs = { sizeof(CHANGEFILTERSTRUCT) };
167 using ChangeWindowMessageFilterExDFN = BOOL(STDAPICALLTYPE)(HWND hWnd, UINT message, DWORD action, PCHANGEFILTERSTRUCT pChangeFilterStruct);
168 CAutoLibrary hUser = AtlLoadSystemLibraryUsingFullPath(L"user32.dll");
169 if (hUser)
171 auto pfnChangeWindowMessageFilterEx = reinterpret_cast<ChangeWindowMessageFilterExDFN*>(GetProcAddress(hUser, "ChangeWindowMessageFilterEx"));
172 if (pfnChangeWindowMessageFilterEx)
173 pfnChangeWindowMessageFilterEx(m_hWnd, TaskBarButtonCreated, MSGFLT_ALLOW, &cfs);
175 m_pTaskbarList.Release();
176 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
177 m_pTaskbarList = nullptr;
179 CRect rectDummy;
180 //IDC_REBASE_DUMY_TAB
182 GetClientRect(m_DlgOrigRect);
183 m_CommitList.GetClientRect(m_CommitListOrigRect);
185 CWnd *pwnd=this->GetDlgItem(IDC_REBASE_DUMY_TAB);
186 pwnd->GetWindowRect(&rectDummy);
187 this->ScreenToClient(rectDummy);
189 if (CTheme::Instance().IsDarkTheme())
190 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CThemeMFCVisualManager));
191 if (!m_ctrlTabCtrl.Create(CTheme::Instance().IsDarkTheme() ? CMFCTabCtrl::STYLE_3D : CMFCTabCtrl::STYLE_FLAT, rectDummy, this, IDC_REBASE_TAB))
193 TRACE0("Failed to create output tab window\n");
194 return FALSE; // fail to create
196 m_ctrlTabCtrl.SetResizeMode(CMFCTabCtrl::RESIZE_NO);
197 // Create output panes:
198 if (!m_FileListCtrl.Create(LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP | WS_CHILD | WS_VISIBLE, rectDummy, &this->m_ctrlTabCtrl, 0))
200 TRACE0("Failed to create output windows\n");
201 return FALSE; // fail to create
203 m_FileListCtrl.m_hwndLogicalParent = this;
205 if (!m_LogMessageCtrl.Create(L"Scintilla", L"source", 0, rectDummy, &m_ctrlTabCtrl, 0))
207 TRACE0("Failed to create log message control");
208 return FALSE;
210 m_ProjectProperties.ReadProps();
211 m_LogMessageCtrl.Init(m_ProjectProperties);
212 m_LogMessageCtrl.SetFont(CAppUtils::GetLogFontName(), CAppUtils::GetLogFontSize());
213 m_LogMessageCtrl.SetReadOnly(true);
215 if (!m_wndOutputRebase.Create(L"Scintilla", L"source", 0, rectDummy, &m_ctrlTabCtrl, 0))
217 TRACE0("Failed to create output windows\n");
218 return -1; // fail to create
220 m_wndOutputRebase.Init(-1);
221 m_wndOutputRebase.SetFont(CAppUtils::GetLogFontName(), CAppUtils::GetLogFontSize());
222 m_wndOutputRebase.SetReadOnly(true);
223 m_wndOutputRebase.Call(SCI_SETUNDOCOLLECTION, 0);
225 m_tooltips.AddTool(IDC_REBASE_CHECK_FORCE,IDS_REBASE_FORCE_TT);
226 m_tooltips.AddTool(IDC_REBASE_ABORT, IDS_REBASE_ABORT_TT);
227 m_tooltips.AddTool(IDC_REBASE_CHECK_PRESERVEMERGES, IDS_REBASE_PRESERVEMERGES_TT);
230 CString temp;
231 temp.LoadString(IDS_PROC_REBASE_SELECTALL_PICK);
232 m_SplitAllOptions.AddEntry(temp);
233 temp.LoadString(IDS_PROC_REBASE_SELECTALL_SQUASH);
234 m_SplitAllOptions.AddEntry(temp);
235 temp.LoadString(IDS_PROC_REBASE_SELECTALL_EDIT);
236 m_SplitAllOptions.AddEntry(temp);
237 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_SKIP);
238 m_SplitAllOptions.AddEntry(temp);
239 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_SQUASH);
240 m_SplitAllOptions.AddEntry(temp);
241 temp.LoadString(IDS_PROC_REBASE_UNSELECTED_EDIT);
242 m_SplitAllOptions.AddEntry(temp);
245 m_FileListCtrl.Init(GITSLC_COLEXT | GITSLC_COLSTATUS | GITSLC_COLADD | GITSLC_COLDEL, L"RebaseDlg", (GITSLC_POPALL ^ (GITSLC_POPCOMMIT | GITSLC_POPRESTORE | GITSLC_POPCHANGELISTS)), false, true, GITSLC_COLEXT | GITSLC_COLSTATUS | GITSLC_COLADD | GITSLC_COLDEL);
247 m_ctrlTabCtrl.AddTab(&m_FileListCtrl, CString(MAKEINTRESOURCE(IDS_PROC_REVISIONFILES)));
248 m_ctrlTabCtrl.AddTab(&m_LogMessageCtrl, CString(MAKEINTRESOURCE(IDS_PROC_COMMITMESSAGE)), 1);
249 AddRebaseAnchor();
251 CAppUtils::SetWindowTitle(*this, g_Git.m_CurrentDir);
253 EnableSaveRestore(L"RebaseDlg");
255 DWORD yPos = CDPIAware::Instance().ScaleY(GetSafeHwnd(), CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RebaseDlgSizer"));
256 RECT rcDlg, rcLogMsg, rcFileList;
257 GetClientRect(&rcDlg);
258 m_CommitList.GetWindowRect(&rcLogMsg);
259 ScreenToClient(&rcLogMsg);
260 this->m_ctrlTabCtrl.GetWindowRect(&rcFileList);
261 ScreenToClient(&rcFileList);
262 if (yPos)
264 RECT rectSplitter;
265 m_wndSplitter.GetWindowRect(&rectSplitter);
266 ScreenToClient(&rectSplitter);
267 const int delta = yPos - rectSplitter.top;
268 if ((rcLogMsg.bottom + delta > rcLogMsg.top) && (rcLogMsg.bottom + delta < rcFileList.bottom - CDPIAware::Instance().ScaleY(GetSafeHwnd(), 30)))
270 m_wndSplitter.SetWindowPos(nullptr, rectSplitter.left, yPos, 0, 0, SWP_NOSIZE);
271 DoSize(delta);
275 if (m_RebaseStage == RebaseStage::Choose_Branch && !m_IsCherryPick)
276 this->LoadBranchInfo();
277 else
279 this->m_BranchCtrl.EnableWindow(FALSE);
280 this->m_UpstreamCtrl.EnableWindow(FALSE);
281 GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(FALSE);
284 m_CommitList.m_ColumnRegKey = L"Rebase";
285 m_CommitList.m_IsIDReplaceAction = TRUE;
286 // m_CommitList.m_IsOldFirst = TRUE;
287 m_CommitList.m_IsRebaseReplaceGraph = TRUE;
288 m_CommitList.m_bNoHightlightHead = TRUE;
289 m_CommitList.m_bIsCherryPick = !!m_IsCherryPick;
291 m_CommitList.InsertGitColumn();
293 this->SetControlEnable();
295 if(m_IsCherryPick)
297 this->m_BranchCtrl.SetCurSel(-1);
298 this->m_BranchCtrl.EnableWindow(FALSE);
299 GetDlgItem(IDC_REBASE_CHECK_FORCE)->ShowWindow(SW_HIDE);
300 GetDlgItem(IDC_REBASE_CHECK_FORCE)->EnableWindow(FALSE);
301 GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->ShowWindow(SW_HIDE);
302 GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->EnableWindow(FALSE);
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(L"HEAD");
307 this->m_UpstreamCtrl.EnableWindow(FALSE);
308 CAppUtils::SetWindowTitle(m_hWnd, g_Git.m_CurrentDir, CString(MAKEINTRESOURCE(IDS_PROGS_TITLE_CHERRYPICK)));
309 m_bAddCherryPickedFrom = CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\CherrypickAddCherryPickedFrom", 0) != 0;
310 UpdateData(FALSE);
311 // fill shown list
312 for (DWORD i = 0; i < m_CommitList.m_logEntries.size(); ++i)
313 m_CommitList.m_arShownList.SafeAdd(&m_CommitList.m_logEntries.GetGitRevAt(i));
314 m_CommitList.SetItemCountEx(static_cast<int>(m_CommitList.m_arShownList.size()));
316 else
318 static_cast<CButton*>(GetDlgItem(IDC_BUTTON_ONTO))->SetCheck(m_Onto.IsEmpty() ? BST_UNCHECKED : BST_CHECKED);
319 GetDlgItem(IDC_CHECK_CHERRYPICKED_FROM)->ShowWindow(SW_HIDE);
320 GetDlgItem(IDC_CHECK_CHERRYPICKED_FROM)->EnableWindow(FALSE);
321 const int iconWidth = GetSystemMetrics(SM_CXSMICON);
322 const int iconHeight = GetSystemMetrics(SM_CYSMICON);
323 static_cast<CButton*>(GetDlgItem(IDC_BUTTON_REVERSE))->SetIcon(CCommonAppUtils::LoadIconEx(IDI_SWITCHLEFTRIGHT, iconWidth, iconHeight));
324 SetContinueButtonText();
325 m_CommitList.DeleteAllItems();
326 FetchLogList();
329 m_CommitList.m_ContextMenuMask &= ~(m_CommitList.GetContextMenuBit(CGitLogListBase::ID_CHERRY_PICK)|
330 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_SWITCHTOREV)|
331 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_RESET)|
332 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REVERTREV)|
333 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_MERGEREV) |
334 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_TO_VERSION)|
335 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REVERTTOREV)|
336 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_COMBINE_COMMIT)|
337 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_TOGGLE_ROLLUP));
339 if(m_CommitList.m_IsOldFirst)
340 this->m_CurrentRebaseIndex = -1;
341 else
342 this->m_CurrentRebaseIndex = static_cast<int>(m_CommitList.m_logEntries.size());
344 SetTheme(CTheme::Instance().IsDarkTheme());
346 if (GetDlgItem(IDC_REBASE_CONTINUE)->IsWindowEnabled() && m_bRebaseAutoStart)
347 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_CONTINUE, BN_CLICKED), reinterpret_cast<LPARAM>(GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd()));
349 return TRUE;
351 // CRebaseDlg message handlers
353 HBRUSH CRebaseDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
355 if (pWnd->GetDlgCtrlID() == IDC_STATUS_STATIC && nCtlColor == CTLCOLOR_STATIC && m_bStatusWarning)
357 pDC->SetBkColor(CTheme::Instance().GetThemeColor(RGB(255, 0, 0)));
358 pDC->SetTextColor(CTheme::Instance().GetThemeColor(RGB(255, 255, 255)));
359 return CreateSolidBrush(CTheme::Instance().GetThemeColor(RGB(255, 0, 0)));
362 return CResizableStandAloneDialog::OnCtlColor(pDC, pWnd, nCtlColor);
365 void CRebaseDlg::SetAllRebaseAction(int action)
367 for (size_t i = 0; i < this->m_CommitList.m_logEntries.size(); ++i)
369 if (action == CGitLogListBase::LOGACTIONS_REBASE_SQUASH && (i == this->m_CommitList.m_logEntries.size() - 1 || (!m_IsCherryPick && m_CommitList.m_logEntries.GetGitRevAt(i).ParentsCount() != 1)))
370 continue;
371 m_CommitList.m_logEntries.GetGitRevAt(i).GetRebaseAction() = action;
373 m_CommitList.Invalidate();
376 void CRebaseDlg::OnBnClickedRebaseSplit()
378 this->UpdateData();
381 LRESULT CRebaseDlg::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam)
383 switch (message) {
384 case WM_NOTIFY:
385 if (wParam == IDC_REBASE_SPLIT)
387 auto pHdr = reinterpret_cast<SPC_NMHDR*>(lParam);
388 DoSize(pHdr->delta);
390 break;
393 return __super::DefWindowProc(message, wParam, lParam);
396 void CRebaseDlg::DoSize(int delta)
398 this->RemoveAllAnchors();
400 auto hdwp = BeginDeferWindowPos(9);
401 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_COMMIT_LIST), 0, 0, 0, delta);
402 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_REBASE_TAB), 0, delta, 0, 0);
403 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_SPLITALLOPTIONS), 0, delta, 0, delta);
404 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_BUTTON_UP), 0, delta, 0, delta);
405 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_BUTTON_DOWN), 0, delta, 0, delta);
406 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_BUTTON_ADD), 0, delta, 0, delta);
407 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_REBASE_CHECK_FORCE), 0, delta, 0, delta);
408 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES), 0, delta, 0, delta);
409 hdwp = CSplitterControl::ChangeRect(hdwp, GetDlgItem(IDC_CHECK_CHERRYPICKED_FROM), 0, delta, 0, delta);
410 EndDeferWindowPos(hdwp);
412 this->AddRebaseAnchor();
413 // adjust the minimum size of the dialog to prevent the resizing from
414 // moving the list control too far down.
415 CRect rcLogMsg;
416 m_CommitList.GetClientRect(rcLogMsg);
417 SetMinTrackSize(CSize(m_DlgOrigRect.Width(), m_DlgOrigRect.Height()-m_CommitListOrigRect.Height()+rcLogMsg.Height()));
419 SetSplitterRange();
420 // m_CommitList.Invalidate();
422 // GetDlgItem(IDC_LOGMESSAGE)->Invalidate();
424 this->m_ctrlTabCtrl.Invalidate();
425 this->m_CommitList.Invalidate();
426 this->m_FileListCtrl.Invalidate();
427 this->m_LogMessageCtrl.Invalidate();
428 m_SplitAllOptions.Invalidate();
429 GetDlgItem(IDC_REBASE_CHECK_FORCE)->Invalidate();
430 GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->Invalidate();
431 GetDlgItem(IDC_CHECK_CHERRYPICKED_FROM)->Invalidate();
432 GetDlgItem(IDC_BUTTON_UP)->Invalidate();
433 GetDlgItem(IDC_BUTTON_DOWN)->Invalidate();
434 GetDlgItem(IDC_BUTTON_ADD)->Invalidate();
437 void CRebaseDlg::SetSplitterRange()
439 if ((m_CommitList)&&(m_ctrlTabCtrl))
441 CRect rcTop;
442 m_CommitList.GetWindowRect(rcTop);
443 ScreenToClient(rcTop);
444 CRect rcMiddle;
445 m_ctrlTabCtrl.GetWindowRect(rcMiddle);
446 ScreenToClient(rcMiddle);
447 if (rcMiddle.Height() && rcMiddle.Width())
448 m_wndSplitter.SetRange(rcTop.top+60, rcMiddle.bottom-80);
452 void CRebaseDlg::OnSize(UINT nType,int cx, int cy)
454 // first, let the resizing take place
455 __super::OnSize(nType, cx, cy);
457 //set range
458 SetSplitterRange();
461 void CRebaseDlg::SaveSplitterPos()
463 if (!IsIconic())
465 CRegDWORD regPos = CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\ResizableState\\RebaseDlgSizer");
466 RECT rectSplitter;
467 m_wndSplitter.GetWindowRect(&rectSplitter);
468 ScreenToClient(&rectSplitter);
469 regPos = CDPIAware::Instance().UnscaleY(GetSafeHwnd(), rectSplitter.top);
473 void CRebaseDlg::LoadBranchInfo()
475 m_BranchCtrl.SetMaxHistoryItems(0x7FFFFFFF);
476 m_UpstreamCtrl.SetMaxHistoryItems(0x7FFFFFFF);
478 STRING_VECTOR list;
479 list.clear();
480 int current = -1;
481 g_Git.GetBranchList(list,&current,CGit::BRANCH_ALL);
482 m_BranchCtrl.SetList(list);
483 if (current >= 0)
484 m_BranchCtrl.SetCurSel(current);
485 else
486 m_BranchCtrl.AddString(g_Git.GetCurrentBranch(true));
487 list.clear();
488 g_Git.GetBranchList(list, nullptr, CGit::BRANCH_ALL_F);
489 g_Git.GetTagList(list);
490 m_UpstreamCtrl.SetList(list);
492 AddBranchToolTips(m_BranchCtrl);
494 if(!m_Upstream.IsEmpty())
495 m_UpstreamCtrl.AddString(m_Upstream);
496 else
498 //Select pull-remote from current branch
499 CString pullRemote, pullBranch;
500 g_Git.GetRemoteTrackedBranchForHEAD(pullRemote, pullBranch);
502 CString defaultUpstream;
503 defaultUpstream.Format(L"remotes/%s/%s", static_cast<LPCWSTR>(pullRemote), static_cast<LPCWSTR>(pullBranch));
504 const int found = m_UpstreamCtrl.FindStringExact(0, defaultUpstream);
505 if(found >= 0)
506 m_UpstreamCtrl.SetCurSel(found);
507 else
508 m_UpstreamCtrl.SetCurSel(-1);
510 AddBranchToolTips(m_UpstreamCtrl);
513 void CRebaseDlg::OnCbnSelchangeBranch()
515 FetchLogList();
518 void CRebaseDlg::OnCbnSelchangeUpstream()
520 FetchLogList();
523 void CRebaseDlg::FetchLogList()
525 CGitHash base,hash,upstream;
526 m_IsFastForward = false;
528 if (m_BranchCtrl.GetString().IsEmpty())
530 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_SELECTBRANCH)));
531 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
532 return;
535 if (g_Git.GetHash(hash, m_BranchCtrl.GetString()))
537 m_CommitList.ShowText(g_Git.GetGitLastErr(L"Could not get hash of \"" + m_BranchCtrl.GetString() + L"\"."));
538 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
539 return;
542 if (m_UpstreamCtrl.GetString().IsEmpty())
544 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_SELECTUPSTREAM)));
545 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
546 return;
549 if (g_Git.GetHash(upstream, m_UpstreamCtrl.GetString()))
551 m_CommitList.ShowText(g_Git.GetGitLastErr(L"Could not get hash of \"" + m_UpstreamCtrl.GetString() + L"\"."));
552 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
553 return;
556 if (hash == upstream)
558 m_CommitList.Clear();
559 CString text;
560 text.FormatMessage(IDS_REBASE_EQUAL_FMT, static_cast<LPCWSTR>(m_BranchCtrl.GetString()), static_cast<LPCWSTR>(this->m_UpstreamCtrl.GetString()));
562 m_CommitList.ShowText(text);
563 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(false);
564 if (m_bRebaseAutoStart)
565 PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_ABORT, BN_CLICKED), reinterpret_cast<LPARAM>(GetDlgItem(IDC_REBASE_ABORT)->GetSafeHwnd()));
566 return;
569 if (g_Git.IsFastForward(m_BranchCtrl.GetString(), m_UpstreamCtrl.GetString(), &base) && m_Onto.IsEmpty())
571 m_IsFastForward = true;
573 m_CommitList.Clear();
574 CString text;
575 text.FormatMessage(IDS_REBASE_FASTFORWARD_FMT, static_cast<LPCWSTR>(m_BranchCtrl.GetString()), static_cast<LPCWSTR>(this->m_UpstreamCtrl.GetString()),
576 static_cast<LPCWSTR>(m_BranchCtrl.GetString()), static_cast<LPCWSTR>(this->m_UpstreamCtrl.GetString()));
578 m_CommitList.ShowText(text);
579 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(true);
580 SetContinueButtonText();
582 return ;
585 if (!m_bForce && m_Onto.IsEmpty())
587 if (base == upstream)
589 m_CommitList.Clear();
590 CString text;
591 text.Format(IDS_REBASE_UPTODATE_FMT, static_cast<LPCWSTR>(m_BranchCtrl.GetString()));
592 m_CommitList.ShowText(text);
593 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(FALSE);
594 SetContinueButtonText();
595 if (m_bRebaseAutoStart)
596 PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_ABORT, BN_CLICKED), reinterpret_cast<LPARAM>(GetDlgItem(IDC_REBASE_ABORT)->GetSafeHwnd()));
597 return;
601 m_CommitList.Clear();
602 CString refFrom = g_Git.FixBranchName(m_UpstreamCtrl.GetString());
603 CString refTo = g_Git.FixBranchName(m_BranchCtrl.GetString());
604 CString range;
605 range.Format(L"%s..%s", static_cast<LPCWSTR>(refFrom), static_cast<LPCWSTR>(refTo));
606 this->m_CommitList.FillGitLog(nullptr, &range, (m_bPreserveMerges ? 0 : CGit::LOG_INFO_NO_MERGE) | CGit::LOG_ORDER_TOPOORDER);
608 if( m_CommitList.GetItemCount() == 0 )
609 m_CommitList.ShowText(CString(MAKEINTRESOURCE(IDS_PROC_NOTHINGTOREBASE)));
611 m_rewrittenCommitsMap.clear();
612 if (m_bPreserveMerges)
614 CGitHash head;
615 if (g_Git.GetHash(head, L"HEAD"))
617 AddLogString(CString(MAKEINTRESOURCE(IDS_PROC_NOHEAD)));
618 return;
620 CGitHash upstreamHash;
621 if (g_Git.GetHash(upstreamHash, m_Onto.IsEmpty() ? m_UpstreamCtrl.GetString() : m_Onto))
623 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of \"" + (m_Onto.IsEmpty() ? m_UpstreamCtrl.GetString() : m_Onto) + L"\"."), L"TortoiseGit", MB_ICONERROR);
624 return;
626 CString mergecmd;
627 mergecmd.Format(L"git merge-base --all %s %s", static_cast<LPCWSTR>(head.ToString()), static_cast<LPCWSTR>(upstreamHash.ToString()));
628 g_Git.Run(mergecmd, [&](const CStringA& line)
630 CGitHash hash = CGitHash::FromHexStr(line);
631 if (hash.IsEmpty())
632 return;
633 m_rewrittenCommitsMap[hash] = upstreamHash;
636 std::vector<size_t> toDrop;
637 for (size_t i = m_CommitList.m_arShownList.size(); i-- > 0;)
639 bool preserve = false;
640 GitRevLoglist* pRev = m_CommitList.m_arShownList.SafeGetAt(i);
641 for (const auto& parent : pRev->m_ParentHash)
643 const auto rewrittenParent = m_rewrittenCommitsMap.find(parent);
644 if (rewrittenParent != m_rewrittenCommitsMap.cend())
646 preserve = true;
647 break;
650 if (preserve)
651 m_rewrittenCommitsMap[pRev->m_CommitHash] = CGitHash();
652 else
653 toDrop.push_back(i);
656 // Drop already included commits
657 std::vector<CGitHash> nonCherryPicked;
658 CString cherryCmd;
659 cherryCmd.Format(L"git rev-list \"%s...%s\" --left-right --cherry-pick", static_cast<LPCWSTR>(refFrom), static_cast<LPCWSTR>(refTo));
660 g_Git.Run(cherryCmd, [&](const CStringA& line)
662 if (line.GetLength() < 2)
663 return;
664 if (line[0] != '>')
665 return;
666 CString hash = CUnicodeUtils::GetUnicode(line.Mid(1));
667 hash.Trim();
668 nonCherryPicked.emplace_back(CGitHash::FromHexStrTry(hash));
670 for (size_t i = m_CommitList.m_arShownList.size(); i-- > 0;)
672 GitRevLoglist* pRev = m_CommitList.m_arShownList.SafeGetAt(i);
673 pRev->GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_PICK;
674 if (m_rewrittenCommitsMap.find(pRev->m_CommitHash) != m_rewrittenCommitsMap.cend() && std::find(nonCherryPicked.cbegin(), nonCherryPicked.cend(), pRev->m_CommitHash) == nonCherryPicked.cend())
676 m_droppedCommitsMap[pRev->m_CommitHash].clear();
677 m_droppedCommitsMap[pRev->m_CommitHash].push_back(pRev->m_ParentHash[0]);
678 toDrop.push_back(i);
679 m_rewrittenCommitsMap.erase(pRev->m_CommitHash);
682 std::sort(toDrop.begin(), toDrop.end());
683 toDrop.erase(unique(toDrop.begin(), toDrop.end()), toDrop.end());
684 for (auto it = toDrop.crbegin(); it != toDrop.crend(); ++it)
686 m_CommitList.m_arShownList.SafeRemoveAt(*it);
687 m_CommitList.m_logEntries.erase(m_CommitList.m_logEntries.begin() + *it);
689 m_CommitList.SetItemCountEx(static_cast<int>(m_CommitList.m_logEntries.size()));
692 #if 0
693 if(m_CommitList.m_logEntries[m_CommitList.m_logEntries.size()-1].m_ParentHash.size() >=0 )
695 if(upstream == m_CommitList.m_logEntries[m_CommitList.m_logEntries.size()-1].m_ParentHash[0])
697 m_CommitList.Clear();
698 m_CommitList.ShowText(L"Nothing Rebase");
701 #endif
703 m_tooltips.Pop();
704 AddBranchToolTips(m_BranchCtrl);
705 AddBranchToolTips(m_UpstreamCtrl);
707 bool bHasSKip = false;
708 if (!m_bPreserveMerges)
710 // Default all actions to 'pick'
711 std::unordered_map<CGitHash, size_t> revIxMap;
712 for (size_t i = 0; i < m_CommitList.m_logEntries.size(); ++i)
714 GitRevLoglist& rev = m_CommitList.m_logEntries.GetGitRevAt(i);
715 rev.GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_PICK;
716 revIxMap[rev.m_CommitHash] = i;
719 // Default to skip when already in upstream
720 if (!m_Onto.IsEmpty())
721 refFrom = g_Git.FixBranchName(m_Onto);
722 CString cherryCmd;
723 cherryCmd.Format(L"git.exe cherry -- \"%s\" \"%s\"", static_cast<LPCWSTR>(refFrom), static_cast<LPCWSTR>(refTo));
724 g_Git.Run(cherryCmd, [&](const CStringA& line)
726 if (line.GetLength() < 2)
727 return;
728 if (line[0] != '-')
729 return; // Don't skip (only skip commits starting with a '-')
730 CString hash = CUnicodeUtils::GetUnicode(line.Mid(1));
731 hash.Trim();
732 auto itIx = revIxMap.find(CGitHash::FromHexStrTry(hash));
733 if (itIx == revIxMap.end())
734 return; // Not found?? Should not occur...
736 // Found. Skip it.
737 m_CommitList.m_logEntries.GetGitRevAt(itIx->second).GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_SKIP;
738 bHasSKip = true;
741 m_CommitList.Invalidate();
742 if (bHasSKip)
744 m_CtrlStatusText.SetWindowText(CString(MAKEINTRESOURCE(IDS_REBASE_AUTOSKIPPED)));
745 m_bStatusWarning = true;
747 else
749 m_CtrlStatusText.SetWindowText(m_sStatusText);
750 m_bStatusWarning = false;
752 m_CtrlStatusText.Invalidate();
754 if(m_CommitList.m_IsOldFirst)
755 this->m_CurrentRebaseIndex = -1;
756 else
757 this->m_CurrentRebaseIndex = static_cast<int>(m_CommitList.m_logEntries.size());
759 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(m_bPreserveMerges || m_CommitList.GetItemCount());
760 SetContinueButtonText();
763 void CRebaseDlg::AddBranchToolTips(CHistoryCombo& pBranch)
765 pBranch.DisableTooltip();
767 CString text = pBranch.GetString();
769 if (text.IsEmpty())
770 return;
772 GitRev rev;
773 if (rev.GetCommit(text))
775 MessageBox(L"Failed to get commit.\n" + rev.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
776 return;
778 rev.ApplyMailmap();
780 CString tooltip;
781 tooltip.Format(L"%s: %s\n%s: %s <%s>\n%s: %s\n%s:\n%s\n%s",
782 static_cast<LPCWSTR>(CString(MAKEINTRESOURCE(IDS_LOG_REVISION))),
783 static_cast<LPCWSTR>(rev.m_CommitHash.ToString()),
784 static_cast<LPCWSTR>(CString(MAKEINTRESOURCE(IDS_LOG_AUTHOR))),
785 static_cast<LPCWSTR>(rev.GetAuthorName()),
786 static_cast<LPCWSTR>(rev.GetAuthorEmail()),
787 static_cast<LPCWSTR>(CString(MAKEINTRESOURCE(IDS_LOG_DATE))),
788 static_cast<LPCWSTR>(CLoglistUtils::FormatDateAndTime(rev.GetAuthorDate(), DATE_LONGDATE)),
789 static_cast<LPCWSTR>(CString(MAKEINTRESOURCE(IDS_LOG_MESSAGE))),
790 static_cast<LPCWSTR>(rev.GetSubject()),
791 static_cast<LPCWSTR>(rev.GetBody()));
793 if (tooltip.GetLength() > 8000)
795 tooltip.Truncate(8000);
796 tooltip += L"...";
799 m_tooltips.AddTool(pBranch.GetComboBoxCtrl(), tooltip);
802 BOOL CRebaseDlg::PreTranslateMessage(MSG*pMsg)
804 if (pMsg->message == WM_KEYDOWN)
806 switch (pMsg->wParam)
808 case ' ':
809 if (LogListHasFocus(pMsg->hwnd)
810 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_PICK)
811 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SQUASH)
812 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_EDIT)
813 && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SKIP))
815 m_CommitList.ShiftSelectedRebaseAction();
816 return TRUE;
818 break;
819 case 'P':
820 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_PICK))
822 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_PICK);
823 return TRUE;
825 break;
826 case 'S':
827 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SKIP))
829 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SKIP);
830 return TRUE;
832 break;
833 case 'Q':
834 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_SQUASH))
836 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
837 return TRUE;
839 break;
840 case 'E':
841 if (LogListHasFocus(pMsg->hwnd) && LogListHasMenuItem(CGitLogListBase::ID_REBASE_EDIT))
843 m_CommitList.SetSelectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
844 return TRUE;
846 break;
847 case 'U':
848 if (LogListHasFocus(pMsg->hwnd) && GetDlgItem(IDC_BUTTON_UP)->IsWindowEnabled() == TRUE)
850 OnBnClickedButtonUp();
851 return TRUE;
853 break;
854 case 'D':
855 if (LogListHasFocus(pMsg->hwnd) && GetDlgItem(IDC_BUTTON_DOWN)->IsWindowEnabled() == TRUE)
857 OnBnClickedButtonDown();
858 return TRUE;
860 break;
861 case 'A':
862 if(LogListHasFocus(pMsg->hwnd) && GetAsyncKeyState(VK_CONTROL) & 0x8000)
864 // select all entries
865 for (int i = 0; i < m_CommitList.GetItemCount(); ++i)
866 m_CommitList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
867 return TRUE;
869 break;
870 case VK_F5:
872 Refresh();
873 return TRUE;
875 break;
876 case VK_RETURN:
878 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
880 if (GetDlgItem(IDC_REBASE_CONTINUE)->IsWindowEnabled())
881 PostMessage(WM_COMMAND, IDC_REBASE_CONTINUE);
882 else if (GetDlgItem(IDC_REBASE_ABORT)->IsWindowEnabled())
883 GetDlgItem(IDC_REBASE_ABORT)->SetFocus();
884 else
885 GetDlgItem(IDHELP)->SetFocus();
886 return TRUE;
889 break;
890 /* Avoid TAB control destroy but dialog exist*/
891 case VK_ESCAPE:
892 case VK_CANCEL:
894 wchar_t buff[128] = { 0 };
895 ::GetClassName(pMsg->hwnd,buff,128);
898 /* Use MSFTEDIT_CLASS http://msdn.microsoft.com/en-us/library/bb531344.aspx */
899 if (_wcsnicmp(buff, MSFTEDIT_CLASS, 128) == 0 || //Unicode and MFC 2012 and later
900 _wcsnicmp(buff, RICHEDIT_CLASS, 128) == 0 || //ANSI or MFC 2010
901 _wcsnicmp(buff, L"Scintilla", 128) == 0 ||
902 _wcsnicmp(buff, L"SysListView32", 128) == 0 ||
903 ::GetParent(pMsg->hwnd) == this->m_ctrlTabCtrl.m_hWnd)
905 this->PostMessage(WM_KEYDOWN,VK_ESCAPE,0);
906 return TRUE;
911 else if (pMsg->message == WM_NEXTDLGCTL)
913 HWND hwnd = GetFocus()->GetSafeHwnd();
914 if (hwnd == m_LogMessageCtrl.GetSafeHwnd() || hwnd == m_wndOutputRebase.GetSafeHwnd())
916 if (GetDlgItem(IDC_REBASE_CONTINUE)->IsWindowEnabled())
917 GetDlgItem(IDC_REBASE_CONTINUE)->SetFocus();
918 else if (GetDlgItem(IDC_REBASE_ABORT)->IsWindowEnabled())
919 GetDlgItem(IDC_REBASE_ABORT)->SetFocus();
920 else
921 GetDlgItem(IDHELP)->SetFocus();
922 return TRUE;
925 return CResizableStandAloneDialog::PreTranslateMessage(pMsg);
928 bool CRebaseDlg::LogListHasFocus(HWND hwnd)
930 wchar_t buff[128] = { 0 };
931 ::GetClassName(hwnd, buff, 128);
933 if (_wcsnicmp(buff, L"SysListView32", 128) == 0)
934 return true;
935 return false;
938 bool CRebaseDlg::LogListHasMenuItem(int i)
940 return (m_CommitList.m_ContextMenuMask & m_CommitList.GetContextMenuBit(i)) != 0;
943 int CRebaseDlg::CheckRebaseCondition()
945 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
947 if( !g_Git.CheckCleanWorkTree() )
949 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)
951 CString out;
952 CString cmd = L"git.exe stash";
953 this->AddLogString(cmd);
954 if (g_Git.Run(cmd, &out, CP_UTF8))
956 MessageBox(out, L"TortoiseGit", MB_OK | MB_ICONERROR);
957 return -1;
959 m_bStashed = true;
961 else
962 return -1;
964 //Todo Check $REBASE_ROOT
965 //Todo Check $DOTEST
967 if (!CAppUtils::CheckUserData(GetSafeHwnd()))
968 return -1;
970 if (!m_IsCherryPick)
972 CString error;
973 DWORD exitcode = 0xFFFFFFFF;
974 CHooks::Instance().SetProjectProperties(g_Git.m_CurrentDir, m_ProjectProperties);
975 if (CHooks::Instance().PreRebase(GetSafeHwnd(), g_Git.m_CurrentDir, m_UpstreamCtrl.GetString(), m_BranchCtrl.GetString(), exitcode, error))
977 if (exitcode)
979 CString sErrorMsg;
980 sErrorMsg.Format(IDS_HOOK_ERRORMSG, static_cast<LPCWSTR>(error));
981 CTaskDialog taskdlg(sErrorMsg, CString(MAKEINTRESOURCE(IDS_HOOKFAILED_TASK2)), L"TortoiseGit", 0, TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS | TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW | TDF_SIZE_TO_CONTENT);
982 taskdlg.AddCommandControl(101, CString(MAKEINTRESOURCE(IDS_HOOKFAILED_TASK3)));
983 taskdlg.AddCommandControl(102, CString(MAKEINTRESOURCE(IDS_HOOKFAILED_TASK4)));
984 taskdlg.SetDefaultCommandControl(101);
985 taskdlg.SetMainIcon(TD_ERROR_ICON);
986 if (taskdlg.DoModal(GetSafeHwnd()) != 102)
987 return -1;
992 return 0;
995 void CRebaseDlg::CheckRestoreStash()
997 const bool autoStash = !m_IsCherryPick && g_Git.GetConfigValueBool(L"rebase.autostash");
998 if (m_bStashed && (autoStash || CMessageBox::Show(GetSafeHwnd(), IDS_DCOMMIT_STASH_POP, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) == IDYES))
999 CAppUtils::StashPop(GetSafeHwnd(), autoStash ? 0 : 1);
1000 m_bStashed = false;
1003 int CRebaseDlg::WriteReflog(CGitHash hash, const char* message)
1005 CAutoRepository repo(g_Git.GetGitRepository());
1006 CAutoReflog reflog;
1007 if (git_reflog_read(reflog.GetPointer(), repo, "HEAD") < 0)
1009 MessageBox(g_Git.GetGitLastErr(L"Could not read HEAD reflog"), L"TortoiseGit", MB_ICONERROR);
1010 return -1;
1012 CAutoSignature signature;
1013 if (git_signature_default(signature.GetPointer(), repo) < 0)
1015 MessageBox(g_Git.GetGitLastErr(L"Could not get signature"), L"TortoiseGit", MB_ICONERROR);
1016 return -1;
1018 if (git_reflog_append(reflog, hash, signature, message) < 0)
1020 MessageBox(g_Git.GetGitLastErr(L"Could not append HEAD reflog"), L"TortoiseGit", MB_ICONERROR);
1021 return -1;
1023 if (git_reflog_write(reflog) < 0)
1025 MessageBox(g_Git.GetGitLastErr(L"Could not write HEAD reflog"), L"TortoiseGit", MB_ICONERROR);
1026 return -1;
1029 return 0;
1032 int CRebaseDlg::StartRebase()
1034 CString cmd,out;
1035 m_OrigHEADBranch = g_Git.GetCurrentBranch(true);
1037 m_OrigHEADHash.Empty();
1038 if (g_Git.GetHash(m_OrigHEADHash, L"HEAD"))
1040 AddLogString(CString(MAKEINTRESOURCE(IDS_PROC_NOHEAD)));
1041 return -1;
1043 //Todo
1044 //git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
1045 // echo "detached HEAD" > "$DOTEST"/head-name
1047 cmd.Format(L"git.exe update-ref ORIG_HEAD %s", static_cast<LPCWSTR>(m_OrigHEADHash.ToString()));
1048 if(g_Git.Run(cmd,&out,CP_UTF8))
1050 AddLogString(L"update ORIG_HEAD Fail");
1051 return -1;
1054 m_OrigUpstreamHash.Empty();
1055 if (g_Git.GetHash(m_OrigUpstreamHash, (m_IsCherryPick || m_Onto.IsEmpty()) ? m_UpstreamCtrl.GetString() : m_Onto))
1057 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of \"" + ((m_IsCherryPick || m_Onto.IsEmpty()) ? m_UpstreamCtrl.GetString() : m_Onto) + L"\"."), L"TortoiseGit", MB_ICONERROR);
1058 return -1;
1061 if( !this->m_IsCherryPick )
1063 if (g_Git.m_IsUseLibGit2)
1064 WriteReflog(m_OrigHEADHash, "rebase: start (" + CUnicodeUtils::GetUTF8(m_OrigHEADBranch) + " on " + CUnicodeUtils::GetUTF8(m_OrigUpstreamHash.ToString()) + ")");
1065 cmd.Format(L"git.exe checkout -f %s --", static_cast<LPCWSTR>(m_OrigUpstreamHash.ToString()));
1066 this->AddLogString(cmd);
1067 if (RunGitCmdRetryOrAbort(cmd))
1068 return -1;
1071 CString log;
1072 if( !this->m_IsCherryPick )
1074 if (g_Git.GetHash(m_OrigBranchHash, m_BranchCtrl.GetString()))
1076 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of \"" + m_BranchCtrl.GetString() + L"\"."), L"TortoiseGit", MB_ICONERROR);
1077 return -1;
1079 log.Format(L"%s\r\n", static_cast<LPCWSTR>(CString(MAKEINTRESOURCE(IDS_PROC_REBASE_STARTREBASE))));
1081 else
1082 log.Format(L"%s\r\n", static_cast<LPCWSTR>(CString(MAKEINTRESOURCE(IDS_PROC_REBASE_STARTCHERRYPICK))));
1084 this->AddLogString(log);
1085 return 0;
1087 int CRebaseDlg::VerifyNoConflict()
1089 const int hasConflicts = g_Git.HasWorkingTreeConflicts();
1090 if (hasConflicts < 0)
1092 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
1093 return -1;
1095 if (hasConflicts)
1097 CMessageBox::Show(GetSafeHwnd(), IDS_PROGRS_CONFLICTSOCCURRED, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
1098 auto locker(m_FileListCtrl.AcquireReadLock());
1099 auto pos = m_FileListCtrl.GetFirstSelectedItemPosition();
1100 while (pos)
1101 m_FileListCtrl.SetItemState(m_FileListCtrl.GetNextSelectedItem(pos), 0, LVIS_SELECTED);
1102 const int nListItems = m_FileListCtrl.GetItemCount();
1103 for (int i = 0; i < nListItems; ++i)
1105 auto entry = m_FileListCtrl.GetListEntry(i);
1106 if (entry->m_Action & CTGitPath::LOGACTIONS_UNMERGED)
1108 m_FileListCtrl.EnsureVisible(i, FALSE);
1109 m_FileListCtrl.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
1110 m_FileListCtrl.SetFocus();
1111 return -1;
1114 return -1;
1116 CleanUpRebaseActiveFolder();
1117 return 0;
1120 int CRebaseDlg::FinishRebase()
1122 if (m_bFinishedRebase)
1123 return 0;
1125 m_bFinishedRebase = true;
1126 if(this->m_IsCherryPick) //cherry pick mode no "branch", working at upstream branch
1128 m_sStatusText.LoadString(IDS_DONE);
1129 m_CtrlStatusText.SetWindowText(m_sStatusText);
1130 m_bStatusWarning = false;
1131 m_CtrlStatusText.Invalidate();
1132 return 0;
1135 RewriteNotes();
1137 CGitHash head;
1138 if (g_Git.GetHash(head, L"HEAD"))
1140 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
1141 return -1;
1144 m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1146 if (g_Git.IsLocalBranch(m_BranchCtrl.GetString()))
1148 CString cmd;
1149 cmd.Format(L"git.exe checkout -f -B %s %s --", static_cast<LPCWSTR>(m_BranchCtrl.GetString()), static_cast<LPCWSTR>(head.ToString()));
1150 AddLogString(cmd);
1151 if (RunGitCmdRetryOrAbort(cmd))
1152 return -1;
1155 CString cmd;
1156 cmd.Format(L"git.exe reset --hard %s --", static_cast<LPCWSTR>(head.ToString()));
1157 AddLogString(cmd);
1158 if (RunGitCmdRetryOrAbort(cmd))
1159 return -1;
1161 if (g_Git.m_IsUseLibGit2)
1162 WriteReflog(head, "rebase: finished");
1164 while (m_ctrlTabCtrl.GetTabsNum() > 1)
1165 m_ctrlTabCtrl.RemoveTab(0);
1166 m_CtrlStatusText.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_REBASEFINISHED)));
1167 m_sStatusText.LoadString(IDS_PROC_REBASEFINISHED);
1168 m_bStatusWarning = false;
1169 m_CtrlStatusText.Invalidate();
1171 m_bRebaseAutoEnd = m_bRebaseAutoStart;
1173 return 0;
1176 void CRebaseDlg::RewriteNotes()
1178 CString rewrites;
1179 for (const auto& entry : m_rewrittenCommitsMap)
1181 if (entry.second.IsEmpty())
1182 continue;
1183 rewrites += entry.first.ToString();
1184 rewrites += L' ';
1185 rewrites += entry.second.ToString();
1186 rewrites += L'\n';
1188 if (rewrites.IsEmpty())
1189 return;
1190 CString tmpfile = GetTempFile();
1191 if (tmpfile.IsEmpty())
1193 MessageBox(L"Could not create temp file.", L"TortoiseGit", MB_OK | MB_ICONERROR);
1194 return;
1196 tmpfile.Replace(L'\\', L'/');
1197 if (!CStringUtils::WriteStringToTextFile(tmpfile, rewrites))
1198 return;
1199 SCOPE_EXIT{ ::DeleteFile(tmpfile); };
1200 CString pipefile = GetTempFile();
1201 if (pipefile.IsEmpty())
1203 MessageBox(L"Could not create temp file.", L"TortoiseGit", MB_OK | MB_ICONERROR);
1204 return;
1206 pipefile.Replace(L'\\', L'/');
1207 CString pipecmd;
1208 pipecmd.Format(L"git notes copy --for-rewrite=rebase < %s", static_cast<LPCWSTR>(tmpfile));
1209 if (!CStringUtils::WriteStringToTextFile(pipefile, pipecmd))
1210 return;
1211 SCOPE_EXIT{ ::DeleteFile(pipefile); };
1212 CString out;
1213 g_Git.Run(L"bash.exe " + pipefile, &out, CP_UTF8);
1216 void CRebaseDlg::OnBnClickedContinue()
1218 if (m_RebaseStage == RebaseStage::Done)
1220 OnOK();
1221 CleanUpRebaseActiveFolder();
1222 CheckRestoreStash();
1223 SaveSplitterPos();
1224 return;
1227 if (m_RebaseStage == RebaseStage::Choose_Branch || m_RebaseStage == RebaseStage::Choose_Commit_Pick_Mode)
1229 if (CAppUtils::IsTGitRebaseActive(GetSafeHwnd()))
1230 return;
1231 if (CheckRebaseCondition())
1232 return;
1235 m_bAbort = FALSE;
1236 if (m_IsFastForward)
1238 GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(FALSE);
1239 CString cmd,out;
1240 if (g_Git.GetHash(m_OrigBranchHash, m_BranchCtrl.GetString()))
1242 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of \"" + m_BranchCtrl.GetString() + L"\"."), L"TortoiseGit", MB_ICONERROR);
1243 GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(TRUE);
1244 return;
1246 if (g_Git.GetHash(m_OrigUpstreamHash, m_UpstreamCtrl.GetString()))
1248 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of \"" + m_UpstreamCtrl.GetString() + L"\"."), L"TortoiseGit", MB_ICONERROR);
1249 GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(TRUE);
1250 return;
1253 if(!g_Git.IsFastForward(this->m_BranchCtrl.GetString(),this->m_UpstreamCtrl.GetString()))
1255 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1256 AddLogString(L"No fast forward possible.\r\nMaybe repository changed");
1257 GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(TRUE);
1258 return;
1261 if (g_Git.IsLocalBranch(m_BranchCtrl.GetString()))
1263 CString endOfOptions;
1264 if (CGit::ms_LastMsysGitVersion >= ConvertVersionToInt(2, 43, 1))
1265 endOfOptions = L" --end-of-options";
1266 cmd.Format(L"git.exe checkout --no-track -f -B %s%s %s --", static_cast<LPCWSTR>(m_BranchCtrl.GetString()), static_cast<LPCWSTR>(endOfOptions), static_cast<LPCWSTR>(m_UpstreamCtrl.GetString()));
1267 AddLogString(cmd);
1268 if (RunGitCmdRetryOrAbort(cmd))
1270 GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(TRUE);
1271 return;
1273 AddLogString(out);
1274 out.Empty();
1277 CString endOfOptions;
1278 if (CGit::ms_LastMsysGitVersion >= ConvertVersionToInt(2, 43, 1))
1279 endOfOptions = L" --end-of-options";
1280 cmd.Format(L"git.exe reset --hard%s %s --", static_cast<LPCWSTR>(endOfOptions), static_cast<LPCWSTR>(g_Git.FixBranchName(this->m_UpstreamCtrl.GetString())));
1281 CString log;
1282 log.Format(IDS_PROC_REBASE_FFTO, static_cast<LPCWSTR>(m_UpstreamCtrl.GetString()));
1283 this->AddLogString(log);
1285 AddLogString(cmd);
1286 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1287 if (RunGitCmdRetryOrAbort(cmd))
1289 GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(TRUE);
1290 return;
1292 AddLogString(out);
1293 AddLogString(CString(MAKEINTRESOURCE(IDS_DONE)));
1294 m_RebaseStage = RebaseStage::Done;
1295 UpdateCurrentStatus();
1297 if (m_bRebaseAutoStart)
1298 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_CONTINUE, BN_CLICKED), reinterpret_cast<LPARAM>(GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd()));
1300 return;
1303 if (m_RebaseStage == RebaseStage::Choose_Branch || m_RebaseStage == RebaseStage::Choose_Commit_Pick_Mode)
1305 m_RebaseStage = RebaseStage::Start;
1306 m_FileListCtrl.Clear();
1307 m_FileListCtrl.SetHasCheckboxes(false);
1308 m_FileListCtrl.m_CurrentVersion.Empty();
1309 m_ctrlTabCtrl.SetTabLabel(REBASE_TAB_CONFLICT, CString(MAKEINTRESOURCE(IDS_PROC_CONFLICTFILES)));
1310 m_ctrlTabCtrl.AddTab(&m_wndOutputRebase, CString(MAKEINTRESOURCE(IDS_LOG)), 2);
1311 m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1314 if (m_RebaseStage == RebaseStage::Finish)
1316 if(FinishRebase())
1317 return ;
1319 SaveSplitterPos();
1320 OnOK();
1323 if (m_RebaseStage == RebaseStage::Squash_Conclict)
1325 if(VerifyNoConflict())
1326 return;
1327 if (CAppUtils::MessageContainsConflictHints(GetSafeHwnd(), m_LogMessageCtrl.GetText()))
1328 return;
1329 GitRevLoglist* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1330 if(this->CheckNextCommitIsSquash())
1331 {//next commit is not squash;
1332 m_RebaseStage = RebaseStage::Squash_Edit;
1333 this->OnRebaseUpdateUI(0,0);
1334 this->UpdateCurrentStatus();
1335 return ;
1337 m_RebaseStage = RebaseStage::Continue;
1338 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1339 m_forRewrite.push_back(curRev->m_CommitHash);
1340 this->UpdateCurrentStatus();
1343 if (m_RebaseStage == RebaseStage::Conclict)
1345 if(VerifyNoConflict())
1346 return;
1348 if (CAppUtils::MessageContainsConflictHints(GetSafeHwnd(), m_LogMessageCtrl.GetText()))
1349 return;
1351 m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1353 GitRevLoglist* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1354 // ***************************************************
1355 // ATTENTION: Similar code in CommitDlg.cpp!!!
1356 // ***************************************************
1357 CMassiveGitTask mgtReAddAfterCommit(L"add --ignore-errors -f");
1358 CMassiveGitTask mgtReDelAfterCommit(L"rm --cached --ignore-unmatch");
1359 CMassiveGitTask mgtAdd(L"add -f");
1360 CMassiveGitTask mgtUpdateIndexForceRemove(L"update-index --force-remove");
1361 CMassiveGitTask mgtUpdateIndex(L"update-index");
1362 CMassiveGitTask mgtRm(L"rm --ignore-unmatch");
1363 CMassiveGitTask mgtRmFCache(L"rm -f --cache");
1364 CMassiveGitTask mgtReset(L"reset", TRUE, true);
1365 auto locker(m_FileListCtrl.AcquireReadLock());
1366 for (int i = 0; i < m_FileListCtrl.GetItemCount(); i++)
1368 auto entry = m_FileListCtrl.GetListEntry(i);
1369 if (entry->m_Checked)
1371 if ((entry->m_Action & CTGitPath::LOGACTIONS_UNVER) || (entry->IsDirectory() && !(entry->m_Action & CTGitPath::LOGACTIONS_DELETED)))
1372 mgtAdd.AddFile(entry->GetGitPathString());
1373 else if (entry->m_Action & CTGitPath::LOGACTIONS_DELETED)
1374 mgtUpdateIndexForceRemove.AddFile(entry->GetGitPathString());
1375 else
1376 mgtUpdateIndex.AddFile(entry->GetGitPathString());
1378 if ((entry->m_Action & CTGitPath::LOGACTIONS_REPLACED) && !entry->GetGitOldPathString().IsEmpty())
1379 mgtRm.AddFile(entry->GetGitOldPathString());
1381 else
1383 if (entry->m_Action & CTGitPath::LOGACTIONS_ADDED || entry->m_Action & CTGitPath::LOGACTIONS_REPLACED)
1385 mgtRmFCache.AddFile(entry->GetGitPathString());
1386 mgtReAddAfterCommit.AddFile(*entry);
1388 if (entry->m_Action & CTGitPath::LOGACTIONS_REPLACED && !entry->GetGitOldPathString().IsEmpty())
1390 mgtReset.AddFile(entry->GetGitOldPathString());
1391 mgtReDelAfterCommit.AddFile(entry->GetGitOldPathString());
1394 else if(!(entry->m_Action & CTGitPath::LOGACTIONS_UNVER))
1396 mgtReset.AddFile(entry->GetGitPathString());
1397 if (entry->m_Action & CTGitPath::LOGACTIONS_DELETED && !(entry->m_Action & CTGitPath::LOGACTIONS_MISSING))
1398 mgtReDelAfterCommit.AddFile(entry->GetGitPathString());
1403 BOOL cancel = FALSE;
1404 bool successful = true;
1405 successful = successful && mgtAdd.Execute(cancel);
1406 successful = successful && mgtUpdateIndexForceRemove.Execute(cancel);
1407 successful = successful && mgtUpdateIndex.Execute(cancel);
1408 successful = successful && mgtRm.Execute(cancel);
1409 successful = successful && mgtRmFCache.Execute(cancel);
1410 successful = successful && mgtReset.Execute(cancel);
1412 if (!successful)
1414 AddLogString(L"An error occurred while updating the index.");
1415 return;
1418 CString allowempty;
1419 bool skipCurrent = false;
1420 if (!m_CurrentCommitEmpty)
1422 if (g_Git.IsResultingCommitBecomeEmpty() == TRUE)
1424 if (CheckNextCommitIsSquash() == 0)
1426 allowempty = L"--allow-empty ";
1427 m_CurrentCommitEmpty = false;
1429 else
1431 const int choose = CMessageBox::ShowCheck(GetSafeHwnd(), IDS_CHERRYPICK_EMPTY, IDS_APPNAME, 1, IDI_QUESTION, IDS_COMMIT_COMMIT, IDS_SKIPBUTTON, IDS_MSGBOX_CANCEL, nullptr, 0);
1432 if (choose == 2)
1433 skipCurrent = true;
1434 else if (choose == 1)
1436 allowempty = L"--allow-empty ";
1437 m_CurrentCommitEmpty = true;
1439 else
1440 return;
1445 CString out;
1446 CString cmd;
1447 cmd.Format(L"git.exe commit %s--allow-empty-message -C %s", static_cast<LPCWSTR>(allowempty), static_cast<LPCWSTR>(curRev->m_CommitHash.ToString()));
1449 AddLogString(cmd);
1451 if (!skipCurrent && g_Git.Run(cmd, &out, CP_UTF8))
1453 AddLogString(out);
1454 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1455 return;
1458 AddLogString(out);
1460 // update commit message if needed
1461 CString str = m_LogMessageCtrl.GetText().Trim();
1462 if (!skipCurrent && str != (curRev->GetSubject() + L'\n' + curRev->GetBody()).Trim())
1464 if (str.IsEmpty())
1466 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_COMMITMESSAGE_EMPTY,IDS_APPNAME, MB_OK | MB_ICONERROR);
1467 return;
1469 CString tempfile = ::GetTempFile();
1470 if (tempfile.IsEmpty())
1472 MessageBox(L"Could not create temp file.", L"TortoiseGit", MB_OK | MB_ICONERROR);
1473 return;
1475 SCOPE_EXIT{ ::DeleteFile(tempfile); };
1476 if (CAppUtils::SaveCommitUnicodeFile(tempfile, str))
1478 CMessageBox::Show(GetSafeHwnd(), L"Could not save commit message", L"TortoiseGit", MB_OK | MB_ICONERROR);
1479 return;
1482 out.Empty();
1483 cmd.Format(L"git.exe commit --amend -F \"%s\"", static_cast<LPCWSTR>(tempfile));
1484 AddLogString(cmd);
1486 if (g_Git.Run(cmd, &out, CP_UTF8))
1488 AddLogString(out);
1489 if (!g_Git.CheckCleanWorkTree())
1491 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1492 return;
1494 CString retry;
1495 retry.LoadString(IDS_MSGBOX_RETRY);
1496 CString ignore;
1497 ignore.LoadString(IDS_MSGBOX_IGNORE);
1498 if (CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", 1, IDI_ERROR, retry, ignore) == 1)
1499 return;
1502 AddLogString(out);
1505 if (static_cast<DWORD>(CRegStdDWORD(L"Software\\TortoiseGit\\ReaddUnselectedAddedFilesAfterCommit", TRUE)) == TRUE)
1507 BOOL cancel2 = FALSE;
1508 mgtReAddAfterCommit.Execute(cancel2);
1509 mgtReDelAfterCommit.Execute(cancel2);
1512 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_EDIT)
1514 m_RebaseStage = RebaseStage::Edit;
1515 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
1516 this->UpdateCurrentStatus();
1517 return;
1519 else
1521 m_RebaseStage = RebaseStage::Continue;
1522 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1523 this->UpdateCurrentStatus();
1525 if (CheckNextCommitIsSquash() == 0) // remember commit msg after edit if next commit if squash
1526 ResetParentForSquash(str);
1527 else
1529 m_SquashMessage.Empty();
1530 CGitHash head;
1531 if (g_Git.GetHash(head, L"HEAD"))
1533 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
1534 return;
1536 m_rewrittenCommitsMap[curRev->m_CommitHash] = head;
1541 if ((m_RebaseStage == RebaseStage::Edit || m_RebaseStage == RebaseStage::Continue || m_bSplitCommit || m_RebaseStage == RebaseStage::Squash_Edit) && CheckNextCommitIsSquash() && (m_bSplitCommit || !g_Git.CheckCleanWorkTree(true)))
1543 if (!m_bSplitCommit && CMessageBox::Show(GetSafeHwnd(), IDS_PROC_REBASE_CONTINUE_NOTCLEAN, IDS_APPNAME, 1, IDI_ERROR, IDS_MSGBOX_OK, IDS_ABORTBUTTON) == 2)
1544 return;
1545 BOOL isFirst = TRUE;
1548 CCommitDlg dlg;
1549 if (isFirst)
1550 dlg.m_sLogMessage = m_LogMessageCtrl.GetText();
1551 dlg.m_bWholeProject = true;
1552 dlg.m_bSelectFilesForCommit = true;
1553 dlg.m_bCommitAmend = isFirst && (m_RebaseStage != RebaseStage::Squash_Edit); // do not amend on squash_edit stage, we need a normal commit there
1554 if (isFirst && m_RebaseStage == RebaseStage::Squash_Edit)
1556 if (m_iSquashdate != 2)
1557 dlg.SetTime(m_SquashFirstMetaData.time);
1558 dlg.SetAuthor(m_SquashFirstMetaData.GetAuthor());
1560 CTGitPathList gpl;
1561 gpl.AddPath(CTGitPath());
1562 dlg.m_pathList = gpl;
1563 dlg.m_bAmendDiffToLastCommit = !m_bSplitCommit;
1564 dlg.m_bNoPostActions = true;
1565 if (dlg.m_bCommitAmend)
1566 dlg.m_AmendStr = dlg.m_sLogMessage;
1567 dlg.m_bWarnDetachedHead = false;
1569 if (dlg.DoModal() != IDOK)
1570 return;
1572 isFirst = !m_bSplitCommit; // only select amend on second+ runs if not in split commit mode
1574 m_SquashMessage.Empty();
1575 m_CurrentCommitEmpty = dlg.m_bCommitMessageOnly;
1576 } while (!g_Git.CheckCleanWorkTree() || (m_bSplitCommit && CMessageBox::Show(GetSafeHwnd(), IDS_REBASE_ADDANOTHERCOMMIT, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) == IDYES));
1578 m_bSplitCommit = FALSE;
1579 UpdateData(FALSE);
1581 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1582 m_RebaseStage = RebaseStage::Continue;
1583 GitRevLoglist* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1584 CGitHash head;
1585 if (g_Git.GetHash(head, L"HEAD"))
1587 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
1588 return;
1590 m_rewrittenCommitsMap[curRev->m_CommitHash] = head;
1591 for (const auto& hash : m_forRewrite)
1592 m_rewrittenCommitsMap[hash] = head;
1593 m_forRewrite.clear();
1594 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1595 this->UpdateCurrentStatus();
1598 if (m_RebaseStage == RebaseStage::Edit || m_RebaseStage == RebaseStage::Squash_Edit)
1600 CString str;
1601 GitRevLoglist* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1603 str=this->m_LogMessageCtrl.GetText();
1604 if(str.Trim().IsEmpty())
1606 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_COMMITMESSAGE_EMPTY,IDS_APPNAME, MB_OK | MB_ICONERROR);
1607 return;
1610 CString tempfile=::GetTempFile();
1611 if (tempfile.IsEmpty())
1613 MessageBox(L"Could not create temp file.", L"TortoiseGit", MB_OK | MB_ICONERROR);
1614 return;
1616 SCOPE_EXIT{ ::DeleteFile(tempfile); };
1617 if (CAppUtils::SaveCommitUnicodeFile(tempfile, str))
1619 CMessageBox::Show(GetSafeHwnd(), L"Could not save commit message", L"TortoiseGit", MB_OK | MB_ICONERROR);
1620 return;
1623 CString out, cmd, options;
1624 bool skipCurrent = false;
1625 if (m_CurrentCommitEmpty)
1626 options = L"--allow-empty ";
1627 else if (g_Git.IsResultingCommitBecomeEmpty(m_RebaseStage != RebaseStage::Squash_Edit) == TRUE)
1629 const int choose = CMessageBox::ShowCheck(GetSafeHwnd(), IDS_CHERRYPICK_EMPTY, IDS_APPNAME, 1, IDI_QUESTION, IDS_COMMIT_COMMIT, IDS_SKIPBUTTON, IDS_MSGBOX_CANCEL, nullptr, 0);
1630 if (choose == 2)
1631 skipCurrent = true;
1632 else if (choose == 1)
1634 options = L"--allow-empty ";
1635 m_CurrentCommitEmpty = true;
1637 else
1638 return;
1641 if (m_RebaseStage == RebaseStage::Squash_Edit)
1642 cmd.Format(L"git.exe commit %s%s-F \"%s\"", static_cast<LPCWSTR>(options), static_cast<LPCWSTR>(m_SquashFirstMetaData.GetAsParam(m_iSquashdate == 2)), static_cast<LPCWSTR>(tempfile));
1643 else
1644 cmd.Format(L"git.exe commit --amend %s-F \"%s\"", static_cast<LPCWSTR>(options), static_cast<LPCWSTR>(tempfile));
1646 if (!skipCurrent && g_Git.Run(cmd, &out, CP_UTF8))
1648 if (!g_Git.CheckCleanWorkTree())
1650 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK | MB_ICONERROR);
1651 return;
1654 CString retry;
1655 retry.LoadString(IDS_MSGBOX_RETRY);
1656 CString ignore;
1657 ignore.LoadString(IDS_MSGBOX_IGNORE);
1658 if (CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", 1, IDI_ERROR, retry, ignore) == 1)
1659 return;
1662 AddLogString(out);
1663 if (CheckNextCommitIsSquash() == 0 && m_RebaseStage != RebaseStage::Squash_Edit) // remember commit msg after edit if next commit if squash; but don't do this if ...->squash(reset here)->pick->squash
1665 ResetParentForSquash(str);
1667 else
1668 m_SquashMessage.Empty();
1669 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1670 m_RebaseStage = RebaseStage::Continue;
1671 CGitHash head;
1672 if (g_Git.GetHash(head, L"HEAD"))
1674 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
1675 return;
1677 m_rewrittenCommitsMap[curRev->m_CommitHash] = head; // we had a reset to parent, so this is not the correct hash
1678 for (const auto& hash : m_forRewrite)
1679 m_rewrittenCommitsMap[hash] = head;
1680 m_forRewrite.clear();
1681 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
1682 this->UpdateCurrentStatus();
1686 InterlockedExchange(&m_bThreadRunning, TRUE);
1687 SetControlEnable();
1689 if (!AfxBeginThread(RebaseThreadEntry, this))
1691 InterlockedExchange(&m_bThreadRunning, FALSE);
1692 CMessageBox::Show(GetSafeHwnd(), L"Create Rebase Thread Fail", L"TortoiseGit", MB_OK | MB_ICONERROR);
1693 SetControlEnable();
1697 void CRebaseDlg::ResetParentForSquash(const CString& commitMessage)
1699 m_SquashMessage = commitMessage;
1700 // reset parent so that we can do "git cherry-pick --no-commit" w/o introducing an unwanted commit
1701 CString cmd = L"git.exe reset --soft HEAD~1 --";
1702 m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
1703 if (RunGitCmdRetryOrAbort(cmd))
1704 return;
1707 int CRebaseDlg::CheckNextCommitIsSquash()
1709 int index;
1710 if(m_CommitList.m_IsOldFirst)
1711 index=m_CurrentRebaseIndex+1;
1712 else
1713 index=m_CurrentRebaseIndex-1;
1715 GitRevLoglist* curRev;
1718 if(index<0)
1719 return -1;
1720 if(index>= m_CommitList.GetItemCount())
1721 return -1;
1723 curRev = m_CommitList.m_arShownList.SafeGetAt(index);
1725 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
1726 return 0;
1727 if (curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SKIP)
1729 if(m_CommitList.m_IsOldFirst)
1730 ++index;
1731 else
1732 --index;
1734 else
1735 return -1;
1737 } while(curRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_SKIP);
1739 return -1;
1742 int CRebaseDlg::GoNext()
1744 if(m_CommitList.m_IsOldFirst)
1745 ++m_CurrentRebaseIndex;
1746 else
1747 --m_CurrentRebaseIndex;
1748 return 0;
1751 void CRebaseDlg::SetContinueButtonText()
1753 CString Text;
1754 switch(this->m_RebaseStage)
1756 case RebaseStage::Choose_Branch:
1757 case RebaseStage::Choose_Commit_Pick_Mode:
1758 if (m_IsFastForward)
1759 Text.LoadString(IDS_PROC_STARTREBASEFFBUTTON);
1760 else
1761 Text.LoadString(IDS_PROC_STARTREBASEBUTTON);
1762 break;
1764 case RebaseStage::Start:
1765 case RebaseStage::Error:
1766 case RebaseStage::Continue:
1767 case RebaseStage::Squash_Conclict:
1768 Text.LoadString(IDS_CONTINUEBUTTON);
1769 break;
1771 case RebaseStage::Conclict:
1772 Text.LoadString(IDS_COMMITBUTTON);
1773 break;
1774 case RebaseStage::Edit:
1775 Text.LoadString(IDS_AMENDBUTTON);
1776 break;
1778 case RebaseStage::Squash_Edit:
1779 Text.LoadString(IDS_COMMITBUTTON);
1780 break;
1782 case RebaseStage::Abort:
1783 case RebaseStage::Finish:
1784 Text.LoadString(IDS_FINISHBUTTON);
1785 break;
1787 case RebaseStage::Done:
1788 Text.LoadString(IDS_DONE);
1789 break;
1791 this->GetDlgItem(IDC_REBASE_CONTINUE)->SetWindowText(Text);
1794 void CRebaseDlg::SetControlEnable()
1796 switch(this->m_RebaseStage)
1798 case RebaseStage::Choose_Branch:
1799 case RebaseStage::Choose_Commit_Pick_Mode:
1801 this->GetDlgItem(IDC_SPLITALLOPTIONS)->EnableWindow(TRUE);
1802 this->GetDlgItem(IDC_BUTTON_UP)->EnableWindow(TRUE);
1803 this->GetDlgItem(IDC_BUTTON_DOWN)->EnableWindow(TRUE);
1804 this->GetDlgItem(IDC_BUTTON_ADD)->EnableWindow(!m_bPreserveMerges);
1805 m_CommitList.EnableDragnDrop(true);
1807 if(!m_IsCherryPick)
1809 this->GetDlgItem(IDC_REBASE_COMBOXEX_BRANCH)->EnableWindow(TRUE);
1810 this->GetDlgItem(IDC_REBASE_COMBOXEX_UPSTREAM)->EnableWindow(TRUE);
1811 this->GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(TRUE);
1812 this->GetDlgItem(IDC_REBASE_CHECK_FORCE)->EnableWindow(TRUE);
1813 this->GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->EnableWindow(TRUE);
1815 this->m_CommitList.m_ContextMenuMask |= m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_PICK)|
1816 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_SQUASH)|
1817 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_EDIT)|
1818 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_REBASE_SKIP)|
1819 m_CommitList.GetContextMenuBit(CGitLogListBase::ID_LOG);
1820 break;
1822 case RebaseStage::Start:
1823 case RebaseStage::Continue:
1824 case RebaseStage::Abort:
1825 case RebaseStage::Error:
1826 case RebaseStage::Finish:
1827 case RebaseStage::Conclict:
1828 case RebaseStage::Edit:
1829 case RebaseStage::Squash_Conclict:
1830 case RebaseStage::Done:
1831 this->GetDlgItem(IDC_SPLITALLOPTIONS)->EnableWindow(FALSE);
1832 this->GetDlgItem(IDC_REBASE_COMBOXEX_BRANCH)->EnableWindow(FALSE);
1833 this->GetDlgItem(IDC_REBASE_COMBOXEX_UPSTREAM)->EnableWindow(FALSE);
1834 this->GetDlgItem(IDC_BUTTON_REVERSE)->EnableWindow(FALSE);
1835 this->GetDlgItem(IDC_REBASE_CHECK_FORCE)->EnableWindow(FALSE);
1836 this->GetDlgItem(IDC_REBASE_CHECK_PRESERVEMERGES)->EnableWindow(FALSE);
1837 this->GetDlgItem(IDC_BUTTON_UP)->EnableWindow(FALSE);
1838 this->GetDlgItem(IDC_BUTTON_DOWN)->EnableWindow(FALSE);
1839 m_CommitList.EnableDragnDrop(false);
1840 this->GetDlgItem(IDC_BUTTON_ADD)->EnableWindow(FALSE);
1841 this->GetDlgItem(IDC_BUTTON_ONTO)->EnableWindow(FALSE);
1842 this->GetDlgItem(IDC_BUTTON_BROWSE)->EnableWindow(FALSE);
1844 if (m_RebaseStage == RebaseStage::Done && (this->m_PostButtonTexts.GetCount() != 0))
1846 this->GetDlgItem(IDC_STATUS_STATIC)->ShowWindow(SW_HIDE);
1847 GetDlgItem(IDC_REBASE_POST_BUTTON)->EnableWindow(TRUE);
1848 this->GetDlgItem(IDC_REBASE_POST_BUTTON)->ShowWindow(SW_SHOWNORMAL);
1849 this->m_PostButton.RemoveAll();
1850 this->m_PostButton.AddEntries(m_PostButtonTexts);
1851 //this->GetDlgItem(IDC_REBASE_POST_BUTTON)->SetWindowText(this->m_PostButtonText);
1853 break;
1856 const bool canSplitCommit = m_RebaseStage == RebaseStage::Edit || m_RebaseStage == RebaseStage::Squash_Edit;
1857 GetDlgItem(IDC_REBASE_SPLIT_COMMIT)->ShowWindow(canSplitCommit ? SW_SHOW : SW_HIDE);
1858 GetDlgItem(IDC_REBASE_SPLIT_COMMIT)->EnableWindow(canSplitCommit);
1860 if(m_bThreadRunning)
1862 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(FALSE);
1865 else if (m_RebaseStage != RebaseStage::Error)
1867 this->GetDlgItem(IDC_REBASE_CONTINUE)->EnableWindow(TRUE);
1871 void CRebaseDlg::UpdateProgress()
1873 int index;
1874 if(m_CommitList.m_IsOldFirst)
1875 index = m_CurrentRebaseIndex+1;
1876 else
1877 index = m_CommitList.GetItemCount()-m_CurrentRebaseIndex;
1879 int finishedCommits = index - 1; // introduced an variable which shows the number handled revisions for the progress bars
1880 if (m_RebaseStage == RebaseStage::Finish || finishedCommits == -1)
1881 finishedCommits = index;
1883 m_ProgressBar.SetRange32(0, m_CommitList.GetItemCount());
1884 m_ProgressBar.SetPos(finishedCommits);
1885 if (m_pTaskbarList)
1887 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
1888 m_pTaskbarList->SetProgressValue(m_hWnd, finishedCommits, m_CommitList.GetItemCount());
1891 if(m_CurrentRebaseIndex>=0 && m_CurrentRebaseIndex< m_CommitList.GetItemCount())
1893 CString text;
1894 text.FormatMessage(IDS_PROC_REBASING_PROGRESS, index, m_CommitList.GetItemCount());
1895 m_sStatusText = text;
1896 m_CtrlStatusText.SetWindowText(text);
1897 m_bStatusWarning = false;
1898 m_CtrlStatusText.Invalidate();
1901 GitRevLoglist* prevRev = nullptr, *curRev = nullptr;
1903 if (m_CurrentRebaseIndex >= 0 && m_CurrentRebaseIndex < static_cast<int>(m_CommitList.m_arShownList.size()))
1904 curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1906 for (int i = 0; i < static_cast<int>(m_CommitList.m_arShownList.size()); ++i)
1908 prevRev = m_CommitList.m_arShownList.SafeGetAt(i);
1909 if (prevRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_CURRENT)
1911 CRect rect;
1912 prevRev->GetRebaseAction() &= ~CGitLogListBase::LOGACTIONS_REBASE_CURRENT;
1913 m_CommitList.GetItemRect(i,&rect,LVIR_BOUNDS);
1914 m_CommitList.InvalidateRect(rect);
1918 if(curRev)
1920 CRect rect;
1921 curRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_CURRENT;
1922 m_CommitList.GetItemRect(m_CurrentRebaseIndex,&rect,LVIR_BOUNDS);
1923 m_CommitList.InvalidateRect(rect);
1925 m_CommitList.EnsureVisible(m_CurrentRebaseIndex,FALSE);
1928 void CRebaseDlg::UpdateCurrentStatus()
1930 SetContinueButtonText();
1931 SetControlEnable();
1932 UpdateProgress();
1933 if (m_RebaseStage == RebaseStage::Done)
1934 GetDlgItem(IDC_REBASE_CONTINUE)->SetFocus();
1937 void CRebaseDlg::AddLogString(const CString& str)
1939 this->m_wndOutputRebase.SendMessage(SCI_SETREADONLY, FALSE);
1940 CStringA sTextA = m_wndOutputRebase.StringForControl(str);//CUnicodeUtils::GetUTF8(str);
1941 this->m_wndOutputRebase.SendMessage(SCI_DOCUMENTEND);
1942 this->m_wndOutputRebase.SendMessage(SCI_REPLACESEL, 0, reinterpret_cast<LPARAM>(static_cast<LPCSTR>(sTextA)));
1943 this->m_wndOutputRebase.SendMessage(SCI_REPLACESEL, 0, reinterpret_cast<LPARAM>("\n"));
1944 this->m_wndOutputRebase.SendMessage(SCI_SETREADONLY, TRUE);
1947 int CRebaseDlg::GetCurrentCommitID()
1949 if(m_CommitList.m_IsOldFirst)
1950 return this->m_CurrentRebaseIndex+1;
1951 else
1952 return m_CommitList.GetItemCount()-m_CurrentRebaseIndex;
1955 int CRebaseDlg::IsCommitEmpty(const CGitHash& hash)
1957 CString cmd, tree, ptree;
1958 cmd.Format(L"git.exe rev-parse -q --verify %s^{tree}", static_cast<LPCWSTR>(hash.ToString()));
1959 if (g_Git.Run(cmd, &tree, CP_UTF8))
1961 AddLogString(cmd);
1962 AddLogString(tree);
1963 return -1;
1965 cmd.Format(L"git.exe rev-parse -q --verify %s^^{tree}", static_cast<LPCWSTR>(hash.ToString()));
1966 if (g_Git.Run(cmd, &ptree, CP_UTF8))
1967 ptree = L"4b825dc642cb6eb9a060e54bf8d69288fbee4904"; // empty tree
1968 return tree == ptree;
1971 static CString GetCommitTitle(const CGitHash& parentHash)
1973 CString str;
1974 GitRev rev;
1975 if (rev.GetCommit(parentHash.ToString()) == 0)
1977 CString commitTitle = rev.GetSubject();
1978 if (commitTitle.GetLength() > 20)
1980 commitTitle.Truncate(20);
1981 commitTitle += L"...";
1983 str.AppendFormat(L"\n%s (%s)", static_cast<LPCWSTR>(CStringUtils::EscapeAccellerators(commitTitle)), static_cast<LPCWSTR>(parentHash.ToString(g_Git.GetShortHASHLength())));
1985 else
1986 str.AppendFormat(L"\n(%s)", static_cast<LPCWSTR>(parentHash.ToString(g_Git.GetShortHASHLength())));
1987 return str;
1990 int CRebaseDlg::DoRebase()
1992 CString cmd,out;
1993 if(m_CurrentRebaseIndex <0)
1994 return 0;
1995 if(m_CurrentRebaseIndex >= m_CommitList.GetItemCount() )
1996 return 0;
1998 GitRevLoglist* pRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
1999 int mode = pRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_MODE_MASK;
2000 CString nocommit;
2002 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SKIP)
2004 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
2005 return 0;
2008 const bool nextCommitIsSquash = (CheckNextCommitIsSquash() == 0);
2009 if (nextCommitIsSquash || mode != CGitLogListBase::LOGACTIONS_REBASE_PICK)
2010 { // next commit is squash or not pick
2011 if (!this->m_SquashMessage.IsEmpty())
2012 this->m_SquashMessage += L"\n\n";
2013 this->m_SquashMessage += pRev->GetSubject();
2014 this->m_SquashMessage += L'\n';
2015 this->m_SquashMessage += pRev->GetBody().TrimRight();
2016 if (m_bAddCherryPickedFrom)
2018 if (!pRev->GetBody().IsEmpty())
2019 m_SquashMessage += L'\n';
2020 m_SquashMessage += L"(cherry picked from commit ";
2021 m_SquashMessage += pRev->m_CommitHash.ToString();
2022 m_SquashMessage += L')';
2025 else
2027 this->m_SquashMessage.Empty();
2028 m_SquashFirstMetaData.Empty();
2031 if (nextCommitIsSquash && mode != CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2032 m_SquashFirstMetaData = SquashFirstMetaData(pRev);
2034 if ((nextCommitIsSquash && mode != CGitLogListBase::LOGACTIONS_REBASE_EDIT) || mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2035 { // next or this commit is squash (don't do this on edit->squash sequence)
2036 nocommit = L" --no-commit ";
2037 if (m_iSquashdate == 1)
2038 m_SquashFirstMetaData.UpdateDate(pRev);
2041 CString log;
2042 log.Format(L"%s %d: %s", static_cast<LPCWSTR>(CGitLogListBase::GetRebaseActionName(mode)), GetCurrentCommitID(), static_cast<LPCWSTR>(pRev->m_CommitHash.ToString()));
2043 AddLogString(log);
2044 AddLogString(pRev->GetSubject());
2045 if (pRev->GetSubject().IsEmpty())
2047 CMessageBox::Show(m_hWnd, IDS_PROC_REBASE_EMPTYCOMMITMSG, IDS_APPNAME, MB_OK | MB_ICONEXCLAMATION);
2048 mode = CGitLogListBase::LOGACTIONS_REBASE_EDIT;
2051 CString cherryPickedFrom;
2052 if (m_bAddCherryPickedFrom)
2053 cherryPickedFrom = L"-x ";
2054 else if (!m_IsCherryPick && nocommit.IsEmpty())
2055 cherryPickedFrom = L"--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."
2057 const int isEmpty = IsCommitEmpty(pRev->m_CommitHash);
2058 if (isEmpty == 1)
2060 cherryPickedFrom += L"--allow-empty ";
2061 if (mode != CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2062 m_CurrentCommitEmpty = true;
2064 else if (isEmpty < 0)
2065 return -1;
2066 else
2067 m_CurrentCommitEmpty = false;
2069 if (m_IsCherryPick && pRev->m_ParentHash.size() > 1)
2071 CString msg;
2072 msg.FormatMessage(IDS_CHERRYPICK_MERGECOMMIT, static_cast<LPCWSTR>(pRev->m_CommitHash.ToString()), static_cast<LPCWSTR>(pRev->GetSubject()));
2073 CString parent1;
2074 parent1.Format(IDS_PARENT, 1);
2075 parent1 += GetCommitTitle(pRev->m_ParentHash.at(0));
2076 CString parent2;
2077 parent2.Format(IDS_PARENT, 2);
2078 parent2 += GetCommitTitle(pRev->m_ParentHash.at(1));
2079 CString cancel;
2080 cancel.LoadString(IDS_MSGBOX_CANCEL);
2081 auto ret = CMessageBox::Show(m_hWnd, msg, L"TortoiseGit", 3, IDI_QUESTION, parent1, parent2, cancel);
2082 if (ret == 3)
2083 return - 1;
2085 cherryPickedFrom.AppendFormat(L"-m %d ", ret);
2088 while (true)
2090 cmd.Format(L"git.exe cherry-pick %s%s %s", static_cast<LPCWSTR>(cherryPickedFrom), static_cast<LPCWSTR>(nocommit), static_cast<LPCWSTR>(pRev->m_CommitHash.ToString()));
2091 if (m_bPreserveMerges)
2093 bool parentRewritten = false;
2094 CGitHash currentHeadHash;
2095 if (g_Git.GetHash(currentHeadHash, L"HEAD"))
2097 m_RebaseStage = RebaseStage::Error;
2098 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2099 return -1;
2101 if (!m_currentCommits.empty())
2103 for (const auto& commit : m_currentCommits)
2104 m_rewrittenCommitsMap[commit] = currentHeadHash;
2105 m_currentCommits.clear();
2107 m_currentCommits.push_back(pRev->m_CommitHash);
2108 GIT_REV_LIST possibleParents = pRev->m_ParentHash;
2109 GIT_REV_LIST newParents;
2110 for (auto it = possibleParents.cbegin(); it != possibleParents.cend(); it = possibleParents.begin())
2112 CGitHash parent = *it;
2113 possibleParents.erase(it);
2115 const auto rewrittenParent = m_rewrittenCommitsMap.find(parent);
2116 if (rewrittenParent == m_rewrittenCommitsMap.cend())
2118 auto droppedCommitParents = m_droppedCommitsMap.find(parent);
2119 if (droppedCommitParents != m_droppedCommitsMap.cend())
2121 parentRewritten = true;
2122 for (auto droppedIt = droppedCommitParents->second.crbegin(); droppedIt != droppedCommitParents->second.crend(); ++droppedIt)
2123 possibleParents.insert(possibleParents.begin(), *droppedIt);
2124 continue;
2127 newParents.push_back(parent);
2128 continue;
2131 if (rewrittenParent->second.IsEmpty() && parent == pRev->m_ParentHash[0] && pRev->ParentsCount() > 1)
2133 m_RebaseStage = RebaseStage::Error;
2134 AddLogString(L"");
2135 AddLogString(L"Unrecoverable error: Merge commit parent missing.");
2136 return -1;
2139 CGitHash newParent = rewrittenParent->second;
2140 if (newParent.IsEmpty()) // use current HEAD as fallback
2141 newParent = currentHeadHash;
2143 if (newParent != parent)
2144 parentRewritten = true;
2146 if (std::find(newParents.begin(), newParents.end(), newParent) == newParents.end())
2147 newParents.push_back(newParent);
2149 if (pRev->ParentsCount() > 1)
2151 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2153 m_RebaseStage = RebaseStage::Error;
2154 AddLogString(L"Cannot squash merge commit on rebase.");
2155 return -1;
2157 if (!parentRewritten && nocommit.IsEmpty())
2158 cmd.Format(L"git.exe reset --hard %s --", static_cast<LPCWSTR>(pRev->m_CommitHash.ToString()));
2159 else
2161 CString parentString;
2162 for (const auto& parent : newParents)
2163 parentString += L' ' + parent.ToString();
2164 cmd.Format(L"git.exe checkout %s --", static_cast<LPCWSTR>(newParents[0].ToString()));
2165 if (RunGitCmdRetryOrAbort(cmd))
2167 m_RebaseStage = RebaseStage::Error;
2168 return -1;
2170 cmd.Format(L"git.exe merge --no-ff%s -- %s", static_cast<LPCWSTR>(nocommit), static_cast<LPCWSTR>(parentString));
2171 if (nocommit.IsEmpty())
2173 if (g_Git.Run(cmd, &out, CP_UTF8))
2175 AddLogString(cmd);
2176 AddLogString(out);
2177 const int hasConflicts = g_Git.HasWorkingTreeConflicts();
2178 if (hasConflicts > 0)
2180 m_RebaseStage = RebaseStage::Conclict;
2181 return -1;
2183 else if (hasConflicts < 0)
2184 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
2185 AddLogString(L"An unrecoverable error occurred.");
2186 m_RebaseStage = RebaseStage::Error;
2187 return -1;
2189 CGitHash newHeadHash;
2190 if (g_Git.GetHash(newHeadHash, L"HEAD"))
2192 m_RebaseStage = RebaseStage::Error;
2193 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2194 return -1;
2196 // do nothing if already up2date
2197 if (currentHeadHash != newHeadHash)
2198 cmd.Format(L"git.exe commit --amend -C %s", static_cast<LPCWSTR>(pRev->m_CommitHash.ToString()));
2202 else
2204 if (mode != CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2206 cmd.Format(L"git.exe checkout %s --", static_cast<LPCWSTR>(newParents[0].ToString()));
2207 if (RunGitCmdRetryOrAbort(cmd))
2209 m_RebaseStage = RebaseStage::Error;
2210 return -1;
2213 cmd.Format(L"git.exe cherry-pick %s%s %s", static_cast<LPCWSTR>(cherryPickedFrom), static_cast<LPCWSTR>(nocommit), static_cast<LPCWSTR>(pRev->m_CommitHash.ToString()));
2217 if(g_Git.Run(cmd,&out,CP_UTF8))
2219 AddLogString(out);
2220 const int hasConflicts = g_Git.HasWorkingTreeConflicts();
2221 if (hasConflicts < 0)
2223 AddLogString(g_Git.GetGitLastErr(L"Checking for conflicts failed.", CGit::GIT_CMD_CHECKCONFLICTS));
2224 return -1;
2226 if (!hasConflicts)
2228 if (out.Find(L"commit --allow-empty") > 0)
2230 const int choose = CMessageBox::ShowCheck(GetSafeHwnd(), IDS_CHERRYPICK_EMPTY, IDS_APPNAME, 1, IDI_QUESTION, IDS_COMMIT_COMMIT, IDS_SKIPBUTTON, IDS_MSGBOX_CANCEL, nullptr, 0);
2231 if (choose != 1)
2233 if (choose == 2 && !RunGitCmdRetryOrAbort(L"git.exe reset --hard"))
2235 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
2236 m_CommitList.Invalidate();
2237 return 0;
2240 m_RebaseStage = RebaseStage::Error;
2241 AddLogString(L"An unrecoverable error occurred.");
2242 return -1;
2245 cmd.Format(L"git.exe commit --allow-empty -C %s", static_cast<LPCWSTR>(pRev->m_CommitHash.ToString()));
2246 out.Empty();
2247 g_Git.Run(cmd, &out, CP_UTF8);
2248 m_CurrentCommitEmpty = true;
2250 else if (mode == CGitLogListBase::LOGACTIONS_REBASE_PICK)
2252 if (m_pTaskbarList)
2253 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
2254 int choose = -1;
2255 if (!m_bAutoSkipFailedCommit)
2257 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);
2258 if (choose == 2)
2260 m_bAutoSkipFailedCommit = FALSE;
2261 continue; // retry cherry pick
2264 if (m_bAutoSkipFailedCommit || choose == 1)
2266 if (!RunGitCmdRetryOrAbort(L"git.exe reset --hard"))
2268 pRev->GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_SKIP;
2269 m_CommitList.Invalidate();
2270 return 0;
2274 m_RebaseStage = RebaseStage::Error;
2275 AddLogString(L"An unrecoverable error occurred.");
2276 return -1;
2278 else if (mode == CGitLogListBase::LOGACTIONS_REBASE_EDIT)
2280 this->m_RebaseStage = RebaseStage::Edit;
2281 return -1; // Edit return -1 to stop rebase.
2283 // Squash Case
2284 else if (CheckNextCommitIsSquash())
2285 { // no squash
2286 // let user edit last commmit message
2287 this->m_RebaseStage = RebaseStage::Squash_Edit;
2288 return -1;
2291 else
2293 if (m_pTaskbarList)
2294 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
2295 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2296 m_RebaseStage = RebaseStage::Squash_Conclict;
2297 else
2298 m_RebaseStage = RebaseStage::Conclict;
2299 return -1;
2303 AddLogString(out);
2304 if (mode == CGitLogListBase::LOGACTIONS_REBASE_PICK)
2306 if (nocommit.IsEmpty())
2308 CGitHash head;
2309 if (g_Git.GetHash(head, L"HEAD"))
2311 MessageBox(g_Git.GetGitLastErr(L"Could not get HEAD hash."), L"TortoiseGit", MB_ICONERROR);
2312 m_RebaseStage = RebaseStage::Error;
2313 return -1;
2315 m_rewrittenCommitsMap[pRev->m_CommitHash] = head;
2317 else
2318 m_forRewrite.push_back(pRev->m_CommitHash);
2319 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
2320 return 0;
2322 if (mode == CGitLogListBase::LOGACTIONS_REBASE_EDIT)
2324 this->m_RebaseStage = RebaseStage::Edit;
2325 return -1; // Edit return -1 to stop rebase.
2328 // Squash Case
2329 if (CheckNextCommitIsSquash())
2330 { // no squash
2331 // let user edit last commmit message
2332 this->m_RebaseStage = RebaseStage::Squash_Edit;
2333 return -1;
2335 else if (mode == CGitLogListBase::LOGACTIONS_REBASE_SQUASH)
2337 pRev->GetRebaseAction() |= CGitLogListBase::LOGACTIONS_REBASE_DONE;
2338 m_forRewrite.push_back(pRev->m_CommitHash);
2341 return 0;
2345 BOOL CRebaseDlg::IsEnd()
2347 if(m_CommitList.m_IsOldFirst)
2348 return m_CurrentRebaseIndex>= this->m_CommitList.GetItemCount();
2349 else
2350 return m_CurrentRebaseIndex<0;
2353 int CRebaseDlg::RebaseThread()
2355 CBlockCacheForPath cacheBlock(g_Git.m_CurrentDir);
2357 int ret=0;
2358 while (!m_bAbort)
2360 if (m_RebaseStage == RebaseStage::Start)
2362 if( this->StartRebase() )
2364 ret = -1;
2365 break;
2367 m_RebaseStage = RebaseStage::Continue;
2369 else if (m_RebaseStage == RebaseStage::Continue)
2371 this->GoNext();
2372 SendMessage(MSG_REBASE_UPDATE_UI);
2373 if(IsEnd())
2375 ret = 0;
2376 m_RebaseStage = RebaseStage::Finish;
2378 else
2380 ret = DoRebase();
2381 if( ret )
2382 break;
2385 else if (m_RebaseStage == RebaseStage::Finish)
2387 SendMessage(MSG_REBASE_UPDATE_UI);
2388 m_RebaseStage = RebaseStage::Done;
2389 break;
2391 else
2392 break;
2393 this->PostMessage(MSG_REBASE_UPDATE_UI);
2396 InterlockedExchange(&m_bThreadRunning, FALSE);
2397 this->PostMessage(MSG_REBASE_UPDATE_UI);
2398 if (m_bAbort)
2399 PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_ABORT, BN_CLICKED), reinterpret_cast<LPARAM>(GetDlgItem(IDC_REBASE_ABORT)->GetSafeHwnd()));
2400 return ret;
2403 void CRebaseDlg::ListConflictFile(bool noStoreScrollPosition)
2405 if (!noStoreScrollPosition)
2406 m_FileListCtrl.StoreScrollPos();
2407 this->m_FileListCtrl.Clear();
2408 m_FileListCtrl.SetHasCheckboxes(true);
2410 if (!m_IsCherryPick)
2412 CString adminDir;
2413 if (GitAdminDir::GetAdminDirPath(g_Git.m_CurrentDir, adminDir))
2415 CString dir(adminDir + L"tgitrebase.active");
2416 ::CreateDirectory(dir, nullptr);
2417 CStringUtils::WriteStringToTextFile(dir + L"\\head-name", m_BranchCtrl.GetString());
2418 CStringUtils::WriteStringToTextFile(dir + L"\\onto", m_Onto.IsEmpty() ? m_UpstreamCtrl.GetString() : m_Onto);
2422 this->m_FileListCtrl.GetStatus(nullptr, true);
2423 m_FileListCtrl.Show(CTGitPath::LOGACTIONS_UNMERGED | CTGitPath::LOGACTIONS_MODIFIED | CTGitPath::LOGACTIONS_ADDED | CTGitPath::LOGACTIONS_DELETED | CTGitPath::LOGACTIONS_REPLACED, CTGitPath::LOGACTIONS_UNMERGED);
2425 m_FileListCtrl.Check(GITSLC_SHOWFILES);
2426 bool hasSubmoduleChange = false;
2427 auto locker(m_FileListCtrl.AcquireReadLock());
2428 for (int i = 0; i < m_FileListCtrl.GetItemCount(); i++)
2430 auto entry = m_FileListCtrl.GetListEntry(i);
2431 if (entry->IsDirectory())
2433 hasSubmoduleChange = true;
2434 break;
2438 if (hasSubmoduleChange)
2440 m_CtrlStatusText.SetWindowText(m_sStatusText + L", " + CString(MAKEINTRESOURCE(IDS_CARE_SUBMODULE_CHANGES)));
2441 m_bStatusWarning = true;
2442 m_CtrlStatusText.Invalidate();
2444 else
2446 m_CtrlStatusText.SetWindowText(m_sStatusText);
2447 m_bStatusWarning = false;
2448 m_CtrlStatusText.Invalidate();
2452 LRESULT CRebaseDlg::OnRebaseUpdateUI(WPARAM,LPARAM)
2454 if (m_RebaseStage == RebaseStage::Finish)
2456 FinishRebase();
2457 return 0;
2459 UpdateCurrentStatus();
2461 if (m_RebaseStage == RebaseStage::Done && m_bRebaseAutoEnd)
2463 m_bRebaseAutoEnd = false;
2464 this->PostMessage(WM_COMMAND, MAKELONG(IDC_REBASE_CONTINUE, BN_CLICKED), reinterpret_cast<LPARAM>(GetDlgItem(IDC_REBASE_CONTINUE)->GetSafeHwnd()));
2467 if (m_RebaseStage == RebaseStage::Done && m_pTaskbarList)
2468 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS); // do not show progress on taskbar any more to show we finished
2469 if(m_CurrentRebaseIndex <0)
2470 return 0;
2471 if(m_CurrentRebaseIndex >= m_CommitList.GetItemCount() )
2472 return 0;
2473 GitRev* curRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
2475 switch(m_RebaseStage)
2477 case RebaseStage::Conclict:
2478 case RebaseStage::Squash_Conclict:
2480 ListConflictFile(true);
2481 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_CONFLICT);
2482 if (m_pTaskbarList)
2483 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
2484 this->m_LogMessageCtrl.SetReadOnly(false);
2485 CString logMessage;
2486 if (m_IsCherryPick)
2488 CString dotGitPath;
2489 GitAdminDir::GetWorktreeAdminDirPath(g_Git.m_CurrentDir, dotGitPath);
2490 // vanilla git also re-uses MERGE_MSG on conflict (listing all conflicted files)
2491 // and it's also needed for cherry-pick in order to get cherry-picked-from included on conflicts
2492 CGit::LoadTextFile(dotGitPath + L"MERGE_MSG", logMessage);
2494 if (logMessage.IsEmpty())
2495 logMessage = curRev->GetSubject() + L'\n' + curRev->GetBody();
2496 this->m_LogMessageCtrl.SetText(logMessage);
2497 m_LogMessageCtrl.ClearUndoBuffer();
2498 break;
2500 case RebaseStage::Edit:
2501 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
2502 if (m_pTaskbarList)
2503 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
2504 this->m_LogMessageCtrl.SetReadOnly(false);
2505 if (m_bAddCherryPickedFrom)
2507 // Since the new commit is done and the HEAD points to it,
2508 // just using the new body modified by git self.
2509 GitRev headRevision;
2510 if (headRevision.GetCommit(L"HEAD"))
2511 MessageBox(headRevision.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
2513 m_LogMessageCtrl.SetText(headRevision.GetSubject() + L'\n' + headRevision.GetBody());
2515 else
2516 m_LogMessageCtrl.SetText(curRev->GetSubject() + L'\n' + curRev->GetBody());
2517 m_LogMessageCtrl.ClearUndoBuffer();
2518 break;
2519 case RebaseStage::Squash_Edit:
2520 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_MESSAGE);
2521 this->m_LogMessageCtrl.SetReadOnly(false);
2522 this->m_LogMessageCtrl.SetText(this->m_SquashMessage);
2523 m_LogMessageCtrl.ClearUndoBuffer();
2524 if (m_pTaskbarList)
2525 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_PAUSED);
2526 break;
2527 default:
2528 this->m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
2530 return 0;
2533 void CRebaseDlg::OnCancel()
2535 OnBnClickedAbort();
2538 void CRebaseDlg::OnBnClickedAbort()
2540 if (m_bThreadRunning)
2542 if (CMessageBox::Show(GetSafeHwnd(), IDS_PROC_REBASE_ABORT, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) != IDYES)
2543 return;
2544 m_bAbort = TRUE;
2545 return;
2548 if (m_pTaskbarList)
2549 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
2551 m_tooltips.Pop();
2553 SaveSplitterPos();
2555 if (m_RebaseStage == RebaseStage::Choose_Branch || m_RebaseStage == RebaseStage::Choose_Commit_Pick_Mode)
2557 __super::OnCancel();
2558 goto end;
2561 if (m_OrigUpstreamHash.IsEmpty() || m_OrigHEADHash.IsEmpty())
2563 __super::OnCancel();
2564 goto end;
2567 if (!m_bAbort && CMessageBox::Show(GetSafeHwnd(), IDS_PROC_REBASE_ABORT, IDS_APPNAME, MB_YESNO | MB_ICONQUESTION) != IDYES)
2568 return;
2570 m_ctrlTabCtrl.SetActiveTab(REBASE_TAB_LOG);
2572 if (g_Git.m_IsUseLibGit2 && !m_IsCherryPick)
2574 CGitHash head;
2575 if (!g_Git.GetHash(head, L"HEAD"))
2576 WriteReflog(head, "rebase: begin aborting...");
2579 if (m_IsFastForward)
2581 CString cmd;
2582 cmd.Format(L"git.exe reset --hard %s --", static_cast<LPCWSTR>(this->m_OrigBranchHash.ToString()));
2583 RunGitCmdRetryOrAbort(cmd);
2584 __super::OnCancel();
2585 goto end;
2588 if (m_IsCherryPick) // there are not "branch" at cherry pick mode
2590 CString cmd;
2591 cmd.Format(L"git.exe reset --hard %s --", static_cast<LPCWSTR>(m_OrigUpstreamHash.ToString()));
2592 RunGitCmdRetryOrAbort(cmd);
2593 __super::OnCancel();
2594 goto end;
2597 if (m_OrigHEADBranch == m_BranchCtrl.GetString())
2599 CString cmd, out;
2600 if (g_Git.IsLocalBranch(m_OrigHEADBranch))
2601 cmd.Format(L"git.exe checkout -f -B %s %s --", static_cast<LPCWSTR>(m_BranchCtrl.GetString()), static_cast<LPCWSTR>(m_OrigBranchHash.ToString()));
2602 else
2603 cmd.Format(L"git.exe checkout -f %s --", static_cast<LPCWSTR>(m_OrigBranchHash.ToString()));
2604 if (g_Git.Run(cmd, &out, CP_UTF8))
2606 AddLogString(out);
2607 ::MessageBox(m_hWnd, L"Unrecoverable error on cleanup:\n" + out, L"TortoiseGit", MB_ICONERROR);
2608 __super::OnCancel();
2609 goto end;
2612 cmd.Format(L"git.exe reset --hard %s --", static_cast<LPCWSTR>(m_OrigBranchHash.ToString()));
2613 RunGitCmdRetryOrAbort(cmd);
2615 else
2617 CString cmd, out;
2618 if (m_OrigHEADBranch != g_Git.GetCurrentBranch(true))
2620 if (g_Git.IsLocalBranch(m_OrigHEADBranch))
2621 cmd.Format(L"git.exe checkout -f -B %s %s --", static_cast<LPCWSTR>(m_OrigHEADBranch), static_cast<LPCWSTR>(m_OrigHEADHash.ToString()));
2622 else
2623 cmd.Format(L"git.exe checkout -f %s --", static_cast<LPCWSTR>(m_OrigHEADHash.ToString()));
2624 if (g_Git.Run(cmd, &out, CP_UTF8))
2626 AddLogString(out);
2627 ::MessageBox(m_hWnd, L"Unrecoverable error on cleanup:\n" + out, L"TortoiseGit", MB_ICONERROR);
2628 // continue to restore moved branch
2632 cmd.Format(L"git.exe reset --hard %s --", static_cast<LPCWSTR>(m_OrigHEADHash.ToString()));
2633 RunGitCmdRetryOrAbort(cmd);
2635 // restore moved branch
2636 if (g_Git.IsLocalBranch(m_BranchCtrl.GetString()))
2638 cmd.Format(L"git.exe branch -f -- %s %s", static_cast<LPCWSTR>(m_BranchCtrl.GetString()), static_cast<LPCWSTR>(m_OrigBranchHash.ToString()));
2639 if (g_Git.Run(cmd, &out, CP_UTF8))
2641 AddLogString(out);
2642 ::MessageBox(m_hWnd, L"Unrecoverable error on cleanup:\n" + out, L"TortoiseGit", MB_ICONERROR);
2643 __super::OnCancel();
2644 goto end;
2648 if (g_Git.m_IsUseLibGit2)
2649 WriteReflog(m_OrigHEADHash, "rebase: aborted");
2650 __super::OnCancel();
2651 end:
2652 CleanUpRebaseActiveFolder();
2653 CheckRestoreStash();
2656 void CRebaseDlg::OnBnClickedButtonReverse()
2658 CString temp = m_BranchCtrl.GetString();
2659 m_BranchCtrl.AddString(m_UpstreamCtrl.GetString());
2660 m_UpstreamCtrl.AddString(temp);
2661 OnCbnSelchangeUpstream();
2664 void CRebaseDlg::OnBnClickedButtonBrowse()
2666 if (CBrowseRefsDlg::PickRefForCombo(m_UpstreamCtrl))
2667 OnCbnSelchangeUpstream();
2670 void CRebaseDlg::OnBnClickedRebaseCheckForce()
2672 this->UpdateData();
2673 GetDlgItem(IDC_BUTTON_ADD)->EnableWindow(!m_bPreserveMerges);
2674 this->FetchLogList();
2677 void CRebaseDlg::OnBnClickedRebasePostButton()
2679 CheckRestoreStash();
2681 this->m_Upstream=this->m_UpstreamCtrl.GetString();
2682 this->m_Branch=this->m_BranchCtrl.GetString();
2684 this->EndDialog(static_cast<int>(IDC_REBASE_POST_BUTTON + this->m_PostButton.GetCurrentEntry()));
2687 LRESULT CRebaseDlg::OnGitStatusListCtrlNeedsRefresh(WPARAM, LPARAM)
2689 Refresh();
2690 return 0;
2693 void CRebaseDlg::Refresh()
2695 if (m_RebaseStage == RebaseStage::Conclict || m_RebaseStage == RebaseStage::Squash_Conclict)
2697 ListConflictFile(false);
2698 return;
2701 if(this->m_IsCherryPick)
2702 return ;
2704 if (m_RebaseStage == RebaseStage::Choose_Branch)
2706 this->UpdateData();
2707 this->LoadBranchInfo();
2708 this->FetchLogList();
2712 void CRebaseDlg::OnBnClickedButtonUp()
2714 POSITION pos;
2715 pos = m_CommitList.GetFirstSelectedItemPosition();
2717 const bool moveToTop = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
2718 // do nothing if the first selected item is the first item in the list
2719 if (!moveToTop && m_CommitList.GetNextSelectedItem(pos) == 0)
2720 return;
2722 pos = m_CommitList.GetFirstSelectedItemPosition();
2724 int count = 0;
2725 bool changed = false;
2726 while(pos)
2728 int index = m_CommitList.GetNextSelectedItem(pos);
2729 count = moveToTop ? count : (index - 1);
2730 while (index > count)
2732 std::swap(m_CommitList.m_logEntries[index], m_CommitList.m_logEntries[index - 1]);
2733 std::swap(m_CommitList.m_arShownList[index], m_CommitList.m_arShownList[index - 1]);
2734 m_CommitList.SetItemState(index - 1, LVIS_SELECTED, LVIS_SELECTED);
2735 m_CommitList.SetItemState(index, 0, LVIS_SELECTED);
2736 changed = true;
2737 index--;
2739 count++;
2741 if (changed)
2743 pos = m_CommitList.GetFirstSelectedItemPosition();
2744 m_CommitList.EnsureVisible(m_CommitList.GetNextSelectedItem(pos), false);
2745 m_CommitList.Invalidate();
2746 m_CommitList.SetFocus();
2750 void CRebaseDlg::OnBnClickedButtonDown()
2752 if (m_CommitList.GetSelectedCount() == 0)
2753 return;
2755 const bool moveToBottom = !!(GetAsyncKeyState(VK_SHIFT) & 0x8000);
2756 POSITION pos;
2757 pos = m_CommitList.GetFirstSelectedItemPosition();
2758 bool changed = false;
2759 // use an array to store all selected item indexes; the user won't select too much items
2760 auto indexes = std::make_unique<int[]>(m_CommitList.GetSelectedCount());
2761 int i = 0;
2762 while(pos)
2763 indexes[i++] = m_CommitList.GetNextSelectedItem(pos);
2764 // don't move any item if the last selected item is the last item in the m_CommitList
2765 // (that would change the order of the selected items)
2766 if (!moveToBottom && indexes[m_CommitList.GetSelectedCount() - 1] >= m_CommitList.GetItemCount() - 1)
2767 return;
2768 int count = m_CommitList.GetItemCount() - 1;
2769 // iterate over the indexes backwards in order to correctly move multiselected items
2770 for (i = m_CommitList.GetSelectedCount() - 1; i >= 0; i--)
2772 int index = indexes[i];
2773 count = moveToBottom ? count : (index + 1);
2774 while (index < count)
2776 std::swap(m_CommitList.m_logEntries[index], m_CommitList.m_logEntries[index + 1]);
2777 std::swap(m_CommitList.m_arShownList[index], m_CommitList.m_arShownList[index + 1]);
2778 m_CommitList.SetItemState(index, 0, LVIS_SELECTED);
2779 m_CommitList.SetItemState(index + 1, LVIS_SELECTED, LVIS_SELECTED);
2780 changed = true;
2781 index++;
2783 count--;
2785 m_CommitList.EnsureVisible(indexes[m_CommitList.GetSelectedCount() - 1] + 1, false);
2786 if (changed)
2788 m_CommitList.Invalidate();
2789 m_CommitList.SetFocus();
2793 LRESULT CRebaseDlg::OnCommitsReordered(WPARAM wParam, LPARAM /*lParam*/)
2795 POSITION pos = m_CommitList.GetFirstSelectedItemPosition();
2796 const int first = m_CommitList.GetNextSelectedItem(pos);
2797 int last = first;
2798 while (pos)
2799 last = m_CommitList.GetNextSelectedItem(pos);
2800 ++last;
2802 for (int i = first; i < last; ++i)
2803 m_CommitList.SetItemState(i, 0, LVIS_SELECTED);
2805 const int dest = static_cast<int>(wParam);
2806 if (dest > first)
2808 std::rotate(m_CommitList.m_logEntries.begin() + first, m_CommitList.m_logEntries.begin() + last, m_CommitList.m_logEntries.begin() + dest);
2809 std::rotate(m_CommitList.m_arShownList.begin() + first, m_CommitList.m_arShownList.begin() + last, m_CommitList.m_arShownList.begin() + dest);
2810 for (int i = first + dest - last; i < dest; ++i)
2811 m_CommitList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2813 else
2815 std::rotate(m_CommitList.m_logEntries.begin() + dest, m_CommitList.m_logEntries.begin() + first, m_CommitList.m_logEntries.begin() + last);
2816 std::rotate(m_CommitList.m_arShownList.begin() + dest, m_CommitList.m_arShownList.begin() + first, m_CommitList.m_arShownList.begin() + last);
2817 for (int i = dest; i < dest + (last - first); ++i)
2818 m_CommitList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
2821 m_CommitList.Invalidate();
2823 return 0;
2826 LRESULT CRebaseDlg::OnTaskbarBtnCreated(WPARAM wParam, LPARAM lParam)
2828 m_pTaskbarList.Release();
2829 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
2830 return __super::OnTaskbarButtonCreated(wParam, lParam);
2833 void CRebaseDlg::OnLvnItemchangedLoglist(NMHDR *pNMHDR, LRESULT *pResult)
2835 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
2836 *pResult = 0;
2837 if(m_CommitList.m_bNoDispUpdates)
2838 return;
2839 if (pNMLV->iItem >= 0)
2841 this->m_CommitList.m_nSearchIndex = pNMLV->iItem;
2842 if (pNMLV->iSubItem != 0)
2843 return;
2844 if (pNMLV->iItem == static_cast<int>(m_CommitList.m_arShownList.size()))
2846 // remove the selected state
2847 if (pNMLV->uChanged & LVIF_STATE)
2849 m_CommitList.SetItemState(pNMLV->iItem, 0, LVIS_SELECTED);
2850 FillLogMessageCtrl();
2852 return;
2854 if (pNMLV->uChanged & LVIF_STATE)
2855 FillLogMessageCtrl();
2857 else
2858 FillLogMessageCtrl();
2861 void CRebaseDlg::FillLogMessageCtrl()
2863 const int selCount = m_CommitList.GetSelectedCount();
2864 if (selCount == 1 && (m_RebaseStage == RebaseStage::Choose_Branch || m_RebaseStage == RebaseStage::Choose_Commit_Pick_Mode))
2866 POSITION pos = m_CommitList.GetFirstSelectedItemPosition();
2867 const int selIndex = m_CommitList.GetNextSelectedItem(pos);
2868 GitRevLoglist* pLogEntry = m_CommitList.m_arShownList.SafeGetAt(selIndex);
2869 OnRefreshFilelist();
2870 m_LogMessageCtrl.SetText(pLogEntry->GetSubject() + L'\n' + pLogEntry->GetBody());
2871 m_LogMessageCtrl.ClearUndoBuffer();
2875 void CRebaseDlg::OnRefreshFilelist()
2877 const int selCount = m_CommitList.GetSelectedCount();
2878 if (selCount == 1 && (m_RebaseStage == RebaseStage::Choose_Branch || m_RebaseStage == RebaseStage::Choose_Commit_Pick_Mode))
2880 POSITION pos = m_CommitList.GetFirstSelectedItemPosition();
2881 const int selIndex = m_CommitList.GetNextSelectedItem(pos);
2882 auto pLogEntry = m_CommitList.m_arShownList.SafeGetAt(selIndex);
2883 auto files = pLogEntry->GetFiles(&m_CommitList);
2884 if (!pLogEntry->m_IsDiffFiles)
2886 m_FileListCtrl.Clear();
2887 m_FileListCtrl.SetBusyString(CString(MAKEINTRESOURCE(IDS_PROC_LOG_FETCHINGFILES)));
2888 m_FileListCtrl.SetBusy(TRUE);
2889 m_FileListCtrl.SetRedraw(TRUE);
2890 return;
2892 m_FileListCtrl.UpdateWithGitPathList(const_cast<CTGitPathList&>(files.m_files));
2893 m_FileListCtrl.m_CurrentVersion = pLogEntry->m_CommitHash;
2894 m_FileListCtrl.Show(GITSLC_SHOWVERSIONED);
2898 void CRebaseDlg::OnBnClickedCheckCherryPickedFrom()
2900 UpdateData();
2901 auto reg = CRegDWORD(L"Software\\TortoiseGit\\TortoiseProc\\CherrypickAddCherryPickedFrom");
2902 reg = m_bAddCherryPickedFrom;
2905 LRESULT CRebaseDlg::OnRebaseActionMessage(WPARAM, LPARAM)
2907 if (m_RebaseStage == RebaseStage::Error || m_RebaseStage == RebaseStage::Conclict)
2909 GitRevLoglist* pRev = m_CommitList.m_arShownList.SafeGetAt(m_CurrentRebaseIndex);
2910 const int mode = pRev->GetRebaseAction() & CGitLogListBase::LOGACTIONS_REBASE_MODE_MASK;
2911 if (mode == CGitLogListBase::LOGACTIONS_REBASE_SKIP)
2913 if (!RunGitCmdRetryOrAbort(L"git.exe reset --hard"))
2915 m_FileListCtrl.Clear();
2916 m_RebaseStage = RebaseStage::Continue;
2917 UpdateCurrentStatus();
2921 return 0;
2925 void CRebaseDlg::OnBnClickedSplitAllOptions()
2927 switch (m_SplitAllOptions.GetCurrentEntry())
2929 case 0:
2930 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_PICK);
2931 break;
2932 case 1:
2933 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
2934 break;
2935 case 2:
2936 SetAllRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
2937 break;
2938 case 3:
2939 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SKIP);
2940 break;
2941 case 4:
2942 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_SQUASH);
2943 break;
2944 case 5:
2945 m_CommitList.SetUnselectedRebaseAction(CGitLogListBase::LOGACTIONS_REBASE_EDIT);
2946 break;
2947 default:
2948 ATLASSERT(false);
2952 void CRebaseDlg::OnBnClickedRebaseSplitCommit()
2954 UpdateData();
2957 static bool GetCompareHash(const CString& ref, const CGitHash& hash)
2959 CGitHash refHash;
2960 if (g_Git.GetHash(refHash, ref))
2961 MessageBox(nullptr, g_Git.GetGitLastErr(L"Could not get hash of \"" + ref + L"\"."), L"TortoiseGit", MB_ICONERROR);
2962 return refHash.IsEmpty() || (hash == refHash);
2965 void CRebaseDlg::OnBnClickedButtonOnto()
2967 m_Onto = CBrowseRefsDlg::PickRef(false, m_Onto);
2968 if (!m_Onto.IsEmpty())
2970 // make sure that the user did not select upstream or the selected branch
2971 CGitHash hash;
2972 if (g_Git.GetHash(hash, m_Onto))
2974 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of \"" + m_BranchCtrl.GetString() + L"\"."), L"TortoiseGit", MB_ICONERROR);
2975 m_Onto.Empty();
2976 static_cast<CButton*>(GetDlgItem(IDC_BUTTON_ONTO))->SetCheck(m_Onto.IsEmpty() ? BST_UNCHECKED : BST_CHECKED);
2977 return;
2979 if (GetCompareHash(m_UpstreamCtrl.GetString(), hash) || GetCompareHash(m_BranchCtrl.GetString(), hash))
2980 m_Onto.Empty();
2982 if (m_Onto.IsEmpty())
2983 m_tooltips.DelTool(IDC_BUTTON_ONTO);
2984 else
2985 m_tooltips.AddTool(IDC_BUTTON_ONTO, m_Onto);
2986 static_cast<CButton*>(GetDlgItem(IDC_BUTTON_ONTO))->SetCheck(m_Onto.IsEmpty() ? BST_UNCHECKED : BST_CHECKED);
2987 FetchLogList();
2990 void CRebaseDlg::OnHelp()
2992 HtmlHelp(0x20000 + (m_IsCherryPick ? IDD_REBASECHERRYPICK : IDD_REBASE));
2995 int CRebaseDlg::RunGitCmdRetryOrAbort(const CString& cmd)
2997 while (true)
2999 CString out;
3000 if (g_Git.Run(cmd, &out, CP_UTF8))
3002 AddLogString(cmd);
3003 AddLogString(CString(MAKEINTRESOURCE(IDS_FAIL)));
3004 AddLogString(out);
3005 CString msg;
3006 msg.Format(L"\"%s\" failed.\n%s", static_cast<LPCWSTR>(cmd), static_cast<LPCWSTR>(out));
3007 if (CMessageBox::Show(GetSafeHwnd(), msg, L"TortoiseGit", 1, IDI_ERROR, CString(MAKEINTRESOURCE(IDS_MSGBOX_RETRY)), CString(MAKEINTRESOURCE(IDS_MSGBOX_ABORT))) != 1)
3008 return -1;
3010 else
3011 return 0;
3015 void CRebaseDlg::SetTheme(bool bDark)
3017 __super::SetTheme(bDark);
3018 CMFCVisualManager::GetInstance()->DestroyInstance();
3019 if (bDark)
3021 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CThemeMFCVisualManager));
3022 m_ctrlTabCtrl.ModifyTabStyle(CMFCTabCtrl::STYLE_3D);
3024 else
3026 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
3027 m_ctrlTabCtrl.ModifyTabStyle(CMFCTabCtrl::STYLE_FLAT);
3029 CMFCVisualManager::RedrawAll();
3032 void CRebaseDlg::OnBnClickedButtonAdd()
3034 CLogDlg dlg;
3035 // tell the dialog to use mode for selecting revisions
3036 dlg.SetSelect(true);
3037 // allow multi-select
3038 dlg.SingleSelection(false);
3039 if (dlg.DoModal() != IDOK || dlg.GetSelectedHash().empty())
3041 BringWindowToTop(); /* cf. issue #3493 */
3042 return;
3045 auto selectedHashes = dlg.GetSelectedHash();
3046 for (auto it = selectedHashes.crbegin(); it != selectedHashes.crend(); ++it)
3048 GitRevLoglist* pRev = m_CommitList.m_logEntries.m_pLogCache->GetCacheData(*it);
3049 if (pRev->GetCommit(it->ToString()))
3050 return;
3051 if (pRev->GetParentFromHash(pRev->m_CommitHash))
3052 return;
3053 pRev->GetRebaseAction() = CGitLogListBase::LOGACTIONS_REBASE_PICK;
3054 if (m_CommitList.m_IsOldFirst)
3056 m_CommitList.m_logEntries.push_back(pRev->m_CommitHash);
3057 m_CommitList.m_arShownList.SafeAdd(pRev);
3059 else
3061 m_CommitList.m_logEntries.insert(m_CommitList.m_logEntries.cbegin(), pRev->m_CommitHash);
3062 m_CommitList.m_arShownList.SafeAddFront(pRev);
3065 m_CommitList.SetItemCountEx(static_cast<int>(m_CommitList.m_logEntries.size()));
3066 m_CommitList.Invalidate();
3068 if (m_CommitList.m_IsOldFirst)
3069 m_CurrentRebaseIndex = -1;
3070 else
3071 m_CurrentRebaseIndex = static_cast<int>(m_CommitList.m_logEntries.size());