OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / cvs / cvsplugin.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
8 **
9 **
10 ** GNU Lesser General Public License Usage
11 **
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.
18 **
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.
22 **
23 ** Other Usage
24 **
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.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **************************************************************************/
32
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"
40
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>
48
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>
62
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>
74
75 namespace CVS {
76     namespace Internal {
77
78 static inline QString msgCannotFindTopLevel(const QString &f)
79 {
80     return CVSPlugin::tr("Cannot find repository for '%1'").
81             arg(QDir::toNativeSeparators(f));
82 }
83
84 static inline QString msgLogParsingFailed()
85 {
86     return CVSPlugin::tr("Parsing of the log output failed");
87 }
88
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";
115
116 static const VCSBase::VCSBaseEditorParameters editorParameters[] = {
117 {
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",
123     "scslog"},
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",
129     "scsfilelog"},
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",
135     "scsannotate"},
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"}
141 };
142
143 // Utility to find a parameter set by type
144 static inline const VCSBase::VCSBaseEditorParameters *findType(int ie)
145 {
146     const VCSBase::EditorContentType et = static_cast<VCSBase::EditorContentType>(ie);
147     return  VCSBase::VCSBaseEditorWidget::findType(editorParameters, sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters), et);
148 }
149
150 static inline QString debugCodec(const QTextCodec *c)
151 {
152     return c ? QString::fromAscii(c->name()) : QString::fromAscii("Null codec");
153 }
154
155 Core::IEditor* locateEditor(const char *property, const QString &entry)
156 {
157     foreach (Core::IEditor *ed, Core::EditorManager::instance()->openedEditors())
158         if (ed->property(property).toString() == entry)
159             return ed;
160     return 0;
161 }
162
163 static inline bool messageBoxQuestion(const QString &title, const QString &question, QWidget *parent = 0)
164 {
165     return QMessageBox::question(parent, title, question, QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes;
166 }
167
168 // ------------- CVSPlugin
169 CVSPlugin *CVSPlugin::m_cvsPluginInstance = 0;
170
171 CVSPlugin::CVSPlugin() :
172     VCSBase::VCSBasePlugin(QLatin1String(CVS::Constants::CVSCOMMITEDITOR_ID)),
173     m_commandLocator(0),
174     m_addAction(0),
175     m_deleteAction(0),
176     m_revertAction(0),
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),
199     m_menuAction(0),
200     m_submitActionTriggered(false)
201 {
202 }
203
204 CVSPlugin::~CVSPlugin()
205 {
206     cleanCommitMessageFile();
207 }
208
209 void CVSPlugin::cleanCommitMessageFile()
210 {
211     if (!m_commitMessageFileName.isEmpty()) {
212         QFile::remove(m_commitMessageFileName);
213         m_commitMessageFileName.clear();
214         m_commitRepository.clear();
215     }
216 }
217 bool CVSPlugin::isCommitEditorOpen() const
218 {
219     return !m_commitMessageFileName.isEmpty();
220 }
221
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
227 };
228
229 static inline Core::Command *createSeparator(QObject *parent,
230                                              Core::ActionManager *ami,
231                                              const char *id,
232                                              const Core::Context &globalcontext)
233 {
234     QAction *tmpaction = new QAction(parent);
235     tmpaction->setSeparator(true);
236     return ami->registerAction(tmpaction, id, globalcontext);
237 }
238
239 bool CVSPlugin::initialize(const QStringList & /*arguments */, QString *errorMessage)
240 {
241     typedef VCSBase::VCSSubmitEditorFactory<CVSSubmitEditor> CVSSubmitEditorFactory;
242     typedef VCSBase::VCSEditorFactory<CVSEditor> CVSEditorFactory;
243     using namespace Constants;
244
245     using namespace Core::Constants;
246     using namespace ExtensionSystem;
247
248     VCSBase::VCSBasePlugin::initialize(new CVSControl(this));
249
250     m_cvsPluginInstance = this;
251     Core::ICore *core = Core::ICore::instance();
252
253     if (!core->mimeDatabase()->addMimeTypes(QLatin1String(":/trolltech.cvs/CVS.mimetypes.xml"), errorMessage))
254         return false;
255
256     if (QSettings *settings = core->settings())
257         m_settings.fromSettings(settings);
258
259     addAutoReleasedObject(new SettingsPage);
260
261     addAutoReleasedObject(new CVSSubmitEditorFactory(&submitParameters));
262
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));
267
268     addAutoReleasedObject(new CheckoutWizard);
269
270     const QString prefix = QLatin1String("cvs");
271     m_commandLocator = new Locator::CommandLocator(QLatin1String("CVS"), prefix, prefix);
272     addAutoReleasedObject(m_commandLocator);
273
274     //register actions
275     Core::ActionManager *ami = core->actionManager();
276     Core::ActionContainer *toolsContainer = ami->actionContainer(M_TOOLS);
277
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();
282
283     Core::Context globalcontext(C_GLOBAL);
284
285     Core::Command *command;
286
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);
295
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);
304
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);
313
314     cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR0, globalcontext));
315
316     m_addAction = new Utils::ParameterAction(tr("Add"), tr("Add \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
317     command = ami->registerAction(m_addAction, CMD_ID_ADD,
318         globalcontext);
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);
324
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);
333
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,
336         globalcontext);
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);
341
342     m_revertAction = new Utils::ParameterAction(tr("Revert..."), tr("Revert \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this);
343     command = ami->registerAction(m_revertAction, CMD_ID_REVERT,
344         globalcontext);
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);
349
350     cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR1, globalcontext));
351
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);
358
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);
365
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);
371
372     cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR2, globalcontext));
373
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,
376         globalcontext);
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);
381
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,
384         globalcontext);
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);
389
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);
396
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);
403
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);
410
411     cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR3, globalcontext));
412
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);
418
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);
424
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);
430
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);
436
437     m_commitAllAction = new QAction(tr("Commit All Files"), this);
438     command = ami->registerAction(m_commitAllAction, CMD_ID_COMMIT_ALL,
439         globalcontext);
440     connect(m_commitAllAction, SIGNAL(triggered()), this, SLOT(startCommitAll()));
441     cvsMenu->addAction(command);
442     m_commandLocator->appendCommand(command);
443
444     m_revertRepositoryAction = new QAction(tr("Revert Repository..."), this);
445     command = ami->registerAction(m_revertRepositoryAction, CMD_ID_REVERT_ALL,
446                                   globalcontext);
447     connect(m_revertRepositoryAction, SIGNAL(triggered()), this, SLOT(revertAll()));
448     cvsMenu->addAction(command);
449     m_commandLocator->appendCommand(command);
450
451     // Actions of the submit editor
452     Core::Context cvscommitcontext(Constants::CVSCOMMITEDITOR);
453
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()));
458
459     m_submitDiffAction = new QAction(VCSBase::VCSBaseSubmitEditor::diffIcon(), tr("Diff Selected Files"), this);
460     command = ami->registerAction(m_submitDiffAction , Constants::DIFF_SELECTED, cvscommitcontext);
461
462     m_submitUndoAction = new QAction(tr("&Undo"), this);
463     command = ami->registerAction(m_submitUndoAction, Core::Constants::UNDO, cvscommitcontext);
464
465     m_submitRedoAction = new QAction(tr("&Redo"), this);
466     command = ami->registerAction(m_submitRedoAction, Core::Constants::REDO, cvscommitcontext);
467     return true;
468 }
469
470 bool CVSPlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor)
471 {
472     if (!isCommitEditorOpen())
473         return true;
474
475     Core::IFile *fileIFace = submitEditor->file();
476     const CVSSubmitEditor *editor = qobject_cast<CVSSubmitEditor *>(submitEditor);
477     if (!fileIFace || !editor)
478         return true;
479
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?!
486
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;
496     switch (answer) {
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
502     default:
503         break;
504     }
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);
511         fileIFace->save();
512         Core::ICore::instance()->fileManager()->unblockFileChange(fileIFace);
513         closeEditor= commit(m_commitMessageFileName, fileList);
514     }
515     if (closeEditor)
516         cleanCommitMessageFile();
517     return closeEditor;
518 }
519
520 void CVSPlugin::diffCommitFiles(const QStringList &files)
521 {
522     cvsDiff(m_commitRepository, files);
523 }
524
525 static inline void setDiffBaseDirectory(Core::IEditor *editor, const QString &db)
526 {
527     if (VCSBase::VCSBaseEditorWidget *ve = qobject_cast<VCSBase::VCSBaseEditorWidget*>(editor->widget()))
528         ve->setDiffBaseDirectory(db);
529 }
530
531 void CVSPlugin::cvsDiff(const QString &workingDir, const QStringList &files)
532 {
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);
538
539     QStringList args(QLatin1String("diff"));
540     args << m_settings.cvsDiffOptions;
541     args.append(files);
542
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:
550         break;
551     case CVSResponse::OtherError:
552         return;
553     }
554
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);
566             return;
567         }
568     }
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);
574 }
575
576 CVSSubmitEditor *CVSPlugin::openCVSSubmitEditor(const QString &fileName)
577 {
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)));
584
585     return submitEditor;
586 }
587
588 void CVSPlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
589 {
590     if (!enableMenuAction(as, m_menuAction)) {
591         m_commandLocator->setEnabled(false);
592         return;
593     }
594
595     const bool hasTopLevel = currentState().hasTopLevel();
596     m_commandLocator->setEnabled(hasTopLevel);
597
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);
608
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);
615
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);
622 }
623
624 void CVSPlugin::addCurrentFile()
625 {
626     const VCSBase::VCSBasePluginState state = currentState();
627     QTC_ASSERT(state.hasFile(), return)
628     vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
629 }
630
631 void CVSPlugin::revertAll()
632 {
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?")))
637         return;
638     QStringList args;
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());
645     } else {
646         QMessageBox::warning(0, title, tr("Revert failed: %1").arg(revertResponse.message), QMessageBox::Ok);
647     }
648 }
649
650 void CVSPlugin::revertCurrentFile()
651 {
652     const VCSBase::VCSBasePluginState state = currentState();
653     QTC_ASSERT(state.hasFile(), return)
654     QStringList args;
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?
663             return;
664         break;
665     case CVSResponse::OtherError:
666         return;
667     }
668
669     if (!messageBoxQuestion(QLatin1String("CVS Revert"),
670                             tr("The file has been changed. Do you want to revert it?")))
671         return;
672
673     Core::FileChangeBlocker fcb(state.currentFile());
674
675     // revert
676     args.clear();
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()));
683     }
684 }
685
686 void CVSPlugin::diffProject()
687 {
688     const VCSBase::VCSBasePluginState state = currentState();
689     QTC_ASSERT(state.hasProject(), return)
690     cvsDiff(state.currentProjectTopLevel(), state.relativeCurrentProject());
691 }
692
693 void CVSPlugin::diffCurrentFile()
694 {
695     const VCSBase::VCSBasePluginState state = currentState();
696     QTC_ASSERT(state.hasFile(), return)
697     cvsDiff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
698 }
699
700 void CVSPlugin::startCommitCurrentFile()
701 {
702     const VCSBase::VCSBasePluginState state = currentState();
703     QTC_ASSERT(state.hasFile(), return)
704     startCommit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
705 }
706
707 void CVSPlugin::startCommitAll()
708 {
709     const VCSBase::VCSBasePluginState state = currentState();
710     QTC_ASSERT(state.hasTopLevel(), return)
711     startCommit(state.topLevel());
712 }
713
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)
718 {
719     if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor())
720         return;
721     if (isCommitEditorOpen()) {
722         VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Another commit is currently being executed."));
723         return;
724     }
725
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)
732         return;
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)) {
739                 ++it;
740             } else {
741                 it = statusOutput.erase(it);
742             }
743         }
744     }
745     if (statusOutput.empty()) {
746         VCSBase::VCSBaseOutputWindow::instance()->append(tr("There are no modified files."));
747         return;
748     }
749     m_commitRepository = workingDir;
750
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()));
756         return;
757     }
758     // TODO: Retrieve submit template from
759     const QString submitTemplate;
760     m_commitMessageFileName = changeTmpFile.fileName();
761     // Create a submit
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);
769 }
770
771 bool CVSPlugin::commit(const QString &messageFile,
772                               const QStringList &fileList)
773 {
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 ;
783 }
784
785 void CVSPlugin::filelogCurrentFile()
786 {
787     const VCSBase::VCSBasePluginState state = currentState();
788     QTC_ASSERT(state.hasFile(), return)
789     filelog(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), true);
790 }
791
792 void CVSPlugin::logProject()
793 {
794     const VCSBase::VCSBasePluginState state = currentState();
795     QTC_ASSERT(state.hasProject(), return)
796     filelog(state.currentProjectTopLevel(), state.relativeCurrentProject());
797 }
798
799 void CVSPlugin::logRepository()
800 {
801     const VCSBase::VCSBasePluginState state = currentState();
802     QTC_ASSERT(state.hasTopLevel(), return)
803     filelog(state.topLevel());
804 }
805
806 void CVSPlugin::filelog(const QString &workingDir,
807                         const QStringList &files,
808                         bool enableAnnotationContextMenu)
809 {
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);
814     QStringList args;
815     args << QLatin1String("log");
816     args.append(files);
817     const CVSResponse response =
818             runCVS(workingDir, args, m_settings.timeOutMS(),
819                    SshPasswordPrompt, codec);
820     if (response.result != CVSResponse::Ok)
821         return;
822
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);
828     } else {
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);
834     }
835 }
836
837 void CVSPlugin::updateProject()
838 {
839     const VCSBase::VCSBasePluginState state = currentState();
840     QTC_ASSERT(state.hasProject(), return)
841     update(state.currentProjectTopLevel(), state.relativeCurrentProject());
842 }
843
844 bool CVSPlugin::update(const QString &topLevel, const QStringList &files)
845 {
846     QStringList args(QLatin1String("update"));
847     args.push_back(QLatin1String("-dR"));
848     args.append(files);
849     const CVSResponse response =
850             runCVS(topLevel, args, m_settings.longTimeOutMS(),
851                    SshPasswordPrompt|ShowStdOutInLogWindow);
852     const bool ok = response.result == CVSResponse::Ok;
853     if (ok)
854         cvsVersionControl()->emitRepositoryChanged(topLevel);
855     return ok;
856 }
857
858 void CVSPlugin::editCurrentFile()
859 {
860     const VCSBase::VCSBasePluginState state = currentState();
861     QTC_ASSERT(state.hasFile(), return)
862     edit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
863 }
864
865 void CVSPlugin::uneditCurrentFile()
866 {
867     const VCSBase::VCSBasePluginState state = currentState();
868     QTC_ASSERT(state.hasFile(), return)
869     unedit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
870 }
871
872 void CVSPlugin::uneditCurrentRepository()
873 {
874     const VCSBase::VCSBasePluginState state = currentState();
875     QTC_ASSERT(state.hasTopLevel(), return)
876     unedit(state.topLevel(), QStringList());
877 }
878
879 void CVSPlugin::annotateCurrentFile()
880 {
881     const VCSBase::VCSBasePluginState state = currentState();
882     QTC_ASSERT(state.hasFile(), return)
883     annotate(state.currentFileTopLevel(), state.relativeCurrentFile());
884 }
885
886 void CVSPlugin::vcsAnnotate(const QString &file, const QString &revision, int lineNumber)
887 {
888     const QFileInfo fi(file);
889     annotate(fi.absolutePath(), fi.fileName(), revision, lineNumber);
890 }
891
892 bool CVSPlugin::edit(const QString &topLevel, const QStringList &files)
893 {
894     QStringList args(QLatin1String("edit"));
895     args.append(files);
896     const CVSResponse response =
897             runCVS(topLevel, args, m_settings.timeOutMS(),
898                    ShowStdOutInLogWindow|SshPasswordPrompt);
899     return response.result == CVSResponse::Ok;
900 }
901
902 bool CVSPlugin::diffCheckModified(const QString &topLevel, const QStringList &files, bool *modified)
903 {
904     // Quick check for modified files using diff
905     *modified = false;
906     QStringList args(QLatin1String("-q"));
907     args << QLatin1String("diff");
908     args.append(files);
909     const CVSResponse response = runCVS(topLevel, args, m_settings.timeOutMS(), 0);
910     if (response.result == CVSResponse::OtherError)
911         return false;
912     *modified = response.result == CVSResponse::NonNullExitCode;
913     return true;
914 }
915
916 bool CVSPlugin::unedit(const QString &topLevel, const QStringList &files)
917 {
918     bool modified;
919     // Prompt and use force flag if modified
920     if (!diffCheckModified(topLevel, files, &modified))
921         return false;
922     if (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))
927             return false;
928     }
929
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
933     if (modified)
934         args.append(QLatin1String("-y"));
935     args.append(files);
936     const CVSResponse response =
937             runCVS(topLevel, args, m_settings.timeOutMS(),
938                    ShowStdOutInLogWindow|SshPasswordPrompt);
939     return response.result == CVSResponse::Ok;
940 }
941
942 void CVSPlugin::annotate(const QString &workingDir, const QString &file,
943                          const QString &revision /* = QString() */,
944                          int lineNumber /* = -1 */)
945 {
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);
950     QStringList args;
951     args << QLatin1String("annotate");
952     if (!revision.isEmpty())
953         args << QLatin1String("-r") << revision;
954     args << file;
955     const CVSResponse response =
956             runCVS(workingDir, args, m_settings.timeOutMS(),
957                    SshPasswordPrompt, codec);
958     if (response.result != CVSResponse::Ok)
959         return;
960
961     // Re-use an existing view if possible to support
962     // the common usage pattern of continuously changing and diffing a file
963     if (lineNumber < 1)
964         lineNumber = VCSBase::VCSBaseEditorWidget::lineNumberOfCurrentEditor(file);
965
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);
970     } else {
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);
975     }
976 }
977
978 bool CVSPlugin::status(const QString &topLevel, const QStringList &files, const QString &title)
979 {
980     QStringList args(QLatin1String("status"));
981     args.append(files);
982     const CVSResponse response =
983             runCVS(topLevel, args, m_settings.timeOutMS(), 0);
984     const bool ok = response.result == CVSResponse::Ok;
985     if (ok)
986         showOutputInEditor(title, response.stdOut, VCSBase::RegularCommandOutput, topLevel, 0);
987     return ok;
988 }
989
990 void CVSPlugin::projectStatus()
991 {
992     const VCSBase::VCSBasePluginState state = currentState();
993     QTC_ASSERT(state.hasProject(), return)
994     status(state.currentProjectTopLevel(), state.relativeCurrentProject(), tr("Project status"));
995 }
996
997 void CVSPlugin::commitProject()
998 {
999     const VCSBase::VCSBasePluginState state = currentState();
1000     QTC_ASSERT(state.hasProject(), return)
1001     startCommit(state.currentProjectTopLevel(), state.relativeCurrentProject());
1002 }
1003
1004 void CVSPlugin::diffRepository()
1005 {
1006     const VCSBase::VCSBasePluginState state = currentState();
1007     QTC_ASSERT(state.hasTopLevel(), return)
1008     cvsDiff(state.topLevel(), QStringList());
1009 }
1010
1011 void CVSPlugin::statusRepository()
1012 {
1013     const VCSBase::VCSBasePluginState state = currentState();
1014     QTC_ASSERT(state.hasTopLevel(), return)
1015     status(state.topLevel(), QStringList(), tr("Repository status"));
1016 }
1017
1018 void CVSPlugin::updateRepository()
1019 {
1020     const VCSBase::VCSBasePluginState state = currentState();
1021     QTC_ASSERT(state.hasTopLevel(), return)
1022     update(state.topLevel(), QStringList());
1023
1024 }
1025
1026 void CVSPlugin::slotDescribe(const QString &source, const QString &changeNr)
1027 {
1028     QString errorMessage;
1029     if (!describe(source, changeNr, &errorMessage))
1030         VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
1031 }
1032
1033 bool CVSPlugin::describe(const QString &file, const QString &changeNr, QString *errorMessage)
1034 {
1035
1036     QString toplevel;
1037     const bool manages = managesDirectory(QFileInfo(file).absolutePath(), &toplevel);
1038     if (!manages || toplevel.isEmpty()) {
1039         *errorMessage = msgCannotFindTopLevel(file);
1040         return false;
1041     }
1042     return describe(toplevel, QDir(toplevel).relativeFilePath(file), changeNr, errorMessage);
1043 }
1044
1045 bool CVSPlugin::describe(const QString &toplevel, const QString &file, const
1046                          QString &changeNr, QString *errorMessage)
1047 {
1048
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
1054     // if desired.
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);
1060         return false;
1061     }
1062     // Run log to obtain commit id and details
1063     QStringList args;
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;
1069         return false;
1070     }
1071     const QList<CVS_LogEntry> fileLog = parseLogEntries(logResponse.stdOut);
1072     if (fileLog.empty() || fileLog.front().revisions.empty()) {
1073         *errorMessage = msgLogParsingFailed();
1074         return false;
1075     }
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);
1084         args.clear();
1085         args << QLatin1String("log") << QLatin1String("-d") << (dateS  + QLatin1Char('<') + nextDayS);
1086
1087         const CVSResponse repoLogResponse =
1088                 runCVS(toplevel, args, m_settings.longTimeOutMS(), SshPasswordPrompt);
1089         if (repoLogResponse.result != CVSResponse::Ok) {
1090             *errorMessage = repoLogResponse.message;
1091             return false;
1092         }
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);
1097             return false;
1098         }
1099         return describe(toplevel, repoEntries, errorMessage);
1100     } else {
1101         // Just describe that single entry
1102         return describe(toplevel, fileLog, errorMessage);
1103     }
1104     return false;
1105 }
1106
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)
1112 {
1113     // Collect logs
1114     QString output;
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
1119         if (!codec)
1120             codec = VCSBase::VCSBaseEditorWidget::getCodec(repositoryPath, QStringList(it->file));
1121         // Run log
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;
1128             return false;
1129         }
1130         output += logResponse.stdOut;
1131     }
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
1140                     << it->file;
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.
1149                 }
1150                 break;
1151             case CVSResponse::OtherError:
1152                 *errorMessage = diffResponse.message;
1153                 return false;
1154             }
1155             output += fixDiffOutput(diffResponse.stdOut);
1156         }
1157     }
1158
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);
1166     } else {
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);
1171     }
1172     return true;
1173 }
1174
1175 void CVSPlugin::submitCurrentLog()
1176 {
1177     m_submitActionTriggered = true;
1178     Core::EditorManager::instance()->closeEditors(QList<Core::IEditor*>()
1179         << Core::EditorManager::instance()->currentEditor());
1180 }
1181
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,
1186                               int timeOut,
1187                               unsigned flags,
1188                               QTextCodec *outputCodec)
1189 {
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!");
1195         return response;
1196     }
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);
1202
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;
1209         break;
1210     case Utils::SynchronousProcessResponse::FinishedError:
1211         response.result = CVSResponse::NonNullExitCode;
1212         break;
1213     case Utils::SynchronousProcessResponse::TerminatedAbnormally:
1214     case Utils::SynchronousProcessResponse::StartFailed:
1215     case Utils::SynchronousProcessResponse::Hang:
1216         break;
1217     }
1218
1219     if (response.result != CVSResponse::Ok)
1220         response.message = sp_resp.exitMessage(executable, timeOut);
1221
1222     return response;
1223 }
1224
1225 Core::IEditor * CVSPlugin::showOutputInEditor(const QString& title, const QString &output,
1226                                                      int editorType, const QString &source,
1227                                                      QTextCodec *codec)
1228 {
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);
1234     QString s = title;
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());
1239     if (!e)
1240         return 0;
1241     s.replace(QLatin1Char(' '), QLatin1Char('_'));
1242     e->setSuggestedFileName(s);
1243     e->setForceReadOnly(true);
1244     if (!source.isEmpty())
1245         e->setSource(source);
1246     if (codec)
1247         e->setCodec(codec);
1248     Core::IEditor *ie = e->editor();
1249     Core::EditorManager::instance()->activateEditor(ie, Core::EditorManager::ModeSwitch);
1250     return ie;
1251 }
1252
1253 CVSSettings CVSPlugin::settings() const
1254 {
1255     return m_settings;
1256 }
1257
1258 void CVSPlugin::setSettings(const CVSSettings &s)
1259 {
1260     if (s != m_settings) {
1261         m_settings = s;
1262         if (QSettings *settings = Core::ICore::instance()->settings())
1263             m_settings.toSettings(settings);
1264     }
1265 }
1266
1267 CVSPlugin *CVSPlugin::cvsPluginInstance()
1268 {
1269     QTC_ASSERT(m_cvsPluginInstance, return m_cvsPluginInstance);
1270     return m_cvsPluginInstance;
1271 }
1272
1273 bool CVSPlugin::vcsAdd(const QString &workingDir, const QString &rawFileName)
1274 {
1275     QStringList args;
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;
1281 }
1282
1283 bool CVSPlugin::vcsDelete(const QString &workingDir, const QString &rawFileName)
1284 {
1285     QStringList args;
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;
1291 }
1292
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
1296 {
1297     if (topLevel)
1298         topLevel->clear();
1299     bool manages = false;
1300     const QDir dir(directory);
1301     do {
1302         if (!dir.exists() || !checkCVSDirectory(dir))
1303             break;
1304         manages = true;
1305         if (!topLevel)
1306             break;
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();
1314                 break;
1315             }
1316         }
1317     } while (false);
1318     if (CVS::Constants::debug) {
1319         QDebug nsp = qDebug().nospace();
1320         nsp << "CVSPlugin::managesDirectory" << directory << manages;
1321         if (topLevel)
1322             nsp << *topLevel;
1323     }
1324     return manages;
1325 }
1326
1327 bool CVSPlugin::checkCVSDirectory(const QDir &directory) const
1328 {
1329     const QString cvsDir = directory.absoluteFilePath(QLatin1String("CVS"));
1330     return QFileInfo(cvsDir).isDir();
1331 }
1332
1333 CVSControl *CVSPlugin::cvsVersionControl() const
1334 {
1335     return static_cast<CVSControl *>(versionControl());
1336 }
1337
1338 }
1339 }
1340 Q_EXPORT_PLUGIN(CVS::Internal::CVSPlugin)