forked from qt-creator/qt-creator
Change-Id: Id154881b4f5d8c488e5c1f5e0f843d36bf838759 Reviewed-by: Orgad Shaneh <orgads@gmail.com> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
237 lines
7.7 KiB
C++
237 lines
7.7 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "mergetool.h"
|
|
|
|
#include "gitclient.h"
|
|
#include "gitplugin.h"
|
|
#include "gittr.h"
|
|
|
|
#include <coreplugin/documentmanager.h>
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/messagebox.h>
|
|
#include <utils/environment.h>
|
|
#include <vcsbase/vcsoutputwindow.h>
|
|
|
|
#include <QMessageBox>
|
|
#include <QPushButton>
|
|
|
|
using namespace Utils;
|
|
using namespace VcsBase;
|
|
|
|
namespace Git::Internal {
|
|
|
|
MergeTool::MergeTool(QObject *parent) : QObject(parent)
|
|
{
|
|
connect(&m_process, &Process::done, this, &MergeTool::done);
|
|
connect(&m_process, &Process::readyReadStandardOutput, this, &MergeTool::readData);
|
|
Environment env = Environment::systemEnvironment();
|
|
env.set("LANG", "C");
|
|
env.set("LANGUAGE", "C");
|
|
m_process.setEnvironment(env);
|
|
m_process.setProcessMode(ProcessMode::Writer);
|
|
m_process.setProcessChannelMode(QProcess::MergedChannels);
|
|
}
|
|
|
|
void MergeTool::start(const FilePath &workingDirectory, const QStringList &files)
|
|
{
|
|
const CommandLine cmd{gitClient().vcsBinary(workingDirectory), {"mergetool", "-y", files}};
|
|
VcsOutputWindow::appendCommand(workingDirectory, cmd);
|
|
m_process.setCommand(cmd);
|
|
m_process.setWorkingDirectory(workingDirectory);
|
|
m_process.start();
|
|
}
|
|
|
|
MergeTool::FileState MergeTool::parseStatus(const QString &line, QString &extraInfo)
|
|
{
|
|
QString state = line.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;
|
|
const QString submodulePrefix("submodule commit ");
|
|
// " {local}: submodule commit <hash>"
|
|
if (state.startsWith(submodulePrefix)) {
|
|
extraInfo = state.mid(submodulePrefix.size());
|
|
return SubmoduleState;
|
|
}
|
|
// " {local}: a symbolic link -> 'foo.cpp'"
|
|
const QString symlinkPrefix("a symbolic link -> '");
|
|
if (state.startsWith(symlinkPrefix)) {
|
|
extraInfo = state.mid(symlinkPrefix.size());
|
|
extraInfo.chop(1); // remove last quote
|
|
return SymbolicLinkState;
|
|
}
|
|
}
|
|
return UnknownState;
|
|
}
|
|
|
|
static MergeTool::MergeType mergeType(const QString &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::tr("Normal");
|
|
case SubmoduleMerge: return Tr::tr("Submodule");
|
|
case DeletedMerge: return Tr::tr("Deleted");
|
|
case SymbolicLinkMerge: return Tr::tr("Symbolic link");
|
|
}
|
|
return {};
|
|
}
|
|
|
|
QString MergeTool::stateName(MergeTool::FileState state, const QString &extraInfo)
|
|
{
|
|
switch (state) {
|
|
case ModifiedState: return Tr::tr("Modified");
|
|
case CreatedState: return Tr::tr("Created");
|
|
case DeletedState: return Tr::tr("Deleted");
|
|
case SubmoduleState: return Tr::tr("Submodule commit %1").arg(extraInfo);
|
|
case SymbolicLinkState: return Tr::tr("Symbolic link -> %1").arg(extraInfo);
|
|
default: break;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
void MergeTool::chooseAction()
|
|
{
|
|
if (m_mergeType == NormalMerge)
|
|
return;
|
|
QMessageBox msgBox;
|
|
msgBox.setWindowTitle(Tr::tr("Merge Conflict"));
|
|
msgBox.setIcon(QMessageBox::Question);
|
|
msgBox.setStandardButtons(QMessageBox::Abort);
|
|
msgBox.setText(Tr::tr("%1 merge conflict for \"%2\"\nLocal: %3\nRemote: %4")
|
|
.arg(mergeTypeName(), m_fileName,
|
|
stateName(m_localState, m_localInfo),
|
|
stateName(m_remoteState, m_remoteInfo)));
|
|
switch (m_mergeType) {
|
|
case SubmoduleMerge:
|
|
case SymbolicLinkMerge:
|
|
addButton(&msgBox, Tr::tr("&Local"), 'l');
|
|
addButton(&msgBox, Tr::tr("&Remote"), 'r');
|
|
break;
|
|
case DeletedMerge:
|
|
if (m_localState == CreatedState || m_remoteState == CreatedState)
|
|
addButton(&msgBox, Tr::tr("&Created"), 'c');
|
|
else
|
|
addButton(&msgBox, Tr::tr("&Modified"), 'm');
|
|
addButton(&msgBox, Tr::tr("&Deleted"), 'd');
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
msgBox.exec();
|
|
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('a'); // abort
|
|
|
|
write(QString(key.toChar()) + '\n');
|
|
}
|
|
|
|
void MergeTool::addButton(QMessageBox *msgBox, const QString &text, char key)
|
|
{
|
|
msgBox->addButton(text, QMessageBox::AcceptRole)->setProperty("key", key);
|
|
}
|
|
|
|
void MergeTool::prompt(const QString &title, const QString &question)
|
|
{
|
|
if (QMessageBox::question(Core::ICore::dialogParent(), title, question,
|
|
QMessageBox::Yes | QMessageBox::No,
|
|
QMessageBox::No) == QMessageBox::Yes) {
|
|
write("y\n");
|
|
} else {
|
|
write("n\n");
|
|
}
|
|
}
|
|
|
|
void MergeTool::readData()
|
|
{
|
|
QString newData = QString::fromLocal8Bit(m_process.readAllRawStandardOutput());
|
|
newData.remove('\r');
|
|
VcsOutputWindow::append(newData);
|
|
QString data = m_unfinishedLine + newData;
|
|
m_unfinishedLine.clear();
|
|
for (;;) {
|
|
const int index = data.indexOf('\n');
|
|
if (index == -1)
|
|
break;
|
|
const QString line = data.left(index + 1);
|
|
readLine(line);
|
|
data = data.mid(index + 1);
|
|
}
|
|
if (data.startsWith("Was the merge successful")) {
|
|
prompt(Tr::tr("Unchanged File"), Tr::tr("Was the merge successful?"));
|
|
} else if (data.startsWith("Continue merging")) {
|
|
prompt(Tr::tr("Continue Merging"), Tr::tr("Continue merging other unresolved paths?"));
|
|
} else if (data.startsWith("Hit return")) {
|
|
QMessageBox::warning(
|
|
Core::ICore::dialogParent(), Tr::tr("Merge Tool"),
|
|
QString("<html><body><p>%1</p>\n<p>%2</p></body></html>").arg(
|
|
Tr::tr("Merge tool is not configured."),
|
|
Tr::tr("Run git config --global merge.tool <tool> "
|
|
"to configure it, then try again.")));
|
|
m_process.stop();
|
|
} else {
|
|
m_unfinishedLine = data;
|
|
}
|
|
}
|
|
|
|
void MergeTool::readLine(const QString &line)
|
|
{
|
|
// {Normal|Deleted|Submodule|Symbolic link} merge conflict for 'foo.cpp'
|
|
const int index = line.indexOf(" merge conflict for ");
|
|
if (index != -1) {
|
|
m_mergeType = mergeType(line.left(index));
|
|
const int quote = line.indexOf('\'');
|
|
m_fileName = line.mid(quote + 1, line.lastIndexOf('\'') - quote - 1);
|
|
} else if (line.startsWith(" {local}")) {
|
|
m_localState = parseStatus(line, m_localInfo);
|
|
} else if (line.startsWith(" {remote}")) {
|
|
m_remoteState = parseStatus(line, m_remoteInfo);
|
|
chooseAction();
|
|
}
|
|
}
|
|
|
|
void MergeTool::done()
|
|
{
|
|
const bool success = m_process.result() == ProcessResult::FinishedWithSuccess;
|
|
if (success)
|
|
VcsOutputWindow::appendMessage(m_process.exitMessage());
|
|
else
|
|
VcsOutputWindow::appendError(m_process.exitMessage());
|
|
|
|
const FilePath workingDirectory = m_process.workingDirectory();
|
|
gitClient().continueCommandIfNeeded(workingDirectory, success);
|
|
emitRepositoryChanged(workingDirectory);
|
|
deleteLater();
|
|
}
|
|
|
|
void MergeTool::write(const QString &str)
|
|
{
|
|
m_process.write(str);
|
|
VcsOutputWindow::append(str);
|
|
}
|
|
|
|
} // Git::Internal
|