forked from qt-creator/qt-creator
Gerrit: Support REST query for HTTP servers
Change-Id: Icc164b9d84abe4efc34deaa5d19dca167fdb14e1 Reviewed-by: Tobias Hunger <tobias.hunger@qt.io> Reviewed-by: André Hartmann <aha_1980@gmx.de>
This commit is contained in:
committed by
Orgad Shaneh
parent
be204a125e
commit
f7a778690d
142
src/plugins/git/gerrit/authenticationdialog.cpp
Normal file
142
src/plugins/git/gerrit/authenticationdialog.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2017 Orgad Shaneh <orgads@gmail.com>.
|
||||
** 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 "authenticationdialog.h"
|
||||
#include "ui_authenticationdialog.h"
|
||||
#include "gerritparameters.h"
|
||||
|
||||
#include <utils/asconst.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QTextStream>
|
||||
|
||||
namespace Gerrit {
|
||||
namespace Internal {
|
||||
|
||||
static QString findEntry(const QString &line, const QString &type)
|
||||
{
|
||||
const QRegularExpression regexp("(?:^|\\s)" + type + "\\s(\\S+)");
|
||||
const QRegularExpressionMatch match = regexp.match(line);
|
||||
if (match.hasMatch())
|
||||
return match.captured(1);
|
||||
return QString();
|
||||
}
|
||||
|
||||
static bool replaceEntry(QString &line, const QString &type, const QString &value)
|
||||
{
|
||||
const QRegularExpression regexp("(?:^|\\s)" + type + "\\s(\\S+)");
|
||||
const QRegularExpressionMatch match = regexp.match(line);
|
||||
if (!match.hasMatch())
|
||||
return false;
|
||||
line.replace(match.capturedStart(1), match.capturedLength(1), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
AuthenticationDialog::AuthenticationDialog(GerritServer *server) :
|
||||
ui(new Ui::AuthenticationDialog),
|
||||
m_server(server)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->descriptionLabel->setText(ui->descriptionLabel->text().replace(
|
||||
"LINK_PLACEHOLDER", server->url() + "/#/settings/http-password"));
|
||||
ui->descriptionLabel->setOpenExternalLinks(true);
|
||||
ui->serverLineEdit->setText(server->host);
|
||||
ui->userLineEdit->setText(server->user.userName);
|
||||
m_netrcFileName = QLatin1String(Utils::HostOsInfo::isWindowsHost() ? "_netrc" : ".netrc");
|
||||
readExistingConf();
|
||||
|
||||
QPushButton *anonymous = ui->buttonBox->addButton(tr("Anonymous"), QDialogButtonBox::AcceptRole);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked,
|
||||
this, [this, anonymous](QAbstractButton *button) {
|
||||
if (button == anonymous)
|
||||
m_authenticated = false;
|
||||
});
|
||||
QPushButton *okButton = ui->buttonBox->button(QDialogButtonBox::Ok);
|
||||
okButton->setEnabled(false);
|
||||
connect(ui->passwordLineEdit, &QLineEdit::editingFinished, this, [this, server, okButton] {
|
||||
setupCredentials();
|
||||
const int result = server->testConnection();
|
||||
okButton->setEnabled(result == 200);
|
||||
});
|
||||
}
|
||||
|
||||
AuthenticationDialog::~AuthenticationDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void AuthenticationDialog::readExistingConf()
|
||||
{
|
||||
QFile netrcFile(QDir::homePath() + '/' + m_netrcFileName);
|
||||
if (!netrcFile.open(QFile::ReadOnly | QFile::Text))
|
||||
return;
|
||||
|
||||
QTextStream stream(&netrcFile);
|
||||
QString line;
|
||||
while (stream.readLineInto(&line)) {
|
||||
m_allMachines << line;
|
||||
const QString machine = findEntry(line, "machine");
|
||||
if (machine == m_server->host) {
|
||||
const QString login = findEntry(line, "login");
|
||||
const QString password = findEntry(line, "password");
|
||||
if (!login.isEmpty())
|
||||
ui->userLineEdit->setText(login);
|
||||
if (!password.isEmpty())
|
||||
ui->passwordLineEdit->setText(password);
|
||||
}
|
||||
}
|
||||
netrcFile.close();
|
||||
}
|
||||
|
||||
bool AuthenticationDialog::setupCredentials()
|
||||
{
|
||||
QString netrcContents;
|
||||
QTextStream out(&netrcContents);
|
||||
bool found = false;
|
||||
const QString user = ui->userLineEdit->text().trimmed();
|
||||
const QString password = ui->passwordLineEdit->text().trimmed();
|
||||
for (QString &line : m_allMachines) {
|
||||
const QString machine = findEntry(line, "machine");
|
||||
if (machine == m_server->host) {
|
||||
found = true;
|
||||
replaceEntry(line, "login", user);
|
||||
replaceEntry(line, "password", password);
|
||||
}
|
||||
out << line << endl;
|
||||
}
|
||||
if (!found)
|
||||
out << "machine " << m_server->host << " login " << user << " password " << password;
|
||||
Utils::FileSaver saver(m_netrcFileName, QFile::WriteOnly | QFile::Truncate | QFile::Text);
|
||||
saver.write(netrcContents.toUtf8());
|
||||
return saver.finalize();
|
||||
}
|
||||
|
||||
} // Internal
|
||||
} // Gerrit
|
61
src/plugins/git/gerrit/authenticationdialog.h
Normal file
61
src/plugins/git/gerrit/authenticationdialog.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2017 Orgad Shaneh <orgads@gmail.com>.
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef AUTHENTICATIONDIALOG_H
|
||||
#define AUTHENTICATIONDIALOG_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDialog>
|
||||
|
||||
namespace Gerrit {
|
||||
namespace Internal {
|
||||
|
||||
class GerritServer;
|
||||
|
||||
namespace Ui { class AuthenticationDialog; }
|
||||
|
||||
class AuthenticationDialog : public QDialog
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(Gerrit::Internal::AuthenticationDialog)
|
||||
|
||||
public:
|
||||
AuthenticationDialog(GerritServer *server);
|
||||
~AuthenticationDialog();
|
||||
bool isAuthenticated() const { return m_authenticated; }
|
||||
|
||||
private:
|
||||
void readExistingConf();
|
||||
bool setupCredentials();
|
||||
Ui::AuthenticationDialog *ui;
|
||||
GerritServer *m_server;
|
||||
QString m_netrcFileName;
|
||||
QStringList m_allMachines;
|
||||
bool m_authenticated = true;
|
||||
};
|
||||
|
||||
} // Internal
|
||||
} // Gerrit
|
||||
|
||||
#endif // AUTHENTICATIONDIALOG_H
|
127
src/plugins/git/gerrit/authenticationdialog.ui
Normal file
127
src/plugins/git/gerrit/authenticationdialog.ui
Normal file
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Gerrit::Internal::AuthenticationDialog</class>
|
||||
<widget class="QDialog" name="Gerrit::Internal::AuthenticationDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>334</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Authentication</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="descriptionLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>Gerrit server with HTTP was detected, but you need to setup credentials for it.</p><p>To get your password, <a href="LINK_PLACEHOLDER"><span style=" text-decoration: underline; color:#007af4;">click here</span></a> (sign in if needed). Click Generate Password if it is blank, and copy the user name and password to this form.</p><p>Choose Anonymous if you do not want authentication for this server. On this case, changes that require authentication (like draft changes, or private projects) will not be displayed.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="userLabel">
|
||||
<property name="text">
|
||||
<string>&User:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>userLineEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="userLineEdit"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="text">
|
||||
<string>&Password:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>passwordLineEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="passwordLineEdit"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="serverLabel">
|
||||
<property name="text">
|
||||
<string>Server:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="serverLineEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Gerrit::Internal::AuthenticationDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Gerrit::Internal::AuthenticationDialog</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>
|
@@ -4,7 +4,8 @@ SOURCES += $$PWD/gerritdialog.cpp \
|
||||
$$PWD/gerritplugin.cpp \
|
||||
$$PWD/gerritoptionspage.cpp \
|
||||
$$PWD/gerritpushdialog.cpp \
|
||||
$$PWD/branchcombobox.cpp
|
||||
$$PWD/branchcombobox.cpp \
|
||||
$$PWD/authenticationdialog.cpp
|
||||
|
||||
HEADERS += $$PWD/gerritdialog.h \
|
||||
$$PWD/gerritmodel.h \
|
||||
@@ -12,8 +13,10 @@ HEADERS += $$PWD/gerritdialog.h \
|
||||
$$PWD/gerritplugin.h \
|
||||
$$PWD/gerritoptionspage.h \
|
||||
$$PWD/gerritpushdialog.h \
|
||||
$$PWD/branchcombobox.h
|
||||
$$PWD/branchcombobox.h \
|
||||
$$PWD/authenticationdialog.h
|
||||
|
||||
FORMS += $$PWD/gerritdialog.ui \
|
||||
$$PWD/gerritpushdialog.ui
|
||||
$$PWD/gerritpushdialog.ui \
|
||||
$$PWD/authenticationdialog.ui
|
||||
|
||||
|
@@ -240,13 +240,9 @@ void GerritDialog::updateRemotes()
|
||||
while (mapIt.hasNext()) {
|
||||
mapIt.next();
|
||||
GerritServer server;
|
||||
if (!server.fillFromRemote(mapIt.value(), m_parameters->server.user.userName))
|
||||
if (!server.fillFromRemote(mapIt.value(), *m_parameters))
|
||||
continue;
|
||||
// Only Ssh is currently supported. In order to extend support for http[s],
|
||||
// we need to move this logic to the model, and attempt connection to each
|
||||
// remote (do it only on refresh, not on each path change)
|
||||
if (server.type == GerritServer::Ssh)
|
||||
addRemote(server, mapIt.key());
|
||||
addRemote(server, mapIt.key());
|
||||
}
|
||||
addRemote(m_parameters->server, tr("Fallback"));
|
||||
m_updatingRemotes = false;
|
||||
@@ -257,7 +253,7 @@ void GerritDialog::addRemote(const GerritServer &server, const QString &name)
|
||||
{
|
||||
for (int i = 0, total = m_ui->remoteComboBox->count(); i < total; ++i) {
|
||||
const GerritServer s = m_ui->remoteComboBox->itemData(i).value<GerritServer>();
|
||||
if (s.host == server.host)
|
||||
if (s == server)
|
||||
return;
|
||||
}
|
||||
m_ui->remoteComboBox->addItem(server.host + QString(" (%1)").arg(name),
|
||||
|
@@ -50,6 +50,7 @@
|
||||
#include <QTimer>
|
||||
#include <QApplication>
|
||||
#include <QFutureWatcher>
|
||||
#include <QUrl>
|
||||
|
||||
enum { debug = 0 };
|
||||
|
||||
@@ -199,7 +200,9 @@ QString GerritChange::filterString() const
|
||||
|
||||
QStringList GerritChange::gitFetchArguments(const GerritServer &server) const
|
||||
{
|
||||
return {"fetch", server.url() + '/' + project, currentPatchSet.ref};
|
||||
const QString url = currentPatchSet.url.isEmpty() ? server.url(true) + '/' + project
|
||||
: currentPatchSet.url;
|
||||
return {"fetch", url, currentPatchSet.ref};
|
||||
}
|
||||
|
||||
QString GerritChange::fullTitle() const
|
||||
@@ -258,13 +261,21 @@ QueryContext::QueryContext(const QString &query,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_binary = p->ssh;
|
||||
if (server.port)
|
||||
m_arguments << p->portFlag << QString::number(server.port);
|
||||
m_arguments << server.sshHostArgument() << "gerrit"
|
||||
<< "query" << "--dependencies"
|
||||
<< "--current-patch-set"
|
||||
<< "--format=JSON" << query;
|
||||
if (server.type == GerritServer::Ssh) {
|
||||
m_binary = p->ssh;
|
||||
if (server.port)
|
||||
m_arguments << p->portFlag << QString::number(server.port);
|
||||
m_arguments << server.hostArgument() << "gerrit"
|
||||
<< "query" << "--dependencies"
|
||||
<< "--current-patch-set"
|
||||
<< "--format=JSON" << query;
|
||||
} else {
|
||||
m_binary = p->curl;
|
||||
const QString url = server.restUrl() + "/changes/?q="
|
||||
+ QString::fromUtf8(QUrl::toPercentEncoding(query))
|
||||
+ "&o=CURRENT_REVISION&o=DETAILED_LABELS&o=DETAILED_ACCOUNTS";
|
||||
m_arguments = GerritServer::curlArguments() << url;
|
||||
}
|
||||
connect(&m_process, &QProcess::readyReadStandardError,
|
||||
this, &QueryContext::readyReadStandardError);
|
||||
connect(&m_process, &QProcess::readyReadStandardOutput,
|
||||
@@ -615,18 +626,178 @@ static GerritChangePtr parseSshOutput(const QJsonObject &object)
|
||||
return change;
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"kind": "gerritcodereview#change",
|
||||
"id": "qt-creator%2Fqt-creator~master~Icc164b9d84abe4efc34deaa5d19dca167fdb14e1",
|
||||
"project": "qt-creator/qt-creator",
|
||||
"branch": "master",
|
||||
"change_id": "Icc164b9d84abe4efc34deaa5d19dca167fdb14e1",
|
||||
"subject": "WIP: Gerrit: Support REST query for HTTP servers",
|
||||
"status": "NEW",
|
||||
"created": "2017-02-22 21:23:39.403000000",
|
||||
"updated": "2017-02-23 21:03:51.055000000",
|
||||
"reviewed": true,
|
||||
"mergeable": false,
|
||||
"_sortkey": "004368cf0002d84f",
|
||||
"_number": 186447,
|
||||
"owner": {
|
||||
"_account_id": 1000534,
|
||||
"name": "Orgad Shaneh",
|
||||
"email": "orgads@gmail.com"
|
||||
},
|
||||
"labels": {
|
||||
"Code-Review": {
|
||||
"all": [
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 1000009,
|
||||
"name": "Tobias Hunger",
|
||||
"email": "tobias.hunger@qt.io"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 1000528,
|
||||
"name": "André Hartmann",
|
||||
"email": "aha_1980@gmx.de"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 1000049,
|
||||
"name": "Qt Sanity Bot",
|
||||
"email": "qt_sanitybot@qt-project.org"
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"-2": "This shall not be merged",
|
||||
"-1": "I would prefer this is not merged as is",
|
||||
" 0": "No score",
|
||||
"+1": "Looks good to me, but someone else must approve",
|
||||
"+2": "Looks good to me, approved"
|
||||
}
|
||||
},
|
||||
"Sanity-Review": {
|
||||
"all": [
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 1000009,
|
||||
"name": "Tobias Hunger",
|
||||
"email": "tobias.hunger@qt.io"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 1000528,
|
||||
"name": "André Hartmann",
|
||||
"email": "aha_1980@gmx.de"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"_account_id": 1000049,
|
||||
"name": "Qt Sanity Bot",
|
||||
"email": "qt_sanitybot@qt-project.org"
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"-2": "Major sanity problems found",
|
||||
"-1": "Sanity problems found",
|
||||
" 0": "No sanity review",
|
||||
"+1": "Sanity review passed"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permitted_labels": {
|
||||
"Code-Review": [
|
||||
"-2",
|
||||
"-1",
|
||||
" 0",
|
||||
"+1",
|
||||
"+2"
|
||||
],
|
||||
"Sanity-Review": [
|
||||
"-2",
|
||||
"-1",
|
||||
" 0",
|
||||
"+1"
|
||||
]
|
||||
},
|
||||
"current_revision": "87916545e2974913d56f56c9f06fc3822a876aca",
|
||||
"revisions": {
|
||||
"87916545e2974913d56f56c9f06fc3822a876aca": {
|
||||
"draft": true,
|
||||
"_number": 2,
|
||||
"fetch": {
|
||||
"http": {
|
||||
"url": "https://codereview.qt-project.org/qt-creator/qt-creator",
|
||||
"ref": "refs/changes/47/186447/2"
|
||||
},
|
||||
"ssh": {
|
||||
"url": "ssh:// *:29418/qt-creator/qt-creator",
|
||||
"ref": "refs/changes/47/186447/2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
static GerritChangePtr parseRestOutput(const QJsonObject &object, const GerritServer &server)
|
||||
{
|
||||
GerritChangePtr change(new GerritChange);
|
||||
change->number = object.value("_number").toInt();
|
||||
change->url = QString("%1/%2").arg(server.url()).arg(change->number);
|
||||
change->title = object.value("subject").toString();
|
||||
change->owner = parseGerritUser(object.value("owner").toObject());
|
||||
change->project = object.value("project").toString();
|
||||
change->branch = object.value("branch").toString();
|
||||
change->status = object.value("status").toString();
|
||||
change->lastUpdated = QDateTime::fromString(object.value("updated").toString(),
|
||||
Qt::DateFormat::ISODate);
|
||||
// Read current patch set.
|
||||
const QJsonObject patchSet = object.value("revisions").toObject().begin().value().toObject();
|
||||
change->currentPatchSet.patchSetNumber = qMax(1, patchSet.value("number").toString().toInt());
|
||||
change->currentPatchSet.ref = patchSet.value("ref").toString();
|
||||
// Replace * in ssh://*:29418/qt-creator/qt-creator with the hostname
|
||||
change->currentPatchSet.url = patchSet.value("url").toString().replace('*', server.host);
|
||||
const QJsonObject labels = object.value("labels").toObject();
|
||||
for (auto it = labels.constBegin(), end = labels.constEnd(); it != end; ++it) {
|
||||
const QJsonArray all = it.value().toObject().value("all").toArray();
|
||||
for (const QJsonValue &av : all) {
|
||||
const QJsonObject ao = av.toObject();
|
||||
const int value = ao.value("value").toInt();
|
||||
if (!value)
|
||||
continue;
|
||||
GerritApproval approval;
|
||||
approval.reviewer = parseGerritUser(ao);
|
||||
approval.approval = value;
|
||||
approval.type = it.key();
|
||||
change->currentPatchSet.approvals.push_back(approval);
|
||||
}
|
||||
}
|
||||
std::stable_sort(change->currentPatchSet.approvals.begin(),
|
||||
change->currentPatchSet.approvals.end(),
|
||||
gerritApprovalLessThan);
|
||||
return change;
|
||||
}
|
||||
|
||||
static bool parseOutput(const QSharedPointer<GerritParameters> ¶meters,
|
||||
const GerritServer &server,
|
||||
const QByteArray &output,
|
||||
QList<GerritChangePtr> &result)
|
||||
{
|
||||
// The output consists of separate lines containing a document each
|
||||
// Add a comma after each line (except the last), and enclose it as an array
|
||||
QByteArray adaptedOutput = '[' + output + ']';
|
||||
adaptedOutput.replace('\n', ',');
|
||||
const int lastComma = adaptedOutput.lastIndexOf(',');
|
||||
if (lastComma >= 0)
|
||||
adaptedOutput[lastComma] = '\n';
|
||||
QByteArray adaptedOutput;
|
||||
if (server.type == GerritServer::Ssh) {
|
||||
// The output consists of separate lines containing a document each
|
||||
// Add a comma after each line (except the last), and enclose it as an array
|
||||
adaptedOutput = '[' + output + ']';
|
||||
adaptedOutput.replace('\n', ',');
|
||||
const int lastComma = adaptedOutput.lastIndexOf(',');
|
||||
if (lastComma >= 0)
|
||||
adaptedOutput[lastComma] = '\n';
|
||||
} else {
|
||||
adaptedOutput = output;
|
||||
// Strip first line, which is )]}'
|
||||
adaptedOutput.remove(0, adaptedOutput.indexOf("\n"));
|
||||
}
|
||||
bool res = true;
|
||||
|
||||
QJsonParseError error;
|
||||
@@ -647,7 +818,9 @@ static bool parseOutput(const QSharedPointer<GerritParameters> ¶meters,
|
||||
// Skip stats line: {"type":"stats","rowCount":9,"runTimeMilliseconds":13}
|
||||
if (object.contains("type"))
|
||||
continue;
|
||||
GerritChangePtr change = parseSshOutput(object);
|
||||
GerritChangePtr change =
|
||||
(server.type == GerritServer::Ssh ? parseSshOutput(object)
|
||||
: parseRestOutput(object, server));
|
||||
if (change->isValid()) {
|
||||
if (change->url.isEmpty()) // No "canonicalWebUrl" is in gerrit.config.
|
||||
change->url = defaultUrl(parameters, server, change->number);
|
||||
|
@@ -57,6 +57,7 @@ public:
|
||||
bool hasApproval(const GerritUser &user) const;
|
||||
int approvalLevel() const;
|
||||
|
||||
QString url;
|
||||
QString ref;
|
||||
int patchSetNumber;
|
||||
QList<GerritApproval> approvals;
|
||||
|
@@ -25,6 +25,10 @@
|
||||
|
||||
#include "gerritparameters.h"
|
||||
#include "gerritplugin.h"
|
||||
#include "authenticationdialog.h"
|
||||
#include "../gitplugin.h"
|
||||
#include "../gitclient.h"
|
||||
#include <coreplugin/shellcommand.h>
|
||||
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/pathchooser.h>
|
||||
@@ -32,6 +36,7 @@
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QRegularExpression>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
@@ -51,6 +56,7 @@ static const char curlKeyC[] = "Curl";
|
||||
static const char httpsKeyC[] = "Https";
|
||||
static const char defaultHostC[] = "codereview.qt-project.org";
|
||||
static const char savedQueriesKeyC[] = "SavedQueries";
|
||||
static const char accountUrlC[] = "/accounts/self";
|
||||
|
||||
static const char defaultPortFlag[] = "-p";
|
||||
|
||||
@@ -141,23 +147,44 @@ GerritParameters::GerritParameters()
|
||||
{
|
||||
}
|
||||
|
||||
QString GerritServer::sshHostArgument() const
|
||||
QString GerritServer::hostArgument() const
|
||||
{
|
||||
return user.userName.isEmpty() ? host : (user.userName + '@' + host);
|
||||
}
|
||||
|
||||
QString GerritServer::url() const
|
||||
QString GerritServer::url(bool withHttpUser) const
|
||||
{
|
||||
QString res = "ssh://" + sshHostArgument();
|
||||
QString protocol;
|
||||
switch (type) {
|
||||
case Ssh: protocol = "ssh"; break;
|
||||
case Http: protocol = "http"; break;
|
||||
case Https: protocol = "https"; break;
|
||||
}
|
||||
QString res = protocol + "://";
|
||||
if (type == Ssh || withHttpUser)
|
||||
res += hostArgument();
|
||||
else
|
||||
res += host;
|
||||
if (port)
|
||||
res += ':' + QString::number(port);
|
||||
if (type != Ssh)
|
||||
res += rootPath;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool GerritServer::fillFromRemote(const QString &remote, const QString &defaultUser)
|
||||
QString GerritServer::restUrl() const
|
||||
{
|
||||
QString res = url(true);
|
||||
if (type != Ssh && authenticated)
|
||||
res += "/a";
|
||||
return res;
|
||||
}
|
||||
|
||||
bool GerritServer::fillFromRemote(const QString &remote, const GerritParameters ¶meters)
|
||||
{
|
||||
static const QRegularExpression remotePattern(
|
||||
"^(?:(?<protocol>[^:]+)://)?(?:(?<user>[^@]+)@)?(?<host>[^:/]+)(?::(?<port>\\d+))?");
|
||||
"^(?:(?<protocol>[^:]+)://)?(?:(?<user>[^@]+)@)?(?<host>[^:/]+)"
|
||||
"(?::(?<port>\\d+))?:?(?<path>/.*)$");
|
||||
|
||||
// Skip local remotes (refer to the root or relative path)
|
||||
if (remote.isEmpty() || remote.startsWith('/') || remote.startsWith('.'))
|
||||
@@ -178,14 +205,95 @@ bool GerritServer::fillFromRemote(const QString &remote, const QString &defaultU
|
||||
else
|
||||
return false;
|
||||
const QString userName = match.captured("user");
|
||||
user.userName = userName.isEmpty() ? defaultUser : userName;
|
||||
user.userName = userName.isEmpty() ? parameters.server.user.userName : userName;
|
||||
host = match.captured("host");
|
||||
port = match.captured("port").toUShort();
|
||||
if (host.contains("github.com")) // Clearly not gerrit
|
||||
return false;
|
||||
if (type != GerritServer::Ssh) {
|
||||
curlBinary = parameters.curl;
|
||||
if (curlBinary.isEmpty() || !QFile::exists(curlBinary))
|
||||
return false;
|
||||
rootPath = match.captured("path");
|
||||
// Strip the last part of the path, which is always the repo name
|
||||
// The rest of the path needs to be inspected to find the root path
|
||||
// (can be http://example.net/review)
|
||||
ascendPath();
|
||||
if (!resolveRoot())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList GerritServer::curlArguments()
|
||||
{
|
||||
// -k - insecure - do not validate certificate
|
||||
// -f - fail silently on server error
|
||||
// -n - use credentials from ~/.netrc (or ~/_netrc on Windows)
|
||||
// -sS - silent, except server error (no progress)
|
||||
// --basic, --digest - try both authentication types
|
||||
return {"-kfnsS", "--basic", "--digest"};
|
||||
}
|
||||
|
||||
int GerritServer::testConnection()
|
||||
{
|
||||
static Git::Internal::GitClient *const client = Git::Internal::GitPlugin::client();
|
||||
const QStringList arguments = curlArguments() << (restUrl() + accountUrlC);
|
||||
const SynchronousProcessResponse resp = client->vcsFullySynchronousExec(
|
||||
QString(), FileName::fromString(curlBinary), arguments,
|
||||
Core::ShellCommand::NoOutput);
|
||||
if (resp.result == SynchronousProcessResponse::Finished) {
|
||||
QString output = resp.stdOut();
|
||||
output.remove(0, output.indexOf('\n')); // Strip first line
|
||||
QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8());
|
||||
if (!doc.isNull())
|
||||
user.fullName = doc.object().value("name").toString();
|
||||
return 200;
|
||||
}
|
||||
const QRegularExpression errorRegexp("returned error: (\\d+)");
|
||||
QRegularExpressionMatch match = errorRegexp.match(resp.stdErr());
|
||||
if (match.hasMatch())
|
||||
return match.captured(1).toInt();
|
||||
return 400;
|
||||
}
|
||||
|
||||
bool GerritServer::setupAuthentication()
|
||||
{
|
||||
AuthenticationDialog dialog(this);
|
||||
if (!dialog.exec())
|
||||
return false;
|
||||
authenticated = dialog.isAuthenticated();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GerritServer::ascendPath()
|
||||
{
|
||||
const int lastSlash = rootPath.lastIndexOf('/');
|
||||
if (lastSlash == -1)
|
||||
return false;
|
||||
rootPath = rootPath.left(lastSlash);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GerritServer::resolveRoot()
|
||||
{
|
||||
for (;;) {
|
||||
switch (testConnection()) {
|
||||
case 200:
|
||||
return true;
|
||||
case 401:
|
||||
return setupAuthentication();
|
||||
case 404:
|
||||
if (!ascendPath())
|
||||
return false;
|
||||
break;
|
||||
default: // unknown error - fail
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GerritParameters::equals(const GerritParameters &rhs) const
|
||||
{
|
||||
return server == rhs.server && ssh == rhs.ssh && curl == rhs.curl && https == rhs.https;
|
||||
|
@@ -32,6 +32,8 @@ QT_FORWARD_DECLARE_CLASS(QSettings)
|
||||
namespace Gerrit {
|
||||
namespace Internal {
|
||||
|
||||
class GerritParameters;
|
||||
|
||||
class GerritUser
|
||||
{
|
||||
public:
|
||||
@@ -55,14 +57,25 @@ public:
|
||||
GerritServer();
|
||||
GerritServer(const QString &host, unsigned short port, const QString &userName, HostType type);
|
||||
bool operator==(const GerritServer &other) const;
|
||||
QString sshHostArgument() const;
|
||||
QString url() const;
|
||||
bool fillFromRemote(const QString &remote, const QString &defaultUser);
|
||||
QString hostArgument() const;
|
||||
QString url(bool withHttpUser = false) const;
|
||||
QString restUrl() const;
|
||||
bool fillFromRemote(const QString &remote, const GerritParameters ¶meters);
|
||||
int testConnection();
|
||||
static QStringList curlArguments();
|
||||
|
||||
QString host;
|
||||
GerritUser user;
|
||||
QString rootPath; // for http
|
||||
unsigned short port = 0;
|
||||
HostType type = Ssh;
|
||||
bool authenticated = true;
|
||||
|
||||
private:
|
||||
QString curlBinary;
|
||||
bool setupAuthentication();
|
||||
bool ascendPath();
|
||||
bool resolveRoot();
|
||||
};
|
||||
|
||||
class GerritParameters
|
||||
|
@@ -77,6 +77,9 @@ QtcPlugin {
|
||||
name: "Gerrit"
|
||||
prefix: "gerrit/"
|
||||
files: [
|
||||
"authenticationdialog.cpp",
|
||||
"authenticationdialog.h",
|
||||
"authenticationdialog.ui",
|
||||
"branchcombobox.cpp",
|
||||
"branchcombobox.h",
|
||||
"gerritdialog.cpp",
|
||||
|
Reference in New Issue
Block a user