Fixed issue #3953: Disabling ShowListFullPathTooltip doesn't hide tooltips in Changed...
[TortoiseGit.git] / src / TortoiseProc / FileDiffDlg.cpp
bloba5c2a06969901eae6374cbe137f593307f64e3e2
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2022 - TortoiseGit
4 // Copyright (C) 2003-2008, 2018 - TortoiseSVN
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "UnicodeUtils.h"
23 #include "MessageBox.h"
24 #include "AppUtils.h"
25 #include "SysImageList.h"
26 #include "IconMenu.h"
27 #include "StringUtils.h"
28 #include "PathUtils.h"
29 #include "BrowseFolder.h"
30 #include "FileDiffDlg.h"
31 #include "GitDiff.h"
32 #include "LoglistCommonResource.h"
33 #include "LoglistUtils.h"
34 #include "BrowseRefsDlg.h"
35 #include "LogDlg.h"
36 #include "RefLogDlg.h"
37 #include "GitStatusListCtrl.h"
38 #include "FormatMessageWrapper.h"
39 #include "GitDataObject.h"
40 #include "LogDlgFileFilter.h"
42 #define ID_COMPARE 1
43 #define ID_BLAME 2
44 #define ID_SAVEAS 3
45 #define ID_EXPORT 4
46 #define ID_CLIPBOARD_PATH 5
47 #define ID_CLIPBOARD_ALL 6
48 #define ID_LOG 7
49 #define ID_GNUDIFFCOMPARE 8
50 #define ID_REVERT1 9
51 #define ID_REVERT2 10
52 #define ID_LOGSUBMODULE 11
54 BOOL CFileDiffDlg::m_bAscending = TRUE;
55 int CFileDiffDlg::m_nSortedColumn = -1;
57 UINT CFileDiffDlg::WM_DISABLEBUTTONS = RegisterWindowMessage(L"TORTOISEGIT_FILEDIFF_DISABLEBUTTONS");
58 UINT CFileDiffDlg::WM_DIFFFINISHED = RegisterWindowMessage(L"TORTOISEGIT_FILEDIFF_DIFFFINISHED");
60 IMPLEMENT_DYNAMIC(CFileDiffDlg, CResizableStandAloneDialog)
61 CFileDiffDlg::CFileDiffDlg(CWnd* pParent /*=nullptr*/)
62 : CResizableStandAloneDialog(CFileDiffDlg::IDD, pParent)
63 , m_bBlame(false)
64 , m_nIconFolder(0)
65 , m_bThreadRunning(FALSE)
66 , m_bIgnoreSpaceAtEol(false)
67 , m_bIgnoreSpaceChange(false)
68 , m_bIgnoreAllSpace(false)
69 , m_bIgnoreBlankLines(false)
70 , m_bCommonAncestorDiff(false)
71 , m_bIsBare(false)
72 , m_bLoadingRef(FALSE)
76 CFileDiffDlg::~CFileDiffDlg()
80 void CFileDiffDlg::DoDataExchange(CDataExchange* pDX)
82 CResizableStandAloneDialog::DoDataExchange(pDX);
83 DDX_Control(pDX, IDC_FILELIST, m_cFileList);
84 DDX_Control(pDX, IDC_SWITCHLEFTRIGHT, m_SwitchButton);
85 DDX_Control(pDX, IDC_REV1BTN, m_cRev1Btn);
86 DDX_Control(pDX, IDC_REV2BTN, m_cRev2Btn);
87 DDX_Control(pDX, IDC_FILTER, m_cFilter);
88 DDX_Control(pDX, IDC_REV1EDIT, m_ctrRev1Edit);
89 DDX_Control(pDX, IDC_REV2EDIT, m_ctrRev2Edit);
90 DDX_Control(pDX, IDC_DIFFOPTION, m_cDiffOptionsBtn);
91 DDX_Control(pDX, IDC_VIEW_PATCH, m_ctrlShowPatch);
95 BEGIN_MESSAGE_MAP(CFileDiffDlg, CResizableStandAloneDialog)
96 ON_NOTIFY(NM_DBLCLK, IDC_FILELIST, OnNMDblclkFilelist)
97 ON_NOTIFY(LVN_GETINFOTIP, IDC_FILELIST, OnLvnGetInfoTipFilelist)
98 ON_NOTIFY(NM_CUSTOMDRAW, IDC_FILELIST, OnNMCustomdrawFilelist)
99 ON_WM_CONTEXTMENU()
100 ON_WM_SETCURSOR()
101 ON_EN_SETFOCUS(IDC_SECONDURL, &CFileDiffDlg::OnEnSetfocusSecondurl)
102 ON_EN_SETFOCUS(IDC_FIRSTURL, &CFileDiffDlg::OnEnSetfocusFirsturl)
103 ON_BN_CLICKED(IDC_SWITCHLEFTRIGHT, &CFileDiffDlg::OnBnClickedSwitchleftright)
104 ON_NOTIFY(HDN_ITEMCLICK, 0, &CFileDiffDlg::OnHdnItemclickFilelist)
105 ON_BN_CLICKED(IDC_REV1BTN, &CFileDiffDlg::OnBnClickedRev1btn)
106 ON_BN_CLICKED(IDC_REV2BTN, &CFileDiffDlg::OnBnClickedRev2btn)
107 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
108 ON_EN_CHANGE(IDC_FILTER, &CFileDiffDlg::OnEnChangeFilter)
109 ON_WM_TIMER()
110 ON_MESSAGE(ENAC_UPDATE, &CFileDiffDlg::OnEnUpdate)
111 ON_MESSAGE(MSG_REF_LOADED, OnRefLoad)
112 ON_REGISTERED_MESSAGE(WM_DISABLEBUTTONS, OnDisableButtons)
113 ON_REGISTERED_MESSAGE(WM_DIFFFINISHED, OnDiffFinished)
114 ON_BN_CLICKED(IDC_DIFFOPTION, OnBnClickedDiffoption)
115 ON_BN_CLICKED(IDC_LOG, &CFileDiffDlg::OnBnClickedLog)
116 ON_NOTIFY(LVN_BEGINDRAG, IDC_FILELIST, OnLvnBegindrag)
117 ON_STN_CLICKED(IDC_VIEW_PATCH, OnStnClickedViewPatch)
118 ON_NOTIFY(LVN_ITEMCHANGED, IDC_FILELIST, OnFileListItemChanged)
119 ON_WM_MOVE()
120 ON_WM_MOVING()
121 ON_WM_SIZING()
122 END_MESSAGE_MAP()
125 void CFileDiffDlg::SetDiff(const CTGitPath* path, const GitRev& baseRev1, const GitRev& rev2)
127 if (path)
129 m_path = *path;
130 m_sFilter = path->GetGitPathString();
132 m_rev1 = baseRev1;
133 m_rev2 = rev2;
136 void CFileDiffDlg::SetDiff(const CTGitPath* path, const CString &baseRev1, const CString& hash2)
138 if (path)
140 m_path = *path;
141 m_sFilter = path->GetGitPathString();
144 if (baseRev1 == GIT_REV_ZERO)
146 m_rev1.m_CommitHash.Empty();
147 m_rev1.GetSubject().LoadString(IDS_WORKING_TREE);
149 else
150 FillRevFromString(&m_rev1, baseRev1);
152 if(hash2 == GIT_REV_ZERO)
154 m_rev2.m_CommitHash.Empty();
155 m_rev2.GetSubject().LoadString(IDS_WORKING_TREE);
157 else
158 FillRevFromString(&m_rev2, hash2);
161 void CFileDiffDlg::SetDiff(const CTGitPath* path, const GitRev &baseRev1)
163 if (path)
165 m_path = *path;
166 m_sFilter = path->GetGitPathString();
168 m_rev1 = baseRev1;
169 m_rev2.m_CommitHash.Empty();
170 m_rev2.GetSubject().LoadString(IDS_PROC_PREVIOUSVERSION);
173 BOOL CFileDiffDlg::OnInitDialog()
175 CResizableStandAloneDialog::OnInitDialog();
176 CString temp;
178 CString sWindowTitle;
179 GetWindowText(sWindowTitle);
180 CString pathText = g_Git.m_CurrentDir;
181 if (!m_path.IsEmpty())
182 pathText = g_Git.CombinePath(m_path);
183 CAppUtils::SetWindowTitle(m_hWnd, pathText, sWindowTitle);
185 this->m_ctrRev1Edit.Init();
186 this->m_ctrRev2Edit.Init();
188 m_tooltips.AddTool(IDC_SWITCHLEFTRIGHT, IDS_FILEDIFF_SWITCHLEFTRIGHT_TT);
190 SetWindowTheme(m_cFileList.GetSafeHwnd(), L"Explorer", nullptr);
191 m_cFileList.SetRedraw(false);
192 m_cFileList.DeleteAllItems();
193 DWORD exStyle = LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP;
194 if (CRegDWORD(L"Software\\TortoiseGit\\FullRowSelect", TRUE))
195 exStyle |= LVS_EX_FULLROWSELECT;
196 m_cFileList.SetExtendedStyle(exStyle);
198 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
199 m_cFileList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
201 int iconWidth = GetSystemMetrics(SM_CXSMICON);
202 int iconHeight = GetSystemMetrics(SM_CYSMICON);
203 m_SwitchButton.SetImage(CCommonAppUtils::LoadIconEx(IDI_SWITCHLEFTRIGHT, iconWidth, iconHeight));
204 m_SwitchButton.Invalidate();
206 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED, 14, 14);
207 m_cFilter.SetInfoIcon(IDI_FILTEREDIT, 19, 19);
208 temp.LoadString(IDS_FILEDIFF_FILTERCUE);
209 temp = L" " + temp;
210 m_cFilter.SetCueBanner(temp);
211 if (!m_sFilter.IsEmpty())
212 m_cFilter.SetWindowText(m_sFilter);
214 int c = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
215 while (c>=0)
216 m_cFileList.DeleteColumn(c--);
218 temp.LoadString(IDS_FILEDIFF_FILE);
219 m_cFileList.InsertColumn(0, temp);
220 temp.LoadString(IDS_FILEDIFF_EXT);
221 m_cFileList.InsertColumn(1, temp);
222 temp.LoadString(IDS_FILEDIFF_ACTION);
223 m_cFileList.InsertColumn(2, temp);
225 temp.LoadString(IDS_FILEDIFF_STATADD);
226 m_cFileList.InsertColumn(3, temp);
227 temp.LoadString(IDS_FILEDIFF_STATDEL);
228 m_cFileList.InsertColumn(4, temp);
230 for (int col = 0, maxcol = m_cFileList.GetHeaderCtrl()->GetItemCount(); col < maxcol; ++col)
231 m_cFileList.SetColumnWidth(col,LVSCW_AUTOSIZE_USEHEADER);
233 m_cFileList.SetRedraw(true);
235 AddAnchor(IDC_DIFFSTATIC1, TOP_LEFT, TOP_RIGHT);
236 AddAnchor(IDC_SWITCHLEFTRIGHT, TOP_RIGHT);
237 AddAnchor(IDC_FIRSTURL, TOP_LEFT, TOP_RIGHT);
238 AddAnchor(IDC_REV1BTN, TOP_RIGHT);
239 //AddAnchor(IDC_DIFFSTATIC2, TOP_LEFT, TOP_RIGHT);
240 AddAnchor(IDC_SECONDURL, TOP_LEFT, TOP_RIGHT);
241 AddAnchor(IDC_REV2BTN, TOP_RIGHT);
242 AddAnchor(IDC_FILTER, TOP_LEFT, TOP_RIGHT);
243 AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT);
244 AddAnchor(IDC_REV1GROUP,TOP_LEFT,TOP_RIGHT);
245 AddAnchor(IDC_REV2GROUP,TOP_LEFT,TOP_RIGHT);
246 AddAnchor(IDC_REV1EDIT,TOP_LEFT);
247 AddAnchor(IDC_REV2EDIT,TOP_LEFT);
248 AddAnchor(IDC_DIFFOPTION, TOP_RIGHT);
249 AddAnchor(IDC_LOG, TOP_RIGHT);
250 AddAnchor(IDC_VIEW_PATCH, BOTTOM_RIGHT);
252 EnableSaveRestore(L"FileDiffDlg");
254 m_bIsBare = GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
256 if(this->m_strRev1.IsEmpty())
257 this->m_ctrRev1Edit.SetWindowText(this->m_rev1.m_CommitHash.ToString());
258 else
260 if (m_rev1.GetCommit(m_strRev1))
262 CString msg;
263 msg.Format(IDS_PROC_REFINVALID, static_cast<LPCWSTR>(m_strRev1));
264 m_cFileList.ShowText(msg + L'\n' + m_rev1.GetLastErr());
266 m_rev1.ApplyMailmap();
268 this->m_ctrRev1Edit.SetWindowText(m_strRev1);
271 if(this->m_strRev2.IsEmpty())
272 this->m_ctrRev2Edit.SetWindowText(this->m_rev2.m_CommitHash.ToString());
273 else
275 if (m_rev2.GetCommit(m_strRev2))
277 CString msg;
278 msg.Format(IDS_PROC_REFINVALID, static_cast<LPCWSTR>(m_strRev2));
279 m_cFileList.ShowText(msg + L'\n' + m_rev1.GetLastErr());
281 m_rev2.ApplyMailmap();
283 this->m_ctrRev2Edit.SetWindowText(m_strRev2);
286 SetURLLabels();
288 InterlockedExchange(&m_bThreadRunning, TRUE);
289 if (!AfxBeginThread(DiffThreadEntry, this))
291 InterlockedExchange(&m_bThreadRunning, FALSE);
292 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
295 InterlockedExchange(&m_bLoadingRef, TRUE);
296 if (!AfxBeginThread(LoadRefThreadEntry, this))
298 InterlockedExchange(&m_bLoadingRef, FALSE);
299 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
302 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
303 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
304 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
306 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
307 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
308 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
310 // Start with focus on file list
311 GetDlgItem(IDC_FILELIST)->SetFocus();
313 if(m_rev2.m_CommitHash.IsEmpty())
314 m_SwitchButton.EnableWindow(FALSE);
316 m_cDiffOptionsBtn.m_bAlwaysShowArrow = true;
318 m_ctrlShowPatch.SetURL(L"");
319 if (g_Git.GetConfigValueBool(_T("tgit.diffshowpatch")))
320 TogglePatchView();
322 if (CRegDWORD(L"Software\\TortoiseGit\\LogFontForFileListCtrl", FALSE))
324 CAppUtils::CreateFontForLogs(m_font);
325 m_cFileList.SetFont(&m_font);
328 KillTimer(IDT_INPUT);
329 return FALSE;
332 UINT CFileDiffDlg::DiffThreadEntry(LPVOID pVoid)
334 return static_cast<CFileDiffDlg*>(pVoid)->DiffThread();
337 UINT CFileDiffDlg::DiffThread()
339 SendMessage(WM_DISABLEBUTTONS);
341 if( m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty())
342 g_Git.RefreshGitIndex();
344 if (m_bCommonAncestorDiff && !(m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty()))
346 CGitHash commonAncestor;
347 g_Git.IsFastForward(m_rev1.m_CommitHash.ToString(), m_rev2.m_CommitHash.ToString(), &commonAncestor);
348 g_Git.GetCommitDiffList(m_rev2.m_CommitHash.ToString(), commonAncestor.ToString(), m_arFileList, m_bIgnoreSpaceAtEol, m_bIgnoreSpaceChange, m_bIgnoreAllSpace, m_bIgnoreBlankLines);
350 else
351 g_Git.GetCommitDiffList(m_rev2.m_CommitHash.ToString(), m_rev1.m_CommitHash.ToString(), m_arFileList, m_bIgnoreSpaceAtEol, m_bIgnoreSpaceChange, m_bIgnoreAllSpace, m_bIgnoreBlankLines);
352 Sort();
354 SendMessage(WM_DIFFFINISHED);
356 InterlockedExchange(&m_bThreadRunning, FALSE);
357 return 0;
360 LRESULT CFileDiffDlg::OnDisableButtons(WPARAM, LPARAM)
362 RefreshCursor();
363 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_FILEDIFF_WAIT)));
364 m_cFileList.DeleteAllItems();
365 m_arFileList.Clear();
366 EnableInputControl(false);
367 return 0;
370 LRESULT CFileDiffDlg::OnDiffFinished(WPARAM, LPARAM)
372 CString sFilterText;
373 m_cFilter.GetWindowText(sFilterText);
374 m_cFileList.SetRedraw(false);
375 Filter(sFilterText);
377 for (int col = 0, maxcol = m_cFileList.GetHeaderCtrl()->GetItemCount(); col < maxcol; ++col)
378 m_cFileList.SetColumnWidth(col, LVSCW_AUTOSIZE_USEHEADER);
380 m_cFileList.ClearText();
381 if (m_arFileList.IsEmpty())
382 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_COMPAREREV_NODIFF)));
383 m_cFileList.SetRedraw(true);
385 InvalidateRect(nullptr);
386 RefreshCursor();
387 EnableInputControl(true);
388 FillPatchView(true);
389 m_cFileList.SetFocus();
390 return 0;
393 static CString GetFilename(const CTGitPath* entry)
395 // similar code in CGitStatusListCtrl::GetCellText
396 static CString from(MAKEINTRESOURCE(IDS_STATUSLIST_FROM));
397 static bool abbreviateRenamings(static_cast<DWORD>(CRegDWORD(L"Software\\TortoiseGit\\AbbreviateRenamings", FALSE)) == TRUE);
399 if (!(entry->m_Action & (CTGitPath::LOGACTIONS_REPLACED | CTGitPath::LOGACTIONS_COPY) && !entry->GetGitOldPathString().IsEmpty()))
400 return entry->GetGitPathString();
402 if (!abbreviateRenamings)
404 CString entryname = entry->GetGitPathString();
405 entryname += L' ';
406 // relative path
407 entryname.AppendFormat(from, static_cast<LPCWSTR>(entry->GetGitOldPathString()));
408 return entryname;
411 return entry->GetAbbreviatedRename();
414 int CFileDiffDlg::AddEntry(const CTGitPath * fd)
416 int ret = -1;
417 if (fd)
419 int index = m_cFileList.GetItemCount();
421 int icon_idx = 0;
422 if (fd->IsDirectory())
423 icon_idx = m_nIconFolder;
424 else
425 icon_idx = SYS_IMAGE_LIST().GetPathIconIndex(fd->GetGitPathString());
427 ret = m_cFileList.InsertItem(index, GetFilename(fd), icon_idx);
428 m_cFileList.SetItemText(index, 1, fd->GetFileExtension());
429 m_cFileList.SetItemText(index, 2, fd->GetActionName());
430 m_cFileList.SetItemText(index, 3, fd->m_StatAdd);
431 m_cFileList.SetItemText(index, 4, fd->m_StatDel);
433 return ret;
436 void CFileDiffDlg::EnableInputControl(bool b)
438 this->m_ctrRev1Edit.EnableWindow(b);
439 this->m_ctrRev2Edit.EnableWindow(b);
440 this->m_cRev1Btn.EnableWindow(b);
441 this->m_cRev2Btn.EnableWindow(b);
442 m_cFilter.EnableWindow(b);
443 m_SwitchButton.EnableWindow(b);
444 GetDlgItem(IDC_LOG)->EnableWindow(b && !(m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty()));
447 void CFileDiffDlg::DoDiff(int selIndex, bool blame)
449 auto fd2 = m_arFilteredList[selIndex];
450 auto fd1 = fd2;
451 if (m_rev2.m_CommitHash.IsEmpty() && g_Git.IsInitRepos())
453 CGitDiff::DiffNull(GetSafeHwnd(), fd2, GIT_REV_ZERO, true, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
454 return;
456 if (fd1->m_Action & CTGitPath::LOGACTIONS_ADDED)
458 CGitDiff::DiffNull(GetSafeHwnd(), fd1, m_rev2.m_CommitHash.ToString(), true, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
459 return;
461 if (fd1->m_Action & CTGitPath::LOGACTIONS_DELETED)
463 CGitDiff::DiffNull(GetSafeHwnd(), fd1, m_rev1.m_CommitHash.ToString(), false, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
464 return;
466 if (fd1->m_Action & CTGitPath::LOGACTIONS_REPLACED)
467 fd2 = new CTGitPath(fd1->GetGitOldPathString());
468 CGitDiff::Diff(GetSafeHwnd(), fd1, fd2, m_rev2.m_CommitHash.ToString(), m_rev1.m_CommitHash.ToString(), blame, FALSE, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
469 if (fd1 != fd2)
470 delete fd2;
474 void CFileDiffDlg::OnNMDblclkFilelist(NMHDR *pNMHDR, LRESULT *pResult)
476 *pResult = 0;
477 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
478 int selIndex = pNMLV->iItem;
479 if (selIndex < 0)
480 return;
481 if (selIndex >= static_cast<int>(m_arFilteredList.size()))
482 return;
484 DoDiff(selIndex, m_bBlame);
487 void CFileDiffDlg::OnLvnGetInfoTipFilelist(NMHDR *pNMHDR, LRESULT *pResult)
489 *pResult = 0;
491 LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMLVGETINFOTIP>(pNMHDR);
492 if (pGetInfoTip->iItem >= static_cast<int>(m_arFilteredList.size()) || CRegDWORD(L"Software\\TortoiseGit\\ShowListFullPathTooltip", TRUE) != TRUE)
493 return;
495 CString path = m_path.GetGitPathString() + L'/' + m_arFilteredList[pGetInfoTip->iItem]->GetGitPathString();
496 if (pGetInfoTip->cchTextMax > path.GetLength())
497 wcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, path, pGetInfoTip->cchTextMax - 1);
500 void CFileDiffDlg::OnNMCustomdrawFilelist(NMHDR *pNMHDR, LRESULT *pResult)
502 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
503 // Take the default processing unless we set this to something else below.
504 *pResult = CDRF_DODEFAULT;
506 // First thing - check the draw stage. If it's the control's prepaint
507 // stage, then tell Windows we want messages for every item.
509 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
511 *pResult = CDRF_NOTIFYITEMDRAW;
513 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
515 // This is the prepaint stage for an item. Here's where we set the
516 // item's text color. Our return value will tell Windows to draw the
517 // item itself, but it will use the new color we set here.
519 // Tell Windows to paint the control itself.
520 *pResult = CDRF_NOTIFYSUBITEMDRAW;
522 COLORREF crText = CTheme::Instance().IsDarkTheme() ? CTheme::darkTextColor : GetSysColor(COLOR_WINDOWTEXT);
524 if (m_arFilteredList.size() > pLVCD->nmcd.dwItemSpec)
526 auto fd = m_arFilteredList[pLVCD->nmcd.dwItemSpec];
527 switch (fd->m_Action)
529 case CTGitPath::LOGACTIONS_ADDED:
530 crText = CTheme::Instance().GetThemeColor(m_colors.GetColor(CColors::Added));
531 break;
532 case CTGitPath::LOGACTIONS_DELETED:
533 crText = CTheme::Instance().GetThemeColor(m_colors.GetColor(CColors::Deleted));
534 break;
535 case CTGitPath::LOGACTIONS_MODIFIED:
536 crText = CTheme::Instance().GetThemeColor(m_colors.GetColor(CColors::Modified));
537 break;
538 default:
539 crText = CTheme::Instance().GetThemeColor(m_colors.GetColor(CColors::PropertyChanged));
540 break;
543 // Store the color back in the NMLVCUSTOMDRAW struct.
544 pLVCD->clrText = crText;
546 else if (pLVCD->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_ITEM | CDDS_SUBITEM))
548 if (pLVCD->iSubItem > 1) // only highlight search matches in filename and extension
549 return;
551 auto filter(m_filter);
552 if (filter->IsFilterActive())
553 *pResult = CGitLogListBase::DrawListItemWithMatches(filter.get(), m_cFileList, pLVCD, m_colors);
557 UINT CFileDiffDlg::LoadRefThread()
559 g_Git.GetBranchList(m_Reflist, nullptr, CGit::BRANCH_ALL_F);
560 g_Git.GetTagList(m_Reflist);
562 this->PostMessage(MSG_REF_LOADED);
563 InterlockedExchange(&m_bLoadingRef, FALSE);
564 return 0;
567 static CString GetCommitTitle(const GitRev& rev)
569 CString str;
570 CString commitTitle = rev.GetSubject();
571 if (commitTitle.GetLength() > 20)
573 commitTitle.Truncate(20);
574 commitTitle += L"...";
576 str.AppendFormat(L"%s (%s)", static_cast<LPCWSTR>(rev.m_CommitHash.ToString(g_Git.GetShortHASHLength())), static_cast<LPCWSTR>(CStringUtils::EscapeAccellerators(commitTitle)));
577 return str;
580 void CFileDiffDlg::OnContextMenu(CWnd* pWnd, CPoint point)
582 if (!pWnd || pWnd != &m_cFileList)
583 return;
584 if (m_cFileList.GetSelectedCount() == 0)
585 return;
586 // if the context menu is invoked through the keyboard, we have to use
587 // a calculated position on where to anchor the menu on
588 if ((point.x == -1) && (point.y == -1))
590 CRect rect;
591 m_cFileList.GetItemRect(m_cFileList.GetSelectionMark(), &rect, LVIR_LABEL);
592 m_cFileList.ClientToScreen(&rect);
593 point = rect.CenterPoint();
595 CIconMenu popup;
596 if (popup.CreatePopupMenu())
598 int firstEntry = -1;
599 POSITION firstPos = m_cFileList.GetFirstSelectedItemPosition();
600 if (firstPos)
601 firstEntry = m_cFileList.GetNextSelectedItem(firstPos);
603 CString menuText;
604 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
605 popup.SetDefaultItem(ID_COMPARE, FALSE);
606 popup.AppendMenuIcon(ID_GNUDIFFCOMPARE, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
607 popup.AppendMenu(MF_SEPARATOR, NULL);
608 if (!m_bIsBare)
610 if (!m_rev1.m_CommitHash.IsEmpty() && (m_rev2.m_CommitHash.IsEmpty() || (!m_rev2.m_CommitHash.IsEmpty() && !(m_arFilteredList[firstEntry]->m_Action & CTGitPath::LOGACTIONS_ADDED))))
612 menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, static_cast<LPCWSTR>(GetCommitTitle(m_rev1)));
613 popup.AppendMenuIcon(ID_REVERT1, menuText, IDI_REVERT);
615 if (!m_rev2.m_CommitHash.IsEmpty() && (m_rev1.m_CommitHash.IsEmpty() || (!m_rev1.m_CommitHash.IsEmpty() && !(m_arFilteredList[firstEntry]->m_Action & CTGitPath::LOGACTIONS_DELETED))))
617 menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, static_cast<LPCWSTR>(GetCommitTitle(m_rev2)));
618 popup.AppendMenuIcon(ID_REVERT2, menuText, IDI_REVERT);
620 popup.AppendMenu(MF_SEPARATOR, NULL);
622 popup.AppendMenuIcon(ID_LOG, IDS_FILEDIFF_LOG, IDI_LOG);
623 if (firstEntry >= 0 && !m_arFilteredList[firstEntry]->IsDirectory())
625 if (!m_bIsBare)
627 popup.AppendMenuIcon(ID_BLAME, IDS_FILEDIFF_POPBLAME, IDI_BLAME);
628 popup.AppendMenu(MF_SEPARATOR, NULL);
630 popup.AppendMenuIcon(ID_EXPORT, IDS_FILEDIFF_POPEXPORT, IDI_EXPORT);
632 else if (firstEntry >= 0)
633 popup.AppendMenuIcon(ID_LOGSUBMODULE, IDS_MENULOGSUBMODULE, IDI_LOG);
634 popup.AppendMenu(MF_SEPARATOR, NULL);
635 popup.AppendMenuIcon(ID_SAVEAS, IDS_FILEDIFF_POPSAVELIST, IDI_SAVEAS);
636 popup.AppendMenuIcon(ID_CLIPBOARD_PATH, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
637 popup.AppendMenuIcon(ID_CLIPBOARD_ALL, IDS_STATUSLIST_CONTEXT_COPYEXT, IDI_COPYCLIP);
639 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
640 switch (cmd)
642 case ID_COMPARE:
644 if (!CheckMultipleDiffs())
645 break;
646 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
647 while (pos)
649 int index = m_cFileList.GetNextSelectedItem(pos);
650 DoDiff(index, false);
653 break;
654 case ID_GNUDIFFCOMPARE:
656 if (!CheckMultipleDiffs())
657 break;
658 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
659 while (pos)
661 auto fd2 = m_arFilteredList[m_cFileList.GetNextSelectedItem(pos)];
662 auto fd1 = fd2;
663 if (fd1->m_Action & CTGitPath::LOGACTIONS_REPLACED)
664 fd2 = new CTGitPath(fd2->GetGitOldPathString());
665 CAppUtils::StartShowUnifiedDiff(m_hWnd, *fd1, m_rev1.m_CommitHash.ToString(), *fd2, m_rev2.m_CommitHash.ToString(), !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
666 if (fd1 != fd2)
667 delete fd2;
670 break;
671 case ID_REVERT1:
672 RevertSelectedItemToVersion(m_rev1.m_CommitHash.ToString(), true);
673 break;
674 case ID_REVERT2:
675 RevertSelectedItemToVersion(m_rev2.m_CommitHash.ToString(), false);
676 break;
677 case ID_BLAME:
679 if (!CheckMultipleDiffs())
680 break;
681 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
682 while (pos)
684 int index = m_cFileList.GetNextSelectedItem(pos);
685 if (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_DELETED)
687 if (!m_rev1.m_CommitHash.IsEmpty())
688 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev1.m_CommitHash.ToString());
689 continue;
691 if (m_rev2.m_CommitHash.IsEmpty() && (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_ADDED))
692 continue;
693 if (m_rev2.m_CommitHash.IsEmpty() && (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_REPLACED))
695 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetGitOldPathString(), m_rev1.m_CommitHash.ToString());
696 continue;
698 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev2.m_CommitHash.ToString());
701 break;
702 case ID_LOG:
703 case ID_LOGSUBMODULE:
705 if (!CheckMultipleDiffs())
706 break;
707 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
708 while (pos)
710 int index = m_cFileList.GetNextSelectedItem(pos);
711 CString sCmd = L"/command:log";
712 if (sCmd == ID_LOGSUBMODULE)
713 sCmd += L" /submodule";
714 sCmd += L" /path:\"" + m_arFilteredList[index]->GetWinPathString() + L"\" ";
715 sCmd += L" /endrev:" + m_rev2.m_CommitHash.ToString();
716 CAppUtils::RunTortoiseGitProc(sCmd);
719 break;
720 case ID_SAVEAS:
722 if (m_cFileList.GetSelectedCount() > 0)
724 CTGitPath savePath;
725 CString pathSave;
726 if (!CAppUtils::FileOpenSave(pathSave, nullptr, IDS_FILEDIFF_POPSAVELIST, IDS_TEXTFILEFILTER, false, m_hWnd, L"txt"))
727 break;
728 savePath = CTGitPath(pathSave);
730 // now open the selected file for writing
733 CStdioFile file(savePath.GetWinPathString(), CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
734 CString temp;
735 if (m_path.IsEmpty())
736 temp.FormatMessage(IDS_FILEDIFF_CHANGEDLISTINTROROOT, static_cast<LPCWSTR>(m_rev1.m_CommitHash.ToString()), static_cast<LPCWSTR>(m_rev2.m_CommitHash.ToString()));
737 else
738 temp.FormatMessage(IDS_FILEDIFF_CHANGEDLISTINTRO, static_cast<LPCWSTR>(m_path.GetGitPathString()), static_cast<LPCWSTR>(m_rev1.m_CommitHash.ToString()), static_cast<LPCWSTR>(m_path.GetGitPathString()), static_cast<LPCWSTR>(m_rev2.m_CommitHash.ToString()));
739 file.WriteString(temp + L"\r\n");
740 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
741 while (pos)
743 int index = m_cFileList.GetNextSelectedItem(pos);
744 auto fd = m_arFilteredList[index];
745 file.WriteString(fd->GetGitPathString());
746 file.WriteString(L"\r\n");
748 file.Close();
750 catch (CFileException* pE)
752 pE->ReportError();
756 break;
757 case ID_CLIPBOARD_PATH:
759 CopySelectionToClipboard();
761 break;
763 case ID_CLIPBOARD_ALL:
765 CopySelectionToClipboard(TRUE);
767 break;
768 case ID_EXPORT:
770 // export all changed files to a folder
771 CBrowseFolder browseFolder;
772 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
773 if (browseFolder.Show(GetSafeHwnd(), m_strExportDir) == CBrowseFolder::OK)
775 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
776 while (pos)
778 int index = m_cFileList.GetNextSelectedItem(pos);
779 auto fd = m_arFilteredList[index];
780 // we cannot export directories or folders
781 if (fd->m_Action == CTGitPath::LOGACTIONS_DELETED || fd->IsDirectory())
782 continue;
783 CPathUtils::MakeSureDirectoryPathExists(m_strExportDir + L'\\' + fd->GetContainingDirectory().GetWinPathString());
784 CString filename = m_strExportDir + L'\\' + fd->GetWinPathString();
785 if (m_rev2.m_CommitHash.ToString() == GIT_REV_ZERO)
787 if(!CopyFile(g_Git.CombinePath(fd), filename, false))
789 MessageBox(CFormatMessageWrapper(), L"TortoiseGit", MB_OK | MB_ICONERROR);
790 return;
793 else
795 if (g_Git.GetOneFile(m_rev2.m_CommitHash.ToString(), *fd, filename))
797 CString out;
798 out.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED, static_cast<LPCWSTR>(fd->GetGitPathString()), static_cast<LPCWSTR>(m_rev2.m_CommitHash.ToString()), static_cast<LPCWSTR>(filename));
799 if (CMessageBox::Show(GetSafeHwnd(), g_Git.GetGitLastErr(out, CGit::GIT_CMD_GETONEFILE), L"TortoiseGit", 2, IDI_WARNING, CString(MAKEINTRESOURCE(IDS_IGNOREBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
800 return;
807 break;
813 BOOL CFileDiffDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
815 if (pWnd != &m_cFileList)
816 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
817 if (m_bThreadRunning == 0)
819 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
820 SetCursor(hCur);
821 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
823 HCURSOR hCur = LoadCursor(nullptr, IDC_WAIT);
824 SetCursor(hCur);
825 return TRUE;
828 void CFileDiffDlg::OnEnSetfocusFirsturl()
830 GetDlgItem(IDC_FIRSTURL)->HideCaret();
833 void CFileDiffDlg::OnEnSetfocusSecondurl()
835 GetDlgItem(IDC_SECONDURL)->HideCaret();
838 void CFileDiffDlg::OnBnClickedSwitchleftright()
840 if (m_bThreadRunning)
841 return;
843 m_cFileList.SetRedraw(true);
844 GitRev rev = m_rev1;
845 m_rev1 = m_rev2;
846 m_rev2 = rev;
848 CString str1,str2;
849 this->m_ctrRev1Edit.GetWindowText(str1);
850 this->m_ctrRev2Edit.GetWindowText(str2);
852 this->m_ctrRev1Edit.SetWindowText(str2);
853 this->m_ctrRev2Edit.SetWindowText(str1);
855 SetURLLabels();
856 //KillTimer(IDT_INPUT);
859 void CFileDiffDlg::SetURLLabels(int mask)
861 if(mask &0x1)
863 SetDlgItemText(IDC_FIRSTURL, m_rev1.m_CommitHash.ToString(g_Git.GetShortHASHLength()) + L": " + m_rev1.GetSubject());
864 if (!m_rev1.m_CommitHash.IsEmpty())
865 m_tooltips.AddTool(IDC_FIRSTURL,
866 CLoglistUtils::FormatDateAndTime(m_rev1.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_rev1.GetAuthorName());
869 if(mask &0x2)
871 SetDlgItemText(IDC_SECONDURL, m_rev2.m_CommitHash.ToString(g_Git.GetShortHASHLength()) + L": " + m_rev2.GetSubject());
872 if (!m_rev2.m_CommitHash.IsEmpty())
873 m_tooltips.AddTool(IDC_SECONDURL,
874 CLoglistUtils::FormatDateAndTime(m_rev2.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_rev2.GetAuthorName());
877 this->GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1BASE)));
878 this->GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2)));
880 if ((mask & 0x3) == 0x3 && !m_rev1.m_CommitHash.IsEmpty() && !m_rev2.m_CommitHash.IsEmpty())
881 if(m_rev1.GetCommitterDate() > m_rev2.GetCommitterDate())
882 GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1BASENEWER)));
883 else if (m_rev1.GetCommitterDate() < m_rev2.GetCommitterDate())
884 GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2NEWER)));
887 void CFileDiffDlg::ClearURLabels(int mask)
889 FillPatchView(true);
890 if(mask&0x1)
892 SetDlgItemText(IDC_FIRSTURL, L"");
893 m_tooltips.AddTool(IDC_FIRSTURL, L"");
896 if(mask&0x2)
898 SetDlgItemText(IDC_SECONDURL, L"");
899 m_tooltips.AddTool(IDC_SECONDURL, L"");
902 BOOL CFileDiffDlg::PreTranslateMessage(MSG* pMsg)
904 if (pMsg->message == WM_KEYDOWN)
906 switch (pMsg->wParam)
908 case 'A':
910 if (GetFocus() != GetDlgItem(IDC_FILELIST))
911 break;
912 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
914 // select all entries
915 for (int i=0; i<m_cFileList.GetItemCount(); ++i)
916 m_cFileList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
917 return TRUE;
920 break;
921 case 'C':
922 case VK_INSERT:
924 if (GetFocus() != GetDlgItem(IDC_FILELIST))
925 break;
926 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
928 CopySelectionToClipboard();
929 return TRUE;
932 break;
933 case '\r':
935 if (GetFocus() == GetDlgItem(IDC_FILELIST))
937 // Return pressed in file list. Show diff, as for double click
938 int selIndex = m_cFileList.GetSelectionMark();
939 if (selIndex >= 0 && selIndex < static_cast<int>(m_arFileList.GetCount()))
940 DoDiff(selIndex, m_bBlame);
941 return TRUE;
944 break;
945 case VK_F5:
947 OnTimer(IDT_INPUT);
949 break;
950 case VK_ESCAPE:
951 if (GetFocus() == GetDlgItem(IDC_FILTER) && m_cFilter.GetWindowTextLength())
953 m_cFilter.SetWindowText(L"");
954 OnClickedCancelFilter(NULL, NULL);
955 return TRUE;
957 break;
960 return __super::PreTranslateMessage(pMsg);
963 void CFileDiffDlg::OnCancel()
965 if (m_bThreadRunning || m_bLoadingRef)
966 return;
967 __super::OnCancel();
970 void CFileDiffDlg::OnHdnItemclickFilelist(NMHDR *pNMHDR, LRESULT *pResult)
972 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
973 if (m_bThreadRunning)
974 return;
976 if (m_nSortedColumn == phdr->iItem)
977 m_bAscending = !m_bAscending;
978 else
979 m_bAscending = TRUE;
980 m_nSortedColumn = phdr->iItem;
981 Sort();
983 CString temp;
984 m_cFileList.SetRedraw(FALSE);
985 m_cFileList.DeleteAllItems();
986 m_cFilter.GetWindowText(temp);
987 Filter(temp);
989 CHeaderCtrl * pHeader = m_cFileList.GetHeaderCtrl();
990 HDITEM HeaderItem = {0};
991 HeaderItem.mask = HDI_FORMAT;
992 for (int i=0; i<pHeader->GetItemCount(); ++i)
994 pHeader->GetItem(i, &HeaderItem);
995 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
996 pHeader->SetItem(i, &HeaderItem);
998 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
999 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
1000 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
1002 m_cFileList.SetRedraw(TRUE);
1004 *pResult = 0;
1007 void CFileDiffDlg::Sort()
1009 if(m_arFileList.GetCount() < 2)
1010 return;
1012 std::sort(m_arFileList.m_paths.begin(), m_arFileList.m_paths.end(), &CFileDiffDlg::SortCompare);
1015 bool CFileDiffDlg::SortCompare(const CTGitPath& Data1, const CTGitPath& Data2)
1017 int result = 0;
1018 int d1, d2;
1019 switch (m_nSortedColumn)
1021 case 0: //path column
1022 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
1023 break;
1024 case 1: //extension column
1025 result = Data1.GetFileExtension().Compare(Data2.GetFileExtension());
1026 break;
1027 case 2: //action column
1028 result = Data1.m_Action - Data2.m_Action;
1029 break;
1030 case 3:
1031 d1 = CSorter::A2L(Data1.m_StatAdd);
1032 d2 = CSorter::A2L(Data2.m_StatAdd);
1033 result = d1 - d2;
1034 break;
1035 case 4:
1036 d1 = CSorter::A2L(Data1.m_StatDel);;
1037 d2 = CSorter::A2L(Data2.m_StatDel);
1038 result = d1 - d2;
1039 break;
1040 default:
1041 break;
1043 // sort by path name as second priority
1044 if (m_nSortedColumn != 0 && result == 0)
1045 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
1047 if (!m_bAscending)
1048 result = -result;
1049 return result < 0;
1053 void CFileDiffDlg::OnBnClickedRev1btn()
1055 ClickRevButton(&this->m_cRev1Btn,&this->m_rev1, &this->m_ctrRev1Edit);
1058 void CFileDiffDlg::ClickRevButton(CMenuButton *button, GitRev *rev, CACEdit *edit)
1060 INT_PTR entry=button->GetCurrentEntry();
1061 if(entry == 0) /* Browse Refence*/
1064 CString str = CBrowseRefsDlg::PickRef();
1065 if(str.IsEmpty())
1066 return;
1068 if(FillRevFromString(rev,str))
1069 return;
1071 edit->SetWindowText(str);
1075 if(entry == 1) /*Log*/
1077 CLogDlg dlg;
1078 if (dlg.IsThreadRunning())
1080 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE, IDS_APPNAME, MB_ICONEXCLAMATION);
1081 return;
1083 CString revision;
1084 edit->GetWindowText(revision);
1085 dlg.SetParams(CTGitPath(), CTGitPath(), revision, revision, 0);
1086 dlg.SetSelect(true);
1087 if(dlg.DoModal() == IDOK)
1089 BringWindowToTop(); /* cf. issue #3493 */
1090 if (dlg.GetSelectedHash().empty())
1091 return;
1093 if (FillRevFromString(rev, dlg.GetSelectedHash().at(0).ToString()))
1094 return;
1096 edit->SetWindowText(dlg.GetSelectedHash().at(0).ToString());
1098 else
1100 BringWindowToTop(); /* cf. issue #3493 */
1101 return;
1105 if(entry == 2) /*RefLog*/
1107 CRefLogDlg dlg;
1108 if(dlg.DoModal() == IDOK)
1110 if (FillRevFromString(rev, dlg.m_SelectedHash.ToString()))
1111 return;
1113 edit->SetWindowText(dlg.m_SelectedHash.ToString());
1115 else
1116 return;
1119 SetURLLabels();
1121 if (InterlockedExchange(&m_bThreadRunning, TRUE))
1122 return;
1123 if (!AfxBeginThread(DiffThreadEntry, this))
1125 InterlockedExchange(&m_bThreadRunning, FALSE);
1126 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1128 KillTimer(IDT_INPUT);
1131 void CFileDiffDlg::OnBnClickedRev2btn()
1133 ClickRevButton(&this->m_cRev2Btn,&this->m_rev2, &this->m_ctrRev2Edit);
1136 LRESULT CFileDiffDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1138 if (m_bThreadRunning)
1140 SetTimer(IDT_FILTER, 1000, nullptr);
1141 return 0L;
1144 KillTimer(IDT_FILTER);
1146 m_cFileList.SetRedraw(FALSE);
1147 m_arFilteredList.clear();
1148 m_cFileList.DeleteAllItems();
1150 Filter(L"");
1152 m_cFileList.SetRedraw(TRUE);
1153 return 0L;
1156 void CFileDiffDlg::OnEnChangeFilter()
1158 SetTimer(IDT_FILTER, 1000, nullptr);
1161 void CFileDiffDlg::OnTimer(UINT_PTR nIDEvent)
1163 if (m_bThreadRunning)
1164 return;
1166 if( nIDEvent == IDT_FILTER)
1168 CString sFilterText;
1169 KillTimer(IDT_FILTER);
1170 m_cFilter.GetWindowText(sFilterText);
1172 m_cFileList.SetRedraw(FALSE);
1173 m_cFileList.DeleteAllItems();
1175 Filter(sFilterText);
1177 m_cFileList.SetRedraw(TRUE);
1179 __super::OnTimer(nIDEvent);
1182 if( nIDEvent == IDT_INPUT)
1184 KillTimer(IDT_INPUT);
1185 TRACE(L"Input Timer\r\n");
1187 GitRev gitrev;
1188 CString str;
1189 int mask = 0;
1190 this->m_ctrRev1Edit.GetWindowText(str);
1191 if (!gitrev.GetCommit(str))
1193 gitrev.ApplyMailmap();
1194 m_rev1 = gitrev;
1195 mask |= 0x1;
1197 else
1199 m_cFileList.DeleteAllItems();
1200 CString msg;
1201 msg.Format(IDS_PROC_REFINVALID, static_cast<LPCWSTR>(str));
1202 m_cFileList.ShowText(msg + L'\n' + gitrev.GetLastErr());
1205 this->m_ctrRev2Edit.GetWindowText(str);
1207 if (!gitrev.GetCommit(str))
1209 gitrev.ApplyMailmap();
1210 m_rev2 = gitrev;
1211 mask |= 0x2;
1213 else
1215 m_cFileList.DeleteAllItems();
1216 CString msg;
1217 msg.Format(IDS_PROC_REFINVALID, static_cast<LPCWSTR>(str));
1218 m_cFileList.ShowText(msg + L'\n' + gitrev.GetLastErr());
1221 this->SetURLLabels(mask);
1223 if(mask == 0x3)
1225 if (InterlockedExchange(&m_bThreadRunning, TRUE))
1227 SetTimer(IDT_INPUT, 1000, nullptr);
1228 return;
1230 if (!AfxBeginThread(DiffThreadEntry, this))
1232 InterlockedExchange(&m_bThreadRunning, FALSE);
1233 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1237 if (nIDEvent == IDT_FILLPATCHVTIMER)
1238 FillPatchView();
1241 void CFileDiffDlg::Filter(const CString& sFilterText)
1243 m_filter = std::make_shared<CLogDlgFileFilter>(sFilterText, 0, 0, false);
1244 auto filter = *m_filter.get();
1246 m_arFilteredList.clear();
1247 for (int i=0;i<m_arFileList.GetCount();i++)
1249 if (filter(m_arFileList[i]))
1251 // Git 2.29.0 or later, --numstat doesn't show stats for the files with only ignored changes. This check hides such files.
1252 bool showItem = m_arFileList[i].IsDirectory() || !(m_arFileList[i].m_StatAdd.IsEmpty() && m_arFileList[i].m_StatDel.IsEmpty());
1253 if (showItem)
1254 m_arFilteredList.push_back(&m_arFileList[i]);
1257 for (const auto path : m_arFilteredList)
1258 AddEntry(path);
1261 void CFileDiffDlg::CopySelectionToClipboard(BOOL isFull)
1263 // copy all selected paths to the clipboard
1264 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1265 int index;
1266 CString sTextForClipboard;
1267 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1269 sTextForClipboard += m_cFileList.GetItemText(index, 0);
1270 sTextForClipboard += L'\t';
1272 if(!isFull)
1273 sTextForClipboard += L"\r\n";
1274 else
1276 sTextForClipboard += m_cFileList.GetItemText(index, 1);
1277 sTextForClipboard += L'\t';
1278 sTextForClipboard += m_cFileList.GetItemText(index, 2);
1279 sTextForClipboard += L'\t';
1280 sTextForClipboard += m_cFileList.GetItemText(index, 3);
1281 sTextForClipboard += L'\t';
1282 sTextForClipboard += m_cFileList.GetItemText(index, 4);
1283 sTextForClipboard += L"\r\n";
1286 CStringUtils::WriteAsciiStringToClipboard(sTextForClipboard);
1290 LRESULT CFileDiffDlg::OnRefLoad(WPARAM /*wParam*/, LPARAM /*lParam*/)
1292 for (size_t i = 0; i < m_Reflist.size(); ++i)
1294 CString str=m_Reflist[i];
1296 if (CStringUtils::StartsWith(str, L"remotes/"))
1297 str = str.Mid(static_cast<int>(wcslen(L"remotes/")));
1299 m_ctrRev1Edit.AddSearchString(str);
1300 m_ctrRev2Edit.AddSearchString(str);
1302 return 0;
1305 BOOL CFileDiffDlg::DestroyWindow()
1307 return CResizableStandAloneDialog::DestroyWindow();
1310 LRESULT CFileDiffDlg::OnEnUpdate(WPARAM /*wParam*/, LPARAM lParam)
1312 if(lParam == IDC_REV1EDIT)
1314 OnTextUpdate(&this->m_ctrRev1Edit);
1315 ClearURLabels(1);
1317 if(lParam == IDC_REV2EDIT)
1319 OnTextUpdate(&this->m_ctrRev2Edit);
1320 ClearURLabels(1<<1);
1322 return 0;
1325 void CFileDiffDlg::OnTextUpdate(CACEdit * /*pEdit*/)
1327 SetTimer(IDT_INPUT, 1000, nullptr);
1328 this->m_cFileList.ShowText(L"Wait For input validate version");
1331 int CFileDiffDlg::RevertSelectedItemToVersion(const CString& rev, bool isOldVersion)
1333 if (rev.IsEmpty() || rev == GIT_REV_ZERO)
1334 return 0;
1336 bool useRecycleBin = CRegDWORD(L"Software\\TortoiseGit\\RevertWithRecycleBin", TRUE);
1338 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1339 int index;
1340 int count = 0;
1341 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1343 CString cmd, out;
1344 auto fentry = m_arFilteredList[index];
1345 if ((isOldVersion && fentry->m_Action == CTGitPath::LOGACTIONS_ADDED) || (!isOldVersion && fentry->m_Action == CTGitPath::LOGACTIONS_DELETED))
1347 cmd.Format(L"git.exe rm --cached -- \"%s\"", static_cast<LPCWSTR>(fentry->GetGitPathString()));
1348 if ((isOldVersion && fentry->m_Action == CTGitPath::LOGACTIONS_ADDED && m_rev2.m_CommitHash.IsEmpty()) || (!isOldVersion && fentry->m_Action == CTGitPath::LOGACTIONS_DELETED && m_rev1.m_CommitHash.IsEmpty()))
1349 CTGitPath(g_Git.CombinePath(fentry->GetGitPathString())).Delete(useRecycleBin, true);
1351 else if (isOldVersion && fentry->m_Action == CTGitPath::LOGACTIONS_REPLACED)
1353 cmd.Format(L"git.exe checkout %s -- \"%s\"", static_cast<LPCWSTR>(rev), static_cast<LPCWSTR>(fentry->GetGitOldPathString()));
1354 if (m_rev2.m_CommitHash.IsEmpty())
1355 CTGitPath(g_Git.CombinePath(fentry->GetGitPathString())).Delete(useRecycleBin, true);
1357 else
1359 cmd.Format(L"git.exe checkout %s -- \"%s\"", static_cast<LPCWSTR>(rev), static_cast<LPCWSTR>(fentry->GetGitPathString()));
1360 if (!isOldVersion && fentry->m_Action == CTGitPath::LOGACTIONS_REPLACED && m_rev1.m_CommitHash.IsEmpty())
1361 CTGitPath(g_Git.CombinePath(fentry->GetGitOldPathString())).Delete(useRecycleBin, true);
1363 if (g_Git.Run(cmd, &out, CP_UTF8))
1365 if (CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", 2, IDI_WARNING, CString(MAKEINTRESOURCE(IDS_IGNOREBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
1366 break;
1368 else
1369 count++;
1372 CString out;
1373 out.FormatMessage(IDS_STATUSLIST_FILESREVERTED, count, static_cast<LPCWSTR>(rev));
1374 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK);
1375 return 0;
1378 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
1380 CString text;
1381 text.LoadString(nTextID);
1382 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
1385 #define DIFFOPTION_IGNORESPACEATEOL 1
1386 #define DIFFOPTION_IGNORESPACECHANGE 2
1387 #define DIFFOPTION_IGNOREALLSPACE 3
1388 #define DIFFOPTION_IGNORBLANKLINES 4
1389 #define DIFFOPTION_COMMONANCESTOR 5
1391 void CFileDiffDlg::OnBnClickedDiffoption()
1393 CMenu popup;
1394 if (popup.CreatePopupMenu())
1396 m_cDiffOptionsBtn.SetCheck(BST_CHECKED);
1397 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORESPACEATEOL, DIFFOPTION_IGNORESPACEATEOL, m_bIgnoreSpaceAtEol);
1398 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORESPACECHANGE, DIFFOPTION_IGNORESPACECHANGE, m_bIgnoreSpaceChange);
1399 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNOREALLSPACE, DIFFOPTION_IGNOREALLSPACE, m_bIgnoreAllSpace);
1400 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORBLANKLINES, DIFFOPTION_IGNORBLANKLINES, m_bIgnoreBlankLines);
1401 popup.AppendMenu(MF_SEPARATOR);
1402 AppendMenuChecked(popup, IDS_COMMON_ANCESTOR, DIFFOPTION_COMMONANCESTOR, m_bCommonAncestorDiff);
1404 m_tooltips.Pop();
1405 RECT rect;
1406 GetDlgItem(IDC_DIFFOPTION)->GetWindowRect(&rect);
1407 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.bottom, this);
1408 switch (selection)
1410 case DIFFOPTION_IGNORESPACEATEOL:
1411 m_bIgnoreSpaceAtEol = !m_bIgnoreSpaceAtEol;
1412 OnTimer(IDT_INPUT);
1413 break;
1414 case DIFFOPTION_IGNORESPACECHANGE:
1415 m_bIgnoreSpaceChange = !m_bIgnoreSpaceChange;
1416 OnTimer(IDT_INPUT);
1417 break;
1418 case DIFFOPTION_IGNOREALLSPACE:
1419 m_bIgnoreAllSpace = !m_bIgnoreAllSpace;
1420 OnTimer(IDT_INPUT);
1421 break;
1422 case DIFFOPTION_IGNORBLANKLINES:
1423 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1424 OnTimer(IDT_INPUT);
1425 break;
1426 case DIFFOPTION_COMMONANCESTOR:
1427 m_bCommonAncestorDiff = !m_bCommonAncestorDiff;
1428 OnTimer(IDT_INPUT);
1429 break;
1430 default:
1431 break;
1433 UpdateData(FALSE);
1434 m_cDiffOptionsBtn.SetCheck((m_bIgnoreSpaceAtEol || m_bIgnoreSpaceChange || m_bIgnoreAllSpace || m_bIgnoreBlankLines || m_bCommonAncestorDiff) ? BST_CHECKED : BST_UNCHECKED);
1438 void CFileDiffDlg::OnBnClickedLog()
1440 CString cmd;
1441 cmd.Format(L"/command:log /range:%s..%s", static_cast<LPCWSTR>(m_rev1.m_CommitHash.ToString()), static_cast<LPCWSTR>(m_rev2.m_CommitHash.ToString()));
1442 CAppUtils::RunTortoiseGitProc(cmd);
1445 bool CFileDiffDlg::CheckMultipleDiffs()
1447 UINT selCount = m_cFileList.GetSelectedCount();
1448 if (selCount > max(DWORD(3), static_cast<DWORD>(CRegDWORD(L"Software\\TortoiseGit\\NumDiffWarning", 10))))
1450 CString message;
1451 message.Format(IDS_STATUSLIST_WARN_MAXDIFF, selCount);
1452 return ::MessageBox(GetSafeHwnd(), message, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) == IDYES;
1454 return true;
1457 void CFileDiffDlg::OnLvnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
1459 *pResult = 0;
1461 // get selected paths
1462 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1463 if (!pos)
1464 return;
1466 CTGitPathList toExport;
1467 int index = -1;
1468 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1470 auto fentry = m_arFilteredList[index];
1471 toExport.AddPath(*fentry);
1475 // build copy source / content
1476 auto pdsrc = std::make_unique<CIDropSource>();
1477 if (!pdsrc)
1478 return;
1480 pdsrc->AddRef();
1482 GitDataObject* pdobj = new GitDataObject(toExport, m_rev2.m_CommitHash);
1483 if (!pdobj)
1484 return;
1485 pdobj->AddRef();
1486 pdobj->SetAsyncMode(TRUE);
1487 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1488 CDragSourceHelper dragsrchelper;
1489 dragsrchelper.InitializeFromWindow(GetSafeHwnd(), pNMLV->ptAction, pdobj);
1490 pdsrc->m_pIDataObj = pdobj;
1491 pdsrc->m_pIDataObj->AddRef();
1493 // Initiate the Drag & Drop
1494 DWORD dwEffect;
1495 ::DoDragDrop(pdobj, pdsrc.get(), DROPEFFECT_MOVE | DROPEFFECT_COPY, &dwEffect);
1496 pdsrc->Release();
1497 pdsrc.release();
1498 pdobj->Release();
1501 void CFileDiffDlg::FillPatchView(bool onlySetTimer)
1503 if (!::IsWindow(this->m_patchViewdlg.m_hWnd))
1504 return;
1506 KillTimer(LOG_FILLPATCHVTIMER);
1507 if (onlySetTimer)
1509 SetTimer(LOG_FILLPATCHVTIMER, 100, nullptr);
1510 return;
1513 if (m_cFileList.HasText() || m_cFileList.GetItemCount() == 0)
1515 m_patchViewdlg.ClearView();
1516 return;
1519 CString ignore;
1520 if (m_bIgnoreSpaceAtEol)
1521 ignore += L" --ignore-space-at-eol";
1522 if (m_bIgnoreSpaceChange)
1523 ignore += L" --ignore-space-change";
1524 if (m_bIgnoreAllSpace)
1525 ignore += L" --ignore-all-space";
1526 if (m_bIgnoreBlankLines)
1527 ignore += L" --ignore-blank-lines";
1529 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1530 CString out;
1531 if (!pos)
1533 CString cmd;
1534 if (m_rev2.m_CommitHash.IsEmpty())
1535 cmd.Format(L"git.exe diff%s --stat -p %s --", static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(m_rev1.m_CommitHash.ToString()));
1536 else if (m_rev1.m_CommitHash.IsEmpty())
1537 cmd.Format(L"git.exe diff%s --stat -Rp %s --", static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(m_rev2.m_CommitHash.ToString()));
1538 else
1539 cmd.Format(L"git.exe diff%s --stat -p %s..%s --", static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(m_rev1.m_CommitHash.ToString()), static_cast<LPCWSTR>(m_rev2.m_CommitHash.ToString()));
1540 g_Git.Run(cmd, &out, CP_UTF8);
1542 else
1544 while (pos)
1546 int nSelect = m_cFileList.GetNextSelectedItem(pos);
1547 auto fentry = m_arFilteredList[nSelect];
1548 CString cmd;
1549 if (m_rev2.m_CommitHash.IsEmpty())
1550 cmd.Format(L"git.exe diff%s %s -- \"%s\"", static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(m_rev1.m_CommitHash.ToString()), static_cast<LPCWSTR>(fentry->GetGitPathString()));
1551 else if (m_rev1.m_CommitHash.IsEmpty())
1552 cmd.Format(L"git.exe diff%s -R %s -- \"%s\"", static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(m_rev2.m_CommitHash.ToString()), static_cast<LPCWSTR>(fentry->GetGitPathString()));
1553 else
1554 cmd.Format(L"git.exe diff%s %s..%s -- \"%s\"", static_cast<LPCWSTR>(ignore), static_cast<LPCWSTR>(m_rev1.m_CommitHash.ToString()), static_cast<LPCWSTR>(m_rev2.m_CommitHash.ToString()), static_cast<LPCWSTR>(fentry->GetGitPathString()));
1555 g_Git.Run(cmd, &out, CP_UTF8);
1559 m_patchViewdlg.SetText(out);
1562 void CFileDiffDlg::TogglePatchView()
1564 OnStnClickedViewPatch();
1567 void CFileDiffDlg::OnStnClickedViewPatch()
1569 m_patchViewdlg.m_ParentDlg = this;
1570 if (!IsWindow(m_patchViewdlg.m_hWnd))
1572 if (g_Git.GetConfigValueBool(L"tgit.diffshowpatch") == FALSE)
1573 g_Git.SetConfigValue(L"tgit.diffshowpatch", L"true");
1574 m_patchViewdlg.Create(IDD_PATCH_VIEW, this);
1575 m_patchViewdlg.ShowAndAlignToParent();
1577 FillPatchView();
1579 m_ctrlShowPatch.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_COMMIT_HIDEPATCH)));
1581 else
1583 g_Git.SetConfigValue(L"tgit.diffshowpatch", L"false");
1584 m_patchViewdlg.ShowWindow(SW_HIDE);
1585 m_patchViewdlg.DestroyWindow();
1586 m_ctrlShowPatch.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_COMMIT_SHOWPATCH)));
1588 m_ctrlShowPatch.Invalidate();
1591 void CFileDiffDlg::OnFileListItemChanged(NMHDR* /*pNMHDR*/, LRESULT* /*pResult*/)
1593 FillPatchView(true);
1596 void CFileDiffDlg::OnMoving(UINT fwSide, LPRECT pRect)
1598 __super::OnMoving(fwSide, pRect);
1600 m_patchViewdlg.ParentOnMoving(m_hWnd, pRect);
1603 void CFileDiffDlg::OnSizing(UINT fwSide, LPRECT pRect)
1605 __super::OnSizing(fwSide, pRect);
1607 m_patchViewdlg.ParentOnSizing(m_hWnd, pRect);