1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "vcsmanager.h"
34 #include "iversioncontrol.h"
36 #include "filemanager.h"
38 #include <extensionsystem/pluginmanager.h>
39 #include <utils/qtcassert.h>
41 #include <QtCore/QString>
42 #include <QtCore/QList>
43 #include <QtCore/QMap>
44 #include <QtCore/QCoreApplication>
46 #include <QtCore/QDebug>
47 #include <QtCore/QFileInfo>
48 #include <QtGui/QMessageBox>
54 typedef QList<IVersionControl *> VersionControlList;
55 typedef QMap<QString, IVersionControl *> VersionControlCache;
57 static inline VersionControlList allVersionControls()
59 return ExtensionSystem::PluginManager::instance()->getObjects<IVersionControl>();
62 // ---- VCSManagerPrivate:
63 // Maintains a cache of top-level directory->version control.
65 class VcsManagerPrivate
68 VersionControlCache m_cachedMatches;
71 VcsManager::VcsManager(QObject *parent) :
73 m_d(new VcsManagerPrivate)
77 VcsManager::~VcsManager()
82 void VcsManager::extensionsInitialized()
84 // Change signal connections
85 FileManager *fileManager = ICore::instance()->fileManager();
86 foreach (IVersionControl *versionControl, allVersionControls()) {
87 connect(versionControl, SIGNAL(filesChanged(QStringList)),
88 fileManager, SIGNAL(filesChangedInternally(QStringList)));
89 connect(versionControl, SIGNAL(repositoryChanged(QString)),
90 this, SIGNAL(repositoryChanged(QString)));
94 static bool longerThanPath(QPair<QString, IVersionControl *> &pair1, QPair<QString, IVersionControl *> &pair2)
96 return pair1.first.size() > pair2.first.size();
99 IVersionControl* VcsManager::findVersionControlForDirectory(const QString &directory,
100 QString *topLevelDirectory)
102 typedef VersionControlCache::const_iterator VersionControlCacheConstIterator;
105 qDebug(">findVersionControlForDirectory %s topLevelPtr %d",
106 qPrintable(directory), (topLevelDirectory != 0));
108 const VersionControlCacheConstIterator cend = m_d->m_cachedMatches.constEnd();
109 for (VersionControlCacheConstIterator it = m_d->m_cachedMatches.constBegin(); it != cend; ++it)
110 qDebug("Cache %s -> '%s'", qPrintable(it.key()), qPrintable(it.value()->displayName()));
113 QTC_ASSERT(!directory.isEmpty(), return 0);
115 const VersionControlCacheConstIterator cacheEnd = m_d->m_cachedMatches.constEnd();
117 if (topLevelDirectory)
118 topLevelDirectory->clear();
120 // First check if the directory has an entry, meaning it is a top level
121 const VersionControlCacheConstIterator fullPathIt = m_d->m_cachedMatches.constFind(directory);
122 if (fullPathIt != cacheEnd) {
123 if (topLevelDirectory)
124 *topLevelDirectory = directory;
126 qDebug("<findVersionControlForDirectory: full cache match for VCS '%s'", qPrintable(fullPathIt.value()->displayName()));
127 return fullPathIt.value();
130 // Split the path, trying to find the matching repository. We start from the reverse
131 // in order to detected nested repositories correctly (say, a git checkout under SVN).
132 // Note that detection of a nested version control will still fail if the
133 // above-located version control is detected and entered into the cache first.
134 // The nested one can then no longer be found due to the splitting of the paths.
135 int pos = directory.size() - 1;
136 const QChar slash = QLatin1Char('/');
138 const int index = directory.lastIndexOf(slash, pos);
139 if (index <= 0) // Stop at '/' or not found
141 const QString directoryPart = directory.left(index);
142 const VersionControlCacheConstIterator it = m_d->m_cachedMatches.constFind(directoryPart);
143 if (it != cacheEnd) {
144 if (topLevelDirectory)
145 *topLevelDirectory = it.key();
147 qDebug("<findVersionControlForDirectory: cache match for VCS '%s', topLevel: %s",
148 qPrintable(it.value()->displayName()), qPrintable(it.key()));
154 // Nothing: ask the IVersionControls directly, insert the toplevel into the cache.
155 const VersionControlList versionControls = allVersionControls();
156 QList<QPair<QString, IVersionControl *> > allThatCanManage;
158 foreach (IVersionControl * versionControl, versionControls) {
160 if (versionControl->managesDirectory(directory, &topLevel)) {
162 qDebug("<findVersionControlForDirectory: %s manages %s",
163 qPrintable(versionControl->displayName()),
164 qPrintable(topLevel));
165 allThatCanManage.push_back(qMakePair(topLevel, versionControl));
169 // To properly find a nested repository (say, git checkout inside SVN),
170 // we need to select the version control with the longest toplevel pathname.
171 qSort(allThatCanManage.begin(), allThatCanManage.end(), longerThanPath);
173 if (!allThatCanManage.isEmpty()) {
174 QString toplevel = allThatCanManage.first().first;
175 IVersionControl *versionControl = allThatCanManage.first().second;
176 m_d->m_cachedMatches.insert(toplevel, versionControl);
177 if (topLevelDirectory)
178 *topLevelDirectory = toplevel;
180 qDebug("<findVersionControlForDirectory: invocation of '%s' matches: %s",
181 qPrintable(versionControl->displayName()), qPrintable(toplevel));
182 return versionControl;
185 qDebug("<findVersionControlForDirectory: No match for %s", qPrintable(directory));
189 bool VcsManager::promptToDelete(const QString &fileName)
191 if (IVersionControl *vc = findVersionControlForDirectory(QFileInfo(fileName).absolutePath()))
192 return promptToDelete(vc, fileName);
196 IVersionControl *VcsManager::checkout(const QString &versionControlType,
197 const QString &directory,
198 const QByteArray &url)
200 foreach (IVersionControl *versionControl, allVersionControls()) {
201 if (versionControl->displayName() == versionControlType
202 && versionControl->supportsOperation(Core::IVersionControl::CheckoutOperation)) {
203 if (versionControl->vcsCheckout(directory, url)) {
204 m_d->m_cachedMatches.insert(directory, versionControl);
205 return versionControl;
213 bool VcsManager::findVersionControl(const QString &versionControlType)
215 foreach (IVersionControl * versionControl, allVersionControls()) {
216 if (versionControl->displayName() == versionControlType)
222 QString VcsManager::repositoryUrl(const QString &directory)
224 IVersionControl *vc = findVersionControlForDirectory(directory);
226 if (vc && vc->supportsOperation(Core::IVersionControl::GetRepositoryRootOperation))
227 return vc->vcsGetRepositoryURL(directory);
231 bool VcsManager::promptToDelete(IVersionControl *vc, const QString &fileName)
233 QTC_ASSERT(vc, return true)
234 if (!vc->supportsOperation(IVersionControl::DeleteOperation))
236 const QString title = tr("Version Control");
237 const QString msg = tr("Would you like to remove this file from the version control system (%1)?\n"
238 "Note: This might remove the local file.").arg(vc->displayName());
239 const QMessageBox::StandardButton button =
240 QMessageBox::question(0, title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
241 if (button != QMessageBox::Yes)
243 return vc->vcsDelete(fileName);
246 CORE_EXPORT QDebug operator<<(QDebug in, const VcsManager &v)
248 QDebug nospace = in.nospace();
249 const VersionControlCache::const_iterator cend = v.m_d->m_cachedMatches.constEnd();
250 for (VersionControlCache::const_iterator it = v.m_d->m_cachedMatches.constBegin(); it != cend; ++it)
251 nospace << "Directory: " << it.key() << ' ' << it.value()->displayName() << '\n';