# # # delete "src/model/ChangesetModel.cpp" # # delete "src/model/ChangesetModel.h" # # patch "guitone.pro" # from [f1810a37e0dc9125bfd25e8c94b29ec0f40a1e83] # to [66e04298e56958d35176ab1aa1adef0d235dd923] # # patch "res/forms/dialogs/changeset_browser.ui" # from [bcf5ff841a664176c09432b2881668896753cf1c] # to [e5ab8adb8f2af1a1b6147950b460e3d4c62398f2] # # patch "src/main.cpp" # from [349519dc838cbc2d958838597dc1374f62513b35] # to [70614a53d6fdc5f3bbc201ede2cc8bb3f0acfd2c] # # patch "src/model/AutomateCommand.cpp" # from [a446924d0adc71e158a902fbd893aad1980bd35d] # to [8fddda37cebb7d2a2c2c7c2c423d3a48f05310e2] # # patch "src/model/Certs.cpp" # from [34054cc893cdab2c96494a7b3dafafade909e4ca] # to [3f7a2d60fc0568e08f8206492a792fcddc623c81] # # patch "src/model/GetBranchLog.cpp" # from [fa62d0f8a15100ef6e0b009e1b95c3d6e25e7517] # to [ba28de4d484911c1e2294d4c2f6f097cac26e283] # # patch "src/model/GetBranchLog.h" # from [682e6a743815ebff68c6d24d75cbbdfd0af6a4e2] # to [d9dac872e5ac3f5e4f3c559ebf4bc4642d8f64b1] # # patch "src/monotone/MonotoneManager.cpp" # from [fe8c65ef5d365f3742c3a5acb220c4d759322dfc] # to [5fa6128ee84079d367d90e4f86d240bed8172de7] # # patch "src/monotone/MonotoneManager.h" # from [94f910a541e1601e05f22fec018bacc86a701972] # to [4d0a313dd2921a3b223d16683c48c81d177c627e] # # patch "src/monotone/MonotoneUtil.cpp" # from [dc08e6301807e7651b29565af6d5e903347f1f1a] # to [9558be31f39398c04dbdb53a24d2a62ee3494604] # # patch "src/monotone/MonotoneUtil.h" # from [b611e171fa57b0ed3f28ea39ea0f7c594c02bd59] # to [0ba7e01b0566dee10d08162b05d48cbd0fa78ccd] # # patch "src/monotone/WorkspaceCreator.cpp" # from [9b0ce5107ac378eb63f703b864a5150779bb11ff] # to [1602c4766b5e4c079f3062cac612af21ce14e42f] # # patch "src/view/dialogs/ChangesetBrowser.cpp" # from [1bb25d7f5197f6f159ebd816095e741cb0c4fbf3] # to [a6deafea64b283358322368092d5c40aeea0690b] # # patch "src/view/dialogs/ChangesetBrowser.h" # from [702cb80e34ccd1dd8d17521a4ade28b50743ca14] # to [40c66f9501bb5a9c36cea24f3bd06a18adda37b1] # # patch "src/view/panels/NodeInfo.cpp" # from [f4ef4b95b5077f82b1e0c07f23304a4edb244b73] # to [3f755b797d508093646290c093c6307dfdbd57b1] # # patch "src/vocab.h" # from [5694058c4d9c3ac022cac2459eac5b61ff6e2fca] # to [4c28eb2a0b3707b2b0423d19a2de14b23ada9646] # ============================================================ --- guitone.pro f1810a37e0dc9125bfd25e8c94b29ec0f40a1e83 +++ guitone.pro 66e04298e56958d35176ab1aa1adef0d235dd923 @@ -94,7 +94,6 @@ HEADERS = src/view/widgets/TreeView.h \ src/model/Tags.h \ src/model/Branches.h \ src/model/Keys.h \ - src/model/ChangesetModel.h \ src/model/Toposort.h \ src/model/Manifest.h \ src/model/Ancestors.h \ @@ -188,7 +187,6 @@ SOURCES += src/view/widgets/TreeView.cpp src/model/Tags.cpp \ src/model/Branches.cpp \ src/model/Keys.cpp \ - src/model/ChangesetModel.cpp \ src/model/Toposort.cpp \ src/model/Manifest.cpp \ src/model/Ancestors.cpp \ ============================================================ --- res/forms/dialogs/changeset_browser.ui bcf5ff841a664176c09432b2881668896753cf1c +++ res/forms/dialogs/changeset_browser.ui e5ab8adb8f2af1a1b6147950b460e3d4c62398f2 @@ -1,7 +1,8 @@ - + + ChangesetBrowser - - + + 0 0 @@ -9,102 +10,330 @@ 617 - + Changeset Browser - - + + :/icons/guitone.png:/icons/guitone.png - + - - + + Qt::Horizontal - - - + + + Qt::Vertical - - - true - - - false - - - false - + + + + 5 + + + 0 + + + + + true + + + false + + + false + + + + + + + + 0 + + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 25 + + + + + 10 + + + + QPushButton +{ + background: qlineargradient(x1:0,y1:0,x2:0,y2:1,stop: 0 palette(midlight), stop: 0.3 palette(light), stop: 1 palette(dark)); + border: 1px solid palette(dark); + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; + padding: 0px 10px; +} + +QPushButton:pressed, QPushButton:checked +{ + background: qlineargradient(x1:0,y1:0,x2:0,y2:1,stop: 0 palette(dark), stop: 0.7 palette(light), stop: 1 palette(midlight)); +} + + + 50 more + + + true + + + false + + + true + + + false + + + true + + + + + + + + 0 + 0 + + + + + 0 + 25 + + + + + 10 + + + + QPushButton +{ + background: qlineargradient(x1:0,y1:0,x2:0,y2:1,stop: 0 palette(midlight), stop: 0.3 palette(light), stop: 1 palette(dark)); + border: 1px solid palette(dark); + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + padding: 0px 10px; +} + +QPushButton:pressed, QPushButton:checked +{ + background: qlineargradient(x1:0,y1:0,x2:0,y2:1,stop: 0 palette(dark), stop: 0.7 palette(light), stop: 1 palette(midlight)); +} + + + fetch all + + + true + + + false + + + true + + + false + + + true + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 1 + 20 + + + + + + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 16777215 + 20 + + + + 0 + + + -1 + + + + + + + QToolButton { + border: 0; + background: transparent no-repeat center middle url(:/images/close.svg); +} + + + + + + + + + + + + + - - + + Qt::Vertical - - - true - - - - + + + + 5 + + + 0 + + + 5 + + + 0 + + + 0 + - - - + + + 0 - - + + + + 12 + + + Show changes against parent + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + - + - - + + true - + QAbstractItemView::SingleSelection - + false - + false + + + true + + - + - - + + display branches as tree - + Qt::Horizontal - + 40 20 @@ -113,38 +342,11 @@ - - - all Changesets - - - - - - - 50 More Changesets - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - + + close - + true @@ -162,12 +364,12 @@ Splitter QSplitter -
Splitter.h
+
Splitter.h
1
- + @@ -176,11 +378,11 @@ ChangesetBrowser accept() - - 656 - 619 + + 747 + 604 - + 359 323 ============================================================ --- src/main.cpp 349519dc838cbc2d958838597dc1374f62513b35 +++ src/main.cpp 70614a53d6fdc5f3bbc201ede2cc8bb3f0acfd2c @@ -59,6 +59,7 @@ int main(int argc, char** argv) try { qRegisterMetaType("TickerMap"); + qRegisterMetaType("CertList"); qRegisterMetaType("MonotoneTaskPtr"); qRegisterMetaType("QProcess::ProcessError"); ============================================================ --- src/model/AutomateCommand.cpp a446924d0adc71e158a902fbd893aad1980bd35d +++ src/model/AutomateCommand.cpp 8fddda37cebb7d2a2c2c7c2c423d3a48f05310e2 @@ -47,6 +47,7 @@ void AutomateCommand::enqueueTask(const QMutexLocker locker(&lock); int threadNumber = thread->getThreadNumber(); + // we want to avoid multiple thread connections if (!connectedThreads.contains(threadNumber)) { @@ -106,13 +107,12 @@ void AutomateCommand::abortThreads() // left items in the task queue... try { - MonotoneThreadPtr thread = APP->manager()->getThreadByNumber(threadNumber); - thread->abort(); - thread->wait(); + APP->manager()->stopThread(threadNumber); } catch (GuitoneException & e) { - W(QString("could not stop thread %1: %2").arg(threadNumber).arg(e.what())); + W(QString("could not stop thread %1: %2") + .arg(threadNumber).arg(e.what())); } } ============================================================ --- src/model/Certs.cpp 34054cc893cdab2c96494a7b3dafafade909e4ca +++ src/model/Certs.cpp 3f7a2d60fc0568e08f8206492a792fcddc623c81 @@ -31,6 +31,7 @@ void Certs::readCerts(const QString & re void Certs::readCerts(const QString & rev) { certs.clear(); + certs.revision = rev; reset(); if (rev.size() == 0) return; @@ -50,81 +51,8 @@ void Certs::processTaskResult(const Mono BasicIOParser parser(task->getDecodedOutput()); I(parser.parse()); - StanzaList list = parser.getStanzas(); + certs.fill(parser.getStanzas()); - for (int i=0, size = list.size(); i < size; ++i) - { - Stanza stanza = list.at(i); - - Cert cert; - - bool isItem = false; - - for (int j=0, size2 = stanza.size(); j < size2; j++) - { - StanzaEntry entry = stanza.at(j); - - // we're now only interested in stanzas starting with a "key" entry - if (j == 0 && entry.sym != "key") break; - - isItem = true; - - if (entry.sym == "key") - { - I(!entry.hash.isEmpty()); - cert.key = entry.hash; - continue; - } - - if (entry.sym == "name") - { - I(entry.vals.size() == 1); - cert.name = entry.vals.at(0); - continue; - } - - if (entry.sym == "value") - { - I(entry.vals.size() == 1); - cert.value = entry.vals.at(0).trimmed(); - continue; - } - - if (entry.sym == "signature") - { - I(entry.vals.size() == 1); - QString sig = entry.vals.at(0); - - if (sig == "ok") cert.signature = Cert::Ok; - else if (sig == "bad") cert.signature = Cert::Bad; - else if (sig == "unknown") cert.signature = Cert::Unknown; - else - W(QString("Unknown cert signature state '%1'.").arg(sig)); - - continue; - } - - if (entry.sym == "trust") - { - I(entry.vals.size() == 1); - QString trust = entry.vals.at(0); - - if (trust == "trusted") cert.trust = Cert::Trusted; - else if (trust == "untrusted") cert.trust = Cert::Untrusted; - else - W(QString("Unknown cert trust '%1'.").arg(trust)); - - continue; - } - - W(QString("Unknown symbol %1.").arg(entry.sym)); - } - - // check if we really processed an item entry - if (!isItem) continue; - certs.append(cert); - } - // reset any connected view(s) reset(); ============================================================ --- src/model/GetBranchLog.cpp fa62d0f8a15100ef6e0b009e1b95c3d6e25e7517 +++ src/model/GetBranchLog.cpp ba28de4d484911c1e2294d4c2f6f097cac26e283 @@ -17,85 +17,278 @@ ***************************************************************************/ #include "GetBranchLog.h" +#include "BasicIOParser.h" -GetBranchLog::GetBranchLog(QObject * parent, const DatabaseFile & db, const QString & b) - : QObject(parent), branch(b), databaseFile(db) +#include + +// FIXME: make this configurable eventually +const int GetBranchLog::PortionSize = 50; + +GetBranchLog::GetBranchLog(QObject * parent, const DatabaseFile & db) + : QAbstractItemModel(parent), AutomateCommand(0), databaseFile(db) +{} + +GetBranchLog::~GetBranchLog() +{} + +void GetBranchLog::readAll(const QString & branch) { - selectModel = new Select(this, databaseFile); - revSortModel = new Toposort(this, databaseFile); - ancestorModel = new Ancestors(this, databaseFile); - revSortModel->setSourceModel(ancestorModel); + readMore(branch, -1); +} - connect( - selectModel, SIGNAL(selectionRead()), - this, SLOT(selectionRead()) - ); +void GetBranchLog::readMore(const QString & branch, int count) +{ + revsRead = 0; + revsToRead = count; - connect( - ancestorModel, SIGNAL(ancestorsRead()), - this, SLOT(ancestorsRead()) - ); + if (!branch.isEmpty() && branch != currentBranch) + { + AutomateCommand::abortThreads(); + currentBranch = branch; - connect( - revSortModel, SIGNAL(sortingFinished()), - this, SLOT(sortingFinished()) - ); + if (!nextRevs.contains(currentBranch)) + { + MonotoneTaskPtr task(new MonotoneTask(QStringList() << "select" << "h:" + branch)); + AutomateCommand::enqueueDatabaseTask(databaseFile, task); + } + else + { + readNext(); + } - selectModel->readSelection("h:" + branch); + reset(); + return; + } + + I(!currentBranch.isEmpty()); + readNext(); } -GetBranchLog::~GetBranchLog() +void GetBranchLog::readNext() { - delete selectModel; - delete revSortModel; - delete ancestorModel; + bool canReadNext = + nextRevs[currentBranch].size() > 0 && + (revsToRead == -1 || revsRead < revsToRead); + if (!canReadNext) + { + emit readingStopped(); + return; + } + + QString next = nextRevs[currentBranch].pop(); + MonotoneTaskPtr task(new MonotoneTask(QStringList() << "parents" << next)); + AutomateCommand::enqueueDatabaseTask(databaseFile, task); } -void GetBranchLog::selectionRead() +void GetBranchLog::stopReading() { - int count = selectModel->rowCount(QModelIndex()); + revsToRead = 0; +} - for (int i = 0; i < count; i++) +void GetBranchLog::processTaskResult(const MonotoneTaskPtr & task) +{ + if (task->getReturnCode() != 0) { - heads.append( - selectModel->data( - selectModel->index(i, 0, QModelIndex()), - Qt::DisplayRole - ).toString() - ); + C(QString("Command returned with a non-zero return code (%1)") + .arg(task->getDecodedOutput())); + return; } - ancestorModel->readAncestors(heads); + QString current = task->getArguments().at(0); + QString output = task->getDecodedOutput(); + + if (current == "select" || current == "parents") + { + // ensure that we never start with a 'select h:' call and have + // already revisions for the current branch + I(current == "parents" || revisionMap[currentBranch].size() == 0); + + QStringList revs = output.split("\n", QString::SkipEmptyParts); + + int newRow = revisionMap[currentBranch].size(); + beginInsertRows(QModelIndex(), newRow, newRow + revs.size() - 1); + revisionMap[currentBranch] += revs; + endInsertRows(); + + foreach (QString rev, revs) + { + revsRead++; + nextRevs[currentBranch].push(rev); + if (!certMap.contains(rev)) + { + MonotoneTaskPtr task(new MonotoneTask(QStringList() << "certs" << rev)); + AutomateCommand::enqueueDatabaseTask(databaseFile, task); + } + } + + readNext(); + } + else + if (current == "certs") + { + QString queriedRev = task->getArguments().at(1); + + BasicIOParser parser(output); + I(parser.parse()); + + CertList certs; + certs.revision = queriedRev; + certs.fill(parser.getStanzas()); + certMap.insert(queriedRev, certs); + + int row = revisionMap[currentBranch].indexOf(queriedRev); + I(row >= 0); + emit dataChanged(index(row, 0, QModelIndex()), index(row, 3, QModelIndex())); + } } -void GetBranchLog::ancestorsRead() +int GetBranchLog::columnCount(const QModelIndex & parent) const { - // if we could not query any ancestors, the sorting won't succeed, - // but we still have this one single (or multiple) head revisions - // we want to show the user - if (ancestorModel->rowCount(QModelIndex()) == 0) + Q_UNUSED(parent); + return 4; +} + +QVariant GetBranchLog::data(const QModelIndex & index, int role) const +{ + if (!index.isValid()) { - emit branchLogRead(branch, heads); + return QVariant(); } + + if (role == Qt::FontRole && index.column() == 3) + { + QFont font; + font.setStyleHint(QFont::Courier); + font.setFamily("Courier"); + return QVariant(font); + } + + if (role == Qt::UserRole) + { + int row = index.row(); + + if (revisionMap.contains(currentBranch) && + row < revisionMap[currentBranch].count()) + { + QString rev = revisionMap[currentBranch].at(row); + + if (certMap.contains(rev)) + { + QList changeLogCerts = certMap[rev].find("changelog"); + QStringList changeLogs; + foreach (Cert cert, changeLogCerts) + { + changeLogs.push_back(cert.htmlValue()); + } + + return QVariant(changeLogs.join("\n---\n")); + } + } + } + + if (role == Qt::DisplayRole) + { + int row = index.row(); + + if (revisionMap.contains(currentBranch) && + row < revisionMap[currentBranch].count()) + { + int col = index.column(); + QString rev = revisionMap[currentBranch].at(row); + + if (!certMap.contains(rev)) + return QVariant(); + + if (col == 0) + { + QList dateCerts = certMap[rev].find("date"); + QStringList dates; + foreach (Cert cert, dateCerts) + { + dates.push_back( + cert.dateTimeValue().toString( + Qt::DefaultLocaleShortDate + ) + ); + } + return QVariant(dates.join(", ")); + } + else if (col == 1) + { + QStringList authors = certMap[rev].findValues("author"); + return QVariant(authors.join(", ")); + } + else if (col == 2) + { + QList changelogCerts = certMap[rev].find("changelog"); + QStringList changelogs; + foreach (Cert cert, changelogCerts) + { + QString plain = cert.plainValue(); + if (plain.size() > 50) + plain = plain.left(47).append("..."); + changelogs.push_back(plain); + } + return QVariant(changelogs.join(", ")); + } + else if (col == 3) + { + return QVariant(rev); + } + } + } + + return QVariant(); } -void GetBranchLog::sortingFinished() +Qt::ItemFlags GetBranchLog::flags(const QModelIndex & index) const { - int count = revSortModel->rowCount(QModelIndex()); - for (int i = 0; i < count; i++) + if (index.isValid()) { - revSortModel->sort(0, Qt::DescendingOrder); - revisions.append( - revSortModel->QSortFilterProxyModel::data( - revSortModel->index( - i, 0, - QModelIndex() - ), - Qt::DisplayRole - ).toString() - ); + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } + return 0; +} - emit branchLogRead(branch, heads + revisions); +QVariant GetBranchLog::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch(section) + { + case(0): + return QVariant(tr("Date")); + case(1): + return QVariant(tr("Author")); + case(2): + return QVariant(tr("Changelog")); + case(3): + return QVariant(tr("Revision ID")); + } + } + return QVariant(); } +int GetBranchLog::rowCount(const QModelIndex & parent) const +{ + Q_UNUSED(parent); + if (!revisionMap.contains(currentBranch)) + return 0; + return revisionMap[currentBranch].count(); +} + +QModelIndex GetBranchLog::index(int row, int column, const QModelIndex & parent) const +{ + if (!hasIndex(row, column, parent)) + { + return QModelIndex(); + } + return createIndex(row, column, 0); +} + +QModelIndex GetBranchLog::parent(const QModelIndex & index) const +{ + Q_UNUSED(index); + return QModelIndex(); +} + ============================================================ --- src/model/GetBranchLog.h 682e6a743815ebff68c6d24d75cbbdfd0af6a4e2 +++ src/model/GetBranchLog.h d9dac872e5ac3f5e4f3c559ebf4bc4642d8f64b1 @@ -23,29 +23,46 @@ #include "Select.h" #include "Toposort.h" -class GetBranchLog : public QObject +#include + +class GetBranchLog : public QAbstractItemModel, public AutomateCommand { Q_OBJECT public: - GetBranchLog(QObject *, const DatabaseFile &, const QString &); + GetBranchLog(QObject *, const DatabaseFile &); ~GetBranchLog(); + // needed Qt Model methods + QVariant data(const QModelIndex &, int) const; + Qt::ItemFlags flags(const QModelIndex &) const; + QVariant headerData(int, Qt::Orientation, int) const; + QModelIndex index(int, int, const QModelIndex &) const; + QModelIndex parent(const QModelIndex &) const; + int rowCount(const QModelIndex &) const; + int columnCount(const QModelIndex &) const; + + static const int PortionSize; + +public slots: + void readMore(const QString & branch = QString(), int revCount = PortionSize); + void readAll(const QString & branch = QString()); + void stopReading(); + signals: - void branchLogRead(const QString &, const QStringList &); + void readingStopped(); private: - QString branch; DatabaseFile databaseFile; - QStringList heads; - QStringList revisions; - Select * selectModel; - Toposort * revSortModel; - Ancestors * ancestorModel; + QMap revisionMap; + QMap certMap; + QMap > nextRevs; + QString currentBranch; -private slots: - void selectionRead(); - void ancestorsRead(); - void sortingFinished(); + int revsRead; + int revsToRead; + + void readNext(); + void processTaskResult(const MonotoneTaskPtr &); }; #endif ============================================================ --- src/monotone/MonotoneManager.cpp fe8c65ef5d365f3742c3a5acb220c4d759322dfc +++ src/monotone/MonotoneManager.cpp 5fa6128ee84079d367d90e4f86d240bed8172de7 @@ -104,15 +104,6 @@ MonotoneThreadPtr MonotoneManager::getTh return getThread(database, QString()); } -MonotoneThreadPtr MonotoneManager::getThreadByNumber(int threadNumber) -{ - if (!threadMap.contains(threadNumber)) - { - throw GuitoneException(tr("No such thread: %1").arg(threadNumber)); - } - return threadMap.value(threadNumber); -} - MonotoneThreadPtr MonotoneManager::getThread(const DatabaseFile & database, const WorkspacePath & workspace) { QMutexLocker locker(&lock); @@ -125,20 +116,28 @@ MonotoneThreadPtr MonotoneManager::getTh // its own process QList threadIDs = identMap.keys(database + "|" + workspace); - if (threadIDs.size() == 0) + QList availableThreads; + foreach (int threadID, threadIDs) { - threadNumber++; + if (stoppingThreads.contains(threadID)) + continue; + availableThreads.append(threadID); + } + if (availableThreads.size() == 0) + { + int newThreadID = ++threadNumber; + MonotoneThreadPtr thread - (new MonotoneThread(threadNumber, mtnPath, database, workspace)); + (new MonotoneThread(newThreadID, mtnPath, database, workspace)); - threadMap.insert(threadNumber, thread); - identMap.insert(threadNumber, database + "|"); + threadMap.insert(newThreadID, thread); + identMap.insert(newThreadID, database + "|"); if (!workspace.isEmpty()) { - identMap.insert(threadNumber, database + "|" + workspace); + identMap.insert(newThreadID, database + "|" + workspace); } - threadIDs.append(threadNumber); + availableThreads.append(newThreadID); connect( thread.data(), SIGNAL(stopped(int)), @@ -157,9 +156,25 @@ MonotoneThreadPtr MonotoneManager::getTh // and the same database here in the future... then we should probably // also make a smarter choice of available threads here instead of just // taking the first! - return threadMap.value(threadIDs.value(0)); + return threadMap.value(availableThreads.value(0)); } +void MonotoneManager::stopThread(int threadID) +{ + QMutexLocker locker(&lock); + + if (!threadMap.contains(threadID)) + { + throw GuitoneException(tr("No such thread: %1").arg(threadID)); + } + + L(QString("stopping thread %1").arg(threadID)); + MonotoneThreadPtr thread = threadMap.value(threadID); + thread->abort(); + + stoppingThreads.insert(threadID); +} + void MonotoneManager::stopWorkspaceThreads(const WorkspacePath & workspace) { WorkspacePath normalizedWorkspace = normalizeWorkspacePath(workspace); @@ -171,10 +186,7 @@ void MonotoneManager::stopWorkspaceThrea foreach (int threadID, threadIDs) { - MonotoneThreadPtr thread = getThreadByNumber(threadID); - thread->abort(); - // this calls QThread::wait() internally - removeThread(threadID); + stopThread(threadID); } } @@ -261,17 +273,17 @@ DatabaseFile MonotoneManager::getDatabas return databaseFilePath; } -void MonotoneManager::removeThread(int threadNumber) +void MonotoneManager::removeThread(int threadID) { QMutexLocker locker(&lock); - if (!threadMap.contains(threadNumber)) + if (!threadMap.contains(threadID)) { - L(QString("thread %1 was already removed").arg(threadNumber)); + L(QString("thread %1 was already removed").arg(threadID)); return; } - MonotoneThreadPtr thread = threadMap.value(threadNumber); + MonotoneThreadPtr thread = threadMap.value(threadID); disconnect( thread.data(), SIGNAL(stopped(int)), @@ -283,29 +295,30 @@ void MonotoneManager::removeThread(int t this, SLOT(aborted(int, QProcess::ProcessError, const QString &)) ); - threadMap.remove(threadNumber); - identMap.remove(threadNumber); + threadMap.remove(threadID); + identMap.remove(threadID); + stoppingThreads.remove(threadID); // wait until the thread has finished execution thread->wait(); } -void MonotoneManager::stopped(int threadNumber) +void MonotoneManager::stopped(int threadID) { - removeThread(threadNumber); - L(QString("thread %1 stopped").arg(threadNumber)); + removeThread(threadID); + L(QString("thread %1 stopped").arg(threadID)); } -void MonotoneManager::aborted(int threadNumber, QProcess::ProcessError error, const QString & message) +void MonotoneManager::aborted(int threadID, QProcess::ProcessError error, const QString & message) { - I(threadMap.contains(threadNumber)); + I(threadMap.contains(threadID)); - MonotoneThreadPtr thread = threadMap.value(threadNumber); + MonotoneThreadPtr thread = threadMap.value(threadID); QString databaseFile = thread->getDatabaseFilePath(); QString workspacePath = thread->getWorkspacePath(); - removeThread(threadNumber); + removeThread(threadID); QString mtnError = MonotoneUtil::stripMtnPrefix(message); QString processErrorTranslated; @@ -335,7 +348,7 @@ void MonotoneManager::aborted(int thread // log something reasonable C(QString("thread %1 died (%2) - stderr was %3") - .arg(threadNumber).arg(processErrorTranslated).arg(mtnError)); + .arg(threadID).arg(processErrorTranslated).arg(mtnError)); // display something understandable to the user QString userMessage(tr( ============================================================ --- src/monotone/MonotoneManager.h 94f910a541e1601e05f22fec018bacc86a701972 +++ src/monotone/MonotoneManager.h 4d0a313dd2921a3b223d16683c48c81d177c627e @@ -22,6 +22,7 @@ #include "MonotoneThread.h" #include +#include class MonotoneManager : public QObject { @@ -45,11 +46,11 @@ public: //! returns an appropriate MonotoneThread for the given database MonotoneThreadPtr getThreadForDatabase(const DatabaseFile &); - //! stops and removes all threads for the given workspace - void stopWorkspaceThreads(const WorkspacePath &); + //! stops the thread with the given thread number + void stopThread(int); - //! returns a thread by a given thread number - MonotoneThreadPtr getThreadByNumber(int); + //! stops all threads for the given workspace + void stopWorkspaceThreads(const WorkspacePath &); //! returns the interface version of a given monotone binary static QString getInterfaceVersion(const QString &); @@ -76,6 +77,7 @@ private: QMap threadMap; QMultiMap identMap; QMap workspaceMap; + QSet stoppingThreads; QString mtnPath; int threadNumber; ============================================================ --- src/monotone/MonotoneUtil.cpp dc08e6301807e7651b29565af6d5e903347f1f1a +++ src/monotone/MonotoneUtil.cpp 9558be31f39398c04dbdb53a24d2a62ee3494604 @@ -205,48 +205,29 @@ QStringList MonotoneUtil::resolveSelecto return revList; } -RevisionCerts MonotoneUtil::getRevisionCerts(const DatabaseFile & db, const QString & revision) +CertList MonotoneUtil::getCerts(const DatabaseFile & db, const QString & revision) { MonotoneTaskPtr task(new MonotoneTask(QStringList() << "certs" << revision)); runSynchronousDatabaseTask(db, task); if (!task->isFinished()) { C(QString("task '%1' aborted").arg(QString(task->getEncodedInput()))); - return RevisionCerts(); + return CertList(); } if (task->getReturnCode() > 0) { C(QString("Couldn't query revision certs for %1: %2") .arg(revision).arg(task->getLast(MonotoneTask::Error))); - return RevisionCerts(); + return CertList(); } BasicIOParser parser(task->getDecodedOutput()); I(parser.parse()); - RevisionCerts certs; - StanzaList stanzas = parser.getStanzas(); - - foreach (const Stanza & st, stanzas) - { - RevisionCert cert; - - foreach (const StanzaEntry & en, st) - { - if (en.sym == "name") - { - cert.first = en.vals.at(0); - } - - if (en.sym == "value") - { - cert.second = en.vals.at(0); - } - } - I(!cert.first.isEmpty() && !cert.second.isEmpty()); - certs.append(cert); - } + CertList certs; + certs.revision = revision; + certs.fill(parser.getStanzas()); return certs; } ============================================================ --- src/monotone/MonotoneUtil.h b611e171fa57b0ed3f28ea39ea0f7c594c02bd59 +++ src/monotone/MonotoneUtil.h 0ba7e01b0566dee10d08162b05d48cbd0fa78ccd @@ -36,7 +36,7 @@ public: static QString getBranchName(const WorkspacePath &, const QString & defaultBranch = tr("[unknown branch]")); static QString getBranchNameShort(const WorkspacePath &); static QStringList resolveSelector(const DatabaseFile &, const QString &); - static RevisionCerts getRevisionCerts(const DatabaseFile &, const QString &); + static CertList getCerts(const DatabaseFile &, const QString &); static FileEntryList getRevisionManifest(const DatabaseFile &, const QString &); static QMap getPrivateKeyList(const DatabaseFile &); static QStringList getPreviousContentMarks(const DatabaseFile &, const QString &, const QString &); ============================================================ --- src/monotone/WorkspaceCreator.cpp 9b0ce5107ac378eb63f703b864a5150779bb11ff +++ src/monotone/WorkspaceCreator.cpp 1602c4766b5e4c079f3062cac612af21ce14e42f @@ -30,15 +30,9 @@ bool WorkspaceCreator::run() { // at first determine the branch name(s) of the revision and let the // user select one branch of a list of branches - RevisionCerts certs = MonotoneUtil::getRevisionCerts(databaseFile, revision); + CertList certs = MonotoneUtil::getCerts(databaseFile, revision); + QStringList branches = certs.findValues("branch"); - QStringList branches; - for (int i = 0, j = certs.size(); i < j; i++) - { - if (certs.at(i).first == "branch") - branches.append(certs.at(i).second); - } - if (branches.size() == 0) { // TODO: shall we ask the user here for a new branch name? ============================================================ --- src/view/dialogs/ChangesetBrowser.cpp 1bb25d7f5197f6f159ebd816095e741cb0c4fbf3 +++ src/view/dialogs/ChangesetBrowser.cpp a6deafea64b283358322368092d5c40aeea0690b @@ -29,6 +29,8 @@ ChangesetBrowser::ChangesetBrowser(QWidg this->setWindowFlags(this->windowFlags() | Qt::WindowMinimizeButtonHint); multipleParents->setVisible(false); + progressWidget->setVisible(false); + fetchWidget->setVisible(false); branchesSplitter->restoreState(); changesetsSplitter->restoreState(); @@ -51,10 +53,20 @@ ChangesetBrowser::ChangesetBrowser(QWidg this, SLOT(toggleTree()) ); - changesetModel = new ChangesetModel(this, databaseFile); - changesets->setModel(changesetModel); + branchLogModel = new GetBranchLog(this, databaseFile); + changesets->setModel(branchLogModel); changesets->setRootIsDecorated(false); + connect( + branchLogModel, SIGNAL(readingStopped()), + this, SLOT(readingStopped()) + ); + + connect( + this, SIGNAL(dialogHidden()), + branchLogModel, SLOT(stopReading()) + ); + revisionModel = new GetRevision(this); revisionView->setModel(revisionModel); @@ -75,15 +87,20 @@ ChangesetBrowser::ChangesetBrowser(QWidg connect( pushAll, SIGNAL(clicked()), - this, SLOT(receiveAll()) + this, SLOT(readAll()) ); connect( pushMore, SIGNAL(clicked()), - this, SLOT(receiveMore()) + this, SLOT(readMore()) ); connect( + pushStop, SIGNAL(clicked()), + branchLogModel, SLOT(stopReading()) + ); + + connect( changesets, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(changesetsDoubleClicked(const QModelIndex &)) ); @@ -99,28 +116,45 @@ ChangesetBrowser::ChangesetBrowser(QWidg ); } -void ChangesetBrowser::receiveAll() -{ - changesetModel->receiveRevisions(true); -} - -void ChangesetBrowser::receiveMore() -{ - changesetModel->receiveRevisions(false); -} - ChangesetBrowser::~ChangesetBrowser() { branchesSplitter->saveState(); changesetsSplitter->saveState(); changeLogSplitter->saveState(); - delete changesetModel; + delete branchLogModel; delete revisionModel; if (branchModel) delete branchModel; Settings::setBool("ChangesetBrowserTree", tree); } +void ChangesetBrowser::readAll() +{ + branchLogModel->readAll(); + progressWidget->setVisible(true); +} + +void ChangesetBrowser::readMore() +{ + branchLogModel->readMore(); + progressWidget->setVisible(true); +} + +void ChangesetBrowser::readingStopped() +{ + progressWidget->setVisible(false); + + // yes, this looks awkward, but there is no other sane + // way to "reset" the state of a set of exclusive buttons + pushMore->setAutoExclusive(false); + pushMore->setChecked(false); + pushMore->setAutoExclusive(true); + + pushAll->setAutoExclusive(false); + pushAll->setChecked(false); + pushAll->setAutoExclusive(true); +} + void ChangesetBrowser::toggleTree() { tree = !tree; @@ -133,14 +167,19 @@ void ChangesetBrowser::branchesClicked(c void ChangesetBrowser::branchesClicked(const QModelIndex & idx) { QString branch = branchModel->data(idx, Qt::ToolTipRole).toString(); - changesetModel->setBranch(branch); + + branchLogModel->readMore(branch); + + fetchWidget->setVisible(true); + progressWidget->setVisible(true); + pushMore->setDown(false); } void ChangesetBrowser::changesetsClicked(const QModelIndex & idx) { - QModelIndex revIdx = changesetModel->index(idx.row(), 3, QModelIndex()); - currentRevision = changesetModel->data(revIdx, Qt::DisplayRole).toString(); - changeLog->setHtml(changesetModel->data(revIdx, Qt::UserRole).toString()); + QModelIndex revIdx = branchLogModel->index(idx.row(), 3, QModelIndex()); + currentRevision = branchLogModel->data(revIdx, Qt::DisplayRole).toString(); + changeLog->setHtml(branchLogModel->data(revIdx, Qt::UserRole).toString()); revisionModel->readDatabaseRevision(databaseFile, currentRevision); revisionParents->clear(); multipleParents->setVisible(false); @@ -176,7 +215,7 @@ void ChangesetBrowser::changesetsDoubleC void ChangesetBrowser::changesetsDoubleClicked(const QModelIndex & index) { if (!index.isValid()) return; - QModelIndex revIdx = changesetModel->index(index.row(), 3, QModelIndex()); + QModelIndex revIdx = branchLogModel->index(index.row(), 3, QModelIndex()); emit revisionManifest(revIdx.data().toString()); } @@ -185,7 +224,7 @@ void ChangesetBrowser::contextMenuReques if (indexList.size() == 0) return; QModelIndex revIdx = - changesetModel->index(indexList.at(0).row(), 3, QModelIndex()); + branchLogModel->index(indexList.at(0).row(), 3, QModelIndex()); QString rev(revIdx.data().toString()); @@ -249,3 +288,9 @@ void ChangesetBrowser::changeViewDoubleC currentRevision); } +void ChangesetBrowser::closeEvent(QCloseEvent * event) +{ + branchLogModel->stopReading(); + event->accept(); +} + ============================================================ --- src/view/dialogs/ChangesetBrowser.h 702cb80e34ccd1dd8d17521a4ade28b50743ca14 +++ src/view/dialogs/ChangesetBrowser.h 40c66f9501bb5a9c36cea24f3bd06a18adda37b1 @@ -22,9 +22,11 @@ #include "ui_changeset_browser.h" #include "Branches.h" #include "Dialog.h" -#include "ChangesetModel.h" +#include "GetBranchLog.h" #include "GetRevision.h" +#include + class ChangesetBrowser : public Dialog, private Ui::ChangesetBrowser { Q_OBJECT @@ -36,13 +38,17 @@ signals: void revisionManifest(const QString &); void diffFile(const QString &, const QString &, const QString &); +protected: + void closeEvent(QCloseEvent *); + private slots: void branchesClicked(const QModelIndex &); void changesetsClicked(const QModelIndex &); void branchesRead(); void toggleTree(); - void receiveAll(); - void receiveMore(); + void readAll(); + void readMore(); + void readingStopped(); void changesetsDoubleClicked(const QModelIndex &); void contextMenuRequested(const QModelIndexList &, const QPoint &); void changeViewDoubleClicked(const QModelIndex &); @@ -54,7 +60,7 @@ private: bool tree; DatabaseFile databaseFile; Branches * branchModel; - ChangesetModel * changesetModel; + GetBranchLog * branchLogModel; GetRevision * revisionModel; QString currentRevision; }; ============================================================ --- src/view/panels/NodeInfo.cpp f4ef4b95b5077f82b1e0c07f23304a4edb244b73 +++ src/view/panels/NodeInfo.cpp 3f755b797d508093646290c093c6307dfdbd57b1 @@ -100,19 +100,20 @@ void NodeInfo::readAndSetInfo() .arg(revs.at(0)) .arg(revs.at(0).left(12)); - RevisionCerts certs = MonotoneUtil::getRevisionCerts(db, revs.at(0)); + CertList certs = MonotoneUtil::getCerts(db, revs.at(0)); - // FIXME: we do not handle merge revisions here - foreach (const RevisionCert & cert, certs) + strLastChangeAuthor = certs.findValues("author").join(", "); + strLastChangelog = certs.findValues("changelog").join("\n---\n"); + QList dateCerts = certs.find("date"); + QStringList dates; + + foreach (const Cert & cert, dateCerts) { - if (cert.first == "author") strLastChangeAuthor = cert.second; - if (cert.first == "changelog") strLastChangelog = cert.second; - if (cert.first == "date") - { - QDateTime dt = QDateTime::fromString(cert.second, "yyyy-MM-ddThh:mm:ss"); - strLastChangeDate = dt.toString(Qt::LocaleDate); - } + dates.push_back( + cert.dateTimeValue().toString(Qt::DefaultLocaleShortDate) + ); } + strLastChangeDate = dates.join(", "); } lastChangeRevision->setText(strLastChangeRevision); ============================================================ --- src/vocab.h 5694058c4d9c3ac022cac2459eac5b61ff6e2fca +++ src/vocab.h 4c28eb2a0b3707b2b0423d19a2de14b23ada9646 @@ -44,16 +44,37 @@ class GuitoneCore; #include #include +// used for BasicIOParser and BasicIOWriter +struct StanzaEntry +{ + QString sym; + QString hash; + QStringList vals; + + StanzaEntry() {} + + StanzaEntry(QString s, QString h) : sym(s), hash(h) {} + + StanzaEntry(QString s, QStringList v) : sym(s), vals(v) {} +}; + +typedef QList Stanza; +typedef QList StanzaList; + // used for manifest entries, if the bool var is true, the entry is a directory -struct FileEntry { - FileEntry() {} - FileEntry(QString p, bool d) : path(p), is_dir(d) {} - FileEntry(QString p, bool d, QString f) : path(p), is_dir(d), fileid(f) {} +struct FileEntry +{ QString path; bool is_dir; QString fileid; QMap attrs; + FileEntry() {} + + FileEntry(QString p, bool d) : path(p), is_dir(d) {} + + FileEntry(QString p, bool d, QString f) : path(p), is_dir(d), fileid(f) {} + inline bool operator<(const FileEntry & other) const { return path < other.path; @@ -64,38 +85,180 @@ typedef QStringList RevisionList; typedef QStringList RevisionList; -// used for revision certs -typedef QString CertKey; -typedef QString CertValue; -typedef QPair RevisionCert; -typedef QList RevisionCerts; - -// used to store the output of automate certs -typedef struct { +#include +struct Cert +{ enum Trust { Trusted, Untrusted } trust; enum Signature { Ok, Bad, Unknown } signature; QString key; QString name; QString value; -} Cert; -typedef QList CertList; -typedef QList ByteArrayList; + QDateTime dateTimeValue() const + { + return QDateTime::fromString(value, "yyyy-MM-ddThh:mm:ss"); + } -// used for BasicIOParser and BasicIOWriter -struct StanzaEntry { - StanzaEntry() {} - StanzaEntry(QString s, QString h) : sym(s), hash(h) {} - StanzaEntry(QString s, QStringList v) : sym(s), vals(v) {} + QString plainValue() const + { + return QString(value).replace(QRegExp("\\s+"), " "); + } - QString sym; - QString hash; - QStringList vals; + QString htmlValue() const + { + QStringList lines = value.split(QRegExp("(\\r\\n|\\n|\\n\\r)")); + QString htmlValue; + bool inList = false; + foreach (const QString & line, lines) + { + if (line.isEmpty()) + { + if (inList) + { + htmlValue += "\n\n"; + inList = false; + continue; + } + + htmlValue += "
\n"; + continue; + } + bool listStart = (0 == line.indexOf(QRegExp("^[*-]"))); + if (listStart) + { + if (inList) + { + htmlValue += "\n"; + } + else + { + inList = true; + htmlValue += "
    \n"; + } + + htmlValue += "
  • " + line.mid(1).trimmed(); + continue; + } + if (inList) + { + htmlValue += " " + line.trimmed(); + continue; + } + htmlValue += line.trimmed() + "
    \n"; + } + if (inList) + { + htmlValue += "
  • \n
\n"; + } + return htmlValue + .replace(QRegExp("(\\b)([_])(\\w+)\\2"), "\\1\\2\\3\\2") + .replace(QRegExp("(\\b)([*])(\\w+)\\2"), "\\1\\2\\3\\2") + .replace(QRegExp("(https?://[^() ]+)"), "\\1") + .replace(QRegExp("\\t"), "    "); + } }; -typedef QList Stanza; -typedef QList StanzaList; +struct CertList +{ + QString revision; + QList certs; + void add(const Cert & c) { certs.push_back(c); } + + int size() const { return certs.size(); } + + Cert at(int pos) const { return certs.at(pos); } + + void clear() + { + certs.clear(); + revision = QString(); + } + + QList find(const QString & name) const + { + QList foundCerts; + foreach (const Cert & cert, certs) + { + if (cert.name != name) + continue; + foundCerts.push_back(cert); + } + return foundCerts; + } + + QStringList findValues(const QString & name) const + { + QStringList values; + QList certs = find(name); + foreach (const Cert & cert, certs) + { + values.push_back(cert.value); + } + return values; + } + + void fill(const StanzaList & stanzas) + { + foreach (const Stanza & st, stanzas) + { + Cert cert; + foreach (const StanzaEntry & entry, st) + { + if (entry.sym == "key") + { + I(!entry.hash.isEmpty()); + cert.key = entry.hash; + continue; + } + if (entry.sym == "name") + { + I(entry.vals.size() == 1); + cert.name = entry.vals.at(0); + continue; + } + if (entry.sym == "value") + { + I(entry.vals.size() == 1); + cert.value = entry.vals.at(0).trimmed(); + continue; + } + if (entry.sym == "signature") + { + I(entry.vals.size() == 1); + QString sig = entry.vals.at(0); + if (sig == "ok") + cert.signature = Cert::Ok; + else if (sig == "bad") + cert.signature = Cert::Bad; + else if (sig == "unknown") + cert.signature = Cert::Unknown; + else + W(QString("Unknown cert signature state '%1'.").arg(sig)); + continue; + } + if (entry.sym == "trust") + { + I(entry.vals.size() == 1); + QString trust = entry.vals.at(0); + if (trust == "trusted") + cert.trust = Cert::Trusted; + else if (trust == "untrusted") + cert.trust = Cert::Untrusted; + else + W(QString("Unknown cert trust '%1'.").arg(trust)); + continue; + } + } + if (cert.key.isEmpty()) + continue; + add(cert); + } + } +}; + +typedef QList ByteArrayList; + struct Ticker { QByteArray name;