From 4e5bfd8e7faa01ba176a9061d867f8568f0d8452 Mon Sep 17 00:00:00 2001 From: devnull Date: Thu, 6 Apr 2023 16:45:22 -0400 Subject: [PATCH 01/21] Extend tools to handle multiple files Extend DiffTool, MergeTool, EditTool, and ShowTool to support one or more files in each tool. --- src/tools/DiffTool.cpp | 151 ++++++++++++++++------------ src/tools/DiffTool.h | 19 +++- src/tools/EditTool.cpp | 54 ++++++---- src/tools/EditTool.h | 9 +- src/tools/ExternalTool.cpp | 39 +++---- src/tools/ExternalTool.h | 20 ++-- src/tools/MergeTool.cpp | 201 +++++++++++++++++++++---------------- src/tools/MergeTool.h | 22 ++-- src/tools/ShowTool.cpp | 33 ++++-- src/tools/ShowTool.h | 10 +- src/ui/FileContextMenu.cpp | 96 +++++++++--------- src/ui/FileContextMenu.h | 2 + 12 files changed, 377 insertions(+), 279 deletions(-) diff --git a/src/tools/DiffTool.cpp b/src/tools/DiffTool.cpp index 67019a67d..091560d49 100644 --- a/src/tools/DiffTool.cpp +++ b/src/tools/DiffTool.cpp @@ -10,17 +10,23 @@ #include "DiffTool.h" #include "git/Command.h" #include "git/Repository.h" +#include "git/Blob.h" #include #include #include -DiffTool::DiffTool(const QString &file, const git::Blob &localBlob, - const git::Blob &remoteBlob, QObject *parent) - : ExternalTool(file, parent), mLocalBlob(localBlob), - mRemoteBlob(remoteBlob) {} +DiffTool::DiffTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent) + : ExternalTool(files, diff, repo, parent) {} bool DiffTool::isValid() const { - return (ExternalTool::isValid() && mLocalBlob.isValid()); + bool isValid = ExternalTool::isValid(); + foreach (const QString &file, mFiles) { + git::Blob fileBlob; + isValid &= DiffTool::getBlob(file, git::Diff::OldFile, fileBlob) & + fileBlob.isValid(); + }; + return isValid; } ExternalTool::Kind DiffTool::kind() const { return Diff; } @@ -35,72 +41,77 @@ bool DiffTool::start() { if (command.isEmpty()) return false; - // Write temporary files. - QString templatePath = QDir::temp().filePath(QFileInfo(mFile).fileName()); - QTemporaryFile *local = new QTemporaryFile(templatePath, this); - if (!local->open()) - return false; - - local->write(mLocalBlob.content()); - local->flush(); - - QString remotePath; - if (!mRemoteBlob.isValid()) { - remotePath = mFile; - } else { - QTemporaryFile *remote = new QTemporaryFile(templatePath, this); - if (!remote->open()) - return false; - - remote->write(mRemoteBlob.content()); - remote->flush(); - - remotePath = remote->fileName(); - } - - // Destroy this after process finishes. - QProcess *process = new QProcess(this); - process->setProcessChannelMode( - QProcess::ProcessChannelMode::ForwardedChannels); - auto signal = QOverload::of(&QProcess::finished); - QObject::connect(process, signal, [this, process] { - qDebug() << "Merge Process Exited!"; - qDebug() << "Stdout: " << process->readAllStandardOutput(); - qDebug() << "Stderr: " << process->readAllStandardError(); - deleteLater(); - }); + int numFiles = mFiles.size(), i = -1; + foreach (const QString &file, mFiles) { + ++i; + git::Blob fileBlob; + if (!DiffTool::getBlob(file, git::Diff::OldFile, fileBlob)) continue; + + // Get the path to any file blob. + QString blobMoniker; + if (!fileBlob.isValid()) { + blobMoniker = file; + } else { + QString templatePath = QDir::temp().filePath(QFileInfo(file).fileName()); + QTemporaryFile *temp= new QTemporaryFile(templatePath, this); + if (!temp->open()) + return false; + + temp->write(fileBlob.content()); + temp->flush(); + + blobMoniker = temp->fileName(); + } + + // Destroy this after process finishes. + QProcess *process = new QProcess(this); + process->setProcessChannelMode( + QProcess::ProcessChannelMode::ForwardedChannels); + auto signal = QOverload::of(&QProcess::finished); + QObject::connect(process, signal, [this, process, &numFiles] { + qDebug() << "Merge Process Exited!"; + qDebug() << "Stdout: " << process->readAllStandardOutput(); + qDebug() << "Stderr: " << process->readAllStandardError(); + if (--numFiles == 0) { + deleteLater(); + } + }); + + // Convert to absolute path. + QString filePath = mRepo.workdir().filePath(file); #if defined(FLATPAK) || defined(DEBUG_FLATPAK) - QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), - "--env=REMOTE=" + remotePath, - "--env=MERGED=" + mFile, "--env=BASE=" + mFile}; - arguments.append("sh"); - arguments.append("-c"); - arguments.append(command); - process->start("flatpak-spawn", arguments); + QStringList arguments = {"--host", "--env=LOCAL=" + filePath, + "--env=REMOTE=" + blobMoniker, + "--env=MERGED=" + file, "--env=BASE=" + file}; + arguments.append("sh"); + arguments.append("-c"); + arguments.append(command); + process->start("flatpak-spawn", arguments); #else - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("LOCAL", local->fileName()); - env.insert("REMOTE", remotePath); - env.insert("MERGED", mFile); - env.insert("BASE", mFile); - process->setProcessEnvironment(env); - - QString bash = git::Command::bashPath(); - if (!bash.isEmpty()) { - process->start(bash, {"-c", command}); - } else if (!shell) { - process->start(git::Command::substitute(env, command)); - } else { - emit error(BashNotFound); - return false; - } + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("LOCAL", filePath); + env.insert("REMOTE", blobMoniker); + env.insert("MERGED", file); + env.insert("BASE", file); + process->setProcessEnvironment(env); + + QString bash = git::Command::bashPath(); + if (!bash.isEmpty()) { + process->start(bash, {"-c", command}); + } else if (!shell) { + process->start(git::Command::substitute(env, command)); + } else { + emit error(BashNotFound); + return false; + } #endif - if (!process->waitForStarted()) { - qDebug() << "DiffTool starting failed"; - return false; + if (!process->waitForStarted()) { + qDebug() << "DiffTool starting failed"; + return false; + } } // Detach from parent. @@ -108,3 +119,13 @@ bool DiffTool::start() { return true; } + +bool DiffTool::getBlob(const QString &file, const git::Diff::File &version, + git::Blob &blob) const +{ + int index = mDiff.indexOf(file); + if (index < 0) return false; + + blob = mRepo.lookupBlob(mDiff.id(index, version)); + return true; +} diff --git a/src/tools/DiffTool.h b/src/tools/DiffTool.h index 29246164f..19cb8ef01 100644 --- a/src/tools/DiffTool.h +++ b/src/tools/DiffTool.h @@ -10,15 +10,22 @@ #ifndef DIFFTOOL_H #define DIFFTOOL_H +#include #include "ExternalTool.h" -#include "git/Blob.h" + +class QObject; +namespace git { +class Diff; +class Repository; +class Blob; +}; class DiffTool : public ExternalTool { Q_OBJECT public: - DiffTool(const QString &file, const git::Blob &localBlob, - const git::Blob &remoteBlob, QObject *parent = nullptr); + DiffTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent); bool isValid() const override; @@ -28,8 +35,10 @@ class DiffTool : public ExternalTool { bool start() override; protected: - git::Blob mLocalBlob; - git::Blob mRemoteBlob; + +private: + bool getBlob(const QString &file, const git::Diff::File &version, + git::Blob &blob) const; }; #endif diff --git a/src/tools/EditTool.cpp b/src/tools/EditTool.cpp index f27ed0278..be7ef4804 100644 --- a/src/tools/EditTool.cpp +++ b/src/tools/EditTool.cpp @@ -14,11 +14,17 @@ #include #include -EditTool::EditTool(const QString &file, QObject *parent) - : ExternalTool(file, parent) {} +EditTool::EditTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent) + : ExternalTool(files, diff, repo, parent) {} bool EditTool::isValid() const { - return (ExternalTool::isValid() && QFileInfo(mFile).isFile()); + if (!ExternalTool::isValid()) return false; + + foreach (const QString file, mFiles) { + if (!QFileInfo(mRepo.workdir().filePath(file)).isFile()) return false; + } + return true; } ExternalTool::Kind EditTool::kind() const { return Edit; } @@ -27,26 +33,31 @@ QString EditTool::name() const { return tr("Edit in External Editor"); } bool EditTool::start() { git::Config config = git::Config::global(); - QString editor = config.value("gui.editor"); + QString baseEditor = config.value("gui.editor"); + + if (baseEditor.isEmpty()) + baseEditor = qgetenv("GIT_EDITOR"); - if (editor.isEmpty()) - editor = qgetenv("GIT_EDITOR"); + if (baseEditor.isEmpty()) + baseEditor = config.value("core.editor"); - if (editor.isEmpty()) - editor = config.value("core.editor"); + if (baseEditor.isEmpty()) + baseEditor = qgetenv("VISUAL"); - if (editor.isEmpty()) - editor = qgetenv("VISUAL"); + if (baseEditor.isEmpty()) + baseEditor = qgetenv("EDITOR"); - if (editor.isEmpty()) - editor = qgetenv("EDITOR"); + if (baseEditor.isEmpty()) { + foreach (const QString &file, mFiles) { + QDesktopServices::openUrl(QUrl::fromLocalFile(file)); + } + return true; + } - if (editor.isEmpty()) - return QDesktopServices::openUrl(QUrl::fromLocalFile(mFile)); + QString editor = baseEditor; // Find arguments. - QStringList args = editor.split("\" \""); - + QStringList args = baseEditor.split("\" \""); if (args.count() > 1) { // Format 1: "Command" "Argument1" "Argument2" editor = args[0]; @@ -62,13 +73,20 @@ bool EditTool::start() { editor = editor.left(li + 1); } else { // Format 3: "Command" (no argument) - // Format 4: Command (no argument) + if (fi == -1) { + // Format 4: Command Argument1 Argument2 + // Format 5: Command (no argument) + args = editor.split(" "); + editor = args.size() ? args[0] : ""; + } } } // Remove command, add filename, trim command. args.removeFirst(); - args.append(mFile); + foreach (const QString &file, mFiles) { + args.append(mRepo.workdir().filePath(file)); + } editor.remove("\""); // Destroy this after process finishes. diff --git a/src/tools/EditTool.h b/src/tools/EditTool.h index 43bf542a9..ad49e432f 100644 --- a/src/tools/EditTool.h +++ b/src/tools/EditTool.h @@ -12,11 +12,18 @@ #include "ExternalTool.h" +class QObject; +namespace git { + class Diff; + class Repository; +}; + class EditTool : public ExternalTool { Q_OBJECT public: - EditTool(const QString &file, QObject *parent = nullptr); + EditTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent); bool isValid() const override; diff --git a/src/tools/ExternalTool.cpp b/src/tools/ExternalTool.cpp index c27f4d2c1..f7080b577 100644 --- a/src/tools/ExternalTool.cpp +++ b/src/tools/ExternalTool.cpp @@ -13,7 +13,7 @@ #include "conf/Settings.h" #include "git/Diff.h" #include "git/Config.h" -#include "git/Index.h" +#include "git/Blob.h" #include "git/Repository.h" #include #include @@ -39,10 +39,12 @@ void splitCommand(const QString &command, QString &program, QString &args) { } // namespace -ExternalTool::ExternalTool(const QString &file, QObject *parent) - : QObject(parent), mFile(file) {} +ExternalTool::ExternalTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent) + : QObject(parent), mFiles(files), mDiff(diff), mRepo(repo) { } -bool ExternalTool::isValid() const { return !mFile.isEmpty(); } +bool ExternalTool::isValid() const { return !mFiles.isEmpty() && + !mFiles.first().isEmpty(); } QString ExternalTool::lookupCommand(const QString &key, bool &shell) { git::Config config = git::Config::global(); @@ -96,30 +98,15 @@ QList ExternalTool::readBuiltInTools(const QString &key) { return tools; } -ExternalTool *ExternalTool::create(const QString &file, const git::Diff &diff, - const git::Repository &repo, - QObject *parent) { - if (!diff.isValid()) - return nullptr; +bool ExternalTool::isConflicted(const QString &file) const +{ + if (!mDiff.isValid()) + return false; - int index = diff.indexOf(file); + int index = mDiff.indexOf(file); if (index < 0) - return nullptr; - - // Convert to absolute path. - QString path = repo.workdir().filePath(file); + return false; // Create merge tool. - if (diff.status(index) == GIT_DELTA_CONFLICTED) { - git::Index::Conflict conflict = repo.index().conflict(file); - git::Blob local = repo.lookupBlob(conflict.ours); - git::Blob remote = repo.lookupBlob(conflict.theirs); - git::Blob base = repo.lookupBlob(conflict.ancestor); - return new MergeTool(path, local, remote, base, parent); - } - - // Create diff tool. - git::Blob local = repo.lookupBlob(diff.id(index, git::Diff::OldFile)); - git::Blob remote = repo.lookupBlob(diff.id(index, git::Diff::NewFile)); - return new DiffTool(path, local, remote, parent); + return mDiff.status(index) == GIT_DELTA_CONFLICTED; } diff --git a/src/tools/ExternalTool.h b/src/tools/ExternalTool.h index 87a9a88fe..3ce6e577d 100644 --- a/src/tools/ExternalTool.h +++ b/src/tools/ExternalTool.h @@ -12,11 +12,8 @@ #include #include - -namespace git { -class Diff; -class Repository; -} // namespace git +#include "git/Diff.h" +#include "git/Repository.h" class ExternalTool : public QObject { Q_OBJECT @@ -40,7 +37,8 @@ class ExternalTool : public QObject { } }; - ExternalTool(const QString &file, QObject *parent = nullptr); + ExternalTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent); virtual bool isValid() const; @@ -49,19 +47,19 @@ class ExternalTool : public QObject { virtual bool start() = 0; + bool isConflicted(const QString &file) const; + static QString lookupCommand(const QString &key, bool &shell); static QList readGlobalTools(const QString &key); static QList readBuiltInTools(const QString &key); - static ExternalTool *create(const QString &file, const git::Diff &diff, - const git::Repository &repo, - QObject *parent = nullptr); - signals: void error(Error error); protected: - QString mFile; + QStringList mFiles; + git::Diff mDiff; + git::Repository mRepo; }; #endif diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index 1afe840c2..e11466704 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -19,15 +19,33 @@ #include #include -MergeTool::MergeTool(const QString &file, const git::Blob &localBlob, - const git::Blob &remoteBlob, const git::Blob &baseBlob, - QObject *parent) - : ExternalTool(file, parent), mLocalBlob(localBlob), - mRemoteBlob(remoteBlob), mBaseBlob(baseBlob) {} +MergeTool::MergeTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent) + : ExternalTool(files, diff, repo, parent) { + + if (!mFiles.empty()) { + foreach (const QString &file, mFiles) { + if (isConflicted(file)) { + mMergeFiles.append(file); + git::Index::Conflict conflict = repo.index().conflict(file); + mLocalEditedBlobs.append(repo.lookupBlob(conflict.ours)); + mRemoteEditedBlobs.append(repo.lookupBlob(conflict.theirs)); + mBaseBlobs.append(repo.lookupBlob(conflict.ancestor)); + } + } + } +} bool MergeTool::isValid() const { - return (ExternalTool::isValid() && mLocalBlob.isValid() && - mRemoteBlob.isValid()); + if (!ExternalTool::isValid()) return false; + + int numBlobs = mLocalEditedBlobs.size(); + for (int i = 0; i < numBlobs; ++i) { + if (!mLocalEditedBlobs[i].isValid() || !mRemoteEditedBlobs[i].isValid()) { + return false; + } + } + return true; } ExternalTool::Kind MergeTool::kind() const { return Merge; } @@ -42,101 +60,110 @@ bool MergeTool::start() { if (command.isEmpty()) return false; - // Write temporary files. - QString templatePath = QDir::temp().filePath(QFileInfo(mFile).fileName()); - QTemporaryFile *local = new QTemporaryFile(templatePath, this); - if (!local->open()) - return false; - - local->write(mLocalBlob.content()); - local->flush(); - - QTemporaryFile *remote = new QTemporaryFile(templatePath, this); - if (!remote->open()) - return false; + int numMergeFiles = mMergeFiles.size(); + for (int i = 0; i < numMergeFiles; ++i) { + // Write temporary files. + QString templatePath = QDir::temp().filePath( + QFileInfo(mMergeFiles[i]).fileName()); + QTemporaryFile *local = new QTemporaryFile(templatePath, this); + if (!local->open()) + return false; - remote->write(mRemoteBlob.content()); - remote->flush(); + local->write(mLocalEditedBlobs[i].content()); + local->flush(); - QString basePath; - if (mBaseBlob.isValid()) { - QTemporaryFile *base = new QTemporaryFile(templatePath, this); - if (!base->open()) + QTemporaryFile *remote = new QTemporaryFile(templatePath, this); + if (!remote->open()) return false; - base->write(mBaseBlob.content()); - base->flush(); + remote->write(mRemoteEditedBlobs[i].content()); + remote->flush(); - basePath = base->fileName(); - } + QString basePath; + if (mBaseBlobs[i].isValid()) { + QTemporaryFile *base = new QTemporaryFile(templatePath, this); + if (!base->open()) + return false; - // Make the backup copy. - QString backupPath = QString("%1.orig").arg(mFile); - if (!QFile::copy(mFile, backupPath)) { - // FIXME: What should happen if the backup already exists? - } + base->write(mBaseBlobs[i].content()); + base->flush(); - // Destroy this after process finishes. - QProcess *process = new QProcess(this); - process->setProcessChannelMode( - QProcess::ProcessChannelMode::ForwardedChannels); - git::Repository repo = mLocalBlob.repo(); - auto signal = QOverload::of(&QProcess::finished); - QObject::connect(process, signal, [this, repo, backupPath, process] { - qDebug() << "Merge Process Exited!"; - qDebug() << "Stdout: " << process->readAllStandardOutput(); - qDebug() << "Stderr: " << process->readAllStandardError(); - - QFileInfo merged(mFile); - QFileInfo backup(backupPath); - git::Config config = git::Config::global(); - bool modified = (merged.lastModified() > backup.lastModified()); - if (!modified || !config.value("mergetool.keepBackup")) - QFile::remove(backupPath); - - if (modified) { - int length = repo.workdir().path().length(); - repo.index().setStaged({mFile.mid(length + 1)}, true); + basePath = base->fileName(); } - deleteLater(); - }); + // Make the backup copy. + QString backupPath = QString("%1.orig").arg(mMergeFiles[i]); + if (!QFile::copy(mMergeFiles[i], backupPath)) { + // FIXME: What should happen if the backup already exists? + } - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("LOCAL", local->fileName()); - env.insert("REMOTE", remote->fileName()); - env.insert("MERGED", mFile); - env.insert("BASE", basePath); - process->setProcessEnvironment(env); + // Destroy this after process finishes. + QProcess *process = new QProcess(this); + process->setProcessChannelMode( + QProcess::ProcessChannelMode::ForwardedChannels); + git::Repository repo = mLocalEditedBlobs[i].repo(); + auto signal = QOverload::of( + &QProcess::finished); + QObject::connect(process, signal, + [this, repo, i, backupPath, process, &numMergeFiles] { + qDebug() << "Merge Process Exited!"; + qDebug() << "Stdout: " << process->readAllStandardOutput(); + qDebug() << "Stderr: " << process->readAllStandardError(); + + QFileInfo merged(mMergeFiles[i]); + QFileInfo backup(backupPath); + git::Config config = git::Config::global(); + bool modified = (merged.lastModified() > backup.lastModified()); + if (!modified || !config.value("mergetool.keepBackup")) + QFile::remove(backupPath); + + if (modified) { + int length = repo.workdir().path().length(); + repo.index().setStaged({mMergeFiles[i].mid(length + 1)}, true); + } + + if (--numMergeFiles) { + deleteLater(); + } + }); + + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("LOCAL", local->fileName()); + env.insert("REMOTE", remote->fileName()); + env.insert("MERGED", mMergeFiles[i]); + env.insert("BASE", basePath); + process->setProcessEnvironment(env); #if defined(FLATPAK) || defined(DEBUG_FLATPAK) - QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), - "--env=REMOTE=" + remote->fileName(), - "--env=MERGED=" + mFile, "--env=BASE=" + basePath}; - arguments.append("sh"); - arguments.append("-c"); - arguments.append(command); - // Debug("Command: " << "flatpak-spawn"); - process->start("flatpak-spawn", arguments); - // Debug("QProcess Arguments: " << process->arguments()); - if (!process->waitForStarted()) { - Debug("MergeTool starting failed"); - return false; - } + QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), + "--env=REMOTE=" + remote->fileName(), + "--env=MERGED=" + mFile, + "--env=BASE=" + basePath}; + arguments.append("sh"); + arguments.append("-c"); + arguments.append(command); + // Debug("Command: " << "flatpak-spawn"); + process->start("flatpak-spawn", arguments); + // Debug("QProcess Arguments: " << process->arguments()); + if (!process->waitForStarted()) { + Debug("MergeTool starting failed"); + return false; + } #else - QString bash = git::Command::bashPath(); - if (!bash.isEmpty()) { - process->start(bash, {"-c", command}); - } else if (!shell) { - process->start(git::Command::substitute(env, command)); - } else { - emit error(BashNotFound); - return false; - } + QString bash = git::Command::bashPath(); + if (!bash.isEmpty()) { + process->start(bash, {"-c", command}); + } else if (!shell) { + process->start(git::Command::substitute(env, command)); + } else { + emit error(BashNotFound); + return false; + } - if (!process->waitForStarted()) - return false; + if (!process->waitForStarted()) + return false; #endif + } // Detach from parent. setParent(nullptr); diff --git a/src/tools/MergeTool.h b/src/tools/MergeTool.h index a9212f67a..2385af825 100644 --- a/src/tools/MergeTool.h +++ b/src/tools/MergeTool.h @@ -10,16 +10,23 @@ #ifndef MERGETOOL_H #define MERGETOOL_H +#include +#include #include "ExternalTool.h" #include "git/Blob.h" +class QObject; +namespace git { + class Diff; + class Repository; +}; + class MergeTool : public ExternalTool { Q_OBJECT public: - MergeTool(const QString &file, const git::Blob &localBlob, - const git::Blob &remoteBlob, const git::Blob &baseBlob, - QObject *parent = nullptr); + MergeTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent); bool isValid() const override; @@ -29,9 +36,12 @@ class MergeTool : public ExternalTool { bool start() override; protected: - git::Blob mLocalBlob; - git::Blob mRemoteBlob; - git::Blob mBaseBlob; + +private: + QVector mMergeFiles; + QVector mLocalEditedBlobs; + QVector mRemoteEditedBlobs; + QVector mBaseBlobs; }; #endif diff --git a/src/tools/ShowTool.cpp b/src/tools/ShowTool.cpp index e2b503d14..2b54e6a53 100644 --- a/src/tools/ShowTool.cpp +++ b/src/tools/ShowTool.cpp @@ -95,24 +95,37 @@ bool ShowTool::openFileManager(QString path) { #endif } -ShowTool::ShowTool(const QString &file, QObject *parent) - : ExternalTool(file, parent) {} +ShowTool::ShowTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent) + : ExternalTool(files, diff, repo, parent) {} ExternalTool::Kind ShowTool::kind() const { return Show; } QString ShowTool::name() const { return tr("Show in %1").arg(tr(NAME)); } bool ShowTool::start() { + + foreach (const QString &file, mFiles) { #if defined(Q_OS_MAC) - return QProcess::startDetached( - "/usr/bin/osascript", {"-e", "tell application \"Finder\"", "-e", - QString("reveal POSIX file \"%1\"").arg(mFile), - "-e", "activate", "-e", "end tell"}); + if (!QProcess::startDetached( + "/usr/bin/osascript", {"-e", "tell application \"Finder\"", "-e", + QString("reveal POSIX file \"%1\"").arg(file), + "-e", "activate", "-e", "end tell"})) { + return false; + } #elif defined(Q_OS_WIN) - return QProcess::startDetached("explorer.exe", - {"/select,", QDir::toNativeSeparators(mFile)}); + if (!QProcess::startDetached("explorer.exe", + {"/select,", + QDir::toNativeSeparators(file)})) { + return false; + } #else - QFileInfo info(mFile); - return openFileManager(info.isDir() ? info.filePath() : info.path()); + QFileInfo info(file); + if (!openFileManager(info.isDir() ? info.filePath() : info.path())) { + return false; + } #endif + } + + return true; } diff --git a/src/tools/ShowTool.h b/src/tools/ShowTool.h index b9624017a..32c174067 100644 --- a/src/tools/ShowTool.h +++ b/src/tools/ShowTool.h @@ -12,13 +12,21 @@ #include "ExternalTool.h" +class QObject; +namespace git { + class Diff; + class Repository; +}; + + class ShowTool : public ExternalTool { Q_OBJECT public: static bool openFileManager(QString path); - ShowTool(const QString &file, QObject *parent = nullptr); + ShowTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent); Kind kind() const override; QString name() const override; diff --git a/src/ui/FileContextMenu.cpp b/src/ui/FileContextMenu.cpp index 9c46b9baf..f14015f6b 100644 --- a/src/ui/FileContextMenu.cpp +++ b/src/ui/FileContextMenu.cpp @@ -19,6 +19,8 @@ #include "host/Repository.h" #include "tools/EditTool.h" #include "tools/ShowTool.h" +#include "tools/DiffTool.h" +#include "tools/MergeTool.h" #include #include #include @@ -82,57 +84,34 @@ void handlePath(const git::Repository &repo, const QString &path, FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, const git::Index &index, QWidget *parent) : QMenu(parent), mView(view), mFiles(files) { - // Show diff and merge tools for the currently selected diff. git::Diff diff = view->diff(); - git::Repository repo = view->repo(); - if (!diff.isValid()) return; - // Create external tools. - QList showTools; - QList editTools; - QList diffTools; - QList mergeTools; - foreach (const QString &file, files) { - // Convert to absolute path. - QString path = repo.workdir().filePath(file); - - // Add show tool. - showTools.append(new ShowTool(path, this)); - - // Add edit tool. - editTools.append(new EditTool(path, this)); - - // Add diff or merge tool. - if (ExternalTool *tool = ExternalTool::create(file, diff, repo, this)) { - switch (tool->kind()) { - case ExternalTool::Diff: - diffTools.append(tool); - break; + auto errFunc = [this](ExternalTool::Error error) { + if (error != ExternalTool::BashNotFound) + return; - case ExternalTool::Merge: - mergeTools.append(tool); - break; + QString title = tr("Bash Not Found"); + QString text = tr("Bash was not found on your PATH."); + QMessageBox msg(QMessageBox::Warning, title, text, QMessageBox::Ok, + this); + msg.setInformativeText( + tr("Bash is required to execute external tools.")); + msg.exec(); + }; - default: - Q_ASSERT(false); - break; - } - - connect(tool, &ExternalTool::error, [this](ExternalTool::Error error) { - if (error != ExternalTool::BashNotFound) - return; + git::Repository repo = view->repo(); - QString title = tr("Bash Not Found"); - QString text = tr("Bash was not found on your PATH."); - QMessageBox msg(QMessageBox::Warning, title, text, QMessageBox::Ok, - this); - msg.setInformativeText( - tr("Bash is required to execute external tools.")); - msg.exec(); - }); - } + // Create external tools. + QList showTools, editTools, diffTools, mergeTools; + attachTool(new ShowTool(files, diff, repo, this), showTools); + attachTool(new EditTool(files, diff, repo, this), editTools); + if (diff.isConflicted()) { + attachTool(new MergeTool(files, diff, repo, this), mergeTools); + } + else { + attachTool(new DiffTool(files, diff, repo, this), diffTools); } // Add external tool actions. @@ -195,7 +174,7 @@ FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, // Navigate QMenu *navigate = addMenu(tr("Navigate to")); QAction *nextAct = navigate->addAction(tr("Next Revision")); - connect(nextAct, &QAction::triggered, [view, file] { + QMenu::connect(nextAct, &QAction::triggered, [view, file] { if (git::Commit next = view->nextRevision(file)) { view->selectCommit(next, file); } else { @@ -204,7 +183,7 @@ FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, }); QAction *prevAct = navigate->addAction(tr("Previous Revision")); - connect(prevAct, &QAction::triggered, [view, file] { + QMenu::connect(prevAct, &QAction::triggered, [view, file] { if (git::Commit prev = view->previousRevision(file)) { view->selectCommit(prev, file); } else { @@ -327,7 +306,7 @@ void FileContextMenu::handleUncommittedChanges(const git::Index &index, QString text = tr("Discard Changes"); QPushButton *discard = dialog->addButton(text, QMessageBox::AcceptRole); discard->setObjectName("DiscardButton"); - connect(discard, &QPushButton::clicked, [view, modified, submodules] { + QMenu::connect(discard, &QPushButton::clicked, [view, modified, submodules] { git::Repository repo = view->repo(); int strategy = GIT_CHECKOUT_FORCE; if (modified.count() && @@ -354,7 +333,7 @@ void FileContextMenu::handleUncommittedChanges(const git::Index &index, // Ignore QAction *ignore = addAction(tr("Ignore")); ignore->setObjectName("IgnoreAction"); - connect(ignore, &QAction::triggered, this, &FileContextMenu::ignoreFile); + QMenu::connect(ignore, &QAction::triggered, this, &FileContextMenu::ignoreFile); foreach (const QString &file, files) { int index = diff.indexOf(file); if (index < 0) @@ -472,7 +451,7 @@ void FileContextMenu::ignoreFile() { d->setAttribute(Qt::WA_DeleteOnClose); auto *view = mView; - connect(d, &QDialog::accepted, [d, view]() { + QMenu::connect(d, &QDialog::accepted, [d, view]() { auto ignore = d->ignoreText(); if (!ignore.isEmpty()) view->ignore(ignore); @@ -541,3 +520,22 @@ void FileContextMenu::addExternalToolsAction( } } } + +void FileContextMenu::attachTool(ExternalTool *tool, + QList &tools) +{ + tools.append(tool); + + QMenu::connect(tool, &ExternalTool::error, [this](ExternalTool::Error error) { + if (error != ExternalTool::BashNotFound) + return; + + QString title = tr("Bash Not Found"); + QString text = tr("Bash was not found on your PATH."); + QMessageBox msg(QMessageBox::Warning, title, text, QMessageBox::Ok, + this); + msg.setInformativeText( + tr("Bash is required to execute external tools.")); + msg.exec(); + }); +} diff --git a/src/ui/FileContextMenu.h b/src/ui/FileContextMenu.h index 8d89543fd..26fa445b9 100644 --- a/src/ui/FileContextMenu.h +++ b/src/ui/FileContextMenu.h @@ -14,6 +14,7 @@ #include "git/Index.h" #include "git/Commit.h" #include +#include class ExternalTool; class RepoView; @@ -36,6 +37,7 @@ private slots: const QStringList &files); void handleCommits(const QList &commits, const QStringList &files); + void attachTool(ExternalTool *tool, QList &tools); RepoView *mView; QStringList mFiles; From 76f07e79580d6bd55f2f7614f514ef5e6a8f0284 Mon Sep 17 00:00:00 2001 From: devnull Date: Thu, 13 Apr 2023 16:48:36 -0400 Subject: [PATCH 02/21] Fix typo in FLATPAK variant. --- src/tools/MergeTool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index e11466704..a1d9c4882 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -137,7 +137,7 @@ bool MergeTool::start() { #if defined(FLATPAK) || defined(DEBUG_FLATPAK) QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), "--env=REMOTE=" + remote->fileName(), - "--env=MERGED=" + mFile, + "--env=MERGED=" + mMergeFiles[i], "--env=BASE=" + basePath}; arguments.append("sh"); arguments.append("-c"); From 01b09f4422a68a211a09e14ab96a2b38578f4446 Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 17 Apr 2023 18:04:25 -0400 Subject: [PATCH 03/21] Close the branch drop-down list after the user checks out a branch. Avoid the inconvenience of having to close the drop-down list manually after checking out a branch. ...and format... --- src/ui/ReferenceView.cpp | 12 ++++++++---- src/ui/ReferenceView.h | 5 +++++ src/ui/ReferenceWidget.cpp | 3 +++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/ui/ReferenceView.cpp b/src/ui/ReferenceView.cpp index 00f7e9843..96ddf87de 100644 --- a/src/ui/ReferenceView.cpp +++ b/src/ui/ReferenceView.cpp @@ -184,7 +184,7 @@ ReferenceView::ReferenceView(const git::Repository &repo, Kinds kinds, git::Reference ref = index.data(Qt::UserRole).value(); if (ref.isValid() && !ref.isHead()) - RepoView::parentView(this)->checkout(ref); + checkout(ref); }); } @@ -263,6 +263,11 @@ QString ReferenceView::kindString(const git::Reference &ref) { return QString(); } +void ReferenceView::checkout(git::Reference &ref) { + RepoView::parentView(this)->checkout(ref); + emit checkedOut(); +} + void ReferenceView::showEvent(QShowEvent *event) { resetTabIndex(); setFocus(); @@ -277,9 +282,8 @@ void ReferenceView::contextMenuEvent(QContextMenuEvent *event) { return; QMenu menu; - QAction *checkout = menu.addAction(tr("Checkout"), [this, ref] { - RepoView::parentView(this)->checkout(ref); - }); + QAction *checkout = + menu.addAction(tr("Checkout"), [this, &ref] { this->checkout(ref); }); RepoView *view = RepoView::parentView(this); checkout->setEnabled(!ref.isHead() && !view->repo().isBare()); diff --git a/src/ui/ReferenceView.h b/src/ui/ReferenceView.h index 2d7d2a7b9..7617df71d 100644 --- a/src/ui/ReferenceView.h +++ b/src/ui/ReferenceView.h @@ -54,10 +54,15 @@ class ReferenceView : public QTreeView { void setCommit(const git::Commit &commit); +signals: + void checkedOut(); + protected: void showEvent(QShowEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; + void checkout(git::Reference &ref); + private: bool mPopup; QAbstractItemModel *mSource; diff --git a/src/ui/ReferenceWidget.cpp b/src/ui/ReferenceWidget.cpp index d7cb14539..f7f54f7d9 100644 --- a/src/ui/ReferenceWidget.cpp +++ b/src/ui/ReferenceWidget.cpp @@ -132,6 +132,9 @@ ReferenceWidget::ReferenceWidget(const git::Repository &repo, mView = new ReferenceView(repo, kinds, false, this); mView->setSelectionModel(new SelectionModel(mView->model(), repo)); + // Close the drop-down list of branches if it has just been checked out. + connect(mView, &ReferenceView::checkedOut, [button] { button->click(); }); + QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); From db90aa8414a6f3ffaa4793913c690d03be0935dd Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 24 Apr 2023 20:32:55 -0400 Subject: [PATCH 04/21] In DiffTool, use old and new blobs for stashes and commits. --- .gitignore | 3 ++ src/tools/DiffTool.cpp | 73 +++++++++++++++++++++++++----------------- src/tools/DiffTool.h | 4 +++ 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index edbbfdfca..c8dedf9e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +.cache .DS_Store .project .vscode/ @@ -6,3 +7,5 @@ CMakeLists.txt.user cmake-build-debug/ cmake-build-release/ .idea/ +compile_commands.json + diff --git a/src/tools/DiffTool.cpp b/src/tools/DiffTool.cpp index 091560d49..7dbd2c735 100644 --- a/src/tools/DiffTool.cpp +++ b/src/tools/DiffTool.cpp @@ -16,14 +16,14 @@ #include DiffTool::DiffTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) + const git::Repository &repo, QObject *parent) : ExternalTool(files, diff, repo, parent) {} bool DiffTool::isValid() const { bool isValid = ExternalTool::isValid(); foreach (const QString &file, mFiles) { git::Blob fileBlob; - isValid &= DiffTool::getBlob(file, git::Diff::OldFile, fileBlob) & + isValid &= DiffTool::getBlob(file, git::Diff::OldFile, fileBlob) & fileBlob.isValid(); }; return isValid; @@ -41,26 +41,21 @@ bool DiffTool::start() { if (command.isEmpty()) return false; - int numFiles = mFiles.size(), i = -1; - foreach (const QString &file, mFiles) { - ++i; - git::Blob fileBlob; - if (!DiffTool::getBlob(file, git::Diff::OldFile, fileBlob)) continue; - - // Get the path to any file blob. - QString blobMoniker; - if (!fileBlob.isValid()) { - blobMoniker = file; - } else { - QString templatePath = QDir::temp().filePath(QFileInfo(file).fileName()); - QTemporaryFile *temp= new QTemporaryFile(templatePath, this); - if (!temp->open()) - return false; + bool isWorkDirDiff = mDiff.isValid() && mDiff.isStatusDiff(); - temp->write(fileBlob.content()); - temp->flush(); + int numFiles = mFiles.size(); + foreach (const QString &filePathAndName, mFiles) { + git::Blob fileBlob1, fileBlob2; + if (!getBlob(filePathAndName, git::Diff::OldFile, fileBlob1) || + !getBlob(filePathAndName, git::Diff::NewFile, fileBlob2)) + continue; - blobMoniker = temp->fileName(); + // Get the path to the file (either a full or relative path). + QString otherPathAndName = filePathAndName; + if (fileBlob1.isValid()) { + otherPathAndName = makeBlobTempFullFilePath(filePathAndName, fileBlob1); + if (otherPathAndName.isEmpty()) + return false; } // Destroy this after process finishes. @@ -78,11 +73,13 @@ bool DiffTool::start() { }); // Convert to absolute path. - QString filePath = mRepo.workdir().filePath(file); + QString fullFilePath = + isWorkDirDiff ? mRepo.workdir().filePath(filePathAndName) + : makeBlobTempFullFilePath(filePathAndName, fileBlob2); #if defined(FLATPAK) || defined(DEBUG_FLATPAK) - QStringList arguments = {"--host", "--env=LOCAL=" + filePath, - "--env=REMOTE=" + blobMoniker, + QStringList arguments = {"--host", "--env=LOCAL=" + fullFilePath, + "--env=REMOTE=" + otherPathAndName, "--env=MERGED=" + file, "--env=BASE=" + file}; arguments.append("sh"); arguments.append("-c"); @@ -91,10 +88,10 @@ bool DiffTool::start() { #else QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("LOCAL", filePath); - env.insert("REMOTE", blobMoniker); - env.insert("MERGED", file); - env.insert("BASE", file); + env.insert("LOCAL", fullFilePath); + env.insert("REMOTE", otherPathAndName); + env.insert("MERGED", filePathAndName); + env.insert("BASE", filePathAndName); process->setProcessEnvironment(env); QString bash = git::Command::bashPath(); @@ -121,11 +118,27 @@ bool DiffTool::start() { } bool DiffTool::getBlob(const QString &file, const git::Diff::File &version, - git::Blob &blob) const -{ + git::Blob &blob) const { int index = mDiff.indexOf(file); - if (index < 0) return false; + if (index < 0) + return false; blob = mRepo.lookupBlob(mDiff.id(index, version)); return true; } + +QString DiffTool::makeBlobTempFullFilePath(const QString &filePathAndName, + const git::Blob &fileBlob) { + QString blobTmpFullFilePath; + + QFileInfo fileInfo(filePathAndName); + QString templatePath = QDir::temp().filePath(fileInfo.fileName()); + QTemporaryFile *temp = new QTemporaryFile(templatePath, this); + if (temp->open()) { + temp->write(fileBlob.content()); + temp->flush(); + blobTmpFullFilePath = temp->fileName(); + } + + return blobTmpFullFilePath; +} diff --git a/src/tools/DiffTool.h b/src/tools/DiffTool.h index 19cb8ef01..570bf1f68 100644 --- a/src/tools/DiffTool.h +++ b/src/tools/DiffTool.h @@ -14,6 +14,7 @@ #include "ExternalTool.h" class QObject; +class QString; namespace git { class Diff; class Repository; @@ -39,6 +40,9 @@ class DiffTool : public ExternalTool { private: bool getBlob(const QString &file, const git::Diff::File &version, git::Blob &blob) const; + + QString makeBlobTempFullFilePath(const QString &filePathAndName, + const git::Blob &fileBlob); }; #endif From f51f2d0bc3a8b6eb94a2e1567b9559f9897a5af6 Mon Sep 17 00:00:00 2001 From: devnull Date: Tue, 25 Apr 2023 12:19:37 -0400 Subject: [PATCH 05/21] In MergeTool, use the workspace file as "merged". Using the workspace file as "merged" allows for the file to be edited in-place for the merge. --- src/tools/MergeTool.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index a1d9c4882..a93d46a30 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -20,7 +20,7 @@ #include MergeTool::MergeTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) + const git::Repository &repo, QObject *parent) : ExternalTool(files, diff, repo, parent) { if (!mFiles.empty()) { @@ -37,7 +37,8 @@ MergeTool::MergeTool(const QStringList &files, const git::Diff &diff, } bool MergeTool::isValid() const { - if (!ExternalTool::isValid()) return false; + if (!ExternalTool::isValid()) + return false; int numBlobs = mLocalEditedBlobs.size(); for (int i = 0; i < numBlobs; ++i) { @@ -63,8 +64,8 @@ bool MergeTool::start() { int numMergeFiles = mMergeFiles.size(); for (int i = 0; i < numMergeFiles; ++i) { // Write temporary files. - QString templatePath = QDir::temp().filePath( - QFileInfo(mMergeFiles[i]).fileName()); + QString templatePath = + QDir::temp().filePath(QFileInfo(mMergeFiles[i]).fileName()); QTemporaryFile *local = new QTemporaryFile(templatePath, this); if (!local->open()) return false; @@ -127,17 +128,18 @@ bool MergeTool::start() { } }); + QString fullFilePath = mRepo.workdir().filePath(mMergeFiles[i]); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("LOCAL", local->fileName()); env.insert("REMOTE", remote->fileName()); - env.insert("MERGED", mMergeFiles[i]); + env.insert("MERGED", fullFilePath); env.insert("BASE", basePath); process->setProcessEnvironment(env); #if defined(FLATPAK) || defined(DEBUG_FLATPAK) QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), "--env=REMOTE=" + remote->fileName(), - "--env=MERGED=" + mMergeFiles[i], + "--env=MERGED=" + fullFilePath, "--env=BASE=" + basePath}; arguments.append("sh"); arguments.append("-c"); From ab696972a25f86cb25407af5c82c7a7803ad3183 Mon Sep 17 00:00:00 2001 From: devnull Date: Tue, 25 Apr 2023 12:54:34 -0400 Subject: [PATCH 06/21] Fix FLATPAK typo. --- src/tools/DiffTool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/DiffTool.cpp b/src/tools/DiffTool.cpp index 7dbd2c735..f9de7be95 100644 --- a/src/tools/DiffTool.cpp +++ b/src/tools/DiffTool.cpp @@ -80,7 +80,8 @@ bool DiffTool::start() { #if defined(FLATPAK) || defined(DEBUG_FLATPAK) QStringList arguments = {"--host", "--env=LOCAL=" + fullFilePath, "--env=REMOTE=" + otherPathAndName, - "--env=MERGED=" + file, "--env=BASE=" + file}; + "--env=MERGED=" + filePathAndName, + "--env=BASE=" + filePathAndName}; arguments.append("sh"); arguments.append("-c"); arguments.append(command); From e20b5d07bf9b7c5ca44d25087dfc2d7d0ac11c98 Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 8 May 2023 11:03:19 -0400 Subject: [PATCH 07/21] Review updates for multi-file tool support. --- src/tools/DiffTool.cpp | 12 +++--- src/tools/MergeTool.cpp | 83 +++++++++++++++++++------------------- src/tools/MergeTool.h | 11 +++-- src/ui/FileContextMenu.cpp | 39 +++++------------- 4 files changed, 66 insertions(+), 79 deletions(-) diff --git a/src/tools/DiffTool.cpp b/src/tools/DiffTool.cpp index f9de7be95..cd0032adb 100644 --- a/src/tools/DiffTool.cpp +++ b/src/tools/DiffTool.cpp @@ -45,15 +45,15 @@ bool DiffTool::start() { int numFiles = mFiles.size(); foreach (const QString &filePathAndName, mFiles) { - git::Blob fileBlob1, fileBlob2; - if (!getBlob(filePathAndName, git::Diff::OldFile, fileBlob1) || - !getBlob(filePathAndName, git::Diff::NewFile, fileBlob2)) + git::Blob filePathOld, filePathNew; + if (!getBlob(filePathAndName, git::Diff::OldFile, filePathOld) || + !getBlob(filePathAndName, git::Diff::NewFile, filePathNew)) continue; // Get the path to the file (either a full or relative path). QString otherPathAndName = filePathAndName; - if (fileBlob1.isValid()) { - otherPathAndName = makeBlobTempFullFilePath(filePathAndName, fileBlob1); + if (filePathOld.isValid()) { + otherPathAndName = makeBlobTempFullFilePath(filePathAndName, filePathOld); if (otherPathAndName.isEmpty()) return false; } @@ -75,7 +75,7 @@ bool DiffTool::start() { // Convert to absolute path. QString fullFilePath = isWorkDirDiff ? mRepo.workdir().filePath(filePathAndName) - : makeBlobTempFullFilePath(filePathAndName, fileBlob2); + : makeBlobTempFullFilePath(filePathAndName, filePathNew); #if defined(FLATPAK) || defined(DEBUG_FLATPAK) QStringList arguments = {"--host", "--env=LOCAL=" + fullFilePath, diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index a93d46a30..b23dd5242 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -26,11 +26,10 @@ MergeTool::MergeTool(const QStringList &files, const git::Diff &diff, if (!mFiles.empty()) { foreach (const QString &file, mFiles) { if (isConflicted(file)) { - mMergeFiles.append(file); git::Index::Conflict conflict = repo.index().conflict(file); - mLocalEditedBlobs.append(repo.lookupBlob(conflict.ours)); - mRemoteEditedBlobs.append(repo.lookupBlob(conflict.theirs)); - mBaseBlobs.append(repo.lookupBlob(conflict.ancestor)); + mMerges.append({file, repo.lookupBlob(conflict.ours), + repo.lookupBlob(conflict.theirs), + repo.lookupBlob(conflict.ancestor)}); } } } @@ -40,9 +39,10 @@ bool MergeTool::isValid() const { if (!ExternalTool::isValid()) return false; - int numBlobs = mLocalEditedBlobs.size(); + int numBlobs = mMerges.size(); for (int i = 0; i < numBlobs; ++i) { - if (!mLocalEditedBlobs[i].isValid() || !mRemoteEditedBlobs[i].isValid()) { + const FileMerge &fileMerge = mMerges.at(i); + if (!fileMerge.local.isValid() || !fileMerge.remote.isValid()) { return false; } } @@ -61,40 +61,41 @@ bool MergeTool::start() { if (command.isEmpty()) return false; - int numMergeFiles = mMergeFiles.size(); + int numMergeFiles = mMerges.size(); for (int i = 0; i < numMergeFiles; ++i) { + const FileMerge &fileMerge = mMerges.at(i); // Write temporary files. QString templatePath = - QDir::temp().filePath(QFileInfo(mMergeFiles[i]).fileName()); + QDir::temp().filePath(QFileInfo(fileMerge.name).fileName()); QTemporaryFile *local = new QTemporaryFile(templatePath, this); if (!local->open()) return false; - local->write(mLocalEditedBlobs[i].content()); + local->write(fileMerge.local.content()); local->flush(); QTemporaryFile *remote = new QTemporaryFile(templatePath, this); if (!remote->open()) return false; - remote->write(mRemoteEditedBlobs[i].content()); + remote->write(fileMerge.remote.content()); remote->flush(); QString basePath; - if (mBaseBlobs[i].isValid()) { + if (fileMerge.base.isValid()) { QTemporaryFile *base = new QTemporaryFile(templatePath, this); if (!base->open()) return false; - base->write(mBaseBlobs[i].content()); + base->write(fileMerge.base.content()); base->flush(); basePath = base->fileName(); } // Make the backup copy. - QString backupPath = QString("%1.orig").arg(mMergeFiles[i]); - if (!QFile::copy(mMergeFiles[i], backupPath)) { + QString backupPath = QString("%1.orig").arg(fileMerge.name); + if (!QFile::copy(fileMerge.name, backupPath)) { // FIXME: What should happen if the backup already exists? } @@ -102,33 +103,33 @@ bool MergeTool::start() { QProcess *process = new QProcess(this); process->setProcessChannelMode( QProcess::ProcessChannelMode::ForwardedChannels); - git::Repository repo = mLocalEditedBlobs[i].repo(); - auto signal = QOverload::of( - &QProcess::finished); - QObject::connect(process, signal, - [this, repo, i, backupPath, process, &numMergeFiles] { - qDebug() << "Merge Process Exited!"; - qDebug() << "Stdout: " << process->readAllStandardOutput(); - qDebug() << "Stderr: " << process->readAllStandardError(); - - QFileInfo merged(mMergeFiles[i]); - QFileInfo backup(backupPath); - git::Config config = git::Config::global(); - bool modified = (merged.lastModified() > backup.lastModified()); - if (!modified || !config.value("mergetool.keepBackup")) - QFile::remove(backupPath); - - if (modified) { - int length = repo.workdir().path().length(); - repo.index().setStaged({mMergeFiles[i].mid(length + 1)}, true); - } - - if (--numMergeFiles) { - deleteLater(); - } - }); - - QString fullFilePath = mRepo.workdir().filePath(mMergeFiles[i]); + git::Repository repo = fileMerge.local.repo(); + auto signal = QOverload::of(&QProcess::finished); + QObject::connect( + process, signal, + [this, repo, fileMerge, backupPath, process, &numMergeFiles] { + qDebug() << "Merge Process Exited!"; + qDebug() << "Stdout: " << process->readAllStandardOutput(); + qDebug() << "Stderr: " << process->readAllStandardError(); + + QFileInfo merged(fileMerge.name); + QFileInfo backup(backupPath); + git::Config config = git::Config::global(); + bool modified = (merged.lastModified() > backup.lastModified()); + if (!modified || !config.value("mergetool.keepBackup")) + QFile::remove(backupPath); + + if (modified) { + int length = repo.workdir().path().length(); + repo.index().setStaged({fileMerge.name.mid(length + 1)}, true); + } + + if (--numMergeFiles) { + deleteLater(); + } + }); + + QString fullFilePath = mRepo.workdir().filePath(fileMerge.name); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("LOCAL", local->fileName()); env.insert("REMOTE", remote->fileName()); diff --git a/src/tools/MergeTool.h b/src/tools/MergeTool.h index 2385af825..ebddfda0a 100644 --- a/src/tools/MergeTool.h +++ b/src/tools/MergeTool.h @@ -38,10 +38,13 @@ class MergeTool : public ExternalTool { protected: private: - QVector mMergeFiles; - QVector mLocalEditedBlobs; - QVector mRemoteEditedBlobs; - QVector mBaseBlobs; + struct FileMerge { + QString name; + git::Blob local; + git::Blob remote; + git::Blob base; + }; + QVector mMerges; }; #endif diff --git a/src/ui/FileContextMenu.cpp b/src/ui/FileContextMenu.cpp index f14015f6b..c8ad1e54a 100644 --- a/src/ui/FileContextMenu.cpp +++ b/src/ui/FileContextMenu.cpp @@ -88,19 +88,6 @@ FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, if (!diff.isValid()) return; - auto errFunc = [this](ExternalTool::Error error) { - if (error != ExternalTool::BashNotFound) - return; - - QString title = tr("Bash Not Found"); - QString text = tr("Bash was not found on your PATH."); - QMessageBox msg(QMessageBox::Warning, title, text, QMessageBox::Ok, - this); - msg.setInformativeText( - tr("Bash is required to execute external tools.")); - msg.exec(); - }; - git::Repository repo = view->repo(); // Create external tools. @@ -109,8 +96,7 @@ FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, attachTool(new EditTool(files, diff, repo, this), editTools); if (diff.isConflicted()) { attachTool(new MergeTool(files, diff, repo, this), mergeTools); - } - else { + } else { attachTool(new DiffTool(files, diff, repo, this), diffTools); } @@ -174,7 +160,7 @@ FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, // Navigate QMenu *navigate = addMenu(tr("Navigate to")); QAction *nextAct = navigate->addAction(tr("Next Revision")); - QMenu::connect(nextAct, &QAction::triggered, [view, file] { + connect(nextAct, &QAction::triggered, [view, file] { if (git::Commit next = view->nextRevision(file)) { view->selectCommit(next, file); } else { @@ -183,7 +169,7 @@ FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, }); QAction *prevAct = navigate->addAction(tr("Previous Revision")); - QMenu::connect(prevAct, &QAction::triggered, [view, file] { + connect(prevAct, &QAction::triggered, [view, file] { if (git::Commit prev = view->previousRevision(file)) { view->selectCommit(prev, file); } else { @@ -306,7 +292,7 @@ void FileContextMenu::handleUncommittedChanges(const git::Index &index, QString text = tr("Discard Changes"); QPushButton *discard = dialog->addButton(text, QMessageBox::AcceptRole); discard->setObjectName("DiscardButton"); - QMenu::connect(discard, &QPushButton::clicked, [view, modified, submodules] { + connect(discard, &QPushButton::clicked, [view, modified, submodules] { git::Repository repo = view->repo(); int strategy = GIT_CHECKOUT_FORCE; if (modified.count() && @@ -333,7 +319,7 @@ void FileContextMenu::handleUncommittedChanges(const git::Index &index, // Ignore QAction *ignore = addAction(tr("Ignore")); ignore->setObjectName("IgnoreAction"); - QMenu::connect(ignore, &QAction::triggered, this, &FileContextMenu::ignoreFile); + connect(ignore, &QAction::triggered, this, &FileContextMenu::ignoreFile); foreach (const QString &file, files) { int index = diff.indexOf(file); if (index < 0) @@ -451,7 +437,7 @@ void FileContextMenu::ignoreFile() { d->setAttribute(Qt::WA_DeleteOnClose); auto *view = mView; - QMenu::connect(d, &QDialog::accepted, [d, view]() { + connect(d, &QDialog::accepted, [d, view]() { auto ignore = d->ignoreText(); if (!ignore.isEmpty()) view->ignore(ignore); @@ -521,21 +507,18 @@ void FileContextMenu::addExternalToolsAction( } } -void FileContextMenu::attachTool(ExternalTool *tool, - QList &tools) -{ +void FileContextMenu::attachTool(ExternalTool *tool, + QList &tools) { tools.append(tool); - QMenu::connect(tool, &ExternalTool::error, [this](ExternalTool::Error error) { + connect(tool, &ExternalTool::error, [this](ExternalTool::Error error) { if (error != ExternalTool::BashNotFound) return; QString title = tr("Bash Not Found"); QString text = tr("Bash was not found on your PATH."); - QMessageBox msg(QMessageBox::Warning, title, text, QMessageBox::Ok, - this); - msg.setInformativeText( - tr("Bash is required to execute external tools.")); + QMessageBox msg(QMessageBox::Warning, title, text, QMessageBox::Ok, this); + msg.setInformativeText(tr("Bash is required to execute external tools.")); msg.exec(); }); } From e8c9d13e9027c775654837089d75f9a97894424b Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 8 May 2023 12:40:08 -0400 Subject: [PATCH 08/21] Review updates for multi-file tool support 2. --- src/tools/MergeTool.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index b23dd5242..c92f70430 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -40,8 +40,7 @@ bool MergeTool::isValid() const { return false; int numBlobs = mMerges.size(); - for (int i = 0; i < numBlobs; ++i) { - const FileMerge &fileMerge = mMerges.at(i); + for (const FileMerge &fileMerge : mMerges) { if (!fileMerge.local.isValid() || !fileMerge.remote.isValid()) { return false; } From 99537fad19e65a0dcddc81e63393fecdb0fecb7f Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 8 May 2023 12:51:52 -0400 Subject: [PATCH 09/21] Review updates for multi-file tool support 3. --- src/tools/MergeTool.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index c92f70430..731409009 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -61,8 +61,7 @@ bool MergeTool::start() { return false; int numMergeFiles = mMerges.size(); - for (int i = 0; i < numMergeFiles; ++i) { - const FileMerge &fileMerge = mMerges.at(i); + for (const FileMerge &fileMerge : mMerges) { // Write temporary files. QString templatePath = QDir::temp().filePath(QFileInfo(fileMerge.name).fileName()); From 1b37de0dd1df0915b0e92c3dbce794e2ec011773 Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 8 May 2023 15:08:38 -0400 Subject: [PATCH 10/21] Review updates for multi-file tool support 4. --- src/tools/MergeTool.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index 731409009..a185c7605 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -39,7 +39,6 @@ bool MergeTool::isValid() const { if (!ExternalTool::isValid()) return false; - int numBlobs = mMerges.size(); for (const FileMerge &fileMerge : mMerges) { if (!fileMerge.local.isValid() || !fileMerge.remote.isValid()) { return false; From fb6218cd290e33cb4c88cfae36e5824d8014d6c6 Mon Sep 17 00:00:00 2001 From: devnull Date: Tue, 9 May 2023 22:09:18 -0400 Subject: [PATCH 11/21] Review updates for multi-file tool support 5 - Fix premature MergeTool deletion. --- src/tools/MergeTool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index a185c7605..755665ee6 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -121,7 +121,7 @@ bool MergeTool::start() { repo.index().setStaged({fileMerge.name.mid(length + 1)}, true); } - if (--numMergeFiles) { + if (--numMergeFiles == 0) { deleteLater(); } }); From c5be6b31bd5fd4a29b982b1f155fe5a36fdbd02f Mon Sep 17 00:00:00 2001 From: devnull Date: Wed, 10 May 2023 17:23:16 -0400 Subject: [PATCH 12/21] Review updates for multi-file tool support 6 - Remove "this" parameter from the QProcess constructor for DiffTool.cpp, EditTool.cpp, and MergeTool.cpp so the external tool process lifetimes are not in anyway tied to gittup's lifetime. This simulates QProcess::startDetached() behavior. --- src/tools/DiffTool.cpp | 2 +- src/tools/EditTool.cpp | 2 +- src/tools/MergeTool.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/DiffTool.cpp b/src/tools/DiffTool.cpp index cd0032adb..01c14a6cc 100644 --- a/src/tools/DiffTool.cpp +++ b/src/tools/DiffTool.cpp @@ -59,7 +59,7 @@ bool DiffTool::start() { } // Destroy this after process finishes. - QProcess *process = new QProcess(this); + QProcess *process = new QProcess(); process->setProcessChannelMode( QProcess::ProcessChannelMode::ForwardedChannels); auto signal = QOverload::of(&QProcess::finished); diff --git a/src/tools/EditTool.cpp b/src/tools/EditTool.cpp index be7ef4804..0ad4d7872 100644 --- a/src/tools/EditTool.cpp +++ b/src/tools/EditTool.cpp @@ -90,7 +90,7 @@ bool EditTool::start() { editor.remove("\""); // Destroy this after process finishes. - QProcess *process = new QProcess(this); + QProcess *process = new QProcess(); auto signal = QOverload::of(&QProcess::finished); QObject::connect(process, signal, this, &ExternalTool::deleteLater); diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index 755665ee6..e39ac7523 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -97,7 +97,7 @@ bool MergeTool::start() { } // Destroy this after process finishes. - QProcess *process = new QProcess(this); + QProcess *process = new QProcess(); process->setProcessChannelMode( QProcess::ProcessChannelMode::ForwardedChannels); git::Repository repo = fileMerge.local.repo(); From 3e344136f3b479635eda04176c1241782c3097f1 Mon Sep 17 00:00:00 2001 From: devnull Date: Wed, 10 May 2023 17:34:37 -0400 Subject: [PATCH 13/21] Review updates for multi-file tool support 7 - formatted --- src/tools/DiffTool.cpp | 2 +- src/tools/DiffTool.h | 3 +-- src/tools/EditTool.cpp | 8 +++++--- src/tools/EditTool.h | 6 +++--- src/tools/ExternalTool.cpp | 12 ++++++------ src/tools/MergeTool.h | 7 +++---- src/tools/ShowTool.cpp | 17 ++++++++--------- src/tools/ShowTool.h | 7 +++---- 8 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/tools/DiffTool.cpp b/src/tools/DiffTool.cpp index 01c14a6cc..6f047faa7 100644 --- a/src/tools/DiffTool.cpp +++ b/src/tools/DiffTool.cpp @@ -80,7 +80,7 @@ bool DiffTool::start() { #if defined(FLATPAK) || defined(DEBUG_FLATPAK) QStringList arguments = {"--host", "--env=LOCAL=" + fullFilePath, "--env=REMOTE=" + otherPathAndName, - "--env=MERGED=" + filePathAndName, + "--env=MERGED=" + filePathAndName, "--env=BASE=" + filePathAndName}; arguments.append("sh"); arguments.append("-c"); diff --git a/src/tools/DiffTool.h b/src/tools/DiffTool.h index 570bf1f68..8b541603e 100644 --- a/src/tools/DiffTool.h +++ b/src/tools/DiffTool.h @@ -19,7 +19,7 @@ namespace git { class Diff; class Repository; class Blob; -}; +}; // namespace git class DiffTool : public ExternalTool { Q_OBJECT @@ -36,7 +36,6 @@ class DiffTool : public ExternalTool { bool start() override; protected: - private: bool getBlob(const QString &file, const git::Diff::File &version, git::Blob &blob) const; diff --git a/src/tools/EditTool.cpp b/src/tools/EditTool.cpp index 0ad4d7872..21c2ee981 100644 --- a/src/tools/EditTool.cpp +++ b/src/tools/EditTool.cpp @@ -15,14 +15,16 @@ #include EditTool::EditTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) + const git::Repository &repo, QObject *parent) : ExternalTool(files, diff, repo, parent) {} bool EditTool::isValid() const { - if (!ExternalTool::isValid()) return false; + if (!ExternalTool::isValid()) + return false; foreach (const QString file, mFiles) { - if (!QFileInfo(mRepo.workdir().filePath(file)).isFile()) return false; + if (!QFileInfo(mRepo.workdir().filePath(file)).isFile()) + return false; } return true; } diff --git a/src/tools/EditTool.h b/src/tools/EditTool.h index ad49e432f..1ee62ae33 100644 --- a/src/tools/EditTool.h +++ b/src/tools/EditTool.h @@ -14,9 +14,9 @@ class QObject; namespace git { - class Diff; - class Repository; -}; +class Diff; +class Repository; +}; // namespace git class EditTool : public ExternalTool { Q_OBJECT diff --git a/src/tools/ExternalTool.cpp b/src/tools/ExternalTool.cpp index f7080b577..4748491b0 100644 --- a/src/tools/ExternalTool.cpp +++ b/src/tools/ExternalTool.cpp @@ -40,11 +40,12 @@ void splitCommand(const QString &command, QString &program, QString &args) { } // namespace ExternalTool::ExternalTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) - : QObject(parent), mFiles(files), mDiff(diff), mRepo(repo) { } + const git::Repository &repo, QObject *parent) + : QObject(parent), mFiles(files), mDiff(diff), mRepo(repo) {} -bool ExternalTool::isValid() const { return !mFiles.isEmpty() && - !mFiles.first().isEmpty(); } +bool ExternalTool::isValid() const { + return !mFiles.isEmpty() && !mFiles.first().isEmpty(); +} QString ExternalTool::lookupCommand(const QString &key, bool &shell) { git::Config config = git::Config::global(); @@ -98,8 +99,7 @@ QList ExternalTool::readBuiltInTools(const QString &key) { return tools; } -bool ExternalTool::isConflicted(const QString &file) const -{ +bool ExternalTool::isConflicted(const QString &file) const { if (!mDiff.isValid()) return false; diff --git a/src/tools/MergeTool.h b/src/tools/MergeTool.h index ebddfda0a..446d5987b 100644 --- a/src/tools/MergeTool.h +++ b/src/tools/MergeTool.h @@ -17,9 +17,9 @@ class QObject; namespace git { - class Diff; - class Repository; -}; +class Diff; +class Repository; +}; // namespace git class MergeTool : public ExternalTool { Q_OBJECT @@ -36,7 +36,6 @@ class MergeTool : public ExternalTool { bool start() override; protected: - private: struct FileMerge { QString name; diff --git a/src/tools/ShowTool.cpp b/src/tools/ShowTool.cpp index 2b54e6a53..1aabd52bf 100644 --- a/src/tools/ShowTool.cpp +++ b/src/tools/ShowTool.cpp @@ -96,7 +96,7 @@ bool ShowTool::openFileManager(QString path) { } ShowTool::ShowTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) + const git::Repository &repo, QObject *parent) : ExternalTool(files, diff, repo, parent) {} ExternalTool::Kind ShowTool::kind() const { return Show; } @@ -107,16 +107,15 @@ bool ShowTool::start() { foreach (const QString &file, mFiles) { #if defined(Q_OS_MAC) - if (!QProcess::startDetached( - "/usr/bin/osascript", {"-e", "tell application \"Finder\"", "-e", - QString("reveal POSIX file \"%1\"").arg(file), - "-e", "activate", "-e", "end tell"})) { - return false; + if (!QProcess::startDetached("/usr/bin/osascript", + {"-e", "tell application \"Finder\"", "-e", + QString("reveal POSIX file \"%1\"").arg(file), + "-e", "activate", "-e", "end tell"})) { + return false; } #elif defined(Q_OS_WIN) - if (!QProcess::startDetached("explorer.exe", - {"/select,", - QDir::toNativeSeparators(file)})) { + if (!QProcess::startDetached( + "explorer.exe", {"/select,", QDir::toNativeSeparators(file)})) { return false; } #else diff --git a/src/tools/ShowTool.h b/src/tools/ShowTool.h index 32c174067..68f756f32 100644 --- a/src/tools/ShowTool.h +++ b/src/tools/ShowTool.h @@ -14,10 +14,9 @@ class QObject; namespace git { - class Diff; - class Repository; -}; - +class Diff; +class Repository; +}; // namespace git class ShowTool : public ExternalTool { Q_OBJECT From 11d27ed5622ef674363e7129ed4145e355c7203a Mon Sep 17 00:00:00 2001 From: devnull Date: Wed, 10 May 2023 17:23:16 -0400 Subject: [PATCH 14/21] Review updates for multi-file tool support 6 - Remove "this" parameter from the QProcess constructor for DiffTool.cpp, EditTool.cpp, and MergeTool.cpp so the external tool process lifetimes are not in anyway tied to gittup's lifetime. This simulates QProcess::startDetached() behavior. --- src/tools/DiffTool.cpp | 2 +- src/tools/EditTool.cpp | 2 +- src/tools/MergeTool.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/DiffTool.cpp b/src/tools/DiffTool.cpp index cd0032adb..01c14a6cc 100644 --- a/src/tools/DiffTool.cpp +++ b/src/tools/DiffTool.cpp @@ -59,7 +59,7 @@ bool DiffTool::start() { } // Destroy this after process finishes. - QProcess *process = new QProcess(this); + QProcess *process = new QProcess(); process->setProcessChannelMode( QProcess::ProcessChannelMode::ForwardedChannels); auto signal = QOverload::of(&QProcess::finished); diff --git a/src/tools/EditTool.cpp b/src/tools/EditTool.cpp index be7ef4804..0ad4d7872 100644 --- a/src/tools/EditTool.cpp +++ b/src/tools/EditTool.cpp @@ -90,7 +90,7 @@ bool EditTool::start() { editor.remove("\""); // Destroy this after process finishes. - QProcess *process = new QProcess(this); + QProcess *process = new QProcess(); auto signal = QOverload::of(&QProcess::finished); QObject::connect(process, signal, this, &ExternalTool::deleteLater); diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index 755665ee6..e39ac7523 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -97,7 +97,7 @@ bool MergeTool::start() { } // Destroy this after process finishes. - QProcess *process = new QProcess(this); + QProcess *process = new QProcess(); process->setProcessChannelMode( QProcess::ProcessChannelMode::ForwardedChannels); git::Repository repo = fileMerge.local.repo(); From cc117e6c31522fcdb30acc22955ba2d82cafc8fd Mon Sep 17 00:00:00 2001 From: devnull Date: Wed, 10 May 2023 17:34:37 -0400 Subject: [PATCH 15/21] Review updates for multi-file tool support 7 - formatted --- src/tools/DiffTool.cpp | 2 +- src/tools/DiffTool.h | 3 +-- src/tools/EditTool.cpp | 8 +++++--- src/tools/EditTool.h | 6 +++--- src/tools/ExternalTool.cpp | 12 ++++++------ src/tools/MergeTool.h | 7 +++---- src/tools/ShowTool.cpp | 17 ++++++++--------- src/tools/ShowTool.h | 7 +++---- 8 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/tools/DiffTool.cpp b/src/tools/DiffTool.cpp index 01c14a6cc..6f047faa7 100644 --- a/src/tools/DiffTool.cpp +++ b/src/tools/DiffTool.cpp @@ -80,7 +80,7 @@ bool DiffTool::start() { #if defined(FLATPAK) || defined(DEBUG_FLATPAK) QStringList arguments = {"--host", "--env=LOCAL=" + fullFilePath, "--env=REMOTE=" + otherPathAndName, - "--env=MERGED=" + filePathAndName, + "--env=MERGED=" + filePathAndName, "--env=BASE=" + filePathAndName}; arguments.append("sh"); arguments.append("-c"); diff --git a/src/tools/DiffTool.h b/src/tools/DiffTool.h index 570bf1f68..8b541603e 100644 --- a/src/tools/DiffTool.h +++ b/src/tools/DiffTool.h @@ -19,7 +19,7 @@ namespace git { class Diff; class Repository; class Blob; -}; +}; // namespace git class DiffTool : public ExternalTool { Q_OBJECT @@ -36,7 +36,6 @@ class DiffTool : public ExternalTool { bool start() override; protected: - private: bool getBlob(const QString &file, const git::Diff::File &version, git::Blob &blob) const; diff --git a/src/tools/EditTool.cpp b/src/tools/EditTool.cpp index 0ad4d7872..21c2ee981 100644 --- a/src/tools/EditTool.cpp +++ b/src/tools/EditTool.cpp @@ -15,14 +15,16 @@ #include EditTool::EditTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) + const git::Repository &repo, QObject *parent) : ExternalTool(files, diff, repo, parent) {} bool EditTool::isValid() const { - if (!ExternalTool::isValid()) return false; + if (!ExternalTool::isValid()) + return false; foreach (const QString file, mFiles) { - if (!QFileInfo(mRepo.workdir().filePath(file)).isFile()) return false; + if (!QFileInfo(mRepo.workdir().filePath(file)).isFile()) + return false; } return true; } diff --git a/src/tools/EditTool.h b/src/tools/EditTool.h index ad49e432f..1ee62ae33 100644 --- a/src/tools/EditTool.h +++ b/src/tools/EditTool.h @@ -14,9 +14,9 @@ class QObject; namespace git { - class Diff; - class Repository; -}; +class Diff; +class Repository; +}; // namespace git class EditTool : public ExternalTool { Q_OBJECT diff --git a/src/tools/ExternalTool.cpp b/src/tools/ExternalTool.cpp index f7080b577..4748491b0 100644 --- a/src/tools/ExternalTool.cpp +++ b/src/tools/ExternalTool.cpp @@ -40,11 +40,12 @@ void splitCommand(const QString &command, QString &program, QString &args) { } // namespace ExternalTool::ExternalTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) - : QObject(parent), mFiles(files), mDiff(diff), mRepo(repo) { } + const git::Repository &repo, QObject *parent) + : QObject(parent), mFiles(files), mDiff(diff), mRepo(repo) {} -bool ExternalTool::isValid() const { return !mFiles.isEmpty() && - !mFiles.first().isEmpty(); } +bool ExternalTool::isValid() const { + return !mFiles.isEmpty() && !mFiles.first().isEmpty(); +} QString ExternalTool::lookupCommand(const QString &key, bool &shell) { git::Config config = git::Config::global(); @@ -98,8 +99,7 @@ QList ExternalTool::readBuiltInTools(const QString &key) { return tools; } -bool ExternalTool::isConflicted(const QString &file) const -{ +bool ExternalTool::isConflicted(const QString &file) const { if (!mDiff.isValid()) return false; diff --git a/src/tools/MergeTool.h b/src/tools/MergeTool.h index ebddfda0a..446d5987b 100644 --- a/src/tools/MergeTool.h +++ b/src/tools/MergeTool.h @@ -17,9 +17,9 @@ class QObject; namespace git { - class Diff; - class Repository; -}; +class Diff; +class Repository; +}; // namespace git class MergeTool : public ExternalTool { Q_OBJECT @@ -36,7 +36,6 @@ class MergeTool : public ExternalTool { bool start() override; protected: - private: struct FileMerge { QString name; diff --git a/src/tools/ShowTool.cpp b/src/tools/ShowTool.cpp index 2b54e6a53..1aabd52bf 100644 --- a/src/tools/ShowTool.cpp +++ b/src/tools/ShowTool.cpp @@ -96,7 +96,7 @@ bool ShowTool::openFileManager(QString path) { } ShowTool::ShowTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) + const git::Repository &repo, QObject *parent) : ExternalTool(files, diff, repo, parent) {} ExternalTool::Kind ShowTool::kind() const { return Show; } @@ -107,16 +107,15 @@ bool ShowTool::start() { foreach (const QString &file, mFiles) { #if defined(Q_OS_MAC) - if (!QProcess::startDetached( - "/usr/bin/osascript", {"-e", "tell application \"Finder\"", "-e", - QString("reveal POSIX file \"%1\"").arg(file), - "-e", "activate", "-e", "end tell"})) { - return false; + if (!QProcess::startDetached("/usr/bin/osascript", + {"-e", "tell application \"Finder\"", "-e", + QString("reveal POSIX file \"%1\"").arg(file), + "-e", "activate", "-e", "end tell"})) { + return false; } #elif defined(Q_OS_WIN) - if (!QProcess::startDetached("explorer.exe", - {"/select,", - QDir::toNativeSeparators(file)})) { + if (!QProcess::startDetached( + "explorer.exe", {"/select,", QDir::toNativeSeparators(file)})) { return false; } #else diff --git a/src/tools/ShowTool.h b/src/tools/ShowTool.h index 32c174067..68f756f32 100644 --- a/src/tools/ShowTool.h +++ b/src/tools/ShowTool.h @@ -14,10 +14,9 @@ class QObject; namespace git { - class Diff; - class Repository; -}; - +class Diff; +class Repository; +}; // namespace git class ShowTool : public ExternalTool { Q_OBJECT From 87a072251b34eab7f8e32bc06df0706f92cad8aa Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 15 May 2023 20:26:05 -0400 Subject: [PATCH 16/21] For a selected directory, add the ability to skip recursive file enumeration. When a directory is selected, all files within it, recursively, are added to the list of files selected. This is not really what the user intends (i.e. if the files are displayed as a tree and the user selects from the first to the last, the user really only wants to interact with the selected files, not all those in any selected directory). Introduce the AccumRepoFiles class to keep track of files actually selected by the user as well as those under any selected directory and provide a way to get either list individually or both file lists together. --- src/ui/DoubleTreeWidget.cpp | 38 +++----------- src/ui/FileContextMenu.cpp | 99 ++++++++++++++++++++++++++++++++++--- src/ui/FileContextMenu.h | 56 +++++++++++++++++++-- test/TreeView.cpp | 5 +- 4 files changed, 157 insertions(+), 41 deletions(-) diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index d497b3a93..8864e016b 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -306,46 +306,24 @@ QModelIndex DoubleTreeWidget::selectedIndex() const { return QModelIndex(); } -static void addNodeToMenu(const git::Index &index, QStringList &files, - const Node *node, bool staged, bool statusDiff) { - Debug("DoubleTreeWidgetr addNodeToMenu()" << node->name()); - - if (node->hasChildren()) { - for (auto child : node->children()) { - addNodeToMenu(index, files, child, staged, statusDiff); - } - - } else { - auto path = node->path(true); - - auto stageState = index.isStaged(path); - - if ((staged && stageState != git::Index::Unstaged) || - (!staged && stageState != git::Index::Staged) || !statusDiff) { - files.append(path); - } - } -} - void DoubleTreeWidget::showFileContextMenu(const QPoint &pos, RepoView *view, QTreeView *tree, bool staged) { - QStringList files; - QModelIndexList indexes = tree->selectionModel()->selectedIndexes(); + QModelIndexList modelIndexes = tree->selectionModel()->selectedIndexes(); const auto diff = view->diff(); if (!diff.isValid()) return; - const bool statusDiff = diff.isStatusDiff(); - foreach (const QModelIndex &index, indexes) { - auto node = index.data(Qt::UserRole).value(); - - addNodeToMenu(view->repo().index(), files, node, staged, statusDiff); + const git::Index repoIndex = view->repo().index(); + AccumRepoFiles accumulatedFiles(staged, diff.isStatusDiff()); + foreach (const QModelIndex &modelIndex, modelIndexes) { + auto node = modelIndex.data(Qt::UserRole).value(); + accumulatedFiles.add(repoIndex, node); } - if (files.isEmpty()) + if (accumulatedFiles.getAllFiles().isEmpty()) return; - auto menu = new FileContextMenu(view, files, git::Index(), tree); + auto menu = new FileContextMenu(view, accumulatedFiles, git::Index(), tree); menu->setAttribute(Qt::WA_DeleteOnClose); menu->popup(tree->mapToGlobal(pos)); } diff --git a/src/ui/FileContextMenu.cpp b/src/ui/FileContextMenu.cpp index c8ad1e54a..54fcfacad 100644 --- a/src/ui/FileContextMenu.cpp +++ b/src/ui/FileContextMenu.cpp @@ -9,6 +9,7 @@ #include "FileContextMenu.h" #include "RepoView.h" +#include "DiffTreeModel.h" #include "IgnoreDialog.h" #include "conf/Settings.h" #include "Debug.h" @@ -81,9 +82,10 @@ void handlePath(const git::Repository &repo, const QString &path, } // namespace -FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, +FileContextMenu::FileContextMenu(RepoView *view, + const AccumRepoFiles &accumFiles, const git::Index &index, QWidget *parent) - : QMenu(parent), mView(view), mFiles(files) { + : QMenu(parent), mView(view), mAccumFiles(accumFiles) { git::Diff diff = view->diff(); if (!diff.isValid()) return; @@ -91,6 +93,7 @@ FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, git::Repository repo = view->repo(); // Create external tools. + const QStringList &files = mAccumFiles.getFiles(); QList showTools, editTools, diffTools, mergeTools; attachTool(new ShowTool(files, diff, repo, this), showTools); attachTool(new EditTool(files, diff, repo, this), editTools); @@ -130,8 +133,10 @@ FileContextMenu::FileContextMenu(RepoView *view, const QStringList &files, } } - addAction(locked ? tr("Unlock") : tr("Lock"), - [view, files, locked] { view->lfsSetLocked(files, !locked); }); + const QStringList &allFiles = files; + addAction(locked ? tr("Unlock") : tr("Lock"), [view, allFiles, locked] { + view->lfsSetLocked(allFiles, !locked); + }); } // Add single selection actions. @@ -430,10 +435,11 @@ void FileContextMenu::handleCommits(const QList &commits, } void FileContextMenu::ignoreFile() { - if (!mFiles.count()) + const QStringList &files = mAccumFiles.getFilesInDirs(); + if (!files.count()) return; - auto d = new IgnoreDialog(mFiles.join('\n'), parentWidget()); + auto d = new IgnoreDialog(files.join('\n'), parentWidget()); d->setAttribute(Qt::WA_DeleteOnClose); auto *view = mView; @@ -522,3 +528,84 @@ void FileContextMenu::attachTool(ExternalTool *tool, msg.exec(); }); } + +AccumRepoFiles::AccumRepoFiles(const QString &file) { mFiles.append(file); } + +AccumRepoFiles::AccumRepoFiles(const QStringList &files) { + mFiles.append(files); +} + +AccumRepoFiles::AccumRepoFiles(bool staged, bool statusDiff) + : mStaged(staged), mStatusDiff(statusDiff) {} + +// Get all files that were accumulated individually, not as part of a +// directory being accumulated. The files contain the file name with any +// path provided (relative or absolute). +const QStringList &AccumRepoFiles::getFiles() const { return mFiles; } + +// Get all directory paths that we accumulated. +QStringList AccumRepoFiles::getAccumulatedDirs() const { + return mFilesInDirMap.keys(); +} + +// Get a list of files from all accumulated directories or just those from +// the requested ones. +QStringList AccumRepoFiles::getFilesInDirs(const QStringList &dirs) const { + QStringList filesInDirs; + + const QStringList &dirKeys = dirs.isEmpty() ? getAccumulatedDirs() : dirs; + for (QString dirKey : dirKeys) { + ConstQMapIterator iter = mFilesInDirMap.find(dirKey); + if (iter == mFilesInDirMap.end()) + continue; + filesInDirs.append(iter.value()); + } + + return filesInDirs; +} + +// Get a list of all accumulated files with paths. These include those +// accumulated individually as well as those that were added as part of +// accumulating a directory. +QStringList AccumRepoFiles::getAllFiles() const { + return getFiles() + getFilesInDirs(); +} + +// Accumulate the given node as either an individual file or as a directory. +// For directories, all the files in the directory will be enumeracted and +// accumulated, recursively. +void AccumRepoFiles::add(const git::Index &index, const Node *node) { + Debug("AccumRepoFiles accumulateFiles() " << node->name()); + + addToFileList(mFiles, index, node); +} + +void AccumRepoFiles::addDirFiles(const git::Index &index, const Node *node) { + Debug("AccumRepoFiles addDirFiles() " << node->name()); + + for (auto childNode : node->children()) { + QFileInfo fileInfo = childNode->path(true); + addToFileList(mFilesInDirMap[fileInfo.path()], index, childNode); + } +} + +void AccumRepoFiles::addToFileList(QStringList &files, const git::Index &index, + const Node *node) { + if (node->hasChildren()) { + addDirFiles(index, node); + } else { + addFile(files, index, node); + } +} + +void AccumRepoFiles::addFile(QStringList &fileList, const git::Index &index, + const Node *node) const { + auto path = node->path(true); + + git::Index::StagedState stageState = index.isStaged(path); + + if ((mStaged && stageState != git::Index::Unstaged) || + (!mStaged && stageState != git::Index::Staged) || !mStatusDiff) { + fileList.append(path); + } +} diff --git a/src/ui/FileContextMenu.h b/src/ui/FileContextMenu.h index 26fa445b9..f9c959dd6 100644 --- a/src/ui/FileContextMenu.h +++ b/src/ui/FileContextMenu.h @@ -18,12 +18,63 @@ class ExternalTool; class RepoView; +class Node; + +class AccumRepoFiles { +public: + AccumRepoFiles(bool staged = false, bool statusDiff = false); + AccumRepoFiles(const QString &file); + AccumRepoFiles(const QStringList &files); + AccumRepoFiles(const AccumRepoFiles &) = default; + AccumRepoFiles &operator=(const AccumRepoFiles &) = default; + ~AccumRepoFiles() = default; + + // Get all files that were accumulated individually, not as part of a + // directory being accumulated. The files contain the file name with any + // path provided (relative or absolute). + const QStringList &getFiles() const; + + // Get all directory paths that we accumulated. + QStringList getAccumulatedDirs() const; + + // Get a list of files from all accumulated directories or just those from + // the requested ones. + QStringList getFilesInDirs(const QStringList &dirs = {}) const; + + // Get a list of all accumulated files with paths. These include those + // accumulated individually as well as those that were added as part of + // accumulating a directory. + QStringList getAllFiles() const; + + // Accumulate the given node as either an individual file or as a directory. + // For directories, all the files in the directory will be enumeracted and + // accumulated, recursively. + void add(const git::Index &index, const Node *node); + +private: + void addToFileList(QStringList &files, const git::Index &index, + const Node *node); + void addDirFiles(const git::Index &index, const Node *node); + void addFile(QStringList &files, const git::Index &index, + const Node *node) const; + + AccumRepoFiles() = delete; + AccumRepoFiles(AccumRepoFiles &&) = delete; + AccumRepoFiles(const AccumRepoFiles &&) = delete; + AccumRepoFiles &&operator=(const AccumRepoFiles &&) = delete; + + typedef QMap::const_iterator ConstQMapIterator; + QMap mFilesInDirMap; + QStringList mFiles; + bool mStaged = false; + bool mStatusDiff = false; +}; class FileContextMenu : public QMenu { Q_OBJECT public: - FileContextMenu(RepoView *view, const QStringList &files, + FileContextMenu(RepoView *view, const AccumRepoFiles &accumFiles, const git::Index &index = git::Index(), QWidget *parent = nullptr); private slots: @@ -40,9 +91,8 @@ private slots: void attachTool(ExternalTool *tool, QList &tools); RepoView *mView; - QStringList mFiles; + AccumRepoFiles mAccumFiles; friend class TestTreeView; }; - #endif diff --git a/test/TreeView.cpp b/test/TreeView.cpp index 246e8efbf..226a0d368 100644 --- a/test/TreeView.cpp +++ b/test/TreeView.cpp @@ -179,10 +179,11 @@ void TestTreeView::discardFiles() { auto *menu = doubleTree->findChild(); QVERIFY(menu); - QCOMPARE(menu->mFiles.count(), 1); + auto files = menu->mAccumFiles.getFiles(); + QCOMPARE(files.count(), 1); // only folder1/file.txt shall get discarded. // folder1/file2.txt shall not discarded! - QCOMPARE(menu->mFiles.at(0), "folder1/file.txt"); + QCOMPARE(files.at(0), "folder1/file.txt"); // From here on everything is tested in TestFileContextMenu } From 11f23045c702567fbbc880c2249ca2f98a3bcb69 Mon Sep 17 00:00:00 2001 From: devnull Date: Tue, 16 May 2023 16:08:55 -0400 Subject: [PATCH 17/21] Update build.yml to build "latest" branch. --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1fe997d6..6e9b0218d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ name: Gittyup on: push: branches: - - master + - latest tags: - 'gittyup_v*' pull_request: @@ -335,12 +335,12 @@ jobs: # https://github.com/marvinpinto/actions/issues/177 needs: [flatpak, build] runs-on: ubuntu-latest # does not matter which - # a prerelase is created when pushing to master + # a prerelase is created when pushing to latest # a release is created when a tag will be set # last condition is the same as IS_RELEASE, # but environment variables cannot be used outside of steps # so it was copied to here too - if: ${{ github.ref == 'refs/heads/master' || (github.event_name == 'push' && github.ref_type == 'tag' && startswith(github.ref_name, 'gittyup_v')) }} + if: ${{ github.ref == 'refs/heads/latest' || (github.event_name == 'push' && github.ref_type == 'tag' && startswith(github.ref_name, 'gittyup_v')) }} steps: - name: Download artifacts uses: actions/download-artifact@v3 From 76031bb7f2ec8c761de557ec96bd636659dea1bd Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 21 Aug 2023 14:18:02 -0400 Subject: [PATCH 18/21] =?UTF-8?q?1e57e7d=20("Spanish=20translation",=20202?= =?UTF-8?q?3-06-17)=20Jos=C3=A9=20Miguel=20Manzano=203c92cc0=20("Adds=20"H?= =?UTF-8?q?ide=20Untracked=20Files"=20option=20to=20DoubleTreeWidget=20cog?= =?UTF-8?q?wheel=20context=20menu",=202023-07-02)=20pvacatel=20a80ffed=20(?= =?UTF-8?q?"Fixes=20code=20format=20according=20to=20clang-format",=202023?= =?UTF-8?q?-07-03)=20pvacatel=20b202f1a=20("Reverts=20format=20changes=20t?= =?UTF-8?q?o=20test/Settings.cpp",=202023-07-04)=20pablov=200a935f8=20("en?= =?UTF-8?q?able=20debug=20build=20by=20setting=20a=20settings=20Reason:=20?= =?UTF-8?q?so=20the=20debug=20possibility=20is=20always=20available=20and?= =?UTF-8?q?=20is=20by=20default=20off=20for=20performance=20reason",=20202?= =?UTF-8?q?3-07-14)=20Martin=20Marmsoler=20198fb7a=20("Update=20src/app/Ap?= =?UTF-8?q?plication.cpp",=202023-07-14)=20Martin=20Marmsoler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 2 +- CMakeLists.txt | 29 +++--- l10n/gittyup_es.ts | 92 ++++++++--------- src/app/Application.cpp | 26 ++--- src/git/Repository.cpp | 14 ++- src/tools/MergeTool.cpp | 201 ++++++++++++++++-------------------- src/ui/DoubleTreeWidget.cpp | 12 +++ src/ui/MenuBar.cpp | 8 ++ src/ui/MenuBar.h | 1 + src/util/CMakeLists.txt | 2 +- src/util/Debug.cpp | 24 +++++ src/util/Debug.h | 12 ++- 12 files changed, 226 insertions(+), 197 deletions(-) create mode 100644 src/util/Debug.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e9b0218d..852cd420a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -278,7 +278,7 @@ jobs: run: | mkdir -p build/release cd build/release - cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DDEBUG_OUTPUT=OFF -DGITTYUP_CI_TESTS=ON ${{ env.CMAKE_FLAGS }} ${{ matrix.env.cmake_flags }} ../.. + cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DGITTYUP_CI_TESTS=ON ${{ env.CMAKE_FLAGS }} ${{ matrix.env.cmake_flags }} ../.. - name: Build Information run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ab13ade7..5b9765beb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,23 +94,20 @@ if(UNIX) set(QT_MODULES ${QT_MODULES} DBus) endif() -if(CMAKE_BUILD_TYPE MATCHES Debug) - option(DEBUG_OUTPUT "Print debug output" ON) - option(DEBUG_OUTPUT_GENERAL "Enable general debug messages" ON) - option(DEBUG_OUTPUT_REFRESH "Enable debug messages to debug the refresh flow" - ON) - if(NOT DEBUG_OUTPUT) - add_compile_definitions(QT_NO_DEBUG_OUTPUT) - else() - if(DEBUG_OUTPUT_GENERAL) - add_compile_definitions(DEBUG_OUTPUT_GENERAL) - endif() - if(DEBUG_OUTPUT_REFRESH) - add_compile_definitions(DEBUG_OUTPUT_REFRESH) - endif() - endif() -else() +option(DEBUG_OUTPUT + "Print debug output (Only available if debug menu is enabled!)" ON) +option(DEBUG_OUTPUT_GENERAL "Enable general debug messages" ON) +option(DEBUG_OUTPUT_REFRESH "Enable debug messages to debug the refresh flow" + OFF) +if(NOT DEBUG_OUTPUT) add_compile_definitions(QT_NO_DEBUG_OUTPUT) +else() + if(DEBUG_OUTPUT_GENERAL) + add_compile_definitions(DEBUG_OUTPUT_GENERAL) + endif() + if(DEBUG_OUTPUT_REFRESH) + add_compile_definitions(DEBUG_OUTPUT_REFRESH) + endif() endif() find_package( diff --git a/l10n/gittyup_es.ts b/l10n/gittyup_es.ts index 76b453d0c..dbc5f75d1 100644 --- a/l10n/gittyup_es.ts +++ b/l10n/gittyup_es.ts @@ -516,7 +516,7 @@ Parents: - + Padres: @@ -583,33 +583,33 @@ Spell Check Language - + Idioma del Corrector Ortográfico The dictionary '%1' is invalid - + El diccionario '%1' es inválido Spell checking is disabled. - + La corrección ortográfica está deshabilitada. The choosen dictionary '%1.dic' is not a valid hunspell dictionary. - + El diccionario elegido '%1.dic' no es un diccionario hunspell válido. Invalid dictionary '%1.dic' - + Diccionario inválido, '%1.dic' Edit User Dictionary - + Editar el Dicccionario de Usuario @@ -738,12 +738,12 @@ Delete Tag %1 - + Borrar Etiqueta %1 Delete Branch %1 - + Borrar Rama %1 @@ -758,7 +758,7 @@ Squash... - + Aplastar... @@ -1148,17 +1148,17 @@ Edit Working Copy - + Editar Copia de Trabajo Edit New Revision - + Editar Revisión Nueva Edit Old Revision - + Editar Revisión Anterior @@ -1699,7 +1699,7 @@ No translation - + No traducir @@ -1737,7 +1737,7 @@ Language: - + Idioma: @@ -2507,12 +2507,12 @@ Ejemplos Squash... - + Aplastar Ctrl+Shift+Q - + Ctrl+Shift+Q @@ -2743,7 +2743,7 @@ Ejemplos Squash - + Aplastar @@ -2773,17 +2773,17 @@ Ejemplos Choose a reference to merge into '%1'. - + Escoje una referencia para fusionar en '%1'. Choose a reference to rebase '%1' on. - + Escoje una referencia para reorganizar en '%1'. Choose a reference to squash into '%1'. - + Escoje una referencia para aplastas en '%1'. Choose a reference to merge into <b>%1</b>. @@ -2807,12 +2807,12 @@ Ejemplos Path to SSH config file: - + Ruta al fichero de configuración SSH: Path to default / fallback SSH key file: - + Ruta a la clave SSH por Defecto/Respaldo: @@ -3065,7 +3065,7 @@ Ejemplos Squash... - + Aplastar... @@ -3250,17 +3250,17 @@ Ejemplos Enter the URL of the remote repository or browse for a local directory - + Ingrese la URL del repositorio remoto o explorea un directorio local ... - ... + ... Choose Directory - Escoger Directorio + Escoger Directorio @@ -3443,7 +3443,7 @@ Ejemplos stage - + preparar @@ -3510,7 +3510,7 @@ Ejemplos initialize - + inicializar @@ -3525,7 +3525,7 @@ Ejemplos deinitialize - + desinicializar @@ -3653,7 +3653,7 @@ Ejemplos The repository is empty. - + El repositorio está vacío. @@ -3778,7 +3778,7 @@ Ejemplos squash - + aplastar @@ -4329,12 +4329,12 @@ Esto revierte la confirmación %2. Prompt to stage directories - + Preguntar al preparar directorios Prompt to stage large files - + Preguntar al preparar ficheros pesados @@ -4392,7 +4392,7 @@ Esto revierte la confirmación %2. Misc - + Varios @@ -4405,17 +4405,17 @@ Esto revierte la confirmación %2. Finder - + Buscador Explorer - + Explorador Default File Browser - + Explorador por defecto @@ -4728,32 +4728,32 @@ Esto revierte la confirmación %2. Replace... - + Reemplazar... Replace All... - + Reemplazar Todo... Ignore - Ignorar + Ignorar Ignore All - + Ignorar Todo Add to User Dictionary - + Añador al Diccionario de Usuario Do not Ignore - + No Ignorar @@ -4809,12 +4809,12 @@ Esto revierte la confirmación %2. Merge - Fusionar + Fusionar Rebase - Reorganizar + Reorganizar diff --git a/src/app/Application.cpp b/src/app/Application.cpp index 229787c69..fa1178947 100644 --- a/src/app/Application.cpp +++ b/src/app/Application.cpp @@ -16,6 +16,7 @@ #include "ui/RepoView.h" #include "ui/TabWidget.h" #include "update/Updater.h" +#include "util/Debug.h" #include #include #include @@ -102,19 +103,6 @@ Application::Application(int &argc, char **argv, bool haltOnParseError) // Register types that are queued at runtime. qRegisterMetaType(); - qDebug() << QString("Root dir: %1").arg(Settings::rootDir().absolutePath()); - qDebug() << QString("App dir: %1").arg(Settings::appDir().absolutePath()); - qDebug() << QString("Doc dir: %1").arg(Settings::docDir().absolutePath()); - qDebug() << QString("Conf dir: %1").arg(Settings::confDir().absolutePath()); - qDebug() << QString("l10n dir: %1").arg(Settings::l10nDir().absolutePath()); - qDebug() << QString("dictionaries dir: %1") - .arg(Settings::dictionariesDir().absolutePath()); - qDebug() << QString("lexer dir: %1").arg(Settings::lexerDir().absolutePath()); - qDebug() - << QString("themes dir: %1").arg(Settings::themesDir().absolutePath()); - qDebug() << QString("pluginsDir dir: %1") - .arg(Settings::pluginsDir().absolutePath()); - // Connect updater signals. connect(Updater::instance(), &Updater::sslErrors, this, &Application::handleSslErrors); @@ -145,6 +133,18 @@ Application::Application(int &argc, char **argv, bool haltOnParseError) // Set debug menu option. MenuBar::setDebugMenuVisible(parser.isSet("debug-menu")); + Debug(QString("Root dir: %1").arg(Settings::rootDir().absolutePath())); + Debug(QString("App dir: %1").arg(Settings::appDir().absolutePath())); + Debug(QString("Doc dir: %1").arg(Settings::docDir().absolutePath())); + Debug(QString("Conf dir: %1").arg(Settings::confDir().absolutePath())); + Debug(QString("l10n dir: %1").arg(Settings::l10nDir().absolutePath())); + Debug(QString("dictionaries dir: %1") + .arg(Settings::dictionariesDir().absolutePath())); + Debug(QString("lexer dir: %1").arg(Settings::lexerDir().absolutePath())); + Debug(QString("themes dir: %1").arg(Settings::themesDir().absolutePath())); + Debug( + QString("pluginsDir dir: %1").arg(Settings::pluginsDir().absolutePath())); + // Set pathspec filter. mPathspec = parser.value("filter"); diff --git a/src/git/Repository.cpp b/src/git/Repository.cpp index adaf0cbd4..311b91c99 100644 --- a/src/git/Repository.cpp +++ b/src/git/Repository.cpp @@ -309,8 +309,11 @@ Diff Repository::status(const Index &index, Diff::Callbacks *callbacks, Diff Repository::diffTreeToIndex(const Tree &tree, const Index &index, bool ignoreWhitespace) const { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS | - GIT_DIFF_INCLUDE_TYPECHANGE; + opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; + + if (!appConfig().value("untracked.hide", false)) + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + if (ignoreWhitespace) opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; @@ -323,8 +326,11 @@ Diff Repository::diffIndexToWorkdir(const Index &index, Diff::Callbacks *callbacks, bool ignoreWhitespace) const { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - opts.flags |= (GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS | - GIT_DIFF_DISABLE_MMAP | GIT_DIFF_INCLUDE_TYPECHANGE); + opts.flags |= (GIT_DIFF_DISABLE_MMAP | GIT_DIFF_INCLUDE_TYPECHANGE); + + if (!appConfig().value("untracked.hide", false)) + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + if (ignoreWhitespace) opts.flags |= GIT_DIFF_IGNORE_WHITESPACE; diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index e39ac7523..677f679b2 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -19,32 +19,15 @@ #include #include -MergeTool::MergeTool(const QStringList &files, const git::Diff &diff, - const git::Repository &repo, QObject *parent) - : ExternalTool(files, diff, repo, parent) { - - if (!mFiles.empty()) { - foreach (const QString &file, mFiles) { - if (isConflicted(file)) { - git::Index::Conflict conflict = repo.index().conflict(file); - mMerges.append({file, repo.lookupBlob(conflict.ours), - repo.lookupBlob(conflict.theirs), - repo.lookupBlob(conflict.ancestor)}); - } - } - } -} +MergeTool::MergeTool(const QString &file, const git::Blob &localBlob, + const git::Blob &remoteBlob, const git::Blob &baseBlob, + QObject *parent) + : ExternalTool(file, parent), mLocalBlob(localBlob), + mRemoteBlob(remoteBlob), mBaseBlob(baseBlob) {} bool MergeTool::isValid() const { - if (!ExternalTool::isValid()) - return false; - - for (const FileMerge &fileMerge : mMerges) { - if (!fileMerge.local.isValid() || !fileMerge.remote.isValid()) { - return false; - } - } - return true; + return (ExternalTool::isValid() && mLocalBlob.isValid() && + mRemoteBlob.isValid()); } ExternalTool::Kind MergeTool::kind() const { return Merge; } @@ -59,111 +42,101 @@ bool MergeTool::start() { if (command.isEmpty()) return false; - int numMergeFiles = mMerges.size(); - for (const FileMerge &fileMerge : mMerges) { - // Write temporary files. - QString templatePath = - QDir::temp().filePath(QFileInfo(fileMerge.name).fileName()); - QTemporaryFile *local = new QTemporaryFile(templatePath, this); - if (!local->open()) - return false; + // Write temporary files. + QString templatePath = QDir::temp().filePath(QFileInfo(mFile).fileName()); + QTemporaryFile *local = new QTemporaryFile(templatePath, this); + if (!local->open()) + return false; + + local->write(mLocalBlob.content()); + local->flush(); + + QTemporaryFile *remote = new QTemporaryFile(templatePath, this); + if (!remote->open()) + return false; - local->write(fileMerge.local.content()); - local->flush(); + remote->write(mRemoteBlob.content()); + remote->flush(); - QTemporaryFile *remote = new QTemporaryFile(templatePath, this); - if (!remote->open()) + QString basePath; + if (mBaseBlob.isValid()) { + QTemporaryFile *base = new QTemporaryFile(templatePath, this); + if (!base->open()) return false; - remote->write(fileMerge.remote.content()); - remote->flush(); + base->write(mBaseBlob.content()); + base->flush(); - QString basePath; - if (fileMerge.base.isValid()) { - QTemporaryFile *base = new QTemporaryFile(templatePath, this); - if (!base->open()) - return false; + basePath = base->fileName(); + } - base->write(fileMerge.base.content()); - base->flush(); + // Make the backup copy. + QString backupPath = QString("%1.orig").arg(mFile); + if (!QFile::copy(mFile, backupPath)) { + // FIXME: What should happen if the backup already exists? + } - basePath = base->fileName(); + // Destroy this after process finishes. + QProcess *process = new QProcess(this); + process->setProcessChannelMode( + QProcess::ProcessChannelMode::ForwardedChannels); + git::Repository repo = mLocalBlob.repo(); + auto signal = QOverload::of(&QProcess::finished); + QObject::connect(process, signal, [this, repo, backupPath, process] { + Debug("Merge Process Exited!"); + Debug("Stdout: " << process->readAllStandardOutput()); + Debug("Stderr: " << process->readAllStandardError()); + + QFileInfo merged(mFile); + QFileInfo backup(backupPath); + git::Config config = git::Config::global(); + bool modified = (merged.lastModified() > backup.lastModified()); + if (!modified || !config.value("mergetool.keepBackup")) + QFile::remove(backupPath); + + if (modified) { + int length = repo.workdir().path().length(); + repo.index().setStaged({mFile.mid(length + 1)}, true); } - // Make the backup copy. - QString backupPath = QString("%1.orig").arg(fileMerge.name); - if (!QFile::copy(fileMerge.name, backupPath)) { - // FIXME: What should happen if the backup already exists? - } + deleteLater(); + }); - // Destroy this after process finishes. - QProcess *process = new QProcess(); - process->setProcessChannelMode( - QProcess::ProcessChannelMode::ForwardedChannels); - git::Repository repo = fileMerge.local.repo(); - auto signal = QOverload::of(&QProcess::finished); - QObject::connect( - process, signal, - [this, repo, fileMerge, backupPath, process, &numMergeFiles] { - qDebug() << "Merge Process Exited!"; - qDebug() << "Stdout: " << process->readAllStandardOutput(); - qDebug() << "Stderr: " << process->readAllStandardError(); - - QFileInfo merged(fileMerge.name); - QFileInfo backup(backupPath); - git::Config config = git::Config::global(); - bool modified = (merged.lastModified() > backup.lastModified()); - if (!modified || !config.value("mergetool.keepBackup")) - QFile::remove(backupPath); - - if (modified) { - int length = repo.workdir().path().length(); - repo.index().setStaged({fileMerge.name.mid(length + 1)}, true); - } - - if (--numMergeFiles == 0) { - deleteLater(); - } - }); - - QString fullFilePath = mRepo.workdir().filePath(fileMerge.name); - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("LOCAL", local->fileName()); - env.insert("REMOTE", remote->fileName()); - env.insert("MERGED", fullFilePath); - env.insert("BASE", basePath); - process->setProcessEnvironment(env); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("LOCAL", local->fileName()); + env.insert("REMOTE", remote->fileName()); + env.insert("MERGED", mFile); + env.insert("BASE", basePath); + process->setProcessEnvironment(env); #if defined(FLATPAK) || defined(DEBUG_FLATPAK) - QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), - "--env=REMOTE=" + remote->fileName(), - "--env=MERGED=" + fullFilePath, - "--env=BASE=" + basePath}; - arguments.append("sh"); - arguments.append("-c"); - arguments.append(command); - // Debug("Command: " << "flatpak-spawn"); - process->start("flatpak-spawn", arguments); - // Debug("QProcess Arguments: " << process->arguments()); - if (!process->waitForStarted()) { - Debug("MergeTool starting failed"); - return false; - } + QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), + "--env=REMOTE=" + remote->fileName(), + "--env=MERGED=" + mFile, "--env=BASE=" + basePath}; + arguments.append("sh"); + arguments.append("-c"); + arguments.append(command); + // Debug("Command: " << "flatpak-spawn"); + process->start("flatpak-spawn", arguments); + // Debug("QProcess Arguments: " << process->arguments()); + if (!process->waitForStarted()) { + Debug("MergeTool starting failed"); + return false; + } #else - QString bash = git::Command::bashPath(); - if (!bash.isEmpty()) { - process->start(bash, {"-c", command}); - } else if (!shell) { - process->start(git::Command::substitute(env, command)); - } else { - emit error(BashNotFound); - return false; - } + QString bash = git::Command::bashPath(); + if (!bash.isEmpty()) { + process->start(bash, {"-c", command}); + } else if (!shell) { + process->start(git::Command::substitute(env, command)); + } else { + emit error(BashNotFound); + return false; + } - if (!process->waitForStarted()) - return false; + if (!process->waitForStarted()) + return false; #endif - } // Detach from parent. setParent(nullptr); diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index 8864e016b..cd6349353 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -20,6 +20,7 @@ #include "conf/Settings.h" #include "DiffView/DiffView.h" #include "git/Index.h" +#include "git/Config.h" #include #include @@ -102,8 +103,19 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent) checked); RepoView::parentView(this)->refresh(); }); + QAction *hideUntrackedFiles = new QAction(tr("Hide Untracked Files")); + hideUntrackedFiles->setCheckable(true); + hideUntrackedFiles->setChecked( + RepoView::parentView(parent)->repo().appConfig().value( + "untracked.hide", false)); + connect(hideUntrackedFiles, &QAction::triggered, this, [this](bool checked) { + RepoView::parentView(this)->repo().appConfig().setValue("untracked.hide", + checked); + RepoView::parentView(this)->refresh(); + }); contextMenu->addAction(singleTree); contextMenu->addAction(listView); + contextMenu->addAction(hideUntrackedFiles); QHBoxLayout *buttonLayout = new QHBoxLayout(); buttonLayout->addStretch(); buttonLayout->addWidget(segmentedButton); diff --git a/src/ui/MenuBar.cpp b/src/ui/MenuBar.cpp index f29e3a2a0..4f14cec16 100644 --- a/src/ui/MenuBar.cpp +++ b/src/ui/MenuBar.cpp @@ -39,6 +39,7 @@ #include "log/LogEntry.h" #include "log/LogView.h" #include "update/Updater.h" +#include "util/Debug.h" #include #include #include @@ -872,6 +873,12 @@ MenuBar::MenuBar(QWidget *parent) : QMenuBar(parent) { connect(remote, &QAction::triggered, [](bool checked) { git::Remote::setLoggingEnabled(checked); }); + QAction *debugMessages = debug->addAction(tr("Log Debug Messages")); + debugMessages->setCheckable(true); + debugMessages->setChecked(Debug::isLogging()); + connect(debugMessages, &QAction::triggered, + [](bool checked) { Debug::setLogging(checked); }); + debug->addSeparator(); QAction *diffs = debug->addAction(tr("Load All Diffs")); @@ -1138,6 +1145,7 @@ QList MenuBar::views() const { } void MenuBar::setDebugMenuVisible(bool visible) { sDebugMenuVisible = visible; } +bool MenuBar::isDebugMenuVisible() { return sDebugMenuVisible; } MenuBar *MenuBar::instance(QWidget *widget) { #ifdef Q_OS_MAC diff --git a/src/ui/MenuBar.h b/src/ui/MenuBar.h index a2bc23548..516eac261 100644 --- a/src/ui/MenuBar.h +++ b/src/ui/MenuBar.h @@ -40,6 +40,7 @@ class MenuBar : public QMenuBar { void updateWindow(); static void setDebugMenuVisible(bool show); + static bool isDebugMenuVisible(); static MenuBar *instance(QWidget *widget); /*! * \brief isMaximized diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index a3e02b7dd..4d039839f 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(util Path.cpp Debug.h) +add_library(util Path.cpp Debug.h Debug.cpp) target_link_libraries(util Qt5::Core) target_include_directories(util INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/util/Debug.cpp b/src/util/Debug.cpp new file mode 100644 index 000000000..ee6f6384a --- /dev/null +++ b/src/util/Debug.cpp @@ -0,0 +1,24 @@ +#include +#include + +namespace { +const QString kLogKey = "debug/log"; +bool readSettings = false; +bool logging = false; +} // namespace + +namespace Debug { + +void setLogging(bool enable) { + logging = enable; + QSettings().setValue(kLogKey, enable); +} + +bool isLogging() { + if (!readSettings) { + readSettings = true; + logging = QSettings().value(kLogKey).toBool(); + } + return logging; +} +}; // namespace Debug \ No newline at end of file diff --git a/src/util/Debug.h b/src/util/Debug.h index 8d737d7c2..eecc0b0fc 100644 --- a/src/util/Debug.h +++ b/src/util/Debug.h @@ -3,10 +3,17 @@ #include +namespace Debug { + +void setLogging(bool enable); +bool isLogging(); +}; // namespace Debug + #ifdef DEBUG_OUTPUT_GENERAL #define Debug(x) \ do { \ - qDebug() << x; \ + if (Debug::isLogging()) \ + qDebug() << x; \ } while (false) #else #define Debug(x) @@ -15,7 +22,8 @@ #ifdef DEBUG_OUTPUT_REFRESH #define DebugRefresh(x) \ do { \ - qDebug() << Q_FUNC_INFO << QStringLiteral(": ") << x; \ + if (Debug::isLogging()) \ + qDebug() << Q_FUNC_INFO << QStringLiteral(": ") << x; \ } while (false) #else #define DebugRefresh(x) From 02f2f391aca0db164fafe91fd711ff2a9c1c56a3 Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 21 Aug 2023 17:30:23 -0400 Subject: [PATCH 19/21] Fixes for previous submission. --- src/tools/MergeTool.cpp | 201 +++++++++++++++++++++++----------------- 1 file changed, 114 insertions(+), 87 deletions(-) diff --git a/src/tools/MergeTool.cpp b/src/tools/MergeTool.cpp index 677f679b2..e39ac7523 100644 --- a/src/tools/MergeTool.cpp +++ b/src/tools/MergeTool.cpp @@ -19,15 +19,32 @@ #include #include -MergeTool::MergeTool(const QString &file, const git::Blob &localBlob, - const git::Blob &remoteBlob, const git::Blob &baseBlob, - QObject *parent) - : ExternalTool(file, parent), mLocalBlob(localBlob), - mRemoteBlob(remoteBlob), mBaseBlob(baseBlob) {} +MergeTool::MergeTool(const QStringList &files, const git::Diff &diff, + const git::Repository &repo, QObject *parent) + : ExternalTool(files, diff, repo, parent) { + + if (!mFiles.empty()) { + foreach (const QString &file, mFiles) { + if (isConflicted(file)) { + git::Index::Conflict conflict = repo.index().conflict(file); + mMerges.append({file, repo.lookupBlob(conflict.ours), + repo.lookupBlob(conflict.theirs), + repo.lookupBlob(conflict.ancestor)}); + } + } + } +} bool MergeTool::isValid() const { - return (ExternalTool::isValid() && mLocalBlob.isValid() && - mRemoteBlob.isValid()); + if (!ExternalTool::isValid()) + return false; + + for (const FileMerge &fileMerge : mMerges) { + if (!fileMerge.local.isValid() || !fileMerge.remote.isValid()) { + return false; + } + } + return true; } ExternalTool::Kind MergeTool::kind() const { return Merge; } @@ -42,101 +59,111 @@ bool MergeTool::start() { if (command.isEmpty()) return false; - // Write temporary files. - QString templatePath = QDir::temp().filePath(QFileInfo(mFile).fileName()); - QTemporaryFile *local = new QTemporaryFile(templatePath, this); - if (!local->open()) - return false; - - local->write(mLocalBlob.content()); - local->flush(); - - QTemporaryFile *remote = new QTemporaryFile(templatePath, this); - if (!remote->open()) - return false; + int numMergeFiles = mMerges.size(); + for (const FileMerge &fileMerge : mMerges) { + // Write temporary files. + QString templatePath = + QDir::temp().filePath(QFileInfo(fileMerge.name).fileName()); + QTemporaryFile *local = new QTemporaryFile(templatePath, this); + if (!local->open()) + return false; - remote->write(mRemoteBlob.content()); - remote->flush(); + local->write(fileMerge.local.content()); + local->flush(); - QString basePath; - if (mBaseBlob.isValid()) { - QTemporaryFile *base = new QTemporaryFile(templatePath, this); - if (!base->open()) + QTemporaryFile *remote = new QTemporaryFile(templatePath, this); + if (!remote->open()) return false; - base->write(mBaseBlob.content()); - base->flush(); + remote->write(fileMerge.remote.content()); + remote->flush(); - basePath = base->fileName(); - } + QString basePath; + if (fileMerge.base.isValid()) { + QTemporaryFile *base = new QTemporaryFile(templatePath, this); + if (!base->open()) + return false; - // Make the backup copy. - QString backupPath = QString("%1.orig").arg(mFile); - if (!QFile::copy(mFile, backupPath)) { - // FIXME: What should happen if the backup already exists? - } + base->write(fileMerge.base.content()); + base->flush(); - // Destroy this after process finishes. - QProcess *process = new QProcess(this); - process->setProcessChannelMode( - QProcess::ProcessChannelMode::ForwardedChannels); - git::Repository repo = mLocalBlob.repo(); - auto signal = QOverload::of(&QProcess::finished); - QObject::connect(process, signal, [this, repo, backupPath, process] { - Debug("Merge Process Exited!"); - Debug("Stdout: " << process->readAllStandardOutput()); - Debug("Stderr: " << process->readAllStandardError()); - - QFileInfo merged(mFile); - QFileInfo backup(backupPath); - git::Config config = git::Config::global(); - bool modified = (merged.lastModified() > backup.lastModified()); - if (!modified || !config.value("mergetool.keepBackup")) - QFile::remove(backupPath); - - if (modified) { - int length = repo.workdir().path().length(); - repo.index().setStaged({mFile.mid(length + 1)}, true); + basePath = base->fileName(); } - deleteLater(); - }); + // Make the backup copy. + QString backupPath = QString("%1.orig").arg(fileMerge.name); + if (!QFile::copy(fileMerge.name, backupPath)) { + // FIXME: What should happen if the backup already exists? + } - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("LOCAL", local->fileName()); - env.insert("REMOTE", remote->fileName()); - env.insert("MERGED", mFile); - env.insert("BASE", basePath); - process->setProcessEnvironment(env); + // Destroy this after process finishes. + QProcess *process = new QProcess(); + process->setProcessChannelMode( + QProcess::ProcessChannelMode::ForwardedChannels); + git::Repository repo = fileMerge.local.repo(); + auto signal = QOverload::of(&QProcess::finished); + QObject::connect( + process, signal, + [this, repo, fileMerge, backupPath, process, &numMergeFiles] { + qDebug() << "Merge Process Exited!"; + qDebug() << "Stdout: " << process->readAllStandardOutput(); + qDebug() << "Stderr: " << process->readAllStandardError(); + + QFileInfo merged(fileMerge.name); + QFileInfo backup(backupPath); + git::Config config = git::Config::global(); + bool modified = (merged.lastModified() > backup.lastModified()); + if (!modified || !config.value("mergetool.keepBackup")) + QFile::remove(backupPath); + + if (modified) { + int length = repo.workdir().path().length(); + repo.index().setStaged({fileMerge.name.mid(length + 1)}, true); + } + + if (--numMergeFiles == 0) { + deleteLater(); + } + }); + + QString fullFilePath = mRepo.workdir().filePath(fileMerge.name); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("LOCAL", local->fileName()); + env.insert("REMOTE", remote->fileName()); + env.insert("MERGED", fullFilePath); + env.insert("BASE", basePath); + process->setProcessEnvironment(env); #if defined(FLATPAK) || defined(DEBUG_FLATPAK) - QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), - "--env=REMOTE=" + remote->fileName(), - "--env=MERGED=" + mFile, "--env=BASE=" + basePath}; - arguments.append("sh"); - arguments.append("-c"); - arguments.append(command); - // Debug("Command: " << "flatpak-spawn"); - process->start("flatpak-spawn", arguments); - // Debug("QProcess Arguments: " << process->arguments()); - if (!process->waitForStarted()) { - Debug("MergeTool starting failed"); - return false; - } + QStringList arguments = {"--host", "--env=LOCAL=" + local->fileName(), + "--env=REMOTE=" + remote->fileName(), + "--env=MERGED=" + fullFilePath, + "--env=BASE=" + basePath}; + arguments.append("sh"); + arguments.append("-c"); + arguments.append(command); + // Debug("Command: " << "flatpak-spawn"); + process->start("flatpak-spawn", arguments); + // Debug("QProcess Arguments: " << process->arguments()); + if (!process->waitForStarted()) { + Debug("MergeTool starting failed"); + return false; + } #else - QString bash = git::Command::bashPath(); - if (!bash.isEmpty()) { - process->start(bash, {"-c", command}); - } else if (!shell) { - process->start(git::Command::substitute(env, command)); - } else { - emit error(BashNotFound); - return false; - } + QString bash = git::Command::bashPath(); + if (!bash.isEmpty()) { + process->start(bash, {"-c", command}); + } else if (!shell) { + process->start(git::Command::substitute(env, command)); + } else { + emit error(BashNotFound); + return false; + } - if (!process->waitForStarted()) - return false; + if (!process->waitForStarted()) + return false; #endif + } // Detach from parent. setParent(nullptr); From 9424dde377da08d038fa1ab893838aea2a4e44e3 Mon Sep 17 00:00:00 2001 From: devnull Date: Thu, 31 Aug 2023 16:30:36 -0400 Subject: [PATCH 20/21] Use columns for file name, directory, and state when files are shown as a list in TreeViews. Resolves Dense layout issue #547 --- .gitignore | 3 +++ src/ui/DiffTreeModel.cpp | 52 ++++++++++++++++++++++++++++++++----- src/ui/DiffTreeModel.h | 6 +++-- src/ui/DoubleTreeWidget.cpp | 19 +++----------- src/ui/TreeProxy.cpp | 6 +++-- src/ui/TreeProxy.h | 7 ++++- src/ui/TreeView.cpp | 26 ++++++++++++++++++- src/ui/TreeView.h | 7 ++++- src/ui/ViewDelegate.cpp | 48 ++++++++++------------------------ src/ui/ViewDelegate.h | 7 +++-- test/TreeView.cpp | 15 +++++++++++ test/index.cpp | 18 +++++++++++++ 12 files changed, 148 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index 6786e6c10..ca6f9e259 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +.cache .DS_Store .project .vscode/ @@ -8,3 +9,5 @@ cmake-build-release/ build .idea/ .venv +compile_commands.json + diff --git a/src/ui/DiffTreeModel.cpp b/src/ui/DiffTreeModel.cpp index a722705c6..6745d18dd 100644 --- a/src/ui/DiffTreeModel.cpp +++ b/src/ui/DiffTreeModel.cpp @@ -7,6 +7,7 @@ // Author: Jason Haslam // +#include #include "DiffTreeModel.h" #include "conf/Settings.h" #include "git/Blob.h" @@ -16,15 +17,30 @@ #include "git/Patch.h" #include #include +#include namespace { const QString kLinkFmt = "%2"; +const std::array kModelHeaders = {QObject::tr("File Name"), + QObject::tr("Relative Path"), + QObject::tr("State")}; + +bool asList() { + return Settings::instance() + ->value(Setting::Id::ShowChangedFilesAsList, false) + .toBool(); +} + } // namespace DiffTreeModel::DiffTreeModel(const git::Repository &repo, QObject *parent) - : QAbstractItemModel(parent), mRepo(repo) {} + : QStandardItemModel(0, kModelHeaders.size(), parent), mRepo(repo) { + for (int i = 0; i < kModelHeaders.size(); ++i) { + setHeaderData(i, Qt::Horizontal, kModelHeaders[i]); + } +} DiffTreeModel::~DiffTreeModel() { delete mRoot; } @@ -91,7 +107,9 @@ int DiffTreeModel::rowCount(const QModelIndex &parent) const { return mDiff ? node(parent)->children().size() : 0; } -int DiffTreeModel::columnCount(const QModelIndex &parent) const { return 1; } +int DiffTreeModel::columnCount(const QModelIndex &parent) const { + return asList() ? QStandardItemModel::columnCount(parent) : 1; +} bool DiffTreeModel::hasChildren(const QModelIndex &parent) const { return mRoot && node(parent)->hasChildren(); @@ -134,7 +152,7 @@ void DiffTreeModel::modelIndices(const QModelIndex &parent, } for (int i = 0; i < n->children().length(); i++) { - auto child = createIndex(i, 0, n->children()[i]); + auto child = createIndex(i, parent.column(), n->children()[i]); if (recursive) modelIndices(child, list); else if (!node(child)->hasChildren()) @@ -195,9 +213,15 @@ QVariant DiffTreeModel::data(const QModelIndex &index, int role) const { return QVariant(); Node *node = this->node(index); + + // Skip intermediate path elements for trees showing file lists only. + if (node->hasChildren() && asList()) + return QVariant(); + switch (role) { - case Qt::DisplayRole: - return node->name(); + case Qt::DisplayRole: { + return getDisplayRole(index); + } // case Qt::DecorationRole: { // QFileInfo info(node->path()); @@ -212,7 +236,7 @@ QVariant DiffTreeModel::data(const QModelIndex &index, int role) const { return node->path(); case Qt::CheckStateRole: { - if (!mDiff.isValid() || !mDiff.isStatusDiff()) + if (!mDiff.isValid() || !mDiff.isStatusDiff() || index.column() > 0) return QVariant(); git::Index index = mDiff.index(); @@ -380,6 +404,22 @@ Node *DiffTreeModel::node(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : mRoot; } +QVariant DiffTreeModel::getDisplayRole(const QModelIndex &index) const { + Node *node = this->node(index); + if (asList()) { + QFileInfo fileInfo(node->path(true)); + switch (index.column()) { + case 0: + return fileInfo.fileName(); + case 1: + return fileInfo.path(); + default: + return ""; + } + } + return node->name(); +} + //############################################################################# //###### DiffTreeModel::Node ############################################## //############################################################################# diff --git a/src/ui/DiffTreeModel.h b/src/ui/DiffTreeModel.h index 9b1d2dba4..0dc424fcc 100644 --- a/src/ui/DiffTreeModel.h +++ b/src/ui/DiffTreeModel.h @@ -14,7 +14,8 @@ #include "git/Index.h" #include "git/Tree.h" #include "git/Repository.h" -#include +#include +#include #include #include "git/Index.h" @@ -80,7 +81,7 @@ class Node : public QObject // item of the model * This Treemodel is similar to the normal tree model, but handles only the * files in the diff it self and not the complete tree */ -class DiffTreeModel : public QAbstractItemModel { +class DiffTreeModel : public QStandardItemModel { Q_OBJECT public: @@ -157,6 +158,7 @@ class DiffTreeModel : public QAbstractItemModel { private: Node *node(const QModelIndex &index) const; + QVariant getDisplayRole(const QModelIndex &index) const; QFileIconProvider mIconProvider; diff --git a/src/ui/DoubleTreeWidget.cpp b/src/ui/DoubleTreeWidget.cpp index c59746705..45a005f83 100644 --- a/src/ui/DoubleTreeWidget.cpp +++ b/src/ui/DoubleTreeWidget.cpp @@ -15,7 +15,6 @@ #include "StatePushButton.h" #include "TreeProxy.h" #include "TreeView.h" -#include "ViewDelegate.h" #include "Debug.h" #include "conf/Settings.h" #include "DiffView/DiffView.h" @@ -98,6 +97,7 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent) listView->setChecked(Settings::instance() ->value(Setting::Id::ShowChangedFilesAsList, false) .toBool()); + RepoView::parentView(this)->refresh(); connect(listView, &QAction::triggered, this, [this](bool checked) { Settings::instance()->setValue(Setting::Id::ShowChangedFilesAsList, checked); @@ -160,13 +160,8 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent) repoView->updateSubmodules(submodules, recursive, init, force_checkout); }); - TreeProxy *treewrapperStaged = new TreeProxy(true, this); - treewrapperStaged->setSourceModel(mDiffTreeModel); - stagedFiles->setModel(treewrapperStaged); - stagedFiles->setHeaderHidden(true); - ViewDelegate *stagedDelegate = new ViewDelegate(); - stagedDelegate->setDrawArrow(false); - stagedFiles->setItemDelegateForColumn(0, stagedDelegate); + + stagedFiles->setModel(new TreeProxy(true, mDiffTreeModel, this)); QHBoxLayout *hBoxLayout = new QHBoxLayout(); QLabel *label = new QLabel(kStagedFiles); @@ -192,13 +187,7 @@ DoubleTreeWidget::DoubleTreeWidget(const git::Repository &repo, QWidget *parent) showFileContextMenu(pos, repoView, unstagedFiles, false); }); - TreeProxy *treewrapperUnstaged = new TreeProxy(false, this); - treewrapperUnstaged->setSourceModel(mDiffTreeModel); - unstagedFiles->setModel(treewrapperUnstaged); - unstagedFiles->setHeaderHidden(true); - ViewDelegate *unstagedDelegate = new ViewDelegate(); - unstagedDelegate->setDrawArrow(false); - unstagedFiles->setItemDelegateForColumn(0, unstagedDelegate); + unstagedFiles->setModel(new TreeProxy(false, mDiffTreeModel, this)); hBoxLayout = new QHBoxLayout(); mUnstagedCommitedFiles = new QLabel(kUnstagedFiles); diff --git a/src/ui/TreeProxy.cpp b/src/ui/TreeProxy.cpp index f956c3a6b..2b447f855 100644 --- a/src/ui/TreeProxy.cpp +++ b/src/ui/TreeProxy.cpp @@ -23,8 +23,10 @@ const QString kLinkFmt = "%2"; } // namespace -TreeProxy::TreeProxy(bool staged, QObject *parent) - : QSortFilterProxyModel(parent), mStaged(staged) {} +TreeProxy::TreeProxy(bool staged, QAbstractItemModel *model, QObject *parent) + : mStaged(staged), QSortFilterProxyModel(parent) { + setSourceModel(model); +} TreeProxy::~TreeProxy() {} diff --git a/src/ui/TreeProxy.h b/src/ui/TreeProxy.h index 2e8d75ae2..93ab7a4fc 100644 --- a/src/ui/TreeProxy.h +++ b/src/ui/TreeProxy.h @@ -16,13 +16,14 @@ #include #include +class QAbstractItemModel; class TreeModel; class TreeProxy : public QSortFilterProxyModel { Q_OBJECT public: - TreeProxy(bool staged, QObject *parent = nullptr); + TreeProxy(bool staged, QAbstractItemModel *model, QObject *parent); virtual ~TreeProxy(); bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole, bool ignoreIndexChanges = false); @@ -30,6 +31,10 @@ class TreeProxy : public QSortFilterProxyModel { void enableFilter(bool enable) { mFilter = enable; } + int columnCount(const QModelIndex &parent = QModelIndex()) const override { + return sourceModel()->columnCount(); + } + private: using QSortFilterProxyModel::setData; bool filterAcceptsRow(int source_row, diff --git a/src/ui/TreeView.cpp b/src/ui/TreeView.cpp index a7895dd18..75c720b41 100644 --- a/src/ui/TreeView.cpp +++ b/src/ui/TreeView.cpp @@ -24,6 +24,9 @@ #include "RepoView.h" #include #include +#include "conf/Settings.h" +#include +#include #ifdef Q_OS_WIN #define ICON_SIZE 48 @@ -41,8 +44,29 @@ const QString kLabelFmt = "

