Merge branch 'scintilla-551'
[TortoiseGit.git] / src / TortoiseMerge / TortoiseMerge.cpp
blobfd3cc5698581a1d4163d91e48dab0121fef18ec3
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2013-2017, 2019-2023 - TortoiseGit
4 // Copyright (C) 2006-2014, 2016 - 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 <dlgs.h>
22 #include "TortoiseMerge.h"
23 #include "MainFrm.h"
24 #include "AboutDlg.h"
25 #include "CmdLineParser.h"
26 #include "version.h"
27 #include "I18NHelper.h"
28 #include "AppUtils.h"
29 #include "PathUtils.h"
30 #include "BrowseFolder.h"
31 #include "DirFileEnum.h"
32 #include "SelectFileFilter.h"
33 #include "FileDlgEventHandler.h"
34 #include "TempFile.h"
35 #include "TaskbarUUID.h"
36 #include "ClipboardHelper.h"
38 #ifdef _DEBUG
39 #define new DEBUG_NEW
40 #endif
42 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
43 #pragma comment(lib, "Propsys.lib")
45 BEGIN_MESSAGE_MAP(CTortoiseMergeApp, CWinAppEx)
46 ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
47 END_MESSAGE_MAP()
49 class PatchOpenDlgEventHandler : public CFileDlgEventHandler
51 public:
52 PatchOpenDlgEventHandler() {}
53 ~PatchOpenDlgEventHandler() {}
55 virtual STDMETHODIMP OnButtonClicked(IFileDialogCustomize* pfdc, DWORD dwIDCtl) override
57 if (dwIDCtl == 101)
59 CComQIPtr<IFileOpenDialog> pDlg = pfdc;
60 if (pDlg)
62 pDlg->Close(S_OK);
65 return S_OK;
70 CTortoiseMergeApp::CTortoiseMergeApp()
72 EnableHtmlHelp();
73 git_libgit2_init();
76 CTortoiseMergeApp::~CTortoiseMergeApp()
78 git_libgit2_shutdown();
81 // The one and only CTortoiseMergeApp object
82 CTortoiseMergeApp theApp;
83 CString sOrigCWD;
84 #if ENABLE_CRASHHANLDER && !_M_ARM64
85 CCrashReportTGit g_crasher(L"TortoiseGitMerge " _T(APP_X64_STRING), TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD, TGIT_VERDATE);
86 #endif
88 CString g_sGroupingUUID;
89 CString g_sGroupingIcon;
90 bool g_bGroupingRemoveIcon = false;
92 // CTortoiseMergeApp initialization
93 BOOL CTortoiseMergeApp::InitInstance()
95 SetDllDirectory(L"");
96 SetTaskIDPerUUID();
97 CCrashReport::Instance().AddUserInfoToReport(L"CommandLine", GetCommandLine());
100 DWORD len = GetCurrentDirectory(0, nullptr);
101 if (len)
103 auto originalCurrentDirectory = std::make_unique<wchar_t[]>(len);
104 if (GetCurrentDirectory(len, originalCurrentDirectory.get()))
106 sOrigCWD = originalCurrentDirectory.get();
107 sOrigCWD = CPathUtils::GetLongPathname(sOrigCWD);
112 //set the resource dll for the required language
113 CRegDWORD loc = CRegDWORD(L"Software\\TortoiseGit\\LanguageID", 1033);
114 long langId = loc;
115 CString langDll;
116 HINSTANCE hInst = nullptr;
119 langDll.Format(L"%sLanguages\\TortoiseMerge%ld.dll", static_cast<LPCWSTR>(CPathUtils::GetAppParentDirectory()), langId);
121 hInst = LoadLibrary(langDll);
122 if (!CI18NHelper::DoVersionStringsMatch(CPathUtils::GetVersionFromFile(langDll), _T(STRPRODUCTVER)))
124 FreeLibrary(hInst);
125 hInst = nullptr;
127 if (hInst)
128 AfxSetResourceHandle(hInst);
129 else
131 DWORD lid = SUBLANGID(langId);
132 lid--;
133 if (lid > 0)
135 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
137 else
138 langId = 0;
140 } while ((!hInst) && (langId != 0));
142 CString langStr;
143 langStr.Format(L"%ld", langId);
144 CCrashReport::Instance().AddUserInfoToReport(L"LanguageID", langStr);
146 free((void*)m_pszHelpFilePath);
147 m_pszHelpFilePath = _wcsdup(CPathUtils::GetAppDirectory() + L"TortoiseMerge_en.chm");
148 setlocale(LC_ALL, "");
149 // We need to explicitly set the thread locale to the system default one to avoid possible problems with saving files in its original codepage
150 // The problems occures when the language of OS differs from the regional settings
151 // See the details here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100887
152 SetThreadLocale(LOCALE_SYSTEM_DEFAULT);
154 // InitCommonControls() is required on Windows XP if an application
155 // manifest specifies use of ComCtl32.dll version 6 or later to enable
156 // visual styles. Otherwise, any window creation will fail.
157 InitCommonControls();
159 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows7));
160 CMFCButton::EnableWindowsTheming();
161 EnableTaskbarInteraction(FALSE);
163 // Initialize all Managers for usage. They are automatically constructed
164 // if not yet present
165 InitContextMenuManager();
166 InitKeyboardManager();
167 InitTooltipManager ();
168 CMFCToolTipInfo params;
169 params.m_bVislManagerTheme = TRUE;
171 GetTooltipManager ()->SetTooltipParams (
172 AFX_TOOLTIP_TYPE_ALL,
173 RUNTIME_CLASS (CMFCToolTipCtrl),
174 &params);
176 CCmdLineParser parser(m_lpCmdLine);
178 g_sGroupingUUID = parser.GetVal(L"groupuuid");
180 if (parser.HasKey(L"?") || parser.HasKey(L"help"))
182 CString sHelpText;
183 sHelpText.LoadString(IDS_COMMANDLINEHELP);
184 MessageBox(nullptr, sHelpText, L"TortoiseGitMerge", MB_ICONINFORMATION);
185 return FALSE;
188 // Initialize OLE libraries
189 if (!AfxOleInit())
191 AfxMessageBox(IDP_OLE_INIT_FAILED);
192 return FALSE;
194 AfxEnableControlContainer();
195 // Standard initialization
196 // If you are not using these features and wish to reduce the size
197 // of your final executable, you should remove from the following
198 // the specific initialization routines you do not need
199 // Change the registry key under which our settings are stored
200 SetRegistryKey(L"TortoiseGitMerge");
202 if (CRegDWORD(L"Software\\TortoiseGitMerge\\Debug", FALSE)==TRUE)
203 AfxMessageBox(AfxGetApp()->m_lpCmdLine, MB_OK | MB_ICONINFORMATION);
205 // To create the main window, this code creates a new frame window
206 // object and then sets it as the application's main window object
207 CMainFrame* pFrame = new CMainFrame;
208 if (!pFrame)
209 return FALSE;
210 m_pMainWnd = pFrame;
211 m_nCmdShow = SW_HIDE;
212 // create and load the frame with its resources
213 if (!pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, nullptr, nullptr))
214 return FALSE;
215 if (pFrame->InitRibbon())
216 return FALSE;
218 // Fill in the command line options
219 pFrame->m_Data.m_baseFile.SetFileName(parser.GetVal(L"base"));
220 pFrame->m_Data.m_baseFile.SetDescriptiveName(parser.GetVal(L"basename"));
221 pFrame->m_Data.m_baseFile.SetReflectedName(parser.GetVal(L"basereflectedname"));
222 pFrame->m_Data.m_theirFile.SetFileName(parser.GetVal(L"theirs"));
223 pFrame->m_Data.m_theirFile.SetDescriptiveName(parser.GetVal(L"theirsname"));
224 pFrame->m_Data.m_theirFile.SetReflectedName(parser.GetVal(L"theirsreflectedname"));
225 pFrame->m_Data.m_yourFile.SetFileName(parser.GetVal(L"mine"));
226 pFrame->m_Data.m_yourFile.SetDescriptiveName(parser.GetVal(L"minename"));
227 pFrame->m_Data.m_yourFile.SetReflectedName(parser.GetVal(L"minereflectedname"));
228 pFrame->m_Data.m_mergedFile.SetFileName(parser.GetVal(L"merged"));
229 pFrame->m_Data.m_mergedFile.SetDescriptiveName(parser.GetVal(L"mergedname"));
230 pFrame->m_Data.m_mergedFile.SetReflectedName(parser.GetVal(L"mergedreflectedname"));
231 pFrame->m_Data.m_sPatchPath = parser.HasVal(L"patchpath") ? parser.GetVal(L"patchpath") : L"";
232 pFrame->m_Data.m_sPatchPath.Replace('/', '\\');
233 if (parser.HasKey(L"patchoriginal"))
234 pFrame->m_Data.m_sPatchOriginal = parser.GetVal(L"patchoriginal");
235 if (parser.HasKey(L"patchpatched"))
236 pFrame->m_Data.m_sPatchPatched = parser.GetVal(L"patchpatched");
237 pFrame->m_Data.m_sDiffFile = parser.GetVal(L"diff");
238 pFrame->m_Data.m_sDiffFile.Replace('/', '\\');
239 if (parser.HasKey(L"oneway"))
240 pFrame->m_bOneWay = TRUE;
241 if (parser.HasKey(L"diff"))
242 pFrame->m_bOneWay = FALSE;
243 if (parser.HasKey(L"reversedpatch"))
244 pFrame->m_bReversedPatch = TRUE;
245 if (parser.HasKey(L"saverequired"))
246 pFrame->m_bSaveRequired = true;
247 if (parser.HasKey(L"saverequiredonconflicts"))
248 pFrame->m_bSaveRequiredOnConflicts = true;
249 if (parser.HasKey(L"deletebasetheirsmineonclose"))
250 pFrame->m_bDeleteBaseTheirsMineOnClose = true;
251 if (pFrame->m_Data.IsBaseFileInUse() && !pFrame->m_Data.IsYourFileInUse() && pFrame->m_Data.IsTheirFileInUse())
253 pFrame->m_Data.m_yourFile.TransferDetailsFrom(pFrame->m_Data.m_theirFile);
256 if ((!parser.HasKey(L"patchpath")) && (parser.HasVal(L"diff")))
258 // a patchfile was given, but not folder path to apply the patch to
259 // If the patchfile is located inside a working copy, then use the parent directory
260 // of the patchfile as the target directory, otherwise ask the user for a path.
261 if (parser.HasKey(L"wc"))
262 pFrame->m_Data.m_sPatchPath = pFrame->m_Data.m_sDiffFile.Left(pFrame->m_Data.m_sDiffFile.ReverseFind('\\'));
263 else
265 CBrowseFolder fbrowser;
266 fbrowser.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
267 if (fbrowser.Show(nullptr, pFrame->m_Data.m_sPatchPath) == CBrowseFolder::CANCEL)
268 return FALSE;
272 if ((parser.HasKey(L"patchpath")) && (!parser.HasVal(L"diff")))
274 // A path was given for applying a patchfile, but
275 // the patchfile itself was not.
276 // So ask the user for that patchfile
278 HRESULT hr;
279 // Create a new common save file dialog
280 CComPtr<IFileOpenDialog> pfd;
281 hr = pfd.CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER);
282 if (SUCCEEDED(hr))
284 // Set the dialog options
285 DWORD dwOptions;
286 if (SUCCEEDED(hr = pfd->GetOptions(&dwOptions)))
288 hr = pfd->SetOptions(dwOptions | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
291 // Set a title
292 if (SUCCEEDED(hr))
294 CString temp;
295 temp.LoadString(IDS_OPENDIFFFILETITLE);
296 pfd->SetTitle(temp);
298 CSelectFileFilter fileFilter(IDS_PATCHFILEFILTER);
299 hr = pfd->SetFileTypes(fileFilter.GetCount(), fileFilter);
300 bool bAdvised = false;
301 DWORD dwCookie = 0;
302 CComObjectStackEx<PatchOpenDlgEventHandler> cbk;
303 CComQIPtr<IFileDialogEvents> pEvents = cbk.GetUnknown();
306 CComPtr<IFileDialogCustomize> pfdCustomize;
307 hr = pfd.QueryInterface(&pfdCustomize);
308 if (SUCCEEDED(hr))
310 // check if there's a unified diff on the clipboard and
311 // add a button to the fileopen dialog if there is.
312 UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
313 CClipboardHelper clipboardHelper;
314 if (cFormat && clipboardHelper.Open(nullptr))
316 HGLOBAL hglb = GetClipboardData(cFormat);
317 if (hglb)
319 pfdCustomize->AddPushButton(101, CString(MAKEINTRESOURCE(IDS_PATCH_COPYFROMCLIPBOARD)));
320 hr = pfd->Advise(pEvents, &dwCookie);
321 bAdvised = SUCCEEDED(hr);
327 // Show the save file dialog
328 if (SUCCEEDED(hr) && SUCCEEDED(hr = pfd->Show(pFrame->m_hWnd)))
330 // Get the selection from the user
331 CComPtr<IShellItem> psiResult;
332 hr = pfd->GetResult(&psiResult);
333 if (bAdvised)
334 pfd->Unadvise(dwCookie);
335 if (SUCCEEDED(hr))
337 CComHeapPtr<WCHAR> pszPath;
338 hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
339 if (SUCCEEDED(hr))
340 pFrame->m_Data.m_sDiffFile = pszPath;
342 else
344 // no result, which means we closed the dialog in our button handler
345 std::wstring sTempFile;
346 if (TrySavePatchFromClipboard(sTempFile))
347 pFrame->m_Data.m_sDiffFile = sTempFile.c_str();
350 else
352 if (bAdvised)
353 pfd->Unadvise(dwCookie);
354 return FALSE;
359 if ( pFrame->m_Data.m_baseFile.GetFilename().IsEmpty() && pFrame->m_Data.m_yourFile.GetFilename().IsEmpty() )
361 int nArgs;
362 LPWSTR *szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
363 if (!szArglist)
364 TRACE("CommandLineToArgvW failed\n");
365 else
367 if ( nArgs==3 || nArgs==4 )
369 // Four parameters:
370 // [0]: Program name
371 // [1]: BASE file
372 // [2]: my file
373 // [3]: THEIR file (optional)
374 // This is the same format CAppUtils::StartExtDiff
375 // uses if %base and %mine are not set and most
376 // other diff tools use it too.
377 if ( PathFileExists(szArglist[1]) && PathFileExists(szArglist[2]) )
379 pFrame->m_Data.m_baseFile.SetFileName(szArglist[1]);
380 pFrame->m_Data.m_yourFile.SetFileName(szArglist[2]);
381 if ( nArgs == 4 && PathFileExists(szArglist[3]) )
383 pFrame->m_Data.m_theirFile.SetFileName(szArglist[3]);
387 else if (nArgs == 2)
389 // only one path specified: use it to fill the "open" dialog
390 if (PathFileExists(szArglist[1]))
392 pFrame->m_Data.m_yourFile.SetFileName(szArglist[1]);
393 pFrame->m_Data.m_yourFile.StoreFileAttributes();
398 // Free memory allocated for CommandLineToArgvW arguments.
399 LocalFree(szArglist);
402 pFrame->m_bReadOnly = !!parser.HasKey(L"readonly");
403 if (GetFileAttributes(pFrame->m_Data.m_yourFile.GetFilename()) & FILE_ATTRIBUTE_READONLY)
404 pFrame->m_bReadOnly = true;
405 pFrame->m_bBlame = !!parser.HasKey(L"blame");
406 // diffing a blame means no editing!
407 if (pFrame->m_bBlame)
408 pFrame->m_bReadOnly = true;
410 pFrame->SetWindowTitle();
412 if (parser.HasKey(L"createunifieddiff"))
414 // user requested to create a unified diff file
415 CString origFile = parser.GetVal(L"origfile");
416 CString modifiedFile = parser.GetVal(L"modifiedfile");
417 if (!origFile.IsEmpty() && !modifiedFile.IsEmpty())
419 CString outfile = parser.GetVal(L"outfile");
420 if (outfile.IsEmpty())
422 CCommonAppUtils::FileOpenSave(outfile, nullptr, IDS_SAVEASTITLE, IDS_COMMONFILEFILTER, false);
424 if (!outfile.IsEmpty())
426 CRegStdDWORD regContextLines(L"Software\\TortoiseGitMerge\\ContextLines", static_cast<DWORD>(-1));
427 CAppUtils::CreateUnifiedDiff(origFile, modifiedFile, outfile, regContextLines, false);
428 return FALSE;
433 pFrame->resolveMsgWnd = parser.HasVal(L"resolvemsghwnd") ? reinterpret_cast<HWND>(parser.GetLongLongVal(L"resolvemsghwnd")) : 0;
434 pFrame->resolveMsgWParam = parser.HasVal(L"resolvemsgwparam") ? static_cast<WPARAM>(parser.GetLongLongVal(L"resolvemsgwparam")) : 0;
435 pFrame->resolveMsgLParam = parser.HasVal(L"resolvemsglparam") ? static_cast<LPARAM>(parser.GetLongLongVal(L"resolvemsglparam")) : 0;
437 // The one and only window has been initialized, so show and update it
438 pFrame->ActivateFrame();
439 pFrame->ShowWindow(SW_SHOW);
440 ::RedrawWindow(pFrame->GetSafeHwnd(), nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW);
441 pFrame->UpdateWindow();
442 pFrame->ShowDiffBar(!pFrame->m_bOneWay);
443 if (!pFrame->m_Data.IsBaseFileInUse() && pFrame->m_Data.m_sPatchPath.IsEmpty() && pFrame->m_Data.m_sDiffFile.IsEmpty())
445 pFrame->OnFileOpen(pFrame->m_Data.m_yourFile.InUse());
446 return TRUE;
449 int line = -2;
450 if (parser.HasVal(L"line"))
452 line = parser.GetLongVal(L"line");
453 line--; // we need the index
456 return pFrame->LoadViews(line);
459 BOOL CTortoiseMergeApp::LoadWindowPlacement(CRect& rectNormalPosition, int& nFflags, int& nShowCmd)
461 BOOL b = CWinAppEx::LoadWindowPlacement(rectNormalPosition, nFflags, nShowCmd);
462 nShowCmd = SW_HIDE;
463 return b;
466 // CTortoiseMergeApp message handlers
468 void CTortoiseMergeApp::OnAppAbout()
470 CAboutDlg aboutDlg;
471 aboutDlg.DoModal();
474 int CTortoiseMergeApp::ExitInstance()
476 // Look for temporary files left around by TortoiseMerge and
477 // remove them. But only delete 'old' files
478 CTempFiles::DeleteOldTempFiles(L"*tsm*.*");
480 CWinAppEx::ExitInstance();
481 return m_hasConflicts ? 1 : 0;
484 void CTortoiseMergeApp::OnClosingMainFrame(CFrameImpl* /*pFrameImpl*/)
486 if (auto pFrame = dynamic_cast<CMainFrame*>(m_pMainWnd))
488 if (pFrame->CheckResolved() >= 0)
489 m_hasConflicts = true;
493 bool CTortoiseMergeApp::HasClipboardPatch()
495 // check if there's a patchfile in the clipboard
496 const UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
497 if (cFormat == 0)
498 return false;
500 CClipboardHelper clipboardHelper;
501 if (!clipboardHelper.Open(nullptr))
502 return false;
504 bool containsPatch = false;
505 UINT enumFormat = 0;
508 if (enumFormat == cFormat)
510 containsPatch = true; // yes, there's a patchfile in the clipboard
512 } while((enumFormat = EnumClipboardFormats(enumFormat))!=0);
514 return containsPatch;
517 bool CTortoiseMergeApp::TrySavePatchFromClipboard(std::wstring& resultFile)
519 resultFile.clear();
521 UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
522 if (cFormat == 0)
523 return false;
524 CClipboardHelper clipboardHelper;
525 if (!clipboardHelper.Open(nullptr))
526 return false;
528 HGLOBAL hglb = GetClipboardData(cFormat);
529 auto lpstr = static_cast<LPCSTR>(GlobalLock(hglb));
531 DWORD len = GetTempPath(0, nullptr);
532 auto path = std::make_unique<wchar_t[]>(len + 1);
533 auto tempF = std::make_unique<wchar_t[]>(len + 100);
534 GetTempPath (len+1, path.get());
535 GetTempFileName (path.get(), L"tsm", 0, tempF.get());
536 std::wstring sTempFile = std::wstring(tempF.get());
538 FILE* outFile = 0;
539 _wfopen_s(&outFile, sTempFile.c_str(), L"wb");
540 if (outFile != 0)
542 size_t patchlen = strlen(lpstr);
543 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
544 if (size == patchlen)
545 resultFile = sTempFile;
547 fclose(outFile);
549 GlobalUnlock(hglb);
551 return !resultFile.empty();