vcsbaseplugin.cpp 26.7 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
Eike Ziller's avatar
Eike Ziller committed
13
14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
25
26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31
#include "vcsbaseplugin.h"
32
33
#include "vcsbasesubmiteditor.h"
#include "vcsplugin.h"
34
#include "commonvcssettings.h"
35
#include "vcsoutputwindow.h"
36
#include "corelistener.h"
hjk's avatar
hjk committed
37
#include "vcscommand.h"
con's avatar
con committed
38

39
#include <coreplugin/documentmanager.h>
con's avatar
con committed
40
#include <coreplugin/icore.h>
41
#include <coreplugin/id.h>
42
#include <coreplugin/iversioncontrol.h>
43
#include <coreplugin/editormanager/editormanager.h>
44
45
46
#include <coreplugin/vcsmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/project.h>
47
#include <projectexplorer/session.h>
48
#include <utils/qtcassert.h>
49
#include <utils/synchronousprocess.h>
con's avatar
con committed
50

51
52
53
54
55
56
57
#include <QDebug>
#include <QDir>
#include <QSharedData>
#include <QScopedPointer>
#include <QSharedPointer>
#include <QProcessEnvironment>
#include <QTextCodec>
58

59
60
61
#include <QAction>
#include <QMessageBox>
#include <QFileDialog>
62

hjk's avatar
hjk committed
63
64
using namespace Utils;

65
enum { debug = 0, debugRepositorySearch = 0 };
con's avatar
con committed
66

67
/*!
hjk's avatar
hjk committed
68
    \namespace VcsBase
69
    \brief The VcsBase namespace contains classes for the VcsBase plugin.
70
71
72
*/

/*!
hjk's avatar
hjk committed
73
    \namespace VcsBase::Internal
74
75
    \brief The Internal namespace contains internal classes for the VcsBase
    plugin.
76
77
    \internal
*/
78

hjk's avatar
hjk committed
79
namespace VcsBase {
con's avatar
con committed
80
81
namespace Internal {

82
/*!
83
    \class VcsBase::Internal::State
84

85
86
    \brief The State class provides the internal state created by the state
    listener and VcsBasePluginState.
87

hjk's avatar
hjk committed
88
    Aggregated in the QSharedData of VcsBase::VcsBasePluginState.
89
*/
90

hjk's avatar
hjk committed
91
92
struct State
{
93
    void clearFile();
94
    void clearPatchFile();
95
96
97
98
    void clearProject();
    inline void clear();

    bool equals(const State &rhs) const;
con's avatar
con committed
99

100
101
    inline bool hasFile() const     { return !currentFileTopLevel.isEmpty(); }
    inline bool hasProject() const  { return !currentProjectTopLevel.isEmpty(); }
102
103
104
105
    inline bool isEmpty() const     { return !hasFile() && !hasProject(); }

    QString currentFile;
    QString currentFileName;
106
107
108
    QString currentPatchFile;
    QString currentPatchFileDisplayName;

109
110
111
112
113
114
115
116
117
    QString currentFileDirectory;
    QString currentFileTopLevel;

