Commit e2ef36a6 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Fixes: Enable multiselection in file list of git submit editor

Details: Refactor the git command to be able to run a batch. Run diff and diff --cached in a batch
parent 8dfdfc06
......@@ -20,7 +20,8 @@ HEADERS += gitplugin.h \
gitversioncontrol.h \
gitsettings.h \
branchdialog.h \
branchmodel.h
branchmodel.h \
gitcommand.h
SOURCES += gitplugin.cpp \
gitoutputwindow.cpp \
gitclient.cpp \
......@@ -34,7 +35,8 @@ SOURCES += gitplugin.cpp \
gitversioncontrol.cpp \
gitsettings.cpp \
branchdialog.cpp \
branchmodel.cpp
branchmodel.cpp \
gitcommand.cpp
FORMS += changeselectiondialog.ui \
settingspage.ui \
gitsubmitpanel.ui \
......
......@@ -32,6 +32,7 @@
***************************************************************************/
#include "gitclient.h"
#include "gitcommand.h"
#include "commitdata.h"
#include "gitconstants.h"
......@@ -49,7 +50,6 @@
#include <utils/qtcassert.h>
#include <vcsbase/vcsbaseeditor.h>
#include <QtCore/QFuture>
#include <QtCore/QRegExp>
#include <QtCore/QTemporaryFile>
#include <QtCore/QTime>
......@@ -61,7 +61,6 @@
using namespace Git;
using namespace Git::Internal;
const char *const kGitCommand = "git";
const char *const kGitDirectoryC = ".git";
const char *const kBranchIndicatorC = "# On branch";
......@@ -186,19 +185,45 @@ VCSBase::VCSBaseEditor
void GitClient::diff(const QString &workingDirectory,
const QStringList &diffArgs,
const QStringList &fileNames)
const QStringList &unstagedFileNames,
const QStringList &stagedFileNames)
{
if (Git::Constants::debug)
qDebug() << "diff" << workingDirectory << fileNames;
QStringList arguments;
arguments << QLatin1String("diff") << diffArgs << QLatin1String("--") << fileNames;
if (Git::Constants::debug)
qDebug() << "diff" << workingDirectory << unstagedFileNames << stagedFileNames;
const QString binary = QLatin1String(Constants::GIT_BINARY);
const QString kind = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_KIND);
const QString title = tr("Git Diff");
VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, workingDirectory, true, "originalFileName", workingDirectory);
executeGit(workingDirectory, arguments, editor);
// Create a batch of 2 commands to be run after each other in case
// we have a mixture of staged/unstaged files as is the case
// when using the submit dialog.
GitCommand *command = createCommand(workingDirectory, editor);
// Directory diff?
if (unstagedFileNames.empty() && stagedFileNames.empty()) {
QStringList arguments;
arguments << QLatin1String("diff") << diffArgs;
m_plugin->outputWindow()->append(formatCommand(binary, arguments));
command->addJob(arguments);
} else {
// Files diff.
if (!unstagedFileNames.empty()) {
QStringList arguments;
arguments << QLatin1String("diff") << diffArgs << QLatin1String("--") << unstagedFileNames;
m_plugin->outputWindow()->append(formatCommand(binary, arguments));
command->addJob(arguments);
}
if (!stagedFileNames.empty()) {
QStringList arguments;
arguments << QLatin1String("diff") << QLatin1String("--cached") << diffArgs << QLatin1String("--") << stagedFileNames;
m_plugin->outputWindow()->append(formatCommand(binary, arguments));
command->addJob(arguments);
}
}
command->execute();
}
void GitClient::diff(const QString &workingDirectory,
......@@ -439,42 +464,47 @@ bool GitClient::synchronousShow(const QString &workingDirectory, const QString &
return true;
}
void GitClient::executeGit(const QString &workingDirectory, const QStringList &arguments,
VCSBase::VCSBaseEditor* editor,
bool outputToWindow)
// Factory function to create an asynchronous command
GitCommand *GitClient::createCommand(const QString &workingDirectory,
VCSBase::VCSBaseEditor* editor,
bool outputToWindow)
{
if (Git::Constants::debug)
qDebug() << "executeGit" << workingDirectory << arguments << editor;
qDebug() << Q_FUNC_INFO << workingDirectory << editor;
GitOutputWindow *outputWindow = m_plugin->outputWindow();
outputWindow->append(formatCommand(QLatin1String(kGitCommand), arguments));
QProcess process;
ProjectExplorer::Environment environment = ProjectExplorer::Environment::systemEnvironment();
if (m_settings.adoptPath)
environment.set(QLatin1String("PATH"), m_settings.path);
GitCommand* command = new GitCommand();
GitCommand* command = new GitCommand(workingDirectory, environment);
if (outputToWindow) {
if (!editor) { // assume that the commands output is the important thing
connect(command, SIGNAL(outputText(QString)), this, SLOT(appendAndPopup(QString)));
connect(command, SIGNAL(outputData(QByteArray)), this, SLOT(appendDataAndPopup(QByteArray)));
} else {
connect(command, SIGNAL(outputText(QString)), outputWindow, SLOT(append(QString)));
connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendData(QByteArray)));
}
} else {
QTC_ASSERT(editor, /**/);
connect(command, SIGNAL(outputText(QString)), editor, SLOT(setPlainText(QString)));
connect(command, SIGNAL(outputData(QByteArray)), editor, SLOT(setPlainTextData(QByteArray)));
}
if (outputWindow)
connect(command, SIGNAL(errorText(QString)), this, SLOT(appendAndPopup(QString)));
return command;
}
command->execute(arguments, workingDirectory, environment);
// Execute a single command
void GitClient::executeGit(const QString &workingDirectory,
const QStringList &arguments,
VCSBase::VCSBaseEditor* editor,
bool outputToWindow)
{
m_plugin->outputWindow()->append(formatCommand(QLatin1String(Constants::GIT_BINARY), arguments));
GitCommand *command = createCommand(workingDirectory, editor, outputToWindow);
command->addJob(arguments);
command->execute();
}
void GitClient::appendDataAndPopup(const QByteArray &data)
......@@ -497,7 +527,7 @@ bool GitClient::synchronousGit(const QString &workingDirectory,
{
if (Git::Constants::debug)
qDebug() << "synchronousGit" << workingDirectory << arguments;
const QString binary = QLatin1String(kGitCommand);
const QString binary = QLatin1String(Constants::GIT_BINARY);
if (logCommandToWindow)
m_plugin->outputWindow()->append(formatCommand(binary, arguments));
......@@ -973,67 +1003,3 @@ void GitClient::setSettings(const GitSettings &s)
}
}
// ------------------------ GitCommand
GitCommand::GitCommand()
{
}
GitCommand::~GitCommand()
{
}
void GitCommand::execute(const QStringList &arguments,
const QString &workingDirectory,
const ProjectExplorer::Environment &environment)
{
if (Git::Constants::debug)
qDebug() << "GitCommand::execute" << workingDirectory << arguments;
// For some reason QtConcurrent::run() only works on this
QFuture<void> task = QtConcurrent::run(this, &GitCommand::run
, arguments
, workingDirectory
, environment);
QString taskName = QLatin1String("Git ") + arguments[0];
Core::ICore *core = ExtensionSystem::PluginManager::instance()->getObject<Core::ICore>();
core->progressManager()->addTask(task, taskName
, QLatin1String("Git.action")
, Core::ProgressManager::CloseOnSuccess);
}
void GitCommand::run(const QStringList &arguments,
const QString &workingDirectory,
const ProjectExplorer::Environment &environment)
{
if (Git::Constants::debug)
qDebug() << "GitCommand::run" << workingDirectory << arguments;
QProcess process;
if (!workingDirectory.isEmpty())
process.setWorkingDirectory(workingDirectory);
ProjectExplorer::Environment env = environment;
if (env.toStringList().isEmpty())
env = ProjectExplorer::Environment::systemEnvironment();
process.setEnvironment(env.toStringList());
process.start(QLatin1String(kGitCommand), arguments);
if (!process.waitForFinished()) {
emit errorText(QLatin1String("Error: Git timed out"));
return;
}
const QByteArray output = process.readAllStandardOutput();
if (output.isEmpty()) {
if (arguments.at(0) == QLatin1String("diff"))
emit outputText(tr("The file does not differ from HEAD"));
} else {
emit outputData(output);
}
const QByteArray error = process.readAllStandardError();
if (!error.isEmpty())
emit errorText(QString::fromLocal8Bit(error));
// As it is used asynchronously, we need to delete ourselves
this->deleteLater();
}
......@@ -38,7 +38,6 @@
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/editormanager/ieditor.h>
#include <projectexplorer/environment.h>
#include <QtCore/QString>
#include <QtCore/QStringList>
......@@ -79,7 +78,8 @@ public:
static QString findRepositoryForDirectory(const QString &dir);
void diff(const QString &workingDirectory, const QStringList &diffArgs, const QString &fileName);
void diff(const QString &workingDirectory, const QStringList &diffArgs, const QStringList &fileNames);
void diff(const QString &workingDirectory, const QStringList &diffArgs,
const QStringList &unstagedFileNames, const QStringList &stagedFileNames= QStringList());
void status(const QString &workingDirectory);
void log(const QString &workingDirectory, const QString &fileName);
......@@ -154,11 +154,14 @@ private:
const char *registerDynamicProperty,
const QString &dynamicPropertyValue) const;
GitCommand *createCommand(const QString &workingDirectory,
VCSBase::VCSBaseEditor* editor = 0,
bool outputToWindow = false);
void executeGit(const QString &workingDirectory,
const QStringList &arguments,
VCSBase::VCSBaseEditor* editor = 0,
bool outputToWindow = false);
const QStringList &arguments,
VCSBase::VCSBaseEditor* editor = 0,
bool outputToWindow = false);
bool synchronousGit(const QString &workingDirectory,
const QStringList &arguments,
......@@ -175,24 +178,6 @@ private:
GitSettings m_settings;
};
class GitCommand : public QObject
{
Q_OBJECT
public:
GitCommand();
~GitCommand();
void execute(const QStringList &arguments,
const QString &workingDirectory,
const ProjectExplorer::Environment &environment);
void run(const QStringList &arguments,
const QString &workingDirectory,
const ProjectExplorer::Environment &environment);
Q_SIGNALS:
void outputData(const QByteArray&);
void outputText(const QString&);
void errorText(const QString&);
};
} // namespace Internal
} // namespace Git
......
/***************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (qt-info@nokia.com)
**
**
** Non-Open Source Usage
**
** Licensees may use this file in accordance with the Qt Beta Version
** License Agreement, Agreement version 2.2 provided with the Software or,
** alternatively, in accordance with the terms contained in a written
** agreement between you and Nokia.
**
** GNU General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the packaging
** of this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
**
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt GPL Exception
** version 1.3, included in the file GPL_EXCEPTION.txt in this package.
**
***************************************************************************/
#include "gitcommand.h"
#include "gitconstants.h"
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <QtCore/QDebug>
#include <QtCore/QProcess>
#include <QtCore/QFuture>
#include <QtCore/QtConcurrentRun>
namespace Git {
namespace Internal {
// Convert environment to list, default to system one.
static inline QStringList environmentToList(const ProjectExplorer::Environment &environment)
{
const QStringList list = environment.toStringList();
if (!list.empty())
return list;
return ProjectExplorer::Environment::systemEnvironment().toStringList();
}
GitCommand::Job::Job(const QStringList &a) :
arguments(a)
{
}
GitCommand::GitCommand(const QString &workingDirectory,
ProjectExplorer::Environment &environment) :
m_workingDirectory(workingDirectory),
m_environment(environmentToList(environment))
{
}
void GitCommand::addJob(const QStringList &arguments)
{
m_jobs.push_back(Job(arguments));
}
void GitCommand::execute()
{
if (Git::Constants::debug)
qDebug() << "GitCommand::execute" << m_workingDirectory << m_jobs.size();
if (m_jobs.empty())
return;
// For some reason QtConcurrent::run() only works on this
QFuture<void> task = QtConcurrent::run(this, &GitCommand::run);
const QString taskName = QLatin1String("Git ") + m_jobs.front().arguments.at(0);
Core::ICore *core = ExtensionSystem::PluginManager::instance()->getObject<Core::ICore>();
core->progressManager()->addTask(task, taskName,
QLatin1String("Git.action"),
Core::ProgressManager::CloseOnSuccess);
}
void GitCommand::run()
{
if (Git::Constants::debug)
qDebug() << "GitCommand::run" << m_workingDirectory << m_jobs.size();
QProcess process;
if (!m_workingDirectory.isEmpty())
process.setWorkingDirectory(m_workingDirectory);
process.setEnvironment(m_environment);
QByteArray output;
QString error;
const int count = m_jobs.size();
bool ok = true;
for (int j = 0; j < count; j++) {
if (Git::Constants::debug)
qDebug() << "GitCommand::run" << j << '/' << count << m_jobs.at(j).arguments;
process.start(QLatin1String(Constants::GIT_BINARY), m_jobs.at(j).arguments);
if (!process.waitForFinished()) {
ok = false;
error += QLatin1String("Error: Git timed out");
break;
}
output += process.readAllStandardOutput();
error += QString::fromLocal8Bit(process.readAllStandardError());
}
// Special hack: Always produce output for diff
if (ok && output.isEmpty() && m_jobs.front().arguments.at(0) == QLatin1String("diff")) {
output += "The file does not differ from HEAD";
}
if (ok && !output.isEmpty())
emit outputData(output);
if (!error.isEmpty())
emit errorText(error);
// As it is used asynchronously, we need to delete ourselves
this->deleteLater();
}
} // namespace Internal
} // namespace Git
/***************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (qt-info@nokia.com)
**
**
** Non-Open Source Usage
**
** Licensees may use this file in accordance with the Qt Beta Version
** License Agreement, Agreement version 2.2 provided with the Software or,
** alternatively, in accordance with the terms contained in a written
** agreement between you and Nokia.
**
** GNU General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the packaging
** of this file. Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
**
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt GPL Exception
** version 1.3, included in the file GPL_EXCEPTION.txt in this package.
**
***************************************************************************/
#ifndef GITCOMMAND_H
#define GITCOMMAND_H
#include <projectexplorer/environment.h>
#include <QtCore/QObject>
namespace Git {
namespace Internal {
class GitCommand : public QObject
{
Q_OBJECT
public:
explicit GitCommand(const QString &workingDirectory,
ProjectExplorer::Environment &environment);
void addJob(const QStringList &arguments);
void execute();
private:
void run();
Q_SIGNALS:
void outputData(const QByteArray&);
void errorText(const QString&);
private:
struct Job {
explicit Job(const QStringList &a);
QStringList arguments;
};
QStringList environment() const;
const QString m_workingDirectory;
const QStringList m_environment;
QList<Job> m_jobs;
};
} // namespace Internal
} // namespace Git
#endif // GITCOMMAND_H
......@@ -47,6 +47,7 @@ const char * const GITSUBMITEDITOR_KIND = "Git Submit Editor";
const char * const SUBMIT_CURRENT = "Nokia.Git.SubmitCurrentLog";
const char * const DIFF_SELECTED = "Nokia.Git.DiffSelectedFilesInLog";
const char * const SUBMIT_MIMETYPE = "application/vnd.nokia.text.git.submit";
const char * const GIT_BINARY = "git";
const char * const DIFF_FILE_INDICATOR = "--- ";
enum { debug = 0 };
......
......@@ -446,16 +446,9 @@ void GitPlugin::extensionsInitialized()
m_projectExplorer = ExtensionSystem::PluginManager::instance()->getObject<ProjectExplorer::ProjectExplorerPlugin>();
}
void GitPlugin::submitEditorDiffStaged(const QStringList &files)
void GitPlugin::submitEditorDiff(const QStringList &unstaged, const QStringList &staged)
{
if (!files.empty())
m_gitClient->diff(m_submitRepository, QStringList(QLatin1String("--cached")), files);
}
void GitPlugin::submitEditorDiffUnstaged(const QStringList &files)
{
if (!files.empty())
m_gitClient->diff(m_submitRepository, QStringList(), files);
m_gitClient->diff(m_submitRepository, QStringList(), unstaged, staged);
}
void GitPlugin::diffCurrentFile()
......@@ -638,8 +631,7 @@ Core::IEditor *GitPlugin::openSubmitEditor(const QString &fileName, const Commit
// mechanism. Disable them correctly.
submitEditor->registerActions(m_undoAction, m_redoAction, m_submitCurrentAction, m_diffSelectedFilesAction);
submitEditor->setCommitData(cd);
connect(submitEditor, SIGNAL(diffStaged(QStringList)), this, SLOT(submitEditorDiffStaged(QStringList)));
connect(submitEditor, SIGNAL(diffUnstaged(QStringList)), this, SLOT(submitEditorDiffUnstaged(QStringList)));
connect(submitEditor, SIGNAL(diff(QStringList,QStringList)), this, SLOT(submitEditorDiff(QStringList,QStringList)));
return editor;
}
......
......@@ -108,8 +108,7 @@ public slots:
private slots:
void diffCurrentFile();
void diffCurrentProject();
void submitEditorDiffUnstaged(const QStringList &);
void submitEditorDiffStaged(const QStringList &);
void submitEditorDiff(const QStringList &unstaged, const QStringList &staged);
void submitCurrentLog();
void statusFile();
void statusProject();
......
......@@ -48,16 +48,14 @@ enum FileType { StagedFile , UnstagedFile, UntrackedFile };
/* The problem with git is that no diff can be obtained to for a random
* multiselection of staged/unstaged files; it requires the --cached
* option for staged files. So, we set the file list to
* single selection and sort the files manual according to a type
* flag we add to the model. */
* option for staged files. So, we sort apart the diff file lists
* according to a type flag we add to the model. */
GitSubmitEditor::GitSubmitEditor(const VCSBase::VCSBaseSubmitEditorParameters *parameters, QWidget *parent) :
VCSBaseSubmitEditor(parameters, new GitSubmitEditorWidget(parent)),
m_model(0)
{
setDisplayName(tr("Git Commit"));
setFileListSelectionMode(QAbstractItemView::SingleSelection);
connect(this, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(slotDiffSelected(QStringList)));
}
......@@ -102,22 +100,29 @@ void GitSubmitEditor::setCommitData(const CommitData &d)
void GitSubmitEditor::slotDiffSelected(const QStringList &files)
{
QList<QStandardItem *> fileRow = m_model->findRow(files.front(), fileNameColumn());
if (fileRow.empty())
return;
const FileType ft = static_cast<FileType>(fileRow.front()->data(FileTypeRole).toInt());
switch (ft) {
case StagedFile:
emit diffStaged(files);
break;
case UnstagedFile:
emit diffUnstaged(files);
break;
case UntrackedFile:
break;
// Sort it apart into staged/unstaged files
QStringList unstagedFiles;
QStringList stagedFiles;
const int fileColumn = fileNameColumn();
const int rowCount = m_model->rowCount();
for (int r = 0; r < rowCount; r++) {
const QString fileName = m_model->item(r, fileColumn)->text();
if (files.contains(fileName)) {
const FileType ft = static_cast<FileType>(m_model->item(r, 0)->data(FileTypeRole).toInt());
switch (ft) {
case StagedFile:
stagedFiles.push_back(fileName);
break;
case UnstagedFile: