diff --git a/kdevplatform/vcs/models/vcsannotationmodel.cpp b/kdevplatform/vcs/models/vcsannotationmodel.cpp index 869b692aad..d92b0a2ae3 100644 --- a/kdevplatform/vcs/models/vcsannotationmodel.cpp +++ b/kdevplatform/vcs/models/vcsannotationmodel.cpp @@ -1,196 +1,196 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "vcsannotationmodel.h" #include "../vcsannotation.h" #include "../vcsrevision.h" #include "../vcsjob.h" #include #include #include #include #include #include #include #include #include #include namespace KDevelop { class VcsAnnotationModelPrivate { public: explicit VcsAnnotationModelPrivate( VcsAnnotationModel* q_ ) : q(q_) {} KDevelop::VcsAnnotation m_annotation; mutable QHash m_brushes; VcsAnnotationModel* q; VcsJob* job; QColor foreground; QColor background; const QBrush& brush(const VcsRevision& revision) const { auto brushIt = m_brushes.find(revision); if (brushIt == m_brushes.end()) { const int background_y = background.red()*0.299 + 0.587*background.green() + 0.114*background.blue(); // get random, but reproducable 8-bit values from last two bytes of the revision hash const uint revisionHash = qHash(revision); const int u = static_cast((0xFF & revisionHash)); const int v = static_cast((0xFF00 & revisionHash) >> 8); const int r = qRound(qMin(255.0, qMax(0.0, background_y + 1.402*(v-128)))); const int g = qRound(qMin(255.0, qMax(0.0, background_y - 0.344*(u-128) - 0.714*(v-128)))); const int b = qRound(qMin(255.0, qMax(0.0, background_y + 1.772*(u-128)))); brushIt = m_brushes.insert(revision, QBrush(QColor(r, g, b))); } return brushIt.value(); } void addLines( KDevelop::VcsJob* job ) { if( job == this->job ) { - foreach( const QVariant& v, job->fetchResults().toList() ) - { + const auto results = job->fetchResults().toList(); + for (const QVariant& v : results) { if( v.canConvert() ) { VcsAnnotationLine l = v.value(); m_annotation.insertLine( l.lineNumber(), l ); emit q->lineChanged( l.lineNumber() ); } } } } }; VcsAnnotationModel::VcsAnnotationModel(VcsJob *job, const QUrl& url, QObject* parent, const QColor &foreground, const QColor &background) : d( new VcsAnnotationModelPrivate( this ) ) { setParent( parent ); d->m_annotation.setLocation( url ); d->job = job; d->foreground = foreground; d->background = background; qsrand( QDateTime().toTime_t() ); connect( d->job, &VcsJob::resultsReady,this, [&] (VcsJob* job) { d->addLines(job); } ); ICore::self()->runController()->registerJob( d->job ); } VcsAnnotationModel::~VcsAnnotationModel() = default; static QString abbreviateLastName(const QString& author) { auto parts = author.split(QLatin1Char(' ')); bool onlyOneFragment = parts.size() == 1 || ( parts.size() == 2 && parts.at(1).isEmpty() ); return onlyOneFragment ? parts.first() : parts.first() + QStringLiteral(" %1.").arg(parts.last()[0]); } static QString annotationToolTip(const VcsAnnotationLine& aline) { const bool textIsLeftToRight = (QApplication::layoutDirection() == Qt::LeftToRight); const QString boldStyle = QStringLiteral(";font-weight:bold"); const QString one = QStringLiteral("1"); const QString two = QStringLiteral("2"); const QString line = QStringLiteral( "" "%%2" "%%4" "").arg( (textIsLeftToRight ? boldStyle : QString()), (textIsLeftToRight ? one : two), (textIsLeftToRight ? QString() : boldStyle), (textIsLeftToRight ? two : one) ); const QString authorLabel = i18n("Author:").toHtmlEscaped(); const QString dateLabel = i18n("Date:").toHtmlEscaped(); const QString messageLabel = i18n("Commit message:").toHtmlEscaped(); const QString author = aline.author().toHtmlEscaped(); const QString date = QLocale().toString(aline.date()).toHtmlEscaped(); const QString message = aline.commitMessage().toHtmlEscaped().replace(QLatin1Char('\n'), QLatin1String("
")); return QLatin1String("") + line.arg(authorLabel, author) + line.arg(dateLabel, date) + line.arg(messageLabel, message) + QLatin1String("
"); } QVariant VcsAnnotationModel::data( int line, Qt::ItemDataRole role ) const { if( line < 0 || !d->m_annotation.containsLine( line ) ) { return QVariant(); } KDevelop::VcsAnnotationLine aline = d->m_annotation.line( line ); if( role == Qt::ForegroundRole ) { return QVariant(QPen(d->foreground)); } if( role == Qt::BackgroundRole ) { return QVariant(d->brush(aline.revision())); } else if( role == Qt::DisplayRole ) { return QVariant( QStringLiteral("%1 ").arg(aline.date().date().year()) + abbreviateLastName(aline.author()) ); } else if( role == (int) KTextEditor::AnnotationModel::GroupIdentifierRole ) { return aline.revision().revisionValue(); } else if( role == Qt::ToolTipRole ) { return QVariant(annotationToolTip(aline)); } return QVariant(); } VcsRevision VcsAnnotationModel::revisionForLine( int line ) const { ///FIXME: update the annotation bar on edit/reload somehow ///BUG: https://bugs.kde.org/show_bug.cgi?id=269757 if (!d->m_annotation.containsLine(line)) { return VcsRevision(); } Q_ASSERT(line >= 0 && d->m_annotation.containsLine(line)); return d->m_annotation.line( line ).revision(); } VcsAnnotationLine VcsAnnotationModel::annotationLine(int line) const { if (line < 0 || !d->m_annotation.containsLine(line)) { return VcsAnnotationLine(); } return d->m_annotation.line(line); } } #include "moc_vcsannotationmodel.cpp" diff --git a/kdevplatform/vcs/vcspluginhelper.cpp b/kdevplatform/vcs/vcspluginhelper.cpp index d5c046af5a..6274951cd6 100644 --- a/kdevplatform/vcs/vcspluginhelper.cpp +++ b/kdevplatform/vcs/vcspluginhelper.cpp @@ -1,528 +1,528 @@ /*************************************************************************** * Copyright 2008 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * Copyright 2017-2018 Friedrich W. H. Kossebau * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "vcspluginhelper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,53,0) #include #endif #include #include #include #include #include #include #include "interfaces/idistributedversioncontrol.h" #include "vcsevent.h" #include "debug.h" #include "widgets/vcsdiffpatchsources.h" namespace KDevelop { class VcsPluginHelperPrivate { public: IPlugin * plugin; IBasicVersionControl * vcs; QList ctxUrls; QAction* commitAction; QAction* addAction; QAction* updateAction; QAction* historyAction; QAction* annotationAction; QAction* diffToBaseAction; QAction* revertAction; QAction* diffForRevAction; QAction* diffForRevGlobalAction; QAction* pushAction; QAction* pullAction; void createActions(VcsPluginHelper* parent) { commitAction = new QAction(QIcon::fromTheme(QStringLiteral("svn-commit")), i18n("Commit..."), parent); updateAction = new QAction(QIcon::fromTheme(QStringLiteral("svn-update")), i18n("Update"), parent); addAction = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Add"), parent); diffToBaseAction = new QAction(QIcon::fromTheme(QStringLiteral("text-x-patch")), i18n("Show Differences..."), parent); revertAction = new QAction(QIcon::fromTheme(QStringLiteral("archive-remove")), i18n("Revert"), parent); historyAction = new QAction(QIcon::fromTheme(QStringLiteral("view-history")), i18n("History..."), parent); annotationAction = new QAction(QIcon::fromTheme(QStringLiteral("user-properties")), i18n("Annotation..."), parent); diffForRevAction = new QAction(QIcon::fromTheme(QStringLiteral("text-x-patch")), i18n("Show Diff..."), parent); diffForRevGlobalAction = new QAction(QIcon::fromTheme(QStringLiteral("text-x-patch")), i18n("Show Diff (all files)..."), parent); pushAction = new QAction(QIcon::fromTheme(QStringLiteral("arrow-up-double")), i18n("Push"), parent); pullAction = new QAction(QIcon::fromTheme(QStringLiteral("arrow-down-double")), i18n("Pull"), parent); QObject::connect(commitAction, &QAction::triggered, parent, &VcsPluginHelper::commit); QObject::connect(addAction, &QAction::triggered, parent, &VcsPluginHelper::add); QObject::connect(updateAction, &QAction::triggered, parent, &VcsPluginHelper::update); QObject::connect(diffToBaseAction, &QAction::triggered, parent, &VcsPluginHelper::diffToBase); QObject::connect(revertAction, &QAction::triggered, parent, &VcsPluginHelper::revert); QObject::connect(historyAction, &QAction::triggered, parent, [=] { parent->history(); }); QObject::connect(annotationAction, &QAction::triggered, parent, &VcsPluginHelper::annotation); QObject::connect(diffForRevAction, &QAction::triggered, parent, QOverload<>::of(&VcsPluginHelper::diffForRev)); QObject::connect(diffForRevGlobalAction, &QAction::triggered, parent, &VcsPluginHelper::diffForRevGlobal); QObject::connect(pullAction, &QAction::triggered, parent, &VcsPluginHelper::pull); QObject::connect(pushAction, &QAction::triggered, parent, &VcsPluginHelper::push); } bool allLocalFiles(const QList& urls) { bool ret=true; for (const QUrl& url : urls) { QFileInfo info(url.toLocalFile()); ret &= info.isFile(); } return ret; } QMenu* createMenu(QWidget* parent) { bool allVersioned=true; - foreach(const QUrl &url, ctxUrls) { + for (const QUrl& url : qAsConst(ctxUrls)) { allVersioned=allVersioned && vcs->isVersionControlled(url); if(!allVersioned) break; } QMenu* menu = new QMenu(vcs->name(), parent); menu->setIcon(QIcon::fromTheme(ICore::self()->pluginController()->pluginInfo(plugin).iconName())); menu->addAction(commitAction); if(plugin->extension()) { menu->addAction(pushAction); menu->addAction(pullAction); } else { menu->addAction(updateAction); } menu->addSeparator(); menu->addAction(addAction); menu->addAction(revertAction); menu->addSeparator(); menu->addAction(historyAction); menu->addAction(annotationAction); menu->addAction(diffToBaseAction); const bool singleVersionedFile = ctxUrls.count() == 1 && allVersioned; historyAction->setEnabled(singleVersionedFile); annotationAction->setEnabled(singleVersionedFile && allLocalFiles(ctxUrls)); diffToBaseAction->setEnabled(singleVersionedFile); commitAction->setEnabled(singleVersionedFile); return menu; } }; VcsPluginHelper::VcsPluginHelper(KDevelop::IPlugin* parent, KDevelop::IBasicVersionControl* vcs) : QObject(parent) , d(new VcsPluginHelperPrivate()) { Q_ASSERT(vcs); Q_ASSERT(parent); d->plugin = parent; d->vcs = vcs; d->createActions(this); } VcsPluginHelper::~VcsPluginHelper() {} void VcsPluginHelper::addContextDocument(const QUrl &url) { d->ctxUrls.append(url); } void VcsPluginHelper::disposeEventually(KTextEditor::View *, bool dont) { if ( ! dont ) { deleteLater(); } } void VcsPluginHelper::disposeEventually(KTextEditor::Document *) { deleteLater(); } void VcsPluginHelper::setupFromContext(Context* context) { d->ctxUrls = context->urls(); } QList VcsPluginHelper::contextUrlList() const { return d->ctxUrls; } QMenu* VcsPluginHelper::commonActions(QWidget* parent) { /* TODO: the following logic to determine which actions need to be enabled * or disabled does not work properly. What needs to be implemented is that * project items that are vc-controlled enable all except add, project * items that are not vc-controlled enable add action. For urls that cannot * be made into a project item, or if the project has no associated VC * plugin we need to check whether a VC controls the parent dir, if we have * one we assume the urls can be added but are not currently controlled. If * the url is already version controlled then just enable all except add */ return d->createMenu(parent); } #define EXECUTE_VCS_METHOD( method ) \ d->plugin->core()->runController()->registerJob( d->vcs-> method ( d->ctxUrls ) ) #define SINGLEURL_SETUP_VARS \ KDevelop::IBasicVersionControl* iface = d->vcs;\ const QUrl &url = d->ctxUrls.front(); void VcsPluginHelper::revert() { VcsJob* job=d->vcs->revert(d->ctxUrls); connect(job, &VcsJob::finished, this, &VcsPluginHelper::revertDone); - foreach(const QUrl &url, d->ctxUrls) { + for (const QUrl& url : qAsConst(d->ctxUrls)) { IDocument* doc=ICore::self()->documentController()->documentForUrl(url); if(doc && doc->textDocument()) { auto* modif = dynamic_cast(doc->textDocument()); if (modif) { modif->setModifiedOnDiskWarning(false); } doc->textDocument()->setModified(false); } } job->setProperty("urls", QVariant::fromValue(d->ctxUrls)); d->plugin->core()->runController()->registerJob(job); } void VcsPluginHelper::revertDone(KJob* job) { auto* modificationTimer = new QTimer; modificationTimer->setInterval(100); connect(modificationTimer, &QTimer::timeout, this, &VcsPluginHelper::delayedModificationWarningOn); connect(modificationTimer, &QTimer::timeout, modificationTimer, &QTimer::deleteLater); modificationTimer->setProperty("urls", job->property("urls")); modificationTimer->start(); } void VcsPluginHelper::delayedModificationWarningOn() { QObject* timer = sender(); const QList urls = timer->property("urls").value>(); for (const QUrl& url : urls) { IDocument* doc=ICore::self()->documentController()->documentForUrl(url); if(doc) { doc->reload(); auto* modif=dynamic_cast(doc->textDocument()); modif->setModifiedOnDiskWarning(true); } } } void VcsPluginHelper::diffJobFinished(KJob* job) { auto* vcsjob = qobject_cast(job); Q_ASSERT(vcsjob); if (vcsjob->status() == KDevelop::VcsJob::JobSucceeded) { KDevelop::VcsDiff d = vcsjob->fetchResults().value(); if(d.isEmpty()) KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18n("There are no differences."), i18n("VCS support")); else { auto* patch=new VCSDiffPatchSource(d); showVcsDiff(patch); } } else { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), vcsjob->errorString(), i18n("Unable to get difference.")); } } void VcsPluginHelper::diffToBase() { SINGLEURL_SETUP_VARS ICore::self()->documentController()->saveAllDocuments(); auto* patch =new VCSDiffPatchSource(new VCSStandardDiffUpdater(iface, url)); showVcsDiff(patch); } void VcsPluginHelper::diffForRev() { if (d->ctxUrls.isEmpty()) { return; } diffForRev(d->ctxUrls.first()); } void VcsPluginHelper::diffForRevGlobal() { if (d->ctxUrls.isEmpty()) { return; } QUrl url = d->ctxUrls.first(); IProject* project = ICore::self()->projectController()->findProjectForUrl( url ); if( project ) { url = project->path().toUrl(); } diffForRev(url); } void VcsPluginHelper::diffForRev(const QUrl& url) { auto* action = qobject_cast( sender() ); Q_ASSERT(action); Q_ASSERT(action->data().canConvert()); VcsRevision rev = action->data().value(); ICore::self()->documentController()->saveAllDocuments(); VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); KDevelop::VcsJob* job = d->vcs->diff(url, prev, rev ); connect(job, &VcsJob::finished, this, &VcsPluginHelper::diffJobFinished); d->plugin->core()->runController()->registerJob(job); } void VcsPluginHelper::history(const VcsRevision& rev) { SINGLEURL_SETUP_VARS QDialog* dlg = new QDialog(ICore::self()->uiController()->activeMainWindow()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle(i18nc("%1: path or URL, %2: name of a version control system", "%2 History (%1)", url.toDisplayString(QUrl::PreferLocalFile), iface->name())); auto *mainLayout = new QVBoxLayout(dlg); auto* logWidget = new KDevelop::VcsEventWidget(url, rev, iface, dlg); mainLayout->addWidget(logWidget); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(buttonBox); dlg->show(); } void VcsPluginHelper::annotation() { SINGLEURL_SETUP_VARS KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (!doc) doc = ICore::self()->documentController()->openDocument(url); KTextEditor::View* view = doc ? doc->activeTextView() : nullptr; KTextEditor::AnnotationInterface* annotateiface = qobject_cast(doc->textDocument()); auto viewiface = qobject_cast(view); if (viewiface && viewiface->isAnnotationBorderVisible()) { viewiface->setAnnotationBorderVisible(false); return; } if (doc && doc->textDocument() && iface) { KDevelop::VcsJob* job = iface->annotate(url); if( !job ) { qCWarning(VCS) << "Couldn't create annotate job for:" << url << "with iface:" << iface << dynamic_cast( iface ); return; } QColor foreground(Qt::black); QColor background(Qt::white); if (view) { KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal); foreground = style->foreground().color(); if (style->hasProperty(QTextFormat::BackgroundBrush)) { background = style->background().color(); } } if (annotateiface && viewiface) { // TODO: only create model if there is none yet (e.g. from another view) auto* model = new KDevelop::VcsAnnotationModel(job, url, doc->textDocument(), foreground, background); annotateiface->setAnnotationModel(model); #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,53,0) auto viewifaceV2 = qobject_cast(view); if (viewifaceV2) { // TODO: only create delegate if there is none yet auto delegate = new VcsAnnotationItemDelegate(view, model, view); viewifaceV2->setAnnotationItemDelegate(delegate); viewifaceV2->setAnnotationUniformItemSizes(true); } #endif viewiface->setAnnotationBorderVisible(true); // can't use new signal slot syntax here, AnnotationInterface is not a QObject connect(view, SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)), this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int))); connect(view, SIGNAL(annotationBorderVisibilityChanged(View*,bool)), this, SLOT(handleAnnotationBorderVisibilityChanged(View*,bool))); } else { KMessageBox::error(nullptr, i18n("Cannot display annotations, missing interface KTextEditor::AnnotationInterface for the editor.")); delete job; } } else { KMessageBox::error(nullptr, i18n("Cannot execute annotate action because the " "document was not found, or was not a text document:\n%1", url.toDisplayString(QUrl::PreferLocalFile))); } } void VcsPluginHelper::annotationContextMenuAboutToShow( KTextEditor::View* view, QMenu* menu, int line ) { #if KTEXTEDITOR_VERSION >= QT_VERSION_CHECK(5,53,0) auto viewifaceV2 = qobject_cast(view); if (viewifaceV2) { viewifaceV2->annotationItemDelegate()->hideTooltip(view); } #endif KTextEditor::AnnotationInterface* annotateiface = qobject_cast(view->document()); auto* model = qobject_cast( annotateiface->annotationModel() ); Q_ASSERT(model); VcsRevision rev = model->revisionForLine(line); // check if the user clicked on a row without revision information if (rev.revisionType() == VcsRevision::Invalid) { // in this case, do not action depending on revision information return; } d->diffForRevAction->setData(QVariant::fromValue(rev)); d->diffForRevGlobalAction->setData(QVariant::fromValue(rev)); menu->addSeparator(); menu->addAction(d->diffForRevAction); menu->addAction(d->diffForRevGlobalAction); QAction* copyAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Revision Id")); connect(copyAction, &QAction::triggered, this, [rev]() { QApplication::clipboard()->setText(rev.revisionValue().toString()); }); QAction* historyAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-history")), i18n("History...")); connect(historyAction, &QAction::triggered, this, [this, rev]() { history(rev); }); } void VcsPluginHelper::handleAnnotationBorderVisibilityChanged(View* view, bool visible) { if (visible) { return; } disconnect(view, SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)), this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int))); disconnect(view, SIGNAL(annotationBorderVisibilityChanged(View*,bool)), this, SLOT(handleAnnotationBorderVisibilityChanged(View*,bool))); // TODO: remove the model if last user of it } void VcsPluginHelper::update() { EXECUTE_VCS_METHOD(update); } void VcsPluginHelper::add() { EXECUTE_VCS_METHOD(add); } void VcsPluginHelper::commit() { Q_ASSERT(!d->ctxUrls.isEmpty()); ICore::self()->documentController()->saveAllDocuments(); QUrl url = d->ctxUrls.first(); // We start the commit UI no matter whether there is real differences, as it can also be used to commit untracked files auto* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(d->vcs, url)); bool ret = showVcsDiff(patchSource); if(!ret) { ScopedDialog commitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } void VcsPluginHelper::push() { - foreach(const QUrl &url, d->ctxUrls) { + for (const QUrl& url : qAsConst(d->ctxUrls)) { VcsJob* job = d->plugin->extension()->push(url, VcsLocation()); ICore::self()->runController()->registerJob(job); } } void VcsPluginHelper::pull() { - foreach(const QUrl &url, d->ctxUrls) { + for (const QUrl& url : qAsConst(d->ctxUrls)) { VcsJob* job = d->plugin->extension()->pull(VcsLocation(), url); ICore::self()->runController()->registerJob(job); } } } diff --git a/kdevplatform/vcs/widgets/vcsdiffpatchsources.cpp b/kdevplatform/vcs/widgets/vcsdiffpatchsources.cpp index 40923b296d..6edeb9b6f4 100644 --- a/kdevplatform/vcs/widgets/vcsdiffpatchsources.cpp +++ b/kdevplatform/vcs/widgets/vcsdiffpatchsources.cpp @@ -1,327 +1,329 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "vcsdiffpatchsources.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vcsdiff.h" #include "vcsjob.h" #include "debug.h" using namespace KDevelop; VCSCommitDiffPatchSource::VCSCommitDiffPatchSource(VCSDiffUpdater* updater) : VCSDiffPatchSource(updater), m_vcs(updater->vcs()) { Q_ASSERT(m_vcs); m_commitMessageWidget = new QWidget; auto* layout = new QVBoxLayout(m_commitMessageWidget.data()); layout->setMargin(0); m_commitMessageEdit = new KTextEdit; m_commitMessageEdit.data()->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_commitMessageEdit.data()->setLineWrapMode(QTextEdit::NoWrap); m_vcs->setupCommitMessageEditor(updater->url(), m_commitMessageEdit.data()); auto* titleLayout = new QHBoxLayout; titleLayout->addWidget(new QLabel(i18n("Commit Message:"))); m_oldMessages = new KComboBox(m_commitMessageWidget.data()); m_oldMessages->addItem(i18n("Old Messages")); - foreach(const QString& message, oldMessages()) + const auto oldMessages = this->oldMessages(); + for (const QString& message : oldMessages) { m_oldMessages->addItem(message, message); + } m_oldMessages->setMaximumWidth(200); connect(m_oldMessages, QOverload::of(&KComboBox::currentIndexChanged), this, &VCSCommitDiffPatchSource::oldMessageChanged); titleLayout->addWidget(m_oldMessages); layout->addLayout(titleLayout); layout->addWidget(m_commitMessageEdit.data()); connect(this, &VCSCommitDiffPatchSource::reviewCancelled, this, &VCSCommitDiffPatchSource::addMessageToHistory); connect(this, &VCSCommitDiffPatchSource::reviewFinished, this, &VCSCommitDiffPatchSource::addMessageToHistory); } QStringList VCSCommitDiffPatchSource::oldMessages() const { KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); return vcsGroup.readEntry("OldCommitMessages", QStringList()); } void VCSCommitDiffPatchSource::addMessageToHistory(const QString& message) { if(ICore::self()->shuttingDown()) return; KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); const int maxMessages = 10; QStringList oldMessages = vcsGroup.readEntry("OldCommitMessages", QStringList()); oldMessages.removeAll(message); oldMessages.push_front(message); oldMessages = oldMessages.mid(0, maxMessages); vcsGroup.writeEntry("OldCommitMessages", oldMessages); } void VCSCommitDiffPatchSource::oldMessageChanged(const QString& text) { if(m_oldMessages->currentIndex() != 0) { m_oldMessages->setCurrentIndex(0); m_commitMessageEdit.data()->setText(text); } } void VCSCommitDiffPatchSource::jobFinished(KJob *job) { if (!job || job->error() != 0 ) { QString details = job ? job->errorText() : QString(); if (details.isEmpty()) { //errorText may be empty details = i18n("For more detailed information please see the Version Control tool view."); } KMessageBox::detailedError(nullptr, i18n("Unable to commit"), details, i18n("Commit unsuccessful")); } deleteLater(); } VCSDiffPatchSource::VCSDiffPatchSource(VCSDiffUpdater* updater) : m_updater(updater) { update(); KDevelop::IBasicVersionControl* vcs = m_updater->vcs(); QUrl url = m_updater->url(); QScopedPointer statusJob(vcs->status(QList() << url)); QVariant varlist; if( statusJob->exec() && statusJob->status() == VcsJob::JobSucceeded ) { varlist = statusJob->fetchResults(); const auto vars = varlist.toList(); m_infos.reserve(m_infos.size() + vars.size()); for (const auto& var : vars) { VcsStatusInfo info = var.value(); m_infos += info; if(info.state()!=VcsStatusInfo::ItemUpToDate) m_selectable[info.url()] = info.state(); } } else qCDebug(VCS) << "Couldn't get status for urls: " << url; } VCSDiffPatchSource::VCSDiffPatchSource(const KDevelop::VcsDiff& diff) : m_updater(nullptr) { updateFromDiff(diff); } VCSDiffPatchSource::~VCSDiffPatchSource() { QFile::remove(m_file.toLocalFile()); delete m_updater; } QUrl VCSDiffPatchSource::baseDir() const { return m_base; } QUrl VCSDiffPatchSource::file() const { return m_file; } QString VCSDiffPatchSource::name() const { return m_name; } uint VCSDiffPatchSource::depth() const { return m_depth; } void VCSDiffPatchSource::updateFromDiff(const VcsDiff& vcsdiff) { if(!m_file.isValid()) { QTemporaryFile temp2(QDir::tempPath() + QLatin1String("/kdevelop_XXXXXX.patch")); temp2.setAutoRemove(false); temp2.open(); QTextStream t2(&temp2); t2 << vcsdiff.diff(); qCDebug(VCS) << "filename:" << temp2.fileName(); m_file = QUrl::fromLocalFile(temp2.fileName()); temp2.close(); }else{ QFile file(m_file.path()); file.open(QIODevice::WriteOnly); QTextStream t2(&file); t2 << vcsdiff.diff(); } qCDebug(VCS) << "using file" << m_file << vcsdiff.diff() << "base" << vcsdiff.baseDiff(); m_name = QStringLiteral("VCS Diff"); m_base = vcsdiff.baseDiff(); m_depth = vcsdiff.depth(); emit patchChanged(); } void VCSDiffPatchSource::update() { if(!m_updater) return; updateFromDiff(m_updater->update()); } VCSCommitDiffPatchSource::~VCSCommitDiffPatchSource() { delete m_commitMessageWidget.data(); } bool VCSCommitDiffPatchSource::canSelectFiles() const { return true; } QMap< QUrl, KDevelop::VcsStatusInfo::State> VCSDiffPatchSource::additionalSelectableFiles() const { return m_selectable; } QWidget* VCSCommitDiffPatchSource::customWidget() const { return m_commitMessageWidget.data(); } QString VCSCommitDiffPatchSource::finishReviewCustomText() const { return i18nc("@action:button To make a commit", "Commit"); } bool VCSCommitDiffPatchSource::canCancel() const { return true; } void VCSCommitDiffPatchSource::cancelReview() { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); emit reviewCancelled(message); deleteLater(); } bool VCSCommitDiffPatchSource::finishReview(const QList& selection) { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); qCDebug(VCS) << "Finishing with selection" << selection; QString files; files.reserve(selection.size()); for (const QUrl& url : selection) { files += QLatin1String("
  • ") + ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain) + QLatin1String("
  • "); } QString text = i18n("Files will be committed:\n
      %1
    \nWith message:\n
    %2
    ", files, message); int res = KMessageBox::warningContinueCancel(nullptr, text, i18n("About to commit to repository"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("ShouldAskConfirmCommit")); if (res != KMessageBox::Continue) { return false; } emit reviewFinished(message, selection); VcsJob* job = m_vcs->commit(message, selection, KDevelop::IBasicVersionControl::NonRecursive); if (!job) { return false; } connect (job, &VcsJob::finished, this, &VCSCommitDiffPatchSource::jobFinished); ICore::self()->runController()->registerJob(job); return true; } bool showVcsDiff(IPatchSource* vcsDiff) { auto* patchReview = ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IPatchReview")); if( patchReview ) { patchReview->startReview(vcsDiff); return true; } else { qCWarning(VCS) << "Patch review plugin not found"; return false; } } VcsDiff VCSStandardDiffUpdater::update() const { QScopedPointer diffJob(m_vcs->diff(m_url, KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Base), KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Working))); const bool success = diffJob ? diffJob->exec() : false; if (!success) { KMessageBox::error(nullptr, i18n("Could not create a patch for the current version.")); return {}; } return diffJob->fetchResults().value(); } VCSStandardDiffUpdater::VCSStandardDiffUpdater(IBasicVersionControl* vcs, const QUrl& url) : m_vcs(vcs) , m_url(url) { } VCSStandardDiffUpdater::~VCSStandardDiffUpdater() { } VCSDiffUpdater::~VCSDiffUpdater() { }