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

0001 /*
0002     SPDX-FileCopyrightText: 2008 Andreas Pakulat <apaku@gmx.de>
0003     SPDX-FileCopyrightText: 2010 Aleix Pol Gonzalez <aleixpol@kde.org>
0004     SPDX-FileCopyrightText: 2017-2018 Friedrich W. H. Kossebau <kossebau@kde.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "vcspluginhelper.h"
0010 
0011 #include <QAction>
0012 #include <QApplication>
0013 #include <QClipboard>
0014 #include <QDialogButtonBox>
0015 #include <QFileInfo>
0016 #include <QMenu>
0017 #include <QTimer>
0018 #include <QVBoxLayout>
0019 #include <QVariant>
0020 
0021 #include <KLocalizedString>
0022 #include <KMessageBox>
0023 #include <KParts/MainWindow>
0024 #include <KTextEditor/AnnotationInterface>
0025 #include <KTextEditor/Document>
0026 #include <KTextEditor/ModificationInterface>
0027 #include <KTextEditor/View>
0028 
0029 #include <interfaces/context.h>
0030 #include <interfaces/contextmenuextension.h>
0031 #include <interfaces/icore.h>
0032 #include <interfaces/idocument.h>
0033 #include <interfaces/idocumentcontroller.h>
0034 #include <interfaces/iplugin.h>
0035 #include <interfaces/iplugincontroller.h>
0036 #include <interfaces/iproject.h>
0037 #include <interfaces/iprojectcontroller.h>
0038 #include <interfaces/iruncontroller.h>
0039 #include <interfaces/isession.h>
0040 #include <interfaces/iuicontroller.h>
0041 #include <util/path.h>
0042 #include <util/scopeddialog.h>
0043 #include <vcs/interfaces/ibasicversioncontrol.h>
0044 #include <vcs/models/vcsannotationmodel.h>
0045 #include <vcs/widgets/vcsannotationitemdelegate.h>
0046 #include <vcs/widgets/vcseventwidget.h>
0047 #include <vcs/widgets/vcscommitdialog.h>
0048 #include <vcs/vcsjob.h>
0049 #include <vcs/vcsrevision.h>
0050 #include <vcs/vcslocation.h>
0051 #include <vcs/vcsdiff.h>
0052 
0053 #include "interfaces/idistributedversioncontrol.h"
0054 #include "vcsevent.h"
0055 #include "debug.h"
0056 #include "widgets/vcsdiffpatchsources.h"
0057 
0058 namespace KDevelop
0059 {
0060 
0061 class VcsPluginHelperPrivate
0062 {
0063 public:
0064     IPlugin * plugin;
0065     IBasicVersionControl * vcs;
0066 
0067     QList<QUrl> ctxUrls;
0068     QAction* commitAction;
0069     QAction* addAction;
0070     QAction* updateAction;
0071     QAction* historyAction;
0072     QAction* annotationAction;
0073     QAction* diffToBaseAction;
0074     QAction* revertAction;
0075     QAction* diffForRevAction;
0076     QAction* diffForRevGlobalAction;
0077     QAction* pushAction;
0078     QAction* pullAction;
0079 
0080     void createActions(VcsPluginHelper* parent) {
0081         auto iconWithFallback = [] (const QString &icon, const QString &fallback) {
0082             return QIcon::fromTheme(icon, QIcon::fromTheme(fallback));
0083         };
0084         commitAction = new QAction(iconWithFallback(QStringLiteral("vcs-commit"), QStringLiteral("svn-commit")), i18nc("@action:inmenu", "Commit..."), parent);
0085         updateAction = new QAction(iconWithFallback(QStringLiteral("vcs-pull"), QStringLiteral("svn-update")), i18nc("@action:inmenu", "Update"), parent);
0086         addAction = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18nc("@action:inmenu", "Add"), parent);
0087         diffToBaseAction = new QAction(iconWithFallback(QStringLiteral("vcs-diff"), QStringLiteral("text-x-patch")), i18nc("@action:inmenu", "Show Differences..."), parent);
0088         revertAction = new QAction(QIcon::fromTheme(QStringLiteral("archive-remove")), i18nc("@action:inmenu", "Revert"), parent);
0089         historyAction = new QAction(QIcon::fromTheme(QStringLiteral("view-history")), i18nc("@action:inmenu revision history", "History..."), parent);
0090         annotationAction = new QAction(QIcon::fromTheme(QStringLiteral("user-properties")), i18nc("@action:inmenu", "Annotation..."), parent);
0091         diffForRevAction = new QAction(iconWithFallback(QStringLiteral("vcs-diff"), QStringLiteral("text-x-patch")), i18nc("@action:inmenu", "Show Diff..."), parent);
0092         diffForRevGlobalAction = new QAction(iconWithFallback(QStringLiteral("vcs-diff"), QStringLiteral("text-x-patch")), i18nc("@action:inmenu", "Show Diff (All Files)..."), parent);
0093         pushAction = new QAction(iconWithFallback(QStringLiteral("vcs-push"), QStringLiteral("arrow-up-double")), i18nc("@action:inmenu", "Push"), parent);
0094         pullAction = new QAction(iconWithFallback(QStringLiteral("vcs-pull"), QStringLiteral("arrow-down-double")), i18nc("@action:inmenu", "Pull"), parent);
0095 
0096         QObject::connect(commitAction, &QAction::triggered, parent, &VcsPluginHelper::commit);
0097         QObject::connect(addAction, &QAction::triggered, parent, &VcsPluginHelper::add);
0098         QObject::connect(updateAction, &QAction::triggered, parent, &VcsPluginHelper::update);
0099         QObject::connect(diffToBaseAction, &QAction::triggered, parent, &VcsPluginHelper::diffToBase);
0100         QObject::connect(revertAction, &QAction::triggered, parent, &VcsPluginHelper::revert);
0101         QObject::connect(historyAction, &QAction::triggered, parent, [=] { parent->history(); });
0102         QObject::connect(annotationAction, &QAction::triggered, parent, &VcsPluginHelper::annotation);
0103         QObject::connect(diffForRevAction, &QAction::triggered, parent, QOverload<>::of(&VcsPluginHelper::diffForRev));
0104         QObject::connect(diffForRevGlobalAction, &QAction::triggered, parent, &VcsPluginHelper::diffForRevGlobal);
0105         QObject::connect(pullAction, &QAction::triggered, parent, &VcsPluginHelper::pull);
0106         QObject::connect(pushAction, &QAction::triggered, parent, &VcsPluginHelper::push);
0107     }
0108 
0109     bool allLocalFiles(const QList<QUrl>& urls)
0110     {
0111         bool ret=true;
0112         for (const QUrl& url : urls) {
0113             QFileInfo info(url.toLocalFile());
0114             ret &= info.isFile();
0115         }
0116         return ret;
0117     }
0118 
0119     QMenu* createMenu(QWidget* parent)
0120     {
0121         auto* menu = new QMenu(vcs->name(), parent);
0122         menu->setIcon(QIcon::fromTheme(ICore::self()->pluginController()->pluginInfo(plugin).iconName()));
0123         menu->addAction(commitAction);
0124         if(plugin->extension<IDistributedVersionControl>()) {
0125             menu->addAction(pushAction);
0126             menu->addAction(pullAction);
0127         } else {
0128             menu->addAction(updateAction);
0129         }
0130         menu->addSeparator();
0131         menu->addAction(addAction);
0132         menu->addAction(revertAction);
0133         menu->addSeparator();
0134         menu->addAction(historyAction);
0135         menu->addAction(annotationAction);
0136         menu->addAction(diffToBaseAction);
0137 
0138         const bool singleVersionedFile = ctxUrls.size() == 1 && vcs->isVersionControlled(ctxUrls.constFirst());
0139         historyAction->setEnabled(singleVersionedFile);
0140         annotationAction->setEnabled(singleVersionedFile && allLocalFiles(ctxUrls));
0141         diffToBaseAction->setEnabled(singleVersionedFile);
0142         commitAction->setEnabled(singleVersionedFile);
0143 
0144         return menu;
0145     }
0146 };
0147 
0148 
0149 VcsPluginHelper::VcsPluginHelper(KDevelop::IPlugin* parent, KDevelop::IBasicVersionControl* vcs)
0150         : QObject(parent)
0151         , d_ptr(new VcsPluginHelperPrivate())
0152 {
0153     Q_D(VcsPluginHelper);
0154 
0155     Q_ASSERT(vcs);
0156     Q_ASSERT(parent);
0157     d->plugin = parent;
0158     d->vcs = vcs;
0159     d->createActions(this);
0160 }
0161 
0162 VcsPluginHelper::~VcsPluginHelper()
0163 {}
0164 
0165 void VcsPluginHelper::addContextDocument(const QUrl &url)
0166 {
0167     Q_D(VcsPluginHelper);
0168 
0169     d->ctxUrls.append(url);
0170 }
0171 
0172 void VcsPluginHelper::disposeEventually(KTextEditor::View *, bool dont)
0173 {
0174     if ( ! dont ) {
0175         deleteLater();
0176     }
0177 }
0178 
0179 void VcsPluginHelper::disposeEventually(KTextEditor::Document *)
0180 {
0181     deleteLater();
0182 }
0183 
0184 void VcsPluginHelper::setupFromContext(Context* context)
0185 {
0186     Q_D(VcsPluginHelper);
0187 
0188     d->ctxUrls = context->urls();
0189 }
0190 
0191 QList<QUrl> VcsPluginHelper::contextUrlList() const
0192 {
0193     Q_D(const VcsPluginHelper);
0194 
0195     return d->ctxUrls;
0196 }
0197 
0198 QMenu* VcsPluginHelper::commonActions(QWidget* parent)
0199 {
0200     Q_D(VcsPluginHelper);
0201 
0202     /* TODO: the following logic to determine which actions need to be enabled
0203      * or disabled does not work properly. What needs to be implemented is that
0204      * project items that are vc-controlled enable all except add, project
0205      * items that are not vc-controlled enable add action. For urls that cannot
0206      * be made into a project item, or if the project has no associated VC
0207      * plugin we need to check whether a VC controls the parent dir, if we have
0208      * one we assume the urls can be added but are not currently controlled. If
0209      * the url is already version controlled then just enable all except add
0210      */
0211     return d->createMenu(parent);
0212 }
0213 
0214 #define EXECUTE_VCS_METHOD( method ) \
0215     d->plugin->core()->runController()->registerJob( d->vcs-> method ( d->ctxUrls ) )
0216 
0217 #define SINGLEURL_SETUP_VARS \
0218     KDevelop::IBasicVersionControl* iface = d->vcs;\
0219     const QUrl &url = d->ctxUrls.front();
0220 
0221 
0222 void VcsPluginHelper::revert()
0223 {
0224     Q_D(VcsPluginHelper);
0225 
0226     VcsJob* job=d->vcs->revert(d->ctxUrls);
0227     connect(job, &VcsJob::finished, this, &VcsPluginHelper::revertDone);
0228 
0229     for (const QUrl& url : qAsConst(d->ctxUrls)) {
0230         IDocument* doc=ICore::self()->documentController()->documentForUrl(url);
0231 
0232         if(doc && doc->textDocument()) {
0233             auto* modif = qobject_cast<KTextEditor::ModificationInterface*>(doc->textDocument());
0234             if (modif) {
0235                 modif->setModifiedOnDiskWarning(false);
0236             }
0237             doc->textDocument()->setModified(false);
0238         }
0239     }
0240     job->setProperty("urls", QVariant::fromValue(d->ctxUrls));
0241 
0242     d->plugin->core()->runController()->registerJob(job);
0243 }
0244 
0245 void VcsPluginHelper::revertDone(KJob* job)
0246 {
0247     auto* modificationTimer = new QTimer;
0248     modificationTimer->setInterval(100);
0249     connect(modificationTimer, &QTimer::timeout, this, &VcsPluginHelper::delayedModificationWarningOn);
0250     connect(modificationTimer, &QTimer::timeout, modificationTimer, &QTimer::deleteLater);
0251 
0252 
0253     modificationTimer->setProperty("urls", job->property("urls"));
0254     modificationTimer->start();
0255 }
0256 
0257 void VcsPluginHelper::delayedModificationWarningOn()
0258 {
0259     QObject* timer = sender();
0260     const QList<QUrl> urls = timer->property("urls").value<QList<QUrl>>();
0261 
0262     for (const QUrl& url : urls) {
0263         IDocument* doc=ICore::self()->documentController()->documentForUrl(url);
0264 
0265         if(doc) {
0266             doc->reload();
0267 
0268             auto* modif = qobject_cast<KTextEditor::ModificationInterface*>(doc->textDocument());
0269             modif->setModifiedOnDiskWarning(true);
0270         }
0271     }
0272 }
0273 
0274 
0275 void VcsPluginHelper::diffJobFinished(KJob* job)
0276 {
0277     auto* vcsjob = qobject_cast<KDevelop::VcsJob*>(job);
0278     Q_ASSERT(vcsjob);
0279 
0280     if (vcsjob->status() == KDevelop::VcsJob::JobSucceeded) {
0281         KDevelop::VcsDiff d = vcsjob->fetchResults().value<KDevelop::VcsDiff>();
0282         if(d.isEmpty())
0283             KMessageBox::information(ICore::self()->uiController()->activeMainWindow(),
0284                                      i18n("There are no differences."),
0285                                      i18nc("@title:window", "VCS Support"));
0286         else {
0287             auto* patch=new VCSDiffPatchSource(d);
0288             showVcsDiff(patch);
0289         }
0290     } else {
0291         KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), vcsjob->errorString(), i18nc("@title:window", "Unable to Get Differences"));
0292     }
0293 }
0294 
0295 void VcsPluginHelper::diffToBase()
0296 {
0297     Q_D(VcsPluginHelper);
0298 
0299     SINGLEURL_SETUP_VARS
0300     if (!ICore::self()->documentController()->saveAllDocuments()) {
0301         return;
0302     }
0303 
0304     auto* patch =new VCSDiffPatchSource(new VCSStandardDiffUpdater(iface, url));
0305     showVcsDiff(patch);
0306 }
0307 
0308 void VcsPluginHelper::diffForRev()
0309 {
0310     Q_D(VcsPluginHelper);
0311 
0312     if (d->ctxUrls.isEmpty()) {
0313         return;
0314     }
0315     diffForRev(d->ctxUrls.first());
0316 }
0317 
0318 void VcsPluginHelper::diffForRevGlobal()
0319 {
0320     Q_D(VcsPluginHelper);
0321 
0322     if (d->ctxUrls.isEmpty()) {
0323         return;
0324     }
0325     QUrl url = d->ctxUrls.first();
0326     IProject* project = ICore::self()->projectController()->findProjectForUrl( url );
0327     if( project ) {
0328         url = project->path().toUrl();
0329     }
0330 
0331     diffForRev(url);
0332 }
0333 
0334 void VcsPluginHelper::diffForRev(const QUrl& url)
0335 {
0336     Q_D(VcsPluginHelper);
0337 
0338     auto* action = qobject_cast<QAction*>( sender() );
0339     Q_ASSERT(action);
0340     Q_ASSERT(action->data().canConvert<VcsRevision>());
0341     VcsRevision rev = action->data().value<VcsRevision>();
0342 
0343     ICore::self()->documentController()->saveAllDocuments();
0344     VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous);
0345     KDevelop::VcsJob* job = d->vcs->diff(url, prev, rev );
0346 
0347     connect(job, &VcsJob::finished, this, &VcsPluginHelper::diffJobFinished);
0348     d->plugin->core()->runController()->registerJob(job);
0349 }
0350 
0351 void VcsPluginHelper::history(const VcsRevision& rev)
0352 {
0353     Q_D(VcsPluginHelper);
0354 
0355     SINGLEURL_SETUP_VARS
0356     auto* dlg = new QDialog(ICore::self()->uiController()->activeMainWindow());
0357     dlg->setAttribute(Qt::WA_DeleteOnClose);
0358     dlg->setWindowTitle(i18nc("@title:window %1: path or URL, %2: name of a version control system",
0359                           "%2 History (%1)", url.toDisplayString(QUrl::PreferLocalFile), iface->name()));
0360 
0361     auto *mainLayout = new QVBoxLayout(dlg);
0362 
0363     auto* logWidget = new KDevelop::VcsEventWidget(url, rev, iface, dlg);
0364     mainLayout->addWidget(logWidget);
0365 
0366     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0367     dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept);
0368     dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject);
0369     mainLayout->addWidget(buttonBox);
0370 
0371     dlg->show();
0372 }
0373 
0374 void VcsPluginHelper::annotation()
0375 {
0376     Q_D(VcsPluginHelper);
0377 
0378     SINGLEURL_SETUP_VARS
0379     KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url);
0380 
0381     if (!doc)
0382         doc = ICore::self()->documentController()->openDocument(url);
0383 
0384     KTextEditor::View* view = doc ? doc->activeTextView() : nullptr;
0385     KTextEditor::AnnotationInterface* annotateiface = qobject_cast<KTextEditor::AnnotationInterface*>(doc->textDocument());
0386     auto viewiface = qobject_cast<KTextEditor::AnnotationViewInterface*>(view);
0387     if (viewiface && viewiface->isAnnotationBorderVisible()) {
0388         viewiface->setAnnotationBorderVisible(false);
0389         return;
0390     }
0391 
0392     if (doc && doc->textDocument() && iface) {
0393         KDevelop::VcsJob* job = iface->annotate(url);
0394         if( !job )
0395         {
0396             qCWarning(VCS) << "Couldn't create annotate job for:" << url << "with iface:" << iface << dynamic_cast<KDevelop::IPlugin*>( iface );
0397             return;
0398         }
0399 
0400         QColor foreground(Qt::black);
0401         QColor background(Qt::white);
0402         if (view) {
0403             KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal);
0404             foreground = style->foreground().color();
0405             if (style->hasProperty(QTextFormat::BackgroundBrush)) {
0406                 background = style->background().color();
0407             }
0408         }
0409 
0410         if (annotateiface && viewiface) {
0411             // TODO: only create model if there is none yet (e.g. from another view)
0412             auto* model = new KDevelop::VcsAnnotationModel(job, url, doc->textDocument(),
0413                                                                                    foreground, background);
0414             annotateiface->setAnnotationModel(model);
0415 
0416             auto viewifaceV2 = qobject_cast<KTextEditor::AnnotationViewInterfaceV2*>(view);
0417             if (viewifaceV2) {
0418                 // TODO: only create delegate if there is none yet
0419                 auto delegate = new VcsAnnotationItemDelegate(view, model, view);
0420                 viewifaceV2->setAnnotationItemDelegate(delegate);
0421                 viewifaceV2->setAnnotationUniformItemSizes(true);
0422             }
0423 
0424             viewiface->setAnnotationBorderVisible(true);
0425             // can't use new signal slot syntax here, AnnotationInterface is not a QObject
0426             connect(view, SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)),
0427                     this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)));
0428             connect(view, SIGNAL(annotationBorderVisibilityChanged(View*,bool)),
0429                     this, SLOT(handleAnnotationBorderVisibilityChanged(View*,bool)));
0430         } else {
0431             KMessageBox::error(nullptr, i18n("Cannot display annotations, missing interface KTextEditor::AnnotationInterface for the editor."));
0432             delete job;
0433         }
0434     } else {
0435         KMessageBox::error(nullptr, i18n("Cannot execute annotate action because the "
0436                                    "document was not found, or was not a text document:\n%1", url.toDisplayString(QUrl::PreferLocalFile)));
0437     }
0438 }
0439 
0440 void VcsPluginHelper::annotationContextMenuAboutToShow( KTextEditor::View* view, QMenu* menu, int line )
0441 {
0442     Q_D(VcsPluginHelper);
0443 
0444     auto viewifaceV2 = qobject_cast<KTextEditor::AnnotationViewInterfaceV2*>(view);
0445     if (viewifaceV2) {
0446         viewifaceV2->annotationItemDelegate()->hideTooltip(view);
0447     }
0448 
0449     KTextEditor::AnnotationInterface* annotateiface =
0450         qobject_cast<KTextEditor::AnnotationInterface*>(view->document());
0451 
0452     auto* model = qobject_cast<VcsAnnotationModel*>( annotateiface->annotationModel() );
0453     Q_ASSERT(model);
0454 
0455     VcsRevision rev = model->revisionForLine(line);
0456     // check if the user clicked on a row without revision information
0457     if (rev.revisionType() == VcsRevision::Invalid) {
0458         // in this case, do not action depending on revision information
0459         return;
0460     }
0461 
0462     d->diffForRevAction->setData(QVariant::fromValue(rev));
0463     d->diffForRevGlobalAction->setData(QVariant::fromValue(rev));
0464     menu->addSeparator();
0465     menu->addAction(d->diffForRevAction);
0466     menu->addAction(d->diffForRevGlobalAction);
0467 
0468     QAction* copyAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "Copy Revision Id"));
0469     connect(copyAction, &QAction::triggered, this, [rev]() {
0470         QApplication::clipboard()->setText(rev.revisionValue().toString());
0471     });
0472 
0473     QAction* historyAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-history")), i18nc("@action:inmenu revision history", "History..."));
0474     connect(historyAction, &QAction::triggered, this, [this, rev]() {
0475         history(rev);
0476     });
0477 }
0478 
0479 void VcsPluginHelper::handleAnnotationBorderVisibilityChanged(View* view, bool visible)
0480 {
0481     if (visible) {
0482         return;
0483     }
0484 
0485     disconnect(view, SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)),
0486                this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)));
0487 
0488     disconnect(view, SIGNAL(annotationBorderVisibilityChanged(View*,bool)),
0489                this, SLOT(handleAnnotationBorderVisibilityChanged(View*,bool)));
0490 
0491     // TODO: remove the model if last user of it
0492 }
0493 
0494 void VcsPluginHelper::update()
0495 {
0496     Q_D(VcsPluginHelper);
0497 
0498     EXECUTE_VCS_METHOD(update);
0499 }
0500 
0501 void VcsPluginHelper::add()
0502 {
0503     Q_D(VcsPluginHelper);
0504 
0505     EXECUTE_VCS_METHOD(add);
0506 }
0507 
0508 void VcsPluginHelper::commit()
0509 {
0510     Q_D(VcsPluginHelper);
0511 
0512     Q_ASSERT(!d->ctxUrls.isEmpty());
0513     ICore::self()->documentController()->saveAllDocuments();
0514 
0515     QUrl url = d->ctxUrls.first();
0516 
0517     // We start the commit UI no matter whether there is real differences, as it can also be used to commit untracked files
0518     auto* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(d->vcs, url));
0519 
0520     bool ret = showVcsDiff(patchSource);
0521 
0522     if(!ret) {
0523         ScopedDialog<VcsCommitDialog> commitDialog(patchSource);
0524         commitDialog->setCommitCandidates(patchSource->infos());
0525         commitDialog->exec();
0526     }
0527 }
0528 
0529 void VcsPluginHelper::push()
0530 {
0531     Q_D(VcsPluginHelper);
0532 
0533     for (const QUrl& url : qAsConst(d->ctxUrls)) {
0534         VcsJob* job = d->plugin->extension<IDistributedVersionControl>()->push(url, VcsLocation());
0535         ICore::self()->runController()->registerJob(job);
0536     }
0537 }
0538 
0539 void VcsPluginHelper::pull()
0540 {
0541     Q_D(VcsPluginHelper);
0542 
0543     for (const QUrl& url : qAsConst(d->ctxUrls)) {
0544         VcsJob* job = d->plugin->extension<IDistributedVersionControl>()->pull(VcsLocation(), url);
0545         ICore::self()->runController()->registerJob(job);
0546     }
0547 }
0548 
0549 }
0550 
0551 #include "moc_vcspluginhelper.cpp"