• R/O
  • SSH
  • HTTPS

tortoisesvn:


File Info

Révision 29197
Taille 18,115 octets
l'heure 2021-05-05 01:51:51
Auteur stefankueng
Message de Log

get rid of TCHAR, use wchar_t instead

Content

// TortoiseSVN - a Windows shell extension for easy version control

// Copyright (C) 2007-2012, 2014-2015, 2018-2019, 2021 - TortoiseSVN

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

#include "stdafx.h"
#include "RepositoryInfo.h"
#include "Containers/CachedLogInfo.h"
#include "LogCacheSettings.h"

#include "SVN.h"
#include "TSVNPath.h"
#include "PathUtils.h"
#include "GoOffline.h"

#include "CriticalSection.h"

// begin namespace LogCache

namespace LogCache
{
// checking prefixes is not that simple ..

bool IsParentDirectory(const CString& parent, const CString& dir)
{
    return (parent == dir) || ((dir.GetLength() > parent.GetLength()) && (dir.GetAt(parent.GetLength()) == '/'));
}

CString UniqueFileName(const CString& fileName)
{
    CString base = fileName;
    for (int i = 0, count = base.GetLength(); i < count; ++i)
    {
        wchar_t c = base[i];
        if ((c == '?') || (c == '/') || (c == '\\') || (c == ':'))
            base.SetAt(i, '_');
    }

    int     num    = 0;
    CString result = base;
    while (GetFileAttributes(result) != INVALID_FILE_ATTRIBUTES)
        result.Format(L"%s(%d)", static_cast<LPCWSTR>(result), ++num);

    return result.MakeLower();
}

// a lookup utility that scans an index range

CString CRepositoryInfo::CData::FindRoot(TPartialIndex::const_iterator begin, TPartialIndex::const_iterator end, const CString& url)
{
    for (TPartialIndex::const_iterator iter = begin; iter != end; ++iter)
    {
        const CString& root = iter->second->root;
        if ((root == url.Left(root.GetLength()) && IsParentDirectory(root, url)))
        {
            return root;
        }
    }

    return CString();
}

// construction / destruction

CRepositoryInfo::CData::CData()
{
}

CRepositoryInfo::CData::~CData()
{
    Clear();
}

// lookup (using current rules setting);
// pass empty strings for unknown values.

CString CRepositoryInfo::CData::FindRoot(const CString& uuid, const CString& url) const
{
    // lookup by UUID, if allowed
    // or if url is already a root

    CRepositoryInfo::SPerRepositoryInfo* info = Lookup(uuid, url);
    if (info != nullptr)
        return info->root;

    // UUID missing?

    if (uuid.IsEmpty())
    {
        // URLs must be unique

        return CSettings::GetAllowAmbiguousURL()
                   ? CString()
                   : FindRoot(urlIndex.begin(), urlIndex.lower_bound(url), url);
    }
    else
    {
        // if URL is empty here, then lookup by UUID alone was not successful
        // -> it will fail here as well

        return FindRoot(uuidIndex.lower_bound(uuid), uuidIndex.upper_bound(uuid), url);
    }
}

CRepositoryInfo::SPerRepositoryInfo*
    CRepositoryInfo::CData::Lookup(const CString& uuid, const CString& root) const
{
    // the full index will only match if uuid and url are both given.
    // That repo info will be valid even if ambiguities are not allowed.

    TFullIndex::const_iterator iter = fullIndex.find(std::make_pair(uuid, root));
    if (iter != fullIndex.end())
        return iter->second;

    // try an URL-agnostic lookup, if that is allowed
    // (will automatically fail if the the UUID should not be given)

    if (!CSettings::GetAllowAmbiguousUUID())
    {
        TPartialIndex::const_iterator iter2 = uuidIndex.find(uuid);
        if (iter2 != uuidIndex.end())
            return iter2->second;
    }

    // try an UUID-agnostic lookup, if that is allowed
    // (will automatically fail if the the URL should not be given)

    if (!CSettings::GetAllowAmbiguousURL())
    {
        TPartialIndex::const_iterator iter2 = urlIndex.find(root);
        if (iter2 != urlIndex.end())
            return iter2->second;
    }

    // not found

    return nullptr;
}

// modification

CRepositoryInfo::SPerRepositoryInfo*
    CRepositoryInfo::CData::AutoInsert(const CString& uuid, const CString& root)
{
    // do we already have a suitable entry?

    CRepositoryInfo::SPerRepositoryInfo* result = Lookup(uuid, root);
    if (result != nullptr)
        return result;

    // no -> add one & return it

    Add(uuid, root);
    return Lookup(uuid, root);
}

void CRepositoryInfo::CData::Add(const SPerRepositoryInfo& info)
{
    SPerRepositoryInfo* newInfo = new SPerRepositoryInfo(info);
    data.push_back(newInfo);

    urlIndex.emplace(newInfo->root, newInfo);
    uuidIndex.emplace(newInfo->uuid, newInfo);
    fullIndex.emplace(std::make_pair(newInfo->uuid, newInfo->root), newInfo);
}

void CRepositoryInfo::CData::Add(const CString& uuid, const CString& root)
{
    SPerRepositoryInfo info;
    info.headRevision    = static_cast<revision_t>(NO_REVISION);
    info.headLookupTime  = -1;
    info.connectionState = online;
    info.root            = root;
    info.uuid            = uuid;
    info.fileName        = UniqueFileName(info.root.Left(60) + info.uuid);

    Add(info);
}

void CRepositoryInfo::CData::Remove(SPerRepositoryInfo* info)
{
    // remove info from the list

    TData::iterator iter = std::find(data.begin(), data.end(), info);
    assert(iter != data.end());

    *iter = data.back();
    data.pop_back();

    // remove it from the indices

    for (TPartialIndex::iterator iter2 = urlIndex.lower_bound(info->root), end = urlIndex.upper_bound(info->root); iter2 != end; ++iter2)
    {
        if (iter2->second == info)
        {
            urlIndex.erase(iter2);
            break;
        }
    }

    for (TPartialIndex::iterator iter2 = uuidIndex.lower_bound(info->uuid), end = uuidIndex.upper_bound(info->uuid); iter2 != end; ++iter2)
    {
        if (iter2->second == info)
        {
            uuidIndex.erase(iter2);
            break;
        }
    }

    fullIndex.erase(std::make_pair(info->uuid, info->root));

    // finally, free the memory

    delete info;
}

// read / write file

void CRepositoryInfo::CData::Load(const CString& fileName)
{
    CFile file;
    if (!file.Open(fileName, CFile::modeRead | CFile::shareDenyWrite))
        return;

    try
    {
        CArchive stream(&file, CArchive::load);

        // format ID

        int version = 0;
        stream >> version;

        // ignore newer formats

        if (version > VERSION)
            return;

        // number of entries to read
        // (old file don't have a version info -> "version" is the count)

        int count = 0;
        if (version >= MIN_COMPATIBLE_VERSION)
            stream >> count;
        else
            count = version;

        // actually load the data

        for (int i = 0; i < count; ++i)
        {
            int connectionState = online;

            SPerRepositoryInfo info;
            stream >> info.root >> info.uuid >> info.headURL >> info.headRevision >> info.headLookupTime;

            if (version >= MIN_COMPATIBLE_VERSION)
                stream >> connectionState;

            info.connectionState = static_cast<ConnectionState>(connectionState);

            if (version >= MIN_FILENAME_VERSION)
                stream >> info.fileName;
            else
                info.fileName = info.uuid;

            // caches from 1.5.x may have a number of alias entries
            // -> use at most one

            if ((version >= MIN_FILENAME_VERSION) || (uuidIndex.find(info.uuid) == uuidIndex.end()))
            {
                Add(info);
            }
        }
    }
    catch (...)
    {
        return;
    }
}

void CRepositoryInfo::CData::Save(const CString& fileName) const
{
    CFile    file(fileName, CFile::modeWrite | CFile::modeCreate);
    CArchive stream(&file, CArchive::store);

    stream << static_cast<int>(VERSION);
    stream << static_cast<int>(data.size());

    for (size_t i = 0, count = data.size(); i < count; ++i)
    {
        SPerRepositoryInfo* info = data[i];

        // temp offline -> be online the next time

        ConnectionState connectionState = static_cast<ConnectionState>(info->connectionState & offline);

        stream << info->root
               << info->uuid
               << info->headURL
               << info->headRevision
               << info->headLookupTime
               << connectionState
               << info->fileName;
    }
}

void CRepositoryInfo::CData::Clear()
{
    uuidIndex.clear();
    urlIndex.clear();
    fullIndex.clear();

    for (size_t i = 0, count = data.size(); i < count; ++i)
        delete data[i];

    data.clear();
}

// status info

bool CRepositoryInfo::CData::empty() const
{
    return data.empty();
}

// data access

size_t CRepositoryInfo::CData::size() const
{
    return data.size();
}

const CRepositoryInfo::SPerRepositoryInfo*
    CRepositoryInfo::CData::operator[](size_t index) const
{
    return data[index];
}

// share the repository info pool throughout this application
// (it is unique per computer anyway)

CRepositoryInfo::CData CRepositoryInfo::data;

// construct the dump file name

CString CRepositoryInfo::GetFileName() const
{
    return cacheFolder + L"Repositories.dat";
}

// used to sync access to the global "data"

async::CCriticalSection& CRepositoryInfo::GetDataMutex()
{
    static async::CCriticalSection mutex;
    return mutex;
}

// read the dump file

void CRepositoryInfo::Load()
{
    modified = false;

    // any cached info at all?

    if (GetFileAttributes(GetFileName()) == INVALID_FILE_ATTRIBUTES)
        return;

    data.Load(GetFileName());
}

// does the user want to be this repository off-line?

bool CRepositoryInfo::IsOffline(SPerRepositoryInfo* info, const CString& sErr, bool& doRetry) const
{
    // is this repository already off-line?

    if (info->connectionState != online)
        return true;

    // something went wrong.

    if ((CSettings::GetDefaultConnectionState() == online) && !svn.IsSuppressedUI())
    {
        // Default behavior is "Ask the user what to do"

        CGoOffline dialog;
        dialog.SetErrorMessage(sErr);
        dialog.DoModal();
        doRetry = dialog.doRetry;
        if (dialog.asDefault)
            CSettings::SetDefaultConnectionState(dialog.selection);

        info->connectionState = dialog.selection;
        return info->connectionState != online;
    }
    else
    {
        // set default

        info->connectionState = CSettings::GetDefaultConnectionState();
        return true;
    }
}

// try to get the HEAD revision from the log cache

void CRepositoryInfo::SetHeadFromCache(SPerRepositoryInfo* info)
{
    SVN             svn;
    CCachedLogInfo* cache = svn.GetLogCachePool()->GetCache(info->uuid, info->root);
    info->headRevision    = cache != nullptr
                                ? cache->GetRevisions().GetLastCachedRevision() - 1
                                : NO_REVISION;

    // HEAD info is outdated

    info->headLookupTime = 0;
}

// construction / destruction: auto-load and save

CRepositoryInfo::CRepositoryInfo(SVN& svn, const CString& cacheFolderPath)
    : modified(false)
    , cacheFolder(cacheFolderPath)
    , svn(svn)
{
    // load the list only if the URL->UUID,head etc. mapping cache shall be used

    async::CCriticalSectionLock lock(GetDataMutex());

    if (data.empty())
        Load();
}

CRepositoryInfo::~CRepositoryInfo()
{
}

// look-up and ask SVN if the info is not in cache.
// cache the result.

CString CRepositoryInfo::GetRepositoryRoot(const CTSVNPath& url) const
{
    CString uuid;
    return GetRepositoryRootAndUUID(url, uuid);
}

CString CRepositoryInfo::GetRepositoryUUID(const CTSVNPath& url) const
{
    CString uuid;
    GetRepositoryRootAndUUID(url, uuid);
    return uuid;
}

CString CRepositoryInfo::GetRepositoryRootAndUUID(const CTSVNPath& url, CString& uuid) const
{
    // reset (potentially) outdated info to prevent it from being
    // the basis for FindRoot / Lookup.

    uuid.Empty();

    async::CCriticalSectionLock lock(GetDataMutex());

    CString             sURL = url.GetSVNPathString();
    CString             root = data.FindRoot(uuid, sURL);
    SPerRepositoryInfo* info = root.IsEmpty()
                                   ? nullptr
                                   : data.Lookup(uuid, root);

    // complete data, if lookup failed with the provided data

    if (info == nullptr)
    {
        root = svn.GetRepositoryRootAndUUID(url, false, uuid);
        info = data.Lookup(uuid, root);
    }
    else
    {
        uuid = info->uuid;
    }

    // add new cache entry if none is available, yet

    if ((svn.GetSVNError() == nullptr) && (info == nullptr))
        data.Add(uuid, root);

    // done

    return root;
}

revision_t CRepositoryInfo::GetHeadRevision(CString uuid, const CTSVNPath& url)
{
    async::CCriticalSectionLock lock(GetDataMutex());

    // get the entry for that repository

    CString             sURL = url.GetSVNPathString();
    CString             root = GetRepositoryRootAndUUID(url, uuid);
    SPerRepositoryInfo* info = data.Lookup(uuid, root);

    if (info == nullptr)
    {
        // there was some problem connecting to the repository

        return static_cast<revision_t>(NO_REVISION);
    }

    // get time stamps and maximum head info age (default: 0 mins)

    __time64_t now = CTime::GetCurrentTime().GetTime();

    // is the current info outdated?
    // (and don't try to update the info if we are off-line,
    //  as long as we have at least *some* HEAD info).

    bool outDated = info->connectionState == online
                        ? now - info->headLookupTime > CSettings::GetMaxHeadAge()
                        : false;

    // is there a valid cached entry?

    if (outDated || !IsParentDirectory(info->headURL, sURL) || (info->headRevision == NO_REVISION))
    {
        // entry outdated or for not valid for this path

        info->headLookupTime = now;
        info->headURL        = sURL;
        bool doRetry         = false;
        do
        {
            doRetry            = false;
            info->headRevision = svn.GetHEADRevision(url, false);

            // if we couldn't connect to the server, ask the user

            bool cancelled = svn.GetSVNError() && (svn.GetSVNError()->apr_err == SVN_ERR_CANCELLED);

            if (!svn.IsSuppressedUI() && !cancelled && (info->headRevision == NO_REVISION) && IsOffline(info, svn.GetLastErrorMessage(), doRetry))
            {
                // user wants to go off-line

                SetHeadFromCache(info);

                // we just ignore our latest error

                svn.ClearSVNError();
            }
        } while (doRetry);

        modified = true;
    }

    // ready

    return info->headRevision;
}

// make sure, we will ask the repository for the HEAD

void CRepositoryInfo::ResetHeadRevision(const CString& uuid, const CString& root)
{
    async::CCriticalSectionLock lock(GetDataMutex());

    // get the entry for that repository

    SPerRepositoryInfo* info = data.Lookup(uuid, root);
    if (info != nullptr)
    {
        // there is actually a cache for this URL.
        // Invalidate the HEAD info and make sure we will
        // connect the server for an update the next time
        // we want to get connect.

        info->headLookupTime  = 0;
        info->connectionState = online;
    }
}

// is the repository offline?
// Don't modify the state if autoSet is false.

bool CRepositoryInfo::IsOffline(const CString& uuid, const CString& root, bool autoSet, const CString& sErr, bool& doRetry) const
{
    async::CCriticalSectionLock lock(GetDataMutex());

    // find the info

    SPerRepositoryInfo* info = data.Lookup(uuid, root);

    // no info -> assume online (i.e. just try to reach the server)

    if (info == nullptr)
        return false;

    // update the online/offline state by contacting the user?
    // (the dialog will only be shown if online and no
    // offline-defaults have been set)

    if (autoSet)
        IsOffline(info, sErr, doRetry);

    // return state

    return info->connectionState != online;
}

// get the connection state (uninterpreted)

ConnectionState
    CRepositoryInfo::GetConnectionState(const CString& uuid, const CString& url)
{
    async::CCriticalSectionLock lock(GetDataMutex());

    // find the info

    SPerRepositoryInfo* info = data.Lookup(uuid, url);

    // no info -> assume online (i.e. just try to reach the server)

    return info == nullptr
               ? online
               : info->connectionState;
}

// remove a specific entry

void CRepositoryInfo::DropEntry(CString uuid, CString url)
{
    async::CCriticalSectionLock lock(GetDataMutex());

    for (SPerRepositoryInfo* info = data.Lookup(uuid, url); info != nullptr; info = data.Lookup(uuid, url))
    {
        data.Remove(info);
        modified = true;
    }
}

// write all changes to disk

void CRepositoryInfo::Flush()
{
    if (!modified)
        return;

    async::CCriticalSectionLock lock(GetDataMutex());

    CString fileName = GetFileName();
    CPathUtils::MakeSureDirectoryPathExists(fileName.Left(fileName.ReverseFind('\\')));
    try
    {
        data.Save(fileName);
        modified = false;
    }
    catch (CException* e)
    {
        e->Delete();
    }
}

// clear cache

void CRepositoryInfo::Clear()
{
    async::CCriticalSectionLock lock(GetDataMutex());

    data.Clear();
}

// get the owning SVN instance

SVN& CRepositoryInfo::GetSVN() const
{
    return svn;
}

// access to the result of the last SVN operation

const svn_error_t* CRepositoryInfo::GetLastError() const
{
    return svn.GetSVNError();
}

// end namespace LogCache

} // namespace LogCache
Afficher sur ancien navigateur de dépôt.