vcsmanager.cpp 9.04 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
con's avatar
con committed
5
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
con's avatar
con committed
9
** No Commercial Usage
10
**
con's avatar
con committed
11
12
13
14
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
con's avatar
con committed
25
26
27
28
29
30
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
con's avatar
con committed
31
**
32
**************************************************************************/
hjk's avatar
hjk committed
33

con's avatar
con committed
34
35
#include "vcsmanager.h"
#include "iversioncontrol.h"
36
37
#include "icore.h"
#include "filemanager.h"
con's avatar
con committed
38
39

#include <extensionsystem/pluginmanager.h>
40
#include <utils/qtcassert.h>
con's avatar
con committed
41
42
43
44
45
46
47
48
49
50
51

#include <QtCore/QString>
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtCore/QCoreApplication>

#include <QtCore/QFileInfo>
#include <QtGui/QMessageBox>

namespace Core {

52
53
54
55
56
57
58
typedef QList<IVersionControl *> VersionControlList;

static inline VersionControlList allVersionControls()
{
    return ExtensionSystem::PluginManager::instance()->getObjects<IVersionControl>();
}

59
60
static const QChar SLASH('/');

61
62
63
// ---- VCSManagerPrivate:
// Maintains a cache of top-level directory->version control.

hjk's avatar
hjk committed
64
65
66
class VcsManagerPrivate
{
public:
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
    class VcsInfo {
    public:
        VcsInfo(IVersionControl *vc, const QString &tl) :
            versionControl(vc), topLevel(tl)
        { }

        bool operator == (const VcsInfo &other) const
        {
            return versionControl == other.versionControl &&
                    topLevel == other.topLevel;
        }

        IVersionControl *versionControl;
        QString topLevel;
    };

    ~VcsManagerPrivate()
    {
        qDeleteAll(m_vcsInfoList);
    }

    VcsInfo *findInCache(const QString &directory)
    {
        const QMap<QString, VcsInfo *>::const_iterator it = m_cachedMatches.constFind(directory);
        if (it != m_cachedMatches.constEnd())
            return it.value();
        return 0;
    }

    VcsInfo *findUpInCache(const QString &directory)
    {
        VcsInfo *result = 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).
        for (int pos = directory.size() - 1; pos >= 0; pos = directory.lastIndexOf(SLASH, pos) - 1) {
            const QString directoryPart = directory.left(pos);
            result = findInCache(directoryPart);
            if (result != 0)
                break;
        }
        return result;
    }

    void cache(IVersionControl *vc, const QString topLevel, const QString directory)
    {
        Q_ASSERT(directory.startsWith(topLevel));

        VcsInfo *newInfo = new VcsInfo(vc, topLevel);
        bool createdNewInfo(true);
        // Do we have a matching VcsInfo already?
        foreach(VcsInfo *i, m_vcsInfoList) {
            if (*i == *newInfo) {
                delete newInfo;
                newInfo = i;
                createdNewInfo = false;
                break;
            }
        }
        if (createdNewInfo)
            m_vcsInfoList.append(newInfo);

        QString tmpDir = directory;
Tobias Hunger's avatar
Tobias Hunger committed
130
        while (tmpDir.count() >= topLevel.count() && tmpDir.count() > 0) {
131
132
133
134
135
136
137
138
            m_cachedMatches.insert(tmpDir, newInfo);
            int slashPos = tmpDir.lastIndexOf(SLASH);
            tmpDir = slashPos >= 0 ? tmpDir.left(tmpDir.lastIndexOf(SLASH)) : QString();
        }
    }

    QMap<QString, VcsInfo *> m_cachedMatches;
    QList<VcsInfo *> m_vcsInfoList;
con's avatar
con committed
139
140
};

hjk's avatar
hjk committed
141
VcsManager::VcsManager(QObject *parent) :
142
   QObject(parent),
hjk's avatar
hjk committed
143
   m_d(new VcsManagerPrivate)
con's avatar
con committed
144
145
146
{
}

147
148
// ---- VCSManager:

hjk's avatar
hjk committed
149
VcsManager::~VcsManager()
con's avatar
con committed
150
151
152
153
{
    delete m_d;
}

hjk's avatar
hjk committed
154
void VcsManager::extensionsInitialized()
155
156
{
    // Change signal connections
157
    FileManager *fileManager = ICore::instance()->fileManager();
158
159
    foreach (IVersionControl *versionControl, allVersionControls()) {
        connect(versionControl, SIGNAL(filesChanged(QStringList)),
160
                fileManager, SIGNAL(filesChangedInternally(QStringList)));
161
162
163
164
165
        connect(versionControl, SIGNAL(repositoryChanged(QString)),
                this, SIGNAL(repositoryChanged(QString)));
    }
}

Sami Tikka's avatar
Sami Tikka committed
166
167
168
169
170
static bool longerThanPath(QPair<QString, IVersionControl *> &pair1, QPair<QString, IVersionControl *> &pair2)
{
    return pair1.first.size() > pair2.first.size();
}

hjk's avatar
hjk committed
171
IVersionControl* VcsManager::findVersionControlForDirectory(const QString &directory,
172
                                                            QString *topLevelDirectory)
