gitplugin.cpp 31.4 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 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
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
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
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "gitplugin.h"
hjk's avatar
hjk committed
31
32
33

#include "changeselectiondialog.h"
#include "commitdata.h"
con's avatar
con committed
34
35
#include "gitclient.h"
#include "gitconstants.h"
hjk's avatar
hjk committed
36
#include "giteditor.h"
con's avatar
con committed
37
#include "gitsubmiteditor.h"
hjk's avatar
hjk committed
38
#include "gitversioncontrol.h"
39
#include "branchdialog.h"
40
#include "clonewizard.h"
41
#include "gitoriousclonewizard.h"
con's avatar
con committed
42
43
44
45
46
47

#include <coreplugin/icore.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/filemanager.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/uniqueidmanager.h>
48
#include <coreplugin/actionmanager/actionmanager.h>
con's avatar
con committed
49
#include <coreplugin/editormanager/editormanager.h>
hjk's avatar
hjk committed
50
51

#include <utils/qtcassert.h>
52
#include <utils/parameteraction.h>
hjk's avatar
hjk committed
53

con's avatar
con committed
54
55
56
#include <vcsbase/basevcseditorfactory.h>
#include <vcsbase/vcsbaseeditor.h>
#include <vcsbase/basevcssubmiteditorfactory.h>
57
#include <vcsbase/vcsbaseoutputwindow.h>
con's avatar
con committed
58

59
60
61
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/project.h>

con's avatar
con committed
62
#include <QtCore/QDebug>
hjk's avatar
hjk committed
63
#include <QtCore/QDir>
con's avatar
con committed
64
65
#include <QtCore/QFileInfo>
#include <QtCore/QTemporaryFile>
66
#include <QtCore/QtPlugin>
hjk's avatar
hjk committed
67

con's avatar
con committed
68
#include <QtGui/QAction>
hjk's avatar
hjk committed
69
70
#include <QtGui/QFileDialog>
#include <QtGui/QMainWindow>
con's avatar
con committed
71
72
73
74
75
76
77
#include <QtGui/QMenu>
#include <QtGui/QMessageBox>

static const VCSBase::VCSBaseEditorParameters editorParameters[] = {
{
    VCSBase::RegularCommandOutput,
    Git::Constants::GIT_COMMAND_LOG_EDITOR_KIND,
78
    Git::Constants::C_GIT_COMMAND_LOG_EDITOR,
con's avatar
con committed
79
80
81
82
    "application/vnd.nokia.text.scs_git_commandlog",
    "gitlog"},
{   VCSBase::LogOutput,
    Git::Constants::GIT_LOG_EDITOR_KIND,
83
    Git::Constants::C_GIT_LOG_EDITOR,
con's avatar
con committed
84
85
86
87
    "application/vnd.nokia.text.scs_git_filelog",
    "gitfilelog"},
{   VCSBase::AnnotateOutput,
    Git::Constants::GIT_BLAME_EDITOR_KIND,
88
    Git::Constants::C_GIT_BLAME_EDITOR,
con's avatar
con committed
89
90
91
92
    "application/vnd.nokia.text.scs_git_annotation",
    "gitsannotate"},
{   VCSBase::DiffOutput,
    Git::Constants::GIT_DIFF_EDITOR_KIND,
93
    Git::Constants::C_GIT_DIFF_EDITOR,
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
123
124
125
126
127
128
129
    "text/x-patch","diff"}
};

// Utility to find a parameter set by type
static inline const VCSBase::VCSBaseEditorParameters *findType(int ie)
{
    const VCSBase::EditorContentType et = static_cast<VCSBase::EditorContentType>(ie);
    return  VCSBase::VCSBaseEditor::findType(editorParameters, sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters), et);
}

using namespace Git;
using namespace Git::Internal;

// CoreListener

bool CoreListener::editorAboutToClose(Core::IEditor *editor)
{
    return m_plugin->editorAboutToClose(editor);
}

// GitPlugin

GitPlugin *GitPlugin::m_instance = 0;

GitPlugin::GitPlugin() :
    m_core(0),
    m_diffAction(0),
    m_diffProjectAction(0),
    m_statusAction(0),
    m_statusProjectAction(0),
    m_logAction(0),
    m_blameAction(0),
    m_logProjectAction(0),
    m_undoFileAction(0),
    m_undoProjectAction(0),
    m_showAction(0),
130
131
132
    m_stageAction(0),
    m_unstageAction(0),
    m_revertAction(0),
con's avatar
con committed
133
134
135
136
137
138
139
    m_commitAction(0),
    m_pullAction(0),
    m_pushAction(0),
    m_submitCurrentAction(0),
    m_diffSelectedFilesAction(0),
    m_undoAction(0),
    m_redoAction(0),
140
141
142
143
    m_stashAction(0),
    m_stashPopAction(0),
    m_stashListAction(0),
    m_branchListAction(0),
