Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi file tool support #535

Draft
wants to merge 33 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4e5bfd8
Extend tools to handle multiple files
gh-devnull Apr 6, 2023
76f07e7
Fix typo in FLATPAK variant.
gh-devnull Apr 13, 2023
959c83d
Merge branch 'master'
gh-devnull Apr 17, 2023
01b09f4
Close the branch drop-down list after the user checks out a branch.
gh-devnull Apr 17, 2023
53cb4ff
Merge branch 'Murmele:master' into latest
gh-devnull Apr 25, 2023
db90aa8
In DiffTool, use old and new blobs for stashes and commits.
gh-devnull Apr 25, 2023
f51f2d0
In MergeTool, use the workspace file as "merged".
gh-devnull Apr 25, 2023
ab69697
Fix FLATPAK typo.
gh-devnull Apr 25, 2023
b17e494
Merge branch 'Murmele:master' into latest
gh-devnull Apr 26, 2023
9a67cee
Merge branch 'Murmele:master' into latest
gh-devnull May 2, 2023
e20b5d0
Review updates for multi-file tool support.
gh-devnull May 8, 2023
e8c9d13
Review updates for multi-file tool support 2.
gh-devnull May 8, 2023
99537fa
Review updates for multi-file tool support 3.
gh-devnull May 8, 2023
1b37de0
Review updates for multi-file tool support 4.
gh-devnull May 8, 2023
fb6218c
Review updates for multi-file tool support 5 - Fix premature MergeToo…
gh-devnull May 10, 2023
c5be6b3
Review updates for multi-file tool support 6 - Remove "this" paramet…
gh-devnull May 10, 2023
3e34413
Review updates for multi-file tool support 7 - formatted
gh-devnull May 10, 2023
11d27ed
Review updates for multi-file tool support 6 - Remove "this" paramet…
gh-devnull May 10, 2023
cc117e6
Review updates for multi-file tool support 7 - formatted
gh-devnull May 10, 2023
87a0722
For a selected directory, add the ability to skip recursive file enum…
gh-devnull May 16, 2023
26f2513
Merge branch 'Murmele:master' into latest
gh-devnull May 16, 2023
009e67f
Merge branch 'Murmele:master' into multi-file-tool-support
gh-devnull May 16, 2023
11f2304
Update build.yml to build "latest" branch.
gh-devnull May 16, 2023
c544198
Merge branch 'Murmele:master' into latest
gh-devnull Jun 8, 2023
f9848ca
Merge branch 'Murmele:master' into multi-file-tool-support
gh-devnull Jun 8, 2023
76031bb
1e57e7d ("Spanish translation", 2023-06-17) José Miguel Manzano
gh-devnull Aug 21, 2023
59a51da
Merge branch 'latest'
gh-devnull Aug 21, 2023
304d037
Merge branch 'Murmele:master' into multi-file-tool-support
gh-devnull Aug 21, 2023
02f2f39
Fixes for previous submission.
gh-devnull Aug 21, 2023
9424dde
Use columns for file name, directory, and state when files are shown …
gh-devnull Aug 31, 2023
64be458
Merge branch 'master'
gh-devnull Sep 1, 2023
9da5e4f
Add sort by column. Force all columns headers to be initialized.
gh-devnull Sep 4, 2023
6b77e6e
Merge branch 'Murmele:master' into latest
gh-devnull Nov 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
build
.cache
.DS_Store
.project
.vscode/
CMakeLists.txt.user
cmake-build-debug/
cmake-build-release/
.idea/
compile_commands.json

165 changes: 100 additions & 65 deletions src/tools/DiffTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@
#include "DiffTool.h"
#include "git/Command.h"
#include "git/Repository.h"
#include "git/Blob.h"
#include <QProcess>
#include <QTemporaryFile>
#include <QDebug>

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; }
Expand All @@ -35,76 +41,105 @@ 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<int, QProcess::ExitStatus>::of(&QProcess::finished);
QObject::connect(process, signal, [this, process] {
qDebug() << "Merge Process Exited!";
qDebug() << "Stdout: " << process->readAllStandardOutput();
qDebug() << "Stderr: " << process->readAllStandardError();
deleteLater();
});
bool isWorkDirDiff = mDiff.isValid() && mDiff.isStatusDiff();

int numFiles = mFiles.size();
foreach (const QString &filePathAndName, mFiles) {
git::Blob filePathOld, filePathNew;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
git::Blob filePathOld, filePathNew;
QProcess *process = new QProcess();
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 (filePathOld.isValid()) {
otherPathAndName = makeBlobTempFullFilePath(filePathAndName, filePathOld);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
otherPathAndName = makeBlobTempFullFilePath(filePathAndName, filePathOld);
otherPathAndName = makeBlobTempFullFilePath(filePathAndName, filePathOld, process);

if (otherPathAndName.isEmpty())
return false;
}

// Destroy this after process finishes.
QProcess *process = new QProcess();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
QProcess *process = new QProcess();

process->setProcessChannelMode(
QProcess::ProcessChannelMode::ForwardedChannels);
auto signal = QOverload<int, QProcess::ExitStatus>::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();
}
Comment on lines +70 to +72
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (--numFiles == 0) {
deleteLater();
}
delete process;

});

