From 64e4b98ff6f5f5008a6a0ba00fadadef3448e8de Mon Sep 17 00:00:00 2001 From: soramimi Date: Fri, 19 Jan 2024 03:45:31 +0900 Subject: [PATCH] =?UTF-8?q?GPG=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Guitar.pri | 2 + src/CommitDetailGetter.cpp | 117 +++++++++++++++++++++++++++++++++ src/CommitDetailGetter.h | 44 +++++++++++++ src/Git.cpp | 11 ++-- src/Git.h | 33 ++++++---- src/LogTableWidget.cpp | 10 +-- src/MainWindow.cpp | 96 +++++++++++++++++++-------- src/MainWindow.h | 9 ++- src/RepositoryWrapperFrame.cpp | 4 +- src/RepositoryWrapperFrame.h | 2 +- 10 files changed, 273 insertions(+), 55 deletions(-) create mode 100644 src/CommitDetailGetter.cpp create mode 100644 src/CommitDetailGetter.h diff --git a/Guitar.pri b/Guitar.pri index 45c8de1e..cdd46f74 100644 --- a/Guitar.pri +++ b/Guitar.pri @@ -111,6 +111,7 @@ macx { } SOURCES += \ + $$PWD/src/CommitDetailGetter.cpp \ src/AboutDialog.cpp \ src/AbstractProcess.cpp \ src/AbstractSettingForm.cpp \ @@ -251,6 +252,7 @@ SOURCES += \ src/coloredit/RingSlider.cpp HEADERS += \ + $$PWD/src/CommitDetailGetter.h \ filetype/filetype.h \ src/AboutDialog.h \ src/AbstractProcess.h \ diff --git a/src/CommitDetailGetter.cpp b/src/CommitDetailGetter.cpp new file mode 100644 index 00000000..f7bbb74a --- /dev/null +++ b/src/CommitDetailGetter.cpp @@ -0,0 +1,117 @@ +#include "CommitDetailGetter.h" + +CommitDetailGetter::~CommitDetailGetter() +{ + stop(); +} + +void CommitDetailGetter::start(GitPtr git) +{ + interrupted_ = false; + git_ = git; + thread_ = std::make_shared([&](){ + while (1) { + Item item; + { + std::unique_lock lock(mutex_); + if (interrupted_) return; + condition_.wait(lock); + if (interrupted_) return; + for (size_t i = 0; i < requests_.size(); i++) { + if (!requests_[i].done && !requests_[i].busy) { + requests_[i].busy = true; + item = requests_[i]; + break; + } + } + } + if (item.id) { + auto c = git_->log_signature(item.id); + if (c) { + bool ok = false; + Git::CommitItem const &commit = *c; + item.value = commit.sign.verify; + item.done = true; + item.busy = false; + { + std::lock_guard lock(mutex_); + for (size_t i = 0; i < requests_.size(); i++) { + if (item.id == requests_[i].id) { + requests_.erase(requests_.begin() + i); + requests_.push_back(item); + requests_[i] = item; + ok = true; + break; + } + } + } + if (ok) { + emit ready(); + } + } + } + } + }); +} + +void CommitDetailGetter::stop() +{ + { + std::lock_guard lock(mutex_); + interrupted_ = true; + condition_.notify_all(); + } + if (thread_) { + if (thread_->joinable()) { + thread_->join(); + } + thread_.reset(); + } + interrupted_ = false; +} + +CommitDetailGetter::Item CommitDetailGetter::request(Git::CommitID id) +{ + if (id.isValid()) { + std::lock_guard lock(mutex_); + for (size_t i = 0; i < requests_.size(); i++) { + if (id == requests_[i].id) { + if (requests_[i].done) { + Item item = requests_[i]; + requests_.erase(requests_.begin() + i); + requests_.push_back(item); + return item; + } + } + } + + Item item; + item.id = id; + requests_.push_back(item); + + size_t n = requests_.size(); + if (n > 100) { + n -= 100; + requests_.erase(requests_.begin(), requests_.begin() + n); + } + + condition_.notify_all(); + } + return {}; +} + +void CommitDetailGetter::apply(Git::CommitItemList *logs) +{ + std::lock_guard lock(mutex_); + for (size_t i = 0; i < requests_.size(); i++) { + if (requests_[i].done) { + Git::CommitID const &id = requests_[i].id; + for (size_t j = 0; j < logs->size(); j++) { + if (id == (*logs)[j].commit_id) { + (*logs)[j].sign.verify = requests_[i].value; + break; + } + } + } + } +} diff --git a/src/CommitDetailGetter.h b/src/CommitDetailGetter.h new file mode 100644 index 00000000..3d5d42cc --- /dev/null +++ b/src/CommitDetailGetter.h @@ -0,0 +1,44 @@ +#ifndef COMMITDETAILGETTER_H +#define COMMITDETAILGETTER_H + +#include "Git.h" +#include +#include +#include +#include +#include + +class CommitDetailGetter : public QObject { + Q_OBJECT +private: + std::mutex mutex_; + std::condition_variable condition_; + std::shared_ptr thread_; + bool interrupted_ = false; + + struct Item { + bool done = false; + bool busy = false; + Git::CommitID id; + int value = 0; + operator bool () const + { + return done; + } + }; + std::vector requests_; + + GitPtr git_; + +public: + CommitDetailGetter() = default; + virtual ~CommitDetailGetter(); + void start(GitPtr git); + void stop(); + CommitDetailGetter::Item request(Git::CommitID id); + void apply(Git::CommitItemList *logs); +signals: + void ready(); +}; + +#endif // COMMITDETAILGETTER_H diff --git a/src/Git.cpp b/src/Git.cpp index cf48b9a6..3f20adad 100644 --- a/src/Git.cpp +++ b/src/Git.cpp @@ -246,9 +246,9 @@ bool Git::chdirexec(std::function const &fn) return ok; } -bool Git::git(QString const &arg, bool chdir, bool errout, AbstractPtyProcess *pty, QString const &prefix) +bool Git::git_(QString const &arg, bool chdir, bool log, bool errout, AbstractPtyProcess *pty, QString const &prefix) { - qDebug() << "git: " << arg; + // qDebug() << "git: " << arg; QFileInfo info(gitCommand()); if (!info.isExecutable()) { qDebug() << "Invalid git command: " << gitCommand(); @@ -281,7 +281,7 @@ bool Git::git(QString const &arg, bool chdir, bool errout, AbstractPtyProcess *p cmd += QString("\"%1\" --no-pager ").arg(gitCommand()); cmd += arg; - if (m->info.fn_log_writer_callback) { + if (log && m->info.fn_log_writer_callback) { QByteArray ba; ba.append("> git "); ba.append(arg.toUtf8()); @@ -755,6 +755,7 @@ std::optional Git::parseCommitItem(QString const &line) item.commit_id = val; } else if (key == "gpg") { // %G? 署名検証結果 item.sign.verify = *val.utf16(); + item.sign.sg = Git::evaluateSignature(item.sign.verify); } else if (key == "key") { // %GF 署名フィンガープリント sign_fp = val.toStdString(); } else if (key == "trust") { @@ -804,7 +805,7 @@ Git::CommitItemList Git::log_all(CommitID const &id, int maxcount) QString cmd = "log --pretty=format:\"id:%H#parent:%P#author:%an#mail:%ae#date:%ci##%s\" --all -%1 %2"; cmd = cmd.arg(maxcount).arg(id.toQString()); - git(cmd); + git_(cmd, true, false, false, nullptr, {}); if (getProcessExitCode() == 0) { QString text = resultQString().trimmed(); QStringList lines = misc::splitLines(text); @@ -829,7 +830,7 @@ std::optional Git::log_signature(CommitID const &id) { QString cmd = "log -1 --show-signature --pretty=format:\"id:%H#gpg:%G?#key:%GF#sub:%GP#trust:%GT##%s\" %1"; cmd = cmd.arg(id.toQString()); - git(cmd); + git_(cmd, true, false, false, nullptr, {}); if (getProcessExitCode() == 0) { QString gpgtext; QString text = resultQString().trimmed(); diff --git a/src/Git.h b/src/Git.h index ba18699d..f1783132 100644 --- a/src/Git.h +++ b/src/Git.h @@ -57,6 +57,10 @@ class Git : QObject { if (!valid && !other.valid) return 0; return memcmp(id, other.id, sizeof(id)); } + operator bool () const + { + return isValid(); + } }; class Context { public: @@ -93,6 +97,15 @@ class Git : QObject { } }; + enum class SignatureGrade { + NoSignature, + Unknown, + Good, + Dubious, + Missing, + Bad, + }; + struct CommitItem { CommitID commit_id; QList parent_ids; @@ -107,6 +120,7 @@ class Git : QObject { char verify = 0; // git log format:%G? std::vector key_fingerprint; QString trust; + SignatureGrade sg; } sign; bool has_child = false; int marker_depth = -1; @@ -160,18 +174,9 @@ class Git : QObject { void makeForSingleFile(Git::Diff *diff, QString const &id_a, QString const &id_b, QString const &path, QString const &mode); }; - enum class SignatureGrade { - NoSignature, - Unknown, - Good, - Dubious, - Missing, - Bad, - }; - - static SignatureGrade evaluateSignature(char s) + static SignatureGrade evaluateSignature(char c) { - switch (s) { + switch (c) { case 'G': return SignatureGrade::Good; case 'U': @@ -366,7 +371,11 @@ class Git : QObject { std::string resultStdString() const; QString resultQString() const; bool chdirexec(std::function const &fn); - bool git(QString const &arg, bool chdir, bool errout = false, AbstractPtyProcess *pty = nullptr, const QString &prefix = {}); + bool git_(QString const &arg, bool chdir, bool log = true, bool errout = false, AbstractPtyProcess *pty = nullptr, const QString &prefix = {}); + bool git(QString const &arg, bool chdir, bool errout = false, AbstractPtyProcess *pty = nullptr, const QString &prefix = {}) + { + return git_(arg, chdir, true, errout, pty, prefix); + } bool git(QString const &arg) { return git(arg, true); diff --git a/src/LogTableWidget.cpp b/src/LogTableWidget.cpp index 8abe44cf..b1f86ba7 100644 --- a/src/LogTableWidget.cpp +++ b/src/LogTableWidget.cpp @@ -51,7 +51,7 @@ class LogTableWidgetDelegate : public MyTableWidgetDelegate { Git::CommitItem const *commit = frame()->commitItem(index.row()); if (commit) { - QIcon icon = frame()->verifiedIcon(commit->sign.verify); + QIcon icon = frame()->signatureVerificationIcon(commit->sign.verify, index.row()); if (!icon.isNull()) { QRect r = opt.rect.adjusted(6, 3, 0, -3); int h = r.height(); @@ -75,7 +75,6 @@ class LogTableWidgetDelegate : public MyTableWidgetDelegate { int row = index.row(); auto icon = frame()->committerIcon(row, {w, h}); if (!icon.isNull()) { - painter->save(); painter->setOpacity(0.5); // 半透明で描画 painter->drawImage(QRect(x, y, w, h), icon); @@ -127,9 +126,9 @@ class LogTableWidgetDelegate : public MyTableWidgetDelegate { QRect r(x0, y0, x1 - x0, y1 - y0); // ラベル枠の描画 - auto DrawLabelFrame = [&](int dx, int dy, QColor const &color){ - painter->setBrush(color); - painter->drawRoundedRect(r.adjusted((int)lround(dx + 3), (int)lround(dy + 3), (int)lround(dx - 3), (int)lround(dy - 3)), 3, 3); + auto DrawLabelFrame = [&](int dx, int dy, QColor const &color){ + painter->setBrush(color); + painter->drawRoundedRect(r.adjusted((int)lround(dx + 3), (int)lround(dy + 3), (int)lround(dx - 3), (int)lround(dy - 3)), 3, 3); }; QColor color = BranchLabel::color(label.kind); // ラベル表面の色 @@ -188,6 +187,7 @@ class LogTableWidgetDelegate : public MyTableWidgetDelegate { drawAvatar(painter, option, index); } + // ラベルの描画 if (index.column() == Message) { QString current_branch = frame()->currentBranchName(); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 0d7a1ae6..548a34e2 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -75,6 +75,7 @@ #include #include #include +#include "CommitDetailGetter.h" #include "GitObjectManager.h" #ifdef Q_OS_MAC @@ -182,6 +183,8 @@ struct MainWindow::Private { QAction *action_detect_profile = nullptr; int current_account_profiles = -1; + + CommitDetailGetter commit_detail_getter; }; MainWindow::MainWindow(QWidget *parent) @@ -289,6 +292,8 @@ MainWindow::MainWindow(QWidget *parent) connect(new QShortcut(QKeySequence("Ctrl+T"), this), &QShortcut::activated, this, &MainWindow::test); + connect(&m->commit_detail_getter, &CommitDetailGetter::ready, this, &MainWindow::onCommitDetailGetterReady); + // QString path = getBookmarksFilePath(); @@ -296,7 +301,7 @@ MainWindow::MainWindow(QWidget *parent) updateRepositoriesList(); // アイコン取得機能 - global->avatar_loader.connectAvatarReady(this, &MainWindow::avatarReady); + global->avatar_loader.connectAvatarReady(this, &MainWindow::onAvatarReady); connect(frame()->filediffwidget(), &FileDiffWidget::textcodecChanged, [&](){ updateDiffView(frame()); }); @@ -321,7 +326,7 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { - global->avatar_loader.disconnectAvatarReady(this, &MainWindow::avatarReady); + global->avatar_loader.disconnectAvatarReady(this, &MainWindow::onAvatarReady); cancelPendingUserEvents(); @@ -837,10 +842,18 @@ QString MainWindow::defaultWorkingDir() const return appsettings()->default_working_dir; } -QIcon MainWindow::verifiedIcon(char s) const +QIcon MainWindow::signatureVerificationIcon(char c, int row) const { - Git::SignatureGrade g = Git::evaluateSignature(s); - switch (g) { + { //@TODO: なんかもっといいかんじにする + auto const *commit = commitItem(frame(), row); + if (commit) { + auto const &item = m->commit_detail_getter.request(commit->commit_id); + c = (char)item.value; + } + } + + Git::SignatureGrade sg = Git::evaluateSignature(c); + switch (sg) { case Git::SignatureGrade::Good: return m->signature_good_icon; case Git::SignatureGrade::Bad: @@ -1893,9 +1906,34 @@ void MainWindow::updateAvatar(const Git::User &user, bool request) ui->widget_avatar_icon->setImage(icon); } -void MainWindow::avatarReady() +void MainWindow::updateCommitLog(int delay) +{ + (void)delay; //TODO: + + ui->tableWidget_log->viewport()->update(); +} + +void MainWindow::onAvatarReady() { updateAvatar(currentGitUser(), false); + updateCommitLog(100); +} + +Git::SignatureGrade MainWindow::hoge(int row) const //TODO: rename function +{ + Git::CommitItemList const &list = getCommitLog(frame()); + if (row >= 0 && row < list.size()) { + Git::SignatureGrade sg = list[row].sign.sg; + return sg; + } + return Git::SignatureGrade::Unknown; +} + +void MainWindow::onCommitDetailGetterReady() +{ + auto *logptr = getCommitLogPtr(frame()); + m->commit_detail_getter.apply(logptr); + updateCommitLog(100); } void MainWindow::setWindowTitle_(const Git::User &user) @@ -3804,6 +3842,9 @@ void MainWindow::openRepositoryWithFrame(RepositoryWrapperFrame *frame, GitPtr g frame->logtablewidget()->verticalScrollBar()->setValue(scroll_pos >= 0 ? scroll_pos : 0); } + m->commit_detail_getter.stop(); + m->commit_detail_getter.start(g->dup()); + updateUI(); } @@ -5031,7 +5072,7 @@ QString MainWindow::findFileID(RepositoryWrapperFrame *frame, const QString &com return lookupFileID(getObjCache(frame), commit_id, file); } -const Git::CommitItem *MainWindow::commitItem(RepositoryWrapperFrame *frame, int row) const +const Git::CommitItem *MainWindow::commitItem(RepositoryWrapperFrame const *frame, int row) const { auto const &logs = getCommitLog(frame); if (row >= 0 && row < (int)logs.size()) { @@ -5047,11 +5088,9 @@ QImage MainWindow::committerIcon(RepositoryWrapperFrame *frame, int row, QSize s auto const &logs = getCommitLog(frame); if (row >= 0 && row < (int)logs.size()) { Git::CommitItem const &commit = logs[row]; - if (misc::isValidMailAddress(commit.email)) { - icon = global->avatar_loader.fetch(commit.email, true); // from gavatar - if (!size.isValid()) { - icon = icon.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } + icon = global->avatar_loader.fetch(commit.email, true); // from gavatar + if (!size.isValid()) { + icon = icon.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } } } @@ -5102,22 +5141,18 @@ void MainWindow::doLogCurrentItemChanged(RepositoryWrapperFrame *frame) { clearFileList(frame); - int row = selectedLogIndex(frame); - QTableWidgetItem *item = frame->logtablewidget()->item(row, 0); - if (item) { - auto const &logs = getCommitLog(frame); - int index = item->data(IndexRole).toInt(); - if (index < (int)logs.size()) { - // ステータスバー更新 - updateStatusBarText(frame); - // 少し待ってファイルリストを更新する - postUserFunctionEvent([&](QVariant const &, void *p){ - RepositoryWrapperFrame *frame = reinterpret_cast(p); - updateCurrentFilesList(frame); - }, {}, reinterpret_cast(frame), 300); // 300ms後(キーボードのオートリピート想定) + Git::CommitItem const *commit = selectedCommitItem(frame); + if (commit) { + if (commit->commit_id) { + m->commit_detail_getter.request(commit->commit_id); } - } else { - row = -1; + // ステータスバー更新 + updateStatusBarText(frame); + // 少し待ってファイルリストを更新する + postUserFunctionEvent([&](QVariant const &, void *p){ + RepositoryWrapperFrame *frame = reinterpret_cast(p); + updateCurrentFilesList(frame); + }, {}, reinterpret_cast(frame), 300); // 300ms後(キーボードのオートリピート想定) } updateAncestorCommitMap(frame); frame->logtablewidget()->viewport()->update(); @@ -5417,6 +5452,13 @@ void MainWindow::keyPressEvent(QKeyEvent *event) test(); return; } + if (QApplication::focusWidget() == ui->tableWidget_log && (c == Qt::Key_Return || c == Qt::Key_Enter)) { + Git::CommitItem const *commit = selectedCommitItem(frame()); + if (commit) { + execCommitPropertyDialog(this, commit); + } + return; + } if (QApplication::focusWidget() == ui->widget_log) { auto write_char = [&](char c){ diff --git a/src/MainWindow.h b/src/MainWindow.h index 6f9a8860..3bb2e9da 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -467,7 +467,7 @@ class MainWindow : public QMainWindow { bool isValidWorkingCopy(GitPtr g) const; void emitWriteLog(const QByteArray &ba, bool receive); QString findFileID(RepositoryWrapperFrame *frame, const QString &commit_id, const QString &file); - const Git::CommitItem *commitItem(RepositoryWrapperFrame *frame, int row) const; + const Git::CommitItem *commitItem(const RepositoryWrapperFrame *frame, int row) const; QImage committerIcon(RepositoryWrapperFrame *frame, int row, QSize size) const; void changeSshKey(const QString &local_dir, const QString &ssh_key, bool save); static QString abbrevCommitID(const Git::CommitItem &commit); @@ -475,7 +475,7 @@ class MainWindow : public QMainWindow { ApplicationSettings *appsettings(); const ApplicationSettings *appsettings() const; QString defaultWorkingDir() const; - QIcon verifiedIcon(char s) const; + QIcon signatureVerificationIcon(char c, int row) const; QAction *addMenuActionProperty(QMenu *menu); QString currentWorkingCopyDir() const; Git::SubmoduleItem const *querySubmoduleByPath(const QString &path, Git::CommitItem *commit); @@ -483,6 +483,8 @@ class MainWindow : public QMainWindow { bool cloneRepository(const Git::CloneData &clonedata, const RepositoryData &repodata); Git::User currentGitUser() const; void setupExternalPrograms(); + void updateCommitLog(int delay); + Git::SignatureGrade hoge(int row) const; public slots: void writeLog_(QByteArray ba, bool receive); private slots: @@ -492,7 +494,8 @@ private slots: void onRepositoriesTreeDropped(); void onAvatarUpdated(RepositoryWrapperFrameP frame); void onInterval10ms(); - void avatarReady(); + void onAvatarReady(); + void onCommitDetailGetterReady(); void on_action_about_triggered(); void on_action_add_repository_triggered(); diff --git a/src/RepositoryWrapperFrame.cpp b/src/RepositoryWrapperFrame.cpp index 8f9cbae7..9e5a93d6 100644 --- a/src/RepositoryWrapperFrame.cpp +++ b/src/RepositoryWrapperFrame.cpp @@ -67,9 +67,9 @@ const Git::CommitItem *RepositoryWrapperFrame::commitItem(int row) return mainwindow()->commitItem(mainwindow()->frame(), row); } -QIcon RepositoryWrapperFrame::verifiedIcon(char s) const +QIcon RepositoryWrapperFrame::signatureVerificationIcon(char s, int row) const { - return mainwindow()->verifiedIcon(s); + return mainwindow()->signatureVerificationIcon(s, row); } QImage RepositoryWrapperFrame::committerIcon(int row, QSize size) const diff --git a/src/RepositoryWrapperFrame.h b/src/RepositoryWrapperFrame.h index f12f610c..460f17cb 100644 --- a/src/RepositoryWrapperFrame.h +++ b/src/RepositoryWrapperFrame.h @@ -38,7 +38,7 @@ class RepositoryWrapperFrame : public QFrame { explicit RepositoryWrapperFrame(QWidget *parent = nullptr); ~RepositoryWrapperFrame() override; Git::CommitItem const *commitItem(int row); - QIcon verifiedIcon(char s) const; + QIcon signatureVerificationIcon(char s, int row) const; QImage committerIcon(int row, QSize size) const; const QList *label(int row) const; QString currentBranchName() const;