subversionplugin.cpp 51.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
32
33
34
35
36
#include "subversionplugin.h"

#include "settingspage.h"
#include "subversioneditor.h"

#include "subversionsubmiteditor.h"
37
#include "subversionclient.h"
con's avatar
con committed
38
39
#include "subversionconstants.h"
#include "subversioncontrol.h"
40
#include "checkoutwizard.h"
con's avatar
con committed
41
42

#include <vcsbase/basevcseditorfactory.h>
hjk's avatar
hjk committed
43
#include <vcsbase/vcscommand.h>
con's avatar
con committed
44
45
#include <vcsbase/vcsbaseeditor.h>
#include <vcsbase/basevcssubmiteditorfactory.h>
46
#include <vcsbase/vcsbaseconstants.h>
47
#include <vcsbase/vcsoutputwindow.h>
48
#include <vcsbase/vcsbaseeditorparameterwidget.h>
con's avatar
con committed
49

50
#include <coreplugin/actionmanager/actioncontainer.h>
51
#include <coreplugin/actionmanager/actionmanager.h>
52
#include <coreplugin/actionmanager/command.h>
53
54
#include <coreplugin/coreconstants.h>
#include <coreplugin/documentmanager.h>
con's avatar
con committed
55
#include <coreplugin/editormanager/editormanager.h>
56
57
#include <coreplugin/icore.h>
#include <coreplugin/id.h>
58
#include <coreplugin/locator/commandlocator.h>
59
60
#include <coreplugin/messagemanager.h>
#include <coreplugin/mimedatabase.h>
Friedemann Kleint's avatar
Friedemann Kleint committed
61

62
63
64
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/parameteraction.h>
hjk's avatar
hjk committed
65
#include <utils/qtcassert.h>
66
#include <utils/synchronousprocess.h>
con's avatar
con committed
67

68
69
70
71
72
73
74
75
76
77
78
79
80
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QTextCodec>
#include <QtPlugin>
#include <QProcessEnvironment>
#include <QUrl>
#include <QXmlStreamReader>
#include <QAction>
#include <QFileDialog>
#include <QMenu>
#include <QMessageBox>
#include <QInputDialog>
81

82
#include <limits.h>
con's avatar
con committed
83

84
85
86
87
#ifdef WITH_TESTS
#include <QTest>
#endif

hjk's avatar
hjk committed
88
89
90
91
using namespace Core;
using namespace Utils;
using namespace VcsBase;

