2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 Brian McGillion and Hugues Delorme
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2015-04-27 15:03:07 +02:00
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
#include "vcscommand.h"
|
2015-04-27 15:03:07 +02:00
|
|
|
|
2022-08-01 18:35:07 +02:00
|
|
|
#include "vcsbaseplugin.h"
|
|
|
|
|
#include "vcsoutputwindow.h"
|
|
|
|
|
|
|
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
#include <utils/environment.h>
|
2022-08-01 18:35:07 +02:00
|
|
|
#include <utils/globalfilechangeblocker.h>
|
2022-08-01 10:49:13 +02:00
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
#include <utils/qtcprocess.h>
|
2022-10-10 09:58:19 +02:00
|
|
|
#include <utils/threadutils.h>
|
2015-04-27 15:03:07 +02:00
|
|
|
|
|
|
|
|
#include <QTextCodec>
|
2017-09-07 11:17:27 +02:00
|
|
|
|
2022-08-01 18:35:07 +02:00
|
|
|
using namespace Core;
|
2022-08-01 10:49:13 +02:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
|
|
|
|
namespace VcsBase {
|
2015-04-27 15:03:07 +02:00
|
|
|
namespace Internal {
|
|
|
|
|
|
2022-09-01 17:13:31 +02:00
|
|
|
class VcsCommandPrivate : public QObject
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
struct Job {
|
2019-06-07 15:27:50 +02:00
|
|
|
CommandLine command;
|
2022-08-02 17:28:15 +02:00
|
|
|
int timeoutS = 10;
|
|
|
|
|
FilePath workingDirectory;
|
|
|
|
|
ExitCodeInterpreter exitCodeInterpreter = {};
|
2015-04-27 15:03:07 +02:00
|
|
|
};
|
|
|
|
|
|
2022-09-01 17:13:31 +02:00
|
|
|
VcsCommandPrivate(VcsCommand *vcsCommand, const FilePath &defaultWorkingDirectory,
|
|
|
|
|
const Environment &environment)
|
|
|
|
|
: q(vcsCommand)
|
|
|
|
|
, m_defaultWorkingDirectory(defaultWorkingDirectory)
|
|
|
|
|
, m_environment(environment)
|
2022-08-01 18:35:07 +02:00
|
|
|
{
|
|
|
|
|
VcsBase::setProcessEnvironment(&m_environment);
|
|
|
|
|
}
|
2021-05-04 05:54:54 +02:00
|
|
|
|
2022-07-13 17:54:59 +02:00
|
|
|
Environment environment()
|
|
|
|
|
{
|
2022-10-05 19:08:53 +02:00
|
|
|
if (!(m_flags & RunFlags::ForceCLocale))
|
2022-07-13 17:54:59 +02:00
|
|
|
return m_environment;
|
|
|
|
|
|
|
|
|
|
m_environment.set("LANG", "C");
|
|
|
|
|
m_environment.set("LANGUAGE", "C");
|
|
|
|
|
return m_environment;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-01 17:13:31 +02:00
|
|
|
int timeoutS() const;
|
|
|
|
|
|
2022-08-26 15:03:22 +02:00
|
|
|
void setup();
|
|
|
|
|
void cleanup();
|
2023-05-03 16:00:22 +02:00
|
|
|
void setupProcess(Process *process, const Job &job);
|
|
|
|
|
void installStdCallbacks(Process *process);
|
2022-10-05 11:14:41 +02:00
|
|
|
EventLoopMode eventLoopMode() const;
|
2023-05-03 16:00:22 +02:00
|
|
|
void handleDone(Process *process);
|
2022-08-25 12:56:03 +02:00
|
|
|
void startAll();
|
|
|
|
|
void startNextJob();
|
|
|
|
|
void processDone();
|
2022-09-01 17:35:36 +02:00
|
|
|
|
2022-09-01 17:13:31 +02:00
|
|
|
VcsCommand *q = nullptr;
|
|
|
|
|
|
2015-04-27 15:03:07 +02:00
|
|
|
QString m_displayName;
|
2021-08-11 10:02:58 +02:00
|
|
|
const FilePath m_defaultWorkingDirectory;
|
2022-07-13 17:54:59 +02:00
|
|
|
Environment m_environment;
|
2016-03-31 23:48:48 +03:00
|
|
|
QTextCodec *m_codec = nullptr;
|
2022-10-07 16:16:35 +02:00
|
|
|
ProgressParser m_progressParser = {};
|
2015-04-27 15:03:07 +02:00
|
|
|
QList<Job> m_jobs;
|
|
|
|
|
|
2022-08-25 12:56:03 +02:00
|
|
|
int m_currentJob = 0;
|
2023-05-03 16:00:22 +02:00
|
|
|
std::unique_ptr<Process> m_process;
|
2022-08-25 12:56:03 +02:00
|
|
|
QString m_stdOut;
|
|
|
|
|
QString m_stdErr;
|
2022-09-19 11:44:07 +02:00
|
|
|
ProcessResult m_result = ProcessResult::StartFailed;
|
2022-08-25 12:56:03 +02:00
|
|
|
|
2022-10-05 19:08:53 +02:00
|
|
|
RunFlags m_flags = RunFlags::None;
|
2015-04-27 15:03:07 +02:00
|
|
|
};
|
|
|
|
|
|
2022-09-01 17:13:31 +02:00
|
|
|
int VcsCommandPrivate::timeoutS() const
|
|
|
|
|
{
|
|
|
|
|
return std::accumulate(m_jobs.cbegin(), m_jobs.cend(), 0,
|
|
|
|
|
[](int sum, const Job &job) { return sum + job.timeoutS; });
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-26 15:03:22 +02:00
|
|
|
void VcsCommandPrivate::setup()
|
|
|
|
|
{
|
2022-10-11 17:00:11 +03:00
|
|
|
if (m_flags & RunFlags::ExpectRepoChanges)
|
|
|
|
|
GlobalFileChangeBlocker::instance()->forceBlocked(true);
|
2022-08-26 15:03:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VcsCommandPrivate::cleanup()
|
|
|
|
|
{
|
2022-10-11 17:00:11 +03:00
|
|
|
if (m_flags & RunFlags::ExpectRepoChanges)
|
|
|
|
|
GlobalFileChangeBlocker::instance()->forceBlocked(false);
|
2022-08-26 15:03:22 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
void VcsCommandPrivate::setupProcess(Process *process, const Job &job)
|
2022-09-01 17:35:36 +02:00
|
|
|
{
|
|
|
|
|
process->setExitCodeInterpreter(job.exitCodeInterpreter);
|
|
|
|
|
process->setTimeoutS(job.timeoutS);
|
|
|
|
|
if (!job.workingDirectory.isEmpty())
|
|
|
|
|
process->setWorkingDirectory(job.workingDirectory);
|
2022-10-05 19:08:53 +02:00
|
|
|
if (!(m_flags & RunFlags::SuppressCommandLogging))
|
2022-10-11 17:55:02 +02:00
|
|
|
VcsOutputWindow::appendCommand(job.workingDirectory, job.command);
|
2022-09-01 17:35:36 +02:00
|
|
|
process->setCommand(job.command);
|
|
|
|
|
process->setDisableUnixTerminal();
|
|
|
|
|
process->setEnvironment(environment());
|
2022-10-05 19:08:53 +02:00
|
|
|
if (m_flags & RunFlags::MergeOutputChannels)
|
2022-09-01 17:35:36 +02:00
|
|
|
process->setProcessChannelMode(QProcess::MergedChannels);
|
|
|
|
|
if (m_codec)
|
|
|
|
|
process->setCodec(m_codec);
|
2022-12-14 12:37:34 +02:00
|
|
|
process->setUseCtrlCStub(true);
|
2022-09-15 17:08:23 +02:00
|
|
|
|
|
|
|
|
installStdCallbacks(process);
|
2022-10-10 09:58:19 +02:00
|
|
|
|
|
|
|
|
if (m_flags & RunFlags::SuppressCommandLogging)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ProcessProgress *progress = new ProcessProgress(process);
|
|
|
|
|
progress->setDisplayName(m_displayName);
|
|
|
|
|
if (m_progressParser)
|
|
|
|
|
progress->setProgressParser(m_progressParser);
|
2022-09-01 17:35:36 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
void VcsCommandPrivate::installStdCallbacks(Process *process)
|
2022-09-01 17:35:36 +02:00
|
|
|
{
|
2022-10-06 15:06:09 +02:00
|
|
|
if (!(m_flags & RunFlags::MergeOutputChannels) && (m_flags & RunFlags::ProgressiveOutput
|
2022-10-10 09:58:19 +02:00
|
|
|
|| m_progressParser || !(m_flags & RunFlags::SuppressStdErr))) {
|
|
|
|
|
process->setTextChannelMode(Channel::Error, TextChannelMode::MultiLine);
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(process, &Process::textOnStandardError, this, [this](const QString &text) {
|
2022-10-05 19:08:53 +02:00
|
|
|
if (!(m_flags & RunFlags::SuppressStdErr))
|
2022-10-11 17:55:02 +02:00
|
|
|
VcsOutputWindow::appendError(text);
|
2022-10-06 15:06:09 +02:00
|
|
|
if (m_flags & RunFlags::ProgressiveOutput)
|
2022-09-01 17:35:36 +02:00
|
|
|
emit q->stdErrText(text);
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-10-06 15:06:09 +02:00
|
|
|
if (m_progressParser || m_flags & RunFlags::ProgressiveOutput
|
|
|
|
|
|| m_flags & RunFlags::ShowStdOut) {
|
2022-10-10 09:58:19 +02:00
|
|
|
process->setTextChannelMode(Channel::Output, TextChannelMode::MultiLine);
|
2023-05-03 16:00:22 +02:00
|
|
|
connect(process, &Process::textOnStandardOutput, this, [this](const QString &text) {
|
2022-12-09 13:59:12 +01:00
|
|
|
if (m_flags & RunFlags::ShowStdOut)
|
|
|
|
|
VcsOutputWindow::append(text);
|
2022-10-06 15:06:09 +02:00
|
|
|
if (m_flags & RunFlags::ProgressiveOutput)
|
2022-09-01 17:35:36 +02:00
|
|
|
emit q->stdOutText(text);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 11:14:41 +02:00
|
|
|
EventLoopMode VcsCommandPrivate::eventLoopMode() const
|
2022-09-01 17:35:36 +02:00
|
|
|
{
|
2022-10-10 09:58:19 +02:00
|
|
|
if ((m_flags & RunFlags::UseEventLoop) && isMainThread())
|
2022-10-05 11:14:41 +02:00
|
|
|
return EventLoopMode::On;
|
|
|
|
|
return EventLoopMode::Off;
|
2022-09-01 17:35:36 +02:00
|
|
|
}
|
|
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
void VcsCommandPrivate::handleDone(Process *process)
|
2022-09-01 17:35:36 +02:00
|
|
|
{
|
2022-09-01 11:49:41 +02:00
|
|
|
// Success/Fail message in appropriate window?
|
|
|
|
|
if (process->result() == ProcessResult::FinishedWithSuccess) {
|
2022-10-05 19:08:53 +02:00
|
|
|
if (m_flags & RunFlags::ShowSuccessMessage)
|
2022-10-11 17:55:02 +02:00
|
|
|
VcsOutputWindow::appendMessage(process->exitMessage());
|
2022-10-05 19:08:53 +02:00
|
|
|
} else if (!(m_flags & RunFlags::SuppressFailMessage)) {
|
2022-10-11 17:55:02 +02:00
|
|
|
VcsOutputWindow::appendError(process->exitMessage());
|
2022-09-01 17:35:36 +02:00
|
|
|
}
|
2022-10-11 17:55:02 +02:00
|
|
|
if (!(m_flags & RunFlags::ExpectRepoChanges))
|
|
|
|
|
return;
|
|
|
|
|
// TODO tell the document manager that the directory now received all expected changes
|
|
|
|
|
// Core::DocumentManager::unexpectDirectoryChange(d->m_workingDirectory);
|
|
|
|
|
VcsManager::emitRepositoryChanged(process->workingDirectory());
|
2022-09-01 17:35:36 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-25 12:56:03 +02:00
|
|
|
void VcsCommandPrivate::startAll()
|
|
|
|
|
{
|
|
|
|
|
// Check that the binary path is not empty
|
|
|
|
|
QTC_ASSERT(!m_jobs.isEmpty(), return);
|
|
|
|
|
QTC_ASSERT(!m_process, return);
|
2022-08-26 15:03:22 +02:00
|
|
|
setup();
|
2022-08-25 12:56:03 +02:00
|
|
|
m_currentJob = 0;
|
|
|
|
|
startNextJob();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VcsCommandPrivate::startNextJob()
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_currentJob < m_jobs.count(), return);
|
2023-05-03 16:00:22 +02:00
|
|
|
m_process.reset(new Process);
|
|
|
|
|
connect(m_process.get(), &Process::done, this, &VcsCommandPrivate::processDone);
|
2022-08-25 12:56:03 +02:00
|
|
|
setupProcess(m_process.get(), m_jobs.at(m_currentJob));
|
|
|
|
|
m_process->start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VcsCommandPrivate::processDone()
|
|
|
|
|
{
|
|
|
|
|
handleDone(m_process.get());
|
|
|
|
|
m_stdOut += m_process->cleanedStdOut();
|
|
|
|
|
m_stdErr += m_process->cleanedStdErr();
|
2022-09-19 11:44:07 +02:00
|
|
|
m_result = m_process->result();
|
2022-08-25 12:56:03 +02:00
|
|
|
++m_currentJob;
|
|
|
|
|
const bool success = m_process->result() == ProcessResult::FinishedWithSuccess;
|
|
|
|
|
if (m_currentJob < m_jobs.count() && success) {
|
|
|
|
|
m_process.release()->deleteLater();
|
|
|
|
|
startNextJob();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-09-19 11:44:07 +02:00
|
|
|
emit q->done();
|
2022-08-26 15:03:22 +02:00
|
|
|
cleanup();
|
2022-08-25 12:56:03 +02:00
|
|
|
q->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-27 15:03:07 +02:00
|
|
|
} // namespace Internal
|
|
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
VcsCommand::VcsCommand(const FilePath &workingDirectory, const Environment &environment) :
|
2022-09-01 17:13:31 +02:00
|
|
|
d(new Internal::VcsCommandPrivate(this, workingDirectory, environment))
|
2016-01-25 15:55:33 +01:00
|
|
|
{
|
2022-08-03 13:25:51 +02:00
|
|
|
VcsOutputWindow::setRepository(d->m_defaultWorkingDirectory);
|
2022-10-11 17:55:02 +02:00
|
|
|
connect(ICore::instance(), &ICore::coreAboutToClose, this, [this] {
|
2022-10-10 09:58:19 +02:00
|
|
|
if (d->m_process && d->m_process->isRunning())
|
2022-09-01 11:49:41 +02:00
|
|
|
d->cleanup();
|
2022-10-10 09:58:19 +02:00
|
|
|
d->m_process.reset();
|
2022-08-01 18:35:07 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
VcsCommand::~VcsCommand()
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
2022-10-10 09:58:19 +02:00
|
|
|
if (d->m_process && d->m_process->isRunning())
|
2022-08-26 15:03:22 +02:00
|
|
|
d->cleanup();
|
2015-04-27 15:03:07 +02:00
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
void VcsCommand::setDisplayName(const QString &name)
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
|
|
|
|
d->m_displayName = name;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 19:08:53 +02:00
|
|
|
void VcsCommand::addFlags(RunFlags f)
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
|
|
|
|
d->m_flags |= f;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
void VcsCommand::addJob(const CommandLine &command, int timeoutS,
|
2022-08-02 17:28:15 +02:00
|
|
|
const FilePath &workingDirectory,
|
|
|
|
|
const ExitCodeInterpreter &interpreter)
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
2022-08-25 12:56:03 +02:00
|
|
|
QTC_ASSERT(!command.executable().isEmpty(), return);
|
2022-08-02 17:28:15 +02:00
|
|
|
d->m_jobs.push_back({command, timeoutS, !workingDirectory.isEmpty()
|
|
|
|
|
? workingDirectory : d->m_defaultWorkingDirectory, interpreter});
|
2015-04-27 15:03:07 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-21 07:06:14 +02:00
|
|
|
void VcsCommand::start()
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
|
|
|
|
if (d->m_jobs.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
2022-08-25 12:56:03 +02:00
|
|
|
d->startAll();
|
2015-04-27 15:03:07 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
void VcsCommand::cancel()
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
2022-08-25 12:56:03 +02:00
|
|
|
if (d->m_process) {
|
|
|
|
|
// TODO: we may want to call cancel here...
|
|
|
|
|
d->m_process->stop();
|
|
|
|
|
// TODO: we may want to not wait here...
|
|
|
|
|
d->m_process->waitForFinished();
|
|
|
|
|
d->m_process.reset();
|
2015-04-27 15:03:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-19 08:27:41 +02:00
|
|
|
QString VcsCommand::cleanedStdOut() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_stdOut;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString VcsCommand::cleanedStdErr() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_stdErr;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-19 11:44:07 +02:00
|
|
|
ProcessResult VcsCommand::result() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 12:38:09 +02:00
|
|
|
CommandResult VcsCommand::runBlocking(const Utils::FilePath &workingDirectory,
|
|
|
|
|
const Utils::Environment &environment,
|
2022-10-05 19:08:53 +02:00
|
|
|
const Utils::CommandLine &command, RunFlags flags,
|
2022-10-05 12:38:09 +02:00
|
|
|
int timeoutS, QTextCodec *codec)
|
|
|
|
|
{
|
|
|
|
|
VcsCommand vcsCommand(workingDirectory, environment);
|
|
|
|
|
vcsCommand.addFlags(flags);
|
|
|
|
|
vcsCommand.setCodec(codec);
|
|
|
|
|
return vcsCommand.runBlockingHelper(command, timeoutS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CommandResult VcsCommand::runBlockingHelper(const CommandLine &command, int timeoutS)
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
2023-05-03 16:00:22 +02:00
|
|
|
Process process;
|
2022-07-29 14:41:15 +02:00
|
|
|
if (command.executable().isEmpty())
|
|
|
|
|
return {};
|
|
|
|
|
|
2022-09-15 17:08:23 +02:00
|
|
|
d->setupProcess(&process, {command, timeoutS, d->m_defaultWorkingDirectory, {}});
|
2015-04-27 15:03:07 +02:00
|
|
|
|
2022-10-05 11:14:41 +02:00
|
|
|
const EventLoopMode eventLoopMode = d->eventLoopMode();
|
2022-10-05 11:40:45 +02:00
|
|
|
process.setTimeOutMessageBoxEnabled(eventLoopMode == EventLoopMode::On);
|
2022-09-15 17:08:23 +02:00
|
|
|
process.runBlocking(eventLoopMode);
|
|
|
|
|
d->handleDone(&process);
|
2015-04-27 15:03:07 +02:00
|
|
|
|
2022-09-15 17:08:23 +02:00
|
|
|
return CommandResult(process);
|
2016-07-05 13:20:10 +02:00
|
|
|
}
|
|
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
void VcsCommand::setCodec(QTextCodec *codec)
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
|
|
|
|
d->m_codec = codec;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 16:16:35 +02:00
|
|
|
void VcsCommand::setProgressParser(const ProgressParser &parser)
|
2015-04-27 15:03:07 +02:00
|
|
|
{
|
|
|
|
|
d->m_progressParser = parser;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-03 16:00:22 +02:00
|
|
|
CommandResult::CommandResult(const Process &process)
|
2022-07-29 14:41:15 +02:00
|
|
|
: m_result(process.result())
|
|
|
|
|
, m_exitCode(process.exitCode())
|
|
|
|
|
, m_exitMessage(process.exitMessage())
|
|
|
|
|
, m_cleanedStdOut(process.cleanedStdOut())
|
|
|
|
|
, m_cleanedStdErr(process.cleanedStdErr())
|
|
|
|
|
, m_rawStdOut(process.rawStdOut())
|
|
|
|
|
{}
|
|
|
|
|
|
2022-12-08 18:50:54 +01:00
|
|
|
CommandResult::CommandResult(const VcsCommand &command)
|
|
|
|
|
: m_result(command.result())
|
|
|
|
|
, m_cleanedStdOut(command.cleanedStdOut())
|
|
|
|
|
, m_cleanedStdErr(command.cleanedStdErr())
|
|
|
|
|
{}
|
|
|
|
|
|
2022-08-01 10:49:13 +02:00
|
|
|
} // namespace VcsBase
|