File indexing completed on 2024-04-28 04:37:04

0001 /*
0002     SPDX-FileCopyrightText: 2010 Aleix Pol Gonzalez <aleixpol@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "projectchangesmodel.h"
0008 
0009 #include "debug.h"
0010 
0011 #include <KLocalizedString>
0012 
0013 #include <vcs/interfaces/ibasicversioncontrol.h>
0014 #include <interfaces/ibranchingversioncontrol.h>
0015 #include <interfaces/iplugin.h>
0016 #include <interfaces/iproject.h>
0017 #include <interfaces/icore.h>
0018 #include <interfaces/iplugincontroller.h>
0019 #include <interfaces/iprojectcontroller.h>
0020 #include <vcs/vcsstatusinfo.h>
0021 #include <vcs/vcsjob.h>
0022 #include <interfaces/iruncontroller.h>
0023 #include <interfaces/idocumentcontroller.h>
0024 #include <project/projectmodel.h>
0025 #include <util/path.h>
0026 
0027 #include <QDir>
0028 #include <QIcon>
0029 
0030 #include <array>
0031 
0032 using namespace KDevelop;
0033 
0034 ProjectChangesModel::ProjectChangesModel(QObject* parent)
0035     : VcsFileChangesModel(parent)
0036 {
0037     const auto projects = ICore::self()->projectController()->projects();
0038     for (IProject* p : projects) {
0039         addProject(p);
0040     }
0041 
0042     connect(ICore::self()->projectController(), &IProjectController::projectOpened,
0043                                               this, &ProjectChangesModel::addProject);
0044     connect(ICore::self()->projectController(), &IProjectController::projectClosing,
0045                                               this, &ProjectChangesModel::removeProject);
0046     
0047     connect(ICore::self()->documentController(), &IDocumentController::documentSaved,
0048                                                 this, &ProjectChangesModel::documentSaved);
0049     connect(ICore::self()->projectController()->projectModel(), &ProjectModel::rowsInserted,
0050                                                 this, &ProjectChangesModel::itemsAdded);
0051     
0052     connect(ICore::self()->runController(), &IRunController::jobUnregistered, this, &ProjectChangesModel::jobUnregistered);
0053 }
0054 
0055 ProjectChangesModel::~ProjectChangesModel()
0056 {}
0057 
0058 void ProjectChangesModel::addProject(IProject* p)
0059 {
0060     auto* it = new QStandardItem(p->name());
0061     it->setData(p->name(), ProjectChangesModel::ProjectNameRole);
0062     IPlugin* plugin = p->versionControlPlugin();
0063     if(plugin) {
0064         auto* vcs = plugin->extension<IBasicVersionControl>();
0065 
0066         auto info = ICore::self()->pluginController()->pluginInfo(plugin);
0067 
0068         it->setIcon(QIcon::fromTheme(info.iconName()));
0069         it->setToolTip(vcs->name());
0070 
0071         auto* branchingExtension = plugin->extension<KDevelop::IBranchingVersionControl>();
0072         if(branchingExtension) {
0073             const auto pathUrl = p->path().toUrl();
0074             branchingExtension->registerRepositoryForCurrentBranchChanges(pathUrl);
0075             // can't use new signal slot syntax here, IBranchingVersionControl is not a QObject
0076             connect(plugin, SIGNAL(repositoryBranchChanged(QUrl)), this, SLOT(repositoryBranchChanged(QUrl)));
0077             repositoryBranchChanged(pathUrl);
0078         } else
0079             reload(QList<IProject*>() << p);
0080     } else {
0081         it->setEnabled(false);
0082     }
0083     
0084     appendRow(it);
0085 }
0086 
0087 void ProjectChangesModel::removeProject(IProject* p)
0088 {
0089     QStandardItem* it=projectItem(p);
0090     if (!it) {
0091         // when the project is closed before it was fully populated, we won't ever see a
0092         // projectOpened signal - handle this gracefully by just ignoring the remove request
0093         return;
0094     }
0095     removeRow(it->row());
0096 }
0097 
0098 QStandardItem* findItemChild(QStandardItem* parent, const QVariant& value, int role = Qt::DisplayRole)
0099 {
0100     for(int i=0; i<parent->rowCount(); i++) {
0101         QStandardItem* curr=parent->child(i);
0102         
0103         if(curr->data(role) == value)
0104             return curr;
0105     }
0106     return nullptr;
0107 }
0108 
0109 QStandardItem* ProjectChangesModel::projectItem(IProject* p) const
0110 {
0111     return findItemChild(invisibleRootItem(), p->name(), ProjectChangesModel::ProjectNameRole);
0112 }
0113 
0114 void ProjectChangesModel::updateState(IProject* p, const KDevelop::VcsStatusInfo& status)
0115 {
0116     QStandardItem* pItem = projectItem(p);
0117     Q_ASSERT(pItem);
0118     
0119     VcsFileChangesModel::updateState(pItem, status);
0120 }
0121 
0122 void ProjectChangesModel::changes(IProject* project, const QList<QUrl>& urls, IBasicVersionControl::RecursionMode mode)
0123 {
0124     IPlugin* vcsplugin=project->versionControlPlugin();
0125     IBasicVersionControl* vcs = vcsplugin ? vcsplugin->extension<IBasicVersionControl>() : nullptr;
0126     
0127     if(vcs && vcs->isVersionControlled(urls.first())) { //TODO: filter?
0128         VcsJob* job=vcs->status(urls, mode);
0129         job->setProperty("urls", QVariant::fromValue<QList<QUrl>>(urls));
0130         job->setProperty("mode", QVariant::fromValue<int>(mode));
0131         job->setProperty("project", QVariant::fromValue(project));
0132         connect(job, &VcsJob::finished, this, &ProjectChangesModel::statusReady);
0133         
0134         ICore::self()->runController()->registerJob(job);
0135     }
0136 }
0137 
0138 void ProjectChangesModel::statusReady(KJob* job)
0139 {
0140     auto* status=static_cast<VcsJob*>(job);
0141 
0142     const QList<QVariant> states = status->fetchResults().toList();
0143     auto* project = job->property("project").value<KDevelop::IProject*>();
0144     if(!project)
0145         return;
0146 
0147     QSet<QUrl> foundUrls;
0148     foundUrls.reserve(states.size());
0149     for (const QVariant& state : states) {
0150         const VcsStatusInfo st = state.value<VcsStatusInfo>();
0151         foundUrls += st.url();
0152 
0153         updateState(project, st);
0154     }
0155 
0156     QStandardItem* itProject = projectItem(project);
0157     if (!itProject) {
0158         qCDebug(PROJECT) << "Project no longer listed in model:" << project->name() << "- skipping update";
0159         return;
0160     }
0161 
0162     IBasicVersionControl::RecursionMode mode = IBasicVersionControl::RecursionMode(job->property("mode").toInt());
0163     const QList<QUrl> projectUrls = urls(itProject);
0164     const QSet<QUrl> uncertainUrls = QSet<QUrl>(projectUrls.begin(), projectUrls.end()).subtract(foundUrls);
0165     const QList<QUrl> sourceUrls = job->property("urls").value<QList<QUrl>>();
0166     for (const QUrl& url : sourceUrls) {
0167         if(url.isLocalFile() && QDir(url.toLocalFile()).exists()) {
0168             for (const QUrl& currentUrl : uncertainUrls) {
0169                 if((mode == IBasicVersionControl::NonRecursive && currentUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) == url.adjusted(QUrl::StripTrailingSlash))
0170                     || (mode == IBasicVersionControl::Recursive && url.isParentOf(currentUrl))
0171                 ) {
0172                     removeUrl(currentUrl);
0173                 }
0174             }
0175         }
0176     }
0177 }
0178 
0179 void ProjectChangesModel::documentSaved(KDevelop::IDocument* document)
0180 {
0181     reload({document->url()});
0182 }
0183 
0184 void ProjectChangesModel::itemsAdded(const QModelIndex& parent, int start, int end)
0185 {
0186     ProjectModel* model=ICore::self()->projectController()->projectModel();
0187     ProjectBaseItem* item=model->itemFromIndex(parent);
0188 
0189     if(!item)
0190         return;
0191 
0192     IProject* project=item->project();
0193     
0194     if(!project)
0195         return;
0196     
0197     QList<QUrl> urls;
0198     
0199     for(int i=start; i<end; i++) {
0200         QModelIndex idx=parent.model()->index(i, 0, parent);
0201         item=model->itemFromIndex(idx);
0202         
0203         if(item->type()==ProjectBaseItem::File || item->type()==ProjectBaseItem::Folder || item->type()==ProjectBaseItem::BuildFolder)
0204             urls += item->path().toUrl();
0205     }
0206         
0207     if(!urls.isEmpty())
0208         changes(project, urls, KDevelop::IBasicVersionControl::NonRecursive);
0209 }
0210 
0211 void ProjectChangesModel::reload(const QList<IProject*>& projects)
0212 {
0213     for (IProject* project : projects) {
0214         changes(project, {project->path().toUrl()}, KDevelop::IBasicVersionControl::Recursive);
0215     }
0216 }
0217 
0218 void ProjectChangesModel::reload(const QList<QUrl>& urls)
0219 {
0220     for (const QUrl& url : urls) {
0221         IProject* project=ICore::self()->projectController()->findProjectForUrl(url);
0222         
0223         if (project) {
0224             // FIXME: merge multiple urls of the same project
0225             changes(project, {url}, KDevelop::IBasicVersionControl::NonRecursive);
0226         }
0227     }
0228 }
0229 
0230 void ProjectChangesModel::reloadAll()
0231 {
0232     QList< IProject* > projects = ICore::self()->projectController()->projects();
0233     reload(projects);
0234 }
0235 
0236 void ProjectChangesModel::jobUnregistered(KJob* job)
0237 {
0238     static const std::array<VcsJob::JobType, 7> readOnly = {
0239         KDevelop::VcsJob::Add,
0240         KDevelop::VcsJob::Remove,
0241         KDevelop::VcsJob::Pull,
0242         KDevelop::VcsJob::Commit,
0243         KDevelop::VcsJob::Move,
0244         KDevelop::VcsJob::Copy,
0245         KDevelop::VcsJob::Revert,
0246     };
0247 
0248     auto* vcsjob = qobject_cast<VcsJob*>(job);
0249     if (vcsjob && std::find(readOnly.begin(), readOnly.end(), vcsjob->type()) != readOnly.end()) {
0250         reloadAll();
0251     }
0252 }
0253 
0254 void ProjectChangesModel::repositoryBranchChanged(const QUrl& url)
0255 {
0256     IProject* project = ICore::self()->projectController()->findProjectForUrl(url);
0257     if(project) {
0258         IPlugin* v = project->versionControlPlugin();
0259         Q_ASSERT(v);
0260         auto* branching = v->extension<IBranchingVersionControl>();
0261         Q_ASSERT(branching);
0262         VcsJob* job = branching->currentBranch(url);
0263         connect(job, &VcsJob::resultsReady, this, &ProjectChangesModel::branchNameReady);
0264         job->setProperty("project", QVariant::fromValue<QObject*>(project));
0265         ICore::self()->runController()->registerJob(job);
0266     }
0267 }
0268 
0269 void ProjectChangesModel::branchNameReady(VcsJob* job)
0270 {
0271     auto* project = qobject_cast<IProject*>(job->property("project").value<QObject*>());
0272     if(job->status()==VcsJob::JobSucceeded) {
0273         QString name = job->fetchResults().toString();
0274         const QString branchName = name.isEmpty() ? i18nc("@item:intext", "no branch") : name;
0275         projectItem(project)->setText(i18nc("project name (branch name)", "%1 (%2)", project->name(), branchName));
0276     } else {
0277         projectItem(project)->setText(project->name());
0278     }
0279 
0280     reload(QList<IProject*>() << project);
0281 }
0282 
0283 #include "moc_projectchangesmodel.cpp"