File indexing completed on 2024-04-14 15:50:56

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2014 Narfinger <Narfinger@users.noreply.github.com>
0003  * SPDX-FileCopyrightText: (C) 2014 Gleb Baryshev <gleb.baryshev@gmail.com>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 #include <QDateTime>
0008 #include <QDebug>
0009 #include <QDir>
0010 #include <QDirIterator>
0011 #include <QMutexLocker>
0012 
0013 #include "basketscene.h"
0014 #include "gitwrapper.h"
0015 #include "settings.h"
0016 
0017 #ifdef WITH_LIBGIT2
0018 
0019 extern "C" {
0020 #include <git2.h>
0021 }
0022 
0023 #include "global.h"
0024 
0025 #define GIT_RETURN_IF_DISABLED()                                                                                                                                                                                                               \
0026     if (!Settings::versionSyncEnabled())                                                                                                                                                                                                       \
0027         return;
0028 
0029 QMutex GitWrapper::gitMutex;
0030 
0031 void GitWrapper::initializeGitRepository(QString folder)
0032 {
0033     GIT_RETURN_IF_DISABLED()
0034     QMutexLocker l(&gitMutex);
0035     // this is not thread safe, we use locking elsewhere
0036     git_repository *repo = nullptr;
0037     QByteArray ba = folder.toUtf8();
0038 
0039     const char *cString = ba.data();
0040 
0041     int error = git_repository_init(&repo, cString, false);
0042     if (error < 0) {
0043         const git_error *e = giterr_last();
0044         qDebug() << e->message;
0045     }
0046 
0047     git_signature *sig = nullptr;
0048     git_index *index = nullptr;
0049     git_oid tree_id;
0050     git_oid commit_id;
0051     git_tree *tree = nullptr;
0052 
0053     // no error handling at the moment
0054     git_signature_now(&sig, "AutoGit", "auto@localhost");
0055     git_repository_index(&index, repo);
0056     git_index_write_tree(&tree_id, index);
0057     git_tree_lookup(&tree, repo, &tree_id);
0058     git_commit_create_v(&commit_id, repo, "HEAD", sig, sig, nullptr, "Initial commit", tree, 0);
0059 
0060     git_signature_free(sig);
0061     git_index_free(index);
0062     git_tree_free(tree);
0063 
0064     // first commit
0065     commitPattern(repo, "*", "Initial full commit");
0066     git_repository_free(repo);
0067 }
0068 
0069 void GitWrapper::commitBasketView()
0070 {
0071     GIT_RETURN_IF_DISABLED()
0072     QMutexLocker l(&gitMutex);
0073 
0074     git_repository *repo = openRepository();
0075     if (repo == nullptr)
0076         return;
0077 
0078     const QDateTime gitdate = getLastCommitDate(repo);
0079 
0080     const QString pathtosave = Global::savesFolder();
0081     QDir basketdir(pathtosave + "baskets/");
0082     bool changed = false;
0083     basketdir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); // this automatically skips baskets.xml file
0084     QDirIterator it(basketdir);
0085     while (!changed && it.hasNext()) {
0086         const QFileInfo file(it.next());
0087         if (file.lastModified() > gitdate)
0088             changed = true;
0089     }
0090 
0091     if (changed) {
0092         commitPattern(repo, "*");
0093     }
0094 
0095     git_repository_free(repo);
0096 }
0097 
0098 void GitWrapper::commitCreateBasket()
0099 {
0100     GIT_RETURN_IF_DISABLED()
0101     QMutexLocker l(&gitMutex);
0102     git_repository *repo = openRepository();
0103     if (repo == nullptr)
0104         return;
0105 
0106     const QDateTime gitdate = getLastCommitDate(repo);
0107 
0108     const QString basketxml = Global::savesFolder() + "baskets/baskets.xml";
0109     const QFileInfo basketxmlinfo(basketxml);
0110 
0111     if (gitdate <= basketxmlinfo.lastModified()) {
0112         git_index *index = nullptr;
0113         int error = git_repository_index(&index, repo);
0114         if (error < 0) {
0115             gitErrorHandling();
0116             return;
0117         }
0118         // this is kind of hacky because somebody could come in between and we still have stuff open
0119         // change basket.xml
0120         const QString basketxml("baskets/baskets.xml");
0121         QByteArray basketxmlba = basketxml.toUtf8();
0122         char *basketxmlCString = basketxmlba.data();
0123         error = git_index_add_bypath(index, basketxmlCString);
0124         if (error < 0) {
0125             gitErrorHandling();
0126             return;
0127         }
0128 
0129         bool result = commitIndex(repo, index);
0130         git_index_free(index);
0131     }
0132 
0133     git_repository_free(repo);
0134 }
0135 
0136 void GitWrapper::commitTagsXml()
0137 {
0138     GIT_RETURN_IF_DISABLED()
0139     QMutexLocker l(&gitMutex);
0140     git_repository *repo = openRepository();
0141     if (repo == nullptr)
0142         return;
0143 
0144     git_index *index = nullptr;
0145     int error = git_repository_index(&index, repo);
0146     if (error < 0) {
0147         gitErrorHandling();
0148         return;
0149     }
0150 
0151     const QString tagsxml("tags.xml");
0152     QByteArray tagsxmlba = tagsxml.toUtf8();
0153     char *tagsxmlCString = tagsxmlba.data();
0154     error = git_index_add_bypath(index, tagsxmlCString);
0155 
0156     bool result = commitIndex(repo, index);
0157     git_index_free(index);
0158 
0159     git_repository_free(repo);
0160 }
0161 
0162 void GitWrapper::commitDeleteBasket(QString basketFolderName)
0163 {
0164     GIT_RETURN_IF_DISABLED()
0165     QMutexLocker l(&gitMutex);
0166 
0167     git_index *index = nullptr;
0168     git_repository *repo = openRepository();
0169     if (repo == nullptr)
0170         return;
0171 
0172     int error = git_repository_index(&index, repo);
0173     if (error < 0) {
0174         gitErrorHandling();
0175         return;
0176     }
0177 
0178     // remove the directory
0179     const QString dir("baskets/" + basketFolderName);
0180     const QByteArray dirba = dir.toUtf8();
0181     const char *dirCString = dirba.data();
0182     error = git_index_remove_directory(index, dirCString, 0);
0183     if (error < 0) {
0184         gitErrorHandling();
0185         return;
0186     }
0187 
0188     // change basket.xml
0189     const QString basketxml("baskets/baskets.xml");
0190     QByteArray basketxmlba = basketxml.toUtf8();
0191     char *basketxmlCString = basketxmlba.data();
0192     error = git_index_add_bypath(index, basketxmlCString);
0193     if (error < 0) {
0194         gitErrorHandling();
0195         return;
0196     }
0197     removeDeletedFromIndex(repo, index);
0198 
0199     bool result = commitIndex(repo, index);
0200 
0201     git_index_free(index);
0202     git_repository_free(repo);
0203 }
0204 
0205 void GitWrapper::commitBasket(BasketScene *basket)
0206 {
0207     GIT_RETURN_IF_DISABLED()
0208     QMutexLocker l(&gitMutex);
0209     git_repository *repo = openRepository();
0210     if (repo == nullptr)
0211         return;
0212 
0213     const QDateTime gitdate = getLastCommitDate(repo);
0214 
0215     const QString fullpath = basket->fullPath();
0216     const QDir basketdir(fullpath);
0217     bool changed = false;
0218     QDirIterator it(basketdir);
0219     while (!changed && it.hasNext()) {
0220         const QFileInfo file(it.next());
0221         if (file.fileName() != ".basket") {
0222             if (file.lastModified() >= gitdate)
0223                 changed = true;
0224         }
0225     }
0226 
0227     if (changed) {
0228         git_index *index = nullptr;
0229 
0230         int error = git_repository_index(&index, repo);
0231         if (error < 0) {
0232             gitErrorHandling();
0233             return;
0234         }
0235 
0236         const QString pattern("baskets/" + basket->folderName() + '*');
0237 
0238         QByteArray patternba = pattern.toUtf8();
0239         char *patternCString = patternba.data();
0240         git_strarray arr = {&patternCString, 1};
0241         error = git_index_add_all(index, &arr, 0, nullptr, nullptr);
0242         if (error < 0) {
0243             gitErrorHandling();
0244             return;
0245         }
0246         const QString basketxml("baskets/baskets.xml");
0247         QByteArray basketxmlba = basketxml.toUtf8();
0248         char *basketxmlCString = basketxmlba.data();
0249         error = git_index_add_bypath(index, basketxmlCString);
0250         if (error < 0) {
0251             gitErrorHandling();
0252             return;
0253         }
0254 
0255         removeDeletedFromIndex(repo, index);
0256 
0257         bool result = commitIndex(repo, index);
0258 
0259         git_index_free(index);
0260     }
0261 
0262     git_repository_free(repo);
0263 }
0264 
0265 bool GitWrapper::commitPattern(git_repository *repo, QString pattern, QString message)
0266 {
0267     git_index *index = nullptr;
0268     int error = git_repository_index(&index, repo);
0269     if (error < 0) {
0270         gitErrorHandling();
0271         return false;
0272     }
0273 
0274     QByteArray patternba = pattern.toUtf8();
0275     char *patternCString = patternba.data();
0276     git_strarray arr = {&patternCString, 1};
0277     error = git_index_add_all(index, &arr, 0, nullptr, nullptr);
0278     if (error < 0) {
0279         gitErrorHandling();
0280         return false;
0281     }
0282 
0283     bool result = commitIndex(repo, index, message);
0284 
0285     git_index_free(index);
0286 
0287     return true;
0288 }
0289 
0290 bool GitWrapper::commitIndex(git_repository *repo, git_index *index, QString message)
0291 {
0292     //  write git index
0293     git_signature *sig = nullptr;
0294     git_oid tree_id;
0295     git_oid commit_id;
0296     git_tree *tree = nullptr;
0297 
0298     int error = git_signature_now(&sig, "AutoGit", "auto@localhost");
0299     if (error < 0) {
0300         gitErrorHandling();
0301         return false;
0302     }
0303 
0304     error = git_repository_index(&index, repo);
0305     if (error < 0) {
0306         gitErrorHandling();
0307         return false;
0308     }
0309 
0310     git_commit *commit = nullptr; /* parent */
0311     git_oid oid_parent_commit;    /* the SHA1 for last commit */
0312 
0313     error = git_reference_name_to_id(&oid_parent_commit, repo, "HEAD");
0314     if (error < 0) {
0315         gitErrorHandling();
0316         return false;
0317     }
0318 
0319     error = git_commit_lookup(&commit, repo, &oid_parent_commit);
0320     if (error < 0) {
0321         gitErrorHandling();
0322         return false;
0323     }
0324 
0325     error = git_index_write(index);
0326     if (error < 0) {
0327         gitErrorHandling();
0328         return false;
0329     }
0330 
0331     error = git_index_write_tree(&tree_id, index);
0332     if (error < 0) {
0333         gitErrorHandling();
0334         return false;
0335     }
0336 
0337     error = git_tree_lookup(&tree, repo, &tree_id);
0338     if (error < 0) {
0339         gitErrorHandling();
0340         return false;
0341     }
0342 
0343     const git_commit *parentarray[] = {commit};
0344     QByteArray commitmessageba = message.toUtf8();
0345     const char *commitmessageCString = commitmessageba.data();
0346     error = git_commit_create(&commit_id, repo, "HEAD", sig, sig, nullptr, commitmessageCString, tree, 1, parentarray);
0347     if (error < 0) {
0348         gitErrorHandling();
0349         return false;
0350     }
0351 
0352     git_signature_free(sig);
0353     git_tree_free(tree);
0354     return true;
0355 }
0356 
0357 void GitWrapper::removeDeletedFromIndex(git_repository *repo, git_index *index)
0358 {
0359     git_status_foreach(
0360         repo,
0361         [](const char *path, unsigned int status_flags, void *payload) -> int {
0362             if (status_flags & GIT_STATUS_WT_DELETED) {
0363                 git_index *index = static_cast<git_index *>(payload);
0364                 git_index_remove_bypath(index, path);
0365             }
0366             return 0;
0367         },
0368         index);
0369 }
0370 
0371 git_repository *GitWrapper::openRepository()
0372 {
0373     QString pathtosave = Global::savesFolder();
0374     QByteArray pathba = pathtosave.toUtf8();
0375     const char *pathCString = pathba.data();
0376     git_repository *repo = nullptr;
0377 
0378     int error = git_repository_open(&repo, pathCString);
0379     if (error < 0) {
0380         gitErrorHandling();
0381         return repo;
0382     }
0383     return repo;
0384 }
0385 
0386 QDateTime GitWrapper::getLastCommitDate(git_repository *repo)
0387 {
0388     if (repo == nullptr)
0389         return QDateTime();
0390     git_oid oid_parent_commit; /* the SHA1 for last commit */
0391     int error = git_reference_name_to_id(&oid_parent_commit, repo, "HEAD");
0392     if (error < 0)
0393         return QDateTime();
0394 
0395     git_commit *head = nullptr;
0396     error = git_commit_lookup(&head, repo, &oid_parent_commit);
0397     if (error < 0)
0398         return QDateTime();
0399     int64_t time = static_cast<int64_t>(git_commit_time(head));
0400 
0401     QDateTime date;
0402     date.setTime_t(time);
0403 
0404     git_commit_free(head);
0405     return date;
0406 }
0407 
0408 void GitWrapper::gitErrorHandling()
0409 {
0410     const git_error *e = giterr_last();
0411     qDebug() << "Error in git (error,class,message)" << e->klass << e->message;
0412 }
0413 
0414 #else
0415 // make everything noop
0416 void GitWrapper::initializeGitRepository(QString folder)
0417 {
0418 }
0419 void GitWrapper::commitBasketView()
0420 {
0421 }
0422 void GitWrapper::commitCreateBasket()
0423 {
0424 }
0425 void GitWrapper::commitDeleteBasket(QString basketFolderName)
0426 {
0427 }
0428 void GitWrapper::commitBasket(BasketScene *basket)
0429 {
0430 }
0431 void GitWrapper::commitTagsXml()
0432 {
0433 }
0434 bool GitWrapper::commitPattern(git_repository *repo, QString pattern, QString message)
0435 {
0436     return true;
0437 }
0438 bool GitWrapper::commitIndex(git_repository *repo, git_index *index, QString message)
0439 {
0440     return true;
0441 }
0442 void GitWrapper::removeDeletedFromIndex(git_repository *repo, git_index *index)
0443 {
0444 }
0445 git_repository *GitWrapper::openRepository()
0446 {
0447     return 0;
0448 }
0449 
0450 QDateTime GitWrapper::getLastCommitDate(git_repository *repo)
0451 {
0452     return QDateTime();
0453 }
0454 
0455 void GitWrapper::gitErrorHandling()
0456 {
0457 }
0458 
0459 #endif