Commit 7003b82e authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Version control: Improve detection.

Merge managesDirectory() and findTopLevelForDirectory()
into one giving managesDirectory() an optional topLevel
parameter. This removes the need to go up the directory
hierarchy twice when checking for Merurial or git and also
saves some checks for CVS/Subversion.
VCSManager: Check cache in reverse order starting out with
the full path first to improve handling of nested repositories.
Rubber-stamped-by: con
Acked-by: dt
parent 58a5da63
......@@ -56,21 +56,12 @@ public:
/*!
* Returns whether files in this directory should be managed with this
* version control.
* If \a topLevel is non-null, it should return the topmost directory,
* for which this IVersionControl should be used. The VCSManager assumes
* that all files in the returned directory are managed by the same IVersionControl.
*/
virtual bool managesDirectory(const QString &filename) const = 0;
/*!
* This function should return the topmost directory, for which this
* IVersionControl should be used. The VCSManager assumes that all files in
* the returned directory are managed by the same IVersionControl.
*
* Note that this is used as an optimization, so that the VCSManager
* doesn't need to call managesDirectory(..) for each directory.
*
* This function is called after finding out that the directory is managed
* by a specific version control.
*/
virtual QString findTopLevelForDirectory(const QString &directory) const = 0;
virtual bool managesDirectory(const QString &filename, QString *topLevel = 0) const = 0;
/*!
* Called to query whether a VCS supports the respective operations.
......
......@@ -90,6 +90,18 @@ IVersionControl* VCSManager::findVersionControlForDirectory(const QString &direc
QString *topLevelDirectory)
{
typedef VersionControlCache::const_iterator VersionControlCacheConstIterator;
if (debug) {
qDebug(">findVersionControlForDirectory %s topLevelPtr %d",
qPrintable(directory), (topLevelDirectory != 0));
if (debug > 1) {
const VersionControlCacheConstIterator cend = m_d->m_cachedMatches.constEnd();
for (VersionControlCacheConstIterator it = m_d->m_cachedMatches.constBegin(); it != cend; ++it)
qDebug("Cache %s -> '%s'", qPrintable(it.key()), qPrintable(it.value()->displayName()));
}
}
QTC_ASSERT(!directory.isEmpty(), return 0);
const VersionControlCacheConstIterator cacheEnd = m_d->m_cachedMatches.constEnd();
if (topLevelDirectory)
......@@ -100,37 +112,51 @@ IVersionControl* VCSManager::findVersionControlForDirectory(const QString &direc
if (fullPathIt != cacheEnd) {
if (topLevelDirectory)
*topLevelDirectory = directory;
if (debug)
qDebug("<findVersionControlForDirectory: full cache match for VCS '%s'", qPrintable(fullPathIt.value()->displayName()));
return fullPathIt.value();
}
// Split the path, starting from top, try to find the matching repository
int pos = 0;
// Split the path, trying to find the matching repository. We start from the reverse
// in order to detected nested repositories correctly (say, a git checkout under SVN).
// Note that detection of a nested version control will still fail if the
// above-located version control is detected and entered into the cache first.
// The nested one can then no longer be found due to the splitting of the paths.
int pos = directory.size() - 1;
const QChar slash = QLatin1Char('/');
while (true) {
const int index = directory.indexOf(slash, pos);
if (index == -1)
const int index = directory.lastIndexOf(slash, pos);
if (index <= 0) // Stop at '/' or not found
break;
const QString directoryPart = directory.left(index);
const VersionControlCacheConstIterator it = m_d->m_cachedMatches.constFind(directoryPart);
if (it != cacheEnd) {
if (topLevelDirectory)
*topLevelDirectory = it.key();
if (debug)
qDebug("<findVersionControlForDirectory: cache match for VCS '%s', topLevel: %s",
qPrintable(it.value()->displayName()), qPrintable(it.key()));
return it.value();
}
pos = index + 1;
pos = index - 1;
}
// Nothing: ask the IVersionControls directly, insert the toplevel into the cache.
const VersionControlList versionControls = allVersionControls();
foreach (IVersionControl * versionControl, versionControls) {
if (versionControl->managesDirectory(directory)) {
const QString topLevel = versionControl->findTopLevelForDirectory(directory);
QString topLevel;
if (versionControl->managesDirectory(directory, &topLevel)) {
m_d->m_cachedMatches.insert(topLevel, versionControl);
if (topLevelDirectory)
*topLevelDirectory = topLevel;
if (debug)
qDebug("<findVersionControlForDirectory: invocation of '%s' matches: %s",
qPrintable(versionControl->displayName()), qPrintable(topLevel));
return versionControl;
}
}
if (debug)
qDebug("<findVersionControlForDirectory: No match for %s", qPrintable(directory));
return 0;
}
......
......@@ -120,14 +120,9 @@ bool CVSControl::vcsAnnotate(const QString &file, int line)
return true;
}
bool CVSControl::managesDirectory(const QString &directory) const
bool CVSControl::managesDirectory(const QString &directory, QString *topLevel) const
{
return m_plugin->managesDirectory(directory);
}
QString CVSControl::findTopLevelForDirectory(const QString &directory) const
{
return m_plugin->findTopLevelForDirectory(directory);
return m_plugin->managesDirectory(directory, topLevel);
}
void CVSControl::emitRepositoryChanged(const QString &s)
......
......@@ -45,8 +45,7 @@ public:
explicit CVSControl(CVSPlugin *plugin);
virtual QString displayName() const;
virtual bool managesDirectory(const QString &directory) const;
virtual QString findTopLevelForDirectory(const QString &directory) const;
virtual bool managesDirectory(const QString &directory, QString *topLevel = 0) const;
virtual bool supportsOperation(Operation operation) const;
virtual bool vcsOpen(const QString &fileName);
......
......@@ -827,8 +827,10 @@ void CVSPlugin::slotDescribe(const QString &source, const QString &changeNr)
bool CVSPlugin::describe(const QString &file, const QString &changeNr, QString *errorMessage)
{
const QString toplevel = findTopLevelForDirectory(QFileInfo(file).absolutePath());
if (toplevel.isEmpty()) {
QString toplevel;
const bool manages = managesDirectory(QFileInfo(file).absolutePath(), &toplevel);
if (!manages || toplevel.isEmpty()) {
*errorMessage = msgCannotFindTopLevel(file);
return false;
}
......@@ -1122,45 +1124,44 @@ bool CVSPlugin::vcsDelete(const QString &workingDir, const QString &rawFileName)
/* CVS has a "CVS" directory in each directory it manages. The top level
* is the first directory under the directory that does not have it. */
bool CVSPlugin::managesDirectory(const QString &directory) const
bool CVSPlugin::managesDirectory(const QString &directory, QString *topLevel /* = 0 */) const
{
if (topLevel)
topLevel->clear();
bool manages = false;
const QDir dir(directory);
const bool rc = dir.exists() && managesDirectory(dir);
if (CVS::Constants::debug)
qDebug() << "CVSPlugin::managesDirectory" << directory << rc;
return rc;
do {
if (!dir.exists() || !checkCVSDirectory(dir))
break;
manages = true;
if (!topLevel)
break;
/* Recursing up, the top level is a child of the first directory that does
* not have a "CVS" directory. The starting directory must be a managed
* one. Go up and try to find the first unmanaged parent dir. */
QDir lastDirectory = dir;
for (QDir parentDir = lastDirectory; parentDir.cdUp() ; lastDirectory = parentDir) {
if (!checkCVSDirectory(parentDir)) {
*topLevel = lastDirectory.absolutePath();
break;
}
}
} while (false);
if (CVS::Constants::debug) {
QDebug nsp = qDebug().nospace();
nsp << "CVSPlugin::managesDirectory" << directory << manages;
if (topLevel)
nsp << *topLevel;
}
return manages;
}
bool CVSPlugin::managesDirectory(const QDir &directory) const
bool CVSPlugin::checkCVSDirectory(const QDir &directory) const
{
const QString cvsDir = directory.absoluteFilePath(QLatin1String("CVS"));
return QFileInfo(cvsDir).isDir();
}
QString CVSPlugin::findTopLevelForDirectory(const QString &directory) const
{
// Debug wrapper
const QString rc = findTopLevelForDirectoryI(directory);
if (CVS::Constants::debug)
qDebug() << "CVSPlugin::findTopLevelForDirectory" << directory << rc;
return rc;
}
QString CVSPlugin::findTopLevelForDirectoryI(const QString &directory) const
{
/* Recursing up, the top level is a child of the first directory that does
* not have a "CVS" directory. The starting directory must be a managed
* one. Go up and try to find the first unmanaged parent dir. */
QDir lastDirectory = QDir(directory);
if (!lastDirectory.exists() || !managesDirectory(lastDirectory))
return QString();
for (QDir parentDir = lastDirectory; parentDir.cdUp() ; lastDirectory = parentDir) {
if (!managesDirectory(parentDir))
return lastDirectory.absolutePath();
}
return QString();
}
CVSControl *CVSPlugin::cvsVersionControl() const
{
return static_cast<CVSControl *>(versionControl());
......
......@@ -95,8 +95,7 @@ public:
// IVersionControl
bool vcsAdd(const QString &workingDir, const QString &fileName);
bool vcsDelete(const QString &workingDir, const QString &fileName);
bool managesDirectory(const QString &directory) const;
QString findTopLevelForDirectory(const QString &directory) const;
bool managesDirectory(const QString &directory, QString *topLevel = 0) const;
static CVSPlugin *cvsPluginInstance();
......@@ -145,7 +144,7 @@ private:
void filelog(const QString &workingDir,
const QStringList &files = QStringList(),
bool enableAnnotationContextMenu = false);
bool managesDirectory(const QDir &directory) const;
bool checkCVSDirectory(const QDir &directory) const;
QString findTopLevelForDirectoryI(const QString &directory) const;
void startCommit(const QString &workingDir, const QStringList &files = QStringList());
bool commit(const QString &messageFile, const QStringList &subVersionFileList);
......
......@@ -206,14 +206,12 @@ bool GitVersionControl::vcsRemoveSnapshot(const QString &topLevel, const QString
&& gitClient()->synchronousStashRemove(topLevel, stashName);
}
bool GitVersionControl::managesDirectory(const QString &directory) const
bool GitVersionControl::managesDirectory(const QString &directory, QString *topLevel) const
{
return !GitClient::findRepositoryForDirectory(directory).isEmpty();
}
QString GitVersionControl::findTopLevelForDirectory(const QString &directory) const
{
return GitClient::findRepositoryForDirectory(directory);
const QString topLevelFound = GitClient::findRepositoryForDirectory(directory);
if (topLevel)
*topLevel = topLevelFound;
return !topLevelFound.isEmpty();
}
bool GitVersionControl::vcsAnnotate(const QString &file, int line)
......
......@@ -46,8 +46,7 @@ public:
virtual QString displayName() const;
bool managesDirectory(const QString &directory) const;
virtual QString findTopLevelForDirectory(const QString &directory) const;
virtual bool managesDirectory(const QString &directory, QString *topLevel) const;
virtual bool supportsOperation(Operation operation) const;
virtual bool vcsOpen(const QString &fileName);
......
......@@ -47,16 +47,13 @@ QString MercurialControl::displayName() const
return tr("Mercurial");
}
bool MercurialControl::managesDirectory(const QString &directory) const
bool MercurialControl::managesDirectory(const QString &directory, QString *topLevel) const
{
QFileInfo dir(directory);
return !mercurialClient->findTopLevelForFile(dir).isEmpty();
}
QString MercurialControl::findTopLevelForDirectory(const QString &directory) const
{
QFileInfo dir(directory);
return mercurialClient->findTopLevelForFile(dir);
const QString topLevelFound = mercurialClient->findTopLevelForFile(dir);
if (topLevel)
*topLevel = topLevelFound;
return !topLevelFound.isEmpty();
}
bool MercurialControl::supportsOperation(Operation operation) const
......@@ -137,8 +134,9 @@ bool MercurialControl::vcsAnnotate(const QString &file, int line)
bool MercurialControl::sccManaged(const QString &filename)
{
const QFileInfo fi(filename);
const QString topLevel = findTopLevelForDirectory(fi.absolutePath());
if (topLevel.isEmpty())
QString topLevel;
const bool managed = managesDirectory(fi.absolutePath(), &topLevel);
if (!managed || topLevel.isEmpty())
return false;
const QDir topLevelDir(topLevel);
return mercurialClient->manifestSync(topLevel, topLevelDir.relativeFilePath(filename));
......
......@@ -50,8 +50,7 @@ public:
explicit MercurialControl(MercurialClient *mercurialClient);
QString displayName() const;
bool managesDirectory(const QString &filename) const;
QString findTopLevelForDirectory(const QString &directory) const;
bool managesDirectory(const QString &filename, QString *topLevel = 0) const;
bool supportsOperation(Operation operation) const;
bool vcsOpen(const QString &fileName);
bool vcsAdd(const QString &filename);
......
......@@ -848,7 +848,20 @@ void PerforcePlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
m_updateAllAction->setEnabled(true);
}
bool PerforcePlugin::managesDirectory(const QString &directory)
bool PerforcePlugin::managesDirectory(const QString &directory, QString *topLevel /* = 0 */)
{
const bool rc = managesDirectoryFstat(directory);
if (topLevel) {
if (rc) {
*topLevel = m_settings.topLevelSymLinkTarget();
} else {
topLevel->clear();
}
}
return rc;
}
bool PerforcePlugin::managesDirectoryFstat(const QString &directory)
{
if (!m_settings.isValid())
return false;
......@@ -875,13 +888,6 @@ bool PerforcePlugin::managesDirectory(const QString &directory)
return managed;
}
QString PerforcePlugin::findTopLevelForDirectory(const QString &dir)
{
if (!m_settings.isValid())
return QString();
return managesDirectory(dir) ? m_settings.topLevelSymLinkTarget() : QString();
}
bool PerforcePlugin::vcsOpen(const QString &workingDir, const QString &fileName)
{
if (Perforce::Constants::debug)
......
......@@ -84,8 +84,7 @@ public:
bool initialize(const QStringList &arguments, QString *error_message);
void extensionsInitialized();
bool managesDirectory(const QString &directory);
QString findTopLevelForDirectory(const QString &directory);
bool managesDirectory(const QString &directory, QString *topLevel = 0);
bool vcsOpen(const QString &workingDir, const QString &fileName);
bool vcsAdd(const QString &workingDir, const QString &fileName);
bool vcsDelete(const QString &workingDir, const QString &filename);
......@@ -194,6 +193,7 @@ private:
void updateCheckout(const QString &workingDir = QString(),
const QStringList &dirs = QStringList());
bool revertProject(const QString &workingDir, const QStringList &args, bool unchangedOnly);
bool managesDirectoryFstat(const QString &directory);
inline PerforceVersionControl *perforceVersionControl() const;
......
......@@ -120,19 +120,15 @@ bool PerforceVersionControl::vcsAnnotate(const QString &file, int line)
return true;
}
bool PerforceVersionControl::managesDirectory(const QString &directory) const
{
const bool rc = m_plugin->managesDirectory(directory);
if (Perforce::Constants::debug)
qDebug() << "managesDirectory" << directory << rc;
return rc;
}
QString PerforceVersionControl::findTopLevelForDirectory(const QString &directory) const
{
const QString rc = m_plugin->findTopLevelForDirectory(directory);
if (Perforce::Constants::debug)
qDebug() << "findTopLevelForDirectory" << directory << rc;
bool PerforceVersionControl::managesDirectory(const QString &directory, QString *topLevel) const
{
const bool rc = m_plugin->managesDirectory(directory, topLevel);
if (Perforce::Constants::debug) {
QDebug nsp = qDebug().nospace();
nsp << "managesDirectory" << directory << rc;
if (topLevel)
nsp << topLevel;
}
return rc;
}
......
......@@ -45,8 +45,8 @@ public:
virtual QString displayName() const;
bool managesDirectory(const QString &directory) const;
virtual QString findTopLevelForDirectory(const QString &directory) const;
virtual bool managesDirectory(const QString &directory, QString *topLevel = 0) const;
virtual bool supportsOperation(Operation operation) const;
virtual bool vcsOpen(const QString &fileName);
......
......@@ -114,14 +114,9 @@ bool SubversionControl::vcsRemoveSnapshot(const QString &, const QString &)
return false;
}
bool SubversionControl::managesDirectory(const QString &directory) const
bool SubversionControl::managesDirectory(const QString &directory, QString *topLevel) const
{
return m_plugin->managesDirectory(directory);
}
QString SubversionControl::findTopLevelForDirectory(const QString &directory) const
{
return m_plugin->findTopLevelForDirectory(directory);
return m_plugin->managesDirectory(directory, topLevel);
}
bool SubversionControl::vcsAnnotate(const QString &file, int line)
......
......@@ -45,8 +45,7 @@ public:
explicit SubversionControl(SubversionPlugin *plugin);
virtual QString displayName() const;
virtual bool managesDirectory(const QString &directory) const;
virtual QString findTopLevelForDirectory(const QString &directory) const;
virtual bool managesDirectory(const QString &directory, QString *topLevel = 0) const;
virtual bool supportsOperation(Operation operation) const;
virtual bool vcsOpen(const QString &fileName);
......
......@@ -940,8 +940,9 @@ void SubversionPlugin::describe(const QString &source, const QString &changeNr)
// To describe a complete change, find the top level and then do
//svn diff -r 472958:472959 <top level>
const QFileInfo fi(source);
const QString topLevel = findTopLevelForDirectory(fi.isDir() ? source : fi.absolutePath());
if (topLevel.isEmpty())
QString topLevel;
const bool manages = managesDirectory(fi.isDir() ? source : fi.absolutePath(), &topLevel);
if (!manages || topLevel.isEmpty())
return;
if (Subversion::Constants::debug)
qDebug() << Q_FUNC_INFO << source << topLevel << changeNr;
......@@ -1184,7 +1185,7 @@ bool SubversionPlugin::vcsAdd14(const QString &workingDir, const QString &rawFil
if (!path.isEmpty())
path += slash;
path += relativePath.at(p);
if (!managesDirectory(QDir(path))) {
if (!checkSVNSubDir(QDir(path))) {
QStringList addDirArgs;
addDirArgs << QLatin1String("add") << QLatin1String("--non-recursive") << QDir::toNativeSeparators(path);
const SubversionResponse addDirResponse = runSvn(workingDir, addDirArgs, m_settings.timeOutMS(), true);
......@@ -1224,16 +1225,40 @@ bool SubversionPlugin::vcsMove(const QString &workingDir, const QString &from, c
/* Subversion has ".svn" directory in each directory
* it manages. The top level is the first directory
* under the directory that does not have a ".svn". */
bool SubversionPlugin::managesDirectory(const QString &directory) const
bool SubversionPlugin::managesDirectory(const QString &directory, QString *topLevel /* = 0 */) const
{
const QDir dir(directory);
const bool rc = dir.exists() && managesDirectory(dir);
if (Subversion::Constants::debug)
qDebug() << "SubversionPlugin::managesDirectory" << directory << rc;
return rc;
if (topLevel)
topLevel->clear();
bool manages = false;
do {
if (!dir.exists() || !checkSVNSubDir(dir))
break;
manages = true;
if (!topLevel)
break;
/* Recursing up, the top level is a child of the first directory that does
* not have a ".svn" directory. The starting directory must be a managed
* one. Go up and try to find the first unmanaged parent dir. */
QDir lastDirectory = dir;
for (QDir parentDir = lastDirectory; parentDir.cdUp() ; lastDirectory = parentDir) {
if (!checkSVNSubDir(parentDir)) {
*topLevel = lastDirectory.absolutePath();
break;
}
}
} while (false);
if (Subversion::Constants::debug) {
QDebug nsp = qDebug().nospace();
nsp << "SubversionPlugin::managesDirectory" << directory << manages;
if (topLevel)
nsp << *topLevel;
}
return manages;
}
bool SubversionPlugin::managesDirectory(const QDir &directory) const
// Check whether SVN management subdirs exist.
bool SubversionPlugin::checkSVNSubDir(const QDir &directory) const
{
const int dirCount = m_svnDirectories.size();
for (int i = 0; i < dirCount; i++) {
......@@ -1244,30 +1269,6 @@ bool SubversionPlugin::managesDirectory(const QDir &directory) const
return false;
}
QString SubversionPlugin::findTopLevelForDirectory(const QString &directory) const
{
// Debug wrapper
const QString rc = findTopLevelForDirectoryI(directory);
if (Subversion::Constants::debug)
qDebug() << "SubversionPlugin::findTopLevelForDirectory" << directory << rc;
return rc;
}
QString SubversionPlugin::findTopLevelForDirectoryI(const QString &directory) const
{
/* Recursing up, the top level is a child of the first directory that does
* not have a ".svn" directory. The starting directory must be a managed
* one. Go up and try to find the first unmanaged parent dir. */
QDir lastDirectory = QDir(directory);
if (!lastDirectory.exists() || !managesDirectory(lastDirectory))
return QString();
for (QDir parentDir = lastDirectory; parentDir.cdUp() ; lastDirectory = parentDir) {
if (!managesDirectory(parentDir))
return lastDirectory.absolutePath();
}
return QString();
}
SubversionControl *SubversionPlugin::subVersionControl() const
{
return static_cast<SubversionControl *>(versionControl());
......
......@@ -95,8 +95,7 @@ public:
bool vcsAdd15(const QString &workingDir, const QString &fileName);
bool vcsDelete(const QString &workingDir, const QString &fileName);
bool vcsMove(const QString &workingDir, const QString &from, const QString &to);
bool managesDirectory(const QString &directory) const;
QString findTopLevelForDirectory(const QString &directory) const;
bool managesDirectory(const QString &directory, QString *topLevel = 0) const;
static SubversionPlugin *subversionPluginInstance();
......@@ -145,8 +144,7 @@ private:
bool enableAnnotationContextMenu = false);
void svnStatus(const QString &workingDir, const QStringList &relativePath = QStringList());
void svnUpdate(const QString &workingDir, const QStringList &relativePaths = QStringList());
bool managesDirectory(const QDir &directory) const;
QString findTopLevelForDirectoryI(const QString &directory) const;
bool checkSVNSubDir(const QDir &directory) const;
void startCommit(const QString &workingDir, const QStringList &files = QStringList());
bool commit(const QString &messageFile, const QStringList &subVersionFileList);
void cleanCommitMessageFile();
......
......@@ -821,8 +821,8 @@ QString VCSBaseEditor::findDiffFile(const QString &f, Core::IVersionControl *con
// Try to locate via repository.
if (!control)
return QString();
const QString topLevel = control->findTopLevelForDirectory(sourceDir);
if (topLevel.isEmpty())
QString topLevel;
if (!control->managesDirectory(sourceDir, &topLevel))
return QString();
const QFileInfo topLevelFileInfo(topLevel + slash + f);
if (topLevelFileInfo.isFile())
......
Markdown is supported
0% or .