Git: Reduce sync processes

Change-Id: I5d83636d4a018464ba6f248aa818fb8f840df9fc
Reviewed-by: André Hartmann <aha_1980@gmx.de>
Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io>
This commit is contained in:
Orgad Shaneh
2023-02-11 20:58:18 +02:00
committed by Orgad Shaneh
parent 6a0a4ac5e4
commit d20f543619
8 changed files with 172 additions and 77 deletions

View File

@@ -229,6 +229,7 @@ public:
QString currentSha;
QDateTime currentDateTime;
QStringList obsoleteLocalBranches;
std::unique_ptr<TaskTree> refreshTask;
bool oldBranchesIncluded = false;
struct OldEntry
@@ -399,29 +400,41 @@ void BranchModel::clear()
d->obsoleteLocalBranches.clear();
}
bool BranchModel::refresh(const FilePath &workingDirectory, QString *errorMessage)
void BranchModel::refresh(const FilePath &workingDirectory, ShowError showError)
{
if (d->refreshTask) {
endResetModel(); // for the running task tree.
d->refreshTask.reset(); // old running tree is reset, no handlers are being called
}
beginResetModel();
clear();
if (workingDirectory.isEmpty()) {
endResetModel();
return true;
return;
}
d->currentSha = d->client->synchronousTopRevision(workingDirectory, &d->currentDateTime);
QStringList args = {"--format=%(objectname)\t%(refname)\t%(upstream:short)\t"
using namespace Tasking;
const Process topRevisionProc =
d->client->topRevision(workingDirectory,
[=](const QString &ref, const QDateTime &dateTime) {
d->currentSha = ref;
d->currentDateTime = dateTime;
});
const auto setupForEachRef = [=](QtcProcess &process) {
d->workingDirectory = workingDirectory;
QStringList args = {"for-each-ref",
"--format=%(objectname)\t%(refname)\t%(upstream:short)\t"
"%(*objectname)\t%(committerdate:raw)\t%(*committerdate:raw)",
"refs/heads/**",
"refs/remotes/**"};
if (d->client->settings().showTags.value())
args << "refs/tags/**";
QString output;
if (!d->client->synchronousForEachRefCmd(workingDirectory, args, &output, errorMessage)) {
endResetModel();
return false;
}
d->client->setupCommand(process, workingDirectory, args);
};
d->workingDirectory = workingDirectory;
const auto forEachRefDone = [=](const QtcProcess &process) {
const QString output = process.stdOut();
const QStringList lines = output.split('\n');
for (const QString &l : lines)
d->parseOutputLine(l);
@@ -435,14 +448,35 @@ bool BranchModel::refresh(const FilePath &workingDirectory, QString *errorMessag
}
if (!d->currentBranch) {
BranchNode *local = d->rootNode->children.at(LocalBranches);
d->currentBranch = d->headNode = new BranchNode(Tr::tr("Detached HEAD"), "HEAD", QString(),
d->currentDateTime);
d->currentBranch = d->headNode = new BranchNode(
Tr::tr("Detached HEAD"), "HEAD", {}, d->currentDateTime);
local->prepend(d->headNode);
}
};
const auto forEachRefError = [=](const QtcProcess &process) {
if (showError == ShowError::No)
return;
const QString message = Tr::tr("Cannot run \"%1\" in \"%2\": %3")
.arg("git for-each-ref")
.arg(workingDirectory.toUserOutput())
.arg(process.cleanedStdErr());
VcsBase::VcsOutputWindow::appendError(message);
};
const auto finalize = [this] {
endResetModel();
d->refreshTask.release()->deleteLater();
};
return true;
const Group root {
topRevisionProc,
Process(setupForEachRef, forEachRefDone, forEachRefError),
OnGroupDone(finalize),
OnGroupError(finalize)
};
d->refreshTask.reset(new TaskTree(root));
d->refreshTask->start();
}
void BranchModel::setCurrentBranch()
@@ -469,7 +503,7 @@ void BranchModel::renameBranch(const QString &oldName, const QString &newName)
&output, &errorMessage))
VcsOutputWindow::appendError(errorMessage);
else
refresh(d->workingDirectory, &errorMessage);
refresh(d->workingDirectory);
}
void BranchModel::renameTag(const QString &oldName, const QString &newName)
@@ -482,7 +516,7 @@ void BranchModel::renameTag(const QString &oldName, const QString &newName)
&output, &errorMessage)) {
VcsOutputWindow::appendError(errorMessage);
} else {
refresh(d->workingDirectory, &errorMessage);
refresh(d->workingDirectory);
}
}

View File

@@ -34,7 +34,8 @@ public:
Qt::ItemFlags flags(const QModelIndex &index) const override;
void clear();
bool refresh(const Utils::FilePath &workingDirectory, QString *errorMessage);
enum class ShowError { No, Yes };
void refresh(const Utils::FilePath &workingDirectory, ShowError showError = ShowError::No);
void renameBranch(const QString &oldName, const QString &newName);
void renameTag(const QString &oldName, const QString &newName);

