forked from qt-creator/qt-creator
GitLab: Allow browsing and cloning projects
Change-Id: I1cc877ea6b5a55ae7bdb8e7a529afeb08d09e0c0 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -3,6 +3,8 @@ add_qtc_plugin(GitLab
|
|||||||
PLUGIN_DEPENDS Core ProjectExplorer Git VcsBase
|
PLUGIN_DEPENDS Core ProjectExplorer Git VcsBase
|
||||||
DEPENDS Utils
|
DEPENDS Utils
|
||||||
SOURCES
|
SOURCES
|
||||||
|
gitlabclonedialog.cpp gitlabclonedialog.h
|
||||||
|
gitlabdialog.cpp gitlabdialog.h gitlabdialog.ui
|
||||||
gitlaboptionspage.cpp gitlaboptionspage.h
|
gitlaboptionspage.cpp gitlaboptionspage.h
|
||||||
gitlabparameters.cpp gitlabparameters.h
|
gitlabparameters.cpp gitlabparameters.h
|
||||||
gitlabplugin.cpp gitlabplugin.h
|
gitlabplugin.cpp gitlabplugin.h
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ QtcPlugin {
|
|||||||
Depends { name: "Utils" }
|
Depends { name: "Utils" }
|
||||||
|
|
||||||
files: [
|
files: [
|
||||||
|
"gitlabclonedialog.cpp",
|
||||||
|
"gitlabclonedialog.h",
|
||||||
|
"gitlabdialog.cpp",
|
||||||
|
"gitlabdialog.h",
|
||||||
|
"gitlabdialog.ui",
|
||||||
"gitlaboptionspage.cpp",
|
"gitlaboptionspage.cpp",
|
||||||
"gitlaboptionspage.h",
|
"gitlaboptionspage.h",
|
||||||
"gitlabparameters.cpp",
|
"gitlabparameters.cpp",
|
||||||
|
|||||||
263
src/plugins/gitlab/gitlabclonedialog.cpp
Normal file
263
src/plugins/gitlab/gitlabclonedialog.cpp
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "gitlabclonedialog.h"
|
||||||
|
|
||||||
|
#include "gitlabprojectsettings.h"
|
||||||
|
#include "resultparser.h"
|
||||||
|
|
||||||
|
#include <coreplugin/documentmanager.h>
|
||||||
|
#include <coreplugin/shellcommand.h>
|
||||||
|
#include <coreplugin/vcsmanager.h>
|
||||||
|
#include <git/gitclient.h>
|
||||||
|
#include <projectexplorer/projectexplorer.h>
|
||||||
|
#include <projectexplorer/projectmanager.h>
|
||||||
|
#include <utils/algorithm.h>
|
||||||
|
#include <utils/commandline.h>
|
||||||
|
#include <utils/environment.h>
|
||||||
|
#include <utils/fancylineedit.h>
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
#include <utils/infolabel.h>
|
||||||
|
#include <utils/mimeutils.h>
|
||||||
|
#include <utils/pathchooser.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/qtcprocess.h>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPlainTextEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
namespace GitLab {
|
||||||
|
|
||||||
|
GitLabCloneDialog::GitLabCloneDialog(const Project &project, QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Clone Repository"));
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||||
|
layout->addWidget(new QLabel(tr("Specify repository URL, checkout path and directory.")));
|
||||||
|
QHBoxLayout *centerLayout = new QHBoxLayout;
|
||||||
|
QFormLayout *form = new QFormLayout;
|
||||||
|
m_repositoryCB = new QComboBox(this);
|
||||||
|
m_repositoryCB->addItems({project.sshUrl, project.httpUrl});
|
||||||
|
form->addRow(tr("Repository"), m_repositoryCB);
|
||||||
|
m_pathChooser = new Utils::PathChooser(this);
|
||||||
|
m_pathChooser->setExpectedKind(Utils::PathChooser::ExistingDirectory);
|
||||||
|
form->addRow(tr("Path"), m_pathChooser);
|
||||||
|
m_directoryLE = new Utils::FancyLineEdit(this);
|
||||||
|
m_directoryLE->setValidationFunction([this](Utils::FancyLineEdit *e, QString *msg) {
|
||||||
|
const Utils::FilePath fullPath = m_pathChooser->filePath().pathAppended(e->text());
|
||||||
|
bool alreadyExists = fullPath.exists();
|
||||||
|
if (alreadyExists && msg)
|
||||||
|
*msg = tr("Path \"%1\" already exists.").arg(fullPath.toUserOutput());
|
||||||
|
return !alreadyExists;
|
||||||
|
});
|
||||||
|
form->addRow(tr("Directory"), m_directoryLE);
|
||||||
|
m_submodulesCB = new QCheckBox(this);
|
||||||
|
form->addRow(tr("Recursive"), m_submodulesCB);
|
||||||
|
centerLayout->addLayout(form);
|
||||||
|
m_cloneOutput = new QPlainTextEdit(this);
|
||||||
|
m_cloneOutput->setReadOnly(true);
|
||||||
|
centerLayout->addWidget(m_cloneOutput);
|
||||||
|
layout->addLayout(centerLayout);
|
||||||
|
m_infoLabel = new Utils::InfoLabel(this);
|
||||||
|
layout->addWidget(m_infoLabel);
|
||||||
|
layout->addStretch(1);
|
||||||
|
auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel, this);
|
||||||
|
m_cloneButton = new QPushButton(tr("Clone"), this);
|
||||||
|
buttons->addButton(m_cloneButton, QDialogButtonBox::ActionRole);
|
||||||
|
m_cancelButton = buttons->button(QDialogButtonBox::Cancel);
|
||||||
|
layout->addWidget(buttons);
|
||||||
|
setLayout(layout);
|
||||||
|
|
||||||
|
m_pathChooser->setFilePath(Core::DocumentManager::projectsDirectory());
|
||||||
|
auto [host, path, port]
|
||||||
|
= GitLabProjectSettings::remotePartsFromRemote(m_repositoryCB->currentText());
|
||||||
|
int slashIndex = path.indexOf('/');
|
||||||
|
QTC_ASSERT(slashIndex > 0, return);
|
||||||
|
m_directoryLE->setText(path.mid(slashIndex + 1));
|
||||||
|
|
||||||
|
connect(m_pathChooser, &Utils::PathChooser::pathChanged, this, [this]() {
|
||||||
|
m_directoryLE->validate();
|
||||||
|
GitLabCloneDialog::updateUi();
|
||||||
|
});
|
||||||
|
connect(m_directoryLE, &Utils::FancyLineEdit::textChanged, this, &GitLabCloneDialog::updateUi);
|
||||||
|
connect(m_cloneButton, &QPushButton::clicked, this, &GitLabCloneDialog::cloneProject);
|
||||||
|
connect(m_cancelButton, &QPushButton::clicked,
|
||||||
|
this, &GitLabCloneDialog::cancel);
|
||||||
|
connect(this, &QDialog::rejected, this, [this]() {
|
||||||
|
if (m_commandRunning) {
|
||||||
|
cancel();
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateUi();
|
||||||
|
resize(575, 265);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabCloneDialog::updateUi()
|
||||||
|
{
|
||||||
|
bool pathValid = m_pathChooser->isValid();
|
||||||
|
bool directoryValid = m_directoryLE->isValid();
|
||||||
|
m_cloneButton->setEnabled(pathValid && directoryValid);
|
||||||
|
if (!pathValid) {
|
||||||
|
m_infoLabel->setText(m_pathChooser->errorMessage());
|
||||||
|
m_infoLabel->setType(Utils::InfoLabel::Error);
|
||||||
|
} else if (!directoryValid) {
|
||||||
|
m_infoLabel->setText(m_directoryLE->errorMessage());
|
||||||
|
m_infoLabel->setType(Utils::InfoLabel::Error);
|
||||||
|
}
|
||||||
|
m_infoLabel->setVisible(!pathValid || !directoryValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabCloneDialog::cloneProject()
|
||||||
|
{
|
||||||
|
Core::IVersionControl *vc = Core::VcsManager::versionControl(Utils::Id::fromString("G.Git"));
|
||||||
|
QTC_ASSERT(vc, return);
|
||||||
|
const QStringList extraArgs = m_submodulesCB->isChecked() ? QStringList{ "--recursive" }
|
||||||
|
: QStringList{};
|
||||||
|
m_command = vc->createInitialCheckoutCommand(m_repositoryCB->currentText(),
|
||||||
|
m_pathChooser->absoluteFilePath(),
|
||||||
|
m_directoryLE->text(), extraArgs);
|
||||||
|
const Utils::FilePath workingDirectory = m_pathChooser->absoluteFilePath();
|
||||||
|
m_command->setProgressiveOutput(true);
|
||||||
|
connect(m_command, &Utils::ShellCommand::stdOutText, this, [this](const QString &text) {
|
||||||
|
m_cloneOutput->appendPlainText(text);
|
||||||
|
});
|
||||||
|
connect(m_command, &Utils::ShellCommand::stdErrText, this, [this](const QString &text) {
|
||||||
|
m_cloneOutput->appendPlainText(text);
|
||||||
|
});
|
||||||
|
connect(m_command, &Utils::ShellCommand::finished, this, &GitLabCloneDialog::cloneFinished);
|
||||||
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
|
|
||||||
|
m_cloneOutput->clear();
|
||||||
|
m_cloneButton->setEnabled(false);
|
||||||
|
m_pathChooser->setReadOnly(true);
|
||||||
|
m_directoryLE->setReadOnly(true);
|
||||||
|
m_commandRunning = true;
|
||||||
|
m_command->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabCloneDialog::cancel()
|
||||||
|
{
|
||||||
|
if (m_commandRunning) {
|
||||||
|
m_cloneOutput->appendPlainText(tr("User canceled process."));
|
||||||
|
m_cancelButton->setEnabled(false);
|
||||||
|
m_command->cancel(); // FIXME does not cancel the git processes... QTCREATORBUG-27567
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Utils::FilePaths scanDirectoryForFiles(const Utils::FilePath &directory)
|
||||||
|
{
|
||||||
|
Utils::FilePaths result;
|
||||||
|
const Utils::FilePaths entries = directory.dirEntries(QDir::AllEntries | QDir::NoDotAndDotDot);
|
||||||
|
|
||||||
|
for (const Utils::FilePath &entry : entries) {
|
||||||
|
if (entry.isDir())
|
||||||
|
result.append(scanDirectoryForFiles(entry));
|
||||||
|
else
|
||||||
|
result.append(entry);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabCloneDialog::cloneFinished(bool ok, int exitCode)
|
||||||
|
{
|
||||||
|
const bool success = (ok && exitCode == 0);
|
||||||
|
m_commandRunning = false;
|
||||||
|
delete m_command;
|
||||||
|
m_command = nullptr;
|
||||||
|
|
||||||
|
const QString emptyLine("\n\n");
|
||||||
|
m_cloneOutput->appendPlainText(emptyLine);
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
m_cloneOutput->appendPlainText(tr("Cloning succeeded.") + emptyLine);
|
||||||
|
m_cloneButton->setEnabled(false);
|
||||||
|
|
||||||
|
const Utils::FilePath base = m_pathChooser->filePath().pathAppended(m_directoryLE->text());
|
||||||
|
Utils::FilePaths filesWeMayOpen
|
||||||
|
= Utils::filtered(scanDirectoryForFiles(base), [](const Utils::FilePath &f) {
|
||||||
|
return ProjectExplorer::ProjectManager::canOpenProjectForMimeType(
|
||||||
|
Utils::mimeTypeForFile(f));
|
||||||
|
});
|
||||||
|
|
||||||
|
// limit the files to the most top-level item(s)
|
||||||
|
int minimum = std::numeric_limits<int>::max();
|
||||||
|
for (const Utils::FilePath &f : filesWeMayOpen) {
|
||||||
|
int parentCount = f.toString().count('/');
|
||||||
|
if (parentCount < minimum)
|
||||||
|
minimum = parentCount;
|
||||||
|
}
|
||||||
|
filesWeMayOpen = Utils::filtered(filesWeMayOpen, [minimum](const Utils::FilePath &f) {
|
||||||
|
return f.toString().count('/') == minimum;
|
||||||
|
});
|
||||||
|
|
||||||
|
hide(); // avoid to many dialogs.. FIXME: maybe change to some wizard approach?
|
||||||
|
if (filesWeMayOpen.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, tr("Warning"),
|
||||||
|
tr("Cloned project does not have a project file that can be "
|
||||||
|
"opened. Try importing the project as a generic project."));
|
||||||
|
accept();
|
||||||
|
} else {
|
||||||
|
const QStringList pFiles = Utils::transform(filesWeMayOpen,
|
||||||
|
[base](const Utils::FilePath &f) {
|
||||||
|
return f.relativePath(base).toUserOutput();
|
||||||
|
});
|
||||||
|
bool ok = false;
|
||||||
|
const QString fileToOpen
|
||||||
|
= QInputDialog::getItem(this, tr("Open Project"),
|
||||||
|
tr("Choose the project file to be opened."),
|
||||||
|
pFiles, 0, false, &ok);
|
||||||
|
accept();
|
||||||
|
if (ok && !fileToOpen.isEmpty())
|
||||||
|
ProjectExplorer::ProjectExplorerPlugin::openProject(base.pathAppended(fileToOpen));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_cloneOutput->appendPlainText(tr("Cloning failed.") + emptyLine);
|
||||||
|
const Utils::FilePath fullPath = m_pathChooser->filePath()
|
||||||
|
.pathAppended(m_directoryLE->text());
|
||||||
|
fullPath.removeRecursively();
|
||||||
|
m_cloneButton->setEnabled(true);
|
||||||
|
m_cancelButton->setEnabled(true);
|
||||||
|
m_pathChooser->setReadOnly(false);
|
||||||
|
m_directoryLE->setReadOnly(false);
|
||||||
|
m_directoryLE->validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GitLab
|
||||||
74
src/plugins/gitlab/gitlabclonedialog.h
Normal file
74
src/plugins/gitlab/gitlabclonedialog.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QCheckBox;
|
||||||
|
class QComboBox;
|
||||||
|
class QPlainTextEdit;
|
||||||
|
class QPushButton;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace Core { class ShellCommand; }
|
||||||
|
|
||||||
|
namespace Utils {
|
||||||
|
class FancyLineEdit;
|
||||||
|
class InfoLabel;
|
||||||
|
class PathChooser;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace GitLab {
|
||||||
|
|
||||||
|
class Project;
|
||||||
|
|
||||||
|
class GitLabCloneDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_DECLARE_TR_FUNCTIONS(GitLab::GitLabCloneDialog)
|
||||||
|
public:
|
||||||
|
explicit GitLabCloneDialog(const Project &project, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateUi();
|
||||||
|
void cloneProject();
|
||||||
|
void cancel();
|
||||||
|
void cloneFinished(bool ok, int exitCode);
|
||||||
|
|
||||||
|
QComboBox * m_repositoryCB = nullptr;
|
||||||
|
QCheckBox *m_submodulesCB = nullptr;
|
||||||
|
QPushButton *m_cloneButton = nullptr;
|
||||||
|
QPushButton *m_cancelButton = nullptr;
|
||||||
|
QPlainTextEdit *m_cloneOutput = nullptr;
|
||||||
|
Utils::PathChooser *m_pathChooser = nullptr;
|
||||||
|
Utils::FancyLineEdit *m_directoryLE = nullptr;
|
||||||
|
Utils::InfoLabel *m_infoLabel = nullptr;
|
||||||
|
Core::ShellCommand *m_command = nullptr;
|
||||||
|
bool m_commandRunning = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace GitLab
|
||||||
294
src/plugins/gitlab/gitlabdialog.cpp
Normal file
294
src/plugins/gitlab/gitlabdialog.cpp
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "gitlabdialog.h"
|
||||||
|
|
||||||
|
#include "gitlabclonedialog.h"
|
||||||
|
#include "gitlabparameters.h"
|
||||||
|
#include "gitlabplugin.h"
|
||||||
|
#include "gitlabprojectsettings.h"
|
||||||
|
|
||||||
|
#include <projectexplorer/session.h>
|
||||||
|
#include <texteditor/fontsettings.h>
|
||||||
|
#include <texteditor/texteditorsettings.h>
|
||||||
|
#include <utils/listmodel.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
#include <utils/utilsicons.h>
|
||||||
|
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QSyntaxHighlighter>
|
||||||
|
|
||||||
|
namespace GitLab {
|
||||||
|
|
||||||
|
GitLabDialog::GitLabDialog(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
, m_lastTreeViewQuery(Query::NoQuery)
|
||||||
|
{
|
||||||
|
m_ui.setupUi(this);
|
||||||
|
m_clonePB = new QPushButton(Utils::Icons::DOWNLOAD.icon(), tr("Clone..."), this);
|
||||||
|
m_ui.buttonBox->addButton(m_clonePB, QDialogButtonBox::ActionRole);
|
||||||
|
m_clonePB->setEnabled(false);
|
||||||
|
|
||||||
|
updateRemotes();
|
||||||
|
|
||||||
|
connect(m_ui.remoteCB, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||||
|
this, &GitLabDialog::requestMainViewUpdate);
|
||||||
|
connect(m_ui.searchLE, &QLineEdit::returnPressed, this, &GitLabDialog::querySearch);
|
||||||
|
connect(m_ui.searchPB, &QPushButton::clicked, this, &GitLabDialog::querySearch);
|
||||||
|
connect(m_clonePB, &QPushButton::clicked, this, &GitLabDialog::cloneSelected);
|
||||||
|
connect(m_ui.firstTB, &QToolButton::clicked, this, &GitLabDialog::queryFirstPage);
|
||||||
|
connect(m_ui.previousTB, &QToolButton::clicked, this, &GitLabDialog::queryPreviousPage);
|
||||||
|
connect(m_ui.nextTB, &QToolButton::clicked, this, &GitLabDialog::queryNextPage);
|
||||||
|
connect(m_ui.lastTB, &QToolButton::clicked, this, &GitLabDialog::queryLastPage);
|
||||||
|
requestMainViewUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::resetTreeView(QTreeView *treeView, QAbstractItemModel *model)
|
||||||
|
{
|
||||||
|
auto oldModel = treeView->model();
|
||||||
|
treeView->setModel(model);
|
||||||
|
delete oldModel;
|
||||||
|
if (model) {
|
||||||
|
connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged,
|
||||||
|
this, [this](const QItemSelection &selected, const QItemSelection &) {
|
||||||
|
m_clonePB->setEnabled(!selected.isEmpty());
|
||||||
|
});
|
||||||
|
m_clonePB->setEnabled(!treeView->selectionModel()->selectedIndexes().isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::updateRemotes()
|
||||||
|
{
|
||||||
|
m_ui.remoteCB->clear();
|
||||||
|
const GitLabParameters *global = GitLabPlugin::globalParameters();
|
||||||
|
for (const GitLabServer &server : qAsConst(global->gitLabServers))
|
||||||
|
m_ui.remoteCB->addItem(server.displayString(), QVariant::fromValue(server));
|
||||||
|
|
||||||
|
m_ui.remoteCB->setCurrentIndex(m_ui.remoteCB->findData(
|
||||||
|
QVariant::fromValue(global->currentDefaultServer())));
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::keyPressEvent(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)
|
||||||
|
return;
|
||||||
|
QDialog::keyPressEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::requestMainViewUpdate()
|
||||||
|
{
|
||||||
|
m_lastPageInformation = PageInformation();
|
||||||
|
m_lastTreeViewQuery = Query(Query::NoQuery);
|
||||||
|
|
||||||
|
m_ui.mainLabel->setText({});
|
||||||
|
m_ui.detailsLabel->setText({});
|
||||||
|
m_ui.treeViewTitle->setText({});
|
||||||
|
m_ui.searchLE->setText({});
|
||||||
|
resetTreeView(m_ui.treeView, nullptr);
|
||||||
|
updatePageButtons();
|
||||||
|
|
||||||
|
bool linked = false;
|
||||||
|
m_currentServerId = Utils::Id();
|
||||||
|
if (auto project = ProjectExplorer::SessionManager::startupProject()) {
|
||||||
|
GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project);
|
||||||
|
if (projSettings->isLinked()) {
|
||||||
|
m_currentServerId = projSettings->currentServer();
|
||||||
|
linked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!m_currentServerId.isValid())
|
||||||
|
m_currentServerId = m_ui.remoteCB->currentData().value<GitLabServer>().id;
|
||||||
|
if (m_currentServerId.isValid()) {
|
||||||
|
const GitLabParameters *global = GitLabPlugin::globalParameters();
|
||||||
|
const GitLabServer server = global->serverForId(m_currentServerId);
|
||||||
|
m_ui.remoteCB->setCurrentIndex(m_ui.remoteCB->findData(QVariant::fromValue(server)));
|
||||||
|
}
|
||||||
|
m_ui.remoteCB->setEnabled(!linked);
|
||||||
|
|
||||||
|
const Query query(Query::User);
|
||||||
|
QueryRunner *runner = new QueryRunner(query, m_currentServerId, this);
|
||||||
|
connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) {
|
||||||
|
handleUser(ResultParser::parseUser(result));
|
||||||
|
});
|
||||||
|
connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); });
|
||||||
|
runner->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::updatePageButtons()
|
||||||
|
{
|
||||||
|
if (m_lastPageInformation.currentPage == -1) {
|
||||||
|
m_ui.currentPage->setVisible(false);
|
||||||
|
m_ui.firstTB->setVisible(false);
|
||||||
|
m_ui.lastTB->setVisible(false);
|
||||||
|
m_ui.previousTB->setVisible(false);
|
||||||
|
m_ui.nextTB->setVisible(false);
|
||||||
|
} else {
|
||||||
|
m_ui.currentPage->setText(QString::number(m_lastPageInformation.currentPage));
|
||||||
|
m_ui.currentPage->setVisible(true);
|
||||||
|
m_ui.firstTB->setVisible(true);
|
||||||
|
m_ui.lastTB->setVisible(true);
|
||||||
|
}
|
||||||
|
if (m_lastPageInformation.currentPage > 1) {
|
||||||
|
m_ui.firstTB->setEnabled(true);
|
||||||
|
m_ui.previousTB->setText(QString::number(m_lastPageInformation.currentPage - 1));
|
||||||
|
m_ui.previousTB->setVisible(true);
|
||||||
|
} else {
|
||||||
|
m_ui.firstTB->setEnabled(false);
|
||||||
|
m_ui.previousTB->setVisible(false);
|
||||||
|
}
|
||||||
|
if (m_lastPageInformation.currentPage < m_lastPageInformation.totalPages) {
|
||||||
|
m_ui.lastTB->setEnabled(true);
|
||||||
|
m_ui.nextTB->setText(QString::number(m_lastPageInformation.currentPage + 1));
|
||||||
|
m_ui.nextTB->setVisible(true);
|
||||||
|
} else {
|
||||||
|
m_ui.lastTB->setEnabled(false);
|
||||||
|
m_ui.nextTB->setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::queryFirstPage()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
|
||||||
|
QTC_ASSERT(m_lastPageInformation.currentPage != -1, return);
|
||||||
|
m_lastTreeViewQuery.setPageParameter(1);
|
||||||
|
fetchProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::queryPreviousPage()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
|
||||||
|
QTC_ASSERT(m_lastPageInformation.currentPage != -1, return);
|
||||||
|
m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.currentPage - 1);
|
||||||
|
fetchProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::queryNextPage()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
|
||||||
|
QTC_ASSERT(m_lastPageInformation.currentPage != -1, return);
|
||||||
|
m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.currentPage + 1);
|
||||||
|
fetchProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::queryLastPage()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
|
||||||
|
QTC_ASSERT(m_lastPageInformation.currentPage != -1, return);
|
||||||
|
m_lastTreeViewQuery.setPageParameter(m_lastPageInformation.totalPages);
|
||||||
|
fetchProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::querySearch()
|
||||||
|
{
|
||||||
|
QTC_ASSERT(m_lastTreeViewQuery.type() != Query::NoQuery, return);
|
||||||
|
m_lastTreeViewQuery.setPageParameter(-1);
|
||||||
|
m_lastTreeViewQuery.setAdditionalParameters({"search=" + m_ui.searchLE->text()});
|
||||||
|
fetchProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::handleUser(const User &user)
|
||||||
|
{
|
||||||
|
m_lastPageInformation = {};
|
||||||
|
m_currentUserId = user.id;
|
||||||
|
|
||||||
|
if (!user.error.message.isEmpty()) {
|
||||||
|
// TODO
|
||||||
|
if (user.error.code == 1) {
|
||||||
|
m_ui.mainLabel->setText(tr("Not logged in."));
|
||||||
|
m_ui.detailsLabel->setText(tr("Insufficient access token."));
|
||||||
|
m_ui.detailsLabel->setToolTip(user.error.message + QLatin1Char('\n')
|
||||||
|
+ tr("Permission scope read_api or api needed."));
|
||||||
|
updatePageButtons();
|
||||||
|
m_ui.treeViewTitle->setText(tr("Projects (%1)").arg(0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.id != -1) {
|
||||||
|
if (user.bot) {
|
||||||
|
m_ui.mainLabel->setText(tr("Using project access token."));
|
||||||
|
m_ui.detailsLabel->setText({});
|
||||||
|
} else {
|
||||||
|
m_ui.mainLabel->setText(tr("Logged in as %1").arg(user.name));
|
||||||
|
m_ui.detailsLabel->setText(tr("Id: %1 (%2)").arg(user.id).arg(user.email));
|
||||||
|
}
|
||||||
|
m_ui.detailsLabel->setToolTip({});
|
||||||
|
} else {
|
||||||
|
m_ui.mainLabel->setText(tr("Not logged in."));
|
||||||
|
m_ui.detailsLabel->setText({});
|
||||||
|
m_ui.detailsLabel->setToolTip({});
|
||||||
|
}
|
||||||
|
m_lastTreeViewQuery = Query(Query::Projects);
|
||||||
|
fetchProjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::handleProjects(const Projects &projects)
|
||||||
|
{
|
||||||
|
Utils::ListModel<Project *> *listModel = new Utils::ListModel<Project *>(this);
|
||||||
|
for (const Project &project : projects.projects)
|
||||||
|
listModel->appendItem(new Project(project));
|
||||||
|
|
||||||
|
// TODO use a real model / delegate..?
|
||||||
|
listModel->setDataAccessor([](Project *data, int /*column*/, int role) -> QVariant {
|
||||||
|
if (role == Qt::DisplayRole)
|
||||||
|
return QString(data->displayName + " (" + data->visibility + ')');
|
||||||
|
if (role == Qt::UserRole)
|
||||||
|
return QVariant::fromValue(*data);
|
||||||
|
return QVariant();
|
||||||
|
});
|
||||||
|
resetTreeView(m_ui.treeView, listModel);
|
||||||
|
int count = projects.error.message.isEmpty() ? projects.pageInfo.total : 0;
|
||||||
|
m_ui.treeViewTitle->setText(tr("Projects (%1)").arg(count));
|
||||||
|
|
||||||
|
m_lastPageInformation = projects.pageInfo;
|
||||||
|
updatePageButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::fetchProjects()
|
||||||
|
{
|
||||||
|
QueryRunner *runner = new QueryRunner(m_lastTreeViewQuery, m_currentServerId, this);
|
||||||
|
connect(runner, &QueryRunner::resultRetrieved, this, [this](const QByteArray &result) {
|
||||||
|
handleProjects(ResultParser::parseProjects(result));
|
||||||
|
});
|
||||||
|
connect(runner, &QueryRunner::finished, [runner]() { runner->deleteLater(); });
|
||||||
|
runner->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitLabDialog::cloneSelected()
|
||||||
|
{
|
||||||
|
const QModelIndexList indexes = m_ui.treeView->selectionModel()->selectedIndexes();
|
||||||
|
QTC_ASSERT(indexes.size() == 1, return);
|
||||||
|
const Project project = indexes.first().data(Qt::UserRole).value<Project>();
|
||||||
|
QTC_ASSERT(!project.sshUrl.isEmpty() && !project.httpUrl.isEmpty(), return);
|
||||||
|
GitLabCloneDialog dialog(project, this);
|
||||||
|
if (dialog.exec() == QDialog::Accepted)
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GitLab
|
||||||
81
src/plugins/gitlab/gitlabdialog.h
Normal file
81
src/plugins/gitlab/gitlabdialog.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_gitlabdialog.h"
|
||||||
|
|
||||||
|
#include "queryrunner.h"
|
||||||
|
#include "resultparser.h"
|
||||||
|
|
||||||
|
#include <utils/filepath.h>
|
||||||
|
#include <utils/id.h>
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QPushButton;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
namespace GitLab {
|
||||||
|
|
||||||
|
class GitLabParameters;
|
||||||
|
class GitLabDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit GitLabDialog(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void updateRemotes();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void resetTreeView(QTreeView *treeView, QAbstractItemModel *model);
|
||||||
|
void requestMainViewUpdate();
|
||||||
|
void updatePageButtons();
|
||||||
|
|
||||||
|
void queryFirstPage();
|
||||||
|
void queryPreviousPage();
|
||||||
|
void queryNextPage();
|
||||||
|
void queryLastPage();
|
||||||
|
void querySearch();
|
||||||
|
void continuePageUpdate();
|
||||||
|
|
||||||
|
void handleUser(const User &user);
|
||||||
|
void handleProjects(const Projects &projects);
|
||||||
|
void fetchProjects();
|
||||||
|
|
||||||
|
void cloneSelected();
|
||||||
|
|
||||||
|
Ui::GitLabDialog m_ui;
|
||||||
|
QPushButton *m_clonePB = nullptr;
|
||||||
|
Utils::Id m_currentServerId;
|
||||||
|
Query m_lastTreeViewQuery;
|
||||||
|
PageInformation m_lastPageInformation;
|
||||||
|
int m_currentUserId = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace GitLab
|
||||||
268
src/plugins/gitlab/gitlabdialog.ui
Normal file
268
src/plugins/gitlab/gitlabdialog.ui
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>GitLab::GitLabDialog</class>
|
||||||
|
<widget class="QDialog" name="GitLab::GitLabDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>665</width>
|
||||||
|
<height>530</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>GitLab</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_0">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_1">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_0">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="mainLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Login</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="detailsLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Details</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_0">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remote:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="remoteCB">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="treeViewTitle">
|
||||||
|
<property name="text">
|
||||||
|
<string>Projects</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_1">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="searchLE">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Search</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="searchPB">
|
||||||
|
<property name="text">
|
||||||
|
<string>Search</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeView" name="treeView">
|
||||||
|
<property name="rootIsDecorated">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="uniformRowHeights">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="itemsExpandable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="expandsOnDoubleClick">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<attribute name="headerVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="firstTB">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">|<</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="previousTB">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="currentPage">
|
||||||
|
<property name="text">
|
||||||
|
<string>0</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="nextTB">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="lastTB">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">>|</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Close</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>GitLab::GitLabDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
||||||
@@ -25,16 +25,27 @@
|
|||||||
|
|
||||||
#include "gitlabplugin.h"
|
#include "gitlabplugin.h"
|
||||||
|
|
||||||
|
#include "gitlabdialog.h"
|
||||||
#include "gitlaboptionspage.h"
|
#include "gitlaboptionspage.h"
|
||||||
#include "gitlabparameters.h"
|
#include "gitlabparameters.h"
|
||||||
#include "gitlabprojectsettings.h"
|
#include "gitlabprojectsettings.h"
|
||||||
|
|
||||||
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
||||||
|
#include <coreplugin/actionmanager/actionmanager.h>
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/icore.h>
|
||||||
|
#include <git/gitplugin.h>
|
||||||
#include <projectexplorer/project.h>
|
#include <projectexplorer/project.h>
|
||||||
#include <projectexplorer/projectpanelfactory.h>
|
#include <projectexplorer/projectpanelfactory.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
namespace GitLab {
|
namespace GitLab {
|
||||||
|
namespace Constants {
|
||||||
|
const char GITLAB_OPEN_VIEW[] = "GitLab.OpenView";
|
||||||
|
} // namespace Constants
|
||||||
|
|
||||||
class GitLabPluginPrivate
|
class GitLabPluginPrivate
|
||||||
{
|
{
|
||||||
@@ -42,6 +53,7 @@ public:
|
|||||||
GitLabParameters parameters;
|
GitLabParameters parameters;
|
||||||
GitLabOptionsPage optionsPage{¶meters};
|
GitLabOptionsPage optionsPage{¶meters};
|
||||||
QHash<ProjectExplorer::Project *, GitLabProjectSettings *> projectSettings;
|
QHash<ProjectExplorer::Project *, GitLabProjectSettings *> projectSettings;
|
||||||
|
QPointer<GitLabDialog> dialog;
|
||||||
};
|
};
|
||||||
|
|
||||||
static GitLabPluginPrivate *dd = nullptr;
|
static GitLabPluginPrivate *dd = nullptr;
|
||||||
@@ -71,9 +83,42 @@ bool GitLabPlugin::initialize(const QStringList & /*arguments*/, QString * /*err
|
|||||||
return new GitLabProjectSettingsWidget(project);
|
return new GitLabProjectSettingsWidget(project);
|
||||||
});
|
});
|
||||||
ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory);
|
ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory);
|
||||||
|
QAction *openViewAction = new QAction(tr("GitLab..."), this);
|
||||||
|
auto gitlabCommand = Core::ActionManager::registerAction(openViewAction,
|
||||||
|
Constants::GITLAB_OPEN_VIEW);
|
||||||
|
connect(openViewAction, &QAction::triggered, this, &GitLabPlugin::openView);
|
||||||
|
Core::ActionContainer *ac = Core::ActionManager::actionContainer(Core::Constants::M_TOOLS);
|
||||||
|
ac->addAction(gitlabCommand);
|
||||||
|
connect(&dd->optionsPage, &GitLabOptionsPage::settingsChanged, this, [this] {
|
||||||
|
if (dd->dialog)
|
||||||
|
dd->dialog->updateRemotes();
|
||||||
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GitLabPlugin::openView()
|
||||||
|
{
|
||||||
|
if (dd->dialog.isNull()) {
|
||||||
|
while (!dd->parameters.isValid()) {
|
||||||
|
QMessageBox::warning(Core::ICore::dialogParent(), tr("Error"),
|
||||||
|
tr("Invalid GitLab configuration. For a fully functional "
|
||||||
|
"configuration, you need to set up host name or address and "
|
||||||
|
"an access token. Providing the path to curl is mandatory."));
|
||||||
|
if (!Core::ICore::showOptionsDialog("GitLab"))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GitLabDialog *gitlabD = new GitLabDialog(Core::ICore::dialogParent());
|
||||||
|
gitlabD->setModal(true);
|
||||||
|
Core::ICore::registerWindow(gitlabD, Core::Context("Git.GitLab"));
|
||||||
|
dd->dialog = gitlabD;
|
||||||
|
}
|
||||||
|
const Qt::WindowStates state = dd->dialog->windowState();
|
||||||
|
if (state & Qt::WindowMinimized)
|
||||||
|
dd->dialog->setWindowState(state & ~Qt::WindowMinimized);
|
||||||
|
dd->dialog->show();
|
||||||
|
dd->dialog->raise();
|
||||||
|
}
|
||||||
|
|
||||||
QList<GitLabServer> GitLabPlugin::allGitLabServers()
|
QList<GitLabServer> GitLabPlugin::allGitLabServers()
|
||||||
{
|
{
|
||||||
QTC_ASSERT(dd, return {});
|
QTC_ASSERT(dd, return {});
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ public:
|
|||||||
static GitLabParameters *globalParameters();
|
static GitLabParameters *globalParameters();
|
||||||
static GitLabProjectSettings *projectSettings(ProjectExplorer::Project *project);
|
static GitLabProjectSettings *projectSettings(ProjectExplorer::Project *project);
|
||||||
static GitLabOptionsPage *optionsPage();
|
static GitLabOptionsPage *optionsPage();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void openView();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace GitLab
|
} // namespace GitLab
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ void GitLabProjectSettingsWidget::updateEnabledStates()
|
|||||||
const bool isGitRepository = m_hostCB->count() > 0;
|
const bool isGitRepository = m_hostCB->count() > 0;
|
||||||
const bool hasGitLabServers = m_linkedGitLabServer->count();
|
const bool hasGitLabServers = m_linkedGitLabServer->count();
|
||||||
const bool linked = m_projectSettings->isLinked();
|
const bool linked = m_projectSettings->isLinked();
|
||||||
|
|
||||||
m_linkedGitLabServer->setEnabled(isGitRepository && !linked);
|
m_linkedGitLabServer->setEnabled(isGitRepository && !linked);
|
||||||
m_hostCB->setEnabled(isGitRepository && !linked);
|
m_hostCB->setEnabled(isGitRepository && !linked);
|
||||||
m_linkWithGitLab->setEnabled(isGitRepository && !linked && hasGitLabServers);
|
m_linkWithGitLab->setEnabled(isGitRepository && !linked && hasGitLabServers);
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ namespace GitLab {
|
|||||||
|
|
||||||
const char API_PREFIX[] = "/api/v4";
|
const char API_PREFIX[] = "/api/v4";
|
||||||
const char QUERY_PROJECT[] = "/projects/%1";
|
const char QUERY_PROJECT[] = "/projects/%1";
|
||||||
|
const char QUERY_PROJECTS[] = "/projects?simple=true";
|
||||||
|
const char QUERY_USER[] = "/user";
|
||||||
|
|
||||||
Query::Query(Type type, const QStringList ¶meter)
|
Query::Query(Type type, const QStringList ¶meter)
|
||||||
: m_type(type)
|
: m_type(type)
|
||||||
@@ -48,6 +50,21 @@ Query::Query(Type type, const QStringList ¶meter)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Query::setPageParameter(int page)
|
||||||
|
{
|
||||||
|
m_pageParameter = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Query::setAdditionalParameters(const QStringList &additional)
|
||||||
|
{
|
||||||
|
m_additionalParameters = additional;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Query::hasPaginatedResults() const
|
||||||
|
{
|
||||||
|
return m_type == Query::Projects;
|
||||||
|
}
|
||||||
|
|
||||||
QString Query::toString() const
|
QString Query::toString() const
|
||||||
{
|
{
|
||||||
QString query = API_PREFIX;
|
QString query = API_PREFIX;
|
||||||
@@ -59,6 +76,20 @@ QString Query::toString() const
|
|||||||
query += QLatin1String(QUERY_PROJECT).arg(QLatin1String(
|
query += QLatin1String(QUERY_PROJECT).arg(QLatin1String(
|
||||||
QUrl::toPercentEncoding(m_parameter.at(0))));
|
QUrl::toPercentEncoding(m_parameter.at(0))));
|
||||||
break;
|
break;
|
||||||
|
case Query::Projects:
|
||||||
|
query += QLatin1String(QUERY_PROJECTS);
|
||||||
|
break;
|
||||||
|
case Query::User:
|
||||||
|
query += QUERY_USER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (m_pageParameter > 0) {
|
||||||
|
query.append(m_type == Query::Projects ? '&' : '?');
|
||||||
|
query.append("page=").append(QString::number(m_pageParameter));
|
||||||
|
}
|
||||||
|
if (!m_additionalParameters.isEmpty()) {
|
||||||
|
query.append((m_type == Query::Projects || m_pageParameter > 0) ? '&' : '?');
|
||||||
|
query.append(m_additionalParameters.join('&'));
|
||||||
}
|
}
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
@@ -69,6 +100,9 @@ QueryRunner::QueryRunner(const Query &query, const Utils::Id &id, QObject *paren
|
|||||||
const GitLabParameters *p = GitLabPlugin::globalParameters();
|
const GitLabParameters *p = GitLabPlugin::globalParameters();
|
||||||
const auto server = p->serverForId(id);
|
const auto server = p->serverForId(id);
|
||||||
QStringList args = server.curlArguments();
|
QStringList args = server.curlArguments();
|
||||||
|
m_paginated = query.hasPaginatedResults();
|
||||||
|
if (m_paginated)
|
||||||
|
args << "-i";
|
||||||
if (!server.token.isEmpty())
|
if (!server.token.isEmpty())
|
||||||
args << "--header" << "PRIVATE-TOKEN: " + server.token;
|
args << "--header" << "PRIVATE-TOKEN: " + server.token;
|
||||||
QString url = "https://" + server.host;
|
QString url = "https://" + server.host;
|
||||||
|
|||||||
@@ -38,15 +38,23 @@ class Query
|
|||||||
public:
|
public:
|
||||||
enum Type {
|
enum Type {
|
||||||
NoQuery,
|
NoQuery,
|
||||||
Project
|
User,
|
||||||
|
Project,
|
||||||
|
Projects
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Query(Type type, const QStringList ¶meters = {});
|
explicit Query(Type type, const QStringList ¶meters = {});
|
||||||
|
void setPageParameter(int page);
|
||||||
|
void setAdditionalParameters(const QStringList &additional);
|
||||||
|
bool hasPaginatedResults() const;
|
||||||
|
Type type() const { return m_type; }
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Type m_type = NoQuery;
|
Type m_type = NoQuery;
|
||||||
QStringList m_parameter;
|
QStringList m_parameter;
|
||||||
|
QStringList m_additionalParameters;
|
||||||
|
int m_pageParameter = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QueryRunner : public QObject
|
class QueryRunner : public QObject
|
||||||
@@ -70,6 +78,7 @@ private:
|
|||||||
|
|
||||||
Utils::QtcProcess m_process;
|
Utils::QtcProcess m_process;
|
||||||
bool m_running = false;
|
bool m_running = false;
|
||||||
|
bool m_paginated = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace GitLab
|
} // namespace GitLab
|
||||||
|
|||||||
@@ -34,6 +34,38 @@
|
|||||||
namespace GitLab {
|
namespace GitLab {
|
||||||
namespace ResultParser {
|
namespace ResultParser {
|
||||||
|
|
||||||
|
static PageInformation paginationInformation(const QByteArray &header)
|
||||||
|
{
|
||||||
|
PageInformation result;
|
||||||
|
const QByteArrayList lines = header.split('\n');
|
||||||
|
for (const QByteArray &line : lines) {
|
||||||
|
const QByteArray lower = line.toLower(); // depending on OS this may be capitalized
|
||||||
|
if (lower.startsWith("x-page: "))
|
||||||
|
result.currentPage = line.mid(8).toInt();
|
||||||
|
else if (lower.startsWith("x-per-page: "))
|
||||||
|
result.perPage = line.mid(12).toInt();
|
||||||
|
else if (lower.startsWith("x-total: "))
|
||||||
|
result.total = line.mid(9).toInt();
|
||||||
|
else if (lower.startsWith("x-total-pages: "))
|
||||||
|
result.totalPages = line.mid(15).toInt();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::pair<QByteArray, QByteArray> splitHeaderAndBody(const QByteArray &input)
|
||||||
|
{
|
||||||
|
QByteArray header;
|
||||||
|
QByteArray json;
|
||||||
|
int emptyLine = input.indexOf("\r\n\r\n"); // we always get \r\n as line separator?
|
||||||
|
if (emptyLine != -1) {
|
||||||
|
header = input.left(emptyLine);
|
||||||
|
json = input.mid(emptyLine + 4);
|
||||||
|
} else {
|
||||||
|
json = input;
|
||||||
|
}
|
||||||
|
return std::make_pair(header, json);
|
||||||
|
}
|
||||||
|
|
||||||
static std::pair<Error, QJsonObject> preHandleSingle(const QByteArray &json)
|
static std::pair<Error, QJsonObject> preHandleSingle(const QByteArray &json)
|
||||||
{
|
{
|
||||||
Error result;
|
Error result;
|
||||||
@@ -59,6 +91,52 @@ static std::pair<Error, QJsonObject> preHandleSingle(const QByteArray &json)
|
|||||||
return std::make_pair(result, object);
|
return std::make_pair(result, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::pair<Error, QJsonDocument> preHandleHeaderAndBody(const QByteArray &header,
|
||||||
|
const QByteArray &json)
|
||||||
|
{
|
||||||
|
Error result;
|
||||||
|
if (header.isEmpty()) {
|
||||||
|
result.message = "Missing Expected Header";
|
||||||
|
return std::make_pair(result, QJsonDocument());
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError error;
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(json, &error);
|
||||||
|
if (error.error != QJsonParseError::NoError) {
|
||||||
|
result.message = error.errorString();
|
||||||
|
return std::make_pair(result, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doc.isObject()) {
|
||||||
|
const QJsonObject obj = doc.object();
|
||||||
|
if (obj.contains("message")) {
|
||||||
|
result = parseErrorMessage(obj.value("message").toString());
|
||||||
|
return std::make_pair(result, doc);
|
||||||
|
} else if (obj.contains("error")) {
|
||||||
|
if (obj.value("error").toString() == "insufficient_scope")
|
||||||
|
result.code = 1;
|
||||||
|
result.message = obj.value("error_description").toString();
|
||||||
|
return std::make_pair(result, doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.isArray())
|
||||||
|
result.message = "Not an Array";
|
||||||
|
|
||||||
|
return std::make_pair(result, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static User userFromJson(const QJsonObject &jsonObj)
|
||||||
|
{
|
||||||
|
User user;
|
||||||
|
user.name = jsonObj.value("username").toString();
|
||||||
|
user.realname = jsonObj.value("name").toString();
|
||||||
|
user.id = jsonObj.value("id").toInt(-1);
|
||||||
|
user.email = jsonObj.value("email").toString();
|
||||||
|
user.bot = jsonObj.value("bot").toBool();
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
static Project projectFromJson(const QJsonObject &jsonObj)
|
static Project projectFromJson(const QJsonObject &jsonObj)
|
||||||
{
|
{
|
||||||
Project project;
|
Project project;
|
||||||
@@ -67,6 +145,8 @@ static Project projectFromJson(const QJsonObject &jsonObj)
|
|||||||
project.pathName = jsonObj.value("path_with_namespace").toString();
|
project.pathName = jsonObj.value("path_with_namespace").toString();
|
||||||
project.id = jsonObj.value("id").toInt(-1);
|
project.id = jsonObj.value("id").toInt(-1);
|
||||||
project.visibility = jsonObj.value("visibility").toString("public");
|
project.visibility = jsonObj.value("visibility").toString("public");
|
||||||
|
project.httpUrl = jsonObj.value("http_url_to_repo").toString();
|
||||||
|
project.sshUrl = jsonObj.value("ssh_url_to_repo").toString();
|
||||||
if (jsonObj.contains("forks_count"))
|
if (jsonObj.contains("forks_count"))
|
||||||
project.forkCount = jsonObj.value("forks_count").toInt();
|
project.forkCount = jsonObj.value("forks_count").toInt();
|
||||||
if (jsonObj.contains("star_count"))
|
if (jsonObj.contains("star_count"))
|
||||||
@@ -82,6 +162,17 @@ static Project projectFromJson(const QJsonObject &jsonObj)
|
|||||||
return project;
|
return project;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
User parseUser(const QByteArray &input)
|
||||||
|
{
|
||||||
|
auto [error, userObj] = preHandleSingle(input);
|
||||||
|
if (!error.message.isEmpty()) {
|
||||||
|
User result;
|
||||||
|
result.error = error;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return userFromJson(userObj);
|
||||||
|
}
|
||||||
|
|
||||||
Project parseProject(const QByteArray &input)
|
Project parseProject(const QByteArray &input)
|
||||||
{
|
{
|
||||||
auto [error, projectObj] = preHandleSingle(input);
|
auto [error, projectObj] = preHandleSingle(input);
|
||||||
@@ -93,6 +184,26 @@ Project parseProject(const QByteArray &input)
|
|||||||
return projectFromJson(projectObj);
|
return projectFromJson(projectObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Projects parseProjects(const QByteArray &input)
|
||||||
|
{
|
||||||
|
auto [header, json] = splitHeaderAndBody(input);
|
||||||
|
auto [error, jsonDoc] = preHandleHeaderAndBody(header, json);
|
||||||
|
Projects result;
|
||||||
|
if (!error.message.isEmpty()) {
|
||||||
|
result.error = error;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.pageInfo = paginationInformation(header);
|
||||||
|
const QJsonArray projectsArray = jsonDoc.array();
|
||||||
|
for (const QJsonValue &value : projectsArray) {
|
||||||
|
if (!value.isObject())
|
||||||
|
continue;
|
||||||
|
const QJsonObject projectObj = value.toObject();
|
||||||
|
result.projects.append(projectFromJson(projectObj));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Error parseErrorMessage(const QString &message)
|
Error parseErrorMessage(const QString &message)
|
||||||
{
|
{
|
||||||
Error error;
|
Error error;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QMetaType>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
namespace GitLab {
|
namespace GitLab {
|
||||||
@@ -35,6 +37,26 @@ struct Error
|
|||||||
QString message;
|
QString message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PageInformation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int currentPage = -1;
|
||||||
|
int totalPages = -1;
|
||||||
|
int perPage = -1;
|
||||||
|
int total = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString name;
|
||||||
|
QString realname;
|
||||||
|
QString email;
|
||||||
|
Error error;
|
||||||
|
int id = -1;
|
||||||
|
bool bot = false;
|
||||||
|
};
|
||||||
|
|
||||||
class Project
|
class Project
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -42,18 +64,32 @@ public:
|
|||||||
QString displayName;
|
QString displayName;
|
||||||
QString pathName;
|
QString pathName;
|
||||||
QString visibility;
|
QString visibility;
|
||||||
|
QString httpUrl;
|
||||||
|
QString sshUrl;
|
||||||
Error error;
|
Error error;
|
||||||
int id = -1;
|
int id = -1;
|
||||||
int starCount = -1;
|
int starCount = -1;
|
||||||
int forkCount = -1;
|
int forkCount = -1;
|
||||||
int issuesCount = -1;
|
int issuesCount = -1;
|
||||||
int accessLevel = -1; // 40 maintainer, 30 developer, 20 reporter, 10 guest
|
int accessLevel = -1; // 50 owner, 40 maintainer, 30 developer, 20 reporter, 10 guest
|
||||||
|
};
|
||||||
|
|
||||||
|
class Projects
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QList<Project> projects;
|
||||||
|
Error error;
|
||||||
|
PageInformation pageInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace ResultParser {
|
namespace ResultParser {
|
||||||
|
|
||||||
|
User parseUser(const QByteArray &input);
|
||||||
Project parseProject(const QByteArray &input);
|
Project parseProject(const QByteArray &input);
|
||||||
|
Projects parseProjects(const QByteArray &input);
|
||||||
Error parseErrorMessage(const QString &message);
|
Error parseErrorMessage(const QString &message);
|
||||||
|
|
||||||
} // namespace ResultParser
|
} // namespace ResultParser
|
||||||
} // namespace GitLab
|
} // namespace GitLab
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(GitLab::Project)
|
||||||
|
|||||||
Reference in New Issue
Block a user