con's avatar
con committed
144
145
    m_gitClient(0),
    m_changeSelectionDialog(0),
146
    m_submitActionTriggered(false)
con's avatar
con committed
147
148
149
150
151
152
{
    m_instance = this;
}

GitPlugin::~GitPlugin()
{
153
    cleanCommitMessageFile();
con's avatar
con committed
154
155
156
157
    delete m_gitClient;
    m_instance = 0;
}

158
void GitPlugin::cleanCommitMessageFile()
con's avatar
con committed
159
{
160
161
162
    if (!m_commitMessageFileName.isEmpty()) {
        QFile::remove(m_commitMessageFileName);
        m_commitMessageFileName.clear();
con's avatar
con committed
163
164
165
    }
}

166
167
168
169
170
bool GitPlugin::isCommitEditorOpen() const
{
    return !m_commitMessageFileName.isEmpty();
}

con's avatar
con committed
171
172
173
174
175
176
177
178
GitPlugin *GitPlugin::instance()
{
    return m_instance;
}

static const VCSBase::VCSBaseSubmitEditorParameters submitParameters = {
    Git::Constants::SUBMIT_MIMETYPE,
    Git::Constants::GITSUBMITEDITOR_KIND,
179
    Git::Constants::C_GITSUBMITEDITOR
con's avatar
con committed
180
181
};

con's avatar
con committed
182
static Core::Command *createSeparator(Core::ActionManager *am,
hjk's avatar
hjk committed
183
184
185
                                       const QList<int> &context,
                                       const QString &id,
                                       QObject *parent)
186
187
188
{
    QAction *a = new QAction(parent);
    a->setSeparator(true);
189
    return am->registerAction(a, id, context);
190
}
con's avatar
con committed
191

192
bool GitPlugin::initialize(const QStringList &arguments, QString *errorMessage)
con's avatar
con committed
193
194
195
196
{
    typedef VCSBase::VCSEditorFactory<GitEditor> GitEditorFactory;
    typedef VCSBase::VCSSubmitEditorFactory<GitSubmitEditor> GitSubmitEditorFactory;

197
198
    Q_UNUSED(arguments)
    Q_UNUSED(errorMessage)
con's avatar
con committed
199

200
    m_core = Core::ICore::instance();
201
    m_gitClient = new GitClient(this);
202
    // Create the globalco6664324b12a3339d18251df1cd69a1da06d1e2dcntext list to register actions accordingly
con's avatar
con committed
203
    QList<int> globalcontext;
204
    globalcontext << m_core->uniqueIDManager()->uniqueIdentifier(Core::Constants::C_GLOBAL);
con's avatar
con committed
205
206

    // Create the settings Page
207
    addAutoReleasedObject(new SettingsPage());
con's avatar
con committed
208
209
210

    static const char *describeSlot = SLOT(show(QString,QString));
    const int editorCount = sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters);
211
212
    for (int i = 0; i < editorCount; i++)
        addAutoReleasedObject(new GitEditorFactory(editorParameters + i, m_gitClient, describeSlot));
con's avatar
con committed
213

214
    addAutoReleasedObject(new CoreListener(this));
con's avatar
con committed
215

216
    addAutoReleasedObject(new GitSubmitEditorFactory(&submitParameters));
con's avatar
con committed
217

218
219
220
221
    GitVersionControl *versionControl = new GitVersionControl(m_gitClient);
    addAutoReleasedObject(versionControl);

    addAutoReleasedObject(new CloneWizard);
222
    addAutoReleasedObject(new Gitorious::Internal::GitoriousCloneWizard);
223

con's avatar
con committed
224
    //register actions
225
    Core::ActionManager *actionManager = m_core->actionManager();
con's avatar
con committed
226

227
    Core::ActionContainer *toolsContainer =
con's avatar
con committed
228
229
        actionManager->actionContainer(Core::Constants::M_TOOLS);

230
    Core::ActionContainer *gitContainer =
con's avatar
con committed
231
232
233
        actionManager->createMenu(QLatin1String("Git"));
    gitContainer->menu()->setTitle(tr("&Git"));
    toolsContainer->addMenu(gitContainer);
234
    if (QAction *ma = gitContainer->menu()->menuAction()) {
235
236
        ma->setEnabled(versionControl->isEnabled());
        connect(versionControl, SIGNAL(enabledChanged(bool)), ma, SLOT(setVisible(bool)));
237
    }
con's avatar
con committed
238

con's avatar
con committed
239
    Core::Command *command;
con's avatar
con committed
240

241
    m_diffAction = new Core::Utils::ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), Core::Utils::ParameterAction::AlwaysEnabled, this);
con's avatar
con committed
242
    command = actionManager->registerAction(m_diffAction, "Git.Diff", globalcontext);
con's avatar
con committed
243
    command->setAttribute(Core::Command::CA_UpdateText);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
244
#ifndef Q_WS_MAC
con's avatar
con committed
245
    command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+D")));
