dbhub: Show list of commits in Remote dock

When opening a clone of a remote database show a list of all branches,
releases, and tags in the Remote dock and show the commit list when
selecting one of them.
This commit is contained in:
Martin Kleusberg
2020-07-12 14:40:48 +02:00
parent ebd8977210
commit 5d89bb8991
9 changed files with 404 additions and 48 deletions

View File

@@ -172,6 +172,7 @@ set(SQLB_MOC_HDR
src/TableBrowser.h src/TableBrowser.h
src/ImageViewer.h src/ImageViewer.h
src/RemoteLocalFilesModel.h src/RemoteLocalFilesModel.h
src/RemoteCommitsModel.h
) )
set(SQLB_SRC set(SQLB_SRC
@@ -232,6 +233,7 @@ set(SQLB_SRC
src/sql/parser/sqlite3_parser.cpp src/sql/parser/sqlite3_parser.cpp
src/ImageViewer.cpp src/ImageViewer.cpp
src/RemoteLocalFilesModel.cpp src/RemoteLocalFilesModel.cpp
src/RemoteCommitsModel.cpp
) )
set(SQLB_FORMS set(SQLB_FORMS

140
src/RemoteCommitsModel.cpp Normal file
View File

@@ -0,0 +1,140 @@
#include <QTreeWidgetItem>
#include "RemoteCommitsModel.h"
#include "Settings.h"
using json = nlohmann::json;
RemoteCommitsModel::RemoteCommitsModel(QObject* parent) :
QAbstractItemModel(parent)
{
QStringList header;
header << tr("Commit ID") << tr("Message") << tr("Date") << tr("Author");
rootItem = new QTreeWidgetItem(header);
}
RemoteCommitsModel::~RemoteCommitsModel()
{
delete rootItem;
}
void RemoteCommitsModel::clear()
{
beginResetModel();
// Remove all data except for the root item
while(rootItem->childCount())
delete rootItem->child(0);
endResetModel();
}
void RemoteCommitsModel::refresh(const std::string& json_data, const std::string& last_commit_id)
{
// Clear previous items
clear();
beginResetModel();
// Read in all commits
json j = json::parse(json_data, nullptr, false);
std::unordered_map<std::string, json::iterator> commit_to_iterator;
for(auto it=j.begin();it!=j.end();++it)
commit_to_iterator.insert({it.value()["id"], it});
// Starting from the last commit add follow the chain up to the first commit
std::string parent_id = last_commit_id;
while(true)
{
if(commit_to_iterator.find(parent_id) == commit_to_iterator.end())
break;
json commit = commit_to_iterator[parent_id].value();
QTreeWidgetItem* item = new QTreeWidgetItem(rootItem);
item->setText(ColumnCommitId, QString::fromStdString(commit["id"]));
item->setText(ColumnMessage, QString::fromStdString(commit["message"]));
item->setText(ColumnDate, QString::fromStdString(commit["timestamp"]));
item->setText(ColumnAuthor, QString::fromStdString(commit["author_name"]) + " <" + QString::fromStdString(commit["author_email"]) + ">");
parent_id = commit["parent"];
}
// Refresh the view
endResetModel();
}
QModelIndex RemoteCommitsModel::index(int row, int column, const QModelIndex& parent) const
{
if(!hasIndex(row, column, parent))
return QModelIndex();
QTreeWidgetItem *parentItem;
if(!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<QTreeWidgetItem*>(parent.internalPointer());
QTreeWidgetItem* childItem = parentItem->child(row);
if(childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex RemoteCommitsModel::parent(const QModelIndex& index) const
{
if(!index.isValid())
return QModelIndex();
QTreeWidgetItem* childItem = static_cast<QTreeWidgetItem*>(index.internalPointer());
QTreeWidgetItem* parentItem = childItem->parent();
if(parentItem == rootItem)
return QModelIndex();
else
return createIndex(0, 0, parentItem);
}
QVariant RemoteCommitsModel::data(const QModelIndex& index, int role) const
{
if(!index.isValid())
return QVariant();
// Get the item the index points at
QTreeWidgetItem* item = static_cast<QTreeWidgetItem*>(index.internalPointer());
// Return data depending on the role
switch(role)
{
case Qt::DisplayRole:
case Qt::EditRole:
return item->text(index.column());
default:
return QVariant();
}
}
QVariant RemoteCommitsModel::headerData(int section, Qt::Orientation orientation, int role) const
{
// Get the header string from the root item
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
return rootItem->data(section, role);
return QVariant();
}
int RemoteCommitsModel::rowCount(const QModelIndex& parent) const
{
if(parent.column() > 0)
return 0;
if(!parent.isValid())
return rootItem->childCount();
else
return static_cast<QTreeWidgetItem*>(parent.internalPointer())->childCount();
}
int RemoteCommitsModel::columnCount(const QModelIndex& /*parent*/) const
{
return rootItem->columnCount();
}

43
src/RemoteCommitsModel.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef REMOTECOMMITSMODEL_H
#define REMOTECOMMITSMODEL_H
#include <QAbstractItemModel>
#include <json.hpp>
class QTreeWidgetItem;
class RemoteCommitsModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit RemoteCommitsModel(QObject* parent);
~RemoteCommitsModel() override;
void clear();
void refresh(const std::string& json_data, const std::string& last_commit_id);
QModelIndex index(int row, int column,const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& index) const override;
QVariant data(const QModelIndex& index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
enum Columns
{
ColumnCommitId,
ColumnMessage,
ColumnDate,
ColumnAuthor,
};
private:
// Pointer to the root item. This contains all the actual item data.
QTreeWidgetItem* rootItem;
};
#endif

View File

@@ -309,6 +309,41 @@ void RemoteDatabase::gotReply(QNetworkReply* reply)
emit uploadFinished(obj["url"]); emit uploadFinished(obj["url"]);
break; break;
} }
case RequestTypeMetadata:
{
// Read and check results
json obj = json::parse(reply->readAll(), nullptr, false);
if(obj.is_discarded() || !obj.is_object())
break;
// Extract and convert data
json obj_branches = obj["branches"];
json obj_commits = obj["commits"];
json obj_releases = obj["releases"];
json obj_tags = obj["tags"];
std::string default_branch = (obj.contains("default_branch") && !obj["default_branch"].empty()) ? obj["default_branch"] : "master";
std::vector<RemoteMetadataBranchInfo> branches;
for(auto it=obj_branches.cbegin();it!=obj_branches.cend();++it)
branches.emplace_back(it.key(), it.value()["commit"], it.value()["description"], it.value()["commit_count"]);
std::vector<RemoteMetadataReleaseInfo> releases;
for(auto it=obj_releases.cbegin();it!=obj_releases.cend();++it)
{
releases.emplace_back(it.key(), it.value()["commit"], it.value()["date"],
it.value()["description"], it.value()["email"],
it.value()["name"], it.value()["size"]);
}
std::vector<RemoteMetadataReleaseInfo> tags;
for(auto it=obj_tags.cbegin();it!=obj_tags.cend();++it)
{
tags.emplace_back(it.key(), it.value()["commit"], it.value()["date"],
it.value()["description"], it.value()["email"],
it.value()["name"], 0);
}
// Send data list to anyone who is interested
emit gotMetadata(branches, obj_commits.dump(), releases, tags, default_branch);
break;
}
} }
// Delete reply later, i.e. after returning from this slot function // Delete reply later, i.e. after returning from this slot function

View File

@@ -15,6 +15,52 @@ class QHttpMultiPart;
class QFile; class QFile;
struct sqlite3; struct sqlite3;
class RemoteMetadataBranchInfo
{
public:
RemoteMetadataBranchInfo(const std::string& _name, const std::string& _commit_id, const std::string& _description, unsigned int _commit_count) :
name(_name),
commit_id(_commit_id),
description(_description),
commit_count(_commit_count)
{}
RemoteMetadataBranchInfo() :
commit_count(0)
{}
std::string name;
std::string commit_id;
std::string description;
unsigned int commit_count;
};
class RemoteMetadataReleaseInfo
{
public:
RemoteMetadataReleaseInfo(const std::string& _name, const std::string& _commit_id, const std::string& _date,
const std::string& _description, const std::string& _email,
const std::string& _user_name, unsigned int _size) :
name(_name),
commit_id(_commit_id),
date(_date),
description(_description),
email(_email),
user_name(_user_name),
size(_size)
{}
RemoteMetadataReleaseInfo() :
size(0)
{}
std::string name;
std::string commit_id;
std::string date;
std::string description;
std::string email;
std::string user_name;
unsigned long size;
};
class RemoteDatabase : public QObject class RemoteDatabase : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -43,6 +89,7 @@ public:
RequestTypePush, RequestTypePush,
RequestTypeLicenceList, RequestTypeLicenceList,
RequestTypeBranchList, RequestTypeBranchList,
RequestTypeMetadata,
}; };
void fetch(const QString& url, RequestType type, const QString& clientCert = QString(), QVariant userdata = QVariant()); void fetch(const QString& url, RequestType type, const QString& clientCert = QString(), QVariant userdata = QVariant());
@@ -98,6 +145,9 @@ signals:
void gotCurrentVersion(QString version, QString url); void gotCurrentVersion(QString version, QString url);
void gotLicenceList(std::vector<std::pair<std::string, std::string>> licences); void gotLicenceList(std::vector<std::pair<std::string, std::string>> licences);
void gotBranchList(std::vector<std::string> branches, std::string default_branch); void gotBranchList(std::vector<std::string> branches, std::string default_branch);
void gotMetadata(std::vector<RemoteMetadataBranchInfo> branches, std::string commits,
std::vector<RemoteMetadataReleaseInfo> releases, std::vector<RemoteMetadataReleaseInfo> tags,
std::string default_branch);
// The uploadFinished() signal is emitted when a push() call is finished, i.e. a database upload has completed. // The uploadFinished() signal is emitted when a push() call is finished, i.e. a database upload has completed.
void uploadFinished(std::string url); void uploadFinished(std::string url);

