// $Id: Merge.cpp 6861 2009-06-25 12:11:07Z kimmov $
#include "stdafx.h"
+#include "Merge.h"
#include "Constants.h"
#include "UnicodeString.h"
#include "unicoder.h"
#include "Environment.h"
#include "OptionsMgr.h"
-#include "Merge.h"
+#include "RegOptionsMgr.h"
#include "OpenDoc.h"
#include "OpenFrm.h"
#include "OpenView.h"
#include "DirDoc.h"
#include "DirView.h"
#include "Splash.h"
+#include "PropBackups.h"
+#include "FileOrFolderSelect.h"
#include "paths.h"
#include "FileFilterHelper.h"
+#include "LineFiltersList.h"
+#include "SyntaxColors.h"
+#include "OptionsSyntaxColors.h"
#include "Plugins.h"
#include "ProjectFile.h"
#include "MergeEditView.h"
#include "ConflictFileParser.h"
#include "codepage.h"
#include "JumpList.h"
+#include "stringdiffs.h"
+#include "TFile.h"
+#include "SourceControl.h"
#include "paths.h"
#include "Constants.h"
/** @brief Location for command line help to open. */
static TCHAR CommandLineHelpLocation[] = _T("::/htmlhelp/Command_line.html");
+// registry dir to WinMerge
+static String f_RegDir = _T("Software\\Thingamahoochie\\WinMerge");
+
+/** @brief Backup file extension. */
+static const TCHAR BACKUP_FILE_EXT[] = _T("bak");
+
#ifndef WIN64
/**
* @brief Turn STL exceptions into MFC exceptions.
, m_nLastCompareResult(0)
, m_bNonInteractive(false)
, m_pOptions(new CRegOptionsMgr())
+, m_pGlobalFileFilter(new FileFilterHelper())
, m_nActiveOperations(0)
, m_pLangDlg(new CLanguageSelect(IDR_MAINFRAME, IDR_MAINFRAME))
+, m_bEscShutdown(FALSE)
+, m_bClearCaseTool(FALSE)
+, m_bExitIfNoDiff(MergeCmdLineInfo::Disabled)
+, m_pLineFilters(new LineFiltersList())
+, m_pSyntaxColors(new SyntaxColors())
+, m_pSourceControl(new SourceControl())
{
// add construction code here,
// Place all significant initialization in InitInstance
CMergeApp::~CMergeApp()
{
+ sd_Close();
}
/////////////////////////////////////////////////////////////////////////////
// The one and only CMergeApp object
// Read last used filter from registry
// If filter fails to set, reset to default
const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
- BOOL bFilterSet = theApp.m_globalFileFilter.SetFilter(filterString.c_str());
+ BOOL bFilterSet = m_pGlobalFileFilter->SetFilter(filterString.c_str());
if (!bFilterSet)
{
- String filter = theApp.m_globalFileFilter.GetFilterNameOrMask();
+ String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
}
+ UpdateCodepageModule();
+
+ if (m_pSourceControl)
+ m_pSourceControl->InitializeSourceControlMembers();
+
+ g_bUnpackerMode = theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL);
+ g_bPredifferMode = theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL);
+
+ if (m_pSyntaxColors)
+ Options::SyntaxColors::Load(m_pSyntaxColors.get());
+
+ if (m_pLineFilters)
+ m_pLineFilters->Initialize(GetOptionsMgr());
+
+ // If there are no filters loaded, and there is filter string in previous
+ // option string, import old filters to new place.
+ if (m_pLineFilters->GetCount() == 0)
+ {
+ String oldFilter = theApp.GetProfileString(_T("Settings"), _T("RegExps"));
+ if (!oldFilter.empty())
+ m_pLineFilters->Import(oldFilter);
+ }
+
+ // Check if filter folder is set, and create it if not
+ String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
+ if (pathMyFolders.empty())
+ {
+ // No filter path, set it to default and make sure it exists.
+ String pathFilters = GetDefaultFilterUserPath(TRUE);
+ GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, pathFilters);
+ theApp.m_pGlobalFileFilter->SetFileFilterPath(pathFilters.c_str());
+ }
+
+ sd_Init(); // String diff init
+ sd_SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
+
CSplashWnd::EnableSplashScreen(!bDisableSplash && !bCommandLineInvoke);
// Initialize i18n (multiple language) support
static void OpenContributersFile(int&)
{
- GetMainFrame()->OpenFileToExternalEditor(paths_ConcatPath(env_GetProgPath(), ContributorsPath));
+ theApp.OpenFileToExternalEditor(paths_ConcatPath(env_GetProgPath(), ContributorsPath));
}
// App command to run the dialog
if (!filterPath.IsEmpty())
{
- m_globalFileFilter.SetUserFilterPath((LPCTSTR)filterPath);
+ m_pGlobalFileFilter->SetUserFilterPath((LPCTSTR)filterPath);
}
- m_globalFileFilter.LoadAllFileFilters();
+ m_pGlobalFileFilter->LoadAllFileFilters();
}
/** @brief Read command line arguments and open files for comparison.
// Set the global file filter.
if (!cmdInfo.m_sFileFilter.empty())
{
- m_globalFileFilter.SetFilter(cmdInfo.m_sFileFilter.c_str());
+ m_pGlobalFileFilter->SetFilter(cmdInfo.m_sFileFilter.c_str());
}
// Set codepage.
// comparison.
if (cmdInfo.m_bShowUsage)
{
- pMainFrame->ShowHelp(CommandLineHelpLocation);
+ ShowHelp(CommandLineHelpLocation);
}
else
{
// Set the required information we need from the command line:
- pMainFrame->m_bClearCaseTool = cmdInfo.m_bClearCaseTool;
- pMainFrame->m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
- pMainFrame->m_bEscShutdown = cmdInfo.m_bEscShutdown;
+ m_bClearCaseTool = cmdInfo.m_bClearCaseTool;
+ m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
+ m_bEscShutdown = cmdInfo.m_bEscShutdown;
- pMainFrame->m_strSaveAsPath = cmdInfo.m_sOutputpath.c_str();
+ m_strSaveAsPath = cmdInfo.m_sOutputpath.c_str();
- pMainFrame->m_strDescriptions[0] = cmdInfo.m_sLeftDesc;
+ m_strDescriptions[0] = cmdInfo.m_sLeftDesc;
if (cmdInfo.m_Files.GetSize() < 3)
{
- pMainFrame->m_strDescriptions[1] = cmdInfo.m_sRightDesc;
+ m_strDescriptions[1] = cmdInfo.m_sRightDesc;
}
else
{
- pMainFrame->m_strDescriptions[1] = cmdInfo.m_sMiddleDesc;
- pMainFrame->m_strDescriptions[2] = cmdInfo.m_sRightDesc;
+ m_strDescriptions[1] = cmdInfo.m_sMiddleDesc;
+ m_strDescriptions[2] = cmdInfo.m_sRightDesc;
}
if (cmdInfo.m_Files.GetSize() > 2)
break;
case 1:
TCHAR buff[32];
- wLangId = theApp.GetLangId();
+ wLangId = GetLangId();
if (GetLocaleInfo(wLangId, LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof(buff)/sizeof(buff[0])))
ucr::setDefaultCodepage(_ttol(buff));
else
}
}
+/**
+ * @brief Send current option settings into codepage module
+ */
+void CMergeApp::UpdateCodepageModule()
+{
+ // Get current codepage settings from the options module
+ // and push them into the codepage module
+ UpdateDefaultCodepage(GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_MODE), GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_CUSTOM));
+}
/** @brief Open help from mainframe when user presses F1*/
void CMergeApp::OnHelp()
{
- GetMainFrame()->ShowHelp();
+ ShowHelp();
+}
+
+/**
+ * @brief Open given file to external editor specified in options.
+ * @param [in] file Full path to file to open.
+ *
+ * Opens file to defined (in Options/system), Notepad by default,
+ * external editor. Path is decorated with quotation marks if needed
+ * (contains spaces). Also '$file' in editor path is replaced by
+ * filename to open.
+ * @param [in] file Full path to file to open.
+ * @param [in] nLineNumber Line number to go to.
+ */
+void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
+{
+ String sCmd = GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD);
+ String sFile(file);
+ string_replace(sCmd, _T("$linenum"), string_to_str(nLineNumber));
+
+ int nIndex = sCmd.find(_T("$file"));
+ if (nIndex > -1)
+ {
+ sFile.insert(0, _T("\""));
+ string_replace(sCmd, _T("$file"), sFile);
+ nIndex = sCmd.find(' ', nIndex + sFile.length());
+ if (nIndex > -1)
+ sCmd.insert(nIndex, _T("\""));
+ else
+ sCmd += '"';
+ }
+ else
+ {
+ sCmd += _T(" \"");
+ sCmd += sFile;
+ sCmd += _T("\"");
+ }
+
+ BOOL retVal = FALSE;
+ STARTUPINFO stInfo = {0};
+ stInfo.cb = sizeof(STARTUPINFO);
+ PROCESS_INFORMATION processInfo;
+
+ retVal = CreateProcess(NULL, (LPTSTR)sCmd.c_str(),
+ NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
+ &stInfo, &processInfo);
+
+ if (!retVal)
+ {
+ // Error invoking external editor
+ String msg = string_format_string1(_("Failed to execute external editor: %1"), sCmd);
+ AfxMessageBox(msg.c_str(), MB_ICONSTOP);
+ }
+ else
+ {
+ CloseHandle(processInfo.hThread);
+ CloseHandle(processInfo.hProcess);
+ }
+}
+
+/**
+ * @brief Open file, if it exists, else open url
+ */
+void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
+{
+ if (paths_DoesPathExist(szFile) == IS_EXISTING_FILE)
+ ShellExecute(NULL, _T("open"), _T("notepad.exe"), szFile, NULL, SW_SHOWNORMAL);
+ else
+ ShellExecute(NULL, _T("open"), szUrl, NULL, NULL, SW_SHOWNORMAL);
+}
+
+/**
+ * @brief Show Help - this is for opening help from outside mainframe.
+ * @param [in] helpLocation Location inside help, if NULL main help is opened.
+ */
+void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= NULL*/)
+{
+ String sPath = env_GetProgPath();
+ LANGID LangId = GetLangId();
+ if (PRIMARYLANGID(LangId) == LANG_JAPANESE)
+ sPath = paths_ConcatPath(sPath, DocsPath_ja);
+ else
+ sPath = paths_ConcatPath(sPath, DocsPath);
+ if (helpLocation == NULL)
+ {
+ if (paths_DoesPathExist(sPath) == IS_EXISTING_FILE)
+ ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOC, NULL);
+ else
+ ShellExecute(NULL, _T("open"), DocsURL, NULL, NULL, SW_SHOWNORMAL);
+ }
+ else
+ {
+ if (paths_DoesPathExist(sPath) == IS_EXISTING_FILE)
+ {
+ sPath += helpLocation;
+ ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
+ }
+ }
+}
+
+/**
+ * @brief Creates backup before file is saved or copied over.
+ * This function handles formatting correct path and filename for
+ * backup file. Formatting is done based on several options available
+ * for users in Options/Backups dialog. After path is formatted, file
+ * is simply just copied. Not much error checking, just if copying
+ * succeeded or failed.
+ * @param [in] bFolder Are we creating backup in folder compare?
+ * @param [in] pszPath Full path to file to backup.
+ * @return TRUE if backup succeeds, or isn't just done.
+ */
+BOOL CMergeApp::CreateBackup(BOOL bFolder, const String& pszPath)
+{
+ // If user doesn't want to backups in folder compare, return
+ // success so operations don't abort.
+ if (bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FOLDERCMP)))
+ return TRUE;
+ // Likewise if user doesn't want backups in file compare
+ else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
+ return TRUE;
+
+ // create backup copy of file if destination file exists
+ if (paths_DoesPathExist(pszPath) == IS_EXISTING_FILE)
+ {
+ String bakPath;
+ String path;
+ String filename;
+ String ext;
+
+ paths_SplitFilename(pszPath, &path, &filename, &ext);
+
+ // Determine backup folder
+ if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
+ PropBackups::FOLDER_ORIGINAL)
+ {
+ // Put backups to same folder than original file
+ bakPath = path;
+ }
+ else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
+ PropBackups::FOLDER_GLOBAL)
+ {
+ // Put backups to global folder defined in options
+ bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
+ if (bakPath.empty())
+ bakPath = path;
+ else
+ bakPath = paths_GetLongPath(bakPath);
+ }
+ else
+ {
+ _RPTF0(_CRT_ERROR, "Unknown backup location!");
+ }
+
+ BOOL success = FALSE;
+ if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
+ {
+ // Don't add dot if there is no existing extension
+ if (ext.size() > 0)
+ ext += _T(".");
+ ext += BACKUP_FILE_EXT;
+ }
+
+ // Append time to filename if wanted so
+ // NOTE just adds timestamp at the moment as I couldn't figure out
+ // nice way to add a real time (invalid chars etc).
+ if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_TIME))
+ {
+ struct tm *tm;
+ time_t curtime = 0;
+ time(&curtime);
+ tm = localtime(&curtime);
+ CString timestr;
+ timestr.Format(_T("%04d%02d%02d%02d%02d%02d"), tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+ filename += _T("-");
+ filename += timestr;
+ }
+
+ // Append filename and extension (+ optional .bak) to path
+ if ((bakPath.length() + filename.length() + ext.length())
+ < MAX_PATH)
+ {
+ success = TRUE;
+ bakPath = paths_ConcatPath(bakPath, filename);
+ bakPath += _T(".");
+ bakPath += ext;
+ }
+
+ if (success)
+ success = CopyFile(pszPath.c_str(), bakPath.c_str(), FALSE);
+
+ if (!success)
+ {
+ String msg = string_format_string1(
+ _("Unable to backup original file:\n%1\n\nContinue anyway?"),
+ pszPath);
+ if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN) != IDYES)
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ // we got here because we're either not backing up of there was nothing to backup
+ return TRUE;
+}
+
+/**
+ * @brief Sync file to Version Control System
+ * @param pszDest [in] Where to copy (incl. filename)
+ * @param bApplyToAll [in,out] Apply user selection to all items
+ * @param psError [out] Error string that can be shown to user in caller func
+ * @return User selection or -1 if error happened
+ * @sa CMainFrame::HandleReadonlySave()
+ * @sa CDirView::PerformActionList()
+ */
+int CMergeApp::SyncFileToVCS(const String& pszDest, BOOL &bApplyToAll,
+ String& sError)
+{
+ String sActionError;
+ String strSavePath(pszDest);
+ int nVerSys = 0;
+
+ nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
+
+ int nRetVal = HandleReadonlySave(strSavePath, TRUE, bApplyToAll);
+ if (nRetVal == IDCANCEL || nRetVal == IDNO)
+ return nRetVal;
+
+ // If VC project opened from VSS sync and version control used
+ if ((nVerSys == SourceControl::VCS_VSS4 || nVerSys == SourceControl::VCS_VSS5) && m_pSourceControl->m_bVCProjSync)
+ {
+ if (!m_pSourceControl->m_vssHelper.ReLinkVCProj(strSavePath, sError))
+ nRetVal = -1;
+ }
+ return nRetVal;
+}
+
+/**
+ * @brief Checks if path (file/folder) is read-only and asks overwriting it.
+ *
+ * @param strSavePath [in,out] Path where to save (file or folder)
+ * @param bMultiFile [in] Single file or multiple files/folder
+ * @param bApplyToAll [in,out] Apply last user selection for all items?
+ * @return Users selection:
+ * - IDOK: Item was not readonly, no actions
+ * - IDYES/IDYESTOALL: Overwrite readonly item
+ * - IDNO: User selected new filename (single file) or user wants to skip
+ * - IDCANCEL: Cancel operation
+ * @sa CMainFrame::SyncFileToVCS()
+ * @sa CMergeDoc::DoSave()
+ */
+int CMergeApp::HandleReadonlySave(String& strSavePath, BOOL bMultiFile,
+ BOOL &bApplyToAll)
+{
+ CFileStatus status;
+ UINT userChoice = 0;
+ int nRetVal = IDOK;
+ BOOL bFileRO = FALSE;
+ BOOL bFileExists = FALSE;
+ String s;
+ String str;
+ CString title;
+ int nVerSys = 0;
+
+ try
+ {
+ TFile file(strSavePath);
+ bFileExists = file.exists();
+ if (bFileExists)
+ bFileRO = !file.canWrite();
+ }
+ catch (...)
+ {
+ }
+ nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
+
+ if (bFileExists && bFileRO)
+ {
+ // Version control system used?
+ // Checkout file from VCS and modify, don't ask about overwriting
+ // RO files etc.
+ if (nVerSys != SourceControl::VCS_NONE)
+ {
+ bool bRetVal = m_pSourceControl->SaveToVersionControl(strSavePath);
+ if (bRetVal)
+ return IDYES;
+ else
+ return IDCANCEL;
+ }
+
+ // Don't ask again if its already asked
+ if (bApplyToAll)
+ userChoice = IDYES;
+ else
+ {
+ // Prompt for user choice
+ if (bMultiFile)
+ {
+ // Multiple files or folder
+ str = string_format_string1(_("%1\nis marked read-only. Would you like to override the read-only item?"), strSavePath);
+ userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
+ MB_ICONWARNING | MB_DEFBUTTON3 | MB_DONT_ASK_AGAIN |
+ MB_YES_TO_ALL, IDS_SAVEREADONLY_MULTI);
+ }
+ else
+ {
+ // Single file
+ str = string_format_string1(_("%1 is marked read-only. Would you like to override the read-only file ? (No to save as new filename.)"), strSavePath);
+ userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
+ MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
+ IDS_SAVEREADONLY_FMT);
+ }
+ }
+ switch (userChoice)
+ {
+ // Overwrite read-only file
+ case IDYESTOALL:
+ bApplyToAll = TRUE; // Don't ask again (no break here)
+ case IDYES:
+ CFile::GetStatus(strSavePath.c_str(), status);
+ status.m_mtime = 0; // Avoid unwanted changes
+ status.m_attribute &= ~CFile::readOnly;
+ CFile::SetStatus(strSavePath.c_str(), status);
+ nRetVal = IDYES;
+ break;
+
+ // Save to new filename (single) /skip this item (multiple)
+ case IDNO:
+ if (!bMultiFile)
+ {
+ if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, strSavePath.c_str(), _("Save As"), _T(""), FALSE))
+ {
+ strSavePath = s;
+ nRetVal = IDNO;
+ }
+ else
+ nRetVal = IDCANCEL;
+ }
+ else
+ nRetVal = IDNO;
+ break;
+
+ // Cancel saving
+ case IDCANCEL:
+ nRetVal = IDCANCEL;
+ break;
+ }
+ }
+ return nRetVal;
+}
+
+/**
+ * @brief Shows VSS error from exception and writes log.
+ */
+void CMergeApp::ShowVSSError(CException *e, const String& strItem)
+{
+ TCHAR errStr[1024] = {0};
+ if (e->GetErrorMessage(errStr, 1024))
+ {
+ String errMsg = theApp.LoadString(IDS_VSS_ERRORFROM);
+ String logMsg = errMsg;
+ errMsg += _T("\n");
+ errMsg += errStr;
+ logMsg += _T(" ");
+ logMsg += errStr;
+ if (!strItem.empty())
+ {
+ errMsg += _T("\n\n");
+ errMsg += strItem;
+ logMsg += _T(": ");
+ logMsg += strItem;
+ }
+ LogErrorString(logMsg);
+ AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
+ }
+ else
+ {
+ LogErrorString(_T("VSSError (unable to GetErrorMessage)"));
+ e->ReportError(MB_ICONSTOP, IDS_VSS_RUN_ERROR);
+ }
}
/**
{
String filter = project.GetFilter();
filter = string_trim_ws(filter);
- m_globalFileFilter.SetFilter(filter);
+ m_pGlobalFileFilter->SetFilter(filter);
}
if (project.HasSubfolders())
bRecursive = project.GetSubfolders() > 0;