Commit 23f32f52 authored by Waqar Ahmed's avatar Waqar Ahmed Committed by Christoph Cullmann
Browse files

Make gitStatus fully concurrent and more efficient

No unnecessary temporary allocations all the time.
parent 78acc8ff
......@@ -5,20 +5,52 @@
*/
#include "gitstatus.h"
#include <gitprocess.h>
#include <KLocalizedString>
#include <QByteArray>
#include <QList>
#include <QProcess>
#include <QScopeGuard>
#include <charconv>
#include <optional>
static void numStatForStatus(QVector<GitUtils::StatusItem> &list, const QString &workDir, bool modified)
{
const auto args = modified ? QStringList{QStringLiteral("diff"), QStringLiteral("--numstat"), QStringLiteral("-z")}
: QStringList{QStringLiteral("diff"), QStringLiteral("--numstat"), QStringLiteral("--staged"), QStringLiteral("-z")};
QProcess git;
setupGitProcess(git, workDir, args);
git.start(QProcess::ReadOnly);
if (git.waitForStarted() && git.waitForFinished(-1)) {
if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
return;
}
}
GitUtils::parseDiffNumStat(list, git.readAllStandardOutput());
}
GitUtils::GitParsedStatus GitUtils::parseStatus(const QByteArray &raw)
GitUtils::GitParsedStatus GitUtils::parseStatus(const QByteArray &raw, bool withNumStat, const QString &workingDir)
{
QVector<GitUtils::StatusItem> untracked;
QVector<GitUtils::StatusItem> unmerge;
QVector<GitUtils::StatusItem> staged;
QVector<GitUtils::StatusItem> changed;
const QList<QByteArray> rawList = raw.split(0x00);
for (const auto &r : rawList) {
if (r.isEmpty() || r.length() < 3) {
int start = 0;
int next = raw.indexOf(char(0x0), start);
const char *p = raw.data();
while (next != -1) {
auto _ = qScopeGuard([&raw, &start, &next] {
start = next + 1;
next = raw.indexOf(char(0), start);
});
std::string_view r(p + start, next - start);
if (r.length() < 3) {
continue;
}
......@@ -27,68 +59,76 @@ GitUtils::GitParsedStatus GitUtils::parseStatus(const QByteArray &raw)
uint16_t xy = (((uint16_t)x) << 8) | y;
using namespace GitUtils;
std::string_view file_view = r.substr(3);
QByteArray file(file_view.data(), file_view.size());
switch (xy) {
case StatusXY::QQ:
untracked.append({r.mid(3), GitStatus::Untracked, 'U', 0, 0});
untracked.append({file, GitStatus::Untracked, 'U', 0, 0});
break;
case StatusXY::II:
untracked.append({r.mid(3), GitStatus::Ignored, 'I', 0, 0});
untracked.append({file, GitStatus::Ignored, 'I', 0, 0});
break;
case StatusXY::DD:
unmerge.append({r.mid(3), GitStatus::Unmerge_BothDeleted, x, 0, 0});
unmerge.append({file, GitStatus::Unmerge_BothDeleted, x, 0, 0});
break;
case StatusXY::AU:
unmerge.append({r.mid(3), GitStatus::Unmerge_AddedByUs, x, 0, 0});
unmerge.append({file, GitStatus::Unmerge_AddedByUs, x, 0, 0});
break;
case StatusXY::UD:
unmerge.append({r.mid(3), GitStatus::Unmerge_DeletedByThem, x, 0, 0});
unmerge.append({file, GitStatus::Unmerge_DeletedByThem, x, 0, 0});
break;
case StatusXY::UA:
unmerge.append({r.mid(3), GitStatus::Unmerge_AddedByThem, x, 0, 0});
unmerge.append({file, GitStatus::Unmerge_AddedByThem, x, 0, 0});
break;
case StatusXY::DU:
unmerge.append({r.mid(3), GitStatus::Unmerge_DeletedByUs, x, 0, 0});
unmerge.append({file, GitStatus::Unmerge_DeletedByUs, x, 0, 0});
break;
case StatusXY::AA:
unmerge.append({r.mid(3), GitStatus::Unmerge_BothAdded, x, 0, 0});
unmerge.append({file, GitStatus::Unmerge_BothAdded, x, 0, 0});
break;
case StatusXY::UU:
unmerge.append({r.mid(3), GitStatus::Unmerge_BothModified, x, 0, 0});
unmerge.append({file, GitStatus::Unmerge_BothModified, x, 0, 0});
break;
}
switch (x) {
case 'M':
staged.append({r.mid(3), GitStatus::Index_Modified, x, 0, 0});
staged.append({file, GitStatus::Index_Modified, x, 0, 0});
break;
case 'A':
staged.append({r.mid(3), GitStatus::Index_Added, x, 0, 0});
staged.append({file, GitStatus::Index_Added, x, 0, 0});
break;
case 'D':
staged.append({r.mid(3), GitStatus::Index_Deleted, x, 0, 0});
staged.append({file, GitStatus::Index_Deleted, x, 0, 0});
break;
case 'R':
staged.append({r.mid(3), GitStatus::Index_Renamed, x, 0, 0});
staged.append({file, GitStatus::Index_Renamed, x, 0, 0});
break;
case 'C':
staged.append({r.mid(3), GitStatus::Index_Copied, x, 0, 0});
staged.append({file, GitStatus::Index_Copied, x, 0, 0});
break;
}
switch (y) {
case 'M':
changed.append({r.mid(3), GitStatus::WorkingTree_Modified, y, 0, 0});
changed.append({file, GitStatus::WorkingTree_Modified, y, 0, 0});
break;
case 'D':
changed.append({r.mid(3), GitStatus::WorkingTree_Deleted, y, 0, 0});
changed.append({file, GitStatus::WorkingTree_Deleted, y, 0, 0});
break;
case 'A':
changed.append({r.mid(3), GitStatus::WorkingTree_IntentToAdd, y, 0, 0});
changed.append({file, GitStatus::WorkingTree_IntentToAdd, y, 0, 0});
break;
}
}
if (withNumStat) {
numStatForStatus(changed, workingDir, true);
numStatForStatus(staged, workingDir, false);
}
return {untracked, unmerge, staged, changed};
}
......@@ -124,18 +164,11 @@ QString GitUtils::statusString(GitUtils::GitStatus s)
return QString();
}
static bool getNum(const QByteArray &numBytes, int *num)
{
bool res = false;
*num = numBytes.toInt(&res);
return res;
}
static void addNumStat(QVector<GitUtils::StatusItem> &items, int add, int sub, const QByteArray &file)
static void addNumStat(QVector<GitUtils::StatusItem> &items, int add, int sub, std::string_view file)
{
// look in modified first, then staged
auto item = std::find_if(items.begin(), items.end(), [&file](const GitUtils::StatusItem &si) {
return si.file == file;
auto item = std::find_if(items.begin(), items.end(), [file](const GitUtils::StatusItem &si) {
return file.compare(0, si.file.size(), si.file.data()) == 0;
});
if (item != items.end()) {
item->linesAdded = add;
......@@ -144,27 +177,64 @@ static void addNumStat(QVector<GitUtils::StatusItem> &items, int add, int sub, c
}
}
static std::optional<int> toInt(std::string_view s)
{
int value{};
auto res = std::from_chars(s.data(), s.data() + s.size(), value);
if (res.ptr == (s.data() + s.size())) {
return value;
}
return std::nullopt;
}
void GitUtils::parseDiffNumStat(QVector<GitUtils::StatusItem> &items, const QByteArray &raw)
{
const auto lines = raw.split(0x00);
for (const auto &line : lines) {
// format: 12(adds)\t10(subs)\tFileName
const auto cols = line.split('\t');
if (cols.length() < 3) {
int start = 0;
int next = raw.indexOf(char(0), start);
const char *r = raw.constData();
// format:
// 12\t10\tFileName
// 12 = add, 10 = sub, fileName at the end
while (next != -1) {
auto _ = qScopeGuard([&raw, &start, &next] {
start = next + 1;
next = raw.indexOf(char(0), start);
});
std::string_view line(r + start, next - start);
size_t addEnd = line.find_first_of('\t');
if (addEnd == std::string_view::npos) {
continue;
}
int add = 0;
if (!getNum(cols.at(0), &add)) {
size_t subStart = line.find_first_not_of('\t', addEnd);
if (subStart == std::string_view::npos) {
continue;
}
size_t subEnd = line.find_first_of('\t', subStart);
if (subEnd == std::string_view::npos) {
continue;
}
std::string_view addStr = line.substr(0, addEnd);
std::string_view subStr = line.substr(subStart, subEnd - subStart);
std::string_view fileStr = line.substr(subEnd + 1, line.size() - (subEnd + 1));
auto add = toInt(addStr);
auto sub = toInt(subStr);
if (!add.has_value()) {
continue;
}
int sub = 0;
if (!getNum(cols.at(1), &sub)) {
if (!add.has_value()) {
continue;
}
const auto file = cols.at(2);
addNumStat(items, add, sub, file);
addNumStat(items, add.value(), sub.value(), fileStr);
}
}
......
......@@ -64,7 +64,7 @@ struct GitParsedStatus {
QVector<StatusItem> changed;
};
GitParsedStatus parseStatus(const QByteArray &raw);
GitParsedStatus parseStatus(const QByteArray &raw, bool withNumStat, const QString &workingDir);
void parseDiffNumStat(QVector<GitUtils::StatusItem> &items, const QByteArray &raw);
......
......@@ -323,7 +323,8 @@ void GitWidget::getStatus(bool untracked, bool submodules)
// no error on status failure
// sendMessage(QString::fromUtf8(git->readAllStandardError()), true);
} else {
auto future = QtConcurrent::run(GitUtils::parseStatus, git->readAllStandardOutput());
const bool withNumStat = m_pluginView->plugin()->showGitStatusWithNumStat();
auto future = QtConcurrent::run(GitUtils::parseStatus, git->readAllStandardOutput(), withNumStat, m_gitPath);
m_gitStatusWatcher.setFuture(future);
}
git->deleteLater();
......@@ -709,32 +710,10 @@ void GitWidget::parseStatusReady()
{
GitUtils::GitParsedStatus s = m_gitStatusWatcher.result();
if (m_pluginView->plugin()->showGitStatusWithNumStat()) {
numStatForStatus(s.changed, true);
numStatForStatus(s.staged, false);
}
m_model->addItems(std::move(s), m_pluginView->plugin()->showGitStatusWithNumStat());
hideEmptyTreeNodes();
}
void GitWidget::numStatForStatus(QVector<GitUtils::StatusItem> &list, bool modified)
{
const auto args = modified ? QStringList{QStringLiteral("diff"), QStringLiteral("--numstat"), QStringLiteral("-z")}
: QStringList{QStringLiteral("diff"), QStringLiteral("--numstat"), QStringLiteral("--staged"), QStringLiteral("-z")};
QProcess git;
setupGitProcess(git, m_gitPath, args);
git.start(QProcess::ReadOnly);
if (git.waitForStarted() && git.waitForFinished(-1)) {
if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
return;
}
}
GitUtils::parseDiffNumStat(list, git.readAllStandardOutput());
}
void GitWidget::branchCompareFiles(const QString &from, const QString &to)
{
if (from.isEmpty() && to.isEmpty()) {
......
......@@ -92,7 +92,6 @@ private:
void launchExternalDiffTool(const QString &file, bool staged);
void commitChanges(const QString &msg, const QString &desc, bool signOff, bool amend = false);
void applyDiff(const QString &fileName, bool staged, bool hunk, KTextEditor::View *v);
void numStatForStatus(QVector<GitUtils::StatusItem> &list, bool modified);
void branchCompareFiles(const QString &from, const QString &to);
QMenu *stashMenu();
......
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