    QString currentProjectPath;
    QString currentProjectName;
    QString currentProjectTopLevel;
};

void State::clearFile()
con's avatar
con committed
118
{
119
120
121
122
    currentFile.clear();
    currentFileName.clear();
    currentFileDirectory.clear();
    currentFileTopLevel.clear();
con's avatar
con committed
123
124
}

125
126
127
128
129
130
void State::clearPatchFile()
{
    currentPatchFile.clear();
    currentPatchFileDisplayName.clear();
}

131
void State::clearProject()
con's avatar
con committed
132
{
133
134
135
    currentProjectPath.clear();
    currentProjectName.clear();
    currentProjectTopLevel.clear();
con's avatar
con committed
136
137
}

138
void State::clear()
con's avatar
con committed
139
{
140
    clearFile();
141
    clearPatchFile();
142
143
144
145
146
147
148
    clearProject();
}

bool State::equals(const State &rhs) const
{
    return currentFile == rhs.currentFile
            && currentFileName == rhs.currentFileName
149
            && currentPatchFile == rhs.currentPatchFile
150
            && currentPatchFileDisplayName == rhs.currentPatchFileDisplayName
151
152
153
154
155
            && currentFileTopLevel == rhs.currentFileTopLevel
            && currentProjectPath == rhs.currentProjectPath
            && currentProjectName == rhs.currentProjectName
            && currentProjectTopLevel == rhs.currentProjectTopLevel;
}
con's avatar
con committed
156

157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
QDebug operator<<(QDebug in, const State &state)
{
    QDebug nospace = in.nospace();
    nospace << "State: ";
    if (state.isEmpty()) {
        nospace << "<empty>";
    } else {
        if (state.hasFile()) {
            nospace << "File=" << state.currentFile
                    << ',' << state.currentFileTopLevel;
        } else {
            nospace << "<no file>";
        }
        nospace << '\n';
        if (state.hasProject()) {
            nospace << "       Project=" << state.currentProjectName
            << ',' << state.currentProjectPath
            << ',' << state.currentProjectTopLevel;

        } else {
            nospace << "<no project>";
        }
        nospace << '\n';
    }
    return in;
}

184
/*!
hjk's avatar
hjk committed
185
    \class VcsBase::Internal::StateListener
186

187
188
    \brief The StateListener class connects to the relevant signals of \QC,
    tries to find version
189
190
191
192
    controls and emits signals to the plugin instances.

    Singleton (as not to do checks multiple times).
*/
193

hjk's avatar
hjk committed
194
195
class StateListener : public QObject
{
196
    Q_OBJECT
hjk's avatar
hjk committed
197

198
199
200
public:
    explicit StateListener(QObject *parent);

201
202
    static QString windowTitleVcsTopic(const QString &filePath);

203
signals:
hjk's avatar
hjk committed
204
    void stateChanged(const VcsBase::Internal::State &s, Core::IVersionControl *vc);
205

206
public slots:
207
208
209
210
211
212
    void slotStateChanged();
};

StateListener::StateListener(QObject *parent) :
        QObject(parent)
{
213
    connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)),
214
            this, SLOT(slotStateChanged()));
215
    connect(Core::EditorManager::instance(), SIGNAL(currentDocumentStateChanged()),
216
            this, SLOT(slotStateChanged()));
hjk's avatar
hjk committed
217
    connect(Core::VcsManager::instance(), SIGNAL(repositoryChanged(QString)),
218
            this, SLOT(slotStateChanged()));
con's avatar
con committed
219

220
221
222
    connect(ProjectExplorer::ProjectExplorerPlugin::instance(),
            SIGNAL(currentProjectChanged(ProjectExplorer::Project*)),
            this, SLOT(slotStateChanged()));
223
224
225
226
227
228
229
230

    Core::EditorManager::setWindowTitleVcsTopicHandler(&StateListener::windowTitleVcsTopic);
}

QString StateListener::windowTitleVcsTopic(const QString &filePath)
{
    QString searchPath;
    if (!filePath.isEmpty()) {
231
        searchPath = QFileInfo(filePath).absolutePath();
232
233
234
235
236
237
238
239
240
241
242
243
    } else {
        // use single project's information if there is only one loaded.
        const QList<ProjectExplorer::Project *> projects = ProjectExplorer::SessionManager::projects();
        if (projects.size() == 1)
            searchPath = projects.first()->projectDirectory().toString();
    }
    if (searchPath.isEmpty())
        return QString();
    QString topLevelPath;
    Core::IVersionControl *vc = Core::VcsManager::findVersionControlForDirectory(
                searchPath, &topLevelPath);
    return (vc && !topLevelPath.isEmpty()) ? vc->vcsTopic(topLevelPath) : QString();
con's avatar
con committed
244
245
}

246
247
static inline QString displayNameOfEditor(const QString &fileName)
{
hjk's avatar
hjk committed
248
    Core::IDocument *document = Core::DocumentModel::documentForFilePath(fileName);
249
250
    if (document)
        return document->displayName();
251
252
253
    return QString();
}

