First integration of libgit2

This commit is contained in:
Matthias Koefferlein 2023-10-25 00:15:52 +02:00
parent 01c19048aa
commit 40bdd63ee4
17 changed files with 624 additions and 50 deletions

View File

@ -39,6 +39,7 @@ HAVE_QT=1
HAVE_PNG=0 HAVE_PNG=0
HAVE_CURL=0 HAVE_CURL=0
HAVE_EXPAT=0 HAVE_EXPAT=0
HAVE_GIT2=1
RUBYINCLUDE="" RUBYINCLUDE=""
RUBYINCLUDE2="" RUBYINCLUDE2=""
@ -209,6 +210,9 @@ while [ "$*" != "" ]; do
-libexpat) -libexpat)
HAVE_EXPAT=1 HAVE_EXPAT=1
;; ;;
-nolibgit2)
HAVE_GIT2=0
;;
-qt5) -qt5)
echo "*** WARNING: -qt5 option is ignored - Qt version is auto-detected now." echo "*** WARNING: -qt5 option is ignored - Qt version is auto-detected now."
;; ;;
@ -495,6 +499,9 @@ fi
if [ $HAVE_PNG != 0 ]; then if [ $HAVE_PNG != 0 ]; then
echo " Uses libpng for PNG generation" echo " Uses libpng for PNG generation"
fi fi
if [ $HAVE_GIT2 != 0 ]; then
echo " Uses libgit2 for Git access"
fi
if [ "$RPATH" = "" ]; then if [ "$RPATH" = "" ]; then
RPATH="$BIN" RPATH="$BIN"
fi fi
@ -578,6 +585,7 @@ echo " HAVE_64BIT_COORD=$HAVE_64BIT_COORD"
echo " HAVE_CURL=$HAVE_CURL" echo " HAVE_CURL=$HAVE_CURL"
echo " HAVE_PNG=$HAVE_PNG" echo " HAVE_PNG=$HAVE_PNG"
echo " HAVE_EXPAT=$HAVE_EXPAT" echo " HAVE_EXPAT=$HAVE_EXPAT"
echo " HAVE_GIT2=$HAVE_GIT2"
echo " RPATH=$RPATH" echo " RPATH=$RPATH"
mkdir -p $BUILD mkdir -p $BUILD
@ -650,6 +658,7 @@ qmake_options=(
HAVE_CURL="$HAVE_CURL" HAVE_CURL="$HAVE_CURL"
HAVE_EXPAT="$HAVE_EXPAT" HAVE_EXPAT="$HAVE_EXPAT"
HAVE_PNG="$HAVE_PNG" HAVE_PNG="$HAVE_PNG"
HAVE_GIT2="$HAVE_GIT2"
PREFIX="$BIN" PREFIX="$BIN"
RPATH="$RPATH" RPATH="$RPATH"
KLAYOUT_VERSION="$KLAYOUT_VERSION" KLAYOUT_VERSION="$KLAYOUT_VERSION"

View File

@ -103,6 +103,15 @@ equals(HAVE_PTHREADS, "1") {
DEFINES += HAVE_PTHREADS DEFINES += HAVE_PTHREADS
} }
equals(HAVE_GIT2, "1") {
!isEmpty(BITS_PATH) {
include($$BITS_PATH/git2/git2.pri)
} else {
LIBS += -lgit2
}
DEFINES += HAVE_GIT2
}
equals(HAVE_RUBY, "1") { equals(HAVE_RUBY, "1") {
!isEmpty(BITS_PATH) { !isEmpty(BITS_PATH) {
include($$BITS_PATH/ruby/ruby.pri) include($$BITS_PATH/ruby/ruby.pri)

View File

@ -770,7 +770,7 @@ ApplicationBase::init_app ()
size_t local_folders = (lay::get_appdata_path ().empty () ? 0 : 1); size_t local_folders = (lay::get_appdata_path ().empty () ? 0 : 1);
for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) { for (std::vector <std::string>::const_iterator p = m_klayout_path.begin (); p != m_klayout_path.end (); ++p) {
if (p - m_klayout_path.begin () < local_folders) { if (size_t (p - m_klayout_path.begin ()) < local_folders) {
mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false); mc->add_path (*p, tl::to_string (QObject::tr ("Local")), std::string (), false);
} else if (m_klayout_path.size () == 1 + local_folders) { } else if (m_klayout_path.size () == 1 + local_folders) {
mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true); mc->add_path (*p, tl::to_string (QObject::tr ("Global")), std::string (), true);

View File

@ -152,7 +152,7 @@ void init (const std::vector<std::string> &_paths)
try { try {
s_plugins.push_back (do_load_plugin (imp)); s_plugins.push_back (do_load_plugin (imp));
modules.insert (*im); modules.insert (*im);
} catch (tl::Exception (&ex)) { } catch (tl::Exception &ex) {
tl::error << ex.msg (); tl::error << ex.msg ();
} }
} }

View File

@ -21,11 +21,16 @@
*/ */
#include "laySalt.h" #include "laySalt.h"
#include "tlString.h" #include "tlString.h"
#include "tlFileUtils.h" #include "tlFileUtils.h"
#include "tlLog.h" #include "tlLog.h"
#include "tlInternational.h" #include "tlInternational.h"
#include "tlWebDAV.h" #include "tlWebDAV.h"
#if defined(HAVE_GIT2)
# include "tlGit.h"
#endif
#include "lymMacro.h" #include "lymMacro.h"
#include <QFileInfo> #include <QFileInfo>
@ -485,9 +490,23 @@ Salt::create_grain (const SaltGrain &templ, SaltGrain &target, double timeout, t
if (templ.url ().find ("http:") == 0 || templ.url ().find ("https:") == 0) { if (templ.url ().find ("http:") == 0 || templ.url ().find ("https:") == 0) {
// otherwise download from the URL // otherwise download from the URL using Git or SVN
tl::info << QObject::tr ("Downloading package from '%1' to '%2' ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ()));
res = tl::WebDAVObject::download (templ.url (), target.path (), timeout, callback); if (templ.protocol () == Git) {
#if defined(HAVE_GIT2)
tl::info << QObject::tr ("Downloading package from '%1' to '%2' using Git protocol ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ()));
res = tl::GitObject::download (templ.url (), target.path (), templ.branch (), timeout, callback);
#else
throw tl::Exception (tl::to_string (QObject::tr ("Unable to install package '%1' - git protocol not compiled in").arg (tl::to_qstring (target.name ()))));
#endif
} else if (templ.protocol () == WebDAV || templ.protocol () == DefaultProtocol) {
tl::info << QObject::tr ("Downloading package from '%1' to '%2' using SVN/WebDAV protocol ..").arg (tl::to_qstring (templ.url ())).arg (tl::to_qstring (target.path ()));
res = tl::WebDAVObject::download (templ.url (), target.path (), timeout, callback);
}
} else { } else {

View File

@ -203,11 +203,40 @@ SaltController::install_packages (const std::vector<std::string> &packages, bool
} }
if (n.find ("http:") == 0 || n.find ("https:") == 0 || n.find ("file:") == 0 || n[0] == '/' || n[0] == '\\') { if (n.find ("http:") == 0 || n.find ("https:") == 0 || n.find ("file:") == 0 || n[0] == '/' || n[0] == '\\') {
// its a URL // its a URL
manager.register_download (std::string (), std::string (), n, v); manager.register_download (std::string (), std::string (), n, DefaultProtocol, std::string (), v);
} else if (n.find ("git@") == 0) {
// git protocol:
// "git@<url>"
// "git@<url>[<branch>]"
std::string url (n, 4);
size_t br = url.find ("[");
std::string branch;
if (br != std::string::npos && url.back () == ']') {
branch = std::string (url, br + 1, url.size () - br - 2);
url = std::string (url, 0, br);
}
manager.register_download (std::string (), std::string (), url, Git, branch, v);
} else if (n.find ("svn@") == 0) {
// svn protocol:
// "svn@<url>"
std::string url (n, 4);
// its a URL
manager.register_download (std::string (), std::string (), url, WebDAV, std::string (), v);
} else { } else {
// its a plain name // its a plain name
manager.register_download (n, std::string (), std::string (), v); manager.register_download (n, std::string (), std::string (), DefaultProtocol, std::string (), v);
} }
} }

View File

@ -58,7 +58,7 @@ ConfirmationDialog::ConfirmationDialog (QWidget *parent)
} }
void void
ConfirmationDialog::add_info (const std::string &name, bool update, const std::string &version, const std::string &url) ConfirmationDialog::add_info (const std::string &name, bool update, const std::string &version, const std::string &url, Protocol protocol, const std::string &branch)
{ {
QTreeWidgetItem *item = new QTreeWidgetItem (list); QTreeWidgetItem *item = new QTreeWidgetItem (list);
m_items_by_name.insert (std::make_pair (name, item)); m_items_by_name.insert (std::make_pair (name, item));
@ -68,7 +68,18 @@ ConfirmationDialog::add_info (const std::string &name, bool update, const std::s
item->setText (0, tl::to_qstring (name)); item->setText (0, tl::to_qstring (name));
item->setText (1, update ? tr ("UPDATE") : tr ("INSTALL")); item->setText (1, update ? tr ("UPDATE") : tr ("INSTALL"));
item->setText (2, tl::to_qstring (version)); item->setText (2, tl::to_qstring (version));
item->setText (3, tl::to_qstring (url));
if (protocol == WebDAV) {
item->setText (3, tl::to_qstring ("svn@" + url));
} else if (protocol == Git) {
if (branch.empty ()) {
item->setText (3, tl::to_qstring ("git@" + url));
} else {
item->setText (3, tl::to_qstring ("git@" + url + "[" + branch + "]"));
}
} else {
item->setText (3, tl::to_qstring (url));
}
for (int column = 0; column < list->colorCount (); ++column) { for (int column = 0; column < list->colorCount (); ++column) {
item->setData (column, Qt::ForegroundRole, QVariant (QBrush (update ? QColor (Qt::blue) : QColor (Qt::black)))); item->setData (column, Qt::ForegroundRole, QVariant (QBrush (update ? QColor (Qt::blue) : QColor (Qt::black))));
@ -169,9 +180,9 @@ SaltDownloadManager::SaltDownloadManager ()
} }
void void
SaltDownloadManager::register_download (const std::string &name, const std::string &token, const std::string &url, const std::string &version) SaltDownloadManager::register_download (const std::string &name, const std::string &token, const std::string &url, Protocol protocol, const std::string &branch, const std::string &version)
{ {
m_registry.push_back (Descriptor (name, token, url, version)); m_registry.push_back (Descriptor (name, token, url, protocol, branch, version));
} }
void void
@ -244,7 +255,7 @@ SaltDownloadManager::compute_list (const lay::Salt &salt, const lay::Salt &salt_
if (tl::verbosity() >= 20) { if (tl::verbosity() >= 20) {
tl::log << "Considering for update as dependency: " << d->name << " (" << d->version << ") with URL " << d->url; tl::log << "Considering for update as dependency: " << d->name << " (" << d->version << ") with URL " << d->url;
} }
m_registry.push_back (Descriptor (d->name, std::string (), d->url, d->version)); m_registry.push_back (Descriptor (d->name, std::string (), d->url, d->protocol, d->branch, d->version));
} else { } else {
if (tl::verbosity() >= 20) { if (tl::verbosity() >= 20) {
@ -257,7 +268,7 @@ SaltDownloadManager::compute_list (const lay::Salt &salt, const lay::Salt &salt_
if (tl::verbosity() >= 20) { if (tl::verbosity() >= 20) {
tl::log << "Considering for download as dependency: " << d->name << " (" << d->version << ") with URL " << d->url; tl::log << "Considering for download as dependency: " << d->name << " (" << d->version << ") with URL " << d->url;
} }
m_registry.push_back (Descriptor (d->name, std::string (), d->url, d->version)); m_registry.push_back (Descriptor (d->name, std::string (), d->url, d->protocol, d->branch, d->version));
} }
@ -330,7 +341,7 @@ SaltDownloadManager::fetch_missing (const lay::Salt &salt, const lay::Salt &salt
} }
try { try {
p->grain = SaltGrain::from_url (p->url); p->grain = SaltGrain::from_url (p->url, p->protocol, p->branch);
} catch (tl::Exception &ex) { } catch (tl::Exception &ex) {
throw tl::Exception (tl::to_string (QObject::tr ("Error fetching spec file for package '%1': %2").arg (tl::to_qstring (p->name)).arg (tl::to_qstring (ex.msg ())))); throw tl::Exception (tl::to_string (QObject::tr ("Error fetching spec file for package '%1': %2").arg (tl::to_qstring (p->name)).arg (tl::to_qstring (ex.msg ()))));
} }
@ -387,7 +398,7 @@ SaltDownloadManager::make_confirmation_dialog (QWidget *parent, const lay::Salt
const lay::SaltGrain *g = salt.grain_by_name (p->name); const lay::SaltGrain *g = salt.grain_by_name (p->name);
if (g) { if (g) {
// \342\206\222 is UTF-8 "right arrow" // \342\206\222 is UTF-8 "right arrow"
dialog->add_info (p->name, true, g->version () + " \342\206\222 " + p->version, p->url); dialog->add_info (p->name, true, g->version () + " \342\206\222 " + p->version, p->url, p->protocol, p->branch);
} }
} }
@ -395,7 +406,7 @@ SaltDownloadManager::make_confirmation_dialog (QWidget *parent, const lay::Salt
for (std::vector<Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) { for (std::vector<Descriptor>::const_iterator p = m_registry.begin (); p != m_registry.end (); ++p) {
const lay::SaltGrain *g = salt.grain_by_name (p->name); const lay::SaltGrain *g = salt.grain_by_name (p->name);
if (!g) { if (!g) {
dialog->add_info (p->name, false, p->version, p->url); dialog->add_info (p->name, false, p->version, p->url, p->protocol, p->branch);
} }
} }

View File

@ -53,7 +53,7 @@ Q_OBJECT
public: public:
ConfirmationDialog (QWidget *parent); ConfirmationDialog (QWidget *parent);
void add_info (const std::string &name, bool update, const std::string &version, const std::string &url); void add_info (const std::string &name, bool update, const std::string &version, const std::string &url, Protocol protocol, const std::string &branch);
bool is_confirmed () const { return m_confirmed; } bool is_confirmed () const { return m_confirmed; }
bool is_cancelled () const { return m_cancelled; } bool is_cancelled () const { return m_cancelled; }
@ -108,7 +108,7 @@ public:
* *
* The target directory can be empty. In this case, the downloader will pick an appropriate one. * The target directory can be empty. In this case, the downloader will pick an appropriate one.
*/ */
void register_download (const std::string &name, const std::string &token, const std::string &url, const std::string &version); void register_download (const std::string &name, const std::string &token, const std::string &url, Protocol protocol, const std::string &branch, const std::string &version);
/** /**
* @brief Computes the dependencies after all required packages have been registered * @brief Computes the dependencies after all required packages have been registered
@ -145,8 +145,8 @@ public:
private: private:
struct Descriptor struct Descriptor
{ {
Descriptor (const std::string &_name, const std::string &_token, const std::string &_url, const std::string &_version) Descriptor (const std::string &_name, const std::string &_token, const std::string &_url, Protocol _protocol, const std::string &_branch, const std::string &_version)
: name (_name), token (_token), url (_url), version (_version), downloaded (false) : name (_name), token (_token), url (_url), protocol (_protocol), branch (_branch), version (_version), downloaded (false)
{ } { }
bool operator< (const Descriptor &other) const bool operator< (const Descriptor &other) const
@ -170,6 +170,8 @@ private:
std::string name; std::string name;
std::string token; std::string token;
std::string url; std::string url;
Protocol protocol;
std::string branch;
std::string version; std::string version;
bool downloaded; bool downloaded;
lay::SaltGrain grain; lay::SaltGrain grain;

View File

@ -25,8 +25,11 @@
#include "tlString.h" #include "tlString.h"
#include "tlXMLParser.h" #include "tlXMLParser.h"
#include "tlHttpStream.h" #include "tlHttpStream.h"
#include "tlWebDAV.h"
#include "tlFileUtils.h" #include "tlFileUtils.h"
#include "tlWebDAV.h"
#if defined(HAVE_GIT2)
# include "tlGit.h"
#endif
#include <memory> #include <memory>
#include <QDir> #include <QDir>
@ -41,7 +44,7 @@ namespace lay
static const std::string grain_filename = "grain.xml"; static const std::string grain_filename = "grain.xml";
SaltGrain::SaltGrain () SaltGrain::SaltGrain ()
: m_hidden (false) : m_protocol (DefaultProtocol), m_hidden (false)
{ {
// .. nothing yet .. // .. nothing yet ..
} }
@ -65,7 +68,10 @@ SaltGrain::operator== (const SaltGrain &other) const
m_license == other.m_license && m_license == other.m_license &&
m_hidden == other.m_hidden && m_hidden == other.m_hidden &&
m_authored_time == other.m_authored_time && m_authored_time == other.m_authored_time &&
m_installed_time == other.m_installed_time; m_installed_time == other.m_installed_time &&
m_protocol == other.m_protocol &&
m_branch == other.m_branch
;
} }
void void
@ -110,6 +116,18 @@ SaltGrain::set_url (const std::string &u)
m_url = u; m_url = u;
} }
void
SaltGrain::set_branch (const std::string &b)
{
m_branch = b;
}
void
SaltGrain::set_protocol (const Protocol &p)
{
m_protocol = p;
}
void void
SaltGrain::set_title (const std::string &t) SaltGrain::set_title (const std::string &t)
{ {
@ -253,18 +271,10 @@ SaltGrain::compare_versions (const std::string &v1, const std::string &v2)
} }
} }
std::string const std::string &
SaltGrain::spec_url (const std::string &url) SaltGrain::spec_file ()
{ {
std::string res = url; return grain_filename;
if (! res.empty()) {
// TODO: use system path separator unless this is a URL
if (res [res.size () - 1] != '/') {
res += "/";
}
res += grain_filename;
}
return res;
} }
bool bool
@ -400,6 +410,30 @@ struct ImageConverter
} }
}; };
struct ProtocolConverter
{
std::string to_string (const Protocol &protocol) const
{
if (protocol == lay::WebDAV) {
return std::string ("svn");
} else if (protocol == lay::Git) {
return std::string ("git");
} else {
return std::string ();
}
}
void from_string (const std::string &s, Protocol &res) const
{
res = lay::DefaultProtocol;
if (s == "svn" || s == "SVN" || s == "WebDAV") {
res = lay::WebDAV;
} else if (s == "git" || s == "Git" || s == "GIT") {
res = lay::Git;
}
}
};
static tl::XMLElementList *sp_xml_elements = 0; static tl::XMLElementList *sp_xml_elements = 0;
tl::XMLElementList & tl::XMLElementList &
@ -416,6 +450,8 @@ SaltGrain::xml_elements ()
tl::make_member (&SaltGrain::doc, &SaltGrain::set_doc, "doc") + tl::make_member (&SaltGrain::doc, &SaltGrain::set_doc, "doc") +
tl::make_member (&SaltGrain::doc_url, &SaltGrain::set_doc_url, "doc-url") + tl::make_member (&SaltGrain::doc_url, &SaltGrain::set_doc_url, "doc-url") +
tl::make_member (&SaltGrain::url, &SaltGrain::set_url, "url") + tl::make_member (&SaltGrain::url, &SaltGrain::set_url, "url") +
tl::make_member (&SaltGrain::branch, &SaltGrain::set_branch, "branch") +
tl::make_member (&SaltGrain::protocol, &SaltGrain::set_protocol, "protocol", ProtocolConverter ()) +
tl::make_member (&SaltGrain::license, &SaltGrain::set_license, "license") + tl::make_member (&SaltGrain::license, &SaltGrain::set_license, "license") +
tl::make_member (&SaltGrain::author, &SaltGrain::set_author, "author") + tl::make_member (&SaltGrain::author, &SaltGrain::set_author, "author") +
tl::make_member (&SaltGrain::author_contact, &SaltGrain::set_author_contact, "author-contact") + tl::make_member (&SaltGrain::author_contact, &SaltGrain::set_author_contact, "author-contact") +
@ -426,6 +462,8 @@ SaltGrain::xml_elements ()
tl::make_element (&SaltGrain::begin_dependencies, &SaltGrain::end_dependencies, &SaltGrain::add_dependency, "depends", tl::make_element (&SaltGrain::begin_dependencies, &SaltGrain::end_dependencies, &SaltGrain::add_dependency, "depends",
tl::make_member (&SaltGrainDependency::name, "name") + tl::make_member (&SaltGrainDependency::name, "name") +
tl::make_member (&SaltGrainDependency::url, "url") + tl::make_member (&SaltGrainDependency::url, "url") +
tl::make_member (&SaltGrainDependency::protocol, "protocol", ProtocolConverter ()) +
tl::make_member (&SaltGrainDependency::branch, "branch") +
tl::make_member (&SaltGrainDependency::version, "version") tl::make_member (&SaltGrainDependency::version, "version")
) )
); );
@ -512,7 +550,7 @@ SaltGrain::from_path (const std::string &path)
} }
tl::InputStream * tl::InputStream *
SaltGrain::stream_from_url (std::string &url, double timeout, tl::InputHttpStreamCallback *callback) SaltGrain::stream_from_url (std::string &url, Protocol protocol, const std::string &branch, double timeout, tl::InputHttpStreamCallback *callback)
{ {
if (url.empty ()) { if (url.empty ()) {
throw tl::Exception (tl::to_string (QObject::tr ("No download link available"))); throw tl::Exception (tl::to_string (QObject::tr ("No download link available")));
@ -533,19 +571,30 @@ SaltGrain::stream_from_url (std::string &url, double timeout, tl::InputHttpStrea
} }
std::string spec_url = SaltGrain::spec_url (url); if (url.find ("http:") == 0 || url.find ("https:") == 0) {
if (spec_url.find ("http:") == 0 || spec_url.find ("https:") == 0) {
return tl::WebDAVObject::download_item (spec_url, timeout, callback); if (protocol == lay::Git) {
#if defined(HAVE_GIT2)
return tl::GitObject::download_item (url, SaltGrain::spec_file (), branch, timeout, callback);
#else
throw tl::Exception (tl::to_string (QObject::tr ("Cannot download from Git - Git support not compiled in")));
#endif
} else {
return tl::WebDAVObject::download_item (url + "/" + SaltGrain::spec_file (), timeout, callback);
}
} else { } else {
return new tl::InputStream (spec_url);
return new tl::InputStream (url);
} }
} }
SaltGrain SaltGrain
SaltGrain::from_url (const std::string &url_in, double timeout, tl::InputHttpStreamCallback *callback) SaltGrain::from_url (const std::string &url_in, Protocol protocol, const std::string &branch, double timeout, tl::InputHttpStreamCallback *callback)
{ {
std::string url = url_in; std::string url = url_in;
std::unique_ptr<tl::InputStream> stream (stream_from_url (url, timeout, callback)); std::unique_ptr<tl::InputStream> stream (stream_from_url (url, protocol, branch, timeout, callback));
SaltGrain g; SaltGrain g;
g.load (*stream); g.load (*stream);

View File

@ -39,6 +39,15 @@ namespace tl
namespace lay namespace lay
{ {
/**
* @brief An enum describing the protocol to use for download
*/
enum Protocol {
DefaultProtocol = 0,
WebDAV = 1,
Git = 2
};
/** /**
* @brief A descriptor for one dependency * @brief A descriptor for one dependency
* A dependency can be specified either through a name (see name property) * A dependency can be specified either through a name (see name property)
@ -49,8 +58,14 @@ namespace lay
*/ */
struct SaltGrainDependency struct SaltGrainDependency
{ {
SaltGrainDependency ()
: protocol (DefaultProtocol)
{ }
std::string name; std::string name;
std::string url; std::string url;
Protocol protocol;
std::string branch;
std::string version; std::string version;
bool operator== (const SaltGrainDependency &other) const bool operator== (const SaltGrainDependency &other) const
@ -341,6 +356,32 @@ public:
*/ */
void set_url (const std::string &u); void set_url (const std::string &u);
/**
* @brief Gets the download protocol
*/
const Protocol &protocol () const
{
return m_protocol;
}
/**
* @brief Sets the download protocol
*/
void set_protocol (const Protocol &p);
/**
* @brief Gets the Git branch
*/
const std::string &branch () const
{
return m_branch;
}
/**
* @brief Sets the Git branch
*/
void set_branch (const std::string &b);
/** /**
* @brief Gets a value indicating whether the grain is hidden * @brief Gets a value indicating whether the grain is hidden
* A grain can be hidden (in Salt.Mine) if it's a pure dependency package * A grain can be hidden (in Salt.Mine) if it's a pure dependency package
@ -466,21 +507,23 @@ public:
* This method will return a grain constructed from the downloaded data. * This method will return a grain constructed from the downloaded data.
* The data is read from "URL/grain.xml". This method will throw an * The data is read from "URL/grain.xml". This method will throw an
* exception if an error occurs during reading. * exception if an error occurs during reading.
* protocol is the protocol to use and branch the Git branch.
*/ */
static SaltGrain from_url (const std::string &url, double timeout = 60.0, tl::InputHttpStreamCallback *callback = 0); static SaltGrain from_url (const std::string &url, Protocol protocol, const std::string &branch, double timeout = 60.0, tl::InputHttpStreamCallback *callback = 0);
/** /**
* @brief Returns a stream prepared for downloading the grain * @brief Returns a stream prepared for downloading the grain
* The stream is a new'd object and needs to be deleted by the caller. * The stream is a new'd object and needs to be deleted by the caller.
* "url" is the download URL on input and gets modified to match the * "url" is the download URL on input and gets modified to match the
* actual URL if it is a relative one. * actual URL if it is a relative one.
* protocol is the protocol to use and branch the Git branch.
*/ */
static tl::InputStream *stream_from_url (std::string &url, double timeout = 60.0, tl::InputHttpStreamCallback *callback = 0); static tl::InputStream *stream_from_url (std::string &url, Protocol protocol, const std::string &branch, double timeout = 60.0, tl::InputHttpStreamCallback *callback = 0);
/** /**
* @brief Forms the spec file download URL from a given download URL * @brief Gets the name of the spec file ("grain.xml")
*/ */
static std::string spec_url (const std::string &url); static const std::string &spec_file ();
/** /**
* @brief Returns a value indicating whether the given path represents is a grain * @brief Returns a value indicating whether the given path represents is a grain
@ -499,6 +542,8 @@ private:
std::string m_author; std::string m_author;
std::string m_author_contact; std::string m_author_contact;
std::string m_license; std::string m_license;
Protocol m_protocol;
std::string m_branch;
bool m_hidden; bool m_hidden;
QDateTime m_authored_time, m_installed_time; QDateTime m_authored_time, m_installed_time;
QImage m_icon, m_screenshot; QImage m_icon, m_screenshot;

View File

@ -586,7 +586,7 @@ SaltGrainPropertiesDialog::accept ()
if (!d->url.empty ()) { if (!d->url.empty ()) {
SaltGrain gdep; SaltGrain gdep;
try { try {
gdep = SaltGrain::from_url (d->url); gdep = SaltGrain::from_url (d->url, d->protocol, d->branch);
if (gdep.name () != d->name) { if (gdep.name () != d->name) {
dependencies_alert->error () << tr ("Package name obtained from download URL is not the expected name.") << tl::endl dependencies_alert->error () << tr ("Package name obtained from download URL is not the expected name.") << tl::endl
<< tr ("Downloaded name: ") << gdep.name () << tl::endl << tr ("Downloaded name: ") << gdep.name () << tl::endl

View File

@ -35,6 +35,7 @@
#include "pya.h" #include "pya.h"
#include <QTextDocument> #include <QTextDocument>
#include <QApplication>
#include <QPainter> #include <QPainter>
#include <QDir> #include <QDir>
#include <QTextStream> #include <QTextStream>
@ -682,7 +683,7 @@ BEGIN_PROTECTED
SaltGrain *g = model->grain_from_index (index); SaltGrain *g = model->grain_from_index (index);
// NOTE: checking for valid_name prevents bad entries inside the download list // NOTE: checking for valid_name prevents bad entries inside the download list
if (g && model->is_marked (g->name ()) && SaltGrain::valid_name (g->name ())) { if (g && model->is_marked (g->name ()) && SaltGrain::valid_name (g->name ())) {
manager.register_download (g->name (), g->token (), g->url (), g->version ()); manager.register_download (g->name (), g->token (), g->url (), g->protocol (), g->branch (), g->version ());
any = true; any = true;
} }
} }
@ -1133,6 +1134,24 @@ BEGIN_PROTECTED
END_PROTECTED END_PROTECTED
} }
namespace
{
/**
* @brief A callback to keep the UI alive (mainly used for Git grain retrieval)
*/
class ProcessEventCallback
: public tl::InputHttpStreamCallback
{
public:
virtual void wait_for_input ()
{
QApplication::processEvents (QEventLoop::ExcludeUserInputEvents);
}
};
}
void void
SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, SaltGrainDetailsTextWidget *details) SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, SaltGrainDetailsTextWidget *details)
{ {
@ -1166,16 +1185,22 @@ SaltManagerDialog::get_remote_grain_info (lay::SaltGrain *g, SaltGrainDetailsTex
"</body>" "</body>"
"</html>" "</html>"
) )
.arg (tl::to_qstring (SaltGrain::spec_url (g->url ()))); .arg (tl::to_qstring (g->url ()));
details->setHtml (html); details->setHtml (html);
std::string url = g->url (); std::string url = g->url ();
Protocol protocol = g->protocol ();
std::string branch = g->branch ();
m_downloaded_grain.reset (new SaltGrain ()); m_downloaded_grain.reset (new SaltGrain ());
m_downloaded_grain->set_url (url); m_downloaded_grain->set_url (url);
m_downloaded_grain->set_protocol (protocol);
m_downloaded_grain->set_branch (branch);
// NOTE: stream_from_url may modify the URL, hence we set it again // NOTE: stream_from_url may modify the URL, hence we set it again
m_downloaded_grain_reader.reset (SaltGrain::stream_from_url (url)); ProcessEventCallback callback;
m_downloaded_grain_reader.reset (SaltGrain::stream_from_url (url, protocol, branch, 60.0, &callback));
m_downloaded_grain->set_url (url); m_downloaded_grain->set_url (url);
tl::InputHttpStream *http = dynamic_cast<tl::InputHttpStream *> (m_downloaded_grain_reader->base ()); tl::InputHttpStream *http = dynamic_cast<tl::InputHttpStream *> (m_downloaded_grain_reader->base ());

View File

@ -121,7 +121,17 @@ HEADERS = \
tlUniqueName.h \ tlUniqueName.h \
tlRecipe.h \ tlRecipe.h \
tlSelect.h \ tlSelect.h \
tlEnv.h tlEnv.h
equals(HAVE_GIT2, "1") {
HEADERS += \
tlGit.h
SOURCES += \
tlGit.cc
}
equals(HAVE_CURL, "1") { equals(HAVE_CURL, "1") {

198
src/tl/tl/tlGit.cc Normal file
View File

@ -0,0 +1,198 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2023 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "tlGit.h"
#include "tlFileUtils.h"
#include "tlProgress.h"
#include "tlStaticObjects.h"
#include <git2.h>
#include <cstdio>
namespace tl
{
// ---------------------------------------------------------------
// Library initialization helper
namespace {
class InitHelper
{
public:
InitHelper ()
{
git_libgit2_init ();
}
~InitHelper ()
{
git_libgit2_shutdown ();
}
static void ensure_initialized ()
{
if (ms_init) {
return;
}
ms_init = new InitHelper ();
tl::StaticObjects::reg (&ms_init);
}
private:
static InitHelper *ms_init;
};
InitHelper *InitHelper::ms_init = 0;
}
// ---------------------------------------------------------------
// GitCollection implementation
GitObject::GitObject (const std::string &local_path)
: m_local_path (local_path), m_is_temp (false)
{
InitHelper::ensure_initialized ();
if (local_path.empty ()) {
// @@@ generic tempnam on Windows/Posix ...
char tmp[] = "git2klayoutXXXXXX";
mkstemp (tmp);
m_local_path = tmp;
m_is_temp = true;
}
tl::mkpath (m_local_path);
// ensures the directory is clean
tl::rm_dir_recursive (m_local_path); // @@@ TODO: error handling?
tl::mkpath (m_local_path);
}
GitObject::~GitObject ()
{
if (m_is_temp) {
tl::rm_dir_recursive (m_local_path);
}
}
static int
fetch_progress (const git_indexer_progress *stats, void *payload)
{
tl::RelativeProgress *progress = reinterpret_cast<tl::RelativeProgress *> (payload);
// first half of progress
size_t count = size_t (5000.0 * double (stats->received_objects) / double (std::max (1u, stats->total_objects)) + 1e-10);
progress->set (count);
return 0;
}
static void
checkout_progress(const char * /*path*/, size_t cur, size_t tot, void *payload)
{
tl::RelativeProgress *progress = reinterpret_cast<tl::RelativeProgress *> (payload);
// first half of progress
size_t count = size_t (5000.0 * double (cur) / double (std::max (size_t (1), tot)) + 1e-10);
progress->set (count + 5000u);
}
void
GitObject::read (const std::string &url, const std::string &filter, const std::string &branch, double timeout, tl::InputHttpStreamCallback *callback)
{
// @@@ use callback, timeout?
tl::RelativeProgress progress (tl::to_string (tr ("Download progress")), 10000, 1 /*yield always*/);
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
const char *paths_cstr[1];
paths_cstr[0] = filter.c_str ();
if (! filter.empty ()) {
checkout_opts.paths.count = 1;
checkout_opts.paths.strings = (char **) &paths_cstr;
}
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE |
GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
checkout_opts.progress_cb = &checkout_progress;
checkout_opts.progress_payload = (void *) &progress;
git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
clone_opts.checkout_opts = checkout_opts;
// NOTE: really has to be a branch! Tags won't work.
if (! branch.empty ()) {
clone_opts.checkout_branch = branch.c_str ();
}
#if LIBGIT2_VER_MAJOR > 1 || (LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR >= 7)
clone_opts.fetch_opts.depth = 1; // shallow (single commit)
#endif
clone_opts.fetch_opts.callbacks.transfer_progress = &fetch_progress;
clone_opts.fetch_opts.callbacks.payload = (void *) &progress;
// Do the clone
git_repository *cloned_repo = NULL;
int error = git_clone (&cloned_repo, url.c_str (), m_local_path.c_str (), &clone_opts);
if (error != 0) {
const git_error *err = git_error_last ();
throw tl::Exception (tl::to_string (tr ("Error cloning Git repo (%d): %s")), err->klass, (const char *) err->message);
}
if (cloned_repo) {
git_repository_free (cloned_repo);
// remove the worktree we don't need
tl::rm_dir_recursive (tl::combine_path (m_local_path, ".git"));
}
}
bool
GitObject::download (const std::string &url, const std::string &target, const std::string &branch, double timeout, tl::InputHttpStreamCallback *callback)
{
GitObject obj (target);
obj.read (url, std::string (), branch, timeout, callback);
return false;
}
tl::InputStream *
GitObject::download_item (const std::string &url, const std::string &file, const std::string &branch, double timeout, tl::InputHttpStreamCallback *callback)
{
GitObject obj;
obj.read (url, file, branch, timeout, callback);
// extract the file and return a memory blob, so we can delete the temp folder
tl::InputStream file_stream (tl::combine_path (obj.local_path (), file));
std::string data = file_stream.read_all ();
char *data_copy = new char [data.size ()];
memcpy (data_copy, data.c_str (), data.size ());
return new tl::InputStream (new tl::InputMemoryStream (data_copy, data.size (), true));
}
}

108
src/tl/tl/tlGit.h Normal file
View File

@ -0,0 +1,108 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2023 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef HDR_tlGit
#define HDR_tlGit
#include "tlCommon.h"
#include "tlStream.h"
#include <string>
#include <vector>
#if !defined(HAVE_GIT2)
# error "tlGit.h can only be used with libgit2 enabled"
#endif
namespace tl
{
class InputHttpStreamCallback;
/**
* @brief Represents an object from a Git URL
* This object can be a file or collection
*/
class TL_PUBLIC GitObject
{
public:
/**
* @brief Open a stream with the given URL
*
* The local_path is the path where to store the files.
* If empty, a temporary folder is created and destroyed once the "GitObject" goes out of scope.
*/
GitObject (const std::string &local_path = std::string ());
/**
* @brief Destructor
*/
~GitObject ();
/**
* @brief Populates the collection from the given URL
*
* "filter" can be a top-level file to download. If filter is non-empty,
* sparse mode is chosen.
*/
void read (const std::string &url, const std::string &filter, const std::string &branch, double timeout = 60.0, tl::InputHttpStreamCallback *callback = 0);
/**
* @brief Downloads the collection or file with the given URL
*
* This method will download the Git object from url to the file path
* given in "target".
*
* For file download, the target must be the path of the target file.
* For collection download, the target must be a directory path. In this
* case, the target directory must exist already.
*
* Sub-directories are created if required.
*
* This method throws an exception if the directory structure could
* not be obtained or downloading of one file failed.
*/
static bool download (const std::string &url, const std::string &target, const std::string &branch, double timeout = 60.0, tl::InputHttpStreamCallback *callback = 0);
/**
* @brief Gets a stream object for downloading the single item of the given URL
*
* The file needs to be a top-level object.
* The stream object returned needs to be deleted by the caller.
*/
static tl::InputStream *download_item (const std::string &url, const std::string &file, const std::string &branch, double timeout = 60.0, tl::InputHttpStreamCallback *callback = 0);
private:
std::string m_local_path;
bool m_is_temp;
const std::string &local_path () const
{
return m_local_path;
}
};
}
#endif

View File

@ -0,0 +1,59 @@
/*
KLayout Layout Viewer
Copyright (C) 2006-2023 Matthias Koefferlein
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#if defined(HAVE_GIT2)
#include "tlGit.h"
#include "tlUnitTest.h"
// @@@
static std::string test_url ("https://github.com/klayoutmatthias/xsection.git");
TEST(1)
{
std::string path = tl::TestBase::tmp_file ("repo");
tl::GitObject repo (path);
repo.read (test_url, std::string (), std::string ());
printf("@@@ done: %s\n", path.c_str ());
}
TEST(2)
{
std::string path = tl::TestBase::tmp_file ("repo");
tl::GitObject repo (path);
repo.read (test_url, std::string ("LICENSE"), std::string ());
printf("@@@ done: %s\n", path.c_str ());
}
TEST(3)
{
std::string path = tl::TestBase::tmp_file ("repo");
tl::GitObject repo (path);
repo.read (test_url, std::string (), std::string ("brxxx"));
printf("@@@ done: %s\n", path.c_str ());
}
#endif

View File

@ -20,6 +20,7 @@ SOURCES = \
tlExpressionTests.cc \ tlExpressionTests.cc \
tlFileSystemWatcherTests.cc \ tlFileSystemWatcherTests.cc \
tlFileUtilsTests.cc \ tlFileUtilsTests.cc \
tlGitTests.cc \
tlHttpStreamTests.cc \ tlHttpStreamTests.cc \
tlIncludeTests.cc \ tlIncludeTests.cc \
tlInt128SupportTests.cc \ tlInt128SupportTests.cc \