View File

@@ -1,10 +1,12 @@
#include <QDesktopServices> #include <QDesktopServices>
#include <QFileInfo> #include <QFileInfo>
#include <QUrl> #include <QUrl>
#include <QUrlQuery>
#include "RemoteDock.h" #include "RemoteDock.h"
#include "ui_RemoteDock.h" #include "ui_RemoteDock.h"
#include "Settings.h" #include "Settings.h"
#include "RemoteCommitsModel.h"
#include "RemoteDatabase.h" #include "RemoteDatabase.h"
#include "RemoteLocalFilesModel.h" #include "RemoteLocalFilesModel.h"
#include "RemoteModel.h" #include "RemoteModel.h"
@@ -18,13 +20,15 @@ RemoteDock::RemoteDock(MainWindow* parent)
mainWindow(parent), mainWindow(parent),
remoteDatabase(parent->getRemote()), remoteDatabase(parent->getRemote()),
remoteModel(new RemoteModel(this, parent->getRemote())), remoteModel(new RemoteModel(this, parent->getRemote())),
remoteLocalFilesModel(new RemoteLocalFilesModel(this, parent->getRemote())) remoteLocalFilesModel(new RemoteLocalFilesModel(this, parent->getRemote())),
remoteCommitsModel(new RemoteCommitsModel(this))
{ {
ui->setupUi(this); ui->setupUi(this);
// Set models // Set models
ui->treeRemote->setModel(remoteModel); ui->treeRemote->setModel(remoteModel);
ui->treeLocal->setModel(remoteLocalFilesModel); ui->treeLocal->setModel(remoteLocalFilesModel);
ui->treeDatabaseCommits->setModel(remoteCommitsModel);
// When a database has been downloaded and must be opened, notify users of this class // When a database has been downloaded and must be opened, notify users of this class
connect(&remoteDatabase, &RemoteDatabase::openFile, this, &RemoteDock::openFile); connect(&remoteDatabase, &RemoteDatabase::openFile, this, &RemoteDock::openFile);
@@ -37,6 +41,9 @@ RemoteDock::RemoteDock(MainWindow* parent)
// Whenever a new directory listing has been parsed, check if it was a new root dir and, if so, open the user's directory // Whenever a new directory listing has been parsed, check if it was a new root dir and, if so, open the user's directory
connect(remoteModel, &RemoteModel::directoryListingParsed, this, &RemoteDock::newDirectoryNode); connect(remoteModel, &RemoteModel::directoryListingParsed, this, &RemoteDock::newDirectoryNode);
// Show metadata for a database when we get it
connect(&remoteDatabase, &RemoteDatabase::gotMetadata, this, &RemoteDock::showMetadata);
// When the Preferences link is clicked in the no-certificates-label, open the preferences dialog. For other links than the ones we know, // When the Preferences link is clicked in the no-certificates-label, open the preferences dialog. For other links than the ones we know,
// just open them in a web browser // just open them in a web browser
connect(ui->labelNoCert, &QLabel::linkActivated, [this](const QString& link) { connect(ui->labelNoCert, &QLabel::linkActivated, [this](const QString& link) {
@@ -50,6 +57,12 @@ RemoteDock::RemoteDock(MainWindow* parent)
} }
}); });
// When changing the current branch in the branches combo box, update the tree view accordingly
connect(ui->comboDatabaseBranch, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int /*index*/) {
remoteCommitsModel->refresh(current_commit_json, ui->comboDatabaseBranch->currentData().toString().toStdString());
ui->treeDatabaseCommits->expandAll();
});
// Initial setup // Initial setup
reloadSettings(); reloadSettings();
} }
@@ -227,6 +240,7 @@ void RemoteDock::fileOpened(const QString& filename)
info = remoteDatabase.localGetLocalFileInfo(filename); info = remoteDatabase.localGetLocalFileInfo(filename);
// Copy information to view // Copy information to view
remoteCommitsModel->clear();
ui->labelDatabaseUser->setText(info.user_name()); ui->labelDatabaseUser->setText(info.user_name());
ui->labelDatabaseFile->setText(QString::fromStdString(info.name)); ui->labelDatabaseFile->setText(QString::fromStdString(info.name));
ui->labelDatabaseBranch->setText(QString::fromStdString(info.branch)); ui->labelDatabaseBranch->setText(QString::fromStdString(info.branch));
@@ -239,7 +253,33 @@ void RemoteDock::fileOpened(const QString& filename)
if(QString::fromStdString(info.identity) != QFileInfo(remoteModel->currentClientCertificate()).fileName()) if(QString::fromStdString(info.identity) != QFileInfo(remoteModel->currentClientCertificate()).fileName())
ui->comboUser->setCurrentIndex(ui->comboUser->findData("/" + QString::fromStdString(info.identity), Qt::UserRole, Qt::MatchEndsWith)); ui->comboUser->setCurrentIndex(ui->comboUser->findData("/" + QString::fromStdString(info.identity), Qt::UserRole, Qt::MatchEndsWith));
// Query more information on database from server
QUrl url(remoteDatabase.getInfoFromClientCert(remoteModel->currentClientCertificate(), RemoteDatabase::CertInfoServer) + "/metadata/get");
QUrlQuery query;
query.addQueryItem("username", info.user_name());
query.addQueryItem("folder", "/");
query.addQueryItem("dbname", QString::fromStdString(info.name));
url.setQuery(query);
remoteDatabase.fetch(url.toString(), RemoteDatabase::RequestTypeMetadata, remoteModel->currentClientCertificate());
// Switch to "Current Database" tab // Switch to "Current Database" tab
ui->tabs->setCurrentIndex(2); ui->tabs->setCurrentIndex(2);
} }
} }
void RemoteDock::showMetadata(const std::vector<RemoteMetadataBranchInfo>& branches, const std::string& commits,
const std::vector<RemoteMetadataReleaseInfo>& releases, const std::vector<RemoteMetadataReleaseInfo>& tags,
const std::string& /*default_branch*/)
{
current_commit_json = commits;
// Fill branches combo box
ui->comboDatabaseBranch->clear();
for(const auto& branch : branches)
ui->comboDatabaseBranch->addItem(QString::fromStdString(branch.name), QString::fromStdString(branch.commit_id));
for(const auto& release : releases)
ui->comboDatabaseBranch->addItem(QString::fromStdString(release.name) + " (" + tr("release") + ")", QString::fromStdString(release.commit_id));
for(const auto& tag : tags)
ui->comboDatabaseBranch->addItem(QString::fromStdString(tag.name) + " (" + tr("tag") + ")", QString::fromStdString(tag.commit_id));
ui->comboDatabaseBranch->setCurrentIndex(ui->comboDatabaseBranch->findText(ui->labelDatabaseBranch->text()));
}

