File indexing completed on 2024-04-28 16:01:33

0001 /******************************************************************************
0002  * This file is part of the libqgit2 library
0003  * Copyright (c) 2011 Laszlo Papp <djszapi@archlinux.us>
0004  * Copyright (C) 2013 Leonardo Giordani
0005  *
0006  * This library is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU Lesser General Public
0008  * License as published by the Free Software Foundation; either
0009  * version 2.1 of the License, or (at your option) any later version.
0010  *
0011  * This library is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014  * Lesser General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU Lesser General Public
0017  * License along with this library; if not, write to the Free Software
0018  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
0019  */
0020 
0021 #include <QtCore/QDir>
0022 #include <QtCore/QFileInfo>
0023 #include <QtCore/QVector>
0024 
0025 #include "qgitrepository.h"
0026 #include "qgitconfig.h"
0027 #include "qgittag.h"
0028 #include "qgitblob.h"
0029 #include "qgitsignature.h"
0030 #include "qgitexception.h"
0031 #include "qgitremote.h"
0032 #include "qgitcredentials.h"
0033 #include "qgitdiff.h"
0034 #include "private/annotatedcommit.h"
0035 #include "private/buffer.h"
0036 #include "private/pathcodec.h"
0037 #include "private/remotecallbacks.h"
0038 #include "private/strarray.h"
0039 
0040 namespace {
0041     void do_not_free(git_repository*) {}
0042 }
0043 
0044 namespace LibQGit2
0045 {
0046 
0047 
0048 class Repository::Private : public internal::RemoteListener
0049 {
0050 public:
0051     typedef QSharedPointer<git_repository> ptr_type;
0052     ptr_type d;
0053     QMap<QString, Credentials> m_remote_credentials;
0054     Repository &m_owner;
0055 
0056     Private(git_repository *repository, bool own, Repository &owner) :
0057         d(repository, own ? git_repository_free : do_not_free),
0058         m_owner(owner)
0059     {
0060     }
0061 
0062     Private(const Private &other, Repository &owner) :
0063         d(other.d),
0064         m_remote_credentials(other.m_remote_credentials),
0065         m_owner(owner)
0066     {
0067     }
0068 
0069     void init(const QString& path, bool isBare)
0070     {
0071         d.clear();
0072         git_repository *repo = 0;
0073         qGitThrow(git_repository_init(&repo, PathCodec::toLibGit2(path), isBare));
0074         setData(repo);
0075     }
0076 
0077     void open(const QString& path)
0078     {
0079         d.clear();
0080         git_repository *repo = 0;
0081         qGitThrow(git_repository_open(&repo, PathCodec::toLibGit2(path)));
0082         setData(repo);
0083     }
0084 
0085     void setData(git_repository *repo)
0086     {
0087         d = ptr_type(repo, git_repository_free);
0088     }
0089 
0090     git_repository* safeData(const char *funcName) const {
0091         if (d.isNull()){
0092             throw Exception("Repository::" + QString(funcName) + "(): no repository available");
0093         }
0094         return d.data();
0095     }
0096 
0097     int progress(int transferProgress)
0098     {
0099         emit m_owner.cloneProgress(transferProgress);
0100         return 0;
0101     }
0102 };
0103 
0104 
0105 #define SAFE_DATA d_ptr->safeData(__func__)
0106 
0107 #define THROW(msg) throw Exception(QString("Repository::") + __func__ + "(): " + msg)
0108 
0109 #define AVOID(statement, msg) if (statement) {\
0110     THROW(msg);\
0111 }
0112 
0113 
0114 Repository::Repository(git_repository *repository, bool own)
0115     : d_ptr(new Private(repository, own, *this))
0116 {
0117 }
0118 
0119 Repository::Repository(const Repository& other)
0120     : d_ptr(new Private(*other.d_ptr, *this))
0121 {
0122 }
0123 
0124 Repository::~Repository()
0125 {
0126 }
0127 
0128 QString Repository::discover(const QString& startPath, bool acrossFs, const QStringList& ceilingDirs)
0129 {
0130     internal::Buffer repoPath;
0131     QByteArray joinedCeilingDirs = PathCodec::toLibGit2(ceilingDirs.join(QChar(GIT_PATH_LIST_SEPARATOR)));
0132     qGitThrow(git_repository_discover(repoPath.data(), PathCodec::toLibGit2(startPath), acrossFs, joinedCeilingDirs));
0133 
0134     return repoPath.asPath();
0135 }
0136 
0137 void Repository::init(const QString& path, bool isBare)
0138 {
0139     d_ptr->init(path, isBare);
0140 }
0141 
0142 void Repository::open(const QString& path)
0143 {
0144     d_ptr->open(path);
0145 }
0146 
0147 void Repository::discoverAndOpen(const QString &startPath,
0148                                      bool acrossFs,
0149                                      const QStringList &ceilingDirs)
0150 {
0151     open(discover(startPath, acrossFs, ceilingDirs));
0152 }
0153 
0154 Reference Repository::head() const
0155 {
0156     git_reference *ref = 0;
0157     qGitThrow(git_repository_head(&ref, SAFE_DATA));
0158     return Reference(ref);
0159 }
0160 
0161 bool Repository::isHeadDetached() const
0162 {
0163     return qGitThrow(git_repository_head_detached(SAFE_DATA)) == 1;
0164 }
0165 
0166 bool Repository::isHeadUnborn() const
0167 {
0168     return qGitThrow(git_repository_head_unborn(SAFE_DATA)) == 1;
0169 }
0170 
0171 bool Repository::isEmpty() const
0172 {
0173     return qGitThrow(git_repository_is_empty(SAFE_DATA)) == 1;
0174 }
0175 
0176 bool Repository::isBare() const
0177 {
0178     return qGitThrow(git_repository_is_bare(SAFE_DATA)) == 1;
0179 }
0180 
0181 QString Repository::name() const
0182 {
0183     QString repoPath = QDir::cleanPath( workDirPath() );
0184     if (isBare())
0185         repoPath = QDir::cleanPath( path() );
0186 
0187     return QFileInfo(repoPath).fileName();
0188 }
0189 
0190 QString Repository::path() const
0191 {
0192     return PathCodec::fromLibGit2(git_repository_path(SAFE_DATA));
0193 }
0194 
0195 QString Repository::workDirPath() const
0196 {
0197     return PathCodec::fromLibGit2(git_repository_workdir(SAFE_DATA));
0198 }
0199 
0200 Config Repository::configuration() const
0201 {
0202     git_config *cfg;
0203     qGitThrow(git_repository_config(&cfg, SAFE_DATA));
0204     return Config(cfg);
0205 }
0206 
0207 Reference Repository::lookupRef(const QString& name) const
0208 {
0209     git_reference *ref = 0;
0210     qGitThrow(git_reference_lookup(&ref, SAFE_DATA, PathCodec::toLibGit2(name)));
0211     return Reference(ref);
0212 }
0213 
0214 OId Repository::lookupRefOId(const QString& name) const
0215 {
0216     git_oid oid;
0217     qGitThrow(git_reference_name_to_id(&oid, SAFE_DATA, PathCodec::toLibGit2(name)));
0218     return OId(&oid);
0219 }
0220 
0221 Reference Repository::lookupShorthandRef(const QString& shorthand) const
0222 {
0223     git_reference *ref = 0;
0224     qGitThrow(git_reference_dwim(&ref, SAFE_DATA, PathCodec::toLibGit2(shorthand)));
0225     return Reference(ref);
0226 }
0227 
0228 Commit Repository::lookupCommit(const OId& oid) const
0229 {
0230     git_commit *commit = 0;
0231     qGitThrow(git_commit_lookup_prefix(&commit, SAFE_DATA, oid.constData(), oid.length()));
0232     return Commit(commit);
0233 }
0234 
0235 Tag Repository::lookupTag(const OId& oid) const
0236 {
0237     git_tag *tag = 0;
0238     qGitThrow(git_tag_lookup_prefix(&tag, SAFE_DATA, oid.constData(), oid.length()));
0239     return Tag(tag);
0240 }
0241 
0242 Tree Repository::lookupTree(const OId& oid) const
0243 {
0244     git_tree *tree = 0;
0245     qGitThrow(git_tree_lookup_prefix(&tree, SAFE_DATA, oid.constData(), oid.length()));
0246     return Tree(tree);
0247 }
0248 
0249 Blob Repository::lookupBlob(const OId& oid) const
0250 {
0251     git_blob *blob = 0;
0252     qGitThrow(git_blob_lookup_prefix(&blob, SAFE_DATA, oid.constData(), oid.length()));
0253     return Blob(blob);
0254 }
0255 
0256 Object Repository::lookupAny(const OId &oid) const
0257 {
0258     git_object *object = 0;
0259     qGitThrow(git_object_lookup_prefix(&object, SAFE_DATA, oid.constData(), oid.length(), GIT_OBJ_ANY));
0260     return Object(object);
0261 }
0262 
0263 Object Repository::lookupRevision(const QString &revspec) const
0264 {
0265     git_object *object = 0;
0266     qGitThrow(git_revparse_single(&object, SAFE_DATA, revspec.toLatin1()));
0267     return Object(object);
0268 }
0269 
0270 Reference Repository::createRef(const QString& name, const LibQGit2::OId& oid, bool overwrite, const QString &message)
0271 {
0272     git_reference *ref = 0;
0273     qGitThrow(git_reference_create(&ref, SAFE_DATA, PathCodec::toLibGit2(name), oid.constData(), overwrite, message.toUtf8()));
0274     return Reference(ref);
0275 }
0276 
0277 Reference Repository::createSymbolicRef(const QString& name, const QString& target, bool overwrite, const QString &message)
0278 {
0279     git_reference *ref = 0;
0280     qGitThrow(git_reference_symbolic_create(&ref, SAFE_DATA, PathCodec::toLibGit2(name), PathCodec::toLibGit2(target), overwrite, message.toUtf8()));
0281     return Reference(ref);
0282 }
0283 
0284 OId Repository::createCommit(const Tree& tree, const QList<Commit>& parents, const Signature& author, const Signature& committer, const QString& message, const QString &ref)
0285 {
0286     QVector<const git_commit*> p;
0287     foreach (const Commit& parent, parents) {
0288         p.append(parent.data());
0289     }
0290 
0291     OId oid;
0292     qGitThrow(git_commit_create(oid.data(), SAFE_DATA, ref.isEmpty() ? NULL : PathCodec::toLibGit2(ref).constData(), author.data(), committer.data(),
0293                                 NULL, message.toUtf8(), tree.data(), p.size(), p.data()));
0294     return oid;
0295 }
0296 
0297 OId Repository::createTag(const QString& name,
0298                                   const Object& target,
0299                                   bool overwrite)
0300 {
0301     OId oid;
0302     qGitThrow(git_tag_create_lightweight(oid.data(), SAFE_DATA, PathCodec::toLibGit2(name),
0303                                          target.data(), overwrite));
0304     return oid;
0305 }
0306 
0307 OId Repository::createTag(const QString& name,
0308                                   const Object& target,
0309                                   const Signature& tagger,
0310                                   const QString& message,
0311                                   bool overwrite)
0312 {
0313     OId oid;
0314     qGitThrow(git_tag_create(oid.data(), SAFE_DATA, PathCodec::toLibGit2(name), target.data(),
0315                              tagger.data(), message.toUtf8(), overwrite));
0316     return oid;
0317 }
0318 
0319 void Repository::deleteTag(const QString& name)
0320 {
0321     qGitThrow(git_tag_delete(SAFE_DATA, PathCodec::toLibGit2(name)));
0322 }
0323 
0324 OId Repository::createBlobFromFile(const QString& path)
0325 {
0326     OId oid;
0327     qGitThrow(git_blob_create_fromdisk(oid.data(), SAFE_DATA, PathCodec::toLibGit2(path)));
0328     return oid;
0329 }
0330 
0331 OId Repository::createBlobFromBuffer(const QByteArray& buffer)
0332 {
0333     OId oid;
0334     qGitThrow(git_blob_create_frombuffer(oid.data(), SAFE_DATA, buffer.data(), buffer.size()));
0335     return oid;
0336 }
0337 
0338 Reference Repository::createBranch(const QString &branchName, const Commit &target, bool force)
0339 {
0340     Commit usedTarget(target);
0341     if (target.isNull()) {
0342         usedTarget = lookupCommit(head().target());
0343     }
0344 
0345     git_reference *ref = NULL;
0346     qGitThrow(git_branch_create(&ref, SAFE_DATA, branchName.toUtf8(), usedTarget.data(), force));
0347     return Reference(ref);
0348 }
0349 
0350 void Repository::deleteBranch(const QString &branchName)
0351 {
0352     Reference branch = lookupShorthandRef(branchName);
0353     qGitThrow(git_branch_delete(branch.data()));
0354 }
0355 
0356 void Repository::cherryPick(const Commit &commit, const CherryPickOptions &opts)
0357 {
0358     AVOID(commit.isNull(), "can not cherry-pick a null commit.")
0359 
0360     qGitThrow(git_cherrypick(SAFE_DATA, commit.data(), opts.data()));
0361 }
0362 
0363 QStringList Repository::listTags(const QString& pattern) const
0364 {
0365     git_strarray tags;
0366     qGitThrow(git_tag_list_match(&tags, qPrintable(pattern), SAFE_DATA));
0367     QStringList list;
0368     for (size_t i = 0; i < tags.count; ++i)
0369     {
0370         list << QString(tags.strings[i]);
0371     }
0372     git_strarray_free(&tags);
0373     return list;
0374 }
0375 
0376 QStringList Repository::listReferences() const
0377 {
0378     git_strarray refs;
0379     qGitThrow(git_reference_list(&refs, SAFE_DATA));
0380     QStringList list;
0381     for (size_t i = 0; i < refs.count; ++i)
0382     {
0383         list << QString(refs.strings[i]);
0384     }
0385     git_strarray_free(&refs);
0386     return list;
0387 }
0388 
0389 Database Repository::database() const
0390 {
0391     git_odb *odb;
0392     qGitThrow( git_repository_odb(&odb, SAFE_DATA) );
0393     return Database(odb);
0394 }
0395 
0396 Index Repository::index() const
0397 {
0398     git_index *idx;
0399     qGitThrow(git_repository_index(&idx, SAFE_DATA));
0400     return Index(idx);
0401 }
0402 
0403 StatusList Repository::status(const StatusOptions &options) const
0404 {
0405     const git_status_options opt = options.constData();
0406     git_status_list *status_list;
0407     qGitThrow(git_status_list_new(&status_list, SAFE_DATA, &opt));
0408     return StatusList(status_list);
0409 }
0410 
0411 Repository::GraphRelationship Repository::commitRelationship(const Commit &local, const Commit &upstream) const
0412 {
0413     GraphRelationship result;
0414     qGitThrow(git_graph_ahead_behind(&result.ahead, &result.behind, SAFE_DATA, local.oid().constData(), upstream.oid().constData()));
0415     return result;
0416 }
0417 
0418 Diff Repository::diffTrees(const Tree &oldTree, const Tree &newTree) const
0419 {
0420     git_diff *diff = NULL;
0421     qGitThrow(git_diff_tree_to_tree(&diff, SAFE_DATA, oldTree.data(), newTree.data(), NULL));
0422     return Diff(diff);
0423 }
0424 
0425 Commit Repository::mergeBase(const Commit &one, const Commit &two) const
0426 {
0427     OId out;
0428     qGitThrow(git_merge_base(out.data(), SAFE_DATA, one.oid().constData(), two.oid().constData()));
0429     return lookupCommit(out);
0430 }
0431 
0432 Index Repository::mergeTrees(const Tree &our, const Tree &their, const Tree &ancestor, const MergeOptions &opts)
0433 {
0434     AVOID(our.isNull() && their.isNull(), "needed at least either 'our' or 'their' tree to merge.")
0435 
0436     git_index *index = NULL;
0437     qGitThrow(git_merge_trees(&index, SAFE_DATA, ancestor.data(), our.data(), their.data(), opts.data()));
0438     return Index(index);
0439 }
0440 
0441 git_repository* Repository::data() const
0442 {
0443     return d_ptr->d.data();
0444 }
0445 
0446 const git_repository* Repository::constData() const
0447 {
0448     return d_ptr->d.data();
0449 }
0450 
0451 
0452 void Repository::setRemoteCredentials(const QString& remoteName, Credentials credentials)
0453 {
0454     d_ptr->m_remote_credentials[remoteName] = credentials;
0455 }
0456 
0457 
0458 void Repository::clone(const QString& url, const QString& path)
0459 {
0460     const QString remoteName("origin");
0461     internal::RemoteCallbacks remoteCallbacks(d_ptr.data(), d_ptr->m_remote_credentials.value(remoteName));
0462 
0463     git_repository *repo = 0;
0464     git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
0465     opts.fetch_opts.callbacks = remoteCallbacks.rawCallbacks();
0466     opts.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
0467     qGitEnsureValue(0, git_clone(&repo, url.toLatin1(), PathCodec::toLibGit2(path), &opts));
0468 
0469     d_ptr->setData(repo);
0470 }
0471 
0472 
0473 void Repository::remoteAdd(const QString& name, const QString& url, bool changeUrlIfExists)
0474 {
0475     git_remote *r = NULL;
0476     switch (git_remote_lookup(&r, SAFE_DATA, name.toLatin1())) {
0477     case GIT_ENOTFOUND:
0478         r = NULL;
0479         qGitThrow(git_remote_create(&r, SAFE_DATA, name.toLatin1(), url.toLatin1()));
0480         break;
0481 
0482     case GIT_OK:
0483         if (QString::fromLatin1(git_remote_url(r)) != url) {
0484             if (changeUrlIfExists) {
0485                 qGitThrow(git_remote_set_url(SAFE_DATA, name.toLatin1(), url.toLatin1()));
0486             } else {
0487                 THROW("remote already exists");
0488             }
0489         }
0490         break;
0491 
0492     default:
0493         throw Exception();
0494         break;
0495     }
0496 }
0497 
0498 
0499 Remote* Repository::remote(const QString &remoteName, QObject *parent) const
0500 {
0501     git_remote *r = NULL;
0502     qGitThrow(git_remote_lookup(&r, SAFE_DATA, remoteName.toLatin1()));
0503     return new Remote(r, d_ptr->m_remote_credentials.value(remoteName), parent);
0504 }
0505 
0506 
0507 void Repository::fetch(const QString& name, const QString& head, const QString &message)
0508 {
0509     git_remote *_remote = NULL;
0510     qGitThrow(git_remote_lookup(&_remote, SAFE_DATA, name.toLatin1()));
0511     Remote remote(_remote, d_ptr->m_remote_credentials.value(name));
0512     connect(&remote, SIGNAL(transferProgress(int)), this, SIGNAL(fetchProgress(int)));
0513 
0514     using internal::StrArray;
0515     StrArray refs;
0516     if (!head.isEmpty()) {
0517         const QString refspec = QString("refs/heads/%2:refs/remotes/%1/%2").arg(name).arg(head);
0518         refs = StrArray(QList<QByteArray>() << refspec.toLatin1());
0519     }
0520 
0521     internal::RemoteCallbacks remoteCallbacks(d_ptr.data(), d_ptr->m_remote_credentials.value(name));
0522     git_fetch_options opts = GIT_FETCH_OPTIONS_INIT;
0523     opts.callbacks = remoteCallbacks.rawCallbacks();
0524     qGitThrow(git_remote_fetch(remote.data(), refs.count() > 0 ? &refs.data() : NULL, &opts, message.isNull() ? NULL : message.toUtf8().constData()));
0525 }
0526 
0527 
0528 QStringList Repository::remoteBranches(const QString& remoteName)
0529 {
0530     git_remote *_remote = NULL;
0531     qGitThrow(git_remote_lookup(&_remote, SAFE_DATA, remoteName.toLatin1()));
0532     Remote remote(_remote, d_ptr->m_remote_credentials.value(remoteName));
0533 
0534     internal::RemoteCallbacks remoteCallbacks(d_ptr.data(), d_ptr->m_remote_credentials.value(remoteName));
0535     const auto callbacks = remoteCallbacks.rawCallbacks();
0536     qGitThrow(git_remote_connect(remote.data(), GIT_DIRECTION_FETCH, &callbacks, nullptr, nullptr));
0537     qGitEnsureValue(1, git_remote_connected(remote.data()));
0538 
0539     /* List the heads on the remote */
0540     const git_remote_head** remote_heads = NULL;
0541     size_t count = 0;
0542     qGitThrow(git_remote_ls(&remote_heads, &count, remote.data()));
0543     QStringList heads;
0544     for (size_t i = 0; i < count; ++i) {
0545         const git_remote_head* head = remote_heads[i];
0546         if (head && head->name) {
0547             QString ref = QString::fromLatin1(head->name);
0548             if (ref.startsWith("refs/heads/")) {
0549                 heads << ref.replace("refs/heads/", "");
0550             }
0551         }
0552     }
0553 
0554     return heads;
0555 }
0556 
0557 
0558 void Repository::checkoutTree(const Object &treeish, const CheckoutOptions &opts)
0559 {
0560     qGitThrow(git_checkout_tree(SAFE_DATA, treeish.constData(), opts.data()));
0561 }
0562 
0563 
0564 void Repository::checkoutHead(const CheckoutOptions &opts)
0565 {
0566     qGitThrow(git_checkout_head(SAFE_DATA, opts.data()));
0567 }
0568 
0569 
0570 void Repository::checkoutRemote(const QString& branch, const CheckoutOptions &opts, const QString& remote)
0571 {
0572     const QString refspec = "refs/remotes/" + remote + "/" + branch;
0573     checkoutTree(lookupRevision(refspec), opts);
0574 
0575     qGitThrow(git_repository_set_head(SAFE_DATA, refspec.toLatin1()));
0576 }
0577 
0578 
0579 void Repository::reset(const Object &target, ResetType type)
0580 {
0581     AVOID(target.isNull(), "can not reset to null target");
0582 
0583     git_reset_t resetType;
0584     switch (type) {
0585     case Soft:
0586         resetType = GIT_RESET_SOFT;
0587         break;
0588     case Mixed:
0589         resetType = GIT_RESET_MIXED;
0590         break;
0591     case Hard:
0592         resetType = GIT_RESET_HARD;
0593         break;
0594     default:
0595         THROW("invalid reset type argument");
0596     }
0597 
0598     qGitThrow(git_reset(SAFE_DATA, target.data(), resetType, NULL));
0599 }
0600 
0601 Rebase Repository::rebase(const Reference &branch, const Reference &upstream, const Reference &onto, const RebaseOptions &opts)
0602 {
0603     git_rebase *rebase;
0604     internal::AnnotatedCommit commitBranch(*this, branch);
0605     internal::AnnotatedCommit commitUpstream(*this, upstream);
0606     internal::AnnotatedCommit commitOnto(*this, onto);
0607     qGitThrow(git_rebase_init(&rebase, data(), commitBranch.constData(), commitUpstream.constData(), commitOnto.constData(), opts.constData()));
0608     return Rebase(rebase);
0609 }
0610 
0611 bool Repository::shouldIgnore(const QString &path) const
0612 {
0613     QString usedPath(QDir::cleanPath(path));
0614 
0615     QFileInfo pathInfo(usedPath);
0616     if (pathInfo.isAbsolute()) {
0617         QString wd(workDirPath());
0618         if (usedPath.startsWith(wd)) {
0619             usedPath = usedPath.mid(wd.size());
0620         } else {
0621             THROW("Given path (" + path + ") is not within this repository's directory (" + wd + ").");
0622         }
0623     }
0624 
0625     int result;
0626     qGitThrow(git_status_should_ignore(&result, SAFE_DATA, PathCodec::toLibGit2(usedPath)));
0627     return result;
0628 }
0629 
0630 void Repository::setIdentity(const Identity &id)
0631 {
0632     const auto name = !id.name.isEmpty() ? id.name.toUtf8() : QByteArray();
0633     const auto email = !id.email.isEmpty() ? id.email.toUtf8() : QByteArray();
0634     qGitThrow(git_repository_set_ident(data(), name, email));
0635 }
0636 
0637 Repository::Identity Repository::identity() const
0638 {
0639     const char *name;
0640     const char *email;
0641     qGitThrow(git_repository_ident(&name, &email, data()));
0642     return Identity{ QString::fromUtf8(name), QString::fromUtf8(email) };
0643 }
0644 
0645 } // namespace LibQGit2