1 /*
2     SPDX-FileCopyrightText: 2007 Dukju Ahn <dukjuahn@gmail.com>
3     SPDX-FileCopyrightText: 2008 Andreas Pakulat <apaku@gmx.de>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "kdevsvnplugin.h"
9 
10 #include <QAction>
11 #include <QDialog>
12 #include <QMenu>
13 #include <QVBoxLayout>
14 #include <QVariant>
15 
16 #include <KFile>
17 #include <KIO/Global>
18 #include <KLocalizedString>
19 #include <KMessageBox>
20 #include <KPluginFactory>
21 #include <KUrlRequester>
22 #include <KUrlRequesterDialog>
23 
24 #include <interfaces/idocument.h>
25 #include <interfaces/icore.h>
26 #include <interfaces/iruncontroller.h>
27 #include <project/projectmodel.h>
28 #include <interfaces/context.h>
29 #include <interfaces/contextmenuextension.h>
30 #include <vcs/vcsrevision.h>
31 #include <vcs/vcsevent.h>
32 #include <vcs/vcsstatusinfo.h>
33 #include <vcs/vcsannotation.h>
34 #include <vcs/widgets/vcsdiffwidget.h>
35 #include <vcs/widgets/vcscommitdialog.h>
36 
37 #include "kdevsvncpp/apr.hpp"
38 
39 #include "svncommitjob.h"
40 #include "svnstatusjob.h"
41 #include "svnaddjob.h"
42 #include "svnrevertjob.h"
43 #include "svnremovejob.h"
44 #include "svnupdatejob.h"
45 #include "svninfojob.h"
46 #include "svndiffjob.h"
47 #include "svncopyjob.h"
48 #include "svnmovejob.h"
49 #include "svnlogjob.h"
50 #include "svnblamejob.h"
51 #include "svnimportjob.h"
52 #include "svncheckoutjob.h"
53 
54 #include "svnimportmetadatawidget.h"
55 #include <vcs/vcspluginhelper.h>
56 #include <vcs/widgets/standardvcslocationwidget.h>
57 #include "svnlocationwidget.h"
58 #include "debug.h"
59 
60 K_PLUGIN_FACTORY_WITH_JSON(KDevSvnFactory, "kdevsubversion.json", registerPlugin<KDevSvnPlugin>();)
61 
KDevSvnPlugin(QObject * parent,const QVariantList &)62 KDevSvnPlugin::KDevSvnPlugin(QObject *parent, const QVariantList &)
63         : KDevelop::IPlugin(QStringLiteral("kdevsubversion"), parent)
64         , m_common(new KDevelop::VcsPluginHelper(this, this))
65         , copy_action( nullptr )
66         , move_action( nullptr )
67         , m_jobQueue(new ThreadWeaver::Queue(this))
68 {
69     qRegisterMetaType<KDevelop::VcsStatusInfo>();
70     qRegisterMetaType<SvnInfoHolder>();
71     qRegisterMetaType<KDevelop::VcsEvent>();
72     qRegisterMetaType<KDevelop::VcsRevision>();
73     qRegisterMetaType<KDevelop::VcsRevision::RevisionSpecialType>();
74     qRegisterMetaType<KDevelop::VcsAnnotation>();
75     qRegisterMetaType<KDevelop::VcsAnnotationLine>();
76 }
77 
~KDevSvnPlugin()78 KDevSvnPlugin::~KDevSvnPlugin()
79 {
80 }
81 
isValidRemoteRepositoryUrl(const QUrl & remoteLocation)82 bool KDevSvnPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation)
83 {
84     const QString scheme = remoteLocation.scheme();
85     if (scheme == QLatin1String("svn") ||
86         scheme == QLatin1String("svn+ssh")) {
87         return true;
88     }
89     return false;
90 }
91 
92 
isVersionControlled(const QUrl & localLocation)93 bool KDevSvnPlugin::isVersionControlled(const QUrl &localLocation)
94 {
95     ///TODO: also check this in the other functions?
96     if (!localLocation.isValid()) {
97         return false;
98     }
99 
100     auto* job = new SvnInfoJob(this);
101 
102     job->setLocation(localLocation);
103 
104     if (job->exec()) {
105         QVariant result = job->fetchResults();
106 
107         if (result.isValid()) {
108             SvnInfoHolder h = result.value<SvnInfoHolder>();
109             return !h.name.isEmpty();
110         }
111     } else {
112         qCDebug(PLUGIN_SVN) << "Couldn't execute job";
113     }
114 
115     return false;
116 }
117 
repositoryLocation(const QUrl & localLocation)118 KDevelop::VcsJob* KDevSvnPlugin::repositoryLocation(const QUrl &localLocation)
119 {
120     auto* job = new SvnInfoJob(this);
121 
122     job->setLocation(localLocation);
123     job->setProvideInformation(SvnInfoJob::RepoUrlOnly);
124     return job;
125 }
126 
status(const QList<QUrl> & localLocations,KDevelop::IBasicVersionControl::RecursionMode mode)127 KDevelop::VcsJob* KDevSvnPlugin::status(const QList<QUrl>& localLocations,
128                                         KDevelop::IBasicVersionControl::RecursionMode mode)
129 {
130     auto* job = new SvnStatusJob(this);
131     job->setLocations(localLocations);
132     job->setRecursive((mode == KDevelop::IBasicVersionControl::Recursive));
133     return job;
134 }
135 
add(const QList<QUrl> & localLocations,KDevelop::IBasicVersionControl::RecursionMode recursion)136 KDevelop::VcsJob* KDevSvnPlugin::add(const QList<QUrl>& localLocations,
137                                      KDevelop::IBasicVersionControl::RecursionMode recursion)
138 {
139     auto* job = new SvnAddJob(this);
140     job->setLocations(localLocations);
141     job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive));
142     return job;
143 }
144 
remove(const QList<QUrl> & localLocations)145 KDevelop::VcsJob* KDevSvnPlugin::remove(const QList<QUrl>& localLocations)
146 {
147     auto* job = new SvnRemoveJob(this);
148     job->setLocations(localLocations);
149     return job;
150 }
151 
edit(const QUrl &)152 KDevelop::VcsJob* KDevSvnPlugin::edit(const QUrl& /*localLocation*/)
153 {
154     return nullptr;
155 }
156 
unedit(const QUrl &)157 KDevelop::VcsJob* KDevSvnPlugin::unedit(const QUrl& /*localLocation*/)
158 {
159     return nullptr;
160 }
161 
localRevision(const QUrl & localLocation,KDevelop::VcsRevision::RevisionType type)162 KDevelop::VcsJob* KDevSvnPlugin::localRevision(const QUrl &localLocation, KDevelop::VcsRevision::RevisionType type)
163 {
164     auto* job = new SvnInfoJob(this);
165 
166     job->setLocation(localLocation);
167     job->setProvideInformation(SvnInfoJob::RevisionOnly);
168     job->setProvideRevisionType(type);
169     return job;
170 }
171 
copy(const QUrl & localLocationSrc,const QUrl & localLocationDstn)172 KDevelop::VcsJob* KDevSvnPlugin::copy(const QUrl &localLocationSrc, const QUrl& localLocationDstn)
173 {
174     auto* job = new SvnCopyJob(this);
175     job->setSourceLocation(localLocationSrc);
176     job->setDestinationLocation(localLocationDstn);
177     return job;
178 }
179 
move(const QUrl & localLocationSrc,const QUrl & localLocationDst)180 KDevelop::VcsJob* KDevSvnPlugin::move(const QUrl &localLocationSrc, const QUrl& localLocationDst)
181 {
182     auto* job = new SvnMoveJob(this);
183     job->setSourceLocation(localLocationSrc);
184     job->setDestinationLocation(localLocationDst);
185     return job;
186 }
187 
revert(const QList<QUrl> & localLocations,KDevelop::IBasicVersionControl::RecursionMode recursion)188 KDevelop::VcsJob* KDevSvnPlugin::revert(const QList<QUrl>& localLocations,
189                                         KDevelop::IBasicVersionControl::RecursionMode recursion)
190 {
191     auto* job = new SvnRevertJob(this);
192     job->setLocations(localLocations);
193     job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive));
194     return job;
195 }
196 
update(const QList<QUrl> & localLocations,const KDevelop::VcsRevision & rev,KDevelop::IBasicVersionControl::RecursionMode recursion)197 KDevelop::VcsJob* KDevSvnPlugin::update(const QList<QUrl>& localLocations,
198                                         const KDevelop::VcsRevision& rev,
199                                         KDevelop::IBasicVersionControl::RecursionMode recursion)
200 {
201     auto* job = new SvnUpdateJob(this);
202     job->setLocations(localLocations);
203     job->setRevision(rev);
204     job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive));
205     return job;
206 }
207 
commit(const QString & message,const QList<QUrl> & localLocations,KDevelop::IBasicVersionControl::RecursionMode recursion)208 KDevelop::VcsJob* KDevSvnPlugin::commit(const QString& message, const QList<QUrl>& localLocations,
209                                         KDevelop::IBasicVersionControl::RecursionMode recursion)
210 {
211     auto* job = new SvnCommitJob(this);
212     qCDebug(PLUGIN_SVN) << "Committing locations:" << localLocations;
213     job->setUrls(localLocations);
214     job->setCommitMessage(message) ;
215     job->setRecursive((recursion == KDevelop::IBasicVersionControl::Recursive));
216     return job;
217 }
218 
diff(const QUrl & fileOrDirectory,const KDevelop::VcsRevision & srcRevision,const KDevelop::VcsRevision & dstRevision,KDevelop::IBasicVersionControl::RecursionMode recurse)219 KDevelop::VcsJob* KDevSvnPlugin::diff(const QUrl &fileOrDirectory,
220                                       const KDevelop::VcsRevision& srcRevision,
221                                       const KDevelop::VcsRevision& dstRevision,
222                                       KDevelop::IBasicVersionControl::RecursionMode recurse)
223 {
224     KDevelop::VcsLocation loc(fileOrDirectory);
225     return diff2(loc, loc, srcRevision, dstRevision, recurse);
226 }
227 
diff2(const KDevelop::VcsLocation & src,const KDevelop::VcsLocation & dst,const KDevelop::VcsRevision & srcRevision,const KDevelop::VcsRevision & dstRevision,KDevelop::IBasicVersionControl::RecursionMode recurse)228 KDevelop::VcsJob* KDevSvnPlugin::diff2(const KDevelop::VcsLocation& src,
229                                        const KDevelop::VcsLocation& dst,
230                                        const KDevelop::VcsRevision& srcRevision,
231                                        const KDevelop::VcsRevision& dstRevision,
232                                        KDevelop::IBasicVersionControl::RecursionMode recurse)
233 {
234     auto* job = new SvnDiffJob(this);
235     job->setSource(src);
236     job->setDestination(dst);
237     job->setSrcRevision(srcRevision);
238     job->setDstRevision(dstRevision);
239     job->setRecursive((recurse == KDevelop::IBasicVersionControl::Recursive));
240     return job;
241 }
242 
log(const QUrl & localLocation,const KDevelop::VcsRevision & rev,unsigned long limit)243 KDevelop::VcsJob* KDevSvnPlugin::log(const QUrl &localLocation, const KDevelop::VcsRevision& rev, unsigned long limit)
244 {
245     auto* job = new SvnLogJob(this);
246     job->setLocation(localLocation);
247     job->setStartRevision(rev);
248     job->setLimit(limit);
249     return job;
250 }
251 
log(const QUrl & localLocation,const KDevelop::VcsRevision & startRev,const KDevelop::VcsRevision & endRev)252 KDevelop::VcsJob* KDevSvnPlugin::log(const QUrl &localLocation,
253                                      const KDevelop::VcsRevision& startRev,
254                                      const KDevelop::VcsRevision& endRev)
255 {
256     auto* job = new SvnLogJob(this);
257     job->setLocation(localLocation);
258     job->setStartRevision(startRev);
259     job->setEndRevision(endRev);
260     return job;
261 }
262 
annotate(const QUrl & localLocation,const KDevelop::VcsRevision & rev)263 KDevelop::VcsJob* KDevSvnPlugin::annotate(const QUrl &localLocation,
264         const KDevelop::VcsRevision& rev)
265 {
266     auto* job = new SvnBlameJob(this);
267     job->setLocation(localLocation);
268     job->setEndRevision(rev);
269     return job;
270 }
271 
merge(const KDevelop::VcsLocation & localOrRepoLocationSrc,const KDevelop::VcsLocation & localOrRepoLocationDst,const KDevelop::VcsRevision & srcRevision,const KDevelop::VcsRevision & dstRevision,const QUrl & localLocation)272 KDevelop::VcsJob* KDevSvnPlugin::merge(const KDevelop::VcsLocation& localOrRepoLocationSrc,
273                                        const KDevelop::VcsLocation& localOrRepoLocationDst,
274                                        const KDevelop::VcsRevision& srcRevision,
275                                        const KDevelop::VcsRevision& dstRevision,
276                                        const QUrl &localLocation)
277 {
278     // TODO implement merge
279     Q_UNUSED(localOrRepoLocationSrc)
280     Q_UNUSED(localOrRepoLocationDst)
281     Q_UNUSED(srcRevision)
282     Q_UNUSED(dstRevision)
283     Q_UNUSED(localLocation)
284     return nullptr;
285 }
286 
resolve(const QList<QUrl> &,KDevelop::IBasicVersionControl::RecursionMode)287 KDevelop::VcsJob* KDevSvnPlugin::resolve(const QList<QUrl>& /*localLocations*/,
288         KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
289 {
290     return nullptr;
291 }
292 
import(const QString & commitMessage,const QUrl & sourceDirectory,const KDevelop::VcsLocation & destinationRepository)293 KDevelop::VcsJob* KDevSvnPlugin::import(const QString & commitMessage, const QUrl &sourceDirectory, const KDevelop::VcsLocation & destinationRepository)
294 {
295     auto* job = new SvnImportJob(this);
296     job->setMapping(sourceDirectory, destinationRepository);
297     job->setMessage(commitMessage);
298     return job;
299 }
300 
createWorkingCopy(const KDevelop::VcsLocation & sourceRepository,const QUrl & destinationDirectory,KDevelop::IBasicVersionControl::RecursionMode recursion)301 KDevelop::VcsJob* KDevSvnPlugin::createWorkingCopy(const KDevelop::VcsLocation & sourceRepository, const QUrl &destinationDirectory, KDevelop::IBasicVersionControl::RecursionMode recursion)
302 {
303     auto* job = new SvnCheckoutJob(this);
304     job->setMapping(sourceRepository, destinationDirectory, recursion);
305     return job;
306 }
307 
308 
contextMenuExtension(KDevelop::Context * context,QWidget * parent)309 KDevelop::ContextMenuExtension KDevSvnPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
310 {
311     m_common->setupFromContext(context);
312 
313     const QList<QUrl> & ctxUrlList  = m_common->contextUrlList();
314 
315     bool hasVersionControlledEntries = false;
316     for (const QUrl& url : ctxUrlList) {
317         if (isVersionControlled(url) || isVersionControlled(KIO::upUrl(url))) {
318             hasVersionControlledEntries = true;
319             break;
320         }
321     }
322 
323     qCDebug(PLUGIN_SVN) << "version controlled?" << hasVersionControlledEntries;
324 
325     if (!hasVersionControlledEntries)
326         return IPlugin::contextMenuExtension(context, parent);
327 
328 
329     QMenu* svnmenu = m_common->commonActions(parent);
330     svnmenu->addSeparator();
331 
332     if( !copy_action )
333     {
334         copy_action = new QAction(i18nc("@action:inmenu", "Copy..."), this);
335         connect(copy_action, &QAction::triggered, this, &KDevSvnPlugin::ctxCopy);
336     }
337     svnmenu->addAction(copy_action);
338 
339     if( !move_action )
340     {
341         move_action = new QAction(i18nc("@action:inmenu", "Move..."), this);
342         connect(move_action, &QAction::triggered, this, &KDevSvnPlugin::ctxMove);
343     }
344     svnmenu->addAction(move_action);
345 
346     KDevelop::ContextMenuExtension menuExt;
347     menuExt.addAction(KDevelop::ContextMenuExtension::VcsGroup, svnmenu->menuAction());
348 
349     return menuExt;
350 }
351 
ctxCopy()352 void KDevSvnPlugin::ctxCopy()
353 {
354     QList<QUrl> const & ctxUrlList = m_common->contextUrlList();
355     if (ctxUrlList.count() > 1) {
356         KMessageBox::error(nullptr, i18n("Please select only one item for this operation"));
357         return;
358     }
359 
360     QUrl source = ctxUrlList.first();
361 
362     if (source.isLocalFile()) {
363         QUrl dir = source;
364         bool isFile = QFileInfo(source.toLocalFile()).isFile();
365 
366         if (isFile) {
367             dir = dir.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash);
368         }
369 
370         KUrlRequesterDialog dlg(dir, i18nc("@label", "Destination file/directory"), nullptr);
371 
372         if (isFile) {
373             dlg.urlRequester()->setMode(KFile::File | KFile::Directory | KFile::LocalOnly);
374         } else {
375             dlg.urlRequester()->setMode(KFile::Directory | KFile::LocalOnly);
376         }
377 
378         if (dlg.exec() == QDialog::Accepted) { // krazy:exclude=crashy
379             KDevelop::ICore::self()->runController()->registerJob(copy(source, dlg.selectedUrl()));
380         }
381     } else {
382         KMessageBox::error(nullptr, i18n("Copying only works on local files"));
383         return;
384     }
385 
386 }
387 
ctxMove()388 void KDevSvnPlugin::ctxMove()
389 {
390     QList<QUrl> const & ctxUrlList = m_common->contextUrlList();
391     if (ctxUrlList.count() != 1) {
392         KMessageBox::error(nullptr, i18n("Please select only one item for this operation"));
393         return;
394     }
395 
396     QUrl source = ctxUrlList.first();
397 
398     if (source.isLocalFile()) {
399         QUrl dir = source;
400         bool isFile = QFileInfo(source.toLocalFile()).isFile();
401 
402         if (isFile) {
403             dir = source.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash);
404         }
405 
406         KUrlRequesterDialog dlg(dir, i18n("Destination file/directory"), nullptr);
407 
408         if (isFile) {
409             dlg.urlRequester()->setMode(KFile::File | KFile::Directory | KFile::LocalOnly);
410         } else {
411             dlg.urlRequester()->setMode(KFile::Directory | KFile::LocalOnly);
412         }
413 
414         if (dlg.exec() == QDialog::Accepted) { // krazy:exclude=crashy
415             KDevelop::ICore::self()->runController()->registerJob(move(source, dlg.selectedUrl()));
416         }
417     } else {
418         KMessageBox::error(nullptr, i18n("Moving only works on local files/dirs"));
419         return;
420     }
421 }
422 
name() const423 QString KDevSvnPlugin::name() const
424 {
425     return i18n("Subversion");
426 }
427 
createImportMetadataWidget(QWidget * parent)428 KDevelop::VcsImportMetadataWidget* KDevSvnPlugin::createImportMetadataWidget(QWidget* parent)
429 {
430     return new SvnImportMetadataWidget(parent);
431 }
432 
vcsLocation(QWidget * parent) const433 KDevelop::VcsLocationWidget* KDevSvnPlugin::vcsLocation(QWidget* parent) const
434 {
435     return new SvnLocationWidget(parent);
436 }
437 
jobQueue() const438 ThreadWeaver::Queue* KDevSvnPlugin::jobQueue() const
439 {
440     return m_jobQueue;
441 }
442 
443 #include "kdevsvnplugin.moc"
444