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.
20 #include "TortoiseProc.h"
22 #include "CommitIsOnRefsDlg.h"
23 #include "StringUtils.h"
24 #include "BrowseRefsDlg.h"
25 #include "RefLogDlg.h"
27 #include "LoglistUtils.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
)
67 ON_REGISTERED_MESSAGE(WM_GETTINGREFSFINISHED
, OnGettingRefsFinished
)
70 // CCommitIsOnRefsDlg message handlers
72 void CCommitIsOnRefsDlg::OnCancel()
77 if (m_bNonModalParentHWND
)
86 void CCommitIsOnRefsDlg::PostNcDestroy()
88 if (m_bNonModalParentHWND
)
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
);
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
);
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);
131 m_cRevEdit
.SetWindowText(m_Rev
);
133 StartGetRefsThread();
135 m_cRevEdit
.SetFocus();
140 void CCommitIsOnRefsDlg::AddToList()
142 m_cRefList
.DeleteAllItems();
145 m_cFilter
.GetWindowText(filter
);
148 for (size_t i
= 0; i
< m_RefList
.size(); ++i
)
151 CString ref
= m_RefList
[i
];
152 if (CStringUtils::StartsWith(ref
, L
"refs/tags/"))
154 else if (CStringUtils::StartsWith(ref
, L
"refs/remotes/"))
156 else if (CStringUtils::StartsWith(ref
, L
"refs/heads/"))
159 if (ref
.Find(filter
) >= 0)
160 m_cRefList
.InsertItem(item
++, ref
, nImage
);
164 m_cRefList
.ShowText(L
"");
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
);
181 else if (nIDEvent
== IDT_INPUT
)
183 KillTimer(IDT_INPUT
);
184 StartGetRefsThread();
187 __super::OnTimer(nIDEvent
);
190 LRESULT
CCommitIsOnRefsDlg::OnClickedCancelFilter(WPARAM
/*wParam*/, LPARAM
/*lParam*/)
196 void CCommitIsOnRefsDlg::OnBnClickedShowLog()
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();
213 m_cRevEdit
.SetWindowText(str
);
217 if (entry
== 1) /*Log*/
220 if (dlg
.IsThreadRunning())
222 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
226 m_cRevEdit
.GetWindowText(revision
);
227 dlg
.SetParams(CTGitPath(), CTGitPath(), revision
, revision
, 0);
229 if (dlg
.DoModal() == IDOK
&& !dlg
.GetSelectedHash().empty())
231 m_cRevEdit
.SetWindowText(dlg
.GetSelectedHash().at(0).ToString());
232 BringWindowToTop(); /* cf. issue #3493 */
236 BringWindowToTop(); /* cf. issue #3493 */
241 if (entry
== 2) /*RefLog*/
244 if (dlg
.DoModal() == IDOK
)
245 m_cRevEdit
.SetWindowText(dlg
.m_SelectedHash
.ToString());
250 m_cRevEdit
.SetFocus();
251 StartGetRefsThread();
254 LRESULT
CCommitIsOnRefsDlg::OnEnChangeCommit(WPARAM
, LPARAM
)
256 SetTimer(IDT_INPUT
, 1000, nullptr);
260 void CCommitIsOnRefsDlg::OnContextMenu(CWnd
* pWnd
, CPoint point
)
262 if (!pWnd
|| pWnd
!= &m_cRefList
)
264 if (m_cRefList
.GetSelectedCount() == 0)
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))
271 m_cRefList
.GetItemRect(m_cRefList
.GetSelectionMark(), &rect
, LVIR_LABEL
);
272 m_cRefList
.ClientToScreen(&rect
);
273 point
= rect
.CenterPoint();
276 if (popup
.CreatePopupMenu())
278 STRING_VECTOR selectedRefs
;
279 POSITION pos
= m_cRefList
.GetFirstSelectedItemPosition();
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
);
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
);
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
);
302 popup
.AppendMenu(MF_SEPARATOR
);
303 popup
.AppendMenuIcon(eCmd_DiffWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
308 if (!selectedRefs
.empty())
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);
329 case eCmd_UnifiedDiff
:
330 CAppUtils::StartShowUnifiedDiff(GetSafeHwnd(), CTGitPath(), selectedRefs
.at(0), CTGitPath(), selectedRefs
.at(1), !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
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
);
340 case eCmd_ViewLogRange
:
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
);
347 case eCmd_ViewLogRangeReachableFromOnlyOne
:
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
);
354 case eCmd_RepoBrowser
:
355 CAppUtils::RunTortoiseGitProc(L
"/command:repobrowser /path:\"" + g_Git
.m_CurrentDir
+ L
"\" /rev:" + selectedRefs
[0]);
358 CopySelectionToClipboard();
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
);
374 void CCommitIsOnRefsDlg::OnItemChangedListRefs(NMHDR
* pNMHDR
, LRESULT
* pResult
)
376 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
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
)
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
);
402 return g_Git
.StripRefName(selectedRefs
.at(0)) + separator
+ g_Git
.StripRefName(lastSelected
);
405 void CCommitIsOnRefsDlg::StartGetRefsThread()
407 if (InterlockedExchange(&m_bThreadRunning
, TRUE
))
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
);
420 m_cRefList
.ShowText(CString(MAKEINTRESOURCE(IDS_STATUSLIST_BUSYMSG
)));
421 m_cRefList
.DeleteAllItems();
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()
442 m_cRevEdit
.RemoveSearchAll();
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
);
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
);
464 SendMessage(WM_GETTINGREFSFINISHED
);
466 InterlockedExchange(&m_bThreadRunning
, FALSE
);
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
);
478 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
480 HCURSOR hCur
= LoadCursor(nullptr, IDC_WAIT
);
485 LRESULT
CCommitIsOnRefsDlg::OnGettingRefsFinished(WPARAM
, LPARAM
)
487 DialogEnableWindow(IDC_SELREF
, TRUE
);
491 m_cRefList
.ShowText(L
"");
492 InvalidateRect(nullptr);
497 if (!m_gitrev
.GetLastErr().IsEmpty())
500 msg
.Format(IDS_PROC_REFINVALID
, static_cast<LPCWSTR
>(m_Rev
));
501 m_cRefList
.ShowText(msg
+ L
'\n' + m_gitrev
.GetLastErr());
503 InvalidateRect(nullptr);
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());
516 InvalidateRect(nullptr);
521 BOOL
CCommitIsOnRefsDlg::PreTranslateMessage(MSG
* pMsg
)
523 if (pMsg
->message
== WM_KEYDOWN
)
525 switch (pMsg
->wParam
)
529 if (GetFocus() != GetDlgItem(IDC_LIST_REF_LEAFS
))
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
);
543 if (GetFocus() != GetDlgItem(IDC_LIST_REF_LEAFS
))
545 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
547 CopySelectionToClipboard();
554 m_bRefsLoaded
= false;
559 if (GetFocus() == GetDlgItem(IDC_FILTER
) && m_cFilter
.GetWindowTextLength())
561 m_cFilter
.SetWindowText(L
"");
568 return __super::PreTranslateMessage(pMsg
);
571 void CCommitIsOnRefsDlg::CopySelectionToClipboard()
573 // copy all selected paths to the clipboard
574 POSITION pos
= m_cRefList
.GetFirstSelectedItemPosition();
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
);