1 // TortoiseGitMerge - a Diff/Patch program
3 // Copyright (C) 2013-2017 - 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.
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"
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
)
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()
74 CTortoiseMergeApp::~CTortoiseMergeApp()
76 git_libgit2_shutdown();
79 // The one and only CTortoiseMergeApp object
80 CTortoiseMergeApp theApp
;
82 #if ENABLE_CRASHHANLDER
83 CCrashReportTGit
g_crasher(L
"TortoiseGitMerge " _T(APP_X64_STRING
), TGIT_VERMAJOR
, TGIT_VERMINOR
, TGIT_VERMICRO
, TGIT_VERBUILD
, TGIT_VERDATE
);
86 CString g_sGroupingUUID
;
87 CString g_sGroupingIcon
;
88 bool g_bGroupingRemoveIcon
= false;
90 // CTortoiseMergeApp initialization
91 BOOL
CTortoiseMergeApp::InitInstance()
95 CCrashReport::Instance().AddUserInfoToReport(L
"CommandLine", GetCommandLine());
98 DWORD len
= GetCurrentDirectory(0, nullptr);
101 auto originalCurrentDirectory
= std::make_unique
<TCHAR
[]>(len
);
102 if (GetCurrentDirectory(len
, originalCurrentDirectory
.get()))
104 sOrigCWD
= originalCurrentDirectory
.get();
105 sOrigCWD
= CPathUtils::GetLongPathname(sOrigCWD
);
110 //set the resource dll for the required language
111 CRegDWORD loc
= CRegDWORD(L
"Software\\TortoiseGit\\LanguageID", 1033);
114 HINSTANCE hInst
= nullptr;
117 langDll
.Format(L
"%sLanguages\\TortoiseMerge%ld.dll", (LPCTSTR
)CPathUtils::GetAppParentDirectory(), langId
);
119 hInst
= LoadLibrary(langDll
);
120 CString sVer
= _T(STRPRODUCTVER
);
121 CString sFileVer
= CPathUtils::GetVersionFromFile(langDll
);
122 if (sFileVer
.Compare(sVer
)!=0)
128 AfxSetResourceHandle(hInst
);
131 DWORD lid
= SUBLANGID(langId
);
135 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
140 } while ((!hInst
) && (langId
!= 0));
143 langStr
.Format(L
"%ld", langId
);
144 CCrashReport::Instance().AddUserInfoToReport(L
"LanguageID", langStr
);
146 TCHAR buf
[6] = { 0 };
147 wcscpy_s(buf
, L
"en");
149 CString sHelppath
= CPathUtils::GetAppDirectory() + L
"TortoiseMerge_en.chm";
150 free((void*)m_pszHelpFilePath
);
151 m_pszHelpFilePath
=_wcsdup(sHelppath
);
152 sHelppath
= CPathUtils::GetAppParentDirectory() + L
"Languages\\TortoiseMerge_en.chm";
155 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO639LANGNAME
, buf
, _countof(buf
));
156 CString sLang
= L
"_";
158 sHelppath
.Replace(L
"_en", sLang
);
159 if (PathFileExists(sHelppath
))
161 free((void*)m_pszHelpFilePath
);
162 m_pszHelpFilePath
=_wcsdup(sHelppath
);
165 sHelppath
.Replace(sLang
, L
"_en");
166 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO3166CTRYNAME
, buf
, _countof(buf
));
169 sHelppath
.Replace(L
"_en", sLang
);
170 if (PathFileExists(sHelppath
))
172 free((void*)m_pszHelpFilePath
);
173 m_pszHelpFilePath
=_wcsdup(sHelppath
);
176 sHelppath
.Replace(sLang
, L
"_en");
178 DWORD lid
= SUBLANGID(langId
);
182 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
187 setlocale(LC_ALL
, "");
188 // We need to explicitly set the thread locale to the system default one to avoid possible problems with saving files in its original codepage
189 // The problems occures when the language of OS differs from the regional settings
190 // See the details here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100887
191 SetThreadLocale(LOCALE_SYSTEM_DEFAULT
);
193 // InitCommonControls() is required on Windows XP if an application
194 // manifest specifies use of ComCtl32.dll version 6 or later to enable
195 // visual styles. Otherwise, any window creation will fail.
196 InitCommonControls();
198 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows7
));
199 CMFCButton::EnableWindowsTheming();
200 EnableTaskbarInteraction(FALSE
);
202 // Initialize all Managers for usage. They are automatically constructed
203 // if not yet present
204 InitContextMenuManager();
205 InitKeyboardManager();
206 InitTooltipManager ();
207 CMFCToolTipInfo params
;
208 params
.m_bVislManagerTheme
= TRUE
;
210 GetTooltipManager ()->SetTooltipParams (
211 AFX_TOOLTIP_TYPE_ALL
,
212 RUNTIME_CLASS (CMFCToolTipCtrl
),
215 CCmdLineParser
parser(m_lpCmdLine
);
217 g_sGroupingUUID
= parser
.GetVal(L
"groupuuid");
219 if (parser
.HasKey(L
"?") || parser
.HasKey(L
"help"))
222 sHelpText
.LoadString(IDS_COMMANDLINEHELP
);
223 MessageBox(nullptr, sHelpText
, L
"TortoiseGitMerge", MB_ICONINFORMATION
);
227 // Initialize OLE libraries
230 AfxMessageBox(IDP_OLE_INIT_FAILED
);
233 AfxEnableControlContainer();
234 // Standard initialization
235 // If you are not using these features and wish to reduce the size
236 // of your final executable, you should remove from the following
237 // the specific initialization routines you do not need
238 // Change the registry key under which our settings are stored
239 SetRegistryKey(L
"TortoiseGitMerge");
241 if (CRegDWORD(L
"Software\\TortoiseGitMerge\\Debug", FALSE
)==TRUE
)
242 AfxMessageBox(AfxGetApp()->m_lpCmdLine
, MB_OK
| MB_ICONINFORMATION
);
244 // To create the main window, this code creates a new frame window
245 // object and then sets it as the application's main window object
246 CMainFrame
* pFrame
= new CMainFrame
;
251 // create and load the frame with its resources
252 if (!pFrame
->LoadFrame(IDR_MAINFRAME
, WS_OVERLAPPEDWINDOW
| FWS_ADDTOTITLE
, nullptr, nullptr))
255 // Fill in the command line options
256 pFrame
->m_Data
.m_baseFile
.SetFileName(parser
.GetVal(L
"base"));
257 pFrame
->m_Data
.m_baseFile
.SetDescriptiveName(parser
.GetVal(L
"basename"));
258 pFrame
->m_Data
.m_baseFile
.SetReflectedName(parser
.GetVal(L
"basereflectedname"));
259 pFrame
->m_Data
.m_theirFile
.SetFileName(parser
.GetVal(L
"theirs"));
260 pFrame
->m_Data
.m_theirFile
.SetDescriptiveName(parser
.GetVal(L
"theirsname"));
261 pFrame
->m_Data
.m_theirFile
.SetReflectedName(parser
.GetVal(L
"theirsreflectedname"));
262 pFrame
->m_Data
.m_yourFile
.SetFileName(parser
.GetVal(L
"mine"));
263 pFrame
->m_Data
.m_yourFile
.SetDescriptiveName(parser
.GetVal(L
"minename"));
264 pFrame
->m_Data
.m_yourFile
.SetReflectedName(parser
.GetVal(L
"minereflectedname"));
265 pFrame
->m_Data
.m_mergedFile
.SetFileName(parser
.GetVal(L
"merged"));
266 pFrame
->m_Data
.m_mergedFile
.SetDescriptiveName(parser
.GetVal(L
"mergedname"));
267 pFrame
->m_Data
.m_mergedFile
.SetReflectedName(parser
.GetVal(L
"mergedreflectedname"));
268 pFrame
->m_Data
.m_sPatchPath
= parser
.HasVal(L
"patchpath") ? parser
.GetVal(L
"patchpath") : L
"";
269 pFrame
->m_Data
.m_sPatchPath
.Replace('/', '\\');
270 if (parser
.HasKey(L
"patchoriginal"))
271 pFrame
->m_Data
.m_sPatchOriginal
= parser
.GetVal(L
"patchoriginal");
272 if (parser
.HasKey(L
"patchpatched"))
273 pFrame
->m_Data
.m_sPatchPatched
= parser
.GetVal(L
"patchpatched");
274 pFrame
->m_Data
.m_sDiffFile
= parser
.GetVal(L
"diff");
275 pFrame
->m_Data
.m_sDiffFile
.Replace('/', '\\');
276 if (parser
.HasKey(L
"oneway"))
277 pFrame
->m_bOneWay
= TRUE
;
278 if (parser
.HasKey(L
"diff"))
279 pFrame
->m_bOneWay
= FALSE
;
280 if (parser
.HasKey(L
"reversedpatch"))
281 pFrame
->m_bReversedPatch
= TRUE
;
282 if (parser
.HasKey(L
"saverequired"))
283 pFrame
->m_bSaveRequired
= true;
284 if (parser
.HasKey(L
"saverequiredonconflicts"))
285 pFrame
->m_bSaveRequiredOnConflicts
= true;
286 if (parser
.HasKey(L
"deletebasetheirsmineonclose"))
287 pFrame
->m_bDeleteBaseTheirsMineOnClose
= true;
288 if (pFrame
->m_Data
.IsBaseFileInUse() && !pFrame
->m_Data
.IsYourFileInUse() && pFrame
->m_Data
.IsTheirFileInUse())
290 pFrame
->m_Data
.m_yourFile
.TransferDetailsFrom(pFrame
->m_Data
.m_theirFile
);
293 if ((!parser
.HasKey(L
"patchpath")) && (parser
.HasVal(L
"diff")))
295 // a patchfile was given, but not folder path to apply the patch to
296 // If the patchfile is located inside a working copy, then use the parent directory
297 // of the patchfile as the target directory, otherwise ask the user for a path.
298 if (parser
.HasKey(L
"wc"))
299 pFrame
->m_Data
.m_sPatchPath
= pFrame
->m_Data
.m_sDiffFile
.Left(pFrame
->m_Data
.m_sDiffFile
.ReverseFind('\\'));
302 CBrowseFolder fbrowser
;
303 fbrowser
.m_style
= BIF_EDITBOX
| BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
304 if (fbrowser
.Show(nullptr, pFrame
->m_Data
.m_sPatchPath
) == CBrowseFolder::CANCEL
)
309 if ((parser
.HasKey(L
"patchpath")) && (!parser
.HasVal(L
"diff")))
311 // A path was given for applying a patchfile, but
312 // the patchfile itself was not.
313 // So ask the user for that patchfile
316 // Create a new common save file dialog
317 CComPtr
<IFileOpenDialog
> pfd
;
318 hr
= pfd
.CoCreateInstance(CLSID_FileOpenDialog
, nullptr, CLSCTX_INPROC_SERVER
);
321 // Set the dialog options
323 if (SUCCEEDED(hr
= pfd
->GetOptions(&dwOptions
)))
325 hr
= pfd
->SetOptions(dwOptions
| FOS_FILEMUSTEXIST
| FOS_FORCEFILESYSTEM
| FOS_PATHMUSTEXIST
);
332 temp
.LoadString(IDS_OPENDIFFFILETITLE
);
335 CSelectFileFilter
fileFilter(IDS_PATCHFILEFILTER
);
336 hr
= pfd
->SetFileTypes(fileFilter
.GetCount(), fileFilter
);
337 bool bAdvised
= false;
339 CComObjectStackEx
<PatchOpenDlgEventHandler
> cbk
;
340 CComQIPtr
<IFileDialogEvents
> pEvents
= cbk
.GetUnknown();
343 CComPtr
<IFileDialogCustomize
> pfdCustomize
;
344 hr
= pfd
.QueryInterface(&pfdCustomize
);
347 // check if there's a unified diff on the clipboard and
348 // add a button to the fileopen dialog if there is.
349 UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
350 if ((cFormat
) && (OpenClipboard(nullptr)))
352 HGLOBAL hglb
= GetClipboardData(cFormat
);
355 pfdCustomize
->AddPushButton(101, CString(MAKEINTRESOURCE(IDS_PATCH_COPYFROMCLIPBOARD
)));
356 hr
= pfd
->Advise(pEvents
, &dwCookie
);
357 bAdvised
= SUCCEEDED(hr
);
364 // Show the save file dialog
365 if (SUCCEEDED(hr
) && SUCCEEDED(hr
= pfd
->Show(pFrame
->m_hWnd
)))
367 // Get the selection from the user
368 CComPtr
<IShellItem
> psiResult
;
369 hr
= pfd
->GetResult(&psiResult
);
371 pfd
->Unadvise(dwCookie
);
374 PWSTR pszPath
= nullptr;
375 hr
= psiResult
->GetDisplayName(SIGDN_FILESYSPATH
, &pszPath
);
378 pFrame
->m_Data
.m_sDiffFile
= pszPath
;
379 CoTaskMemFree(pszPath
);
384 // no result, which means we closed the dialog in our button handler
385 std::wstring sTempFile
;
386 if (TrySavePatchFromClipboard(sTempFile
))
387 pFrame
->m_Data
.m_sDiffFile
= sTempFile
.c_str();
393 pfd
->Unadvise(dwCookie
);
399 if ( pFrame
->m_Data
.m_baseFile
.GetFilename().IsEmpty() && pFrame
->m_Data
.m_yourFile
.GetFilename().IsEmpty() )
402 LPWSTR
*szArglist
= CommandLineToArgvW(GetCommandLineW(), &nArgs
);
404 TRACE("CommandLineToArgvW failed\n");
407 if ( nArgs
==3 || nArgs
==4 )
413 // [3]: THEIR file (optional)
414 // This is the same format CAppUtils::StartExtDiff
415 // uses if %base and %mine are not set and most
416 // other diff tools use it too.
417 if ( PathFileExists(szArglist
[1]) && PathFileExists(szArglist
[2]) )
419 pFrame
->m_Data
.m_baseFile
.SetFileName(szArglist
[1]);
420 pFrame
->m_Data
.m_yourFile
.SetFileName(szArglist
[2]);
421 if ( nArgs
== 4 && PathFileExists(szArglist
[3]) )
423 pFrame
->m_Data
.m_theirFile
.SetFileName(szArglist
[3]);
429 // only one path specified: use it to fill the "open" dialog
430 if (PathFileExists(szArglist
[1]))
432 pFrame
->m_Data
.m_yourFile
.SetFileName(szArglist
[1]);
433 pFrame
->m_Data
.m_yourFile
.StoreFileAttributes();
438 // Free memory allocated for CommandLineToArgvW arguments.
439 LocalFree(szArglist
);
442 pFrame
->m_bReadOnly
= !!parser
.HasKey(L
"readonly");
443 if (GetFileAttributes(pFrame
->m_Data
.m_yourFile
.GetFilename()) & FILE_ATTRIBUTE_READONLY
)
444 pFrame
->m_bReadOnly
= true;
445 pFrame
->m_bBlame
= !!parser
.HasKey(L
"blame");
446 // diffing a blame means no editing!
447 if (pFrame
->m_bBlame
)
448 pFrame
->m_bReadOnly
= true;
450 pFrame
->SetWindowTitle();
452 if (parser
.HasKey(L
"createunifieddiff"))
454 // user requested to create a unified diff file
455 CString origFile
= parser
.GetVal(L
"origfile");
456 CString modifiedFile
= parser
.GetVal(L
"modifiedfile");
457 if (!origFile
.IsEmpty() && !modifiedFile
.IsEmpty())
459 CString outfile
= parser
.GetVal(L
"outfile");
460 if (outfile
.IsEmpty())
462 CCommonAppUtils::FileOpenSave(outfile
, nullptr, IDS_SAVEASTITLE
, IDS_COMMONFILEFILTER
, false);
464 if (!outfile
.IsEmpty())
466 CRegStdDWORD
regContextLines(L
"Software\\TortoiseGitMerge\\ContextLines", (DWORD
)-1);
467 CAppUtils::CreateUnifiedDiff(origFile
, modifiedFile
, outfile
, regContextLines
, false);
473 pFrame
->resolveMsgWnd
= parser
.HasVal(L
"resolvemsghwnd") ? (HWND
)parser
.GetLongLongVal(L
"resolvemsghwnd") : 0;
474 pFrame
->resolveMsgWParam
= parser
.HasVal(L
"resolvemsgwparam") ? (WPARAM
)parser
.GetLongLongVal(L
"resolvemsgwparam") : 0;
475 pFrame
->resolveMsgLParam
= parser
.HasVal(L
"resolvemsglparam") ? (LPARAM
)parser
.GetLongLongVal(L
"resolvemsglparam") : 0;
477 // The one and only window has been initialized, so show and update it
478 pFrame
->ActivateFrame();
479 pFrame
->ShowWindow(SW_SHOW
);
480 pFrame
->UpdateWindow();
481 pFrame
->ShowDiffBar(!pFrame
->m_bOneWay
);
482 if (!pFrame
->m_Data
.IsBaseFileInUse() && pFrame
->m_Data
.m_sPatchPath
.IsEmpty() && pFrame
->m_Data
.m_sDiffFile
.IsEmpty())
484 pFrame
->OnFileOpen(pFrame
->m_Data
.m_yourFile
.InUse());
489 if (parser
.HasVal(L
"line"))
491 line
= parser
.GetLongVal(L
"line");
492 line
--; // we need the index
495 return pFrame
->LoadViews(line
);
498 // CTortoiseMergeApp message handlers
500 void CTortoiseMergeApp::OnAppAbout()
506 int CTortoiseMergeApp::ExitInstance()
508 // Look for temporary files left around by TortoiseMerge and
509 // remove them. But only delete 'old' files
510 CTempFiles::DeleteOldTempFiles(L
"*tsm*.*");
512 return CWinAppEx::ExitInstance();
515 bool CTortoiseMergeApp::HasClipboardPatch()
517 // check if there's a patchfile in the clipboard
518 const UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
522 if (OpenClipboard(nullptr) == 0)
525 bool containsPatch
= false;
529 if (enumFormat
== cFormat
)
531 containsPatch
= true; // yes, there's a patchfile in the clipboard
533 } while((enumFormat
= EnumClipboardFormats(enumFormat
))!=0);
536 return containsPatch
;
539 bool CTortoiseMergeApp::TrySavePatchFromClipboard(std::wstring
& resultFile
)
543 UINT cFormat
= RegisterClipboardFormat(L
"TGIT_UNIFIEDDIFF");
546 if (OpenClipboard(nullptr) == 0)
549 HGLOBAL hglb
= GetClipboardData(cFormat
);
550 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
552 DWORD len
= GetTempPath(0, nullptr);
553 auto path
= std::make_unique
<TCHAR
[]>(len
+ 1);
554 auto tempF
= std::make_unique
<TCHAR
[]>(len
+ 100);
555 GetTempPath (len
+1, path
.get());
556 GetTempFileName (path
.get(), L
"tsm", 0, tempF
.get());
557 std::wstring sTempFile
= std::wstring(tempF
.get());
560 _wfopen_s(&outFile
, sTempFile
.c_str(), L
"wb");
563 size_t patchlen
= strlen(lpstr
);
564 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
565 if (size
== patchlen
)
566 resultFile
= sTempFile
;
573 return !resultFile
.empty();