254
void StateListener::slotStateChanged()
con's avatar
con committed
255
{
256
257
258
    // Get the current file. Are we on a temporary submit editor indicated by
    // temporary path prefix or does the file contains a hash, indicating a project
    // folder?
259
    State state;
260
    Core::IDocument *currentDocument = Core::EditorManager::currentDocument();
261
    if (!currentDocument) {
262
        state.currentFile.clear();
263
    } else {
264
        state.currentFile = currentDocument->filePath();
265
        if (state.currentFile.isEmpty() || currentDocument->isTemporary())
jkobus's avatar
jkobus committed
266
            state.currentFile = VcsBasePlugin::source(currentDocument);
267
    }
268
    QScopedPointer<QFileInfo> currentFileInfo; // Instantiate QFileInfo only once if required.
269
    if (!state.currentFile.isEmpty()) {
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
        const bool isTempFile = state.currentFile.startsWith(QDir::tempPath());
        // Quick check: Does it look like a patch?
        const bool isPatch = state.currentFile.endsWith(QLatin1String(".patch"))
                             || state.currentFile.endsWith(QLatin1String(".diff"));
        if (isPatch) {
            // Patch: Figure out a name to display. If it is a temp file, it could be
            // Codepaster. Use the display name of the editor.
            state.currentPatchFile = state.currentFile;
            if (isTempFile)
                state.currentPatchFileDisplayName = displayNameOfEditor(state.currentPatchFile);
            if (state.currentPatchFileDisplayName.isEmpty()) {
                currentFileInfo.reset(new QFileInfo(state.currentFile));
                state.currentPatchFileDisplayName = currentFileInfo->fileName();
            }
        }
        // For actual version control operations on it:
        // Do not show temporary files and project folders ('#')
        if (isTempFile || state.currentFile.contains(QLatin1Char('#')))
288
289
            state.currentFile.clear();
    }
290

291
292
293
    // Get the file and its control. Do not use the file unless we find one
    Core::IVersionControl *fileControl = 0;
    if (!state.currentFile.isEmpty()) {
294
295
        if (currentFileInfo.isNull())
            currentFileInfo.reset(new QFileInfo(state.currentFile));
296
297
298
299
300
301
302
        if (currentFileInfo->isDir()) {
            state.currentFile.clear();
            state.currentFileDirectory = currentFileInfo->absoluteFilePath();
        } else {
            state.currentFileDirectory = currentFileInfo->absolutePath();
            state.currentFileName = currentFileInfo->fileName();
        }
303
304
305
306
307
        fileControl = Core::VcsManager::findVersionControlForDirectory(
                    state.currentFileDirectory,
                    &state.currentFileTopLevel);
        if (!fileControl)
            state.clearFile();
308
309
310
    }
    // Check for project, find the control
    Core::IVersionControl *projectControl = 0;
311
    if (const ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectExplorerPlugin::currentProject()) {
312
        state.currentProjectPath = currentProject->projectDirectory().toString();
313
        state.currentProjectName = currentProject->displayName();
hjk's avatar
hjk committed
314
        projectControl = Core::VcsManager::findVersionControlForDirectory(state.currentProjectPath,
315
316
317
                                                                    &state.currentProjectTopLevel);
        if (projectControl) {
            // If we have both, let the file's one take preference
318
            if (fileControl && projectControl != fileControl)
319
320
321
322
323
324
                state.clearProject();
        } else {
            state.clearProject(); // No control found
        }
    }
    // Assemble state and emit signal.
325
326
327
    Core::IVersionControl *vc = fileControl;
    if (!vc)
        vc = projectControl;
328
    if (!vc)
Orgad Shaneh's avatar
Orgad Shaneh committed
329
        state.clearPatchFile(); // Need a repository to patch
330
    if (debug)
331
        qDebug() << state << (vc ? vc->displayName() : QLatin1String("No version control"));
332
    Core::EditorManager::updateWindowTitles();
333
    emit stateChanged(state, vc);
con's avatar
con committed
334
}
335

336
337
} // namespace Internal

hjk's avatar
hjk committed
338
class VcsBasePluginStateData : public QSharedData
hjk's avatar
hjk committed
339
{
340
341
342
343
public:
    Internal::State m_state;
};

