File indexing completed on 2024-05-19 04:47:12

0001 #include "project.h"
0002 #include "projectmanager.h"
0003 
0004 #include <QDebug>
0005 #include <QFileInfo>
0006 #include <QDir>
0007 #include <QColor>
0008 #include <QFileSystemWatcher>
0009 #include <QTimer>
0010 
0011 #include <QtConcurrent>
0012 #include <QFuture>
0013 
0014 #include <KI18n/KLocalizedString>
0015 
0016 #include <libkommit/commands/commandclone.h>
0017 #include <libkommit/models/logsmodel.h>
0018 #include <libkommit/gitglobal.h>
0019 #include <libkommit/gitlog.h>
0020 #include <libkommit/models/remotesmodel.h>
0021 
0022 #include <libkommit/gitmanager.h>
0023 
0024 #include "actionrunner.h"
0025 
0026 Project::Project(QObject *parent) : QObject(parent)
0027   ,m_manager(new Git::Manager())
0028   ,m_cloneWatcher(nullptr)
0029   ,m_status(new StatusMessage(this))
0030   ,m_gitDirWacther(new QFileSystemWatcher(this))
0031   ,m_watcherTimer(new QTimer(this))
0032 {
0033     //    qRegisterMetaType<Status>("Status"); // this is needed for QML to know of WindowDecorations
0034 
0035     qRegisterMetaType<const Git::RemotesModel*>("const Git::RemotesModel *");
0036     connect(this, &Project::urlChanged, this, &Project::setData);
0037 //    connect(this, &Project::currentBranchChanged, this, &Project::setCurrentBranchRemote);
0038 
0039 
0040     //Watch the git directory in case somehting happens, if so then refresh the needed parts
0041     connect(m_gitDirWacther, &QFileSystemWatcher::directoryChanged, [this](const QString &dir)
0042     {
0043         qDebug() << "GIT DIR CHANGED REACT TO IT ???????????????????" << dir;
0044 
0045         m_watcherTimer->start();
0046     });
0047 
0048     connect(m_gitDirWacther, &QFileSystemWatcher::fileChanged, [this](const QString &dir)
0049     {
0050         qDebug() << "GIT FILE CHANGED REACT TO IT ???????????????????" << dir;
0051 
0052         m_watcherTimer->start();
0053     });
0054 
0055     m_watcherTimer->setSingleShot(true);
0056     m_watcherTimer->setInterval(1000);
0057 
0058     connect(m_watcherTimer, &QTimer::timeout, [this]()
0059     {
0060         qDebug() << "GIT DIR CHANGED REACT TO IT";
0061 
0062         if(!m_manager->isValid())
0063             return;
0064 
0065         m_manager->logsModel()->load();
0066         m_manager->logsModel()->reset();
0067         loadData();
0068 
0069         Q_EMIT repoChanged();
0070     });
0071 }
0072 
0073 Project::~Project()
0074 {
0075     if(m_cloneWatcher)
0076     {
0077         m_cloneWatcher->cancel();
0078         m_cloneWatcher->deleteLater();
0079     }
0080 
0081     m_manager->deleteLater();
0082 }
0083 
0084 QString Project::url() const
0085 {
0086     return m_url;
0087 }
0088 
0089 void Project::setData(const QString &url)
0090 {    
0091     const auto mUrl = QUrl::fromUserInput(url);
0092 
0093     QFileInfo fileInfo(mUrl.toLocalFile());
0094     if(!fileInfo.exists())
0095     {
0096         setStatus(StatusMessage::Error, i18n("Directory does not exists."));
0097         return;
0098     }
0099 
0100     m_manager->setPath(mUrl.toLocalFile());
0101     if(!m_manager->isValid())
0102     {
0103         Q_EMIT error(i18n("URL is not a valid repo."));
0104         setStatus(StatusMessage::Error, mUrl.toString());
0105         return;
0106     }
0107 
0108     m_gitDirWacther->addPath(mUrl.toLocalFile());
0109     m_gitDirWacther->addPath(mUrl.toLocalFile()+"/.git/objects");
0110     m_gitDirWacther->addPath(mUrl.toLocalFile()+"/.git/refs/heads");
0111     m_gitDirWacther->addPath(mUrl.toLocalFile()+"/.git/index");
0112 
0113     setStatus(StatusMessage::Loading, i18n("Loading local repository."));
0114 
0115     if(loadData())
0116         setStatus(StatusMessage::Ready, i18n("Ready."));
0117 
0118 }
0119 
0120 bool Project::loadData()
0121 {
0122     const auto mUrl = QUrl::fromUserInput(m_url);
0123     QFileInfo fileInfo(mUrl.toLocalFile());
0124 
0125     m_logo = ProjectManager::projectLogo(mUrl);
0126     Q_EMIT this->logoChanged(m_logo);
0127 
0128     m_readmeFile = ProjectManager::readmeFile(mUrl);
0129     Q_EMIT this->readmeFileChanged(m_readmeFile);
0130 
0131     m_title = fileInfo.fileName();
0132     Q_EMIT this->titleChanged(m_title);
0133 
0134     m_currentBranch = m_manager->currentBranch();
0135     Q_EMIT this->currentBranchChanged(m_currentBranch);
0136 
0137     this->setHeadBranch();
0138 
0139     m_filesStatus = m_manager->repoFilesStatus();
0140 
0141     return true;
0142 }
0143 
0144 QString Project::getTitle() const
0145 {
0146     return m_title;
0147 }
0148 
0149 QUrl Project::getLogo() const
0150 {
0151     return m_logo;
0152 }
0153 
0154 QString Project::currentBranch() const
0155 {
0156     return m_currentBranch;
0157 }
0158 
0159 Git::LogsModel *Project::getCommitsModel()
0160 {
0161     if(!m_manager->isValid())
0162         return nullptr;
0163 
0164     return m_manager->logsModel();
0165 }
0166 
0167 QStringList Project::repoStatus() const
0168 {
0169     return m_repoStatus;
0170 }
0171 
0172 QUrl Project::readmeFile() const
0173 {
0174     return m_readmeFile;
0175 }
0176 
0177 Git::RemotesModel *Project::remotesModel() const
0178 {
0179     if(!m_manager->isValid())
0180         return nullptr;
0181 
0182     qDebug() << "ASKIGN FOR THE RMEOTES MODEL" << m_manager->remotesModel()->rowCount(QModelIndex());
0183     return m_manager->remotesModel();
0184 }
0185 
0186 
0187 QVariantMap Project::getHeadBranch() const
0188 {
0189     return m_headBranch;
0190 }
0191 
0192 void Project::setUrl(const QString &url)
0193 {
0194     qDebug() << "SET URL " << url;
0195     if (m_url == url)
0196         return;
0197 
0198     m_url = url;
0199     Q_EMIT urlChanged(m_url);
0200 }
0201 
0202 void Project::setCurrentBranch(const QString &currentBranch)
0203 {
0204     if (m_currentBranch == currentBranch)
0205         return;
0206 
0207     m_currentBranch = currentBranch;
0208     Q_EMIT currentBranchChanged(m_currentBranch);
0209 }
0210 
0211 static QString statusIcon(Git::FileStatus::Status status)
0212 {
0213     switch (status) {
0214     case Git::FileStatus::Status::Added:
0215         return QStringLiteral("git-status-added");
0216     case Git::FileStatus::Status::Ignored:
0217         return QStringLiteral("git-status-ignored");
0218     case Git::FileStatus::Status::Modified:
0219         return QStringLiteral("git-status-modified");
0220     case Git::FileStatus::Status::Removed:
0221         return QStringLiteral("git-status-removed");
0222     case Git::FileStatus::Status::Renamed:
0223         return QStringLiteral("git-status-renamed");
0224     case Git::FileStatus::Status::Unknown:
0225     case Git::FileStatus::Status::Untracked:
0226         return QStringLiteral("git-status-unknown");
0227     case Git::FileStatus::Status::Copied:
0228     case Git::FileStatus::Status::UpdatedButInmerged:
0229     case Git::FileStatus::Status::Unmodified:
0230         return QStringLiteral("git-status-update");
0231     default:
0232         qWarning() << "Unknown icon" ;
0233     }
0234     return QStringLiteral("git-status-update");
0235 }
0236 
0237 
0238 QString Project::fileStatusIcon(const QString &file)
0239 {
0240     if(!m_manager->isValid())
0241     {
0242         return "error";
0243     }
0244 
0245     auto url = QUrl::fromUserInput(m_url);
0246 
0247     if(!url.isParentOf(QUrl::fromUserInput(file)))
0248     {
0249         return "love";
0250     }
0251 
0252     QFileInfo info(QUrl::fromUserInput(file).toLocalFile());
0253     if(info.isDir())
0254     {
0255         Git::FileStatus::Status status = Git::FileStatus::Unmodified;
0256         for (const auto &s : std::as_const(m_filesStatus))
0257         {
0258             const auto filePath = m_manager->path() + QLatin1Char('/') + s.name();
0259 
0260             if (!filePath.startsWith(info.absoluteFilePath()))
0261             {
0262                 continue;
0263             }
0264 
0265             if (status == Git::FileStatus::Unmodified)
0266             {
0267                 status = s.status();
0268             } else if (status != s.status()) {
0269                 return statusIcon(Git::FileStatus::Modified);
0270             }
0271         }
0272 
0273         return statusIcon(status);
0274     }
0275 
0276     auto relativeUrl = QString(file).replace(url.toString()+"/", "");
0277 
0278     auto result = std::find_if(m_filesStatus.constBegin(), m_filesStatus.constEnd(), [relativeUrl](const Git::FileStatus &file)
0279     {
0280         return relativeUrl == file.name();
0281     });
0282 
0283     if(result != m_filesStatus.constEnd())
0284     {
0285         return statusIcon((*result).status());
0286     }
0287     //    return Git::statusIcon()
0288     return statusIcon(Git::FileStatus::Unmodified);
0289 
0290 }
0291 
0292 QString Project::createHashLink(const QString &hash) const
0293 {
0294     // TODO: remove also this one
0295     auto log = m_manager->logsModel()->findLogByHash(hash);
0296     if (!log)
0297         return {};
0298 
0299     return QStringLiteral(R"(<a href="hash:%1">%2</a> )").arg(log->commitHash(), log->subject());
0300 }
0301 
0302 
0303 QVariantMap Project::commitAuthor(const QString &id)
0304 {
0305     QVariantMap res;
0306     if(!m_manager->isValid())
0307         return res;
0308 
0309     auto commit = m_manager->logsModel()->findLogByHash(id, Git::LogsModel::LogMatchType::BeginMatch);
0310 
0311     if(!commit)
0312         return res;
0313 
0314     res.insert("message", commit->body());
0315     res.insert("shortMessage", commit->subject());
0316 
0317     res.insert("name", commit->authorName());
0318     res.insert("fullName", commit->committerName());
0319     res.insert("email", commit->authorEmail());
0320     res.insert("date", commit->commitDate());
0321     res.insert("branch", commit->branch());
0322 
0323     QStringList parentHashHtml;
0324     for (const auto &parent : commit->parents())
0325         parentHashHtml.append(createHashLink(parent));
0326 
0327     QStringList childsHashHtml;
0328     for (const auto &child : commit->childs())
0329         childsHashHtml.append(createHashLink(child));
0330 
0331     res.insert("parentCommits", parentHashHtml);
0332     res.insert("childCommits", childsHashHtml);
0333 
0334     auto files = m_manager->changedFiles(id);
0335 
0336     QVariantList filesHtml;
0337 
0338     for (auto i = files.constBegin(); i != files.constEnd(); ++i)
0339     {
0340         QString color;
0341         switch (i.value()) {
0342         case Git::ChangeStatus::Modified:
0343             color = "orange";
0344             break;
0345         case Git::ChangeStatus::Added:
0346             color = "green";
0347             break;
0348         case Git::ChangeStatus::Removed:
0349             color = "red";
0350             break;
0351 
0352         case Git::ChangeStatus::Unknown:
0353         case Git::ChangeStatus::Unmodified:
0354         case Git::ChangeStatus::Renamed:
0355         case Git::ChangeStatus::Copied:
0356         case Git::ChangeStatus::UpdatedButInmerged:
0357         case Git::ChangeStatus::Ignored:
0358         case Git::ChangeStatus::Untracked:
0359             break;
0360         }
0361 
0362         filesHtml << QVariantMap {{"color", color}, {"url",i.key()}};
0363     }
0364 
0365 
0366     res.insert("changedFiles", filesHtml);
0367 
0368 
0369 
0370     qDebug() << "CHANGED FILES" << childsHashHtml;
0371     return res;
0372 }
0373 
0374 QVariantMap Project::remoteInfo(const QString &remoteName)
0375 {
0376     QVariantMap res;
0377     if(!m_manager->isValid())
0378         return res;
0379 
0380 
0381     auto remote = m_manager->remoteDetails(remoteName);
0382 
0383     res.insert("name", remote.name);
0384     res.insert("url", remote.pushUrl);
0385 
0386     return res;
0387 }
0388 
0389 void Project::clone(const QString &url)
0390 {
0391     const auto mUrl = QUrl::fromUserInput(m_url);
0392     QDir dir (mUrl.toLocalFile());
0393     if(!dir.exists())
0394     {
0395         if(!dir.mkpath("."))
0396         {
0397             setStatus(StatusMessage::Error, i18n("Failed to create directory for repository."));
0398             return;
0399         }else
0400         {
0401             m_title = dir.dirName();
0402             Q_EMIT titleChanged(m_title);
0403         }
0404     }else
0405     {
0406         QDir gitDir (mUrl.toLocalFile()+"/.dir");
0407         if(gitDir.exists())
0408         {
0409             return;
0410         }
0411     }
0412 
0413     auto op = [remoteUrl = url, where = mUrl]()
0414     {
0415 
0416         Git::CloneCommand cmd;
0417         cmd.setRepoUrl(remoteUrl);
0418         cmd.setLocalPath(where.toLocalFile());
0419         Git::run(where.toLocalFile(), cmd);
0420     };
0421 
0422     m_cloneWatcher = new QFutureWatcher<void>;
0423 
0424     connect(m_cloneWatcher, &QFutureWatcher<void>::finished, [this]()
0425     {
0426         qDebug() << "Setting data" << m_url;
0427         this->setData(m_url);
0428     });
0429 
0430     auto future = QtConcurrent::run(op);
0431     m_cloneWatcher->setFuture(future);
0432 
0433     setStatus(StatusMessage::Loading, i18n("Start cloning %1 repository.", m_title));
0434 }
0435 
0436 void Project::checkout(const QString &target, const QString &remote, bool force, Git::CommandSwitchBranch::Mode mode)
0437 {
0438     auto cmd = new Git::CommandSwitchBranch(m_manager);
0439 
0440     cmd->setTarget(target);
0441     cmd->setMode(mode);
0442     cmd->setForce(force);
0443     cmd->setRemoteBranch(remote);
0444 
0445     auto runner = new ActionRunner(m_manager->path());
0446     connect(runner, &ActionRunner::actionFished, [this, runner](bool ok, const QString &message)
0447     {
0448         Q_EMIT actionFinished(ok, message);
0449         runner->deleteLater();
0450     });
0451     runner->run(cmd);
0452 }
0453 
0454 void Project::stash()
0455 {
0456     auto runner = new ActionRunner(m_manager->path());
0457     connect(runner, &ActionRunner::actionFished, [this, runner](bool ok, const QString &message)
0458     {
0459         Q_EMIT actionFinished(ok, message);
0460         runner->deleteLater();
0461     });
0462     runner->run({"stash"});
0463 }
0464 
0465 void Project::stashPop()
0466 {
0467     auto runner = new ActionRunner(m_manager->path());
0468     connect(runner, &ActionRunner::actionFished, [this, runner](bool ok, const QString &message)
0469     {
0470         Q_EMIT actionFinished(ok, message);
0471         runner->deleteLater();
0472     });
0473     runner->run({"stash pop"});
0474 }
0475 
0476 void Project::pull(const QString &remote, const QString &branch, Git::CommandPull::Rebase rebase, Git::CommandPull::FastForward fastforward, bool squash, bool noCommit, bool prune, bool tags)
0477 {
0478     auto cmd = new Git::CommandPull();
0479 
0480     cmd->setRemote(remote);
0481     cmd->setBranch(branch);
0482     cmd->setRebase(rebase);
0483     cmd->setFastForward(fastforward);
0484     cmd->setSquash(squash);
0485     cmd->setNoCommit(noCommit);
0486     cmd->setPrune(prune);
0487     cmd->setTags(tags);
0488 
0489     auto runner = new ActionRunner(m_manager->path());
0490     connect(runner, &ActionRunner::actionFished, [this, runner](bool ok, const QString &message)
0491             {
0492                 Q_EMIT actionFinished(ok, message);
0493                 runner->deleteLater();
0494             });
0495     runner->run(cmd);
0496 }
0497 
0498 StatusMessage* Project::status() const
0499 {
0500     return m_status;
0501 }
0502 
0503 QStringList Project::allBranches() const
0504 {
0505     if(!m_manager->isValid())
0506         return {};
0507 
0508     return m_manager->branches();
0509 }
0510 
0511 QStringList Project::remoteBranches() const
0512 {
0513     if(!m_manager->isValid())
0514         return {};
0515 
0516     return m_manager->remotes();
0517 }
0518 
0519 
0520 void Project::setHeadBranch()
0521 {
0522     if(!m_manager->isValid())
0523         return;
0524 
0525 
0526     m_headBranch.clear();
0527     auto headRef = m_manager->currentBranch();
0528 
0529     m_headBranch.insert("name", headRef);
0530     m_headBranch.insert("remote", m_manager->currentRemote());
0531     //        m_headBranch.insert("isLocal", headRef.isLocal());
0532     //        m_headBranch.insert("isRemote", headRef.isRemote());
0533     //        m_headBranch.insert("prefix", headRef.prefix());
0534     //        m_headBranch.insert("upstreamRemoteName", headRef.upstreamRemoteName(r));
0535     //        m_headBranch.insert("upstreamName", headRef.upstreamName(r));
0536     //        m_headBranch.insert("isCurrentBranch", headRef.isCurrentBranch());
0537 
0538 
0539 
0540     Q_EMIT headBranchChanged(m_headBranch);
0541 }
0542 
0543 void Project::setStatus(StatusMessage::StatusCode code, const QString &message)
0544 {
0545     m_status->code = code;
0546     m_status->message = message;
0547     Q_EMIT statusChanged();
0548 }
0549 
0550 
0551 StatusMessage::StatusMessage(QObject *parent) : QObject(parent)
0552   ,code(StatusCode::Ready)
0553   ,message(i18n("Nothing to see."))
0554 {
0555 
0556 }