con's avatar
con committed
173
{
174
175
    if (directory.isEmpty())
        return 0;
176

Tobias Hunger's avatar
Tobias Hunger committed
177
    VcsManagerPrivate::VcsInfo *cachedData = m_d->findInCache(directory);
178
    if (cachedData) {
179
        if (topLevelDirectory)
180
181
            *topLevelDirectory = cachedData->topLevel;
        return cachedData->versionControl;
con's avatar
con committed
182
183
    }

184
    // Nothing: ask the IVersionControls directly.
185
    const VersionControlList versionControls = allVersionControls();
Sami Tikka's avatar
Sami Tikka committed
186
187
    QList<QPair<QString, IVersionControl *> > allThatCanManage;

hjk's avatar
hjk committed
188
    foreach (IVersionControl * versionControl, versionControls) {
Tobias Hunger's avatar
Tobias Hunger committed
189
        QString topLevel;
190
        if (versionControl->managesDirectory(directory, &topLevel))
Sami Tikka's avatar
Sami Tikka committed
191
            allThatCanManage.push_back(qMakePair(topLevel, versionControl));
con's avatar
con committed
192
    }
Sami Tikka's avatar
Sami Tikka committed
193
194
195
196
197

    // To properly find a nested repository (say, git checkout inside SVN),
    // we need to select the version control with the longest toplevel pathname.
    qSort(allThatCanManage.begin(), allThatCanManage.end(), longerThanPath);

198
    if (allThatCanManage.isEmpty()) {
Tobias Hunger's avatar
Tobias Hunger committed
199
        m_d->cache(0, QString(), directory); // register that nothing was found!
200
201

        // report result;
Sami Tikka's avatar
Sami Tikka committed
202
        if (topLevelDirectory)
203
204
            topLevelDirectory->clear();
        return 0;
Sami Tikka's avatar
Sami Tikka committed
205
    }
206
207
208
209
210
211
212
213
214
215
216
217
218
219

    // Register Vcs(s) with the cache
    QString tmpDir = directory;
    for (QList<QPair<QString, IVersionControl *> >::const_iterator i = allThatCanManage.constBegin();
         i != allThatCanManage.constEnd(); ++i) {
        m_d->cache(i->second, i->first, tmpDir);
        tmpDir = i->first;
        tmpDir = tmpDir.left(tmpDir.lastIndexOf(SLASH));
    }

    // return result
    if (topLevelDirectory)
        *topLevelDirectory = allThatCanManage.first().first;
    return allThatCanManage.first().second;
con's avatar
con committed
220
221
}

hjk's avatar
hjk committed
222
bool VcsManager::promptToDelete(const QString &fileName)
con's avatar
con committed
223
{
224
225
226
227
228
    if (IVersionControl *vc = findVersionControlForDirectory(QFileInfo(fileName).absolutePath()))
        return promptToDelete(vc, fileName);
    return true;
}

hjk's avatar
hjk committed
229
IVersionControl *VcsManager::checkout(const QString &versionControlType,
230
231
232
                                      const QString &directory,
                                      const QByteArray &url)
{
Tobias Hunger's avatar
Tobias Hunger committed
233
234
235
236
    foreach (IVersionControl *versionControl, allVersionControls()) {
        if (versionControl->displayName() == versionControlType
            && versionControl->supportsOperation(Core::IVersionControl::CheckoutOperation)) {
            if (versionControl->vcsCheckout(directory, url)) {
237
                m_d->cache(versionControl, directory, directory);
238
239
240
241
242
243
244
245
                return versionControl;
            }
            return 0;
        }
    }
    return 0;
}

hjk's avatar
hjk committed
246
bool VcsManager::findVersionControl(const QString &versionControlType)
247
248
{
    foreach (IVersionControl * versionControl, allVersionControls()) {
Tobias Hunger's avatar
Tobias Hunger committed
249
        if (versionControl->displayName() == versionControlType)
250
251
252
253
254
            return true;
    }
    return false;
}

hjk's avatar
hjk committed
255
QString VcsManager::repositoryUrl(const QString &directory)
256
257
258
259
260
261
262
263
{
    IVersionControl *vc = findVersionControlForDirectory(directory);

    if (vc && vc->supportsOperation(Core::IVersionControl::GetRepositoryRootOperation))
       return vc->vcsGetRepositoryURL(directory);
    return QString();
}

hjk's avatar
hjk committed
264
bool VcsManager::promptToDelete(IVersionControl *vc, const QString &fileName)
265
266
267
{
    QTC_ASSERT(vc, return true)
    if (!vc->supportsOperation(IVersionControl::DeleteOperation))
268
        return true;
hjk's avatar
hjk committed
269
270
271
    const QString title = tr("Version Control");
    const QString msg = tr("Would you like to remove this file from the version control system (%1)?\n"
                           "Note: This might remove the local file.").arg(vc->displayName());
con's avatar
con committed
272
273
    const QMessageBox::StandardButton button =
        QMessageBox::question(0, title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
274
275
276
    if (button != QMessageBox::Yes)
        return true;
    return vc->vcsDelete(fileName);
con's avatar
con committed
277
278
}

hjk's avatar
hjk committed
279
} // namespace Core