344
/*!
hjk's avatar
hjk committed
345
    \class  VcsBase::VcsBasePluginState
346

347
348
    \brief The VcsBasePluginState class provides relevant state information
    about the VCS plugins.
349
350
351
352

    Qt Creator's state relevant to VCS plugins is a tuple of

    \list
353
354
    \li Current file and it's version system control/top level
    \li Current project and it's version system control/top level
355
356
    \endlist

hjk's avatar
hjk committed
357
    \sa VcsBase::VcsBasePlugin
358
359
*/

hjk's avatar
hjk committed
360
VcsBasePluginState::VcsBasePluginState() : data(new VcsBasePluginStateData)
con's avatar
con committed
361
362
363
{
}

hjk's avatar
hjk committed
364
VcsBasePluginState::VcsBasePluginState(const VcsBasePluginState &rhs) : data(rhs.data)
365
366
367
{
}

hjk's avatar
hjk committed
368
VcsBasePluginState &VcsBasePluginState::operator=(const VcsBasePluginState &rhs)
369
{
370
371
372
373
374
    if (this != &rhs)
        data.operator=(rhs.data);
    return *this;
}

hjk's avatar
hjk committed
375
VcsBasePluginState::~VcsBasePluginState()
376
377
378
{
}

hjk's avatar
hjk committed
379
QString VcsBasePluginState::currentFile() const
380
381
382
383
{
    return data->m_state.currentFile;
}

hjk's avatar
hjk committed
384
QString VcsBasePluginState::currentFileName() const
385
386
387
388
{
    return data->m_state.currentFileName;
}

hjk's avatar
hjk committed
389
QString VcsBasePluginState::currentFileTopLevel() const
390
391
392
393
{
    return data->m_state.currentFileTopLevel;
}

hjk's avatar
hjk committed
394
QString VcsBasePluginState::currentFileDirectory() const
395
396
397
398
{
    return data->m_state.currentFileDirectory;
}

hjk's avatar
hjk committed
399
QString VcsBasePluginState::relativeCurrentFile() const
400
{
401
    QTC_ASSERT(hasFile(), return QString());
402
403
404
    return QDir(data->m_state.currentFileTopLevel).relativeFilePath(data->m_state.currentFile);
}

hjk's avatar
hjk committed
405
QString VcsBasePluginState::currentPatchFile() const
406
407
408
409
{
    return data->m_state.currentPatchFile;
}

hjk's avatar
hjk committed
410
QString VcsBasePluginState::currentPatchFileDisplayName() const
411
412
413
414
{
    return data->m_state.currentPatchFileDisplayName;
}

hjk's avatar
hjk committed
415
QString VcsBasePluginState::currentProjectPath() const
416
417
418
419
{
    return data->m_state.currentProjectPath;
}

hjk's avatar
hjk committed
420
QString VcsBasePluginState::currentProjectName() const
421
422
423
424
{
    return data->m_state.currentProjectName;
}

hjk's avatar
hjk committed
425
QString VcsBasePluginState::currentProjectTopLevel() const
426
427
428
429
{
    return data->m_state.currentProjectTopLevel;
}

430
QString VcsBasePluginState::relativeCurrentProject() const
431
{
432
    QTC_ASSERT(hasProject(), return QString());
433
    if (data->m_state.currentProjectTopLevel != data->m_state.currentProjectPath)
434
435
        return QDir(data->m_state.currentProjectTopLevel).relativeFilePath(data->m_state.currentProjectPath);
    return QString();
436
437
}

hjk's avatar
hjk committed
438
bool VcsBasePluginState::hasTopLevel() const
439
{
440
441
442
    return data->m_state.hasFile() || data->m_state.hasProject();
}

hjk's avatar
hjk committed
443
QString VcsBasePluginState::topLevel() const
444
445
446
447
{
    return hasFile() ? data->m_state.currentFileTopLevel : data->m_state.currentProjectTopLevel;
}

hjk's avatar
hjk committed
448
bool VcsBasePluginState::equals(const Internal::State &rhs) const
449
450
451
452
{
    return data->m_state.equals(rhs);
}

hjk's avatar
hjk committed
453
bool VcsBasePluginState::equals(const VcsBasePluginState &rhs) const
454
455
456
457
{
    return equals(rhs.data->m_state);
}

