Started moving fossil operations into Bridge

FossilOrigin-Name: 7a4ea959c64034b6e20dec41a4a8c16a09397bfa
This commit is contained in:
kostas 2015-04-26 10:36:55 +00:00
parent d17850e07c
commit de78beb1b5
8 changed files with 612 additions and 17 deletions

View File

@ -51,7 +51,8 @@ SOURCES += src/main.cpp\
src/CloneDialog.cpp \
src/LoggedProcess.cpp \
src/BrowserWidget.cpp \
src/CustomWebView.cpp
src/CustomWebView.cpp \
src/Bridge.cpp
HEADERS += src/MainWindow.h \
src/CommitDialog.h \
@ -62,7 +63,8 @@ HEADERS += src/MainWindow.h \
src/CloneDialog.h \
src/LoggedProcess.h \
src/BrowserWidget.h \
src/CustomWebView.h
src/CustomWebView.h \
src/Bridge.h
FORMS += ui/MainWindow.ui \
ui/CommitDialog.ui \

View File

@ -1,5 +1,5 @@
C Create\snew\sbranch\snamed\s"refactor"
D 2015-04-26T09:57:06.445
C Started\smoving\sfossil\soperations\sinto\sBridge\n
D 2015-04-26T10:36:55.373
F .travis.yml 77966888a81c4ceee1fcc79bce842c9667ad8a35
F debian/changelog eb4304dfcb6bb66850ec740838090eb50ce1249b
F debian/compat b6abd567fa79cbe0196d093a067271361dc6ca8b
@ -15,7 +15,7 @@ F dist/win/fuel.iss ef3558dbba409eb194938b930377fc9ee27d319e
F doc/Building.txt 17b43fa23da764b5d1b828cc48c5a95e612bbd8f
F doc/Changes.txt b03302545e4a6c0b16a30d623a7627f8aef65ef6
F doc/License.txt 4cc77b90af91e615a64ae04893fdffa7939db84c
F fuel.pro 844a18c3faf5239e0d0025d8b7feac3900c28e71
F fuel.pro fe3ce3763affa5e809b1154fffd33b437d03ca5b
F intl/convert.bat 4222ae403418381452b843929d15259ea9850ab1 x
F intl/convert.sh 2ca2179ff53e727f241925b75e19182607883c45 x
F intl/de_DE.ts e2faceab920ac60c97bbc6fba038e261d51fc741
@ -184,6 +184,8 @@ F rsrc/icons/fuel.icns 81e535004b62db801a02f3e15d0a33afc9d4070b
F rsrc/icons/fuel.ico eb529ab3332a17b9302ef3e851db5b9ebce2a038
F rsrc/icons/fuel.png 40daf53b7f6bdcdd0d6aa5ef433d078ec5ea4342
F rsrc/resources.qrc 4098be128fd6c045db933d041fe8844b14643a6f
F src/Bridge.cpp b34f14d8d887c9db6b27a84b6423931bf262e7a0
F src/Bridge.h a2167ed1bb8d1d80eb2ff0fda157eb4c8d176b16
F src/BrowserWidget.cpp 8b8f545cdff4a4188edc698a1b4777f5df46f056
F src/BrowserWidget.h 764d66aa9a93b890298bd0301097739cb4e16597
F src/CloneDialog.cpp 812ef7d361c16da21540b7047c9d4d5e74f18539
@ -198,13 +200,13 @@ F src/FileTableView.cpp 5ddf8c391c9a3ac449ec61fb1db837b577afeec2
F src/FileTableView.h 03e56d87c2d46411b9762b87f4d301619aaf18df
F src/LoggedProcess.cpp 2a1e5c94bc1e57c8984563e66c210e43a14dc60c
F src/LoggedProcess.h 85df7c635c807a5a0e8c4763f17a0752aaff7261
F src/MainWindow.cpp f023407f57730b695d4eeff8f27eef569c9eec66
F src/MainWindow.h dc0a9ed7de8a338e56c38c00ec303796f31bd24d
F src/MainWindow.cpp 7d07c97213b1a448d0a850c017cf226fb1c553da
F src/MainWindow.h a69b2e5ad3e6cd42343bccf145fa99dc9eb6c4f2
F src/SettingsDialog.cpp a46cff5e5dd425e3dbdd15632abfd5829f5562b4
F src/SettingsDialog.h 4e2790f581e991c744ae9f86580f1972b8c7ff43
F src/Utils.cpp 9aff456712e4276b49083426301b3b96d3819c77
F src/Utils.h c546e478a1225a28c99cd4c30f70cf9be9804a2a
F src/main.cpp 0bba433f16072096cba1d48733b4e801df144800
F src/main.cpp 2ac8badc2a63fa123ceae53382ce24cfe1b5a54b
F tools/git-push.sh 62cc58434cae5b7bcd6bd9d4cce8b08739f31cd7 x
F tools/pack.sh d7f38a498c4e9327fecd6a6e5ac27be270d43008 x
F ui/BrowserWidget.ui 5ad98b13773afadb20a1a2c22148aaebe5dbd95d
@ -213,10 +215,7 @@ F ui/CommitDialog.ui 6200f6cabdcf40a20812e811be28e0793f82516f
F ui/FileActionDialog.ui 89bb4dc2d0b8adcd41adcb11ec65f2028a09a12d
F ui/MainWindow.ui 8677f5c8bca5bf7561d5f64bfdd0cef5157c6ac7
F ui/SettingsDialog.ui 2b7c2870e0054b0f4106f495d85d02c0b814df8b
P 6b51d631f61ea685cf4ce84b3c34954dcd6338e2
R 7cb8f99d09af03a8c7fc0a6cb6e77784
T *branch * refactor
T *sym-refactor *
T -sym-trunk *
P 7f876a0b9e2c3c802938bd843e3e387e5e7e027a
R 5d10ed53db083d8513096d001d39f7ff
U kostas
Z 7368bbf2e485a2aa190b88109453d953
Z 36c407da296e8989c57827d80d13d2da

