Fixed issue #1507: Submodule Diff Dialog should show dirty state only on working...
[TortoiseGit.git] / src / TortoiseMerge / TortoiseMerge.cpp
bloba2700aac54db828cbee798410b6c50431e7234ff
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.
19 #include "stdafx.h"
20 #include <dlgs.h>
21 #include "TortoiseMerge.h"
22 #include "MainFrm.h"
23 #include "AboutDlg.h"
24 #include "CmdLineParser.h"
25 #include "version.h"
26 #include "AppUtils.h"
27 #include "PathUtils.h"
28 #include "BrowseFolder.h"
29 #include "DirFileEnum.h"
31 #ifdef _DEBUG
32 #define new DEBUG_NEW
33 #endif
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)
39 END_MESSAGE_MAP()
42 CTortoiseMergeApp::CTortoiseMergeApp()
44 EnableHtmlHelp();
45 m_bLoadUserToolbars = FALSE;
46 m_bSaveState = FALSE;
49 // The one and only CTortoiseMergeApp object
50 CTortoiseMergeApp theApp;
51 CString sOrigCWD;
52 CCrashReportTGit g_crasher(L"TortoiseMerge " _T(APP_X64_STRING));
54 // CTortoiseMergeApp initialization
55 BOOL CTortoiseMergeApp::InitInstance()
57 SetDllDirectory(L"");
58 CCrashReport::Instance().AddUserInfoToReport(L"CommandLine", GetCommandLine());
60 CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
61 CMFCButton::EnableWindowsTheming();
64 DWORD len = GetCurrentDirectory(0, NULL);
65 if (len)
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);
78 long langId = loc;
79 CString langDll;
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)
90 FreeLibrary(hInst);
91 hInst = NULL;
93 if (hInst != NULL)
94 AfxSetResourceHandle(hInst);
95 else
97 DWORD lid = SUBLANGID(langId);
98 lid--;
99 if (lid > 0)
101 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
103 else
104 langId = 0;
106 } while ((hInst == NULL) && (langId != 0));
107 TCHAR buf[6];
108 _tcscpy_s(buf, _T("en"));
109 langId = loc;
110 CString sHelppath;
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("_");
121 sLang += buf;
122 sHelppath.Replace(_T("_en"), sLang);
123 if (PathFileExists(sHelppath))
125 free((void*)m_pszHelpFilePath);
126 m_pszHelpFilePath=_tcsdup(sHelppath);
127 break;
129 sHelppath.Replace(sLang, _T("_en"));
130 GetLocaleInfo(MAKELCID(langId, SORT_DEFAULT), LOCALE_SISO3166CTRYNAME, buf, _countof(buf));
131 sLang += _T("_");
132 sLang += buf;
133 sHelppath.Replace(_T("_en"), sLang);
134 if (PathFileExists(sHelppath))
136 free((void*)m_pszHelpFilePath);
137 m_pszHelpFilePath=_tcsdup(sHelppath);
138 break;
140 sHelppath.Replace(sLang, _T("_en"));
142 DWORD lid = SUBLANGID(langId);
143 lid--;
144 if (lid > 0)
146 langId = MAKELANGID(PRIMARYLANGID(langId), lid);
148 else
149 langId = 0;
150 } while (langId);
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")))
171 CString sHelpText;
172 sHelpText.LoadString(IDS_COMMANDLINEHELP);
173 MessageBox(NULL, sHelpText, _T("TortoiseMerge"), MB_ICONINFORMATION);
174 return FALSE;
177 // Initialize OLE libraries
178 if (!AfxOleInit())
180 AfxMessageBox(IDP_OLE_INIT_FAILED);
181 return FALSE;
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;
197 if (pFrame == NULL)
198 return FALSE;
199 m_pMainWnd = pFrame;
201 // create and load the frame with its resources
202 if (!pFrame->LoadFrame(IDR_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, NULL))
203 return FALSE;
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('\\'));
240 else
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)
245 return FALSE;
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);
262 CString temp;
263 temp.LoadString(IDS_OPENDIFFFILETITLE);
264 if (temp.IsEmpty())
265 ofn.lpstrTitle = NULL;
266 else
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"));
272 if (cFormat)
274 if (OpenClipboard(NULL))
276 UINT enumFormat = 0;
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);
289 CloseClipboard();
293 CString sFilter;
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)
301 if (*ptr == '|')
302 *ptr = '\0';
303 ptr--;
305 ofn.lpstrFilter = pszFilters;
306 ofn.nFilterIndex = 1;
308 // Display the Open dialog box.
309 CString tempfile;
310 if (GetOpenFileName(&ofn)==FALSE)
312 delete [] pszFilters;
313 return FALSE;
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() )
321 LPWSTR *szArglist;
322 int nArgs;
324 szArglist = CommandLineToArgvW(GetCommandLineW(), &nArgs);
325 if( NULL == szArglist )
327 TRACE("CommandLineToArgvW failed\n");
329 else
331 if ( nArgs==3 || nArgs==4 )
333 // Four parameters:
334 // [0]: Program name
335 // [1]: BASE file
336 // [2]: my file
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"));
384 else
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);
413 CString temp;
414 temp.LoadString(IDS_SAVEASTITLE);
415 if (!temp.IsEmpty())
416 ofn.lpstrTitle = temp;
417 ofn.Flags = OFN_OVERWRITEPROMPT;
418 CString sFilter;
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)
426 if (*ptr == '|')
427 *ptr = '\0';
428 ptr--;
430 ofn.lpstrFilter = pszFilters;
431 ofn.nFilterIndex = 1;
433 // Display the Save dialog box.
434 CString sFile;
435 if (GetSaveFileName(&ofn)==TRUE)
437 outfile = CString(ofn.lpstrFile);
439 delete [] pszFilters;
441 if (!outfile.IsEmpty())
443 CAppUtils::CreateUnifiedDiff(origFile, modifiedFile, outfile, false);
444 return 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();
456 return TRUE;
459 return pFrame->LoadViews();
462 // CTortoiseMergeApp message handlers
464 void CTortoiseMergeApp::OnAppAbout()
466 CAboutDlg aboutDlg;
467 aboutDlg.DoModal();
470 UINT_PTR CALLBACK
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);
491 delete [] path;
492 delete [] tempF;
494 FILE * outFile;
495 size_t patchlen = strlen(lpstr);
496 _tfopen_s(&outFile, sTempFile.c_str(), _T("wb"));
497 if(outFile)
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)));
505 fclose(outFile);
507 GlobalUnlock(hglb);
508 CloseClipboard();
511 return 0;
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);
521 if (len != 0)
523 CSimpleFileFind finder = CSimpleFileFind(path, _T("*tsm*.*"));
524 FILETIME systime_;
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);
544 else
545 ::CloseHandle(hFile);
549 delete[] path;
551 return CWinAppEx::ExitInstance();