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