246
#endif
con's avatar
con committed
247
248
249
    connect(m_diffAction, SIGNAL(triggered()), this, SLOT(diffCurrentFile()));
    gitContainer->addAction(command);

250
    m_statusAction = new Core::Utils::ParameterAction(tr("File Status"), tr("Status Related to \"%1\""), Core::Utils::ParameterAction::AlwaysEnabled, this);
con's avatar
con committed
251
    command = actionManager->registerAction(m_statusAction, "Git.Status", globalcontext);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
252
#ifndef Q_WS_MAC
con's avatar
con committed
253
    command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+S")));
254
#endif
con's avatar
con committed
255
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
256
257
258
    connect(m_statusAction, SIGNAL(triggered()), this, SLOT(statusFile()));
    gitContainer->addAction(command);

259
    m_logAction = new Core::Utils::ParameterAction(tr("Log File"), tr("Log of \"%1\""), Core::Utils::ParameterAction::AlwaysEnabled, this);
con's avatar
con committed
260
    command = actionManager->registerAction(m_logAction, "Git.Log", globalcontext);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
261
#ifndef Q_WS_MAC
con's avatar
con committed
262
    command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+L")));
263
#endif
con's avatar
con committed
264
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
265
266
267
    connect(m_logAction, SIGNAL(triggered()), this, SLOT(logFile()));
    gitContainer->addAction(command);

268
    m_blameAction = new Core::Utils::ParameterAction(tr("Blame"), tr("Blame for \"%1\""), Core::Utils::ParameterAction::AlwaysEnabled, this);
con's avatar
con committed
269
    command = actionManager->registerAction(m_blameAction, "Git.Blame", globalcontext);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
270
#ifndef Q_WS_MAC
con's avatar
con committed
271
    command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+B")));
272
#endif
con's avatar
con committed
273
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
274
275
276
    connect(m_blameAction, SIGNAL(triggered()), this, SLOT(blameFile()));
    gitContainer->addAction(command);

277
    m_undoFileAction = new Core::Utils::ParameterAction(tr("Undo Changes"), tr("Undo Changes for \"%1\""),  Core::Utils::ParameterAction::AlwaysEnabled, this);
con's avatar
con committed
278
    command = actionManager->registerAction(m_undoFileAction, "Git.Undo", globalcontext);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
279
#ifndef Q_WS_MAC
con's avatar
con committed
280
    command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+U")));
281
#endif
con's avatar
con committed
282
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
283
284
285
    connect(m_undoFileAction, SIGNAL(triggered()), this, SLOT(undoFileChanges()));
    gitContainer->addAction(command);

286
    m_stageAction = new Core::Utils::ParameterAction(tr("Stage File for Commit"), tr("Stage \"%1\" for Commit"), Core::Utils::ParameterAction::AlwaysEnabled, this);
287
    command = actionManager->registerAction(m_stageAction, "Git.Stage", globalcontext);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
288
#ifndef Q_WS_MAC
con's avatar
con committed
289
    command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+A")));
290
#endif
con's avatar
con committed
291
    command->setAttribute(Core::Command::CA_UpdateText);
292
293
294
    connect(m_stageAction, SIGNAL(triggered()), this, SLOT(stageFile()));
    gitContainer->addAction(command);

295
    m_unstageAction = new Core::Utils::ParameterAction(tr("Unstage File from Commit"), tr("Unstage \"%1\" from Commit"), Core::Utils::ParameterAction::AlwaysEnabled, this);
296
    command = actionManager->registerAction(m_unstageAction, "Git.Unstage", globalcontext);
con's avatar
con committed
297
    command->setAttribute(Core::Command::CA_UpdateText);
298
299
300
    connect(m_unstageAction, SIGNAL(triggered()), this, SLOT(unstageFile()));
    gitContainer->addAction(command);

301
    m_revertAction = new Core::Utils::ParameterAction(tr("Revert..."), tr("Revert \"%1\"..."), Core::Utils::ParameterAction::AlwaysEnabled, this);
302
    command = actionManager->registerAction(m_revertAction, "Git.Revert", globalcontext);
con's avatar
con committed
303
    command->setAttribute(Core::Command::CA_UpdateText);
304
    connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revertFile()));
con's avatar
con committed
305
306
    gitContainer->addAction(command);

307
    gitContainer->addAction(createSeparator(actionManager, globalcontext, QLatin1String("Git.Sep.Project"), this));
con's avatar
con committed
308

309
    m_diffProjectAction = new Core::Utils::ParameterAction(tr("Diff Current Project"), tr("Diff Project \"%1\""), Core::Utils::ParameterAction::AlwaysEnabled, this);
con's avatar
con committed
310
    command = actionManager->registerAction(m_diffProjectAction, "Git.DiffProject", globalcontext);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
311
#ifndef Q_WS_MAC
con's avatar
con committed
312
    command->setDefaultKeySequence(QKeySequence("Alt+G,Alt+Shift+D"));