hjk's avatar
hjk committed
458
void VcsBasePluginState::clear()
459
460
461
462
{
    data->m_state.clear();
}

hjk's avatar
hjk committed
463
void VcsBasePluginState::setState(const Internal::State &s)
464
465
466
467
{
    data->m_state = s;
}

hjk's avatar
hjk committed
468
bool VcsBasePluginState::isEmpty() const
469
470
471
472
{
    return data->m_state.isEmpty();
}

hjk's avatar
hjk committed
473
bool VcsBasePluginState::hasFile() const
474
475
476
477
{
    return data->m_state.hasFile();
}

hjk's avatar
hjk committed
478
bool VcsBasePluginState::hasPatchFile() const
479
480
481
482
{
    return !data->m_state.currentPatchFile.isEmpty();
}

hjk's avatar
hjk committed
483
bool VcsBasePluginState::hasProject() const
484
485
486
487
{
    return data->m_state.hasProject();
}

hjk's avatar
hjk committed
488
VCSBASE_EXPORT QDebug operator<<(QDebug in, const VcsBasePluginState &state)
489
490
491
492
493
{
    in << state.data->m_state;
    return in;
}

494
/*!
hjk's avatar
hjk committed
495
    \class VcsBase::VcsBasePlugin
496

497
498
    \brief The VcsBasePlugin class is the base class for all version control
    plugins.
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522

    The plugin connects to the
    relevant change signals in Qt Creator and calls the virtual
    updateActions() for the plugins to update their menu actions
    according to the new state. This is done centrally to avoid
    single plugins repeatedly invoking searches/QFileInfo on files,
    etc.

    Independently, there are accessors for current patch files, which return
    a file name if the current file could be a patch file which could be applied
    and a repository exists.

    If current file/project are managed
    by different version controls, the project is discarded and only
    the current file is taken into account, allowing to do a diff
    also when the project of a file is not opened.

    When triggering an action, a copy of the state should be made to
    keep it, as it may rapidly change due to context changes, etc.

    The class also detects the VCS plugin submit editor closing and calls
    the virtual submitEditorAboutToClose() to trigger the submit process.
*/

Tobias Hunger's avatar
Tobias Hunger committed
523
class VcsBasePluginPrivate
hjk's avatar
hjk committed
524
{
Tobias Hunger's avatar
Tobias Hunger committed
525
public:
526
    explicit VcsBasePluginPrivate();
527

528
529
    inline bool supportsRepositoryCreation() const;

530
    QPointer<VcsBaseSubmitEditor> m_submitEditor;
531
    Core::IVersionControl *m_versionControl;
532
    Core::Context m_context;
hjk's avatar
hjk committed
533
    VcsBasePluginState m_state;
534
535
536
537
538
    int m_actionState;

    static Internal::StateListener *m_listener;
};

539
VcsBasePluginPrivate::VcsBasePluginPrivate() :
540
    m_versionControl(0),
Tobias Hunger's avatar
Tobias Hunger committed
541
    m_actionState(-1)
542
543
544
{
}

hjk's avatar
hjk committed
545
bool VcsBasePluginPrivate::supportsRepositoryCreation() const
546
547
548
549
{
    return m_versionControl && m_versionControl->supportsOperation(Core::IVersionControl::CreateRepositoryOperation);
}

hjk's avatar
hjk committed
550
Internal::StateListener *VcsBasePluginPrivate::m_listener = 0;
551

552
553
VcsBasePlugin::VcsBasePlugin() :
    d(new VcsBasePluginPrivate())
554
555
556
{
}

hjk's avatar
hjk committed
557
VcsBasePlugin::~VcsBasePlugin()
558
559
560
561
{
    delete d;
}

