RebaseDlg: Correctly remember commits for rewriting on Squash after (Edit|Squash...
[TortoiseGit.git] / src / TortoiseProc / CommitIsOnRefsDlg.cpp
blob9599158b06e0e63c4a06b00a675bcdcca6dfbffa
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2016-2021, 2023 - 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.
19 #include "stdafx.h"
20 #include "TortoiseProc.h"
21 #include "Git.h"
22 #include "CommitIsOnRefsDlg.h"
23 #include "StringUtils.h"
24 #include "BrowseRefsDlg.h"
25 #include "RefLogDlg.h"
26 #include "LogDlg.h"
27 #include "LoglistUtils.h"
28 #include "AppUtils.h"
29 #include "FileDiffDlg.h"
30 #include "MessageBox.h"
32 // CCommitIsOnRefsDlg dialog
34 UINT CCommitIsOnRefsDlg::WM_GETTINGREFSFINISHED = RegisterWindowMessage(L"TORTOISEGIT_CommitIsOnRefs_GETTINGREFSFINISHED");
36 IMPLEMENT_DYNAMIC(CCommitIsOnRefsDlg, CResizableStandAloneDialog)
38 CCommitIsOnRefsDlg::CCommitIsOnRefsDlg(CWnd* pParent /*=nullptr*/)
39 : CResizableStandAloneDialog(CCommitIsOnRefsDlg::IDD, pParent)
43 CCommitIsOnRefsDlg::~CCommitIsOnRefsDlg()
47 void CCommitIsOnRefsDlg::DoDataExchange(CDataExchange* pDX)
49 CDialog::DoDataExchange(pDX);
50 DDX_Control(pDX, IDC_LIST_REF_LEAFS, m_cRefList);
51 DDX_Control(pDX, IDC_FILTER, m_cFilter);
52 DDX_Control(pDX, IDC_COMMIT, m_cRevEdit);
53 DDX_Control(pDX, IDC_SELREF, m_cSelRevBtn);
56 BEGIN_MESSAGE_MAP(CCommitIsOnRefsDlg, CResizableStandAloneDialog)
57 ON_BN_CLICKED(IDC_SELREF, OnBnClickedSelRevBtn)
58 ON_BN_CLICKED(IDC_LOG, OnBnClickedShowLog)
59 ON_EN_CHANGE(IDC_FILTER, OnEnChangeEditFilter)
60 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
61 ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST_REF_LEAFS, OnItemChangedListRefs)
62 ON_NOTIFY(NM_DBLCLK, IDC_LIST_REF_LEAFS, OnNMDblClickListRefs)
63 ON_MESSAGE(ENAC_UPDATE, OnEnChangeCommit)
64 ON_WM_TIMER()
65 ON_WM_CONTEXTMENU()
66 ON_WM_SETCURSOR()
67 ON_REGISTERED_MESSAGE(WM_GETTINGREFSFINISHED, OnGettingRefsFinished)
68 END_MESSAGE_MAP()
70 // CCommitIsOnRefsDlg message handlers
72 void CCommitIsOnRefsDlg::OnCancel()
74 if (m_bThreadRunning)
75 return;
77 if (m_bNonModalParentHWND)
79 DestroyWindow();
80 return;
83 __super::OnCancel();
86 void CCommitIsOnRefsDlg::PostNcDestroy()
88 if (m_bNonModalParentHWND)
89 delete this;
92 BOOL CCommitIsOnRefsDlg::OnInitDialog()
94 __super::OnInitDialog();
96 AddAnchor(IDC_FILTER, BOTTOM_LEFT, BOTTOM_RIGHT);
97 AddAnchor(IDC_LABEL_FILTER, BOTTOM_LEFT);
98 AddAnchor(IDC_SELREF, TOP_RIGHT);
99 AddAnchor(IDC_COMMIT, TOP_LEFT, TOP_RIGHT);
100 AddAnchor(IDC_STATIC_SUBJECT, TOP_LEFT, TOP_RIGHT);
101 AddAnchor(IDC_LIST_REF_LEAFS, TOP_LEFT, BOTTOM_RIGHT);
102 AddAnchor(IDC_LOG, TOP_RIGHT);
103 AddOthersToAnchor();
105 m_bHasWC = !GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
107 m_cSelRevBtn.m_bRightArrow = TRUE;
108 m_cSelRevBtn.m_bDefaultClick = FALSE;
109 m_cSelRevBtn.m_bMarkDefault = FALSE;
110 m_cSelRevBtn.m_bShowCurrentItem = FALSE;
111 m_cSelRevBtn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
112 m_cSelRevBtn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
113 m_cSelRevBtn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
115 EnableSaveRestore(L"CommitIsOnRefsDlg");
117 CImageList* imagelist = new CImageList();
118 imagelist->Create(IDB_BITMAP_REFTYPE, 16, 3, RGB(255, 255, 255));
119 m_cRefList.SetImageList(imagelist, LVSIL_SMALL);
121 CRect rect;
122 m_cRefList.GetClientRect(&rect);
123 m_cRefList.InsertColumn(0, L"Ref", 0, rect.Width() - 50);
124 if (CRegDWORD(L"Software\\TortoiseGit\\FullRowSelect", TRUE))
125 m_cRefList.SetExtendedStyle(m_cRefList.GetExtendedStyle() | LVS_EX_FULLROWSELECT);
127 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED, 14, 14);
128 m_cFilter.SetInfoIcon(IDI_FILTEREDIT, 19, 19);
130 m_cRevEdit.Init();
131 m_cRevEdit.SetWindowText(m_Rev);
133 StartGetRefsThread();
135 m_cRevEdit.SetFocus();
137 return FALSE;
140 void CCommitIsOnRefsDlg::AddToList()
142 m_cRefList.DeleteAllItems();
144 CString filter;
145 m_cFilter.GetWindowText(filter);
147 int item = 0;
148 for (size_t i = 0; i < m_RefList.size(); ++i)
150 int nImage = -1;
151 CString ref = m_RefList[i];
152 if (CStringUtils::StartsWith(ref, L"refs/tags/"))
153 nImage = 0;
154 else if (CStringUtils::StartsWith(ref, L"refs/remotes/"))
155 nImage = 2;
156 else if (CStringUtils::StartsWith(ref, L"refs/heads/"))
157 nImage = 1;
159 if (ref.Find(filter) >= 0)
160 m_cRefList.InsertItem(item++, ref, nImage);
163 if (item)
164 m_cRefList.ShowText(L"");
165 else
166 m_cRefList.ShowText(CString(MAKEINTRESOURCE(IDS_ERROR_NOREF)));
169 void CCommitIsOnRefsDlg::OnEnChangeEditFilter()
171 SetTimer(IDT_FILTER, 1000, nullptr);
174 void CCommitIsOnRefsDlg::OnTimer(UINT_PTR nIDEvent)
176 if (nIDEvent == IDT_FILTER)
178 KillTimer(IDT_FILTER);
179 AddToList();
181 else if (nIDEvent == IDT_INPUT)
183 KillTimer(IDT_INPUT);
184 StartGetRefsThread();
187 __super::OnTimer(nIDEvent);
190 LRESULT CCommitIsOnRefsDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
192 OnTimer(IDT_FILTER);
193 return TRUE;
196 void CCommitIsOnRefsDlg::OnBnClickedShowLog()
198 CString cmd;
199 cmd.Format(L"/command:log /rev:%s", static_cast<LPCWSTR>(m_gitrev.m_CommitHash.ToString()));
200 CAppUtils::RunTortoiseGitProc(cmd);
203 void CCommitIsOnRefsDlg::OnBnClickedSelRevBtn()
205 INT_PTR entry = m_cSelRevBtn.GetCurrentEntry();
206 if (entry == 0) /* Browse Refence*/
209 CString str = CBrowseRefsDlg::PickRef();
210 if (str.IsEmpty())
211 return;
213 m_cRevEdit.SetWindowText(str);
217 if (entry == 1) /*Log*/
219 CLogDlg dlg;
220 if (dlg.IsThreadRunning())
222 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
223 return;
225 CString revision;
226 m_cRevEdit.GetWindowText(revision);
227 dlg.SetParams(CTGitPath(), CTGitPath(), revision, revision, 0);
228 dlg.SetSelect(true);
229 if (dlg.DoModal() == IDOK && !dlg.GetSelectedHash().empty())
231 m_cRevEdit.SetWindowText(dlg.GetSelectedHash().at(0).ToString());
232 BringWindowToTop(); /* cf. issue #3493 */
234 else
236 BringWindowToTop(); /* cf. issue #3493 */
237 return;
241 if (entry == 2) /*RefLog*/
243 CRefLogDlg dlg;
244 if (dlg.DoModal() == IDOK)
245 m_cRevEdit.SetWindowText(dlg.m_SelectedHash.ToString());
246 else
247 return;
250 m_cRevEdit.SetFocus();
251 StartGetRefsThread();
254 LRESULT CCommitIsOnRefsDlg::OnEnChangeCommit(WPARAM, LPARAM)
256 SetTimer(IDT_INPUT, 1000, nullptr);
257 return 0;
260 void CCommitIsOnRefsDlg::OnContextMenu(CWnd* pWnd, CPoint point)
262 if (!pWnd || pWnd != &m_cRefList)
263 return;
264 if (m_cRefList.GetSelectedCount() == 0)
265 return;
266 // if the context menu is invoked through the keyboard, we have to use
267 // a calculated position on where to anchor the menu on
268 if ((point.x == -1) && (point.y == -1))
270 CRect rect;
271 m_cRefList.GetItemRect(m_cRefList.GetSelectionMark(), &rect, LVIR_LABEL);
272 m_cRefList.ClientToScreen(&rect);
273 point = rect.CenterPoint();
275 CIconMenu popup;
276 if (popup.CreatePopupMenu())
278 STRING_VECTOR selectedRefs;
279 POSITION pos = m_cRefList.GetFirstSelectedItemPosition();
280 while (pos)
281 selectedRefs.push_back(m_cRefList.GetItemText(m_cRefList.GetNextSelectedItem(pos), 0));
282 bool needSep = false;
283 if (selectedRefs.size() == 2)
285 popup.AppendMenuIcon(eCmd_Diff, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
286 popup.AppendMenuIcon(eCmd_UnifiedDiff, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
287 popup.AppendMenu(MF_SEPARATOR, NULL);
288 CString menu;
289 menu.Format(IDS_SHOWLOG_OF, static_cast<LPCWSTR>(GetTwoSelectedRefs(selectedRefs, m_sLastSelected, L"..")));
290 popup.AppendMenuIcon(eCmd_ViewLogRange, menu, IDI_LOG);
291 menu.Format(IDS_SHOWLOG_OF, static_cast<LPCWSTR>(GetTwoSelectedRefs(selectedRefs, m_sLastSelected, L"...")));
292 popup.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne, menu, IDI_LOG);
293 needSep = true;
295 else if (selectedRefs.size() == 1)
297 popup.AppendMenuIcon(eCmd_ViewLog, IDS_FILEDIFF_LOG, IDI_LOG);
298 popup.AppendMenu(MF_SEPARATOR, NULL);
299 popup.AppendMenuIcon(eCmd_RepoBrowser, IDS_LOG_BROWSEREPO, IDI_REPOBROWSE);
300 if (m_bHasWC)
302 popup.AppendMenu(MF_SEPARATOR);
303 popup.AppendMenuIcon(eCmd_DiffWC, IDS_LOG_POPUP_COMPARE, IDI_DIFF);
305 needSep = true;
308 if (!selectedRefs.empty())
310 if (needSep)
311 popup.AppendMenu(MF_SEPARATOR, NULL);
313 popup.AppendMenuIcon(eCmd_Copy, IDS_SCIEDIT_COPY, IDI_COPYCLIP);
316 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
317 switch (cmd)
319 case eCmd_Diff:
321 CFileDiffDlg dlg;
322 dlg.SetDiff(
323 nullptr,
324 selectedRefs[0],
325 selectedRefs[1]);
326 dlg.DoModal();
328 break;
329 case eCmd_UnifiedDiff:
330 CAppUtils::StartShowUnifiedDiff(GetSafeHwnd(), CTGitPath(), selectedRefs.at(0), CTGitPath(), selectedRefs.at(1), !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
331 break;
332 case eCmd_ViewLog:
334 CString sCmd = L"/command:log";
335 sCmd += L" /path:\"" + g_Git.m_CurrentDir + L"\" ";
336 sCmd += L" /endrev:" + selectedRefs.at(0);
337 CAppUtils::RunTortoiseGitProc(sCmd);
339 break;
340 case eCmd_ViewLogRange:
342 CString sCmd;
343 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCWSTR>(g_Git.m_CurrentDir), static_cast<LPCWSTR>(GetTwoSelectedRefs(selectedRefs, m_sLastSelected, L"..")));
344 CAppUtils::RunTortoiseGitProc(sCmd);
346 break;
347 case eCmd_ViewLogRangeReachableFromOnlyOne:
349 CString sCmd;
350 sCmd.Format(L"/command:log /path:\"%s\" /range:\"%s\"", static_cast<LPCWSTR>(g_Git.m_CurrentDir), static_cast<LPCWSTR>(GetTwoSelectedRefs(selectedRefs, m_sLastSelected, L"...")));
351 CAppUtils::RunTortoiseGitProc(sCmd);
353 break;
354 case eCmd_RepoBrowser:
355 CAppUtils::RunTortoiseGitProc(L"/command:repobrowser /path:\"" + g_Git.m_CurrentDir + L"\" /rev:" + selectedRefs[0]);
356 break;
357 case eCmd_Copy:
358 CopySelectionToClipboard();
359 break;
360 case eCmd_DiffWC:
362 CString sCmd;
363 sCmd.Format(L"/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s", static_cast<LPCWSTR>(g_Git.m_CurrentDir), static_cast<LPCWSTR>(selectedRefs[0]), static_cast<LPCWSTR>(GitRev::GetWorkingCopy()));
364 if (!!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
365 sCmd += L" /alternative";
367 CAppUtils::RunTortoiseGitProc(sCmd);
369 break;
374 void CCommitIsOnRefsDlg::OnItemChangedListRefs(NMHDR* pNMHDR, LRESULT* pResult)
376 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
377 *pResult = 0;
379 if (pNMListView->iItem >= 0 && m_RefList.size() > static_cast<size_t>(pNMListView->iItem) && (pNMListView->uNewState & LVIS_SELECTED))
380 m_sLastSelected = m_RefList[pNMListView->iItem];
383 void CCommitIsOnRefsDlg::OnNMDblClickListRefs(NMHDR* pNMHDR, LRESULT* pResult)
385 *pResult = 0;
387 LPNMLISTVIEW pNMListView = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
388 if (m_bNonModalParentHWND && pNMListView->iItem >= 0 && m_RefList.size() > static_cast<size_t>(pNMListView->iItem))
390 if (::SendMessage(m_bNonModalParentHWND, CGitLogListBase::m_ScrollToRef, reinterpret_cast<WPARAM>(&m_RefList[pNMListView->iItem]), 0) != 0)
391 FlashWindowEx(FLASHW_ALL, 2, 100);
395 CString CCommitIsOnRefsDlg::GetTwoSelectedRefs(const STRING_VECTOR& selectedRefs, const CString& lastSelected, const CString& separator)
397 ASSERT(selectedRefs.size() == 2);
399 if (selectedRefs.at(0) == lastSelected)
400 return g_Git.StripRefName(selectedRefs.at(1)) + separator + g_Git.StripRefName(lastSelected);
401 else
402 return g_Git.StripRefName(selectedRefs.at(0)) + separator + g_Git.StripRefName(lastSelected);
405 void CCommitIsOnRefsDlg::StartGetRefsThread()
407 if (InterlockedExchange(&m_bThreadRunning, TRUE))
408 return;
410 KillTimer(IDT_INPUT);
412 SetDlgItemText(IDC_STATIC_SUBJECT, L"");
413 m_tooltips.DelTool(IDC_STATIC_SUBJECT);
415 DialogEnableWindow(IDC_LOG, FALSE);
416 DialogEnableWindow(IDC_SELREF, FALSE);
417 DialogEnableWindow(IDC_FILTER, FALSE);
419 m_RefList.clear();
420 m_cRefList.ShowText(CString(MAKEINTRESOURCE(IDS_STATUSLIST_BUSYMSG)));
421 m_cRefList.DeleteAllItems();
422 RefreshCursor();
424 m_cRevEdit.GetWindowText(m_Rev);
426 if (!AfxBeginThread(GetRefsThreadEntry, this))
428 InterlockedExchange(&m_bThreadRunning, FALSE);
429 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
433 UINT CCommitIsOnRefsDlg::GetRefsThreadEntry(LPVOID pVoid)
435 return static_cast<CCommitIsOnRefsDlg*>(pVoid)->GetRefsThread();
438 UINT CCommitIsOnRefsDlg::GetRefsThread()
440 if (!m_bRefsLoaded)
442 m_cRevEdit.RemoveSearchAll();
443 STRING_VECTOR refs;
444 g_Git.GetRefList(refs);
445 for (const auto& ref : refs)
446 m_cRevEdit.AddSearchString(ref);
447 m_bRefsLoaded = true;
450 if (m_Rev.IsEmpty() || m_gitrev.GetCommit(m_Rev))
452 SendMessage(WM_GETTINGREFSFINISHED);
454 InterlockedExchange(&m_bThreadRunning, FALSE);
455 return 0;
457 m_gitrev.ApplyMailmap();
459 if (g_Git.GetRefsCommitIsOn(m_RefList, m_gitrev.m_CommitHash, true, true, CGit::BRANCH_ALL))
461 MessageBox(g_Git.GetGitLastErr(L"Could not get all refs."), L"TortoiseGit", MB_ICONERROR);
462 return 0;
464 SendMessage(WM_GETTINGREFSFINISHED);
466 InterlockedExchange(&m_bThreadRunning, FALSE);
467 return 0;
470 BOOL CCommitIsOnRefsDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
472 if (pWnd != &m_cRefList)
473 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
474 if (!m_bThreadRunning)
476 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
477 SetCursor(hCur);
478 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
480 HCURSOR hCur = LoadCursor(nullptr, IDC_WAIT);
481 SetCursor(hCur);
482 return TRUE;
485 LRESULT CCommitIsOnRefsDlg::OnGettingRefsFinished(WPARAM, LPARAM)
487 DialogEnableWindow(IDC_SELREF, TRUE);
489 if (m_Rev.IsEmpty())
491 m_cRefList.ShowText(L"");
492 InvalidateRect(nullptr);
493 RefreshCursor();
494 return 0;
497 if (!m_gitrev.GetLastErr().IsEmpty())
499 CString msg;
500 msg.Format(IDS_PROC_REFINVALID, static_cast<LPCWSTR>(m_Rev));
501 m_cRefList.ShowText(msg + L'\n' + m_gitrev.GetLastErr());
503 InvalidateRect(nullptr);
504 RefreshCursor();
505 return 0;
508 DialogEnableWindow(IDC_LOG, TRUE);
509 DialogEnableWindow(IDC_FILTER, TRUE);
510 SetDlgItemText(IDC_STATIC_SUBJECT, m_gitrev.m_CommitHash.ToString(g_Git.GetShortHASHLength()) + L": " + m_gitrev.GetSubject());
511 if (!m_gitrev.m_CommitHash.IsEmpty())
512 m_tooltips.AddTool(IDC_STATIC_SUBJECT, CLoglistUtils::FormatDateAndTime(m_gitrev.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_gitrev.GetAuthorName());
514 AddToList();
516 InvalidateRect(nullptr);
517 RefreshCursor();
518 return 0;
521 BOOL CCommitIsOnRefsDlg::PreTranslateMessage(MSG* pMsg)
523 if (pMsg->message == WM_KEYDOWN)
525 switch (pMsg->wParam)
527 case 'A':
529 if (GetFocus() != GetDlgItem(IDC_LIST_REF_LEAFS))
530 break;
531 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
533 // select all entries
534 for (int i = 0; i < m_cRefList.GetItemCount(); ++i)
535 m_cRefList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
536 return TRUE;
539 break;
540 case 'C':
541 case VK_INSERT:
543 if (GetFocus() != GetDlgItem(IDC_LIST_REF_LEAFS))
544 break;
545 if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
547 CopySelectionToClipboard();
548 return TRUE;
551 break;
552 case VK_F5:
554 m_bRefsLoaded = false;
555 OnTimer(IDT_INPUT);
557 break;
558 case VK_ESCAPE:
559 if (GetFocus() == GetDlgItem(IDC_FILTER) && m_cFilter.GetWindowTextLength())
561 m_cFilter.SetWindowText(L"");
562 OnTimer(IDT_FILTER);
563 return TRUE;
565 break;
568 return __super::PreTranslateMessage(pMsg);
571 void CCommitIsOnRefsDlg::CopySelectionToClipboard()
573 // copy all selected paths to the clipboard
574 POSITION pos = m_cRefList.GetFirstSelectedItemPosition();
575 int index;
576 CString sTextForClipboard;
577 while ((index = m_cRefList.GetNextSelectedItem(pos)) >= 0)
579 sTextForClipboard += m_cRefList.GetItemText(index, 0);
580 sTextForClipboard += L"\r\n";
582 CStringUtils::WriteAsciiStringToClipboard(sTextForClipboard);