View File

@ -1 +1 @@
7f876a0b9e2c3c802938bd843e3e387e5e7e027a
7a4ea959c64034b6e20dec41a4a8c16a09397bfa

453
src/Bridge.cpp Normal file
View File

@ -0,0 +1,453 @@
#include "Bridge.h"
#include <QStringList>
#include <QCoreApplication>
#include <LoggedProcess.h>
#include <QTextCodec>
#include <QDebug>
#include <QMessageBox>
#include <QDir>
#include <QTemporaryFile>
static const unsigned char UTF8_BOM[] = { 0xEF, 0xBB, 0xBF };
#include "Utils.h"
Bridge::RepoStatus Bridge::getRepoStatus()
{
QStringList res;
int exit_code = EXIT_FAILURE;
// We need to determine the reason why fossil has failed
// so we delay processing of the exit_code
if(!runFossilRaw(QStringList() << "info", &res, &exit_code, RUNFLAGS_SILENT_ALL))
return REPO_NOT_FOUND;
bool run_ok = exit_code == EXIT_SUCCESS;
for(QStringList::iterator it=res.begin(); it!=res.end(); ++it)
{
int col_index = it->indexOf(':');
if(col_index==-1)
continue;
QString key = it->left(col_index).trimmed();
QString value = it->mid(col_index+1).trimmed();
if(key=="fossil")
{
if(value=="incorrect repository schema version")
return REPO_OLD_SCHEMA;
else if(value=="not within an open checkout")
return REPO_NOT_FOUND;
}
if(run_ok)
{
if(key=="project-name")
projectName = value;
else if(key=="repository")
repositoryFile = value;
}
}
return run_ok ? REPO_OK : REPO_NOT_FOUND;
}
//------------------------------------------------------------------------------
static QString ParseFossilQuery(QString line)
{
// Extract question
int qend = line.lastIndexOf('(');
if(qend == -1)
qend = line.lastIndexOf('[');
Q_ASSERT(qend!=-1);
line = line.left(qend);
line = line.trimmed();
line += "?";
line[0]=QString(line[0]).toUpper()[0];
return line;
}
//------------------------------------------------------------------------------
bool Bridge::runFossil(const QStringList &args, QStringList *output, int runFlags)
{
int exit_code = EXIT_FAILURE;
if(!runFossilRaw(args, output, &exit_code, runFlags))
return false;
return exit_code == EXIT_SUCCESS;
}
//------------------------------------------------------------------------------
// Run fossil. Returns true if execution was successful regardless if fossil
// issued an error
bool Bridge::runFossilRaw(const QStringList &args, QStringList *output, int *exitCode, int runFlags)
{
bool silent_input = (runFlags & RUNFLAGS_SILENT_INPUT) != 0;
bool silent_output = (runFlags & RUNFLAGS_SILENT_OUTPUT) != 0;
bool detached = (runFlags & RUNFLAGS_DETACHED) != 0;
if(!silent_input)
{
QString params;
foreach(QString p, args)
{
if(p.indexOf(' ')!=-1)
params += '"' + p + "\" ";
else
params += p + ' ';
}
log("<b>&gt; fossil "+params+"</b><br>", true);
}
QString wkdir = getCurrentWorkspace();
QString fossil = getFossilPath();
// Detached processes use the command-line only, to avoid having to wait
// for the temporary args file to be released before returing
if(detached)
return QProcess::startDetached(fossil, args, wkdir);
// Make StatusBar message
#if 0 // FIXME
QString status_msg = tr("Running Fossil");
if(args.length() > 0)
status_msg = QString("Fossil %0").arg(args[0].toCaseFolded());
ScopedStatus status(status_msg, ui, progressBar);
#endif
// Generate args file
const QStringList *final_args = &args;
QTemporaryFile args_file;
if(!args_file.open())
{
log(tr("Could not generate command line file"));
return false;
}
// Write BOM
args_file.write(reinterpret_cast<const char *>(UTF8_BOM), sizeof(UTF8_BOM));
// Write Args
foreach(const QString &arg, args)
{
args_file.write(arg.toUtf8());
args_file.write("\n");
}
args_file.close();
// Replace args with args filename
QStringList run_args;
run_args.append("--args");
run_args.append(args_file.fileName());
final_args = &run_args;
// Create fossil process
LoggedProcess process(parentWidget);
process.setWorkingDirectory(wkdir);
process.start(fossil, *final_args);
if(!process.waitForStarted())
{
log(tr("Could not start Fossil executable '%0'").arg(fossil)+"\n");
return false;
}
const QChar EOL_MARK('\n');
QString ans_yes = 'y' + EOL_MARK;
QString ans_no = 'n' + EOL_MARK;
QString ans_always = 'a' + EOL_MARK;
QString ans_convert = 'c' + EOL_MARK;
abortOperation = false;
QString buffer;
#ifdef Q_OS_WIN
QTextCodec *codec = QTextCodec::codecForName("UTF-8");
#else
QTextCodec *codec = QTextCodec::codecForLocale();
#endif
Q_ASSERT(codec);
QTextDecoder *decoder = codec->makeDecoder();
Q_ASSERT(decoder);
while(true)
{
QProcess::ProcessState state = process.state();
qint64 bytes_avail = process.logBytesAvailable();
if(state!=QProcess::Running && bytes_avail<1)
break;
if(abortOperation)
{
log("\n* "+tr("Terminated")+" *\n");
#ifdef Q_OS_WIN // Verify this is still true on Qt5
process.kill(); // QT on windows cannot terminate console processes with QProcess::terminate
#else
process.terminate();
#endif
break;
}
QByteArray input;
process.getLogAndClear(input);
#ifdef QT_DEBUG // Log fossil output in debug builds
if(!input.isEmpty())
qDebug() << input;
#endif
buffer += decoder->toUnicode(input);
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
if(buffer.isEmpty())
continue;
// Normalize line endings
buffer = buffer.replace("\r\n", "\n");
buffer = buffer.replace("\r", "\n");
// Extract the last line
int last_line_start = buffer.lastIndexOf(EOL_MARK);
QString last_line;
QString before_last_line;
if(last_line_start != -1)
{
last_line = buffer.mid(last_line_start+1); // Including the EOL
// Detect previous line
if(last_line_start>0)
{
int before_last_line_start = buffer.lastIndexOf(EOL_MARK, last_line_start-1);
// No line before ?
if(before_last_line_start==-1)
before_last_line_start = 0; // Use entire line
// Extract previous line
before_last_line = buffer.mid(before_last_line_start, last_line_start-before_last_line_start);
}
}
else
last_line = buffer;
last_line = last_line.trimmed();
// Check if we have a query
bool ends_qmark = !last_line.isEmpty() && last_line[last_line.length()-1]=='?';
bool have_yn_query = last_line.toLower().indexOf("y/n")!=-1;
bool have_yna_query = last_line.toLower().indexOf("a=always/y/n")!=-1 || last_line.toLower().indexOf("yes/no/all")!=-1 || last_line.toLower().indexOf("a=all/y/n")!=-1;
bool have_an_query = last_line.toLower().indexOf("a=always/n")!=-1;
bool have_acyn_query = last_line.toLower().indexOf("a=all/c=convert/y/n")!=-1;
bool have_query = ends_qmark && (have_yn_query || have_yna_query || have_an_query || have_acyn_query);
// Flush all complete lines to the log and output
QStringList log_lines = buffer.left(last_line_start).split(EOL_MARK);
for(int l=0; l<log_lines.length(); ++l)
{
// Do not output the last line if it not complete
if(l==log_lines.length()-1 && buffer[buffer.length()-1] != EOL_MARK )
continue;
QString line = log_lines[l].trimmed();
if(line.isEmpty())
continue;
if(output)
output->append(line);
if(!silent_output)
log(line+"\n");
}
// Remove everything we processed (including the EOL)
buffer = buffer.mid(last_line_start+1) ;
// Now process any query
if(have_query && (have_yna_query || have_acyn_query))
{
log(last_line);
QString query = ParseFossilQuery(last_line);
QMessageBox::StandardButtons buttons = QMessageBox::YesToAll|QMessageBox::Yes|QMessageBox::No;
// Add any extra text available to the query
before_last_line = before_last_line.trimmed();
if(!before_last_line.isEmpty())
query = before_last_line + "\n" + query;
// Map the Convert option to the Apply button
if(have_acyn_query)
buttons |= QMessageBox::Apply;
QMessageBox::StandardButton res = DialogQuery(parentWidget, "Fossil", query, buttons);
if(res==QMessageBox::Yes)
{
process.write(ans_yes.toLatin1());
log("Y\n");
}
else if(res==QMessageBox::YesAll)
{
process.write(ans_always.toLatin1());
log("A\n");
}
else if(res==QMessageBox::Apply)
{
process.write(ans_convert.toLatin1());
log("C\n");
}
else
{
process.write(ans_no.toLatin1());
log("N\n");
}
buffer.clear();
}
else if(have_query && have_yn_query)
{
log(last_line);
QString query = ParseFossilQuery(last_line);
QMessageBox::StandardButton res = DialogQuery(parentWidget, "Fossil", query);
if(res==QMessageBox::Yes)
{
process.write(ans_yes.toLatin1());
log("Y\n");
}
else
{
process.write(ans_no.toLatin1());
log("N\n");
}
buffer.clear();
}
else if(have_query && have_an_query)
{
log(last_line);
QString query = ParseFossilQuery(last_line);
QMessageBox::StandardButton res = DialogQuery(parentWidget, "Fossil", query, QMessageBox::YesToAll|QMessageBox::No);
if(res==QMessageBox::YesAll)
{
process.write(ans_always.toLatin1());
log("A\n");
}
else
{
process.write(ans_no.toLatin1());
log("N\n");
}
buffer.clear();
}
}
delete decoder;
// Must be finished by now
Q_ASSERT(process.state()==QProcess::NotRunning);
QProcess::ExitStatus es = process.exitStatus();
if(es!=QProcess::NormalExit)
return false;
if(exitCode)
*exitCode = process.exitCode();
return true;
}
QString Bridge::getFossilPath()
{
// Use the user-specified fossil if available
QString fossil_path = fossilPath;
if(!fossil_path.isEmpty())
return QDir::toNativeSeparators(fossil_path);
QString fossil_exe = "fossil";
#ifdef Q_OS_WIN
fossil_exe += ".exe";
#endif
// Use our fossil if available
QString fuel_fossil = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + QDir::separator() + fossil_exe);
if(QFile::exists(fuel_fossil))
return fuel_fossil;
// Otherwise assume there is a "fossil" executable in the path
return fossil_exe;
}
#define FOSSIL_CHECKOUT1 "_FOSSIL_"
#define FOSSIL_CHECKOUT2 ".fslckout"
#define FOSSIL_EXT "fossil"
#define PATH_SEP "/"
bool Bridge::isWorkspace(const QString &path)
{
if(path.length()==0)
return false;
QFileInfo fi(path);
QString wkspace = path;
wkspace = fi.absoluteDir().absolutePath();
QString checkout_file1 = wkspace + PATH_SEP + FOSSIL_CHECKOUT1;
QString checkout_file2 = wkspace + PATH_SEP + FOSSIL_CHECKOUT2;
return (QFileInfo(checkout_file1).exists() || QFileInfo(checkout_file2).exists());
}
//------------------------------------------------------------------------------
bool Bridge::uiRunning() const
{
return fossilUI.state() == QProcess::Running;
}
//------------------------------------------------------------------------------
bool Bridge::startUI(const QString &httpPort)
{
if(uiRunning())
{
log(tr("Fossil UI is already running")+"\n");
return true;
}
fossilUI.setParent(parentWidget);
fossilUI.setProcessChannelMode(QProcess::MergedChannels);
fossilUI.setWorkingDirectory(getCurrentWorkspace());
log("<b>&gt; fossil ui</b><br>", true);
log(tr("Starting Fossil browser UI. Please wait.")+"\n");
QString fossil = getFossilPath();
fossilUI.start(fossil, QStringList() << "server" << "--localauth" << "-P" << httpPort );
if(!fossilUI.waitForStarted() || fossilUI.state()!=QProcess::Running)
{
log(tr("Could not start Fossil executable '%s'").arg(fossil)+"\n");
return false;
}
return true;
}
//------------------------------------------------------------------------------
void Bridge::stopUI()
{
if(uiRunning())
{
#ifdef Q_WS_WIN
fossilUI.kill(); // QT on windows cannot terminate console processes with QProcess::terminate
#else
fossilUI.terminate();
#endif
}
fossilUI.close();
}

