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"