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