View File

@@ -3,7 +3,10 @@
#include <QDialog> #include <QDialog>
class RemoteCommitsModel;
class RemoteDatabase; class RemoteDatabase;
class RemoteMetadataBranchInfo;
class RemoteMetadataReleaseInfo;
class RemoteLocalFilesModel; class RemoteLocalFilesModel;
class RemoteModel; class RemoteModel;
class MainWindow; class MainWindow;
@@ -39,6 +42,9 @@ private slots:
void newDirectoryNode(const QModelIndex& parent); void newDirectoryNode(const QModelIndex& parent);
void switchToMainView(); void switchToMainView();
void openLocalFile(const QModelIndex& idx); void openLocalFile(const QModelIndex& idx);
void showMetadata(const std::vector<RemoteMetadataBranchInfo>& branches, const std::string& commits,
const std::vector<RemoteMetadataReleaseInfo>& releases, const std::vector<RemoteMetadataReleaseInfo>& tags,
const std::string& default_branch);
signals: signals:
void openFile(QString file); void openFile(QString file);
@@ -51,6 +57,9 @@ private:
RemoteDatabase& remoteDatabase; RemoteDatabase& remoteDatabase;
RemoteModel* remoteModel; RemoteModel* remoteModel;
RemoteLocalFilesModel* remoteLocalFilesModel; RemoteLocalFilesModel* remoteLocalFilesModel;
RemoteCommitsModel* remoteCommitsModel;
std::string current_commit_json;
void refreshLocalFileList(); void refreshLocalFileList();
}; };