562
void VcsBasePlugin::initializeVcs(Core::IVersionControl *vc, const Core::Context &context)
563
564
{
    d->m_versionControl = vc;
565
    d->m_context = context;
566
567
    addAutoReleasedObject(vc);

hjk's avatar
hjk committed
568
569
570
    Internal::VcsPlugin *plugin = Internal::VcsPlugin::instance();
    connect(plugin->coreListener(), SIGNAL(submitEditorAboutToClose(VcsBaseSubmitEditor*,bool*)),
            this, SLOT(slotSubmitEditorAboutToClose(VcsBaseSubmitEditor*,bool*)));
571
    // First time: create new listener
hjk's avatar
hjk committed
572
573
574
    if (!VcsBasePluginPrivate::m_listener)
        VcsBasePluginPrivate::m_listener = new Internal::StateListener(plugin);
    connect(VcsBasePluginPrivate::m_listener,
Robert Loehning's avatar
Robert Loehning committed
575
            SIGNAL(stateChanged(VcsBase::Internal::State,Core::IVersionControl*)),
576
            this,
hjk's avatar
hjk committed
577
            SLOT(slotStateChanged(VcsBase::Internal::State,Core::IVersionControl*)));
578
579
    // VCSes might have become (un-)available, so clear the VCS directory cache
    connect(vc, SIGNAL(configurationChanged()),
hjk's avatar
hjk committed
580
            Core::VcsManager::instance(), SLOT(clearVersionControlCache()));
581
582
    connect(vc, SIGNAL(configurationChanged()),
            VcsBasePluginPrivate::m_listener, SLOT(slotStateChanged()));
583
584
}

hjk's avatar
hjk committed
585
void VcsBasePlugin::extensionsInitialized()
586
587
{
    // Initialize enable menus.
hjk's avatar
hjk committed
588
    VcsBasePluginPrivate::m_listener->slotStateChanged();
589
590
}

hjk's avatar
hjk committed
591
void VcsBasePlugin::slotSubmitEditorAboutToClose(VcsBaseSubmitEditor *submitEditor, bool *result)
592
593
{
    if (debug)
594
        qDebug() << this << "plugin's submit editor"
595
596
597
                 << d->m_submitEditor << (d->m_submitEditor ? d->m_submitEditor->document()->id().name() : "")
                 << "closing submit editor" << submitEditor
                 << (submitEditor ? submitEditor->document()->id().name() : "");
598
599
    if (submitEditor == d->m_submitEditor)
        *result = submitEditorAboutToClose();
600
601
}

hjk's avatar
hjk committed
602
Core::IVersionControl *VcsBasePlugin::versionControl() const
603
604
605
606
{
    return d->m_versionControl;
}

hjk's avatar
hjk committed
607
void VcsBasePlugin::slotStateChanged(const VcsBase::Internal::State &newInternalState, Core::IVersionControl *vc)
608
609
610
611
612
{
    if (vc == d->m_versionControl) {
        // We are directly affected: Change state
        if (!d->m_state.equals(newInternalState)) {
            d->m_state.setState(newInternalState);
hjk's avatar
hjk committed
613
            updateActions(VcsEnabled);
614
            Core::ICore::addAdditionalContext(d->m_context);
615
        }
616
617
    } else {
        // Some other VCS plugin or state changed: Reset us to empty state.
hjk's avatar
hjk committed
618
        const ActionState newActionState = vc ? OtherVcsEnabled : NoVcsEnabled;
619
620
        if (d->m_actionState != newActionState || !d->m_state.isEmpty()) {
            d->m_actionState = newActionState;
hjk's avatar
hjk committed
621
            const VcsBasePluginState emptyState;
622
623
624
            d->m_state = emptyState;
            updateActions(newActionState);
        }
625
        Core::ICore::removeAdditionalContext(d->m_context);
626
    }
627
628
}

hjk's avatar
hjk committed
629
const VcsBasePluginState &VcsBasePlugin::currentState() const
630
{
631
632
633
    return d->m_state;
}

hjk's avatar
hjk committed
634
bool VcsBasePlugin::enableMenuAction(ActionState as, QAction *menuAction) const
635
636
637
638
{
    if (debug)
        qDebug() << "enableMenuAction" << menuAction->text() << as;
    switch (as) {
639
    case NoVcsEnabled: {
640
        const bool supportsCreation = d->supportsRepositoryCreation();
641
        menuAction->setVisible(supportsCreation);
642
643
644
        menuAction->setEnabled(supportsCreation);
        return supportsCreation;
    }
645
    case OtherVcsEnabled:
646
647
        menuAction->setVisible(false);
        return false;
648
    case VcsEnabled:
649
650
651
652
653
        menuAction->setVisible(true);
        menuAction->setEnabled(true);
        break;
    }
    return true;
654
655
}

