Commit 9ac98a40 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

VCS[git]: Add support for stashes.

Add non-modal stash management dialog and additional menu option
"Stash snapshot..." to stash away changes prompting for a description,
which will immediately replay the stash (take snapshot and continue
working).
Add interface to IVersionControl for creating/restoring/deleting
snapshots for backup/complex undo operations (currently supported
by git only). Add test options to VCSBasePlugin.
Clean up and extend git client accordingly.
parent cbaa9b9f
......@@ -41,8 +41,11 @@ class CORE_EXPORT IVersionControl : public QObject
{
Q_OBJECT
public:
enum Operation { AddOperation, DeleteOperation, OpenOperation,
CreateRepositoryOperation };
enum Operation {
AddOperation, DeleteOperation, OpenOperation,
CreateRepositoryOperation,
SnapshotOperations
};
explicit IVersionControl(QObject *parent = 0) : QObject(parent) {}
virtual ~IVersionControl() {}
......@@ -98,10 +101,31 @@ public:
virtual bool vcsDelete(const QString &filename) = 0;
/*!
* Called to initialize the version control systemin a directory.
* Called to initialize the version control system in a directory.
*/
virtual bool vcsCreateRepository(const QString &directory) = 0;
/*!
* Create a snapshot of the current state and return an identifier or
* an empty string in case of failure.
*/
virtual QString vcsCreateSnapshot(const QString &topLevel) = 0;
/*!
* List snapshots.
*/
virtual QStringList vcsSnapshots(const QString &topLevel) = 0;
/*!
* Restore a snapshot.
*/
virtual bool vcsRestoreSnapshot(const QString &topLevel, const QString &name) = 0;
/*!
* Remove a snapshot.
*/
virtual bool vcsRemoveSnapshot(const QString &topLevel, const QString &name) = 0;
signals:
void repositoryChanged(const QString &repository);
void filesChanged(const QStringList &files);
......
......@@ -55,6 +55,7 @@ bool CVSControl::supportsOperation(Operation operation) const
break;
case OpenOperation:
case CreateRepositoryOperation:
case SnapshotOperations:
rc = false;
break;
}
......@@ -84,6 +85,26 @@ bool CVSControl::vcsCreateRepository(const QString &)
return false;
}
QString CVSControl::vcsCreateSnapshot(const QString &)
{
return QString();
}
QStringList CVSControl::vcsSnapshots(const QString &)
{
return QStringList();
}
bool CVSControl::vcsRestoreSnapshot(const QString &, const QString &)
{
return false;
}
bool CVSControl::vcsRemoveSnapshot(const QString &, const QString &)
{
return false;
}
bool CVSControl::managesDirectory(const QString &directory) const
{
return m_plugin->managesDirectory(directory);
......
......@@ -53,6 +53,10 @@ public:
virtual bool vcsAdd(const QString &fileName);
virtual bool vcsDelete(const QString &filename);
virtual bool vcsCreateRepository(const QString &directory);
virtual QString vcsCreateSnapshot(const QString &topLevel);
virtual QStringList vcsSnapshots(const QString &topLevel);
virtual bool vcsRestoreSnapshot(const QString &topLevel, const QString &name);
virtual bool vcsRemoveSnapshot(const QString &topLevel, const QString &name);
void emitRepositoryChanged(const QString &s);
void emitFilesChanged(const QStringList &l);
......
......@@ -22,7 +22,9 @@ HEADERS += gitplugin.h \
branchmodel.h \
gitcommand.h \
clonewizard.h \
clonewizardpage.h
clonewizardpage.h \
stashdialog.h \
gitutils.h
SOURCES += gitplugin.cpp \
gitclient.cpp \
changeselectiondialog.cpp \
......@@ -38,11 +40,13 @@ SOURCES += gitplugin.cpp \
branchmodel.cpp \
gitcommand.cpp \
clonewizard.cpp \
clonewizardpage.cpp
clonewizardpage.cpp \
stashdialog.cpp \
gitutils.cpp
FORMS += changeselectiondialog.ui \
settingspage.ui \
gitsubmitpanel.ui \
branchdialog.ui
branchdialog.ui \
stashdialog.ui
OTHER_FILES += ScmGit.pluginspec
include(gitorious/gitorious.pri)
This diff is collapsed.
......@@ -41,6 +41,7 @@
QT_BEGIN_NAMESPACE
class QErrorMessage;
class QSignalMapper;
class QDebug;
QT_END_NAMESPACE
namespace Core {
......@@ -59,12 +60,15 @@ class GitOutputWindow;
class GitCommand;
struct CommitData;
struct GitSubmitEditorPanelData;
struct Stash;
class GitClient : public QObject
{
Q_OBJECT
public:
static const char *stashNamePrefix;
explicit GitClient(GitPlugin *plugin);
~GitClient();
......@@ -84,17 +88,37 @@ public:
bool enableAnnotationContextMenu = false);
void blame(const QString &workingDirectory, const QString &fileName,
const QString &revision = QString(), int lineNumber = -1);
void showCommit(const QString &workingDirectory, const QString &commit);
void checkout(const QString &workingDirectory, const QString &file);
void checkoutBranch(const QString &workingDirectory, const QString &branch);
void hardReset(const QString &workingDirectory, const QString &commit);
void hardReset(const QString &workingDirectory, const QString &commit = QString());
void addFile(const QString &workingDirectory, const QString &fileName);
bool synchronousAdd(const QString &workingDirectory, const QStringList &files);
bool synchronousReset(const QString &workingDirectory, const QStringList &files);
bool synchronousReset(const QString &workingDirectory, const QStringList &files, QString *errorMessage);
bool synchronousReset(const QString &workingDirectory,
const QStringList &files = QStringList(),
QString *errorMessage = 0);
bool synchronousInit(const QString &workingDirectory);
bool synchronousCheckout(const QString &workingDirectory, const QStringList &files, QString *errorMessage);
bool synchronousStash(const QString &workingDirectory, QString *errorMessage);
bool synchronousCheckoutFiles(const QString &workingDirectory,
QStringList files = QStringList(),
QString revision = QString(), QString *errorMessage = 0);
// Checkout branch
bool synchronousCheckoutBranch(const QString &workingDirectory, const QString &branch, QString *errorMessage = 0);
// Do a stash and return identier.
enum { StashPromptDescription = 0x1, StashImmediateRestore = 0x2, StashIgnoreUnchanged = 0x4 };
QString synchronousStash(const QString &workingDirectory,
const QString &messageKeyword = QString(),
unsigned flags = 0, bool *unchanged = 0);
bool executeSynchronousStash(const QString &workingDirectory,
const QString &message = QString(),
QString *errorMessage = 0);
bool synchronousStashRestore(const QString &workingDirectory,
const QString &stash,
const QString &branch = QString(),
QString *errorMessage = 0);
bool synchronousStashRemove(const QString &workingDirectory,
const QString &stash = QString(),
QString *errorMessage = 0);
bool synchronousBranchCmd(const QString &workingDirectory, QStringList branchArgs,
QString *output, QString *errorMessage);
bool synchronousShow(const QString &workingDirectory, const QString &id,
......@@ -110,15 +134,23 @@ public:
const QString &format, QString *description, QString *errorMessage);
bool synchronousShortDescriptions(const QString &workingDirectory, const QStringList &revisions,
QStringList *descriptions, QString *errorMessage);
bool synchronousTopRevision(const QString &workingDirectory, QString *revision = 0,
QString *branch = 0, QString *errorMessage = 0);
void pull(const QString &workingDirectory);
void push(const QString &workingDirectory);
void stash(const QString &workingDirectory);
void stashPop(const QString &workingDirectory);
void revert(const QStringList &files);
void branchList(const QString &workingDirectory);
void stashList(const QString &workingDirectory);
bool synchronousStashList(const QString &workingDirectory,
QList<Stash> *stashes,
QString *errorMessage = 0);
// Resolve a stash name from message (for IVersionControl's names).
bool stashNameFromMessage(const QString &workingDirectory,
const QString &messge, QString *name,
QString *errorMessage = 0);
QString readConfig(const QString &workingDirectory, const QStringList &configVar);
......
......@@ -39,6 +39,7 @@
#include "branchdialog.h"
#include "clonewizard.h"
#include "gitoriousclonewizard.h"
#include "stashdialog.h"
#include <coreplugin/icore.h>
#include <coreplugin/coreconstants.h>
......@@ -134,6 +135,7 @@ GitPlugin::GitPlugin() :
m_undoAction(0),
m_redoAction(0),
m_stashAction(0),
m_stashSnapshotAction(0),
m_stashPopAction(0),
m_stashListAction(0),
m_branchListAction(0),
......@@ -314,36 +316,37 @@ bool GitPlugin::initialize(const QStringList &arguments, QString *errorMessage)
gitContainer->addAction(createSeparator(actionManager, globalcontext, QLatin1String("Git.Sep.Global"), this));
m_stashSnapshotAction = new QAction(tr("Stash snapshot..."), this);
m_stashSnapshotAction->setToolTip(tr("Saves the current state of your work."));
command = actionManager->registerAction(m_stashSnapshotAction, "Git.StashSnapshot", globalcontext);
connect(m_stashSnapshotAction, SIGNAL(triggered()), this, SLOT(stashSnapshot()));
gitContainer->addAction(command);
m_stashAction = new QAction(tr("Stash"), this);
m_stashAction->setToolTip(tr("Saves the current state of your work."));
m_stashAction->setToolTip(tr("Saves the current state of your work and resets the repository."));
command = actionManager->registerAction(m_stashAction, "Git.Stash", globalcontext);
command->setAttribute(Core::Command::CA_UpdateText);
connect(m_stashAction, SIGNAL(triggered()), this, SLOT(stash()));
gitContainer->addAction(command);
m_pullAction = new QAction(tr("Pull"), this);
command = actionManager->registerAction(m_pullAction, "Git.Pull", globalcontext);
command->setAttribute(Core::Command::CA_UpdateText);
connect(m_pullAction, SIGNAL(triggered()), this, SLOT(pull()));
gitContainer->addAction(command);
m_stashPopAction = new QAction(tr("Stash Pop"), this);
m_stashAction->setToolTip(tr("Restores changes saved to the stash list using \"Stash\"."));
command = actionManager->registerAction(m_stashPopAction, "Git.StashPop", globalcontext);
command->setAttribute(Core::Command::CA_UpdateText);
connect(m_stashPopAction, SIGNAL(triggered()), this, SLOT(stashPop()));
gitContainer->addAction(command);
m_commitAction = new QAction(tr("Commit..."), this);
command = actionManager->registerAction(m_commitAction, "Git.Commit", globalcontext);
command->setDefaultKeySequence(QKeySequence(tr("Alt+G,Alt+C")));
command->setAttribute(Core::Command::CA_UpdateText);
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);
command->setAttribute(Core::Command::CA_UpdateText);
connect(m_pushAction, SIGNAL(triggered()), this, SLOT(push()));
gitContainer->addAction(command);
......@@ -351,22 +354,30 @@ bool GitPlugin::initialize(const QStringList &arguments, QString *errorMessage)
m_branchListAction = new QAction(tr("Branches..."), this);
command = actionManager->registerAction(m_branchListAction, "Git.BranchList", globalcontext);
command->setAttribute(Core::Command::CA_UpdateText);
connect(m_branchListAction, SIGNAL(triggered()), this, SLOT(branchList()));
gitContainer->addAction(command);
m_stashListAction = new QAction(tr("List Stashes"), this);
m_stashListAction = new QAction(tr("Stashes..."), this);
command = actionManager->registerAction(m_stashListAction, "Git.StashList", globalcontext);
command->setAttribute(Core::Command::CA_UpdateText);
connect(m_stashListAction, SIGNAL(triggered()), this, SLOT(stashList()));
gitContainer->addAction(command);
m_showAction = new QAction(tr("Show Commit..."), this);
command = actionManager->registerAction(m_showAction, "Git.ShowCommit", globalcontext);
command->setAttribute(Core::Command::CA_UpdateText);
connect(m_showAction, SIGNAL(triggered()), this, SLOT(showCommit()));
gitContainer->addAction(command);
if (0) {
const QList<QAction*> snapShotActions = createSnapShotTestActions();
const int count = snapShotActions.size();
for (int i = 0; i < count; i++) {
command = actionManager->registerAction(snapShotActions.at(i),
QLatin1String("Git.Snapshot.") + QString::number(i),
globalcontext);
gitContainer->addAction(command);
}
}
// Submit editor
QList<int> submitContext;
submitContext.push_back(m_core->uniqueIDManager()->uniqueIdentifier(QLatin1String(Constants::C_GITSUBMITEDITOR)));
......@@ -648,9 +659,22 @@ void GitPlugin::push()
void GitPlugin::stash()
{
// Simple stash without prompt, reset repo.
const VCSBase::VCSBasePluginState state = currentState();
QTC_ASSERT(state.hasTopLevel(), return)
const QString id = m_gitClient->synchronousStash(state.topLevel(), QString(), 0);
if (!id.isEmpty() && m_stashDialog)
m_stashDialog->refresh(state.topLevel(), true);
}
void GitPlugin::stashSnapshot()
{
// Prompt for description, restore immediately and keep on working.
const VCSBase::VCSBasePluginState state = currentState();
QTC_ASSERT(state.hasTopLevel(), return)
m_gitClient->stash(state.topLevel());
const QString id = m_gitClient->synchronousStash(state.topLevel(), QString(), GitClient::StashImmediateRestore|GitClient::StashPromptDescription);
if (!id.isEmpty() && m_stashDialog)
m_stashDialog->refresh(state.topLevel(), true);
}
void GitPlugin::stashPop()
......@@ -676,9 +700,15 @@ void GitPlugin::branchList()
void GitPlugin::stashList()
{
const VCSBase::VCSBasePluginState state = currentState();
QTC_ASSERT(state.hasTopLevel(), return)
m_gitClient->stashList(state.topLevel());
// Raise non-modal stash dialog.
if (m_stashDialog) {
m_stashDialog->show();
m_stashDialog->raise();
} else {
m_stashDialog = new StashDialog(Core::ICore::instance()->mainWindow());
m_stashDialog->refresh(currentState().topLevel(), true);
m_stashDialog->show();
}
}
void GitPlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
......@@ -716,6 +746,7 @@ void GitPlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
m_branchListAction->setEnabled(repositoryEnabled);
m_stashListAction->setEnabled(repositoryEnabled);
m_stashAction->setEnabled(repositoryEnabled);
m_stashSnapshotAction->setEnabled(repositoryEnabled);
m_pullAction->setEnabled(repositoryEnabled);
m_commitAction->setEnabled(repositoryEnabled);
m_stashPopAction->setEnabled(repositoryEnabled);
......@@ -723,6 +754,9 @@ void GitPlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
m_undoRepositoryAction->setEnabled(repositoryEnabled);
m_pushAction->setEnabled(repositoryEnabled);
if (m_stashDialog)
m_stashDialog->refresh(currentState().topLevel(), false);
// Prompts for repo.
m_showAction->setEnabled(true);
}
......
......@@ -39,6 +39,7 @@
#include <QtCore/QObject>
#include <QtCore/QProcess>
#include <QtCore/QStringList>
#include <QtCore/QPointer>
QT_BEGIN_NAMESPACE
class QFile;
......@@ -64,6 +65,7 @@ class ChangeSelectionDialog;
class GitSubmitEditor;
struct CommitData;
struct GitSettings;
class StashDialog;
class GitPlugin : public VCSBase::VCSBasePlugin
{
......@@ -104,6 +106,7 @@ private slots:
void showCommit();
void startCommit();
void stash();
void stashSnapshot();
void stashPop();
void branchList();
void stashList();
......@@ -145,6 +148,7 @@ private:
QAction *m_undoAction;
QAction *m_redoAction;
QAction *m_stashAction;
QAction *m_stashSnapshotAction;
QAction *m_stashPopAction;
QAction *m_stashListAction;
QAction *m_branchListAction;
......@@ -152,11 +156,13 @@ private:
GitClient *m_gitClient;
ChangeSelectionDialog *m_changeSelectionDialog;
QPointer<StashDialog> m_stashDialog;
QString m_submitRepository;
QStringList m_submitOrigCommitFiles;
QStringList m_submitOrigDeleteFiles;
QString m_commitMessageFileName;
bool m_submitActionTriggered;
};
} // namespace Git
......
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** 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.
**
** GNU Lesser General Public License Usage
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "gitutils.h"
#include <QtCore/QDebug>
#include <QtCore/QStringList>
#include <QtGui/QInputDialog>
#include <QtGui/QLineEdit>
namespace Git {
namespace Internal {
QDebug operator<<(QDebug d, const Stash &s)
{
QDebug nospace = d.nospace();
nospace << "name=" << s.name << " branch=" << s.branch << " message=" << s.message;
return d;
}
void Stash::clear()
{
name.clear();
branch.clear();
message.clear();
}
/* Parse a stash line in its 2 manifestations (with message/without message containing
* <base_sha1>+subject):
\code
stash@{1}: WIP on <branch>: <base_sha1> <subject_base_sha1>
stash@{2}: On <branch>: <message>
\endcode */
bool Stash::parseStashLine(const QString &l)
{
const QChar colon = QLatin1Char(':');
const int branchPos = l.indexOf(colon);
if (branchPos < 0)
return false;
const int messagePos = l.indexOf(colon, branchPos + 1);
if (messagePos < 0)
return false;
// Name
const QString newName = l.left(branchPos);
// Branch spec
const QString branchSpec = l.mid(branchPos + 1, messagePos - branchPos - 1);
const bool emptyMessage = branchSpec.contains(QLatin1String("WIP")); // "Work in Progress or sth"
const int onIndex = branchSpec.indexOf(QLatin1String("on "), 0, Qt::CaseInsensitive);
if (onIndex == -1)
return false;
const QString newBranch = branchSpec.mid(onIndex + 3);
// Happy!
name = newName;
branch = newBranch;
if (!emptyMessage)
message = l.mid(messagePos + 2); // skip blank
return true;
}
// Make QInputDialog play nicely, widen it a bit.
bool inputText(QWidget *parent, const QString &title, const QString &prompt, QString *s)
{
QInputDialog dialog(parent);
dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
dialog.setWindowTitle(title);
dialog.setLabelText(prompt);
dialog.setTextValue(*s);
// Nasty hack:
if (QLineEdit *le = qFindChild<QLineEdit*>(&dialog))
le->setMinimumWidth(500);
if (dialog.exec() != QDialog::Accepted)
return false;
*s = dialog.textValue();
return true;
}
} // namespace Internal
} // namespace Git
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** 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.
**
** GNU Lesser General Public License Usage
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef GITUTILS_H
#define GITUTILS_H
#include <QtCore/QString>
QT_BEGIN_NAMESPACE
class QDebug;
class QWidget;
QT_END_NAMESPACE
namespace Git {
namespace Internal {
struct Stash {
void clear();
bool parseStashLine(const QString &l);
QString name;
QString branch;
QString message;
};
QDebug operator<<(QDebug d, const Stash &);
// Make QInputDialog play nicely
bool inputText(QWidget *parent, const QString &title, const QString &prompt, QString *s);
} // namespace Internal
} // namespace Git
#endif // GITUTILS_H
......@@ -30,10 +30,19 @@
#include "gitversioncontrol.h"
#include "gitclient.h"
#include "gitplugin.h"
#include "gitutils.h"
static const char stashMessageKeywordC[] = "IVersionControl@";
static const char stashRevisionIdC[] = "revision";
namespace Git {
namespace Internal {
static inline GitClient *gitClient()
{
return GitPlugin::instance()->gitClient();
}
GitVersionControl::GitVersionControl(GitClient *client) :
m_enabled(true),
m_client(client)
......@@ -54,6 +63,7 @@ bool GitVersionControl::supportsOperation(Operation operation) const
case OpenOperation:
break;
case CreateRepositoryOperation:
case SnapshotOperations:
rc = true;
break;
}
......@@ -78,7 +88,91 @@ bool GitVersionControl::vcsDelete(const QString & /*fileName*/)
bool GitVersionControl::vcsCreateRepository(const QString &directory)