1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Brian McGillion
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "mercurialplugin.h"
27
28 #include "commiteditor.h"
29 #include "constants.h"
30 #include "mercurialclient.h"
31 #include "mercurialeditor.h"
32 #include "mercurialsettings.h"
33 #include "revertdialog.h"
34 #include "srcdestdialog.h"
35
36 #include <coreplugin/actionmanager/actionmanager.h>
37 #include <coreplugin/actionmanager/actioncontainer.h>
38 #include <coreplugin/actionmanager/command.h>
39 #include <coreplugin/documentmanager.h>
40 #include <coreplugin/vcsmanager.h>
41 #include <coreplugin/coreconstants.h>
42 #include <coreplugin/icore.h>
43 #include <coreplugin/idocument.h>
44 #include <coreplugin/editormanager/editormanager.h>
45
46 #include <coreplugin/locator/commandlocator.h>
47
48 #include <utils/parameteraction.h>
49 #include <utils/qtcassert.h>
50
51 #include <vcsbase/basevcseditorfactory.h>
52 #include <vcsbase/basevcssubmiteditorfactory.h>
53 #include <vcsbase/vcsbaseeditor.h>
54 #include <vcsbase/vcsbaseconstants.h>
55 #include <vcsbase/vcsoutputwindow.h>
56 #include <vcsbase/vcscommand.h>
57
58 #include <QAction>
59 #include <QMenu>
60 #include <QDebug>
61 #include <QtGlobal>
62 #include <QDir>
63 #include <QDialog>
64 #include <QFileDialog>
65
66 #ifdef WITH_TESTS
67 #include <QTest>
68 #endif
69
70 using namespace VcsBase;
71 using namespace Utils;
72 using namespace std::placeholders;
73
74 namespace Mercurial {
75 namespace Internal {
76
77 class MercurialTopicCache : public Core::IVersionControl::TopicCache
78 {
79 public:
MercurialTopicCache(MercurialClient * client)80 MercurialTopicCache(MercurialClient *client) : m_client(client) {}
81
82 protected:
trackFile(const QString & repository)83 QString trackFile(const QString &repository) override
84 {
85 return repository + QLatin1String("/.hg/branch");
86 }
87
refreshTopic(const QString & repository)88 QString refreshTopic(const QString &repository) override
89 {
90 return m_client->branchQuerySync(repository);
91 }
92
93 private:
94 MercurialClient *m_client;
95 };
96
97 const VcsBaseEditorParameters logEditorParameters {
98 LogOutput,
99 Constants::FILELOG_ID,
100 Constants::FILELOG_DISPLAY_NAME,
101 Constants::LOGAPP
102 };
103
104 const VcsBaseEditorParameters annotateEditorParameters {
105 AnnotateOutput,
106 Constants::ANNOTATELOG_ID,
107 Constants::ANNOTATELOG_DISPLAY_NAME,
108 Constants::ANNOTATEAPP
109 };
110
111 const VcsBaseEditorParameters diffEditorParameters {
112 DiffOutput,
113 Constants::DIFFLOG_ID,
114 Constants::DIFFLOG_DISPLAY_NAME,
115 Constants::DIFFAPP
116 };
117
118 const VcsBaseSubmitEditorParameters submitEditorParameters {
119 Constants::COMMITMIMETYPE,
120 Constants::COMMIT_ID,
121 Constants::COMMIT_DISPLAY_NAME,
122 VcsBaseSubmitEditorParameters::DiffFiles
123 };
124
125 class MercurialPluginPrivate final : public VcsBase::VcsBasePluginPrivate
126 {
127 Q_DECLARE_TR_FUNCTIONS(Mercurial::Internal::MercurialPlugin)
128
129 public:
130 MercurialPluginPrivate();
131
132 // IVersionControl
133 QString displayName() const final;
134 Utils::Id id() const final;
135 bool isVcsFileOrDirectory(const Utils::FilePath &fileName) const final;
136
137 bool managesDirectory(const QString &filename, QString *topLevel = nullptr) const final;
138 bool managesFile(const QString &workingDirectory, const QString &fileName) const final;
139 bool isConfigured() const final;
140 bool supportsOperation(Operation operation) const final;
141 bool vcsOpen(const QString &fileName) final;
142 bool vcsAdd(const QString &filename) final;
143 bool vcsDelete(const QString &filename) final;
144 bool vcsMove(const QString &from, const QString &to) final;
145 bool vcsCreateRepository(const QString &directory) final;
146 void vcsAnnotate(const QString &file, int line) final;
vcsDescribe(const QString & source,const QString & id)147 void vcsDescribe(const QString &source, const QString &id) final { m_client.view(source, id); }
148
149 Core::ShellCommand *createInitialCheckoutCommand(const QString &url,
150 const Utils::FilePath &baseDirectory,
151 const QString &localName,
152 const QStringList &extraArgs) final;
153
154 bool sccManaged(const QString &filename);
155
156 // To be connected to the HgTask's success signal to emit the repository/
157 // files changed signals according to the variant's type:
158 // String -> repository, StringList -> files
159 void changed(const QVariant&);
160
161 private:
162 void updateActions(VcsBase::VcsBasePluginPrivate::ActionState) final;
163 bool submitEditorAboutToClose() final;
164
165 // File menu action slots
166 void addCurrentFile();
167 void annotateCurrentFile();
168 void diffCurrentFile();
169 void logCurrentFile();
170 void revertCurrentFile();
171 void statusCurrentFile();
172
173 // Directory menu action slots
174 void diffRepository();
175 void logRepository();
176 void revertMulti();
177 void statusMulti();
178
179 // Repository menu action slots
180 void pull();
181 void push();
182 void update();
183 void import();
184 void incoming();
185 void outgoing();
186 void commit();
187 void showCommitWidget(const QList<VcsBase::VcsBaseClient::StatusItem> &status);
188 void commitFromEditor() override;
189 void diffFromEditorSelected(const QStringList &files);
190
191 void createMenu(const Core::Context &context);
192 void createFileActions(const Core::Context &context);
193 void createDirectoryActions(const Core::Context &context);
194 void createRepositoryActions(const Core::Context &context);
195
196 // Variables
197 MercurialSettings m_settings;
198 MercurialClient m_client{&m_settings};
199 MercurialSettingsPage m_settingsPage{&m_settings};
200
201 Core::CommandLocator *m_commandLocator = nullptr;
202 Core::ActionContainer *m_mercurialContainer = nullptr;
203
204 QList<QAction *> m_repositoryActionList;
205
206 // Menu items (file actions)
207 ParameterAction *m_addAction = nullptr;
208 ParameterAction *m_deleteAction = nullptr;
209 ParameterAction *annotateFile = nullptr;
210 ParameterAction *diffFile = nullptr;
211 ParameterAction *logFile = nullptr;
212 ParameterAction *revertFile = nullptr;
213 ParameterAction *statusFile = nullptr;
214
215 QAction *m_createRepositoryAction = nullptr;
216 QAction *m_menuAction = nullptr;
217
218 QString m_submitRepository;
219
220 bool m_submitActionTriggered = false;
221
222 public:
223 VcsSubmitEditorFactory submitEditorFactory {
224 submitEditorParameters,
__anon06cc70230102null225 [] { return new CommitEditor; },
226 this
227 };
228
229 VcsEditorFactory logEditorFactory {
230 &logEditorParameters,
__anon06cc70230202null231 [this] { return new MercurialEditorWidget(&m_client); },
232 std::bind(&MercurialPluginPrivate::vcsDescribe, this, _1, _2)
233 };
234
235 VcsEditorFactory annotateEditorFactory {
236 &annotateEditorParameters,
__anon06cc70230302null237 [this] { return new MercurialEditorWidget(&m_client); },
238 std::bind(&MercurialPluginPrivate::vcsDescribe, this, _1, _2)
239 };
240
241 VcsEditorFactory diffEditorFactory {
242 &diffEditorParameters,
__anon06cc70230402null243 [this] { return new MercurialEditorWidget(&m_client); },
244 std::bind(&MercurialPluginPrivate::vcsDescribe, this, _1, _2)
245 };
246 };
247
248 static MercurialPluginPrivate *dd = nullptr;
249
~MercurialPlugin()250 MercurialPlugin::~MercurialPlugin()
251 {
252 delete dd;
253 dd = nullptr;
254 }
255
initialize(const QStringList &,QString *)256 bool MercurialPlugin::initialize(const QStringList & /* arguments */, QString * /*errorMessage */)
257 {
258 dd = new MercurialPluginPrivate;
259 return true;
260 }
261
extensionsInitialized()262 void MercurialPlugin::extensionsInitialized()
263 {
264 dd->extensionsInitialized();
265 }
266
MercurialPluginPrivate()267 MercurialPluginPrivate::MercurialPluginPrivate()
268 : VcsBase::VcsBasePluginPrivate(Core::Context(Constants::MERCURIAL_CONTEXT))
269 {
270 dd = this;
271
272 setTopicCache(new MercurialTopicCache(&m_client));
273
274 Core::Context context(Constants::MERCURIAL_CONTEXT);
275
276 connect(&m_client, &VcsBaseClient::changed, this, &MercurialPluginPrivate::changed);
277 connect(&m_client, &MercurialClient::needUpdate, this, &MercurialPluginPrivate::update);
278
279 const QString prefix = QLatin1String("hg");
280 m_commandLocator = new Core::CommandLocator("Mercurial", prefix, prefix, this);
281 m_commandLocator->setDescription(tr("Triggers a Mercurial version control operation."));
282
283 createMenu(context);
284
285 connect(&m_settings, &AspectContainer::applied, this, &IVersionControl::configurationChanged);
286 }
287
createMenu(const Core::Context & context)288 void MercurialPluginPrivate::createMenu(const Core::Context &context)
289 {
290 // Create menu item for Mercurial
291 m_mercurialContainer = Core::ActionManager::createMenu("Mercurial.MercurialMenu");
292 QMenu *menu = m_mercurialContainer->menu();
293 menu->setTitle(tr("Me&rcurial"));
294
295 createFileActions(context);
296 m_mercurialContainer->addSeparator(context);
297 createDirectoryActions(context);
298 m_mercurialContainer->addSeparator(context);
299 createRepositoryActions(context);
300 m_mercurialContainer->addSeparator(context);
301
302 // Request the Tools menu and add the Mercurial menu to it
303 Core::ActionContainer *toolsMenu = Core::ActionManager::actionContainer(Utils::Id(Core::Constants::M_TOOLS));
304 toolsMenu->addMenu(m_mercurialContainer);
305 m_menuAction = m_mercurialContainer->menu()->menuAction();
306 }
307
createFileActions(const Core::Context & context)308 void MercurialPluginPrivate::createFileActions(const Core::Context &context)
309 {
310 Core::Command *command;
311
312 annotateFile = new ParameterAction(tr("Annotate Current File"), tr("Annotate \"%1\""), ParameterAction::EnabledWithParameter, this);
313 command = Core::ActionManager::registerAction(annotateFile, Utils::Id(Constants::ANNOTATE), context);
314 command->setAttribute(Core::Command::CA_UpdateText);
315 connect(annotateFile, &QAction::triggered, this, &MercurialPluginPrivate::annotateCurrentFile);
316 m_mercurialContainer->addAction(command);
317 m_commandLocator->appendCommand(command);
318
319 diffFile = new ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), ParameterAction::EnabledWithParameter, this);
320 command = Core::ActionManager::registerAction(diffFile, Utils::Id(Constants::DIFF), context);
321 command->setAttribute(Core::Command::CA_UpdateText);
322 command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+H,Meta+D") : tr("Alt+G,Alt+D")));
323 connect(diffFile, &QAction::triggered, this, &MercurialPluginPrivate::diffCurrentFile);
324 m_mercurialContainer->addAction(command);
325 m_commandLocator->appendCommand(command);
326
327 logFile = new ParameterAction(tr("Log Current File"), tr("Log \"%1\""), ParameterAction::EnabledWithParameter, this);
328 command = Core::ActionManager::registerAction(logFile, Utils::Id(Constants::LOG), context);
329 command->setAttribute(Core::Command::CA_UpdateText);
330 command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+H,Meta+L") : tr("Alt+G,Alt+L")));
331 connect(logFile, &QAction::triggered, this, &MercurialPluginPrivate::logCurrentFile);
332 m_mercurialContainer->addAction(command);
333 m_commandLocator->appendCommand(command);
334
335 statusFile = new ParameterAction(tr("Status Current File"), tr("Status \"%1\""), ParameterAction::EnabledWithParameter, this);
336 command = Core::ActionManager::registerAction(statusFile, Utils::Id(Constants::STATUS), context);
337 command->setAttribute(Core::Command::CA_UpdateText);
338 command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+H,Meta+S") : tr("Alt+G,Alt+S")));
339 connect(statusFile, &QAction::triggered, this, &MercurialPluginPrivate::statusCurrentFile);
340 m_mercurialContainer->addAction(command);
341 m_commandLocator->appendCommand(command);
342
343 m_mercurialContainer->addSeparator(context);
344
345 m_addAction = new ParameterAction(tr("Add"), tr("Add \"%1\""), ParameterAction::EnabledWithParameter, this);
346 command = Core::ActionManager::registerAction(m_addAction, Utils::Id(Constants::ADD), context);
347 command->setAttribute(Core::Command::CA_UpdateText);
348 connect(m_addAction, &QAction::triggered, this, &MercurialPluginPrivate::addCurrentFile);
349 m_mercurialContainer->addAction(command);
350 m_commandLocator->appendCommand(command);
351
352 m_deleteAction = new ParameterAction(tr("Delete..."), tr("Delete \"%1\"..."), ParameterAction::EnabledWithParameter, this);
353 command = Core::ActionManager::registerAction(m_deleteAction, Utils::Id(Constants::DELETE), context);
354 command->setAttribute(Core::Command::CA_UpdateText);
355 connect(m_deleteAction, &QAction::triggered, this, &MercurialPluginPrivate::promptToDeleteCurrentFile);
356 m_mercurialContainer->addAction(command);
357 m_commandLocator->appendCommand(command);
358
359 revertFile = new ParameterAction(tr("Revert Current File..."), tr("Revert \"%1\"..."), ParameterAction::EnabledWithParameter, this);
360 command = Core::ActionManager::registerAction(revertFile, Utils::Id(Constants::REVERT), context);
361 command->setAttribute(Core::Command::CA_UpdateText);
362 connect(revertFile, &QAction::triggered, this, &MercurialPluginPrivate::revertCurrentFile);
363 m_mercurialContainer->addAction(command);
364 m_commandLocator->appendCommand(command);
365 }
366
addCurrentFile()367 void MercurialPluginPrivate::addCurrentFile()
368 {
369 const VcsBasePluginState state = currentState();
370 QTC_ASSERT(state.hasFile(), return);
371 m_client.synchronousAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
372 }
373
annotateCurrentFile()374 void MercurialPluginPrivate::annotateCurrentFile()
375 {
376 int currentLine = -1;
377 if (Core::IEditor *editor = Core::EditorManager::currentEditor())
378 currentLine = editor->currentLine();
379 const VcsBasePluginState state = currentState();
380 QTC_ASSERT(state.hasFile(), return);
381 m_client.annotate(state.currentFileTopLevel(), state.relativeCurrentFile(), QString(), currentLine);
382 }
383
diffCurrentFile()384 void MercurialPluginPrivate::diffCurrentFile()
385 {
386 const VcsBasePluginState state = currentState();
387 QTC_ASSERT(state.hasFile(), return);
388 m_client.diff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
389 }
390
logCurrentFile()391 void MercurialPluginPrivate::logCurrentFile()
392 {
393 const VcsBasePluginState state = currentState();
394 QTC_ASSERT(state.hasFile(), return);
395 m_client.log(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()),
396 QStringList(), true);
397 }
398
revertCurrentFile()399 void MercurialPluginPrivate::revertCurrentFile()
400 {
401 const VcsBasePluginState state = currentState();
402 QTC_ASSERT(state.hasFile(), return);
403
404 RevertDialog reverter(Core::ICore::dialogParent());
405 if (reverter.exec() != QDialog::Accepted)
406 return;
407 m_client.revertFile(state.currentFileTopLevel(), state.relativeCurrentFile(), reverter.revision());
408 }
409
statusCurrentFile()410 void MercurialPluginPrivate::statusCurrentFile()
411 {
412 const VcsBasePluginState state = currentState();
413 QTC_ASSERT(state.hasFile(), return);
414 m_client.status(state.currentFileTopLevel(), state.relativeCurrentFile());
415 }
416
createDirectoryActions(const Core::Context & context)417 void MercurialPluginPrivate::createDirectoryActions(const Core::Context &context)
418 {
419 auto action = new QAction(tr("Diff"), this);
420 m_repositoryActionList.append(action);
421 Core::Command *command = Core::ActionManager::registerAction(action, Utils::Id(Constants::DIFFMULTI), context);
422 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::diffRepository);
423 m_mercurialContainer->addAction(command);
424 m_commandLocator->appendCommand(command);
425
426 action = new QAction(tr("Log"), this);
427 m_repositoryActionList.append(action);
428 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::LOGMULTI), context);
429 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::logRepository);
430 m_mercurialContainer->addAction(command);
431 m_commandLocator->appendCommand(command);
432
433 action = new QAction(tr("Revert..."), this);
434 m_repositoryActionList.append(action);
435 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::REVERTMULTI), context);
436 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::revertMulti);
437 m_mercurialContainer->addAction(command);
438 m_commandLocator->appendCommand(command);
439
440 action = new QAction(tr("Status"), this);
441 m_repositoryActionList.append(action);
442 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::STATUSMULTI), context);
443 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::statusMulti);
444 m_mercurialContainer->addAction(command);
445 m_commandLocator->appendCommand(command);
446 }
447
diffRepository()448 void MercurialPluginPrivate::diffRepository()
449 {
450 const VcsBasePluginState state = currentState();
451 QTC_ASSERT(state.hasTopLevel(), return);
452 m_client.diff(state.topLevel());
453 }
454
logRepository()455 void MercurialPluginPrivate::logRepository()
456 {
457 const VcsBasePluginState state = currentState();
458 QTC_ASSERT(state.hasTopLevel(), return);
459 m_client.log(state.topLevel());
460 }
461
revertMulti()462 void MercurialPluginPrivate::revertMulti()
463 {
464 const VcsBasePluginState state = currentState();
465 QTC_ASSERT(state.hasTopLevel(), return);
466
467 RevertDialog reverter(Core::ICore::dialogParent());
468 if (reverter.exec() != QDialog::Accepted)
469 return;
470 m_client.revertAll(state.topLevel(), reverter.revision());
471 }
472
statusMulti()473 void MercurialPluginPrivate::statusMulti()
474 {
475 const VcsBasePluginState state = currentState();
476 QTC_ASSERT(state.hasTopLevel(), return);
477
478 m_client.status(state.topLevel());
479 }
480
createRepositoryActions(const Core::Context & context)481 void MercurialPluginPrivate::createRepositoryActions(const Core::Context &context)
482 {
483 auto action = new QAction(tr("Pull..."), this);
484 m_repositoryActionList.append(action);
485 Core::Command *command = Core::ActionManager::registerAction(action, Utils::Id(Constants::PULL), context);
486 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::pull);
487 m_mercurialContainer->addAction(command);
488 m_commandLocator->appendCommand(command);
489
490 action = new QAction(tr("Push..."), this);
491 m_repositoryActionList.append(action);
492 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::PUSH), context);
493 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::push);
494 m_mercurialContainer->addAction(command);
495 m_commandLocator->appendCommand(command);
496
497 action = new QAction(tr("Update..."), this);
498 m_repositoryActionList.append(action);
499 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::UPDATE), context);
500 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::update);
501 m_mercurialContainer->addAction(command);
502 m_commandLocator->appendCommand(command);
503
504 action = new QAction(tr("Import..."), this);
505 m_repositoryActionList.append(action);
506 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::IMPORT), context);
507 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::import);
508 m_mercurialContainer->addAction(command);
509 m_commandLocator->appendCommand(command);
510
511 action = new QAction(tr("Incoming..."), this);
512 m_repositoryActionList.append(action);
513 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::INCOMING), context);
514 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::incoming);
515 m_mercurialContainer->addAction(command);
516 m_commandLocator->appendCommand(command);
517
518 action = new QAction(tr("Outgoing..."), this);
519 m_repositoryActionList.append(action);
520 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::OUTGOING), context);
521 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::outgoing);
522 m_mercurialContainer->addAction(command);
523 m_commandLocator->appendCommand(command);
524
525 action = new QAction(tr("Commit..."), this);
526 m_repositoryActionList.append(action);
527 command = Core::ActionManager::registerAction(action, Utils::Id(Constants::COMMIT), context);
528 command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+H,Meta+C") : tr("Alt+G,Alt+C")));
529 connect(action, &QAction::triggered, this, &MercurialPluginPrivate::commit);
530 m_mercurialContainer->addAction(command);
531 m_commandLocator->appendCommand(command);
532
533 m_createRepositoryAction = new QAction(tr("Create Repository..."), this);
534 command = Core::ActionManager::registerAction(m_createRepositoryAction, Utils::Id(Constants::CREATE_REPOSITORY), context);
535 connect(m_createRepositoryAction, &QAction::triggered, this, &MercurialPluginPrivate::createRepository);
536 m_mercurialContainer->addAction(command);
537 }
538
pull()539 void MercurialPluginPrivate::pull()
540 {
541 const VcsBasePluginState state = currentState();
542 QTC_ASSERT(state.hasTopLevel(), return);
543
544 SrcDestDialog dialog(state, SrcDestDialog::incoming, Core::ICore::dialogParent());
545 dialog.setWindowTitle(tr("Pull Source"));
546 if (dialog.exec() != QDialog::Accepted)
547 return;
548 m_client.synchronousPull(dialog.workingDir(), dialog.getRepositoryString());
549 }
550
push()551 void MercurialPluginPrivate::push()
552 {
553 const VcsBasePluginState state = currentState();
554 QTC_ASSERT(state.hasTopLevel(), return);
555
556 SrcDestDialog dialog(state, SrcDestDialog::outgoing, Core::ICore::dialogParent());
557 dialog.setWindowTitle(tr("Push Destination"));
558 if (dialog.exec() != QDialog::Accepted)
559 return;
560 m_client.synchronousPush(dialog.workingDir(), dialog.getRepositoryString());
561 }
562
update()563 void MercurialPluginPrivate::update()
564 {
565 const VcsBasePluginState state = currentState();
566 QTC_ASSERT(state.hasTopLevel(), return);
567
568 RevertDialog updateDialog(Core::ICore::dialogParent());
569 updateDialog.setWindowTitle(tr("Update"));
570 if (updateDialog.exec() != QDialog::Accepted)
571 return;
572 m_client.update(state.topLevel(), updateDialog.revision());
573 }
574
import()575 void MercurialPluginPrivate::import()
576 {
577 const VcsBasePluginState state = currentState();
578 QTC_ASSERT(state.hasTopLevel(), return);
579
580 QFileDialog importDialog(Core::ICore::dialogParent());
581 importDialog.setFileMode(QFileDialog::ExistingFiles);
582 importDialog.setViewMode(QFileDialog::Detail);
583
584 if (importDialog.exec() != QDialog::Accepted)
585 return;
586
587 const QStringList fileNames = importDialog.selectedFiles();
588 m_client.import(state.topLevel(), fileNames);
589 }
590
incoming()591 void MercurialPluginPrivate::incoming()
592 {
593 const VcsBasePluginState state = currentState();
594 QTC_ASSERT(state.hasTopLevel(), return);
595
596 SrcDestDialog dialog(state, SrcDestDialog::incoming, Core::ICore::dialogParent());
597 dialog.setWindowTitle(tr("Incoming Source"));
598 if (dialog.exec() != QDialog::Accepted)
599 return;
600 m_client.incoming(state.topLevel(), dialog.getRepositoryString());
601 }
602
outgoing()603 void MercurialPluginPrivate::outgoing()
604 {
605 const VcsBasePluginState state = currentState();
606 QTC_ASSERT(state.hasTopLevel(), return);
607 m_client.outgoing(state.topLevel());
608 }
609
commit()610 void MercurialPluginPrivate::commit()
611 {
612 if (!promptBeforeCommit())
613 return;
614
615 if (raiseSubmitEditor())
616 return;
617
618 const VcsBasePluginState state = currentState();
619 QTC_ASSERT(state.hasTopLevel(), return);
620
621 m_submitRepository = state.topLevel();
622
623 connect(&m_client, &MercurialClient::parsedStatus, this, &MercurialPluginPrivate::showCommitWidget);
624 m_client.emitParsedStatus(m_submitRepository);
625 }
626
showCommitWidget(const QList<VcsBaseClient::StatusItem> & status)627 void MercurialPluginPrivate::showCommitWidget(const QList<VcsBaseClient::StatusItem> &status)
628 {
629 //Once we receive our data release the connection so it can be reused elsewhere
630 disconnect(&m_client, &MercurialClient::parsedStatus, this, &MercurialPluginPrivate::showCommitWidget);
631
632 if (status.isEmpty()) {
633 VcsOutputWindow::appendError(tr("There are no changes to commit."));
634 return;
635 }
636
637 // Start new temp file
638 TempFileSaver saver;
639 // Keep the file alive, else it removes self and forgets its name
640 saver.setAutoRemove(false);
641 if (!saver.finalize()) {
642 VcsOutputWindow::appendError(saver.errorString());
643 return;
644 }
645
646 Core::IEditor *editor = Core::EditorManager::openEditor(saver.filePath(), Constants::COMMIT_ID);
647 if (!editor) {
648 VcsOutputWindow::appendError(tr("Unable to create an editor for the commit."));
649 return;
650 }
651
652 QTC_ASSERT(qobject_cast<CommitEditor *>(editor), return);
653 auto commitEditor = static_cast<CommitEditor *>(editor);
654 setSubmitEditor(commitEditor);
655
656 connect(commitEditor, &VcsBaseSubmitEditor::diffSelectedFiles,
657 this, &MercurialPluginPrivate::diffFromEditorSelected);
658 commitEditor->setCheckScriptWorkingDirectory(m_submitRepository);
659
660 const QString msg = tr("Commit changes for \"%1\".").
661 arg(QDir::toNativeSeparators(m_submitRepository));
662 commitEditor->document()->setPreferredDisplayName(msg);
663
664 const QString branch = vcsTopic(m_submitRepository);
665 commitEditor->setFields(QFileInfo(m_submitRepository), branch,
666 m_settings.userName.value(),
667 m_settings.userEmail.value(), status);
668 }
669
diffFromEditorSelected(const QStringList & files)670 void MercurialPluginPrivate::diffFromEditorSelected(const QStringList &files)
671 {
672 m_client.diff(m_submitRepository, files);
673 }
674
commitFromEditor()675 void MercurialPluginPrivate::commitFromEditor()
676 {
677 // Close the submit editor
678 m_submitActionTriggered = true;
679 QTC_ASSERT(submitEditor(), return);
680 Core::EditorManager::closeDocuments({submitEditor()->document()});
681 }
682
submitEditorAboutToClose()683 bool MercurialPluginPrivate::submitEditorAboutToClose()
684 {
685 auto commitEditor = qobject_cast<CommitEditor *>(submitEditor());
686 QTC_ASSERT(commitEditor, return true);
687 Core::IDocument *editorFile = commitEditor->document();
688 QTC_ASSERT(editorFile, return true);
689
690 const VcsBaseSubmitEditor::PromptSubmitResult response =
691 commitEditor->promptSubmit(this, nullptr, !m_submitActionTriggered);
692 m_submitActionTriggered = false;
693
694 switch (response) {
695 case VcsBaseSubmitEditor::SubmitCanceled:
696 return false;
697 case VcsBaseSubmitEditor::SubmitDiscarded:
698 return true;
699 default:
700 break;
701 }
702
703 const QStringList files = commitEditor->checkedFiles();
704 if (!files.empty()) {
705 //save the commit message
706 if (!Core::DocumentManager::saveDocument(editorFile))
707 return false;
708
709 QStringList extraOptions;
710 if (!commitEditor->committerInfo().isEmpty())
711 extraOptions << QLatin1String("-u") << commitEditor->committerInfo();
712 m_client.commit(m_submitRepository, files, editorFile->filePath().toString(),
713 extraOptions);
714 }
715 return true;
716 }
717
updateActions(VcsBasePluginPrivate::ActionState as)718 void MercurialPluginPrivate::updateActions(VcsBasePluginPrivate::ActionState as)
719 {
720 if (!enableMenuAction(as, m_menuAction)) {
721 m_commandLocator->setEnabled(false);
722 return;
723 }
724 const QString filename = currentState().currentFileName();
725 const bool repoEnabled = currentState().hasTopLevel();
726 m_commandLocator->setEnabled(repoEnabled);
727
728 annotateFile->setParameter(filename);
729 diffFile->setParameter(filename);
730 logFile->setParameter(filename);
731 m_addAction->setParameter(filename);
732 m_deleteAction->setParameter(filename);
733 revertFile->setParameter(filename);
734 statusFile->setParameter(filename);
735
736 foreach (QAction *repoAction, m_repositoryActionList)
737 repoAction->setEnabled(repoEnabled);
738 }
739
displayName() const740 QString MercurialPluginPrivate::displayName() const
741 {
742 return tr("Mercurial");
743 }
744
id() const745 Utils::Id MercurialPluginPrivate::id() const
746 {
747 return {VcsBase::Constants::VCS_ID_MERCURIAL};
748 }
749
isVcsFileOrDirectory(const Utils::FilePath & fileName) const750 bool MercurialPluginPrivate::isVcsFileOrDirectory(const Utils::FilePath &fileName) const
751 {
752 return m_client.isVcsDirectory(fileName);
753 }
754
managesDirectory(const QString & directory,QString * topLevel) const755 bool MercurialPluginPrivate::managesDirectory(const QString &directory, QString *topLevel) const
756 {
757 QFileInfo dir(directory);
758 const QString topLevelFound = m_client.findTopLevelForFile(dir);
759 if (topLevel)
760 *topLevel = topLevelFound;
761 return !topLevelFound.isEmpty();
762 }
763
managesFile(const QString & workingDirectory,const QString & fileName) const764 bool MercurialPluginPrivate::managesFile(const QString &workingDirectory, const QString &fileName) const
765 {
766 return m_client.managesFile(workingDirectory, fileName);
767 }
768
isConfigured() const769 bool MercurialPluginPrivate::isConfigured() const
770 {
771 const FilePath binary = m_settings.binaryPath.filePath();
772 if (binary.isEmpty())
773 return false;
774 QFileInfo fi = binary.toFileInfo();
775 return fi.exists() && fi.isFile() && fi.isExecutable();
776 }
777
supportsOperation(Operation operation) const778 bool MercurialPluginPrivate::supportsOperation(Operation operation) const
779 {
780 bool supported = isConfigured();
781 switch (operation) {
782 case Core::IVersionControl::AddOperation:
783 case Core::IVersionControl::DeleteOperation:
784 case Core::IVersionControl::MoveOperation:
785 case Core::IVersionControl::CreateRepositoryOperation:
786 case Core::IVersionControl::AnnotateOperation:
787 case Core::IVersionControl::InitialCheckoutOperation:
788 break;
789 case Core::IVersionControl::SnapshotOperations:
790 supported = false;
791 break;
792 }
793 return supported;
794 }
795
vcsOpen(const QString & filename)796 bool MercurialPluginPrivate::vcsOpen(const QString &filename)
797 {
798 Q_UNUSED(filename)
799 return true;
800 }
801
vcsAdd(const QString & filename)802 bool MercurialPluginPrivate::vcsAdd(const QString &filename)
803 {
804 const QFileInfo fi(filename);
805 return m_client.synchronousAdd(fi.absolutePath(), fi.fileName());
806 }
807
vcsDelete(const QString & filename)808 bool MercurialPluginPrivate::vcsDelete(const QString &filename)
809 {
810 const QFileInfo fi(filename);
811 return m_client.synchronousRemove(fi.absolutePath(), fi.fileName());
812 }
813
vcsMove(const QString & from,const QString & to)814 bool MercurialPluginPrivate::vcsMove(const QString &from, const QString &to)
815 {
816 const QFileInfo fromInfo(from);
817 const QFileInfo toInfo(to);
818 return m_client.synchronousMove(fromInfo.absolutePath(),
819 fromInfo.absoluteFilePath(),
820 toInfo.absoluteFilePath());
821 }
822
vcsCreateRepository(const QString & directory)823 bool MercurialPluginPrivate::vcsCreateRepository(const QString &directory)
824 {
825 return m_client.synchronousCreateRepository(directory);
826 }
827
vcsAnnotate(const QString & file,int line)828 void MercurialPluginPrivate::vcsAnnotate(const QString &file, int line)
829 {
830 const QFileInfo fi(file);
831 m_client.annotate(fi.absolutePath(), fi.fileName(), QString(), line);
832 }
833
createInitialCheckoutCommand(const QString & url,const Utils::FilePath & baseDirectory,const QString & localName,const QStringList & extraArgs)834 Core::ShellCommand *MercurialPluginPrivate::createInitialCheckoutCommand(const QString &url,
835 const Utils::FilePath &baseDirectory,
836 const QString &localName,
837 const QStringList &extraArgs)
838 {
839 QStringList args;
840 args << QLatin1String("clone") << extraArgs << url << localName;
841 auto command = new VcsBase::VcsCommand(baseDirectory.toString(),
842 m_client.processEnvironment());
843 command->addJob({m_settings.binaryPath.filePath(), args}, -1);
844 return command;
845 }
846
sccManaged(const QString & filename)847 bool MercurialPluginPrivate::sccManaged(const QString &filename)
848 {
849 const QFileInfo fi(filename);
850 QString topLevel;
851 const bool managed = managesDirectory(fi.absolutePath(), &topLevel);
852 if (!managed || topLevel.isEmpty())
853 return false;
854 const QDir topLevelDir(topLevel);
855 return m_client.manifestSync(topLevel, topLevelDir.relativeFilePath(filename));
856 }
857
changed(const QVariant & v)858 void MercurialPluginPrivate::changed(const QVariant &v)
859 {
860 switch (v.type()) {
861 case QVariant::String:
862 emit repositoryChanged(v.toString());
863 break;
864 case QVariant::StringList:
865 emit filesChanged(v.toStringList());
866 break;
867 default:
868 break;
869 }
870 }
871
872 #ifdef WITH_TESTS
873
testDiffFileResolving_data()874 void MercurialPlugin::testDiffFileResolving_data()
875 {
876 QTest::addColumn<QByteArray>("header");
877 QTest::addColumn<QByteArray>("fileName");
878
879 QTest::newRow("New") << QByteArray(
880 "diff --git a/src/plugins/mercurial/mercurialeditor.cpp b/src/plugins/mercurial/mercurialeditor.cpp\n"
881 "new file mode 100644\n"
882 "--- /dev/null\n"
883 "+++ b/src/plugins/mercurial/mercurialeditor.cpp\n"
884 "@@ -0,0 +1,112 @@\n\n")
885 << QByteArray("src/plugins/mercurial/mercurialeditor.cpp");
886 QTest::newRow("Deleted") << QByteArray(
887 "diff --git a/src/plugins/mercurial/mercurialeditor.cpp b/src/plugins/mercurial/mercurialeditor.cpp\n"
888 "deleted file mode 100644\n"
889 "--- a/src/plugins/mercurial/mercurialeditor.cpp\n"
890 "+++ /dev/null\n"
891 "@@ -1,112 +0,0 @@\n\n")
892 << QByteArray("src/plugins/mercurial/mercurialeditor.cpp");
893 QTest::newRow("Normal") << QByteArray(
894 "diff --git a/src/plugins/mercurial/mercurialeditor.cpp b/src/plugins/mercurial/mercurialeditor.cpp\n"
895 "--- a/src/plugins/mercurial/mercurialeditor.cpp\n"
896 "+++ b/src/plugins/mercurial/mercurialeditor.cpp\n"
897 "@@ -49,6 +49,8 @@\n\n")
898 << QByteArray("src/plugins/mercurial/mercurialeditor.cpp");
899 }
900
testDiffFileResolving()901 void MercurialPlugin::testDiffFileResolving()
902 {
903 VcsBaseEditorWidget::testDiffFileResolving(dd->diffEditorFactory);
904 }
905
testLogResolving()906 void MercurialPlugin::testLogResolving()
907 {
908 QByteArray data(
909 "changeset: 18473:692cbda1eb50\n"
910 "branch: stable\n"
911 "bookmark: @\n"
912 "tag: tip\n"
913 "user: FUJIWARA Katsunori <foozy@lares.dti.ne.jp>\n"
914 "date: Wed Jan 23 22:52:55 2013 +0900\n"
915 "summary: revset: evaluate sub expressions correctly (issue3775)\n"
916 "\n"
917 "changeset: 18472:37100f30590f\n"
918 "branch: stable\n"
919 "user: Pierre-Yves David <pierre-yves.david@ens-lyon.org>\n"
920 "date: Sat Jan 19 04:08:16 2013 +0100\n"
921 "summary: test-rebase: add another test for rebase with multiple roots\n"
922 );
923 VcsBaseEditorWidget::testLogResolving(dd->logEditorFactory, data, "18473:692cbda1eb50", "18472:37100f30590f");
924 }
925 #endif
926
927 } // namespace Internal
928 } // namespace Mercurial
929