OSDN Git Service

Update license.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / subversion / subversionplugin.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 "subversionplugin.h"
34
35 #include "settingspage.h"
36 #include "subversioneditor.h"
37
38 #include "subversionsubmiteditor.h"
39 #include "subversionconstants.h"
40 #include "subversioncontrol.h"
41 #include "checkoutwizard.h"
42
43 #include <vcsbase/basevcseditorfactory.h>
44 #include <vcsbase/vcsbaseeditor.h>
45 #include <vcsbase/basevcssubmiteditorfactory.h>
46 #include <vcsbase/vcsbaseoutputwindow.h>
47 #include <utils/synchronousprocess.h>
48 #include <utils/parameteraction.h>
49
50 #include <coreplugin/icore.h>
51 #include <coreplugin/coreconstants.h>
52 #include <coreplugin/filemanager.h>
53 #include <coreplugin/messagemanager.h>
54 #include <coreplugin/mimedatabase.h>
55 #include <coreplugin/actionmanager/actionmanager.h>
56 #include <coreplugin/actionmanager/actioncontainer.h>
57 #include <coreplugin/actionmanager/command.h>
58 #include <coreplugin/uniqueidmanager.h>
59 #include <coreplugin/editormanager/editormanager.h>
60
61 #include <locator/commandlocator.h>
62
63 #include <utils/qtcassert.h>
64
65 #include <QtCore/QDebug>
66 #include <QtCore/QDir>
67 #include <QtCore/QFileInfo>
68 #include <QtCore/QTemporaryFile>
69 #include <QtCore/QTextCodec>
70 #include <QtCore/QtPlugin>
71 #include <QtCore/QProcessEnvironment>
72 #include <QtCore/QUrl>
73 #include <QtGui/QAction>
74 #include <QtGui/QFileDialog>
75 #include <QtGui/QMainWindow>
76 #include <QtGui/QMenu>
77 #include <QtGui/QMessageBox>
78 #include <QtGui/QInputDialog>
79 #include <QtXml/QXmlStreamReader>
80 #include <limits.h>
81
82 using namespace Subversion::Internal;
83
84 static const char * const CMD_ID_SUBVERSION_MENU    = "Subversion.Menu";
85 static const char * const CMD_ID_ADD                = "Subversion.Add";
86 static const char * const CMD_ID_DELETE_FILE        = "Subversion.Delete";
87 static const char * const CMD_ID_REVERT             = "Subversion.Revert";
88 static const char * const CMD_ID_SEPARATOR0         = "Subversion.Separator0";
89 static const char * const CMD_ID_DIFF_PROJECT       = "Subversion.DiffAll";
90 static const char * const CMD_ID_DIFF_CURRENT       = "Subversion.DiffCurrent";
91 static const char * const CMD_ID_SEPARATOR1         = "Subversion.Separator1";
92 static const char * const CMD_ID_COMMIT_ALL         = "Subversion.CommitAll";
93 static const char * const CMD_ID_REVERT_ALL         = "Subversion.RevertAll";
94 static const char * const CMD_ID_COMMIT_CURRENT     = "Subversion.CommitCurrent";
95 static const char * const CMD_ID_SEPARATOR2         = "Subversion.Separator2";
96 static const char * const CMD_ID_FILELOG_CURRENT    = "Subversion.FilelogCurrent";
97 static const char * const CMD_ID_ANNOTATE_CURRENT   = "Subversion.AnnotateCurrent";
98 static const char * const CMD_ID_SEPARATOR3         = "Subversion.Separator3";
99 static const char * const CMD_ID_SEPARATOR4         = "Subversion.Separator4";
100 static const char * const CMD_ID_STATUS             = "Subversion.Status";
101 static const char * const CMD_ID_PROJECTLOG         = "Subversion.ProjectLog";
102 static const char * const CMD_ID_REPOSITORYLOG      = "Subversion.RepositoryLog";
103 static const char * const CMD_ID_REPOSITORYUPDATE   = "Subversion.RepositoryUpdate";
104 static const char * const CMD_ID_REPOSITORYDIFF     = "Subversion.RepositoryDiff";
105 static const char * const CMD_ID_REPOSITORYSTATUS   = "Subversion.RepositoryStatus";
106 static const char * const CMD_ID_UPDATE             = "Subversion.Update";
107 static const char * const CMD_ID_COMMIT_PROJECT     = "Subversion.CommitProject";
108 static const char * const CMD_ID_DESCRIBE           = "Subversion.Describe";
109
110 static const char *nonInteractiveOptionC = "--non-interactive";
111
112
113
114 static const VCSBase::VCSBaseEditorParameters editorParameters[] = {
115 {
116     VCSBase::RegularCommandOutput,
117     "Subversion Command Log Editor", // id
118     QT_TRANSLATE_NOOP("VCS", "Subversion Command Log Editor"), // display name
119     "Subversion Command Log Editor", // context
120     "application/vnd.nokia.text.scs_svn_commandlog",
121     "scslog"},
122 {   VCSBase::LogOutput,
123     "Subversion File Log Editor",   // id
124     QT_TRANSLATE_NOOP("VCS", "Subversion File Log Editor"),   // display_name
125     "Subversion File Log Editor",   // context
126     "application/vnd.nokia.text.scs_svn_filelog",
127     "scsfilelog"},
128 {    VCSBase::AnnotateOutput,
129     "Subversion Annotation Editor",  // id
130     QT_TRANSLATE_NOOP("VCS", "Subversion Annotation Editor"),   // display_name
131     "Subversion Annotation Editor",  // context
132     "application/vnd.nokia.text.scs_svn_annotation",
133     "scsannotate"},
134 {   VCSBase::DiffOutput,
135     "Subversion Diff Editor",  // id
136     QT_TRANSLATE_NOOP("VCS", "Subversion Diff Editor"),   // display_name
137     "Subversion Diff Editor",  // context
138     "text/x-patch","diff"}
139 };
140
141 // Utility to find a parameter set by type
142 static inline const VCSBase::VCSBaseEditorParameters *findType(int ie)
143 {
144     const VCSBase::EditorContentType et = static_cast<VCSBase::EditorContentType>(ie);
145     return  VCSBase::VCSBaseEditorWidget::findType(editorParameters, sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters), et);
146 }
147
148 static inline QString debugCodec(const QTextCodec *c)
149 {
150     return c ? QString::fromAscii(c->name()) : QString::fromAscii("Null codec");
151 }
152
153 Core::IEditor* locateEditor(const char *property, const QString &entry)
154 {
155     foreach (Core::IEditor *ed, Core::EditorManager::instance()->openedEditors())
156         if (ed->property(property).toString() == entry)
157             return ed;
158     return 0;
159 }
160
161 // Parse "svn status" output for added/modified/deleted files
162 // "M<7blanks>file"
163 typedef QList<SubversionSubmitEditor::StatusFilePair> StatusList;
164
165 StatusList parseStatusOutput(const QString &output)
166 {
167     StatusList changeSet;
168     const QString newLine = QString(QLatin1Char('\n'));
169     const QStringList list = output.split(newLine, QString::SkipEmptyParts);
170     foreach (const QString &l, list) {
171         const QString line =l.trimmed();
172         if (line.size() > 8) {
173             const QChar state = line.at(0);
174             if (state == QLatin1Char('A') || state == QLatin1Char('D') || state == QLatin1Char('M')) {
175                 const QString fileName = line.mid(7); // Column 8 starting from svn 1.6
176                 changeSet.push_back(SubversionSubmitEditor::StatusFilePair(QString(state), fileName.trimmed()));
177             }
178
179         }
180     }
181     return changeSet;
182 }
183
184 // Return a list of names for the internal svn directories
185 static inline QStringList svnDirectories()
186 {
187     QStringList rc(QLatin1String(".svn"));
188 #ifdef Q_OS_WIN
189     // Option on Windows systems to avoid hassle with some IDEs
190     rc.push_back(QLatin1String("_svn"));
191 #endif
192     return rc;
193 }
194
195 // ------------- SubversionPlugin
196 SubversionPlugin *SubversionPlugin::m_subversionPluginInstance = 0;
197
198 SubversionPlugin::SubversionPlugin() :
199     VCSBase::VCSBasePlugin(QLatin1String(Subversion::Constants::SUBVERSIONCOMMITEDITOR_ID)),
200     m_svnDirectories(svnDirectories()),
201     m_commandLocator(0),
202     m_addAction(0),
203     m_deleteAction(0),
204     m_revertAction(0),
205     m_diffProjectAction(0),
206     m_diffCurrentAction(0),
207     m_logProjectAction(0),
208     m_logRepositoryAction(0),
209     m_commitAllAction(0),
210     m_revertRepositoryAction(0),
211     m_diffRepositoryAction(0),
212     m_statusRepositoryAction(0),
213     m_updateRepositoryAction(0),
214     m_commitCurrentAction(0),
215     m_filelogCurrentAction(0),
216     m_annotateCurrentAction(0),
217     m_statusProjectAction(0),
218     m_updateProjectAction(0),
219     m_commitProjectAction(0),
220     m_describeAction(0),
221     m_submitCurrentLogAction(0),
222     m_submitDiffAction(0),
223     m_submitUndoAction(0),
224     m_submitRedoAction(0),
225     m_menuAction(0),
226     m_submitActionTriggered(false)
227 {
228 }
229
230 SubversionPlugin::~SubversionPlugin()
231 {
232     cleanCommitMessageFile();
233 }
234
235 void SubversionPlugin::cleanCommitMessageFile()
236 {
237     if (!m_commitMessageFileName.isEmpty()) {
238         QFile::remove(m_commitMessageFileName);
239         m_commitMessageFileName.clear();
240         m_commitRepository.clear();
241     }
242 }
243
244 bool SubversionPlugin::isCommitEditorOpen() const
245 {
246     return !m_commitMessageFileName.isEmpty();
247 }
248
249 static const VCSBase::VCSBaseSubmitEditorParameters submitParameters = {
250     Subversion::Constants::SUBVERSION_SUBMIT_MIMETYPE,
251     Subversion::Constants::SUBVERSIONCOMMITEDITOR_ID,
252     Subversion::Constants::SUBVERSIONCOMMITEDITOR_DISPLAY_NAME,
253     Subversion::Constants::SUBVERSIONCOMMITEDITOR
254 };
255
256 static inline Core::Command *createSeparator(QObject *parent,
257                                              Core::ActionManager *ami,
258                                              const char*id,
259                                              const Core::Context &globalcontext)
260 {
261     QAction *tmpaction = new QAction(parent);
262     tmpaction->setSeparator(true);
263     return ami->registerAction(tmpaction, id, globalcontext);
264 }
265
266 bool SubversionPlugin::initialize(const QStringList & /*arguments */, QString *errorMessage)
267 {
268     typedef VCSBase::VCSSubmitEditorFactory<SubversionSubmitEditor> SubversionSubmitEditorFactory;
269     typedef VCSBase::VCSEditorFactory<SubversionEditor> SubversionEditorFactory;
270     using namespace Constants;
271
272     using namespace Core::Constants;
273     using namespace ExtensionSystem;
274
275     VCSBase::VCSBasePlugin::initialize(new SubversionControl(this));
276
277     m_subversionPluginInstance = this;
278     Core::ICore *core = Core::ICore::instance();
279
280     if (!core->mimeDatabase()->addMimeTypes(QLatin1String(":/trolltech.subversion/Subversion.mimetypes.xml"), errorMessage))
281         return false;
282
283     if (QSettings *settings = core->settings())
284         m_settings.fromSettings(settings);
285
286     addAutoReleasedObject(new SettingsPage);
287
288     addAutoReleasedObject(new SubversionSubmitEditorFactory(&submitParameters));
289
290     static const char *describeSlot = SLOT(describe(QString,QString));
291     const int editorCount = sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters);
292     for (int i = 0; i < editorCount; i++)
293         addAutoReleasedObject(new SubversionEditorFactory(editorParameters + i, this, describeSlot));
294
295     addAutoReleasedObject(new CheckoutWizard);
296
297     const QString description = QLatin1String("Subversion");
298     const QString prefix = QLatin1String("svn");
299     m_commandLocator = new Locator::CommandLocator(description, prefix, prefix);
300     addAutoReleasedObject(m_commandLocator);
301
302     //register actions
303     Core::ActionManager *ami = core->actionManager();
304     Core::ActionContainer *toolsContainer = ami->actionContainer(M_TOOLS);
305
306     Core::ActionContainer *subversionMenu =
307         ami->createMenu(Core::Id(CMD_ID_SUBVERSION_MENU));
308     subversionMenu->menu()->setTitle(tr("&Subversion"));
309     toolsContainer->addMenu(subversionMenu);
310     m_menuAction = subversionMenu->menu()->menuAction();
311     Core::Context globalcontext(C_GLOBAL);
312     Core::Command *command;
313
314     m_diffCurrentAction = new Utils::ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
315     command = ami->registerAction(m_diffCurrentAction,
316         CMD_ID_DIFF_CURRENT, globalcontext);
317     command->setAttribute(Core::Command::CA_UpdateText);
318     command->setDefaultKeySequence(QKeySequence(tr("Alt+S,Alt+D")));
319     connect(m_diffCurrentAction, SIGNAL(triggered()), this, SLOT(diffCurrentFile()));
320     subversionMenu->addAction(command);
321     m_commandLocator->appendCommand(command);
322
323     m_filelogCurrentAction = new Utils::ParameterAction(tr("Filelog Current File"), tr("Filelog \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
324     command = ami->registerAction(m_filelogCurrentAction,
325         CMD_ID_FILELOG_CURRENT, globalcontext);
326     command->setAttribute(Core::Command::CA_UpdateText);
327     connect(m_filelogCurrentAction, SIGNAL(triggered()), this,
328         SLOT(filelogCurrentFile()));
329     subversionMenu->addAction(command);
330     m_commandLocator->appendCommand(command);
331
332     m_annotateCurrentAction = new Utils::ParameterAction(tr("Annotate Current File"), tr("Annotate \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
333     command = ami->registerAction(m_annotateCurrentAction,
334         CMD_ID_ANNOTATE_CURRENT, globalcontext);
335     command->setAttribute(Core::Command::CA_UpdateText);
336     connect(m_annotateCurrentAction, SIGNAL(triggered()), this,
337         SLOT(annotateCurrentFile()));
338     subversionMenu->addAction(command);
339     m_commandLocator->appendCommand(command);
340
341     subversionMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR0, globalcontext));
342
343     m_addAction = new Utils::ParameterAction(tr("Add"), tr("Add \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
344     command = ami->registerAction(m_addAction, CMD_ID_ADD,
345         globalcontext);
346     command->setAttribute(Core::Command::CA_UpdateText);
347     command->setDefaultKeySequence(QKeySequence(tr("Alt+S,Alt+A")));
348     connect(m_addAction, SIGNAL(triggered()), this, SLOT(addCurrentFile()));
349     subversionMenu->addAction(command);
350     m_commandLocator->appendCommand(command);
351
352     m_commitCurrentAction = new Utils::ParameterAction(tr("Commit Current File"), tr("Commit \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
353     command = ami->registerAction(m_commitCurrentAction,
354         CMD_ID_COMMIT_CURRENT, globalcontext);
355     command->setAttribute(Core::Command::CA_UpdateText);
356     command->setDefaultKeySequence(QKeySequence(tr("Alt+S,Alt+C")));
357     connect(m_commitCurrentAction, SIGNAL(triggered()), this, SLOT(startCommitCurrentFile()));
358     subversionMenu->addAction(command);
359     m_commandLocator->appendCommand(command);
360
361     m_deleteAction = new Utils::ParameterAction(tr("Delete..."), tr("Delete \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this);
362     command = ami->registerAction(m_deleteAction, CMD_ID_DELETE_FILE,
363         globalcontext);
364     command->setAttribute(Core::Command::CA_UpdateText);
365     connect(m_deleteAction, SIGNAL(triggered()), this, SLOT(promptToDeleteCurrentFile()));
366     subversionMenu->addAction(command);
367     m_commandLocator->appendCommand(command);
368
369     m_revertAction = new Utils::ParameterAction(tr("Revert..."), tr("Revert \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this);
370     command = ami->registerAction(m_revertAction, CMD_ID_REVERT,
371         globalcontext);
372     command->setAttribute(Core::Command::CA_UpdateText);
373     connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revertCurrentFile()));
374     subversionMenu->addAction(command);
375     m_commandLocator->appendCommand(command);
376
377     subversionMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR1, globalcontext));
378
379     m_diffProjectAction = new Utils::ParameterAction(tr("Diff Project"), tr("Diff Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
380     command = ami->registerAction(m_diffProjectAction, CMD_ID_DIFF_PROJECT,
381         globalcontext);
382     command->setAttribute(Core::Command::CA_UpdateText);
383     connect(m_diffProjectAction, SIGNAL(triggered()), this, SLOT(diffProject()));
384     subversionMenu->addAction(command);
385     m_commandLocator->appendCommand(command);
386
387     m_statusProjectAction = new Utils::ParameterAction(tr("Project Status"), tr("Status of Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
388     command = ami->registerAction(m_statusProjectAction, CMD_ID_STATUS,
389         globalcontext);
390     command->setAttribute(Core::Command::CA_UpdateText);
391     connect(m_statusProjectAction, SIGNAL(triggered()), this, SLOT(projectStatus()));
392     subversionMenu->addAction(command);
393     m_commandLocator->appendCommand(command);
394
395     m_logProjectAction = new Utils::ParameterAction(tr("Log Project"), tr("Log Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
396     command = ami->registerAction(m_logProjectAction, CMD_ID_PROJECTLOG, globalcontext);
397     command->setAttribute(Core::Command::CA_UpdateText);
398     connect(m_logProjectAction, SIGNAL(triggered()), this, SLOT(logProject()));
399     subversionMenu->addAction(command);
400     m_commandLocator->appendCommand(command);
401
402     m_updateProjectAction = new Utils::ParameterAction(tr("Update Project"), tr("Update Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
403     command = ami->registerAction(m_updateProjectAction, CMD_ID_UPDATE, globalcontext);
404     connect(m_updateProjectAction, SIGNAL(triggered()), this, SLOT(updateProject()));
405     command->setAttribute(Core::Command::CA_UpdateText);
406     subversionMenu->addAction(command);
407     m_commandLocator->appendCommand(command);
408
409     m_commitProjectAction = new Utils::ParameterAction(tr("Commit Project"), tr("Commit Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
410     command = ami->registerAction(m_commitProjectAction, CMD_ID_COMMIT_PROJECT, globalcontext);
411     connect(m_commitProjectAction, SIGNAL(triggered()), this, SLOT(startCommitProject()));
412     command->setAttribute(Core::Command::CA_UpdateText);
413     subversionMenu->addAction(command);
414     m_commandLocator->appendCommand(command);
415
416     subversionMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR2, globalcontext));
417
418     m_diffRepositoryAction = new QAction(tr("Diff Repository"), this);
419     command = ami->registerAction(m_diffRepositoryAction, CMD_ID_REPOSITORYDIFF, globalcontext);
420     connect(m_diffRepositoryAction, SIGNAL(triggered()), this, SLOT(diffRepository()));
421     subversionMenu->addAction(command);
422     m_commandLocator->appendCommand(command);
423
424     m_statusRepositoryAction = new QAction(tr("Repository Status"), this);
425     command = ami->registerAction(m_statusRepositoryAction, CMD_ID_REPOSITORYSTATUS, globalcontext);
426     connect(m_statusRepositoryAction, SIGNAL(triggered()), this, SLOT(statusRepository()));
427     subversionMenu->addAction(command);
428     m_commandLocator->appendCommand(command);
429
430     m_logRepositoryAction = new QAction(tr("Log Repository"), this);
431     command = ami->registerAction(m_logRepositoryAction, CMD_ID_REPOSITORYLOG, globalcontext);
432     connect(m_logRepositoryAction, SIGNAL(triggered()), this, SLOT(logRepository()));
433     subversionMenu->addAction(command);
434     m_commandLocator->appendCommand(command);
435
436     m_updateRepositoryAction = new QAction(tr("Update Repository"), this);
437     command = ami->registerAction(m_updateRepositoryAction, CMD_ID_REPOSITORYUPDATE, globalcontext);
438     connect(m_updateRepositoryAction, SIGNAL(triggered()), this, SLOT(updateRepository()));
439     subversionMenu->addAction(command);
440     m_commandLocator->appendCommand(command);
441
442     m_commitAllAction = new QAction(tr("Commit All Files"), this);
443     command = ami->registerAction(m_commitAllAction, CMD_ID_COMMIT_ALL,
444         globalcontext);
445     connect(m_commitAllAction, SIGNAL(triggered()), this, SLOT(startCommitAll()));
446     subversionMenu->addAction(command);
447     m_commandLocator->appendCommand(command);
448
449     m_describeAction = new QAction(tr("Describe..."), this);
450     command = ami->registerAction(m_describeAction, CMD_ID_DESCRIBE, globalcontext);
451     connect(m_describeAction, SIGNAL(triggered()), this, SLOT(slotDescribe()));
452     subversionMenu->addAction(command);
453
454     m_revertRepositoryAction = new QAction(tr("Revert Repository..."), this);
455     command = ami->registerAction(m_revertRepositoryAction, CMD_ID_REVERT_ALL,
456         globalcontext);
457     connect(m_revertRepositoryAction, SIGNAL(triggered()), this, SLOT(revertAll()));
458     subversionMenu->addAction(command);
459     m_commandLocator->appendCommand(command);
460
461     // Actions of the submit editor
462     Core::Context svncommitcontext(Constants::SUBVERSIONCOMMITEDITOR);
463
464     m_submitCurrentLogAction = new QAction(VCSBase::VCSBaseSubmitEditor::submitIcon(), tr("Commit"), this);
465     command = ami->registerAction(m_submitCurrentLogAction, Constants::SUBMIT_CURRENT, svncommitcontext);
466     command->setAttribute(Core::Command::CA_UpdateText);
467     connect(m_submitCurrentLogAction, SIGNAL(triggered()), this, SLOT(submitCurrentLog()));
468
469     m_submitDiffAction = new QAction(VCSBase::VCSBaseSubmitEditor::diffIcon(), tr("Diff Selected Files"), this);
470     command = ami->registerAction(m_submitDiffAction , Constants::DIFF_SELECTED, svncommitcontext);
471
472     m_submitUndoAction = new QAction(tr("&Undo"), this);
473     command = ami->registerAction(m_submitUndoAction, Core::Constants::UNDO, svncommitcontext);
474
475     m_submitRedoAction = new QAction(tr("&Redo"), this);
476     command = ami->registerAction(m_submitRedoAction, Core::Constants::REDO, svncommitcontext);
477
478     return true;
479 }
480
481 bool SubversionPlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor)
482 {
483     if (!isCommitEditorOpen())
484         return true;
485
486     Core::IFile *fileIFace = submitEditor->file();
487     const SubversionSubmitEditor *editor = qobject_cast<SubversionSubmitEditor *>(submitEditor);
488     if (!fileIFace || !editor)
489         return true;
490
491     // Submit editor closing. Make it write out the commit message
492     // and retrieve files
493     const QFileInfo editorFile(fileIFace->fileName());
494     const QFileInfo changeFile(m_commitMessageFileName);
495     if (editorFile.absoluteFilePath() != changeFile.absoluteFilePath())
496         return true; // Oops?!
497
498     // Prompt user. Force a prompt unless submit was actually invoked (that
499     // is, the editor was closed or shutdown).
500     SubversionSettings newSettings = m_settings;
501     const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer =
502             editor->promptSubmit(tr("Closing Subversion Editor"),
503                                  tr("Do you want to commit the change?"),
504                                  tr("The commit message check failed. Do you want to commit the change?"),
505                                  &newSettings.promptToSubmit, !m_submitActionTriggered);
506     m_submitActionTriggered = false;
507     switch (answer) {
508     case VCSBase::VCSBaseSubmitEditor::SubmitCanceled:
509         return false; // Keep editing and change file
510     case VCSBase::VCSBaseSubmitEditor::SubmitDiscarded:
511         cleanCommitMessageFile();
512         return true; // Cancel all
513     default:
514         break;
515     }
516     setSettings(newSettings); // in case someone turned prompting off
517     const QStringList fileList = editor->checkedFiles();
518     bool closeEditor = true;
519     if (!fileList.empty()) {
520         // get message & commit
521         Core::ICore::instance()->fileManager()->blockFileChange(fileIFace);
522         fileIFace->save();
523         Core::ICore::instance()->fileManager()->unblockFileChange(fileIFace);
524         closeEditor= commit(m_commitMessageFileName, fileList);
525     }
526     if (closeEditor)
527         cleanCommitMessageFile();
528     return closeEditor;
529 }
530
531 void SubversionPlugin::diffCommitFiles(const QStringList &files)
532 {
533     svnDiff(m_commitRepository, files);
534 }
535
536 static inline void setDiffBaseDirectory(Core::IEditor *editor, const QString &db)
537 {
538     if (VCSBase::VCSBaseEditorWidget *ve = qobject_cast<VCSBase::VCSBaseEditorWidget*>(editor->widget()))
539         ve->setDiffBaseDirectory(db);
540 }
541
542 void SubversionPlugin::svnDiff(const QString &workingDir, const QStringList &files, QString diffname)
543 {
544     if (Subversion::Constants::debug)
545         qDebug() << Q_FUNC_INFO << files << diffname;
546     const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, files);
547     QTextCodec *codec = source.isEmpty() ? static_cast<QTextCodec *>(0) : VCSBase::VCSBaseEditorWidget::getCodec(source);
548
549     if (files.count() == 1 && diffname.isEmpty())
550         diffname = QFileInfo(files.front()).fileName();
551
552     QStringList args(QLatin1String("diff"));
553     args << files;
554
555     const SubversionResponse response =
556             runSvn(workingDir, args, m_settings.timeOutMS(), 0, codec);
557     if (response.error)
558         return;
559
560     // diff of a single file? re-use an existing view if possible to support
561     // the common usage pattern of continuously changing and diffing a file
562     if (files.count() == 1) {
563         // Show in the same editor if diff has been executed before
564         if (Core::IEditor *editor = locateEditor("originalFileName", files.front())) {
565             editor->createNew(response.stdOut);
566             Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
567             setDiffBaseDirectory(editor, workingDir);
568             return;
569         }
570     }
571     const QString title = QString::fromLatin1("svn diff %1").arg(diffname);
572     Core::IEditor *editor = showOutputInEditor(title, response.stdOut, VCSBase::DiffOutput, source, codec);
573     setDiffBaseDirectory(editor, workingDir);
574     if (files.count() == 1)
575         editor->setProperty("originalFileName", files.front());
576 }
577
578 SubversionSubmitEditor *SubversionPlugin::openSubversionSubmitEditor(const QString &fileName)
579 {
580     Core::IEditor *editor = Core::EditorManager::instance()->openEditor(fileName,
581                                                                         QLatin1String(Constants::SUBVERSIONCOMMITEDITOR_ID),
582                                                                         Core::EditorManager::ModeSwitch);
583     SubversionSubmitEditor *submitEditor = qobject_cast<SubversionSubmitEditor*>(editor);
584     QTC_ASSERT(submitEditor, /**/);
585     submitEditor->registerActions(m_submitUndoAction, m_submitRedoAction, m_submitCurrentLogAction, m_submitDiffAction);
586     connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(diffCommitFiles(QStringList)));
587     submitEditor->setCheckScriptWorkingDirectory(m_commitRepository);
588     return submitEditor;
589 }
590
591 void SubversionPlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
592 {
593     if (!enableMenuAction(as, m_menuAction)) {
594         m_commandLocator->setEnabled(false);
595         return;
596     }
597     const bool hasTopLevel = currentState().hasTopLevel();
598     m_commandLocator->setEnabled(hasTopLevel);
599     m_logRepositoryAction->setEnabled(hasTopLevel);
600
601     const QString projectName = currentState().currentProjectName();
602     m_diffProjectAction->setParameter(projectName);
603     m_statusProjectAction->setParameter(projectName);
604     m_updateProjectAction->setParameter(projectName);
605     m_logProjectAction->setParameter(projectName);
606     m_commitProjectAction->setParameter(projectName);
607
608     const bool repoEnabled = currentState().hasTopLevel();
609     m_commitAllAction->setEnabled(repoEnabled);
610     m_describeAction->setEnabled(repoEnabled);
611     m_revertRepositoryAction->setEnabled(repoEnabled);
612     m_diffRepositoryAction->setEnabled(repoEnabled);
613     m_statusRepositoryAction->setEnabled(repoEnabled);
614     m_updateRepositoryAction->setEnabled(repoEnabled);
615
616     const QString fileName = currentState().currentFileName();
617
618     m_addAction->setParameter(fileName);
619     m_deleteAction->setParameter(fileName);
620     m_revertAction->setParameter(fileName);
621     m_diffCurrentAction->setParameter(fileName);
622     m_commitCurrentAction->setParameter(fileName);
623     m_filelogCurrentAction->setParameter(fileName);
624     m_annotateCurrentAction->setParameter(fileName);
625 }
626
627 void SubversionPlugin::addCurrentFile()
628 {
629     const VCSBase::VCSBasePluginState state = currentState();
630     QTC_ASSERT(state.hasFile(), return)
631     vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
632 }
633
634 void SubversionPlugin::revertAll()
635 {
636     const VCSBase::VCSBasePluginState state = currentState();
637     QTC_ASSERT(state.hasTopLevel(), return)
638     const QString title = tr("Revert repository");
639     if (QMessageBox::warning(0, title, tr("Revert all pending changes to the repository?"),
640                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
641         return;
642     // NoteL: Svn "revert ." doesn not work.
643     QStringList args;
644     args << QLatin1String("revert") << QLatin1String("--recursive") << state.topLevel();
645     const SubversionResponse revertResponse =
646             runSvn(state.topLevel(), args, m_settings.timeOutMS(),
647                    SshPasswordPrompt|ShowStdOutInLogWindow);
648     if (revertResponse.error) {
649         QMessageBox::warning(0, title, tr("Revert failed: %1").arg(revertResponse.message), QMessageBox::Ok);
650     } else {
651         subVersionControl()->emitRepositoryChanged(state.topLevel());
652     }
653 }
654
655 void SubversionPlugin::revertCurrentFile()
656 {
657     const VCSBase::VCSBasePluginState state = currentState();
658     QTC_ASSERT(state.hasFile(), return)
659
660     QStringList args(QLatin1String("diff"));
661     args.push_back(state.relativeCurrentFile());
662
663     const SubversionResponse diffResponse =
664             runSvn(state.currentFileTopLevel(), args, m_settings.timeOutMS(), 0);
665     if (diffResponse.error)
666         return;
667
668     if (diffResponse.stdOut.isEmpty())
669         return;
670     if (QMessageBox::warning(0, QLatin1String("svn revert"), tr("The file has been changed. Do you want to revert it?"),
671                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
672         return;
673
674
675     Core::FileChangeBlocker fcb(state.currentFile());
676
677     // revert
678     args.clear();
679     args << QLatin1String("revert") << state.relativeCurrentFile();
680
681     const SubversionResponse revertResponse =
682             runSvn(state.currentFileTopLevel(), args, m_settings.timeOutMS(),
683                    SshPasswordPrompt|ShowStdOutInLogWindow);
684
685     if (!revertResponse.error) {
686         subVersionControl()->emitFilesChanged(QStringList(state.currentFile()));
687     }
688 }
689
690 void SubversionPlugin::diffProject()
691 {
692     const VCSBase::VCSBasePluginState state = currentState();
693     QTC_ASSERT(state.hasProject(), return)
694     svnDiff(state.currentProjectTopLevel(), state.relativeCurrentProject(), state.currentProjectName());
695 }
696
697 void SubversionPlugin::diffCurrentFile()
698 {
699     const VCSBase::VCSBasePluginState state = currentState();
700     QTC_ASSERT(state.hasFile(), return)
701     svnDiff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
702 }
703
704 void SubversionPlugin::startCommitCurrentFile()
705 {
706     const VCSBase::VCSBasePluginState state = currentState();
707     QTC_ASSERT(state.hasFile(), return)
708     startCommit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
709 }
710
711 void SubversionPlugin::startCommitAll()
712 {
713     const VCSBase::VCSBasePluginState state = currentState();
714     QTC_ASSERT(state.hasTopLevel(), return);
715     startCommit(state.topLevel());
716 }
717
718 void SubversionPlugin::startCommitProject()
719 {
720     const VCSBase::VCSBasePluginState state = currentState();
721     QTC_ASSERT(state.hasProject(), return);
722     startCommit(state.currentProjectPath());
723 }
724
725 /* Start commit of files of a single repository by displaying
726  * template and files in a submit editor. On closing, the real
727  * commit will start. */
728 void SubversionPlugin::startCommit(const QString &workingDir, const QStringList &files)
729 {
730     if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor())
731         return;
732     if (isCommitEditorOpen()) {
733         VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Another commit is currently being executed."));
734         return;
735     }
736
737     QStringList args(QLatin1String("status"));
738     args += files;
739
740     const SubversionResponse response =
741             runSvn(workingDir, args, m_settings.timeOutMS(), 0);
742     if (response.error)
743         return;
744
745     // Get list of added/modified/deleted files
746     const StatusList statusOutput = parseStatusOutput(response.stdOut);
747     if (statusOutput.empty()) {
748         VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("There are no modified files."));
749         return;
750     }
751     m_commitRepository = workingDir;
752     // Create a new submit change file containing the submit template
753     QTemporaryFile changeTmpFile;
754     changeTmpFile.setAutoRemove(false);
755     if (!changeTmpFile.open()) {
756         VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot create temporary file: %1").arg(changeTmpFile.errorString()));
757         return;
758     }
759     m_commitMessageFileName = changeTmpFile.fileName();
760     // TODO: Regitctrieve submit template from
761     const QString submitTemplate;
762     // Create a submit
763     changeTmpFile.write(submitTemplate.toUtf8());
764     changeTmpFile.flush();
765     changeTmpFile.close();
766     // Create a submit editor and set file list
767     SubversionSubmitEditor *editor = openSubversionSubmitEditor(m_commitMessageFileName);
768     editor->setStatusList(statusOutput);
769 }
770
771 bool SubversionPlugin::commit(const QString &messageFile,
772                               const QStringList &subVersionFileList)
773 {
774     if (Subversion::Constants::debug)
775         qDebug() << Q_FUNC_INFO << messageFile << subVersionFileList;
776     // Transform the status list which is sth
777     // "[ADM]<blanks>file" into an args list. The files of the status log
778     // can be relative or absolute depending on where the command was run.
779     QStringList args = QStringList(QLatin1String("commit"));
780     args << QLatin1String(nonInteractiveOptionC) << QLatin1String("--file") << messageFile;
781     args.append(subVersionFileList);
782     const SubversionResponse response =
783             runSvn(m_commitRepository, args, m_settings.longTimeOutMS(),
784                    SshPasswordPrompt|ShowStdOutInLogWindow);
785     return !response.error ;
786 }
787
788 void SubversionPlugin::filelogCurrentFile()
789 {
790     const VCSBase::VCSBasePluginState state = currentState();
791     QTC_ASSERT(state.hasFile(), return)
792    filelog(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), true);
793 }
794
795 void SubversionPlugin::logProject()
796 {
797     const VCSBase::VCSBasePluginState state = currentState();
798     QTC_ASSERT(state.hasProject(), return)
799     filelog(state.currentProjectTopLevel(), state.relativeCurrentProject());
800 }
801
802 void SubversionPlugin::logRepository()
803 {
804     const VCSBase::VCSBasePluginState state = currentState();
805     QTC_ASSERT(state.hasTopLevel(), return)
806     filelog(state.topLevel());
807 }
808
809 void SubversionPlugin::diffRepository()
810 {
811     const VCSBase::VCSBasePluginState state = currentState();
812     QTC_ASSERT(state.hasTopLevel(), return)
813     svnDiff(state.topLevel(), QStringList());
814 }
815
816 void SubversionPlugin::statusRepository()
817 {
818     const VCSBase::VCSBasePluginState state = currentState();
819     QTC_ASSERT(state.hasTopLevel(), return)
820     svnStatus(state.topLevel());
821 }
822
823 void SubversionPlugin::updateRepository()
824 {
825     const VCSBase::VCSBasePluginState state = currentState();
826     QTC_ASSERT(state.hasTopLevel(), return)
827     svnUpdate(state.topLevel());
828 }
829
830 void SubversionPlugin::svnStatus(const QString &workingDir, const QStringList &relativePaths)
831 {
832     const VCSBase::VCSBasePluginState state = currentState();
833     QTC_ASSERT(state.hasTopLevel(), return)
834     QStringList args(QLatin1String("status"));
835     if (!relativePaths.isEmpty())
836         args.append(relativePaths);
837     VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
838     outwin->setRepository(workingDir);
839     runSvn(workingDir, args, m_settings.timeOutMS(),
840            ShowStdOutInLogWindow|ShowSuccessMessage);
841     outwin->clearRepository();
842 }
843
844 void SubversionPlugin::filelog(const QString &workingDir,
845                                const QStringList &files,
846                                bool enableAnnotationContextMenu)
847 {
848     // no need for temp file
849     QStringList args(QLatin1String("log"));
850     if (m_settings.logCount > 0)
851         args << QLatin1String("-l") << QString::number(m_settings.logCount);
852     foreach(const QString &file, files)
853         args.append(QDir::toNativeSeparators(file));
854
855     // subversion stores log in UTF-8 and returns it back in user system locale.
856     // So we do not need to encode it.
857     const SubversionResponse response =
858             runSvn(workingDir, args, m_settings.timeOutMS(),
859                    SshPasswordPrompt, 0/*codec*/);
860     if (response.error)
861         return;
862
863     // Re-use an existing view if possible to support
864     // the common usage pattern of continuously changing and diffing a file
865
866     const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDir, files);
867     if (Core::IEditor *editor = locateEditor("logFileName", id)) {
868         editor->createNew(response.stdOut);
869         Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
870     } else {
871         const QString title = QString::fromLatin1("svn log %1").arg(id);
872         const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, files);
873         Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::LogOutput, source, /*codec*/0);
874         newEditor->setProperty("logFileName", id);
875         if (enableAnnotationContextMenu)
876             VCSBase::VCSBaseEditorWidget::getVcsBaseEditor(newEditor)->setFileLogAnnotateEnabled(true);
877     }
878 }
879
880 void SubversionPlugin::updateProject()
881 {
882     const VCSBase::VCSBasePluginState state = currentState();
883     QTC_ASSERT(state.hasProject(), return);
884     svnUpdate(state.currentProjectTopLevel(), state.relativeCurrentProject());
885 }
886
887 void SubversionPlugin::svnUpdate(const QString &workingDir, const QStringList &relativePaths)
888 {
889     QStringList args(QLatin1String("update"));
890     args.push_back(QLatin1String(nonInteractiveOptionC));
891     if (!relativePaths.isEmpty())
892         args.append(relativePaths);
893         const SubversionResponse response =
894                 runSvn(workingDir, args, m_settings.longTimeOutMS(),
895                        SshPasswordPrompt|ShowStdOutInLogWindow);
896     if (!response.error)
897         subVersionControl()->emitRepositoryChanged(workingDir);
898 }
899
900 void SubversionPlugin::annotateCurrentFile()
901 {
902     const VCSBase::VCSBasePluginState state = currentState();
903     QTC_ASSERT(state.hasFile(), return);
904     vcsAnnotate(state.currentFileTopLevel(), state.relativeCurrentFile());
905 }
906
907 void SubversionPlugin::annotateVersion(const QString &file,
908                                        const QString &revision,
909                                        int lineNr)
910 {
911     const QFileInfo fi(file);
912     vcsAnnotate(fi.absolutePath(), fi.fileName(), revision, lineNr);
913 }
914
915 void SubversionPlugin::vcsAnnotate(const QString &workingDir, const QString &file,
916                                 const QString &revision /* = QString() */,
917                                 int lineNumber /* = -1 */)
918 {
919     const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, file);
920     QTextCodec *codec = VCSBase::VCSBaseEditorWidget::getCodec(source);
921
922     QStringList args(QLatin1String("annotate"));
923     if (m_settings.spaceIgnorantAnnotation)
924         args << QLatin1String("-x") << QLatin1String("-uw");
925     if (!revision.isEmpty())
926         args << QLatin1String("-r") << revision;
927     args.push_back(QLatin1String("-v"));
928     args.append(QDir::toNativeSeparators(file));
929
930     const SubversionResponse response =
931             runSvn(workingDir, args, m_settings.timeOutMS(),
932                    SshPasswordPrompt|ForceCLocale, codec);
933     if (response.error)
934         return;
935
936     // Re-use an existing view if possible to support
937     // the common usage pattern of continuously changing and diffing a file
938     if (lineNumber <= 0)
939         lineNumber = VCSBase::VCSBaseEditorWidget::lineNumberOfCurrentEditor(source);
940     // Determine id
941     const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDir, QStringList(file), revision);
942
943     if (Core::IEditor *editor = locateEditor("annotateFileName", id)) {
944         editor->createNew(response.stdOut);
945         VCSBase::VCSBaseEditorWidget::gotoLineOfEditor(editor, lineNumber);
946         Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
947     } else {
948         const QString title = QString::fromLatin1("svn annotate %1").arg(id);
949         Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::AnnotateOutput, source, codec);
950         newEditor->setProperty("annotateFileName", id);
951         VCSBase::VCSBaseEditorWidget::gotoLineOfEditor(newEditor, lineNumber);
952     }
953 }
954
955 void SubversionPlugin::projectStatus()
956 {
957     const VCSBase::VCSBasePluginState state = currentState();
958     QTC_ASSERT(state.hasProject(), return);
959     svnStatus(state.currentFileTopLevel(), state.relativeCurrentProject());
960 }
961
962 void SubversionPlugin::describe(const QString &source, const QString &changeNr)
963 {
964     // To describe a complete change, find the top level and then do
965     //svn diff -r 472958:472959 <top level>
966     const QFileInfo fi(source);
967     QString topLevel;
968     const bool manages = managesDirectory(fi.isDir() ? source : fi.absolutePath(), &topLevel);
969     if (!manages || topLevel.isEmpty())
970         return;
971     if (Subversion::Constants::debug)
972         qDebug() << Q_FUNC_INFO << source << topLevel << changeNr;
973     // Number must be > 1
974     bool ok;
975     const int number = changeNr.toInt(&ok);
976     if (!ok || number < 2)
977         return;
978     // Run log to obtain message (local utf8)
979     QString description;
980     QStringList args(QLatin1String("log"));
981     args.push_back(QLatin1String("-r"));
982     args.push_back(changeNr);
983     const SubversionResponse logResponse =
984             runSvn(topLevel, args, m_settings.timeOutMS(), SshPasswordPrompt);
985     if (logResponse.error)
986         return;
987     description = logResponse.stdOut;
988
989     // Run diff (encoding via source codec)
990     args.clear();
991     args.push_back(QLatin1String("diff"));
992     args.push_back(QLatin1String("-r"));
993     QString diffArg;
994     QTextStream(&diffArg) << (number - 1) << ':' << number;
995     args.push_back(diffArg);
996
997     QTextCodec *codec = VCSBase::VCSBaseEditorWidget::getCodec(source);
998     const SubversionResponse response =
999             runSvn(topLevel, args, m_settings.timeOutMS(),
1000                    SshPasswordPrompt, codec);
1001     if (response.error)
1002         return;
1003     description += response.stdOut;
1004
1005     // Re-use an existing view if possible to support
1006     // the common usage pattern of continuously changing and diffing a file
1007     const QString id = diffArg + source;
1008     if (Core::IEditor *editor = locateEditor("describeChange", id)) {
1009         editor->createNew(description);
1010         Core::EditorManager::instance()->activateEditor(editor, Core::EditorManager::ModeSwitch);
1011     } else {
1012         const QString title = QString::fromLatin1("svn describe %1#%2").arg(fi.fileName(), changeNr);
1013         Core::IEditor *newEditor = showOutputInEditor(title, description, VCSBase::DiffOutput, source, codec);
1014         newEditor->setProperty("describeChange", id);
1015     }
1016 }
1017
1018 void SubversionPlugin::slotDescribe()
1019 {
1020     const VCSBase::VCSBasePluginState state = currentState();
1021     QTC_ASSERT(state.hasTopLevel(), return);
1022
1023     QInputDialog inputDialog(Core::ICore::instance()->mainWindow());
1024     inputDialog.setWindowFlags(inputDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
1025     inputDialog.setInputMode(QInputDialog::IntInput);
1026     inputDialog.setIntRange(2, INT_MAX);
1027     inputDialog.setWindowTitle(tr("Describe"));
1028     inputDialog.setLabelText(tr("Revision number:"));
1029     if (inputDialog.exec() != QDialog::Accepted)
1030         return;
1031
1032     const int revision = inputDialog.intValue();
1033     describe(state.topLevel(), QString::number(revision));
1034 }
1035
1036 void SubversionPlugin::submitCurrentLog()
1037 {
1038     m_submitActionTriggered = true;
1039     Core::EditorManager::instance()->closeEditors(QList<Core::IEditor*>()
1040         << Core::EditorManager::instance()->currentEditor());
1041 }
1042
1043 SubversionResponse
1044         SubversionPlugin::runSvn(const QString &workingDir,
1045                                  const QStringList &arguments,
1046                                  int timeOut,
1047                                  unsigned flags,
1048                                  QTextCodec *outputCodec)
1049 {
1050
1051     return m_settings.hasAuthentication() ?
1052             runSvn(workingDir, m_settings.user, m_settings.password,
1053                    arguments, timeOut, flags, outputCodec) :
1054             runSvn(workingDir, QString(), QString(),
1055                    arguments, timeOut, flags, outputCodec);
1056 }
1057
1058 // Add authorization options to the command line arguments.
1059 // SVN pre 1.5 does not accept "--userName" for "add", which is most likely
1060 // an oversight. As no password is needed for the option, generally omit it.
1061 QStringList SubversionPlugin::addAuthenticationOptions(const QStringList &args,
1062                                                       const QString &userName, const QString &password)
1063 {
1064     if (userName.isEmpty())
1065         return args;
1066     if (!args.empty() && args.front() == QLatin1String("add"))
1067         return args;
1068     QStringList rc;
1069     rc.push_back(QLatin1String("--username"));
1070     rc.push_back(userName);
1071     if (!password.isEmpty()) {
1072         rc.push_back(QLatin1String("--password"));
1073         rc.push_back(password);
1074     }
1075     rc.append(args);
1076     return rc;
1077 }
1078
1079 SubversionResponse SubversionPlugin::runSvn(const QString &workingDir,
1080                           const QString &userName, const QString &password,
1081                           const QStringList &arguments, int timeOut,
1082                           unsigned flags, QTextCodec *outputCodec)
1083 {
1084     const QString executable = m_settings.svnCommand;
1085     SubversionResponse response;
1086     if (executable.isEmpty()) {
1087         response.error = true;
1088         response.message =tr("No subversion executable specified!");
1089         return response;
1090     }
1091
1092     const QStringList completeArguments = SubversionPlugin::addAuthenticationOptions(arguments, userName, password);
1093     const Utils::SynchronousProcessResponse sp_resp =
1094             VCSBase::VCSBasePlugin::runVCS(workingDir, executable,
1095                                            completeArguments, timeOut, flags, outputCodec);
1096
1097     response.error = sp_resp.result != Utils::SynchronousProcessResponse::Finished;
1098     if (response.error)
1099         response.message = sp_resp.exitMessage(executable, timeOut);
1100     response.stdErr = sp_resp.stdErr;
1101     response.stdOut = sp_resp.stdOut;
1102     return response;
1103 }
1104
1105 Core::IEditor * SubversionPlugin::showOutputInEditor(const QString& title, const QString &output,
1106                                                      int editorType, const QString &source,
1107                                                      QTextCodec *codec)
1108 {
1109     const VCSBase::VCSBaseEditorParameters *params = findType(editorType);
1110     QTC_ASSERT(params, return 0);
1111     const QString id = params->id;
1112     if (Subversion::Constants::debug)
1113         qDebug() << "SubversionPlugin::showOutputInEditor" << title << id <<  "Size= " << output.size() <<  " Type=" << editorType << debugCodec(codec);
1114     QString s = title;
1115     Core::IEditor *editor = Core::EditorManager::instance()->openEditorWithContents(id, &s, output);
1116     connect(editor, SIGNAL(annotateRevisionRequested(QString,QString,int)),
1117             this, SLOT(annotateVersion(QString,QString,int)));
1118     SubversionEditor *e = qobject_cast<SubversionEditor*>(editor->widget());
1119     if (!e)
1120         return 0;
1121     e->setForceReadOnly(true);
1122     s.replace(QLatin1Char(' '), QLatin1Char('_'));
1123     e->setSuggestedFileName(s);
1124     if (!source.isEmpty())
1125         e->setSource(source);
1126     if (codec)
1127         e->setCodec(codec);
1128     Core::IEditor *ie = e->editor();
1129     Core::EditorManager::instance()->activateEditor(ie, Core::EditorManager::ModeSwitch);
1130     return ie;
1131 }
1132
1133 SubversionSettings SubversionPlugin::settings() const
1134 {
1135     return m_settings;
1136 }
1137
1138 void SubversionPlugin::setSettings(const SubversionSettings &s)
1139 {
1140     if (s != m_settings) {
1141         m_settings = s;
1142         if (QSettings *settings = Core::ICore::instance()->settings())
1143             m_settings.toSettings(settings);
1144     }
1145 }
1146
1147 SubversionPlugin *SubversionPlugin::subversionPluginInstance()
1148 {
1149     QTC_ASSERT(m_subversionPluginInstance, return m_subversionPluginInstance);
1150     return m_subversionPluginInstance;
1151 }
1152
1153 bool SubversionPlugin::vcsAdd(const QString &workingDir, const QString &rawFileName)
1154 {
1155 #ifdef Q_OS_MAC // See below.
1156     return vcsAdd14(workingDir, rawFileName);
1157 #else
1158     return vcsAdd15(workingDir, rawFileName);
1159 #endif
1160 }
1161
1162 // Post 1.4 add: Use "--parents" to add directories
1163 bool SubversionPlugin::vcsAdd15(const QString &workingDir, const QString &rawFileName)
1164 {
1165     const QString file = QDir::toNativeSeparators(rawFileName);
1166     QStringList args;
1167     args << QLatin1String("add") << QLatin1String("--parents") << file;
1168     const SubversionResponse response =
1169             runSvn(workingDir, args, m_settings.timeOutMS(),
1170                    SshPasswordPrompt|ShowStdOutInLogWindow);
1171     return !response.error;
1172 }
1173
1174 // Pre 1.5 add: Add directories in a loop. To be deprecated
1175 // once Mac ships newer svn-versions
1176 bool SubversionPlugin::vcsAdd14(const QString &workingDir, const QString &rawFileName)
1177 {
1178     const QChar slash = QLatin1Char('/');
1179     const QStringList relativePath = rawFileName.split(slash);
1180     // Add directories (dir1/dir2/file.cpp) in a loop.
1181     if (relativePath.size() > 1) {
1182         QString path;
1183         const int lastDir = relativePath.size() - 1;
1184         for (int p = 0; p < lastDir; p++) {
1185             if (!path.isEmpty())
1186                 path += slash;
1187             path += relativePath.at(p);
1188             if (!checkSVNSubDir(QDir(path))) {
1189                 QStringList addDirArgs;
1190                 addDirArgs << QLatin1String("add") << QLatin1String("--non-recursive") << QDir::toNativeSeparators(path);
1191                 const SubversionResponse addDirResponse =
1192                         runSvn(workingDir, addDirArgs, m_settings.timeOutMS(),
1193                                SshPasswordPrompt|ShowStdOutInLogWindow);
1194                 if (addDirResponse.error)
1195                     return false;
1196             }
1197         }
1198     }
1199     // Add file
1200     QStringList args;
1201     args << QLatin1String("add") << QDir::toNativeSeparators(rawFileName);
1202     const SubversionResponse response =
1203             runSvn(workingDir, args, m_settings.timeOutMS(),
1204                    SshPasswordPrompt|ShowStdOutInLogWindow);
1205     return !response.error;
1206 }
1207
1208 bool SubversionPlugin::vcsDelete(const QString &workingDir, const QString &rawFileName)
1209 {
1210     const QString file = QDir::toNativeSeparators(rawFileName);
1211
1212     QStringList args(QLatin1String("delete"));
1213     args.push_back(file);
1214
1215     const SubversionResponse response =
1216             runSvn(workingDir, args, m_settings.timeOutMS(),
1217                    SshPasswordPrompt|ShowStdOutInLogWindow);
1218     return !response.error;
1219 }
1220
1221 bool SubversionPlugin::vcsMove(const QString &workingDir, const QString &from, const QString &to)
1222 {
1223     QStringList args(QLatin1String("move"));
1224     args << QDir::toNativeSeparators(from) << QDir::toNativeSeparators(to);
1225     const SubversionResponse response =
1226             runSvn(workingDir, args, m_settings.timeOutMS(),
1227                    SshPasswordPrompt|ShowStdOutInLogWindow|FullySynchronously);
1228     return !response.error;
1229 }
1230
1231 bool SubversionPlugin::vcsCheckout(const QString &directory, const QByteArray &url)
1232 {
1233     QUrl tempUrl;
1234     tempUrl.setEncodedUrl(url);
1235     QString username = tempUrl.userName();
1236     QString password = tempUrl.password();
1237     QStringList args = QStringList(QLatin1String("checkout"));
1238     args << QLatin1String(nonInteractiveOptionC) ;
1239
1240     if(!username.isEmpty() && !password.isEmpty())
1241     {
1242         // If url contains username and password we have to use separate username and password
1243         // arguments instead of passing those in the url. Otherwise the subversion 'non-interactive'
1244         // authentication will always fail (if the username and password data are not stored locally),
1245         // if for example we are logging into a new host for the first time using svn. There seems to
1246         // be a bug in subversion, so this might get fixed in the future.
1247         tempUrl.setUserInfo("");
1248         args << tempUrl.toEncoded() << directory;
1249         const SubversionResponse response = runSvn(directory, username, password, args,
1250                                                    m_settings.longTimeOutMS(),
1251                                                    VCSBase::VCSBasePlugin::SshPasswordPrompt);
1252         return !response.error;
1253     } else {
1254         args << url << directory;
1255         const SubversionResponse response = runSvn(directory, args, m_settings.longTimeOutMS(),
1256                                                    VCSBase::VCSBasePlugin::SshPasswordPrompt);
1257         return !response.error;
1258     }
1259 }
1260
1261 QString SubversionPlugin::vcsGetRepositoryURL(const QString &directory)
1262 {
1263     QXmlStreamReader xml;
1264     QStringList args = QStringList(QLatin1String("info"));
1265     args << QLatin1String("--xml");
1266
1267     const SubversionResponse response = runSvn(directory, args, m_settings.longTimeOutMS(), SuppressCommandLogging);
1268     xml.addData(response.stdOut);
1269
1270     bool repo = false;
1271     bool root = false;
1272
1273     while(!xml.atEnd() && !xml.hasError()) {
1274         switch(xml.readNext()) {
1275         case QXmlStreamReader::StartDocument:
1276             break;
1277         case QXmlStreamReader::StartElement:
1278             if(xml.name() == QLatin1String("repository"))
1279                 repo = true;
1280             else if(repo && xml.name() == QLatin1String("root"))
1281                 root = true;
1282             break;
1283         case QXmlStreamReader::EndElement:
1284             if(xml.name() == QLatin1String("repository"))
1285                 repo = false;
1286             else if(repo && xml.name() == QLatin1String("root"))
1287                 root = false;
1288             break;
1289         case QXmlStreamReader::Characters:
1290             if (repo && root)
1291                 return xml.text().toString();
1292             break;
1293         default:
1294             break;
1295         }
1296     }
1297     return QString();
1298 }
1299
1300 /* Subversion has ".svn" directory in each directory
1301  * it manages. The top level is the first directory
1302  * under the directory that does not have a  ".svn". */
1303 bool SubversionPlugin::managesDirectory(const QString &directory, QString *topLevel /* = 0 */) const
1304 {
1305     const QDir dir(directory);
1306     if (topLevel)
1307         topLevel->clear();
1308     bool manages = false;
1309     do {
1310         if (!dir.exists() || !checkSVNSubDir(dir))
1311             break;
1312         manages = true;
1313         if (!topLevel)
1314             break;
1315         /* Recursing up, the top level is a child of the first directory that does
1316          * not have a  ".svn" directory. The starting directory must be a managed
1317          * one. Go up and try to find the first unmanaged parent dir. */
1318         QDir lastDirectory = dir;
1319         for (QDir parentDir = lastDirectory; parentDir.cdUp() ; lastDirectory = parentDir) {
1320             if (!checkSVNSubDir(parentDir)) {
1321                 *topLevel = lastDirectory.absolutePath();
1322                 break;
1323             }
1324         }
1325     } while (false);
1326     if (Subversion::Constants::debug) {
1327         QDebug nsp = qDebug().nospace();
1328         nsp << "SubversionPlugin::managesDirectory" << directory << manages;
1329         if (topLevel)
1330             nsp << *topLevel;
1331     }
1332     return manages;
1333 }
1334
1335 // Check whether SVN management subdirs exist.
1336 bool SubversionPlugin::checkSVNSubDir(const QDir &directory) const
1337 {
1338     const int dirCount = m_svnDirectories.size();
1339     for (int i = 0; i < dirCount; i++) {
1340         const QString svnDir = directory.absoluteFilePath(m_svnDirectories.at(i));
1341         if (QFileInfo(svnDir).isDir())
1342             return true;
1343     }
1344     return false;
1345 }
1346
1347 SubversionControl *SubversionPlugin::subVersionControl() const
1348 {
1349     return static_cast<SubversionControl *>(versionControl());
1350 }
1351
1352 Q_EXPORT_PLUGIN(SubversionPlugin)