hjk's avatar
hjk committed
656
void VcsBasePlugin::promptToDeleteCurrentFile()
657
{
hjk's avatar
hjk committed
658
    const VcsBasePluginState state = currentState();
659
    QTC_ASSERT(state.hasFile(), return);
hjk's avatar
hjk committed
660
    const bool rc = Core::VcsManager::promptToDelete(versionControl(), state.currentFile());
661
    if (!rc)
662
        QMessageBox::warning(Core::ICore::dialogParent(), tr("Version Control"),
663
                             tr("The file \"%1\" could not be deleted.").
664
665
                             arg(QDir::toNativeSeparators(state.currentFile())),
                             QMessageBox::Ok);
666
667
}

668
669
670
671
672
673
674
static inline bool ask(QWidget *parent, const QString &title, const QString &question, bool defaultValue = true)

{
    const QMessageBox::StandardButton defaultButton = defaultValue ? QMessageBox::Yes : QMessageBox::No;
    return QMessageBox::question(parent, title, question, QMessageBox::Yes|QMessageBox::No, defaultButton) == QMessageBox::Yes;
}

hjk's avatar
hjk committed
675
void VcsBasePlugin::createRepository()
676
677
678
679
{
    QTC_ASSERT(d->m_versionControl->supportsOperation(Core::IVersionControl::CreateRepositoryOperation), return);
    // Find current starting directory
    QString directory;
680
    if (const ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectExplorerPlugin::currentProject())
681
        directory = QFileInfo(currentProject->document()->filePath()).absolutePath();
682
    // Prompt for a directory that is not under version control yet
683
    QWidget *mw = Core::ICore::mainWindow();
684
    do {
685
        directory = QFileDialog::getExistingDirectory(mw, tr("Choose Repository Directory"), directory);
686
687
        if (directory.isEmpty())
            return;
hjk's avatar
hjk committed
688
        const Core::IVersionControl *managingControl = Core::VcsManager::findVersionControlForDirectory(directory);
689
690
        if (managingControl == 0)
            break;
691
        const QString question = tr("The directory \"%1\" is already managed by a version control system (%2)."
692
693
694
695
696
697
698
                                    " Would you like to specify another directory?").arg(directory, managingControl->displayName());

        if (!ask(mw, tr("Repository already under version control"), question))
            return;
    } while (true);
    // Create
    const bool rc = d->m_versionControl->vcsCreateRepository(directory);
699
    const QString nativeDir = QDir::toNativeSeparators(directory);
700
    if (rc) {
Leena Miettinen's avatar
Leena Miettinen committed
701
        QMessageBox::information(mw, tr("Repository Created"),
702
703
                                 tr("A version control repository has been created in %1.").
                                 arg(nativeDir));
704
    } else {
Leena Miettinen's avatar
Leena Miettinen committed
705
        QMessageBox::warning(mw, tr("Repository Creation Failed"),
706
707
                                 tr("A version control repository could not be created in %1.").
                                 arg(nativeDir));
708
709
710
    }
}

711
712
713
714
715
716
717
718
719
720
721
722
723
724
void VcsBasePlugin::setSubmitEditor(VcsBaseSubmitEditor *submitEditor)
{
    d->m_submitEditor = submitEditor;
}

VcsBaseSubmitEditor *VcsBasePlugin::submitEditor() const
{
    return d->m_submitEditor;
}

bool VcsBasePlugin::raiseSubmitEditor() const
{
    if (!d->m_submitEditor)
        return false;
Eike Ziller's avatar
Eike Ziller committed
725
    Core::EditorManager::activateEditor(d->m_submitEditor, Core::EditorManager::IgnoreNavigationHistory);
726
727
728
    return true;
}

