Commit cf6298ff authored by Orgad Shaneh's avatar Orgad Shaneh Committed by Orgad Shaneh
Browse files

Git: Introduce MergeTool support



Change-Id: I906c3c692d9f4819bdf2a1489c42ae04f292894d
Reviewed-by: default avatarTobias Hunger <tobias.hunger@digia.com>
parent 438e4af7
......@@ -28,7 +28,8 @@ HEADERS += gitplugin.h \
remotemodel.h \
remotedialog.h \
branchadddialog.h \
resetdialog.h
resetdialog.h \
mergetool.h
SOURCES += gitplugin.cpp \
gitclient.cpp \
......@@ -50,7 +51,8 @@ SOURCES += gitplugin.cpp \
remotemodel.cpp \
remotedialog.cpp \
branchadddialog.cpp \
resetdialog.cpp
resetdialog.cpp \
mergetool.cpp
FORMS += changeselectiondialog.ui \
settingspage.ui \
......
......@@ -54,6 +54,8 @@ QtcPlugin {
"gitutils.h",
"gitversioncontrol.cpp",
"gitversioncontrol.h",
"mergetool.cpp",
"mergetool.h",
"remoteadditiondialog.ui",
"remotedialog.cpp",
"remotedialog.h",
......
......@@ -35,6 +35,7 @@
#include "gitplugin.h"
#include "gitsubmiteditor.h"
#include "gitversioncontrol.h"
#include "mergetool.h"
#include <vcsbase/submitfilemodel.h>
......@@ -572,6 +573,13 @@ void GitClient::diffBranch(const QString &workingDirectory,
executeGit(workingDirectory, cmdArgs, editor);
}
void GitClient::merge(const QString &workingDirectory, const QStringList &unmergedFileNames)
{
MergeTool *mergeTool = new MergeTool(this);
if (!mergeTool->start(workingDirectory, unmergedFileNames))
delete mergeTool;
}
void GitClient::status(const QString &workingDirectory)
{
// @TODO: Use "--no-color" once it is supported
......
......@@ -92,6 +92,7 @@ public:
void diffBranch(const QString &workingDirectory,
const QStringList &diffArgs,
const QString &branchName);
void merge(const QString &workingDirectory, const QStringList &unmergedFileNames = QStringList());
void status(const QString &workingDirectory);
void graphLog(const QString &workingDirectory) { graphLog(workingDirectory, QString()); }
......
......@@ -43,6 +43,7 @@
#include "stashdialog.h"
#include "settingspage.h"
#include "resetdialog.h"
#include "mergetool.h"
#include "gerrit/gerritplugin.h"
......@@ -519,6 +520,10 @@ bool GitPlugin::initialize(const QStringList &arguments, QString *errorMessage)
tr("Amend Last Commit..."), Core::Id("Git.AmendCommit"),
globalcontext, true, SLOT(startAmendCommit()));
createRepositoryAction(gitContainer,
tr("Merge Tool"), Core::Id("Git.MergeTool"),
globalcontext, true, SLOT(startMergeTool()));
// Subversion in a submenu.
gitContainer->addSeparator(globalcontext);
......@@ -565,6 +570,11 @@ void GitPlugin::submitEditorDiff(const QStringList &unstaged, const QStringList
m_gitClient->diff(m_submitRepository, QStringList(), unstaged, staged);
}
void GitPlugin::submitEditorMerge(const QStringList &unmerged)
{
m_gitClient->merge(m_submitRepository, unmerged);
}
void GitPlugin::diffCurrentFile()
{
const VcsBase::VcsBasePluginState state = currentState();
......@@ -708,6 +718,7 @@ Core::IEditor *GitPlugin::openSubmitEditor(const QString &fileName, const Commit
if (amend) // Allow for just correcting the message
submitEditor->setEmptyFileListEnabled(true);
connect(submitEditor, SIGNAL(diff(QStringList,QStringList)), this, SLOT(submitEditorDiff(QStringList,QStringList)));
connect(submitEditor, SIGNAL(merge(QStringList)), this, SLOT(submitEditorMerge(QStringList)));
return editor;
}
......@@ -804,6 +815,13 @@ void GitPlugin::push()
m_gitClient->synchronousPush(state.topLevel());
}
void GitPlugin::startMergeTool()
{
const VcsBase::VcsBasePluginState state = currentState();
QTC_ASSERT(state.hasTopLevel(), return);
m_gitClient->merge(state.topLevel());
}
// Retrieve member function of git client stored as user data of action
static inline GitClientMemberFunc memberFunctionFromAction(const QObject *o)
{
......
......@@ -105,6 +105,7 @@ private slots:
void diffCurrentProject();
void diffRepository();
void submitEditorDiff(const QStringList &unstaged, const QStringList &staged);
void submitEditorMerge(const QStringList &unmerged);
void submitCurrentLog();
void logFile();
void blameFile();
......@@ -131,6 +132,7 @@ private slots:
void fetch();
void pull();
void push();
void startMergeTool();
#ifdef WITH_TESTS
void testStatusParsing_data();
......
......@@ -81,7 +81,8 @@ void GitSubmitEditor::setCommitData(const CommitData &d)
void GitSubmitEditor::slotDiffSelected(const QStringList &files)
{
// Sort it apart into staged/unstaged files
// Sort it apart into unmerged/staged/unstaged files
QStringList unmergedFiles;
QStringList unstagedFiles;
QStringList stagedFiles;
const int fileColumn = fileNameColumn();
......@@ -90,7 +91,9 @@ void GitSubmitEditor::slotDiffSelected(const QStringList &files)
const QString fileName = m_model->item(r, fileColumn)->text();
if (files.contains(fileName)) {
const FileStates state = static_cast<FileStates>(m_model->extraData(r).toInt());
if (state & StagedFile)
if (state & UnmergedFile)
unmergedFiles.push_back(fileName);
else if (state & StagedFile)
stagedFiles.push_back(fileName);
else if (state != UntrackedFile)
unstagedFiles.push_back(fileName);
......@@ -98,6 +101,8 @@ void GitSubmitEditor::slotDiffSelected(const QStringList &files)
}
if (!unstagedFiles.empty() || !stagedFiles.empty())
emit diff(unstagedFiles, stagedFiles);
if (!unmergedFiles.empty())
emit merge(unmergedFiles);
}
GitSubmitEditorPanelData GitSubmitEditor::panelData() const
......
......@@ -56,6 +56,7 @@ public:
signals:
void diff(const QStringList &unstagedFiles, const QStringList &stagedFiles);
void merge(const QStringList &unmergedFiles);
protected:
QByteArray fileContents() const;
......
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** 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
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "gitclient.h"
#include "gitplugin.h"
#include "mergetool.h"
#include <QMessageBox>
#include <QProcess>
#include <QPushButton>
#include <QRegExp>
namespace Git {
namespace Internal {
MergeTool::MergeTool(QObject *parent) :
QObject(parent),
m_process(0)
{
}
MergeTool::~MergeTool()
{
delete m_process;
}
bool MergeTool::start(const QString &workingDirectory, const QStringList &files)
{
QStringList arguments;
arguments << QLatin1String("mergetool") << QLatin1String("-y");
GitClient *client = GitPlugin::instance()->gitClient();
if (!files.isEmpty()) {
if (client->gitVersion() < 0x010708) {
QMessageBox::warning(0, tr("Error"), tr("Files input for mergetool requires git >= 1.7.8"));
return false;
}
arguments << files;
}
m_process = new QProcess(this);
m_process->setWorkingDirectory(workingDirectory);
m_process->start(QLatin1String("git"), arguments);
if (m_process->waitForStarted()) {
connect(m_process, SIGNAL(finished(int)), this, SLOT(done()));
connect(m_process, SIGNAL(readyRead()), this, SLOT(readData()));
}
else {
delete m_process;
m_process = 0;
return false;
}
return true;
}
MergeTool::FileState MergeTool::waitAndReadStatus(QString &extraInfo)
{
QByteArray state;
if (m_process->canReadLine() || (m_process->waitForReadyRead(500) && m_process->canReadLine())) {
state = m_process->readLine().trimmed();
// " {local}: modified file"
// " {remote}: deleted"
if (!state.isEmpty()) {
state = state.mid(state.indexOf(':') + 2);
if (state == "deleted")
return DeletedState;
if (state.startsWith("modified"))
return ModifiedState;
if (state.startsWith("created"))
return CreatedState;
QByteArray submodulePrefix("submodule commit ");
// " {local}: submodule commit <hash>"
if (state.startsWith(submodulePrefix)) {
extraInfo = QString::fromLocal8Bit(state.mid(submodulePrefix.size()));
return SubmoduleState;
}
// " {local}: a symbolic link -> 'foo.cpp'"
QByteArray symlinkPrefix("a symbolic link -> '");
if (state.startsWith(symlinkPrefix)) {
extraInfo = QString::fromLocal8Bit(state.mid(symlinkPrefix.size()));
extraInfo.chop(1); // remove last quote
return SymbolicLinkState;
}
}
}
return UnknownState;
}
static MergeTool::MergeType mergeType(const QByteArray &type)
{
if (type == "Normal")
return MergeTool::NormalMerge;
if (type == "Deleted")
return MergeTool::DeletedMerge;
if (type == "Submodule")
return MergeTool::SubmoduleMerge;
else
return MergeTool::SymbolicLinkMerge;
}
QString MergeTool::mergeTypeName()
{
switch (m_mergeType) {
case NormalMerge: return tr("Normal");
case SubmoduleMerge: return tr("Submodule");
case DeletedMerge: return tr("Deleted");
case SymbolicLinkMerge: return tr("Symbolic link");
}
return QString();
}
QString MergeTool::stateName(MergeTool::FileState state, const QString &extraInfo)
{
switch (state) {
case ModifiedState: return tr("Modified");
case CreatedState: return tr("Created");
case DeletedState: return tr("Deleted");
case SubmoduleState: return tr("Submodule commit %1").arg(extraInfo);
case SymbolicLinkState: return tr("Symbolic link -> %1").arg(extraInfo);
default: break;
}
return QString();
}
void MergeTool::chooseAction()
{
m_merging = (m_mergeType == NormalMerge);
if (m_merging)
return;
QMessageBox msgBox;
msgBox.setWindowTitle(tr("Merge Conflict"));
msgBox.setIcon(QMessageBox::Question);
msgBox.setStandardButtons(QMessageBox::Abort);
msgBox.setText(tr("%1 merge conflict for '%2'\nLocal: %3\nRemote: %4")
.arg(mergeTypeName())
.arg(m_fileName)
.arg(stateName(m_localState, m_localInfo))
.arg(stateName(m_remoteState, m_remoteInfo))
);
switch (m_mergeType) {
case SubmoduleMerge:
case SymbolicLinkMerge:
addButton(&msgBox, tr("&Local"), 'l');
addButton(&msgBox, tr("&Remote"), 'r');
break;
case DeletedMerge:
if (m_localState == CreatedState || m_remoteState == CreatedState)
addButton(&msgBox, tr("&Created"), 'c');
else
addButton(&msgBox, tr("&Modified"), 'm');
addButton(&msgBox, tr("&Deleted"), 'd');
break;
default:
break;
}
msgBox.exec();
QByteArray ba;
QVariant key;
QAbstractButton *button = msgBox.clickedButton();
if (button)
key = button->property("key");
// either the message box was closed without clicking anything, or abort was clicked
if (!key.isValid())
key = QVariant(QLatin1Char('a')); // abort
ba.append(key.toChar().toLatin1());
ba.append('\n');
m_process->write(ba);
}
void MergeTool::addButton(QMessageBox *msgBox, const QString &text, char key)
{
msgBox->addButton(text, QMessageBox::AcceptRole)->setProperty("key", key);
}
void MergeTool::readData()
{
while (m_process->bytesAvailable()) {
QByteArray line = m_process->canReadLine() ? m_process->readLine() : m_process->readAllStandardOutput();
// {Normal|Deleted|Submodule|Symbolic link} merge conflict for 'foo.cpp'
int index = line.indexOf(" merge conflict for ");
if (index != -1) {
m_mergeType = mergeType(line.left(index));
int quote = line.indexOf('\'');
m_fileName = QString::fromLocal8Bit(line.mid(quote + 1, line.lastIndexOf('\'') - quote - 1));
m_localState = waitAndReadStatus(m_localInfo);
m_remoteState = waitAndReadStatus(m_remoteInfo);
chooseAction();
} else if (m_merging && line.startsWith("Continue merging")) {
if (QMessageBox::question(0, tr("Continue Merging"),
tr("Continue merging other unresolved paths?"),
QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
m_process->write("y\n");
} else {
m_process->write("n\n");
}
}
}
}
void MergeTool::done()
{
QMessageBox::information(0, tr("Done"), tr("Merge done"));
deleteLater();
}
} // namespace Internal
} // namespace Git
/****************************************************************************
**
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** 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
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef MERGETOOL_H
#define MERGETOOL_H
#include <QObject>
#include <QStringList>
QT_BEGIN_NAMESPACE
class QProcess;
class QMessageBox;
QT_END_NAMESPACE
namespace Git {
namespace Internal {
class MergeTool : public QObject
{
Q_OBJECT
enum FileState {
UnknownState,
ModifiedState,
CreatedState,
DeletedState,
SubmoduleState,
SymbolicLinkState
};
public:
explicit MergeTool(QObject *parent = 0);
~MergeTool();
bool start(const QString &workingDirectory, const QStringList &files = QStringList());
enum MergeType {
NormalMerge,
SubmoduleMerge,
DeletedMerge,
SymbolicLinkMerge
};
private slots:
void readData();
void done();
private:
FileState waitAndReadStatus(QString &extraInfo);
QString mergeTypeName();
QString stateName(FileState state, const QString &extraInfo);
void chooseAction();
void addButton(QMessageBox *msgBox, const QString &text, char key);
QProcess *m_process;
MergeType m_mergeType;
QString m_fileName;
FileState m_localState;
QString m_localInfo;
FileState m_remoteState;
QString m_remoteInfo;
bool m_merging;
};
} // namespace Internal
} // namespace Git
#endif // MERGETOOL_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment