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"