Пример #1
0
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;
}
Пример #2
0
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;
}
Пример #3
0
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);
}
Пример #4
0
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;
}
Пример #5
0
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]);
}
Пример #6
0
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;
}
Пример #7
0
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();
}