View File

@@ -160,9 +160,7 @@ void BranchView::refresh(const FilePath &repository, bool force)
if (!isVisible())
return;
QString errorMessage;
if (!m_model->refresh(m_repository, &errorMessage))
VcsBase::VcsOutputWindow::appendError(errorMessage);
m_model->refresh(m_repository, BranchModel::ShowError::Yes);
}
void BranchView::refreshCurrentBranch()
@@ -225,6 +223,7 @@ void BranchView::slotCustomContextMenu(const QPoint &point)
const bool isTag = m_model->isTag(index);
const bool hasActions = m_model->isLeaf(index);
const bool currentLocal = m_model->isLocal(currentBranch);
std::unique_ptr<TaskTree> taskTree;
QMenu contextMenu;
contextMenu.addAction(Tr::tr("&Add..."), this, &BranchView::add);
@@ -268,19 +267,19 @@ void BranchView::slotCustomContextMenu(const QPoint &point)
resetMenu->addAction(Tr::tr("&Mixed"), this, [this] { reset("mixed"); });
resetMenu->addAction(Tr::tr("&Soft"), this, [this] { reset("soft"); });
contextMenu.addMenu(resetMenu);
QString mergeTitle;
if (isFastForwardMerge()) {
contextMenu.addAction(Tr::tr("&Merge \"%1\" into \"%2\" (Fast-Forward)")
QAction *mergeAction = contextMenu.addAction(Tr::tr("&Merge \"%1\" into \"%2\"")
.arg(indexName, currentName),
this, [this] { merge(true); });
mergeTitle = Tr::tr("Merge \"%1\" into \"%2\" (No &Fast-Forward)")
.arg(indexName, currentName);
} else {
mergeTitle = Tr::tr("&Merge \"%1\" into \"%2\"")
.arg(indexName, currentName);
}
this,
[this] { merge(false); });
taskTree.reset(onFastForwardMerge([&] {
auto ffMerge = new QAction(
Tr::tr("&Merge \"%1\" into \"%2\" (Fast-Forward)").arg(indexName, currentName));
connect(ffMerge, &QAction::triggered, this, [this] { merge(true); });
contextMenu.insertAction(mergeAction, ffMerge);
mergeAction->setText(Tr::tr("Merge \"%1\" into \"%2\" (No &Fast-Forward)")
.arg(indexName, currentName));
}));
contextMenu.addAction(mergeTitle, this, [this] { merge(false); });
contextMenu.addAction(Tr::tr("&Rebase \"%1\" on \"%2\"")
.arg(currentName, indexName),
this, &BranchView::rebase);
@@ -523,13 +522,50 @@ bool BranchView::reset(const QByteArray &resetType)
return false;
}
bool BranchView::isFastForwardMerge()
TaskTree *BranchView::onFastForwardMerge(const std::function<void()> &callback)
{
using namespace Tasking;
const QModelIndex selected = selectedIndex();
QTC_CHECK(selected != m_model->currentBranch());
const QString branch = m_model->fullName(selected, true);
return GitClient::instance()->isFastForwardMerge(m_repository, branch);
struct FastForwardStorage
{
QString mergeBase;
QString topRevision;
};
const TreeStorage<FastForwardStorage> storage;
GitClient *client = GitClient::instance();
const auto setupMergeBase = [=](QtcProcess &process) {
client->setupCommand(process, m_repository, {"merge-base", "HEAD", branch});
};
const auto onMergeBaseDone = [storage](const QtcProcess &process) {
storage->mergeBase = process.cleanedStdOut().trimmed();
};
const Process topRevisionProc = client->topRevision(
m_repository,
[storage](const QString &revision, const QDateTime &) {
storage->topRevision = revision;
});
const Group root {
Storage(storage),
parallel,
Process(setupMergeBase, onMergeBaseDone),
topRevisionProc,
OnGroupDone([storage, callback] {
if (storage->mergeBase == storage->topRevision)
callback();
})
};
auto taskTree = new TaskTree(root);
taskTree->start();
return taskTree;
}
bool BranchView::merge(bool allowFastForward)

View File

