OSDN Git Service

af16ec78bc67da53264bbe25bbac6ce15788b1a1
[qt-creator-jp/qt-creator-jp.git] / src / plugins / coreplugin / vcsmanager.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "vcsmanager.h"
35 #include "iversioncontrol.h"
36 #include "icore.h"
37 #include "filemanager.h"
38
39 #include <extensionsystem/pluginmanager.h>
40 #include <utils/qtcassert.h>
41
42 #include <QtCore/QString>
43 #include <QtCore/QList>
44 #include <QtCore/QMap>
45 #include <QtCore/QCoreApplication>
46
47 #include <QtCore/QDebug>
48 #include <QtCore/QFileInfo>
49 #include <QtGui/QMessageBox>
50
51 enum { debug = 0 };
52
53 namespace Core {
54
55 typedef QList<IVersionControl *> VersionControlList;
56 typedef QMap<QString, IVersionControl *> VersionControlCache;
57
58 static inline VersionControlList allVersionControls()
59 {
60     return ExtensionSystem::PluginManager::instance()->getObjects<IVersionControl>();
61 }
62
63 // ---- VCSManagerPrivate:
64 // Maintains a cache of top-level directory->version control.
65
66 class VcsManagerPrivate
67 {
68 public:
69     VersionControlCache m_cachedMatches;
70 };
71
72 VcsManager::VcsManager(QObject *parent) :
73    QObject(parent),
74    m_d(new VcsManagerPrivate)
75 {
76 }
77
78 VcsManager::~VcsManager()
79 {
80     delete m_d;
81 }
82
83 void VcsManager::extensionsInitialized()
84 {
85     // Change signal connections
86     FileManager *fileManager = ICore::instance()->fileManager();
87     foreach (IVersionControl *versionControl, allVersionControls()) {
88         connect(versionControl, SIGNAL(filesChanged(QStringList)),
89                 fileManager, SIGNAL(filesChangedInternally(QStringList)));
90         connect(versionControl, SIGNAL(repositoryChanged(QString)),
91                 this, SIGNAL(repositoryChanged(QString)));
92     }
93 }
94
95 static bool longerThanPath(QPair<QString, IVersionControl *> &pair1, QPair<QString, IVersionControl *> &pair2)
96 {
97     return pair1.first.size() > pair2.first.size();
98 }
99
100 IVersionControl* VcsManager::findVersionControlForDirectory(const QString &directory,
101                                                             QString *topLevelDirectory)
102 {
103     typedef VersionControlCache::const_iterator VersionControlCacheConstIterator;
104
105     if (debug) {
106         qDebug(">findVersionControlForDirectory %s topLevelPtr %d",
107                qPrintable(directory), (topLevelDirectory != 0));
108         if (debug > 1) {
109             const VersionControlCacheConstIterator cend = m_d->m_cachedMatches.constEnd();
110             for (VersionControlCacheConstIterator it = m_d->m_cachedMatches.constBegin(); it != cend; ++it)
111                 qDebug("Cache %s -> '%s'", qPrintable(it.key()), qPrintable(it.value()->displayName()));
112         }
113     }
114     QTC_ASSERT(!directory.isEmpty(), return 0);
115
116     const VersionControlCacheConstIterator cacheEnd = m_d->m_cachedMatches.constEnd();
117
118     if (topLevelDirectory)
119         topLevelDirectory->clear();
120
121     // First check if the directory has an entry, meaning it is a top level
122     const VersionControlCacheConstIterator fullPathIt = m_d->m_cachedMatches.constFind(directory);
123     if (fullPathIt != cacheEnd) {
124         if (topLevelDirectory)
125             *topLevelDirectory = directory;
126         if (debug)
127             qDebug("<findVersionControlForDirectory: full cache match for VCS '%s'", qPrintable(fullPathIt.value()->displayName()));
128         return fullPathIt.value();
129     }
130
131     // Split the path, trying to find the matching repository. We start from the reverse
132     // in order to detected nested repositories correctly (say, a git checkout under SVN).
133     // Note that detection of a nested version control will still fail if the
134     // above-located version control is detected and entered into the cache first.
135     // The nested one can then no longer be found due to the splitting of the paths.
136     int pos = directory.size() - 1;
137     const QChar slash = QLatin1Char('/');
138     while (true) {
139         const int index = directory.lastIndexOf(slash, pos);
140         if (index <= 0) // Stop at '/' or not found
141             break;
142         const QString directoryPart = directory.left(index);
143         const VersionControlCacheConstIterator it = m_d->m_cachedMatches.constFind(directoryPart);
144         if (it != cacheEnd) {
145             if (topLevelDirectory)
146                 *topLevelDirectory = it.key();
147             if (debug)
148                 qDebug("<findVersionControlForDirectory: cache match for VCS '%s', topLevel: %s",
149                        qPrintable(it.value()->displayName()), qPrintable(it.key()));
150             return it.value();
151         }
152         pos = index - 1;
153     }
154
155     // Nothing: ask the IVersionControls directly, insert the toplevel into the cache.
156     const VersionControlList versionControls = allVersionControls();
157     QList<QPair<QString, IVersionControl *> > allThatCanManage;
158
159     foreach (IVersionControl * versionControl, versionControls) {
160         QString topLevel;
161         if (versionControl->managesDirectory(directory, &topLevel)) {
162             if (debug)
163                 qDebug("<findVersionControlForDirectory: %s manages %s",
164                        qPrintable(versionControl->displayName()),
165                        qPrintable(topLevel));
166             allThatCanManage.push_back(qMakePair(topLevel, versionControl));
167         }
168     }
169
170     // To properly find a nested repository (say, git checkout inside SVN),
171     // we need to select the version control with the longest toplevel pathname.
172     qSort(allThatCanManage.begin(), allThatCanManage.end(), longerThanPath);
173
174     if (!allThatCanManage.isEmpty()) {
175         QString toplevel = allThatCanManage.first().first;
176         IVersionControl *versionControl = allThatCanManage.first().second;
177         m_d->m_cachedMatches.insert(toplevel, versionControl);
178         if (topLevelDirectory)
179             *topLevelDirectory = toplevel;
180         if (debug)
181             qDebug("<findVersionControlForDirectory: invocation of '%s' matches: %s",
182                    qPrintable(versionControl->displayName()), qPrintable(toplevel));
183         return versionControl;
184     }
185     if (debug)
186         qDebug("<findVersionControlForDirectory: No match for %s", qPrintable(directory));
187     return 0;
188 }
189
190 bool VcsManager::promptToDelete(const QString &fileName)
191 {
192     if (IVersionControl *vc = findVersionControlForDirectory(QFileInfo(fileName).absolutePath()))
193         return promptToDelete(vc, fileName);
194     return true;
195 }
196
197 IVersionControl *VcsManager::checkout(const QString &versionControlType,
198                                       const QString &directory,
199                                       const QByteArray &url)
200 {
201     foreach (IVersionControl *versionControl, allVersionControls()) {
202         if (versionControl->displayName() == versionControlType
203             && versionControl->supportsOperation(Core::IVersionControl::CheckoutOperation)) {
204             if (versionControl->vcsCheckout(directory, url)) {
205                 m_d->m_cachedMatches.insert(directory, versionControl);
206                 return versionControl;
207             }
208             return 0;
209         }
210     }
211     return 0;
212 }
213
214 bool VcsManager::findVersionControl(const QString &versionControlType)
215 {
216     foreach (IVersionControl * versionControl, allVersionControls()) {
217         if (versionControl->displayName() == versionControlType)
218             return true;
219     }
220     return false;
221 }
222
223 QString VcsManager::repositoryUrl(const QString &directory)
224 {
225     IVersionControl *vc = findVersionControlForDirectory(directory);
226
227     if (vc && vc->supportsOperation(Core::IVersionControl::GetRepositoryRootOperation))
228        return vc->vcsGetRepositoryURL(directory);
229     return QString();
230 }
231
232 bool VcsManager::promptToDelete(IVersionControl *vc, const QString &fileName)
233 {
234     QTC_ASSERT(vc, return true)
235     if (!vc->supportsOperation(IVersionControl::DeleteOperation))
236         return true;
237     const QString title = tr("Version Control");
238     const QString msg = tr("Would you like to remove this file from the version control system (%1)?\n"
239                            "Note: This might remove the local file.").arg(vc->displayName());
240     const QMessageBox::StandardButton button =
241         QMessageBox::question(0, title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
242     if (button != QMessageBox::Yes)
243         return true;
244     return vc->vcsDelete(fileName);
245 }
246
247 CORE_EXPORT QDebug operator<<(QDebug in, const VcsManager &v)
248 {
249     QDebug nospace = in.nospace();
250     const VersionControlCache::const_iterator cend = v.m_d->m_cachedMatches.constEnd();
251     for (VersionControlCache::const_iterator it = v.m_d->m_cachedMatches.constBegin(); it != cend; ++it)
252         nospace << "Directory: " << it.key() << ' ' << it.value()->displayName() << '\n';
253     nospace << '\n';
254     return in;
255 }
256
257 } // namespace Core