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