TortoiseGitMerge: Use native ribbons
[TortoiseGit.git] / src / TortoiseMerge / TortoiseMerge.cpp
blob3001d479f03bcbe277ab76934eb04fabc53dcd16
1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2013-2016 - 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 "AppUtils.h"
28 #include "PathUtils.h"
29 #include "BrowseFolder.h"
30 #include "DirFileEnum.h"
31 #include "SelectFileFilter.h"
32 #include "FileDlgEventHandler.h"
33 #include "TempFile.h"
34 #include "TaskbarUUID.h"
36 #ifdef _DEBUG
37 #define new DEBUG_NEW
38 #endif
40 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
41 #pragma comment(lib, "Propsys.lib")
43 BEGIN_MESSAGE_MAP(CTortoiseMergeApp, CWinAppEx)
44 ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
45 END_MESSAGE_MAP()
47 class PatchOpenDlgEventHandler : public CFileDlgEventHandler
49 public:
50 PatchOpenDlgEventHandler() {}
51 ~PatchOpenDlgEventHandler() {}
53 virtual STDMETHODIMP OnButtonClicked(IFileDialogCustomize* pfdc, DWORD dwIDCtl)
55 if (dwIDCtl == 101)
57 CComQIPtr<IFileOpenDialog> pDlg = pfdc;
58 if (pDlg)
60 pDlg->Close(S_OK);
63 return S_OK;
68 CTortoiseMergeApp::CTortoiseMergeApp()
69 : m_nAppLook(0)
71 EnableHtmlHelp();
72 git_libgit2_init();
75 CTortoiseMergeApp::~CTortoiseMergeApp()
77 git_libgit2_shutdown();
80 // The one and only CTortoiseMergeApp object
81 CTortoiseMergeApp theApp;
82 CString sOrigCWD;
83 #if ENABLE_CRASHHANLDER
84 CCrashReportTGit g_crasher(L"TortoiseGitMerge " _T(APP_X64_STRING), TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD, TGIT_VERDATE);
85 #endif
87 CString g_sGroupingUUID;
88 CString g_sGroupingIcon;
89 bool g_bGroupingRemoveIcon = false;
91 // CTortoiseMergeApp initialization
92 BOOL CTortoiseMergeApp::InitInstance()
94 SetDllDirectory(L"");
95 SetTaskIDPerUUID();
96 CCrashReport::Instance().AddUserInfoToReport(L"CommandLine", GetCommandLine());
99 DWORD len = GetCurrentDirectory(0, nullptr);
100 if (len)
102 auto originalCurrentDirectory = std::make_unique<TCHAR[]>(len);
103 if (GetCurrentDirectory(len, originalCurrentDirectory.get()))
105 sOrigCWD = originalCurrentDirectory.get();
106 sOrigCWD = CPathUtils::GetLongPathname(sOrigCWD);
111 //set the resource dll for the required language
112 CRegDWORD loc = CRegDWORD(L"Software\\TortoiseGit\\LanguageID", 1033);
113 long langId = loc;
114 CString langDll;
115 HINSTANCE hInst = nullptr;
118 langDll.Format(L"%sLanguages\\TortoiseMerge%ld.dll", (LPCTSTR)CPathUtils::GetAppParentDirectory(), langId);
120 hInst = LoadLibrary(langDll);
121 CString sVer = _T(STRPRODUCTVER);
122 CString sFileVer = CPathUtils::GetVersionFromFile(langDll);
123 if (sFileVer.Compare(sVer)!=0)
125 FreeLibrary(hInst);
126 hInst = nullptr;
128 if (hInst)
129 AfxSetResourceHandle(hInst);
130 else
132 DWORD lid = SUBLANGID(langId);
133 lid--;
134 if (lid > 0)
136 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
138 else
139 langId = 0;
141 } while ((!hInst) && (langId != 0));
143 CString langStr;
144 langStr.Format(L"%ld", langId);
145 CCrashReport::Instance().AddUserInfoToReport(L"LanguageID", langStr);
147 TCHAR buf[6] = { 0 };
148 wcscpy_s(buf, L"en");
149 langId = loc;
150 CString sHelppath = CPathUtils::GetAppDirectory() + L"TortoiseMerge_en.chm";
151 free((void*)m_pszHelpFilePath);
152 m_pszHelpFilePath=_wcsdup(sHelppath);
153 sHelppath = CPathUtils::GetAppParentDirectory() + L"Languages\\TortoiseMerge_en.chm";
156 GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO639LANGNAME, buf, _countof(buf));
157 CString sLang = L"_";
158 sLang += buf;
159 sHelppath.Replace(L"_en", sLang);
160 if (PathFileExists(sHelppath))
162 free((void*)m_pszHelpFilePath);
163 m_pszHelpFilePath=_wcsdup(sHelppath);
164 break;
166 sHelppath.Replace(sLang, L"_en");
167 GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf));
168 sLang += L'_';
169 sLang += buf;
170 sHelppath.Replace(L"_en", sLang);
171 if (PathFileExists(sHelppath))
173 free((void*)m_pszHelpFilePath);
174 m_pszHelpFilePath=_wcsdup(sHelppath);
175 break;
177 sHelppath.Replace(sLang, L"_en");
179 DWORD lid = SUBLANGID(langId);
180 lid--;
181 if (lid > 0)
183 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
185 else
186 langId = 0;
187 } while (langId);
188 setlocale(LC_ALL, "");
189 // We need to explicitly set the thread locale to the system default one to avoid possible problems with saving files in its original codepage
190 // The problems occures when the language of OS differs from the regional settings
191 // See the details here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100887
192 SetThreadLocale(LOCALE_SYSTEM_DEFAULT);
194 // InitCommonControls() is required on Windows XP if an application
195 // manifest specifies use of ComCtl32.dll version 6 or later to enable
196 // visual styles. Otherwise, any window creation will fail.
197 InitCommonControls();
199 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
200 CMFCButton::EnableWindowsTheming();
201 EnableTaskbarInteraction(FALSE);
203 // Initialize all Managers for usage. They are automatically constructed
204 // if not yet present
205 InitContextMenuManager();
206 InitKeyboardManager();
207 InitTooltipManager ();
208 CMFCToolTipInfo params;
209 params.m_bVislManagerTheme = TRUE;
211 GetTooltipManager ()->SetTooltipParams (
212 AFX_TOOLTIP_TYPE_ALL,
213 RUNTIME_CLASS (CMFCToolTipCtrl),
214 &params);
216 CCmdLineParser parser(m_lpCmdLine);
218 g_sGroupingUUID = parser.GetVal(L"groupuuid");
220 if (parser.HasKey(L"?") || parser.HasKey(L"help"))
222 CString sHelpText;
223 sHelpText.LoadString(IDS_COMMANDLINEHELP);
224 MessageBox(nullptr, sHelpText, L"TortoiseGitMerge", MB_ICONINFORMATION);
225 return FALSE;
228 // Initialize OLE libraries
229 if (!AfxOleInit())
231 AfxMessageBox(IDP_OLE_INIT_FAILED);
232 return FALSE;
234 AfxEnableControlContainer();
235 // Standard initialization
236 // If you are not using these features and wish to reduce the size
237 // of your final executable, you should remove from the following
238 // the specific initialization routines you do not need
239 // Change the registry key under which our settings are stored
240 SetRegistryKey(L"TortoiseGitMerge");
242 if (CRegDWORD(L"Software\\TortoiseGitMerge\\Debug", FALSE)==TRUE)
243 AfxMessageBox(AfxGetApp()->m_lpCmdLine, MB_OK | MB_ICONINFORMATION);
245 // To create the main window, this code creates a new frame window
246 // object and then sets it as the application's main window object
247 CMainFrame* pFrame = new CMainFrame;
248 if (!pFrame)
249 return FALSE;
250 m_pMainWnd = pFrame;
252 // create and load the frame with its resources
253 if (!pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, nullptr, nullptr))
254 return FALSE;
256 // Fill in the command line options
257 pFrame->m_Data.m_baseFile.SetFileName(parser.GetVal(L"base"));
258 pFrame->m_Data.m_baseFile.SetDescriptiveName(parser.GetVal(L"basename"));
259 pFrame->m_Data.m_baseFile.SetReflectedName(parser.GetVal(L"basereflectedname"));
260 pFrame->m_Data.m_theirFile.SetFileName(parser.GetVal(L"theirs"));
261 pFrame->m_Data.m_theirFile.SetDescriptiveName(parser.GetVal(L"theirsname"));
262 pFrame->m_Data.m_theirFile.SetReflectedName(parser.GetVal(L"theirsreflectedname"));
263 pFrame->m_Data.m_yourFile.SetFileName(parser.GetVal(L"mine"));
264 pFrame->m_Data.m_yourFile.SetDescriptiveName(parser.GetVal(L"minename"));
265 pFrame->m_Data.m_yourFile.SetReflectedName(parser.GetVal(L"minereflectedname"));
266 pFrame->m_Data.m_mergedFile.SetFileName(parser.GetVal(L"merged"));
267 pFrame->m_Data.m_mergedFile.SetDescriptiveName(parser.GetVal(L"mergedname"));
268 pFrame->m_Data.m_mergedFile.SetReflectedName(parser.GetVal(L"mergedreflectedname"));
269 pFrame->m_Data.m_sPatchPath = parser.HasVal(L"patchpath") ? parser.GetVal(L"patchpath") : L"";
270 pFrame->m_Data.m_sPatchPath.Replace('/', '\\');
271 if (parser.HasKey(L"patchoriginal"))
272 pFrame->m_Data.m_sPatchOriginal = parser.GetVal(L"patchoriginal");
273 if (parser.HasKey(L"patchpatched"))
274 pFrame->m_Data.m_sPatchPatched = parser.GetVal(L"patchpatched");
275 pFrame->m_Data.m_sDiffFile = parser.GetVal(L"diff");
276 pFrame->m_Data.m_sDiffFile.Replace('/', '\\');
277 if (parser.HasKey(L"oneway"))
278 pFrame->m_bOneWay = TRUE;
279 if (parser.HasKey(L"diff"))
280 pFrame->m_bOneWay = FALSE;
281 if (parser.HasKey(L"reversedpatch"))
282 pFrame->m_bReversedPatch = TRUE;
283 if (parser.HasKey(L"saverequired"))
284 pFrame->m_bSaveRequired = true;
285 if (parser.HasKey(L"saverequiredonconflicts"))
286 pFrame->m_bSaveRequiredOnConflicts = true;
287 if (parser.HasKey(L"deletebasetheirsmineonclose"))
288 pFrame->m_bDeleteBaseTheirsMineOnClose = true;
289 if (pFrame->m_Data.IsBaseFileInUse() && !pFrame->m_Data.IsYourFileInUse() && pFrame->m_Data.IsTheirFileInUse())
291 pFrame->m_Data.m_yourFile.TransferDetailsFrom(pFrame->m_Data.m_theirFile);
294 if ((!parser.HasKey(L"patchpath")) && (parser.HasVal(L"diff")))
296 // a patchfile was given, but not folder path to apply the patch to
297 // If the patchfile is located inside a working copy, then use the parent directory
298 // of the patchfile as the target directory, otherwise ask the user for a path.
299 if (parser.HasKey(L"wc"))
300 pFrame->m_Data.m_sPatchPath = pFrame->m_Data.m_sDiffFile.Left(pFrame->m_Data.m_sDiffFile.ReverseFind('\\'));
301 else
303 CBrowseFolder fbrowser;
304 fbrowser.m_style = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS;
305 if (fbrowser.Show(nullptr, pFrame->m_Data.m_sPatchPath) == CBrowseFolder::CANCEL)
306 return FALSE;
310 if ((parser.HasKey(L"patchpath")) && (!parser.HasVal(L"diff")))
312 // A path was given for applying a patchfile, but
313 // the patchfile itself was not.
314 // So ask the user for that patchfile
316 HRESULT hr;
317 // Create a new common save file dialog
318 CComPtr<IFileOpenDialog> pfd;
319 hr = pfd.CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER);
320 if (SUCCEEDED(hr))
322 // Set the dialog options
323 DWORD dwOptions;
324 if (SUCCEEDED(hr = pfd->GetOptions(&dwOptions)))
326 hr = pfd->SetOptions(dwOptions | FOS_FILEMUSTEXIST | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
329 // Set a title
330 if (SUCCEEDED(hr))
332 CString temp;
333 temp.LoadString(IDS_OPENDIFFFILETITLE);
334 pfd->SetTitle(temp);
336 CSelectFileFilter fileFilter(IDS_PATCHFILEFILTER);
337 hr = pfd->SetFileTypes(fileFilter.GetCount(), fileFilter);
338 bool bAdvised = false;
339 DWORD dwCookie = 0;
340 CComObjectStackEx<PatchOpenDlgEventHandler> cbk;
341 CComQIPtr<IFileDialogEvents> pEvents = cbk.GetUnknown();
344 CComPtr<IFileDialogCustomize> pfdCustomize;
345 hr = pfd.QueryInterface(&pfdCustomize);
346 if (SUCCEEDED(hr))
348 // check if there's a unified diff on the clipboard and
349 // add a button to the fileopen dialog if there is.
350 UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
351 if ((cFormat) && (OpenClipboard(nullptr)))
353 HGLOBAL hglb = GetClipboardData(cFormat);
354 if (hglb)
356 pfdCustomize->AddPushButton(101, CString(MAKEINTRESOURCE(IDS_PATCH_COPYFROMCLIPBOARD)));
357 hr = pfd->Advise(pEvents, &dwCookie);
358 bAdvised = SUCCEEDED(hr);
360 CloseClipboard();
365 // Show the save file dialog
366 if (SUCCEEDED(hr) && SUCCEEDED(hr = pfd->Show(pFrame->m_hWnd)))
368 // Get the selection from the user
369 CComPtr<IShellItem> psiResult;
370 hr = pfd->GetResult(&psiResult);
371 if (bAdvised)
372 pfd->Unadvise(dwCookie);
373 if (SUCCEEDED(hr))
375 PWSTR pszPath = nullptr;
376 hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszPath);
377 if (SUCCEEDED(hr))
379 pFrame->m_Data.m_sDiffFile = pszPath;
380 CoTaskMemFree(pszPath);
383 else
385 // no result, which means we closed the dialog in our button handler
386 std::wstring sTempFile;
387 if (TrySavePatchFromClipboard(sTempFile))
388 pFrame->m_Data.m_sDiffFile = sTempFile.c_str();
391 else
393 if (bAdvised)
394 pfd->Unadvise(dwCookie);
395 return FALSE;
400 if ( pFrame->m_Data.m_baseFile.GetFilename().IsEmpty() && pFrame->m_Data.m_yourFile.GetFilename().IsEmpty() )
402 int nArgs;
403 LPWSTR *szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
404 if (!szArglist)
405 TRACE("CommandLineToArgvW failed\n");
406 else
408 if ( nArgs==3 || nArgs==4 )
410 // Four parameters:
411 // [0]: Program name
412 // [1]: BASE file
413 // [2]: my file
414 // [3]: THEIR file (optional)
415 // This is the same format CAppUtils::StartExtDiff
416 // uses if %base and %mine are not set and most
417 // other diff tools use it too.
418 if ( PathFileExists(szArglist[1]) && PathFileExists(szArglist[2]) )
420 pFrame->m_Data.m_baseFile.SetFileName(szArglist[1]);
421 pFrame->m_Data.m_yourFile.SetFileName(szArglist[2]);
422 if ( nArgs == 4 && PathFileExists(szArglist[3]) )
424 pFrame->m_Data.m_theirFile.SetFileName(szArglist[3]);
428 else if (nArgs == 2)
430 // only one path specified: use it to fill the "open" dialog
431 if (PathFileExists(szArglist[1]))
433 pFrame->m_Data.m_yourFile.SetFileName(szArglist[1]);
434 pFrame->m_Data.m_yourFile.StoreFileAttributes();
439 // Free memory allocated for CommandLineToArgvW arguments.
440 LocalFree(szArglist);
443 pFrame->m_bReadOnly = !!parser.HasKey(L"readonly");
444 if (GetFileAttributes(pFrame->m_Data.m_yourFile.GetFilename()) & FILE_ATTRIBUTE_READONLY)
445 pFrame->m_bReadOnly = true;
446 pFrame->m_bBlame = !!parser.HasKey(L"blame");
447 // diffing a blame means no editing!
448 if (pFrame->m_bBlame)
449 pFrame->m_bReadOnly = true;
451 pFrame->SetWindowTitle();
453 if (parser.HasKey(L"createunifieddiff"))
455 // user requested to create a unified diff file
456 CString origFile = parser.GetVal(L"origfile");
457 CString modifiedFile = parser.GetVal(L"modifiedfile");
458 if (!origFile.IsEmpty() && !modifiedFile.IsEmpty())
460 CString outfile = parser.GetVal(L"outfile");
461 if (outfile.IsEmpty())
463 CCommonAppUtils::FileOpenSave(outfile, nullptr, IDS_SAVEASTITLE, IDS_COMMONFILEFILTER, false);
465 if (!outfile.IsEmpty())
467 CRegStdDWORD regContextLines(L"Software\\TortoiseGitMerge\\ContextLines", (DWORD)-1);
468 CAppUtils::CreateUnifiedDiff(origFile, modifiedFile, outfile, regContextLines, false);
469 return FALSE;
474 pFrame->resolveMsgWnd = parser.HasVal(L"resolvemsghwnd") ? (HWND)parser.GetLongLongVal(L"resolvemsghwnd") : 0;
475 pFrame->resolveMsgWParam = parser.HasVal(L"resolvemsgwparam") ? (WPARAM)parser.GetLongLongVal(L"resolvemsgwparam") : 0;
476 pFrame->resolveMsgLParam = parser.HasVal(L"resolvemsglparam") ? (LPARAM)parser.GetLongLongVal(L"resolvemsglparam") : 0;
478 // The one and only window has been initialized, so show and update it
479 pFrame->ActivateFrame();
480 pFrame->ShowWindow(SW_SHOW);
481 pFrame->UpdateWindow();
482 pFrame->ShowDiffBar(!pFrame->m_bOneWay);
483 if (!pFrame->m_Data.IsBaseFileInUse() && pFrame->m_Data.m_sPatchPath.IsEmpty() && pFrame->m_Data.m_sDiffFile.IsEmpty())
485 pFrame->OnFileOpen(pFrame->m_Data.m_yourFile.InUse());
486 return TRUE;
489 int line = -2;
490 if (parser.HasVal(L"line"))
492 line = parser.GetLongVal(L"line");
493 line--; // we need the index
496 return pFrame->LoadViews(line);
499 // CTortoiseMergeApp message handlers
501 void CTortoiseMergeApp::OnAppAbout()
503 CAboutDlg aboutDlg;
504 aboutDlg.DoModal();
507 int CTortoiseMergeApp::ExitInstance()
509 // Look for temporary files left around by TortoiseMerge and
510 // remove them. But only delete 'old' files
511 CTempFiles::DeleteOldTempFiles(L"*tsm*.*");
513 return CWinAppEx::ExitInstance();
516 bool CTortoiseMergeApp::HasClipboardPatch()
518 // check if there's a patchfile in the clipboard
519 const UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
520 if (cFormat == 0)
521 return false;
523 if (OpenClipboard(nullptr) == 0)
524 return false;
526 bool containsPatch = false;
527 UINT enumFormat = 0;
530 if (enumFormat == cFormat)
532 containsPatch = true; // yes, there's a patchfile in the clipboard
534 } while((enumFormat = EnumClipboardFormats(enumFormat))!=0);
535 CloseClipboard();
537 return containsPatch;
540 bool CTortoiseMergeApp::TrySavePatchFromClipboard(std::wstring& resultFile)
542 resultFile.clear();
544 UINT cFormat = RegisterClipboardFormat(L"TGIT_UNIFIEDDIFF");
545 if (cFormat == 0)
546 return false;
547 if (OpenClipboard(nullptr) == 0)
548 return false;
550 HGLOBAL hglb = GetClipboardData(cFormat);
551 LPCSTR lpstr = (LPCSTR)GlobalLock(hglb);
553 DWORD len = GetTempPath(0, nullptr);
554 auto path = std::make_unique<TCHAR[]>(len + 1);
555 auto tempF = std::make_unique<TCHAR[]>(len + 100);
556 GetTempPath (len+1, path.get());
557 GetTempFileName (path.get(), L"tsm", 0, tempF.get());
558 std::wstring sTempFile = std::wstring(tempF.get());
560 FILE* outFile = 0;
561 _wfopen_s(&outFile, sTempFile.c_str(), L"wb");
562 if (outFile != 0)
564 size_t patchlen = strlen(lpstr);
565 size_t size = fwrite(lpstr, sizeof(char), patchlen, outFile);
566 if (size == patchlen)
567 resultFile = sTempFile;
569 fclose(outFile);
571 GlobalUnlock(hglb);
572 CloseClipboard();
574 return !resultFile.empty();