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