1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2013-2014 - TortoiseGit
4 // Copyright (C) 2006-2014 - 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.
22 #include "TortoiseMerge.h"
25 #include "CmdLineParser.h"
28 #include "PathUtils.h"
29 #include "BrowseFolder.h"
30 #include "DirFileEnum.h"
31 #include "SelectFileFilter.h"
32 #include "FileDlgEventHandler.h"
34 #include "TaskbarUUID.h"
35 #include "git2/threads.h"
41 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
43 BEGIN_MESSAGE_MAP(CTortoiseMergeApp
, CWinAppEx
)
44 ON_COMMAND(ID_APP_ABOUT
, OnAppAbout
)
47 class PatchOpenDlgEventHandler
: public CFileDlgEventHandler
50 PatchOpenDlgEventHandler() {}
51 ~PatchOpenDlgEventHandler() {}
53 virtual STDMETHODIMP
OnButtonClicked(IFileDialogCustomize
* pfdc
, DWORD dwIDCtl
)
57 CComQIPtr
<IFileOpenDialog
> pDlg
= pfdc
;
68 CTortoiseMergeApp::CTortoiseMergeApp()
72 m_bHiColorIcons
= TRUE
;
76 CTortoiseMergeApp::~CTortoiseMergeApp()
78 git_libgit2_shutdown();
81 // The one and only CTortoiseMergeApp object
82 CTortoiseMergeApp theApp
;
84 #if ENABLE_CRASHHANLDER
85 CCrashReportTGit
g_crasher(L
"TortoiseGitMerge " _T(APP_X64_STRING
), TGIT_VERMAJOR
, TGIT_VERMINOR
, TGIT_VERMICRO
, TGIT_VERBUILD
, TGIT_VERDATE
);
88 CString g_sGroupingUUID
;
89 CString g_sGroupingIcon
;
90 bool g_bGroupingRemoveIcon
= false;
92 // CTortoiseMergeApp initialization
93 BOOL
CTortoiseMergeApp::InitInstance()
97 CCrashReport::Instance().AddUserInfoToReport(L
"CommandLine", GetCommandLine());
100 DWORD len
= GetCurrentDirectory(0, NULL
);
103 std::unique_ptr
<TCHAR
[]> originalCurrentDirectory(new TCHAR
[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(_T("Software\\TortoiseGit\\LanguageID"), 1033);
116 HINSTANCE hInst
= NULL
;
119 langDll
.Format(_T("%sLanguages\\TortoiseMerge%ld.dll"), (LPCTSTR
)CPathUtils::GetAppParentDirectory(), langId
);
121 hInst
= LoadLibrary(langDll
);
122 CString sVer
= _T(STRPRODUCTVER
);
123 CString sFileVer
= CPathUtils::GetVersionFromFile(langDll
);
124 if (sFileVer
.Compare(sVer
)!=0)
130 AfxSetResourceHandle(hInst
);
133 DWORD lid
= SUBLANGID(langId
);
137 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
142 } while ((hInst
== NULL
) && (langId
!= 0));
143 TCHAR buf
[6] = { 0 };
144 _tcscpy_s(buf
, _T("en"));
146 CString sHelppath
= CPathUtils::GetAppDirectory() + _T("TortoiseMerge_en.chm");
147 free((void*)m_pszHelpFilePath
);
148 m_pszHelpFilePath
=_tcsdup(sHelppath
);
149 sHelppath
= CPathUtils::GetAppParentDirectory() + _T("Languages\\TortoiseMerge_en.chm");
152 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO639LANGNAME
, buf
, _countof(buf
));
153 CString sLang
= _T("_");
155 sHelppath
.Replace(_T("_en"), sLang
);
156 if (PathFileExists(sHelppath
))
158 free((void*)m_pszHelpFilePath
);
159 m_pszHelpFilePath
=_tcsdup(sHelppath
);
162 sHelppath
.Replace(sLang
, _T("_en"));
163 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO3166CTRYNAME
, buf
, _countof(buf
));
166 sHelppath
.Replace(_T("_en"), sLang
);
167 if (PathFileExists(sHelppath
))
169 free((void*)m_pszHelpFilePath
);
170 m_pszHelpFilePath
=_tcsdup(sHelppath
);
173 sHelppath
.Replace(sLang
, _T("_en"));
175 DWORD lid
= SUBLANGID(langId
);
179 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
184 setlocale(LC_ALL
, "");
185 // We need to explicitly set the thread locale to the system default one to avoid possible problems with saving files in its original codepage
186 // The problems occures when the language of OS differs from the regional settings
187 // See the details here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100887
188 SetThreadLocale(LOCALE_SYSTEM_DEFAULT
);
190 // InitCommonControls() is required on Windows XP if an application
191 // manifest specifies use of ComCtl32.dll version 6 or later to enable
192 // visual styles. Otherwise, any window creation will fail.
193 InitCommonControls();
195 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows
));
196 CMFCButton::EnableWindowsTheming();
197 EnableTaskbarInteraction(FALSE
);
199 // Initialize all Managers for usage. They are automatically constructed
200 // if not yet present
201 InitContextMenuManager();
202 InitKeyboardManager();
203 InitTooltipManager ();
204 CMFCToolTipInfo params
;
205 params
.m_bVislManagerTheme
= TRUE
;
207 GetTooltipManager ()->SetTooltipParams (
208 AFX_TOOLTIP_TYPE_ALL
,
209 RUNTIME_CLASS (CMFCToolTipCtrl
),
212 CCmdLineParser parser
= CCmdLineParser(this->m_lpCmdLine
);
214 g_sGroupingUUID
= parser
.GetVal(L
"groupuuid");
216 if (parser
.HasKey(_T("?")) || parser
.HasKey(_T("help")))
219 sHelpText
.LoadString(IDS_COMMANDLINEHELP
);
220 MessageBox(NULL
, sHelpText
, _T("TortoiseGitMerge"), MB_ICONINFORMATION
);
224 // Initialize OLE libraries
227 AfxMessageBox(IDP_OLE_INIT_FAILED
);
230 AfxEnableControlContainer();
231 // Standard initialization
232 // If you are not using these features and wish to reduce the size
233 // of your final executable, you should remove from the following
234 // the specific initialization routines you do not need
235 // Change the registry key under which our settings are stored
236 SetRegistryKey(_T("TortoiseGitMerge"));
238 if (CRegDWORD(_T("Software\\TortoiseGitMerge\\Debug"), FALSE
)==TRUE
)
239 AfxMessageBox(AfxGetApp()->m_lpCmdLine
, MB_OK
| MB_ICONINFORMATION
);
241 // To create the main window, this code creates a new frame window
242 // object and then sets it as the application's main window object
243 CMainFrame
* pFrame
= new CMainFrame
;
248 // create and load the frame with its resources
249 if (!pFrame
->LoadFrame(IDR_MAINFRAME
, WS_OVERLAPPEDWINDOW
| FWS_ADDTOTITLE
, NULL
, NULL
))
252 // Fill in the command line options
253 pFrame
->m_Data
.m_baseFile
.SetFileName(parser
.GetVal(_T("base")));
254 pFrame
->m_Data
.m_baseFile
.SetDescriptiveName(parser
.GetVal(_T("basename")));
255 pFrame
->m_Data
.m_baseFile
.SetReflectedName(parser
.GetVal(_T("basereflectedname")));
256 pFrame
->m_Data
.m_theirFile
.SetFileName(parser
.GetVal(_T("theirs")));
257 pFrame
->m_Data
.m_theirFile
.SetDescriptiveName(parser
.GetVal(_T("theirsname")));
258 pFrame
->m_Data
.m_theirFile
.SetReflectedName(parser
.GetVal(_T("theirsreflectedname")));
259 pFrame
->m_Data
.m_yourFile
.SetFileName(parser
.GetVal(_T("mine")));
260 pFrame
->m_Data
.m_yourFile
.SetDescriptiveName(parser
.GetVal(_T("minename")));
261 pFrame
->m_Data
.m_yourFile
.SetReflectedName(parser
.GetVal(_T("minereflectedname")));
262 pFrame
->m_Data
.m_mergedFile
.SetFileName(parser
.GetVal(_T("merged")));
263 pFrame
->m_Data
.m_mergedFile
.SetDescriptiveName(parser
.GetVal(_T("mergedname")));
264 pFrame
->m_Data
.m_mergedFile
.SetReflectedName(parser
.GetVal(_T("mergedreflectedname")));
265 pFrame
->m_Data
.m_sPatchPath
= parser
.HasVal(_T("patchpath")) ? parser
.GetVal(_T("patchpath")) : _T("");
266 pFrame
->m_Data
.m_sPatchPath
.Replace('/', '\\');
267 if (parser
.HasKey(_T("patchoriginal")))
268 pFrame
->m_Data
.m_sPatchOriginal
= parser
.GetVal(_T("patchoriginal"));
269 if (parser
.HasKey(_T("patchpatched")))
270 pFrame
->m_Data
.m_sPatchPatched
= parser
.GetVal(_T("patchpatched"));
271 pFrame
->m_Data
.m_sDiffFile
= parser
.GetVal(_T("diff"));
272 pFrame
->m_Data
.m_sDiffFile
.Replace('/', '\\');
273 if (parser
.HasKey(_T("oneway")))
274 pFrame
->m_bOneWay
= TRUE
;
275 if (parser
.HasKey(_T("diff")))
276 pFrame
->m_bOneWay
= FALSE
;
277 if (parser
.HasKey(_T("reversedpatch")))
278 pFrame
->m_bReversedPatch
= TRUE
;
279 if (parser
.HasKey(_T("saverequired")))
280 pFrame
->m_bSaveRequired
= true;
281 if (parser
.HasKey(_T("saverequiredonconflicts")))
282 pFrame
->m_bSaveRequiredOnConflicts
= true;
283 if (parser
.HasKey(_T("deletebasetheirsmineonclose")))
284 pFrame
->m_bDeleteBaseTheirsMineOnClose
= true;
285 if (pFrame
->m_Data
.IsBaseFileInUse() && !pFrame
->m_Data
.IsYourFileInUse() && pFrame
->m_Data
.IsTheirFileInUse())
287 pFrame
->m_Data
.m_yourFile
.TransferDetailsFrom(pFrame
->m_Data
.m_theirFile
);
290 if ((!parser
.HasKey(_T("patchpath")))&&(parser
.HasVal(_T("diff"))))
292 // a patchfile was given, but not folder path to apply the patch to
293 // If the patchfile is located inside a working copy, then use the parent directory
294 // of the patchfile as the target directory, otherwise ask the user for a path.
295 if (parser
.HasKey(_T("wc")))
296 pFrame
->m_Data
.m_sPatchPath
= pFrame
->m_Data
.m_sDiffFile
.Left(pFrame
->m_Data
.m_sDiffFile
.ReverseFind('\\'));
299 CBrowseFolder fbrowser
;
300 fbrowser
.m_style
= BIF_EDITBOX
| BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
301 if (fbrowser
.Show(NULL
, pFrame
->m_Data
.m_sPatchPath
)==CBrowseFolder::CANCEL
)
306 if ((parser
.HasKey(_T("patchpath")))&&(!parser
.HasVal(_T("diff"))))
308 // A path was given for applying a patchfile, but
309 // the patchfile itself was not.
310 // So ask the user for that patchfile
313 // Create a new common save file dialog
314 CComPtr
<IFileOpenDialog
> pfd
= NULL
;
315 hr
= pfd
.CoCreateInstance(CLSID_FileOpenDialog
, NULL
, CLSCTX_INPROC_SERVER
);
318 // Set the dialog options
320 if (SUCCEEDED(hr
= pfd
->GetOptions(&dwOptions
)))
322 hr
= pfd
->SetOptions(dwOptions
| FOS_FILEMUSTEXIST
| FOS_FORCEFILESYSTEM
| FOS_PATHMUSTEXIST
);
329 temp
.LoadString(IDS_OPENDIFFFILETITLE
);
332 CSelectFileFilter
fileFilter(IDS_PATCHFILEFILTER
);
333 hr
= pfd
->SetFileTypes(fileFilter
.GetCount(), fileFilter
);
334 bool bAdvised
= false;
336 CComObjectStackEx
<PatchOpenDlgEventHandler
> cbk
;
337 CComQIPtr
<IFileDialogEvents
> pEvents
= cbk
.GetUnknown();
340 CComPtr
<IFileDialogCustomize
> pfdCustomize
;
341 hr
= pfd
->QueryInterface(IID_PPV_ARGS(&pfdCustomize
));
344 // check if there's a unified diff on the clipboard and
345 // add a button to the fileopen dialog if there is.
346 UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
347 if ((cFormat
)&&(OpenClipboard(NULL
)))
349 HGLOBAL hglb
= GetClipboardData(cFormat
);
352 pfdCustomize
->AddPushButton(101, CString(MAKEINTRESOURCE(IDS_PATCH_COPYFROMCLIPBOARD
)));
353 hr
= pfd
->Advise(pEvents
, &dwCookie
);
354 bAdvised
= SUCCEEDED(hr
);
361 // Show the save file dialog
362 if (SUCCEEDED(hr
) && SUCCEEDED(hr
= pfd
->Show(pFrame
->m_hWnd
)))
364 // Get the selection from the user
365 CComPtr
<IShellItem
> psiResult
= NULL
;
366 hr
= pfd
->GetResult(&psiResult
);
368 pfd
->Unadvise(dwCookie
);
371 PWSTR pszPath
= NULL
;
372 hr
= psiResult
->GetDisplayName(SIGDN_FILESYSPATH
, &pszPath
);
375 pFrame
->m_Data
.m_sDiffFile
= pszPath
;
376 CoTaskMemFree(pszPath
);
381 // no result, which means we closed the dialog in our button handler
382 std::wstring sTempFile
;
383 if (TrySavePatchFromClipboard(sTempFile
))
384 pFrame
->m_Data
.m_sDiffFile
= sTempFile
.c_str();
390 pfd
->Unadvise(dwCookie
);
396 OPENFILENAME ofn
= {0}; // common dialog box structure
397 TCHAR szFile
[MAX_PATH
] = {0}; // buffer for file name
398 // Initialize OPENFILENAME
399 ofn
.lStructSize
= sizeof(OPENFILENAME
);
400 ofn
.hwndOwner
= pFrame
->m_hWnd
;
401 ofn
.lpstrFile
= szFile
;
402 ofn
.nMaxFile
= _countof(szFile
);
404 temp
.LoadString(IDS_OPENDIFFFILETITLE
);
406 ofn
.lpstrTitle
= NULL
;
408 ofn
.lpstrTitle
= temp
;
410 ofn
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_EXPLORER
;
411 if( HasClipboardPatch() ) {
412 ofn
.Flags
|= ( OFN_ENABLETEMPLATE
| OFN_ENABLEHOOK
);
413 ofn
.hInstance
= AfxGetResourceHandle();
414 ofn
.lpTemplateName
= MAKEINTRESOURCE(IDD_PATCH_FILE_OPEN_CUSTOM
);
415 ofn
.lpfnHook
= CreatePatchFileOpenHook
;
418 CSelectFileFilter
fileFilter(IDS_PATCHFILEFILTER
);
419 ofn
.lpstrFilter
= fileFilter
;
420 ofn
.nFilterIndex
= 1;
422 // Display the Open dialog box.
423 if (GetOpenFileName(&ofn
)==FALSE
)
427 pFrame
->m_Data
.m_sDiffFile
= ofn
.lpstrFile
;
431 if ( pFrame
->m_Data
.m_baseFile
.GetFilename().IsEmpty() && pFrame
->m_Data
.m_yourFile
.GetFilename().IsEmpty() )
434 LPWSTR
*szArglist
= CommandLineToArgvW(GetCommandLineW(), &nArgs
);
435 if( NULL
== szArglist
)
437 TRACE("CommandLineToArgvW failed\n");
441 if ( nArgs
==3 || nArgs
==4 )
447 // [3]: THEIR file (optional)
448 // This is the same format CAppUtils::StartExtDiff
449 // uses if %base and %mine are not set and most
450 // other diff tools use it too.
451 if ( PathFileExists(szArglist
[1]) && PathFileExists(szArglist
[2]) )
453 pFrame
->m_Data
.m_baseFile
.SetFileName(szArglist
[1]);
454 pFrame
->m_Data
.m_yourFile
.SetFileName(szArglist
[2]);
455 if ( nArgs
== 4 && PathFileExists(szArglist
[3]) )
457 pFrame
->m_Data
.m_theirFile
.SetFileName(szArglist
[3]);
463 // only one path specified: use it to fill the "open" dialog
464 if (PathFileExists(szArglist
[1]))
466 pFrame
->m_Data
.m_yourFile
.SetFileName(szArglist
[1]);
467 pFrame
->m_Data
.m_yourFile
.StoreFileAttributes();
472 // Free memory allocated for CommandLineToArgvW arguments.
473 LocalFree(szArglist
);
476 pFrame
->m_bReadOnly
= !!parser
.HasKey(_T("readonly"));
477 if (GetFileAttributes(pFrame
->m_Data
.m_yourFile
.GetFilename()) & FILE_ATTRIBUTE_READONLY
)
478 pFrame
->m_bReadOnly
= true;
479 pFrame
->m_bBlame
= !!parser
.HasKey(_T("blame"));
480 // diffing a blame means no editing!
481 if (pFrame
->m_bBlame
)
482 pFrame
->m_bReadOnly
= true;
484 pFrame
->SetWindowTitle();
486 if (parser
.HasKey(_T("createunifieddiff")))
488 // user requested to create a unified diff file
489 CString origFile
= parser
.GetVal(_T("origfile"));
490 CString modifiedFile
= parser
.GetVal(_T("modifiedfile"));
491 if (!origFile
.IsEmpty() && !modifiedFile
.IsEmpty())
493 CString outfile
= parser
.GetVal(_T("outfile"));
494 if (outfile
.IsEmpty())
496 CCommonAppUtils::FileOpenSave(outfile
, NULL
, IDS_SAVEASTITLE
, IDS_COMMONFILEFILTER
, false, NULL
);
498 if (!outfile
.IsEmpty())
500 CRegStdDWORD
regContextLines(L
"Software\\TortoiseGitMerge\\ContextLines", (DWORD
)-1);
501 CAppUtils::CreateUnifiedDiff(origFile
, modifiedFile
, outfile
, regContextLines
, false);
507 pFrame
->resolveMsgWnd
= parser
.HasVal(L
"resolvemsghwnd") ? (HWND
)parser
.GetLongLongVal(L
"resolvemsghwnd") : 0;
508 pFrame
->resolveMsgWParam
= parser
.HasVal(L
"resolvemsgwparam") ? (WPARAM
)parser
.GetLongLongVal(L
"resolvemsgwparam") : 0;
509 pFrame
->resolveMsgLParam
= parser
.HasVal(L
"resolvemsglparam") ? (LPARAM
)parser
.GetLongLongVal(L
"resolvemsglparam") : 0;
511 // The one and only window has been initialized, so show and update it
512 pFrame
->ActivateFrame();
513 pFrame
->ShowWindow(SW_SHOW
);
514 pFrame
->UpdateWindow();
515 pFrame
->ShowDiffBar(!pFrame
->m_bOneWay
);
516 if (!pFrame
->m_Data
.IsBaseFileInUse() && pFrame
->m_Data
.m_sPatchPath
.IsEmpty() && pFrame
->m_Data
.m_sDiffFile
.IsEmpty())
518 pFrame
->OnFileOpen(pFrame
->m_Data
.m_yourFile
.InUse());
523 if (parser
.HasVal(_T("line")))
525 line
= parser
.GetLongVal(_T("line"));
526 line
--; // we need the index
529 return pFrame
->LoadViews(line
);
532 // CTortoiseMergeApp message handlers
534 void CTortoiseMergeApp::OnAppAbout()
541 CTortoiseMergeApp::CreatePatchFileOpenHook(HWND hDlg
, UINT uiMsg
, WPARAM wParam
, LPARAM
/*lParam*/)
543 if(uiMsg
== WM_COMMAND
&& LOWORD(wParam
) == IDC_PATCH_TO_CLIPBOARD
)
545 HWND hFileDialog
= GetParent(hDlg
);
547 // if there's a patchfile in the clipboard, we save it
548 // to a temporary file and tell TortoiseMerge to use that one
549 std::wstring sTempFile
;
550 if (TrySavePatchFromClipboard(sTempFile
))
552 CommDlg_OpenSave_SetControlText(hFileDialog
, edt1
, sTempFile
.c_str());
553 PostMessage(hFileDialog
, WM_COMMAND
, MAKEWPARAM(IDOK
, BM_CLICK
), (LPARAM
)(GetDlgItem(hDlg
, IDOK
)));
559 int CTortoiseMergeApp::ExitInstance()
561 // Look for temporary files left around by TortoiseMerge and
562 // remove them. But only delete 'old' files
563 CTempFiles::DeleteOldTempFiles(_T("*tsm*.*"));
565 return CWinAppEx::ExitInstance();
568 bool CTortoiseMergeApp::HasClipboardPatch()
570 // check if there's a patchfile in the clipboard
571 const UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
575 if (OpenClipboard(NULL
) == 0)
578 bool containsPatch
= false;
582 if (enumFormat
== cFormat
)
584 containsPatch
= true; // yes, there's a patchfile in the clipboard
586 } while((enumFormat
= EnumClipboardFormats(enumFormat
))!=0);
589 return containsPatch
;
592 bool CTortoiseMergeApp::TrySavePatchFromClipboard(std::wstring
& resultFile
)
596 UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
599 if (OpenClipboard(NULL
) == 0)
602 HGLOBAL hglb
= GetClipboardData(cFormat
);
603 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
605 DWORD len
= GetTempPath(0, NULL
);
606 std::unique_ptr
<TCHAR
[]> path(new TCHAR
[len
+1]);
607 std::unique_ptr
<TCHAR
[]> tempF(new TCHAR
[len
+100]);
608 GetTempPath (len
+1, path
.get());
609 GetTempFileName (path
.get(), _T("tsm"), 0, tempF
.get());
610 std::wstring sTempFile
= std::wstring(tempF
.get());
613 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
616 size_t patchlen
= strlen(lpstr
);
617 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
618 if (size
== patchlen
)
619 resultFile
= sTempFile
;
626 return !resultFile
.empty();