313
#endif
con's avatar
con committed
314
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
315
316
317
    connect(m_diffProjectAction, SIGNAL(triggered()), this, SLOT(diffCurrentProject()));
    gitContainer->addAction(command);

318
    m_statusProjectAction = new Core::Utils::ParameterAction(tr("Project Status"), tr("Status Project \"%1\""), Core::Utils::ParameterAction::AlwaysEnabled, this);
con's avatar
con committed
319
    command = actionManager->registerAction(m_statusProjectAction, "Git.StatusProject", globalcontext);
con's avatar
con committed
320
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
321
322
323
    connect(m_statusProjectAction, SIGNAL(triggered()), this, SLOT(statusProject()));
    gitContainer->addAction(command);

324
    m_logProjectAction = new Core::Utils::ParameterAction(tr("Log Project"), tr("Log Project \"%1\""), Core::Utils::ParameterAction::AlwaysEnabled, this);
con's avatar
con committed
325
    command = actionManager->registerAction(m_logProjectAction, "Git.LogProject", globalcontext);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
326
#ifndef Q_WS_MAC
con's avatar
con committed
327
    command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+K")));
328
#endif
con's avatar
con committed
329
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
330
331
332
333
334
    connect(m_logProjectAction, SIGNAL(triggered()), this, SLOT(logProject()));
    gitContainer->addAction(command);

    m_undoProjectAction = new QAction(tr("Undo Project Changes"), this);
    command = actionManager->registerAction(m_undoProjectAction, "Git.UndoProject", globalcontext);
con's avatar
con committed
335
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
336
337
338
    connect(m_undoProjectAction, SIGNAL(triggered()), this, SLOT(undoProjectChanges()));
    gitContainer->addAction(command);

339
340
341
    gitContainer->addAction(createSeparator(actionManager, globalcontext, QLatin1String("Git.Sep.Global"), this));

    m_stashAction = new QAction(tr("Stash"), this);
342
    m_stashAction->setToolTip(tr("Saves the current state of your work."));
343
    command = actionManager->registerAction(m_stashAction, "Git.Stash", globalcontext);
con's avatar
con committed
344
    command->setAttribute(Core::Command::CA_UpdateText);
345
    connect(m_stashAction, SIGNAL(triggered()), this, SLOT(stash()));
con's avatar
con committed
346
347
    gitContainer->addAction(command);

348
349
    m_pullAction = new QAction(tr("Pull"), this);
    command = actionManager->registerAction(m_pullAction, "Git.Pull", globalcontext);
con's avatar
con committed
350
    command->setAttribute(Core::Command::CA_UpdateText);
351
352
353
    connect(m_pullAction, SIGNAL(triggered()), this, SLOT(pull()));
    gitContainer->addAction(command);

354
    m_stashPopAction = new QAction(tr("Stash Pop"), this);
355
    m_stashAction->setToolTip(tr("Restores changes saved to the stash list using \"Stash\"."));
356
    command = actionManager->registerAction(m_stashPopAction, "Git.StashPop", globalcontext);
con's avatar
con committed
357
    command->setAttribute(Core::Command::CA_UpdateText);
358
    connect(m_stashPopAction, SIGNAL(triggered()), this, SLOT(stashPop()));
con's avatar
con committed
359
360
361
362
    gitContainer->addAction(command);

    m_commitAction = new QAction(tr("Commit..."), this);
    command = actionManager->registerAction(m_commitAction, "Git.Commit", globalcontext);
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
363
#ifndef Q_WS_MAC
con's avatar
con committed
364
    command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+C")));
365
#endif
con's avatar
con committed
366
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
367
368
369
370
371
    connect(m_commitAction, SIGNAL(triggered()), this, SLOT(startCommit()));
    gitContainer->addAction(command);

    m_pushAction = new QAction(tr("Push"), this);
    command = actionManager->registerAction(m_pushAction, "Git.Push", globalcontext);
con's avatar
con committed
372
    command->setAttribute(Core::Command::CA_UpdateText);
con's avatar
con committed
373
374
375
    connect(m_pushAction, SIGNAL(triggered()), this, SLOT(push()));
    gitContainer->addAction(command);

376
377
    gitContainer->addAction(createSeparator(actionManager, globalcontext, QLatin1String("Git.Sep.Branch"), this));

378
    m_branchListAction = new QAction(tr("Branches..."), this);
379
    command = actionManager->registerAction(m_branchListAction, "Git.BranchList", globalcontext);
con's avatar
con committed
380
    command->setAttribute(Core::Command::CA_UpdateText);
381
382
383
    connect(m_branchListAction, SIGNAL(triggered()), this, SLOT(branchList()));
    gitContainer->addAction(command);

384
    m_stashListAction = new QAction(tr("List Stashes"), this);
385
    command = actionManager->registerAction(m_stashListAction, "Git.StashList", globalcontext);
