1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
31 **************************************************************************/
33 #include "cvsplugin.h"
34 #include "settingspage.h"
35 #include "cvseditor.h"
36 #include "cvssubmiteditor.h"
37 #include "cvsconstants.h"
38 #include "cvscontrol.h"
39 #include "checkoutwizard.h"
41 #include <vcsbase/basevcseditorfactory.h>
42 #include <vcsbase/vcsbaseeditor.h>
43 #include <vcsbase/basevcssubmiteditorfactory.h>
44 #include <vcsbase/vcsbaseoutputwindow.h>
45 #include <locator/commandlocator.h>
46 #include <utils/synchronousprocess.h>
47 #include <utils/parameteraction.h>
49 #include <coreplugin/icore.h>
50 #include <coreplugin/coreconstants.h>
51 #include <coreplugin/filemanager.h>
52 #include <coreplugin/messagemanager.h>
53 #include <coreplugin/mimedatabase.h>
54 #include <coreplugin/actionmanager/actionmanager.h>
55 #include <coreplugin/actionmanager/actioncontainer.h>
56 #include <coreplugin/actionmanager/command.h>
57 #include <coreplugin/uniqueidmanager.h>
58 #include <coreplugin/editormanager/editormanager.h>
59 #include <coreplugin/vcsmanager.h>
60 #include <utils/stringutils.h>
61 #include <utils/qtcassert.h>
63 #include <QtCore/QDebug>
64 #include <QtCore/QDate>
65 #include <QtCore/QDir>
66 #include <QtCore/QFileInfo>
67 #include <QtCore/QTextCodec>
68 #include <QtCore/QtPlugin>
69 #include <QtCore/QTemporaryFile>
70 #include <QtGui/QAction>
71 #include <QtGui/QMainWindow>
72 #include <QtGui/QMenu>
73 #include <QtGui/QMessageBox>
78 static inline QString msgCannotFindTopLevel(const QString &f)
80 return CVSPlugin::tr("Cannot find repository for '%1'").
81 arg(QDir::toNativeSeparators(f));
84 static inline QString msgLogParsingFailed()
86 return CVSPlugin::tr("Parsing of the log output failed");
89 static const char * const CMD_ID_CVS_MENU = "CVS.Menu";
90 static const char * const CMD_ID_ADD = "CVS.Add";
91 static const char * const CMD_ID_DELETE_FILE = "CVS.Delete";
92 static const char * const CMD_ID_EDIT_FILE = "CVS.EditFile";
93 static const char * const CMD_ID_UNEDIT_FILE = "CVS.UneditFile";
94 static const char * const CMD_ID_UNEDIT_REPOSITORY = "CVS.UneditRepository";
95 static const char * const CMD_ID_REVERT = "CVS.Revert";
96 static const char * const CMD_ID_SEPARATOR0 = "CVS.Separator0";
97 static const char * const CMD_ID_DIFF_PROJECT = "CVS.DiffAll";
98 static const char * const CMD_ID_DIFF_CURRENT = "CVS.DiffCurrent";
99 static const char * const CMD_ID_SEPARATOR1 = "CVS.Separator1";
100 static const char * const CMD_ID_COMMIT_ALL = "CVS.CommitAll";
101 static const char * const CMD_ID_REVERT_ALL = "CVS.RevertAll";
102 static const char * const CMD_ID_COMMIT_CURRENT = "CVS.CommitCurrent";
103 static const char * const CMD_ID_SEPARATOR2 = "CVS.Separator2";
104 static const char * const CMD_ID_FILELOG_CURRENT = "CVS.FilelogCurrent";
105 static const char * const CMD_ID_ANNOTATE_CURRENT = "CVS.AnnotateCurrent";
106 static const char * const CMD_ID_STATUS = "CVS.Status";
107 static const char * const CMD_ID_UPDATE = "CVS.Update";
108 static const char * const CMD_ID_PROJECTLOG = "CVS.ProjectLog";
109 static const char * const CMD_ID_PROJECTCOMMIT = "CVS.ProjectCommit";
110 static const char * const CMD_ID_REPOSITORYLOG = "CVS.RepositoryLog";
111 static const char * const CMD_ID_REPOSITORYDIFF = "CVS.RepositoryDiff";
112 static const char * const CMD_ID_REPOSITORYSTATUS = "CVS.RepositoryStatus";
113 static const char * const CMD_ID_REPOSITORYUPDATE = "CVS.RepositoryUpdate";
114 static const char * const CMD_ID_SEPARATOR3 = "CVS.Separator3";
116 static const VCSBase::VCSBaseEditorParameters editorParameters[] = {
118 VCSBase::RegularCommandOutput,
119 "CVS Command Log Editor", // id
120 QT_TRANSLATE_NOOP("VCS", "CVS Command Log Editor"), // display name
121 "CVS Command Log Editor", // context
122 "application/vnd.nokia.text.scs_cvs_commandlog",
124 { VCSBase::LogOutput,
125 "CVS File Log Editor", // id
126 QT_TRANSLATE_NOOP("VCS", "CVS File Log Editor"), // display name
127 "CVS File Log Editor", // context
128 "application/vnd.nokia.text.scs_cvs_filelog",
130 { VCSBase::AnnotateOutput,
131 "CVS Annotation Editor", // id
132 QT_TRANSLATE_NOOP("VCS", "CVS Annotation Editor"), // display name
133 "CVS Annotation Editor", // context
134 "application/vnd.nokia.text.scs_cvs_annotation",
136 { VCSBase::DiffOutput,
137 "CVS Diff Editor", // id
138 QT_TRANSLATE_NOOP("VCS", "CVS Diff Editor"), // display name
139 "CVS Diff Editor", // context
140 "text/x-patch","diff"}
143 // Utility to find a parameter set by type
144 static inline const VCSBase::VCSBaseEditorParameters *findType(int ie)
146 const VCSBase::EditorContentType et = static_cast<VCSBase::EditorContentType>(ie);
147 return VCSBase::VCSBaseEditorWidget::findType(editorParameters, sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters), et);
150 static inline QString debugCodec(const QTextCodec *c)
152 return c ? QString::fromAscii(c->name()) : QString::fromAscii("Null codec");
155 Core::IEditor* locateEditor(const char *property, const QString &entry)
157 foreach (Core::IEditor *ed, Core::EditorManager::instance()->openedEditors())
158 if (ed->property(property).toString() == entry)
163 static inline bool messageBoxQuestion(const QString &title, const QString &question, QWidget *parent = 0)
165 return QMessageBox::question(parent, title, question, QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes;
168 // ------------- CVSPlugin
169 CVSPlugin *CVSPlugin::m_cvsPluginInstance = 0;
171 CVSPlugin::CVSPlugin() :
172 VCSBase::VCSBasePlugin(QLatin1String(CVS::Constants::CVSCOMMITEDITOR_ID)),
177 m_editCurrentAction(0),
178 m_uneditCurrentAction(0),
179 m_uneditRepositoryAction(0),
180 m_diffProjectAction(0),
181 m_diffCurrentAction(0),
182 m_logProjectAction(0),
183 m_logRepositoryAction(0),
184 m_commitAllAction(0),
185 m_revertRepositoryAction(0),
186 m_commitCurrentAction(0),
187 m_filelogCurrentAction(0),
188 m_annotateCurrentAction(0),
189 m_statusProjectAction(0),
190 m_updateProjectAction(0),
191 m_commitProjectAction(0),
192 m_diffRepositoryAction(0),
193 m_updateRepositoryAction(0),
194 m_statusRepositoryAction(0),
195 m_submitCurrentLogAction(0),
196 m_submitDiffAction(0),
197 m_submitUndoAction(0),
198 m_submitRedoAction(0),
200 m_submitActionTriggered(false)
204 CVSPlugin::~CVSPlugin()
206 cleanCommitMessageFile();
209 void CVSPlugin::cleanCommitMessageFile()
211 if (!m_commitMessageFileName.isEmpty()) {
212 QFile::remove(m_commitMessageFileName);
213 m_commitMessageFileName.clear();
214 m_commitRepository.clear();
217 bool CVSPlugin::isCommitEditorOpen() const
219 return !m_commitMessageFileName.isEmpty();
222 static const VCSBase::VCSBaseSubmitEditorParameters submitParameters = {
223 CVS::Constants::CVS_SUBMIT_MIMETYPE,
224 CVS::Constants::CVSCOMMITEDITOR_ID,
225 CVS::Constants::CVSCOMMITEDITOR_DISPLAY_NAME,
226 CVS::Constants::CVSCOMMITEDITOR
229 static inline Core::Command *createSeparator(QObject *parent,
230 Core::ActionManager *ami,
232 const Core::Context &globalcontext)
234 QAction *tmpaction = new QAction(parent);
235 tmpaction->setSeparator(true);
236 return ami->registerAction(tmpaction, id, globalcontext);
239 bool CVSPlugin::initialize(const QStringList & /*arguments */, QString *errorMessage)
241 typedef VCSBase::VCSSubmitEditorFactory<CVSSubmitEditor> CVSSubmitEditorFactory;
242 typedef VCSBase::VCSEditorFactory<CVSEditor> CVSEditorFactory;
243 using namespace Constants;
245 using namespace Core::Constants;
246 using namespace ExtensionSystem;
248 VCSBase::VCSBasePlugin::initialize(new CVSControl(this));
250 m_cvsPluginInstance = this;
251 Core::ICore *core = Core::ICore::instance();
253 if (!core->mimeDatabase()->addMimeTypes(QLatin1String(":/trolltech.cvs/CVS.mimetypes.xml"), errorMessage))
256 if (QSettings *settings = core->settings())
257 m_settings.fromSettings(settings);
259 addAutoReleasedObject(new SettingsPage);
261 addAutoReleasedObject(new CVSSubmitEditorFactory(&submitParameters));
263 static const char *describeSlotC = SLOT(slotDescribe(QString,QString));
264 const int editorCount = sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters);
265 for (int i = 0; i < editorCount; i++)
266 addAutoReleasedObject(new CVSEditorFactory(editorParameters + i, this, describeSlotC));
268 addAutoReleasedObject(new CheckoutWizard);
270 const QString prefix = QLatin1String("cvs");
271 m_commandLocator = new Locator::CommandLocator(QLatin1String("CVS"), prefix, prefix);
272 addAutoReleasedObject(m_commandLocator);
275 Core::ActionManager *ami = core->actionManager();
276 Core::ActionContainer *toolsContainer = ami->actionContainer(M_TOOLS);
278 Core::ActionContainer *cvsMenu = ami->createMenu(Core::Id(CMD_ID_CVS_MENU));
279 cvsMenu->menu()->setTitle(tr("&CVS"));
280 toolsContainer->addMenu(cvsMenu);
281 m_menuAction = cvsMenu->menu()->menuAction();
283 Core::Context globalcontext(C_GLOBAL);
285 Core::Command *command;
287 m_diffCurrentAction = new Utils::ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
288 command = ami->registerAction(m_diffCurrentAction,
289 CMD_ID_DIFF_CURRENT, globalcontext);
290 command->setAttribute(Core::Command::CA_UpdateText);
291 command->setDefaultKeySequence(QKeySequence(tr("Alt+C,Alt+D")));
292 connect(m_diffCurrentAction, SIGNAL(triggered()), this, SLOT(diffCurrentFile()));
293 cvsMenu->addAction(command);
294 m_commandLocator->appendCommand(command);
296 m_filelogCurrentAction = new Utils::ParameterAction(tr("Filelog Current File"), tr("Filelog \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
297 command = ami->registerAction(m_filelogCurrentAction,
298 CMD_ID_FILELOG_CURRENT, globalcontext);
299 command->setAttribute(Core::Command::CA_UpdateText);
300 connect(m_filelogCurrentAction, SIGNAL(triggered()), this,
301 SLOT(filelogCurrentFile()));
302 cvsMenu->addAction(command);
303 m_commandLocator->appendCommand(command);
305 m_annotateCurrentAction = new Utils::ParameterAction(tr("Annotate Current File"), tr("Annotate \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
306 command = ami->registerAction(m_annotateCurrentAction,
307 CMD_ID_ANNOTATE_CURRENT, globalcontext);
308 command->setAttribute(Core::Command::CA_UpdateText);
309 connect(m_annotateCurrentAction, SIGNAL(triggered()), this,
310 SLOT(annotateCurrentFile()));
311 cvsMenu->addAction(command);
312 m_commandLocator->appendCommand(command);
314 cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR0, globalcontext));
316 m_addAction = new Utils::ParameterAction(tr("Add"), tr("Add \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
317 command = ami->registerAction(m_addAction, CMD_ID_ADD,
319 command->setAttribute(Core::Command::CA_UpdateText);
320 command->setDefaultKeySequence(QKeySequence(tr("Alt+C,Alt+A")));
321 connect(m_addAction, SIGNAL(triggered()), this, SLOT(addCurrentFile()));
322 cvsMenu->addAction(command);
323 m_commandLocator->appendCommand(command);
325 m_commitCurrentAction = new Utils::ParameterAction(tr("Commit Current File"), tr("Commit \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
326 command = ami->registerAction(m_commitCurrentAction,
327 CMD_ID_COMMIT_CURRENT, globalcontext);
328 command->setAttribute(Core::Command::CA_UpdateText);
329 command->setDefaultKeySequence(QKeySequence(tr("Alt+C,Alt+C")));
330 connect(m_commitCurrentAction, SIGNAL(triggered()), this, SLOT(startCommitCurrentFile()));
331 cvsMenu->addAction(command);
332 m_commandLocator->appendCommand(command);
334 m_deleteAction = new Utils::ParameterAction(tr("Delete..."), tr("Delete \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this);
335 command = ami->registerAction(m_deleteAction, CMD_ID_DELETE_FILE,
337 command->setAttribute(Core::Command::CA_UpdateText);
338 connect(m_deleteAction, SIGNAL(triggered()), this, SLOT(promptToDeleteCurrentFile()));
339 cvsMenu->addAction(command);
340 m_commandLocator->appendCommand(command);
342 m_revertAction = new Utils::ParameterAction(tr("Revert..."), tr("Revert \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this);
343 command = ami->registerAction(m_revertAction, CMD_ID_REVERT,
345 command->setAttribute(Core::Command::CA_UpdateText);
346 connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revertCurrentFile()));
347 cvsMenu->addAction(command);
348 m_commandLocator->appendCommand(command);
350 cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR1, globalcontext));
352 m_editCurrentAction = new Utils::ParameterAction(tr("Edit"), tr("Edit \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
353 command = ami->registerAction(m_editCurrentAction, CMD_ID_EDIT_FILE, globalcontext);
354 command->setAttribute(Core::Command::CA_UpdateText);
355 connect(m_editCurrentAction, SIGNAL(triggered()), this, SLOT(editCurrentFile()));
356 cvsMenu->addAction(command);
357 m_commandLocator->appendCommand(command);
359 m_uneditCurrentAction = new Utils::ParameterAction(tr("Unedit"), tr("Unedit \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
360 command = ami->registerAction(m_uneditCurrentAction, CMD_ID_UNEDIT_FILE, globalcontext);
361 command->setAttribute(Core::Command::CA_UpdateText);
362 connect(m_uneditCurrentAction, SIGNAL(triggered()), this, SLOT(uneditCurrentFile()));
363 cvsMenu->addAction(command);
364 m_commandLocator->appendCommand(command);
366 m_uneditRepositoryAction = new QAction(tr("Unedit Repository"), this);
367 command = ami->registerAction(m_uneditRepositoryAction, CMD_ID_UNEDIT_REPOSITORY, globalcontext);
368 connect(m_uneditRepositoryAction, SIGNAL(triggered()), this, SLOT(uneditCurrentRepository()));
369 cvsMenu->addAction(command);
370 m_commandLocator->appendCommand(command);
372 cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR2, globalcontext));
374 m_diffProjectAction = new Utils::ParameterAction(tr("Diff Project"), tr("Diff Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
375 command = ami->registerAction(m_diffProjectAction, CMD_ID_DIFF_PROJECT,
377 command->setAttribute(Core::Command::CA_UpdateText);
378 connect(m_diffProjectAction, SIGNAL(triggered()), this, SLOT(diffProject()));
379 cvsMenu->addAction(command);
380 m_commandLocator->appendCommand(command);
382 m_statusProjectAction = new Utils::ParameterAction(tr("Project Status"), tr("Status of Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
383 command = ami->registerAction(m_statusProjectAction, CMD_ID_STATUS,
385 command->setAttribute(Core::Command::CA_UpdateText);
386 connect(m_statusProjectAction, SIGNAL(triggered()), this, SLOT(projectStatus()));
387 cvsMenu->addAction(command);
388 m_commandLocator->appendCommand(command);
390 m_logProjectAction = new Utils::ParameterAction(tr("Log Project"), tr("Log Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
391 command = ami->registerAction(m_logProjectAction, CMD_ID_PROJECTLOG, globalcontext);
392 command->setAttribute(Core::Command::CA_UpdateText);
393 connect(m_logProjectAction, SIGNAL(triggered()), this, SLOT(logProject()));
394 cvsMenu->addAction(command);
395 m_commandLocator->appendCommand(command);
397 m_updateProjectAction = new Utils::ParameterAction(tr("Update Project"), tr("Update Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
398 command = ami->registerAction(m_updateProjectAction, CMD_ID_UPDATE, globalcontext);
399 command->setAttribute(Core::Command::CA_UpdateText);
400 connect(m_updateProjectAction, SIGNAL(triggered()), this, SLOT(updateProject()));
401 cvsMenu->addAction(command);
402 m_commandLocator->appendCommand(command);
404 m_commitProjectAction = new Utils::ParameterAction(tr("Commit Project"), tr("Commit Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
405 command = ami->registerAction(m_commitProjectAction, CMD_ID_PROJECTCOMMIT, globalcontext);
406 command->setAttribute(Core::Command::CA_UpdateText);
407 connect(m_commitProjectAction, SIGNAL(triggered()), this, SLOT(commitProject()));
408 cvsMenu->addAction(command);
409 m_commandLocator->appendCommand(command);
411 cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR3, globalcontext));
413 m_diffRepositoryAction = new QAction(tr("Diff Repository"), this);
414 command = ami->registerAction(m_diffRepositoryAction, CMD_ID_REPOSITORYDIFF, globalcontext);
415 connect(m_diffRepositoryAction, SIGNAL(triggered()), this, SLOT(diffRepository()));
416 cvsMenu->addAction(command);
417 m_commandLocator->appendCommand(command);
419 m_statusRepositoryAction = new QAction(tr("Repository Status"), this);
420 command = ami->registerAction(m_statusRepositoryAction, CMD_ID_REPOSITORYSTATUS, globalcontext);
421 connect(m_statusRepositoryAction, SIGNAL(triggered()), this, SLOT(statusRepository()));
422 cvsMenu->addAction(command);
423 m_commandLocator->appendCommand(command);
425 m_logRepositoryAction = new QAction(tr("Repository Log"), this);
426 command = ami->registerAction(m_logRepositoryAction, CMD_ID_REPOSITORYLOG, globalcontext);
427 connect(m_logRepositoryAction, SIGNAL(triggered()), this, SLOT(logRepository()));
428 cvsMenu->addAction(command);
429 m_commandLocator->appendCommand(command);
431 m_updateRepositoryAction = new QAction(tr("Update Repository"), this);
432 command = ami->registerAction(m_updateRepositoryAction, CMD_ID_REPOSITORYUPDATE, globalcontext);
433 connect(m_updateRepositoryAction, SIGNAL(triggered()), this, SLOT(updateRepository()));
434 cvsMenu->addAction(command);
435 m_commandLocator->appendCommand(command);
437 m_commitAllAction = new QAction(tr("Commit All Files"), this);
438 command = ami->registerAction(m_commitAllAction, CMD_ID_COMMIT_ALL,
440 connect(m_commitAllAction, SIGNAL(triggered()), this, SLOT(startCommitAll()));
441 cvsMenu->addAction(command);
442 m_commandLocator->appendCommand(command);
444 m_revertRepositoryAction = new QAction(tr("Revert Repository..."), this);
445 command = ami->registerAction(m_revertRepositoryAction, CMD_ID_REVERT_ALL,
447 connect(m_revertRepositoryAction, SIGNAL(triggered()), this, SLOT(revertAll()));
448 cvsMenu->addAction(command);
449 m_commandLocator->appendCommand(command);
451 // Actions of the submit editor
452 Core::Context cvscommitcontext(Constants::CVSCOMMITEDITOR);
454 m_submitCurrentLogAction = new QAction(VCSBase::VCSBaseSubmitEditor::submitIcon(), tr("Commit"), this);
455 command = ami->registerAction(m_submitCurrentLogAction, Constants::SUBMIT_CURRENT, cvscommitcontext);
456 command->setAttribute(Core::Command::CA_UpdateText);
457 connect(m_submitCurrentLogAction, SIGNAL(triggered()), this, SLOT(submitCurrentLog()));
459 m_submitDiffAction = new QAction(VCSBase::VCSBaseSubmitEditor::diffIcon(), tr("Diff Selected Files"), this);
460 command = ami->registerAction(m_submitDiffAction , Constants::DIFF_SELECTED, cvscommitcontext);
462 m_submitUndoAction = new QAction(tr("&Undo"), this);
463 command = ami->registerAction(m_submitUndoAction, Core::Constants::UNDO, cvscommitcontext);
465 m_submitRedoAction = new QAction(tr("&Redo"), this);
466 command = ami->registerAction(m_submitRedoAction, Core::Constants::REDO, cvscommitcontext);
470 bool CVSPlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor)
472 if (!isCommitEditorOpen())
475 Core::IFile *fileIFace = submitEditor->file();
476 const CVSSubmitEditor *editor = qobject_cast<CVSSubmitEditor *>(submitEditor);
477 if (!fileIFace || !editor)
480 // Submit editor closing. Make it write out the commit message
481 // and retrieve files
482 const QFileInfo editorFile(fileIFace->fileName());
483 const QFileInfo changeFile(m_commitMessageFileName);
484 if (editorFile.absoluteFilePath() != changeFile.absoluteFilePath())
485 return true; // Oops?!
487 // Prompt user. Force a prompt unless submit was actually invoked (that
488 // is, the editor was closed or shutdown).
489 CVSSettings newSettings = m_settings;
490 const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer =
491 editor->promptSubmit(tr("Closing CVS Editor"),
492 tr("Do you want to commit the change?"),
493 tr("The commit message check failed. Do you want to commit the change?"),
494 &newSettings.promptToSubmit, !m_submitActionTriggered);
495 m_submitActionTriggered = false;
497 case VCSBase::VCSBaseSubmitEditor::SubmitCanceled:
498 return false; // Keep editing and change file
499 case VCSBase::VCSBaseSubmitEditor::SubmitDiscarded:
500 cleanCommitMessageFile();
501 return true; // Cancel all
505 setSettings(newSettings); // in case someone turned prompting off
506 const QStringList fileList = editor->checkedFiles();
507 bool closeEditor = true;
508 if (!fileList.empty()) {
509 // get message & commit
510 Core::ICore::instance()->fileManager()->blockFileChange(fileIFace);
512 Core::ICore::instance()->fileManager()->unblockFileChange(fileIFace);
513 closeEditor= commit(m_commitMessageFileName, fileList);
516 cleanCommitMessageFile();
520 void CVSPlugin::diffCommitFiles(const QStringList &files)
522 cvsDiff(m_commitRepository, files);
525 static inline void setDiffBaseDirectory(Core::IEditor *editor, const QString &db)
527 if (VCSBase::VCSBaseEditorWidget *ve = qobject_cast<VCSBase::VCSBaseEditorWidget*>(editor->widget()))
528 ve->setDiffBaseDirectory(db);
531 void CVSPlugin::cvsDiff(const QString &workingDir, const QStringList &files)
533 if (CVS::Constants::debug)
534 qDebug() << Q_FUNC_INFO << files;
535 const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, files);
536 QTextCodec *codec = VCSBase::VCSBaseEditorWidget::getCodec(workingDir, files);
537 const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDir, files);
539 QStringList args(QLatin1String("diff"));
540 args << m_settings.cvsDiffOptions;
543 // CVS returns the diff exit code (1 if files differ), which is
544 // undistinguishable from a "file not found" error, unfortunately.
545 const CVSResponse response =
546 runCVS(workingDir, args, m_settings.timeOutMS(), 0, codec);
547 switch (response.result) {
548 case CVSResponse::NonNullExitCode:
549 case CVSResponse::Ok:
551 case CVSResponse::OtherError:
555 QString output = fixDiffOutput(response.stdOut);
556 if (output.isEmpty())
557 output = tr("The files do not differ.");
558 // diff of a single file? re-use an existing view if possible to support
559 // the common usage pattern of continuously changing and diffing a file
560 if (files.count() == 1) {
561 // Show in the same editor if diff has been executed before
562 if (Core::IEditor *editor = locateEditor("originalFileName", id)) {
563 editor->createNew(output);
564 Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
565 setDiffBaseDirectory(editor, workingDir);
569 const QString title = QString::fromLatin1("cvs diff %1").arg(id);
570 Core::IEditor *editor = showOutputInEditor(title, output, VCSBase::DiffOutput, source, codec);
571 if (files.count() == 1)
572 editor->setProperty("originalFileName", id);
573 setDiffBaseDirectory(editor, workingDir);
576 CVSSubmitEditor *CVSPlugin::openCVSSubmitEditor(const QString &fileName)
578 Core::IEditor *editor = Core::EditorManager::instance()->openEditor(fileName, QLatin1String(Constants::CVSCOMMITEDITOR_ID),
579 Core::EditorManager::ModeSwitch);
580 CVSSubmitEditor *submitEditor = qobject_cast<CVSSubmitEditor*>(editor);
581 QTC_ASSERT(submitEditor, /**/);
582 submitEditor->registerActions(m_submitUndoAction, m_submitRedoAction, m_submitCurrentLogAction, m_submitDiffAction);
583 connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(diffCommitFiles(QStringList)));
588 void CVSPlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
590 if (!enableMenuAction(as, m_menuAction)) {
591 m_commandLocator->setEnabled(false);
595 const bool hasTopLevel = currentState().hasTopLevel();
596 m_commandLocator->setEnabled(hasTopLevel);
598 const QString currentFileName = currentState().currentFileName();
599 m_addAction->setParameter(currentFileName);
600 m_deleteAction->setParameter(currentFileName);
601 m_revertAction->setParameter(currentFileName);
602 m_diffCurrentAction->setParameter(currentFileName);
603 m_commitCurrentAction->setParameter(currentFileName);
604 m_filelogCurrentAction->setParameter(currentFileName);
605 m_annotateCurrentAction->setParameter(currentFileName);
606 m_editCurrentAction->setParameter(currentFileName);
607 m_uneditCurrentAction->setParameter(currentFileName);
609 const QString currentProjectName = currentState().currentProjectName();
610 m_diffProjectAction->setParameter(currentProjectName);
611 m_statusProjectAction->setParameter(currentProjectName);
612 m_updateProjectAction->setParameter(currentProjectName);
613 m_logProjectAction->setParameter(currentProjectName);
614 m_commitProjectAction->setParameter(currentProjectName);
616 m_diffRepositoryAction->setEnabled(hasTopLevel);
617 m_statusRepositoryAction->setEnabled(hasTopLevel);
618 m_updateRepositoryAction->setEnabled(hasTopLevel);
619 m_commitAllAction->setEnabled(hasTopLevel);
620 m_logRepositoryAction->setEnabled(hasTopLevel);
621 m_uneditRepositoryAction->setEnabled(hasTopLevel);
624 void CVSPlugin::addCurrentFile()
626 const VCSBase::VCSBasePluginState state = currentState();
627 QTC_ASSERT(state.hasFile(), return)
628 vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
631 void CVSPlugin::revertAll()
633 const VCSBase::VCSBasePluginState state = currentState();
634 QTC_ASSERT(state.hasTopLevel(), return)
635 const QString title = tr("Revert repository");
636 if (!messageBoxQuestion(title, tr("Revert all pending changes to the repository?")))
639 args << QLatin1String("update") << QLatin1String("-C") << state.topLevel();
640 const CVSResponse revertResponse =
641 runCVS(state.topLevel(), args, m_settings.timeOutMS(),
642 SshPasswordPrompt|ShowStdOutInLogWindow);
643 if (revertResponse.result == CVSResponse::Ok) {
644 cvsVersionControl()->emitRepositoryChanged(state.topLevel());
646 QMessageBox::warning(0, title, tr("Revert failed: %1").arg(revertResponse.message), QMessageBox::Ok);
650 void CVSPlugin::revertCurrentFile()
652 const VCSBase::VCSBasePluginState state = currentState();
653 QTC_ASSERT(state.hasFile(), return)
655 args << QLatin1String("diff") << state.relativeCurrentFile();
656 const CVSResponse diffResponse =
657 runCVS(state.currentFileTopLevel(), args, m_settings.timeOutMS(), 0);
658 switch (diffResponse.result) {
659 case CVSResponse::Ok:
660 return; // Not modified, diff exit code 0
661 case CVSResponse::NonNullExitCode: // Diff exit code != 0
662 if (diffResponse.stdOut.isEmpty()) // Paranoia: Something else failed?
665 case CVSResponse::OtherError:
669 if (!messageBoxQuestion(QLatin1String("CVS Revert"),
670 tr("The file has been changed. Do you want to revert it?")))
673 Core::FileChangeBlocker fcb(state.currentFile());
677 args << QLatin1String("update") << QLatin1String("-C") << state.relativeCurrentFile();
678 const CVSResponse revertResponse =
679 runCVS(state.currentFileTopLevel(), args, m_settings.timeOutMS(),
680 SshPasswordPrompt|ShowStdOutInLogWindow);
681 if (revertResponse.result == CVSResponse::Ok) {
682 cvsVersionControl()->emitFilesChanged(QStringList(state.currentFile()));
686 void CVSPlugin::diffProject()
688 const VCSBase::VCSBasePluginState state = currentState();
689 QTC_ASSERT(state.hasProject(), return)
690 cvsDiff(state.currentProjectTopLevel(), state.relativeCurrentProject());
693 void CVSPlugin::diffCurrentFile()
695 const VCSBase::VCSBasePluginState state = currentState();
696 QTC_ASSERT(state.hasFile(), return)
697 cvsDiff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
700 void CVSPlugin::startCommitCurrentFile()
702 const VCSBase::VCSBasePluginState state = currentState();
703 QTC_ASSERT(state.hasFile(), return)
704 startCommit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
707 void CVSPlugin::startCommitAll()
709 const VCSBase::VCSBasePluginState state = currentState();
710 QTC_ASSERT(state.hasTopLevel(), return)
711 startCommit(state.topLevel());
714 /* Start commit of files of a single repository by displaying
715 * template and files in a submit editor. On closing, the real
716 * commit will start. */
717 void CVSPlugin::startCommit(const QString &workingDir, const QStringList &files)
719 if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor())
721 if (isCommitEditorOpen()) {
722 VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Another commit is currently being executed."));
726 // We need the "Examining <subdir>" stderr output to tell
727 // where we are, so, have stdout/stderr channels merged.
728 QStringList args = QStringList(QLatin1String("status"));
729 const CVSResponse response =
730 runCVS(workingDir, args, m_settings.timeOutMS(), MergeOutputChannels);
731 if (response.result != CVSResponse::Ok)
733 // Get list of added/modified/deleted files and purge out undesired ones
734 // (do not run status with relative arguments as it will omit the directories)
735 StateList statusOutput = parseStatusOutput(QString(), response.stdOut);
736 if (!files.isEmpty()) {
737 for (StateList::iterator it = statusOutput.begin(); it != statusOutput.end() ; ) {
738 if (files.contains(it->second)) {
741 it = statusOutput.erase(it);
745 if (statusOutput.empty()) {
746 VCSBase::VCSBaseOutputWindow::instance()->append(tr("There are no modified files."));
749 m_commitRepository = workingDir;
751 // Create a new submit change file containing the submit template
752 QTemporaryFile changeTmpFile;
753 changeTmpFile.setAutoRemove(false);
754 if (!changeTmpFile.open()) {
755 VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot create temporary file: %1").arg(changeTmpFile.errorString()));
758 // TODO: Retrieve submit template from
759 const QString submitTemplate;
760 m_commitMessageFileName = changeTmpFile.fileName();
762 changeTmpFile.write(submitTemplate.toUtf8());
763 changeTmpFile.flush();
764 changeTmpFile.close();
765 // Create a submit editor and set file list
766 CVSSubmitEditor *editor = openCVSSubmitEditor(m_commitMessageFileName);
767 editor->setCheckScriptWorkingDirectory(m_commitRepository);
768 editor->setStateList(statusOutput);
771 bool CVSPlugin::commit(const QString &messageFile,
772 const QStringList &fileList)
774 if (CVS::Constants::debug)
775 qDebug() << Q_FUNC_INFO << messageFile << fileList;
776 QStringList args = QStringList(QLatin1String("commit"));
777 args << QLatin1String("-F") << messageFile;
778 args.append(fileList);
779 const CVSResponse response =
780 runCVS(m_commitRepository, args, m_settings.longTimeOutMS(),
781 SshPasswordPrompt|ShowStdOutInLogWindow);
782 return response.result == CVSResponse::Ok ;
785 void CVSPlugin::filelogCurrentFile()
787 const VCSBase::VCSBasePluginState state = currentState();
788 QTC_ASSERT(state.hasFile(), return)
789 filelog(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), true);
792 void CVSPlugin::logProject()
794 const VCSBase::VCSBasePluginState state = currentState();
795 QTC_ASSERT(state.hasProject(), return)
796 filelog(state.currentProjectTopLevel(), state.relativeCurrentProject());
799 void CVSPlugin::logRepository()
801 const VCSBase::VCSBasePluginState state = currentState();
802 QTC_ASSERT(state.hasTopLevel(), return)
803 filelog(state.topLevel());
806 void CVSPlugin::filelog(const QString &workingDir,
807 const QStringList &files,
808 bool enableAnnotationContextMenu)
810 QTextCodec *codec = VCSBase::VCSBaseEditorWidget::getCodec(workingDir, files);
811 // no need for temp file
812 const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDir, files);
813 const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, files);
815 args << QLatin1String("log");
817 const CVSResponse response =
818 runCVS(workingDir, args, m_settings.timeOutMS(),
819 SshPasswordPrompt, codec);
820 if (response.result != CVSResponse::Ok)
823 // Re-use an existing view if possible to support
824 // the common usage pattern of continuously changing and diffing a file
825 if (Core::IEditor *editor = locateEditor("logFileName", id)) {
826 editor->createNew(response.stdOut);
827 Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
829 const QString title = QString::fromLatin1("cvs log %1").arg(id);
830 Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::LogOutput, source, codec);
831 newEditor->setProperty("logFileName", id);
832 if (enableAnnotationContextMenu)
833 VCSBase::VCSBaseEditorWidget::getVcsBaseEditor(newEditor)->setFileLogAnnotateEnabled(true);
837 void CVSPlugin::updateProject()
839 const VCSBase::VCSBasePluginState state = currentState();
840 QTC_ASSERT(state.hasProject(), return)
841 update(state.currentProjectTopLevel(), state.relativeCurrentProject());
844 bool CVSPlugin::update(const QString &topLevel, const QStringList &files)
846 QStringList args(QLatin1String("update"));
847 args.push_back(QLatin1String("-dR"));
849 const CVSResponse response =
850 runCVS(topLevel, args, m_settings.longTimeOutMS(),
851 SshPasswordPrompt|ShowStdOutInLogWindow);
852 const bool ok = response.result == CVSResponse::Ok;
854 cvsVersionControl()->emitRepositoryChanged(topLevel);
858 void CVSPlugin::editCurrentFile()
860 const VCSBase::VCSBasePluginState state = currentState();
861 QTC_ASSERT(state.hasFile(), return)
862 edit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
865 void CVSPlugin::uneditCurrentFile()
867 const VCSBase::VCSBasePluginState state = currentState();
868 QTC_ASSERT(state.hasFile(), return)
869 unedit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
872 void CVSPlugin::uneditCurrentRepository()
874 const VCSBase::VCSBasePluginState state = currentState();
875 QTC_ASSERT(state.hasTopLevel(), return)
876 unedit(state.topLevel(), QStringList());
879 void CVSPlugin::annotateCurrentFile()
881 const VCSBase::VCSBasePluginState state = currentState();
882 QTC_ASSERT(state.hasFile(), return)
883 annotate(state.currentFileTopLevel(), state.relativeCurrentFile());
886 void CVSPlugin::vcsAnnotate(const QString &file, const QString &revision, int lineNumber)
888 const QFileInfo fi(file);
889 annotate(fi.absolutePath(), fi.fileName(), revision, lineNumber);
892 bool CVSPlugin::edit(const QString &topLevel, const QStringList &files)
894 QStringList args(QLatin1String("edit"));
896 const CVSResponse response =
897 runCVS(topLevel, args, m_settings.timeOutMS(),
898 ShowStdOutInLogWindow|SshPasswordPrompt);
899 return response.result == CVSResponse::Ok;
902 bool CVSPlugin::diffCheckModified(const QString &topLevel, const QStringList &files, bool *modified)
904 // Quick check for modified files using diff
906 QStringList args(QLatin1String("-q"));
907 args << QLatin1String("diff");
909 const CVSResponse response = runCVS(topLevel, args, m_settings.timeOutMS(), 0);
910 if (response.result == CVSResponse::OtherError)
912 *modified = response.result == CVSResponse::NonNullExitCode;
916 bool CVSPlugin::unedit(const QString &topLevel, const QStringList &files)
919 // Prompt and use force flag if modified
920 if (!diffCheckModified(topLevel, files, &modified))
923 const QString question = files.isEmpty() ?
924 tr("Would you like to discard your changes to the repository '%1'?").arg(topLevel) :
925 tr("Would you like to discard your changes to the file '%1'?").arg(files.front());
926 if (!messageBoxQuestion(tr("Unedit"), question))
930 QStringList args(QLatin1String("unedit"));
931 // Note: Option '-y' to force 'yes'-answer to CVS' 'undo change' prompt,
932 // exists in CVSNT only as of 6.8.2010. Standard CVS will otherwise prompt
934 args.append(QLatin1String("-y"));
936 const CVSResponse response =
937 runCVS(topLevel, args, m_settings.timeOutMS(),
938 ShowStdOutInLogWindow|SshPasswordPrompt);
939 return response.result == CVSResponse::Ok;
942 void CVSPlugin::annotate(const QString &workingDir, const QString &file,
943 const QString &revision /* = QString() */,
944 int lineNumber /* = -1 */)
946 const QStringList files(file);
947 QTextCodec *codec = VCSBase::VCSBaseEditorWidget::getCodec(workingDir, files);
948 const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDir, files, revision);
949 const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, file);
951 args << QLatin1String("annotate");
952 if (!revision.isEmpty())
953 args << QLatin1String("-r") << revision;
955 const CVSResponse response =
956 runCVS(workingDir, args, m_settings.timeOutMS(),
957 SshPasswordPrompt, codec);
958 if (response.result != CVSResponse::Ok)
961 // Re-use an existing view if possible to support
962 // the common usage pattern of continuously changing and diffing a file
964 lineNumber = VCSBase::VCSBaseEditorWidget::lineNumberOfCurrentEditor(file);
966 if (Core::IEditor *editor = locateEditor("annotateFileName", id)) {
967 editor->createNew(response.stdOut);
968 VCSBase::VCSBaseEditorWidget::gotoLineOfEditor(editor, lineNumber);
969 Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
971 const QString title = QString::fromLatin1("cvs annotate %1").arg(id);
972 Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::AnnotateOutput, source, codec);
973 newEditor->setProperty("annotateFileName", id);
974 VCSBase::VCSBaseEditorWidget::gotoLineOfEditor(newEditor, lineNumber);
978 bool CVSPlugin::status(const QString &topLevel, const QStringList &files, const QString &title)
980 QStringList args(QLatin1String("status"));
982 const CVSResponse response =
983 runCVS(topLevel, args, m_settings.timeOutMS(), 0);
984 const bool ok = response.result == CVSResponse::Ok;
986 showOutputInEditor(title, response.stdOut, VCSBase::RegularCommandOutput, topLevel, 0);
990 void CVSPlugin::projectStatus()
992 const VCSBase::VCSBasePluginState state = currentState();
993 QTC_ASSERT(state.hasProject(), return)
994 status(state.currentProjectTopLevel(), state.relativeCurrentProject(), tr("Project status"));
997 void CVSPlugin::commitProject()
999 const VCSBase::VCSBasePluginState state = currentState();
1000 QTC_ASSERT(state.hasProject(), return)
1001 startCommit(state.currentProjectTopLevel(), state.relativeCurrentProject());
1004 void CVSPlugin::diffRepository()
1006 const VCSBase::VCSBasePluginState state = currentState();
1007 QTC_ASSERT(state.hasTopLevel(), return)
1008 cvsDiff(state.topLevel(), QStringList());
1011 void CVSPlugin::statusRepository()
1013 const VCSBase::VCSBasePluginState state = currentState();
1014 QTC_ASSERT(state.hasTopLevel(), return)
1015 status(state.topLevel(), QStringList(), tr("Repository status"));
1018 void CVSPlugin::updateRepository()
1020 const VCSBase::VCSBasePluginState state = currentState();
1021 QTC_ASSERT(state.hasTopLevel(), return)
1022 update(state.topLevel(), QStringList());
1026 void CVSPlugin::slotDescribe(const QString &source, const QString &changeNr)
1028 QString errorMessage;
1029 if (!describe(source, changeNr, &errorMessage))
1030 VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
1033 bool CVSPlugin::describe(const QString &file, const QString &changeNr, QString *errorMessage)
1037 const bool manages = managesDirectory(QFileInfo(file).absolutePath(), &toplevel);
1038 if (!manages || toplevel.isEmpty()) {
1039 *errorMessage = msgCannotFindTopLevel(file);
1042 return describe(toplevel, QDir(toplevel).relativeFilePath(file), changeNr, errorMessage);
1045 bool CVSPlugin::describe(const QString &toplevel, const QString &file, const
1046 QString &changeNr, QString *errorMessage)
1049 // In CVS, revisions of files are normally unrelated, there is
1050 // no global revision/change number. The only thing that groups
1051 // a commit is the "commit-id" (as shown in the log).
1052 // This function makes use of it to find all files related to
1053 // a commit in order to emulate a "describe global change" functionality
1055 if (CVS::Constants::debug)
1056 qDebug() << Q_FUNC_INFO << file << changeNr;
1057 // Number must be > 1
1058 if (isFirstRevision(changeNr)) {
1059 *errorMessage = tr("The initial revision %1 cannot be described.").arg(changeNr);
1062 // Run log to obtain commit id and details
1064 args << QLatin1String("log") << (QLatin1String("-r") + changeNr) << file;
1065 const CVSResponse logResponse =
1066 runCVS(toplevel, args, m_settings.timeOutMS(), SshPasswordPrompt);
1067 if (logResponse.result != CVSResponse::Ok) {
1068 *errorMessage = logResponse.message;
1071 const QList<CVS_LogEntry> fileLog = parseLogEntries(logResponse.stdOut);
1072 if (fileLog.empty() || fileLog.front().revisions.empty()) {
1073 *errorMessage = msgLogParsingFailed();
1076 if (m_settings.describeByCommitId) {
1077 // Run a log command over the repo, filtering by the commit date
1078 // and commit id, collecting all files touched by the commit.
1079 const QString commitId = fileLog.front().revisions.front().commitId;
1080 // Date range "D1<D2" in ISO format "YYYY-MM-DD"
1081 const QString dateS = fileLog.front().revisions.front().date;
1082 const QDate date = QDate::fromString(dateS, Qt::ISODate);
1083 const QString nextDayS = date.addDays(1).toString(Qt::ISODate);
1085 args << QLatin1String("log") << QLatin1String("-d") << (dateS + QLatin1Char('<') + nextDayS);
1087 const CVSResponse repoLogResponse =
1088 runCVS(toplevel, args, m_settings.longTimeOutMS(), SshPasswordPrompt);
1089 if (repoLogResponse.result != CVSResponse::Ok) {
1090 *errorMessage = repoLogResponse.message;
1093 // Describe all files found, pass on dir to obtain correct absolute paths.
1094 const QList<CVS_LogEntry> repoEntries = parseLogEntries(repoLogResponse.stdOut, QString(), commitId);
1095 if (repoEntries.empty()) {
1096 *errorMessage = tr("Could not find commits of id '%1' on %2.").arg(commitId, dateS);
1099 return describe(toplevel, repoEntries, errorMessage);
1101 // Just describe that single entry
1102 return describe(toplevel, fileLog, errorMessage);
1107 // Describe a set of files and revisions by
1108 // concatenating log and diffs to previous revisions
1109 bool CVSPlugin::describe(const QString &repositoryPath,
1110 QList<CVS_LogEntry> entries,
1111 QString *errorMessage)
1115 QTextCodec *codec = 0;
1116 const QList<CVS_LogEntry>::iterator lend = entries.end();
1117 for (QList<CVS_LogEntry>::iterator it = entries.begin(); it != lend; ++it) {
1118 // Before fiddling file names, try to find codec
1120 codec = VCSBase::VCSBaseEditorWidget::getCodec(repositoryPath, QStringList(it->file));
1122 QStringList args(QLatin1String("log"));
1123 args << (QLatin1String("-r") + it->revisions.front().revision) << it->file;
1124 const CVSResponse logResponse =
1125 runCVS(repositoryPath, args, m_settings.timeOutMS(), SshPasswordPrompt);
1126 if (logResponse.result != CVSResponse::Ok) {
1127 *errorMessage = logResponse.message;
1130 output += logResponse.stdOut;
1132 // Collect diffs relative to repository
1133 for (QList<CVS_LogEntry>::iterator it = entries.begin(); it != lend; ++it) {
1134 const QString &revision = it->revisions.front().revision;
1135 if (!isFirstRevision(revision)) {
1136 const QString previousRev = previousRevision(revision);
1137 QStringList args(QLatin1String("diff"));
1138 args << m_settings.cvsDiffOptions << QLatin1String("-r") << previousRev
1139 << QLatin1String("-r") << it->revisions.front().revision
1141 const CVSResponse diffResponse =
1142 runCVS(repositoryPath, args, m_settings.timeOutMS(), 0, codec);
1143 switch (diffResponse.result) {
1144 case CVSResponse::Ok:
1145 case CVSResponse::NonNullExitCode: // Diff exit code != 0
1146 if (diffResponse.stdOut.isEmpty()) {
1147 *errorMessage = diffResponse.message;
1148 return false; // Something else failed.
1151 case CVSResponse::OtherError:
1152 *errorMessage = diffResponse.message;
1155 output += fixDiffOutput(diffResponse.stdOut);
1159 // Re-use an existing view if possible to support
1160 // the common usage pattern of continuously changing and diffing a file
1161 const QString commitId = entries.front().revisions.front().commitId;
1162 if (Core::IEditor *editor = locateEditor("describeChange", commitId)) {
1163 editor->createNew(output);
1164 Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
1165 setDiffBaseDirectory(editor, repositoryPath);
1167 const QString title = QString::fromLatin1("cvs describe %1").arg(commitId);
1168 Core::IEditor *newEditor = showOutputInEditor(title, output, VCSBase::DiffOutput, entries.front().file, codec);
1169 newEditor->setProperty("describeChange", commitId);
1170 setDiffBaseDirectory(newEditor, repositoryPath);
1175 void CVSPlugin::submitCurrentLog()
1177 m_submitActionTriggered = true;
1178 Core::EditorManager::instance()->closeEditors(QList<Core::IEditor*>()
1179 << Core::EditorManager::instance()->currentEditor());
1182 // Run CVS. At this point, file arguments must be relative to
1183 // the working directory (see above).
1184 CVSResponse CVSPlugin::runCVS(const QString &workingDirectory,
1185 const QStringList &arguments,
1188 QTextCodec *outputCodec)
1190 const QString executable = m_settings.cvsCommand;
1191 CVSResponse response;
1192 if (executable.isEmpty()) {
1193 response.result = CVSResponse::OtherError;
1194 response.message =tr("No cvs executable specified!");
1197 // Run, connect stderr to the output window
1198 const Utils::SynchronousProcessResponse sp_resp =
1199 runVCS(workingDirectory, executable,
1200 m_settings.addOptions(arguments),
1201 timeOut, flags, outputCodec);
1203 response.result = CVSResponse::OtherError;
1204 response.stdErr = sp_resp.stdErr;
1205 response.stdOut = sp_resp.stdOut;
1206 switch (sp_resp.result) {
1207 case Utils::SynchronousProcessResponse::Finished:
1208 response.result = CVSResponse::Ok;
1210 case Utils::SynchronousProcessResponse::FinishedError:
1211 response.result = CVSResponse::NonNullExitCode;
1213 case Utils::SynchronousProcessResponse::TerminatedAbnormally:
1214 case Utils::SynchronousProcessResponse::StartFailed:
1215 case Utils::SynchronousProcessResponse::Hang:
1219 if (response.result != CVSResponse::Ok)
1220 response.message = sp_resp.exitMessage(executable, timeOut);
1225 Core::IEditor * CVSPlugin::showOutputInEditor(const QString& title, const QString &output,
1226 int editorType, const QString &source,
1229 const VCSBase::VCSBaseEditorParameters *params = findType(editorType);
1230 QTC_ASSERT(params, return 0);
1231 const QString id = params->id;
1232 if (CVS::Constants::debug)
1233 qDebug() << "CVSPlugin::showOutputInEditor" << title << id << "source=" << source << "Size= " << output.size() << " Type=" << editorType << debugCodec(codec);
1235 Core::IEditor *editor = Core::EditorManager::instance()->openEditorWithContents(id, &s, output.toLocal8Bit());
1236 connect(editor, SIGNAL(annotateRevisionRequested(QString,QString,int)),
1237 this, SLOT(vcsAnnotate(QString,QString,int)));
1238 CVSEditor *e = qobject_cast<CVSEditor*>(editor->widget());
1241 s.replace(QLatin1Char(' '), QLatin1Char('_'));
1242 e->setSuggestedFileName(s);
1243 e->setForceReadOnly(true);
1244 if (!source.isEmpty())
1245 e->setSource(source);
1248 Core::IEditor *ie = e->editor();
1249 Core::EditorManager::instance()->activateEditor(ie, Core::EditorManager::ModeSwitch);
1253 CVSSettings CVSPlugin::settings() const
1258 void CVSPlugin::setSettings(const CVSSettings &s)
1260 if (s != m_settings) {
1262 if (QSettings *settings = Core::ICore::instance()->settings())
1263 m_settings.toSettings(settings);
1267 CVSPlugin *CVSPlugin::cvsPluginInstance()
1269 QTC_ASSERT(m_cvsPluginInstance, return m_cvsPluginInstance);
1270 return m_cvsPluginInstance;
1273 bool CVSPlugin::vcsAdd(const QString &workingDir, const QString &rawFileName)
1276 args << QLatin1String("add") << rawFileName;
1277 const CVSResponse response =
1278 runCVS(workingDir, args, m_settings.timeOutMS(),
1279 SshPasswordPrompt|ShowStdOutInLogWindow);
1280 return response.result == CVSResponse::Ok;
1283 bool CVSPlugin::vcsDelete(const QString &workingDir, const QString &rawFileName)
1286 args << QLatin1String("remove") << QLatin1String("-f") << rawFileName;
1287 const CVSResponse response =
1288 runCVS(workingDir, args, m_settings.timeOutMS(),
1289 SshPasswordPrompt|ShowStdOutInLogWindow);
1290 return response.result == CVSResponse::Ok;
1293 /* CVS has a "CVS" directory in each directory it manages. The top level
1294 * is the first directory under the directory that does not have it. */
1295 bool CVSPlugin::managesDirectory(const QString &directory, QString *topLevel /* = 0 */) const
1299 bool manages = false;
1300 const QDir dir(directory);
1302 if (!dir.exists() || !checkCVSDirectory(dir))
1307 /* Recursing up, the top level is a child of the first directory that does
1308 * not have a "CVS" directory. The starting directory must be a managed
1309 * one. Go up and try to find the first unmanaged parent dir. */
1310 QDir lastDirectory = dir;
1311 for (QDir parentDir = lastDirectory; parentDir.cdUp() ; lastDirectory = parentDir) {
1312 if (!checkCVSDirectory(parentDir)) {
1313 *topLevel = lastDirectory.absolutePath();
1318 if (CVS::Constants::debug) {
1319 QDebug nsp = qDebug().nospace();
1320 nsp << "CVSPlugin::managesDirectory" << directory << manages;
1327 bool CVSPlugin::checkCVSDirectory(const QDir &directory) const
1329 const QString cvsDir = directory.absoluteFilePath(QLatin1String("CVS"));
1330 return QFileInfo(cvsDir).isDir();
1333 CVSControl *CVSPlugin::cvsVersionControl() const
1335 return static_cast<CVSControl *>(versionControl());
1340 Q_EXPORT_PLUGIN(CVS::Internal::CVSPlugin)