729
730
731
732
733
734
735
// Find top level for version controls like git/Mercurial that have
// a directory at the top of the repository.
// Note that checking for the existence of files is preferred over directories
// since checking for directories can cause them to be created when
// AutoFS is used (due its automatically creating mountpoints when querying
// a directory). In addition, bail out when reaching the home directory
// of the user or root (generally avoid '/', where mountpoints are created).
hjk's avatar
hjk committed
736
QString VcsBasePlugin::findRepositoryForDirectory(const QString &dirS,
737
738
739
                                                  const QString &checkFile)
{
    if (debugRepositorySearch)
hjk's avatar
hjk committed
740
        qDebug() << ">VcsBasePlugin::findRepositoryForDirectory" << dirS << checkFile;
741
742
743
744
745
746
747
748
749
750
751
752
753
    QTC_ASSERT(!dirS.isEmpty() && !checkFile.isEmpty(), return QString());

    const QString root = QDir::rootPath();
    const QString home = QDir::homePath();

    QDir directory(dirS);
    do {
        const QString absDirPath = directory.absolutePath();
        if (absDirPath == root || absDirPath == home)
            break;

        if (QFileInfo(directory, checkFile).isFile()) {
            if (debugRepositorySearch)
hjk's avatar
hjk committed
754
                qDebug() << "<VcsBasePlugin::findRepositoryForDirectory> " << absDirPath;
755
756
            return absDirPath;
        }
Orgad Shaneh's avatar
Orgad Shaneh committed
757
    } while (!directory.isRoot() && directory.cdUp());
758
    if (debugRepositorySearch)
hjk's avatar
hjk committed
759
        qDebug() << "<VcsBasePlugin::findRepositoryForDirectory bailing out at " << directory.absolutePath();
760
761
762
    return QString();
}

763
// Is SSH prompt configured?
764
QString VcsBasePlugin::sshPrompt()
765
{
766
    return Internal::VcsPlugin::instance()->settings().sshPasswordPrompt;
767
768
}

hjk's avatar
hjk committed
769
bool VcsBasePlugin::isSshPromptConfigured()
770
771
772
773
{
    return !sshPrompt().isEmpty();
}

jkobus's avatar
jkobus committed
774
775
static const char SOURCE_PROPERTY[] = "qtcreator_source";

776
void VcsBasePlugin::setSource(Core::IDocument *document, const QString &source)
jkobus's avatar
jkobus committed
777
{
778
    document->setProperty(SOURCE_PROPERTY, source);
779
    VcsBasePluginPrivate::m_listener->slotStateChanged();
jkobus's avatar
jkobus committed
780
781
}

782
QString VcsBasePlugin::source(Core::IDocument *document)
jkobus's avatar
jkobus committed
783
{
784
    return document->property(SOURCE_PROPERTY).toString();
jkobus's avatar
jkobus committed
785
786
}

787
788
789
void VcsBasePlugin::setProcessEnvironment(QProcessEnvironment *e,
                                          bool forceCLocale,
                                          const QString &sshPromptBinary)
790
{
791
792
    if (forceCLocale)
        e->insert(QLatin1String("LANG"), QString(QLatin1Char('C')));
793
794
795
796
    if (!sshPromptBinary.isEmpty())
        e->insert(QLatin1String("SSH_ASKPASS"), sshPromptBinary);
}

797
// Run a process synchronously, returning Utils::SynchronousProcessResponse
hjk's avatar
hjk committed
798
799
// response struct and using the VcsBasePlugin flags as applicable
SynchronousProcessResponse VcsBasePlugin::runVcs(const QString &workingDir,
800
                                                 const Utils::FileName &binary,
hjk's avatar
hjk committed
801
802
803
                                                 const QStringList &arguments,
                                                 int timeOutMS,
                                                 unsigned flags,
804
805
806
                                                 QTextCodec *outputCodec,
                                                 const QProcessEnvironment &env)
{
hjk's avatar
hjk committed
807
808
    VcsCommand command(binary, workingDir,
                       env.isEmpty() ? QProcessEnvironment::systemEnvironment() : env);
809
810
811
    command.addFlags(flags);
    command.setCodec(outputCodec);
    return command.runVcs(arguments, timeOutMS);
812
813
}

hjk's avatar
hjk committed
814
} // namespace VcsBase
con's avatar
con committed
815

816
#include "vcsbaseplugin.moc"