92
93
namespace Subversion {
namespace Internal {
con's avatar
con committed
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
const char CMD_ID_SUBVERSION_MENU[]    = "Subversion.Menu";
const char CMD_ID_ADD[]                = "Subversion.Add";
const char CMD_ID_DELETE_FILE[]        = "Subversion.Delete";
const char CMD_ID_REVERT[]             = "Subversion.Revert";
const char CMD_ID_DIFF_PROJECT[]       = "Subversion.DiffAll";
const char CMD_ID_DIFF_CURRENT[]       = "Subversion.DiffCurrent";
const char CMD_ID_COMMIT_ALL[]         = "Subversion.CommitAll";
const char CMD_ID_REVERT_ALL[]         = "Subversion.RevertAll";
const char CMD_ID_COMMIT_CURRENT[]     = "Subversion.CommitCurrent";
const char CMD_ID_FILELOG_CURRENT[]    = "Subversion.FilelogCurrent";
const char CMD_ID_ANNOTATE_CURRENT[]   = "Subversion.AnnotateCurrent";
const char CMD_ID_STATUS[]             = "Subversion.Status";
const char CMD_ID_PROJECTLOG[]         = "Subversion.ProjectLog";
const char CMD_ID_REPOSITORYLOG[]      = "Subversion.RepositoryLog";
const char CMD_ID_REPOSITORYUPDATE[]   = "Subversion.RepositoryUpdate";
const char CMD_ID_REPOSITORYDIFF[]     = "Subversion.RepositoryDiff";
const char CMD_ID_REPOSITORYSTATUS[]   = "Subversion.RepositoryStatus";
const char CMD_ID_UPDATE[]             = "Subversion.Update";
const char CMD_ID_COMMIT_PROJECT[]     = "Subversion.CommitProject";
const char CMD_ID_DESCRIBE[]           = "Subversion.Describe";

const char SUBVERSION_SUBMIT_MIMETYPE[] = "text/vnd.qtcreator.svn.submit";
const char SUBVERSIONCOMMITEDITOR_ID[]  = "Subversion Commit Editor";
const char SUBVERSIONCOMMITEDITOR_DISPLAY_NAME[]  = QT_TRANSLATE_NOOP("VCS", "Subversion Commit Editor");
const char SUBMIT_CURRENT[]             = "Subversion.SubmitCurrentLog";
const char DIFF_SELECTED[]              = "Subversion.DiffSelectedFilesInLog";

const VcsBaseEditorParameters editorParameters[] = {
con's avatar
con committed
123
{
hjk's avatar
hjk committed
124
    LogOutput,
125
126
    "Subversion File Log Editor",   // id
    QT_TRANSLATE_NOOP("VCS", "Subversion File Log Editor"),   // display_name
Orgad Shaneh's avatar
Orgad Shaneh committed
127
    "text/vnd.qtcreator.svn.log"},
hjk's avatar
hjk committed
128
{    AnnotateOutput,
129
130
    "Subversion Annotation Editor",  // id
    QT_TRANSLATE_NOOP("VCS", "Subversion Annotation Editor"),   // display_name
Orgad Shaneh's avatar
Orgad Shaneh committed
131
    "text/vnd.qtcreator.svn.annotation"},
hjk's avatar
hjk committed
132
{   DiffOutput,
133
134
    "Subversion Diff Editor",  // id
    QT_TRANSLATE_NOOP("VCS", "Subversion Diff Editor"),   // display_name
135
    "text/x-patch"}
con's avatar
con committed
136
137
138
};

// Utility to find a parameter set by type
hjk's avatar
hjk committed
139
static const VcsBaseEditorParameters *findType(int ie)
con's avatar
con committed
140
{
hjk's avatar
hjk committed
141
    const EditorContentType et = static_cast<EditorContentType>(ie);
142
    return VcsBaseEditor::findType(editorParameters, sizeof(editorParameters)/sizeof(editorParameters[0]), et);
con's avatar
con committed
143
144
145
146
}

static inline QString debugCodec(const QTextCodec *c)
{
147
    return c ? QString::fromLatin1(c->name()) : QString::fromLatin1("Null codec");
con's avatar
con committed
148
149
}

150
151
152
153
154
155
156
157
158
159
160
161
162
163
// Parse "svn status" output for added/modified/deleted files
// "M<7blanks>file"
typedef QList<SubversionSubmitEditor::StatusFilePair> StatusList;

StatusList parseStatusOutput(const QString &output)
{
    StatusList changeSet;
    const QString newLine = QString(QLatin1Char('\n'));
    const QStringList list = output.split(newLine, QString::SkipEmptyParts);
    foreach (const QString &l, list) {
        const QString line =l.trimmed();
        if (line.size() > 8) {
            const QChar state = line.at(0);
            if (state == QLatin1Char('A') || state == QLatin1Char('D') || state == QLatin1Char('M')) {
164
165
                const QString fileName = line.mid(7); // Column 8 starting from svn 1.6
                changeSet.push_back(SubversionSubmitEditor::StatusFilePair(QString(state), fileName.trimmed()));
166
167
168
169
170
171
172
            }

        }
    }
    return changeSet;
}

173
174
175
176
// Return a list of names for the internal svn directories
static inline QStringList svnDirectories()
{
    QStringList rc(QLatin1String(".svn"));
hjk's avatar
hjk committed
177
    if (HostOsInfo::isWindowsHost())
178
179
        // Option on Windows systems to avoid hassle with some IDEs
        rc.push_back(QLatin1String("_svn"));
180
181
182
    return rc;
}

con's avatar
con committed
183
184
185
186
// ------------- SubversionPlugin
SubversionPlugin *SubversionPlugin::m_subversionPluginInstance = 0;

SubversionPlugin::SubversionPlugin() :
187
    m_svnDirectories(svnDirectories()),
Friedemann Kleint's avatar
Friedemann Kleint committed
188
    m_commandLocator(0),
con's avatar
con committed
189
190
191
192
193
    m_addAction(0),
    m_deleteAction(0),
    m_revertAction(0),
    m_diffProjectAction(0),
    m_diffCurrentAction(0),
194
195
    m_logProjectAction(0),
    m_logRepositoryAction(0),
con's avatar
con committed
196
    m_commitAllAction(0),
197
    m_revertRepositoryAction(0),
198
199
200
    m_diffRepositoryAction(0),
    m_statusRepositoryAction(0),
    m_updateRepositoryAction(0),
con's avatar
con committed
201
202
203
    m_commitCurrentAction(0),
    m_filelogCurrentAction(0),
    m_annotateCurrentAction(0),
204
    m_statusProjectAction(0),
con's avatar
con committed
205
    m_updateProjectAction(0),
206
    m_commitProjectAction(0),
207
    m_describeAction(0),
con's avatar
con committed
208
209
210
    m_submitCurrentLogAction(0),
    m_submitDiffAction(0),
    m_submitUndoAction(0),
211
    m_submitRedoAction(0),
212
    m_menuAction(0),
213
    m_submitActionTriggered(false)
con's avatar
con committed
214
215
216
217
218
{
}

SubversionPlugin::~SubversionPlugin()
{
219
    delete m_client;
220
    cleanCommitMessageFile();
con's avatar
con committed
221
222
}

223
void SubversionPlugin::cleanCommitMessageFile()
con's avatar
con committed
224
{
225
226
227
    if (!m_commitMessageFileName.isEmpty()) {
        QFile::remove(m_commitMessageFileName);
        m_commitMessageFileName.clear();
228
        m_commitRepository.clear();
con's avatar
con committed
229
230
231
    }
}

232
233
234
235
236
bool SubversionPlugin::isCommitEditorOpen() const
{
    return !m_commitMessageFileName.isEmpty();
}

hjk's avatar
hjk committed
237
const VcsBaseSubmitEditorParameters submitParameters = {
238
239
240
    SUBVERSION_SUBMIT_MIMETYPE,
    SUBVERSIONCOMMITEDITOR_ID,
    SUBVERSIONCOMMITEDITOR_DISPLAY_NAME,
hjk's avatar
hjk committed
241
    VcsBaseSubmitEditorParameters::DiffFiles
con's avatar
con committed
242
243
};

244
bool SubversionPlugin::initialize(const QStringList & /*arguments */, QString *errorMessage)
con's avatar
con committed
245
246
247
248
{
    using namespace Constants;
    using namespace Core::Constants;

249
    initializeVcs(new SubversionControl(this));
250

con's avatar
con committed
251
252
    m_subversionPluginInstance = this;

hjk's avatar
hjk committed
253
    if (!MimeDatabase::addMimeTypes(QLatin1String(":/trolltech.subversion/Subversion.mimetypes.xml"), errorMessage))
con's avatar
con committed
254
255
        return false;

hjk's avatar
hjk committed
256
    m_settings.readSettings(ICore::settings());
257
    m_client = new SubversionClient(&m_settings);
con's avatar
con committed
258

259
    addAutoReleasedObject(new SettingsPage);
con's avatar
con committed
260

261
262
    addAutoReleasedObject(new VcsSubmitEditorFactory(&submitParameters,
        []() { return new SubversionSubmitEditor(&submitParameters); }));
con's avatar
con committed
263
264

    static const char *describeSlot = SLOT(describe(QString,QString));
hjk's avatar
hjk committed
265
    const int editorCount = sizeof(editorParameters) / sizeof(editorParameters[0]);
266
    const auto widgetCreator = []() { return new SubversionEditorWidget; };
267
    for (int i = 0; i < editorCount; i++)
268
        addAutoReleasedObject(new VcsEditorFactory(editorParameters + i, widgetCreator, this, describeSlot));
con's avatar
con committed
269

270
271
272
273
274
275
276
277
278
    auto checkoutWizardFactory = new BaseCheckoutWizardFactory;
    checkoutWizardFactory->setId(QLatin1String(VcsBase::Constants::VCS_ID_SUBVERSION));
    checkoutWizardFactory->setIcon(QIcon(QLatin1String(":/subversion/images/subversion.png")));
    checkoutWizardFactory->setDescription(tr("Checks out a Subversion repository and tries to load the contained project."));
    checkoutWizardFactory->setDisplayName(tr("Subversion Checkout"));
    checkoutWizardFactory->setWizardCreator([this] (const FileName &path, QWidget *parent) {
        return new CheckoutWizard(path, parent);
    });
    addAutoReleasedObject(checkoutWizardFactory);
279

Friedemann Kleint's avatar
Friedemann Kleint committed
280
    const QString prefix = QLatin1String("svn");
hjk's avatar
hjk committed
281
    m_commandLocator = new CommandLocator("Subversion", prefix, prefix);
Friedemann Kleint's avatar
Friedemann Kleint committed
282
283
    addAutoReleasedObject(m_commandLocator);

hjk's avatar
hjk committed
284
285
    // Register actions
    ActionContainer *toolsContainer = ActionManager::actionContainer(M_TOOLS);
con's avatar
con committed
286

hjk's avatar
hjk committed
287
    ActionContainer *subversionMenu = ActionManager::createMenu(Id(CMD_ID_SUBVERSION_MENU));
con's avatar
con committed
288
289
    subversionMenu->menu()->setTitle(tr("&Subversion"));
    toolsContainer->addMenu(subversionMenu);
290
    m_menuAction = subversionMenu->menu()->menuAction();
hjk's avatar
hjk committed
291
    Context globalcontext(C_GLOBAL);
con's avatar
con committed
292
    Core::Command *command;
293

hjk's avatar
hjk committed
294
295
    m_diffCurrentAction = new ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_diffCurrentAction,
296
        CMD_ID_DIFF_CURRENT, globalcontext);
con's avatar
con committed
297
    command->setAttribute(Core::Command::CA_UpdateText);
hjk's avatar
hjk committed
298
    command->setDefaultKeySequence(QKeySequence(UseMacShortcuts ? tr("Meta+S,Meta+D") : tr("Alt+S,Alt+D")));
299
    connect(m_diffCurrentAction, SIGNAL(triggered()), this, SLOT(diffCurrentFile()));
con's avatar
con committed
300
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
301
    m_commandLocator->appendCommand(command);
con's avatar
con committed
302

hjk's avatar
hjk committed
303
304
    m_filelogCurrentAction = new ParameterAction(tr("Filelog Current File"), tr("Filelog \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_filelogCurrentAction,
305
        CMD_ID_FILELOG_CURRENT, globalcontext);
con's avatar
con committed
306
    command->setAttribute(Core::Command::CA_UpdateText);
307
308
    connect(m_filelogCurrentAction, SIGNAL(triggered()), this,
        SLOT(filelogCurrentFile()));
con's avatar
con committed
309
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
310
    m_commandLocator->appendCommand(command);
con's avatar
con committed
311

hjk's avatar
hjk committed
312
313
    m_annotateCurrentAction = new ParameterAction(tr("Annotate Current File"), tr("Annotate \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_annotateCurrentAction,
314
        CMD_ID_ANNOTATE_CURRENT, globalcontext);
con's avatar
con committed
315
    command->setAttribute(Core::Command::CA_UpdateText);
316
317
    connect(m_annotateCurrentAction, SIGNAL(triggered()), this,
        SLOT(annotateCurrentFile()));
con's avatar
con committed
318
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
319
    m_commandLocator->appendCommand(command);
con's avatar
con committed
320

321
    subversionMenu->addSeparator(globalcontext);
con's avatar
con committed
322

hjk's avatar
hjk committed
323
324
    m_addAction = new ParameterAction(tr("Add"), tr("Add \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_addAction, CMD_ID_ADD,
con's avatar
con committed
325
        globalcontext);
326
    command->setAttribute(Core::Command::CA_UpdateText);
hjk's avatar
hjk committed
327
    command->setDefaultKeySequence(QKeySequence(UseMacShortcuts ? tr("Meta+S,Meta+A") : tr("Alt+S,Alt+A")));
328
    connect(m_addAction, SIGNAL(triggered()), this, SLOT(addCurrentFile()));
con's avatar
con committed
329
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
330
    m_commandLocator->appendCommand(command);
con's avatar
con committed
331

hjk's avatar
hjk committed
332
333
    m_commitCurrentAction = new ParameterAction(tr("Commit Current File"), tr("Commit \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_commitCurrentAction,
334
        CMD_ID_COMMIT_CURRENT, globalcontext);
con's avatar
con committed
335
    command->setAttribute(Core::Command::CA_UpdateText);
hjk's avatar
hjk committed
336
    command->setDefaultKeySequence(QKeySequence(UseMacShortcuts ? tr("Meta+S,Meta+C") : tr("Alt+S,Alt+C")));
con's avatar
con committed
337
338
    connect(m_commitCurrentAction, SIGNAL(triggered()), this, SLOT(startCommitCurrentFile()));
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
339
    m_commandLocator->appendCommand(command);
con's avatar
con committed
340

hjk's avatar
hjk committed
341
342
    m_deleteAction = new ParameterAction(tr("Delete..."), tr("Delete \"%1\"..."), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_deleteAction, CMD_ID_DELETE_FILE,
343
        globalcontext);
con's avatar
con committed
344
    command->setAttribute(Core::Command::CA_UpdateText);
345
    connect(m_deleteAction, SIGNAL(triggered()), this, SLOT(promptToDeleteCurrentFile()));
con's avatar
con committed
346
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
347
    m_commandLocator->appendCommand(command);
con's avatar
con committed
348

hjk's avatar
hjk committed
349
350
    m_revertAction = new ParameterAction(tr("Revert..."), tr("Revert \"%1\"..."), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_revertAction, CMD_ID_REVERT,
351
        globalcontext);
con's avatar
con committed
352
    command->setAttribute(Core::Command::CA_UpdateText);
353
    connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revertCurrentFile()));
con's avatar
con committed
354
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
355
    m_commandLocator->appendCommand(command);
con's avatar
con committed
356

357
    subversionMenu->addSeparator(globalcontext);
358

hjk's avatar
hjk committed
359
360
    m_diffProjectAction = new ParameterAction(tr("Diff Project"), tr("Diff Project \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_diffProjectAction, CMD_ID_DIFF_PROJECT,
361
362
363
364
        globalcontext);
    command->setAttribute(Core::Command::CA_UpdateText);
    connect(m_diffProjectAction, SIGNAL(triggered()), this, SLOT(diffProject()));
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
365
    m_commandLocator->appendCommand(command);
con's avatar
con committed
366

hjk's avatar
hjk committed
367
368
    m_statusProjectAction = new ParameterAction(tr("Project Status"), tr("Status of Project \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_statusProjectAction, CMD_ID_STATUS,
con's avatar
con committed
369
        globalcontext);
370
371
    command->setAttribute(Core::Command::CA_UpdateText);
    connect(m_statusProjectAction, SIGNAL(triggered()), this, SLOT(projectStatus()));
con's avatar
con committed
372
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
373
    m_commandLocator->appendCommand(command);
con's avatar
con committed
374

hjk's avatar
hjk committed
375
376
    m_logProjectAction = new ParameterAction(tr("Log Project"), tr("Log Project \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_logProjectAction, CMD_ID_PROJECTLOG, globalcontext);
377
378
379
    command->setAttribute(Core::Command::CA_UpdateText);
    connect(m_logProjectAction, SIGNAL(triggered()), this, SLOT(logProject()));
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
380
    m_commandLocator->appendCommand(command);
381

hjk's avatar
hjk committed
382
383
    m_updateProjectAction = new ParameterAction(tr("Update Project"), tr("Update Project \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_updateProjectAction, CMD_ID_UPDATE, globalcontext);
con's avatar
con committed
384
    connect(m_updateProjectAction, SIGNAL(triggered()), this, SLOT(updateProject()));
385
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
386
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
387
    m_commandLocator->appendCommand(command);
con's avatar
con committed
388

hjk's avatar
hjk committed
389
390
    m_commitProjectAction = new ParameterAction(tr("Commit Project"), tr("Commit Project \"%1\""), ParameterAction::EnabledWithParameter, this);
    command = ActionManager::registerAction(m_commitProjectAction, CMD_ID_COMMIT_PROJECT, globalcontext);
391
392
393
394
395
    connect(m_commitProjectAction, SIGNAL(triggered()), this, SLOT(startCommitProject()));
    command->setAttribute(Core::Command::CA_UpdateText);
    subversionMenu->addAction(command);
    m_commandLocator->appendCommand(command);

396
    subversionMenu->addSeparator(globalcontext);
397

398
    m_diffRepositoryAction = new QAction(tr("Diff Repository"), this);
hjk's avatar
hjk committed
399
    command = ActionManager::registerAction(m_diffRepositoryAction, CMD_ID_REPOSITORYDIFF, globalcontext);
400
401
402
403
404
    connect(m_diffRepositoryAction, SIGNAL(triggered()), this, SLOT(diffRepository()));
    subversionMenu->addAction(command);
    m_commandLocator->appendCommand(command);

    m_statusRepositoryAction = new QAction(tr("Repository Status"), this);
hjk's avatar
hjk committed
405
    command = ActionManager::registerAction(m_statusRepositoryAction, CMD_ID_REPOSITORYSTATUS, globalcontext);
406
407
408
409
410
    connect(m_statusRepositoryAction, SIGNAL(triggered()), this, SLOT(statusRepository()));
    subversionMenu->addAction(command);
    m_commandLocator->appendCommand(command);

    m_logRepositoryAction = new QAction(tr("Log Repository"), this);
hjk's avatar
hjk committed
411
    command = ActionManager::registerAction(m_logRepositoryAction, CMD_ID_REPOSITORYLOG, globalcontext);
412
413
    connect(m_logRepositoryAction, SIGNAL(triggered()), this, SLOT(logRepository()));
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
414
    m_commandLocator->appendCommand(command);
415

416
    m_updateRepositoryAction = new QAction(tr("Update Repository"), this);
hjk's avatar
hjk committed
417
    command = ActionManager::registerAction(m_updateRepositoryAction, CMD_ID_REPOSITORYUPDATE, globalcontext);
418
    connect(m_updateRepositoryAction, SIGNAL(triggered()), this, SLOT(updateRepository()));
419
    subversionMenu->addAction(command);
420
    m_commandLocator->appendCommand(command);
421
422

    m_commitAllAction = new QAction(tr("Commit All Files"), this);
hjk's avatar
hjk committed
423
    command = ActionManager::registerAction(m_commitAllAction, CMD_ID_COMMIT_ALL,
424
425
426
        globalcontext);
    connect(m_commitAllAction, SIGNAL(triggered()), this, SLOT(startCommitAll()));
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
427
    m_commandLocator->appendCommand(command);
428

429
    m_describeAction = new QAction(tr("Describe..."), this);
hjk's avatar
hjk committed
430
    command = ActionManager::registerAction(m_describeAction, CMD_ID_DESCRIBE, globalcontext);
431
432
433
    connect(m_describeAction, SIGNAL(triggered()), this, SLOT(slotDescribe()));
    subversionMenu->addAction(command);

434
    m_revertRepositoryAction = new QAction(tr("Revert Repository..."), this);
hjk's avatar
hjk committed
435
    command = ActionManager::registerAction(m_revertRepositoryAction, CMD_ID_REVERT_ALL,
436
437
438
        globalcontext);
    connect(m_revertRepositoryAction, SIGNAL(triggered()), this, SLOT(revertAll()));
    subversionMenu->addAction(command);
Friedemann Kleint's avatar
Friedemann Kleint committed
439
    m_commandLocator->appendCommand(command);
440

con's avatar
con committed
441
    // Actions of the submit editor
442
    Context svncommitcontext(SUBVERSIONCOMMITEDITOR_ID);
con's avatar
con committed
443

hjk's avatar
hjk committed
444
    m_submitCurrentLogAction = new QAction(VcsBaseSubmitEditor::submitIcon(), tr("Commit"), this);
445
    command = ActionManager::registerAction(m_submitCurrentLogAction, SUBMIT_CURRENT, svncommitcontext);
446
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
447
448
    connect(m_submitCurrentLogAction, SIGNAL(triggered()), this, SLOT(submitCurrentLog()));

hjk's avatar
hjk committed
449
    m_submitDiffAction = new QAction(VcsBaseSubmitEditor::diffIcon(), tr("Diff &Selected Files"), this);
450
    command = ActionManager::registerAction(m_submitDiffAction , DIFF_SELECTED, svncommitcontext);
con's avatar
con committed
451
452

    m_submitUndoAction = new QAction(tr("&Undo"), this);
hjk's avatar
hjk committed
453
    command = ActionManager::registerAction(m_submitUndoAction, Core::Constants::UNDO, svncommitcontext);
con's avatar
con committed
454
455

    m_submitRedoAction = new QAction(tr("&Redo"), this);
hjk's avatar
hjk committed
456
    command = ActionManager::registerAction(m_submitRedoAction, Core::Constants::REDO, svncommitcontext);
con's avatar
con committed
457
458
459
460

    return true;
}

461
bool SubversionPlugin::submitEditorAboutToClose()
con's avatar
con committed
462
{
463
    if (!isCommitEditorOpen())
con's avatar
con committed
464
465
        return true;

466
467
    SubversionSubmitEditor *editor = qobject_cast<SubversionSubmitEditor *>(submitEditor());
    QTC_ASSERT(editor, return true);
hjk's avatar
hjk committed
468
    IDocument *editorDocument = editor->document();
469
    QTC_ASSERT(editorDocument, return true);
con's avatar
con committed
470
471
472

    // Submit editor closing. Make it write out the commit message
    // and retrieve files
473
    const QFileInfo editorFile(editorDocument->filePath());
474
    const QFileInfo changeFile(m_commitMessageFileName);
con's avatar
con committed
475
476
477
    if (editorFile.absoluteFilePath() != changeFile.absoluteFilePath())
        return true; // Oops?!

478
479
    // Prompt user. Force a prompt unless submit was actually invoked (that
    // is, the editor was closed or shutdown).
480
    SubversionSettings newSettings = m_settings;
hjk's avatar
hjk committed
481
    const VcsBaseSubmitEditor::PromptSubmitResult answer =
482
483
            editor->promptSubmit(tr("Closing Subversion Editor"),
                                 tr("Do you want to commit the change?"),
484
                                 tr("The commit message check failed. Do you want to commit the change?"),
485
486
                                 newSettings.boolPointer(SubversionSettings::promptOnSubmitKey),
                                 !m_submitActionTriggered);
487
    m_submitActionTriggered = false;
con's avatar
con committed
488
    switch (answer) {
hjk's avatar
hjk committed
489
    case VcsBaseSubmitEditor::SubmitCanceled:
con's avatar
con committed
490
        return false; // Keep editing and change file
hjk's avatar
hjk committed
491
    case VcsBaseSubmitEditor::SubmitDiscarded:
492
        cleanCommitMessageFile();
con's avatar
con committed
493
494
495
496
        return true; // Cancel all
    default:
        break;
    }
497
    setSettings(newSettings); // in case someone turned prompting off
con's avatar
con committed
498
    const QStringList fileList = editor->checkedFiles();
499
    bool closeEditor = true;
con's avatar
con committed
500
501
    if (!fileList.empty()) {
        // get message & commit
hjk's avatar
hjk committed
502
        closeEditor = DocumentManager::saveDocument(editorDocument);
503
        if (closeEditor) {
hjk's avatar
hjk committed
504
505
506
            VcsCommand *commitCmd = m_client->createCommitCmd(m_commitRepository,
                                                              fileList,
                                                              m_commitMessageFileName);
507
508
            QObject::connect(commitCmd, &VcsCommand::finished,
                             this, [this]() { cleanCommitMessageFile(); });
509
510
            commitCmd->execute();
        }
con's avatar
con committed
511
    }
512
    return closeEditor;
con's avatar
con committed
513
514
}

515
void SubversionPlugin::diffCommitFiles(const QStringList &files)
con's avatar
con committed
516
{
517
    m_client->diff(m_commitRepository, files);
518
519
}

con's avatar
con committed
520
521
SubversionSubmitEditor *SubversionPlugin::openSubversionSubmitEditor(const QString &fileName)
{
522
    IEditor *editor = EditorManager::openEditor(fileName, SUBVERSIONCOMMITEDITOR_ID);
con's avatar
con committed
523
    SubversionSubmitEditor *submitEditor = qobject_cast<SubversionSubmitEditor*>(editor);
524
    QTC_ASSERT(submitEditor, return 0);
525
    setSubmitEditor(submitEditor);
526
    submitEditor->registerActions(m_submitUndoAction, m_submitRedoAction, m_submitCurrentLogAction, m_submitDiffAction);
527
    connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(diffCommitFiles(QStringList)));
528
    submitEditor->setCheckScriptWorkingDirectory(m_commitRepository);
con's avatar
con committed
529
530
531
    return submitEditor;
}

hjk's avatar
hjk committed
532
void SubversionPlugin::updateActions(VcsBasePlugin::ActionState as)
con's avatar
con committed
533
{
Friedemann Kleint's avatar
Friedemann Kleint committed
534
535
    if (!enableMenuAction(as, m_menuAction)) {
        m_commandLocator->setEnabled(false);
536
        return;
Friedemann Kleint's avatar
Friedemann Kleint committed
537
538
539
540
    }
    const bool hasTopLevel = currentState().hasTopLevel();
    m_commandLocator->setEnabled(hasTopLevel);
    m_logRepositoryAction->setEnabled(hasTopLevel);
541

542
543
544
545
    const QString projectName = currentState().currentProjectName();
    m_diffProjectAction->setParameter(projectName);
    m_statusProjectAction->setParameter(projectName);
    m_updateProjectAction->setParameter(projectName);
546
    m_logProjectAction->setParameter(projectName);
547
    m_commitProjectAction->setParameter(projectName);
548
549
550
551

    const bool repoEnabled = currentState().hasTopLevel();
    m_commitAllAction->setEnabled(repoEnabled);
    m_describeAction->setEnabled(repoEnabled);
552
    m_revertRepositoryAction->setEnabled(repoEnabled);
553
554
555
    m_diffRepositoryAction->setEnabled(repoEnabled);
    m_statusRepositoryAction->setEnabled(repoEnabled);
    m_updateRepositoryAction->setEnabled(repoEnabled);
556
557
558
559
560
561
562
563
564
565

    const QString fileName = currentState().currentFileName();

    m_addAction->setParameter(fileName);
    m_deleteAction->setParameter(fileName);
    m_revertAction->setParameter(fileName);
    m_diffCurrentAction->setParameter(fileName);
    m_commitCurrentAction->setParameter(fileName);
    m_filelogCurrentAction->setParameter(fileName);
    m_annotateCurrentAction->setParameter(fileName);
con's avatar
con committed
566
567
568
569
}

void SubversionPlugin::addCurrentFile()
{
hjk's avatar
hjk committed
570
    const VcsBasePluginState state = currentState();
571
    QTC_ASSERT(state.hasFile(), return);
572
    vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
con's avatar
con committed
573
574
}

575
576
void SubversionPlugin::revertAll()
{
hjk's avatar
hjk committed
577
    const VcsBasePluginState state = currentState();
578
    QTC_ASSERT(state.hasTopLevel(), return);
579
    const QString title = tr("Revert repository");
hjk's avatar
hjk committed
580
    if (QMessageBox::warning(ICore::dialogParent(), title,
581
                             tr("Revert all pending changes to the repository?"),
582
583
584
585
586
                             QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
        return;
    // NoteL: Svn "revert ." doesn not work.
    QStringList args;
    args << QLatin1String("revert") << QLatin1String("--recursive") << state.topLevel();
587
    const SubversionResponse revertResponse =
588
            runSvn(state.topLevel(), args, m_settings.timeOutMs(),
589
                   SshPasswordPrompt|ShowStdOutInLogWindow);
590
    if (revertResponse.error)
hjk's avatar
hjk committed
591
        QMessageBox::warning(ICore::dialogParent(), title,
592
                             tr("Revert failed: %1").arg(revertResponse.message), QMessageBox::Ok);
593
    else
594
595
596
        subVersionControl()->emitRepositoryChanged(state.topLevel());
}

con's avatar
con committed
597
598
void SubversionPlugin::revertCurrentFile()
{
hjk's avatar
hjk committed
599
    const VcsBasePluginState state = currentState();
600
    QTC_ASSERT(state.hasFile(), return);
con's avatar
con committed
601
602

    QStringList args(QLatin1String("diff"));
603
    args.push_back(state.relativeCurrentFile());
con's avatar
con committed
604

605
    const SubversionResponse diffResponse =
606
            runSvn(state.currentFileTopLevel(), args, m_settings.timeOutMs(), 0);
con's avatar
con committed
607
608
609
610
611
    if (diffResponse.error)
        return;

    if (diffResponse.stdOut.isEmpty())
        return;
hjk's avatar
hjk committed
612
    if (QMessageBox::warning(ICore::dialogParent(), QLatin1String("svn revert"),
613
                             tr("The file has been changed. Do you want to revert it?"),
con's avatar
con committed
614
615
616
                             QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
        return;

617

hjk's avatar
hjk committed
618
    FileChangeBlocker fcb(state.currentFile());
con's avatar
con committed
619
620
621

    // revert
    args.clear();
622
    args << QLatin1String("revert") << state.relativeCurrentFile();
con's avatar
con committed
623

624
    const SubversionResponse revertResponse =
625
            runSvn(state.currentFileTopLevel(), args, m_settings.timeOutMs(),
626
627
                   SshPasswordPrompt|ShowStdOutInLogWindow);

628
    if (!revertResponse.error)
629
        subVersionControl()->emitFilesChanged(QStringList(state.currentFile()));
con's avatar
con committed
630
631
632
633
}

void SubversionPlugin::diffProject()
{
hjk's avatar
hjk committed
634
    const VcsBasePluginState state = currentState();
635
    QTC_ASSERT(state.hasProject(), return);
636
637
638
    const QString relativeProject = state.relativeCurrentProject();
    m_client->diff(state.currentProjectTopLevel(),
                      relativeProject.isEmpty() ? QStringList() : QStringList(relativeProject));
con's avatar
con committed
639
640
641
642
}

void SubversionPlugin::diffCurrentFile()
{
hjk's avatar
hjk committed
643
    const VcsBasePluginState state = currentState();
644
    QTC_ASSERT(state.hasFile(), return);
645
    m_client->diff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
con's avatar
con committed
646
647
648
649
}

void SubversionPlugin::startCommitCurrentFile()
{
hjk's avatar
hjk committed
650
    const VcsBasePluginState state = currentState();
651
    QTC_ASSERT(state.hasFile(), return);
652
    startCommit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
con's avatar
con committed
653
654
655
656
}

void SubversionPlugin::startCommitAll()
{
hjk's avatar
hjk committed
657
    const VcsBasePluginState state = currentState();
658
659
    QTC_ASSERT(state.hasTopLevel(), return);
    startCommit(state.topLevel());
con's avatar
con committed
660
661
}

662
663
void SubversionPlugin::startCommitProject()
{
hjk's avatar
hjk committed
664
    const VcsBasePluginState state = currentState();
665
666
667
668
    QTC_ASSERT(state.hasProject(), return);
    startCommit(state.currentProjectPath());
}

con's avatar
con committed
669
670
671
/* Start commit of files of a single repository by displaying
 * template and files in a submit editor. On closing, the real
 * commit will start. */
672
void SubversionPlugin::startCommit(const QString &workingDir, const QStringList &files)
con's avatar
con committed
673
{
674
    if (raiseSubmitEditor())
675
        return;
676
    if (isCommitEditorOpen()) {
677
        VcsOutputWindow::appendWarning(tr("Another commit is currently being executed."));
con's avatar
con committed
678
679
680
681
682
683
        return;
    }

    QStringList args(QLatin1String("status"));
    args += files;

684
    const SubversionResponse response =
685
            runSvn(workingDir, args, m_settings.timeOutMs(), 0);
con's avatar
con committed
686
687
    if (response.error)
        return;
688

con's avatar
con committed
689
    // Get list of added/modified/deleted files
690
    const StatusList statusOutput = parseStatusOutput(response.stdOut);
con's avatar
con committed
691
    if (statusOutput.empty()) {
692
        VcsOutputWindow::appendWarning(tr("There are no modified files."));
con's avatar
con committed
693
694
        return;
    }
695
    m_commitRepository = workingDir;
con's avatar
con committed
696
    // Create a new submit change file containing the submit template
hjk's avatar
hjk committed
697
    TempFileSaver saver;
698
699
    saver.setAutoRemove(false);
    // TODO: Retrieve submit template from
con's avatar
con committed
700
701
    const QString submitTemplate;
    // Create a submit
702
703
    saver.write(submitTemplate.toUtf8());
    if (!saver.finalize()) {
704
        VcsOutputWindow::appendError(saver.errorString());
705
706
707
        return;
    }
    m_commitMessageFileName = saver.fileName();
con's avatar
con committed
708
    // Create a submit editor and set file list
709
    SubversionSubmitEditor *editor = openSubversionSubmitEditor(m_commitMessageFileName);
710
    QTC_ASSERT(editor, return);
711
    editor->setStatusList(statusOutput);
con's avatar
con committed
712
713
714
715
}

void SubversionPlugin::filelogCurrentFile()
{
hjk's avatar
hjk committed
716
    const VcsBasePluginState state = currentState();
717
    QTC_ASSERT(state.hasFile(), return);
718
    filelog(state.currentFileTopLevel(), state.relativeCurrentFile(), true);
719
720
721
722
}

void SubversionPlugin::logProject()
{
hjk's avatar
hjk committed
723
    const VcsBasePluginState state = currentState();
724
    QTC_ASSERT(state.hasProject(), return);
725
726
727
728
729
    filelog(state.currentProjectTopLevel(), state.relativeCurrentProject());
}

void SubversionPlugin::logRepository()
{
hjk's avatar
hjk committed
730
    const VcsBasePluginState state = currentState();
731
    QTC_ASSERT(state.hasTopLevel(), return);
732
    filelog(state.topLevel());
con's avatar
con committed
733
734
}

735
736
void SubversionPlugin::diffRepository()
{
hjk's avatar
hjk committed
737
    const VcsBasePluginState state = currentState();
738
    QTC_ASSERT(state.hasTopLevel(), return);
739
    m_client->diff(state.topLevel(), QStringList());
740
741
742
743
}

void SubversionPlugin::statusRepository()
{
hjk's avatar
hjk committed
744
    const VcsBasePluginState state = currentState();
745
    QTC_ASSERT(state.hasTopLevel(), return);
746
747
748
749
750
    svnStatus(state.topLevel());
}

void SubversionPlugin::updateRepository()
{
hjk's avatar
hjk committed
751
    const VcsBasePluginState state = currentState();
752
    QTC_ASSERT(state.hasTopLevel(), return);
753
754
755
    svnUpdate(state.topLevel());
}

756
void SubversionPlugin::svnStatus(const QString &workingDir, const QString &relativePath)
757
{
hjk's avatar
hjk committed
758
    const VcsBasePluginState state = currentState();
759
    QTC_ASSERT(state.hasTopLevel(), return);
760
    QStringList args(QLatin1String("status"));
761
762
    if (!relativePath.isEmpty())
        args.append(relativePath);
763
    VcsOutputWindow::setRepository(workingDir);
764
    runSvn(workingDir, args, m_settings.timeOutMs(),
765
           ShowStdOutInLogWindow|ShowSuccessMessage);
766
    VcsOutputWindow::clearRepository();
767
768
}

769
void SubversionPlugin::filelog(const QString &workingDir,
770
                               const QString &file,
771
                               bool enableAnnotationContextMenu)
con's avatar
con committed
772
773
774
{
    // no need for temp file
    QStringList args(QLatin1String("log"));
775
776
777
778
    if (m_settings.intValue(SubversionSettings::logCountKey) > 0) {
        args << QLatin1String("-l")
             << QString::number(m_settings.intValue(SubversionSettings::logCountKey));
    }
779
    if (!file.isEmpty())
780
        args.append(QDir::toNativeSeparators(file));
con's avatar
con committed
781

Roman Kovalev's avatar
Roman Kovalev committed
782
783
    // subversion stores log in UTF-8 and returns it back in user system locale.
    // So we do not need to encode it.
784
    const SubversionResponse response =
785
            runSvn(workingDir, args, m_settings.timeOutMs(),
Roman Kovalev's avatar
Roman Kovalev committed
786
                   SshPasswordPrompt, 0/*codec*/);
con's avatar
con committed
787
788
789
790
791
792
    if (response.error)
        return;

    // Re-use an existing view if possible to support
    // the common usage pattern of continuously changing and diffing a file

793
794
    const QString id = VcsBaseEditor::getTitleId(workingDir, QStringList(file));
    const QString tag = VcsBaseEditor::editorTag(LogOutput, workingDir,
795
                                                                QStringList(file));
796
    if (IEditor *editor = VcsBaseEditor::locateEditorByTag(tag)) {
797
        editor->document()->setContents(response.stdOut.toUtf8());
hjk's avatar
hjk committed
798
        EditorManager::activateEditor(editor);
con's avatar
con committed
799
    } else {
800
        const QString title = QString::fromLatin1("svn log %1").arg(id);
801
        const QString source = VcsBaseEditor::getSource(workingDir, file);
hjk's avatar
hjk committed
802
        IEditor *newEditor = showOutputInEditor(title, response.stdOut, LogOutput, source, /*codec*/0);
803
        VcsBaseEditor::tagEditor(newEditor, tag);
804
        if (enableAnnotationContextMenu)
805
            VcsBaseEditor::getVcsBaseEditor(newEditor)->setFileLogAnnotateEnabled(true);
con's avatar
con committed
806
807
808
809
810
    }
}

void SubversionPlugin::updateProject()
{
hjk's avatar
hjk committed
811
    const VcsBasePluginState state = currentState();
812
    QTC_ASSERT(state.hasProject(), return);
813
814
    svnUpdate(state.currentProjectTopLevel(), state.relativeCurrentProject());
}
con's avatar
con committed
815

816
void SubversionPlugin::svnUpdate(const QString &workingDir, const QString &relativePath)
817
{
con's avatar
con committed
818
    QStringList args(QLatin1String("update"));
819
    args.push_back(QLatin1String(Constants::NON_INTERACTIVE_OPTION));
820
821
    if (!relativePath.isEmpty())
        args.append(relativePath);
822
        const SubversionResponse response =
823
                runSvn(workingDir, args, 10 * m_settings.timeOutMs(),
824
                       SshPasswordPrompt|ShowStdOutInLogWindow);
825
    if (!response.error)
826
        subVersionControl()->emitRepositoryChanged(workingDir);
con's avatar
con committed
827
828
829
830
}

void SubversionPlugin::annotateCurrentFile()
{
hjk's avatar
hjk committed
831
    const VcsBasePluginState state = currentState();
832
    QTC_ASSERT(state.hasFile(), return);
833
    vcsAnnotate(state.currentFileTopLevel(), state.relativeCurrentFile());
con's avatar