1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2016 - TortoiseGit
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "TortoiseProc.h"
21 #include "CommitIsOnRefsDlg.h"
22 #include "StringUtils.h"
23 #include "BrowseRefsDlg.h"
24 #include "RefLogDlg.h"
26 #include "LoglistUtils.h"
28 #include "FileDiffDlg.h"
30 // CCommitIsOnRefsDlg dialog
32 UINT
CCommitIsOnRefsDlg::WM_GETTINGREFSFINISHED
= RegisterWindowMessage(_T("TORTOISEGIT_CommitIsOnRefs_GETTINGREFSFINISHED"));
34 IMPLEMENT_DYNAMIC(CCommitIsOnRefsDlg
, CResizableStandAloneDialog
)
36 CCommitIsOnRefsDlg::CCommitIsOnRefsDlg(CWnd
* pParent
/*=nullptr*/)
37 : CResizableStandAloneDialog(CCommitIsOnRefsDlg::IDD
, pParent
)
38 , m_bThreadRunning(FALSE
)
39 , m_bRefsLoaded(false)
44 CCommitIsOnRefsDlg::~CCommitIsOnRefsDlg()
48 void CCommitIsOnRefsDlg::DoDataExchange(CDataExchange
* pDX
)
50 CDialog::DoDataExchange(pDX
);
51 DDX_Control(pDX
, IDC_LIST_REF_LEAFS
, m_cRefList
);
52 DDX_Control(pDX
, IDC_FILTER
, m_cFilter
);
53 DDX_Control(pDX
, IDC_COMMIT
, m_cRevEdit
);
54 DDX_Control(pDX
, IDC_SELREF
, m_cSelRevBtn
);
57 BEGIN_MESSAGE_MAP(CCommitIsOnRefsDlg
, CResizableStandAloneDialog
)
58 ON_BN_CLICKED(IDC_SELREF
, OnBnClickedSelRevBtn
)
59 ON_BN_CLICKED(IDC_LOG
, OnBnClickedShowLog
)
60 ON_EN_CHANGE(IDC_FILTER
, OnEnChangeEditFilter
)
61 ON_NOTIFY(LVN_ITEMCHANGED
, IDC_LIST_REF_LEAFS
, OnItemChangedListRefs
)
62 ON_MESSAGE(ENAC_UPDATE
, OnEnChangeCommit
)
66 ON_REGISTERED_MESSAGE(WM_GETTINGREFSFINISHED
, OnGettingRefsFinished
)
69 // CCommitIsOnRefsDlg message handlers
71 void CCommitIsOnRefsDlg::OnCancel()
79 BOOL
CCommitIsOnRefsDlg::OnInitDialog()
81 __super::OnInitDialog();
83 AddAnchor(IDC_FILTER
, BOTTOM_LEFT
, BOTTOM_RIGHT
);
84 AddAnchor(IDC_LABEL_FILTER
, BOTTOM_LEFT
);
85 AddAnchor(IDC_SELREF
, TOP_RIGHT
);
86 AddAnchor(IDC_COMMIT
, TOP_LEFT
, TOP_RIGHT
);
87 AddAnchor(IDC_STATIC_SUBJECT
, TOP_LEFT
, TOP_RIGHT
);
88 AddAnchor(IDC_LIST_REF_LEAFS
, TOP_LEFT
, BOTTOM_RIGHT
);
89 AddAnchor(IDC_LOG
, TOP_RIGHT
);
92 m_bHasWC
= !GitAdminDir::IsBareRepo(g_Git
.m_CurrentDir
);
94 m_cSelRevBtn
.m_bRightArrow
= TRUE
;
95 m_cSelRevBtn
.m_bDefaultClick
= FALSE
;
96 m_cSelRevBtn
.m_bMarkDefault
= FALSE
;
97 m_cSelRevBtn
.m_bShowCurrentItem
= FALSE
;
98 m_cSelRevBtn
.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE
)));
99 m_cSelRevBtn
.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG
)));
100 m_cSelRevBtn
.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG
)));
102 EnableSaveRestore(L
"CommitIsOnRefsDlg");
104 CImageList
* imagelist
= new CImageList();
105 imagelist
->Create(IDB_BITMAP_REFTYPE
, 16, 3, RGB(255, 255, 255));
106 m_cRefList
.SetImageList(imagelist
, LVSIL_SMALL
);
109 m_cRefList
.GetClientRect(&rect
);
110 m_cRefList
.InsertColumn(0, L
"Ref", 0, rect
.Width() - 50);
112 m_cFilter
.SetCancelBitmaps(IDI_CANCELNORMAL
, IDI_CANCELPRESSED
);
113 m_cFilter
.SetInfoIcon(IDI_FILTEREDIT
);
116 m_cRevEdit
.SetWindowText(m_Rev
);
118 StartGetRefsThread();
120 m_cRevEdit
.SetFocus();
125 void CCommitIsOnRefsDlg::AddToList()
127 m_cRefList
.DeleteAllItems();
130 m_cFilter
.GetWindowText(filter
);
133 for (size_t i
= 0; i
< m_RefList
.size(); ++i
)
136 CString ref
= m_RefList
[i
];
137 if (CStringUtils::StartsWith(ref
, L
"refs/tags/"))
139 else if (CStringUtils::StartsWith(ref
, L
"refs/remotes/"))
141 else if (CStringUtils::StartsWith(ref
, L
"refs/heads/"))
144 if (ref
.Find(filter
) >= 0)
145 m_cRefList
.InsertItem(item
++, ref
, nImage
);
149 m_cRefList
.ShowText(L
"");
151 m_cRefList
.ShowText(CString(MAKEINTRESOURCE(IDS_ERROR_NOREF
)));
154 void CCommitIsOnRefsDlg::OnEnChangeEditFilter()
156 SetTimer(IDT_FILTER
, 1000, nullptr);
159 void CCommitIsOnRefsDlg::OnTimer(UINT_PTR nIDEvent
)
161 if (nIDEvent
== IDT_FILTER
)
163 KillTimer(IDT_FILTER
);
166 else if (nIDEvent
== IDT_INPUT
)
168 KillTimer(IDT_INPUT
);
169 StartGetRefsThread();
172 __super::OnTimer(nIDEvent
);
175 void CCommitIsOnRefsDlg::OnBnClickedShowLog()
178 cmd
.Format(L
"/command:log /rev:%s", (LPCTSTR
)m_gitrev
.m_CommitHash
.ToString());
179 CAppUtils::RunTortoiseGitProc(cmd
);
182 void CCommitIsOnRefsDlg::OnBnClickedSelRevBtn()
184 INT_PTR entry
= m_cSelRevBtn
.GetCurrentEntry();
185 if (entry
== 0) /* Browse Refence*/
188 CString str
= CBrowseRefsDlg::PickRef();
192 if (FillRevFromString(str
))
195 m_cRevEdit
.SetWindowText(str
);
199 if (entry
== 1) /*Log*/
203 m_cRevEdit
.GetWindowText(revision
);
204 dlg
.SetParams(CTGitPath(), CTGitPath(), revision
, revision
, 0);
206 if (dlg
.DoModal() == IDOK
)
208 if (dlg
.GetSelectedHash().empty())
211 if (FillRevFromString(dlg
.GetSelectedHash().at(0).ToString()))
214 m_cRevEdit
.SetWindowText(dlg
.GetSelectedHash().at(0).ToString());
220 if (entry
== 2) /*RefLog*/
223 if (dlg
.DoModal() == IDOK
)
225 if (FillRevFromString(dlg
.m_SelectedHash
))
228 m_cRevEdit
.SetWindowText(dlg
.m_SelectedHash
);
234 StartGetRefsThread();
235 KillTimer(IDT_INPUT
);
238 LRESULT
CCommitIsOnRefsDlg::OnEnChangeCommit(WPARAM
, LPARAM
)
240 SetTimer(IDT_INPUT
, 1000, nullptr);
244 void CCommitIsOnRefsDlg::OnContextMenu(CWnd
* pWnd
, CPoint point
)
246 if (!pWnd
|| pWnd
!= &m_cRefList
)
248 if (m_cRefList
.GetSelectedCount() == 0)
250 // if the context menu is invoked through the keyboard, we have to use
251 // a calculated position on where to anchor the menu on
252 if ((point
.x
== -1) && (point
.y
== -1))
255 m_cRefList
.GetItemRect(m_cRefList
.GetSelectionMark(), &rect
, LVIR_LABEL
);
256 m_cRefList
.ClientToScreen(&rect
);
257 point
= rect
.CenterPoint();
260 if (popup
.CreatePopupMenu())
262 STRING_VECTOR selectedRefs
;
263 POSITION pos
= m_cRefList
.GetFirstSelectedItemPosition();
265 selectedRefs
.push_back(m_cRefList
.GetItemText(m_cRefList
.GetNextSelectedItem(pos
), 0));
266 bool needSep
= false;
267 if (selectedRefs
.size() == 2)
269 popup
.AppendMenuIcon(eCmd_Diff
, IDS_LOG_POPUP_COMPARETWO
, IDI_DIFF
);
270 popup
.AppendMenuIcon(eCmd_UnifiedDiff
, IDS_LOG_POPUP_GNUDIFF
, IDI_DIFF
);
271 popup
.AppendMenu(MF_SEPARATOR
, NULL
);
273 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedRefs
, m_sLastSelected
, L
".."));
274 popup
.AppendMenuIcon(eCmd_ViewLogRange
, menu
, IDI_LOG
);
275 menu
.Format(IDS_SHOWLOG_OF
, (LPCTSTR
)GetTwoSelectedRefs(selectedRefs
, m_sLastSelected
, L
"..."));
276 popup
.AppendMenuIcon(eCmd_ViewLogRangeReachableFromOnlyOne
, menu
, IDI_LOG
);
279 else if (selectedRefs
.size() == 1)
281 popup
.AppendMenuIcon(eCmd_ViewLog
, IDS_FILEDIFF_LOG
, IDI_LOG
);
282 popup
.AppendMenu(MF_SEPARATOR
, NULL
);
283 popup
.AppendMenuIcon(eCmd_RepoBrowser
, IDS_LOG_BROWSEREPO
, IDI_REPOBROWSE
);
286 popup
.AppendMenu(MF_SEPARATOR
);
287 popup
.AppendMenuIcon(eCmd_DiffWC
, IDS_LOG_POPUP_COMPARE
, IDI_DIFF
);
292 if (!selectedRefs
.empty())
295 popup
.AppendMenu(MF_SEPARATOR
, NULL
);
297 popup
.AppendMenuIcon(eCmd_Copy
, IDS_SCIEDIT_COPY
, IDI_COPYCLIP
);
300 int cmd
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, point
.x
, point
.y
, this);
308 selectedRefs
[0] + L
"^{}",
309 selectedRefs
[1] + L
"^{}");
313 case eCmd_UnifiedDiff
:
314 CAppUtils::StartShowUnifiedDiff(GetSafeHwnd(), CTGitPath(), selectedRefs
.at(0), CTGitPath(), selectedRefs
.at(1), !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
318 CString sCmd
= L
"/command:log";
319 sCmd
+= L
" /path:\"" + g_Git
.m_CurrentDir
+ L
"\" ";
320 sCmd
+= L
" /endrev:" + selectedRefs
.at(0);
321 CAppUtils::RunTortoiseGitProc(sCmd
);
324 case eCmd_ViewLogRange
:
327 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedRefs
, m_sLastSelected
, L
".."));
328 CAppUtils::RunTortoiseGitProc(sCmd
);
331 case eCmd_ViewLogRangeReachableFromOnlyOne
:
334 sCmd
.Format(L
"/command:log /path:\"%s\" /range:\"%s\"", (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)GetTwoSelectedRefs(selectedRefs
, m_sLastSelected
, L
"..."));
335 CAppUtils::RunTortoiseGitProc(sCmd
);
338 case eCmd_RepoBrowser
:
339 CAppUtils::RunTortoiseGitProc(L
"/command:repobrowser /path:\"" + g_Git
.m_CurrentDir
+ L
"\" /rev:" + selectedRefs
[0]);
342 CopySelectionToClipboard();
347 sCmd
.Format(_T("/command:showcompare /path:\"%s\" /revision1:%s /revision2:%s"), (LPCTSTR
)g_Git
.m_CurrentDir
, (LPCTSTR
)selectedRefs
[0], (LPCTSTR
)GitRev::GetWorkingCopy());
348 if (!!(GetAsyncKeyState(VK_SHIFT
) & 0x8000))
349 sCmd
+= L
" /alternative";
351 CAppUtils::RunTortoiseGitProc(sCmd
);
358 void CCommitIsOnRefsDlg::OnItemChangedListRefs(NMHDR
* pNMHDR
, LRESULT
* pResult
)
360 LPNMLISTVIEW pNMListView
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
363 if (pNMListView
->iItem
>= 0 && m_RefList
.size() > pNMListView
->iItem
&& (pNMListView
->uNewState
& LVIS_SELECTED
))
364 m_sLastSelected
= m_RefList
[pNMListView
->iItem
];
367 CString
CCommitIsOnRefsDlg::GetTwoSelectedRefs(const STRING_VECTOR
& selectedRefs
, const CString
& lastSelected
, const CString
& separator
)
369 ASSERT(selectedRefs
.size() == 2);
371 if (selectedRefs
.at(0) == lastSelected
)
372 return g_Git
.StripRefName(selectedRefs
.at(1)) + separator
+ g_Git
.StripRefName(lastSelected
);
374 return g_Git
.StripRefName(selectedRefs
.at(0)) + separator
+ g_Git
.StripRefName(lastSelected
);
377 void CCommitIsOnRefsDlg::StartGetRefsThread()
379 if (InterlockedExchange(&m_bThreadRunning
, TRUE
))
382 KillTimer(IDT_INPUT
);
384 SetDlgItemText(IDC_STATIC_SUBJECT
, L
"");
385 m_tooltips
.DelTool(IDC_STATIC_SUBJECT
);
387 DialogEnableWindow(IDC_LOG
, FALSE
);
388 DialogEnableWindow(IDC_SELREF
, FALSE
);
389 DialogEnableWindow(IDC_FILTER
, FALSE
);
392 m_cRefList
.ShowText(CString(MAKEINTRESOURCE(IDS_STATUSLIST_BUSYMSG
)));
393 m_cRefList
.DeleteAllItems();
396 m_cRevEdit
.GetWindowText(m_Rev
);
398 if (!AfxBeginThread(GetRefsThreadEntry
, this))
400 InterlockedExchange(&m_bThreadRunning
, FALSE
);
401 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED
, IDS_APPNAME
, MB_OK
| MB_ICONERROR
);
405 UINT
CCommitIsOnRefsDlg::GetRefsThreadEntry(LPVOID pVoid
)
407 return ((CCommitIsOnRefsDlg
*)pVoid
)->GetRefsThread();
410 UINT
CCommitIsOnRefsDlg::GetRefsThread()
414 m_cRevEdit
.RemoveSearchAll();
416 g_Git
.GetRefList(refs
);
417 for (const auto& ref
: refs
)
418 m_cRevEdit
.AddSearchString(ref
);
419 m_bRefsLoaded
= true;
422 if (m_Rev
.IsEmpty() || m_gitrev
.GetCommit(m_Rev
))
424 SendMessage(WM_GETTINGREFSFINISHED
);
426 InterlockedExchange(&m_bThreadRunning
, FALSE
);
430 if (g_Git
.GetRefsCommitIsOn(m_RefList
, m_gitrev
.m_CommitHash
, true, true, CGit::BRANCH_ALL
))
432 MessageBox(g_Git
.GetGitLastErr(L
"Could not get all refs."), L
"TortoiseGit", MB_ICONERROR
);
435 SendMessage(WM_GETTINGREFSFINISHED
);
437 InterlockedExchange(&m_bThreadRunning
, FALSE
);
441 BOOL
CCommitIsOnRefsDlg::OnSetCursor(CWnd
* pWnd
, UINT nHitTest
, UINT message
)
443 if (pWnd
!= &m_cRefList
)
444 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
445 if (!m_bThreadRunning
)
447 HCURSOR hCur
= LoadCursor(nullptr, IDC_ARROW
);
449 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
451 HCURSOR hCur
= LoadCursor(nullptr, IDC_WAIT
);
456 LRESULT
CCommitIsOnRefsDlg::OnGettingRefsFinished(WPARAM
, LPARAM
)
458 DialogEnableWindow(IDC_LOG
, TRUE
);
459 DialogEnableWindow(IDC_SELREF
, TRUE
);
460 DialogEnableWindow(IDC_FILTER
, TRUE
);
464 m_cRefList
.ShowText(L
"");
465 InvalidateRect(nullptr);
470 if (!m_gitrev
.GetLastErr().IsEmpty())
473 msg
.Format(IDS_PROC_REFINVALID
, (LPCTSTR
)m_Rev
);
474 m_cRefList
.ShowText(msg
+ _T("\n") + m_gitrev
.GetLastErr());
476 InvalidateRect(nullptr);
481 SetDlgItemText(IDC_STATIC_SUBJECT
, m_gitrev
.m_CommitHash
.ToString().Left(8) + L
": " + m_gitrev
.GetSubject());
482 if (!m_gitrev
.m_CommitHash
.IsEmpty())
483 m_tooltips
.AddTool(IDC_STATIC_SUBJECT
, CLoglistUtils::FormatDateAndTime(m_gitrev
.GetAuthorDate(), DATE_SHORTDATE
) + L
" " + m_gitrev
.GetAuthorName());
487 InvalidateRect(nullptr);
492 BOOL
CCommitIsOnRefsDlg::PreTranslateMessage(MSG
* pMsg
)
494 if (pMsg
->message
== WM_KEYDOWN
)
496 switch (pMsg
->wParam
)
500 if (GetFocus() != GetDlgItem(IDC_LIST_REF_LEAFS
))
502 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
504 // select all entries
505 for (int i
= 0; i
< m_cRefList
.GetItemCount(); ++i
)
506 m_cRefList
.SetItemState(i
, LVIS_SELECTED
, LVIS_SELECTED
);
514 if (GetFocus() != GetDlgItem(IDC_LIST_REF_LEAFS
))
516 if (GetAsyncKeyState(VK_CONTROL
) & 0x8000)
518 CopySelectionToClipboard();
525 m_bRefsLoaded
= false;
530 if (GetFocus() == GetDlgItem(IDC_FILTER
) && m_cFilter
.GetWindowTextLength())
532 m_cFilter
.SetWindowText(L
"");
539 return __super::PreTranslateMessage(pMsg
);
542 void CCommitIsOnRefsDlg::CopySelectionToClipboard()
544 // copy all selected paths to the clipboard
545 POSITION pos
= m_cRefList
.GetFirstSelectedItemPosition();
547 CString sTextForClipboard
;
548 while ((index
= m_cRefList
.GetNextSelectedItem(pos
)) >= 0)
550 sTextForClipboard
+= m_cRefList
.GetItemText(index
, 0);
551 sTextForClipboard
+= _T("\r\n");
553 CStringUtils::WriteAsciiStringToClipboard(sTextForClipboard
);