Something went wrong. Try again.
The agentic engineering control plane for the posthuman future
Something went wrong. Try again.
11 kB · 240 lines
C++
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241#include "JjBridge.h"
#include <QDir>#include <QFileInfo>
JjBridge::JjBridge(QObject* parent) : QObject(parent) {}
QProcess* JjBridge::run(const QStringList& args, const QString& workingDirectory) { auto* process = new QProcess(); if (!workingDirectory.isEmpty()) { process->setWorkingDirectory(workingDirectory); } process->setProcessChannelMode(QProcess::SeparateChannels); process->setProgram(QStringLiteral("jj")); process->setArguments(args);
// The process owns itself: after emitting finished it schedules a deferred // delete, which runs after every finished handler (including callers that // read stdout/stderr) has completed. A shared_ptr here would double-free: // deleteLater() deletes the raw object, then the shared_ptr refcount held // by the connection functor reaches zero and deletes it again. QObject::connect(process, &QProcess::finished, this, [this, process]() { m_busy = false; emit busyChanged(); process->deleteLater(); });
m_busy = true; emit busyChanged(); process->start(); return process;}
void JjBridge::repoRoot(const QString& path) { QString current = QDir::cleanPath(path); if (current.isEmpty()) { current = QDir::currentPath(); }
QString foundRoot; while (true) { const QString canonical = QFileInfo(current).canonicalFilePath(); if (canonical.isEmpty()) { break; } if (QFileInfo(canonical + QStringLiteral("/.jj")).isDir()) { foundRoot = canonical; break; } const QString parent = QFileInfo(canonical).absolutePath(); if (parent == canonical) { break; } current = parent; }
emit repoRootResolved(path, foundRoot);}
void JjBridge::listWorkspaces(const QString& repoRoot) { const QStringList args = { QStringLiteral("workspace"), QStringLiteral("list"), QStringLiteral("-R"), repoRoot, QStringLiteral("-T"), QStringLiteral("name ++ \"\\t\" ++ root ++ \"\\n\"")}; QProcess* process = run(args, QString());
QObject::connect(process, &QProcess::finished, this, [this, process]() { const QByteArray output = process->readAllStandardOutput(); QVariantList items; const QStringList lines = QString::fromUtf8(output).split(QLatin1Char('\n'), Qt::SkipEmptyParts); for (const QString& line : lines) { const qsizetype tab = line.indexOf(QLatin1Char('\t')); if (tab < 0) { continue; } const QString name = line.left(static_cast<int>(tab)); const QString root = line.mid(static_cast<int>(tab) + 1); if (root.isEmpty()) { continue; } QVariantMap item; item.insert(QStringLiteral("name"), name); item.insert(QStringLiteral("root"), root); items.append(item); } emit workspacesListed(items); });}
void JjBridge::addWorkspace(const QString& repoRoot, const QString& name, const QString& dest) { const QStringList args = {QStringLiteral("workspace"), QStringLiteral("add"), dest, QStringLiteral("--name"), name, QStringLiteral("-R"), repoRoot}; QProcess* process = run(args, QString());
QObject::connect(process, &QProcess::finished, this, [this, process, dest, name]() { const bool ok = process->exitStatus() == QProcess::NormalExit && process->exitCode() == 0; if (ok) { emit workspaceAdded(true, QString(), QFileInfo(dest).canonicalFilePath(), name); } else { emit workspaceAdded( false, QString::fromUtf8(process->readAllStandardError()).trimmed(), QString(), name); } });}
void JjBridge::renameWorkspace(const QString& workspaceRoot, const QString& newName) { // The working directory, not -R, selects the current workspace, which is // the only target `jj workspace rename` accepts. const QStringList args = {QStringLiteral("workspace"), QStringLiteral("rename"), newName}; QProcess* process = run(args, workspaceRoot);
QObject::connect(process, &QProcess::finished, this, [this, process]() { const bool ok = process->exitStatus() == QProcess::NormalExit && process->exitCode() == 0; if (ok) { emit workspaceRenamed(true, QString()); } else { emit workspaceRenamed( false, QString::fromUtf8(process->readAllStandardError()).trimmed()); } });}
void JjBridge::forgetWorkspace(const QString& repoRoot, const QString& name) { const QStringList args = {QStringLiteral("workspace"), QStringLiteral("forget"), name, QStringLiteral("-R"), repoRoot}; QProcess* process = run(args, QString());
QObject::connect(process, &QProcess::finished, this, [this, process]() { const bool ok = process->exitStatus() == QProcess::NormalExit && process->exitCode() == 0; if (ok) { emit workspaceForgotten(true, QString()); } else { emit workspaceForgotten( false, QString::fromUtf8(process->readAllStandardError()).trimmed()); } });}
void JjBridge::identify(const QString& path) { QString current = QDir::cleanPath(path); if (current.isEmpty()) { current = QDir::currentPath(); }
// `jj root` has no path argument; run it with the path as working // directory so it resolves that directory's workspace root. QProcess* process = run({QStringLiteral("root")}, current);
QObject::connect(process, &QProcess::finished, this, [this, process, path, current]() { const bool ok = process->exitStatus() == QProcess::NormalExit && process->exitCode() == 0; if (!ok) { emit identified(path, QString(), QString()); return; } // Canonicalize (resolves symlinks, e.g. /tmp -> // /private/tmp on macOS) so a jj workspace and the git // repo card of the same repository produce the SAME // repoRoot string. Fall back to the clean path when the // directory disappeared mid-probe (canonicalFilePath() // returns empty for a missing file). const QString cleaned = QDir::cleanPath(QString::fromUtf8( process->readAllStandardOutput()) .trimmed()); const QFileInfo rootInfo(cleaned); const QString root = rootInfo.canonicalFilePath().isEmpty() ? cleaned : rootInfo.canonicalFilePath();
// Determine which workspace entry corresponds to `path`. QProcess* list = run({QStringLiteral("workspace"), QStringLiteral("list"), QStringLiteral("-R"), root, QStringLiteral("-T"), QStringLiteral("name ++ \"\\t\" ++ root ++ \"\\n\"")}, QString()); QObject::connect(list, &QProcess::finished, this, [this, list, path, root]() { QString name; if (list->exitStatus() == QProcess::NormalExit && list->exitCode() == 0) { const QFileInfo request(path); const QString requestPath = QDir::cleanPath(request.absoluteFilePath()); qsizetype bestLength = -1; const QStringList lines = QString::fromUtf8( list->readAllStandardOutput()) .split(QLatin1Char('\n'), Qt::SkipEmptyParts); for (const QString& line : lines) { const qsizetype tab = line.indexOf(QLatin1Char('\t')); if (tab < 0) { continue; } const QString entryName = line.left(static_cast<int>(tab)); const QString entryRoot = QDir::cleanPath( line.mid(static_cast<int>(tab) + 1)); if (entryRoot.isEmpty()) { continue; } // Prefer the entry that contains the // requesting path; the longest matching // root is the nearest one. if (requestPath.startsWith( entryRoot + QLatin1Char('/')) || requestPath == entryRoot) { if (entryRoot.size() > bestLength) { bestLength = entryRoot.size(); name = entryName; } } } } if (name.isEmpty()) { name = QStringLiteral("default"); } emit identified(path, root, name); }); });}