con's avatar
con committed
386
    command->setAttribute(Core::Command::CA_UpdateText);
387
388
389
    connect(m_stashListAction, SIGNAL(triggered()), this, SLOT(stashList()));
    gitContainer->addAction(command);

390
    m_showAction = new QAction(tr("Show Commit..."), this);
391
    command = actionManager->registerAction(m_showAction, "Git.ShowCommit", globalcontext);
con's avatar
con committed
392
    command->setAttribute(Core::Command::CA_UpdateText);
393
394
395
    connect(m_showAction, SIGNAL(triggered()), this, SLOT(showCommit()));
    gitContainer->addAction(command);

con's avatar
con committed
396
397
398
    // Submit editor
    QList<int> submitContext;
    submitContext.push_back(m_core->uniqueIDManager()->uniqueIdentifier(QLatin1String(Constants::C_GITSUBMITEDITOR)));
399
    m_submitCurrentAction = new QAction(VCSBase::VCSBaseSubmitEditor::submitIcon(), tr("Commit"), this);
con's avatar
con committed
400
401
402
    command = actionManager->registerAction(m_submitCurrentAction, Constants::SUBMIT_CURRENT, submitContext);
    connect(m_submitCurrentAction, SIGNAL(triggered()), this, SLOT(submitCurrentLog()));

403
    m_diffSelectedFilesAction = new QAction(VCSBase::VCSBaseSubmitEditor::diffIcon(), tr("Diff Selected Files"), this);
con's avatar
con committed
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
    command = actionManager->registerAction(m_diffSelectedFilesAction, Constants::DIFF_SELECTED, submitContext);

    m_undoAction = new QAction(tr("&Undo"), this);
    command = actionManager->registerAction(m_undoAction, Core::Constants::UNDO, submitContext);

    m_redoAction = new QAction(tr("&Redo"), this);
    command = actionManager->registerAction(m_redoAction, Core::Constants::REDO, submitContext);

    // Ask for updates of our actions, in case context switches
    connect(m_core, SIGNAL(contextChanged(Core::IContext *)),
        this, SLOT(updateActions()));
    connect(m_core->fileManager(), SIGNAL(currentFileChanged(const QString &)),
        this, SLOT(updateActions()));

    return true;
}

void GitPlugin::extensionsInitialized()
{
}

425
void GitPlugin::submitEditorDiff(const QStringList &unstaged, const QStringList &staged)
con's avatar
con committed
426
{
427
    m_gitClient->diff(m_submitRepository, QStringList(), unstaged, staged);
con's avatar
con committed
428
429
430
431
}

void GitPlugin::diffCurrentFile()
{
432
433
434
435
    const QFileInfo fileInfo = currentFile();
    const QString fileName = fileInfo.fileName();
    const QString workingDirectory = fileInfo.absolutePath();
    m_gitClient->diff(workingDirectory, QStringList(), fileName);
con's avatar
con committed
436
437
438
439
440
441
442
}

void GitPlugin::diffCurrentProject()
{
    QString workingDirectory = getWorkingDirectory();
    if (workingDirectory.isEmpty())
        return;
443
    m_gitClient->diff(workingDirectory, QStringList(), QString());
con's avatar
con committed
444
445
}

446
QFileInfo GitPlugin::currentFile() const
con's avatar
con committed
447
448
449
450
451
452
453
454
455
{
    QString fileName = m_core->fileManager()->currentFile();
    QFileInfo fileInfo(fileName);
    return fileInfo;
}

QString GitPlugin::getWorkingDirectory()
{
    QString workingDirectory;
456
457
458
459
    if (const ProjectExplorer::ProjectExplorerPlugin *p = ProjectExplorer::ProjectExplorerPlugin::instance())
        if (p && p->currentNode())
            workingDirectory = QFileInfo(p->currentNode()->path()).absolutePath();

con's avatar
con committed
460
461
462
463
464
465
466
467
468
    if (Git::Constants::debug > 1)
        qDebug() << Q_FUNC_INFO << "Project" << workingDirectory;

    if (workingDirectory.isEmpty())
        workingDirectory = QFileInfo(m_core->fileManager()->currentFile()).absolutePath();
    if (Git::Constants::debug > 1)
        qDebug() << Q_FUNC_INFO << "file" << workingDirectory;

    if (workingDirectory.isEmpty()) {
469
        VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Could not find working directory"));
con's avatar
con committed
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
        return QString();
    }
    return workingDirectory;
}

void GitPlugin::statusProject()
{
    QString workingDirectory = getWorkingDirectory();
    if (workingDirectory.isEmpty())
        return;
    m_gitClient->status(workingDirectory);
}

void GitPlugin::statusFile()
{
    m_gitClient->status(currentFile().absolutePath());
}

void GitPlugin::logFile()
{
    const QFileInfo fileInfo = currentFile();
    const QString fileName = fileInfo.fileName();
    const QString workingDirectory = fileInfo.absolutePath();
    m_gitClient->log(workingDirectory, fileName);
}

