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.
21 #include "TortoiseProc.h"
22 #include "UnicodeUtils.h"
23 #include "MessageBox.h"
25 #include "SysImageList.h"
27 #include "StringUtils.h"
28 #include "PathUtils.h"
29 #include "BrowseFolder.h"
30 #include "FileDiffDlg.h"
32 #include "LoglistCommonResource.h"
33 #include "LoglistUtils.h"
34 #include "BrowseRefsDlg.h"
36 #include "RefLogDlg.h"
37 #include "GitStatusListCtrl.h"
38 #include "FormatMessageWrapper.h"
39 #include "GitDataObject.h"
40 #include "LogDlgFileFilter.h"
46 #define ID_CLIPBOARD_PATH 5
47 #define ID_CLIPBOARD_ALL 6
49 #define ID_GNUDIFFCOMPARE 8
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
)
65 , m_bThreadRunning(FALSE
)
66 , m_bIgnoreSpaceAtEol(false)
67 , m_bIgnoreSpaceChange(false)
68 , m_bIgnoreAllSpace(false)
69 , m_bIgnoreBlankLines(false)
70 , m_bCommonAncestorDiff(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
)
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
)
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
)
125 void CFileDiffDlg::SetDiff(const CTGitPath
* path
, const GitRev
& baseRev1
, const GitRev
& rev2
)
130 m_sFilter
= path
->GetGitPathString();
136 void CFileDiffDlg::SetDiff(const CTGitPath
* path
, const CString
&baseRev1
, const CString
& hash2
)
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
);
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
);
158 FillRevFromString(&m_rev2
, hash2
);
161 void CFileDiffDlg::SetDiff(const CTGitPath
* path
, const GitRev
&baseRev1
)
166 m_sFilter
= path
->GetGitPathString();
169 m_rev2
.m_CommitHash
.Empty();
170 m_rev2
.GetSubject().LoadString(IDS_PROC_PREVIOUSVERSION
);
173 BOOL
CFileDiffDlg::OnInitDialog()
175 CResizableStandAloneDialog::OnInitDialog();
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
);
210 m_cFilter
.SetCueBanner(temp
);
211 if (!m_sFilter
.IsEmpty())
212 m_cFilter
.SetWindowText(m_sFilter
);
214 int c
= m_cFileList
.GetHeaderCtrl()->GetItemCount() - 1;
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());
260 if (m_rev1
.GetCommit(m_strRev1
))
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());
275 if (m_rev2
.GetCommit(m_strRev2
))
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
);
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")))
322 if (CRegDWORD(L
"Software\\TortoiseGit\\LogFontForFileListCtrl", FALSE
))
324 CAppUtils::CreateFontForLogs(m_font
);
325 m_cFileList
.SetFont(&m_font
);
328 KillTimer(IDT_INPUT
);
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
);
351 g_Git
.GetCommitDiffList(m_rev2
.m_CommitHash
.ToString(), m_rev1
.m_CommitHash
.ToString(), m_arFileList
, m_bIgnoreSpaceAtEol
, m_bIgnoreSpaceChange
, m_bIgnoreAllSpace
, m_bIgnoreBlankLines
);
354 SendMessage(WM_DIFFFINISHED
);
356 InterlockedExchange(&m_bThreadRunning
, FALSE
);
360 LRESULT
CFileDiffDlg::OnDisableButtons(WPARAM
, LPARAM
)
363 m_cFileList
.ShowText(CString(MAKEINTRESOURCE(IDS_FILEDIFF_WAIT
)));
364 m_cFileList
.DeleteAllItems();
365 m_arFileList
.Clear();
366 EnableInputControl(false);
370 LRESULT
CFileDiffDlg::OnDiffFinished(WPARAM
, LPARAM
)
373 m_cFilter
.GetWindowText(sFilterText
);
374 m_cFileList
.SetRedraw(false);
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);
387 EnableInputControl(true);
389 m_cFileList
.SetFocus();
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();
407 entryname
.AppendFormat(from
, static_cast<LPCWSTR
>(entry
->GetGitOldPathString()));
411 return entry
->GetAbbreviatedRename();
414 int CFileDiffDlg::AddEntry(const CTGitPath
* fd
)
419 int index
= m_cFileList
.GetItemCount();
422 if (fd
->IsDirectory())
423 icon_idx
= m_nIconFolder
;
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
);
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
];
451 if (m_rev2
.m_CommitHash
.IsEmpty() && g_Git
.IsInitRepos())
453 CGitDiff::DiffNull(GetSafeHwnd(), fd2
, GIT_REV_ZERO
, true, 0, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
456 if (fd1
->m_Action
& CTGitPath::LOGACTIONS_ADDED
)
458 CGitDiff::DiffNull(GetSafeHwnd(), fd1
, m_rev2
.m_CommitHash
.ToString(), true, 0, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
461 if (fd1
->m_Action
& CTGitPath::LOGACTIONS_DELETED
)
463 CGitDiff::DiffNull(GetSafeHwnd(), fd1
, m_rev1
.m_CommitHash
.ToString(), false, 0, !!(GetAsyncKeyState(VK_SHIFT
) & 0x8000));
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));
474 void CFileDiffDlg::OnNMDblclkFilelist(NMHDR
*pNMHDR
, LRESULT
*pResult
)
477 LPNMLISTVIEW pNMLV
= reinterpret_cast<LPNMLISTVIEW
>(pNMHDR
);
478 int selIndex
= pNMLV
->iItem
;
481 if (selIndex
>= static_cast<int>(m_arFilteredList
.size()))
484 DoDiff(selIndex
, m_bBlame
);
487 void CFileDiffDlg::OnLvnGetInfoTipFilelist(NMHDR
*pNMHDR
, LRESULT
*pResult
)
491 LPNMLVGETINFOTIP pGetInfoTip
= reinterpret_cast<LPNMLVGETINFOTIP
>(pNMHDR
);
492 if (pGetInfoTip
->iItem
>= static_cast<int>(m_arFilteredList
.size()) || CRegDWORD(L
"Software\\TortoiseGit\\ShowListFullPathTooltip", TRUE
) != TRUE
)
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
));
532 case CTGitPath::LOGACTIONS_DELETED
:
533 crText
= CTheme::Instance().GetThemeColor(m_colors
.GetColor(CColors::Deleted
));
535 case CTGitPath::LOGACTIONS_MODIFIED
:
536 crText
= CTheme::Instance().GetThemeColor(m_colors
.GetColor(CColors::Modified
));
539 crText
= CTheme::Instance().GetThemeColor(m_colors
.GetColor(CColors::PropertyChanged
));
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
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
);
567 static CString
GetCommitTitle(const GitRev
& rev
)
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
)));
580 void CFileDiffDlg::OnContextMenu(CWnd
* pWnd
, CPoint point
)
582 if (!pWnd
|| pWnd
!= &m_cFileList
)
584 if (m_cFileList
.GetSelectedCount() == 0)
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))
591 m_cFileList
.GetItemRect(m_cFileList
.GetSelectionMark(), &rect
, LVIR_LABEL
);
592 m_cFileList
.ClientToScreen(&rect
);
593 point
= rect
.CenterPoint();
596 if (popup
.CreatePopupMenu())
599 POSITION firstPos
= m_cFileList
.GetFirstSelectedItemPosition();
601 firstEntry
= m_cFileList
.GetNextSelectedItem(firstPos
);
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
);
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())
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);
644 if (!CheckMultipleDiffs())
646 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
649 int index
= m_cFileList
.GetNextSelectedItem(pos
);
650 DoDiff(index
, false);
654 case ID_GNUDIFFCOMPARE
:
656 if (!CheckMultipleDiffs())
658 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
661 auto fd2
= m_arFilteredList
[m_cFileList
.GetNextSelectedItem(pos
)];
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));
672 RevertSelectedItemToVersion(m_rev1
.m_CommitHash
.ToString(), true);
675 RevertSelectedItemToVersion(m_rev2
.m_CommitHash
.ToString(), false);
679 if (!CheckMultipleDiffs())
681 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
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());
691 if (m_rev2
.m_CommitHash
.IsEmpty() && (m_arFilteredList
[index
]->m_Action
& CTGitPath::LOGACTIONS_ADDED
))
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());
698 CAppUtils::LaunchTortoiseBlame(m_arFilteredList
[index
]->GetWinPathString(), m_rev2
.m_CommitHash
.ToString());
703 case ID_LOGSUBMODULE
:
705 if (!CheckMultipleDiffs())
707 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
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
);
722 if (m_cFileList
.GetSelectedCount() > 0)
726 if (!CAppUtils::FileOpenSave(pathSave
, nullptr, IDS_FILEDIFF_POPSAVELIST
, IDS_TEXTFILEFILTER
, false, m_hWnd
, L
"txt"))
728 savePath
= CTGitPath(pathSave
);
730 // now open the selected file for writing
733 CStdioFile
file(savePath
.GetWinPathString(), CFile::typeBinary
| CFile::modeReadWrite
| CFile::modeCreate
);
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()));
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();
743 int index
= m_cFileList
.GetNextSelectedItem(pos
);
744 auto fd
= m_arFilteredList
[index
];
745 file
.WriteString(fd
->GetGitPathString());
746 file
.WriteString(L
"\r\n");
750 catch (CFileException
* pE
)
757 case ID_CLIPBOARD_PATH
:
759 CopySelectionToClipboard();
763 case ID_CLIPBOARD_ALL
:
765 CopySelectionToClipboard(TRUE
);
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();
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())
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
);
795 if (g_Git
.GetOneFile(m_rev2
.m_CommitHash
.ToString(), *fd
, filename
))
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)
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
);
821 return CResizableStandAloneDialog::OnSetCursor(pWnd
, nHitTest
, message
);
823 HCURSOR hCur
= LoadCursor(nullptr, IDC_WAIT
);
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
)
843 m_cFileList
.SetRedraw(true);
849 this->m_ctrRev1Edit
.GetWindowText(str1
);
850 this->m_ctrRev2Edit
.GetWindowText(str2
);
852 this->m_ctrRev1Edit
.SetWindowText(str2
);
853 this->m_ctrRev2Edit
.SetWindowText(str1
);
856 //KillTimer(IDT_INPUT);
859 void CFileDiffDlg::SetURLLabels(int mask
)
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());
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
)
892 SetDlgItemText(IDC_FIRSTURL
, L
"");
893 m_tooltips
.AddTool(IDC_FIRSTURL
, L
"");
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
)
910 if (GetFocus() != GetDlgItem(IDC_FILELIST
))
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
);
924 if (GetFocus() != GetDlgItem(IDC_FILELIST
))
926 if (GetAsyncKeyState(VK_CONTROL
)&0x8000)
928 CopySelectionToClipboard();
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
);
951 if (GetFocus() == GetDlgItem(IDC_FILTER
) && m_cFilter
.GetWindowTextLength())
953 m_cFilter
.SetWindowText(L
"");
954 OnClickedCancelFilter(NULL
, NULL
);
960 return __super::PreTranslateMessage(pMsg
);
963 void CFileDiffDlg::OnCancel()
965 if (m_bThreadRunning
|| m_bLoadingRef
)
970 void CFileDiffDlg::OnHdnItemclickFilelist(NMHDR
*pNMHDR
, LRESULT
*pResult
)
972 LPNMHEADER phdr
= reinterpret_cast<LPNMHEADER
>(pNMHDR
);
973 if (m_bThreadRunning
)
976 if (m_nSortedColumn
== phdr
->iItem
)
977 m_bAscending
= !m_bAscending
;
980 m_nSortedColumn
= phdr
->iItem
;
984 m_cFileList
.SetRedraw(FALSE
);
985 m_cFileList
.DeleteAllItems();
986 m_cFilter
.GetWindowText(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
);
1007 void CFileDiffDlg::Sort()
1009 if(m_arFileList
.GetCount() < 2)
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
)
1019 switch (m_nSortedColumn
)
1021 case 0: //path column
1022 result
= Data1
.GetWinPathString().Compare(Data2
.GetWinPathString());
1024 case 1: //extension column
1025 result
= Data1
.GetFileExtension().Compare(Data2
.GetFileExtension());
1027 case 2: //action column
1028 result
= Data1
.m_Action
- Data2
.m_Action
;
1031 d1
= CSorter::A2L(Data1
.m_StatAdd
);
1032 d2
= CSorter::A2L(Data2
.m_StatAdd
);
1036 d1
= CSorter::A2L(Data1
.m_StatDel
);;
1037 d2
= CSorter::A2L(Data2
.m_StatDel
);
1043 // sort by path name as second priority
1044 if (m_nSortedColumn
!= 0 && result
== 0)
1045 result
= Data1
.GetWinPathString().Compare(Data2
.GetWinPathString());
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();
1068 if(FillRevFromString(rev
,str
))
1071 edit
->SetWindowText(str
);
1075 if(entry
== 1) /*Log*/
1078 if (dlg
.IsThreadRunning())
1080 CMessageBox::Show(GetSafeHwnd(), IDS_PROC_LOG_ONLYONCE
, IDS_APPNAME
, MB_ICONEXCLAMATION
);
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())
1093 if (FillRevFromString(rev
, dlg
.GetSelectedHash().at(0).ToString()))
1096 edit
->SetWindowText(dlg
.GetSelectedHash().at(0).ToString());
1100 BringWindowToTop(); /* cf. issue #3493 */
1105 if(entry
== 2) /*RefLog*/
1108 if(dlg
.DoModal() == IDOK
)
1110 if (FillRevFromString(rev
, dlg
.m_SelectedHash
.ToString()))
1113 edit
->SetWindowText(dlg
.m_SelectedHash
.ToString());
1121 if (InterlockedExchange(&m_bThreadRunning
, TRUE
))
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);
1144 KillTimer(IDT_FILTER
);
1146 m_cFileList
.SetRedraw(FALSE
);
1147 m_arFilteredList
.clear();
1148 m_cFileList
.DeleteAllItems();
1152 m_cFileList
.SetRedraw(TRUE
);
1156 void CFileDiffDlg::OnEnChangeFilter()
1158 SetTimer(IDT_FILTER
, 1000, nullptr);
1161 void CFileDiffDlg::OnTimer(UINT_PTR nIDEvent
)
1163 if (m_bThreadRunning
)
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");
1190 this->m_ctrRev1Edit
.GetWindowText(str
);
1191 if (!gitrev
.GetCommit(str
))
1193 gitrev
.ApplyMailmap();
1199 m_cFileList
.DeleteAllItems();
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();
1215 m_cFileList
.DeleteAllItems();
1217 msg
.Format(IDS_PROC_REFINVALID
, static_cast<LPCWSTR
>(str
));
1218 m_cFileList
.ShowText(msg
+ L
'\n' + gitrev
.GetLastErr());
1221 this->SetURLLabels(mask
);
1225 if (InterlockedExchange(&m_bThreadRunning
, TRUE
))
1227 SetTimer(IDT_INPUT
, 1000, nullptr);
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
)
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());
1254 m_arFilteredList
.push_back(&m_arFileList
[i
]);
1257 for (const auto path
: m_arFilteredList
)
1261 void CFileDiffDlg::CopySelectionToClipboard(BOOL isFull
)
1263 // copy all selected paths to the clipboard
1264 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
1266 CString sTextForClipboard
;
1267 while ((index
= m_cFileList
.GetNextSelectedItem(pos
)) >= 0)
1269 sTextForClipboard
+= m_cFileList
.GetItemText(index
, 0);
1270 sTextForClipboard
+= L
'\t';
1273 sTextForClipboard
+= L
"\r\n";
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
);
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
);
1317 if(lParam
== IDC_REV2EDIT
)
1319 OnTextUpdate(&this->m_ctrRev2Edit
);
1320 ClearURLabels(1<<1);
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
)
1336 bool useRecycleBin
= CRegDWORD(L
"Software\\TortoiseGit\\RevertWithRecycleBin", TRUE
);
1338 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
1341 while ((index
= m_cFileList
.GetNextSelectedItem(pos
)) >= 0)
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);
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)
1373 out
.FormatMessage(IDS_STATUSLIST_FILESREVERTED
, count
, static_cast<LPCWSTR
>(rev
));
1374 CMessageBox::Show(GetSafeHwnd(), out
, L
"TortoiseGit", MB_OK
);
1378 static void AppendMenuChecked(CMenu
&menu
, UINT nTextID
, UINT_PTR nItemID
, BOOL checked
= FALSE
, BOOL enabled
= TRUE
)
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()
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
);
1406 GetDlgItem(IDC_DIFFOPTION
)->GetWindowRect(&rect
);
1407 int selection
= popup
.TrackPopupMenu(TPM_RETURNCMD
| TPM_LEFTALIGN
| TPM_NONOTIFY
, rect
.left
, rect
.bottom
, this);
1410 case DIFFOPTION_IGNORESPACEATEOL
:
1411 m_bIgnoreSpaceAtEol
= !m_bIgnoreSpaceAtEol
;
1414 case DIFFOPTION_IGNORESPACECHANGE
:
1415 m_bIgnoreSpaceChange
= !m_bIgnoreSpaceChange
;
1418 case DIFFOPTION_IGNOREALLSPACE
:
1419 m_bIgnoreAllSpace
= !m_bIgnoreAllSpace
;
1422 case DIFFOPTION_IGNORBLANKLINES
:
1423 m_bIgnoreBlankLines
= !m_bIgnoreBlankLines
;
1426 case DIFFOPTION_COMMONANCESTOR
:
1427 m_bCommonAncestorDiff
= !m_bCommonAncestorDiff
;
1434 m_cDiffOptionsBtn
.SetCheck((m_bIgnoreSpaceAtEol
|| m_bIgnoreSpaceChange
|| m_bIgnoreAllSpace
|| m_bIgnoreBlankLines
|| m_bCommonAncestorDiff
) ? BST_CHECKED
: BST_UNCHECKED
);
1438 void CFileDiffDlg::OnBnClickedLog()
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))))
1451 message
.Format(IDS_STATUSLIST_WARN_MAXDIFF
, selCount
);
1452 return ::MessageBox(GetSafeHwnd(), message
, L
"TortoiseGit", MB_YESNO
| MB_ICONQUESTION
) == IDYES
;
1457 void CFileDiffDlg::OnLvnBegindrag(NMHDR
* pNMHDR
, LRESULT
* pResult
)
1461 // get selected paths
1462 POSITION pos
= m_cFileList
.GetFirstSelectedItemPosition();
1466 CTGitPathList toExport
;
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
>();
1482 GitDataObject
* pdobj
= new GitDataObject(toExport
, m_rev2
.m_CommitHash
);
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
1495 ::DoDragDrop(pdobj
, pdsrc
.get(), DROPEFFECT_MOVE
| DROPEFFECT_COPY
, &dwEffect
);
1501 void CFileDiffDlg::FillPatchView(bool onlySetTimer
)
1503 if (!::IsWindow(this->m_patchViewdlg
.m_hWnd
))
1506 KillTimer(LOG_FILLPATCHVTIMER
);
1509 SetTimer(LOG_FILLPATCHVTIMER
, 100, nullptr);
1513 if (m_cFileList
.HasText() || m_cFileList
.GetItemCount() == 0)
1515 m_patchViewdlg
.ClearView();
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();
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()));
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
);
1546 int nSelect
= m_cFileList
.GetNextSelectedItem(pos
);
1547 auto fentry
= m_arFilteredList
[nSelect
];
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()));
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();
1579 m_ctrlShowPatch
.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_COMMIT_HIDEPATCH
)));
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
);