View File

@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>534</width> <width>534</width>
<height>298</height> <height>357</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -129,56 +129,91 @@
<attribute name="title"> <attribute name="title">
<string>Current Database</string> <string>Current Database</string>
</attribute> </attribute>
<layout class="QFormLayout" name="formLayout"> <layout class="QVBoxLayout" name="verticalLayout_7">
<item row="0" column="0"> <item>
<widget class="QLabel" name="label"> <widget class="QGroupBox" name="groupBox">
<property name="text"> <property name="title">
<string>User</string> <string>Clone</string>
</property> </property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>User</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="labelDatabaseUser">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Database</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="labelDatabaseFile">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Branch</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelDatabaseBranch">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item>
<widget class="QLabel" name="labelDatabaseUser"> <widget class="QGroupBox" name="groupBox_2">
<property name="text"> <property name="title">
<string/> <string>Commits</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Database</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="labelDatabaseFile">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Branch</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelDatabaseBranch">
<property name="text">
<string/>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Commits for</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboDatabaseBranch"/>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="treeDatabaseCommits"/>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@@ -22,6 +22,7 @@ CONFIG(unittest) {
HEADERS += \ HEADERS += \
ImageViewer.h \ ImageViewer.h \
RemoteCommitsModel.h \
RemoteLocalFilesModel.h \ RemoteLocalFilesModel.h \
sqlitedb.h \ sqlitedb.h \
MainWindow.h \ MainWindow.h \
@@ -84,6 +85,7 @@ HEADERS += \
SOURCES += \ SOURCES += \
ImageViewer.cpp \ ImageViewer.cpp \
RemoteCommitsModel.cpp \
RemoteLocalFilesModel.cpp \ RemoteLocalFilesModel.cpp \
sqlitedb.cpp \ sqlitedb.cpp \
MainWindow.cpp \ MainWindow.cpp \