void GitPlugin::blameFile()
{
    const QFileInfo fileInfo = currentFile();
    const QString fileName = fileInfo.fileName();
    const QString workingDirectory = fileInfo.absolutePath();
    m_gitClient->blame(workingDirectory, fileName);
}

void GitPlugin::logProject()
{
    QString workingDirectory = getWorkingDirectory();
    if (workingDirectory.isEmpty())
        return;
    m_gitClient->log(workingDirectory, QString());
}

void GitPlugin::undoFileChanges()
{
514
    const QFileInfo fileInfo = currentFile();
515
516
517
    Core::FileChangeBlocker fcb(fileInfo.filePath());
    fcb.setModifiedReload(true);

518
    m_gitClient->revert(QStringList(fileInfo.absoluteFilePath()));
con's avatar
con committed
519
520
521
522
}

void GitPlugin::undoProjectChanges()
{
523
    const QString workingDirectory = getWorkingDirectory();
con's avatar
con committed
524
525
    if (workingDirectory.isEmpty())
        return;
526
527
528
529
530
531
532
533
    const QMessageBox::StandardButton answer
            = QMessageBox::question(m_core->mainWindow(),
                                    tr("Revert"),
                                    tr("Would you like to revert all pending changes to the project?"),
                                    QMessageBox::Yes|QMessageBox::No,
                                    QMessageBox::No);
    if (answer == QMessageBox::No)
        return;
con's avatar
con committed
534
535
536
    m_gitClient->hardReset(workingDirectory, QString());
}

537
void GitPlugin::stageFile()
con's avatar
con committed
538
{
539
540
541
    const QFileInfo fileInfo = currentFile();
    const QString fileName = fileInfo.fileName();
    const QString workingDirectory = fileInfo.absolutePath();
con's avatar
con committed
542
543
544
    m_gitClient->addFile(workingDirectory, fileName);
}

545
546
547
548
549
550
551
552
void GitPlugin::unstageFile()
{
    const QFileInfo fileInfo = currentFile();
    const QString fileName = fileInfo.fileName();
    const QString workingDirectory = fileInfo.absolutePath();
    m_gitClient->synchronousReset(workingDirectory, QStringList(fileName));
}

con's avatar
con committed
553
554
void GitPlugin::startCommit()
{
555
556
    if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor())
        return;
557
    if (isCommitEditorOpen()) {
558
        VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Another submit is currently being executed."));
con's avatar
con committed
559
560
561
562
563
564
565
566
567
568
569
570
        return;
    }

    // Find repository and get commit data
    const QFileInfo currentFileInfo = currentFile();
    if (!currentFileInfo.exists())
        return;

    const QString workingDirectory = currentFileInfo.absolutePath();
    QString errorMessage, commitTemplate;
    CommitData data;
    if (!m_gitClient->getCommitData(workingDirectory, &commitTemplate, &data, &errorMessage)) {
571
        VCSBase::VCSBaseOutputWindow::instance()->append(errorMessage);
con's avatar
con committed
572
573
574
        return;
    }

575
576
    // Store repository for diff and the original list of
    // files to be able to unstage files the user unchecks
con's avatar
con committed
577
    m_submitRepository = data.panelInfo.repository;
578
    m_submitOrigCommitFiles = data.stagedFileNames();
579
    m_submitOrigDeleteFiles = data.stagedFileNames("deleted");
con's avatar
con committed
580
581
582
583
584

    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << data << commitTemplate;

    // Start new temp file with message template
585
586
587
588
    QTemporaryFile changeTmpFile;
    changeTmpFile.setAutoRemove(false);
    if (!changeTmpFile.open()) {
        VCSBase::VCSBaseOutputWindow::instance()->append(tr("Cannot create temporary file: %1").arg(changeTmpFile.errorString()));
con's avatar
con committed
589
590
        return;
    }
591
592
593
    m_commitMessageFileName = changeTmpFile.fileName();
    changeTmpFile.write(commitTemplate.toLocal8Bit());
    changeTmpFile.flush();
con's avatar
con committed
594
595
    // Keep the file alive, else it removes self and forgets
    // its name
596
597
    changeTmpFile.close();
    openSubmitEditor(m_commitMessageFileName, data);
con's avatar
con committed
598
599
600
601
}

