/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** 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 The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "mergetool.h" #include "gitclient.h" #include "gitplugin.h" #include "gitversioncontrol.h" #include #include #include #include #include #include #include using namespace VcsBase; namespace Git { namespace Internal { MergeTool::MergeTool(QObject *parent) : QObject(parent) { } MergeTool::~MergeTool() { delete m_process; } bool MergeTool::start(const QString &workingDirectory, const QStringList &files) { QStringList arguments; arguments << "mergetool" << "-y" << files; QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("LANG", "C"); env.insert("LANGUAGE", "C"); m_process = new QProcess(this); m_process->setWorkingDirectory(workingDirectory); m_process->setProcessEnvironment(env); m_process->setReadChannelMode(QProcess::MergedChannels); const Utils::FileName binary = GitPlugin::client()->vcsBinary(); VcsOutputWindow::appendCommand(workingDirectory, binary, arguments); m_process->start(binary.toString(), arguments); if (m_process->waitForStarted()) { connect(m_process, static_cast(&QProcess::finished), this, &MergeTool::done); connect(m_process, &QIODevice::readyRead, this, &MergeTool::readData); } else { delete m_process; m_process = nullptr; return false; } return true; } MergeTool::FileState MergeTool::parseStatus(const QByteArray &line, QString &extraInfo) { QByteArray 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; QByteArray submodulePrefix("submodule commit "); // " {local}: submodule commit " 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('a'); // abort ba.append(key.toChar().toLatin1()); ba.append('\n'); write(ba); } 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() { bool waitForFurtherInput = false; while (m_process->bytesAvailable()) { const bool hasLine = m_process->canReadLine(); const QByteArray line = hasLine ? m_process->readLine() : m_process->readAllStandardOutput(); VcsOutputWindow::append(QString::fromLocal8Bit(line)); m_line += line; // {Normal|Deleted|Submodule|Symbolic link} merge conflict for 'foo.cpp' const int index = m_line.indexOf(" merge conflict for "); if (index != -1) { m_mergeType = mergeType(m_line.left(index)); int quote = m_line.indexOf('\''); m_fileName = QString::fromLocal8Bit(m_line.mid(quote + 1, m_line.lastIndexOf('\'') - quote - 1)); m_line.clear(); } else if (m_line.startsWith(" {local}")) { waitForFurtherInput = !hasLine; if (waitForFurtherInput) continue; m_localState = parseStatus(m_line, m_localInfo); m_line.clear(); } else if (m_line.startsWith(" {remote}")) { waitForFurtherInput = !hasLine; if (waitForFurtherInput) continue; m_remoteState = parseStatus(m_line, m_remoteInfo); m_line.clear(); chooseAction(); } else if (m_line.startsWith("Was the merge successful")) { prompt(tr("Unchanged File"), tr("Was the merge successful?")); } else if (m_line.startsWith("Continue merging")) { prompt(tr("Continue Merging"), tr("Continue merging other unresolved paths?")); } else if (m_line.startsWith("Hit return")) { QMessageBox::warning( Core::ICore::dialogParent(), tr("Merge Tool"), QString("

%1

\n

%2

").arg( tr("Merge tool is not configured."), tr("Run git config --global merge.tool <tool> " "to configure it, then try again."))); m_process->kill(); } else if (m_line.endsWith('\n')) { // Skip unidentified lines m_line.clear(); } } if (!waitForFurtherInput) m_line.clear(); } void MergeTool::done() { const QString workingDirectory = m_process->workingDirectory(); int exitCode = m_process->exitCode(); if (!exitCode) { VcsOutputWindow::appendMessage(tr("Merge tool process finished successfully.")); } else { VcsOutputWindow::appendError(tr("Merge tool process terminated with exit code %1") .arg(exitCode)); } GitPlugin::client()->continueCommandIfNeeded(workingDirectory, exitCode == 0); GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory); deleteLater(); } void MergeTool::write(const QByteArray &bytes) { m_process->write(bytes); m_process->waitForBytesWritten(); VcsOutputWindow::append(QString::fromLocal8Bit(bytes)); } } // namespace Internal } // namespace Git