1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "cvsplugin.h"
35 #include "settingspage.h"
36 #include "cvseditor.h"
37 #include "cvssubmiteditor.h"
38 #include "cvsconstants.h"
39 #include "cvscontrol.h"
40 #include "checkoutwizard.h"
42 #include <vcsbase/basevcseditorfactory.h>
43 #include <vcsbase/vcsbaseeditor.h>
44 #include <vcsbase/basevcssubmiteditorfactory.h>
45 #include <vcsbase/vcsbaseoutputwindow.h>
46 #include <locator/commandlocator.h>
47 #include <utils/synchronousprocess.h>
48 #include <utils/parameteraction.h>
50 #include <coreplugin/icore.h>
51 #include <coreplugin/coreconstants.h>
52 #include <coreplugin/filemanager.h>
53 #include <coreplugin/messagemanager.h>
54 #include <coreplugin/mimedatabase.h>
55 #include <coreplugin/actionmanager/actionmanager.h>
56 #include <coreplugin/actionmanager/actioncontainer.h>
57 #include <coreplugin/actionmanager/command.h>
58 #include <coreplugin/uniqueidmanager.h>
59 #include <coreplugin/editormanager/editormanager.h>
60 #include <coreplugin/vcsmanager.h>
61 #include <utils/stringutils.h>
62 #include <utils/qtcassert.h>
64 #include <QtCore/QDebug>
65 #include <QtCore/QDate>
66 #include <QtCore/QDir>
67 #include <QtCore/QFileInfo>
68 #include <QtCore/QTextCodec>
69 #include <QtCore/QtPlugin>
70 #include <QtCore/QTemporaryFile>
71 #include <QtGui/QAction>
72 #include <QtGui/QMainWindow>
73 #include <QtGui/QMenu>
74 #include <QtGui/QMessageBox>
79 static inline QString msgCannotFindTopLevel(const QString &f)
81 return CVSPlugin::tr("Cannot find repository for '%1'").
82 arg(QDir::toNativeSeparators(f));
85 static inline QString msgLogParsingFailed()
87 return CVSPlugin::tr("Parsing of the log output failed");
90 static const char * const CMD_ID_CVS_MENU = "CVS.Menu";
91 static const char * const CMD_ID_ADD = "CVS.Add";
92 static const char * const CMD_ID_DELETE_FILE = "CVS.Delete";
93 static const char * const CMD_ID_EDIT_FILE = "CVS.EditFile";
94 static const char * const CMD_ID_UNEDIT_FILE = "CVS.UneditFile";
95 static const char * const CMD_ID_UNEDIT_REPOSITORY = "CVS.UneditRepository";
96 static const char * const CMD_ID_REVERT = "CVS.Revert";
97 static const char * const CMD_ID_SEPARATOR0 = "CVS.Separator0";
98 static const char * const CMD_ID_DIFF_PROJECT = "CVS.DiffAll";
99 static const char * const CMD_ID_DIFF_CURRENT = "CVS.DiffCurrent";
100 static const char * const CMD_ID_SEPARATOR1 = "CVS.Separator1";
101 static const char * const CMD_ID_COMMIT_ALL = "CVS.CommitAll";
102 static const char * const CMD_ID_REVERT_ALL = "CVS.RevertAll";
103 static const char * const CMD_ID_COMMIT_CURRENT = "CVS.CommitCurrent";
104 static const char * const CMD_ID_SEPARATOR2 = "CVS.Separator2";
105 static const char * const CMD_ID_FILELOG_CURRENT = "CVS.FilelogCurrent";
106 static const char * const CMD_ID_ANNOTATE_CURRENT = "CVS.AnnotateCurrent";
107 static const char * const CMD_ID_STATUS = "CVS.Status";
108 static const char * const CMD_ID_UPDATE = "CVS.Update";
109 static const char * const CMD_ID_PROJECTLOG = "CVS.ProjectLog";
110 static const char * const CMD_ID_PROJECTCOMMIT = "CVS.ProjectCommit";
111 static const char * const CMD_ID_REPOSITORYLOG = "CVS.RepositoryLog";
112 static const char * const CMD_ID_REPOSITORYDIFF = "CVS.RepositoryDiff";
113 static const char * const CMD_ID_REPOSITORYSTATUS = "CVS.RepositoryStatus";
114 static const char * const CMD_ID_REPOSITORYUPDATE = "CVS.RepositoryUpdate";
115 static const char * const CMD_ID_SEPARATOR3 = "CVS.Separator3";
117 static const VCSBase::VCSBaseEditorParameters editorParameters[] = {
119 VCSBase::RegularCommandOutput,
120 "CVS Command Log Editor", // id
121 QT_TRANSLATE_NOOP("VCS", "CVS Command Log Editor"), // display name
122 "CVS Command Log Editor", // context
123 "application/vnd.nokia.text.scs_cvs_commandlog",
125 { VCSBase::LogOutput,
126 "CVS File Log Editor", // id
127 QT_TRANSLATE_NOOP("VCS", "CVS File Log Editor"), // display name
128 "CVS File Log Editor", // context
129 "application/vnd.nokia.text.scs_cvs_filelog",
131 { VCSBase::AnnotateOutput,
132 "CVS Annotation Editor", // id
133 QT_TRANSLATE_NOOP("VCS", "CVS Annotation Editor"), // display name
134 "CVS Annotation Editor", // context
135 "application/vnd.nokia.text.scs_cvs_annotation",
137 { VCSBase::DiffOutput,
138 "CVS Diff Editor", // id
139 QT_TRANSLATE_NOOP("VCS", "CVS Diff Editor"), // display name
140 "CVS Diff Editor", // context
141 "text/x-patch","diff"}
144 // Utility to find a parameter set by type
145 static inline const VCSBase::VCSBaseEditorParameters *findType(int ie)
147 const VCSBase::EditorContentType et = static_cast<VCSBase::EditorContentType>(ie);
148 return VCSBase::VCSBaseEditor::findType(editorParameters, sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters), et);
151 static inline QString debugCodec(const QTextCodec *c)
153 return c ? QString::fromAscii(c->name()) : QString::fromAscii("Null codec");
156 Core::IEditor* locateEditor(const char *property, const QString &entry)
158 foreach (Core::IEditor *ed, Core::EditorManager::instance()->openedEditors())
159 if (ed->property(property).toString() == entry)
164 static inline bool messageBoxQuestion(const QString &title, const QString &question, QWidget *parent = 0)
166 return QMessageBox::question(parent, title, question, QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes;
169 // ------------- CVSPlugin
170 CVSPlugin *CVSPlugin::m_cvsPluginInstance = 0;
172 CVSPlugin::CVSPlugin() :
173 VCSBase::VCSBasePlugin(QLatin1String(CVS::Constants::CVSCOMMITEDITOR_ID)),
178 m_editCurrentAction(0),
179 m_uneditCurrentAction(0),
180 m_uneditRepositoryAction(0),
181 m_diffProjectAction(0),
182 m_diffCurrentAction(0),
183 m_logProjectAction(0),
184 m_logRepositoryAction(0),
185 m_commitAllAction(0),
186 m_revertRepositoryAction(0),
187 m_commitCurrentAction(0),
188 m_filelogCurrentAction(0),
189 m_annotateCurrentAction(0),
190 m_statusProjectAction(0),
191 m_updateProjectAction(0),
192 m_commitProjectAction(0),
193 m_diffRepositoryAction(0),
194 m_updateRepositoryAction(0),
195 m_statusRepositoryAction(0),
196 m_submitCurrentLogAction(0),
197 m_submitDiffAction(0),
198 m_submitUndoAction(0),
199 m_submitRedoAction(0),
201 m_submitActionTriggered(false)
205 CVSPlugin::~CVSPlugin()
207 cleanCommitMessageFile();
210 void CVSPlugin::cleanCommitMessageFile()
212 if (!m_commitMessageFileName.isEmpty()) {
213 QFile::remove(m_commitMessageFileName);
214 m_commitMessageFileName.clear();
215 m_commitRepository.clear();
218 bool CVSPlugin::isCommitEditorOpen() const
220 return !m_commitMessageFileName.isEmpty();
223 static const VCSBase::VCSBaseSubmitEditorParameters submitParameters = {
224 CVS::Constants::CVS_SUBMIT_MIMETYPE,
225 CVS::Constants::CVSCOMMITEDITOR_ID,
226 CVS::Constants::CVSCOMMITEDITOR_DISPLAY_NAME,
227 CVS::Constants::CVSCOMMITEDITOR
230 static inline Core::Command *createSeparator(QObject *parent,
231 Core::ActionManager *ami,
233 const Core::Context &globalcontext)
235 QAction *tmpaction = new QAction(parent);
236 tmpaction->setSeparator(true);
237 return ami->registerAction(tmpaction, id, globalcontext);
240 bool CVSPlugin::initialize(const QStringList & /*arguments */, QString *errorMessage)
242 typedef VCSBase::VCSSubmitEditorFactory<CVSSubmitEditor> CVSSubmitEditorFactory;
243 typedef VCSBase::VCSEditorFactory<CVSEditor> CVSEditorFactory;
244 using namespace Constants;
246 using namespace Core::Constants;
247 using namespace ExtensionSystem;
249 VCSBase::VCSBasePlugin::initialize(new CVSControl(this));
251 m_cvsPluginInstance = this;
252 Core::ICore *core = Core::ICore::instance();
254 if (!core->mimeDatabase()->addMimeTypes(QLatin1String(":/trolltech.cvs/CVS.mimetypes.xml"), errorMessage))
257 if (QSettings *settings = core->settings())
258 m_settings.fromSettings(settings);
260 addAutoReleasedObject(new SettingsPage);
262 addAutoReleasedObject(new CVSSubmitEditorFactory(&submitParameters));
264 static const char *describeSlotC = SLOT(slotDescribe(QString,QString));
265 const int editorCount = sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters);
266 for (int i = 0; i < editorCount; i++)
267 addAutoReleasedObject(new CVSEditorFactory(editorParameters + i, this, describeSlotC));
269 addAutoReleasedObject(new CheckoutWizard);
271 const QString prefix = QLatin1String("cvs");
272 m_commandLocator = new Locator::CommandLocator(QLatin1String("CVS"), prefix, prefix);
273 addAutoReleasedObject(m_commandLocator);
276 Core::ActionManager *ami = core->actionManager();
277 Core::ActionContainer *toolsContainer = ami->actionContainer(M_TOOLS);
279 Core::ActionContainer *cvsMenu = ami->createMenu(Core::Id(CMD_ID_CVS_MENU));
280 cvsMenu->menu()->setTitle(tr("&CVS"));
281 toolsContainer->addMenu(cvsMenu);
282 m_menuAction = cvsMenu->menu()->menuAction();
284 Core::Context globalcontext(C_GLOBAL);
286 Core::Command *command;
288 m_diffCurrentAction = new Utils::ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
289 command = ami->registerAction(m_diffCurrentAction,
290 CMD_ID_DIFF_CURRENT, globalcontext);
291 command->setAttribute(Core::Command::CA_UpdateText);
292 command->setDefaultKeySequence(QKeySequence(tr("Alt+C,Alt+D")));
293 connect(m_diffCurrentAction, SIGNAL(triggered()), this, SLOT(diffCurrentFile()));
294 cvsMenu->addAction(command);
295 m_commandLocator->appendCommand(command);
297 m_filelogCurrentAction = new Utils::ParameterAction(tr("Filelog Current File"), tr("Filelog \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
298 command = ami->registerAction(m_filelogCurrentAction,
299 CMD_ID_FILELOG_CURRENT, globalcontext);
300 command->setAttribute(Core::Command::CA_UpdateText);
301 connect(m_filelogCurrentAction, SIGNAL(triggered()), this,
302 SLOT(filelogCurrentFile()));
303 cvsMenu->addAction(command);
304 m_commandLocator->appendCommand(command);
306 m_annotateCurrentAction = new Utils::ParameterAction(tr("Annotate Current File"), tr("Annotate \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
307 command = ami->registerAction(m_annotateCurrentAction,
308 CMD_ID_ANNOTATE_CURRENT, globalcontext);
309 command->setAttribute(Core::Command::CA_UpdateText);
310 connect(m_annotateCurrentAction, SIGNAL(triggered()), this,
311 SLOT(annotateCurrentFile()));
312 cvsMenu->addAction(command);
313 m_commandLocator->appendCommand(command);
315 cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR0, globalcontext));
317 m_addAction = new Utils::ParameterAction(tr("Add"), tr("Add \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
318 command = ami->registerAction(m_addAction, CMD_ID_ADD,
320 command->setAttribute(Core::Command::CA_UpdateText);
321 command->setDefaultKeySequence(QKeySequence(tr("Alt+C,Alt+A")));
322 connect(m_addAction, SIGNAL(triggered()), this, SLOT(addCurrentFile()));
323 cvsMenu->addAction(command);
324 m_commandLocator->appendCommand(command);
326 m_commitCurrentAction = new Utils::ParameterAction(tr("Commit Current File"), tr("Commit \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
327 command = ami->registerAction(m_commitCurrentAction,
328 CMD_ID_COMMIT_CURRENT, globalcontext);
329 command->setAttribute(Core::Command::CA_UpdateText);
330 command->setDefaultKeySequence(QKeySequence(tr("Alt+C,Alt+C")));
331 connect(m_commitCurrentAction, SIGNAL(triggered()), this, SLOT(startCommitCurrentFile()));
332 cvsMenu->addAction(command);
333 m_commandLocator->appendCommand(command);
335 m_deleteAction = new Utils::ParameterAction(tr("Delete..."), tr("Delete \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this);
336 command = ami->registerAction(m_deleteAction, CMD_ID_DELETE_FILE,
338 command->setAttribute(Core::Command::CA_UpdateText);
339 connect(m_deleteAction, SIGNAL(triggered()), this, SLOT(promptToDeleteCurrentFile()));
340 cvsMenu->addAction(command);
341 m_commandLocator->appendCommand(command);
343 m_revertAction = new Utils::ParameterAction(tr("Revert..."), tr("Revert \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this);
344 command = ami->registerAction(m_revertAction, CMD_ID_REVERT,
346 command->setAttribute(Core::Command::CA_UpdateText);
347 connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revertCurrentFile()));
348 cvsMenu->addAction(command);
349 m_commandLocator->appendCommand(command);
351 cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR1, globalcontext));
353 m_editCurrentAction = new Utils::ParameterAction(tr("Edit"), tr("Edit \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
354 command = ami->registerAction(m_editCurrentAction, CMD_ID_EDIT_FILE, globalcontext);
355 command->setAttribute(Core::Command::CA_UpdateText);
356 connect(m_editCurrentAction, SIGNAL(triggered()), this, SLOT(editCurrentFile()));
357 cvsMenu->addAction(command);
358 m_commandLocator->appendCommand(command);
360 m_uneditCurrentAction = new Utils::ParameterAction(tr("Unedit"), tr("Unedit \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
361 command = ami->registerAction(m_uneditCurrentAction, CMD_ID_UNEDIT_FILE, globalcontext);
362 command->setAttribute(Core::Command::CA_UpdateText);
363 connect(m_uneditCurrentAction, SIGNAL(triggered()), this, SLOT(uneditCurrentFile()));
364 cvsMenu->addAction(command);
365 m_commandLocator->appendCommand(command);
367 m_uneditRepositoryAction = new QAction(tr("Unedit Repository"), this);
368 command = ami->registerAction(m_uneditRepositoryAction, CMD_ID_UNEDIT_REPOSITORY, globalcontext);
369 connect(m_uneditRepositoryAction, SIGNAL(triggered()), this, SLOT(uneditCurrentRepository()));
370 cvsMenu->addAction(command);
371 m_commandLocator->appendCommand(command);
373 cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR2, globalcontext));
375 m_diffProjectAction = new Utils::ParameterAction(tr("Diff Project"), tr("Diff Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
376 command = ami->registerAction(m_diffProjectAction, CMD_ID_DIFF_PROJECT,
378 command->setAttribute(Core::Command::CA_UpdateText);
379 connect(m_diffProjectAction, SIGNAL(triggered()), this, SLOT(diffProject()));
380 cvsMenu->addAction(command);
381 m_commandLocator->appendCommand(command);
383 m_statusProjectAction = new Utils::ParameterAction(tr("Project Status"), tr("Status of Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
384 command = ami->registerAction(m_statusProjectAction, CMD_ID_STATUS,
386 command->setAttribute(Core::Command::CA_UpdateText);
387 connect(m_statusProjectAction, SIGNAL(triggered()), this, SLOT(projectStatus()));
388 cvsMenu->addAction(command);
389 m_commandLocator->appendCommand(command);
391 m_logProjectAction = new Utils::ParameterAction(tr("Log Project"), tr("Log Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
392 command = ami->registerAction(m_logProjectAction, CMD_ID_PROJECTLOG, globalcontext);
393 command->setAttribute(Core::Command::CA_UpdateText);
394 connect(m_logProjectAction, SIGNAL(triggered()), this, SLOT(logProject()));
395 cvsMenu->addAction(command);
396 m_commandLocator->appendCommand(command);
398 m_updateProjectAction = new Utils::ParameterAction(tr("Update Project"), tr("Update Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
399 command = ami->registerAction(m_updateProjectAction, CMD_ID_UPDATE, globalcontext);
400 command->setAttribute(Core::Command::CA_UpdateText);
401 connect(m_updateProjectAction, SIGNAL(triggered()), this, SLOT(updateProject()));
402 cvsMenu->addAction(command);
403 m_commandLocator->appendCommand(command);
405 m_commitProjectAction = new Utils::ParameterAction(tr("Commit Project"), tr("Commit Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
406 command = ami->registerAction(m_commitProjectAction, CMD_ID_PROJECTCOMMIT, globalcontext);
407 command->setAttribute(Core::Command::CA_UpdateText);
408 connect(m_commitProjectAction, SIGNAL(triggered()), this, SLOT(commitProject()));
409 cvsMenu->addAction(command);
410 m_commandLocator->appendCommand(command);
412 cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR3, globalcontext));
414 m_diffRepositoryAction = new QAction(tr("Diff Repository"), this);
415 command = ami->registerAction(m_diffRepositoryAction, CMD_ID_REPOSITORYDIFF, globalcontext);
416 connect(m_diffRepositoryAction, SIGNAL(triggered()), this, SLOT(diffRepository()));
417 cvsMenu->addAction(command);
418 m_commandLocator->appendCommand(command);
420 m_statusRepositoryAction = new QAction(tr("Repository Status"), this);
421 command = ami->registerAction(m_statusRepositoryAction, CMD_ID_REPOSITORYSTATUS, globalcontext);
422 connect(m_statusRepositoryAction, SIGNAL(triggered()), this, SLOT(statusRepository()));
423 cvsMenu->addAction(command);
424 m_commandLocator->appendCommand(command);
426 m_logRepositoryAction = new QAction(tr("Repository Log"), this);
427 command = ami->registerAction(m_logRepositoryAction, CMD_ID_REPOSITORYLOG, globalcontext);
428 connect(m_logRepositoryAction, SIGNAL(triggered()), this, SLOT(logRepository()));
429 cvsMenu->addAction(command);
430 m_commandLocator->appendCommand(command);
432 m_updateRepositoryAction = new QAction(tr("Update Repository"), this);
433 command = ami->registerAction(m_updateRepositoryAction, CMD_ID_REPOSITORYUPDATE, globalcontext);
434 connect(m_updateRepositoryAction, SIGNAL(triggered()), this, SLOT(updateRepository()));
435 cvsMenu->addAction(command);
436 m_commandLocator->appendCommand(command);
438 m_commitAllAction = new QAction(tr("Commit All Files"), this);
439 command = ami->registerAction(m_commitAllAction, CMD_ID_COMMIT_ALL,
441 connect(m_commitAllAction, SIGNAL(triggered()), this, SLOT(startCommitAll()));
442 cvsMenu->addAction(command);
443 m_commandLocator->appendCommand(command);
445 m_revertRepositoryAction = new QAction(tr("Revert Repository..."), this);
446 command = ami->registerAction(m_revertRepositoryAction, CMD_ID_REVERT_ALL,
448 connect(m_revertRepositoryAction, SIGNAL(triggered()), this, SLOT(revertAll()));
449 cvsMenu->addAction(command);
450 m_commandLocator->appendCommand(command);
452 // Actions of the submit editor
453 Core::Context cvscommitcontext(Constants::CVSCOMMITEDITOR);
455 m_submitCurrentLogAction = new QAction(VCSBase::VCSBaseSubmitEditor::submitIcon(), tr("Commit"), this);
456 command = ami->registerAction(m_submitCurrentLogAction, Constants::SUBMIT_CURRENT, cvscommitcontext);
457 command->setAttribute(Core::Command::CA_UpdateText);
458 connect(m_submitCurrentLogAction, SIGNAL(triggered()), this, SLOT(submitCurrentLog()));
460 m_submitDiffAction = new QAction(VCSBase::VCSBaseSubmitEditor::diffIcon(), tr("Diff Selected Files"), this);
461 command = ami->registerAction(m_submitDiffAction , Constants::DIFF_SELECTED, cvscommitcontext);
463 m_submitUndoAction = new QAction(tr("&Undo"), this);
464 command = ami->registerAction(m_submitUndoAction, Core::Constants::UNDO, cvscommitcontext);
466 m_submitRedoAction = new QAction(tr("&Redo"), this);
467 command = ami->registerAction(m_submitRedoAction, Core::Constants::REDO, cvscommitcontext);
471 bool CVSPlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor)
473 if (!isCommitEditorOpen())
476 Core::IFile *fileIFace = submitEditor->file();
477 const CVSSubmitEditor *editor = qobject_cast<CVSSubmitEditor *>(submitEditor);
478 if (!fileIFace || !editor)
481 // Submit editor closing. Make it write out the commit message
482 // and retrieve files
483 const QFileInfo editorFile(fileIFace->fileName());
484 const QFileInfo changeFile(m_commitMessageFileName);
485 if (editorFile.absoluteFilePath() != changeFile.absoluteFilePath())
486 return true; // Oops?!
488 // Prompt user. Force a prompt unless submit was actually invoked (that
489 // is, the editor was closed or shutdown).
490 CVSSettings newSettings = m_settings;
491 const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer =
492 editor->promptSubmit(tr("Closing CVS Editor"),
493 tr("Do you want to commit the change?"),
494 tr("The commit message check failed. Do you want to commit the change?"),
495 &newSettings.promptToSubmit, !m_submitActionTriggered);
496 m_submitActionTriggered = false;
498 case VCSBase::VCSBaseSubmitEditor::SubmitCanceled:
499 return false; // Keep editing and change file
500 case VCSBase::VCSBaseSubmitEditor::SubmitDiscarded:
501 cleanCommitMessageFile();
502 return true; // Cancel all
506 setSettings(newSettings); // in case someone turned prompting off
507 const QStringList fileList = editor->checkedFiles();
508 bool closeEditor = true;
509 if (!fileList.empty()) {
510 // get message & commit
511 Core::ICore::instance()->fileManager()->blockFileChange(fileIFace);
513 Core::ICore::instance()->fileManager()->unblockFileChange(fileIFace);
514 closeEditor= commit(m_commitMessageFileName, fileList);
517 cleanCommitMessageFile();
521 void CVSPlugin::diffCommitFiles(const QStringList &files)
523 cvsDiff(m_commitRepository, files);
526 static inline void setDiffBaseDirectory(Core::IEditor *editor, const QString &db)
528 if (VCSBase::VCSBaseEditor *ve = qobject_cast<VCSBase::VCSBaseEditor*>(editor->widget()))
529 ve->setDiffBaseDirectory(db);
532 void CVSPlugin::cvsDiff(const QString &workingDir, const QStringList &files)
534 if (CVS::Constants::debug)
535 qDebug() << Q_FUNC_INFO << files;
536 const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files);
537 QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
538 const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files);
540 QStringList args(QLatin1String("diff"));
541 args << m_settings.cvsDiffOptions;
544 // CVS returns the diff exit code (1 if files differ), which is
545 // undistinguishable from a "file not found" error, unfortunately.
546 const CVSResponse response =
547 runCVS(workingDir, args, m_settings.timeOutMS(), 0, codec);
548 switch (response.result) {
549 case CVSResponse::NonNullExitCode:
550 case CVSResponse::Ok:
552 case CVSResponse::OtherError:
556 QString output = fixDiffOutput(response.stdOut);
557 if (output.isEmpty())
558 output = tr("The files do not differ.");
559 // diff of a single file? re-use an existing view if possible to support
560 // the common usage pattern of continuously changing and diffing a file
561 if (files.count() == 1) {
562 // Show in the same editor if diff has been executed before
563 if (Core::IEditor *editor = locateEditor("originalFileName", id)) {
564 editor->createNew(output);
565 Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
566 setDiffBaseDirectory(editor, workingDir);
570 const QString title = QString::fromLatin1("cvs diff %1").arg(id);
571 Core::IEditor *editor = showOutputInEditor(title, output, VCSBase::DiffOutput, source, codec);
572 if (files.count() == 1)
573 editor->setProperty("originalFileName", id);
574 setDiffBaseDirectory(editor, workingDir);
577 CVSSubmitEditor *CVSPlugin::openCVSSubmitEditor(const QString &fileName)
579 Core::IEditor *editor = Core::EditorManager::instance()->openEditor(fileName, QLatin1String(Constants::CVSCOMMITEDITOR_ID),
580 Core::EditorManager::ModeSwitch);
581 CVSSubmitEditor *submitEditor = qobject_cast<CVSSubmitEditor*>(editor);
582 QTC_ASSERT(submitEditor, /**/);
583 submitEditor->registerActions(m_submitUndoAction, m_submitRedoAction, m_submitCurrentLogAction, m_submitDiffAction);
584 connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(diffCommitFiles(QStringList)));
589 void CVSPlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
591 if (!enableMenuAction(as, m_menuAction)) {
592 m_commandLocator->setEnabled(false);
596 const bool hasTopLevel = currentState().hasTopLevel();
597 m_commandLocator->setEnabled(hasTopLevel);
599 const QString currentFileName = currentState().currentFileName();
600 m_addAction->setParameter(currentFileName);
601 m_deleteAction->setParameter(currentFileName);
602 m_revertAction->setParameter(currentFileName);
603 m_diffCurrentAction->setParameter(currentFileName);
604 m_commitCurrentAction->setParameter(currentFileName);
605 m_filelogCurrentAction->setParameter(currentFileName);
606 m_annotateCurrentAction->setParameter(currentFileName);
607 m_editCurrentAction->setParameter(currentFileName);
608 m_uneditCurrentAction->setParameter(currentFileName);
610 const QString currentProjectName = currentState().currentProjectName();
611 m_diffProjectAction->setParameter(currentProjectName);
612 m_statusProjectAction->setParameter(currentProjectName);
613 m_updateProjectAction->setParameter(currentProjectName);
614 m_logProjectAction->setParameter(currentProjectName);
615 m_commitProjectAction->setParameter(currentProjectName);
617 m_diffRepositoryAction->setEnabled(hasTopLevel);
618 m_statusRepositoryAction->setEnabled(hasTopLevel);
619 m_updateRepositoryAction->setEnabled(hasTopLevel);
620 m_commitAllAction->setEnabled(hasTopLevel);
621 m_logRepositoryAction->setEnabled(hasTopLevel);
622 m_uneditRepositoryAction->setEnabled(hasTopLevel);
625 void CVSPlugin::addCurrentFile()
627 const VCSBase::VCSBasePluginState state = currentState();
628 QTC_ASSERT(state.hasFile(), return)
629 vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
632 void CVSPlugin::revertAll()
634 const VCSBase::VCSBasePluginState state = currentState();
635 QTC_ASSERT(state.hasTopLevel(), return)
636 const QString title = tr("Revert repository");
637 if (!messageBoxQuestion(title, tr("Revert all pending changes to the repository?")))
640 args << QLatin1String("update") << QLatin1String("-C") << state.topLevel();
641 const CVSResponse revertResponse =
642 runCVS(state.topLevel(), args, m_settings.timeOutMS(),
643 SshPasswordPrompt|ShowStdOutInLogWindow);
644 if (revertResponse.result == CVSResponse::Ok) {
645 cvsVersionControl()->emitRepositoryChanged(state.topLevel());
647 QMessageBox::warning(0, title, tr("Revert failed: %1").arg(revertResponse.message), QMessageBox::Ok);
651 void CVSPlugin::revertCurrentFile()
653 const VCSBase::VCSBasePluginState state = currentState();
654 QTC_ASSERT(state.hasFile(), return)
656 args << QLatin1String("diff") << state.relativeCurrentFile();
657 const CVSResponse diffResponse =
658 runCVS(state.currentFileTopLevel(), args, m_settings.timeOutMS(), 0);
659 switch (diffResponse.result) {
660 case CVSResponse::Ok:
661 return; // Not modified, diff exit code 0
662 case CVSResponse::NonNullExitCode: // Diff exit code != 0
663 if (diffResponse.stdOut.isEmpty()) // Paranoia: Something else failed?
666 case CVSResponse::OtherError:
670 if (!messageBoxQuestion(QLatin1String("CVS Revert"),
671 tr("The file has been changed. Do you want to revert it?")))
674 Core::FileChangeBlocker fcb(state.currentFile());
678 args << QLatin1String("update") << QLatin1String("-C") << state.relativeCurrentFile();
679 const CVSResponse revertResponse =
680 runCVS(state.currentFileTopLevel(), args, m_settings.timeOutMS(),
681 SshPasswordPrompt|ShowStdOutInLogWindow);
682 if (revertResponse.result == CVSResponse::Ok) {
683 cvsVersionControl()->emitFilesChanged(QStringList(state.currentFile()));
687 void CVSPlugin::diffProject()
689 const VCSBase::VCSBasePluginState state = currentState();
690 QTC_ASSERT(state.hasProject(), return)
691 cvsDiff(state.currentProjectTopLevel(), state.relativeCurrentProject());
694 void CVSPlugin::diffCurrentFile()
696 const VCSBase::VCSBasePluginState state = currentState();
697 QTC_ASSERT(state.hasFile(), return)
698 cvsDiff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
701 void CVSPlugin::startCommitCurrentFile()
703 const VCSBase::VCSBasePluginState state = currentState();
704 QTC_ASSERT(state.hasFile(), return)
705 startCommit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
708 void CVSPlugin::startCommitAll()
710 const VCSBase::VCSBasePluginState state = currentState();
711 QTC_ASSERT(state.hasTopLevel(), return)
712 startCommit(state.topLevel());
715 /* Start commit of files of a single repository by displaying
716 * template and files in a submit editor. On closing, the real
717 * commit will start. */
718 void CVSPlugin::startCommit(const QString &workingDir, const QStringList &files)
720 if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor())
722 if (isCommitEditorOpen()) {
723 VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Another commit is currently being executed."));
727 // We need the "Examining <subdir>" stderr output to tell
728 // where we are, so, have stdout/stderr channels merged.
729 QStringList args = QStringList(QLatin1String("status"));
730 const CVSResponse response =
731 runCVS(workingDir, args, m_settings.timeOutMS(), MergeOutputChannels);
732 if (response.result != CVSResponse::Ok)
734 // Get list of added/modified/deleted files and purge out undesired ones
735 // (do not run status with relative arguments as it will omit the directories)
736 StateList statusOutput = parseStatusOutput(QString(), response.stdOut);
737 if (!files.isEmpty()) {
738 for (StateList::iterator it = statusOutput.begin(); it != statusOutput.end() ; ) {
739 if (files.contains(it->second)) {
742 it = statusOutput.erase(it);
746 if (statusOutput.empty()) {
747 VCSBase::VCSBaseOutputWindow::instance()->append(tr("There are no modified files."));
750 m_commitRepository = workingDir;
752 // Create a new submit change file containing the submit template
753 QTemporaryFile changeTmpFile;
754 changeTmpFile.setAutoRemove(false);
755 if (!changeTmpFile.open()) {
756 VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot create temporary file: %1").arg(changeTmpFile.errorString()));
759 // TODO: Retrieve submit template from
760 const QString submitTemplate;
761 m_commitMessageFileName = changeTmpFile.fileName();
763 changeTmpFile.write(submitTemplate.toUtf8());
764 changeTmpFile.flush();
765 changeTmpFile.close();
766 // Create a submit editor and set file list
767 CVSSubmitEditor *editor = openCVSSubmitEditor(m_commitMessageFileName);
768 editor->setCheckScriptWorkingDirectory(m_commitRepository);
769 editor->setStateList(statusOutput);
772 bool CVSPlugin::commit(const QString &messageFile,
773 const QStringList &fileList)
775 if (CVS::Constants::debug)
776 qDebug() << Q_FUNC_INFO << messageFile << fileList;
777 QStringList args = QStringList(QLatin1String("commit"));
778 args << QLatin1String("-F") << messageFile;
779 args.append(fileList);
780 const CVSResponse response =
781 runCVS(m_commitRepository, args, m_settings.longTimeOutMS(),
782 SshPasswordPrompt|ShowStdOutInLogWindow);
783 return response.result == CVSResponse::Ok ;
786 void CVSPlugin::filelogCurrentFile()
788 const VCSBase::VCSBasePluginState state = currentState();
789 QTC_ASSERT(state.hasFile(), return)
790 filelog(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), true);
793 void CVSPlugin::logProject()
795 const VCSBase::VCSBasePluginState state = currentState();
796 QTC_ASSERT(state.hasProject(), return)
797 filelog(state.currentProjectTopLevel(), state.relativeCurrentProject());
800 void CVSPlugin::logRepository()
802 const VCSBase::VCSBasePluginState state = currentState();
803 QTC_ASSERT(state.hasTopLevel(), return)
804 filelog(state.topLevel());
807 void CVSPlugin::filelog(const QString &workingDir,
808 const QStringList &files,
809 bool enableAnnotationContextMenu)
811 QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
812 // no need for temp file
813 const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files);
814 const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files);
816 args << QLatin1String("log");
818 const CVSResponse response =
819 runCVS(workingDir, args, m_settings.timeOutMS(),
820 SshPasswordPrompt, codec);
821 if (response.result != CVSResponse::Ok)
824 // Re-use an existing view if possible to support
825 // the common usage pattern of continuously changing and diffing a file
826 if (Core::IEditor *editor = locateEditor("logFileName", id)) {
827 editor->createNew(response.stdOut);
828 Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
830 const QString title = QString::fromLatin1("cvs log %1").arg(id);
831 Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::LogOutput, source, codec);
832 newEditor->setProperty("logFileName", id);
833 if (enableAnnotationContextMenu)
834 VCSBase::VCSBaseEditor::getVcsBaseEditor(newEditor)->setFileLogAnnotateEnabled(true);
838 void CVSPlugin::updateProject()
840 const VCSBase::VCSBasePluginState state = currentState();
841 QTC_ASSERT(state.hasProject(), return)
842 update(state.currentProjectTopLevel(), state.relativeCurrentProject());
845 bool CVSPlugin::update(const QString &topLevel, const QStringList &files)
847 QStringList args(QLatin1String("update"));
848 args.push_back(QLatin1String("-dR"));
850 const CVSResponse response =
851 runCVS(topLevel, args, m_settings.longTimeOutMS(),
852 SshPasswordPrompt|ShowStdOutInLogWindow);
853 const bool ok = response.result == CVSResponse::Ok;
855 cvsVersionControl()->emitRepositoryChanged(topLevel);
859 void CVSPlugin::editCurrentFile()
861 const VCSBase::VCSBasePluginState state = currentState();
862 QTC_ASSERT(state.hasFile(), return)
863 edit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
866 void CVSPlugin::uneditCurrentFile()
868 const VCSBase::VCSBasePluginState state = currentState();
869 QTC_ASSERT(state.hasFile(), return)
870 unedit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
873 void CVSPlugin::uneditCurrentRepository()
875 const VCSBase::VCSBasePluginState state = currentState();
876 QTC_ASSERT(state.hasTopLevel(), return)
877 unedit(state.topLevel(), QStringList());
880 void CVSPlugin::annotateCurrentFile()
882 const VCSBase::VCSBasePluginState state = currentState();
883 QTC_ASSERT(state.hasFile(), return)
884 annotate(state.currentFileTopLevel(), state.relativeCurrentFile());
887 void CVSPlugin::vcsAnnotate(const QString &file, const QString &revision, int lineNumber)
889 const QFileInfo fi(file);
890 annotate(fi.absolutePath(), fi.fileName(), revision, lineNumber);
893 bool CVSPlugin::edit(const QString &topLevel, const QStringList &files)
895 QStringList args(QLatin1String("edit"));
897 const CVSResponse response =
898 runCVS(topLevel, args, m_settings.timeOutMS(),
899 ShowStdOutInLogWindow|SshPasswordPrompt);
900 return response.result == CVSResponse::Ok;
903 bool CVSPlugin::diffCheckModified(const QString &topLevel, const QStringList &files, bool *modified)
905 // Quick check for modified files using diff
907 QStringList args(QLatin1String("-q"));
908 args << QLatin1String("diff");
910 const CVSResponse response = runCVS(topLevel, args, m_settings.timeOutMS(), 0);
911 if (response.result == CVSResponse::OtherError)
913 *modified = response.result == CVSResponse::NonNullExitCode;
917 bool CVSPlugin::unedit(const QString &topLevel, const QStringList &files)
920 // Prompt and use force flag if modified
921 if (!diffCheckModified(topLevel, files, &modified))
924 const QString question = files.isEmpty() ?
925 tr("Would you like to discard your changes to the repository '%1'?").arg(topLevel) :
926 tr("Would you like to discard your changes to the file '%1'?").arg(files.front());
927 if (!messageBoxQuestion(tr("Unedit"), question))
931 QStringList args(QLatin1String("unedit"));
932 // Note: Option '-y' to force 'yes'-answer to CVS' 'undo change' prompt,
933 // exists in CVSNT only as of 6.8.2010. Standard CVS will otherwise prompt
935 args.append(QLatin1String("-y"));
937 const CVSResponse response =
938 runCVS(topLevel, args, m_settings.timeOutMS(),
939 ShowStdOutInLogWindow|SshPasswordPrompt);
940 return response.result == CVSResponse::Ok;
943 void CVSPlugin::annotate(const QString &workingDir, const QString &file,
944 const QString &revision /* = QString() */,
945 int lineNumber /* = -1 */)
947 const QStringList files(file);
948 QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
949 const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files, revision);
950 const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, file);
952 args << QLatin1String("annotate");
953 if (!revision.isEmpty())
954 args << QLatin1String("-r") << revision;
956 const CVSResponse response =
957 runCVS(workingDir, args, m_settings.timeOutMS(),
958 SshPasswordPrompt, codec);
959 if (response.result != CVSResponse::Ok)
962 // Re-use an existing view if possible to support
963 // the common usage pattern of continuously changing and diffing a file
965 lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(file);
967 if (Core::IEditor *editor = locateEditor("annotateFileName", id)) {
968 editor->createNew(response.stdOut);
969 VCSBase::VCSBaseEditor::gotoLineOfEditor(editor, lineNumber);
970 Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
972 const QString title = QString::fromLatin1("cvs annotate %1").arg(id);
973 Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::AnnotateOutput, source, codec);
974 newEditor->setProperty("annotateFileName", id);
975 VCSBase::VCSBaseEditor::gotoLineOfEditor(newEditor, lineNumber);
979 bool CVSPlugin::status(const QString &topLevel, const QStringList &files, const QString &title)
981 QStringList args(QLatin1String("status"));
983 const CVSResponse response =
984 runCVS(topLevel, args, m_settings.timeOutMS(), 0);
985 const bool ok = response.result == CVSResponse::Ok;
987 showOutputInEditor(title, response.stdOut, VCSBase::RegularCommandOutput, topLevel, 0);
991 void CVSPlugin::projectStatus()
993 const VCSBase::VCSBasePluginState state = currentState();
994 QTC_ASSERT(state.hasProject(), return)
995 status(state.currentProjectTopLevel(), state.relativeCurrentProject(), tr("Project status"));
998 void CVSPlugin::commitProject()
1000 const VCSBase::VCSBasePluginState state = currentState();
1001 QTC_ASSERT(state.hasProject(), return)
1002 startCommit(state.currentProjectTopLevel(), state.relativeCurrentProject());
1005 void CVSPlugin::diffRepository()
1007 const VCSBase::VCSBasePluginState state = currentState();
1008 QTC_ASSERT(state.hasTopLevel(), return)
1009 cvsDiff(state.topLevel(), QStringList());
1012 void CVSPlugin::statusRepository()
1014 const VCSBase::VCSBasePluginState state = currentState();
1015 QTC_ASSERT(state.hasTopLevel(), return)
1016 status(state.topLevel(), QStringList(), tr("Repository status"));
1019 void CVSPlugin::updateRepository()
1021 const VCSBase::VCSBasePluginState state = currentState();
1022 QTC_ASSERT(state.hasTopLevel(), return)
1023 update(state.topLevel(), QStringList());
1027 void CVSPlugin::slotDescribe(const QString &source, const QString &changeNr)
1029 QString errorMessage;
1030 if (!describe(source, changeNr, &errorMessage))
1031 VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
1034 bool CVSPlugin::describe(const QString &file, const QString &changeNr, QString *errorMessage)
1038 const bool manages = managesDirectory(QFileInfo(file).absolutePath(), &toplevel);
1039 if (!manages || toplevel.isEmpty()) {
1040 *errorMessage = msgCannotFindTopLevel(file);
1043 return describe(toplevel, QDir(toplevel).relativeFilePath(file), changeNr, errorMessage);
1046 bool CVSPlugin::describe(const QString &toplevel, const QString &file, const
1047 QString &changeNr, QString *errorMessage)
1050 // In CVS, revisions of files are normally unrelated, there is
1051 // no global revision/change number. The only thing that groups
1052 // a commit is the "commit-id" (as shown in the log).
1053 // This function makes use of it to find all files related to
1054 // a commit in order to emulate a "describe global change" functionality
1056 if (CVS::Constants::debug)
1057 qDebug() << Q_FUNC_INFO << file << changeNr;
1058 // Number must be > 1
1059 if (isFirstRevision(changeNr)) {
1060 *errorMessage = tr("The initial revision %1 cannot be described.").arg(changeNr);
1063 // Run log to obtain commit id and details
1065 args << QLatin1String("log") << (QLatin1String("-r") + changeNr) << file;
1066 const CVSResponse logResponse =
1067 runCVS(toplevel, args, m_settings.timeOutMS(), SshPasswordPrompt);
1068 if (logResponse.result != CVSResponse::Ok) {
1069 *errorMessage = logResponse.message;
1072 const QList<CVS_LogEntry> fileLog = parseLogEntries(logResponse.stdOut);
1073 if (fileLog.empty() || fileLog.front().revisions.empty()) {
1074 *errorMessage = msgLogParsingFailed();
1077 if (m_settings.describeByCommitId) {
1078 // Run a log command over the repo, filtering by the commit date
1079 // and commit id, collecting all files touched by the commit.
1080 const QString commitId = fileLog.front().revisions.front().commitId;
1081 // Date range "D1<D2" in ISO format "YYYY-MM-DD"
1082 const QString dateS = fileLog.front().revisions.front().date;
1083 const QDate date = QDate::fromString(dateS, Qt::ISODate);
1084 const QString nextDayS = date.addDays(1).toString(Qt::ISODate);
1086 args << QLatin1String("log") << QLatin1String("-d") << (dateS + QLatin1Char('<') + nextDayS);
1088 const CVSResponse repoLogResponse =
1089 runCVS(toplevel, args, m_settings.longTimeOutMS(), SshPasswordPrompt);
1090 if (repoLogResponse.result != CVSResponse::Ok) {
1091 *errorMessage = repoLogResponse.message;
1094 // Describe all files found, pass on dir to obtain correct absolute paths.
1095 const QList<CVS_LogEntry> repoEntries = parseLogEntries(repoLogResponse.stdOut, QString(), commitId);
1096 if (repoEntries.empty()) {
1097 *errorMessage = tr("Could not find commits of id '%1' on %2.").arg(commitId, dateS);
1100 return describe(toplevel, repoEntries, errorMessage);
1102 // Just describe that single entry
1103 return describe(toplevel, fileLog, errorMessage);
1108 // Describe a set of files and revisions by
1109 // concatenating log and diffs to previous revisions
1110 bool CVSPlugin::describe(const QString &repositoryPath,
1111 QList<CVS_LogEntry> entries,
1112 QString *errorMessage)
1116 QTextCodec *codec = 0;
1117 const QList<CVS_LogEntry>::iterator lend = entries.end();
1118 for (QList<CVS_LogEntry>::iterator it = entries.begin(); it != lend; ++it) {
1119 // Before fiddling file names, try to find codec
1121 codec = VCSBase::VCSBaseEditor::getCodec(repositoryPath, QStringList(it->file));
1123 QStringList args(QLatin1String("log"));
1124 args << (QLatin1String("-r") + it->revisions.front().revision) << it->file;
1125 const CVSResponse logResponse =
1126 runCVS(repositoryPath, args, m_settings.timeOutMS(), SshPasswordPrompt);
1127 if (logResponse.result != CVSResponse::Ok) {
1128 *errorMessage = logResponse.message;
1131 output += logResponse.stdOut;
1133 // Collect diffs relative to repository
1134 for (QList<CVS_LogEntry>::iterator it = entries.begin(); it != lend; ++it) {
1135 const QString &revision = it->revisions.front().revision;
1136 if (!isFirstRevision(revision)) {
1137 const QString previousRev = previousRevision(revision);
1138 QStringList args(QLatin1String("diff"));
1139 args << m_settings.cvsDiffOptions << QLatin1String("-r") << previousRev
1140 << QLatin1String("-r") << it->revisions.front().revision
1142 const CVSResponse diffResponse =
1143 runCVS(repositoryPath, args, m_settings.timeOutMS(), 0, codec);
1144 switch (diffResponse.result) {
1145 case CVSResponse::Ok:
1146 case CVSResponse::NonNullExitCode: // Diff exit code != 0
1147 if (diffResponse.stdOut.isEmpty()) {
1148 *errorMessage = diffResponse.message;
1149 return false; // Something else failed.
1152 case CVSResponse::OtherError:
1153 *errorMessage = diffResponse.message;
1156 output += fixDiffOutput(diffResponse.stdOut);
1160 // Re-use an existing view if possible to support
1161 // the common usage pattern of continuously changing and diffing a file
1162 const QString commitId = entries.front().revisions.front().commitId;
1163 if (Core::IEditor *editor = locateEditor("describeChange", commitId)) {
1164 editor->createNew(output);
1165 Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
1166 setDiffBaseDirectory(editor, repositoryPath);
1168 const QString title = QString::fromLatin1("cvs describe %1").arg(commitId);
1169 Core::IEditor *newEditor = showOutputInEditor(title, output, VCSBase::DiffOutput, entries.front().file, codec);
1170 newEditor->setProperty("describeChange", commitId);
1171 setDiffBaseDirectory(newEditor, repositoryPath);
1176 void CVSPlugin::submitCurrentLog()
1178 m_submitActionTriggered = true;
1179 Core::EditorManager::instance()->closeEditors(QList<Core::IEditor*>()
1180 << Core::EditorManager::instance()->currentEditor());
1183 // Run CVS. At this point, file arguments must be relative to
1184 // the working directory (see above).
1185 CVSResponse CVSPlugin::runCVS(const QString &workingDirectory,
1186 const QStringList &arguments,
1189 QTextCodec *outputCodec)
1191 const QString executable = m_settings.cvsCommand;
1192 CVSResponse response;
1193 if (executable.isEmpty()) {
1194 response.result = CVSResponse::OtherError;
1195 response.message =tr("No cvs executable specified!");
1198 // Run, connect stderr to the output window
1199 const Utils::SynchronousProcessResponse sp_resp =
1200 runVCS(workingDirectory, executable,
1201 m_settings.addOptions(arguments),
1202 timeOut, flags, outputCodec);
1204 response.result = CVSResponse::OtherError;
1205 response.stdErr = sp_resp.stdErr;
1206 response.stdOut = sp_resp.stdOut;
1207 switch (sp_resp.result) {
1208 case Utils::SynchronousProcessResponse::Finished:
1209 response.result = CVSResponse::Ok;
1211 case Utils::SynchronousProcessResponse::FinishedError:
1212 response.result = CVSResponse::NonNullExitCode;
1214 case Utils::SynchronousProcessResponse::TerminatedAbnormally:
1215 case Utils::SynchronousProcessResponse::StartFailed:
1216 case Utils::SynchronousProcessResponse::Hang:
1220 if (response.result != CVSResponse::Ok)
1221 response.message = sp_resp.exitMessage(executable, timeOut);
1226 Core::IEditor * CVSPlugin::showOutputInEditor(const QString& title, const QString &output,
1227 int editorType, const QString &source,
1230 const VCSBase::VCSBaseEditorParameters *params = findType(editorType);
1231 QTC_ASSERT(params, return 0);
1232 const QString id = params->id;
1233 if (CVS::Constants::debug)
1234 qDebug() << "CVSPlugin::showOutputInEditor" << title << id << "source=" << source << "Size= " << output.size() << " Type=" << editorType << debugCodec(codec);
1236 Core::IEditor *editor = Core::EditorManager::instance()->openEditorWithContents(id, &s, output.toLocal8Bit());
1237 connect(editor, SIGNAL(annotateRevisionRequested(QString,QString,int)),
1238 this, SLOT(vcsAnnotate(QString,QString,int)));
1239 CVSEditor *e = qobject_cast<CVSEditor*>(editor->widget());
1242 s.replace(QLatin1Char(' '), QLatin1Char('_'));
1243 e->setSuggestedFileName(s);
1244 e->setForceReadOnly(true);
1245 if (!source.isEmpty())
1246 e->setSource(source);
1249 Core::IEditor *ie = e->editableInterface();
1250 Core::EditorManager::instance()->activateEditor(ie, Core::EditorManager::ModeSwitch);
1254 CVSSettings CVSPlugin::settings() const
1259 void CVSPlugin::setSettings(const CVSSettings &s)
1261 if (s != m_settings) {
1263 if (QSettings *settings = Core::ICore::instance()->settings())
1264 m_settings.toSettings(settings);
1268 CVSPlugin *CVSPlugin::cvsPluginInstance()
1270 QTC_ASSERT(m_cvsPluginInstance, return m_cvsPluginInstance);
1271 return m_cvsPluginInstance;
1274 bool CVSPlugin::vcsAdd(const QString &workingDir, const QString &rawFileName)
1277 args << QLatin1String("add") << rawFileName;
1278 const CVSResponse response =
1279 runCVS(workingDir, args, m_settings.timeOutMS(),
1280 SshPasswordPrompt|ShowStdOutInLogWindow);
1281 return response.result == CVSResponse::Ok;
1284 bool CVSPlugin::vcsDelete(const QString &workingDir, const QString &rawFileName)
1287 args << QLatin1String("remove") << QLatin1String("-f") << rawFileName;
1288 const CVSResponse response =
1289 runCVS(workingDir, args, m_settings.timeOutMS(),
1290 SshPasswordPrompt|ShowStdOutInLogWindow);
1291 return response.result == CVSResponse::Ok;
1294 /* CVS has a "CVS" directory in each directory it manages. The top level
1295 * is the first directory under the directory that does not have it. */
1296 bool CVSPlugin::managesDirectory(const QString &directory, QString *topLevel /* = 0 */) const
1300 bool manages = false;
1301 const QDir dir(directory);
1303 if (!dir.exists() || !checkCVSDirectory(dir))
1308 /* Recursing up, the top level is a child of the first directory that does
1309 * not have a "CVS" directory. The starting directory must be a managed
1310 * one. Go up and try to find the first unmanaged parent dir. */
1311 QDir lastDirectory = dir;
1312 for (QDir parentDir = lastDirectory; parentDir.cdUp() ; lastDirectory = parentDir) {
1313 if (!checkCVSDirectory(parentDir)) {
1314 *topLevel = lastDirectory.absolutePath();
1319 if (CVS::Constants::debug) {
1320 QDebug nsp = qDebug().nospace();
1321 nsp << "CVSPlugin::managesDirectory" << directory << manages;
1328 bool CVSPlugin::checkCVSDirectory(const QDir &directory) const
1330 const QString cvsDir = directory.absoluteFilePath(QLatin1String("CVS"));
1331 return QFileInfo(cvsDir).isDir();
1334 CVSControl *CVSPlugin::cvsVersionControl() const
1336 return static_cast<CVSControl *>(versionControl());
1341 Q_EXPORT_PLUGIN(CVS::Internal::CVSPlugin)