OSDN Git Service

359e02b8684f974050ea245d47f329236c191e5d
[qt-creator-jp/qt-creator-jp.git] / src / plugins / vcsbase / vcsbaseplugin.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 (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "vcsbaseplugin.h"
35 #include "vcsbasesubmiteditor.h"
36 #include "vcsplugin.h"
37 #include "commonvcssettings.h"
38 #include "vcsbaseoutputwindow.h"
39 #include "corelistener.h"
40
41 #include <coreplugin/icore.h>
42 #include <coreplugin/ifile.h>
43 #include <coreplugin/iversioncontrol.h>
44 #include <coreplugin/filemanager.h>
45 #include <coreplugin/editormanager/editormanager.h>
46 #include <coreplugin/editormanager/ieditor.h>
47 #include <coreplugin/vcsmanager.h>
48 #include <projectexplorer/projectexplorer.h>
49 #include <projectexplorer/project.h>
50 #include <utils/qtcassert.h>
51 #include <utils/synchronousprocess.h>
52
53 #include <QtCore/QDebug>
54 #include <QtCore/QDir>
55 #include <QtCore/QSharedData>
56 #include <QtCore/QScopedPointer>
57 #include <QtCore/QSharedPointer>
58 #include <QtCore/QProcessEnvironment>
59 #include <QtCore/QTextStream>
60 #include <QtCore/QTextCodec>
61
62 #include <QtGui/QAction>
63 #include <QtGui/QMessageBox>
64 #include <QtGui/QFileDialog>
65 #include <QtGui/QMainWindow>
66
67 enum { debug = 0, debugRepositorySearch = 0, debugExecution = 0 };
68
69 namespace VCSBase {
70
71 namespace Internal {
72
73 // Internal state created by the state listener and
74 // VCSBasePluginState.
75
76 struct State {
77     void clearFile();
78     void clearPatchFile();
79     void clearProject();
80     inline void clear();
81
82     bool equals(const State &rhs) const;
83
84     inline bool hasFile() const     { return !currentFile.isEmpty(); }
85     inline bool hasProject() const  { return !currentProjectPath.isEmpty(); }
86     inline bool isEmpty() const     { return !hasFile() && !hasProject(); }
87
88     QString currentFile;
89     QString currentFileName;
90     QString currentPatchFile;
91     QString currentPatchFileDisplayName;
92
93     QString currentFileDirectory;
94     QString currentFileTopLevel;
95
96     QString currentProjectPath;
97     QString currentProjectName;
98     QString currentProjectTopLevel;
99 };
100
101 void State::clearFile()
102 {
103     currentFile.clear();
104     currentFileName.clear();
105     currentFileDirectory.clear();
106     currentFileTopLevel.clear();
107 }
108
109 void State::clearPatchFile()
110 {
111     currentPatchFile.clear();
112     currentPatchFileDisplayName.clear();
113 }
114
115 void State::clearProject()
116 {
117     currentProjectPath.clear();
118     currentProjectName.clear();
119     currentProjectTopLevel.clear();
120 }
121
122 void State::clear()
123 {
124     clearFile();
125     clearPatchFile();
126     clearProject();
127 }
128
129 bool State::equals(const State &rhs) const
130 {
131     return currentFile == rhs.currentFile
132             && currentFileName == rhs.currentFileName
133             && currentPatchFile == rhs.currentPatchFile
134             && currentPatchFileDisplayName == rhs.currentPatchFileDisplayName
135             && currentFileTopLevel == rhs.currentFileTopLevel
136             && currentProjectPath == rhs.currentProjectPath
137             && currentProjectName == rhs.currentProjectName
138             && currentProjectTopLevel == rhs.currentProjectTopLevel;
139 }
140
141 QDebug operator<<(QDebug in, const State &state)
142 {
143     QDebug nospace = in.nospace();
144     nospace << "State: ";
145     if (state.isEmpty()) {
146         nospace << "<empty>";
147     } else {
148         if (state.hasFile()) {
149             nospace << "File=" << state.currentFile
150                     << ',' << state.currentFileTopLevel;
151         } else {
152             nospace << "<no file>";
153         }
154         nospace << '\n';
155         if (state.hasProject()) {
156             nospace << "       Project=" << state.currentProjectName
157             << ',' << state.currentProjectPath
158             << ',' << state.currentProjectTopLevel;
159
160         } else {
161             nospace << "<no project>";
162         }
163         nospace << '\n';
164     }
165     return in;
166 }
167
168 // StateListener: Connects to the relevant signals, tries to find version
169 // controls and emits signals to the plugin instances.
170
171 class StateListener : public QObject {
172     Q_OBJECT
173 public:
174     explicit StateListener(QObject *parent);
175
176 signals:
177     void stateChanged(const VCSBase::Internal::State &s, Core::IVersionControl *vc);
178
179 public slots:
180     void slotStateChanged();
181 };
182
183 StateListener::StateListener(QObject *parent) :
184         QObject(parent)
185 {
186     Core::ICore *core = Core::ICore::instance();
187     connect(core->fileManager(), SIGNAL(currentFileChanged(QString)),
188             this, SLOT(slotStateChanged()));
189     connect(core->editorManager()->instance(), SIGNAL(currentEditorStateChanged(Core::IEditor*)),
190             this, SLOT(slotStateChanged()));
191
192     if (ProjectExplorer::ProjectExplorerPlugin *pe = ProjectExplorer::ProjectExplorerPlugin::instance())
193         connect(pe, SIGNAL(currentProjectChanged(ProjectExplorer::Project*)),
194                 this, SLOT(slotStateChanged()));
195 }
196
197 static inline QString displayNameOfEditor(const QString &fileName)
198 {
199     const QList<Core::IEditor*> editors = Core::EditorManager::instance()->editorsForFileName(fileName);
200     if (!editors.isEmpty())
201         return editors.front()->displayName();
202     return QString();
203 }
204
205 void StateListener::slotStateChanged()
206 {
207     const ProjectExplorer::ProjectExplorerPlugin *pe = ProjectExplorer::ProjectExplorerPlugin::instance();
208     const Core::ICore *core = Core::ICore::instance();
209     Core::VcsManager *vcsManager = core->vcsManager();
210
211     // Get the current file. Are we on a temporary submit editor indicated by
212     // temporary path prefix or does the file contains a hash, indicating a project
213     // folder?
214     State state;
215     state.currentFile = core->fileManager()->currentFile();
216     QScopedPointer<QFileInfo> currentFileInfo; // Instantiate QFileInfo only once if required.
217     if (!state.currentFile.isEmpty()) {
218         const bool isTempFile = state.currentFile.startsWith(QDir::tempPath());
219         // Quick check: Does it look like a patch?
220         const bool isPatch = state.currentFile.endsWith(QLatin1String(".patch"))
221                              || state.currentFile.endsWith(QLatin1String(".diff"));
222         if (isPatch) {
223             // Patch: Figure out a name to display. If it is a temp file, it could be
224             // Codepaster. Use the display name of the editor.
225             state.currentPatchFile = state.currentFile;
226             if (isTempFile)
227                 state.currentPatchFileDisplayName = displayNameOfEditor(state.currentPatchFile);
228             if (state.currentPatchFileDisplayName.isEmpty()) {
229                 currentFileInfo.reset(new QFileInfo(state.currentFile));
230                 state.currentPatchFileDisplayName = currentFileInfo->fileName();
231             }
232         }
233         // For actual version control operations on it:
234         // Do not show temporary files and project folders ('#')
235         if (isTempFile || state.currentFile.contains(QLatin1Char('#')))
236             state.currentFile.clear();
237     }
238
239     // Get the file and its control. Do not use the file unless we find one
240     Core::IVersionControl *fileControl = 0;
241     if (!state.currentFile.isEmpty()) {
242         if (currentFileInfo.isNull())
243             currentFileInfo.reset(new QFileInfo(state.currentFile));
244         state.currentFileDirectory = currentFileInfo->absolutePath();
245         state.currentFileName = currentFileInfo->fileName();
246         fileControl = vcsManager->findVersionControlForDirectory(state.currentFileDirectory,
247                                                                  &state.currentFileTopLevel);
248         if (!fileControl)
249             state.clearFile();
250     }
251     // Check for project, find the control
252     Core::IVersionControl *projectControl = 0;
253     if (const ProjectExplorer::Project *currentProject = pe->currentProject()) {
254         state.currentProjectPath = currentProject->projectDirectory();
255         state.currentProjectName = currentProject->displayName();
256         projectControl = vcsManager->findVersionControlForDirectory(state.currentProjectPath,
257                                                                     &state.currentProjectTopLevel);
258         if (projectControl) {
259             // If we have both, let the file's one take preference
260             if (fileControl && projectControl != fileControl) {
261                 state.clearProject();
262             }
263         } else {
264             state.clearProject(); // No control found
265         }
266     }
267     // Assemble state and emit signal.
268     Core::IVersionControl *vc = state.currentFile.isEmpty() ? projectControl : fileControl;
269     if (!vc) // Need a repository to patch
270         state.clearPatchFile();
271     if (debug)
272         qDebug() << state << (vc ? vc->displayName() : QString(QLatin1String("No version control")));
273     emit stateChanged(state, vc);
274 }
275
276 } // namespace Internal
277
278 class VCSBasePluginStateData : public QSharedData {
279 public:
280     Internal::State m_state;
281 };
282
283 VCSBasePluginState::VCSBasePluginState() : data(new VCSBasePluginStateData)
284 {
285 }
286
287 VCSBasePluginState::VCSBasePluginState(const VCSBasePluginState &rhs) : data(rhs.data)
288 {
289 }
290
291 VCSBasePluginState &VCSBasePluginState::operator=(const VCSBasePluginState &rhs)
292 {
293     if (this != &rhs)
294         data.operator=(rhs.data);
295     return *this;
296 }
297
298 VCSBasePluginState::~VCSBasePluginState()
299 {
300 }
301
302 QString VCSBasePluginState::currentFile() const
303 {
304     return data->m_state.currentFile;
305 }
306
307 QString VCSBasePluginState::currentFileName() const
308 {
309     return data->m_state.currentFileName;
310 }
311
312 QString VCSBasePluginState::currentFileTopLevel() const
313 {
314     return data->m_state.currentFileTopLevel;
315 }
316
317 QString VCSBasePluginState::currentFileDirectory() const
318 {
319     return data->m_state.currentFileDirectory;
320 }
321
322 QString VCSBasePluginState::relativeCurrentFile() const
323 {
324     QTC_ASSERT(hasFile(), return QString())
325     return QDir(data->m_state.currentFileTopLevel).relativeFilePath(data->m_state.currentFile);
326 }
327
328 QString VCSBasePluginState::currentPatchFile() const
329 {
330     return data->m_state.currentPatchFile;
331 }
332
333 QString VCSBasePluginState::currentPatchFileDisplayName() const
334 {
335     return data->m_state.currentPatchFileDisplayName;
336 }
337
338 QString VCSBasePluginState::currentProjectPath() const
339 {
340     return data->m_state.currentProjectPath;
341 }
342
343 QString VCSBasePluginState::currentProjectName() const
344 {
345     return data->m_state.currentProjectName;
346 }
347
348 QString VCSBasePluginState::currentProjectTopLevel() const
349 {
350     return data->m_state.currentProjectTopLevel;
351 }
352
353 QStringList VCSBasePluginState::relativeCurrentProject() const
354 {
355     QStringList rc;
356     QTC_ASSERT(hasProject(), return rc)
357     if (data->m_state.currentProjectTopLevel != data->m_state.currentProjectPath)
358         rc.append(QDir(data->m_state.currentProjectTopLevel).relativeFilePath(data->m_state.currentProjectPath));
359     return rc;
360 }
361
362 bool VCSBasePluginState::hasTopLevel() const
363 {
364     return data->m_state.hasFile() || data->m_state.hasProject();
365 }
366
367 QString VCSBasePluginState::topLevel() const
368 {
369     return hasFile() ? data->m_state.currentFileTopLevel : data->m_state.currentProjectTopLevel;
370 }
371
372 bool VCSBasePluginState::equals(const Internal::State &rhs) const
373 {
374     return data->m_state.equals(rhs);
375 }
376
377 bool VCSBasePluginState::equals(const VCSBasePluginState &rhs) const
378 {
379     return equals(rhs.data->m_state);
380 }
381
382 void VCSBasePluginState::clear()
383 {
384     data->m_state.clear();
385 }
386
387 void VCSBasePluginState::setState(const Internal::State &s)
388 {
389     data->m_state = s;
390 }
391
392 bool VCSBasePluginState::isEmpty() const
393 {
394     return data->m_state.isEmpty();
395 }
396
397 bool VCSBasePluginState::hasFile() const
398 {
399     return data->m_state.hasFile();
400 }
401
402 bool VCSBasePluginState::hasPatchFile() const
403 {
404     return !data->m_state.currentPatchFile.isEmpty();
405 }
406
407 bool VCSBasePluginState::hasProject() const
408 {
409     return data->m_state.hasProject();
410 }
411
412 VCSBASE_EXPORT QDebug operator<<(QDebug in, const VCSBasePluginState &state)
413 {
414     in << state.data->m_state;
415     return in;
416 }
417
418 //  VCSBasePlugin
419 struct VCSBasePluginPrivate {
420     explicit VCSBasePluginPrivate(const QString &submitEditorId);
421
422     inline bool supportsRepositoryCreation() const;
423
424     const QString m_submitEditorId;
425     Core::IVersionControl *m_versionControl;
426     VCSBasePluginState m_state;
427     int m_actionState;
428     QAction *m_testSnapshotAction;
429     QAction *m_testListSnapshotsAction;
430     QAction *m_testRestoreSnapshotAction;
431     QAction *m_testRemoveSnapshotAction;
432     QString m_testLastSnapshot;
433
434     static Internal::StateListener *m_listener;
435 };
436
437 VCSBasePluginPrivate::VCSBasePluginPrivate(const QString &submitEditorId) :
438     m_submitEditorId(submitEditorId),
439     m_versionControl(0),
440     m_actionState(-1),
441     m_testSnapshotAction(0),
442     m_testListSnapshotsAction(0),
443     m_testRestoreSnapshotAction(0),
444     m_testRemoveSnapshotAction(0)
445 {
446 }
447
448 bool VCSBasePluginPrivate::supportsRepositoryCreation() const
449 {
450     return m_versionControl && m_versionControl->supportsOperation(Core::IVersionControl::CreateRepositoryOperation);
451 }
452
453 Internal::StateListener *VCSBasePluginPrivate::m_listener = 0;
454
455 VCSBasePlugin::VCSBasePlugin(const QString &submitEditorId) :
456     d(new VCSBasePluginPrivate(submitEditorId))
457 {
458 }
459
460 VCSBasePlugin::~VCSBasePlugin()
461 {
462     delete d;
463 }
464
465 void VCSBasePlugin::initialize(Core::IVersionControl *vc)
466 {
467     d->m_versionControl = vc;
468     addAutoReleasedObject(vc);
469
470     Internal::VCSPlugin *plugin = Internal::VCSPlugin::instance();
471     connect(plugin->coreListener(), SIGNAL(submitEditorAboutToClose(VCSBaseSubmitEditor*,bool*)),
472             this, SLOT(slotSubmitEditorAboutToClose(VCSBaseSubmitEditor*,bool*)));
473     // First time: create new listener
474     if (!VCSBasePluginPrivate::m_listener)
475         VCSBasePluginPrivate::m_listener = new Internal::StateListener(plugin);
476     connect(VCSBasePluginPrivate::m_listener,
477             SIGNAL(stateChanged(VCSBase::Internal::State, Core::IVersionControl*)),
478             this,
479             SLOT(slotStateChanged(VCSBase::Internal::State,Core::IVersionControl*)));
480 }
481
482 void VCSBasePlugin::extensionsInitialized()
483 {
484     // Initialize enable menus.
485     VCSBasePluginPrivate::m_listener->slotStateChanged();
486 }
487
488 void VCSBasePlugin::slotSubmitEditorAboutToClose(VCSBaseSubmitEditor *submitEditor, bool *result)
489 {
490     if (debug)
491         qDebug() << this << d->m_submitEditorId << "Closing submit editor" << submitEditor << submitEditor->id();
492     if (submitEditor->id() == d->m_submitEditorId)
493         *result = submitEditorAboutToClose(submitEditor);
494 }
495
496 Core::IVersionControl *VCSBasePlugin::versionControl() const
497 {
498     return d->m_versionControl;
499 }
500
501 void VCSBasePlugin::slotStateChanged(const VCSBase::Internal::State &newInternalState, Core::IVersionControl *vc)
502 {
503     if (vc == d->m_versionControl) {
504         // We are directly affected: Change state
505         if (!d->m_state.equals(newInternalState)) {
506             d->m_state.setState(newInternalState);
507             updateActions(VCSEnabled);
508         }
509     } else {
510         // Some other VCS plugin or state changed: Reset us to empty state.
511         const ActionState newActionState = vc ? OtherVCSEnabled : NoVCSEnabled;
512         if (d->m_actionState != newActionState || !d->m_state.isEmpty()) {
513             d->m_actionState = newActionState;
514             const VCSBasePluginState emptyState;
515             d->m_state = emptyState;
516             updateActions(newActionState);
517         }
518     }
519 }
520
521 const VCSBasePluginState &VCSBasePlugin::currentState() const
522 {
523     return d->m_state;
524 }
525
526 bool VCSBasePlugin::enableMenuAction(ActionState as, QAction *menuAction) const
527 {
528     if (debug)
529         qDebug() << "enableMenuAction" << menuAction->text() << as;
530     switch (as) {
531     case VCSBase::VCSBasePlugin::NoVCSEnabled: {
532         const bool supportsCreation = d->supportsRepositoryCreation();
533         menuAction->setVisible(supportsCreation);
534         menuAction->setEnabled(supportsCreation);
535         return supportsCreation;
536     }
537     case VCSBase::VCSBasePlugin::OtherVCSEnabled:
538         menuAction->setVisible(false);
539         return false;
540     case VCSBase::VCSBasePlugin::VCSEnabled:
541         menuAction->setVisible(true);
542         menuAction->setEnabled(true);
543         break;
544     }
545     return true;
546 }
547
548 void VCSBasePlugin::promptToDeleteCurrentFile()
549 {
550     const VCSBasePluginState state = currentState();
551     QTC_ASSERT(state.hasFile(), return)
552     const bool rc = Core::ICore::instance()->vcsManager()->promptToDelete(versionControl(), state.currentFile());
553     if (!rc)
554         QMessageBox::warning(0, tr("Version Control"),
555                              tr("The file '%1' could not be deleted.").
556                              arg(QDir::toNativeSeparators(state.currentFile())),
557                              QMessageBox::Ok);
558 }
559
560 static inline bool ask(QWidget *parent, const QString &title, const QString &question, bool defaultValue = true)
561
562 {
563     const QMessageBox::StandardButton defaultButton = defaultValue ? QMessageBox::Yes : QMessageBox::No;
564     return QMessageBox::question(parent, title, question, QMessageBox::Yes|QMessageBox::No, defaultButton) == QMessageBox::Yes;
565 }
566
567 void VCSBasePlugin::createRepository()
568 {
569     QTC_ASSERT(d->m_versionControl->supportsOperation(Core::IVersionControl::CreateRepositoryOperation), return);
570     // Find current starting directory
571     QString directory;
572     if (const ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectExplorerPlugin::instance()->currentProject())
573         directory = QFileInfo(currentProject->file()->fileName()).absolutePath();
574     // Prompt for a directory that is not under version control yet
575     QMainWindow *mw = Core::ICore::instance()->mainWindow();
576     do {
577         directory = QFileDialog::getExistingDirectory(mw, tr("Choose Repository Directory"), directory);
578         if (directory.isEmpty())
579             return;
580         const Core::IVersionControl *managingControl = Core::ICore::instance()->vcsManager()->findVersionControlForDirectory(directory);
581         if (managingControl == 0)
582             break;
583         const QString question = tr("The directory '%1' is already managed by a version control system (%2)."
584                                     " Would you like to specify another directory?").arg(directory, managingControl->displayName());
585
586         if (!ask(mw, tr("Repository already under version control"), question))
587             return;
588     } while (true);
589     // Create
590     const bool rc = d->m_versionControl->vcsCreateRepository(directory);
591     const QString nativeDir = QDir::toNativeSeparators(directory);
592     if (rc) {
593         QMessageBox::information(mw, tr("Repository Created"),
594                                  tr("A version control repository has been created in %1.").
595                                  arg(nativeDir));
596     } else {
597         QMessageBox::warning(mw, tr("Repository Creation Failed"),
598                                  tr("A version control repository could not be created in %1.").
599                                  arg(nativeDir));
600     }
601 }
602
603 // For internal tests: Create actions driving IVersionControl's snapshot interface.
604 QList<QAction*> VCSBasePlugin::createSnapShotTestActions()
605 {
606     if (!d->m_testSnapshotAction) {
607         d->m_testSnapshotAction = new QAction(QLatin1String("Take snapshot"), this);
608         connect(d->m_testSnapshotAction, SIGNAL(triggered()), this, SLOT(slotTestSnapshot()));
609         d->m_testListSnapshotsAction = new QAction(QLatin1String("List snapshots"), this);
610         connect(d->m_testListSnapshotsAction, SIGNAL(triggered()), this, SLOT(slotTestListSnapshots()));
611         d->m_testRestoreSnapshotAction = new QAction(QLatin1String("Restore snapshot"), this);
612         connect(d->m_testRestoreSnapshotAction, SIGNAL(triggered()), this, SLOT(slotTestRestoreSnapshot()));
613         d->m_testRemoveSnapshotAction = new QAction(QLatin1String("Remove snapshot"), this);
614         connect(d->m_testRemoveSnapshotAction, SIGNAL(triggered()), this, SLOT(slotTestRemoveSnapshot()));
615     }
616     QList<QAction*> rc;
617     rc << d->m_testSnapshotAction << d->m_testListSnapshotsAction << d->m_testRestoreSnapshotAction
618        << d->m_testRemoveSnapshotAction;
619     return rc;
620 }
621
622 void VCSBasePlugin::slotTestSnapshot()
623 {
624     QTC_ASSERT(currentState().hasTopLevel(), return)
625     d->m_testLastSnapshot = versionControl()->vcsCreateSnapshot(currentState().topLevel());
626     qDebug() << "Snapshot " << d->m_testLastSnapshot;
627     VCSBaseOutputWindow::instance()->append(QLatin1String("Snapshot: ") + d->m_testLastSnapshot);
628     if (!d->m_testLastSnapshot.isEmpty())
629         d->m_testRestoreSnapshotAction->setText(QLatin1String("Restore snapshot ") + d->m_testLastSnapshot);
630 }
631
632 void VCSBasePlugin::slotTestListSnapshots()
633 {
634     QTC_ASSERT(currentState().hasTopLevel(), return)
635     const QStringList snapshots = versionControl()->vcsSnapshots(currentState().topLevel());
636     qDebug() << "Snapshots " << snapshots;
637     VCSBaseOutputWindow::instance()->append(QLatin1String("Snapshots: ") + snapshots.join(QLatin1String(", ")));
638 }
639
640 void VCSBasePlugin::slotTestRestoreSnapshot()
641 {
642     QTC_ASSERT(currentState().hasTopLevel() && !d->m_testLastSnapshot.isEmpty(), return)
643     const bool ok = versionControl()->vcsRestoreSnapshot(currentState().topLevel(), d->m_testLastSnapshot);
644     const QString msg = d->m_testLastSnapshot+ (ok ? QLatin1String(" restored") : QLatin1String(" failed"));
645     qDebug() << msg;
646     VCSBaseOutputWindow::instance()->append(msg);
647 }
648
649 void VCSBasePlugin::slotTestRemoveSnapshot()
650 {
651     QTC_ASSERT(currentState().hasTopLevel() && !d->m_testLastSnapshot.isEmpty(), return)
652     const bool ok = versionControl()->vcsRemoveSnapshot(currentState().topLevel(), d->m_testLastSnapshot);
653     const QString msg = d->m_testLastSnapshot+ (ok ? QLatin1String(" removed") : QLatin1String(" failed"));
654     qDebug() << msg;
655     VCSBaseOutputWindow::instance()->append(msg);
656     d->m_testLastSnapshot.clear();
657 }
658
659 // Find top level for version controls like git/Mercurial that have
660 // a directory at the top of the repository.
661 // Note that checking for the existence of files is preferred over directories
662 // since checking for directories can cause them to be created when
663 // AutoFS is used (due its automatically creating mountpoints when querying
664 // a directory). In addition, bail out when reaching the home directory
665 // of the user or root (generally avoid '/', where mountpoints are created).
666 QString VCSBasePlugin::findRepositoryForDirectory(const QString &dirS,
667                                                   const QString &checkFile)
668 {
669     if (debugRepositorySearch)
670         qDebug() << ">VCSBasePlugin::findRepositoryForDirectory" << dirS << checkFile;
671     QTC_ASSERT(!dirS.isEmpty() && !checkFile.isEmpty(), return QString());
672
673     const QString root = QDir::rootPath();
674     const QString home = QDir::homePath();
675
676     QDir directory(dirS);
677     do {
678         const QString absDirPath = directory.absolutePath();
679         if (absDirPath == root || absDirPath == home)
680             break;
681
682         if (QFileInfo(directory, checkFile).isFile()) {
683             if (debugRepositorySearch)
684                 qDebug() << "<VCSBasePlugin::findRepositoryForDirectory> " << absDirPath;
685             return absDirPath;
686         }
687     } while (directory.cdUp());
688     if (debugRepositorySearch)
689         qDebug() << "<VCSBasePlugin::findRepositoryForDirectory bailing out at " << directory.absolutePath();
690     return QString();
691 }
692
693 // Is SSH prompt configured?
694 static inline QString sshPrompt()
695 {
696     return VCSBase::Internal::VCSPlugin::instance()->settings().sshPasswordPrompt;
697 }
698
699 bool VCSBasePlugin::isSshPromptConfigured()
700 {
701     return !sshPrompt().isEmpty();
702 }
703
704 void VCSBasePlugin::setProcessEnvironment(QProcessEnvironment *e, bool forceCLocale)
705 {
706     if (forceCLocale)
707         e->insert(QLatin1String("LANG"), QString(QLatin1Char('C')));
708     const QString sshPromptBinary = sshPrompt();
709     if (!sshPromptBinary.isEmpty())
710         e->insert(QLatin1String("SSH_ASKPASS"), sshPromptBinary);
711 }
712
713 // Run a process fully synchronously, returning Utils::SynchronousProcessResponse
714 // response struct and using the VCSBasePlugin flags as applicable
715 static Utils::SynchronousProcessResponse
716     runVCS_FullySynchronously(const QString &workingDir,
717                               const QString &binary,
718                               const QStringList &arguments,
719                               int timeOutMS,
720                               QProcessEnvironment env,
721                               unsigned flags,
722                               QTextCodec *outputCodec = 0)
723 {
724     VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
725
726     // Set up process
727     unsigned processFlags = 0;
728     if (VCSBasePlugin::isSshPromptConfigured() && (flags & VCSBasePlugin::SshPasswordPrompt))
729         processFlags |= Utils::SynchronousProcess::UnixTerminalDisabled;
730     QSharedPointer<QProcess> process = Utils::SynchronousProcess::createProcess(processFlags);
731     if (!workingDir.isEmpty())
732         process->setWorkingDirectory(workingDir);
733     process->setProcessEnvironment(env);
734     if (flags & VCSBasePlugin::MergeOutputChannels)
735         process->setProcessChannelMode(QProcess::MergedChannels);
736
737     // Start
738     process->start(binary, arguments);
739     Utils::SynchronousProcessResponse response;
740     if (!process->waitForStarted()) {
741         response.result = Utils::SynchronousProcessResponse::StartFailed;
742         return response;
743     }
744
745     // process output
746     QByteArray stdOut;
747     QByteArray stdErr;
748     const bool timedOut =
749             !Utils::SynchronousProcess::readDataFromProcess(*process.data(), timeOutMS,
750                                                             &stdOut, &stdErr, true);
751
752     if (!stdErr.isEmpty()) {
753         response.stdErr = QString::fromLocal8Bit(stdErr).remove('\r');
754         if (!(flags & VCSBasePlugin::SuppressStdErrInLogWindow))
755             outputWindow->append(response.stdErr);
756     }
757
758     if (!stdOut.isEmpty()) {
759         response.stdOut = (outputCodec ? outputCodec->toUnicode(stdOut) : QString::fromLocal8Bit(stdOut))
760                           .remove('\r');
761         if (flags & VCSBasePlugin::ShowStdOutInLogWindow)
762             outputWindow->append(response.stdOut);
763     }
764
765     // Result
766     if (timedOut) {
767         response.result = Utils::SynchronousProcessResponse::Hang;
768     } else if (process->exitStatus() != QProcess::NormalExit) {
769         response.result = Utils::SynchronousProcessResponse::TerminatedAbnormally;
770     } else {
771         response.result = process->exitCode() == 0 ?
772                           Utils::SynchronousProcessResponse::Finished :
773                           Utils::SynchronousProcessResponse::FinishedError;
774     }
775     return response;
776 }
777
778
779 Utils::SynchronousProcessResponse
780         VCSBasePlugin::runVCS(const QString &workingDir,
781                               const QString &binary,
782                               const QStringList &arguments,
783                               int timeOutMS,
784                               unsigned flags,
785                               QTextCodec *outputCodec /* = 0 */)
786 {
787     const QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
788     return runVCS(workingDir, binary, arguments, timeOutMS, env,
789                   flags, outputCodec);
790 }
791
792 Utils::SynchronousProcessResponse
793         VCSBasePlugin::runVCS(const QString &workingDir,
794                               const QString &binary,
795                               const QStringList &arguments,
796                               int timeOutMS,
797                               QProcessEnvironment env,
798                               unsigned flags,
799                               QTextCodec *outputCodec /* = 0 */)
800 {
801     VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
802
803     if (!(flags & SuppressCommandLogging))
804         outputWindow->appendCommand(workingDir, binary, arguments);
805
806     const bool sshPromptConfigured = VCSBasePlugin::isSshPromptConfigured();
807     if (debugExecution) {
808         QDebug nsp = qDebug().nospace();
809         nsp << "VCSBasePlugin::runVCS" << workingDir << binary << arguments
810                 << timeOutMS;
811         if (flags & ShowStdOutInLogWindow)
812             nsp << "stdout";
813         if (flags & SuppressStdErrInLogWindow)
814             nsp << "suppress_stderr";
815         if (flags & SuppressFailMessageInLogWindow)
816             nsp << "suppress_fail_msg";
817         if (flags & MergeOutputChannels)
818             nsp << "merge_channels";
819         if (flags & SshPasswordPrompt)
820             nsp << "ssh (" << sshPromptConfigured << ')';
821         if (flags & SuppressCommandLogging)
822             nsp << "suppress_log";
823         if (flags & ForceCLocale)
824             nsp << "c_locale";
825         if (flags & FullySynchronously)
826             nsp << "fully_synchronously";
827         if (outputCodec)
828             nsp << " Codec: " << outputCodec->name();
829     }
830
831     VCSBase::VCSBasePlugin::setProcessEnvironment(&env, (flags & ForceCLocale));
832
833     Utils::SynchronousProcessResponse response;
834
835     if (flags & FullySynchronously) {
836         response = runVCS_FullySynchronously(workingDir, binary, arguments, timeOutMS,
837                                              env, flags, outputCodec);
838     } else {
839         // Run, connect stderr to the output window
840         Utils::SynchronousProcess process;
841         if (!workingDir.isEmpty())
842             process.setWorkingDirectory(workingDir);
843
844         process.setProcessEnvironment(env);
845         process.setTimeout(timeOutMS);
846         if (outputCodec)
847             process.setStdOutCodec(outputCodec);
848
849         // Suppress terminal on UNIX for ssh prompts if it is configured.
850         if (sshPromptConfigured && (flags & SshPasswordPrompt))
851             process.setFlags(Utils::SynchronousProcess::UnixTerminalDisabled);
852
853         // connect stderr to the output window if desired
854         if (flags & MergeOutputChannels) {
855             process.setProcessChannelMode(QProcess::MergedChannels);
856         } else {
857             if (!(flags & SuppressStdErrInLogWindow)) {
858                 process.setStdErrBufferedSignalsEnabled(true);
859                 connect(&process, SIGNAL(stdErrBuffered(QString,bool)), outputWindow, SLOT(append(QString)));
860             }
861         }
862
863         // connect stdout to the output window if desired
864         if (flags & ShowStdOutInLogWindow) {
865             process.setStdOutBufferedSignalsEnabled(true);
866             connect(&process, SIGNAL(stdOutBuffered(QString,bool)), outputWindow, SLOT(append(QString)));
867         }
868
869         process.setTimeOutMessageBoxEnabled(true);
870
871         // Run!
872         response = process.run(binary, arguments);
873     }
874
875     // Success/Fail message in appropriate window?
876     if (response.result == Utils::SynchronousProcessResponse::Finished) {
877         if (flags & ShowSuccessMessage)
878             outputWindow->append(response.exitMessage(binary, timeOutMS));
879     } else {
880         if (!(flags & SuppressFailMessageInLogWindow))
881             outputWindow->appendError(response.exitMessage(binary, timeOutMS));
882     }
883
884     return response;
885 }
886 } // namespace VCSBase
887
888 #include "vcsbaseplugin.moc"