1 // TortoiseMerge - a Diff/Patch program
3 // Copyright (C) 2006-2009 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include "TortoiseMerge.h"
24 #include "CmdLineParser.h"
27 #include "PathUtils.h"
28 #include "BrowseFolder.h"
29 #include "DirFileEnum.h"
35 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
37 BEGIN_MESSAGE_MAP(CTortoiseMergeApp
, CWinAppEx
)
38 ON_COMMAND(ID_APP_ABOUT
, OnAppAbout
)
42 CTortoiseMergeApp::CTortoiseMergeApp()
45 m_bLoadUserToolbars
= FALSE
;
49 // The one and only CTortoiseMergeApp object
50 CTortoiseMergeApp theApp
;
52 CCrashReportTGit
g_crasher(L
"TortoiseMerge " _T(APP_X64_STRING
));
54 // CTortoiseMergeApp initialization
55 BOOL
CTortoiseMergeApp::InitInstance()
58 CCrashReport::Instance().AddUserInfoToReport(L
"CommandLine", GetCommandLine());
60 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows
));
61 CMFCButton::EnableWindowsTheming();
64 DWORD len
= GetCurrentDirectory(0, NULL
);
67 auto_buffer
<TCHAR
> originalCurrentDirectory(len
);
68 if (GetCurrentDirectory(len
, originalCurrentDirectory
))
70 sOrigCWD
= originalCurrentDirectory
;
71 sOrigCWD
= CPathUtils::GetLongPathname(sOrigCWD
);
76 //set the resource dll for the required language
77 CRegDWORD loc
= CRegDWORD(_T("Software\\TortoiseGit\\LanguageID"), 1033);
80 HINSTANCE hInst
= NULL
;
83 langDll
.Format(_T("%sLanguages\\TortoiseMerge%d.dll"), (LPCTSTR
)CPathUtils::GetAppParentDirectory(), langId
);
85 hInst
= LoadLibrary(langDll
);
86 CString sVer
= _T(STRPRODUCTVER
);
87 CString sFileVer
= CPathUtils::GetVersionFromFile(langDll
);
88 if (sFileVer
.Compare(sVer
)!=0)
94 AfxSetResourceHandle(hInst
);
97 DWORD lid
= SUBLANGID(langId
);
101 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
106 } while ((hInst
== NULL
) && (langId
!= 0));
108 _tcscpy_s(buf
, _T("en"));
111 sHelppath
= this->m_pszHelpFilePath
;
112 sHelppath
= sHelppath
.MakeLower();
113 sHelppath
.Replace(_T(".chm"), _T("_en.chm"));
114 free((void*)m_pszHelpFilePath
);
115 m_pszHelpFilePath
=_tcsdup(sHelppath
);
116 sHelppath
= CPathUtils::GetAppParentDirectory() + _T("Languages\\TortoiseMerge_en.chm");
119 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO639LANGNAME
, buf
, _countof(buf
));
120 CString sLang
= _T("_");
122 sHelppath
.Replace(_T("_en"), sLang
);
123 if (PathFileExists(sHelppath
))
125 free((void*)m_pszHelpFilePath
);
126 m_pszHelpFilePath
=_tcsdup(sHelppath
);
129 sHelppath
.Replace(sLang
, _T("_en"));
130 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO3166CTRYNAME
, buf
, _countof(buf
));
133 sHelppath
.Replace(_T("_en"), sLang
);
134 if (PathFileExists(sHelppath
))
136 free((void*)m_pszHelpFilePath
);
137 m_pszHelpFilePath
=_tcsdup(sHelppath
);
140 sHelppath
.Replace(sLang
, _T("_en"));
142 DWORD lid
= SUBLANGID(langId
);
146 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
151 setlocale(LC_ALL
, "");
152 // We need to explicitly set the thread locale to the system default one to avoid possible problems with saving files in its original codepage
153 // The problems occures when the language of OS differs from the regional settings
154 // See the details here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100887
155 SetThreadLocale(LOCALE_SYSTEM_DEFAULT
);
157 // InitCommonControls() is required on Windows XP if an application
158 // manifest specifies use of ComCtl32.dll version 6 or later to enable
159 // visual styles. Otherwise, any window creation will fail.
160 InitCommonControls();
162 // Initialize all Managers for usage. They are automatically constructed
163 // if not yet present
164 InitContextMenuManager();
165 InitKeyboardManager();
167 CCmdLineParser parser
= CCmdLineParser(this->m_lpCmdLine
);
169 if (parser
.HasKey(_T("?")) || parser
.HasKey(_T("help")))
172 sHelpText
.LoadString(IDS_COMMANDLINEHELP
);
173 MessageBox(NULL
, sHelpText
, _T("TortoiseMerge"), MB_ICONINFORMATION
);
177 // Initialize OLE libraries
180 AfxMessageBox(IDP_OLE_INIT_FAILED
);
183 AfxEnableControlContainer();
184 // Standard initialization
185 // If you are not using these features and wish to reduce the size
186 // of your final executable, you should remove from the following
187 // the specific initialization routines you do not need
188 // Change the registry key under which our settings are stored
189 SetRegistryKey(_T("TortoiseMerge"));
191 if (CRegDWORD(_T("Software\\TortoiseMerge\\Debug"), FALSE
)==TRUE
)
192 AfxMessageBox(AfxGetApp()->m_lpCmdLine
, MB_OK
| MB_ICONINFORMATION
);
194 // To create the main window, this code creates a new frame window
195 // object and then sets it as the application's main window object
196 CMainFrame
* pFrame
= new CMainFrame
;
201 // create and load the frame with its resources
202 if (!pFrame
->LoadFrame(IDR_MAINFRAME
, WS_OVERLAPPEDWINDOW
| FWS_ADDTOTITLE
, NULL
, NULL
))
205 // Fill in the command line options
206 pFrame
->m_Data
.m_baseFile
.SetFileName(parser
.GetVal(_T("base")));
207 pFrame
->m_Data
.m_baseFile
.SetDescriptiveName(parser
.GetVal(_T("basename")));
208 pFrame
->m_Data
.m_theirFile
.SetFileName(parser
.GetVal(_T("theirs")));
209 pFrame
->m_Data
.m_theirFile
.SetDescriptiveName(parser
.GetVal(_T("theirsname")));
210 pFrame
->m_Data
.m_yourFile
.SetFileName(parser
.GetVal(_T("mine")));
211 pFrame
->m_Data
.m_yourFile
.SetDescriptiveName(parser
.GetVal(_T("minename")));
212 pFrame
->m_Data
.m_mergedFile
.SetFileName(parser
.GetVal(_T("merged")));
213 pFrame
->m_Data
.m_mergedFile
.SetDescriptiveName(parser
.GetVal(_T("mergedname")));
214 pFrame
->m_Data
.m_sPatchPath
= parser
.HasVal(_T("patchpath")) ? parser
.GetVal(_T("patchpath")) : _T("");
215 pFrame
->m_Data
.m_sPatchPath
.Replace('/', '\\');
216 if (parser
.HasKey(_T("patchoriginal")))
217 pFrame
->m_Data
.m_sPatchOriginal
= parser
.GetVal(_T("patchoriginal"));
218 if (parser
.HasKey(_T("patchpatched")))
219 pFrame
->m_Data
.m_sPatchPatched
= parser
.GetVal(_T("patchpatched"));
220 pFrame
->m_Data
.m_sDiffFile
= parser
.GetVal(_T("diff"));
221 pFrame
->m_Data
.m_sDiffFile
.Replace('/', '\\');
222 if (parser
.HasKey(_T("oneway")))
223 pFrame
->m_bOneWay
= TRUE
;
224 if (parser
.HasKey(_T("diff")))
225 pFrame
->m_bOneWay
= FALSE
;
226 if (parser
.HasKey(_T("reversedpatch")))
227 pFrame
->m_bReversedPatch
= TRUE
;
228 if (pFrame
->m_Data
.IsBaseFileInUse() && !pFrame
->m_Data
.IsYourFileInUse() && pFrame
->m_Data
.IsTheirFileInUse())
230 pFrame
->m_Data
.m_yourFile
.TransferDetailsFrom(pFrame
->m_Data
.m_theirFile
);
233 if ((!parser
.HasKey(_T("patchpath")))&&(parser
.HasVal(_T("diff"))))
235 // a patchfile was given, but not folder path to apply the patch to
236 // If the patchfile is located inside a working copy, then use the parent directory
237 // of the patchfile as the target directory, otherwise ask the user for a path.
238 if (parser
.HasKey(_T("wc")))
239 pFrame
->m_Data
.m_sPatchPath
= pFrame
->m_Data
.m_sDiffFile
.Left(pFrame
->m_Data
.m_sDiffFile
.ReverseFind('\\'));
242 CBrowseFolder fbrowser
;
243 fbrowser
.m_style
= BIF_EDITBOX
| BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
244 if (fbrowser
.Show(NULL
, pFrame
->m_Data
.m_sPatchPath
)==CBrowseFolder::CANCEL
)
249 if ((parser
.HasKey(_T("patchpath")))&&(!parser
.HasVal(_T("diff"))))
251 // A path was given for applying a patchfile, but
252 // the patchfile itself was not.
253 // So ask the user for that patchfile
255 OPENFILENAME ofn
= {0}; // common dialog box structure
256 TCHAR szFile
[MAX_PATH
] = {0}; // buffer for file name
257 // Initialize OPENFILENAME
258 ofn
.lStructSize
= sizeof(OPENFILENAME
);
259 ofn
.hwndOwner
= pFrame
->m_hWnd
;
260 ofn
.lpstrFile
= szFile
;
261 ofn
.nMaxFile
= _countof(szFile
);
263 temp
.LoadString(IDS_OPENDIFFFILETITLE
);
265 ofn
.lpstrTitle
= NULL
;
267 ofn
.lpstrTitle
= temp
;
269 ofn
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_EXPLORER
;
270 // check if there's a patchfile in the clipboard
271 UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
274 if (OpenClipboard(NULL
))
279 if (enumFormat
== cFormat
)
281 // yes, there's a patchfile in the clipboard
282 ofn
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_ENABLETEMPLATE
| OFN_ENABLEHOOK
| OFN_EXPLORER
;
284 ofn
.hInstance
= AfxGetResourceHandle();
285 ofn
.lpTemplateName
= MAKEINTRESOURCE(IDD_PATCH_FILE_OPEN_CUSTOM
);
286 ofn
.lpfnHook
= CreatePatchFileOpenHook
;
288 } while((enumFormat
= EnumClipboardFormats(enumFormat
))!=0);
294 sFilter
.LoadString(IDS_PATCHFILEFILTER
);
295 TCHAR
* pszFilters
= new TCHAR
[sFilter
.GetLength()+4];
296 _tcscpy_s (pszFilters
, sFilter
.GetLength()+4, sFilter
);
297 // Replace '|' delimiters with '\0's
298 TCHAR
*ptr
= pszFilters
+ _tcslen(pszFilters
); //set ptr at the NULL
299 while (ptr
!= pszFilters
)
305 ofn
.lpstrFilter
= pszFilters
;
306 ofn
.nFilterIndex
= 1;
308 // Display the Open dialog box.
310 if (GetOpenFileName(&ofn
)==FALSE
)
312 delete [] pszFilters
;
315 delete [] pszFilters
;
316 pFrame
->m_Data
.m_sDiffFile
= ofn
.lpstrFile
;
319 if ( pFrame
->m_Data
.m_baseFile
.GetFilename().IsEmpty() && pFrame
->m_Data
.m_yourFile
.GetFilename().IsEmpty() )
324 szArglist
= CommandLineToArgvW(GetCommandLineW(), &nArgs
);
325 if( NULL
== szArglist
)
327 TRACE("CommandLineToArgvW failed\n");
331 if ( nArgs
==3 || nArgs
==4 )
337 // [3]: THEIR file (optional)
338 // This is the same format CAppUtils::StartExtDiff
339 // uses if %base and %mine are not set and most
340 // other diff tools use it too.
341 if ( PathFileExists(szArglist
[1]) && PathFileExists(szArglist
[2]) )
343 pFrame
->m_Data
.m_baseFile
.SetFileName(szArglist
[1]);
344 pFrame
->m_Data
.m_yourFile
.SetFileName(szArglist
[2]);
345 if ( nArgs
== 4 && PathFileExists(szArglist
[3]) )
347 pFrame
->m_Data
.m_theirFile
.SetFileName(szArglist
[3]);
353 // Free memory allocated for CommandLineToArgvW arguments.
354 LocalFree(szArglist
);
357 pFrame
->m_bReadOnly
= !!parser
.HasKey(_T("readonly"));
358 if (GetFileAttributes(pFrame
->m_Data
.m_yourFile
.GetFilename()) & FILE_ATTRIBUTE_READONLY
)
359 pFrame
->m_bReadOnly
= true;
360 pFrame
->m_bBlame
= !!parser
.HasKey(_T("blame"));
361 // diffing a blame means no editing!
362 if (pFrame
->m_bBlame
)
363 pFrame
->m_bReadOnly
= true;
365 // try to find a suitable window title
366 CString sYour
= pFrame
->m_Data
.m_yourFile
.GetDescriptiveName();
367 if (sYour
.Find(_T(" - "))>=0)
368 sYour
= sYour
.Left(sYour
.Find(_T(" - ")));
369 if (sYour
.Find(_T(" : "))>=0)
370 sYour
= sYour
.Left(sYour
.Find(_T(" : ")));
371 CString sTheir
= pFrame
->m_Data
.m_theirFile
.GetDescriptiveName();
372 if (sTheir
.Find(_T(" - "))>=0)
373 sTheir
= sTheir
.Left(sTheir
.Find(_T(" - ")));
374 if (sTheir
.Find(_T(" : "))>=0)
375 sTheir
= sTheir
.Left(sTheir
.Find(_T(" : ")));
377 if (!sYour
.IsEmpty() && !sTheir
.IsEmpty())
379 if (sYour
.CompareNoCase(sTheir
)==0)
380 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
381 else if ((sYour
.GetLength() < 10) &&
382 (sTheir
.GetLength() < 10))
383 pFrame
->SetWindowText(sYour
+ _T(" - ") + sTheir
+ _T(" - TortoiseMerge"));
386 // we have two very long descriptive texts here, which
387 // means we have to find a way to use them as a window
388 // title in a shorter way.
389 // for simplicity, we just use the one from "yourfile"
390 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
393 else if (!sYour
.IsEmpty())
394 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
395 else if (!sTheir
.IsEmpty())
396 pFrame
->SetWindowText(sTheir
+ _T(" - TortoiseMerge"));
398 if (parser
.HasKey(_T("createunifieddiff")))
400 // user requested to create a unified diff file
401 CString origFile
= parser
.GetVal(_T("origfile"));
402 CString modifiedFile
= parser
.GetVal(_T("modifiedfile"));
403 if (!origFile
.IsEmpty() && !modifiedFile
.IsEmpty())
405 CString outfile
= parser
.GetVal(_T("outfile"));
406 if (outfile
.IsEmpty())
408 OPENFILENAME ofn
= {0}; // common dialog box structure
409 TCHAR szFile
[MAX_PATH
] = {0}; // buffer for file name
410 ofn
.lStructSize
= sizeof(OPENFILENAME
);
411 ofn
.lpstrFile
= szFile
;
412 ofn
.nMaxFile
= _countof(szFile
);
414 temp
.LoadString(IDS_SAVEASTITLE
);
416 ofn
.lpstrTitle
= temp
;
417 ofn
.Flags
= OFN_OVERWRITEPROMPT
;
419 sFilter
.LoadString(IDS_COMMONFILEFILTER
);
420 TCHAR
* pszFilters
= new TCHAR
[sFilter
.GetLength()+4];
421 _tcscpy_s (pszFilters
, sFilter
.GetLength()+4, sFilter
);
422 // Replace '|' delimiters with '\0's
423 TCHAR
*ptr
= pszFilters
+ _tcslen(pszFilters
); //set ptr at the NULL
424 while (ptr
!= pszFilters
)
430 ofn
.lpstrFilter
= pszFilters
;
431 ofn
.nFilterIndex
= 1;
433 // Display the Save dialog box.
435 if (GetSaveFileName(&ofn
)==TRUE
)
437 outfile
= CString(ofn
.lpstrFile
);
439 delete [] pszFilters
;
441 if (!outfile
.IsEmpty())
443 CAppUtils::CreateUnifiedDiff(origFile
, modifiedFile
, outfile
, false);
448 // The one and only window has been initialized, so show and update it
449 pFrame
->ActivateFrame();
450 pFrame
->ShowWindow(SW_SHOW
);
451 pFrame
->UpdateWindow();
452 pFrame
->ShowDiffBar(!pFrame
->m_bOneWay
);
453 if (!pFrame
->m_Data
.IsBaseFileInUse() && pFrame
->m_Data
.m_sPatchPath
.IsEmpty() && pFrame
->m_Data
.m_sDiffFile
.IsEmpty())
455 pFrame
->OnFileOpen();
459 return pFrame
->LoadViews();
462 // CTortoiseMergeApp message handlers
464 void CTortoiseMergeApp::OnAppAbout()
471 CTortoiseMergeApp::CreatePatchFileOpenHook(HWND hDlg
, UINT uiMsg
, WPARAM wParam
, LPARAM
/*lParam*/)
473 if(uiMsg
== WM_COMMAND
&& LOWORD(wParam
) == IDC_PATCH_TO_CLIPBOARD
)
475 HWND hFileDialog
= GetParent(hDlg
);
477 // if there's a patchfile in the clipboard, we save it
478 // to a temporary file and tell TortoiseMerge to use that one
479 UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
480 if ((cFormat
)&&(OpenClipboard(NULL
)))
482 HGLOBAL hglb
= GetClipboardData(cFormat
);
483 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
485 DWORD len
= GetTempPath(0, NULL
);
486 TCHAR
* path
= new TCHAR
[len
+1];
487 TCHAR
* tempF
= new TCHAR
[len
+100];
488 GetTempPath (len
+1, path
);
489 GetTempFileName (path
, TEXT("tsm"), 0, tempF
);
490 std::wstring sTempFile
= std::wstring(tempF
);
495 size_t patchlen
= strlen(lpstr
);
496 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
499 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
500 if (size
== patchlen
)
502 CommDlg_OpenSave_SetControlText(hFileDialog
, edt1
, sTempFile
.c_str());
503 PostMessage(hFileDialog
, WM_COMMAND
, MAKEWPARAM(IDOK
, BM_CLICK
), (LPARAM
)(GetDlgItem(hDlg
, IDOK
)));
514 int CTortoiseMergeApp::ExitInstance()
516 // Look for temporary files left around by TortoiseMerge and
517 // remove them. But only delete 'old' files
518 DWORD len
= ::GetTempPath(0, NULL
);
519 TCHAR
* path
= new TCHAR
[len
+ 100];
520 len
= ::GetTempPath (len
+100, path
);
523 CSimpleFileFind finder
= CSimpleFileFind(path
, _T("*tsm*.*"));
525 ::GetSystemTimeAsFileTime(&systime_
);
526 __int64 systime
= (((_int64
)systime_
.dwHighDateTime
)<<32) | ((__int64
)systime_
.dwLowDateTime
);
527 while (finder
.FindNextFileNoDirectories())
529 CString filepath
= finder
.GetFilePath();
530 HANDLE hFile
= ::CreateFile(filepath
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, NULL
, NULL
);
531 if (hFile
!= INVALID_HANDLE_VALUE
)
533 FILETIME createtime_
;
534 if (::GetFileTime(hFile
, &createtime_
, NULL
, NULL
))
536 ::CloseHandle(hFile
);
537 __int64 createtime
= (((_int64
)createtime_
.dwHighDateTime
)<<32) | ((__int64
)createtime_
.dwLowDateTime
);
538 if ((createtime
+ 864000000000) < systime
) //only delete files older than a day
540 ::SetFileAttributes(filepath
, FILE_ATTRIBUTE_NORMAL
);
541 ::DeleteFile(filepath
);
545 ::CloseHandle(hFile
);
551 return CWinAppEx::ExitInstance();