%1

"; } // namespace TreeView::TreeView(QWidget *parent, const QString &name) - : QTreeView(parent), mSharedDelegate(new ViewDelegate(this)), mName(name) { + : QTreeView(parent), mDelegateCol(0), + mFileListDelegatePtr(std::make_unique(this, true)), + mFileTreeDelegatePtr(std::make_unique(this)), mName(name) { setObjectName(name); + connect(RepoView::parentView(this)->repo().notifier(), + &git::RepositoryNotifier::referenceUpdated, this, + &TreeView::updateView); +} + +void TreeView::updateView() { + QAbstractItemModel *itemModel = model(); + if (!itemModel) + return; + + // Remove any previous delegate on the current column, get the new current + // column, and set the delegate on that. + setItemDelegateForColumn(mDelegateCol, nullptr); + mDelegateCol = itemModel->columnCount() - 1; + setItemDelegateForColumn(mDelegateCol, mDelegateCol + ? mFileListDelegatePtr.get() + : mFileTreeDelegatePtr.get()); + + setHeaderHidden(mDelegateCol ? false : true); } void TreeView::setModel(QAbstractItemModel *model) { diff --git a/src/ui/TreeView.h b/src/ui/TreeView.h index 138fd34e0..87cc642f1 100644 --- a/src/ui/TreeView.h +++ b/src/ui/TreeView.h @@ -11,6 +11,8 @@ #define TREEVIEW_H #include +#include +#include "ViewDelegate.h" class QItemDelegate; class DiffTreeModel; @@ -96,9 +98,12 @@ public slots: bool suppressDeselectionHandling{false}; int mCollapseCount; // Counts the number of collapsed folders. bool mSupressItemExpandStateChanged{false}; + void updateView(); - QItemDelegate *mSharedDelegate; QString mName; + std::unique_ptr mFileListDelegatePtr; + std::unique_ptr mFileTreeDelegatePtr; + int mDelegateCol = 0; }; #endif // TREEVIEW_H diff --git a/src/ui/ViewDelegate.cpp b/src/ui/ViewDelegate.cpp index 194a915a8..8e69662ef 100644 --- a/src/ui/ViewDelegate.cpp +++ b/src/ui/ViewDelegate.cpp @@ -10,37 +10,6 @@ void ViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QStyleOptionViewItem opt = option; drawBackground(painter, opt, index); - // Draw >. - if (mDrawArrow && index.model()->hasChildren(index)) { - painter->save(); - painter->setRenderHint(QPainter::Antialiasing, true); - - QColor color = opt.palette.color(QPalette::Active, QPalette::BrightText); - if (opt.state & QStyle::State_Selected) - color = - !opt.showDecorationSelected - ? opt.palette.color(QPalette::Active, QPalette::WindowText) - : opt.palette.color(QPalette::Active, QPalette::HighlightedText); - - painter->setPen(color); - painter->setBrush(color); - - int x = opt.rect.x() + opt.rect.width() - 3; - int y = opt.rect.y() + (opt.rect.height() / 2); - - QPainterPath path; - path.moveTo(x, y); - path.lineTo(x - 5, y - 3); - path.lineTo(x - 5, y + 3); - path.closeSubpath(); - painter->drawPath(path); - - painter->restore(); - - // Adjust rect to exclude the arrow. - opt.rect.adjust(0, 0, -11, 0); - } - // Draw badges. QString status = index.data(TreeModel::StatusRole).toString(); if (!status.isEmpty()) { @@ -49,19 +18,30 @@ void ViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, int width = size.width(); int height = size.height(); + auto startIter = status.cbegin(), endIter = status.cend(); + int leftAdjust = 0, rightAdjust = -3, leftWidth = 0, rightWidth = -width; + if (mMultiColumn) { + leftAdjust = 3; + rightAdjust = 0; + leftWidth = width; + rightWidth = 0; + std::reverse(status.begin(), status.end()); + } + // Add extra space. - opt.rect.adjust(0, 0, -3, 0); + opt.rect.adjust(leftAdjust, 0, rightAdjust, 0); for (int i = 0; i < status.count(); ++i) { int x = opt.rect.x() + opt.rect.width(); int y = opt.rect.y() + (opt.rect.height() / 2); - QRect rect(x - width, y - (height / 2), width, height); + QRect rect(mMultiColumn ? opt.rect.x() : x - width, y - (height / 2), + width, height); Badge::paint(painter, {Badge::Label(Badge::Label::Type::Status, status.at(i))}, rect, &opt); // Adjust rect. - opt.rect.adjust(0, 0, -width - 3, 0); + opt.rect.adjust(leftWidth + leftAdjust, 0, rightWidth + rightAdjust, 0); } } diff --git a/src/ui/ViewDelegate.h b/src/ui/ViewDelegate.h index 03f168a08..fc81c09ae 100644 --- a/src/ui/ViewDelegate.h +++ b/src/ui/ViewDelegate.h @@ -9,9 +9,8 @@ */ class ViewDelegate : public QItemDelegate { public: - ViewDelegate(QObject *parent = nullptr) : QItemDelegate(parent) {} - - void setDrawArrow(bool enable) { mDrawArrow = enable; } + ViewDelegate(QObject *parent, bool multiColumn = false) + : QItemDelegate(parent), mMultiColumn(multiColumn) {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; @@ -20,7 +19,7 @@ class ViewDelegate : public QItemDelegate { const QModelIndex &index) const override; private: - bool mDrawArrow = true; + bool mMultiColumn = false; }; #endif // VIEWDELEGATE_H diff --git a/test/TreeView.cpp b/test/TreeView.cpp index 246e8efbf..d85767227 100644 --- a/test/TreeView.cpp +++ b/test/TreeView.cpp @@ -3,7 +3,9 @@ #include "ui/MainWindow.h" #include "ui/DoubleTreeWidget.h" #include "ui/TreeView.h" +#include "ui/TreeProxy.h" #include "ui/FileContextMenu.h" +#include "conf/Settings.h" #include @@ -22,6 +24,18 @@ using namespace QTest; \ RepoView *repoView = window.currentView(); +static void disableListView(TreeView &treeView, RepoView &repoView) { + auto treeProxy = dynamic_cast(treeView.model()); + QVERIFY(treeProxy); + + auto diffTreeModel = dynamic_cast(treeProxy->sourceModel()); + QVERIFY(diffTreeModel); + + diffTreeModel->enableListView(false); + Settings::instance()->setValue(Setting::Id::ShowChangedFilesAsList, false); + repoView.refresh(); +} + class TestTreeView : public QObject { Q_OBJECT @@ -46,6 +60,7 @@ void TestTreeView::restoreStagedFileAfterCommit() { { auto unstagedTree = doubleTree->findChild("Unstaged"); QVERIFY(unstagedTree); + disableListView(*unstagedTree, *view); QAbstractItemModel *unstagedModel = unstagedTree->model(); // Wait for refresh auto timeout = Timeout(10000, "Repository didn't refresh in time"); diff --git a/test/index.cpp b/test/index.cpp index c6592afe6..f3fdc3c80 100644 --- a/test/index.cpp +++ b/test/index.cpp @@ -13,6 +13,8 @@ #include "ui/MainWindow.h" #include "ui/RepoView.h" #include "ui/TreeView.h" +#include "ui/TreeProxy.h" +#include "conf/Settings.h" #include #include #include @@ -20,6 +22,18 @@ using namespace Test; using namespace QTest; +static void disableListView(TreeView &treeView, RepoView &repoView) { + auto treeProxy = dynamic_cast(treeView.model()); + QVERIFY(treeProxy); + + auto diffTreeModel = dynamic_cast(treeProxy->sourceModel()); + QVERIFY(diffTreeModel); + + diffTreeModel->enableListView(false); + Settings::instance()->setValue(Setting::Id::ShowChangedFilesAsList, false); + repoView.refresh(); +} + class TestIndex : public QObject { Q_OBJECT @@ -56,6 +70,8 @@ void TestIndex::stageAddition() { auto unstagedFiles = doubleTree->findChild("Unstaged"); QVERIFY(unstagedFiles); + disableListView(*unstagedFiles, *view); + auto stagedFiles = doubleTree->findChild("Staged"); QVERIFY(stagedFiles); @@ -151,6 +167,8 @@ void TestIndex::stageDirectory() { auto unstagedFiles = doubleTree->findChild("Unstaged"); QVERIFY(unstagedFiles); + disableListView(*unstagedFiles, *view); + auto stagedFiles = doubleTree->findChild("Staged"); QVERIFY(stagedFiles); From 9da5e4fbb18687352471540c4837ad4a1464e28c Mon Sep 17 00:00:00 2001 From: devnull Date: Mon, 4 Sep 2023 16:20:40 -0400 Subject: [PATCH 21/21] Add sort by column. Force all columns headers to be initialized. --- src/ui/DiffTreeModel.cpp | 12 ++++++++++-- src/ui/DiffTreeModel.h | 1 + src/ui/TreeView.cpp | 4 ++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ui/DiffTreeModel.cpp b/src/ui/DiffTreeModel.cpp index 6745d18dd..a9f5de35d 100644 --- a/src/ui/DiffTreeModel.cpp +++ b/src/ui/DiffTreeModel.cpp @@ -36,10 +36,17 @@ bool asList() { } // namespace DiffTreeModel::DiffTreeModel(const git::Repository &repo, QObject *parent) - : QStandardItemModel(0, kModelHeaders.size(), parent), mRepo(repo) { + : QStandardItemModel(0, kModelHeaders.size(), parent), mRepo(repo), + mConstructed(false) { + // mConstructed only exists so that columnCount() returns the maximum number + // of columns when it is called internally by setHeaderData(). Without this, + // columnCount() will return 1 in the case that "List View" isn't selected and + // that means columns beyond the first won't be assigned the proper header + // value in the loop below. for (int i = 0; i < kModelHeaders.size(); ++i) { setHeaderData(i, Qt::Horizontal, kModelHeaders[i]); } + mConstructed = true; } DiffTreeModel::~DiffTreeModel() { delete mRoot; } @@ -108,7 +115,8 @@ int DiffTreeModel::rowCount(const QModelIndex &parent) const { } int DiffTreeModel::columnCount(const QModelIndex &parent) const { - return asList() ? QStandardItemModel::columnCount(parent) : 1; + return asList() || !mConstructed ? QStandardItemModel::columnCount(parent) + : 1; } bool DiffTreeModel::hasChildren(const QModelIndex &parent) const { diff --git a/src/ui/DiffTreeModel.h b/src/ui/DiffTreeModel.h index 0dc424fcc..8cc14b6c7 100644 --- a/src/ui/DiffTreeModel.h +++ b/src/ui/DiffTreeModel.h @@ -167,6 +167,7 @@ class DiffTreeModel : public QStandardItemModel { git::Repository mRepo; bool mListView = false; + bool mConstructed = false; }; #endif /* DIFFTREEMODEL */ diff --git a/src/ui/TreeView.cpp b/src/ui/TreeView.cpp index 75c720b41..aa38d506c 100644 --- a/src/ui/TreeView.cpp +++ b/src/ui/TreeView.cpp @@ -81,6 +81,10 @@ void TreeView::setModel(QAbstractItemModel *model) { connect(model, &QAbstractItemModel::rowsInserted, this, QOverload::of( &TreeView::updateCollapseCount)); + + // Allow column sorting and set the default column and sort order. + setSortingEnabled(true); + sortByColumn(0, Qt::AscendingOrder); } void TreeView::discard(const QModelIndex &index, const bool force) {