// Convert to absolute path.
QString fullFilePath =
isWorkDirDiff ? mRepo.workdir().filePath(filePathAndName)
: makeBlobTempFullFilePath(filePathAndName, filePathNew);

#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=" + fullFilePath,
"--env=REMOTE=" + otherPathAndName,
"--env=MERGED=" + filePathAndName,
"--env=BASE=" + filePathAndName};
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", fullFilePath);
env.insert("REMOTE", otherPathAndName);
env.insert("MERGED", filePathAndName);
env.insert("BASE", filePathAndName);
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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false;
delete process;
return false;

}
#endif

if (!process->waitForStarted()) {
qDebug() << "DiffTool starting failed";
return false;
if (!process->waitForStarted()) {
qDebug() << "DiffTool starting failed";
return false;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return false;
delete process;
return false;

}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}
deleteLater();

}

// Detach from parent.
setParent(nullptr);

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;
}

QString DiffTool::makeBlobTempFullFilePath(const QString &filePathAndName,
const git::Blob &fileBlob) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const git::Blob &fileBlob) {
const git::Blob &fileBlob, QObject* parent) {

QString blobTmpFullFilePath;

QFileInfo fileInfo(filePathAndName);
QString templatePath = QDir::temp().filePath(fileInfo.fileName());
QTemporaryFile *temp = new QTemporaryFile(templatePath, this);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
QTemporaryFile *temp = new QTemporaryFile(templatePath, this);
QTemporaryFile *temp = new QTemporaryFile(templatePath, parent);

if (temp->open()) {
temp->write(fileBlob.content());
temp->flush();
blobTmpFullFilePath = temp->fileName();
}

return blobTmpFullFilePath;
}
22 changes: 17 additions & 5 deletions src/tools/DiffTool.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@
#ifndef DIFFTOOL_H
#define DIFFTOOL_H

#include <QString>
#include "ExternalTool.h"
#include "git/Blob.h"

class QObject;
class QString;
namespace git {
class Diff;
class Repository;
class Blob;
}; // namespace git

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;

Expand All @@ -28,8 +36,12 @@ 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;

QString makeBlobTempFullFilePath(const QString &filePathAndName,
const git::Blob &fileBlob);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const git::Blob &fileBlob);
const git::Blob &fileBlob, QObject* parent);

};

#endif
58 changes: 39 additions & 19 deletions src/tools/EditTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@
#include <QProcess>
#include <QUrl>

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; }
Expand All @@ -27,26 +35,31 @@ QString EditTool::name() const { return tr("Edit in External Editor"); }

bool EditTool::start() {
git::Config config = git::Config::global();
QString editor = config.value<QString>("gui.editor");
QString baseEditor = config.value<QString>("gui.editor");

if (editor.isEmpty())
editor = qgetenv("GIT_EDITOR");
if (baseEditor.isEmpty())
baseEditor = qgetenv("GIT_EDITOR");

if (editor.isEmpty())
editor = config.value<QString>("core.editor");
if (baseEditor.isEmpty())
baseEditor = config.value<QString>("core.editor");

if (editor.isEmpty())
editor = qgetenv("VISUAL");
if (baseEditor.isEmpty())
baseEditor = qgetenv("VISUAL");

if (editor.isEmpty())
editor = qgetenv("EDITOR");
if (baseEditor.isEmpty())
baseEditor = qgetenv("EDITOR");

if (editor.isEmpty())
return QDesktopServices::openUrl(QUrl::fromLocalFile(mFile));
if (baseEditor.isEmpty()) {
foreach (const QString &file, mFiles) {
QDesktopServices::openUrl(QUrl::fromLocalFile(file));
}
return true;
}

// Find arguments.
QStringList args = editor.split("\" \"");
QString editor = baseEditor;

// Find arguments.
QStringList args = baseEditor.split("\" \"");
if (args.count() > 1) {
// Format 1: "Command" "Argument1" "Argument2"
editor = args[0];
Expand All @@ -62,17 +75,24 @@ 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.
QProcess *process = new QProcess(this);
QProcess *process = new QProcess();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have to be carefull here, because without parent, the process object has to be deleted manually.
If you pass a parent, the Destructor of the parent will delete all childs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that accounts for the cryptic comment. 😉 I'll review this.

auto signal = QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished);
QObject::connect(process, signal, this, &ExternalTool::deleteLater);

Expand Down
9 changes: 8 additions & 1 deletion src/tools/EditTool.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@

#include "ExternalTool.h"

class QObject;
namespace git {
class Diff;
class Repository;
}; // namespace git

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;

Expand Down
Loading