File indexing completed on 2024-04-28 04:38:51
0001 /* 0002 SPDX-FileCopyrightText: 2020 Jonathan L. Verner <jonathan.verner@matfyz.cz> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "repostatusmodel.h" 0008 0009 #include "debug.h" 0010 #include "gitplugin.h" 0011 0012 #include <interfaces/icore.h> 0013 #include <interfaces/idocumentcontroller.h> 0014 #include <interfaces/iplugin.h> 0015 #include <interfaces/iplugincontroller.h> 0016 #include <interfaces/iproject.h> 0017 #include <interfaces/iprojectcontroller.h> 0018 #include <interfaces/iruncontroller.h> 0019 #include <project/projectmodel.h> 0020 #include <util/path.h> 0021 #include <vcs/interfaces/ibasicversioncontrol.h> 0022 #include <vcs/interfaces/ibranchingversioncontrol.h> 0023 #include <vcs/vcsjob.h> 0024 #include <vcs/vcsstatusinfo.h> 0025 0026 #include <KLocalizedString> 0027 0028 #include <QDir> 0029 #include <QIcon> 0030 #include <QMimeDatabase> 0031 0032 #include <array> 0033 0034 using namespace KDevelop; 0035 0036 Q_DECLARE_METATYPE(IProject*) 0037 0038 RepoStatusModel::RepoStatusModel(QObject* parent) 0039 : QStandardItemModel(parent) 0040 { 0041 const auto projects = ICore::self()->projectController()->projects(); 0042 for (IProject* p : projects) { 0043 addProject(p); 0044 } 0045 0046 connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &RepoStatusModel::addProject); 0047 connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, 0048 &RepoStatusModel::removeProject); 0049 connect(ICore::self()->projectController()->projectModel(), &ProjectModel::rowsInserted, this, 0050 &RepoStatusModel::itemsAdded); 0051 connect(ICore::self()->documentController(), &IDocumentController::documentSaved, this, 0052 &RepoStatusModel::documentSaved); 0053 connect(ICore::self()->runController(), &IRunController::jobUnregistered, this, &RepoStatusModel::jobUnregistered); 0054 } 0055 0056 RepoStatusModel::~RepoStatusModel() {} 0057 0058 void RepoStatusModel::addProject(const IProject* p) 0059 { 0060 if (const GitPlugin* const plugin = p->versionControlPlugin()->extension<GitPlugin>()) { 0061 const auto projectIt = new QStandardItem(p->name()); 0062 const auto indexIt 0063 = new QStandardItem(QIcon::fromTheme(QStringLiteral("flag-green")), 0064 i18nc("Files in a vcs which have changes staged for commit", "Staged changes")); 0065 const auto worktreeIt = new QStandardItem( 0066 QIcon::fromTheme(QStringLiteral("flag-yellow")), 0067 i18nc("Files in a vcs which have changes not yet staged for commit", "Unstaged changes")); 0068 const auto conflictIt 0069 = new QStandardItem(QIcon::fromTheme(QStringLiteral("flag-red")), 0070 i18nc("Files in a vcs which have unresolved (merge) conflits", "Conflicts")); 0071 const auto untrackedIt = new QStandardItem(QIcon::fromTheme(QStringLiteral("flag-black")), 0072 i18nc("Files which are not tracked by a vcs", "Untracked files")); 0073 const auto pluginInfo = ICore::self()->pluginController()->pluginInfo(plugin); 0074 const auto pathUrl = p->path().toUrl(); 0075 0076 projectIt->setData(p->name(), RepoStatusModel::NameRole); 0077 projectIt->setData(pathUrl, RepoStatusModel::ProjectUrlRole); 0078 projectIt->setData(ProjectRoot, AreaRole); 0079 projectIt->setSelectable(false); 0080 projectIt->setIcon(QIcon::fromTheme(pluginInfo.iconName())); 0081 0082 indexIt->setData(i18nc("Files in a vcs which have changes staged for commit", "Staged"), 0083 RepoStatusModel::NameRole); 0084 indexIt->setToolTip(i18n("Files with changes staged for commit")); 0085 indexIt->setData(IndexRoot, AreaRole); 0086 indexIt->setData(pathUrl, RepoStatusModel::ProjectUrlRole); 0087 indexIt->setSelectable(false); 0088 0089 worktreeIt->setData(i18nc("Files in a vcs which have changes not checked in to repo", "Modified"), 0090 RepoStatusModel::NameRole); 0091 worktreeIt->setToolTip(i18n("Files with changes")); 0092 worktreeIt->setData(WorkTreeRoot, AreaRole); 0093 worktreeIt->setData(pathUrl, RepoStatusModel::ProjectUrlRole); 0094 worktreeIt->setSelectable(false); 0095 0096 conflictIt->setData(i18nc("Files in git which have unresolved (merge) conflits", "Conflicts"), 0097 RepoStatusModel::NameRole); 0098 conflictIt->setToolTip(i18n("Files with unresolved (merge) conflicts")); 0099 conflictIt->setData(ConflictRoot, AreaRole); 0100 conflictIt->setData(pathUrl, RepoStatusModel::ProjectUrlRole); 0101 conflictIt->setSelectable(false); 0102 0103 untrackedIt->setData(i18nc("Files which are not tracked by a vcs", "Untracked"), RepoStatusModel::NameRole); 0104 untrackedIt->setToolTip(i18n("Files not tracked in VCS")); 0105 untrackedIt->setData(UntrackedRoot, AreaRole); 0106 untrackedIt->setData(pathUrl, RepoStatusModel::ProjectUrlRole); 0107 untrackedIt->setSelectable(false); 0108 0109 appendRow(projectIt); 0110 projectIt->appendRows({ indexIt, worktreeIt, conflictIt, untrackedIt }); 0111 0112 /* The project has the current branch appended to its name in the display, 0113 * we therefore need to update it whenever the branch changes */ 0114 // can't use new signal slot syntax here, IBranchingVersionControl is not a QObject 0115 connect(plugin, SIGNAL(repositoryBranchChanged(QUrl)), this, SLOT(repositoryBranchChanged(QUrl))); 0116 repositoryBranchChanged(pathUrl); 0117 } 0118 } 0119 0120 void RepoStatusModel::removeProject(const IProject* p) 0121 { 0122 // when the project is closed before it was fully populated, we won't ever see a 0123 // projectOpened signal - handle this gracefully by just ignoring the remove request 0124 // Also, we need to ignore this for projects which don't have a git VCS and thus were 0125 // never added 0126 if (auto it = findProject(p)) { 0127 removeRow(it->row()); 0128 } 0129 } 0130 0131 /* Finds the immediate child of a `parent` item whose data with role `role` has value `value` */ 0132 QStandardItem* findItemChild(const QStandardItem* parent, const QVariant& value, int role = Qt::DisplayRole) 0133 { 0134 for (int i = 0; i < parent->rowCount(); i++) { 0135 QStandardItem* curr = parent->child(i); 0136 0137 if (curr->data(role) == value) 0138 return curr; 0139 } 0140 0141 return nullptr; 0142 } 0143 0144 QStandardItem* RepoStatusModel::findProject(const IProject* project) const 0145 { 0146 if (!project) 0147 return nullptr; 0148 return findItemChild(invisibleRootItem(), project->name(), RepoStatusModel::NameRole); 0149 } 0150 0151 RepoStatusModel::ProjectItem RepoStatusModel::projectItem(const IProject* p) const 0152 { 0153 if (auto proj = findProject(p)) { 0154 return projectItem(proj); 0155 } 0156 return {}; // This occurs when the project has another VCS or no VCS configured. 0157 } 0158 0159 RepoStatusModel::ProjectItem RepoStatusModel::projectItem(QStandardItem* proj) const 0160 { 0161 Q_ASSERT(proj); 0162 ProjectItem pi; 0163 pi.project = proj; 0164 pi.index = findItemChild(proj, RepoStatusModel::IndexRoot, RepoStatusModel::AreaRole); 0165 pi.worktree = findItemChild(proj, RepoStatusModel::WorkTreeRoot, RepoStatusModel::AreaRole); 0166 pi.conflicts = findItemChild(proj, RepoStatusModel::ConflictRoot, RepoStatusModel::AreaRole); 0167 pi.untracked = findItemChild(proj, RepoStatusModel::UntrackedRoot, RepoStatusModel::AreaRole); 0168 return pi; 0169 } 0170 0171 bool RepoStatusModel::showInIndex(const GitPlugin::ExtendedState state) 0172 { 0173 switch (state) { 0174 case GitPlugin::GitMX: 0175 case GitPlugin::GitMM: 0176 case GitPlugin::GitMD: 0177 case GitPlugin::GitAX: 0178 case GitPlugin::GitAM: 0179 case GitPlugin::GitAD: 0180 case GitPlugin::GitDX: 0181 case GitPlugin::GitDR: 0182 case GitPlugin::GitDC: 0183 case GitPlugin::GitRX: 0184 case GitPlugin::GitRM: 0185 case GitPlugin::GitRD: 0186 case GitPlugin::GitCX: 0187 case GitPlugin::GitCM: 0188 case GitPlugin::GitCD: 0189 return true; 0190 default: 0191 return false; 0192 } 0193 } 0194 0195 bool RepoStatusModel::showInWorkTree(const GitPlugin::ExtendedState state) 0196 { 0197 switch (state) { 0198 case GitPlugin::GitXM: 0199 case GitPlugin::GitXD: 0200 case GitPlugin::GitXR: 0201 case GitPlugin::GitXC: 0202 case GitPlugin::GitMM: 0203 case GitPlugin::GitMD: 0204 case GitPlugin::GitAM: 0205 case GitPlugin::GitAD: 0206 case GitPlugin::GitDR: 0207 case GitPlugin::GitDC: 0208 case GitPlugin::GitRM: 0209 case GitPlugin::GitRD: 0210 case GitPlugin::GitCM: 0211 case GitPlugin::GitCD: 0212 return true; 0213 default: 0214 return false; 0215 } 0216 } 0217 0218 QString extendedStateToStr(const GitPlugin::ExtendedState state) 0219 { 0220 switch (state) { 0221 case GitPlugin::GitXM: 0222 return i18nc("@item file has unstaged changes", "Modified (unstaged)"); 0223 case GitPlugin::GitXD: 0224 return i18nc("@item file was deleted from worktree", "Deleted (unstaged)"); 0225 case GitPlugin::GitXR: 0226 return i18nc("@item file was renamed in worktree", "Renamed (unstaged)"); 0227 case GitPlugin::GitXC: 0228 return i18nc("@item file was copied in worktree", "Copied (unstaged)"); 0229 case GitPlugin::GitMX: 0230 return i18nc("@item file has staged changes", "Modified (staged)"); 0231 case GitPlugin::GitMM: 0232 return i18nc("@item file has both staged and unstaged changes", "Modified (unstaged changes)"); 0233 case GitPlugin::GitMD: 0234 return i18nc("@item file has staged changes and was deleted in worktree", "Modified (unstaged deletion)"); 0235 case GitPlugin::GitAM: 0236 return i18nc("@item file was added to versioncontrolsystem and has unstaged changes", 0237 "Added (unstaged changes)"); 0238 case GitPlugin::GitAD: 0239 return i18nc("@item file was added to versioncontrolsystem and deleted in worktree", 0240 "Added (unstaged deletion)"); 0241 case GitPlugin::GitDR: 0242 return i18nc("@item file was deleted from versioncontrolsystem and renamed in worktree", 0243 "Deleted (unstaged rename)"); 0244 case GitPlugin::GitDC: 0245 return i18nc("@item file was deleted from versioncontrolsystem and copied in worktree", 0246 "Deleted (unstaged copy)"); 0247 case GitPlugin::GitRX: 0248 return i18nc("@item file was renamed in versioncontrolsystem", "Renamed (staged)"); 0249 case GitPlugin::GitRM: 0250 return i18nc("@item file was renamed in versioncontrolsystem and has unstaged changes", 0251 "Renamed (unstaged changes)"); 0252 case GitPlugin::GitRD: 0253 return i18nc("@item file was renamed in versioncontrolsystem and was deleted in worktree", 0254 "Renamed (unstaged deletion)"); 0255 case GitPlugin::GitCX: 0256 return i18nc("@item file was copied in versioncontrolsystem", "Copied"); 0257 case GitPlugin::GitCM: 0258 return i18nc("@item file was copied in versioncontrolsystem and has unstaged changes", 0259 "Copied (unstaged changes)"); 0260 case GitPlugin::GitCD: 0261 return i18nc("@item file was copied in versioncontrolsystem and was deleted in worktree", 0262 "Copied (unstaged deletion)"); 0263 case GitPlugin::GitConflicts: 0264 return i18nc("@item file has unresolved merge conflicts", "Unresolved conflicts"); 0265 case GitPlugin::GitUntracked: 0266 return i18nc("@item file is not under vcs", "Untracked"); 0267 case GitPlugin::GitInvalid: 0268 default: 0269 return i18nc("file in unknown (invalid) state", "Unknown"); 0270 } 0271 } 0272 0273 bool RepoStatusModel::showInConflicts(const GitPlugin::ExtendedState state) 0274 { 0275 switch (state) { 0276 case GitPlugin::GitConflicts: 0277 return true; 0278 default: 0279 return false; 0280 } 0281 } 0282 0283 bool RepoStatusModel::showInUntracked(const GitPlugin::ExtendedState state) 0284 { 0285 switch (state) { 0286 case GitPlugin::GitUntracked: 0287 return true; 0288 default: 0289 return false; 0290 } 0291 } 0292 0293 const QList<QStandardItem*> RepoStatusModel::allItems(const QStandardItem* parent) const 0294 { 0295 QList<QStandardItem*> ret; 0296 if (!parent) 0297 parent = invisibleRootItem(); 0298 const int rc = parent->rowCount(); 0299 ret.reserve(rc); 0300 for (int i = 0; i < rc; i++) { 0301 QStandardItem* child = parent->child(i); 0302 ret << parent->child(i); 0303 ret += allItems(child); 0304 } 0305 0306 return ret; 0307 } 0308 0309 void RepoStatusModel::removeUrl(const QUrl& url, const QStandardItem* parent) 0310 { 0311 for (const auto item : allItems(parent)) { 0312 if (item->data(UrlRole).toUrl() == url) 0313 removeRow(item->index().row(), item->parent()->index()); 0314 } 0315 } 0316 0317 QList<QUrl> RepoStatusModel::childUrls(const ProjectItem& pItem) const 0318 { 0319 Q_ASSERT(pItem.project); 0320 if (!pItem.project) { 0321 qCWarning(PLUGIN_GIT) << "A null QStandardItem passed to" << Q_FUNC_INFO; 0322 return {}; 0323 } 0324 0325 const auto children 0326 = allItems(pItem.index) + allItems(pItem.worktree) + allItems(pItem.conflicts) + allItems(pItem.untracked); 0327 0328 QList<QUrl> ret; 0329 ret.reserve(children.size()); 0330 for (const auto ch : children) { 0331 ret << indexFromItem(ch).data(UrlRole).toUrl(); 0332 } 0333 0334 return ret; 0335 } 0336 0337 void RepoStatusModel::updateState(const ProjectItem& pItem, const VcsStatusInfo& status) 0338 { 0339 Q_ASSERT(pItem.project); 0340 0341 /* To make the code cleaner, we remove the item from the 0342 * model first before adding it again to the correct areas */ 0343 removeUrl(status.url(), pItem.project); 0344 0345 QString path = ICore::self()->projectController()->prettyFileName(status.url(), IProjectController::FormatPlain); 0346 int pos = path.indexOf(QLatin1Char(':')); 0347 if (pos > -1) 0348 path = path.mid(pos + 1); 0349 QMimeType mime = status.url().isLocalFile() 0350 ? QMimeDatabase().mimeTypeForFile(status.url().toLocalFile(), QMimeDatabase::MatchExtension) 0351 : QMimeDatabase().mimeTypeForUrl(status.url()); 0352 QIcon icon = QIcon::fromTheme(mime.iconName()); 0353 0354 QStandardItem* item = nullptr; 0355 GitPlugin::ExtendedState state = (GitPlugin::ExtendedState)status.extendedState(); 0356 QString stateStr = extendedStateToStr(state); 0357 0358 if (showInIndex(state)) { 0359 item = new QStandardItem(icon, path); 0360 item->setData(Index, AreaRole); 0361 item->setData(status.url(), UrlRole); 0362 item->setToolTip(status.url().path() + i18n(" (staged)")); 0363 pItem.index->appendRow(item); 0364 } 0365 0366 if (showInWorkTree(state)) { 0367 item = new QStandardItem(icon, path); 0368 item->setData(WorkTree, AreaRole); 0369 item->setData(status.url(), UrlRole); 0370 item->setToolTip(status.url().path() + i18n(" (unstaged)")); 0371 pItem.worktree->appendRow(item); 0372 } 0373 0374 if (showInConflicts(state)) { 0375 item = new QStandardItem(icon, path); 0376 item->setData(Conflicts, AreaRole); 0377 item->setData(status.url(), UrlRole); 0378 item->setToolTip(status.url().path() + i18n(" (conflicts)")); 0379 pItem.conflicts->appendRow(item); 0380 } 0381 0382 if (showInUntracked(state)) { 0383 item = new QStandardItem(icon, path); 0384 item->setData(Untracked, AreaRole); 0385 item->setData(status.url(), UrlRole); 0386 item->setToolTip(status.url().path() + i18n(" (untracked)")); 0387 pItem.untracked->appendRow(item); 0388 } 0389 0390 if (item) { 0391 item->setData(state, StatusRole); 0392 item->setData(stateStr, ReadableStatusRole); 0393 item->setData(pItem.project->data(ProjectUrlRole), ProjectUrlRole); 0394 } 0395 } 0396 0397 void RepoStatusModel::fetchStatusesForUrls(IProject* project, const QList<QUrl>& urls, 0398 IBasicVersionControl::RecursionMode mode) 0399 { 0400 IPlugin* vcsplugin = project->versionControlPlugin(); 0401 0402 if (auto* vcs = vcsplugin ? vcsplugin->extension<IBasicVersionControl>() : nullptr) { 0403 VcsJob* job = vcs->status(urls, mode); 0404 job->setProperty("urls", QVariant::fromValue<QList<QUrl>>(urls)); 0405 job->setProperty("mode", QVariant::fromValue<int>(mode)); 0406 job->setProperty("project", QVariant::fromValue(project)); 0407 connect(job, &VcsJob::finished, this, &RepoStatusModel::statusReady); 0408 ICore::self()->runController()->registerJob(job); 0409 } 0410 } 0411 0412 const QList<QStandardItem*> RepoStatusModel::projectRoots() const 0413 { 0414 QList<QStandardItem*> ret; 0415 auto* root = invisibleRootItem(); 0416 for (int i = 0; i < root->rowCount(); i++) { 0417 QStandardItem* child = root->child(i); 0418 0419 if (child->data(AreaRole) == ProjectRoot) 0420 ret << child; 0421 } 0422 0423 return ret; 0424 } 0425 0426 const QList<QStandardItem*> RepoStatusModel::items(const QStandardItem* project, Areas area) const 0427 { 0428 QList<QStandardItem*> ret; 0429 for (auto* it : allItems(project)) { 0430 if ((RepoStatusModel::Areas)it->data(RepoStatusModel::AreaRole).toInt() == area) 0431 ret << it; 0432 } 0433 0434 return ret; 0435 } 0436 0437 void RepoStatusModel::statusReady(KJob* job) 0438 { 0439 auto* status = qobject_cast<VcsJob*>(job); 0440 if (!status) 0441 return; 0442 0443 const QList<QVariant> states = status->fetchResults().toList(); 0444 auto* project = job->property("project").value<IProject*>(); 0445 0446 const auto itProject = projectItem(project); 0447 if (!itProject.isValid()) 0448 return; 0449 0450 QSet<QUrl> foundUrls; 0451 foundUrls.reserve(states.size()); 0452 for (const auto& state : states) { 0453 const VcsStatusInfo st = state.value<VcsStatusInfo>(); 0454 foundUrls += st.url(); 0455 updateState(itProject, st); 0456 } 0457 0458 IBasicVersionControl::RecursionMode mode = IBasicVersionControl::RecursionMode(job->property("mode").toInt()); 0459 const QList<QUrl> projectUrls = childUrls(itProject); 0460 const QSet<QUrl> uncertainUrls = QSet<QUrl>(projectUrls.begin(), projectUrls.end()).subtract(foundUrls); 0461 const QList<QUrl> sourceUrls = job->property("urls").value<QList<QUrl>>(); 0462 for (const QUrl& url : sourceUrls) { 0463 if (url.isLocalFile() && QDir(url.toLocalFile()).exists()) { 0464 for (const QUrl& currentUrl : uncertainUrls) { 0465 if ((mode == IBasicVersionControl::NonRecursive 0466 && currentUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash) 0467 == url.adjusted(QUrl::StripTrailingSlash)) 0468 || (mode == IBasicVersionControl::Recursive && url.isParentOf(currentUrl))) { 0469 removeUrl(currentUrl, itProject.project); 0470 } 0471 } 0472 } 0473 } 0474 } 0475 0476 void RepoStatusModel::documentSaved(const IDocument* document) 0477 { 0478 reload({ document->url() }); 0479 } 0480 0481 void RepoStatusModel::itemsAdded(const QModelIndex& parent, int start, int end) 0482 { 0483 ProjectModel* model = ICore::self()->projectController()->projectModel(); 0484 ProjectBaseItem* item = model->itemFromIndex(parent); 0485 0486 if (!item) 0487 return; 0488 0489 IProject* project = item->project(); 0490 0491 if (!findProject(project)) 0492 return; 0493 0494 QList<QUrl> urls; 0495 0496 for (int i = start; i < end; i++) { 0497 QModelIndex idx = parent.model()->index(i, 0, parent); 0498 item = model->itemFromIndex(idx); 0499 0500 if (item->type() == ProjectBaseItem::File || item->type() == ProjectBaseItem::Folder 0501 || item->type() == ProjectBaseItem::BuildFolder) 0502 urls += item->path().toUrl(); 0503 } 0504 0505 if (!urls.isEmpty()) 0506 fetchStatusesForUrls(project, urls, IBasicVersionControl::NonRecursive); 0507 } 0508 0509 void RepoStatusModel::reload(const QList<IProject*>& projects) 0510 { 0511 for (IProject* project : projects) { 0512 if (findProject(project)) { 0513 fetchStatusesForUrls(project, { project->path().toUrl() }, IBasicVersionControl::Recursive); 0514 } 0515 } 0516 } 0517 0518 void RepoStatusModel::reload(const QList<QUrl>& urls) 0519 { 0520 for (const QUrl& url : urls) { 0521 IProject* project = ICore::self()->projectController()->findProjectForUrl(url); 0522 0523 if (findProject(project)) { 0524 // FIXME: merge multiple urls of the same project 0525 fetchStatusesForUrls(project, { url }, IBasicVersionControl::NonRecursive); 0526 } 0527 } 0528 } 0529 0530 void RepoStatusModel::reloadAll() 0531 { 0532 QList<IProject*> projects = ICore::self()->projectController()->projects(); 0533 reload(projects); 0534 } 0535 0536 void RepoStatusModel::jobUnregistered(KJob* job) 0537 { 0538 static const std::array<VcsJob::JobType, 9> readOnly = { 0539 VcsJob::Add, 0540 VcsJob::Remove, 0541 VcsJob::Pull, 0542 VcsJob::Commit, 0543 VcsJob::Move, 0544 VcsJob::Copy, 0545 VcsJob::Revert, 0546 VcsJob::Reset, 0547 VcsJob::Apply 0548 }; 0549 0550 auto* vcsjob = qobject_cast<VcsJob*>(job); 0551 if (vcsjob && std::find(readOnly.begin(), readOnly.end(), vcsjob->type()) != readOnly.end()) { 0552 reloadAll(); 0553 } 0554 } 0555 0556 void RepoStatusModel::repositoryBranchChanged(const QUrl& url) 0557 { 0558 IProject* project = ICore::self()->projectController()->findProjectForUrl(url); 0559 if (findProject(project)) { 0560 IPlugin* v = project->versionControlPlugin(); 0561 Q_ASSERT(v); 0562 auto* branching = v->extension<IBranchingVersionControl>(); 0563 Q_ASSERT(branching); 0564 VcsJob* job = branching->currentBranch(url); 0565 connect(job, &VcsJob::resultsReady, this, &RepoStatusModel::branchNameReady); 0566 job->setProperty("project", QVariant::fromValue<QObject*>(project)); 0567 ICore::self()->runController()->registerJob(job); 0568 } 0569 } 0570 0571 void RepoStatusModel::branchNameReady(VcsJob* job) 0572 { 0573 auto* const project = qobject_cast<IProject*>(job->property("project").value<QObject*>()); 0574 const auto item = findProject(project); 0575 if (!item) 0576 return; 0577 0578 if (job->status() == VcsJob::JobSucceeded) { 0579 QString name = job->fetchResults().toString(); 0580 QString branchName = name.isEmpty() ? i18n("no branch") : name; 0581 item->setText(i18nc("project name (branch name)", "%1 (%2)", project->name(), branchName)); 0582 item->setData(branchName, RepoStatusModel::BranchNameRole); 0583 } else { 0584 item->setData(QStringLiteral("unknown"), RepoStatusModel::BranchNameRole); 0585 item->setText(project->name()); 0586 } 0587 0588 reload(QList<IProject*>() << project); 0589 } 0590 0591 #include "moc_repostatusmodel.cpp"