Core::IEditor *GitPlugin::openSubmitEditor(const QString &fileName, const CommitData &cd)
{
602
    Core::IEditor *editor = m_core->editorManager()->openEditor(fileName, QLatin1String(Constants::GITSUBMITEDITOR_KIND));
con's avatar
con committed
603
604
605
606
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << fileName << editor;
    m_core->editorManager()->ensureEditorManagerVisible();
    GitSubmitEditor *submitEditor = qobject_cast<GitSubmitEditor*>(editor);
hjk's avatar
hjk committed
607
    QTC_ASSERT(submitEditor, return 0);
con's avatar
con committed
608
609
    // The actions are for some reason enabled by the context switching
    // mechanism. Disable them correctly.
610
    submitEditor->registerActions(m_undoAction, m_redoAction, m_submitCurrentAction, m_diffSelectedFilesAction);
con's avatar
con committed
611
    submitEditor->setCommitData(cd);
612
    connect(submitEditor, SIGNAL(diff(QStringList,QStringList)), this, SLOT(submitEditorDiff(QStringList,QStringList)));
con's avatar
con committed
613
614
615
616
617
618
    return editor;
}

void GitPlugin::submitCurrentLog()
{
    // Close the submit editor
619
    m_submitActionTriggered = true;
con's avatar
con committed
620
621
622
623
624
625
626
627
    QList<Core::IEditor*> editors;
    editors.push_back(m_core->editorManager()->currentEditor());
    m_core->editorManager()->closeEditors(editors);
}

bool GitPlugin::editorAboutToClose(Core::IEditor *iEditor)
{
    // Closing a submit editor?
628
    if (!iEditor || !isCommitEditorOpen() || qstrcmp(iEditor->kind(), Constants::GITSUBMITEDITOR_KIND))
con's avatar
con committed
629
630
631
632
633
634
635
636
        return true;
    Core::IFile *fileIFace = iEditor->file();
    const GitSubmitEditor *editor = qobject_cast<GitSubmitEditor *>(iEditor);
    if (!fileIFace || !editor)
        return true;
    // Submit editor closing. Make it write out the commit message
    // and retrieve files
    const QFileInfo editorFile(fileIFace->fileName());
637
    const QFileInfo changeFile(m_commitMessageFileName);
con's avatar
con committed
638
639
640
    // Paranoia!
    if (editorFile.absoluteFilePath() != changeFile.absoluteFilePath())
        return true;
641
642
    // Prompt user. Force a prompt unless submit was actually invoked (that
    // is, the editor was closed or shutdown).
643
644
    GitSettings settings = m_gitClient->settings();
    const bool wantedPrompt = settings.promptToSubmit;
645
646
647
    const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer =
            editor->promptSubmit(tr("Closing git editor"),
                                 tr("Do you want to commit the change?"),
648
                                 tr("The commit message check failed. Do you want to commit the change?"),
649
650
                                 &settings.promptToSubmit, !m_submitActionTriggered);
    m_submitActionTriggered = false;    
con's avatar
con committed
651
    switch (answer) {
652
    case VCSBase::VCSBaseSubmitEditor::SubmitCanceled:
con's avatar
con committed
653
        return false; // Keep editing and change file
654
    case VCSBase::VCSBaseSubmitEditor::SubmitDiscarded:
655
        cleanCommitMessageFile();
con's avatar
con committed
656
657
658
659
        return true; // Cancel all
    default:
        break;
    }
660
661
    if (wantedPrompt != settings.promptToSubmit)
        m_gitClient->setSettings(settings);
con's avatar
con committed
662
663
664
665
    // Go ahead!
    const QStringList fileList = editor->checkedFiles();
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << fileList;
666
    bool closeEditor = true;
con's avatar
con committed
667
668
669
670
671
672
    if (!fileList.empty()) {
        // get message & commit
        m_core->fileManager()->blockFileChange(fileIFace);
        fileIFace->save();
        m_core->fileManager()->unblockFileChange(fileIFace);

673
674
        closeEditor = m_gitClient->addAndCommit(m_submitRepository,
                                                editor->panelData(),
675
                                                m_commitMessageFileName,
676
                                                fileList,
677
678
                                                m_submitOrigCommitFiles,
                                                m_submitOrigDeleteFiles);
con's avatar
con committed
679
    }
680
    if (closeEditor)
681
        cleanCommitMessageFile();
682
    return closeEditor;
con's avatar
con committed
683
684
685
686
}

void GitPlugin::pull()
{
687
    const QString workingDirectory = getWorkingDirectory();
688
689
690
691
692
693
694
695
696
697
698
    if (workingDirectory.isEmpty())
        return;

    switch (m_gitClient->ensureStash(workingDirectory)) {
        case GitClient::StashUnchanged:
        case GitClient::Stashed:
        case GitClient::NotStashed:
            m_gitClient->pull(workingDirectory);
        default:
        break;
    }
con's avatar
con committed
699
700
701
702
}

void GitPlugin::push()
{
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
    const QString workingDirectory = getWorkingDirectory();
    if (!workingDirectory.isEmpty())
        m_gitClient->push(workingDirectory);
}

void GitPlugin::stash()
{
    const QString workingDirectory = getWorkingDirectory();
    if (!workingDirectory.isEmpty())
        m_gitClient->stash(workingDirectory);
}