93
src/Bridge.h Normal file
View File

@ -0,0 +1,93 @@
#ifndef BRIDGE_H
#define BRIDGE_H
class QStringList;
#include <QString>
#include <QObject>
#include <QProcess>
class Bridge : public QObject
{
public:
Bridge()
: QObject(0)
, parentWidget(0)
, abortOperation(false)
, logCallbackObject(0)
{
}
bool runFossil(const QStringList &args, QStringList *output, int runFlags);
bool runFossilRaw(const QStringList &args, QStringList *output, int *exitCode, int runFlags);
enum RunFlags
{
RUNFLAGS_NONE = 0<<0,
RUNFLAGS_SILENT_INPUT = 1<<0,
RUNFLAGS_SILENT_OUTPUT = 1<<1,
RUNFLAGS_SILENT_ALL = RUNFLAGS_SILENT_INPUT | RUNFLAGS_SILENT_OUTPUT,
RUNFLAGS_DETACHED = 1<<2
};
typedef void(*log_callback_t)(const QString &text, bool isHTML, QObject *object);
void Init(QWidget *parent, log_callback_t callback, QObject *callbackObject, const QString &fossPath, const QString &workspace)
{
parentWidget = parent;
logCallback = callback;
logCallbackObject = callbackObject;
fossilPath = fossPath;
currentWorkspace = workspace;
}
static bool isWorkspace(const QString &path);
enum RepoStatus
{
REPO_OK,
REPO_NOT_FOUND,
REPO_OLD_SCHEMA
};
RepoStatus getRepoStatus();
bool uiRunning() const;
bool startUI(const QString &httpPort);
void stopUI();
QString projectName;
QString repositoryFile;
private:
void log(const QString &text, bool isHTML=false)
{
if(logCallback)
(*logCallback)(text, isHTML, logCallbackObject);
}
const QString &getCurrentWorkspace()
{
return currentWorkspace;
}
QString getFossilPath();
QWidget *parentWidget; // fixme
bool abortOperation; // FIXME: No GUI for it yet
log_callback_t logCallback;
QObject *logCallbackObject;
QString currentWorkspace;
QString fossilPath; // The value from the settings
QProcess fossilUI;
};
#endif // BRIDGE_H

