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()
46 m_bLoadUserToolbars
= FALSE
;
50 // The one and only CTortoiseMergeApp object
51 CTortoiseMergeApp theApp
;
52 CCrashReport
g_crasher("tortoisesvn@gmail.com", "Crash Report for TortoiseMerge " APP_X64_STRING
" : " STRPRODUCTVER
, TRUE
);
54 // CTortoiseMergeApp initialization
55 BOOL
CTortoiseMergeApp::InitInstance()
57 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows
));
58 CMFCButton::EnableWindowsTheming();
59 //set the resource dll for the required language
60 CRegDWORD loc
= CRegDWORD(_T("Software\\TortoiseGit\\LanguageID"), 1033);
63 HINSTANCE hInst
= NULL
;
66 langDll
.Format(_T("..\\Languages\\TortoiseMerge%d.dll"), langId
);
68 hInst
= LoadLibrary(langDll
);
69 CString sVer
= _T(STRPRODUCTVER
);
70 CString sFileVer
= CPathUtils::GetVersionFromFile(langDll
);
71 if (sFileVer
.Compare(sVer
)!=0)
77 AfxSetResourceHandle(hInst
);
80 DWORD lid
= SUBLANGID(langId
);
84 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
89 } while ((hInst
== NULL
) && (langId
!= 0));
91 _tcscpy_s(buf
, _T("en"));
94 sHelppath
= this->m_pszHelpFilePath
;
95 sHelppath
= sHelppath
.MakeLower();
96 sHelppath
.Replace(_T(".chm"), _T("_en.chm"));
97 free((void*)m_pszHelpFilePath
);
98 m_pszHelpFilePath
=_tcsdup(sHelppath
);
99 sHelppath
= CPathUtils::GetAppParentDirectory() + _T("Languages\\TortoiseMerge_en.chm");
102 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO639LANGNAME
, buf
, _countof(buf
));
103 CString sLang
= _T("_");
105 sHelppath
.Replace(_T("_en"), sLang
);
106 if (PathFileExists(sHelppath
))
108 free((void*)m_pszHelpFilePath
);
109 m_pszHelpFilePath
=_tcsdup(sHelppath
);
112 sHelppath
.Replace(sLang
, _T("_en"));
113 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO3166CTRYNAME
, buf
, _countof(buf
));
116 sHelppath
.Replace(_T("_en"), sLang
);
117 if (PathFileExists(sHelppath
))
119 free((void*)m_pszHelpFilePath
);
120 m_pszHelpFilePath
=_tcsdup(sHelppath
);
123 sHelppath
.Replace(sLang
, _T("_en"));
125 DWORD lid
= SUBLANGID(langId
);
129 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
134 setlocale(LC_ALL
, "");
135 // We need to explicitly set the thread locale to the system default one to avoid possible problems with saving files in its original codepage
136 // The problems occures when the language of OS differs from the regional settings
137 // See the details here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100887
138 SetThreadLocale(LOCALE_SYSTEM_DEFAULT
);
140 // InitCommonControls() is required on Windows XP if an application
141 // manifest specifies use of ComCtl32.dll version 6 or later to enable
142 // visual styles. Otherwise, any window creation will fail.
143 InitCommonControls();
145 // Initialize all Managers for usage. They are automatically constructed
146 // if not yet present
147 InitContextMenuManager();
148 InitKeyboardManager();
150 CCmdLineParser parser
= CCmdLineParser(this->m_lpCmdLine
);
152 if (parser
.HasKey(_T("?")) || parser
.HasKey(_T("help")))
155 sHelpText
.LoadString(IDS_COMMANDLINEHELP
);
156 MessageBox(NULL
, sHelpText
, _T("TortoiseMerge"), MB_ICONINFORMATION
);
160 // Initialize OLE libraries
163 AfxMessageBox(IDP_OLE_INIT_FAILED
);
166 AfxEnableControlContainer();
167 // Standard initialization
168 // If you are not using these features and wish to reduce the size
169 // of your final executable, you should remove from the following
170 // the specific initialization routines you do not need
171 // Change the registry key under which our settings are stored
172 SetRegistryKey(_T("TortoiseMerge"));
174 if (CRegDWORD(_T("Software\\TortoiseMerge\\Debug"), FALSE
)==TRUE
)
175 AfxMessageBox(AfxGetApp()->m_lpCmdLine
, MB_OK
| MB_ICONINFORMATION
);
177 // To create the main window, this code creates a new frame window
178 // object and then sets it as the application's main window object
179 CMainFrame
* pFrame
= new CMainFrame
;
184 // create and load the frame with its resources
185 pFrame
->LoadFrame(IDR_MAINFRAME
,
186 WS_OVERLAPPEDWINDOW
| FWS_ADDTOTITLE
, NULL
,
189 // Fill in the command line options
190 pFrame
->m_Data
.m_baseFile
.SetFileName(parser
.GetVal(_T("base")));
191 pFrame
->m_Data
.m_baseFile
.SetDescriptiveName(parser
.GetVal(_T("basename")));
192 pFrame
->m_Data
.m_theirFile
.SetFileName(parser
.GetVal(_T("theirs")));
193 pFrame
->m_Data
.m_theirFile
.SetDescriptiveName(parser
.GetVal(_T("theirsname")));
194 pFrame
->m_Data
.m_yourFile
.SetFileName(parser
.GetVal(_T("mine")));
195 pFrame
->m_Data
.m_yourFile
.SetDescriptiveName(parser
.GetVal(_T("minename")));
196 pFrame
->m_Data
.m_mergedFile
.SetFileName(parser
.GetVal(_T("merged")));
197 pFrame
->m_Data
.m_mergedFile
.SetDescriptiveName(parser
.GetVal(_T("mergedname")));
198 pFrame
->m_Data
.m_sPatchPath
= parser
.HasVal(_T("patchpath")) ? parser
.GetVal(_T("patchpath")) : _T("");
199 pFrame
->m_Data
.m_sPatchPath
.Replace('/', '\\');
200 if (parser
.HasKey(_T("patchoriginal")))
201 pFrame
->m_Data
.m_sPatchOriginal
= parser
.GetVal(_T("patchoriginal"));
202 if (parser
.HasKey(_T("patchpatched")))
203 pFrame
->m_Data
.m_sPatchPatched
= parser
.GetVal(_T("patchpatched"));
204 pFrame
->m_Data
.m_sDiffFile
= parser
.GetVal(_T("diff"));
205 pFrame
->m_Data
.m_sDiffFile
.Replace('/', '\\');
206 if (parser
.HasKey(_T("oneway")))
207 pFrame
->m_bOneWay
= TRUE
;
208 if (parser
.HasKey(_T("diff")))
209 pFrame
->m_bOneWay
= FALSE
;
210 if (parser
.HasKey(_T("reversedpatch")))
211 pFrame
->m_bReversedPatch
= TRUE
;
212 if (pFrame
->m_Data
.IsBaseFileInUse() && !pFrame
->m_Data
.IsYourFileInUse() && pFrame
->m_Data
.IsTheirFileInUse())
214 pFrame
->m_Data
.m_yourFile
.TransferDetailsFrom(pFrame
->m_Data
.m_theirFile
);
217 if ((!parser
.HasKey(_T("patchpath")))&&(parser
.HasVal(_T("diff"))))
219 // a patchfile was given, but not folder path to apply the patch to
220 // If the patchfile is located inside a working copy, then use the parent directory
221 // of the patchfile as the target directory, otherwise ask the user for a path.
222 if (parser
.HasKey(_T("wc")))
223 pFrame
->m_Data
.m_sPatchPath
= pFrame
->m_Data
.m_sDiffFile
.Left(pFrame
->m_Data
.m_sDiffFile
.ReverseFind('\\'));
226 CBrowseFolder fbrowser
;
227 fbrowser
.m_style
= BIF_EDITBOX
| BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
228 if (fbrowser
.Show(NULL
, pFrame
->m_Data
.m_sPatchPath
)==CBrowseFolder::CANCEL
)
233 if ((parser
.HasKey(_T("patchpath")))&&(!parser
.HasVal(_T("diff"))))
235 // A path was given for applying a patchfile, but
236 // the patchfile itself was not.
237 // So ask the user for that patchfile
239 OPENFILENAME ofn
= {0}; // common dialog box structure
240 TCHAR szFile
[MAX_PATH
] = {0}; // buffer for file name
241 // Initialize OPENFILENAME
242 ofn
.lStructSize
= sizeof(OPENFILENAME
);
243 ofn
.hwndOwner
= pFrame
->m_hWnd
;
244 ofn
.lpstrFile
= szFile
;
245 ofn
.nMaxFile
= _countof(szFile
);
247 temp
.LoadString(IDS_OPENDIFFFILETITLE
);
249 ofn
.lpstrTitle
= NULL
;
251 ofn
.lpstrTitle
= temp
;
253 ofn
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_EXPLORER
;
254 // check if there's a patchfile in the clipboard
255 UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
258 if (OpenClipboard(NULL
))
263 if (enumFormat
== cFormat
)
265 // yes, there's a patchfile in the clipboard
266 ofn
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_ENABLETEMPLATE
| OFN_ENABLEHOOK
| OFN_EXPLORER
;
268 ofn
.hInstance
= AfxGetResourceHandle();
269 ofn
.lpTemplateName
= MAKEINTRESOURCE(IDD_PATCH_FILE_OPEN_CUSTOM
);
270 ofn
.lpfnHook
= CreatePatchFileOpenHook
;
272 } while((enumFormat
= EnumClipboardFormats(enumFormat
))!=0);
278 sFilter
.LoadString(IDS_PATCHFILEFILTER
);
279 TCHAR
* pszFilters
= new TCHAR
[sFilter
.GetLength()+4];
280 _tcscpy_s (pszFilters
, sFilter
.GetLength()+4, sFilter
);
281 // Replace '|' delimiters with '\0's
282 TCHAR
*ptr
= pszFilters
+ _tcslen(pszFilters
); //set ptr at the NULL
283 while (ptr
!= pszFilters
)
289 ofn
.lpstrFilter
= pszFilters
;
290 ofn
.nFilterIndex
= 1;
292 // Display the Open dialog box.
294 if (GetOpenFileName(&ofn
)==FALSE
)
296 delete [] pszFilters
;
299 delete [] pszFilters
;
300 pFrame
->m_Data
.m_sDiffFile
= ofn
.lpstrFile
;
303 if ( pFrame
->m_Data
.m_baseFile
.GetFilename().IsEmpty() && pFrame
->m_Data
.m_yourFile
.GetFilename().IsEmpty() )
308 szArglist
= CommandLineToArgvW(GetCommandLineW(), &nArgs
);
309 if( NULL
== szArglist
)
311 TRACE("CommandLineToArgvW failed\n");
315 if ( nArgs
==3 || nArgs
==4 )
321 // [3]: THEIR file (optional)
322 // This is the same format CAppUtils::StartExtDiff
323 // uses if %base and %mine are not set and most
324 // other diff tools use it too.
325 if ( PathFileExists(szArglist
[1]) && PathFileExists(szArglist
[2]) )
327 pFrame
->m_Data
.m_baseFile
.SetFileName(szArglist
[1]);
328 pFrame
->m_Data
.m_yourFile
.SetFileName(szArglist
[2]);
329 if ( nArgs
== 4 && PathFileExists(szArglist
[3]) )
331 pFrame
->m_Data
.m_theirFile
.SetFileName(szArglist
[3]);
337 // Free memory allocated for CommandLineToArgvW arguments.
338 LocalFree(szArglist
);
341 pFrame
->m_bReadOnly
= !!parser
.HasKey(_T("readonly"));
342 if (GetFileAttributes(pFrame
->m_Data
.m_yourFile
.GetFilename()) & FILE_ATTRIBUTE_READONLY
)
343 pFrame
->m_bReadOnly
= true;
344 pFrame
->m_bBlame
= !!parser
.HasKey(_T("blame"));
345 // diffing a blame means no editing!
346 if (pFrame
->m_bBlame
)
347 pFrame
->m_bReadOnly
= true;
349 // try to find a suitable window title
350 CString sYour
= pFrame
->m_Data
.m_yourFile
.GetDescriptiveName();
351 if (sYour
.Find(_T(" - "))>=0)
352 sYour
= sYour
.Left(sYour
.Find(_T(" - ")));
353 if (sYour
.Find(_T(" : "))>=0)
354 sYour
= sYour
.Left(sYour
.Find(_T(" : ")));
355 CString sTheir
= pFrame
->m_Data
.m_theirFile
.GetDescriptiveName();
356 if (sTheir
.Find(_T(" - "))>=0)
357 sTheir
= sTheir
.Left(sTheir
.Find(_T(" - ")));
358 if (sTheir
.Find(_T(" : "))>=0)
359 sTheir
= sTheir
.Left(sTheir
.Find(_T(" : ")));
361 if (!sYour
.IsEmpty() && !sTheir
.IsEmpty())
363 if (sYour
.CompareNoCase(sTheir
)==0)
364 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
365 else if ((sYour
.GetLength() < 10) &&
366 (sTheir
.GetLength() < 10))
367 pFrame
->SetWindowText(sYour
+ _T(" - ") + sTheir
+ _T(" - TortoiseMerge"));
370 // we have two very long descriptive texts here, which
371 // means we have to find a way to use them as a window
372 // title in a shorter way.
373 // for simplicity, we just use the one from "yourfile"
374 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
377 else if (!sYour
.IsEmpty())
378 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
379 else if (!sTheir
.IsEmpty())
380 pFrame
->SetWindowText(sTheir
+ _T(" - TortoiseMerge"));
382 if (parser
.HasKey(_T("createunifieddiff")))
384 // user requested to create a unified diff file
385 CString origFile
= parser
.GetVal(_T("origfile"));
386 CString modifiedFile
= parser
.GetVal(_T("modifiedfile"));
387 if (!origFile
.IsEmpty() && !modifiedFile
.IsEmpty())
389 CString outfile
= parser
.GetVal(_T("outfile"));
390 if (outfile
.IsEmpty())
392 OPENFILENAME ofn
= {0}; // common dialog box structure
393 TCHAR szFile
[MAX_PATH
] = {0}; // buffer for file name
394 ofn
.lStructSize
= sizeof(OPENFILENAME
);
395 ofn
.lpstrFile
= szFile
;
396 ofn
.nMaxFile
= _countof(szFile
);
398 temp
.LoadString(IDS_SAVEASTITLE
);
400 ofn
.lpstrTitle
= temp
;
401 ofn
.Flags
= OFN_OVERWRITEPROMPT
;
403 sFilter
.LoadString(IDS_COMMONFILEFILTER
);
404 TCHAR
* pszFilters
= new TCHAR
[sFilter
.GetLength()+4];
405 _tcscpy_s (pszFilters
, sFilter
.GetLength()+4, sFilter
);
406 // Replace '|' delimiters with '\0's
407 TCHAR
*ptr
= pszFilters
+ _tcslen(pszFilters
); //set ptr at the NULL
408 while (ptr
!= pszFilters
)
414 ofn
.lpstrFilter
= pszFilters
;
415 ofn
.nFilterIndex
= 1;
417 // Display the Save dialog box.
419 if (GetSaveFileName(&ofn
)==TRUE
)
421 outfile
= CString(ofn
.lpstrFile
);
423 delete [] pszFilters
;
425 if (!outfile
.IsEmpty())
427 CAppUtils::CreateUnifiedDiff(origFile
, modifiedFile
, outfile
, false);
432 // The one and only window has been initialized, so show and update it
433 pFrame
->ActivateFrame();
434 pFrame
->ShowWindow(SW_SHOW
);
435 pFrame
->UpdateWindow();
436 pFrame
->ShowDiffBar(!pFrame
->m_bOneWay
);
437 if (!pFrame
->m_Data
.IsBaseFileInUse() && pFrame
->m_Data
.m_sPatchPath
.IsEmpty() && pFrame
->m_Data
.m_sDiffFile
.IsEmpty())
439 pFrame
->OnFileOpen();
443 return pFrame
->LoadViews();
446 // CTortoiseMergeApp message handlers
448 void CTortoiseMergeApp::OnAppAbout()
455 CTortoiseMergeApp::CreatePatchFileOpenHook(HWND hDlg
, UINT uiMsg
, WPARAM wParam
, LPARAM
/*lParam*/)
457 if(uiMsg
== WM_COMMAND
&& LOWORD(wParam
) == IDC_PATCH_TO_CLIPBOARD
)
459 HWND hFileDialog
= GetParent(hDlg
);
461 // if there's a patchfile in the clipboard, we save it
462 // to a temporary file and tell TortoiseMerge to use that one
463 UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
464 if ((cFormat
)&&(OpenClipboard(NULL
)))
466 HGLOBAL hglb
= GetClipboardData(cFormat
);
467 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
469 DWORD len
= GetTempPath(0, NULL
);
470 TCHAR
* path
= new TCHAR
[len
+1];
471 TCHAR
* tempF
= new TCHAR
[len
+100];
472 GetTempPath (len
+1, path
);
473 GetTempFileName (path
, TEXT("tsm"), 0, tempF
);
474 std::wstring sTempFile
= std::wstring(tempF
);
479 size_t patchlen
= strlen(lpstr
);
480 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
483 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
484 if (size
== patchlen
)
486 CommDlg_OpenSave_SetControlText(hFileDialog
, edt1
, sTempFile
.c_str());
487 PostMessage(hFileDialog
, WM_COMMAND
, MAKEWPARAM(IDOK
, BM_CLICK
), (LPARAM
)(GetDlgItem(hDlg
, IDOK
)));
498 int CTortoiseMergeApp::ExitInstance()
500 // Look for temporary files left around by TortoiseMerge and
501 // remove them. But only delete 'old' files
502 DWORD len
= ::GetTempPath(0, NULL
);
503 TCHAR
* path
= new TCHAR
[len
+ 100];
504 len
= ::GetTempPath (len
+100, path
);
507 CSimpleFileFind finder
= CSimpleFileFind(path
, _T("*tsm*.*"));
509 ::GetSystemTimeAsFileTime(&systime_
);
510 __int64 systime
= (((_int64
)systime_
.dwHighDateTime
)<<32) | ((__int64
)systime_
.dwLowDateTime
);
511 while (finder
.FindNextFileNoDirectories())
513 CString filepath
= finder
.GetFilePath();
514 HANDLE hFile
= ::CreateFile(filepath
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, NULL
, NULL
);
515 if (hFile
!= INVALID_HANDLE_VALUE
)
517 FILETIME createtime_
;
518 if (::GetFileTime(hFile
, &createtime_
, NULL
, NULL
))
520 ::CloseHandle(hFile
);
521 __int64 createtime
= (((_int64
)createtime_
.dwHighDateTime
)<<32) | ((__int64
)createtime_
.dwLowDateTime
);
522 if ((createtime
+ 864000000000) < systime
) //only delete files older than a day
524 ::SetFileAttributes(filepath
, FILE_ATTRIBUTE_NORMAL
);
525 ::DeleteFile(filepath
);
529 ::CloseHandle(hFile
);
535 return CWinAppEx::ExitInstance();