void GitPlugin::stashPop()
{
    const QString workingDirectory = getWorkingDirectory();
    if (!workingDirectory.isEmpty())
        m_gitClient->stashPop(workingDirectory);
}

void GitPlugin::branchList()
{
    const QString workingDirectory = getWorkingDirectory();
725
726
727
728
729
730
    if (workingDirectory.isEmpty())
        return;
    QString errorMessage;
    BranchDialog dialog(m_core->mainWindow());

    if (!dialog.init(m_gitClient, workingDirectory, &errorMessage)) {
731
        VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
732
733
734
        return;
    }
    dialog.exec();
735
736
737
738
739
740
741
}

void GitPlugin::stashList()
{
    const QString workingDirectory = getWorkingDirectory();
    if (!workingDirectory.isEmpty())
        m_gitClient->stashList(workingDirectory);
con's avatar
con committed
742
743
744
745
}

void GitPlugin::updateActions()
{
746
    const QFileInfo current = currentFile();
con's avatar
con committed
747
748
    const QString fileName = current.fileName();
    const QString currentDirectory = getWorkingDirectory();
749
    const QString repository = m_gitClient->findRepositoryForFile(current.absoluteFilePath());
con's avatar
con committed
750
751
    // First check for file commands and if the current file is inside
    // a Git-repository
752
753
754
755
756
757
758
759
    m_diffAction->setParameter(fileName);
    m_statusAction->setParameter(fileName);
    m_logAction->setParameter(fileName);
    m_blameAction->setParameter(fileName);
    m_undoFileAction->setParameter(fileName);
    m_stageAction->setParameter(fileName);
    m_unstageAction->setParameter(fileName);
    m_revertAction->setParameter(fileName);
760
761
762
763
764
765
766
767
768
769
770

    bool enabled = !fileName.isEmpty() && !repository.isEmpty();
    m_diffAction->setEnabled(enabled);
    m_statusAction->setEnabled(enabled);
    m_logAction->setEnabled(enabled);
    m_blameAction->setEnabled(enabled);
    m_undoFileAction->setEnabled(enabled);
    m_stageAction->setEnabled(enabled);
    m_unstageAction->setEnabled(enabled);
    m_revertAction->setEnabled(enabled);

con's avatar
con committed
771
772
773
774
    if (repository.isEmpty()) {
        // If the file is not in a repository, the corresponding project will
        // be neither and we can disable everything and return
        m_diffProjectAction->setEnabled(false);
775
776
        m_diffProjectAction->setParameter(repository);
        m_statusProjectAction->setParameter(repository);
con's avatar
con committed
777
        m_statusProjectAction->setEnabled(false);
778
        m_logProjectAction->setParameter(repository);
con's avatar
con committed
779
780
781
782
        m_logProjectAction->setEnabled(false);
        return;
    }

783
784
785
    // We only know the file is in some repository, we do not know
    // anything about any project so far.
    QString project;
786
787
788
    if (const ProjectExplorer::ProjectExplorerPlugin *p = ProjectExplorer::ProjectExplorerPlugin::instance()) {
        if (const ProjectExplorer::Node *node = p->currentNode())
            if (const ProjectExplorer::Node *projectNode = node->projectNode())
789
                project = QFileInfo(projectNode->path()).completeBaseName();
con's avatar
con committed
790
    }
791
792
793

    enabled = !project.isEmpty();
    m_diffProjectAction->setEnabled(enabled);
794
    m_diffProjectAction->setParameter(project);
795
    m_statusProjectAction->setEnabled(enabled);
796
    m_statusProjectAction->setParameter(project);
797
    m_logProjectAction->setEnabled(enabled);
798
    m_logProjectAction->setParameter(project);
con's avatar
con committed
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
}

void GitPlugin::showCommit()
{
    if (!m_changeSelectionDialog)
        m_changeSelectionDialog = new ChangeSelectionDialog();

    const QFileInfo currentInfo = currentFile();
    QString repositoryLocation = m_gitClient->findRepositoryForFile(currentInfo.absoluteFilePath());
    if (!repositoryLocation.isEmpty())
        m_changeSelectionDialog->m_ui.repositoryEdit->setText(repositoryLocation);

    if (m_changeSelectionDialog->exec() != QDialog::Accepted)
        return;
    const QString change = m_changeSelectionDialog->m_ui.changeNumberEdit->text();
814
    if (change.isEmpty())
con's avatar
con committed
815
816
817
818
819
        return;

    m_gitClient->show(m_changeSelectionDialog->m_ui.repositoryEdit->text(), change);
}

820
821
822
823
824
825
826
827
828
829
GitSettings GitPlugin::settings() const
{
    return m_gitClient->settings();
}

void GitPlugin::setSettings(const GitSettings &s)
{
    m_gitClient->setSettings(s);
}

830
831
832
833
834
GitClient *GitPlugin::gitClient() const
{
    return m_gitClient;
}

con's avatar
con committed
835
Q_EXPORT_PLUGIN(GitPlugin)