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 ¤tBranch) 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 }