Fixed issue #3307: Abort Merge on a single file always results in a parameter error...
[TortoiseGit.git] / src / TortoiseProc / FileDiffDlg.cpp
blob093542eaf0853e038f2b905586192458325fe715
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2008-2018 - 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"
41 #define ID_COMPARE 1
42 #define ID_BLAME 2
43 #define ID_SAVEAS 3
44 #define ID_EXPORT 4
45 #define ID_CLIPBOARD_PATH 5
46 #define ID_CLIPBOARD_ALL 6
47 #define ID_LOG 7
48 #define ID_GNUDIFFCOMPARE 8
49 #define ID_REVERT1 9
50 #define ID_REVERT2 10
51 #define ID_LOGSUBMODULE 11
53 BOOL CFileDiffDlg::m_bAscending = TRUE;
54 int CFileDiffDlg::m_nSortedColumn = -1;
56 UINT CFileDiffDlg::WM_DISABLEBUTTONS = RegisterWindowMessage(L"TORTOISEGIT_FILEDIFF_DISABLEBUTTONS");
57 UINT CFileDiffDlg::WM_DIFFFINISHED = RegisterWindowMessage(L"TORTOISEGIT_FILEDIFF_DIFFFINISHED");
59 IMPLEMENT_DYNAMIC(CFileDiffDlg, CResizableStandAloneDialog)
60 CFileDiffDlg::CFileDiffDlg(CWnd* pParent /*=nullptr*/)
61 : CResizableStandAloneDialog(CFileDiffDlg::IDD, pParent)
62 , m_bBlame(false)
63 , m_nIconFolder(0)
64 , m_bThreadRunning(FALSE)
65 , m_bIgnoreSpaceAtEol(false)
66 , m_bIgnoreSpaceChange(false)
67 , m_bIgnoreAllSpace(false)
68 , m_bIgnoreBlankLines(false)
69 , m_bIsBare(false)
70 , m_bLoadingRef(FALSE)
74 CFileDiffDlg::~CFileDiffDlg()
78 void CFileDiffDlg::DoDataExchange(CDataExchange* pDX)
80 CResizableStandAloneDialog::DoDataExchange(pDX);
81 DDX_Control(pDX, IDC_FILELIST, m_cFileList);
82 DDX_Control(pDX, IDC_SWITCHLEFTRIGHT, m_SwitchButton);
83 DDX_Control(pDX, IDC_REV1BTN, m_cRev1Btn);
84 DDX_Control(pDX, IDC_REV2BTN, m_cRev2Btn);
85 DDX_Control(pDX, IDC_FILTER, m_cFilter);
86 DDX_Control(pDX, IDC_REV1EDIT, m_ctrRev1Edit);
87 DDX_Control(pDX, IDC_REV2EDIT, m_ctrRev2Edit);
88 DDX_Control(pDX, IDC_DIFFOPTION, m_cDiffOptionsBtn);
92 BEGIN_MESSAGE_MAP(CFileDiffDlg, CResizableStandAloneDialog)
93 ON_NOTIFY(NM_DBLCLK, IDC_FILELIST, OnNMDblclkFilelist)
94 ON_NOTIFY(LVN_GETINFOTIP, IDC_FILELIST, OnLvnGetInfoTipFilelist)
95 ON_NOTIFY(NM_CUSTOMDRAW, IDC_FILELIST, OnNMCustomdrawFilelist)
96 ON_WM_CONTEXTMENU()
97 ON_WM_SETCURSOR()
98 ON_EN_SETFOCUS(IDC_SECONDURL, &CFileDiffDlg::OnEnSetfocusSecondurl)
99 ON_EN_SETFOCUS(IDC_FIRSTURL, &CFileDiffDlg::OnEnSetfocusFirsturl)
100 ON_BN_CLICKED(IDC_SWITCHLEFTRIGHT, &CFileDiffDlg::OnBnClickedSwitchleftright)
101 ON_NOTIFY(HDN_ITEMCLICK, 0, &CFileDiffDlg::OnHdnItemclickFilelist)
102 ON_BN_CLICKED(IDC_REV1BTN, &CFileDiffDlg::OnBnClickedRev1btn)
103 ON_BN_CLICKED(IDC_REV2BTN, &CFileDiffDlg::OnBnClickedRev2btn)
104 ON_REGISTERED_MESSAGE(CFilterEdit::WM_FILTEREDIT_CANCELCLICKED, OnClickedCancelFilter)
105 ON_EN_CHANGE(IDC_FILTER, &CFileDiffDlg::OnEnChangeFilter)
106 ON_WM_TIMER()
107 ON_MESSAGE(ENAC_UPDATE, &CFileDiffDlg::OnEnUpdate)
108 ON_MESSAGE(MSG_REF_LOADED, OnRefLoad)
109 ON_REGISTERED_MESSAGE(WM_DISABLEBUTTONS, OnDisableButtons)
110 ON_REGISTERED_MESSAGE(WM_DIFFFINISHED, OnDiffFinished)
111 ON_BN_CLICKED(IDC_DIFFOPTION, OnBnClickedDiffoption)
112 ON_BN_CLICKED(IDC_LOG, &CFileDiffDlg::OnBnClickedLog)
113 ON_NOTIFY(LVN_BEGINDRAG, IDC_FILELIST, OnLvnBegindrag)
114 END_MESSAGE_MAP()
117 void CFileDiffDlg::SetDiff(const CTGitPath* path, const GitRev& baseRev1, const GitRev& rev2)
119 if (path)
121 m_path1 = *path;
122 m_path2 = *path;
123 m_sFilter = path->GetGitPathString();
125 m_rev1 = baseRev1;
126 m_rev2 = rev2;
129 void CFileDiffDlg::SetDiff(const CTGitPath* path, const CString &baseRev1, const CString& hash2)
131 if (path)
133 m_path1 = *path;
134 m_path2 = *path;
135 m_sFilter = path->GetGitPathString();
138 BYTE_VECTOR logout;
140 if (baseRev1 == GIT_REV_ZERO)
142 m_rev1.m_CommitHash.Empty();
143 m_rev1.GetSubject().LoadString(IDS_WORKING_TREE);
145 else
147 if (m_rev1.GetCommit(baseRev1))
148 MessageBox(m_rev1.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
151 logout.clear();
153 if(hash2 == GIT_REV_ZERO)
155 m_rev2.m_CommitHash.Empty();
156 m_rev2.GetSubject().LoadString(IDS_WORKING_TREE);
158 else
160 if (m_rev2.GetCommit(hash2))
161 MessageBox(m_rev2.GetLastErr(), L"TortoiseGit", MB_ICONERROR);
165 void CFileDiffDlg::SetDiff(const CTGitPath* path, const GitRev &baseRev1)
167 if (path)
169 m_path1 = *path;
170 m_path2 = *path;
171 m_sFilter = path->GetGitPathString();
173 m_rev1 = baseRev1;
174 m_rev2.m_CommitHash.Empty();
175 m_rev2.GetSubject().LoadString(IDS_PROC_PREVIOUSVERSION);
177 //this->GetDlgItem()->EnableWindow(FALSE);
180 BOOL CFileDiffDlg::OnInitDialog()
182 CResizableStandAloneDialog::OnInitDialog();
183 CString temp;
185 CString sWindowTitle;
186 GetWindowText(sWindowTitle);
187 CString pathText = g_Git.m_CurrentDir;
188 if (!m_path1.IsEmpty())
189 pathText = g_Git.CombinePath(m_path1);
190 CAppUtils::SetWindowTitle(m_hWnd, pathText, sWindowTitle);
192 this->m_ctrRev1Edit.Init();
193 this->m_ctrRev2Edit.Init();
195 m_tooltips.AddTool(IDC_SWITCHLEFTRIGHT, IDS_FILEDIFF_SWITCHLEFTRIGHT_TT);
197 m_cFileList.SetRedraw(false);
198 m_cFileList.DeleteAllItems();
199 DWORD exStyle = LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP;
200 if (CRegDWORD(L"Software\\TortoiseGit\\FullRowSelect", TRUE))
201 exStyle |= LVS_EX_FULLROWSELECT;
202 m_cFileList.SetExtendedStyle(exStyle);
204 m_nIconFolder = SYS_IMAGE_LIST().GetDirIconIndex();
205 m_cFileList.SetImageList(&SYS_IMAGE_LIST(), LVSIL_SMALL);
207 int iconWidth = GetSystemMetrics(SM_CXSMICON);
208 int iconHeight = GetSystemMetrics(SM_CYSMICON);
209 m_SwitchButton.SetImage(CCommonAppUtils::LoadIconEx(IDI_SWITCHLEFTRIGHT, iconWidth, iconHeight));
210 m_SwitchButton.Invalidate();
212 m_cFilter.SetCancelBitmaps(IDI_CANCELNORMAL, IDI_CANCELPRESSED, 14, 14);
213 m_cFilter.SetInfoIcon(IDI_LOGFILTER, 19, 19);
214 temp.LoadString(IDS_FILEDIFF_FILTERCUE);
215 temp = L" " + temp;
216 m_cFilter.SetCueBanner(temp);
217 if (!m_sFilter.IsEmpty())
218 m_cFilter.SetWindowText(m_sFilter);
220 int c = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
221 while (c>=0)
222 m_cFileList.DeleteColumn(c--);
224 temp.LoadString(IDS_FILEDIFF_FILE);
225 m_cFileList.InsertColumn(0, temp);
226 temp.LoadString(IDS_FILEDIFF_EXT);
227 m_cFileList.InsertColumn(1, temp);
228 temp.LoadString(IDS_FILEDIFF_ACTION);
229 m_cFileList.InsertColumn(2, temp);
231 temp.LoadString(IDS_FILEDIFF_STATADD);
232 m_cFileList.InsertColumn(3, temp);
233 temp.LoadString(IDS_FILEDIFF_STATDEL);
234 m_cFileList.InsertColumn(4, temp);
236 int mincol = 0;
237 int maxcol = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
238 int col;
239 for (col = mincol; col <= maxcol; col++)
240 m_cFileList.SetColumnWidth(col,LVSCW_AUTOSIZE_USEHEADER);
242 m_cFileList.SetRedraw(true);
244 AddAnchor(IDC_DIFFSTATIC1, TOP_LEFT, TOP_RIGHT);
245 AddAnchor(IDC_SWITCHLEFTRIGHT, TOP_RIGHT);
246 AddAnchor(IDC_FIRSTURL, TOP_LEFT, TOP_RIGHT);
247 AddAnchor(IDC_REV1BTN, TOP_RIGHT);
248 //AddAnchor(IDC_DIFFSTATIC2, TOP_LEFT, TOP_RIGHT);
249 AddAnchor(IDC_SECONDURL, TOP_LEFT, TOP_RIGHT);
250 AddAnchor(IDC_REV2BTN, TOP_RIGHT);
251 AddAnchor(IDC_FILTER, TOP_LEFT, TOP_RIGHT);
252 AddAnchor(IDC_FILELIST, TOP_LEFT, BOTTOM_RIGHT);
253 AddAnchor(IDC_REV1GROUP,TOP_LEFT,TOP_RIGHT);
254 AddAnchor(IDC_REV2GROUP,TOP_LEFT,TOP_RIGHT);
255 AddAnchor(IDC_REV1EDIT,TOP_LEFT);
256 AddAnchor(IDC_REV2EDIT,TOP_LEFT);
257 AddAnchor(IDC_DIFFOPTION, TOP_RIGHT);
258 AddAnchor(IDC_LOG, TOP_RIGHT);
260 EnableSaveRestore(L"FileDiffDlg");
262 m_bIsBare = GitAdminDir::IsBareRepo(g_Git.m_CurrentDir);
264 if(this->m_strRev1.IsEmpty())
265 this->m_ctrRev1Edit.SetWindowText(this->m_rev1.m_CommitHash.ToString());
266 else
268 if (m_rev1.GetCommit(m_strRev1))
270 CString msg;
271 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)m_strRev1);
272 m_cFileList.ShowText(msg + L'\n' + m_rev1.GetLastErr());
275 this->m_ctrRev1Edit.SetWindowText(m_strRev1);
278 if(this->m_strRev2.IsEmpty())
279 this->m_ctrRev2Edit.SetWindowText(this->m_rev2.m_CommitHash.ToString());
280 else
282 if (m_rev2.GetCommit(m_strRev2))
284 CString msg;
285 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)m_strRev2);
286 m_cFileList.ShowText(msg + L'\n' + m_rev1.GetLastErr());
289 this->m_ctrRev2Edit.SetWindowText(m_strRev2);
292 SetURLLabels();
294 InterlockedExchange(&m_bThreadRunning, TRUE);
295 if (!AfxBeginThread(DiffThreadEntry, this))
297 InterlockedExchange(&m_bThreadRunning, FALSE);
298 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
301 InterlockedExchange(&m_bLoadingRef, TRUE);
302 if (!AfxBeginThread(LoadRefThreadEntry, this))
304 InterlockedExchange(&m_bLoadingRef, FALSE);
305 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
308 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
309 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
310 this->m_cRev1Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
312 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFBROWSE)));
313 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_LOG)));
314 this->m_cRev2Btn.AddEntry(CString(MAKEINTRESOURCE(IDS_REFLOG)));
316 // Start with focus on file list
317 GetDlgItem(IDC_FILELIST)->SetFocus();
319 if(m_rev2.m_CommitHash.IsEmpty())
320 m_SwitchButton.EnableWindow(FALSE);
322 m_cDiffOptionsBtn.m_bAlwaysShowArrow = true;
324 KillTimer(IDT_INPUT);
325 return FALSE;
328 UINT CFileDiffDlg::DiffThreadEntry(LPVOID pVoid)
330 return reinterpret_cast<CFileDiffDlg*>(pVoid)->DiffThread();
333 UINT CFileDiffDlg::DiffThread()
335 SendMessage(WM_DISABLEBUTTONS);
337 if( m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty())
338 g_Git.RefreshGitIndex();
340 g_Git.GetCommitDiffList(m_rev2.m_CommitHash.ToString(), m_rev1.m_CommitHash.ToString(), m_arFileList, m_bIgnoreSpaceAtEol, m_bIgnoreSpaceChange, m_bIgnoreAllSpace, m_bIgnoreBlankLines);
341 Sort();
343 SendMessage(WM_DIFFFINISHED);
345 InterlockedExchange(&m_bThreadRunning, FALSE);
346 return 0;
349 LRESULT CFileDiffDlg::OnDisableButtons(WPARAM, LPARAM)
351 RefreshCursor();
352 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_FILEDIFF_WAIT)));
353 m_cFileList.DeleteAllItems();
354 m_arFileList.Clear();
355 EnableInputControl(false);
356 return 0;
359 LRESULT CFileDiffDlg::OnDiffFinished(WPARAM, LPARAM)
361 CString sFilterText;
362 m_cFilter.GetWindowText(sFilterText);
363 m_cFileList.SetRedraw(false);
364 Filter(sFilterText);
365 if (!m_arFileList.IsEmpty())
367 // Highlight first entry in file list
368 m_cFileList.SetSelectionMark(0);
369 m_cFileList.SetItemState(0, LVIS_SELECTED, LVIS_SELECTED);
372 int mincol = 0;
373 int maxcol = m_cFileList.GetHeaderCtrl()->GetItemCount() - 1;
374 int col;
375 for (col = mincol; col <= maxcol; ++col)
376 m_cFileList.SetColumnWidth(col, LVSCW_AUTOSIZE_USEHEADER);
378 m_cFileList.ClearText();
379 if (m_arFileList.IsEmpty())
380 m_cFileList.ShowText(CString(MAKEINTRESOURCE(IDS_COMPAREREV_NODIFF)));
381 m_cFileList.SetRedraw(true);
383 InvalidateRect(nullptr);
384 RefreshCursor();
385 EnableInputControl(true);
386 return 0;
389 int CFileDiffDlg::AddEntry(const CTGitPath * fd)
391 int ret = -1;
392 if (fd)
394 int index = m_cFileList.GetItemCount();
396 int icon_idx = 0;
397 if (fd->IsDirectory())
398 icon_idx = m_nIconFolder;
399 else
400 icon_idx = SYS_IMAGE_LIST().GetPathIconIndex(fd->GetGitPathString());
402 ret = m_cFileList.InsertItem(index, fd->GetGitPathString(), icon_idx);
403 m_cFileList.SetItemText(index, 1, fd->GetFileExtension());
404 m_cFileList.SetItemText(index, 2, fd->GetActionName());
405 m_cFileList.SetItemText(index, 3, fd->m_StatAdd);
406 m_cFileList.SetItemText(index, 4, fd->m_StatDel);
408 return ret;
411 void CFileDiffDlg::EnableInputControl(bool b)
413 this->m_ctrRev1Edit.EnableWindow(b);
414 this->m_ctrRev2Edit.EnableWindow(b);
415 this->m_cRev1Btn.EnableWindow(b);
416 this->m_cRev2Btn.EnableWindow(b);
417 m_cFilter.EnableWindow(b);
418 m_SwitchButton.EnableWindow(b);
419 GetDlgItem(IDC_LOG)->EnableWindow(b && !(m_rev1.m_CommitHash.IsEmpty() || m_rev2.m_CommitHash.IsEmpty()));
422 void CFileDiffDlg::DoDiff(int selIndex, bool blame)
424 CTGitPath* fd2 = m_arFilteredList[selIndex];
425 CTGitPath* fd1 = fd2;
426 if (m_rev2.m_CommitHash.IsEmpty() && g_Git.IsInitRepos())
428 CGitDiff::DiffNull(GetSafeHwnd(), fd2, GIT_REV_ZERO, true, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
429 return;
431 if (fd1->m_Action & CTGitPath::LOGACTIONS_ADDED)
433 CGitDiff::DiffNull(GetSafeHwnd(), fd1, m_rev2.m_CommitHash.ToString(), true, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
434 return;
436 if (fd1->m_Action & CTGitPath::LOGACTIONS_DELETED)
438 CGitDiff::DiffNull(GetSafeHwnd(), fd1, m_rev1.m_CommitHash.ToString(), false, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
439 return;
441 if (fd1->m_Action & CTGitPath::LOGACTIONS_REPLACED)
442 fd2 = new CTGitPath(fd1->GetGitOldPathString());
443 CGitDiff::Diff(GetSafeHwnd(), fd1, fd2, m_rev2.m_CommitHash.ToString(), m_rev1.m_CommitHash.ToString(), blame, FALSE, 0, !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
444 if (fd1 != fd2)
445 delete fd2;
449 void CFileDiffDlg::OnNMDblclkFilelist(NMHDR *pNMHDR, LRESULT *pResult)
451 *pResult = 0;
452 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
453 int selIndex = pNMLV->iItem;
454 if (selIndex < 0)
455 return;
456 if (selIndex >= (int)m_arFilteredList.size())
457 return;
459 DoDiff(selIndex, m_bBlame);
462 void CFileDiffDlg::OnLvnGetInfoTipFilelist(NMHDR *pNMHDR, LRESULT *pResult)
464 LPNMLVGETINFOTIP pGetInfoTip = reinterpret_cast<LPNMLVGETINFOTIP>(pNMHDR);
465 if (pGetInfoTip->iItem >= (int)m_arFilteredList.size())
466 return;
468 CString path = m_path1.GetGitPathString() + L'/' + m_arFilteredList[pGetInfoTip->iItem]->GetGitPathString();
469 if (pGetInfoTip->cchTextMax > path.GetLength())
470 wcsncpy_s(pGetInfoTip->pszText, pGetInfoTip->cchTextMax, path, pGetInfoTip->cchTextMax - 1);
472 *pResult = 0;
475 void CFileDiffDlg::OnNMCustomdrawFilelist(NMHDR *pNMHDR, LRESULT *pResult)
477 NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>( pNMHDR );
478 // Take the default processing unless we set this to something else below.
479 *pResult = CDRF_DODEFAULT;
481 // First thing - check the draw stage. If it's the control's prepaint
482 // stage, then tell Windows we want messages for every item.
484 if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
486 *pResult = CDRF_NOTIFYITEMDRAW;
488 else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
490 // This is the prepaint stage for an item. Here's where we set the
491 // item's text color. Our return value will tell Windows to draw the
492 // item itself, but it will use the new color we set here.
494 // Tell Windows to paint the control itself.
495 *pResult = CDRF_DODEFAULT;
497 COLORREF crText = GetSysColor(COLOR_WINDOWTEXT);
499 if (m_arFilteredList.size() > pLVCD->nmcd.dwItemSpec)
501 CTGitPath * fd = m_arFilteredList[pLVCD->nmcd.dwItemSpec];
502 switch (fd->m_Action)
504 case CTGitPath::LOGACTIONS_ADDED:
505 crText = m_colors.GetColor(CColors::Added);
506 break;
507 case CTGitPath::LOGACTIONS_DELETED:
508 crText = m_colors.GetColor(CColors::Deleted);
509 break;
510 case CTGitPath::LOGACTIONS_MODIFIED:
511 crText = m_colors.GetColor(CColors::Modified);
512 break;
513 default:
514 crText = m_colors.GetColor(CColors::PropertyChanged);
515 break;
518 // Store the color back in the NMLVCUSTOMDRAW struct.
519 pLVCD->clrText = crText;
523 UINT CFileDiffDlg::LoadRefThread()
525 g_Git.GetBranchList(m_Reflist, nullptr, CGit::BRANCH_ALL_F);
526 g_Git.GetTagList(m_Reflist);
528 this->PostMessage(MSG_REF_LOADED);
529 InterlockedExchange(&m_bLoadingRef, FALSE);
530 return 0;
533 static CString GetCommitTitle(const GitRev& rev)
535 CString str;
536 CString commitTitle = rev.GetSubject();
537 if (commitTitle.GetLength() > 20)
539 commitTitle.Truncate(20);
540 commitTitle += L"...";
542 str.AppendFormat(L"%s (%s)", (LPCTSTR)rev.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()), (LPCTSTR)commitTitle);
543 return str;
546 void CFileDiffDlg::OnContextMenu(CWnd* pWnd, CPoint point)
548 if (!pWnd || pWnd != &m_cFileList)
549 return;
550 if (m_cFileList.GetSelectedCount() == 0)
551 return;
552 // if the context menu is invoked through the keyboard, we have to use
553 // a calculated position on where to anchor the menu on
554 if ((point.x == -1) && (point.y == -1))
556 CRect rect;
557 m_cFileList.GetItemRect(m_cFileList.GetSelectionMark(), &rect, LVIR_LABEL);
558 m_cFileList.ClientToScreen(&rect);
559 point = rect.CenterPoint();
561 CIconMenu popup;
562 if (popup.CreatePopupMenu())
564 int firstEntry = -1;
565 POSITION firstPos = m_cFileList.GetFirstSelectedItemPosition();
566 if (firstPos)
567 firstEntry = m_cFileList.GetNextSelectedItem(firstPos);
569 CString menuText;
570 popup.AppendMenuIcon(ID_COMPARE, IDS_LOG_POPUP_COMPARETWO, IDI_DIFF);
571 popup.SetDefaultItem(ID_COMPARE, FALSE);
572 popup.AppendMenuIcon(ID_GNUDIFFCOMPARE, IDS_LOG_POPUP_GNUDIFF, IDI_DIFF);
573 popup.AppendMenu(MF_SEPARATOR, NULL);
574 if (!m_bIsBare)
576 if (!m_rev1.m_CommitHash.IsEmpty())
578 menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, (LPCTSTR)GetCommitTitle(m_rev1));
579 popup.AppendMenuIcon(ID_REVERT1, menuText, IDI_REVERT);
581 if (!m_rev2.m_CommitHash.IsEmpty())
583 menuText.Format(IDS_FILEDIFF_POPREVERTTOREV, (LPCTSTR)GetCommitTitle(m_rev2));
584 popup.AppendMenuIcon(ID_REVERT2, menuText, IDI_REVERT);
586 popup.AppendMenu(MF_SEPARATOR, NULL);
588 popup.AppendMenuIcon(ID_LOG, IDS_FILEDIFF_LOG, IDI_LOG);
589 if (firstEntry >= 0 && !m_arFilteredList[firstEntry]->IsDirectory())
591 if (!m_bIsBare)
593 popup.AppendMenuIcon(ID_BLAME, IDS_FILEDIFF_POPBLAME, IDI_BLAME);
594 popup.AppendMenu(MF_SEPARATOR, NULL);
596 popup.AppendMenuIcon(ID_EXPORT, IDS_FILEDIFF_POPEXPORT, IDI_EXPORT);
598 else if (firstEntry >= 0)
599 popup.AppendMenuIcon(ID_LOGSUBMODULE, IDS_MENULOGSUBMODULE, IDI_LOG);
600 popup.AppendMenu(MF_SEPARATOR, NULL);
601 popup.AppendMenuIcon(ID_SAVEAS, IDS_FILEDIFF_POPSAVELIST, IDI_SAVEAS);
602 popup.AppendMenuIcon(ID_CLIPBOARD_PATH, IDS_STATUSLIST_CONTEXT_COPY, IDI_COPYCLIP);
603 popup.AppendMenuIcon(ID_CLIPBOARD_ALL, IDS_STATUSLIST_CONTEXT_COPYEXT, IDI_COPYCLIP);
605 int cmd = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, point.x, point.y, this);
606 switch (cmd)
608 case ID_COMPARE:
610 if (!CheckMultipleDiffs())
611 break;
612 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
613 while (pos)
615 int index = m_cFileList.GetNextSelectedItem(pos);
616 DoDiff(index, false);
619 break;
620 case ID_GNUDIFFCOMPARE:
622 if (!CheckMultipleDiffs())
623 break;
624 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
625 while (pos)
627 CTGitPath *fd2 = m_arFilteredList[m_cFileList.GetNextSelectedItem(pos)];
628 CTGitPath *fd1 = fd2;
629 if (fd1->m_Action & CTGitPath::LOGACTIONS_REPLACED)
630 fd2 = new CTGitPath(fd2->GetGitOldPathString());
631 CAppUtils::StartShowUnifiedDiff(m_hWnd, *fd1, m_rev1.m_CommitHash.ToString(), *fd2, m_rev2.m_CommitHash.ToString(), !!(GetAsyncKeyState(VK_SHIFT) & 0x8000));
632 if (fd1 != fd2)
633 delete fd2;
636 break;
637 case ID_REVERT1:
638 RevertSelectedItemToVersion(m_rev1.m_CommitHash.ToString());
639 break;
640 case ID_REVERT2:
641 RevertSelectedItemToVersion(m_rev2.m_CommitHash.ToString());
642 break;
643 case ID_BLAME:
645 if (!CheckMultipleDiffs())
646 break;
647 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
648 while (pos)
650 int index = m_cFileList.GetNextSelectedItem(pos);
651 if (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_DELETED)
653 if (!m_rev1.m_CommitHash.IsEmpty())
654 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev1.m_CommitHash.ToString());
655 continue;
657 if (m_rev2.m_CommitHash.IsEmpty() && (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_ADDED))
658 continue;
659 if (m_rev2.m_CommitHash.IsEmpty() && (m_arFilteredList[index]->m_Action & CTGitPath::LOGACTIONS_REPLACED))
661 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetGitOldPathString(), m_rev1.m_CommitHash.ToString());
662 continue;
664 CAppUtils::LaunchTortoiseBlame(m_arFilteredList[index]->GetWinPathString(), m_rev2.m_CommitHash.ToString());
667 break;
668 case ID_LOG:
669 case ID_LOGSUBMODULE:
671 if (!CheckMultipleDiffs())
672 break;
673 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
674 while (pos)
676 int index = m_cFileList.GetNextSelectedItem(pos);
677 CString sCmd = L"/command:log";
678 if (sCmd == ID_LOGSUBMODULE)
679 sCmd += L" /submodule";
680 sCmd += L" /path:\"" + m_arFilteredList[index]->GetWinPathString() + L"\" ";
681 sCmd += L" /endrev:" + m_rev2.m_CommitHash.ToString();
682 CAppUtils::RunTortoiseGitProc(sCmd);
685 break;
686 case ID_SAVEAS:
688 if (m_cFileList.GetSelectedCount() > 0)
690 CTGitPath savePath;
691 CString pathSave;
692 if (!CAppUtils::FileOpenSave(pathSave, nullptr, IDS_FILEDIFF_POPSAVELIST, IDS_TEXTFILEFILTER, false, m_hWnd, L"txt"))
693 break;
694 savePath = CTGitPath(pathSave);
696 // now open the selected file for writing
699 CStdioFile file(savePath.GetWinPathString(), CFile::typeBinary | CFile::modeReadWrite | CFile::modeCreate);
700 CString temp;
701 if (m_path1.IsEmpty() && m_path2.IsEmpty())
702 temp.FormatMessage(IDS_FILEDIFF_CHANGEDLISTINTROROOT, (LPCTSTR)m_rev1.m_CommitHash.ToString(), (LPCTSTR)m_rev2.m_CommitHash.ToString());
703 else
704 temp.FormatMessage(IDS_FILEDIFF_CHANGEDLISTINTRO, (LPCTSTR)m_path1.GetGitPathString(), (LPCTSTR)m_rev1.m_CommitHash.ToString(), (LPCTSTR)m_path2.GetGitPathString(), (LPCTSTR)m_rev2.m_CommitHash.ToString());
705 file.WriteString(temp + L"\r\n");
706 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
707 while (pos)
709 int index = m_cFileList.GetNextSelectedItem(pos);
710 CTGitPath* fd = m_arFilteredList[index];
711 file.WriteString(fd->GetGitPathString());
712 file.WriteString(L"\r\n");
714 file.Close();
716 catch (CFileException* pE)
718 pE->ReportError();
722 break;
723 case ID_CLIPBOARD_PATH:
725 CopySelectionToClipboard();
727 break;
729 case ID_CLIPBOARD_ALL:
731 CopySelectionToClipboard(TRUE);
733 break;
734 case ID_EXPORT:
736 // export all changed files to a folder
737 CBrowseFolder browseFolder;
738 browseFolder.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
739 if (browseFolder.Show(GetSafeHwnd(), m_strExportDir) == CBrowseFolder::OK)
741 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
742 while (pos)
744 int index = m_cFileList.GetNextSelectedItem(pos);
745 CTGitPath* fd = m_arFilteredList[index];
746 // we cannot export directories or folders
747 if (fd->m_Action == CTGitPath::LOGACTIONS_DELETED || fd->IsDirectory())
748 continue;
749 CPathUtils::MakeSureDirectoryPathExists(m_strExportDir + L'\\' + fd->GetContainingDirectory().GetWinPathString());
750 CString filename = m_strExportDir + L'\\' + fd->GetWinPathString();
751 if (m_rev2.m_CommitHash.ToString() == GIT_REV_ZERO)
753 if(!CopyFile(g_Git.CombinePath(fd), filename, false))
755 MessageBox(CFormatMessageWrapper(), L"TortoiseGit", MB_OK | MB_ICONERROR);
756 return;
759 else
761 if (g_Git.GetOneFile(m_rev2.m_CommitHash.ToString(), *fd, filename))
763 CString out;
764 out.FormatMessage(IDS_STATUSLIST_CHECKOUTFILEFAILED, (LPCTSTR)fd->GetGitPathString(), (LPCTSTR)m_rev2.m_CommitHash.ToString(), (LPCTSTR)filename);
765 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)
766 return;
773 break;
779 BOOL CFileDiffDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
781 if (pWnd != &m_cFileList)
782 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
783 if (m_bThreadRunning == 0)
785 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
786 SetCursor(hCur);
787 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
789 HCURSOR hCur = LoadCursor(nullptr, IDC_WAIT);
790 SetCursor(hCur);
791 return TRUE;
794 void CFileDiffDlg::OnEnSetfocusFirsturl()
796 GetDlgItem(IDC_FIRSTURL)->HideCaret();
799 void CFileDiffDlg::OnEnSetfocusSecondurl()
801 GetDlgItem(IDC_SECONDURL)->HideCaret();
804 void CFileDiffDlg::OnBnClickedSwitchleftright()
806 if (m_bThreadRunning)
807 return;
809 #if 0
810 CString sFilterString;
811 m_cFilter.GetWindowText(sFilterString);
813 m_cFileList.SetRedraw(false);
814 m_cFileList.DeleteAllItems();
815 for (int i=0; i<(int)m_arFileList.GetCount(); ++i)
817 CTGitPath fd = m_arFileList[i];
818 if (fd.m_Action == CTGitPath::LOGACTIONS_ADDED)
819 fd.m_Action = CTGitPath::LOGACTIONS_DELETED;
820 else if (fd.m_Action == CTGitPath::LOGACTIONS_DELETED)
821 fd.m_Action = CTGitPath::LOGACTIONS_ADDED;
822 std::swap(fd.m_StatAdd, fd.m_StatDel);
823 (CTGitPath&)m_arFileList[i] = fd;
825 Filter(sFilterString);
826 #endif
828 m_cFileList.SetRedraw(true);
829 CTGitPath path = m_path1;
830 m_path1 = m_path2;
831 m_path2 = path;
832 GitRev rev = m_rev1;
833 m_rev1 = m_rev2;
834 m_rev2 = rev;
836 CString str1,str2;
837 this->m_ctrRev1Edit.GetWindowText(str1);
838 this->m_ctrRev2Edit.GetWindowText(str2);
840 this->m_ctrRev1Edit.SetWindowText(str2);
841 this->m_ctrRev2Edit.SetWindowText(str1);
843 SetURLLabels();
844 //KillTimer(IDT_INPUT);
847 void CFileDiffDlg::SetURLLabels(int mask)
849 if(mask &0x1)
851 SetDlgItemText(IDC_FIRSTURL, m_rev1.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + L": " + m_rev1.GetSubject());
852 if (!m_rev1.m_CommitHash.IsEmpty())
853 m_tooltips.AddTool(IDC_FIRSTURL,
854 CLoglistUtils::FormatDateAndTime(m_rev1.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_rev1.GetAuthorName());
857 if(mask &0x2)
859 SetDlgItemText(IDC_SECONDURL, m_rev2.m_CommitHash.ToString().Left(g_Git.GetShortHASHLength()) + L": " + m_rev2.GetSubject());
860 if (!m_rev2.m_CommitHash.IsEmpty())
861 m_tooltips.AddTool(IDC_SECONDURL,
862 CLoglistUtils::FormatDateAndTime(m_rev2.GetAuthorDate(), DATE_SHORTDATE) + L" " + m_rev2.GetAuthorName());
865 this->GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1BASE)));
866 this->GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2)));
868 if ((mask & 0x3) == 0x3 && !m_rev1.m_CommitHash.IsEmpty() && !m_rev2.m_CommitHash.IsEmpty())
869 if(m_rev1.GetCommitterDate() > m_rev2.GetCommitterDate())
870 GetDlgItem(IDC_REV1GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION1BASENEWER)));
871 else if (m_rev1.GetCommitterDate() < m_rev2.GetCommitterDate())
872 GetDlgItem(IDC_REV2GROUP)->SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_FILEDIFF_VERSION2NEWER)));
875 void CFileDiffDlg::ClearURLabels(int mask)
877 if(mask&0x1)
879 SetDlgItemText(IDC_FIRSTURL, L"");
880 m_tooltips.AddTool(IDC_FIRSTURL, L"");
883 if(mask&0x2)
885 SetDlgItemText(IDC_SECONDURL, L"");
886 m_tooltips.AddTool(IDC_SECONDURL, L"");
889 BOOL CFileDiffDlg::PreTranslateMessage(MSG* pMsg)
891 if (pMsg->message == WM_KEYDOWN)
893 switch (pMsg->wParam)
895 case 'A':
897 if (GetFocus() != GetDlgItem(IDC_FILELIST))
898 break;
899 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
901 // select all entries
902 for (int i=0; i<m_cFileList.GetItemCount(); ++i)
903 m_cFileList.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
904 return TRUE;
907 break;
908 case 'C':
909 case VK_INSERT:
911 if (GetFocus() != GetDlgItem(IDC_FILELIST))
912 break;
913 if (GetAsyncKeyState(VK_CONTROL)&0x8000)
915 CopySelectionToClipboard();
916 return TRUE;
919 break;
920 case '\r':
922 if (GetFocus() == GetDlgItem(IDC_FILELIST))
924 // Return pressed in file list. Show diff, as for double click
925 int selIndex = m_cFileList.GetSelectionMark();
926 if ((selIndex >= 0) && (selIndex < (int)m_arFileList.GetCount()))
927 DoDiff(selIndex, m_bBlame);
928 return TRUE;
931 break;
932 case VK_F5:
934 OnTimer(IDT_INPUT);
936 break;
937 case VK_ESCAPE:
938 if (GetFocus() == GetDlgItem(IDC_FILTER) && m_cFilter.GetWindowTextLength())
940 m_cFilter.SetWindowText(L"");
941 OnClickedCancelFilter(NULL, NULL);
942 return TRUE;
944 break;
947 return __super::PreTranslateMessage(pMsg);
950 void CFileDiffDlg::OnCancel()
952 if (m_bThreadRunning)
953 return;
954 __super::OnCancel();
957 void CFileDiffDlg::OnHdnItemclickFilelist(NMHDR *pNMHDR, LRESULT *pResult)
959 LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
960 if (m_bThreadRunning)
961 return;
963 if (m_nSortedColumn == phdr->iItem)
964 m_bAscending = !m_bAscending;
965 else
966 m_bAscending = TRUE;
967 m_nSortedColumn = phdr->iItem;
968 Sort();
970 CString temp;
971 m_cFileList.SetRedraw(FALSE);
972 m_cFileList.DeleteAllItems();
973 m_cFilter.GetWindowText(temp);
974 Filter(temp);
976 CHeaderCtrl * pHeader = m_cFileList.GetHeaderCtrl();
977 HDITEM HeaderItem = {0};
978 HeaderItem.mask = HDI_FORMAT;
979 for (int i=0; i<pHeader->GetItemCount(); ++i)
981 pHeader->GetItem(i, &HeaderItem);
982 HeaderItem.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
983 pHeader->SetItem(i, &HeaderItem);
985 pHeader->GetItem(m_nSortedColumn, &HeaderItem);
986 HeaderItem.fmt |= (m_bAscending ? HDF_SORTUP : HDF_SORTDOWN);
987 pHeader->SetItem(m_nSortedColumn, &HeaderItem);
989 m_cFileList.SetRedraw(TRUE);
991 *pResult = 0;
994 void CFileDiffDlg::Sort()
996 if(m_arFileList.GetCount() < 2)
997 return;
999 std::sort(m_arFileList.m_paths.begin(), m_arFileList.m_paths.end(), &CFileDiffDlg::SortCompare);
1002 bool CFileDiffDlg::SortCompare(const CTGitPath& Data1, const CTGitPath& Data2)
1004 int result = 0;
1005 int d1, d2;
1006 switch (m_nSortedColumn)
1008 case 0: //path column
1009 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
1010 break;
1011 case 1: //extension column
1012 result = Data1.GetFileExtension().Compare(Data2.GetFileExtension());
1013 break;
1014 case 2: //action column
1015 result = Data1.m_Action - Data2.m_Action;
1016 break;
1017 case 3:
1018 d1 = CSorter::A2L(Data1.m_StatAdd);
1019 d2 = CSorter::A2L(Data2.m_StatAdd);
1020 result = d1 - d2;
1021 break;
1022 case 4:
1023 d1 = CSorter::A2L(Data1.m_StatDel);;
1024 d2 = CSorter::A2L(Data2.m_StatDel);
1025 result = d1 - d2;
1026 break;
1027 default:
1028 break;
1030 // sort by path name as second priority
1031 if (m_nSortedColumn != 0 && result == 0)
1032 result = Data1.GetWinPathString().Compare(Data2.GetWinPathString());
1034 if (!m_bAscending)
1035 result = -result;
1036 return result < 0;
1040 void CFileDiffDlg::OnBnClickedRev1btn()
1042 ClickRevButton(&this->m_cRev1Btn,&this->m_rev1, &this->m_ctrRev1Edit);
1045 void CFileDiffDlg::ClickRevButton(CMenuButton *button, GitRev *rev, CACEdit *edit)
1047 INT_PTR entry=button->GetCurrentEntry();
1048 if(entry == 0) /* Browse Refence*/
1051 CString str = CBrowseRefsDlg::PickRef();
1052 if(str.IsEmpty())
1053 return;
1055 if(FillRevFromString(rev,str))
1056 return;
1058 edit->SetWindowText(str);
1062 if(entry == 1) /*Log*/
1064 CLogDlg dlg;
1065 CString revision;
1066 edit->GetWindowText(revision);
1067 dlg.SetParams(CTGitPath(), CTGitPath(), revision, revision, 0);
1068 dlg.SetSelect(true);
1069 if(dlg.DoModal() == IDOK)
1071 if (dlg.GetSelectedHash().empty())
1072 return;
1074 if (FillRevFromString(rev, dlg.GetSelectedHash().at(0).ToString()))
1075 return;
1077 edit->SetWindowText(dlg.GetSelectedHash().at(0).ToString());
1079 else
1080 return;
1083 if(entry == 2) /*RefLog*/
1085 CRefLogDlg dlg;
1086 if(dlg.DoModal() == IDOK)
1088 if (FillRevFromString(rev, dlg.m_SelectedHash.ToString()))
1089 return;
1091 edit->SetWindowText(dlg.m_SelectedHash.ToString());
1093 else
1094 return;
1097 SetURLLabels();
1099 InterlockedExchange(&m_bThreadRunning, TRUE);
1100 if (!AfxBeginThread(DiffThreadEntry, this))
1102 InterlockedExchange(&m_bThreadRunning, FALSE);
1103 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1105 KillTimer(IDT_INPUT);
1108 void CFileDiffDlg::OnBnClickedRev2btn()
1110 ClickRevButton(&this->m_cRev2Btn,&this->m_rev2, &this->m_ctrRev2Edit);
1113 LRESULT CFileDiffDlg::OnClickedCancelFilter(WPARAM /*wParam*/, LPARAM /*lParam*/)
1115 if (m_bThreadRunning)
1117 SetTimer(IDT_FILTER, 1000, nullptr);
1118 return 0L;
1121 KillTimer(IDT_FILTER);
1123 m_cFileList.SetRedraw(FALSE);
1124 m_arFilteredList.clear();
1125 m_cFileList.DeleteAllItems();
1127 Filter(L"");
1129 m_cFileList.SetRedraw(TRUE);
1130 return 0L;
1133 void CFileDiffDlg::OnEnChangeFilter()
1135 SetTimer(IDT_FILTER, 1000, nullptr);
1138 void CFileDiffDlg::OnTimer(UINT_PTR nIDEvent)
1140 if (m_bThreadRunning)
1141 return;
1143 if( nIDEvent == IDT_FILTER)
1145 CString sFilterText;
1146 KillTimer(IDT_FILTER);
1147 m_cFilter.GetWindowText(sFilterText);
1149 m_cFileList.SetRedraw(FALSE);
1150 m_cFileList.DeleteAllItems();
1152 Filter(sFilterText);
1154 m_cFileList.SetRedraw(TRUE);
1156 __super::OnTimer(nIDEvent);
1159 if( nIDEvent == IDT_INPUT)
1161 KillTimer(IDT_INPUT);
1162 TRACE(L"Input Timer\r\n");
1164 GitRev gitrev;
1165 CString str;
1166 int mask = 0;
1167 this->m_ctrRev1Edit.GetWindowText(str);
1168 if (!gitrev.GetCommit(str))
1170 m_rev1 = gitrev;
1171 mask |= 0x1;
1173 else
1175 CString msg;
1176 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)str);
1177 m_cFileList.ShowText(msg + L'\n' + gitrev.GetLastErr());
1180 this->m_ctrRev2Edit.GetWindowText(str);
1182 if (!gitrev.GetCommit(str))
1184 m_rev2 = gitrev;
1185 mask |= 0x2;
1187 else
1189 CString msg;
1190 msg.Format(IDS_PROC_REFINVALID, (LPCTSTR)str);
1191 m_cFileList.ShowText(msg + L'\n' + gitrev.GetLastErr());
1194 this->SetURLLabels(mask);
1196 if(mask == 0x3)
1198 InterlockedExchange(&m_bThreadRunning, TRUE);
1199 if (!AfxBeginThread(DiffThreadEntry, this))
1201 InterlockedExchange(&m_bThreadRunning, FALSE);
1202 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
1208 void CFileDiffDlg::Filter(CString sFilterText)
1210 sFilterText.MakeLower();
1212 m_arFilteredList.clear();
1214 for (int i=0;i<m_arFileList.GetCount();i++)
1216 CString sPath = m_arFileList[i].GetGitPathString();
1217 sPath.MakeLower();
1218 if (sPath.Find(sFilterText) >= 0)
1219 m_arFilteredList.push_back((CTGitPath*)&(m_arFileList[i]));
1221 for (const auto path : m_arFilteredList)
1222 AddEntry(path);
1225 void CFileDiffDlg::CopySelectionToClipboard(BOOL isFull)
1227 // copy all selected paths to the clipboard
1228 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1229 int index;
1230 CString sTextForClipboard;
1231 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1233 sTextForClipboard += m_cFileList.GetItemText(index, 0);
1234 sTextForClipboard += L'\t';
1236 if(!isFull)
1237 sTextForClipboard += L"\r\n";
1238 else
1240 sTextForClipboard += m_cFileList.GetItemText(index, 1);
1241 sTextForClipboard += L'\t';
1242 sTextForClipboard += m_cFileList.GetItemText(index, 2);
1243 sTextForClipboard += L'\t';
1244 sTextForClipboard += m_cFileList.GetItemText(index, 3);
1245 sTextForClipboard += L'\t';
1246 sTextForClipboard += m_cFileList.GetItemText(index, 4);
1247 sTextForClipboard += L"\r\n";
1250 CStringUtils::WriteAsciiStringToClipboard(sTextForClipboard);
1254 LRESULT CFileDiffDlg::OnRefLoad(WPARAM /*wParam*/, LPARAM /*lParam*/)
1256 for (size_t i = 0; i < m_Reflist.size(); ++i)
1258 CString str=m_Reflist[i];
1260 if (CStringUtils::StartsWith(str, L"remotes/"))
1261 str = str.Mid((int)wcslen(L"remotes/"));
1263 m_ctrRev1Edit.AddSearchString(str);
1264 m_ctrRev2Edit.AddSearchString(str);
1266 return 0;
1269 BOOL CFileDiffDlg::DestroyWindow()
1271 return CResizableStandAloneDialog::DestroyWindow();
1274 LRESULT CFileDiffDlg::OnEnUpdate(WPARAM /*wParam*/, LPARAM lParam)
1276 if(lParam == IDC_REV1EDIT)
1278 OnTextUpdate(&this->m_ctrRev1Edit);
1279 ClearURLabels(1);
1281 if(lParam == IDC_REV2EDIT)
1283 OnTextUpdate(&this->m_ctrRev2Edit);
1284 ClearURLabels(1<<1);
1286 return 0;
1289 void CFileDiffDlg::OnTextUpdate(CACEdit * /*pEdit*/)
1291 SetTimer(IDT_INPUT, 1000, nullptr);
1292 this->m_cFileList.ShowText(L"Wait For input validate version");
1295 int CFileDiffDlg::RevertSelectedItemToVersion(CString rev)
1297 if (rev.IsEmpty() || rev == GIT_REV_ZERO)
1298 return 0;
1300 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1301 int index;
1302 int count = 0;
1303 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1305 CString cmd, out;
1306 CTGitPath* fentry = m_arFilteredList[index];
1307 cmd.Format(L"git.exe checkout %s -- \"%s\"", (LPCTSTR)rev, (LPCTSTR)fentry->GetGitPathString());
1308 if (g_Git.Run(cmd, &out, CP_UTF8))
1310 if (CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", 2, IDI_WARNING, CString(MAKEINTRESOURCE(IDS_IGNOREBUTTON)), CString(MAKEINTRESOURCE(IDS_ABORTBUTTON))) == 2)
1311 break;
1313 else
1314 count++;
1317 CString out;
1318 out.FormatMessage(IDS_STATUSLIST_FILESREVERTED, count, (LPCTSTR)rev);
1319 CMessageBox::Show(GetSafeHwnd(), out, L"TortoiseGit", MB_OK);
1320 return 0;
1323 static void AppendMenuChecked(CMenu &menu, UINT nTextID, UINT_PTR nItemID, BOOL checked = FALSE, BOOL enabled = TRUE)
1325 CString text;
1326 text.LoadString(nTextID);
1327 menu.AppendMenu(MF_STRING | (enabled ? MF_ENABLED : MF_DISABLED) | (checked ? MF_CHECKED : MF_UNCHECKED), nItemID, text);
1330 #define DIFFOPTION_IGNORESPACEATEOL 1
1331 #define DIFFOPTION_IGNORESPACECHANGE 2
1332 #define DIFFOPTION_IGNOREALLSPACE 3
1333 #define DIFFOPTION_IGNORBLANKLINES 4
1335 void CFileDiffDlg::OnBnClickedDiffoption()
1337 CMenu popup;
1338 if (popup.CreatePopupMenu())
1340 m_cDiffOptionsBtn.SetCheck(BST_CHECKED);
1341 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORESPACEATEOL, DIFFOPTION_IGNORESPACEATEOL, m_bIgnoreSpaceAtEol);
1342 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORESPACECHANGE, DIFFOPTION_IGNORESPACECHANGE, m_bIgnoreSpaceChange);
1343 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNOREALLSPACE, DIFFOPTION_IGNOREALLSPACE, m_bIgnoreAllSpace);
1344 AppendMenuChecked(popup, IDS_DIFFOPTION_IGNORBLANKLINES, DIFFOPTION_IGNORBLANKLINES, m_bIgnoreBlankLines);
1346 m_tooltips.Pop();
1347 RECT rect;
1348 GetDlgItem(IDC_DIFFOPTION)->GetWindowRect(&rect);
1349 int selection = popup.TrackPopupMenu(TPM_RETURNCMD | TPM_LEFTALIGN | TPM_NONOTIFY, rect.left, rect.bottom, this);
1350 switch (selection)
1352 case DIFFOPTION_IGNORESPACEATEOL:
1353 m_bIgnoreSpaceAtEol = !m_bIgnoreSpaceAtEol;
1354 OnTimer(IDT_INPUT);
1355 break;
1356 case DIFFOPTION_IGNORESPACECHANGE:
1357 m_bIgnoreSpaceChange = !m_bIgnoreSpaceChange;
1358 OnTimer(IDT_INPUT);
1359 break;
1360 case DIFFOPTION_IGNOREALLSPACE:
1361 m_bIgnoreAllSpace = !m_bIgnoreAllSpace;
1362 OnTimer(IDT_INPUT);
1363 break;
1364 case DIFFOPTION_IGNORBLANKLINES:
1365 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1366 OnTimer(IDT_INPUT);
1367 break;
1368 default:
1369 break;
1371 UpdateData(FALSE);
1372 m_cDiffOptionsBtn.SetCheck((m_bIgnoreSpaceAtEol || m_bIgnoreSpaceChange || m_bIgnoreAllSpace || m_bIgnoreBlankLines) ? BST_CHECKED : BST_UNCHECKED);
1376 void CFileDiffDlg::OnBnClickedLog()
1378 CLogDlg dlg;
1379 dlg.SetRange(m_rev1.m_CommitHash.ToString() + L".." + m_rev2.m_CommitHash.ToString());
1380 dlg.DoModal();
1383 bool CFileDiffDlg::CheckMultipleDiffs()
1385 UINT selCount = m_cFileList.GetSelectedCount();
1386 if (selCount > max((DWORD)3, (DWORD)CRegDWORD(L"Software\\TortoiseGit\\NumDiffWarning", 10)))
1388 CString message;
1389 message.Format(IDS_STATUSLIST_WARN_MAXDIFF, selCount);
1390 return ::MessageBox(GetSafeHwnd(), message, L"TortoiseGit", MB_YESNO | MB_ICONQUESTION) == IDYES;
1392 return true;
1395 void CFileDiffDlg::OnLvnBegindrag(NMHDR* pNMHDR, LRESULT* pResult)
1397 *pResult = 0;
1399 // get selected paths
1400 POSITION pos = m_cFileList.GetFirstSelectedItemPosition();
1401 if (!pos)
1402 return;
1404 CTGitPathList toExport;
1405 int index = -1;
1406 while ((index = m_cFileList.GetNextSelectedItem(pos)) >= 0)
1408 auto fentry = m_arFilteredList[index];
1409 toExport.AddPath(*fentry);
1413 // build copy source / content
1414 auto pdsrc = std::make_unique<CIDropSource>();
1415 if (!pdsrc)
1416 return;
1418 pdsrc->AddRef();
1420 GitDataObject* pdobj = new GitDataObject(toExport, m_rev2.m_CommitHash);
1421 if (!pdobj)
1422 return;
1423 pdobj->AddRef();
1424 pdobj->SetAsyncMode(TRUE);
1425 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
1426 CDragSourceHelper dragsrchelper;
1427 dragsrchelper.InitializeFromWindow(GetSafeHwnd(), pNMLV->ptAction, pdobj);
1428 pdsrc->m_pIDataObj = pdobj;
1429 pdsrc->m_pIDataObj->AddRef();
1431 // Initiate the Drag & Drop
1432 DWORD dwEffect;
1433 ::DoDragDrop(pdobj, pdsrc.get(), DROPEFFECT_MOVE | DROPEFFECT_COPY, &dwEffect);
1434 pdsrc->Release();
1435 pdsrc.release();
1436 pdobj->Release();