CStatusCacheEntry CSVNStatusCache::GetStatusForPath(const CTSVNPath& path, DWORD flags, bool bFetch /* = true */) { bool bRecursive = !!(flags & TSVNCACHE_FLAGS_RECUSIVE_STATUS); // Check a very short-lived 'mini-cache' of the last thing we were asked for. long now = (long)GetTickCount(); if(now-m_mostRecentExpiresAt < 0) { if(path.IsEquivalentToWithoutCase(m_mostRecentAskedPath)) { return m_mostRecentStatus; } } { AutoLocker lock(m_critSec); m_mostRecentAskedPath = path; m_mostRecentExpiresAt = now+1000; } if (IsPathGood(path) && m_shellCache.IsPathAllowed(path.GetWinPath())) { // Stop the crawler starting on a new folder while we're doing this much more important task... // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath(). CCrawlInhibitor crawlInhibit(&m_folderCrawler); CTSVNPath dirpath = path.GetContainingDirectory(); if (dirpath.IsEmpty()) dirpath = path.GetDirectory(); CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath); if (cachedDir != NULL) { CStatusCacheEntry entry = cachedDir->GetStatusForMember(path, bRecursive, bFetch); { AutoLocker lock(m_critSec); m_mostRecentStatus = entry; return m_mostRecentStatus; } } cachedDir = GetDirectoryCacheEntry(path.GetDirectory()); if (cachedDir != NULL) { CStatusCacheEntry entry = cachedDir->GetStatusForMember(path, bRecursive, bFetch); { AutoLocker lock(m_critSec); m_mostRecentStatus = entry; return m_mostRecentStatus; } } } AutoLocker lock(m_critSec); m_mostRecentStatus = CStatusCacheEntry(); if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.HasSVNAdminDir(path.GetWinPath(), true)) { m_mostRecentStatus.ForceStatus(svn_wc_status_normal); } return m_mostRecentStatus; }
bool RenameCommand::RenameWithReplace(HWND hWnd, const CTSVNPathList &srcPathList, const CTSVNPath &destPath, const CString &message /* = L"" */, bool move_as_child /* = false */, bool make_parents /* = false */) const { SVN svn; UINT idret = IDYES; bool bRet = true; if (destPath.Exists() && !destPath.IsDirectory() && !destPath.IsEquivalentToWithoutCase(srcPathList[0])) { CString sReplace; sReplace.Format(IDS_PROC_REPLACEEXISTING, destPath.GetWinPath()); CTaskDialog taskdlg(sReplace, CString(MAKEINTRESOURCE(IDS_PROC_REPLACEEXISTING_TASK2)), L"TortoiseSVN", 0, TDF_USE_COMMAND_LINKS | TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW); taskdlg.AddCommandControl(1, CString(MAKEINTRESOURCE(IDS_PROC_REPLACEEXISTING_TASK3))); taskdlg.AddCommandControl(2, CString(MAKEINTRESOURCE(IDS_PROC_REPLACEEXISTING_TASK4))); taskdlg.SetCommonButtons(TDCBF_CANCEL_BUTTON); taskdlg.SetDefaultCommandControl(2); taskdlg.SetMainIcon(TD_WARNING_ICON); INT_PTR ret = taskdlg.DoModal(hWnd); if (ret == 1) // replace idret = IDYES; else idret = IDNO; if (idret == IDYES) { if (!svn.Remove(CTSVNPathList(destPath), true, false)) { destPath.Delete(true); } } } if ((idret != IDNO)&&(!svn.Move(srcPathList, destPath, message, move_as_child, make_parents))) { auto apr_err = svn.GetSVNError()->apr_err; if ((apr_err == SVN_ERR_ENTRY_NOT_FOUND) || (apr_err == SVN_ERR_WC_PATH_NOT_FOUND)) { bRet = !!MoveFile(srcPathList[0].GetWinPath(), destPath.GetWinPath()); } else { svn.ShowErrorDialog(hWnd, srcPathList.GetCommonDirectory()); bRet = false; } } if (idret == IDNO) bRet = false; return bRet; }
CCachedDirectory::CCachedDirectory(const CTSVNPath& directoryPath) : m_wcDbFileTime(0) , m_bCurrentFullStatusValid(false) , m_currentFullStatus(svn_wc_status_none) , m_mostImportantFileStatus(svn_wc_status_none) , m_pCtx(NULL) , m_FetchingStatus(FALSE) { ATLASSERT(directoryPath.IsDirectory() || !PathFileExists(directoryPath.GetWinPath())); m_directoryPath = directoryPath; }
void GetDirectoryTest() { // Bit tricky, this test, because we need to know something about the file // layout on the machine which is running the test TCHAR winDir[MAX_PATH + 1] = { 0 }; GetWindowsDirectory(winDir, _countof(winDir)); CString sWinDir(winDir); CTSVNPath testPath; // This is a file which we know will always be there testPath.SetFromUnknown(sWinDir + L"\\win.ini"); ATLASSERT(!testPath.IsDirectory()); ATLASSERT(testPath.GetDirectory().GetWinPathString() == sWinDir); ATLASSERT(testPath.GetContainingDirectory().GetWinPathString() == sWinDir); // Now do the test on the win directory itself - It's hard to be sure about the containing directory // but we know it must be different to the directory itself testPath.SetFromUnknown(sWinDir); ATLASSERT(testPath.IsDirectory()); ATLASSERT(testPath.GetDirectory().GetWinPathString() == sWinDir); ATLASSERT(testPath.GetContainingDirectory().GetWinPathString() != sWinDir); ATLASSERT(testPath.GetContainingDirectory().GetWinPathString().GetLength() < sWinDir.GetLength()); // Try a root path testPath.SetFromUnknown(L"C:\\"); ATLASSERT(testPath.IsDirectory()); ATLASSERT(testPath.GetDirectory().GetWinPathString().CompareNoCase(L"C:\\")==0); ATLASSERT(testPath.GetContainingDirectory().IsEmpty()); // Try a root UNC path testPath.SetFromUnknown(L"\\MYSTATION"); ATLASSERT(testPath.GetContainingDirectory().IsEmpty()); // test the UI path methods testPath.SetFromUnknown(L"c:\\testing%20test"); ATLASSERT(testPath.GetUIFileOrDirectoryName().CompareNoCase(L"testing%20test") == 0); #ifdef _MFC_VER testPath.SetFromUnknown(L"http://server.com/testing%20special%20chars%20%c3%a4%c3%b6%c3%bc"); ATLASSERT(testPath.GetUIFileOrDirectoryName().CompareNoCase(L"testing special chars \344\366\374") == 0); #endif }
void CCachedDirectory::AddEntry(const CTSVNPath& path, const svn_client_status_t* pSVNStatus, bool needsLock, bool forceNormal) { svn_wc_status_kind nodestatus = forceNormal ? svn_wc_status_normal : (pSVNStatus ? pSVNStatus->node_status : svn_wc_status_none); if(path.IsDirectory()) { // no lock here: // AutoLocker lock(m_critSec); // because GetDirectoryCacheEntry() can try to obtain a write lock CCachedDirectory * childDir = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path); if (childDir) { if ((childDir->GetCurrentFullStatus() != svn_wc_status_ignored)||(pSVNStatus==NULL)||(nodestatus != svn_wc_status_unversioned)) childDir->m_ownStatus.SetStatus(pSVNStatus, needsLock, forceNormal); childDir->m_ownStatus.SetKind(svn_node_dir); } } else { AutoLocker lock(m_critSec); CStringA cachekey = GetCacheKey(path); CacheEntryMap::iterator entry_it = m_entryCache.lower_bound(cachekey); if (entry_it != m_entryCache.end() && entry_it->first == cachekey) { if (pSVNStatus) { if (entry_it->second.GetEffectiveStatus() > svn_wc_status_none && entry_it->second.GetEffectiveStatus() != nodestatus) { CSVNStatusCache::Instance().UpdateShell(path); CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": shell update for %s\n"), path.GetWinPath()); } } } else { entry_it = m_entryCache.insert(entry_it, std::make_pair(cachekey, CStatusCacheEntry())); } entry_it->second = CStatusCacheEntry(pSVNStatus, needsLock, path.GetLastWriteTime(), forceNormal); } }
bool DropExternalCommand::Execute() { bool bSuccess = false; CString droppath = parser.GetVal(L"droptarget"); CTSVNPath droptsvnpath = CTSVNPath(droppath); if (droptsvnpath.IsAdminDir()) droptsvnpath = droptsvnpath.GetDirectory(); if (!droptsvnpath.IsDirectory()) droptsvnpath = droptsvnpath.GetDirectory(); // first get the svn:externals property from the target folder SVNProperties props(droptsvnpath, SVNRev::REV_WC, false, false); std::string sExternalsValue; for (int i = 0; i < props.GetCount(); ++i) { if (props.GetItemName(i).compare(SVN_PROP_EXTERNALS) == 0) { sExternalsValue = props.GetItemValue(i); break; } } // we don't add admin dirs as externals pathList.RemoveAdminPaths(); if (pathList.GetCount() == 0) return bSuccess; SVNStatus status; status.GetStatus(droptsvnpath); CString sTargetRepoRootUrl; if (status.status && status.status->repos_root_url) { sTargetRepoRootUrl = CUnicodeUtils::GetUnicode(status.status->repos_root_url); } if (sTargetRepoRootUrl.IsEmpty()) { // failed to get the status and/or the repo root url CString messageString; messageString.Format(IDS_ERR_NOURLOFFILE, droptsvnpath.GetWinPath()); ::MessageBox(GetExplorerHWND(), messageString, L"TortoiseSVN", MB_ICONERROR); } SVN svn; for (auto i = 0; i < pathList.GetCount(); ++i) { CTSVNPath destPath = droptsvnpath; destPath.AppendPathString(pathList[i].GetFileOrDirectoryName()); bool bExists = !!PathFileExists(destPath.GetWinPath()); if (!bExists && (PathIsDirectory(pathList[i].GetWinPath()) || CopyFile(pathList[i].GetWinPath(), destPath.GetWinPath(), TRUE))) { SVNStatus sourceStatus; sourceStatus.GetStatus(pathList[i]); if (sourceStatus.status && sourceStatus.status->repos_root_url) { CString sExternalRootUrl = CUnicodeUtils::GetUnicode(sourceStatus.status->repos_root_url); CString sExternalUrl = svn.GetURLFromPath(pathList[i]); CString sExtValue = sExternalUrl + L" " + pathList[i].GetFileOrDirectoryName(); // check if the url is from the same repo as the target, and if it is // use a relative external url instead of a full url if (sTargetRepoRootUrl.Compare(sExternalRootUrl) == 0) { sExtValue = L"^" + sExternalUrl.Mid(sTargetRepoRootUrl.GetLength()) + L" " + pathList[i].GetFileOrDirectoryName(); } if (!sExternalsValue.empty()) { if (sExternalsValue[sExternalsValue.size() - 1] != '\n') sExternalsValue += "\n"; } sExternalsValue += CUnicodeUtils::StdGetUTF8((LPCWSTR)sExtValue); bSuccess = true; } } else { // the file already exists, there can't be an external with the // same name. CString messageString; messageString.Format(IDS_DROPEXT_FILEEXISTS, (LPCWSTR)pathList[i].GetFileOrDirectoryName()); ::MessageBox(GetExplorerHWND(), messageString, L"TortoiseSVN", MB_ICONERROR); bSuccess = false; } } if (bSuccess) { bSuccess = !!props.Add(SVN_PROP_EXTERNALS, sExternalsValue, true); if (bSuccess) { CString sInfo; sInfo.Format(IDS_DROPEXT_UPDATE_TASK1, (LPCTSTR)droptsvnpath.GetFileOrDirectoryName()); CTaskDialog taskdlg(sInfo, CString(MAKEINTRESOURCE(IDS_DROPEXT_UPDATE_TASK2)), L"TortoiseSVN", 0, TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS | TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW | TDF_SIZE_TO_CONTENT); taskdlg.AddCommandControl(1, CString(MAKEINTRESOURCE(IDS_DROPEXT_UPDATE_TASK3))); taskdlg.AddCommandControl(2, CString(MAKEINTRESOURCE(IDS_DROPEXT_UPDATE_TASK4))); taskdlg.SetCommonButtons(TDCBF_CANCEL_BUTTON); taskdlg.SetDefaultCommandControl(2); taskdlg.SetMainIcon(TD_WARNING_ICON); bool doUpdate = (taskdlg.DoModal(GetExplorerHWND()) == 1); if (doUpdate) { DWORD exitcode = 0; CString error; ProjectProperties pprops; pprops.ReadPropsPathList(pathList); CHooks::Instance().SetProjectProperties(droptsvnpath, pprops); if (CHooks::Instance().StartUpdate(GetExplorerHWND(), pathList, exitcode, error)) { if (exitcode) { CString temp; temp.Format(IDS_ERR_HOOKFAILED, (LPCTSTR)error); ::MessageBox(GetExplorerHWND(), temp, L"TortoiseSVN", MB_ICONERROR); return FALSE; } } CSVNProgressDlg progDlg; theApp.m_pMainWnd = &progDlg; progDlg.SetCommand(CSVNProgressDlg::SVNProgress_Update); progDlg.SetAutoClose(parser); progDlg.SetOptions(ProgOptSkipPreChecks); progDlg.SetPathList(CTSVNPathList(droptsvnpath)); progDlg.SetRevision(SVNRev(L"HEAD")); progDlg.SetProjectProperties(pprops); progDlg.SetDepth(svn_depth_unknown); progDlg.DoModal(); return !progDlg.DidErrorsOccur(); } } else { // adding the svn:externals property failed, remove all the copied files props.ShowErrorDialog(GetExplorerHWND()); } } return bSuccess != false; }
bool SVNDiff::DiffWCFile(const CTSVNPath& filePath, bool ignoreprops, svn_wc_status_kind status, /* = svn_wc_status_none */ svn_wc_status_kind text_status /* = svn_wc_status_none */, svn_wc_status_kind prop_status /* = svn_wc_status_none */, svn_wc_status_kind remotetext_status /* = svn_wc_status_none */, svn_wc_status_kind remoteprop_status /* = svn_wc_status_none */) { CTSVNPath basePath; CTSVNPath remotePath; SVNRev remoteRev; svn_revnum_t baseRev = 0; // first diff the remote properties against the wc props // TODO: should we attempt to do a three way diff with the properties too // if they're modified locally and remotely? if (!ignoreprops && (remoteprop_status > svn_wc_status_normal)) { DiffProps(filePath, SVNRev::REV_HEAD, SVNRev::REV_WC, baseRev); } if (!ignoreprops && (prop_status > svn_wc_status_normal)&&(filePath.IsDirectory())) { DiffProps(filePath, SVNRev::REV_WC, SVNRev::REV_BASE, baseRev); } if (filePath.IsDirectory()) return true; if ((status > svn_wc_status_normal) || (text_status > svn_wc_status_normal)) { basePath = SVN::GetPristinePath(filePath); if (baseRev == 0) { SVNStatus stat; CTSVNPath dummy; svn_client_status_t * s = stat.GetFirstFileStatus(filePath, dummy); if (s) baseRev = s->revision >= 0 ? s->revision : s->changed_rev; } // If necessary, convert the line-endings on the file before diffing if ((DWORD)CRegDWORD(L"Software\\TortoiseSVN\\ConvertBase", TRUE)) { CTSVNPath temporaryFile = CTempFiles::Instance().GetTempFilePath(m_bRemoveTempFiles, filePath, SVNRev::REV_BASE); if (!m_pSVN->Export(filePath, temporaryFile, SVNRev(SVNRev::REV_BASE), SVNRev(SVNRev::REV_BASE))) { temporaryFile.Reset(); } else { basePath = temporaryFile; SetFileAttributes(basePath.GetWinPath(), FILE_ATTRIBUTE_READONLY); } } } if (remotetext_status > svn_wc_status_normal) { remotePath = CTempFiles::Instance().GetTempFilePath(false, filePath, SVNRev::REV_HEAD); CProgressDlg progDlg; progDlg.SetTitle(IDS_APPNAME); progDlg.SetTime(false); m_pSVN->SetAndClearProgressInfo(&progDlg, true); // activate progress bar progDlg.ShowModeless(GetHWND()); progDlg.FormatPathLine(1, IDS_PROGRESSGETFILE, (LPCTSTR)filePath.GetUIFileOrDirectoryName()); remoteRev = SVNRev::REV_HEAD; if (!m_pSVN->Export(filePath, remotePath, remoteRev, remoteRev)) { progDlg.Stop(); m_pSVN->SetAndClearProgressInfo((HWND)NULL); m_pSVN->ShowErrorDialog(GetHWND()); return false; } progDlg.Stop(); m_pSVN->SetAndClearProgressInfo((HWND)NULL); SetFileAttributes(remotePath.GetWinPath(), FILE_ATTRIBUTE_READONLY); } CString name = filePath.GetUIFileOrDirectoryName(); CString n1, n2, n3; n1.Format(IDS_DIFF_WCNAME, (LPCTSTR)name); if (baseRev) n2.FormatMessage(IDS_DIFF_BASENAMEREV, (LPCTSTR)name, baseRev); else n2.Format(IDS_DIFF_BASENAME, (LPCTSTR)name); n3.Format(IDS_DIFF_REMOTENAME, (LPCTSTR)name); if ((text_status <= svn_wc_status_normal)&&(prop_status <= svn_wc_status_normal)&&(status <= svn_wc_status_normal)) { // Hasn't changed locally - diff remote against WC return CAppUtils::StartExtDiff( filePath, remotePath, n1, n3, filePath, filePath, SVNRev::REV_WC, remoteRev, remoteRev, CAppUtils::DiffFlags().AlternativeTool(m_bAlternativeTool), m_JumpLine, filePath.GetFileOrDirectoryName(), L""); } else if (remotePath.IsEmpty()) { return DiffFileAgainstBase(filePath, baseRev, ignoreprops, status, text_status, prop_status); } else { // Three-way diff CAppUtils::MergeFlags flags; flags.bAlternativeTool = m_bAlternativeTool; flags.bReadOnly = true; return !!CAppUtils::StartExtMerge(flags, basePath, remotePath, filePath, CTSVNPath(), false, n2, n3, n1, CString(), filePath.GetFileOrDirectoryName()); } }
bool SVNDiff::UnifiedDiff(CTSVNPath& tempfile, const CTSVNPath& url1, const SVNRev& rev1, const CTSVNPath& url2, const SVNRev& rev2, const SVNRev& peg, const CString& options, bool bIgnoreAncestry /* = false */, bool bIgnoreProperties /* = true */) { tempfile = CTempFiles::Instance().GetTempFilePath(m_bRemoveTempFiles, CTSVNPath(L"Test.diff")); bool bIsUrl = !!SVN::PathIsURL(url1); CProgressDlg progDlg; progDlg.SetTitle(IDS_APPNAME); progDlg.SetLine(1, CString(MAKEINTRESOURCE(IDS_PROGRESS_UNIFIEDDIFF))); progDlg.SetTime(false); m_pSVN->SetAndClearProgressInfo(&progDlg); progDlg.ShowModeless(GetHWND()); // find the root of the files CTSVNPathList plist; plist.AddPath(url1); plist.AddPath(url2); CTSVNPath relativeTo = plist.GetCommonRoot(); if (!relativeTo.IsUrl()) { if (!relativeTo.IsDirectory()) relativeTo = relativeTo.GetContainingDirectory(); } if (relativeTo.IsEmpty() && url1.Exists() && url2.IsUrl()) { // the source path exists, i.e. it's a local path, so // use this as the relative url relativeTo = url1.GetDirectory(); } // the 'relativeTo' path must be a path: svn throws an error if it's used for urls. else if ((!url2.IsEquivalentTo(url1) && (relativeTo.IsEquivalentTo(url1) || relativeTo.IsEquivalentTo(url2))) || url1.IsUrl() || url2.IsUrl()) relativeTo.Reset(); if ((!url1.IsEquivalentTo(url2))||((rev1.IsWorking() || rev1.IsBase())&&(rev2.IsWorking() || rev2.IsBase()))) { if (!m_pSVN->Diff(url1, rev1, url2, rev2, relativeTo, svn_depth_infinity, true, false, false, false, false, false, bIgnoreProperties, false, options, bIgnoreAncestry, tempfile)) { progDlg.Stop(); m_pSVN->SetAndClearProgressInfo((HWND)NULL); m_pSVN->ShowErrorDialog(GetHWND()); return false; } } else { if (!m_pSVN->PegDiff(url1, (peg.IsValid() ? peg : (bIsUrl ? m_headPeg : SVNRev::REV_WC)), rev1, rev2, relativeTo, svn_depth_infinity, true, false, false, false, false, false, bIgnoreProperties, false, options, false, tempfile)) { if (!m_pSVN->Diff(url1, rev1, url2, rev2, relativeTo, svn_depth_infinity, true, false, false, false, false, false, bIgnoreProperties, false, options, false, tempfile)) { progDlg.Stop(); m_pSVN->SetAndClearProgressInfo((HWND)NULL); m_pSVN->ShowErrorDialog(GetHWND()); return false; } } } if (CAppUtils::CheckForEmptyDiff(tempfile)) { progDlg.Stop(); m_pSVN->SetAndClearProgressInfo((HWND)NULL); TaskDialog(GetHWND(), AfxGetResourceHandle(), MAKEINTRESOURCE(IDS_APPNAME), MAKEINTRESOURCE(IDS_ERR_ERROROCCURED), MAKEINTRESOURCE(IDS_ERR_EMPTYDIFF), TDCBF_OK_BUTTON, TD_ERROR_ICON, NULL); return false; } progDlg.Stop(); m_pSVN->SetAndClearProgressInfo((HWND)NULL); return true; }
bool SVNDiff::DiffFileAgainstBase( const CTSVNPath& filePath, svn_revnum_t & baseRev, bool ignoreprops, svn_wc_status_kind status /*= svn_wc_status_none*/, svn_wc_status_kind text_status /*= svn_wc_status_none*/, svn_wc_status_kind prop_status /*= svn_wc_status_none*/ ) { bool retvalue = false; bool fileexternal = false; if ((text_status == svn_wc_status_none)||(prop_status == svn_wc_status_none)) { SVNStatus stat; stat.GetStatus(filePath); if (stat.status == NULL) return false; text_status = stat.status->text_status; prop_status = stat.status->prop_status; fileexternal = stat.status->file_external != 0; } if (!ignoreprops && (prop_status > svn_wc_status_normal)) { DiffProps(filePath, SVNRev::REV_WC, SVNRev::REV_BASE, baseRev); } if (filePath.IsDirectory()) return true; if ((status >= svn_wc_status_normal) || (text_status >= svn_wc_status_normal)) { CTSVNPath basePath(SVN::GetPristinePath(filePath)); if (baseRev == 0) { SVNInfo info; const SVNInfoData * infodata = info.GetFirstFileInfo(filePath, SVNRev(), SVNRev()); if (infodata) { if (infodata->copyfromurl && infodata->copyfromurl[0]) baseRev = infodata->copyfromrev; else baseRev = infodata->lastchangedrev; } } // If necessary, convert the line-endings on the file before diffing // note: file externals can not be exported if (((DWORD)CRegDWORD(L"Software\\TortoiseSVN\\ConvertBase", TRUE)) && (!fileexternal)) { CTSVNPath temporaryFile = CTempFiles::Instance().GetTempFilePath(m_bRemoveTempFiles, filePath, SVNRev::REV_BASE); if (!m_pSVN->Export(filePath, temporaryFile, SVNRev(SVNRev::REV_BASE), SVNRev(SVNRev::REV_BASE))) { temporaryFile.Reset(); } else { basePath = temporaryFile; SetFileAttributes(basePath.GetWinPath(), FILE_ATTRIBUTE_READONLY); } } // for added/deleted files, we don't have a BASE file. // create an empty temp file to be used. if (!basePath.Exists()) { basePath = CTempFiles::Instance().GetTempFilePath(m_bRemoveTempFiles, filePath, SVNRev::REV_BASE); SetFileAttributes(basePath.GetWinPath(), FILE_ATTRIBUTE_READONLY); } CString name = filePath.GetFilename(); CTSVNPath wcFilePath = filePath; if (!wcFilePath.Exists()) { wcFilePath = CTempFiles::Instance().GetTempFilePath(m_bRemoveTempFiles, filePath, SVNRev::REV_BASE); SetFileAttributes(wcFilePath.GetWinPath(), FILE_ATTRIBUTE_READONLY); } CString n1, n2; n1.Format(IDS_DIFF_WCNAME, (LPCTSTR)name); if (baseRev) n2.FormatMessage(IDS_DIFF_BASENAMEREV, (LPCTSTR)name, baseRev); else n2.Format(IDS_DIFF_BASENAME, (LPCTSTR)name); retvalue = CAppUtils::StartExtDiff( basePath, wcFilePath, n2, n1, filePath, filePath, SVNRev::REV_BASE, SVNRev::REV_WC, SVNRev::REV_BASE, CAppUtils::DiffFlags().Wait().AlternativeTool(m_bAlternativeTool), m_JumpLine, name, L""); } return retvalue; }
void CShellUpdater::WorkerThread() { HANDLE hWaitHandles[2]; hWaitHandles[0] = m_hTerminationEvent; hWaitHandles[1] = m_hWakeEvent; for(;;) { DWORD waitResult = WaitForMultipleObjects(_countof(hWaitHandles), hWaitHandles, FALSE, INFINITE); // exit event/working loop if the first event (m_hTerminationEvent) // has been signaled or if one of the events has been abandoned // (i.e. ~CShellUpdater() is being executed) if(waitResult == WAIT_OBJECT_0 || waitResult == WAIT_ABANDONED_0 || waitResult == WAIT_ABANDONED_0+1) { // Termination event break; } // wait some time before we notify the shell Sleep(50); for(;;) { CTSVNPath workingPath; if (!m_bRunning) return; Sleep(0); { AutoLocker lock(m_critSec); if(m_pathsToUpdate.empty()) { // Nothing left to do break; } if(InterlockedExchange(&m_bItemsAddedSinceLastUpdate, FALSE)) { m_pathsToUpdate.erase(std::unique(m_pathsToUpdate.begin(), m_pathsToUpdate.end(), &CTSVNPath::PredLeftEquivalentToRight), m_pathsToUpdate.end()); } workingPath = m_pathsToUpdate.front(); m_pathsToUpdate.pop_front(); } if (workingPath.IsEmpty()) continue; CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": shell notification for %s\n", workingPath.GetWinPath()); if (workingPath.IsDirectory()) { // first send a notification about a sub folder change, so explorer doesn't discard // the folder notification. Since we only know for sure that the subversion admin // dir is present, we send a notification for that folder. CString admindir = workingPath.GetWinPathString() + L"\\" + g_SVNAdminDir.GetAdminDirName(); SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, (LPCTSTR)admindir, NULL); SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL); // Sending an UPDATEDIR notification somehow overwrites/deletes the UPDATEITEM message. And without // that message, the folder overlays in the current view don't get updated without hitting F5. // Drawback is, without UPDATEDIR, the left tree view isn't always updated... //SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL); } else SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, workingPath.GetWinPath(), NULL); } } _endthread(); }
CStatusCacheEntry CSVNStatusCache::GetStatusForPath(const CTSVNPath& path, DWORD flags, bool bFetch /* = true */) { bool bRecursive = !!(flags & TSVNCACHE_FLAGS_RECUSIVE_STATUS); // Check a very short-lived 'mini-cache' of the last thing we were asked for. LONGLONG now = (LONGLONG)GetTickCount64(); if(now-m_mostRecentExpiresAt < 0) { if(path.IsEquivalentToWithoutCase(m_mostRecentAskedPath)) { return m_mostRecentStatus; } } { AutoLocker lock(m_critSec); m_mostRecentAskedPath = path; m_mostRecentExpiresAt = now+1000; } if (m_shellCache.IsPathAllowed(path.GetWinPath())) { if (IsPathGood(path)) { // Stop the crawler starting on a new folder while we're doing this much more important task... // Please note, that this may be a second "lock" used concurrently to the one in RemoveCacheForPath(). CCrawlInhibitor crawlInhibit(&m_folderCrawler); CTSVNPath dirpath = path.GetContainingDirectory(); if (dirpath.IsEmpty()) dirpath = path.GetDirectory(); CCachedDirectory * cachedDir = GetDirectoryCacheEntry(dirpath); if (cachedDir != NULL) { CStatusCacheEntry entry = cachedDir->GetStatusForMember(path, bRecursive, bFetch); { AutoLocker lock(m_critSec); m_mostRecentStatus = entry; return m_mostRecentStatus; } } cachedDir = GetDirectoryCacheEntry(path.GetDirectory()); if (cachedDir != NULL) { CStatusCacheEntry entry = cachedDir->GetStatusForMember(path, bRecursive, bFetch); { AutoLocker lock(m_critSec); m_mostRecentStatus = entry; return m_mostRecentStatus; } } } else { // path is blocked for some reason: return the cached status if we have one // we do here only a cache search, absolutely no disk access is allowed! CCachedDirectory::ItDir itMap = m_directoryCache.find(path); if ((itMap != m_directoryCache.end())&&(itMap->second)) { // We've found this directory in the cache CCachedDirectory * cachedDir = itMap->second; CStatusCacheEntry entry = cachedDir->GetCacheStatusForMember(path); { AutoLocker lock(m_critSec); m_mostRecentStatus = entry; return m_mostRecentStatus; } } } } AutoLocker lock(m_critSec); m_mostRecentStatus = CStatusCacheEntry(); if (m_shellCache.ShowExcludedAsNormal() && path.IsDirectory() && m_shellCache.IsVersioned(path.GetWinPath(), true, true)) { m_mostRecentStatus.ForceStatus(svn_wc_status_normal); } return m_mostRecentStatus; }
bool RemoveCommand::Execute() { bool bRet = false; // removing items from a working copy is done item-by-item so we // have a chance to show a progress bar // // removing items from an URL in the repository requires that we // ask the user for a log message. SVN svn; if ((pathList.GetCount())&&(SVN::PathIsURL(pathList[0]))) { // Delete using URL's, not wc paths svn.SetPromptApp(&theApp); CInputLogDlg dlg; CString sUUID; svn.GetRepositoryRootAndUUID(pathList[0], true, sUUID); dlg.SetUUID(sUUID); CString sHint; if (pathList.GetCount() == 1) sHint.Format(IDS_INPUT_REMOVEONE, (LPCTSTR)pathList[0].GetSVNPathString()); else sHint.Format(IDS_INPUT_REMOVEMORE, pathList.GetCount()); dlg.SetActionText(sHint); if (dlg.DoModal()==IDOK) { if (!svn.Remove(pathList, true, !!parser.HasKey(L"keep"), dlg.GetLogMessage())) { svn.ShowErrorDialog(GetExplorerHWND(), pathList.GetCommonDirectory()); return false; } return true; } return false; } else { bool bForce = false; for(int nPath = 0; nPath < pathList.GetCount(); nPath++) { CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) L": remove file %s\n", (LPCTSTR)pathList[nPath].GetUIPathString()); // even though SVN::Remove takes a list of paths to delete at once // we delete each item individually so we can prompt the user // if something goes wrong or unversioned/modified items are // to be deleted CTSVNPathList removePathList(pathList[nPath]); if ((bForce)&&(!parser.HasKey(L"keep"))) { CTSVNPath delPath = removePathList[0]; if (!delPath.IsDirectory()) delPath.Delete(true); // note: we don't move folders to the trash bin, so they can't // get restored anymore - svn removes *all* files in a removed // folder, even modified and unversioned ones // We could move the folders here to the trash bin too, but then // the folder would be gone and will require a recursive commit. // Of course: a solution would be to first create a copy of the folder, // move the original folder to the trash, then rename the copied folder // to the original name, then let svn delete the folder - but // that would just take too much time for bigger folders... } if (!svn.Remove(removePathList, bForce, !!parser.HasKey(L"keep"))) { if ((svn.GetSVNError()->apr_err == SVN_ERR_UNVERSIONED_RESOURCE) || (svn.GetSVNError()->apr_err == SVN_ERR_CLIENT_MODIFIED)) { UINT ret = 0; CString msg; if (pathList[nPath].IsDirectory()) msg.Format(IDS_PROC_REMOVEFORCE_TASK1_2, (LPCTSTR)svn.GetLastErrorMessage(0), (LPCTSTR)pathList[nPath].GetFileOrDirectoryName()); else msg.Format(IDS_PROC_REMOVEFORCE_TASK1, (LPCTSTR)svn.GetLastErrorMessage(0), (LPCTSTR)pathList[nPath].GetFileOrDirectoryName()); CTaskDialog taskdlg(msg, CString(MAKEINTRESOURCE(IDS_PROC_REMOVEFORCE_TASK2)), L"TortoiseSVN", 0, TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS | TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW); taskdlg.AddCommandControl(IDYES, CString(MAKEINTRESOURCE(IDS_PROC_REMOVEFORCE_TASK3))); taskdlg.AddCommandControl(IDNO, CString(MAKEINTRESOURCE(IDS_PROC_REMOVEFORCE_TASK4))); taskdlg.SetCommonButtons(TDCBF_CANCEL_BUTTON); taskdlg.SetVerificationCheckboxText(CString(MAKEINTRESOURCE(IDS_PROC_REMOVEFORCE_TASK5))); taskdlg.SetDefaultCommandControl(IDNO); taskdlg.SetMainIcon(TD_WARNING_ICON); ret = (UINT)taskdlg.DoModal(GetExplorerHWND()); if (taskdlg.GetVerificationCheckboxState()) bForce = true; if (ret == IDYESTOALL) bForce = true; if ((ret == IDYES)||(ret==IDYESTOALL)) { if (!parser.HasKey(L"keep")) { CTSVNPath delPath = removePathList[0]; if (!delPath.IsDirectory()) delPath.Delete(true); // note: see comment for the delPath.Delete() above } if (!svn.Remove(removePathList, true, !!parser.HasKey(L"keep"))) { svn.ShowErrorDialog(GetExplorerHWND(), removePathList.GetCommonDirectory()); } else bRet = true; } } else svn.ShowErrorDialog(GetExplorerHWND(), removePathList.GetCommonDirectory()); } } } if (bRet) CShellUpdater::Instance().AddPathsForUpdate(pathList); return bRet; }
svn_error_t * CCachedDirectory::GetStatusCallback(void *baton, const char *path, const svn_client_status_t *status, apr_pool_t * pool) { CCachedDirectory* pThis = (CCachedDirectory*)baton; if (path == NULL) return SVN_NO_ERROR; CTSVNPath svnPath; bool forceNormal = false; bool needsLock = false; const svn_wc_status_kind nodeStatus = status->node_status; if(status->versioned) { if ((nodeStatus != svn_wc_status_none)&&(nodeStatus != svn_wc_status_ignored)) svnPath.SetFromSVN(path, (status->kind == svn_node_dir)); else svnPath.SetFromSVN(path); if(svnPath.IsDirectory()) { if(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath)) { // Make sure we know about this child directory // This initial status value is likely to be overwritten from below at some point svn_wc_status_kind s = nodeStatus; if (status->conflicted) s = SVNStatus::GetMoreImportant(s, svn_wc_status_conflicted); CCachedDirectory * cdir = CSVNStatusCache::Instance().GetDirectoryCacheEntryNoCreate(svnPath); if (cdir) { // This child directory is already in our cache! // So ask this dir about its recursive status svn_wc_status_kind st = SVNStatus::GetMoreImportant(s, cdir->GetCurrentFullStatus()); pThis->SetChildStatus(svnPath, st); } else { // the child directory is not in the cache. Create a new entry for it in the cache which is // initially 'unversioned'. But we added that directory to the crawling list above, which // means the cache will be updated soon. CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath); pThis->SetChildStatus(svnPath, s); } } } else { // only fetch the svn:needs-lock property if the status of this file is 'normal', because // if the status is something else, the needs-lock overlay won't show up anyway if ((pThis->m_pCtx)&&(nodeStatus == svn_wc_status_normal)) { const svn_string_t * value = NULL; svn_error_t * err = svn_wc_prop_get2(&value, pThis->m_pCtx->wc_ctx, path, "svn:needs-lock", pool, pool); if ((err==NULL) && value) needsLock = true; if (err) svn_error_clear(err); } } } else { if ((status->kind != svn_node_unknown)&&(status->kind != svn_node_none)) svnPath.SetFromSVN(path, status->kind == svn_node_dir); else svnPath.SetFromSVN(path); // Subversion returns no 'entry' field for versioned folders if they're // part of another working copy (nested layouts). // So we have to make sure that such an 'unversioned' folder really // is unversioned. if (((nodeStatus == svn_wc_status_unversioned)||(nodeStatus == svn_wc_status_ignored))&&(!svnPath.IsEquivalentToWithoutCase(pThis->m_directoryPath))&&(svnPath.IsDirectory())) { if (svnPath.IsWCRoot()) { CSVNStatusCache::Instance().AddFolderForCrawling(svnPath); // Mark the directory as 'versioned' (status 'normal' for now). // This initial value will be overwritten from below some time later pThis->SetChildStatus(svnPath, svn_wc_status_normal); // Make sure the entry is also in the cache CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath); // also mark the status in the status object as normal forceNormal = true; } else { pThis->SetChildStatus(svnPath, nodeStatus); } } else if (nodeStatus == svn_wc_status_external) { if ((status->kind == svn_node_dir) || (svnPath.IsDirectory())) { CSVNStatusCache::Instance().AddFolderForCrawling(svnPath); // Mark the directory as 'versioned' (status 'normal' for now). // This initial value will be overwritten from below some time later pThis->SetChildStatus(svnPath, svn_wc_status_normal); // we have added a directory to the child-directory list of this // directory. We now must make sure that this directory also has // an entry in the cache. CSVNStatusCache::Instance().GetDirectoryCacheEntry(svnPath); // also mark the status in the status object as normal forceNormal = true; } } else { if (svnPath.IsDirectory()) { svn_wc_status_kind s = nodeStatus; if (status->conflicted) s = SVNStatus::GetMoreImportant(s, svn_wc_status_conflicted); pThis->SetChildStatus(svnPath, s); } } } pThis->AddEntry(svnPath, status, needsLock, forceNormal); return SVN_NO_ERROR; }
CStatusCacheEntry CCachedDirectory::GetStatusForMember(const CTSVNPath& path, bool bRecursive, bool bFetch /* = true */) { CStringA strCacheKey; bool bThisDirectoryIsUnversioned = false; bool bRequestForSelf = false; if(path.IsEquivalentToWithoutCase(m_directoryPath)) { bRequestForSelf = true; } // In all most circumstances, we ask for the status of a member of this directory. ATLASSERT(m_directoryPath.IsEquivalentToWithoutCase(path.GetContainingDirectory()) || bRequestForSelf); long long dbFileTime = CSVNStatusCache::Instance().WCRoots()->GetDBFileTime(m_directoryPath); bool wcDbFileTimeChanged = (m_wcDbFileTime != dbFileTime); if ( !wcDbFileTimeChanged ) { if(m_wcDbFileTime == 0) { // We are a folder which is not in a working copy bThisDirectoryIsUnversioned = true; m_ownStatus.SetStatus(NULL, false, false); // If a user removes the .svn directory, we get here with m_entryCache // not being empty, but still us being unversioned if (!m_entryCache.empty()) { m_entryCache.clear(); } ATLASSERT(m_entryCache.empty()); // However, a member *DIRECTORY* might be the top of WC // so we need to ask them to get their own status if(!path.IsDirectory()) { if ((PathFileExists(path.GetWinPath()))||(bRequestForSelf)) return CStatusCacheEntry(); // the entry doesn't exist anymore! // but we can't remove it from the cache here: // the GetStatusForMember() method is called only with a read // lock and not a write lock! // So mark it for crawling, and let the crawler remove it // later CSVNStatusCache::Instance().AddFolderForCrawling(path.GetContainingDirectory()); return CStatusCacheEntry(); } else { // If we're in the special case of a directory being asked for its own status // and this directory is unversioned, then we should just return that here if(bRequestForSelf) { return CStatusCacheEntry(); } } } if (CSVNStatusCache::Instance().GetDirectoryCacheEntryNoCreate(path) != NULL) { // We don't have directory status in our cache // Ask the directory if it knows its own status CCachedDirectory * dirEntry = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path); if ((dirEntry)&&(dirEntry->IsOwnStatusValid())) { // To keep recursive status up to date, we'll request that children are all crawled again // We have to do this because the directory watcher isn't very reliable (especially under heavy load) // and also has problems with SUBSTed drives. // If nothing has changed in those directories, this crawling is fast and only // accesses two files for each directory. if (bRecursive) { AutoLocker lock(dirEntry->m_critSec); ChildDirStatus::const_iterator it; for(it = dirEntry->m_childDirectories.begin(); it != dirEntry->m_childDirectories.end(); ++it) { CTSVNPath newpath; CString winPath = CUnicodeUtils::GetUnicode (it->first); newpath.SetFromWin(winPath, true); CSVNStatusCache::Instance().AddFolderForCrawling(newpath); } } return dirEntry->GetOwnStatus(bRecursive); } } else { { // if we currently are fetching the status of the directory // we want the status for, we just return an empty entry here // and don't wait for that fetching to finish. // That's because fetching the status can take a *really* long // time (e.g. if a commit is also in progress on that same // directory), and we don't want to make the explorer appear // to hang. if ((!bFetch)&&(m_FetchingStatus)) { if (m_directoryPath.IsAncestorOf(path)) { m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none; return GetCacheStatusForMember(path); } } } // Look up a file in our own cache AutoLocker lock(m_critSec); strCacheKey = GetCacheKey(path); CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey); if(itMap != m_entryCache.end()) { // We've hit the cache - check for timeout if(!itMap->second.HasExpired((long)GetTickCount())) { if(itMap->second.DoesFileTimeMatch(path.GetLastWriteTime())) { if ((itMap->second.GetEffectiveStatus()!=svn_wc_status_missing)||(!PathFileExists(path.GetWinPath()))) { // Note: the filetime matches after a modified has been committed too. // So in that case, we would return a wrong status (e.g. 'modified' instead // of 'normal') here. return itMap->second; } } } } } } else { if ((!bFetch)&&(m_FetchingStatus)) { if (m_directoryPath.IsAncestorOf(path)) { // returning empty status (status fetch in progress) // also set the status to 'none' to have the status change and // the shell updater invoked in the crawler m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none; CSVNStatusCache::Instance().AddFolderForCrawling(m_directoryPath.GetDirectory()); return GetCacheStatusForMember(path); } } // if we're fetching the status for the explorer, // we don't refresh the status but use the one // we already have (to save time and make the explorer // more responsive in stress conditions). // We leave the refreshing to the crawler. if ((!bFetch)&&(m_wcDbFileTime)) { CSVNStatusCache::Instance().AddFolderForCrawling(m_directoryPath.GetDirectory()); return GetCacheStatusForMember(path); } AutoLocker lock(m_critSec); m_entryCache.clear(); strCacheKey = GetCacheKey(path); } // We've not got this item in the cache - let's add it // We never bother asking SVN for the status of just one file, always for its containing directory if (g_SVNAdminDir.IsAdminDirPath(path.GetWinPathString())) { // We're being asked for the status of an .SVN directory // It's not worth asking for this return CStatusCacheEntry(); } { if ((!bFetch)&&(m_FetchingStatus)) { if (m_directoryPath.IsAncestorOf(path)) { m_currentFullStatus = m_mostImportantFileStatus = svn_wc_status_none; return GetCacheStatusForMember(path); } } } { AutoLocker lock(m_critSec); m_mostImportantFileStatus = svn_wc_status_none; m_childDirectories.clear(); m_entryCache.clear(); m_ownStatus.SetStatus(NULL, false, false); } if(!bThisDirectoryIsUnversioned) { if (!SvnUpdateMembersStatus()) { m_wcDbFileTime = 0; return CStatusCacheEntry(); } } // Now that we've refreshed our SVN status, we can see if it's // changed the 'most important' status value for this directory. // If it has, then we should tell our parent UpdateCurrentStatus(); m_wcDbFileTime = dbFileTime; if (path.IsDirectory()) { CCachedDirectory * dirEntry = CSVNStatusCache::Instance().GetDirectoryCacheEntry(path); if ((dirEntry)&&(dirEntry->IsOwnStatusValid())) { //CSVNStatusCache::Instance().AddFolderForCrawling(path); return dirEntry->GetOwnStatus(bRecursive); } // If the status *still* isn't valid here, it means that // the current directory is unversioned, and we shall need to ask its children for info about themselves if ((dirEntry)&&(dirEntry != this)) return dirEntry->GetStatusForMember(path,bRecursive); // add the path for crawling: if it's really unversioned, the crawler will // only check for the admin dir and do nothing more. But if it is // versioned (could happen in a nested layout) the crawler will update its // status correctly CSVNStatusCache::Instance().AddFolderForCrawling(path); return CStatusCacheEntry(); } else { CacheEntryMap::iterator itMap = m_entryCache.find(strCacheKey); if(itMap != m_entryCache.end()) { return itMap->second; } } AddEntry(path, NULL, false, false); return CStatusCacheEntry(); }
bool RemoveCommand::Execute() { bool bRet = false; // removing items from a working copy is done item-by-item so we // have a chance to show a progress bar // // removing items from an URL in the repository requires that we // ask the user for a log message. bool bForce = false; SVN svn; if ((pathList.GetCount())&&(SVN::PathIsURL(pathList[0]))) { // Delete using URL's, not wc paths svn.SetPromptApp(&theApp); CInputLogDlg dlg; CString sUUID; svn.GetRepositoryRootAndUUID(pathList[0], true, sUUID); dlg.SetUUID(sUUID); CString sHint; if (pathList.GetCount() == 1) sHint.Format(IDS_INPUT_REMOVEONE, (LPCTSTR)pathList[0].GetSVNPathString()); else sHint.Format(IDS_INPUT_REMOVEMORE, pathList.GetCount()); dlg.SetActionText(sHint); if (dlg.DoModal()==IDOK) { if (!svn.Remove(pathList, true, !!parser.HasKey(_T("keep")), dlg.GetLogMessage())) { CMessageBox::Show(hwndExplorer, svn.GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR); return false; } return true; } return false; } else { for(int nPath = 0; nPath < pathList.GetCount(); nPath++) { TRACE(_T("remove file %s\n"), (LPCTSTR)pathList[nPath].GetUIPathString()); // even though SVN::Remove takes a list of paths to delete at once // we delete each item individually so we can prompt the user // if something goes wrong or unversioned/modified items are // to be deleted CTSVNPathList removePathList(pathList[nPath]); if ((bForce)&&(!parser.HasKey(_T("keep")))) { CTSVNPath delPath = removePathList[0]; if (!delPath.IsDirectory()) delPath.Delete(true); // note: we don't move folders to the trash bin, so they can't // get restored anymore - svn removes *all* files in a removed // folder, even modified and unversioned ones // We could move the folders here to the trash bin too, but then // the folder would be gone and will require a recursive commit. // Of course: a solution would be to first create a copy of the folder, // move the original folder to the trash, then rename the copied folder // to the original name, then let svn delete the folder - but // that would just take too much time for bigger folders... } if (!svn.Remove(removePathList, bForce, !!parser.HasKey(_T("keep")))) { if ((svn.Err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE) || (svn.Err->apr_err == SVN_ERR_CLIENT_MODIFIED)) { CString msg, yes, no, yestoall; if (pathList[nPath].IsDirectory()) { msg.Format(IDS_PROC_REMOVEFORCEFOLDER, pathList[nPath].GetWinPath()); } else { msg.Format(IDS_PROC_REMOVEFORCE, (LPCTSTR)svn.GetLastErrorMessage()); } yes.LoadString(IDS_MSGBOX_YES); no.LoadString(IDS_MSGBOX_NO); yestoall.LoadString(IDS_PROC_YESTOALL); UINT ret = CMessageBox::Show(hwndExplorer, msg, _T("TortoiseSVN"), 2, IDI_ERROR, yes, no, yestoall); if (ret == 3) bForce = true; if ((ret == 1)||(ret==3)) { if (!parser.HasKey(_T("keep"))) { CTSVNPath delPath = removePathList[0]; if (!delPath.IsDirectory()) delPath.Delete(true); // note: see comment for the delPath.Delete() above } if (!svn.Remove(removePathList, true, !!parser.HasKey(_T("keep")))) { CMessageBox::Show(hwndExplorer, svn.GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR); } else bRet = true; } } else CMessageBox::Show(hwndExplorer, svn.GetLastErrorMessage(), _T("TortoiseSVN"), MB_ICONERROR); } } } if (bRet) CShellUpdater::Instance().AddPathsForUpdate(pathList); return bRet; }