@@ -20,6 +20,7 @@ QT_END_NAMESPACE;
namespace Utils {
class ElidingLabel;
class NavigationTreeView;
class TaskTree;
} // Utils
namespace Git::Internal {
@@ -54,7 +55,7 @@ private:
bool remove();
bool rename();
bool reset(const QByteArray &resetType);
bool isFastForwardMerge();
Utils::TaskTree *onFastForwardMerge(const std::function<void()> &callback);
bool merge(bool allowFastForward);
void rebase();
bool cherryPick();

View File

@@ -1730,19 +1730,28 @@ bool GitClient::synchronousRevParseCmd(const FilePath &workingDirectory, const Q
}
// Retrieve head revision
QString GitClient::synchronousTopRevision(const FilePath &workingDirectory, QDateTime *dateTime)
Utils::Tasking::Process GitClient::topRevision(
const FilePath &workingDirectory,
const std::function<void(const QString &, const QDateTime &)> &callback)
{
const QStringList arguments = {"show", "-s", "--pretty=format:%H:%ct", HEAD};
const CommandResult result = vcsSynchronousExec(workingDirectory, arguments, RunFlags::NoOutput);
if (result.result() != ProcessResult::FinishedWithSuccess)
return QString();
const QStringList output = result.cleanedStdOut().trimmed().split(':');
if (dateTime && output.size() > 1) {
using namespace Tasking;
const auto setupProcess = [=](QtcProcess &process) {
setupCommand(process, workingDirectory, {"show", "-s", "--pretty=format:%H:%ct", HEAD});
};
const auto onProcessDone = [=](const QtcProcess &process) {
const QStringList output = process.cleanedStdOut().trimmed().split(':');
QDateTime dateTime;
if (output.size() > 1) {
bool ok = false;
const qint64 timeT = output.at(1).toLongLong(&ok);
*dateTime = ok ? QDateTime::fromSecsSinceEpoch(timeT) : QDateTime();
if (ok)
dateTime = QDateTime::fromSecsSinceEpoch(timeT);
}
return output.first();
callback(output.first(), dateTime);
};
return Process(setupProcess, onProcessDone);
}
bool GitClient::isRemoteCommit(const FilePath &workingDirectory, const QString &commit)
@@ -1752,13 +1761,6 @@ bool GitClient::isRemoteCommit(const FilePath &workingDirectory, const QString &
return !result.rawStdOut().isEmpty();
}
bool GitClient::isFastForwardMerge(const FilePath &workingDirectory, const QString &branch)
{
const CommandResult result = vcsSynchronousExec(workingDirectory,
{"merge-base", HEAD, branch}, RunFlags::NoOutput);
return result.cleanedStdOut().trimmed() == synchronousTopRevision(workingDirectory);
}
// Format an entry in a one-liner for selection list using git log.
QString GitClient::synchronousShortDescription(const FilePath &workingDirectory, const QString &revision,
const QString &format) const

View File

@@ -13,6 +13,7 @@
#include <utils/fileutils.h>
#include <utils/futuresynchronizer.h>
#include <utils/qtcprocess.h>
#include <QObject>
#include <QString>
@@ -241,9 +242,10 @@ public:
QString synchronousTopic(const Utils::FilePath &workingDirectory) const;
bool synchronousRevParseCmd(const Utils::FilePath &workingDirectory, const QString &ref,
QString *output, QString *errorMessage = nullptr) const;
QString synchronousTopRevision(const Utils::FilePath &workingDirectory, QDateTime *dateTime = nullptr);
Utils::Tasking::Process topRevision(
const Utils::FilePath &workingDirectory,
const std::function<void(const QString &, const QDateTime &)> &callback);
bool isRemoteCommit(const Utils::FilePath &workingDirectory, const QString &commit);
bool isFastForwardMerge(const Utils::FilePath &workingDirectory, const QString &branch);
void fetch(const Utils::FilePath &workingDirectory, const QString &remote);
void pull(const Utils::FilePath &workingDirectory, bool rebase);

View File

@@ -22,6 +22,7 @@
#include <utils/commandline.h>
#include <utils/environment.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <QDebug>
#include <QStringList>
@@ -89,6 +90,16 @@ VcsCommand *VcsBaseClientImpl::createCommand(const FilePath &workingDirectory,
return cmd;
}
void VcsBaseClientImpl::setupCommand(Utils::QtcProcess &process,
const FilePath &workingDirectory,
const QStringList &args) const
{
process.setEnvironment(processEnvironment());
process.setWorkingDirectory(workingDirectory);
process.setCommand({vcsBinary(), args});
process.setUseCtrlCStub(true);
}
void VcsBaseClientImpl::enqueueJob(VcsCommand *cmd, const QStringList &args,
const ExitCodeInterpreter &interpreter) const
{

View File

@@ -23,6 +23,10 @@ class QTextCodec;
class QToolBar;
QT_END_NAMESPACE
namespace Utils {
class QtcProcess;
}
namespace VcsBase {
class CommandResult;
@@ -56,6 +60,10 @@ public:
VcsCommand *createCommand(const Utils::FilePath &workingDirectory,
VcsBaseEditorWidget *editor = nullptr) const;
void setupCommand(Utils::QtcProcess &process,
const Utils::FilePath &workingDirectory,
const QStringList &args) const;
void enqueueJob(VcsCommand *cmd, const QStringList &args,
const Utils::ExitCodeInterpreter &interpreter = {}) const;