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
;
51 CCrashReport
g_crasher("tortoisesvn@gmail.com", "Crash Report for TortoiseMerge " APP_X64_STRING
" : " STRPRODUCTVER
, TRUE
);
53 // CTortoiseMergeApp initialization
54 BOOL
CTortoiseMergeApp::InitInstance()
56 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows
));
57 CMFCButton::EnableWindowsTheming();
58 //set the resource dll for the required language
59 CRegDWORD loc
= CRegDWORD(_T("Software\\TortoiseSVN\\LanguageID"), 1033);
62 HINSTANCE hInst
= NULL
;
65 langDll
.Format(_T("..\\Languages\\TortoiseMerge%d.dll"), langId
);
67 hInst
= LoadLibrary(langDll
);
68 CString sVer
= _T(STRPRODUCTVER
);
69 CString sFileVer
= CPathUtils::GetVersionFromFile(langDll
);
70 if (sFileVer
.Compare(sVer
)!=0)
76 AfxSetResourceHandle(hInst
);
79 DWORD lid
= SUBLANGID(langId
);
83 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
88 } while ((hInst
== NULL
) && (langId
!= 0));
90 _tcscpy_s(buf
, _T("en"));
93 sHelppath
= this->m_pszHelpFilePath
;
94 sHelppath
= sHelppath
.MakeLower();
95 sHelppath
.Replace(_T(".chm"), _T("_en.chm"));
96 free((void*)m_pszHelpFilePath
);
97 m_pszHelpFilePath
=_tcsdup(sHelppath
);
98 sHelppath
= CPathUtils::GetAppParentDirectory() + _T("Languages\\TortoiseMerge_en.chm");
101 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO639LANGNAME
, buf
, sizeof(buf
)/sizeof(TCHAR
));
102 CString sLang
= _T("_");
104 sHelppath
.Replace(_T("_en"), sLang
);
105 if (PathFileExists(sHelppath
))
107 free((void*)m_pszHelpFilePath
);
108 m_pszHelpFilePath
=_tcsdup(sHelppath
);
111 sHelppath
.Replace(sLang
, _T("_en"));
112 GetLocaleInfo(MAKELCID(langId
, SORT_DEFAULT
), LOCALE_SISO3166CTRYNAME
, buf
, sizeof(buf
)/sizeof(TCHAR
));
115 sHelppath
.Replace(_T("_en"), sLang
);
116 if (PathFileExists(sHelppath
))
118 free((void*)m_pszHelpFilePath
);
119 m_pszHelpFilePath
=_tcsdup(sHelppath
);
122 sHelppath
.Replace(sLang
, _T("_en"));
124 DWORD lid
= SUBLANGID(langId
);
128 langId
= MAKELANGID(PRIMARYLANGID(langId
), lid
);
133 setlocale(LC_ALL
, "");
134 // We need to explicitly set the thread locale to the system default one to avoid possible problems with saving files in its original codepage
135 // The problems occures when the language of OS differs from the regional settings
136 // See the details here: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100887
137 SetThreadLocale(LOCALE_SYSTEM_DEFAULT
);
139 // InitCommonControls() is required on Windows XP if an application
140 // manifest specifies use of ComCtl32.dll version 6 or later to enable
141 // visual styles. Otherwise, any window creation will fail.
142 InitCommonControls();
144 // Initialize all Managers for usage. They are automatically constructed
145 // if not yet present
146 InitContextMenuManager();
147 InitKeyboardManager();
149 CCmdLineParser parser
= CCmdLineParser(this->m_lpCmdLine
);
151 if (parser
.HasKey(_T("?")) || parser
.HasKey(_T("help")))
154 sHelpText
.LoadString(IDS_COMMANDLINEHELP
);
155 MessageBox(NULL
, sHelpText
, _T("TortoiseMerge"), MB_ICONINFORMATION
);
159 // Initialize OLE libraries
162 AfxMessageBox(IDP_OLE_INIT_FAILED
);
165 AfxEnableControlContainer();
166 // Standard initialization
167 // If you are not using these features and wish to reduce the size
168 // of your final executable, you should remove from the following
169 // the specific initialization routines you do not need
170 // Change the registry key under which our settings are stored
171 SetRegistryKey(_T("TortoiseMerge"));
173 if (CRegDWORD(_T("Software\\TortoiseMerge\\Debug"), FALSE
)==TRUE
)
174 AfxMessageBox(AfxGetApp()->m_lpCmdLine
, MB_OK
| MB_ICONINFORMATION
);
176 // To create the main window, this code creates a new frame window
177 // object and then sets it as the application's main window object
178 CMainFrame
* pFrame
= new CMainFrame
;
183 // create and load the frame with its resources
184 pFrame
->LoadFrame(IDR_MAINFRAME
,
185 WS_OVERLAPPEDWINDOW
| FWS_ADDTOTITLE
, NULL
,
188 // Fill in the command line options
189 pFrame
->m_Data
.m_baseFile
.SetFileName(parser
.GetVal(_T("base")));
190 pFrame
->m_Data
.m_baseFile
.SetDescriptiveName(parser
.GetVal(_T("basename")));
191 pFrame
->m_Data
.m_theirFile
.SetFileName(parser
.GetVal(_T("theirs")));
192 pFrame
->m_Data
.m_theirFile
.SetDescriptiveName(parser
.GetVal(_T("theirsname")));
193 pFrame
->m_Data
.m_yourFile
.SetFileName(parser
.GetVal(_T("mine")));
194 pFrame
->m_Data
.m_yourFile
.SetDescriptiveName(parser
.GetVal(_T("minename")));
195 pFrame
->m_Data
.m_mergedFile
.SetFileName(parser
.GetVal(_T("merged")));
196 pFrame
->m_Data
.m_mergedFile
.SetDescriptiveName(parser
.GetVal(_T("mergedname")));
197 pFrame
->m_Data
.m_sPatchPath
= parser
.HasVal(_T("patchpath")) ? parser
.GetVal(_T("patchpath")) : _T("");
198 pFrame
->m_Data
.m_sPatchPath
.Replace('/', '\\');
199 if (parser
.HasKey(_T("patchoriginal")))
200 pFrame
->m_Data
.m_sPatchOriginal
= parser
.GetVal(_T("patchoriginal"));
201 if (parser
.HasKey(_T("patchpatched")))
202 pFrame
->m_Data
.m_sPatchPatched
= parser
.GetVal(_T("patchpatched"));
203 pFrame
->m_Data
.m_sDiffFile
= parser
.GetVal(_T("diff"));
204 pFrame
->m_Data
.m_sDiffFile
.Replace('/', '\\');
205 if (parser
.HasKey(_T("oneway")))
206 pFrame
->m_bOneWay
= TRUE
;
207 if (parser
.HasKey(_T("diff")))
208 pFrame
->m_bOneWay
= FALSE
;
209 if (parser
.HasKey(_T("reversedpatch")))
210 pFrame
->m_bReversedPatch
= TRUE
;
211 if (pFrame
->m_Data
.IsBaseFileInUse() && !pFrame
->m_Data
.IsYourFileInUse() && pFrame
->m_Data
.IsTheirFileInUse())
213 pFrame
->m_Data
.m_yourFile
.TransferDetailsFrom(pFrame
->m_Data
.m_theirFile
);
216 if ((!parser
.HasKey(_T("patchpath")))&&(parser
.HasVal(_T("diff"))))
218 // a patchfile was given, but not folder path to apply the patch to
219 // If the patchfile is located inside a working copy, then use the parent directory
220 // of the patchfile as the target directory, otherwise ask the user for a path.
221 if (parser
.HasKey(_T("wc")))
222 pFrame
->m_Data
.m_sPatchPath
= pFrame
->m_Data
.m_sDiffFile
.Left(pFrame
->m_Data
.m_sDiffFile
.ReverseFind('\\'));
225 CBrowseFolder fbrowser
;
226 fbrowser
.m_style
= BIF_EDITBOX
| BIF_NEWDIALOGSTYLE
| BIF_RETURNFSANCESTORS
| BIF_RETURNONLYFSDIRS
;
227 if (fbrowser
.Show(NULL
, pFrame
->m_Data
.m_sPatchPath
)==CBrowseFolder::CANCEL
)
232 if ((parser
.HasKey(_T("patchpath")))&&(!parser
.HasVal(_T("diff"))))
234 // A path was given for applying a patchfile, but
235 // the patchfile itself was not.
236 // So ask the user for that patchfile
238 OPENFILENAME ofn
= {0}; // common dialog box structure
239 TCHAR szFile
[MAX_PATH
] = {0}; // buffer for file name
240 // Initialize OPENFILENAME
241 ofn
.lStructSize
= sizeof(OPENFILENAME
);
242 ofn
.hwndOwner
= pFrame
->m_hWnd
;
243 ofn
.lpstrFile
= szFile
;
244 ofn
.nMaxFile
= sizeof(szFile
)/sizeof(TCHAR
);
246 temp
.LoadString(IDS_OPENDIFFFILETITLE
);
248 ofn
.lpstrTitle
= NULL
;
250 ofn
.lpstrTitle
= temp
;
252 ofn
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_EXPLORER
;
253 // check if there's a patchfile in the clipboard
254 UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
257 if (OpenClipboard(NULL
))
262 if (enumFormat
== cFormat
)
264 // yes, there's a patchfile in the clipboard
265 ofn
.Flags
= OFN_FILEMUSTEXIST
| OFN_PATHMUSTEXIST
| OFN_HIDEREADONLY
| OFN_ENABLETEMPLATE
| OFN_ENABLEHOOK
| OFN_EXPLORER
;
267 ofn
.hInstance
= AfxGetResourceHandle();
268 ofn
.lpTemplateName
= MAKEINTRESOURCE(IDD_PATCH_FILE_OPEN_CUSTOM
);
269 ofn
.lpfnHook
= CreatePatchFileOpenHook
;
271 } while((enumFormat
= EnumClipboardFormats(enumFormat
))!=0);
277 sFilter
.LoadString(IDS_PATCHFILEFILTER
);
278 TCHAR
* pszFilters
= new TCHAR
[sFilter
.GetLength()+4];
279 _tcscpy_s (pszFilters
, sFilter
.GetLength()+4, sFilter
);
280 // Replace '|' delimiters with '\0's
281 TCHAR
*ptr
= pszFilters
+ _tcslen(pszFilters
); //set ptr at the NULL
282 while (ptr
!= pszFilters
)
288 ofn
.lpstrFilter
= pszFilters
;
289 ofn
.nFilterIndex
= 1;
291 // Display the Open dialog box.
293 if (GetOpenFileName(&ofn
)==FALSE
)
295 delete [] pszFilters
;
298 delete [] pszFilters
;
299 pFrame
->m_Data
.m_sDiffFile
= ofn
.lpstrFile
;
302 if ( pFrame
->m_Data
.m_baseFile
.GetFilename().IsEmpty() && pFrame
->m_Data
.m_yourFile
.GetFilename().IsEmpty() )
307 szArglist
= CommandLineToArgvW(GetCommandLineW(), &nArgs
);
308 if( NULL
== szArglist
)
310 TRACE("CommandLineToArgvW failed\n");
314 if ( nArgs
==3 || nArgs
==4 )
320 // [3]: THEIR file (optional)
321 // This is the same format CAppUtils::StartExtDiff
322 // uses if %base and %mine are not set and most
323 // other diff tools use it too.
324 if ( PathFileExists(szArglist
[1]) && PathFileExists(szArglist
[2]) )
326 pFrame
->m_Data
.m_baseFile
.SetFileName(szArglist
[1]);
327 pFrame
->m_Data
.m_yourFile
.SetFileName(szArglist
[2]);
328 if ( nArgs
== 4 && PathFileExists(szArglist
[3]) )
330 pFrame
->m_Data
.m_theirFile
.SetFileName(szArglist
[3]);
336 // Free memory allocated for CommandLineToArgvW arguments.
337 LocalFree(szArglist
);
340 pFrame
->m_bReadOnly
= !!parser
.HasKey(_T("readonly"));
341 if (GetFileAttributes(pFrame
->m_Data
.m_yourFile
.GetFilename()) & FILE_ATTRIBUTE_READONLY
)
342 pFrame
->m_bReadOnly
= true;
343 pFrame
->m_bBlame
= !!parser
.HasKey(_T("blame"));
344 // diffing a blame means no editing!
345 if (pFrame
->m_bBlame
)
346 pFrame
->m_bReadOnly
= true;
348 // try to find a suitable window title
349 CString sYour
= pFrame
->m_Data
.m_yourFile
.GetDescriptiveName();
350 if (sYour
.Find(_T(" - "))>=0)
351 sYour
= sYour
.Left(sYour
.Find(_T(" - ")));
352 if (sYour
.Find(_T(" : "))>=0)
353 sYour
= sYour
.Left(sYour
.Find(_T(" : ")));
354 CString sTheir
= pFrame
->m_Data
.m_theirFile
.GetDescriptiveName();
355 if (sTheir
.Find(_T(" - "))>=0)
356 sTheir
= sTheir
.Left(sTheir
.Find(_T(" - ")));
357 if (sTheir
.Find(_T(" : "))>=0)
358 sTheir
= sTheir
.Left(sTheir
.Find(_T(" : ")));
360 if (!sYour
.IsEmpty() && !sTheir
.IsEmpty())
362 if (sYour
.CompareNoCase(sTheir
)==0)
363 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
364 else if ((sYour
.GetLength() < 10) &&
365 (sTheir
.GetLength() < 10))
366 pFrame
->SetWindowText(sYour
+ _T(" - ") + sTheir
+ _T(" - TortoiseMerge"));
369 // we have two very long descriptive texts here, which
370 // means we have to find a way to use them as a window
371 // title in a shorter way.
372 // for simplicity, we just use the one from "yourfile"
373 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
376 else if (!sYour
.IsEmpty())
377 pFrame
->SetWindowText(sYour
+ _T(" - TortoiseMerge"));
378 else if (!sTheir
.IsEmpty())
379 pFrame
->SetWindowText(sTheir
+ _T(" - TortoiseMerge"));
381 if (parser
.HasKey(_T("createunifieddiff")))
383 // user requested to create a unified diff file
384 CString origFile
= parser
.GetVal(_T("origfile"));
385 CString modifiedFile
= parser
.GetVal(_T("modifiedfile"));
386 if (!origFile
.IsEmpty() && !modifiedFile
.IsEmpty())
388 CString outfile
= parser
.GetVal(_T("outfile"));
389 if (outfile
.IsEmpty())
391 OPENFILENAME ofn
= {0}; // common dialog box structure
392 TCHAR szFile
[MAX_PATH
] = {0}; // buffer for file name
393 ofn
.lStructSize
= sizeof(OPENFILENAME
);
394 ofn
.lpstrFile
= szFile
;
395 ofn
.nMaxFile
= sizeof(szFile
)/sizeof(TCHAR
);
397 temp
.LoadString(IDS_SAVEASTITLE
);
399 ofn
.lpstrTitle
= temp
;
400 ofn
.Flags
= OFN_OVERWRITEPROMPT
;
402 sFilter
.LoadString(IDS_COMMONFILEFILTER
);
403 TCHAR
* pszFilters
= new TCHAR
[sFilter
.GetLength()+4];
404 _tcscpy_s (pszFilters
, sFilter
.GetLength()+4, sFilter
);
405 // Replace '|' delimiters with '\0's
406 TCHAR
*ptr
= pszFilters
+ _tcslen(pszFilters
); //set ptr at the NULL
407 while (ptr
!= pszFilters
)
413 ofn
.lpstrFilter
= pszFilters
;
414 ofn
.nFilterIndex
= 1;
416 // Display the Save dialog box.
418 if (GetSaveFileName(&ofn
)==TRUE
)
420 outfile
= CString(ofn
.lpstrFile
);
422 delete [] pszFilters
;
424 if (!outfile
.IsEmpty())
426 CAppUtils::CreateUnifiedDiff(origFile
, modifiedFile
, outfile
, false);
431 // The one and only window has been initialized, so show and update it
432 pFrame
->ActivateFrame();
433 pFrame
->ShowWindow(SW_SHOW
);
434 pFrame
->UpdateWindow();
435 pFrame
->ShowDiffBar(!pFrame
->m_bOneWay
);
436 if (!pFrame
->m_Data
.IsBaseFileInUse() && pFrame
->m_Data
.m_sPatchPath
.IsEmpty() && pFrame
->m_Data
.m_sDiffFile
.IsEmpty())
438 pFrame
->OnFileOpen();
442 return pFrame
->LoadViews();
445 // CTortoiseMergeApp message handlers
447 void CTortoiseMergeApp::OnAppAbout()
454 CTortoiseMergeApp::CreatePatchFileOpenHook(HWND hDlg
, UINT uiMsg
, WPARAM wParam
, LPARAM
/*lParam*/)
456 if(uiMsg
== WM_COMMAND
&& LOWORD(wParam
) == IDC_PATCH_TO_CLIPBOARD
)
458 HWND hFileDialog
= GetParent(hDlg
);
460 // if there's a patchfile in the clipboard, we save it
461 // to a temporary file and tell TortoiseMerge to use that one
462 UINT cFormat
= RegisterClipboardFormat(_T("TSVN_UNIFIEDDIFF"));
463 if ((cFormat
)&&(OpenClipboard(NULL
)))
465 HGLOBAL hglb
= GetClipboardData(cFormat
);
466 LPCSTR lpstr
= (LPCSTR
)GlobalLock(hglb
);
468 DWORD len
= GetTempPath(0, NULL
);
469 TCHAR
* path
= new TCHAR
[len
+1];
470 TCHAR
* tempF
= new TCHAR
[len
+100];
471 GetTempPath (len
+1, path
);
472 GetTempFileName (path
, TEXT("tsm"), 0, tempF
);
473 std::wstring sTempFile
= std::wstring(tempF
);
478 size_t patchlen
= strlen(lpstr
);
479 _tfopen_s(&outFile
, sTempFile
.c_str(), _T("wb"));
482 size_t size
= fwrite(lpstr
, sizeof(char), patchlen
, outFile
);
483 if (size
== patchlen
)
485 CommDlg_OpenSave_SetControlText(hFileDialog
, edt1
, sTempFile
.c_str());
486 PostMessage(hFileDialog
, WM_COMMAND
, MAKEWPARAM(IDOK
, BM_CLICK
), (LPARAM
)(GetDlgItem(hDlg
, IDOK
)));
497 int CTortoiseMergeApp::ExitInstance()
499 // Look for temporary files left around by TortoiseMerge and
500 // remove them. But only delete 'old' files
501 DWORD len
= ::GetTempPath(0, NULL
);
502 TCHAR
* path
= new TCHAR
[len
+ 100];
503 len
= ::GetTempPath (len
+100, path
);
506 CSimpleFileFind finder
= CSimpleFileFind(path
, _T("*tsm*.*"));
508 ::GetSystemTimeAsFileTime(&systime_
);
509 __int64 systime
= (((_int64
)systime_
.dwHighDateTime
)<<32) | ((__int64
)systime_
.dwLowDateTime
);
510 while (finder
.FindNextFileNoDirectories())
512 CString filepath
= finder
.GetFilePath();
513 HANDLE hFile
= ::CreateFile(filepath
, GENERIC_READ
, FILE_SHARE_READ
, NULL
, OPEN_EXISTING
, NULL
, NULL
);
514 if (hFile
!= INVALID_HANDLE_VALUE
)
516 FILETIME createtime_
;
517 if (::GetFileTime(hFile
, &createtime_
, NULL
, NULL
))
519 ::CloseHandle(hFile
);
520 __int64 createtime
= (((_int64
)createtime_
.dwHighDateTime
)<<32) | ((__int64
)createtime_
.dwLowDateTime
);
521 if ((createtime
+ 864000000000) < systime
) //only delete files older than a day
523 ::SetFileAttributes(filepath
, FILE_ATTRIBUTE_NORMAL
);
524 ::DeleteFile(filepath
);
528 ::CloseHandle(hFile
);
534 return CWinAppEx::ExitInstance();