View File

@ -34,6 +34,7 @@ static const unsigned char UTF8_BOM[] = { 0xEF, 0xBB, 0xBF };
// 19: [5c46757d4b9765] on 2012-04-22 04:41:15
static const QRegExp REGEX_STASH("\\s*(\\d+):\\s+\\[(.*)\\] on (\\d+)-(\\d+)-(\\d+) (\\d+):(\\d+):(\\d+)", Qt::CaseInsensitive);
//#define BRIDGE_DISABLED
//-----------------------------------------------------------------------------
enum
@ -235,6 +236,8 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP
applySettings();
bridge.Init(this, 0, 0, "", "");
// Apply any explicit workspace path if available
if(workspacePath && !workspacePath->isEmpty())
openWorkspace(*workspacePath);
@ -980,6 +983,7 @@ void MainWindow::updateFileView()
}
//------------------------------------------------------------------------------
#ifdef BRIDGE_DISABLED
MainWindow::RepoStatus MainWindow::getRepoStatus()
{
QStringList res;
@ -1020,6 +1024,12 @@ MainWindow::RepoStatus MainWindow::getRepoStatus()
return run_ok ? REPO_OK : REPO_NOT_FOUND;
}
#else
MainWindow::RepoStatus MainWindow::getRepoStatus()
{
return (MainWindow::RepoStatus) bridge.getRepoStatus();
}
#endif
//------------------------------------------------------------------------------
void MainWindow::updateStashView()
{
@ -1064,6 +1074,8 @@ void MainWindow::on_actionClearLog_triggered()
ui->textBrowser->clear();
}
#ifdef BRIDGE_DISABLED
//------------------------------------------------------------------------------
bool MainWindow::runFossil(const QStringList &args, QStringList *output, int runFlags)
{
@ -1391,6 +1403,21 @@ QString MainWindow::getFossilPath()
// Otherwise assume there is a "fossil" executable in the path
return fossil_exe;
}
#else
bool MainWindow::runFossil(const QStringList &args, QStringList *output, int runFlags)
{
return bridge.runFossil(args, output, runFlags);
}
//------------------------------------------------------------------------------
bool MainWindow::runFossilRaw(const QStringList &args, QStringList *output, int *exitCode, int runFlags)
{
return bridge.runFossilRaw(args, output, exitCode, runFlags);
}
#endif
//------------------------------------------------------------------------------
void MainWindow::applySettings()
{
@ -1695,6 +1722,7 @@ void MainWindow::on_actionDiff_triggered()
}
//------------------------------------------------------------------------------
#ifdef BRIDGE_DISABLED
bool MainWindow::startUI()
{
if(uiRunning())
@ -1741,6 +1769,23 @@ void MainWindow::stopUI()
ui->actionFossilUI->setChecked(false);
}
#else
bool MainWindow::startUI()
{
QString port = settings.GetValue(FUEL_SETTING_HTTP_PORT).toString();
bool started = bridge.startUI(port);
ui->actionFossilUI->setChecked(started);
return started;
}
//------------------------------------------------------------------------------
void MainWindow::stopUI()
{
bridge.stopUI();
ui->actionFossilUI->setChecked(false);
}
#endif
//------------------------------------------------------------------------------
void MainWindow::on_actionFossilUI_triggered()

View File

@ -10,6 +10,7 @@
#include <QProcess>
#include <QSet>
#include "SettingsDialog.h"
#include "Bridge.h"
namespace Ui {
class MainWindow;
@ -261,6 +262,8 @@ private:
ViewMode viewMode;
stringset_t selectedDirs; // The directory selected in the tree
Bridge bridge;
// Repository State
typedef QList<RepoFile*> filelist_t;
typedef QMap<QString, RepoFile*> filemap_t;

View File

@ -5,8 +5,8 @@ int main(int argc, char *argv[])
{
QApplication app(argc, argv);
app.setApplicationName("Fuel");
app.setApplicationVersion("1.0.0");
app.setOrganizationDomain("fuel-scm.org");
app.setApplicationVersion("2.0.0");
app.setOrganizationDomain("fuelscm.org");
app.setOrganizationName("Fuel-SCM");