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; }
void CSVNStatusCache::RemoveCacheForPath(const CTSVNPath& path) { // Stop the crawler starting on a new folder CCrawlInhibitor crawlInhibit(&m_folderCrawler); CCachedDirectory::ItDir itMap = m_directoryCache.find(path); CCachedDirectory * dirtoremove = NULL; if ((itMap != m_directoryCache.end())&&(itMap->second)) dirtoremove = itMap->second; if (dirtoremove == NULL) return; ATLASSERT(path.IsEquivalentToWithoutCase(dirtoremove->m_directoryPath)); RemoveCacheForDirectory(dirtoremove); }
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; }
void CCachedDirectory::RefreshStatus(bool bRecursive) { // Make sure that our own status is up-to-date GetStatusForMember(m_directoryPath,bRecursive); CTSVNPathList updatePathList; CTSVNPathList crawlPathList; CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": RefreshStatus for %s\n"), m_directoryPath.GetWinPath()); DWORD now = GetTickCount(); { // get the file write times with FindFirstFile/FindNextFile since those // APIs only access the folder, not each file individually. // This reduces the disk access a *lot*. std::map<CStringA, ULONGLONG> filetimes; WIN32_FIND_DATA FindFileData; CAutoFindFile hFind = FindFirstFile(m_directoryPath.GetWinPathString() + L"\\*.*", &FindFileData); if (hFind) { while (FindNextFile(hFind, &FindFileData)) { if ( (wcscmp(FindFileData.cFileName, L"..")==0) || (wcscmp(FindFileData.cFileName, L".")==0) ) continue; ULARGE_INTEGER ft; ft.LowPart = FindFileData.ftLastWriteTime.dwLowDateTime; ft.HighPart = FindFileData.ftLastWriteTime.dwHighDateTime; CStringA nameUTF8 = CUnicodeUtils::GetUTF8(FindFileData.cFileName); filetimes[nameUTF8] = ft.QuadPart; } hFind.CloseHandle(); // explicit close handle to shorten its life time } AutoLocker lock(m_critSec); // We also need to check if all our file members have the right date on them for (CacheEntryMap::iterator itMembers = m_entryCache.begin(); itMembers != m_entryCache.end(); ++itMembers) { if ((itMembers->first)&&(!itMembers->first.IsEmpty())) { CTSVNPath filePath (GetFullPathString (itMembers->first)); if (!filePath.IsEquivalentToWithoutCase(m_directoryPath)) { // we only have file members in our entry cache ATLASSERT(!itMembers->second.IsDirectory()); auto ftIt = filetimes.find(itMembers->first.Mid(1)); if (ftIt != filetimes.end()) { ULONGLONG ft = ftIt->second; if ((itMembers->second.HasExpired(now))||(!itMembers->second.DoesFileTimeMatch(ft))) { // We need to request this item as well updatePathList.AddPath(filePath); } } } } } if (bRecursive) { // crawl all sub folders too! Otherwise a change deep inside the // tree which has changed won't get propagated up the tree. for(ChildDirStatus::const_iterator it = m_childDirectories.begin(); it != m_childDirectories.end(); ++it) { CTSVNPath path; CString winPath = CUnicodeUtils::GetUnicode (it->first); path.SetFromWin (winPath, true); crawlPathList.AddPath(path); } } } for (int i = 0; i < updatePathList.GetCount(); ++i) GetStatusForMember(updatePathList[i], bRecursive); for (int i = 0; i < crawlPathList.GetCount(); ++i) CSVNStatusCache::Instance().AddFolderForCrawling(crawlPathList[i]); }
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(); }