1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "vcsbaseplugin.h"
27 #include "vcsbasesubmiteditor.h"
28 #include "vcsplugin.h"
29 #include "commonvcssettings.h"
30 #include "vcsoutputwindow.h"
31 #include "vcscommand.h"
32 
33 #include <coreplugin/documentmanager.h>
34 #include <coreplugin/icore.h>
35 #include <coreplugin/idocument.h>
36 #include <coreplugin/editormanager/editormanager.h>
37 #include <projectexplorer/projecttree.h>
38 #include <projectexplorer/project.h>
39 #include <projectexplorer/session.h>
40 #include <utils/qtcassert.h>
41 #include <utils/qtcprocess.h>
42 
43 #include <QDebug>
44 #include <QDir>
45 #include <QLoggingCategory>
46 #include <QSharedData>
47 #include <QScopedPointer>
48 #include <QSharedPointer>
49 #include <QProcessEnvironment>
50 #include <QTextCodec>
51 
52 #include <QAction>
53 #include <QMessageBox>
54 #include <QFileDialog>
55 
56 using namespace Core;
57 using namespace Utils;
58 using namespace ProjectExplorer;
59 
60 namespace {
61 static Q_LOGGING_CATEGORY(baseLog, "qtc.vcs.base", QtWarningMsg)
62 static Q_LOGGING_CATEGORY(findRepoLog, "qtc.vcs.find-repo", QtWarningMsg)
63 static Q_LOGGING_CATEGORY(stateLog, "qtc.vcs.state", QtWarningMsg)
64 }
65 
66 /*!
67     \namespace VcsBase
68     \brief The VcsBase namespace contains classes for the VcsBase plugin.
69 */
70 
71 /*!
72     \namespace VcsBase::Internal
73     \brief The Internal namespace contains internal classes for the VcsBase
74     plugin.
75     \internal
76 */
77 
78 namespace VcsBase {
79 namespace Internal {
80 
81 /*!
82     \class VcsBase::Internal::State
83 
84     \brief The State class provides the internal state created by the state
85     listener and VcsBasePluginState.
86 
87     Aggregated in the QSharedData of VcsBase::VcsBasePluginState.
88 */
89 
90 class State
91 {
92 public:
93     void clearFile();
94     void clearPatchFile();
95     void clearProject();
96     inline void clear();
97 
98     bool equals(const State &rhs) const;
99 
hasFile() const100     inline bool hasFile() const     { return !currentFileTopLevel.isEmpty(); }
hasProject() const101     inline bool hasProject() const  { return !currentProjectTopLevel.isEmpty(); }
isEmpty() const102     inline bool isEmpty() const     { return !hasFile() && !hasProject(); }
103 
104     QString currentFile;
105     QString currentFileName;
106     QString currentPatchFile;
107     QString currentPatchFileDisplayName;
108 
109     QString currentFileDirectory;
110     QString currentFileTopLevel;
111 
112     QString currentProjectPath;
113     QString currentProjectName;
114     QString currentProjectTopLevel;
115 };
116 
clearFile()117 void State::clearFile()
118 {
119     currentFile.clear();
120     currentFileName.clear();
121     currentFileDirectory.clear();
122     currentFileTopLevel.clear();
123 }
124 
clearPatchFile()125 void State::clearPatchFile()
126 {
127     currentPatchFile.clear();
128     currentPatchFileDisplayName.clear();
129 }
130 
clearProject()131 void State::clearProject()
132 {
133     currentProjectPath.clear();
134     currentProjectName.clear();
135     currentProjectTopLevel.clear();
136 }
137 
clear()138 void State::clear()
139 {
140     clearFile();
141     clearPatchFile();
142     clearProject();
143 }
144 
equals(const State & rhs) const145 bool State::equals(const State &rhs) const
146 {
147     return currentFile == rhs.currentFile
148             && currentFileName == rhs.currentFileName
149             && currentPatchFile == rhs.currentPatchFile
150             && currentPatchFileDisplayName == rhs.currentPatchFileDisplayName
151             && currentFileTopLevel == rhs.currentFileTopLevel
152             && currentProjectPath == rhs.currentProjectPath
153             && currentProjectName == rhs.currentProjectName
154             && currentProjectTopLevel == rhs.currentProjectTopLevel;
155 }
156 
operator <<(QDebug in,const State & state)157 QDebug operator<<(QDebug in, const State &state)
158 {
159     QDebug nospace = in.nospace();
160     nospace << "State: ";
161     if (state.isEmpty()) {
162         nospace << "<empty>";
163     } else {
164         if (state.hasFile()) {
165             nospace << "File=" << state.currentFile
166                     << ',' << state.currentFileTopLevel;
167         } else {
168             nospace << "<no file>";
169         }
170         nospace << '\n';
171         if (state.hasProject()) {
172             nospace << "       Project=" << state.currentProjectName
173             << ',' << state.currentProjectPath
174             << ',' << state.currentProjectTopLevel;
175 
176         } else {
177             nospace << "<no project>";
178         }
179         nospace << '\n';
180     }
181     return in;
182 }
183 
184 /*!
185     \class VcsBase::Internal::StateListener
186 
187     \brief The StateListener class connects to the relevant signals of \QC,
188     tries to find version
189     controls and emits signals to the plugin instances.
190 
191     Singleton (as not to do checks multiple times).
192 */
193 
194 class StateListener : public QObject
195 {
196     Q_OBJECT
197 
198 public:
199     explicit StateListener(QObject *parent);
200 
201     static QString windowTitleVcsTopic(const QString &filePath);
202 
203 signals:
204     void stateChanged(const VcsBase::Internal::State &s, IVersionControl *vc);
205 
206 public slots:
207     void slotStateChanged();
208 };
209 
StateListener(QObject * parent)210 StateListener::StateListener(QObject *parent) : QObject(parent)
211 {
212     connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
213             this, &StateListener::slotStateChanged);
214     connect(EditorManager::instance(), &EditorManager::currentDocumentStateChanged,
215             this, &StateListener::slotStateChanged);
216     connect(VcsManager::instance(), &VcsManager::repositoryChanged,
217             this, &StateListener::slotStateChanged);
218 
219     connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged,
220             this, &StateListener::slotStateChanged);
221     connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
222             this, &StateListener::slotStateChanged);
223 
224 
225     EditorManager::setWindowTitleVcsTopicHandler(&StateListener::windowTitleVcsTopic);
226 }
227 
windowTitleVcsTopic(const QString & filePath)228 QString StateListener::windowTitleVcsTopic(const QString &filePath)
229 {
230     QString searchPath;
231     if (!filePath.isEmpty()) {
232         searchPath = QFileInfo(filePath).absolutePath();
233     } else {
234         // use single project's information if there is only one loaded.
235         const QList<Project *> projects = SessionManager::projects();
236         if (projects.size() == 1)
237             searchPath = projects.first()->projectDirectory().toString();
238     }
239     if (searchPath.isEmpty())
240         return QString();
241     QString topLevelPath;
242     IVersionControl *vc = VcsManager::findVersionControlForDirectory(
243                 searchPath, &topLevelPath);
244     return (vc && !topLevelPath.isEmpty()) ? vc->vcsTopic(topLevelPath) : QString();
245 }
246 
displayNameOfEditor(const QString & fileName)247 static inline QString displayNameOfEditor(const QString &fileName)
248 {
249     IDocument *document = DocumentModel::documentForFilePath(FilePath::fromString(fileName));
250     if (document)
251         return document->displayName();
252     return QString();
253 }
254 
slotStateChanged()255 void StateListener::slotStateChanged()
256 {
257     // Get the current file. Are we on a temporary submit editor indicated by
258     // temporary path prefix or does the file contains a hash, indicating a project
259     // folder?
260     State state;
261     IDocument *currentDocument = EditorManager::currentDocument();
262     if (currentDocument) {
263         state.currentFile = currentDocument->filePath().toString();
264         if (state.currentFile.isEmpty() || currentDocument->isTemporary())
265             state.currentFile = VcsBase::source(currentDocument);
266     }
267 
268     // Get the file and its control. Do not use the file unless we find one
269     IVersionControl *fileControl = nullptr;
270 
271     if (!state.currentFile.isEmpty()) {
272         QFileInfo currentFi(state.currentFile);
273 
274         if (currentFi.exists()) {
275             // Quick check: Does it look like a patch?
276             const bool isPatch = state.currentFile.endsWith(".patch")
277                     || state.currentFile.endsWith(".diff");
278             if (isPatch) {
279                 // Patch: Figure out a name to display. If it is a temp file, it could be
280                 // Codepaster. Use the display name of the editor.
281                 state.currentPatchFile = state.currentFile;
282                 state.currentPatchFileDisplayName = displayNameOfEditor(state.currentPatchFile);
283                 if (state.currentPatchFileDisplayName.isEmpty())
284                     state.currentPatchFileDisplayName = currentFi.fileName();
285             }
286 
287             if (currentFi.isDir()) {
288                 state.currentFile.clear();
289                 state.currentFileDirectory = currentFi.absoluteFilePath();
290             } else {
291                 state.currentFileDirectory = currentFi.absolutePath();
292                 state.currentFileName = currentFi.fileName();
293             }
294             fileControl = VcsManager::findVersionControlForDirectory(state.currentFileDirectory,
295                                                                      &state.currentFileTopLevel);
296         }
297 
298         if (!fileControl)
299             state.clearFile();
300     }
301 
302     // Check for project, find the control
303     IVersionControl *projectControl = nullptr;
304     Project *currentProject = ProjectTree::currentProject();
305     if (!currentProject)
306         currentProject = SessionManager::startupProject();
307 
308     if (currentProject) {
309         state.currentProjectPath = currentProject->projectDirectory().toString();
310         state.currentProjectName = currentProject->displayName();
311         projectControl = VcsManager::findVersionControlForDirectory(state.currentProjectPath,
312                                                                     &state.currentProjectTopLevel);
313         if (projectControl) {
314             // If we have both, let the file's one take preference
315             if (fileControl && projectControl != fileControl)
316                 state.clearProject();
317         } else {
318             state.clearProject(); // No control found
319         }
320     }
321 
322     // Assemble state and emit signal.
323     IVersionControl *vc = fileControl;
324     if (!vc)
325         vc = projectControl;
326     if (!vc)
327         state.clearPatchFile(); // Need a repository to patch
328 
329     qCDebug(stateLog).noquote() << "VC:" << (vc ? vc->displayName() : QString("None")) << state;
330     EditorManager::updateWindowTitles();
331     emit stateChanged(state, vc);
332 }
333 
334 } // namespace Internal
335 
336 class VcsBasePluginStateData : public QSharedData
337 {
338 public:
339     Internal::State m_state;
340 };
341 
342 /*!
343     \class  VcsBase::VcsBasePluginState
344 
345     \brief The VcsBasePluginState class provides relevant state information
346     about the VCS plugins.
347 
348     Qt Creator's state relevant to VCS plugins is a tuple of
349 
350     \list
351     \li Current file and it's version system control/top level
352     \li Current project and it's version system control/top level
353     \endlist
354 
355     \sa VcsBase::VcsBasePlugin
356 */
357 
VcsBasePluginState()358 VcsBasePluginState::VcsBasePluginState() : data(new VcsBasePluginStateData)
359 { }
360 
361 
VcsBasePluginState(const VcsBasePluginState & rhs)362 VcsBasePluginState::VcsBasePluginState(const VcsBasePluginState &rhs) : data(rhs.data)
363 { }
364 
365 VcsBasePluginState::~VcsBasePluginState() = default;
366 
operator =(const VcsBasePluginState & rhs)367 VcsBasePluginState &VcsBasePluginState::operator=(const VcsBasePluginState &rhs)
368 {
369     if (this != &rhs)
370         data.operator=(rhs.data);
371     return *this;
372 }
373 
currentFile() const374 QString VcsBasePluginState::currentFile() const
375 {
376     return data->m_state.currentFile;
377 }
378 
currentFileName() const379 QString VcsBasePluginState::currentFileName() const
380 {
381     return data->m_state.currentFileName;
382 }
383 
currentFileTopLevel() const384 QString VcsBasePluginState::currentFileTopLevel() const
385 {
386     return data->m_state.currentFileTopLevel;
387 }
388 
currentFileDirectory() const389 QString VcsBasePluginState::currentFileDirectory() const
390 {
391     return data->m_state.currentFileDirectory;
392 }
393 
relativeCurrentFile() const394 QString VcsBasePluginState::relativeCurrentFile() const
395 {
396     QTC_ASSERT(hasFile(), return QString());
397     return QDir(data->m_state.currentFileTopLevel).relativeFilePath(data->m_state.currentFile);
398 }
399 
currentPatchFile() const400 QString VcsBasePluginState::currentPatchFile() const
401 {
402     return data->m_state.currentPatchFile;
403 }
404 
currentPatchFileDisplayName() const405 QString VcsBasePluginState::currentPatchFileDisplayName() const
406 {
407     return data->m_state.currentPatchFileDisplayName;
408 }
409 
currentProjectPath() const410 QString VcsBasePluginState::currentProjectPath() const
411 {
412     return data->m_state.currentProjectPath;
413 }
414 
currentProjectName() const415 QString VcsBasePluginState::currentProjectName() const
416 {
417     return data->m_state.currentProjectName;
418 }
419 
currentProjectTopLevel() const420 QString VcsBasePluginState::currentProjectTopLevel() const
421 {
422     return data->m_state.currentProjectTopLevel;
423 }
424 
relativeCurrentProject() const425 QString VcsBasePluginState::relativeCurrentProject() const
426 {
427     QTC_ASSERT(hasProject(), return QString());
428     if (data->m_state.currentProjectTopLevel != data->m_state.currentProjectPath)
429         return QDir(data->m_state.currentProjectTopLevel).relativeFilePath(data->m_state.currentProjectPath);
430     return QString();
431 }
432 
hasTopLevel() const433 bool VcsBasePluginState::hasTopLevel() const
434 {
435     return data->m_state.hasFile() || data->m_state.hasProject();
436 }
437 
topLevel() const438 QString VcsBasePluginState::topLevel() const
439 {
440     return hasFile() ? data->m_state.currentFileTopLevel : data->m_state.currentProjectTopLevel;
441 }
442 
equals(const Internal::State & rhs) const443 bool VcsBasePluginState::equals(const Internal::State &rhs) const
444 {
445     return data->m_state.equals(rhs);
446 }
447 
equals(const VcsBasePluginState & rhs) const448 bool VcsBasePluginState::equals(const VcsBasePluginState &rhs) const
449 {
450     return equals(rhs.data->m_state);
451 }
452 
clear()453 void VcsBasePluginState::clear()
454 {
455     data->m_state.clear();
456 }
457 
setState(const Internal::State & s)458 void VcsBasePluginState::setState(const Internal::State &s)
459 {
460     data->m_state = s;
461 }
462 
isEmpty() const463 bool VcsBasePluginState::isEmpty() const
464 {
465     return data->m_state.isEmpty();
466 }
467 
hasFile() const468 bool VcsBasePluginState::hasFile() const
469 {
470     return data->m_state.hasFile();
471 }
472 
hasPatchFile() const473 bool VcsBasePluginState::hasPatchFile() const
474 {
475     return !data->m_state.currentPatchFile.isEmpty();
476 }
477 
hasProject() const478 bool VcsBasePluginState::hasProject() const
479 {
480     return data->m_state.hasProject();
481 }
482 
operator <<(QDebug in,const VcsBasePluginState & state)483 VCSBASE_EXPORT QDebug operator<<(QDebug in, const VcsBasePluginState &state)
484 {
485     in << state.data->m_state;
486     return in;
487 }
488 
489 /*!
490     \class VcsBase::VcsBasePlugin
491 
492     \brief The VcsBasePlugin class is the base class for all version control
493     plugins.
494 
495     The plugin connects to the
496     relevant change signals in Qt Creator and calls the virtual
497     updateActions() for the plugins to update their menu actions
498     according to the new state. This is done centrally to avoid
499     single plugins repeatedly invoking searches/QFileInfo on files,
500     etc.
501 
502     Independently, there are accessors for current patch files, which return
503     a file name if the current file could be a patch file which could be applied
504     and a repository exists.
505 
506     If current file/project are managed
507     by different version controls, the project is discarded and only
508     the current file is taken into account, allowing to do a diff
509     also when the project of a file is not opened.
510 
511     When triggering an action, a copy of the state should be made to
512     keep it, as it may rapidly change due to context changes, etc.
513 
514     The class also detects the VCS plugin submit editor closing and calls
515     the virtual submitEditorAboutToClose() to trigger the submit process.
516 */
517 
supportsRepositoryCreation() const518 bool VcsBasePluginPrivate::supportsRepositoryCreation() const
519 {
520     return supportsOperation(IVersionControl::CreateRepositoryOperation);
521 }
522 
523 static Internal::StateListener *m_listener = nullptr;
524 
VcsBasePluginPrivate(const Context & context)525 VcsBasePluginPrivate::VcsBasePluginPrivate(const Context &context)
526     : m_context(context)
527 {
528     Internal::VcsPlugin *plugin = Internal::VcsPlugin::instance();
529     connect(plugin, &Internal::VcsPlugin::submitEditorAboutToClose,
530             this, &VcsBasePluginPrivate::slotSubmitEditorAboutToClose);
531     // First time: create new listener
532     if (!m_listener)
533         m_listener = new Internal::StateListener(plugin);
534     connect(m_listener, &Internal::StateListener::stateChanged,
535             this, &VcsBasePluginPrivate::slotStateChanged);
536     // VCSes might have become (un-)available, so clear the VCS directory cache
537     connect(this, &IVersionControl::configurationChanged,
538             VcsManager::instance(), &VcsManager::clearVersionControlCache);
539     connect(this, &IVersionControl::configurationChanged,
540             m_listener, &Internal::StateListener::slotStateChanged);
541 }
542 
extensionsInitialized()543 void VcsBasePluginPrivate::extensionsInitialized()
544 {
545     // Initialize enable menus.
546     m_listener->slotStateChanged();
547 }
548 
slotSubmitEditorAboutToClose(VcsBaseSubmitEditor * submitEditor,bool * result)549 void VcsBasePluginPrivate::slotSubmitEditorAboutToClose(VcsBaseSubmitEditor *submitEditor, bool *result)
550 {
551     qCDebug(baseLog) << this << "plugin's submit editor" << m_submitEditor
552                      << (m_submitEditor ? m_submitEditor->document()->id().name() : QByteArray())
553                      << "closing submit editor" << submitEditor
554                      << (submitEditor ? submitEditor->document()->id().name() : QByteArray());
555     if (submitEditor == m_submitEditor)
556         *result = submitEditorAboutToClose();
557 }
558 
slotStateChanged(const Internal::State & newInternalState,Core::IVersionControl * vc)559 void VcsBasePluginPrivate::slotStateChanged(const Internal::State &newInternalState, Core::IVersionControl *vc)
560 {
561     if (vc == this) {
562         // We are directly affected: Change state
563         if (!m_state.equals(newInternalState)) {
564             m_state.setState(newInternalState);
565             updateActions(VcsEnabled);
566             ICore::addAdditionalContext(m_context);
567         }
568     } else {
569         // Some other VCS plugin or state changed: Reset us to empty state.
570         const ActionState newActionState = vc ? OtherVcsEnabled : NoVcsEnabled;
571         if (m_actionState != newActionState || !m_state.isEmpty()) {
572             m_actionState = newActionState;
573             const VcsBasePluginState emptyState;
574             m_state = emptyState;
575             updateActions(newActionState);
576         }
577         ICore::removeAdditionalContext(m_context);
578     }
579 }
580 
currentState() const581 const VcsBasePluginState &VcsBasePluginPrivate::currentState() const
582 {
583     return m_state;
584 }
585 
enableMenuAction(ActionState as,QAction * menuAction) const586 bool VcsBasePluginPrivate::enableMenuAction(ActionState as, QAction *menuAction) const
587 {
588     qCDebug(baseLog) << "enableMenuAction" << menuAction->text() << as;
589     switch (as) {
590     case NoVcsEnabled: {
591         const bool supportsCreation = supportsRepositoryCreation();
592         menuAction->setVisible(supportsCreation);
593         menuAction->setEnabled(supportsCreation);
594         return supportsCreation;
595     }
596     case OtherVcsEnabled:
597         menuAction->setVisible(false);
598         return false;
599     case VcsEnabled:
600         menuAction->setVisible(true);
601         menuAction->setEnabled(true);
602         break;
603     }
604     return true;
605 }
606 
commitDisplayName() const607 QString VcsBasePluginPrivate::commitDisplayName() const
608 {
609     return tr("Commit", "name of \"commit\" action of the VCS.");
610 }
611 
promptBeforeCommit()612 bool VcsBasePluginPrivate::promptBeforeCommit()
613 {
614     return DocumentManager::saveAllModifiedDocuments(tr("Save before %1?")
615                                                      .arg(commitDisplayName().toLower()));
616 }
617 
promptToDeleteCurrentFile()618 void VcsBasePluginPrivate::promptToDeleteCurrentFile()
619 {
620     const VcsBasePluginState state = currentState();
621     QTC_ASSERT(state.hasFile(), return);
622     const bool rc = VcsManager::promptToDelete(this, state.currentFile());
623     if (!rc)
624         QMessageBox::warning(ICore::dialogParent(), tr("Version Control"),
625                              tr("The file \"%1\" could not be deleted.").
626                              arg(QDir::toNativeSeparators(state.currentFile())),
627                              QMessageBox::Ok);
628 }
629 
ask(QWidget * parent,const QString & title,const QString & question,bool defaultValue=true)630 static inline bool ask(QWidget *parent, const QString &title, const QString &question, bool defaultValue = true)
631 
632 {
633     const QMessageBox::StandardButton defaultButton = defaultValue ? QMessageBox::Yes : QMessageBox::No;
634     return QMessageBox::question(parent, title, question, QMessageBox::Yes|QMessageBox::No, defaultButton) == QMessageBox::Yes;
635 }
636 
createRepository()637 void VcsBasePluginPrivate::createRepository()
638 {
639     QTC_ASSERT(supportsOperation(IVersionControl::CreateRepositoryOperation), return);
640     // Find current starting directory
641     QString directory;
642     if (const Project *currentProject = ProjectTree::currentProject())
643         directory = currentProject->projectFilePath().absolutePath().toString();
644     // Prompt for a directory that is not under version control yet
645     QWidget *mw = ICore::dialogParent();
646     do {
647         directory = QFileDialog::getExistingDirectory(mw, tr("Choose Repository Directory"), directory);
648         if (directory.isEmpty())
649             return;
650         const IVersionControl *managingControl = VcsManager::findVersionControlForDirectory(directory);
651         if (managingControl == nullptr)
652             break;
653         const QString question = tr("The directory \"%1\" is already managed by a version control system (%2)."
654                                     " Would you like to specify another directory?").arg(directory, managingControl->displayName());
655 
656         if (!ask(mw, tr("Repository already under version control"), question))
657             return;
658     } while (true);
659     // Create
660     const bool rc = vcsCreateRepository(directory);
661     const QString nativeDir = QDir::toNativeSeparators(directory);
662     if (rc) {
663         QMessageBox::information(mw, tr("Repository Created"),
664                                  tr("A version control repository has been created in %1.").
665                                  arg(nativeDir));
666     } else {
667         QMessageBox::warning(mw, tr("Repository Creation Failed"),
668                                  tr("A version control repository could not be created in %1.").
669                                  arg(nativeDir));
670     }
671 }
672 
setSubmitEditor(VcsBaseSubmitEditor * submitEditor)673 void VcsBasePluginPrivate::setSubmitEditor(VcsBaseSubmitEditor *submitEditor)
674 {
675     m_submitEditor = submitEditor;
676 }
677 
submitEditor() const678 VcsBaseSubmitEditor *VcsBasePluginPrivate::submitEditor() const
679 {
680     return m_submitEditor;
681 }
682 
raiseSubmitEditor() const683 bool VcsBasePluginPrivate::raiseSubmitEditor() const
684 {
685     if (!m_submitEditor)
686         return false;
687     EditorManager::activateEditor(m_submitEditor, EditorManager::IgnoreNavigationHistory);
688     return true;
689 }
690 
691 // Find top level for version controls like git/Mercurial that have
692 // a directory at the top of the repository.
693 // Note that checking for the existence of files is preferred over directories
694 // since checking for directories can cause them to be created when
695 // AutoFS is used (due its automatically creating mountpoints when querying
696 // a directory). In addition, bail out when reaching the home directory
697 // of the user or root (generally avoid '/', where mountpoints are created).
findRepositoryForDirectory(const QString & dirS,const QString & checkFile)698 QString findRepositoryForDirectory(const QString &dirS, const QString &checkFile)
699 {
700     qCDebug(findRepoLog) << ">" << dirS << checkFile;
701     QTC_ASSERT(!dirS.isEmpty() && !checkFile.isEmpty(), return QString());
702 
703     const QString root = QDir::rootPath();
704     const QString home = QDir::homePath();
705 
706     QDir directory(dirS);
707     do {
708         const QString absDirPath = directory.absolutePath();
709         if (absDirPath == root || absDirPath == home)
710             break;
711 
712         if (QFileInfo(directory, checkFile).isFile()) {
713             qCDebug(findRepoLog) << "<" << absDirPath;
714             return absDirPath;
715         }
716     } while (!directory.isRoot() && directory.cdUp());
717     qCDebug(findRepoLog) << "< bailing out at" << directory.absolutePath();
718     return QString();
719 }
720 
721 // Is SSH prompt configured?
sshPrompt()722 QString sshPrompt()
723 {
724     return Internal::VcsPlugin::instance()->settings().sshPasswordPrompt.value();
725 }
726 
isSshPromptConfigured()727 bool isSshPromptConfigured()
728 {
729     return !sshPrompt().isEmpty();
730 }
731 
732 static const char SOURCE_PROPERTY[] = "qtcreator_source";
733 
setSource(IDocument * document,const QString & source)734 void setSource(IDocument *document, const QString &source)
735 {
736     document->setProperty(SOURCE_PROPERTY, source);
737     m_listener->slotStateChanged();
738 }
739 
source(IDocument * document)740 QString source(IDocument *document)
741 {
742     return document->property(SOURCE_PROPERTY).toString();
743 }
744 
setProcessEnvironment(Environment * e,bool forceCLocale,const QString & sshPromptBinary)745 void setProcessEnvironment(Environment *e, bool forceCLocale, const QString &sshPromptBinary)
746 {
747     if (forceCLocale) {
748         e->set("LANG", "C");
749         e->set("LANGUAGE", "C");
750     }
751     if (!sshPromptBinary.isEmpty())
752         e->set("SSH_ASKPASS", sshPromptBinary);
753 }
754 
755 } // namespace VcsBase
756 
757 #include "vcsbaseplugin.moc"
758