OSDN Git Service

Update license.
[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 (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
33 #include "vcsmanager.h"
34 #include "iversioncontrol.h"
35 #include "icore.h"
36 #include "filemanager.h"
37
38 #include <extensionsystem/pluginmanager.h>
39 #include <utils/qtcassert.h>
40
41 #include <QtCore/QString>
42 #include <QtCore/QList>
43 #include <QtCore/QMap>
44 #include <QtCore/QCoreApplication>
45
46 #include <QtCore/QDebug>
47 #include <QtCore/QFileInfo>
48 #include <QtGui/QMessageBox>
49
50 enum { debug = 0 };
51
52 namespace Core {
53
54 typedef QList<IVersionControl *> VersionControlList;
55 typedef QMap<QString, IVersionControl *> VersionControlCache;
56
57 static inline VersionControlList allVersionControls()
58 {
59     return ExtensionSystem::PluginManager::instance()->getObjects<IVersionControl>();
60 }
61
62 // ---- VCSManagerPrivate:
63 // Maintains a cache of top-level directory->version control.
64
65 class VcsManagerPrivate
66 {
67 public:
68     VersionControlCache m_cachedMatches;
69 };
70
71 VcsManager::VcsManager(QObject *parent) :
72    QObject(parent),
73    m_d(new VcsManagerPrivate)
74 {
75 }
76
77 VcsManager::~VcsManager()
78 {
79     delete m_d;
80 }
81
82 void VcsManager::extensionsInitialized()
83 {
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)));
91     }
92 }
93
94 static bool longerThanPath(QPair<QString, IVersionControl *> &pair1, QPair<QString, IVersionControl *> &pair2)
95 {
96     return pair1.first.size() > pair2.first.size();
97 }
98
99 IVersionControl* VcsManager::findVersionControlForDirectory(const QString &directory,
100                                                             QString *topLevelDirectory)
101 {
102     typedef VersionControlCache::const_iterator VersionControlCacheConstIterator;
103
104     if (debug) {
105         qDebug(">findVersionControlForDirectory %s topLevelPtr %d",
106                qPrintable(directory), (topLevelDirectory != 0));
107         if (debug > 1) {
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()));
111         }
112     }
113     QTC_ASSERT(!directory.isEmpty(), return 0);
114
115     const VersionControlCacheConstIterator cacheEnd = m_d->m_cachedMatches.constEnd();
116
117     if (topLevelDirectory)
118         topLevelDirectory->clear();
119
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;
125         if (debug)
126             qDebug("<findVersionControlForDirectory: full cache match for VCS '%s'", qPrintable(fullPathIt.value()->displayName()));
127         return fullPathIt.value();
128     }
129
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('/');
137     while (true) {
138         const int index = directory.lastIndexOf(slash, pos);
139         if (index <= 0) // Stop at '/' or not found
140             break;
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();
146             if (debug)
147                 qDebug("<findVersionControlForDirectory: cache match for VCS '%s', topLevel: %s",
148                        qPrintable(it.value()->displayName()), qPrintable(it.key()));
149             return it.value();
150         }
151         pos = index - 1;
152     }
153
154     // Nothing: ask the IVersionControls directly, insert the toplevel into the cache.
155     const VersionControlList versionControls = allVersionControls();
156     QList<QPair<QString, IVersionControl *> > allThatCanManage;
157
158     foreach (IVersionControl * versionControl, versionControls) {
159         QString topLevel;
160         if (versionControl->managesDirectory(directory, &topLevel)) {
161             if (debug)
162                 qDebug("<findVersionControlForDirectory: %s manages %s",
163                        qPrintable(versionControl->displayName()),
164                        qPrintable(topLevel));
165             allThatCanManage.push_back(qMakePair(topLevel, versionControl));
166         }
167     }
168
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);
172
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;
179         if (debug)
180             qDebug("<findVersionControlForDirectory: invocation of '%s' matches: %s",
181                    qPrintable(versionControl->displayName()), qPrintable(toplevel));
182         return versionControl;
183     }
184     if (debug)
185         qDebug("<findVersionControlForDirectory: No match for %s", qPrintable(directory));
186     return 0;
187 }
188
189 bool VcsManager::promptToDelete(const QString &fileName)
190 {
191     if (IVersionControl *vc = findVersionControlForDirectory(QFileInfo(fileName).absolutePath()))
192         return promptToDelete(vc, fileName);
193     return true;
194 }
195
196 IVersionControl *VcsManager::checkout(const QString &versionControlType,
197                                       const QString &directory,
198                                       const QByteArray &url)
199 {
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;
206             }
207             return 0;
208         }
209     }
210     return 0;
211 }
212
213 bool VcsManager::findVersionControl(const QString &versionControlType)
214 {
215     foreach (IVersionControl * versionControl, allVersionControls()) {
216         if (versionControl->displayName() == versionControlType)
217             return true;
218     }
219     return false;
220 }
221
222 QString VcsManager::repositoryUrl(const QString &directory)
223 {
224     IVersionControl *vc = findVersionControlForDirectory(directory);
225
226     if (vc && vc->supportsOperation(Core::IVersionControl::GetRepositoryRootOperation))
227        return vc->vcsGetRepositoryURL(directory);
228     return QString();
229 }
230
231 bool VcsManager::promptToDelete(IVersionControl *vc, const QString &fileName)
232 {
233     QTC_ASSERT(vc, return true)
234     if (!vc->supportsOperation(IVersionControl::DeleteOperation))
235         return true;
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)
242         return true;
243     return vc->vcsDelete(fileName);
244 }
245
246 CORE_EXPORT QDebug operator<<(QDebug in, const VcsManager &v)
247 {
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';
252     nospace << '\n';
253     return in;
254 }
255
256 } // namespace Core