diff --git a/doc/qtcreator/src/qtcreator-toc.qdoc b/doc/qtcreator/src/qtcreator-toc.qdoc index 62fbde3516e..f24a85857d1 100644 --- a/doc/qtcreator/src/qtcreator-toc.qdoc +++ b/doc/qtcreator/src/qtcreator-toc.qdoc @@ -46,6 +46,7 @@ \li \l{Using Bazaar} \li \l{Using ClearCase} \li \l{Using CVS} + \li \l{Using Fossil} \li \l{Using Git} \li \l{Using GitLab} \li \l{Using Mercurial} diff --git a/doc/qtcreator/src/vcs/creator-only/creator-vcs-cvs.qdoc b/doc/qtcreator/src/vcs/creator-only/creator-vcs-cvs.qdoc index 540972e83dd..53e7fc604ed 100644 --- a/doc/qtcreator/src/vcs/creator-only/creator-vcs-cvs.qdoc +++ b/doc/qtcreator/src/vcs/creator-only/creator-vcs-cvs.qdoc @@ -10,7 +10,7 @@ /*! \previouspage creator-vcs-clearcase.html \page creator-vcs-cvs.html - \nextpage creator-vcs-git.html + \nextpage creator-vcs-fossil.html \title Using CVS diff --git a/doc/qtcreator/src/vcs/creator-only/creator-vcs-fossil.qdoc b/doc/qtcreator/src/vcs/creator-only/creator-vcs-fossil.qdoc new file mode 100644 index 00000000000..6148a58076b --- /dev/null +++ b/doc/qtcreator/src/vcs/creator-only/creator-vcs-fossil.qdoc @@ -0,0 +1,65 @@ +// Copyright (C) 2018 Artur Shepilko +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page creator-vcs-fossil.html + \previouspage creator-vcs-cvs.html + \nextpage creator-vcs-git.html + + \title Using Fossil + + Fossil is an open source distributed version control system, designed + and developed by the creator of SQLite. A stand-alone Fossil executable + contains a source control management engine, web interface, issue tracker, + wiki, and built-in web server. Fossil is available for Linux, Windows, + and \macos. + + To use Fossil from \QC, you must install and configure it, as described in + the following sections. + + \section1 Configuring Fossil + + \list 1 + \li Download the \l{http://fossil-scm.org}{Fossil SCM client} and + install the \c fossil executable file in your \c PATH. + + \li Create or designate a directory to store local Fossil repositories + and remote clones. For example: \c ~/fossils/qt. + + \li Select \uicontrol Edit > \uicontrol Preferences > + \uicontrol {Version Control} > \uicontrol Fossil, and set the + designated directory in the \uicontrol {Default path} field. + \endlist + + To create a local Fossil repository, select \uicontrol Tools > + \uicontrol Fossil > \uicontrol {Create Repository}. + + To clone a remote Fossil repository, select \uicontrol File > + \uicontrol {New Project} > \uicontrol {Import Project} > + \uicontrol {Fossil Clone}. + + \section1 Additional Fossil Functions + + In addition to the standard version control system functions described in + \l {Using Version Control Systems}, the \uicontrol Fossil submenu contains + the following items: + + \table + \header + \li Menu Item + \li Description + \row + \li \uicontrol Pull + \li Pull changes from the remote repository. + \row + \li \uicontrol Push + \li Push committed changes to the remote repository. + \row + \li \uicontrol Update + \li Change the version of the current checkout. Any uncommitted + changes are retained and applied to the new checkout. + \row + \li \uicontrol Settings + \li Configure the settings of the local repository. + \endtable +*/ diff --git a/doc/qtcreator/src/vcs/creator-only/creator-vcs.qdoc b/doc/qtcreator/src/vcs/creator-only/creator-vcs.qdoc index c935dfbc2d0..3631b4023a5 100644 --- a/doc/qtcreator/src/vcs/creator-only/creator-vcs.qdoc +++ b/doc/qtcreator/src/vcs/creator-only/creator-vcs.qdoc @@ -37,14 +37,12 @@ \li \l{http://www.nongnu.org/cvs/} \li \row - \li \l{https://doc.qt.io/qtcreator/creator-vcs-fossil.html}{Fossil} + \li \l{Using Fossil}{Fossil} \li \l{https://fossil-scm.org/index.html/doc/trunk/www/index.wiki} - \li To use Fossil, you need to build the Fossil plugin from - \l{https://code.qt.io/cgit/qt-creator/plugin-fossil-scm.git/} - {sources}, and install Fossil as described in - \l{https://doc.qt.io/qtcreator/creator-vcs-fossil.html} - {Qt Creator Fossil Plugin Manual}. - \row + \li Disabled by default. To enable the plugin, select + \uicontrol Help > \uicontrol {About Plugins} > + \uicontrol {Version Control} > \uicontrol Fossil. + \row \li \l{Using Git}{Git} \li \l{http://git-scm.com/} \li Git version 1.9.0, or later @@ -66,8 +64,6 @@ Disabled by default. To enable the plugin, select \uicontrol Help > \uicontrol {About Plugins} > \uicontrol {Version Control} > \uicontrol Perforce. - Then select \uicontrol {Restart Now} to restart \QC - and load the plugin. \row \li \l{Using Subversion}{Subversion} \li \l{http://subversion.apache.org/} @@ -103,8 +99,7 @@ \li \l{Using Bazaar} \li \l{Using ClearCase} \li \l{Using CVS} - \li \l{https://doc.qt.io/qtcreator/creator-vcs-fossil.html} - {Qt Creator Fossil Plugin Manual} + \li \l{Using Fossil} \li \l{Using Git} \li \l{Using GitLab} \li \l{Using Mercurial} diff --git a/doc/qtcreator/src/vcs/creator-vcs-git.qdoc b/doc/qtcreator/src/vcs/creator-vcs-git.qdoc index 21da4da3f04..1acf233eb0b 100644 --- a/doc/qtcreator/src/vcs/creator-vcs-git.qdoc +++ b/doc/qtcreator/src/vcs/creator-vcs-git.qdoc @@ -13,7 +13,7 @@ \previouspage studio-developer-topics.html \nextpage studio-porting-projects.html \else - \previouspage creator-vcs-cvs.html + \previouspage creator-vcs-fossil.html \nextpage creator-vcs-gitlab.html \endif diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/CMakeLists.txt b/share/qtcreator/templates/wizards/projects/qtquickapplication/CMakeLists.txt index d5725099e15..63e2498dff2 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/CMakeLists.txt +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication/CMakeLists.txt @@ -2,10 +2,22 @@ cmake_minimum_required(VERSION 3.16) project(%{ProjectName} VERSION 0.1 LANGUAGES CXX) +@if !%{HasQSPSetup} set(CMAKE_AUTOMOC ON) +@endif set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 6.2 REQUIRED COMPONENTS Quick) +find_package(Qt6 %{MinimumSupportedQtVersion} REQUIRED COMPONENTS Quick) + +@if %{HasQSPSetup} +@if %{UsesAutoResourcePrefix} +qt_standard_project_setup( + MIN_VERSION 6.5 +) +@else +qt_standard_project_setup() +@endif +@endif qt_add_executable(%{TargetName} main.cpp @@ -14,7 +26,7 @@ qt_add_executable(%{TargetName} qt_add_qml_module(%{TargetName} URI %{ProjectName} VERSION 1.0 - QML_FILES main.qml + QML_FILES Main.qml ) set_target_properties(%{TargetName} PROPERTIES diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/main.qml.tpl b/share/qtcreator/templates/wizards/projects/qtquickapplication/Main.qml.tpl similarity index 100% rename from share/qtcreator/templates/wizards/projects/qtquickapplication/main.qml.tpl rename to share/qtcreator/templates/wizards/projects/qtquickapplication/Main.qml.tpl diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/main.cpp b/share/qtcreator/templates/wizards/projects/qtquickapplication/main.cpp index bdfd80e1c35..155667fb122 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/main.cpp +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication/main.cpp @@ -10,13 +10,25 @@ int main(int argc, char *argv[]) QGuiApplication app(argc, argv); QQmlApplicationEngine engine; - const QUrl url(u"qrc:/%{JS: value('ProjectName')}/main.qml"_qs); +@if !%{HasLoadFromModule} + const QUrl url(u"qrc:/%{JS: value('ProjectName')}/Main.qml"_qs); +@endif +@if %{HasFailureSignal} + QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, + &app, []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); +@else QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); +@endif +@if %{HasLoadFromModule} + engine.loadFromModule("%{JS: value('ProjectName')}", "Main"); +@else engine.load(url); +@endif return app.exec(); } diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/tmpl.qbs b/share/qtcreator/templates/wizards/projects/qtquickapplication/tmpl.qbs index 3c5b8127cc9..050ace84150 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/tmpl.qbs +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication/tmpl.qbs @@ -16,6 +16,6 @@ CppApplication { Group { Qt.core.resourcePrefix: "%{ProjectName}/" fileTags: ["qt.qml.qml", "qt.core.resource_data"] - files: ["main.qml"] + files: ["Main.qml"] } } diff --git a/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json index 885a8487e31..88d63e19f9a 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickapplication/wizard.json @@ -17,6 +17,10 @@ { "key": "MainCppFileName", "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" }, { "key": "UseVirtualKeyboardByDefault", "value": "%{JS: value('Plugins').indexOf('Boot2Qt') >= 0 || value('Plugins').indexOf('Boot2QtQdb') >= 0 }" }, { "key": "TargetName", "value": "%{JS: 'app' + value('ProjectName') }" }, + { "key": "HasQSPSetup", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.2' }"}, + { "key": "HasFailureSignal", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.3' }"}, + { "key": "UsesAutoResourcePrefix", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.4' && value('BuildSystem') === 'cmake' }"}, + { "key": "HasLoadFromModule", "value": "%{JS: value('MinimumSupportedQtVersion') > '6.4' && value('UsesAutoResourcePrefix') }"}, { "key": "QdsWizardPath", "value": "%{IDE:ResourcePath}/qmldesigner/studio_templates/projects" }, { "key": "QdsProjectStyle", "value": "%{JS: value('BuildSystem') === 'cmake' ? %{QdsProjectStyleInput} : false }" }, { "key": "NoQdsProjectStyle", "value": "%{JS: !%{QdsProjectStyle} }" }, @@ -104,6 +108,16 @@ { "checked": "%{UseVirtualKeyboardByDefault}" } + }, + { + "name": "MinimumSupportedQtVersion", + "trDisplayName": "The minimum version of Qt you want to build the application for", + "type": "ComboBox", + "data": + { + "items": [ "6.2", "6.4", "6.5" ], + "index": 1 + } } ] }, @@ -146,8 +160,8 @@ "condition": "%{NoQdsProjectStyle}" }, { - "source": "main.qml.tpl", - "target": "main.qml", + "source": "Main.qml.tpl", + "target": "Main.qml", "openInEditor": true, "condition": "%{NoQdsProjectStyle}" }, diff --git a/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json b/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json index ac45450734c..aba8309365b 100644 --- a/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json +++ b/share/qtcreator/templates/wizards/projects/qtquickuiprototype/wizard.json @@ -9,17 +9,12 @@ "icon": "qtquickuiprototype.png", "iconKind": "Themed", "enabled": "%{JS: value('Plugins').indexOf('QmlProjectManager') >= 0}", - "featuresRequired": [ "QtSupport.Wizards.FeatureQtQuickProject", "QtSupport.Wizards.FeatureQt" ], + "featuresRequired": [ "QtSupport.Wizards.FeatureQtQuickProject", "QtSupport.Wizards.FeatureQtQuick.6" ], "options": [ { "key": "QmlProjectFileName", "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qmlproject')}" }, - { "key": "IsQt6", "value": "%{JS: value('QtVersion').IsQt6}" }, { "key": "MainQmlFileName", "value": "%{JS: Util.fileName(value('ProjectName'), 'qml')}" }, - { "key": "QtQuickVersion", "value": "%{JS: value('QtVersion').QtQuickVersion}" }, - { "key": "QtQuickWindowVersion", "value": "%{JS: value('QtVersion').QtQuickWindowVersion}" }, - { "key": "QtQuickVirtualKeyboardImport", "value": "%{JS: value('QtVersion').QtQuickVirtualKeyboardImport}" }, - { "key": "QtQuickFeature", "value": "%{JS: (value('QtQuickVersion')=='') ? 'QtSupport.Wizards.FeatureQtQuick.6' : 'QtSupport.Wizards.FeatureQtQuick.%{QtQuickVersion}'}" }, { "key": "UseVirtualKeyboardByDefault", "value": "%{JS: value('Plugins').indexOf('Boot2Qt') >= 0 || value('Plugins').indexOf('Boot2QtQdb') >= 0}" } ], @@ -36,68 +31,6 @@ "typeId": "Fields", "data": [ - { - "name": "QtVersion", - "trDisplayName": "Minimum required Qt version:", - "type": "ComboBox", - "data": - { - "index": 1, - "items": - [ - { - "trKey": "Qt 6", - "value": - { - "QtQuickVersion": "", - "QtQuickWindowVersion": "", - "QtQuickVirtualKeyboardImport": "", - "IsQt6": true - } - }, - { - "trKey": "Qt 5.15", - "value": - { - "QtQuickVersion": "2.15", - "QtQuickWindowVersion": "2.15", - "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.15", - "IsQt6": false - } - }, - { - "trKey": "Qt 5.14", - "value": - { - "QtQuickVersion": "2.14", - "QtQuickWindowVersion": "2.14", - "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.14", - "IsQt6": false - } - }, - { - "trKey": "Qt 5.13", - "value": - { - "QtQuickVersion": "2.13", - "QtQuickWindowVersion": "2.13", - "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.4", - "IsQt6": false - } - }, - { - "trKey": "Qt 5.12", - "value": - { - "QtQuickVersion": "2.12", - "QtQuickWindowVersion": "2.12", - "QtQuickVirtualKeyboardImport": "QtQuick.VirtualKeyboard 2.4", - "IsQt6": false - } - } - ] - } - }, { "name": "UseVirtualKeyboard", "trDisplayName": "Use Qt Virtual Keyboard", @@ -116,7 +49,7 @@ "enabled": "%{JS: ! %{IsSubproject}}", "data": { "projectFilePath": "%{QmlProjectFileName}", - "requiredFeatures": [ "QtSupport.Wizards.FeatureQt", "%{QtQuickFeature}" ] + "requiredFeatures": [ "QtSupport.Wizards.FeatureQtQuickProject", "QtSupport.Wizards.FeatureQtQuick.6" ] } }, { @@ -137,7 +70,7 @@ "openAsProject": true }, { - "source": "../qtquickapplication/empty/main.qml.tpl", + "source": "../qtquickapplication/Main.qml.tpl", "target": "%{ProjectDirectory}/%{MainQmlFileName}", "openInEditor": true }, diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h index dfc4af4108a..41aaf7dcccb 100644 --- a/src/libs/utils/filepath.h +++ b/src/libs/utils/filepath.h @@ -283,6 +283,7 @@ public: std::function environment; std::function isSameDevice; std::function(const FilePath &)> localSource; + std::function openTerminal; }; } // Utils diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp index 25e40fad26a..547e03e3c2b 100644 --- a/src/libs/utils/qtcprocess.cpp +++ b/src/libs/utils/qtcprocess.cpp @@ -1348,83 +1348,6 @@ QString QtcProcess::errorString() const // Path utilities -// Locate a binary in a directory, applying all kinds of -// extensions the operating system supports. -static QString checkBinary(const QDir &dir, const QString &binary) -{ - // naive UNIX approach - const QFileInfo info(dir.filePath(binary)); - if (info.isFile() && info.isExecutable()) - return info.absoluteFilePath(); - - // Does the OS have some weird extension concept or does the - // binary have a 3 letter extension? - if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost()) - return {}; - const int dotIndex = binary.lastIndexOf(QLatin1Char('.')); - if (dotIndex != -1 && dotIndex == binary.size() - 4) - return {}; - - switch (HostOsInfo::hostOs()) { - case OsTypeLinux: - case OsTypeOtherUnix: - case OsTypeOther: - break; - case OsTypeWindows: { - static const char *windowsExtensions[] = {".cmd", ".bat", ".exe", ".com"}; - // Check the Windows extensions using the order - const int windowsExtensionCount = sizeof(windowsExtensions)/sizeof(const char*); - for (int e = 0; e < windowsExtensionCount; e ++) { - const QFileInfo windowsBinary(dir.filePath(binary + QLatin1String(windowsExtensions[e]))); - if (windowsBinary.isFile() && windowsBinary.isExecutable()) - return windowsBinary.absoluteFilePath(); - } - } - break; - case OsTypeMac: { - // Check for Mac app folders - const QFileInfo appFolder(dir.filePath(binary + QLatin1String(".app"))); - if (appFolder.isDir()) { - QString macBinaryPath = appFolder.absoluteFilePath(); - macBinaryPath += QLatin1String("/Contents/MacOS/"); - macBinaryPath += binary; - const QFileInfo macBinary(macBinaryPath); - if (macBinary.isFile() && macBinary.isExecutable()) - return macBinary.absoluteFilePath(); - } - } - break; - } - return {}; -} - -QString QtcProcess::locateBinary(const QString &path, const QString &binary) -{ - // Absolute file? - const QFileInfo absInfo(binary); - if (absInfo.isAbsolute()) - return checkBinary(absInfo.dir(), absInfo.fileName()); - - // Windows finds binaries in the current directory - if (HostOsInfo::isWindowsHost()) { - const QString currentDirBinary = checkBinary(QDir::current(), binary); - if (!currentDirBinary.isEmpty()) - return currentDirBinary; - } - - const QStringList paths = path.split(HostOsInfo::pathListSeparator()); - if (paths.empty()) - return {}; - const QStringList::const_iterator cend = paths.constEnd(); - for (QStringList::const_iterator it = paths.constBegin(); it != cend; ++it) { - const QDir dir(*it); - const QString rc = checkBinary(dir, binary); - if (!rc.isEmpty()) - return rc; - } - return {}; -} - Environment QtcProcess::systemEnvironmentForBinary(const FilePath &filePath) { if (filePath.needsDevice()) { diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h index ae99bd666c2..0451c0cd046 100644 --- a/src/libs/utils/qtcprocess.h +++ b/src/libs/utils/qtcprocess.h @@ -127,11 +127,8 @@ public: // These (or some of them) may be potentially moved outside of the class. // For some we may aggregate in another public utils class (or subclass of QtcProcess)? - // TODO: How below 2 methods relate to QtcProcess? - // Action: move/merge them somewhere else, FilePath::searchInPath() ? - // Helpers to find binaries. Do not use it for other path variables - // and file types. - static QString locateBinary(const QString &path, const QString &binary); + // TODO: How below method relates to QtcProcess? + // Action: move/merge it somewhere else static QString normalizeNewlines(const QString &text); // TODO: Unused currently? Should it serve as a compartment for contrary of remoteEnvironment? diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp index 2c1887ea41e..9b72dc58006 100644 --- a/src/libs/utils/tasktree.cpp +++ b/src/libs/utils/tasktree.cpp @@ -154,9 +154,10 @@ public: int currentLimit() const; TaskAction childDone(bool success); void groupDone(bool success); + void treeDone(bool success); void invokeEndHandler(bool success); - void resetSuccessBit(); - void updateSuccessBit(bool success); + void resetSuccessBit(); // only on start + void updateSuccessBit(bool success); // only on childDone void createStorages(); void deleteStorages(); @@ -233,11 +234,7 @@ public: QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " "exist in task tree. Its handlers will never be called.")); } - const TaskAction action = m_root->start(); - if (action == TaskAction::StopWithDone) - emitDone(); - else if (action == TaskAction::StopWithError) - emitError(); + m_root->start(); } void stop() { QTC_ASSERT(m_root, return); @@ -366,17 +363,17 @@ TaskAction TaskContainer::start() const bool success = groupAction == TaskAction::StopWithDone; m_taskTreePrivate->advanceProgress(m_taskCount); invokeEndHandler(success); + groupDone(success); return groupAction; } resetSuccessBit(); - - GuardLocker locker(m_startGuard); return startChildren(0); } TaskAction TaskContainer::startChildren(int nextChild) { + GuardLocker locker(m_startGuard); const int childCount = m_children.size(); for (int i = nextChild; i < childCount; ++i) { const int limit = currentLimit(); @@ -396,6 +393,7 @@ TaskAction TaskContainer::startChildren(int nextChild) for (int j = i + 1; j < limit; ++j) skippedTaskCount += m_children.at(j)->taskCount(); m_taskTreePrivate->advanceProgress(skippedTaskCount); + treeDone(finalizeAction == TaskAction::StopWithDone); return finalizeAction; } @@ -458,13 +456,19 @@ TaskAction TaskContainer::childDone(bool success) void TaskContainer::groupDone(bool success) { - if (isStarting()) - return; - if (m_parentContainer) { - m_parentContainer->childDone(success); + if (!m_parentContainer->isStarting()) + m_parentContainer->childDone(success); return; } + if (!isStarting()) + treeDone(success); +} + +void TaskContainer::treeDone(bool success) +{ + if (m_parentContainer) + return; if (success) m_taskTreePrivate->emitDone(); else diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index ebcab57a361..8c4f2e2bb68 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -45,6 +45,7 @@ add_subdirectory(cvs) add_subdirectory(designer) add_subdirectory(docker) add_subdirectory(fakevim) +add_subdirectory(fossil) add_subdirectory(genericprojectmanager) add_subdirectory(git) add_subdirectory(mercurial) diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp index 611f7bba5c4..5477bf14bfe 100644 --- a/src/plugins/android/androidconfigurations.cpp +++ b/src/plugins/android/androidconfigurations.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include diff --git a/src/plugins/autotest/testresultspane.cpp b/src/plugins/autotest/testresultspane.cpp index aacd12adcff..32dd8425cbe 100644 --- a/src/plugins/autotest/testresultspane.cpp +++ b/src/plugins/autotest/testresultspane.cpp @@ -609,7 +609,7 @@ void TestResultsPane::onCustomContextMenuRequested(const QPoint &pos) QAction *action = new QAction(Tr::tr("Copy"), &menu); action->setShortcut(QKeySequence(QKeySequence::Copy)); action->setEnabled(resultsAvailable && clicked.isValid()); - connect(action, &QAction::triggered, this, [this, clicked] { + connect(action, &QAction::triggered, this, [this, &clicked] { onCopyItemTriggered(clicked); }); menu.addAction(action); @@ -627,14 +627,14 @@ void TestResultsPane::onCustomContextMenuRequested(const QPoint &pos) const auto correlatingItem = (enabled && clicked.isValid()) ? clicked.findTestTreeItem() : nullptr; action = new QAction(Tr::tr("Run This Test"), &menu); action->setEnabled(correlatingItem && correlatingItem->canProvideTestConfiguration()); - connect(action, &QAction::triggered, this, [this, clicked] { + connect(action, &QAction::triggered, this, [this, &clicked] { onRunThisTestTriggered(TestRunMode::Run, clicked); }); menu.addAction(action); action = new QAction(Tr::tr("Run This Test Without Deployment"), &menu); action->setEnabled(correlatingItem && correlatingItem->canProvideTestConfiguration()); - connect(action, &QAction::triggered, this, [this, clicked] { + connect(action, &QAction::triggered, this, [this, &clicked] { onRunThisTestTriggered(TestRunMode::RunWithoutDeploy, clicked); }); menu.addAction(action); @@ -648,14 +648,14 @@ void TestResultsPane::onCustomContextMenuRequested(const QPoint &pos) } } action->setEnabled(debugEnabled); - connect(action, &QAction::triggered, this, [this, clicked] { + connect(action, &QAction::triggered, this, [this, &clicked] { onRunThisTestTriggered(TestRunMode::Debug, clicked); }); menu.addAction(action); action = new QAction(Tr::tr("Debug This Test Without Deployment"), &menu); action->setEnabled(debugEnabled); - connect(action, &QAction::triggered, this, [this, clicked] { + connect(action, &QAction::triggered, this, [this, &clicked] { onRunThisTestTriggered(TestRunMode::DebugWithoutDeploy, clicked); }); menu.addAction(action); diff --git a/src/plugins/beautifier/artisticstyle/artisticstylesettings.cpp b/src/plugins/beautifier/artisticstyle/artisticstylesettings.cpp index f3479428c60..1cff800ed1f 100644 --- a/src/plugins/beautifier/artisticstyle/artisticstylesettings.cpp +++ b/src/plugins/beautifier/artisticstyle/artisticstylesettings.cpp @@ -7,7 +7,6 @@ #include -#include #include #include diff --git a/src/plugins/beautifier/beautifierplugin.cpp b/src/plugins/beautifier/beautifierplugin.cpp index 9e0b3f49e88..76db40daeab 100644 --- a/src/plugins/beautifier/beautifierplugin.cpp +++ b/src/plugins/beautifier/beautifierplugin.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include diff --git a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp index cc7e41ba421..687758bb0ae 100644 --- a/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp +++ b/src/plugins/clangcodemodel/clangeditordocumentprocessor.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include diff --git a/src/plugins/clangformat/clangformatbaseindenter.cpp b/src/plugins/clangformat/clangformatbaseindenter.cpp index d61cbf2f1a8..fc08158d2d1 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.cpp +++ b/src/plugins/clangformat/clangformatbaseindenter.cpp @@ -38,7 +38,11 @@ void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style, #else style.SortIncludes = false; #endif +#if LLVM_VERSION_MAJOR >= 16 + style.SortUsingDeclarations = clang::format::FormatStyle::SUD_Never; +#else style.SortUsingDeclarations = false; +#endif // This is a separate pass, don't do it unless it's the full formatting. style.FixNamespaceComments = false; diff --git a/src/plugins/clangformat/clangformatutils.cpp b/src/plugins/clangformat/clangformatutils.cpp index c7eb900fed5..d1d6bee684b 100644 --- a/src/plugins/clangformat/clangformatutils.cpp +++ b/src/plugins/clangformat/clangformatutils.cpp @@ -151,7 +151,11 @@ clang::format::FormatStyle qtcStyle() #else style.SortIncludes = true; #endif +#if LLVM_VERSION_MAJOR >= 16 + style.SortUsingDeclarations = FormatStyle::SUD_Lexicographic; +#else style.SortUsingDeclarations = true; +#endif style.SpaceAfterCStyleCast = true; style.SpaceAfterTemplateKeyword = false; style.SpaceBeforeAssignmentOperators = true; diff --git a/src/plugins/clangtools/clangtoolrunner.cpp b/src/plugins/clangtools/clangtoolrunner.cpp index bb48621c2f0..4c2a908a789 100644 --- a/src/plugins/clangtools/clangtoolrunner.cpp +++ b/src/plugins/clangtools/clangtoolrunner.cpp @@ -144,6 +144,7 @@ TaskItem clangToolTask(const AnalyzeInputData &input, const auto onProcessSetup = [=](QtcProcess &process) { process.setEnvironment(input.environment); process.setUseCtrlCStub(true); + process.setLowPriority(); process.setWorkingDirectory(input.outputDirPath); // Current clang-cl puts log file into working dir. const ClangToolStorage *data = storage.activeStorage(); diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp index f479bdcb355..2606791942f 100644 --- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp +++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include diff --git a/src/plugins/coreplugin/coreplugin.cpp b/src/plugins/coreplugin/coreplugin.cpp index e406f5b7a9a..b02a47d4f0e 100644 --- a/src/plugins/coreplugin/coreplugin.cpp +++ b/src/plugins/coreplugin/coreplugin.cpp @@ -347,19 +347,19 @@ void CorePlugin::addToPathChooserContextMenu(Utils::PathChooser *pathChooser, QM QList actions = menu->actions(); QAction *firstAction = actions.isEmpty() ? nullptr : actions.first(); - if (QDir().exists(pathChooser->filePath().toString())) { - auto *showInGraphicalShell = new QAction(Core::FileUtils::msgGraphicalShellAction(), menu); + if (pathChooser->filePath().exists()) { + auto showInGraphicalShell = new QAction(FileUtils::msgGraphicalShellAction(), menu); connect(showInGraphicalShell, &QAction::triggered, pathChooser, [pathChooser] { Core::FileUtils::showInGraphicalShell(pathChooser, pathChooser->filePath()); }); menu->insertAction(firstAction, showInGraphicalShell); - auto *showInTerminal = new QAction(Core::FileUtils::msgTerminalHereAction(), menu); + auto showInTerminal = new QAction(FileUtils::msgTerminalHereAction(), menu); connect(showInTerminal, &QAction::triggered, pathChooser, [pathChooser] { if (pathChooser->openTerminalHandler()) pathChooser->openTerminalHandler()(); else - FileUtils::openTerminal(pathChooser->filePath()); + FileUtils::openTerminal(pathChooser->filePath(), {}); }); menu->insertAction(firstAction, showInTerminal); diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index 4128cbdc0d7..2953a833b70 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -2618,7 +2619,7 @@ void EditorManagerPrivate::openTerminal() { if (!d->m_contextMenuEntry || d->m_contextMenuEntry->filePath().isEmpty()) return; - FileUtils::openTerminal(d->m_contextMenuEntry->filePath().parentDir()); + FileUtils::openTerminal(d->m_contextMenuEntry->filePath().parentDir(), {}); } void EditorManagerPrivate::findInDirectory() diff --git a/src/plugins/coreplugin/fileutils.cpp b/src/plugins/coreplugin/fileutils.cpp index 3525d602a6b..05aeb219b2e 100644 --- a/src/plugins/coreplugin/fileutils.cpp +++ b/src/plugins/coreplugin/fileutils.cpp @@ -32,15 +32,6 @@ #include #include -#ifdef Q_OS_WIN - -#include -#include -#include - -#endif - - using namespace Utils; namespace Core { @@ -111,69 +102,10 @@ void FileUtils::showInFileSystemView(const FilePath &path) navWidget->syncWithFilePath(path); } -static void startTerminalEmulator(const QString &workingDir, const Environment &env) -{ -#ifdef Q_OS_WIN - STARTUPINFO si; - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - - PROCESS_INFORMATION pinfo; - ZeroMemory(&pinfo, sizeof(pinfo)); - - static const auto quoteWinCommand = [](const QString &program) { - const QChar doubleQuote = QLatin1Char('"'); - - // add the program as the first arg ... it works better - QString programName = program; - programName.replace(QLatin1Char('/'), QLatin1Char('\\')); - if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote) - && programName.contains(QLatin1Char(' '))) { - programName.prepend(doubleQuote); - programName.append(doubleQuote); - } - return programName; - }; - const QString cmdLine = quoteWinCommand(qtcEnvironmentVariable("COMSPEC")); - // cmdLine is assumed to be detached - - // https://blogs.msdn.microsoft.com/oldnewthing/20090601-00/?p=18083 - - const QString totalEnvironment = env.toStringList().join(QChar(QChar::Null)) + QChar(QChar::Null); - LPVOID envPtr = (env != Environment::systemEnvironment()) - ? (WCHAR *)(totalEnvironment.utf16()) : nullptr; - - const bool success = CreateProcessW(0, (WCHAR *)cmdLine.utf16(), - 0, 0, FALSE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, - envPtr, workingDir.isEmpty() ? 0 : (WCHAR *)workingDir.utf16(), - &si, &pinfo); - - if (success) { - CloseHandle(pinfo.hThread); - CloseHandle(pinfo.hProcess); - } -#else - const TerminalCommand term = TerminalCommand::terminalEmulator(); - QProcess process; - process.setProgram(term.command); - process.setArguments(ProcessArgs::splitArgs(term.openArgs)); - process.setProcessEnvironment(env.toProcessEnvironment()); - process.setWorkingDirectory(workingDir); - process.startDetached(); -#endif -} - -void FileUtils::openTerminal(const FilePath &path) -{ - openTerminal(path, Environment::systemEnvironment()); -} - void FileUtils::openTerminal(const FilePath &path, const Environment &env) { - const QFileInfo fileInfo = path.toFileInfo(); - const QString workingDir = QDir::toNativeSeparators(fileInfo.isDir() ? - fileInfo.absoluteFilePath() : - fileInfo.absolutePath()); - startTerminalEmulator(workingDir, env); + QTC_ASSERT(DeviceFileHooks::instance().openTerminal, return); + DeviceFileHooks::instance().openTerminal(path, env); } QString FileUtils::msgFindInDirectory() diff --git a/src/plugins/coreplugin/fileutils.h b/src/plugins/coreplugin/fileutils.h index f54f890d7ec..2b5c65c800d 100644 --- a/src/plugins/coreplugin/fileutils.h +++ b/src/plugins/coreplugin/fileutils.h @@ -22,7 +22,6 @@ struct CORE_EXPORT FileUtils // Helpers for common directory browser options. static void showInGraphicalShell(QWidget *parent, const Utils::FilePath &path); static void showInFileSystemView(const Utils::FilePath &path); - static void openTerminal(const Utils::FilePath &path); static void openTerminal(const Utils::FilePath &path, const Utils::Environment &env); static QString msgFindInDirectory(); static QString msgFileSystemAction(); diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index 239c3230d1e..7db63ec7afb 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index da2bf677f10..3dae225cea3 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -3475,7 +3475,6 @@ public: } bool hasSourceFile() const { return m_headerFile != m_sourceFile; } - bool isHeaderHeaderFile() const { return m_isHeaderHeaderFile; } protected: void insertAndIndent(const RefactoringFilePtr &file, @@ -3671,6 +3670,7 @@ protected: const CppRefactoringChanges m_changes; const InsertionPointLocator m_locator; const CppRefactoringFilePtr m_headerFile; + bool m_isHeaderHeaderFile = false; // the "header" (where the class is defined) can be a source file const CppRefactoringFilePtr m_sourceFile; CppQuickFixSettings *const m_settings = CppQuickFixProjectsSettings::getQuickFixSettings( ProjectExplorer::ProjectTree::currentProject()); @@ -3683,7 +3683,6 @@ private: InsertionLocation m_sourceFileInsertionPoint; QString m_sourceFileCode; QMap m_headerFileCode; - bool m_isHeaderHeaderFile; // the "header" (where the class is defined) can be a source file }; class GenerateGetterSetterOp : public CppQuickFixOperation @@ -9018,7 +9017,7 @@ private: addSourceFileCode(implCode); } else if (constructorLocation == CppQuickFixSettings::FunctionLocation::OutsideClass) { - if (isHeaderHeaderFile()) + if (m_isHeaderHeaderFile) implCode.prepend("inline "); insertAndIndent(m_headerFile, implLoc, implCode); } diff --git a/src/plugins/fossil/CMakeLists.txt b/src/plugins/fossil/CMakeLists.txt new file mode 100644 index 00000000000..f5cb697c57e --- /dev/null +++ b/src/plugins/fossil/CMakeLists.txt @@ -0,0 +1,22 @@ + +add_qtc_plugin(Fossil + PLUGIN_DEPENDS + Core TextEditor ProjectExplorer VcsBase + SOURCES + annotationhighlighter.cpp annotationhighlighter.h + branchinfo.h + commiteditor.cpp commiteditor.h + configuredialog.cpp configuredialog.h configuredialog.ui + constants.h + fossil.qrc + fossilclient.cpp fossilclient.h + fossilcommitpanel.ui + fossilcommitwidget.cpp fossilcommitwidget.h + fossileditor.cpp fossileditor.h + fossilplugin.cpp fossilplugin.h + fossilsettings.cpp fossilsettings.h + pullorpushdialog.cpp pullorpushdialog.h pullorpushdialog.ui + revertdialog.ui + revisioninfo.h + wizard/fossiljsextension.cpp wizard/fossiljsextension.h +) diff --git a/src/plugins/fossil/Fossil.json.in b/src/plugins/fossil/Fossil.json.in new file mode 100644 index 00000000000..eb4719af8f9 --- /dev/null +++ b/src/plugins/fossil/Fossil.json.in @@ -0,0 +1,20 @@ +{ + \"Name\" : \"Fossil\", + \"Version\" : \"$$QTCREATOR_VERSION\", + \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\", + \"DisabledByDefault\" : true, + \"Vendor\" : \"Artur Shepilko\", + \"Copyright\" : \"(C) 2018 Artur Shepilko\", + \"License\" : [ \"Commercial Usage\", + \"\", + \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\", + \"\", + \"GNU General Public License Usage\", + \"\", + \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\" + ], + \"Category\" : \"Version Control\", + \"Description\" : \"Fossil SCM integration.\", + \"Url\" : \"http://www.qt.io\", + $$dependencyList +} diff --git a/src/plugins/fossil/annotationhighlighter.cpp b/src/plugins/fossil/annotationhighlighter.cpp new file mode 100644 index 00000000000..92aee0959ed --- /dev/null +++ b/src/plugins/fossil/annotationhighlighter.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "annotationhighlighter.h" +#include "constants.h" + +#include + +namespace Fossil { +namespace Internal { + +FossilAnnotationHighlighter::FossilAnnotationHighlighter(const ChangeNumbers &changeNumbers, + QTextDocument *document) : + VcsBase::BaseAnnotationHighlighter(changeNumbers, document), + m_changesetIdPattern(Constants::CHANGESET_ID) +{ + QTC_CHECK(m_changesetIdPattern.isValid()); +} + +QString FossilAnnotationHighlighter::changeNumber(const QString &block) const +{ + QRegularExpressionMatch changesetIdMatch = m_changesetIdPattern.match(block); + if (changesetIdMatch.hasMatch()) + return changesetIdMatch.captured(1); + return {}; +} + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/annotationhighlighter.h b/src/plugins/fossil/annotationhighlighter.h new file mode 100644 index 00000000000..bd7160a3542 --- /dev/null +++ b/src/plugins/fossil/annotationhighlighter.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +namespace Fossil { +namespace Internal { + +class FossilAnnotationHighlighter : public VcsBase::BaseAnnotationHighlighter +{ +public: + explicit FossilAnnotationHighlighter(const ChangeNumbers &changeNumbers, + QTextDocument *document = nullptr); + +private: + QString changeNumber(const QString &block) const final; + QRegularExpression m_changesetIdPattern; +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/branchinfo.h b/src/plugins/fossil/branchinfo.h new file mode 100644 index 00000000000..336ea9ec46c --- /dev/null +++ b/src/plugins/fossil/branchinfo.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace Fossil { +namespace Internal { + +class BranchInfo +{ +public: + enum BranchFlag { + Current = 0x01, + Closed = 0x02, + Private = 0x04 + }; + Q_DECLARE_FLAGS(BranchFlags, BranchFlag) + + bool isCurrent() const { return flags.testFlag(Current); } + + QString name; + BranchFlags flags; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(BranchInfo::BranchFlags) + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/commiteditor.cpp b/src/plugins/fossil/commiteditor.cpp new file mode 100644 index 00000000000..9b02ab7a30a --- /dev/null +++ b/src/plugins/fossil/commiteditor.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "branchinfo.h" +#include "commiteditor.h" +#include "constants.h" +#include "fossilcommitwidget.h" + +#include +#include +#include +#include + +namespace Fossil { +namespace Internal { + +CommitEditor::CommitEditor() : + VcsBase::VcsBaseSubmitEditor(new FossilCommitWidget) +{ + document()->setPreferredDisplayName(tr("Commit Editor")); +} + +FossilCommitWidget *CommitEditor::commitWidget() +{ + return static_cast(widget()); +} + +void CommitEditor::setFields(const Utils::FilePath &repositoryRoot, const BranchInfo &branch, + const QStringList &tags, const QString &userName, + const QList &repoStatus) +{ + FossilCommitWidget *fossilWidget = commitWidget(); + QTC_ASSERT(fossilWidget, return); + + fossilWidget->setFields(repositoryRoot, branch, tags, userName); + + m_fileModel = new VcsBase::SubmitFileModel(this); + m_fileModel->setRepositoryRoot(repositoryRoot); + m_fileModel->setFileStatusQualifier([](const QString &status, const QVariant &) { + if (status == Constants::FSTATUS_ADDED + || status == Constants::FSTATUS_ADDED_BY_MERGE + || status == Constants::FSTATUS_ADDED_BY_INTEGRATE) { + return VcsBase::SubmitFileModel::FileAdded; + } else if (status == Constants::FSTATUS_EDITED + || status == Constants::FSTATUS_UPDATED_BY_MERGE + || status == Constants::FSTATUS_UPDATED_BY_INTEGRATE) { + return VcsBase::SubmitFileModel::FileModified; + } else if (status == Constants::FSTATUS_DELETED) { + return VcsBase::SubmitFileModel::FileDeleted; + } else if (status == Constants::FSTATUS_RENAMED) { + return VcsBase::SubmitFileModel::FileRenamed; + } + return VcsBase::SubmitFileModel::FileStatusUnknown; + }); + + const QList toAdd = Utils::filtered(repoStatus, + [](const VcsBase::VcsBaseClient::StatusItem &item) + { return item.flags != Constants::FSTATUS_UNKNOWN; }); + for (const VcsBase::VcsBaseClient::StatusItem &item : toAdd) + m_fileModel->addFile(item.file, item.flags); + + setFileModel(m_fileModel); +} + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/commiteditor.h b/src/plugins/fossil/commiteditor.h new file mode 100644 index 00000000000..531628f8cc1 --- /dev/null +++ b/src/plugins/fossil/commiteditor.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +namespace VcsBase { class SubmitFileModel; } + +namespace Fossil { +namespace Internal { + +class BranchInfo; +class FossilCommitWidget; + +class CommitEditor : public VcsBase::VcsBaseSubmitEditor +{ + Q_OBJECT + +public: + CommitEditor(); + + void setFields(const Utils::FilePath &repositoryRoot, const BranchInfo &branch, + const QStringList &tags, const QString &userName, + const QList &repoStatus); + + FossilCommitWidget *commitWidget(); + +private: + VcsBase::SubmitFileModel *m_fileModel = nullptr; +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/configuredialog.cpp b/src/plugins/fossil/configuredialog.cpp new file mode 100644 index 00000000000..a19ae5d9a83 --- /dev/null +++ b/src/plugins/fossil/configuredialog.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "configuredialog.h" +#include "ui_configuredialog.h" + +#include "fossilsettings.h" + +#include + +#include + +namespace Fossil { +namespace Internal { + +class ConfigureDialogPrivate { +public: + RepositorySettings settings() const + { + return {m_ui.userLineEdit->text().trimmed(), + m_ui.sslIdentityFilePathChooser->filePath().toString(), + m_ui.disableAutosyncCheckBox->isChecked() + ? RepositorySettings::AutosyncOff : RepositorySettings::AutosyncOn}; + } + + void updateUi() { + m_ui.userLineEdit->setText(m_settings.user.trimmed()); + m_ui.userLineEdit->selectAll(); + m_ui.sslIdentityFilePathChooser->setPath(QDir::toNativeSeparators(m_settings.sslIdentityFile)); + m_ui.disableAutosyncCheckBox->setChecked(m_settings.autosync == RepositorySettings::AutosyncOff); + } + + Ui::ConfigureDialog m_ui; + RepositorySettings m_settings; +}; + +ConfigureDialog::ConfigureDialog(QWidget *parent) : QDialog(parent), + d(new ConfigureDialogPrivate) +{ + d->m_ui.setupUi(this); + d->m_ui.sslIdentityFilePathChooser->setExpectedKind(Utils::PathChooser::File); + d->m_ui.sslIdentityFilePathChooser->setPromptDialogTitle(tr("SSL/TLS Identity Key")); + setWindowTitle(tr("Configure Repository")); + d->updateUi(); +} + +ConfigureDialog::~ConfigureDialog() +{ + delete d; +} + +const RepositorySettings ConfigureDialog::settings() const +{ + return d->settings(); +} + +void ConfigureDialog::setSettings(const RepositorySettings &settings) +{ + d->m_settings = settings; + d->updateUi(); +} + +void ConfigureDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + d->m_ui.retranslateUi(this); + break; + default: + break; + } +} + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/configuredialog.h b/src/plugins/fossil/configuredialog.h new file mode 100644 index 00000000000..702624983a7 --- /dev/null +++ b/src/plugins/fossil/configuredialog.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace Fossil { +namespace Internal { + +struct RepositorySettings; +class ConfigureDialogPrivate; + +class ConfigureDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ConfigureDialog(QWidget *parent = nullptr); + ~ConfigureDialog() final; + + const RepositorySettings settings() const; + void setSettings(const RepositorySettings &settings); + +protected: + void changeEvent(QEvent *e) final; + +private: + ConfigureDialogPrivate *d = nullptr; +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/configuredialog.ui b/src/plugins/fossil/configuredialog.ui new file mode 100644 index 00000000000..d68ad2f6b5f --- /dev/null +++ b/src/plugins/fossil/configuredialog.ui @@ -0,0 +1,132 @@ + + + Fossil::Internal::ConfigureDialog + + + + 0 + 0 + 385 + 202 + + + + Configure Repository + + + + + + Repository User + + + + + + User: + + + + + + + Existing user to become an author of changes made to the repository. + + + + + + + + + + Repository Settings + + + + + + SSL/TLS identity: + + + + + + + SSL/TLS client identity key to use if requested by the server. + + + + + + + Disable automatic pull prior to commit or update and automatic push after commit or tag or branch creation. + + + Disable auto-sync + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + Utils::PathChooser + QWidget +
utils/pathchooser.h
+ 1 + + editingFinished() + browsingFinished() + +
+
+ + + + buttonBox + accepted() + Fossil::Internal::ConfigureDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Fossil::Internal::ConfigureDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/plugins/fossil/constants.h b/src/plugins/fossil/constants.h new file mode 100644 index 00000000000..b1e6170b346 --- /dev/null +++ b/src/plugins/fossil/constants.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace Fossil { +namespace Constants { + +const char VCS_ID_FOSSIL[] = "I.Fossil"; + +const char FOSSIL[] = "fossil"; +#if defined(Q_OS_WIN) || defined(Q_OS_CYGWIN) +const char FOSSILREPO[] = "_FOSSIL_"; +#else +const char FOSSILREPO[] = ".fslckout"; +#endif +const char FOSSILDEFAULT[] = "fossil"; +const char FOSSIL_CONTEXT[] = "Fossil Context"; + +const char FOSSIL_FILE_SUFFIX[] = ".fossil"; +const char FOSSIL_FILE_FILTER[] = "Fossil Repositories (*.fossil *.fsl);;All Files (*)"; + +//changeset identifiers +const char CHANGESET_ID[] = "([0-9a-f]{5,40})"; // match and capture +const char CHANGESET_ID_EXACT[] = "[0-9a-f]{5,40}"; // match + +//diff chunk identifiers +const char DIFFFILE_ID_EXACT[] = "[+]{3} (.*)\\s*"; // match and capture + +//BaseEditorParameters +const char FILELOG_ID[] = "Fossil File Log Editor"; +const char FILELOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("VCS", "Fossil File Log Editor"); +const char LOGAPP[] = "text/vnd.qtcreator.fossil.log"; + +const char ANNOTATELOG_ID[] = "Fossil Annotation Editor"; +const char ANNOTATELOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("VCS", "Fossil Annotation Editor"); +const char ANNOTATEAPP[] = "text/vnd.qtcreator.fossil.annotation"; + +const char DIFFLOG_ID[] = "Fossil Diff Editor"; +const char DIFFLOG_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("VCS", "Fossil Diff Editor"); +const char DIFFAPP[] = "text/x-patch"; + +//SubmitEditorParameters +const char COMMIT_ID[] = "Fossil Commit Log Editor"; +const char COMMIT_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("VCS", "Fossil Commit Log Editor"); +const char COMMITMIMETYPE[] = "text/vnd.qtcreator.fossil.commit"; + +//menu items +//File menu actions +const char ADD[] = "Fossil.AddSingleFile"; +const char DELETE[] = "Fossil.DeleteSingleFile"; +const char ANNOTATE[] = "Fossil.Annotate"; +const char DIFF[] = "Fossil.DiffSingleFile"; +const char LOG[] = "Fossil.LogSingleFile"; +const char REVERT[] = "Fossil.RevertSingleFile"; +const char STATUS[] = "Fossil.Status"; + +//directory menu Actions +const char DIFFMULTI[] = "Fossil.Action.DiffMulti"; +const char REVERTMULTI[] = "Fossil.Action.RevertAll"; +const char STATUSMULTI[] = "Fossil.Action.StatusMulti"; +const char LOGMULTI[] = "Fossil.Action.LogMulti"; + +//repository menu actions +const char PULL[] = "Fossil.Action.Pull"; +const char PUSH[] = "Fossil.Action.Push"; +const char UPDATE[] = "Fossil.Action.Update"; +const char COMMIT[] = "Fossil.Action.Commit"; +const char CONFIGURE_REPOSITORY[] = "Fossil.Action.Settings"; +const char CREATE_REPOSITORY[] = "Fossil.Action.CreateRepository"; + +// File status hint +const char FSTATUS_ADDED[] = "Added"; +const char FSTATUS_ADDED_BY_MERGE[] = "Added by Merge"; +const char FSTATUS_ADDED_BY_INTEGRATE[] = "Added by Integrate"; +const char FSTATUS_DELETED[] = "Deleted"; +const char FSTATUS_EDITED[] = "Edited"; +const char FSTATUS_UPDATED_BY_MERGE[] = "Updated by Merge"; +const char FSTATUS_UPDATED_BY_INTEGRATE[] = "Updated by Integrate"; +const char FSTATUS_RENAMED[] = "Renamed"; +const char FSTATUS_UNKNOWN[] = "Unknown"; + +// Fossil Json Wizards +const char WIZARD_PATH[] = ":/fossil/wizard"; + +} // namespace Constants +} // namespace Fossil diff --git a/src/plugins/fossil/fossil.qbs b/src/plugins/fossil/fossil.qbs new file mode 100644 index 00000000000..0bab349e392 --- /dev/null +++ b/src/plugins/fossil/fossil.qbs @@ -0,0 +1,39 @@ +import qbs 1.0 + +QtcPlugin { + name: "Fossil" + + Depends { name: "Qt.widgets" } + Depends { name: "Utils" } + + Depends { name: "Core" } + Depends { name: "TextEditor" } + Depends { name: "ProjectExplorer" } + Depends { name: "VcsBase" } + + files: [ + "constants.h", + "fossilclient.cpp", "fossilclient.h", + "fossilplugin.cpp", "fossilplugin.h", + "fossilsettings.cpp", "fossilsettings.h", + "commiteditor.cpp", "commiteditor.h", + "fossilcommitwidget.cpp", "fossilcommitwidget.h", + "fossileditor.cpp", "fossileditor.h", + "annotationhighlighter.cpp", "annotationhighlighter.h", + "pullorpushdialog.cpp", "pullorpushdialog.h", "pullorpushdialog.ui", + "branchinfo.h", + "configuredialog.cpp", "configuredialog.h", "configuredialog.ui", + "revisioninfo.h", + "fossil.qrc", + "revertdialog.ui", + "fossilcommitpanel.ui", + ] + + Group { + name: "Wizards" + prefix: "wizard/" + files: [ + "fossiljsextension.h", "fossiljsextension.cpp", + ] + } +} diff --git a/src/plugins/fossil/fossil.qrc b/src/plugins/fossil/fossil.qrc new file mode 100644 index 00000000000..f301bd15e19 --- /dev/null +++ b/src/plugins/fossil/fossil.qrc @@ -0,0 +1,7 @@ + + + wizard/projects/vcs/icon.png + wizard/projects/vcs/icon@2x.png + wizard/projects/vcs/wizard.json + + diff --git a/src/plugins/fossil/fossilclient.cpp b/src/plugins/fossil/fossilclient.cpp new file mode 100644 index 00000000000..492fcd1f6ab --- /dev/null +++ b/src/plugins/fossil/fossilclient.cpp @@ -0,0 +1,1205 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "fossilclient.h" +#include "fossileditor.h" +#include "constants.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Utils; +using namespace VcsBase; + +namespace Fossil { +namespace Internal { + +const RunFlags s_pullFlags = RunFlags::ShowStdOut | RunFlags::ShowSuccessMessage; + +// Parameter widget controlling whitespace diff mode, associated with a parameter +class FossilDiffConfig : public VcsBaseEditorConfig +{ + Q_OBJECT + +public: + FossilDiffConfig(FossilClient *client, QToolBar *toolBar) : + VcsBaseEditorConfig(toolBar) + { + QTC_ASSERT(client, return); + + FossilClient::SupportedFeatures features = client->supportedFeatures(); + + addReloadButton(); + if (features.testFlag(FossilClient::DiffIgnoreWhiteSpaceFeature)) { + mapSetting(addToggleButton("-w", tr("Ignore All Whitespace")), + &client->settings().diffIgnoreAllWhiteSpace); + mapSetting(addToggleButton("--strip-trailing-cr", tr("Strip Trailing CR")), + &client->settings().diffStripTrailingCR); + } + } +}; + +// Parameter widget controlling annotate/blame mode +class FossilAnnotateConfig : public VcsBaseEditorConfig +{ + Q_OBJECT + +public: + FossilAnnotateConfig(FossilClient *client, QToolBar *toolBar) : + VcsBaseEditorConfig(toolBar) + { + QTC_ASSERT(client, return); + + FossilSettings &settings = client->settings(); + FossilClient::SupportedFeatures features = client->supportedFeatures(); + + if (features.testFlag(FossilClient::AnnotateBlameFeature)) { + mapSetting(addToggleButton("|BLAME|", tr("Show Committers")), + &settings.annotateShowCommitters); + } + + // Force listVersions setting to false by default. + // This way the annotated line number would not get offset by the version list. + settings.annotateListVersions.setValue(false); + + mapSetting(addToggleButton("--log", tr("List Versions")), + &settings.annotateListVersions); + } +}; + +class FossilLogCurrentFileConfig : public VcsBaseEditorConfig +{ + Q_OBJECT + +public: + FossilLogCurrentFileConfig(FossilClient *client, QToolBar *toolBar) : + VcsBaseEditorConfig(toolBar) + { + QTC_ASSERT(client, return); + addReloadButton(); + } +}; + +class FossilLogConfig : public VcsBaseEditorConfig +{ + Q_OBJECT + +public: + FossilLogConfig(FossilClient *client, QToolBar *toolBar) : + VcsBaseEditorConfig(toolBar), + m_client(client) + { + QTC_ASSERT(client, return); + + addReloadButton(); + addLineageComboBox(); + addVerboseToggleButton(); + addItemTypeComboBox(); + } + + void addLineageComboBox() + { + FossilSettings &settings = m_client->settings(); + + // ancestors/descendants filter + // This is a positional argument not an option. + // Normally it takes the checkin/branch/tag as an additional parameter + // (trunk by default) + // So we kludge this by coding it as a meta-option (pipe-separated), + // then parse it out in arguments. + // All-choice is a blank argument with no additional parameters + const QList lineageFilterChoices = { + ChoiceItem(tr("Ancestors"), "ancestors"), + ChoiceItem(tr("Descendants"), "descendants"), + ChoiceItem(tr("Unfiltered"), "") + }; + mapSetting(addChoices(tr("Lineage"), QStringList("|LINEAGE|%1|current"), lineageFilterChoices), + &settings.timelineLineageFilter); + } + + void addVerboseToggleButton() + { + FossilSettings &settings = m_client->settings(); + + // show files + mapSetting(addToggleButton("-showfiles", tr("Verbose"), + tr("Show files changed in each revision")), + &settings.timelineVerbose); + } + + void addItemTypeComboBox() + { + FossilSettings &settings = m_client->settings(); + + // option: -t + const QList itemTypeChoices = { + ChoiceItem(tr("All Items"), "all"), + ChoiceItem(tr("File Commits"), "ci"), + ChoiceItem(tr("Technical Notes"), "e"), + ChoiceItem(tr("Tags"), "g"), + ChoiceItem(tr("Tickets"), "t"), + ChoiceItem(tr("Wiki Commits"), "w") + }; + + // here we setup the ComboBox to map to the "-t ", which will produce + // the enquoted option-values (e.g "-t all"). + // Fossil expects separate arguments for option and value ( i.e. "-t" "all") + // so we need to handle the splitting explicitly in arguments(). + mapSetting(addChoices(tr("Item Types"), QStringList("-t %1"), itemTypeChoices), + &settings.timelineItemType); + } + + QStringList arguments() const final + { + QStringList args; + + // split "-t val" => "-t" "val" + const QStringList arguments = VcsBaseEditorConfig::arguments(); + for (const QString &arg : arguments) { + if (arg.startsWith("-t")) { + args << arg.split(' '); + + } else if (arg.startsWith('|')){ + // meta-option: "|OPT|val|extra1|..." + QStringList params = arg.split('|'); + QString option = params[1]; + for (int i = 2; i < params.size(); ++i) { + if (option == "LINEAGE" && params[i].isEmpty()) { + // empty lineage filter == Unfiltered + break; + } + args << params[i]; + } + } else { + args << arg; + } + } + return args; + } + +private: + FossilClient *m_client; +}; + +unsigned FossilClient::makeVersionNumber(int major, int minor, int patch) +{ + return (QString().setNum(major).toUInt(0,16) << 16) + + (QString().setNum(minor).toUInt(0,16) << 8) + + (QString().setNum(patch).toUInt(0,16)); +} + +static inline QString versionPart(unsigned part) +{ + return QString::number(part & 0xff, 16); +} + +QString FossilClient::makeVersionString(unsigned version) +{ + return QString::fromLatin1("%1.%2.%3") + .arg(versionPart(version >> 16)) + .arg(versionPart(version >> 8)) + .arg(versionPart(version)); +} + +FossilClient::FossilClient(FossilSettings *settings) + : VcsBaseClient(settings), m_settings(settings) +{ + setDiffConfigCreator([this](QToolBar *toolBar) { + return new FossilDiffConfig(this, toolBar); + }); +} + +FossilSettings &FossilClient::settings() const +{ + return *m_settings; +} + +unsigned int FossilClient::synchronousBinaryVersion() const +{ + if (settings().binaryPath.value().isEmpty()) + return 0; + + const CommandResult result = vcsSynchronousExec({}, QStringList{"version"}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return 0; + + const QString output = result.cleanedStdOut().trimmed(); + + // fossil version: + // "This is fossil version 1.27 [ccdefa355b] 2013-09-30 11:47:18 UTC" + QRegularExpression versionPattern("(\\d+)\\.(\\d+)"); + QTC_ASSERT(versionPattern.isValid(), return 0); + QRegularExpressionMatch versionMatch = versionPattern.match(output); + QTC_ASSERT(versionMatch.hasMatch(), return 0); + const int major = versionMatch.captured(1).toInt(); + const int minor = versionMatch.captured(2).toInt(); + const int patch = 0; + return makeVersionNumber(major, minor, patch); +} + +QList FossilClient::branchListFromOutput(const QString &output, + const BranchInfo::BranchFlags defaultFlags) +{ + // Branch list format: + // " branch-name" + // "* current-branch" + return Utils::transform(output.split('\n', Qt::SkipEmptyParts), + [=](const QString &l) -> BranchInfo { + const QString &name = l.mid(2); + QTC_ASSERT(!name.isEmpty(), return {}); + const BranchInfo::BranchFlags flags = (l.startsWith("* ") + ? defaultFlags | BranchInfo::Current : defaultFlags); + return {name, flags}; + }); +} + +BranchInfo FossilClient::synchronousCurrentBranch(const FilePath &workingDirectory) +{ + if (workingDirectory.isEmpty()) + return {}; + + // First try to get the current branch from the list of open branches + const CommandResult result = vcsSynchronousExec(workingDirectory, {"branch", "list"}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + + const QString output = sanitizeFossilOutput(result.cleanedStdOut()); + BranchInfo currentBranch = Utils::findOrDefault(branchListFromOutput(output), [](const BranchInfo &b) { + return b.isCurrent(); + }); + + if (!currentBranch.isCurrent()) { + // If not available from open branches, request it from the list of closed branches. + const CommandResult result = vcsSynchronousExec(workingDirectory, + {"branch", "list", "--closed"}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + + const QString output = sanitizeFossilOutput(result.cleanedStdOut()); + currentBranch = Utils::findOrDefault(branchListFromOutput(output, BranchInfo::Closed), [](const BranchInfo &b) { + return b.isCurrent(); + }); + } + + return currentBranch; +} + +QList FossilClient::synchronousBranchQuery(const FilePath &workingDirectory) +{ + // Return a list of all branches, including the closed ones. + // Sort the list by branch name. + + if (workingDirectory.isEmpty()) + return {}; + + // First get list of open branches + CommandResult result = vcsSynchronousExec(workingDirectory, {"branch", "list"}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + + QString output = sanitizeFossilOutput(result.cleanedStdOut()); + QList branches = branchListFromOutput(output); + + // Append a list of closed branches. + result = vcsSynchronousExec(workingDirectory, {"branch", "list", "--closed"}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + + output = sanitizeFossilOutput(result.cleanedStdOut()); + branches.append(branchListFromOutput(output, BranchInfo::Closed)); + + std::sort(branches.begin(), branches.end(), + [](const BranchInfo &a, const BranchInfo &b) { return a.name < b.name; }); + return branches; +} + +QStringList FossilClient::parseRevisionCommentLine(const QString &commentLine) +{ + // "comment: This is a (test) commit message (user: the.name)" + + const QRegularExpression commentRx("^comment:\\s+(.*)\\s\\(user:\\s(.*)\\)$", + QRegularExpression::CaseInsensitiveOption); + QTC_ASSERT(commentRx.isValid(), return {}); + + const QRegularExpressionMatch match = commentRx.match(commentLine); + if (!match.hasMatch()) + return {}; + + return {match.captured(1), match.captured(2)}; +} + +RevisionInfo FossilClient::synchronousRevisionQuery(const FilePath &workingDirectory, + const QString &id, + bool getCommentMsg) const +{ + // Query details of the given revision/check-out id, + // if none specified, provide information about current revision + if (workingDirectory.isEmpty()) + return {}; + + QStringList args("info"); + if (!id.isEmpty()) + args << id; + + const CommandResult result = vcsSynchronousExec(workingDirectory, args, + RunFlags::SuppressCommandLogging); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + + const QString output = sanitizeFossilOutput(result.cleanedStdOut()); + + QString revisionId; + QString parentId; + QStringList mergeParentIds; + QString commentMsg; + QString committer; + + const QRegularExpression idRx("([0-9a-f]{5,40})"); + QTC_ASSERT(idRx.isValid(), return {}); + + const QString hashToken = + QString::fromUtf8(supportedFeatures().testFlag(InfoHashFeature) ? "hash: " : "uuid: "); + + for (const QString &l : output.split('\n', Qt::SkipEmptyParts)) { + if (l.startsWith("checkout: ", Qt::CaseInsensitive) + || l.startsWith(hashToken, Qt::CaseInsensitive)) { + const QRegularExpressionMatch idMatch = idRx.match(l); + QTC_ASSERT(idMatch.hasMatch(), return {}); + revisionId = idMatch.captured(1); + + } else if (l.startsWith("parent: ", Qt::CaseInsensitive)){ + const QRegularExpressionMatch idMatch = idRx.match(l); + if (idMatch.hasMatch()) + parentId = idMatch.captured(1); + } else if (l.startsWith("merged-from: ", Qt::CaseInsensitive)) { + const QRegularExpressionMatch idMatch = idRx.match(l); + if (idMatch.hasMatch()) + mergeParentIds.append(idMatch.captured(1)); + } else if (getCommentMsg && l.startsWith("comment: ", Qt::CaseInsensitive)) { + const QStringList commentLineParts = parseRevisionCommentLine(l); + commentMsg = commentLineParts.value(0); + committer = commentLineParts.value(1); + } + } + + // make sure id at least partially matches the retrieved revisionId + QTC_ASSERT(revisionId.startsWith(id, Qt::CaseInsensitive), return {}); + + if (parentId.isEmpty()) + parentId = revisionId; // root + + return {revisionId, parentId, mergeParentIds, commentMsg, committer}; +} + +QStringList FossilClient::synchronousTagQuery(const FilePath &workingDirectory, const QString &id) +{ + // Return a list of tags for the given revision. + // If no revision specified, all defined tags are listed. + // Tag list includes branch names. + + if (workingDirectory.isEmpty()) + return {}; + + QStringList args({"tag", "list"}); + if (!id.isEmpty()) + args << id; + const CommandResult result = vcsSynchronousExec(workingDirectory, args); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + + return sanitizeFossilOutput(result.cleanedStdOut()).split('\n', Qt::SkipEmptyParts); +} + +RepositorySettings FossilClient::synchronousSettingsQuery(const FilePath &workingDirectory) +{ + if (workingDirectory.isEmpty()) + return {}; + + const CommandResult result = vcsSynchronousExec(workingDirectory, QStringList{"settings"}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + const QString output = sanitizeFossilOutput(result.cleanedStdOut()); + + RepositorySettings repoSettings; + repoSettings.user = synchronousUserDefaultQuery(workingDirectory); + if (repoSettings.user.isEmpty()) + repoSettings.user = settings().userName.value(); + + for (const QString &line : output.split('\n', Qt::SkipEmptyParts)) { + // parse settings line: + // <(local|global)> + // Fossil properties are case-insensitive; force them to lower-case. + // Values may be in mixed-case; force lower-case for fixed values. + const QStringList fields = line.split(' ', Qt::SkipEmptyParts); + + const QString property = fields.at(0).toLower(); + const QString value = (fields.size() >= 3 ? fields.at(2) : QString()); + const QString lcValue = value.toLower(); + + if (property == "autosync") { + if (lcValue == "on" + || lcValue == "1") + repoSettings.autosync = RepositorySettings::AutosyncOn; + else if (lcValue == "off" + || lcValue == "0") + repoSettings.autosync = RepositorySettings::AutosyncOff; + else if (lcValue == "pullonly" + || lcValue == "2") + repoSettings.autosync = RepositorySettings::AutosyncPullOnly; + } + else if (property == "ssl-identity") { + repoSettings.sslIdentityFile = value; + } + } + + return repoSettings; +} + +bool FossilClient::synchronousSetSetting(const FilePath &workingDirectory, const QString &property, + const QString &value, bool isGlobal) +{ + // set a repository property to the given value + // if no value is given, unset the property + + if (workingDirectory.isEmpty() || property.isEmpty()) + return false; + + QStringList args; + if (value.isEmpty()) + args << "unset" << property; + else + args << "settings" << property << value; + + if (isGlobal) + args << "--global"; + + return vcsSynchronousExec(workingDirectory, args).result() + == ProcessResult::FinishedWithSuccess; +} + +bool FossilClient::synchronousConfigureRepository(const FilePath &workingDirectory, const RepositorySettings &newSettings, + const RepositorySettings ¤tSettings) +{ + if (workingDirectory.isEmpty()) + return false; + + // apply updated settings vs. current setting if given + const bool applyAll = (currentSettings == RepositorySettings()); + + if (!newSettings.user.isEmpty() + && (applyAll || newSettings.user != currentSettings.user) + && !synchronousSetUserDefault(workingDirectory, newSettings.user)) { + return false; + } + + if ((applyAll || newSettings.sslIdentityFile != currentSettings.sslIdentityFile) + && !synchronousSetSetting(workingDirectory, "ssl-identity", newSettings.sslIdentityFile)) { + return false; + } + + if (applyAll || newSettings.autosync != currentSettings.autosync) { + QString value; + switch (newSettings.autosync) { + case RepositorySettings::AutosyncOff: + value = "off"; + break; + case RepositorySettings::AutosyncOn: + value = "on"; + break; + case RepositorySettings::AutosyncPullOnly: + value = "pullonly"; + break; + } + + if (!synchronousSetSetting(workingDirectory, "autosync", value)) + return false; + } + + return true; +} + +QString FossilClient::synchronousUserDefaultQuery(const FilePath &workingDirectory) +{ + if (workingDirectory.isEmpty()) + return {}; + + const CommandResult result = vcsSynchronousExec(workingDirectory, {"user", "default"}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + + return sanitizeFossilOutput(result.cleanedStdOut()).trimmed(); +} + +bool FossilClient::synchronousSetUserDefault(const FilePath &workingDirectory, const QString &userName) +{ + if (workingDirectory.isEmpty() || userName.isEmpty()) + return false; + + // set repository-default user + const QStringList args({"user", "default", userName, "--user", userName}); + return vcsSynchronousExec(workingDirectory, args).result() + == ProcessResult::FinishedWithSuccess; +} + +QString FossilClient::synchronousGetRepositoryURL(const FilePath &workingDirectory) +{ + if (workingDirectory.isEmpty()) + return {}; + + const CommandResult result = vcsSynchronousExec(workingDirectory, QStringList{"remote-url"}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return {}; + const QString output = sanitizeFossilOutput(result.cleanedStdOut()).trimmed(); + + // Fossil returns "off" when no remote-url is set. + if (output.toLower() == "off") + return {}; + + return output; +} + +QString FossilClient::synchronousTopic(const FilePath &workingDirectory) +{ + if (workingDirectory.isEmpty()) + return {}; + + // return current branch name + return synchronousCurrentBranch(workingDirectory).name; +} + +bool FossilClient::synchronousCreateRepository(const FilePath &workingDirectory, const QStringList &extraOptions) +{ + VcsOutputWindow *outputWindow = VcsOutputWindow::instance(); + + // init repository file of the same name as the working directory + // use the configured default repository location for path + // use the configured default user for admin + + const QString repoName = workingDirectory.fileName().simplified(); + const QString repoPath = settings().defaultRepoPath.value(); + const QString adminUser = settings().userName.value(); + + if (repoName.isEmpty() || repoPath.isEmpty()) + return false; + + // @TODO: handle spaces in the path + // @TODO: what about --template options? + + const FilePath fullRepoName = FilePath::fromStringWithExtension(repoName, Constants::FOSSIL_FILE_SUFFIX); + const FilePath repoFilePath = FilePath::fromString(repoPath) + .pathAppended(fullRepoName.toString()); + QStringList args(vcsCommandString(CreateRepositoryCommand)); + if (!adminUser.isEmpty()) + args << "--admin-user" << adminUser; + args << extraOptions << repoFilePath.toUserOutput(); + CommandResult result = vcsSynchronousExec(workingDirectory, args); + if (result.result() != ProcessResult::FinishedWithSuccess) + return false; + outputWindow->append(sanitizeFossilOutput(result.cleanedStdOut())); + + // check out the created repository file into the working directory + result = vcsSynchronousExec(workingDirectory, {"open", repoFilePath.toUserOutput()}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return false; + outputWindow->append(sanitizeFossilOutput(result.cleanedStdOut())); + + // set user default to admin if specified + if (!adminUser.isEmpty()) { + result = vcsSynchronousExec(workingDirectory, + {"user", "default", adminUser, "--user", adminUser}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return false; + outputWindow->append(sanitizeFossilOutput(result.cleanedStdOut())); + } + + resetCachedVcsInfo(workingDirectory); + return true; +} + +bool FossilClient::synchronousMove(const FilePath &workingDir, + const QString &from, const QString &to, + const QStringList &extraOptions) +{ + // Fossil move does not rename actual file on disk, only changes it in repo + // So try to move the actual file first, then move it in repo to preserve + // history in case actual move fails. + + if (!QFile::rename(from, to)) + return false; + + QStringList args(vcsCommandString(MoveCommand)); + args << extraOptions << from << to; + return vcsSynchronousExec(workingDir, args).result() == ProcessResult::FinishedWithSuccess; +} + +bool FossilClient::synchronousPull(const FilePath &workingDir, const QString &srcLocation, const QStringList &extraOptions) +{ + QStringList args(vcsCommandString(PullCommand)); + if (srcLocation.isEmpty()) { + const QString defaultURL(synchronousGetRepositoryURL(workingDir)); + if (defaultURL.isEmpty()) + return false; + } else { + args << srcLocation; + } + + args << extraOptions; + const CommandResult result = vcsSynchronousExec(workingDir, args, s_pullFlags); + const bool success = (result.result() == ProcessResult::FinishedWithSuccess); + if (success) + emit changed(workingDir.toVariant()); + return success; +} + +bool FossilClient::synchronousPush(const FilePath &workingDir, const QString &dstLocation, const QStringList &extraOptions) +{ + QStringList args(vcsCommandString(PushCommand)); + if (dstLocation.isEmpty()) { + const QString defaultURL(synchronousGetRepositoryURL(workingDir)); + if (defaultURL.isEmpty()) + return false; + } else { + args << dstLocation; + } + + args << extraOptions; + return vcsSynchronousExec(workingDir, args, s_pullFlags).result() + == ProcessResult::FinishedWithSuccess; +} + +void FossilClient::commit(const FilePath &repositoryRoot, const QStringList &files, + const QString &commitMessageFile, const QStringList &extraOptions) +{ + VcsBaseClient::commit(repositoryRoot, files, commitMessageFile, + QStringList(extraOptions) << "-M" << commitMessageFile); +} + +void FossilClient::annotate(const FilePath &workingDir, const QString &file, int lineNumber, + const QString &revision, const QStringList &extraOptions, int firstLine) +{ + Q_UNUSED(firstLine) + // 'fossil annotate' command has a variant 'fossil blame'. + // blame command attributes a committing username to source lines, + // annotate shows line numbers + + QString vcsCmdString = vcsCommandString(AnnotateCommand); + const Id kind = vcsEditorKind(AnnotateCommand); + const QString id = VcsBaseEditor::getTitleId(workingDir, QStringList(file), revision); + const QString title = vcsEditorTitle(vcsCmdString, id); + const FilePath source = VcsBaseEditor::getSource(workingDir, file); + + VcsBaseEditorWidget *editor = createVcsEditor(kind, title, source, + VcsBaseEditor::getCodec(source), + vcsCmdString.toLatin1().constData(), id); + + auto *fossilEditor = qobject_cast(editor); + QTC_ASSERT(fossilEditor, return); + + if (!fossilEditor->editorConfig()) { + if (VcsBaseEditorConfig *editorConfig = createAnnotateEditor(fossilEditor)) { + editorConfig->setBaseArguments(extraOptions); + // editor has been just created, createVcsEditor() didn't set a configuration widget yet + connect(editorConfig, &VcsBaseEditorConfig::commandExecutionRequested, this, [=] { + const int line = VcsBaseEditor::lineNumberOfCurrentEditor(); + annotate(workingDir, file, line, revision, editorConfig->arguments()); + }); + fossilEditor->setEditorConfig(editorConfig); + } + } + QStringList effectiveArgs = extraOptions; + if (VcsBaseEditorConfig *editorConfig = fossilEditor->editorConfig()) + effectiveArgs = editorConfig->arguments(); + + // here we introduce a "|BLAME|" meta-option to allow both annotate and blame modes + int pos = effectiveArgs.indexOf("|BLAME|"); + if (pos != -1) { + vcsCmdString = "blame"; + effectiveArgs.removeAt(pos); + } + QStringList args(vcsCmdString); + if (!revision.isEmpty() && supportedFeatures().testFlag(AnnotateRevisionFeature)) + args << "-r" << revision; + args << effectiveArgs << file; + + // When version list requested, ignore the source line. + if (args.contains("--log")) + lineNumber = -1; + editor->setDefaultLineNumber(lineNumber); + + enqueueJob(createCommand(workingDir, fossilEditor), args); +} + +bool FossilClient::isVcsFileOrDirectory(const FilePath &filePath) const +{ + // false for any dir or file other than fossil checkout db-file + return filePath.toFileInfo().isFile() + && !filePath.fileName().compare(Constants::FOSSILREPO, + HostOsInfo::fileNameCaseSensitivity()); +} + +FilePath FossilClient::findTopLevelForFile(const FilePath &file) const +{ + return findRepositoryForFile(file, Constants::FOSSILREPO); +} + +bool FossilClient::managesFile(const FilePath &workingDirectory, const QString &fileName) const +{ + const CommandResult result = vcsSynchronousExec(workingDirectory, {"finfo", fileName}); + if (result.result() != ProcessResult::FinishedWithSuccess) + return false; + QString output = sanitizeFossilOutput(result.cleanedStdOut()); + return !output.startsWith("no history for file", Qt::CaseInsensitive); +} + +unsigned int FossilClient::binaryVersion() const +{ + static unsigned int cachedBinaryVersion = 0; + static QString cachedBinaryPath; + + const QString currentBinaryPath = settings().binaryPath.value(); + + if (currentBinaryPath.isEmpty()) + return 0; + + // Invalidate cache on failed version result. + // Assume that fossil client options have been changed and will change again. + if (!cachedBinaryVersion || currentBinaryPath != cachedBinaryPath) { + cachedBinaryVersion = synchronousBinaryVersion(); + if (cachedBinaryVersion) + cachedBinaryPath = currentBinaryPath; + else + cachedBinaryPath.clear(); + } + + return cachedBinaryVersion; +} + +QString FossilClient::binaryVersionString() const +{ + const unsigned int version = binaryVersion(); + + // Fossil itself does not report patch version, only maj.min + // Here we include the patch part for general convention consistency + + return makeVersionString(version); +} + +FossilClient::SupportedFeatures FossilClient::supportedFeatures() const +{ + // use for legacy client support to test for feature presence + // e.g. supportedFeatures().testFlag(TimelineWidthFeature) + + SupportedFeatures features = AllSupportedFeatures; // all inclusive by default (~0U) + + const unsigned int version = binaryVersion(); + + if (version < 0x21200) { + features &= ~InfoHashFeature; + if (version < 0x20400) + features &= ~AnnotateRevisionFeature; + if (version < 0x13000) + features &= ~TimelinePathFeature; + if (version < 0x12900) + features &= ~DiffIgnoreWhiteSpaceFeature; + if (version < 0x12800) { + features &= ~AnnotateBlameFeature; + features &= ~TimelineWidthFeature; + } + } + + return features; +} + +void FossilClient::view(const FilePath &source, const QString &id, const QStringList &extraOptions) +{ + const FilePath workingDirectory = source.isFile() ? source.absolutePath() : source; + + const RevisionInfo revisionInfo = synchronousRevisionQuery(workingDirectory, id); + const QStringList args{"diff", "--from", revisionInfo.parentId, "--to", revisionInfo.id, "-v"}; + const Id kind = vcsEditorKind(DiffCommand); + const QString title = vcsEditorTitle(vcsCommandString(DiffCommand), id); + + VcsBaseEditorWidget *editor = createVcsEditor(kind, title, source, + VcsBaseEditor::getCodec(source), "view", id); + editor->setWorkingDirectory(workingDirectory); + + enqueueJob(createCommand(workingDirectory, editor), args + extraOptions); +} + +class FossilLogHighlighter : QSyntaxHighlighter +{ +public: + explicit FossilLogHighlighter(QTextDocument *parent); + virtual void highlightBlock(const QString &text) final; + +private: + const QRegularExpression m_revisionIdRx; + const QRegularExpression m_dateRx; +}; + +FossilLogHighlighter::FossilLogHighlighter(QTextDocument * parent) : + QSyntaxHighlighter(parent), + m_revisionIdRx(Constants::CHANGESET_ID), + m_dateRx("([0-9]{4}-[0-9]{2}-[0-9]{2})") +{ + QTC_CHECK(m_revisionIdRx.isValid()); + QTC_CHECK(m_dateRx.isValid()); +} + +void FossilLogHighlighter::highlightBlock(const QString &text) +{ + // Match the revision-ids and dates -- highlight them for convenience. + + // Format revision-ids + QRegularExpressionMatchIterator i = m_revisionIdRx.globalMatch(text); + while (i.hasNext()) { + const QRegularExpressionMatch revisionIdMatch = i.next(); + QTextCharFormat charFormat = format(0); + charFormat.setForeground(Qt::darkBlue); + //charFormat.setFontItalic(true); + setFormat(revisionIdMatch.capturedStart(0), revisionIdMatch.capturedLength(0), charFormat); + } + + // Format dates + i = m_dateRx.globalMatch(text); + while (i.hasNext()) { + const QRegularExpressionMatch dateMatch = i.next(); + QTextCharFormat charFormat = format(0); + charFormat.setForeground(Qt::darkBlue); + charFormat.setFontWeight(QFont::DemiBold); + setFormat(dateMatch.capturedStart(0), dateMatch.capturedLength(0), charFormat); + } +} + +void FossilClient::log(const FilePath &workingDir, const QStringList &files, + const QStringList &extraOptions, + bool enableAnnotationContextMenu, + const std::function &addAuthOptions) +{ + // Show timeline for both repository and a file or path (--path ) + // When used for log repository, the files list is empty + + // LEGACY:fallback to log current file with legacy clients + SupportedFeatures features = supportedFeatures(); + if (!files.isEmpty() + && !features.testFlag(TimelinePathFeature)) { + logCurrentFile(workingDir, files, extraOptions, enableAnnotationContextMenu, addAuthOptions); + return; + } + + const QString vcsCmdString = vcsCommandString(LogCommand); + const Id kind = vcsEditorKind(LogCommand); + const QString id = VcsBaseEditor::getTitleId(workingDir, files); + const QString title = vcsEditorTitle(vcsCmdString, id); + const FilePath source = VcsBaseEditor::getSource(workingDir, files); + VcsBaseEditorWidget *editor = createVcsEditor(kind, title, source, + VcsBaseEditor::getCodec(source), + vcsCmdString.toLatin1().constData(), id); + + auto *fossilEditor = qobject_cast(editor); + QTC_ASSERT(fossilEditor, return); + + fossilEditor->setFileLogAnnotateEnabled(enableAnnotationContextMenu); + + if (!fossilEditor->editorConfig()) { + if (VcsBaseEditorConfig *editorConfig = createLogEditor(fossilEditor)) { + editorConfig->setBaseArguments(extraOptions); + // editor has been just created, createVcsEditor() didn't set a configuration widget yet + connect(editorConfig, &VcsBaseEditorConfig::commandExecutionRequested, + [=]() { this->log(workingDir, files, editorConfig->arguments(), enableAnnotationContextMenu, addAuthOptions); } ); + fossilEditor->setEditorConfig(editorConfig); + } + } + QStringList effectiveArgs = extraOptions; + if (VcsBaseEditorConfig *editorConfig = fossilEditor->editorConfig()) + effectiveArgs = editorConfig->arguments(); + + //@TODO: move highlighter and widgets to fossil editor sources. + + new FossilLogHighlighter(fossilEditor->document()); + + QStringList args(vcsCmdString); + args << effectiveArgs; + if (!files.isEmpty()) + args << "--path" << files; + enqueueJob(createCommand(workingDir, fossilEditor), args); +} + +void FossilClient::logCurrentFile(const FilePath &workingDir, const QStringList &files, + const QStringList &extraOptions, + bool enableAnnotationContextMenu, + const std::function &addAuthOptions) +{ + // Show commit history for the given file/file-revision + // NOTE: 'fossil finfo' shows full history from all branches. + + // With newer clients, 'fossil timeline' can handle both repository and file + SupportedFeatures features = supportedFeatures(); + if (features.testFlag(TimelinePathFeature)) { + log(workingDir, files, extraOptions, enableAnnotationContextMenu, addAuthOptions); + return; + } + + const QString vcsCmdString = "finfo"; + const Id kind = vcsEditorKind(LogCommand); + const QString id = VcsBaseEditor::getTitleId(workingDir, files); + const QString title = vcsEditorTitle(vcsCmdString, id); + const FilePath source = VcsBaseEditor::getSource(workingDir, files); + VcsBaseEditorWidget *editor = createVcsEditor(kind, title, source, + VcsBaseEditor::getCodec(source), + vcsCmdString.toLatin1().constData(), id); + + auto *fossilEditor = qobject_cast(editor); + QTC_ASSERT(fossilEditor, return); + + fossilEditor->setFileLogAnnotateEnabled(enableAnnotationContextMenu); + + if (!fossilEditor->editorConfig()) { + if (VcsBaseEditorConfig *editorConfig = createLogCurrentFileEditor(fossilEditor)) { + editorConfig->setBaseArguments(extraOptions); + // editor has been just created, createVcsEditor() didn't set a configuration widget yet + connect(editorConfig, &VcsBaseEditorConfig::commandExecutionRequested, + [=]() { this->logCurrentFile(workingDir, files, editorConfig->arguments(), enableAnnotationContextMenu, addAuthOptions); } ); + fossilEditor->setEditorConfig(editorConfig); + } + } + QStringList effectiveArgs = extraOptions; + if (VcsBaseEditorConfig *editorConfig = fossilEditor->editorConfig()) + effectiveArgs = editorConfig->arguments(); + + //@TODO: move highlighter and widgets to fossil editor sources. + + new FossilLogHighlighter(fossilEditor->document()); + + QStringList args(vcsCmdString); + args << effectiveArgs << files; + enqueueJob(createCommand(workingDir, fossilEditor), args); +} + +void FossilClient::revertFile(const FilePath &workingDir, + const QString &file, + const QString &revision, + const QStringList &extraOptions) +{ + QStringList args(vcsCommandString(RevertCommand)); + if (!revision.isEmpty()) + args << "-r" << revision; + args << extraOptions << file; + + // Indicate file list + VcsCommand *cmd = createCommand(workingDir); + const QStringList files = {workingDir.toString() + "/" + file}; + connect(cmd, &VcsCommand::done, this, [this, files, cmd] { + if (cmd->result() == ProcessResult::FinishedWithSuccess) + emit changed(files); + }); + enqueueJob(cmd, args); +} + +void FossilClient::revertAll(const FilePath &workingDir, const QString &revision, const QStringList &extraOptions) +{ + // Fossil allows whole tree revert to latest revision (effectively undoing uncommitted changes). + // However it disallows revert to a specific revision for the whole tree, only for selected files. + // Use checkout --force command for such case. + // NOTE: all uncommitted changes will not be backed up by checkout, unlike revert. + // Thus undo for whole tree revert should not be possible. + + QStringList args; + if (revision.isEmpty()) + args << vcsCommandString(RevertCommand) << extraOptions; + else + args << "checkout" << revision << "--force" << extraOptions; + + // Indicate repository change + VcsCommand *cmd = createCommand(workingDir); + const QStringList files = QStringList(workingDir.toString()); + connect(cmd, &VcsCommand::done, this, [this, files, cmd] { + if (cmd->result() == ProcessResult::FinishedWithSuccess) + emit changed(files); + }); + enqueueJob(createCommand(workingDir), args); +} + +QString FossilClient::sanitizeFossilOutput(const QString &output) const +{ +#if defined(Q_OS_WIN) || defined(Q_OS_CYGWIN) + // Strip possible extra '\r' in output from the Fossil client on Windows. + + // Fossil client contained a long-standing bug which caused an extraneous '\r' + // added to output lines from certain commands in excess of the expected . + // While the output appeared normal on a terminal, in non-interactive context + // it would get incorrectly split, resulting in extra empty lines. + // Bug fix is fairly recent, so for compatibility we need to strip the '\r'. + QString result(output); + return result.remove('\r'); +#else + return output; +#endif +} + +QString FossilClient::vcsCommandString(VcsCommandTag cmd) const +{ + // override specific client commands + // otherwise return baseclient command + + switch (cmd) { + case RemoveCommand: return "rm"; + case MoveCommand: return "mv"; + case LogCommand: return "timeline"; + default: return VcsBaseClient::vcsCommandString(cmd); + } +} + +Id FossilClient::vcsEditorKind(VcsCommandTag cmd) const +{ + switch (cmd) { + case AnnotateCommand: + return Constants::ANNOTATELOG_ID; + case DiffCommand: + return Constants::DIFFLOG_ID; + case LogCommand: + return Constants::FILELOG_ID; + default: + return {}; + } +} + +QStringList FossilClient::revisionSpec(const QString &revision) const +{ + // Pass the revision verbatim. + // Fossil uses a variety of ways to spec the revisions. + // In most cases revision is passed directly (SHA1) or via tag. + // Tag name may need to be prefixed with tag: to disambiguate it from hex (beef). + // Handle the revision option per specific command (e.g. diff, revert ). + + QStringList args; + if (!revision.isEmpty()) + args << revision; + return args; +} + +FossilClient::StatusItem FossilClient::parseStatusLine(const QString &line) const +{ + StatusItem item; + + // Ref: fossil source 'src/checkin.c' status_report() + // Expect at least one non-leading blank space. + + int pos = line.indexOf(' '); + + if (line.isEmpty() || pos < 1) + return {}; + + QString label(line.left(pos)); + QString flags; + + if (label == "EDITED") + flags = Constants::FSTATUS_EDITED; + else if (label == "ADDED") + flags = Constants::FSTATUS_ADDED; + else if (label == "RENAMED") + flags = Constants::FSTATUS_RENAMED; + else if (label == "DELETED") + flags = Constants::FSTATUS_DELETED; + else if (label == "MISSING") + flags = "Missing"; + else if (label == "ADDED_BY_MERGE") + flags = Constants::FSTATUS_ADDED_BY_MERGE; + else if (label == "UPDATED_BY_MERGE") + flags = Constants::FSTATUS_UPDATED_BY_MERGE; + else if (label == "ADDED_BY_INTEGRATE") + flags = Constants::FSTATUS_ADDED_BY_INTEGRATE; + else if (label == "UPDATED_BY_INTEGRATE") + flags = Constants::FSTATUS_UPDATED_BY_INTEGRATE; + else if (label == "CONFLICT") + flags = "Conflict"; + else if (label == "EXECUTABLE") + flags = "Set Exec"; + else if (label == "SYMLINK") + flags = "Set Symlink"; + else if (label == "UNEXEC") + flags = "Unset Exec"; + else if (label == "UNLINK") + flags = "Unset Symlink"; + else if (label == "NOT_A_FILE") + flags = Constants::FSTATUS_UNKNOWN; + + if (flags.isEmpty()) + return {}; + + // adjust the position to the last space before the file name + for (int size = line.size(); (pos + 1) < size && line[pos + 1].isSpace(); ++pos) + ; + + item.flags = flags; + item.file = line.mid(pos + 1); + return item; +} + +VcsBaseEditorConfig *FossilClient::createAnnotateEditor(VcsBaseEditorWidget *editor) +{ + return new FossilAnnotateConfig(this, editor->toolBar()); +} + +VcsBaseEditorConfig *FossilClient::createLogCurrentFileEditor(VcsBaseEditorWidget *editor) +{ + SupportedFeatures features = supportedFeatures(); + + if (features.testFlag(TimelinePathFeature)) + return createLogEditor(editor); + + return new FossilLogCurrentFileConfig(this, editor->toolBar()); +} + +VcsBaseEditorConfig *FossilClient::createLogEditor(VcsBaseEditorWidget *editor) +{ + return new FossilLogConfig(this, editor->toolBar()); +} + +} // namespace Internal +} // namespace Fossil + +#include "fossilclient.moc" diff --git a/src/plugins/fossil/fossilclient.h b/src/plugins/fossil/fossilclient.h new file mode 100644 index 00000000000..37d32468e94 --- /dev/null +++ b/src/plugins/fossil/fossilclient.h @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "fossilsettings.h" +#include "branchinfo.h" +#include "revisioninfo.h" + +#include + +#include + +namespace Fossil { +namespace Internal { + +class FossilSettings; +class FossilPluginPrivate; + +class FossilClient : public VcsBase::VcsBaseClient +{ + Q_OBJECT +public: + enum SupportedFeature { + AnnotateBlameFeature = 0x2, + TimelineWidthFeature = 0x4, + DiffIgnoreWhiteSpaceFeature = 0x8, + TimelinePathFeature = 0x10, + AnnotateRevisionFeature = 0x20, + InfoHashFeature = 0x40, + AllSupportedFeatures = // | all defined features + AnnotateBlameFeature + | TimelineWidthFeature + | DiffIgnoreWhiteSpaceFeature + | TimelinePathFeature + | AnnotateRevisionFeature + | InfoHashFeature + }; + Q_DECLARE_FLAGS(SupportedFeatures, SupportedFeature) + + static unsigned makeVersionNumber(int major, int minor, int patch); + static QString makeVersionString(unsigned version); + + explicit FossilClient(FossilSettings *settings); + FossilSettings &settings() const; + + unsigned int synchronousBinaryVersion() const; + BranchInfo synchronousCurrentBranch(const Utils::FilePath &workingDirectory); + QList synchronousBranchQuery(const Utils::FilePath &workingDirectory); + RevisionInfo synchronousRevisionQuery(const Utils::FilePath &workingDirectory, + const QString &id = {}, bool getCommentMsg = false) const; + QStringList synchronousTagQuery(const Utils::FilePath &workingDirectory, const QString &id = {}); + RepositorySettings synchronousSettingsQuery(const Utils::FilePath &workingDirectory); + bool synchronousSetSetting(const Utils::FilePath &workingDirectory, const QString &property, + const QString &value = {}, bool isGlobal = false); + bool synchronousConfigureRepository(const Utils::FilePath &workingDirectory, + const RepositorySettings &newSettings, + const RepositorySettings ¤tSettings = {}); + QString synchronousUserDefaultQuery(const Utils::FilePath &workingDirectory); + bool synchronousSetUserDefault(const Utils::FilePath &workingDirectory, const QString &userName); + QString synchronousGetRepositoryURL(const Utils::FilePath &workingDirectory); + QString synchronousTopic(const Utils::FilePath &workingDirectory); + bool synchronousCreateRepository(const Utils::FilePath &workingDirectory, + const QStringList &extraOptions = {}) final; + bool synchronousMove(const Utils::FilePath &workingDir, const QString &from, const QString &to, + const QStringList &extraOptions = {}) final; + bool synchronousPull(const Utils::FilePath &workingDir, const QString &srcLocation, + const QStringList &extraOptions = {}) final; + bool synchronousPush(const Utils::FilePath &workingDir, const QString &dstLocation, + const QStringList &extraOptions = {}) final; + void commit(const Utils::FilePath &repositoryRoot, const QStringList &files, + const QString &commitMessageFile, const QStringList &extraOptions = {}) final; + void annotate(const Utils::FilePath &workingDir, const QString &file, + int lineNumber = -1, const QString &revision = {}, + const QStringList &extraOptions = {}, int firstLine = -1) final; + void log(const Utils::FilePath &workingDir, const QStringList &files = {}, + const QStringList &extraOptions = {}, bool enableAnnotationContextMenu = false, + const std::function &addAuthOptions = {}) final; + void logCurrentFile(const Utils::FilePath &workingDir, const QStringList &files = {}, + const QStringList &extraOptions = {}, + bool enableAnnotationContextMenu = false, + const std::function &addAuthOptions = {}); + void revertFile(const Utils::FilePath &workingDir, const QString &file, + const QString &revision = {}, const QStringList &extraOptions = {}) final; + void revertAll(const Utils::FilePath &workingDir, const QString &revision = {}, + const QStringList &extraOptions = {}) final; + bool isVcsFileOrDirectory(const Utils::FilePath &filePath) const; + Utils::FilePath findTopLevelForFile(const Utils::FilePath &file) const final; + bool managesFile(const Utils::FilePath &workingDirectory, const QString &fileName) const; + unsigned int binaryVersion() const; + QString binaryVersionString() const; + SupportedFeatures supportedFeatures() const; + void view(const Utils::FilePath &source, const QString &id, const QStringList &extraOptions = {}) final; + +private: + static QList branchListFromOutput(const QString &output, + const BranchInfo::BranchFlags defaultFlags = {}); + static QStringList parseRevisionCommentLine(const QString &commentLine); + + QString sanitizeFossilOutput(const QString &output) const; + QString vcsCommandString(VcsCommandTag cmd) const final; + Utils::Id vcsEditorKind(VcsCommandTag cmd) const final; + QStringList revisionSpec(const QString &revision) const final; + StatusItem parseStatusLine(const QString &line) const final; + VcsBase::VcsBaseEditorConfig *createAnnotateEditor(VcsBase::VcsBaseEditorWidget *editor); + VcsBase::VcsBaseEditorConfig *createLogCurrentFileEditor(VcsBase::VcsBaseEditorWidget *editor); + VcsBase::VcsBaseEditorConfig *createLogEditor(VcsBase::VcsBaseEditorWidget *editor); + + friend class FossilPluginPrivate; + FossilSettings *m_settings = nullptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(FossilClient::SupportedFeatures) + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/fossilcommitpanel.ui b/src/plugins/fossil/fossilcommitpanel.ui new file mode 100644 index 00000000000..b08e7883a5f --- /dev/null +++ b/src/plugins/fossil/fossilcommitpanel.ui @@ -0,0 +1,177 @@ + + + Fossil::Internal::FossilCommitPanel + + + + 0 + 0 + 374 + 270 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Current Information + + + + QFormLayout::ExpandingFieldsGrow + + + + + Local root: + + + + + + + Qt::NoFocus + + + true + + + + + + + Branch: + + + + + + + Qt::NoFocus + + + true + + + + + + + Tags: + + + + + + + Qt::NoFocus + + + true + + + + + + + + + + Commit Information + + + + + + New branch: + + + + + + + + + + + 50 + 20 + + + + + + + :/projectexplorer/images/compile_error.png + + + + + + + Create a private check-in that is never synced. +Children of private check-ins are automatically private. +Private check-ins are not pushed to the remote repository by default. + + + Private + + + + + + + Tags: + + + + + + + Tag names to apply; comma-separated. + + + + + + + Author: + + + + + + + + + + Qt::Horizontal + + + + 160 + 20 + + + + + + + + + + + + diff --git a/src/plugins/fossil/fossilcommitwidget.cpp b/src/plugins/fossil/fossilcommitwidget.cpp new file mode 100644 index 00000000000..f45fcacde86 --- /dev/null +++ b/src/plugins/fossil/fossilcommitwidget.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "fossilcommitwidget.h" +#include "branchinfo.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace Fossil { +namespace Internal { + +// Retrieve the comment char format from the text editor. +static QTextCharFormat commentFormat() +{ + const TextEditor::FontSettings settings = TextEditor::TextEditorSettings::instance()->fontSettings(); + return settings.toTextCharFormat(TextEditor::C_COMMENT); +} + +// Highlighter for Fossil submit messages. +// Marks up [ticket-id] fields in the message. +class FossilSubmitHighlighter : QSyntaxHighlighter +{ +public: + explicit FossilSubmitHighlighter(Utils::CompletingTextEdit *parent); + void highlightBlock(const QString &text) final; + +private: + const QTextCharFormat m_commentFormat; + const QRegularExpression m_keywordPattern; +}; + +FossilSubmitHighlighter::FossilSubmitHighlighter(Utils::CompletingTextEdit *parent) : QSyntaxHighlighter(parent), + m_commentFormat(commentFormat()), + m_keywordPattern("\\[([0-9a-f]{5,40})\\]") +{ + QTC_CHECK(m_keywordPattern.isValid()); +} + +void FossilSubmitHighlighter::highlightBlock(const QString &text) +{ + // Fossil commit message allows listing of [ticket-id], + // where ticket-id is a partial SHA1. + // Match the ticket-ids and highlight them for convenience. + + // Format keywords + QRegularExpressionMatchIterator i = m_keywordPattern.globalMatch(text); + while (i.hasNext()) { + const QRegularExpressionMatch keywordMatch = i.next(); + QTextCharFormat charFormat = format(0); + charFormat.setFontItalic(true); + setFormat(keywordMatch.capturedStart(0), keywordMatch.capturedLength(0), charFormat); + } +} + + +FossilCommitWidget::FossilCommitWidget() : m_commitPanel(new QWidget) +{ + m_commitPanelUi.setupUi(m_commitPanel); + insertTopWidget(m_commitPanel); + new FossilSubmitHighlighter(descriptionEdit()); + m_branchValidator = new QRegularExpressionValidator(QRegularExpression("[^\\n]*"), this); + + connect(m_commitPanelUi.branchLineEdit, &QLineEdit::textChanged, + this, &FossilCommitWidget::branchChanged); +} + +void FossilCommitWidget::setFields(const Utils::FilePath &repoPath, const BranchInfo &branch, + const QStringList &tags, const QString &userName) +{ + m_commitPanelUi.localRootLineEdit->setText(repoPath.toUserOutput()); + m_commitPanelUi.currentBranchLineEdit->setText(branch.name); + const QString tagsText = tags.join(", "); + m_commitPanelUi.currentTagsLineEdit->setText(tagsText); + m_commitPanelUi.authorLineEdit->setText(userName); + + branchChanged(); +} + +QString FossilCommitWidget::newBranch() const +{ + return m_commitPanelUi.branchLineEdit->text().trimmed(); +} + +QStringList FossilCommitWidget::tags() const +{ + QString tagsText = m_commitPanelUi.tagsLineEdit->text().trimmed(); + if (tagsText.isEmpty()) + return {}; + + return tagsText.replace(',', ' ').split(' ', Qt::SkipEmptyParts); +} + +QString FossilCommitWidget::committer() const +{ + return m_commitPanelUi.authorLineEdit->text(); +} + +bool FossilCommitWidget::isPrivateOptionEnabled() const +{ + return m_commitPanelUi.isPrivateCheckBox->isChecked(); +} + +bool FossilCommitWidget::canSubmit(QString *whyNot) const +{ + QString message = cleanupDescription(descriptionText()).trimmed(); + + if (m_commitPanelUi.invalidBranchLabel->isVisible() || message.isEmpty()) { + if (whyNot) + *whyNot = tr("Message check failed."); + return false; + } + + return VcsBase::SubmitEditorWidget::canSubmit(); +} + +void FossilCommitWidget::branchChanged() +{ + m_commitPanelUi.invalidBranchLabel->setVisible(!isValidBranch()); + + updateSubmitAction(); +} + +bool FossilCommitWidget::isValidBranch() const +{ + int pos = m_commitPanelUi.branchLineEdit->cursorPosition(); + QString text = m_commitPanelUi.branchLineEdit->text(); + return m_branchValidator->validate(text, pos) == QValidator::Acceptable; +} + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/fossilcommitwidget.h b/src/plugins/fossil/fossilcommitwidget.h new file mode 100644 index 00000000000..21180d28357 --- /dev/null +++ b/src/plugins/fossil/fossilcommitwidget.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "ui_fossilcommitpanel.h" + +#include + +QT_BEGIN_NAMESPACE +class QValidator; +QT_END_NAMESPACE + +namespace Utils { class FilePath; } + +namespace Fossil { +namespace Internal { + +class BranchInfo; + +/*submit editor widget based on git SubmitEditor + Some extra fields have been added to the standard SubmitEditorWidget, + to help to conform to the commit style that is used by both git and Fossil*/ + +class FossilCommitWidget : public VcsBase::SubmitEditorWidget +{ + Q_OBJECT + +public: + FossilCommitWidget(); + + void setFields(const Utils::FilePath &repoPath, const BranchInfo &newBranch, + const QStringList &tags, const QString &userName); + + QString newBranch() const; + QStringList tags() const; + QString committer() const; + bool isPrivateOptionEnabled() const; + +protected: + bool canSubmit(QString *whyNot = nullptr) const; + +private slots: + void branchChanged(); + +private: + bool isValidBranch() const; + + QWidget *m_commitPanel; + Ui::FossilCommitPanel m_commitPanelUi; + QValidator *m_branchValidator; +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/fossileditor.cpp b/src/plugins/fossil/fossileditor.cpp new file mode 100644 index 00000000000..a3f95390ac5 --- /dev/null +++ b/src/plugins/fossil/fossileditor.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "fossileditor.h" +#include "annotationhighlighter.h" +#include "constants.h" +#include "fossilplugin.h" +#include "fossilclient.h" + +#include + +#include +#include + +namespace Fossil { +namespace Internal { + +class FossilEditorWidgetPrivate +{ +public: + FossilEditorWidgetPrivate() : + m_exactChangesetId(Constants::CHANGESET_ID_EXACT) + { + QTC_ASSERT(m_exactChangesetId.isValid(), return); + } + + + const QRegularExpression m_exactChangesetId; +}; + +FossilEditorWidget::FossilEditorWidget() : + d(new FossilEditorWidgetPrivate) +{ + setAnnotateRevisionTextFormat(tr("&Annotate %1")); + setAnnotatePreviousRevisionTextFormat(tr("Annotate &Parent Revision %1")); + setDiffFilePattern(Constants::DIFFFILE_ID_EXACT); + setLogEntryPattern("^.*\\[([0-9a-f]{5,40})\\]"); + setAnnotationEntryPattern(QString("^") + Constants::CHANGESET_ID + " "); +} + +FossilEditorWidget::~FossilEditorWidget() +{ + delete d; +} + +QString FossilEditorWidget::changeUnderCursor(const QTextCursor &cursorIn) const +{ + QTextCursor cursor = cursorIn; + cursor.select(QTextCursor::WordUnderCursor); + if (cursor.hasSelection()) { + const QString change = cursor.selectedText(); + const QRegularExpressionMatch exactChangesetIdMatch = d->m_exactChangesetId.match(change); + if (exactChangesetIdMatch.hasMatch()) + return change; + } + return {}; +} + +QString FossilEditorWidget::decorateVersion(const QString &revision) const +{ + static const int shortChangesetIdSize(10); + static const int maxTextSize(120); + + const Utils::FilePath workingDirectory = source().parentDir(); + const FossilClient *client = FossilPlugin::client(); + const RevisionInfo revisionInfo = client->synchronousRevisionQuery(workingDirectory, revision, + true); + // format: 'revision (committer "comment...")' + QString output = revision.left(shortChangesetIdSize) + + " (" + revisionInfo.committer + + " \"" + revisionInfo.commentMsg.left(maxTextSize); + + if (output.size() > maxTextSize) { + output.truncate(maxTextSize - 3); + output.append("..."); + } + output.append("\")"); + return output; +} + +QStringList FossilEditorWidget::annotationPreviousVersions(const QString &revision) const +{ + const Utils::FilePath workingDirectory = source().parentDir(); + const FossilClient *client = FossilPlugin::client(); + const RevisionInfo revisionInfo = client->synchronousRevisionQuery(workingDirectory, revision); + if (revisionInfo.parentId.isEmpty()) + return {}; + + QStringList revisions{revisionInfo.parentId}; + revisions.append(revisionInfo.mergeParentIds); + return revisions; +} + +VcsBase::BaseAnnotationHighlighter *FossilEditorWidget::createAnnotationHighlighter( + const QSet &changes) const +{ + return new FossilAnnotationHighlighter(changes); +} + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/fossileditor.h b/src/plugins/fossil/fossileditor.h new file mode 100644 index 00000000000..78215792231 --- /dev/null +++ b/src/plugins/fossil/fossileditor.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace Fossil { +namespace Internal { + +class FossilEditorWidgetPrivate; + +class FossilEditorWidget final : public VcsBase::VcsBaseEditorWidget +{ + Q_OBJECT + +public: + FossilEditorWidget(); + ~FossilEditorWidget() final; + +private: + QString changeUnderCursor(const QTextCursor &cursor) const final; + QString decorateVersion(const QString &revision) const final; + QStringList annotationPreviousVersions(const QString &revision) const final; + VcsBase::BaseAnnotationHighlighter *createAnnotationHighlighter( + const QSet &changes) const final; + + FossilEditorWidgetPrivate *d; +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/fossilplugin.cpp b/src/plugins/fossil/fossilplugin.cpp new file mode 100644 index 00000000000..d40585fa4e5 --- /dev/null +++ b/src/plugins/fossil/fossilplugin.cpp @@ -0,0 +1,1149 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "fossilplugin.h" +#include "constants.h" +#include "fossilclient.h" +#include "fossilcommitwidget.h" +#include "fossileditor.h" +#include "pullorpushdialog.h" +#include "configuredialog.h" +#include "commiteditor.h" +#include "wizard/fossiljsextension.h" + +#include "ui_revertdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Core; +using namespace Utils; +using namespace VcsBase; +using namespace std::placeholders; + +namespace Fossil { +namespace Internal { + +class FossilTopicCache : public Core::IVersionControl::TopicCache +{ +public: + FossilTopicCache(FossilClient *client) : + m_client(client) + { } + +protected: + FilePath trackFile(const FilePath &repository) final + { + return repository.pathAppended(Constants::FOSSILREPO); + } + + QString refreshTopic(const FilePath &repository) final + { + return m_client->synchronousTopic(repository); + } + +private: + FossilClient *m_client; +}; + +const VcsBaseEditorParameters fileLogParameters { + LogOutput, + Constants::FILELOG_ID, + Constants::FILELOG_DISPLAY_NAME, + Constants::LOGAPP +}; + +const VcsBaseEditorParameters annotateLogParameters { + AnnotateOutput, + Constants::ANNOTATELOG_ID, + Constants::ANNOTATELOG_DISPLAY_NAME, + Constants::ANNOTATEAPP +}; + +const VcsBaseEditorParameters diffParameters { + DiffOutput, + Constants::DIFFLOG_ID, + Constants::DIFFLOG_DISPLAY_NAME, + Constants::DIFFAPP +}; + +const VcsBaseSubmitEditorParameters submitEditorParameters { + Constants::COMMITMIMETYPE, + Constants::COMMIT_ID, + Constants::COMMIT_DISPLAY_NAME, + VcsBaseSubmitEditorParameters::DiffFiles +}; + + +class FossilPluginPrivate final : public VcsBase::VcsBasePluginPrivate +{ + Q_DECLARE_TR_FUNCTIONS(Fossil::Internal::FossilPlugin) + +public: + enum SyncMode { + SyncPull, + SyncPush + }; + + FossilPluginPrivate(); + + // IVersionControl + QString displayName() const final; + Id id() const final; + + bool isVcsFileOrDirectory(const FilePath &filePath) const final; + + bool managesDirectory(const FilePath &directory, FilePath *topLevel) const final; + bool managesFile(const FilePath &workingDirectory, const QString &fileName) const final; + + bool isConfigured() const final; + bool supportsOperation(Operation operation) const final; + bool vcsOpen(const FilePath &fileName) final; + bool vcsAdd(const FilePath &fileName) final; + bool vcsDelete(const FilePath &filename) final; + bool vcsMove(const FilePath &from, const FilePath &to) final; + bool vcsCreateRepository(const FilePath &directory) final; + + void vcsAnnotate(const FilePath &file, int line) final; + void vcsDescribe(const FilePath &source, const QString &id) final; + + VcsCommand *createInitialCheckoutCommand(const QString &url, + const Utils::FilePath &baseDirectory, + const QString &localName, + const QStringList &extraArgs) final; + + void updateActions(VcsBase::VcsBasePluginPrivate::ActionState) override; + bool activateCommit() override; + + // File menu action slots + void addCurrentFile(); + void deleteCurrentFile(); + void annotateCurrentFile(); + void diffCurrentFile(); + void logCurrentFile(); + void revertCurrentFile(); + void statusCurrentFile(); + + // Directory menu action slots + void diffRepository(); + void logRepository(); + void revertAll(); + void statusMulti(); + + // Repository menu action slots + void pull() { pullOrPush(SyncPull); } + void push() { pullOrPush(SyncPush); } + void update(); + void configureRepository(); + void commit(); + void showCommitWidget(const QList &status); + void diffFromEditorSelected(const QStringList &files); + void createRepository(); + + // Methods + void createMenu(const Core::Context &context); + void createFileActions(const Core::Context &context); + void createDirectoryActions(const Core::Context &context); + void createRepositoryActions(const Core::Context &context); + + bool pullOrPush(SyncMode mode); + + // Variables + FossilSettings m_fossilSettings; + FossilClient m_client{&m_fossilSettings}; + + OptionsPage optionPage{[this] { configurationChanged(); }, &m_fossilSettings}; + + VcsSubmitEditorFactory submitEditorFactory { + submitEditorParameters, + [] { return new CommitEditor; }, + this + }; + + VcsEditorFactory fileLogFactory { + &fileLogParameters, + [] { return new FossilEditorWidget; }, + std::bind(&FossilPluginPrivate::vcsDescribe, this, _1, _2) + }; + + VcsEditorFactory annotateLogFactory { + &annotateLogParameters, + [] { return new FossilEditorWidget; }, + std::bind(&FossilPluginPrivate::vcsDescribe, this, _1, _2) + }; + + VcsEditorFactory diffFactory { + &diffParameters, + [] { return new FossilEditorWidget; }, + std::bind(&FossilPluginPrivate::vcsDescribe, this, _1, _2) + }; + + Core::CommandLocator *m_commandLocator = nullptr; + Core::ActionContainer *m_fossilContainer = nullptr; + + QList m_repositoryActionList; + + // Menu Items (file actions) + Utils::ParameterAction *m_addAction = nullptr; + Utils::ParameterAction *m_deleteAction = nullptr; + Utils::ParameterAction *m_annotateFile = nullptr; + Utils::ParameterAction *m_diffFile = nullptr; + Utils::ParameterAction *m_logFile = nullptr; + Utils::ParameterAction *m_revertFile = nullptr; + Utils::ParameterAction *m_statusFile = nullptr; + + QAction *m_createRepositoryAction = nullptr; + + // Submit editor actions + QAction *m_menuAction = nullptr; + + Utils::FilePath m_submitRepository; + + // To be connected to the VcsTask's success signal to emit the repository/ + // files changed signals according to the variant's type: + // String -> repository, StringList -> files + void changed(const QVariant &); +}; + +static FossilPluginPrivate *dd = nullptr; + +FossilPlugin::~FossilPlugin() +{ + delete dd; + dd = nullptr; +} + +bool FossilPlugin::initialize(const QStringList &arguments, QString *errorMessage) +{ + Q_UNUSED(arguments); + Q_UNUSED(errorMessage); + + dd = new FossilPluginPrivate; + + ProjectExplorer::JsonWizardFactory::addWizardPath(":/fossil/wizard/"); + + return true; +} + +void FossilPlugin::extensionsInitialized() +{ + dd->extensionsInitialized(); +} + +const FossilSettings &FossilPlugin::settings() +{ + return dd->m_fossilSettings; +} + +FossilClient *FossilPlugin::client() +{ + return &dd->m_client; +} + +FossilPluginPrivate::FossilPluginPrivate() + : VcsBase::VcsBasePluginPrivate(Core::Context(Constants::FOSSIL_CONTEXT)) +{ + Core::Context context(Constants::FOSSIL_CONTEXT); + + setTopicCache(new FossilTopicCache(&m_client)); + connect(&m_client, &VcsBase::VcsBaseClient::changed, this, &FossilPluginPrivate::changed); + + m_commandLocator = new Core::CommandLocator("Fossil", "fossil", "fossil", this); + + ProjectExplorer::JsonWizardFactory::addWizardPath(Utils::FilePath::fromString(Constants::WIZARD_PATH)); + Core::JsExpander::registerGlobalObject("Fossil", [this] { + return new FossilJsExtension(&m_fossilSettings); + }); + + createMenu(context); +} + +void FossilPluginPrivate::createMenu(const Core::Context &context) +{ + // Create menu item for Fossil + m_fossilContainer = Core::ActionManager::createMenu("Fossil.FossilMenu"); + QMenu *menu = m_fossilContainer->menu(); + menu->setTitle(tr("&Fossil")); + + createFileActions(context); + m_fossilContainer->addSeparator(context); + createDirectoryActions(context); + m_fossilContainer->addSeparator(context); + createRepositoryActions(context); + m_fossilContainer->addSeparator(context); + + // Request the Tools menu and add the Fossil menu to it + Core::ActionContainer *toolsMenu = Core::ActionManager::actionContainer(Core::Constants::M_TOOLS); + toolsMenu->addMenu(m_fossilContainer); + m_menuAction = m_fossilContainer->menu()->menuAction(); +} + +void FossilPluginPrivate::createFileActions(const Core::Context &context) +{ + Core::Command *command; + + m_annotateFile = new Utils::ParameterAction(tr("Annotate Current File"), tr("Annotate \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = Core::ActionManager::registerAction(m_annotateFile, Constants::ANNOTATE, context); + command->setAttribute(Core::Command::CA_UpdateText); + connect(m_annotateFile, &QAction::triggered, this, &FossilPluginPrivate::annotateCurrentFile); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + m_diffFile = new Utils::ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = Core::ActionManager::registerAction(m_diffFile, Constants::DIFF, context); + command->setAttribute(Core::Command::CA_UpdateText); + command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+I,Meta+D") : tr("ALT+I,Alt+D"))); + connect(m_diffFile, &QAction::triggered, this, &FossilPluginPrivate::diffCurrentFile); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + m_logFile = new Utils::ParameterAction(tr("Timeline Current File"), tr("Timeline \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = Core::ActionManager::registerAction(m_logFile, Constants::LOG, context); + command->setAttribute(Core::Command::CA_UpdateText); + command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+I,Meta+L") : tr("ALT+I,Alt+L"))); + connect(m_logFile, &QAction::triggered, this, &FossilPluginPrivate::logCurrentFile); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + m_statusFile = new Utils::ParameterAction(tr("Status Current File"), tr("Status \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = Core::ActionManager::registerAction(m_statusFile, Constants::STATUS, context); + command->setAttribute(Core::Command::CA_UpdateText); + command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+I,Meta+S") : tr("ALT+I,Alt+S"))); + connect(m_statusFile, &QAction::triggered, this, &FossilPluginPrivate::statusCurrentFile); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + m_fossilContainer->addSeparator(context); + + m_addAction = new Utils::ParameterAction(tr("Add Current File"), tr("Add \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = Core::ActionManager::registerAction(m_addAction, Constants::ADD, context); + command->setAttribute(Core::Command::CA_UpdateText); + connect(m_addAction, &QAction::triggered, this, &FossilPluginPrivate::addCurrentFile); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + m_deleteAction = new Utils::ParameterAction(tr("Delete Current File..."), tr("Delete \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this); + command = Core::ActionManager::registerAction(m_deleteAction, Constants::DELETE, context); + command->setAttribute(Core::Command::CA_UpdateText); + connect(m_deleteAction, &QAction::triggered, this, &FossilPluginPrivate::deleteCurrentFile); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + m_revertFile = new Utils::ParameterAction(tr("Revert Current File..."), tr("Revert \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this); + command = Core::ActionManager::registerAction(m_revertFile, Constants::REVERT, context); + command->setAttribute(Core::Command::CA_UpdateText); + connect(m_revertFile, &QAction::triggered, this, &FossilPluginPrivate::revertCurrentFile); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); +} + +void FossilPluginPrivate::addCurrentFile() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return); + m_client.synchronousAdd(state.currentFileTopLevel(), state.relativeCurrentFile()); +} + +void FossilPluginPrivate::deleteCurrentFile() +{ + promptToDeleteCurrentFile(); +} + +void FossilPluginPrivate::annotateCurrentFile() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return); + const int lineNumber = VcsBase::VcsBaseEditor::lineNumberOfCurrentEditor(state.currentFile()); + m_client.annotate(state.currentFileTopLevel(), state.relativeCurrentFile(), lineNumber); +} + +void FossilPluginPrivate::diffCurrentFile() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return); + m_client.diff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile())); +} + +void FossilPluginPrivate::logCurrentFile() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return); + FossilClient::SupportedFeatures features = m_client.supportedFeatures(); + QStringList extraOptions; + extraOptions << "-n" << QString::number(m_client.settings().logCount.value()); + + if (features.testFlag(FossilClient::TimelineWidthFeature)) + extraOptions << "-W" << QString::number(m_client.settings().timelineWidth.value()); + + // disable annotate context menu for older client versions, used to be supported for current revision only + bool enableAnnotationContextMenu = features.testFlag(FossilClient::AnnotateRevisionFeature); + + m_client.logCurrentFile(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), + extraOptions, enableAnnotationContextMenu); +} + +void FossilPluginPrivate::revertCurrentFile() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return); + + QDialog dialog(Core::ICore::dialogParent()); + Ui::RevertDialog revertUi; + revertUi.setupUi(&dialog); + if (dialog.exec() != QDialog::Accepted) + return; + m_client.revertFile(state.currentFileTopLevel(), + state.relativeCurrentFile(), + revertUi.revisionLineEdit->text()); +} + +void FossilPluginPrivate::statusCurrentFile() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return); + m_client.status(state.currentFileTopLevel(), state.relativeCurrentFile()); +} + +void FossilPluginPrivate::createDirectoryActions(const Core::Context &context) +{ + QAction *action; + Core::Command *command; + + action = new QAction(tr("Diff"), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::DIFFMULTI, context); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::diffRepository); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + action = new QAction(tr("Timeline"), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::LOGMULTI, context); + command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+I,Meta+T") : tr("ALT+I,Alt+T"))); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::logRepository); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + action = new QAction(tr("Revert..."), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::REVERTMULTI, context); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::revertAll); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + action = new QAction(tr("Status"), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::STATUSMULTI, context); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::statusMulti); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); +} + + +void FossilPluginPrivate::diffRepository() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasTopLevel(), return); + m_client.diff(state.topLevel()); +} + +void FossilPluginPrivate::logRepository() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasTopLevel(), return); + FossilClient::SupportedFeatures features = m_client.supportedFeatures(); + QStringList extraOptions; + extraOptions << "-n" << QString::number(m_client.settings().logCount.value()); + + if (features.testFlag(FossilClient::TimelineWidthFeature)) + extraOptions << "-W" << QString::number(m_client.settings().timelineWidth.value()); + + m_client.log(state.topLevel(), {}, extraOptions); +} + +void FossilPluginPrivate::revertAll() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasTopLevel(), return); + + QDialog dialog(Core::ICore::dialogParent()); + Ui::RevertDialog revertUi; + revertUi.setupUi(&dialog); + if (dialog.exec() != QDialog::Accepted) + return; + m_client.revertAll(state.topLevel(), revertUi.revisionLineEdit->text()); +} + +void FossilPluginPrivate::statusMulti() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasTopLevel(), return); + m_client.status(state.topLevel()); +} + +void FossilPluginPrivate::createRepositoryActions(const Core::Context &context) +{ + QAction *action = 0; + Core::Command *command = 0; + + action = new QAction(tr("Pull..."), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::PULL, context); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::pull); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + action = new QAction(tr("Push..."), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::PUSH, context); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::push); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + action = new QAction(tr("Update..."), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::UPDATE, context); + command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+I,Meta+U") : tr("ALT+I,Alt+U"))); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::update); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + action = new QAction(tr("Commit..."), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::COMMIT, context); + command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+I,Meta+C") : tr("ALT+I,Alt+C"))); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::commit); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + action = new QAction(tr("Settings ..."), this); + m_repositoryActionList.append(action); + command = Core::ActionManager::registerAction(action, Constants::CONFIGURE_REPOSITORY, context); + connect(action, &QAction::triggered, this, &FossilPluginPrivate::configureRepository); + m_fossilContainer->addAction(command); + m_commandLocator->appendCommand(command); + + // Register "Create Repository..." action in global context, so that it's visible + // without active repository to allow creating a new one. + m_createRepositoryAction = new QAction(tr("Create Repository..."), this); + command = Core::ActionManager::registerAction(m_createRepositoryAction, Constants::CREATE_REPOSITORY); + connect(m_createRepositoryAction, &QAction::triggered, this, &FossilPluginPrivate::createRepository); + m_fossilContainer->addAction(command); +} + +bool FossilPluginPrivate::pullOrPush(FossilPluginPrivate::SyncMode mode) +{ + PullOrPushDialog::Mode pullOrPushMode; + switch (mode) { + case SyncPull: + pullOrPushMode = PullOrPushDialog::PullMode; + break; + case SyncPush: + pullOrPushMode = PullOrPushDialog::PushMode; + break; + default: + return false; + } + + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasTopLevel(), return false); + + PullOrPushDialog dialog(pullOrPushMode, Core::ICore::dialogParent()); + dialog.setLocalBaseDirectory(m_client.settings().defaultRepoPath.value()); + const QString defaultURL(m_client.synchronousGetRepositoryURL(state.topLevel())); + dialog.setDefaultRemoteLocation(defaultURL); + if (dialog.exec() != QDialog::Accepted) + return true; + + QString remoteLocation(dialog.remoteLocation()); + if (remoteLocation.isEmpty() && defaultURL.isEmpty()) { + VcsBase::VcsOutputWindow::appendError(tr("Remote repository is not defined.")); + return false; + } else if (remoteLocation == defaultURL) { + remoteLocation.clear(); + } + + QStringList extraOptions; + if (!remoteLocation.isEmpty() && !dialog.isRememberOptionEnabled()) + extraOptions << "--once"; + if (dialog.isPrivateOptionEnabled()) + extraOptions << "--private"; + switch (mode) { + case SyncPull: + return m_client.synchronousPull(state.topLevel(), remoteLocation, extraOptions); + case SyncPush: + return m_client.synchronousPush(state.topLevel(), remoteLocation, extraOptions); + default: + return false; + } +} + +void FossilPluginPrivate::update() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasTopLevel(), return); + + QDialog dialog(Core::ICore::dialogParent()); + Ui::RevertDialog revertUi; + revertUi.setupUi(&dialog); + dialog.setWindowTitle(tr("Update")); + if (dialog.exec() != QDialog::Accepted) + return; + m_client.update(state.topLevel(), revertUi.revisionLineEdit->text()); +} + +void FossilPluginPrivate::configureRepository() +{ + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasTopLevel(), return); + + ConfigureDialog dialog; + + // retrieve current settings from the repository + RepositorySettings currentSettings = m_client.synchronousSettingsQuery(state.topLevel()); + + dialog.setSettings(currentSettings); + if (dialog.exec() != QDialog::Accepted) + return; + const RepositorySettings newSettings = dialog.settings(); + + m_client.synchronousConfigureRepository(state.topLevel(), newSettings, currentSettings); +} + +void FossilPluginPrivate::commit() +{ + if (!promptBeforeCommit()) + return; + + if (raiseSubmitEditor()) + return; + + const VcsBase::VcsBasePluginState state = currentState(); + QTC_ASSERT(state.hasTopLevel(), return); + + m_submitRepository = state.topLevel(); + connect(&m_client, &VcsBaseClient::parsedStatus, this, &FossilPluginPrivate::showCommitWidget); + m_client.emitParsedStatus(m_submitRepository, {}); +} + +void FossilPluginPrivate::showCommitWidget(const QList &status) +{ + //Once we receive our data release the connection so it can be reused elsewhere + disconnect(&m_client, &VcsBaseClient::parsedStatus, + this, &FossilPluginPrivate::showCommitWidget); + + if (status.isEmpty()) { + VcsBase::VcsOutputWindow::appendError(tr("There are no changes to commit.")); + return; + } + + // Start new temp file for commit message + Utils::TempFileSaver saver; + // Keep the file alive, else it removes self and forgets its name + saver.setAutoRemove(false); + if (!saver.finalize()) { + VcsBase::VcsOutputWindow::appendError(saver.errorString()); + return; + } + + Core::IEditor *editor = Core::EditorManager::openEditor(saver.filePath(), Constants::COMMIT_ID); + if (!editor) { + VcsBase::VcsOutputWindow::appendError(tr("Unable to create an editor for the commit.")); + return; + } + + CommitEditor *commitEditor = qobject_cast(editor); + + if (!commitEditor) { + VcsBase::VcsOutputWindow::appendError(tr("Unable to create a commit editor.")); + return; + } + setSubmitEditor(commitEditor); + + const QString msg = tr("Commit changes for \"%1\".").arg(m_submitRepository.toUserOutput()); + commitEditor->document()->setPreferredDisplayName(msg); + + const RevisionInfo currentRevision = m_client.synchronousRevisionQuery(m_submitRepository); + const BranchInfo currentBranch = m_client.synchronousCurrentBranch(m_submitRepository); + const QString currentUser = m_client.synchronousUserDefaultQuery(m_submitRepository); + QStringList tags = m_client.synchronousTagQuery(m_submitRepository, currentRevision.id); + // Fossil includes branch name in tag list -- remove. + tags.removeAll(currentBranch.name); + commitEditor->setFields(m_submitRepository, currentBranch, tags, currentUser, status); + + connect(commitEditor, &VcsBase::VcsBaseSubmitEditor::diffSelectedFiles, + this, &FossilPluginPrivate::diffFromEditorSelected); + commitEditor->setCheckScriptWorkingDirectory(m_submitRepository); +} + +void FossilPluginPrivate::diffFromEditorSelected(const QStringList &files) +{ + m_client.diff(m_submitRepository, files); +} + +static inline bool ask(QWidget *parent, const QString &title, const QString &question, bool defaultValue = true) + +{ + const QMessageBox::StandardButton defaultButton = defaultValue ? QMessageBox::Yes : QMessageBox::No; + return QMessageBox::question(parent, title, question, QMessageBox::Yes|QMessageBox::No, defaultButton) == QMessageBox::Yes; +} + +void FossilPluginPrivate::createRepository() +{ + // re-implemented from void VcsBasePlugin::createRepository() + + // Find current starting directory + Utils::FilePath directory; + if (const ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectTree::currentProject()) + directory = currentProject->projectDirectory(); + // Prompt for a directory that is not under version control yet + QWidget *mw = Core::ICore::dialogParent(); + do { + directory = FileUtils::getExistingDirectory(nullptr, tr("Choose Checkout Directory"), directory); + if (directory.isEmpty()) + return; + const Core::IVersionControl *managingControl = Core::VcsManager::findVersionControlForDirectory(directory); + if (managingControl == 0) + break; + const QString question = tr("The directory \"%1\" is already managed by a version control system (%2)." + " Would you like to specify another directory?").arg(directory.toUserOutput(), managingControl->displayName()); + + if (!ask(mw, tr("Repository already under version control"), question)) + return; + } while (true); + // Create + const bool rc = vcsCreateRepository(directory); + const QString nativeDir = directory.toUserOutput(); + if (rc) { + QMessageBox::information(mw, tr("Repository Created"), + tr("A version control repository has been created in %1."). + arg(nativeDir)); + } else { + QMessageBox::warning(mw, tr("Repository Creation Failed"), + tr("A version control repository could not be created in %1."). + arg(nativeDir)); + } +} + +bool FossilPluginPrivate::activateCommit() +{ + CommitEditor *commitEditor = qobject_cast(submitEditor()); + QTC_ASSERT(commitEditor, return true); + Core::IDocument *editorDocument = commitEditor->document(); + QTC_ASSERT(editorDocument, return true); + + QStringList files = commitEditor->checkedFiles(); + if (!files.empty()) { + //save the commit message + if (!Core::DocumentManager::saveDocument(editorDocument)) + return false; + + //rewrite entries of the form 'file => newfile' to 'newfile' because + //this would mess the commit command + for (QStringList::iterator iFile = files.begin(); iFile != files.end(); ++iFile) { + const QStringList parts = iFile->split(" => ", Qt::SkipEmptyParts); + if (!parts.isEmpty()) + *iFile = parts.last(); + } + + FossilCommitWidget *commitWidget = commitEditor->commitWidget(); + QStringList extraOptions; + // Author -- override the repository-default user + if (!commitWidget->committer().isEmpty()) + extraOptions << "--user" << commitWidget->committer(); + // Branch + QString branch = commitWidget->newBranch(); + if (!branch.isEmpty()) { + // @TODO: make enquote utility function + QString enquotedBranch = branch; + if (branch.contains(QRegularExpression("\\s"))) + enquotedBranch = QString("\"") + branch + "\""; + extraOptions << "--branch" << enquotedBranch; + } + // Tags + const QStringList tags = commitWidget->tags(); + for (const QString &tag : tags) { + extraOptions << "--tag" << tag; + } + + // Whether local commit or not + if (commitWidget->isPrivateOptionEnabled()) + extraOptions += "--private"; + m_client.commit(m_submitRepository, files, editorDocument->filePath().toString(), extraOptions); + } + return true; +} + + +void FossilPluginPrivate::updateActions(VcsBase::VcsBasePluginPrivate::ActionState as) +{ + m_createRepositoryAction->setEnabled(true); + + if (!enableMenuAction(as, m_menuAction)) { + m_commandLocator->setEnabled(false); + return; + } + const QString filename = currentState().currentFileName(); + const bool repoEnabled = currentState().hasTopLevel(); + m_commandLocator->setEnabled(repoEnabled); + + m_annotateFile->setParameter(filename); + m_diffFile->setParameter(filename); + m_logFile->setParameter(filename); + m_addAction->setParameter(filename); + m_deleteAction->setParameter(filename); + m_revertFile->setParameter(filename); + m_statusFile->setParameter(filename); + + for (QAction *repoAction : qAsConst(m_repositoryActionList)) + repoAction->setEnabled(repoEnabled); +} + +QString FossilPluginPrivate::displayName() const +{ + return tr("Fossil"); +} + +Id FossilPluginPrivate::id() const +{ + return Id(Constants::VCS_ID_FOSSIL); +} + +bool FossilPluginPrivate::isVcsFileOrDirectory(const FilePath &filePath) const +{ + return m_client.isVcsFileOrDirectory(filePath); +} + +bool FossilPluginPrivate::managesDirectory(const FilePath &directory, FilePath *topLevel) const +{ + const FilePath topLevelFound = m_client.findTopLevelForFile(directory); + if (topLevel) + *topLevel = topLevelFound; + return !topLevelFound.isEmpty(); +} + +bool FossilPluginPrivate::managesFile(const FilePath &workingDirectory, const QString &fileName) const +{ + return m_client.managesFile(workingDirectory, fileName); +} + +bool FossilPluginPrivate::isConfigured() const +{ + const Utils::FilePath binary = m_client.vcsBinary(); + if (binary.isEmpty()) + return false; + + const QFileInfo fi = binary.toFileInfo(); + if ( !(fi.exists() && fi.isFile() && fi.isExecutable()) ) + return false; + + // Local repositories default path must be set and exist + const QString repoPath = m_client.settings().defaultRepoPath.value(); + if (repoPath.isEmpty()) + return false; + + const QDir dir(repoPath); + if (!dir.exists()) + return false; + + return true; +} + +bool FossilPluginPrivate::supportsOperation(Operation operation) const +{ + bool supported = isConfigured(); + + switch (operation) { + case Core::IVersionControl::AddOperation: + case Core::IVersionControl::DeleteOperation: + case Core::IVersionControl::MoveOperation: + case Core::IVersionControl::CreateRepositoryOperation: + case Core::IVersionControl::AnnotateOperation: + case Core::IVersionControl::InitialCheckoutOperation: + break; + case Core::IVersionControl::SnapshotOperations: + supported = false; + break; + } + return supported; +} + +bool FossilPluginPrivate::vcsOpen(const FilePath &filePath) +{ + Q_UNUSED(filePath) + return true; +} + +bool FossilPluginPrivate::vcsAdd(const FilePath &filePath) +{ + return m_client.synchronousAdd(filePath.absolutePath(), filePath.fileName()); +} + +bool FossilPluginPrivate::vcsDelete(const FilePath &filePath) +{ + return m_client.synchronousRemove(filePath.absolutePath(), filePath.fileName()); +} + +bool FossilPluginPrivate::vcsMove(const FilePath &from, const FilePath &to) +{ + const QFileInfo fromInfo = from.toFileInfo(); + const QFileInfo toInfo = to.toFileInfo(); + return m_client.synchronousMove(from.absolutePath(), fromInfo.absoluteFilePath(), + toInfo.absoluteFilePath()); +} + +bool FossilPluginPrivate::vcsCreateRepository(const FilePath &directory) +{ + return m_client.synchronousCreateRepository(directory); +} + +void FossilPluginPrivate::vcsAnnotate(const FilePath &filePath, int line) +{ + m_client.annotate(filePath.absolutePath(), filePath.fileName(), line); +} + +void FossilPluginPrivate::vcsDescribe(const FilePath &source, const QString &id) +{ + m_client.view(source, id); +} + +VcsCommand *FossilPluginPrivate::createInitialCheckoutCommand(const QString &sourceUrl, + const FilePath &baseDirectory, + const QString &localName, + const QStringList &extraArgs) +{ + const QMap options = FossilJsExtension::parseArgOptions(extraArgs); + + // Two operating modes: + // 1) CloneCheckout: + // -- clone from remote-URL or a local-fossil a repository into a local-clone fossil. + // -- open/checkout the local-clone fossil + // The local-clone fossil must not point to an existing repository. + // Clone URL may be either schema-based (http, ssh, file) or an absolute local path. + // + // 2) LocalCheckout: + // -- open/checkout an existing local fossil + // Clone URL is an absolute local path and is the same as the local fossil. + + const Utils::FilePath checkoutPath = baseDirectory.pathAppended(localName); + const QString fossilFile = options.value("fossil-file"); + const Utils::FilePath fossilFilePath = Utils::FilePath::fromUserInput(QDir::fromNativeSeparators(fossilFile)); + const QString fossilFileNative = fossilFilePath.toUserOutput(); + const QFileInfo cloneRepository(fossilFilePath.toString()); + + // Check when requested to clone a local repository and clone-into repository file is the same + // or not specified. + // In this case handle it as local fossil checkout request. + const QUrl url(sourceUrl); + bool isLocalRepository = (options.value("repository-type") == "localRepo"); + + if (url.isLocalFile() || url.isRelative()) { + const QFileInfo sourcePath(url.path()); + isLocalRepository = (sourcePath.canonicalFilePath() == cloneRepository.canonicalFilePath()); + } + + // set clone repository admin user to configured user name + // OR override it with the specified user from clone panel + const QString adminUser = options.value("admin-user"); + const bool disableAutosync = (options.value("settings-autosync") == "off"); + const QString checkoutBranch = options.value("branch-tag"); + + // first create the checkout directory, + // as it needs to become a working directory for wizard command jobs + checkoutPath.createDir(); + + // Setup the wizard page command job + auto command = VcsBaseClient::createVcsCommand(checkoutPath, m_client.processEnvironment()); + + if (!isLocalRepository + && !cloneRepository.exists()) { + + const QString sslIdentityFile = options.value("ssl-identity"); + const Utils::FilePath sslIdentityFilePath = Utils::FilePath::fromUserInput(QDir::fromNativeSeparators(sslIdentityFile)); + const bool includePrivate = (options.value("include-private") == "true"); + + QStringList extraOptions; + if (includePrivate) + extraOptions << "--private"; + if (!sslIdentityFile.isEmpty()) + extraOptions << "--ssl-identity" << sslIdentityFilePath.toUserOutput(); + if (!adminUser.isEmpty()) + extraOptions << "--admin-user" << adminUser; + + // Fossil allows saving the remote address and login. This is used to + // facilitate autosync (commit/update) functionality. + // When no password is given, it prompts for that. + // When both username and password are specified, it prompts whether to + // save them. + // NOTE: In non-interactive context, these prompts won't work. + // Fossil currently does not support SSH_ASKPASS way for login query. + // + // Alternatively, "--once" option does not save the remote details. + // In such case remote details must be provided on the command-line every + // time. This also precludes autosync. + // + // So here we want Fossil to save the remote details when specified. + + QStringList args; + args << m_client.vcsCommandString(FossilClient::CloneCommand) + << extraOptions + << sourceUrl + << fossilFileNative; + command->addJob({m_client.vcsBinary(), args}, -1); + } + + // check out the cloned repository file into the working copy directory; + // by default the latest revision is checked out + + QStringList args({"open", fossilFileNative}); + if (!checkoutBranch.isEmpty()) + args << checkoutBranch; + command->addJob({m_client.vcsBinary(), args}, -1); + + // set user default to admin user if specified + if (!isLocalRepository + && !adminUser.isEmpty()) { + const QStringList args({ "user", "default", adminUser, "--user", adminUser}); + command->addJob({m_client.vcsBinary(), args}, -1); + } + + // turn-off autosync if requested + if (!isLocalRepository + && disableAutosync) { + const QStringList args({"settings", "autosync", "off"}); + command->addJob({m_client.vcsBinary(), args}, -1); + } + + return command; +} + +void FossilPluginPrivate::changed(const QVariant &v) +{ + switch (v.type()) { + case QVariant::String: + emit repositoryChanged(Utils::FilePath::fromVariant(v)); + break; + case QVariant::StringList: + emit filesChanged(v.toStringList()); + break; + default: + break; + } +} + +} // namespace Internal +} // namespace Fossil + +#ifdef WITH_TESTS +#include + +void Fossil::Internal::FossilPlugin::testDiffFileResolving_data() +{ + QTest::addColumn("header"); + QTest::addColumn("fileName"); + + QTest::newRow("New") << QByteArray( + "ADDED src/plugins/fossil/fossilclient.cpp\n" + "Index: src/plugins/fossil/fossilclient.cpp\n" + "==================================================================\n" + "--- src/plugins/fossil/fossilclient.cpp\n" + "+++ src/plugins/fossil/fossilclient.cpp\n" + "@@ -0,0 +1,295 @@\n" + ) + << QByteArray("src/plugins/fossil/fossilclient.cpp"); + QTest::newRow("Deleted") << QByteArray( + "DELETED src/plugins/fossil/fossilclient.cpp\n" + "Index: src/plugins/fossil/fossilclient.cpp\n" + "==================================================================\n" + "--- src/plugins/fossil/fossilclient.cpp\n" + "+++ src/plugins/fossil/fossilclient.cpp\n" + "@@ -1,266 +0,0 @@\n" + ) + << QByteArray("src/plugins/fossil/fossilclient.cpp"); + QTest::newRow("Modified") << QByteArray( + "Index: src/plugins/fossil/fossilclient.cpp\n" + "==================================================================\n" + "--- src/plugins/fossil/fossilclient.cpp\n" + "+++ src/plugins/fossil/fossilclient.cpp\n" + "@@ -112,22 +112,37 @@\n" + ) + << QByteArray("src/plugins/fossil/fossilclient.cpp"); +} + +void Fossil::Internal::FossilPlugin::testDiffFileResolving() +{ + VcsBase::VcsBaseEditorWidget::testDiffFileResolving(dd->diffFactory); +} + +void Fossil::Internal::FossilPlugin::testLogResolving() +{ + QByteArray data( + "=== 2014-03-08 ===\n" + "22:14:02 [ac6d1129b8] Change scaling algorithm. (user: ninja tags: ninja-fixes-5.1)\n" + " EDITED src/core/scaler.cpp\n" + "20:23:51 [56d6917c3b] *BRANCH* Add width option (conditional). (user: ninja tags: ninja-fixes-5.1)\n" + " EDITED src/core/scaler.cpp\n" + " EDITED src/core/scaler.h\n" + ); + VcsBase::VcsBaseEditorWidget::testLogResolving(dd->fileLogFactory, data, "ac6d1129b8", "56d6917c3b"); +} +#endif diff --git a/src/plugins/fossil/fossilplugin.h b/src/plugins/fossil/fossilplugin.h new file mode 100644 index 00000000000..4046a83f888 --- /dev/null +++ b/src/plugins/fossil/fossilplugin.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "fossilsettings.h" + +#include +#include +#include + +namespace Fossil { +namespace Internal { + +class FossilClient; + +class FossilPlugin final : public ExtensionSystem::IPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Fossil.json") + + ~FossilPlugin() final; + + bool initialize(const QStringList &arguments, QString *errorMessage) final; + void extensionsInitialized() final; + +public: + static const FossilSettings &settings(); + static FossilClient *client(); + +#ifdef WITH_TESTS +private slots: + void testDiffFileResolving_data(); + void testDiffFileResolving(); + void testLogResolving(); +#endif +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/fossilsettings.cpp b/src/plugins/fossil/fossilsettings.cpp new file mode 100644 index 00000000000..72bb99dba68 --- /dev/null +++ b/src/plugins/fossil/fossilsettings.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "fossilsettings.h" + +#include "constants.h" + +#include + +#include +#include + +#include + +using namespace Utils; + +namespace Fossil { +namespace Internal { + +FossilSettings::FossilSettings() +{ + setSettingsGroup(Constants::FOSSIL); + setAutoApply(false); + + registerAspect(&binaryPath); + binaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); + binaryPath.setExpectedKind(PathChooser::ExistingCommand); + binaryPath.setDefaultValue(Constants::FOSSILDEFAULT); + binaryPath.setDisplayName(tr("Fossil Command")); + binaryPath.setHistoryCompleter("Fossil.Command.History"); + binaryPath.setLabelText(tr("Command:")); + + registerAspect(&defaultRepoPath); + defaultRepoPath.setSettingsKey("defaultRepoPath"); + defaultRepoPath.setDisplayStyle(StringAspect::PathChooserDisplay); + defaultRepoPath.setExpectedKind(PathChooser::Directory); + defaultRepoPath.setDisplayName(tr("Fossil Repositories")); + defaultRepoPath.setLabelText(tr("Default path:")); + defaultRepoPath.setToolTip(tr("Directory to store local repositories by default.")); + + registerAspect(&userName); + userName.setDisplayStyle(StringAspect::LineEditDisplay); + userName.setLabelText(tr("Default user:")); + userName.setToolTip(tr("Existing user to become an author of changes made to the repository.")); + + registerAspect(&sslIdentityFile); + sslIdentityFile.setSettingsKey("sslIdentityFile"); + sslIdentityFile.setDisplayStyle(StringAspect::PathChooserDisplay); + sslIdentityFile.setExpectedKind(PathChooser::File); + sslIdentityFile.setDisplayName(tr("SSL/TLS Identity Key")); + sslIdentityFile.setLabelText(tr("SSL/TLS identity:")); + sslIdentityFile.setToolTip(tr("SSL/TLS client identity key to use if requested by the server.")); + + registerAspect(&diffIgnoreAllWhiteSpace); + diffIgnoreAllWhiteSpace.setSettingsKey("diffIgnoreAllWhiteSpace"); + + registerAspect(&diffStripTrailingCR); + diffStripTrailingCR.setSettingsKey("diffStripTrailingCR"); + + registerAspect(&annotateShowCommitters); + annotateShowCommitters.setSettingsKey("annotateShowCommitters"); + + registerAspect(&annotateListVersions); + annotateListVersions.setSettingsKey("annotateListVersions"); + + registerAspect(&timelineWidth); + timelineWidth.setSettingsKey("timelineWidth"); + timelineWidth.setLabelText(tr("Log width:")); + timelineWidth.setToolTip(tr("The width of log entry line (>20). " + "Choose 0 to see a single line per entry.")); + + registerAspect(&timelineLineageFilter); + timelineLineageFilter.setSettingsKey("timelineLineageFilter"); + + registerAspect(&timelineVerbose); + timelineVerbose.setSettingsKey("timelineVerbose"); + + registerAspect(&timelineItemType); + timelineItemType.setDefaultValue("all"); + timelineItemType.setSettingsKey("timelineItemType"); + + registerAspect(&disableAutosync); + disableAutosync.setSettingsKey("disableAutosync"); + disableAutosync.setDefaultValue(true); + disableAutosync.setLabelText(tr("Disable auto-sync")); + disableAutosync.setToolTip(tr("Disable automatic pull prior to commit or update and " + "automatic push after commit or tag or branch creation.")); + + registerAspect(&timeout); + timeout.setLabelText(tr("Timeout:")); + timeout.setSuffix(tr("s")); + + registerAspect(&logCount); + logCount.setLabelText(tr("Log count:")); + logCount.setToolTip(tr("The number of recent commit log entries to show. " + "Choose 0 to see all entries.")); +}; + +// OptionsPage + +class OptionsPageWidget final : public Core::IOptionsPageWidget +{ + Q_DECLARE_TR_FUNCTIONS(Fossil::Internal::OptionsPageWidget) + +public: + OptionsPageWidget(const std::function &onApply, FossilSettings *settings); + void apply() final; + +private: + const std::function m_onApply; + FossilSettings *m_settings; +}; + +void OptionsPageWidget::apply() +{ + if (!m_settings->isDirty()) + return; + + m_settings->apply(); + m_onApply(); +} + +OptionsPageWidget::OptionsPageWidget(const std::function &onApply, FossilSettings *settings) : + m_onApply(onApply), + m_settings(settings) +{ + FossilSettings &s = *m_settings; + + using namespace Layouting; + + Column { + Group { + title(tr("Configuration")), + Row { s.binaryPath } + }, + + Group { + title(tr("Local Repositories")), + Row { s.defaultRepoPath } + }, + Group { + title(tr("User")), + Form { + s.userName, br, + s.sslIdentityFile + } + }, + + Group { + title(tr("Miscellaneous")), + Column { + Row { + s.logCount, + s.timelineWidth, + s.timeout, + st + }, + s.disableAutosync + }, + }, + st + + }.attachTo(this); +} + +OptionsPage::OptionsPage(const std::function &onApply, FossilSettings *settings) +{ + setId(Constants::VCS_ID_FOSSIL); + setDisplayName(OptionsPageWidget::tr("Fossil")); + setWidgetCreator([onApply, settings]() { return new OptionsPageWidget(onApply, settings); }); + setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); +} + +} // Internal +} // Fossil diff --git a/src/plugins/fossil/fossilsettings.h b/src/plugins/fossil/fossilsettings.h new file mode 100644 index 00000000000..f06e683c6ea --- /dev/null +++ b/src/plugins/fossil/fossilsettings.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +namespace Fossil { +namespace Internal { + +class FossilSettings : public VcsBase::VcsBaseSettings +{ +public: + Utils::StringAspect defaultRepoPath; + Utils::StringAspect sslIdentityFile; + Utils::BoolAspect diffIgnoreAllWhiteSpace; + Utils::BoolAspect diffStripTrailingCR; + Utils::BoolAspect annotateShowCommitters; + Utils::BoolAspect annotateListVersions; + Utils::IntegerAspect timelineWidth; + Utils::StringAspect timelineLineageFilter; + Utils::BoolAspect timelineVerbose; + Utils::StringAspect timelineItemType; + Utils::BoolAspect disableAutosync; + + FossilSettings(); +}; + +struct RepositorySettings +{ + enum AutosyncMode {AutosyncOff, AutosyncOn, AutosyncPullOnly}; + + QString user; + QString sslIdentityFile; + AutosyncMode autosync = AutosyncOn; +}; + +inline bool operator==(const RepositorySettings &lh, const RepositorySettings &rh) +{ + return (lh.user == rh.user && + lh.sslIdentityFile == rh.sslIdentityFile && + lh.autosync == rh.autosync); +} + +class OptionsPage : public Core::IOptionsPage +{ +public: + OptionsPage(const std::function &onApply, FossilSettings *settings); +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/pullorpushdialog.cpp b/src/plugins/fossil/pullorpushdialog.cpp new file mode 100644 index 00000000000..660407087d3 --- /dev/null +++ b/src/plugins/fossil/pullorpushdialog.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "pullorpushdialog.h" +#include "ui_pullorpushdialog.h" + +#include "constants.h" + +#include + +namespace Fossil { +namespace Internal { + +PullOrPushDialog::PullOrPushDialog(Mode mode, QWidget *parent) : QDialog(parent), + m_mode(mode), + m_ui(new Ui::PullOrPushDialog) +{ + m_ui->setupUi(this); + m_ui->localPathChooser->setExpectedKind(Utils::PathChooser::File); + m_ui->localPathChooser->setPromptDialogFilter(tr(Constants::FOSSIL_FILE_FILTER)); + + switch (m_mode) { + case PullMode: + this->setWindowTitle(tr("Pull Source")); + break; + case PushMode: + this->setWindowTitle(tr("Push Destination")); + break; + } + + // select URL text in line edit when clicking the radio button + m_ui->localButton->setFocusProxy(m_ui->localPathChooser); + m_ui->urlButton->setFocusProxy(m_ui->urlLineEdit); + connect(m_ui->urlButton, &QRadioButton::clicked, m_ui->urlLineEdit, &QLineEdit::selectAll); + + this->adjustSize(); +} + +PullOrPushDialog::~PullOrPushDialog() +{ + delete m_ui; +} + +QString PullOrPushDialog::remoteLocation() const +{ + if (m_ui->defaultButton->isChecked()) + return QString(); + if (m_ui->localButton->isChecked()) + return m_ui->localPathChooser->filePath().toString(); + return m_ui->urlLineEdit->text(); +} + +bool PullOrPushDialog::isRememberOptionEnabled() const +{ + if (m_ui->defaultButton->isChecked()) + return false; + return m_ui->rememberCheckBox->isChecked(); +} + +bool PullOrPushDialog::isPrivateOptionEnabled() const +{ + return m_ui->privateCheckBox->isChecked(); +} + +void PullOrPushDialog::setDefaultRemoteLocation(const QString &url) +{ + m_ui->urlLineEdit->setText(url); +} + +void PullOrPushDialog::setLocalBaseDirectory(const QString &dir) +{ + m_ui->localPathChooser->setBaseDirectory(Utils::FilePath::fromString(dir)); +} + +void PullOrPushDialog::changeEvent(QEvent *e) +{ + QDialog::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + m_ui->retranslateUi(this); + break; + default: + break; + } +} + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/pullorpushdialog.h b/src/plugins/fossil/pullorpushdialog.h new file mode 100644 index 00000000000..f992212c491 --- /dev/null +++ b/src/plugins/fossil/pullorpushdialog.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace Fossil { +namespace Internal { + +namespace Ui { class PullOrPushDialog; } + +class PullOrPushDialog : public QDialog +{ + Q_OBJECT + +public: + enum Mode { + PullMode, + PushMode + }; + + explicit PullOrPushDialog(Mode mode, QWidget *parent = nullptr); + ~PullOrPushDialog() final; + + // Common parameters and options + QString remoteLocation() const; + bool isRememberOptionEnabled() const; + bool isPrivateOptionEnabled() const; + void setDefaultRemoteLocation(const QString &url); + void setLocalBaseDirectory(const QString &dir); + // Pull-specific options + // Push-specific options + +protected: + void changeEvent(QEvent *e) final; + +private: + Mode m_mode; + Ui::PullOrPushDialog *m_ui; +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/pullorpushdialog.ui b/src/plugins/fossil/pullorpushdialog.ui new file mode 100644 index 00000000000..eec47059a67 --- /dev/null +++ b/src/plugins/fossil/pullorpushdialog.ui @@ -0,0 +1,235 @@ + + + Fossil::Internal::PullOrPushDialog + + + + 0 + 0 + 477 + 268 + + + + Dialog + + + + + + Remote Location + + + + + + Default location + + + true + + + + + + + Local filesystem: + + + + + + + false + + + + + + + For example: https://[user[:pass]@]host[:port]/[path] + + + Specify URL: + + + + + + + false + + + For example: https://[user[:pass]@]host[:port]/[path] + + + + + + + + + + Options + + + + + + false + + + Remember specified location as default + + + + + + + Allow transfer of private branches. + + + Include private branches + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Qt::Vertical + + + + 20 + 4 + + + + + + + + + Utils::PathChooser + QWidget +
utils/pathchooser.h
+ 1 + + editingFinished() + browsingFinished() + +
+
+ + + + buttonBox + accepted() + Fossil::Internal::PullOrPushDialog + accept() + + + 257 + 177 + + + 157 + 274 + + + + + buttonBox + rejected() + Fossil::Internal::PullOrPushDialog + reject() + + + 325 + 177 + + + 286 + 274 + + + + + urlButton + toggled(bool) + urlLineEdit + setEnabled(bool) + + + 80 + 121 + + + 332 + 123 + + + + + localButton + toggled(bool) + localPathChooser + setEnabled(bool) + + + 112 + 81 + + + 346 + 81 + + + + + urlButton + toggled(bool) + rememberCheckBox + setEnabled(bool) + + + 71 + 92 + + + 163 + 153 + + + + + localButton + toggled(bool) + rememberCheckBox + setEnabled(bool) + + + 71 + 67 + + + 163 + 153 + + + + +
diff --git a/src/plugins/fossil/revertdialog.ui b/src/plugins/fossil/revertdialog.ui new file mode 100644 index 00000000000..1d43276410d --- /dev/null +++ b/src/plugins/fossil/revertdialog.ui @@ -0,0 +1,106 @@ + + + Fossil::Internal::RevertDialog + + + + 0 + 0 + 400 + 120 + + + + Revert + + + + + + Specify a revision other than the default? + + + true + + + false + + + + + 10 + 30 + 361 + 31 + + + + + + + Checkout revision, can also be a branch or a tag name. + + + Revision: + + + + + + + Checkout revision, can also be a branch or a tag name. + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Fossil::Internal::RevertDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Fossil::Internal::RevertDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/plugins/fossil/revisioninfo.h b/src/plugins/fossil/revisioninfo.h new file mode 100644 index 00000000000..984c61bdf6e --- /dev/null +++ b/src/plugins/fossil/revisioninfo.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +namespace Fossil { +namespace Internal { + +class RevisionInfo +{ +public: + const QString id; + const QString parentId; + const QStringList mergeParentIds; + const QString commentMsg; + const QString committer; +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/wizard/fossiljsextension.cpp b/src/plugins/fossil/wizard/fossiljsextension.cpp new file mode 100644 index 00000000000..dad175dde43 --- /dev/null +++ b/src/plugins/fossil/wizard/fossiljsextension.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "fossiljsextension.h" +#include "../constants.h" +#include "../fossilclient.h" +#include "../fossilplugin.h" + +#include +#include + +#include +#include + +using namespace Core; + +namespace Fossil { +namespace Internal { + +class FossilJsExtensionPrivate { +public: + FossilJsExtensionPrivate(FossilSettings *settings) : + m_vscId(Constants::VCS_ID_FOSSIL), + m_settings(settings) + { + } + + Utils::Id m_vscId; + FossilSettings *m_settings; +}; + + +QMap FossilJsExtension::parseArgOptions(const QStringList &args) +{ + QMap options; + for (const QString &arg : args) { + if (arg.isEmpty()) + continue; + const QStringList opt = arg.split('|', Qt::KeepEmptyParts); + options.insert(opt[0], opt.size() > 1 ? opt[1] : QString()); + } + return options; +} + +FossilJsExtension::FossilJsExtension(FossilSettings *settings) : + d(new FossilJsExtensionPrivate(settings)) +{ } + +FossilJsExtension::~FossilJsExtension() +{ + delete d; +} + +bool FossilJsExtension::isConfigured() const +{ + IVersionControl *vc = VcsManager::versionControl(d->m_vscId); + return vc && vc->isConfigured(); +} + +QString FossilJsExtension::displayName() const +{ + IVersionControl *vc = VcsManager::versionControl(d->m_vscId); + return vc ? vc->displayName() : QString(); +} + +QString FossilJsExtension::defaultAdminUser() const +{ + if (!isConfigured()) + return QString(); + + return d->m_settings->userName.value(); +} + +QString FossilJsExtension::defaultSslIdentityFile() const +{ + if (!isConfigured()) + return QString(); + + return d->m_settings->sslIdentityFile.value(); +} + +QString FossilJsExtension::defaultLocalRepoPath() const +{ + if (!isConfigured()) + return QString(); + + return d->m_settings->defaultRepoPath.value(); +} + +bool FossilJsExtension::defaultDisableAutosync() const +{ + if (!isConfigured()) + return false; + + return d->m_settings->disableAutosync.value(); +} + +} // namespace Internal +} // namespace Fossil + diff --git a/src/plugins/fossil/wizard/fossiljsextension.h b/src/plugins/fossil/wizard/fossiljsextension.h new file mode 100644 index 00000000000..3d04f235434 --- /dev/null +++ b/src/plugins/fossil/wizard/fossiljsextension.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (c) 2018 Artur Shepilko +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +#include +#include +#include + +namespace Fossil { +namespace Internal { + +class FossilJsExtensionPrivate; +class FossilSettings; + +class FossilJsExtension : public QObject +{ + Q_OBJECT + +public: + static QMap parseArgOptions(const QStringList &args); + + FossilJsExtension(FossilSettings *settings); + ~FossilJsExtension(); + + Q_INVOKABLE bool isConfigured() const; + Q_INVOKABLE QString displayName() const; + Q_INVOKABLE QString defaultAdminUser() const; + Q_INVOKABLE QString defaultSslIdentityFile() const; + Q_INVOKABLE QString defaultLocalRepoPath() const; + Q_INVOKABLE bool defaultDisableAutosync() const; + +private: + FossilJsExtensionPrivate *d = nullptr; +}; + +} // namespace Internal +} // namespace Fossil diff --git a/src/plugins/fossil/wizard/projects/vcs/icon.png b/src/plugins/fossil/wizard/projects/vcs/icon.png new file mode 100644 index 00000000000..66cf5c5ca37 Binary files /dev/null and b/src/plugins/fossil/wizard/projects/vcs/icon.png differ diff --git a/src/plugins/fossil/wizard/projects/vcs/icon@2x.png b/src/plugins/fossil/wizard/projects/vcs/icon@2x.png new file mode 100644 index 00000000000..2b4cb285e09 Binary files /dev/null and b/src/plugins/fossil/wizard/projects/vcs/icon@2x.png differ diff --git a/src/plugins/fossil/wizard/projects/vcs/wizard.json b/src/plugins/fossil/wizard/projects/vcs/wizard.json new file mode 100644 index 00000000000..56e08b63774 --- /dev/null +++ b/src/plugins/fossil/wizard/projects/vcs/wizard.json @@ -0,0 +1,255 @@ +{ + "version": 1, + "supportedProjectTypes": [ "UNKNOWN_PROJECT" ], + "id": "I.Fossil", + "category": "T.Import", + "trDescription": "Clones a Fossil repository and tries to load the contained project.", + "trDisplayName": "Fossil Clone", + "trDisplayCategory": "Import Project", + "icon": "icon.png", + "enabled": "%{JS: [ %{Plugins} ].indexOf('Fossil') >= 0}", + + "options": + [ + { "key": "vcsId", "value": "I.Fossil" }, + { "key": "vcsName", "value": "%{JS: Vcs.displayName('%{vcsId}')}" }, + { "key": "isCloneRepo", "value": "%{JS: '%{RepoType}' === 'cloneRepo' }" }, + { "key": "isLocalRepo", "value": "%{JS: '%{RepoType}' === 'localRepo' }" }, + { "key": "SR", "value": "%{JS: '%{Repo}'.substr('%{Repo}'.indexOf(':') + 1).replace(/[^/@]+@/,'').replace(/:[0-9]+$/,'').replace(/[.](fossil|fsl)$/, '') }"}, + { "key": "defaultDir", "value": "%{JS: %{isCloneRepo} ? '%{SR}'.substr('%{SR}'.lastIndexOf('/') + 1).replace(/[.:\"]+/g, '-') : %{isLocalRepo} ? Util.baseName('%{LocalRepo}') : '' }"}, + { "key": "defaultFossilName", "value": "%{JS: %{isCloneRepo} ? '%{defaultDir}' : %{isLocalRepo} ? Util.completeBaseName('%{LocalRepo}') : '' }" }, + { "key": "defaultLocalRepoPath", "value": "%{JS: Fossil.defaultLocalRepoPath() }" }, + { "key": "defaultSslIdentityFile", "value": "%{JS: Fossil.defaultSslIdentityFile() }" }, + { "key": "defaultDisableAutosync", "value": "%{JS: Fossil.defaultDisableAutosync() }" }, + { "key": "SourceRepo", "value": "%{JS: %{isCloneRepo} ? '%{Repo}' : %{isLocalRepo} ? '%{LocalRepo}' : '' }" }, + { "key": "TargetPath", "value": "%{Path}/%{Dir}" }, + { "key": "FossilFile", "value": "%{defaultLocalRepoPath}/%{FossilName}.fossil" }, + { "key": "argRepoType", "value": "repository-type|%{RepoType}" }, + { "key": "argBranchTag", "value": "%{JS: '%{Branch}' ? 'branch-tag|%{Branch}' : '' }" }, + { "key": "argAdminUser", "value": "%{JS: '%{AdminUser}' ? 'admin-user|%{AdminUser}' : '' }" }, + { "key": "argSslIdentity", "value": "%{JS: '%{SslIdentity}' ? 'ssl-identity|%{SslIdentity}' : '' }" }, + { "key": "argIncludePrivate", "value": "%{JS: '%{IncludePrivate}' ? 'include-private|%{IncludePrivate}' : '' }" }, + { "key": "argDisableAutosync", "value": "%{JS: '%{DisableAutosync}' ? 'settings-autosync|%{DisableAutosync}' : '' }" }, + { "key": "argFossilFile", "value": "fossil-file|%{FossilFile}" } + ], + + "pages": + [ + { + "trDisplayName": "Configuration", + "trShortTitle": "Configuration", + "trSubTitle": "Please configure %{vcsName} now.", + "typeId": "VcsConfiguration", + "enabled": "%{JS: !Vcs.isConfigured('%{vcsId}')}", + "data": { "vcsId": "%{vcsId}" } + }, + { + "trDisplayName": "Select repository location type", + "trShortTitle": "Repository", + "typeId": "Fields", + "data": + [ + { + "name": "RepoType", + "type": "ComboBox", + "data": + { + "index": 0, + "items": + [ + { "trKey": "Remote repository clone", "value": "cloneRepo" }, + { "trKey": "Local repository checkout", "value": "localRepo" } + ] + } + } + ] + }, + { + "trDisplayName": "Location", + "trShortTitle": "Location", + "trSubTitle": "Specify repository location, branch, checkout destination, and options.", + "typeId": "Fields", + "data" : + [ + { + "name": "GotSource", + "type": "LineEdit", + "visible": false, + "mandatory": true, + "isComplete": "%{JS: '%{FossilName}' === '' || (%{isCloneRepo} && !Util.exists('%{FossilFile}')) }", + "trIncompleteMessage": "The clone fossil already exists in local repositories path.", + "data": + { + "trText": "%{JS: (%{isCloneRepo} && '%{Repo}' !== '' && '%{FossilName}' !== '') || (%{isLocalRepo} && '%{LocalRepo}' !== '') ? 'true' : '' }" + } + }, + { + "name": "Repo", + "trDisplayName": "Remote repository:", + "trToolTip": "For example: https://[user[:pass]@]host[:port]/[path]", + "type": "LineEdit", + "enabled": "%{isCloneRepo}", + "mandatory": false + }, + { + "name": "FossilName", + "trDisplayName": "Local clone:", + "trToolTip": "Base name of a new local fossil file to clone into.", + "type": "LineEdit", + "enabled": "%{isCloneRepo}", + "mandatory": false, + "data": + { + "trText": "%{defaultFossilName}" + } + }, + { + "name": "LocalRepo", + "trDisplayName": "Local repository:", + "trToolTip": "Path of an existing local fossil file to check out from.", + "type": "PathChooser", + "enabled": "%{isLocalRepo}", + "mandatory": false, + "data": + { + "kind": "file", + "basePath": "%{defaultLocalRepoPath}" + } + }, + { + "name": "Branch", + "trDisplayName": "Branch:", + "type": "LineEdit", + "mandatory": false, + "data": + { + "trPlaceholder": "" + } + }, + { + "name": "Sp1", + "type": "Spacer", + "data": { "factor": 2 } + }, + { + "name": "Dir", + "trDisplayName": "Checkout directory:", + "type": "LineEdit", + "isComplete": "%{JS: '%{Dir}' === '' || !Util.exists('%{TargetPath}')}", + "trIncompleteMessage": "The checkout directory already exists in the filesystem.", + "data": + { + "trText": "%{defaultDir}" + } + }, + { + "name": "Path", + "trDisplayName": "Create in:", + "type": "PathChooser", + "data": + { + "kind": "existingDirectory", + "basePath": "%{InitialPath}", + "path": "%{InitialPath}" + } + }, + { + "name": "Sp2", + "type": "Spacer", + "data": { "factor": 2 } + }, + { + "name": "AdminUser", + "trDisplayName": "Admin user:", + "trToolTip": "Privileged user added automatically to the created local repository.", + "type": "LineEdit", + "mandatory": false, + "enabled": "%{isCloneRepo}", + "data": + { + "trText": "%{JS: Fossil.defaultAdminUser()}" + } + }, + { + "name": "SslIdentity", + "trDisplayName": "SSL/TLS identity:", + "trToolTip": "SSL/TLS client identity key to use if requested by the server.", + "type": "PathChooser", + "mandatory": false, + "enabled": "%{isCloneRepo}", + "data": + { + "kind": "file", + "path": "%{defaultSslIdentityFile}" + } + }, + { + "name": "IncludePrivate", + "trDisplayName": "Include private branches", + "trToolTip": "Allow transfer of private branches.", + "type": "CheckBox", + "enabled": "%{isCloneRepo}", + "data": + { + "checkedValue": "true", + "uncheckedValue": "" + } + }, + { + "name": "DisableAutosync", + "trDisplayName": "Disable auto-sync", + "trToolTip": "Disable automatic pull prior to commit or update and automatic push after commit or tag or branch creation.", + "type": "CheckBox", + "enabled": "%{isCloneRepo}", + "data": + { + "checkedValue": "off", + "uncheckedValue": "", + "checked": "%{defaultDisableAutosync}" + } + } + + ] + }, + { + "trDisplayName": "Checkout", + "trShortTitle": "Checkout", + "typeId": "VcsCommand", + "data" : + { + "vcsId": "%{vcsId}", + "trRunMessage": "Running Fossil clone...", + "repository": "%{SourceRepo}", + "baseDirectory": "%{Path}", + "checkoutName": "%{Dir}", + "extraArguments": + [ + "%{argRepoType}", + "%{argBranchTag}", + "%{argAdminUser}", + "%{argSslIdentity}", + "%{argIncludePrivate}", + "%{argDisableAutosync}", + "%{argFossilFile}" + ], + "extraJobs" : + [ + { + "command": "fossil", + "arguments": [ "version" ] + } + ] + } + } + ], + + "generators": + [ + { + "typeId": "Scanner", + "data": { + "subdirectoryPatterns": [ "^src$" ] + } + } + ] +} diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index 932dde44406..395ce703eca 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -322,7 +322,7 @@ ShowController::ShowController(IDocument *document, const QString &id) }; const auto setupDescription = [this, id](QtcProcess &process) { - process.setCodec(m_instance->encoding(workingDirectory(), "i18n.commitEncoding")); + process.setCodec(m_instance->encoding(GitClient::EncodingCommit, workingDirectory())); setupCommand(process, {"show", "-s", noColorOption, showFormatC, id}); VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); setDescription(Tr::tr("Waiting for data...")); @@ -821,13 +821,27 @@ FilePaths GitClient::unmanagedFiles(const FilePaths &filePaths) const return res; } -QTextCodec *GitClient::codecFor(GitClient::CodecType codecType, const FilePath &source) const +QTextCodec *GitClient::encoding(GitClient::EncodingType encodingType, const FilePath &source) const { - if (codecType == CodecSource) - return source.isFile() ? VcsBaseEditor::getCodec(source) : encoding(source, "gui.encoding"); - if (codecType == CodecLogOutput) - return encoding(source, "i18n.logOutputEncoding"); - return nullptr; + auto codec = [this](const FilePath &workingDirectory, const QString &configVar) { + const QString codecName = readConfigValue(workingDirectory, configVar).trimmed(); + // Set default commit encoding to 'UTF-8', when it's not set, + // to solve displaying error of commit log with non-latin characters. + if (codecName.isEmpty()) + return QTextCodec::codecForName("UTF-8"); + return QTextCodec::codecForName(codecName.toUtf8()); + }; + + switch (encodingType) { + case EncodingSource: + return source.isFile() ? VcsBaseEditor::getCodec(source) : codec(source, "gui.encoding"); + case EncodingLogOutput: + return codec(source, "i18n.logOutputEncoding"); + case EncodingCommit: + return codec(source, "i18n.commitEncoding"); + default: + return nullptr; + } } void GitClient::chunkActionsRequested(DiffEditor::DiffEditorController *controller, @@ -1057,7 +1071,7 @@ void GitClient::log(const FilePath &workingDirectory, const QString &fileName, const FilePath sourceFile = VcsBaseEditor::getSource(workingDir, fileName); GitEditorWidget *editor = static_cast( createVcsEditor(editorId, title, sourceFile, - codecFor(CodecLogOutput), "logTitle", msgArg)); + encoding(EncodingLogOutput), "logTitle", msgArg)); VcsBaseEditorConfig *argWidget = editor->editorConfig(); if (!argWidget) { argWidget = new GitLogArgumentsWidget(settings(), !fileName.isEmpty(), editor); @@ -1112,7 +1126,7 @@ void GitClient::reflog(const FilePath &workingDirectory, const QString &ref) // Creating document might change the referenced workingDirectory. Store a copy and use it. const FilePath workingDir = workingDirectory; GitEditorWidget *editor = static_cast( - createVcsEditor(editorId, title, workingDir, codecFor(CodecLogOutput), + createVcsEditor(editorId, title, workingDir, encoding(EncodingLogOutput), "reflogRepository", workingDir.toString())); VcsBaseEditorConfig *argWidget = editor->editorConfig(); if (!argWidget) { @@ -1225,7 +1239,7 @@ void GitClient::annotate(const Utils::FilePath &workingDir, const QString &file, const FilePath sourceFile = VcsBaseEditor::getSource(workingDir, file); VcsBaseEditorWidget *editor = createVcsEditor(editorId, title, sourceFile, - codecFor(CodecSource, sourceFile), "blameFileName", id); + encoding(EncodingSource, sourceFile), "blameFileName", id); VcsBaseEditorConfig *argWidget = editor->editorConfig(); if (!argWidget) { argWidget = new GitBlameArgumentsWidget(settings(), editor->toolBar()); @@ -1403,7 +1417,7 @@ bool GitClient::synchronousLog(const FilePath &workingDirectory, const QStringLi allArguments.append(arguments); const CommandResult result = vcsSynchronousExec(workingDirectory, allArguments, flags, - vcsTimeoutS(), encoding(workingDirectory, "i18n.logOutputEncoding")); + vcsTimeoutS(), encoding(EncodingLogOutput, workingDirectory)); if (result.result() == ProcessResult::FinishedWithSuccess) { *output = result.cleanedStdOut(); return true; @@ -2566,16 +2580,6 @@ FilePath GitClient::vcsBinary() const return binary; } -QTextCodec *GitClient::encoding(const FilePath &workingDirectory, const QString &configVar) const -{ - const QString codecName = readConfigValue(workingDirectory, configVar).trimmed(); - // Set default commit encoding to 'UTF-8', when it's not set, - // to solve displaying error of commit log with non-latin characters. - if (codecName.isEmpty()) - return QTextCodec::codecForName("UTF-8"); - return QTextCodec::codecForName(codecName.toUtf8()); -} - // returns first line from log and removes it static QByteArray shiftLogLine(QByteArray &logText) { @@ -2709,7 +2713,7 @@ bool GitClient::getCommitData(const FilePath &workingDirectory, } } - commitData.commitEncoding = encoding(workingDirectory, "i18n.commitEncoding"); + commitData.commitEncoding = encoding(EncodingCommit, workingDirectory); // Get the commit template or the last commit message switch (commitData.commitType) { @@ -3124,7 +3128,7 @@ void GitClient::subversionLog(const FilePath &workingDirectory) const const QString title = Tr::tr("Git SVN Log"); const Id editorId = Git::Constants::GIT_SVN_LOG_EDITOR_ID; const FilePath sourceFile = VcsBaseEditor::getSource(workingDirectory, QStringList()); - VcsBaseEditorWidget *editor = createVcsEditor(editorId, title, sourceFile, codecFor(CodecNone), + VcsBaseEditorWidget *editor = createVcsEditor(editorId, title, sourceFile, encoding(EncodingDefault), "svnLog", sourceFile.toString()); editor->setWorkingDirectory(workingDirectory); vcsExecWithEditor(workingDirectory, arguments, editor); diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h index 51f07f9b759..2dfd6ddaaa1 100644 --- a/src/plugins/git/gitclient.h +++ b/src/plugins/git/gitclient.h @@ -284,7 +284,6 @@ public: void setConfigValue(const Utils::FilePath &workingDirectory, const QString &configVar, const QString &value) const; - QTextCodec *encoding(const Utils::FilePath &workingDirectory, const QString &configVar) const; bool readDataFromCommit(const Utils::FilePath &repoDirectory, const QString &commit, CommitData &commitData, QString *errorMessage = nullptr, QString *commitTemplate = nullptr); @@ -345,6 +344,10 @@ public: const Utils::FilePath &path, ShowEditor showSetting = ShowEditor::Always); Author getAuthor(const Utils::FilePath &workingDirectory); + + enum EncodingType { EncodingSource, EncodingLogOutput, EncodingCommit, EncodingDefault }; + QTextCodec *encoding(EncodingType encodingType, const Utils::FilePath &source = {}) const; + private: void finishSubmoduleUpdate(); void chunkActionsRequested(DiffEditor::DiffEditorController *controller, @@ -354,9 +357,6 @@ private: void stage(DiffEditor::DiffEditorController *diffController, const QString &patch, bool revert) const; - enum CodecType { CodecSource, CodecLogOutput, CodecNone }; - QTextCodec *codecFor(CodecType codecType, const Utils::FilePath &source = {}) const; - void requestReload(const QString &documentId, const Utils::FilePath &source, const QString &title, const Utils::FilePath &workingDirectory, std::function factory) const; diff --git a/src/plugins/git/giteditor.cpp b/src/plugins/git/giteditor.cpp index b0793461319..7f73842cb02 100644 --- a/src/plugins/git/giteditor.cpp +++ b/src/plugins/git/giteditor.cpp @@ -274,8 +274,7 @@ void GitEditorWidget::aboutToOpen(const FilePath &filePath, const FilePath &real || editorId == Git::Constants::GIT_REBASE_EDITOR_ID) { const FilePath gitPath = filePath.absolutePath(); setSource(gitPath); - textDocument()->setCodec( - GitClient::instance()->encoding(gitPath, "i18n.commitEncoding")); + textDocument()->setCodec(GitClient::instance()->encoding(GitClient::EncodingCommit, gitPath)); } } diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp index 62296994816..25c7febadb1 100644 --- a/src/plugins/git/gitplugin.cpp +++ b/src/plugins/git/gitplugin.cpp @@ -1581,9 +1581,10 @@ void GitPluginPrivate::instantBlame() const CommitInfo info = parseBlameOutput(output.split('\n'), filePath, m_author); m_blameMark.reset(new BlameMark(filePath, line, info)); }; + QTextCodec *codec = GitClient::instance()->encoding(GitClient::EncodingCommit, workingDirectory); GitClient::instance()->vcsExecWithHandler(workingDirectory, {"blame", "-p", "-L", lineString, "--", filePath.toString()}, - this, commandHandler, RunFlags::NoOutput); + this, commandHandler, RunFlags::NoOutput, codec); } void GitPluginPrivate::stopInstantBlame() diff --git a/src/plugins/marketplace/productlistmodel.cpp b/src/plugins/marketplace/productlistmodel.cpp index e4f7e12edce..cdfb8d79e5f 100644 --- a/src/plugins/marketplace/productlistmodel.cpp +++ b/src/plugins/marketplace/productlistmodel.cpp @@ -173,7 +173,7 @@ void SectionedProducts::onFetchSingleCollectionFinished(QNetworkReply *reply) product->handle = handle; const QJsonArray tags = obj.value("tags").toArray(); - for (auto val : tags) + for (const auto &val : tags) product->tags.append(val.toString()); const auto images = obj.value("images").toArray(); diff --git a/src/plugins/perfprofiler/perftimelinemodelmanager.h b/src/plugins/perfprofiler/perftimelinemodelmanager.h index 384cc66b825..8b7b9c1c8fc 100644 --- a/src/plugins/perfprofiler/perftimelinemodelmanager.h +++ b/src/plugins/perfprofiler/perftimelinemodelmanager.h @@ -8,7 +8,6 @@ #include #include -#include #include #include diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs index 69ed458bfa4..b878e76c3e3 100644 --- a/src/plugins/plugins.qbs +++ b/src/plugins/plugins.qbs @@ -37,6 +37,7 @@ Project { "diffeditor/diffeditor.qbs", "docker/docker.qbs", "fakevim/fakevim.qbs", + "fossil/fossil.qbs", "emacskeys/emacskeys.qbs", "genericprojectmanager/genericprojectmanager.qbs", "git/git.qbs", diff --git a/src/plugins/projectexplorer/abstractprocessstep.cpp b/src/plugins/projectexplorer/abstractprocessstep.cpp index 08d58117c68..c838458aa5d 100644 --- a/src/plugins/projectexplorer/abstractprocessstep.cpp +++ b/src/plugins/projectexplorer/abstractprocessstep.cpp @@ -246,7 +246,7 @@ void AbstractProcessStep::runTaskTree(const Tasking::Group &recipe) d->m_taskTree.reset(new TaskTree(recipe)); connect(d->m_taskTree.get(), &TaskTree::progressValueChanged, this, [this](int value) { - emit progress(qRound(double(value) * 100 / d->m_taskTree->progressMaximum()), {}); + emit progress(qRound(double(value) * 100 / std::max(d->m_taskTree->progressMaximum(), 1)), {}); }); connect(d->m_taskTree.get(), &TaskTree::done, this, [this] { emit finished(true); diff --git a/src/plugins/projectexplorer/buildmanager.cpp b/src/plugins/projectexplorer/buildmanager.cpp index 228e575ddda..4a78ccfd0f4 100644 --- a/src/plugins/projectexplorer/buildmanager.cpp +++ b/src/plugins/projectexplorer/buildmanager.cpp @@ -31,7 +31,6 @@ #include #include -#include #include #include diff --git a/src/plugins/projectexplorer/buildstep.cpp b/src/plugins/projectexplorer/buildstep.cpp index 101eb9546e4..f3889a693ea 100644 --- a/src/plugins/projectexplorer/buildstep.cpp +++ b/src/plugins/projectexplorer/buildstep.cpp @@ -9,7 +9,6 @@ #include "deployconfiguration.h" #include "kitinformation.h" #include "project.h" -#include "projectexplorer.h" #include "projectexplorerconstants.h" #include "sanitizerparser.h" #include "target.h" @@ -19,7 +18,6 @@ #include #include #include -#include #include #include diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp index 097f175d664..57984a6b11c 100644 --- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp +++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp @@ -17,16 +17,74 @@ #include #include #include +#include #include #include #include +#ifdef Q_OS_WIN +#include +#include +#include +#endif + using namespace ProjectExplorer::Constants; using namespace Utils; namespace ProjectExplorer { +static void startTerminalEmulator(const QString &workingDir, const Environment &env) +{ +#ifdef Q_OS_WIN + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + PROCESS_INFORMATION pinfo; + ZeroMemory(&pinfo, sizeof(pinfo)); + + static const auto quoteWinCommand = [](const QString &program) { + const QChar doubleQuote = QLatin1Char('"'); + + // add the program as the first arg ... it works better + QString programName = program; + programName.replace(QLatin1Char('/'), QLatin1Char('\\')); + if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote) + && programName.contains(QLatin1Char(' '))) { + programName.prepend(doubleQuote); + programName.append(doubleQuote); + } + return programName; + }; + const QString cmdLine = quoteWinCommand(qtcEnvironmentVariable("COMSPEC")); + // cmdLine is assumed to be detached - + // https://blogs.msdn.microsoft.com/oldnewthing/20090601-00/?p=18083 + + const QString totalEnvironment = env.toStringList().join(QChar(QChar::Null)) + QChar(QChar::Null); + LPVOID envPtr = (env != Environment::systemEnvironment()) + ? (WCHAR *)(totalEnvironment.utf16()) : nullptr; + + const bool success = CreateProcessW(0, (WCHAR *)cmdLine.utf16(), + 0, 0, FALSE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, + envPtr, workingDir.isEmpty() ? 0 : (WCHAR *)workingDir.utf16(), + &si, &pinfo); + + if (success) { + CloseHandle(pinfo.hThread); + CloseHandle(pinfo.hProcess); + } +#else + const TerminalCommand term = TerminalCommand::terminalEmulator(); + QProcess process; + process.setProgram(term.command); + process.setArguments(ProcessArgs::splitArgs(term.openArgs)); + process.setProcessEnvironment(env.toProcessEnvironment()); + process.setWorkingDirectory(workingDir); + process.startDetached(); +#endif +} + DesktopDevice::DesktopDevice() { setFileAccess(DesktopDeviceFileAccess::instance()); @@ -43,8 +101,14 @@ DesktopDevice::DesktopDevice() const QString portRange = QString::fromLatin1("%1-%2").arg(DESKTOP_PORT_START).arg(DESKTOP_PORT_END); setFreePorts(Utils::PortList::fromString(portRange)); - setOpenTerminal([](const Environment &env, const FilePath &workingDir) { - Core::FileUtils::openTerminal(workingDir, env); + + setOpenTerminal([](const Environment &env, const FilePath &path) { + const QFileInfo fileInfo = path.toFileInfo(); + const QString workingDir = QDir::toNativeSeparators(fileInfo.isDir() ? + fileInfo.absoluteFilePath() : + fileInfo.absolutePath()); + const Environment realEnv = env.hasChanges() ? env : Environment::systemEnvironment(); + startTerminalEmulator(workingDir, realEnv); }); } diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp index e6ecfe4659b..9f8e0594fd8 100644 --- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp +++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp @@ -436,6 +436,12 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_uniqueensureReachable(other); }; + deviceHooks.openTerminal = [](const FilePath &filePath, const Environment &env) { + auto device = DeviceManager::deviceForPath(filePath); + QTC_ASSERT(device, return); + device->openTerminal(env, filePath); + }; + DeviceProcessHooks processHooks; processHooks.processImplHook = [](const FilePath &filePath) -> ProcessInterface * { diff --git a/src/plugins/projectexplorer/treescanner.cpp b/src/plugins/projectexplorer/treescanner.cpp index 1fcf47a504f..abcc66c3cc3 100644 --- a/src/plugins/projectexplorer/treescanner.cpp +++ b/src/plugins/projectexplorer/treescanner.cpp @@ -3,15 +3,12 @@ #include "treescanner.h" -#include "projectexplorerconstants.h" #include "projectnodeshelper.h" #include "projecttree.h" #include #include -#include - #include #include #include diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp index 9ac68629eab..d9167049a56 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp @@ -1,7 +1,6 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "assetexporter.h" -#include "assetexportpluginconstants.h" #include "componentexporter.h" #include "exportnotification.h" @@ -14,7 +13,6 @@ #include "coreplugin/editormanager/editormanager.h" #include "utils/qtcassert.h" #include "utils/runextensions.h" -#include "variantproperty.h" #include "projectexplorer/session.h" #include "projectexplorer/project.h" diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp index 2338b08ba1e..da1d82b73d0 100644 --- a/src/plugins/qmljstools/qmljsmodelmanager.cpp +++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp @@ -38,13 +38,12 @@ #include #include #include -#include +#include #include #include #include #include #include -#include #include using namespace Utils; diff --git a/src/plugins/qmlprofiler/qmlprofilermodelmanager.cpp b/src/plugins/qmlprofiler/qmlprofilermodelmanager.cpp index 5caeb257bde..9817b84240e 100644 --- a/src/plugins/qmlprofiler/qmlprofilermodelmanager.cpp +++ b/src/plugins/qmlprofiler/qmlprofilermodelmanager.cpp @@ -1,7 +1,6 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "qmlprofilerconstants.h" #include "qmlprofilerdetailsrewriter.h" #include "qmlprofilermodelmanager.h" #include "qmlprofilernotesmodel.h" @@ -10,7 +9,6 @@ #include #include -#include #include #include diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp index 1ab85fe137c..6b1d64600f4 100644 --- a/src/plugins/qtsupport/baseqtversion.cpp +++ b/src/plugins/qtsupport/baseqtversion.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include diff --git a/src/plugins/qtsupport/externaleditors.cpp b/src/plugins/qtsupport/externaleditors.cpp index 001e017ea1d..afff6a0b77f 100644 --- a/src/plugins/qtsupport/externaleditors.cpp +++ b/src/plugins/qtsupport/externaleditors.cpp @@ -39,6 +39,83 @@ struct Tr { Q_DECLARE_TR_FUNCTIONS(::QmakeProjectManager) }; +// Locate a binary in a directory, applying all kinds of +// extensions the operating system supports. +static QString checkBinary(const QDir &dir, const QString &binary) +{ + // naive UNIX approach + const QFileInfo info(dir.filePath(binary)); + if (info.isFile() && info.isExecutable()) + return info.absoluteFilePath(); + + // Does the OS have some weird extension concept or does the + // binary have a 3 letter extension? + if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost()) + return {}; + const int dotIndex = binary.lastIndexOf(QLatin1Char('.')); + if (dotIndex != -1 && dotIndex == binary.size() - 4) + return {}; + + switch (HostOsInfo::hostOs()) { + case OsTypeLinux: + case OsTypeOtherUnix: + case OsTypeOther: + break; + case OsTypeWindows: { + static const char *windowsExtensions[] = {".cmd", ".bat", ".exe", ".com"}; + // Check the Windows extensions using the order + const int windowsExtensionCount = sizeof(windowsExtensions)/sizeof(const char*); + for (int e = 0; e < windowsExtensionCount; e ++) { + const QFileInfo windowsBinary(dir.filePath(binary + QLatin1String(windowsExtensions[e]))); + if (windowsBinary.isFile() && windowsBinary.isExecutable()) + return windowsBinary.absoluteFilePath(); + } + } + break; + case OsTypeMac: { + // Check for Mac app folders + const QFileInfo appFolder(dir.filePath(binary + QLatin1String(".app"))); + if (appFolder.isDir()) { + QString macBinaryPath = appFolder.absoluteFilePath(); + macBinaryPath += QLatin1String("/Contents/MacOS/"); + macBinaryPath += binary; + const QFileInfo macBinary(macBinaryPath); + if (macBinary.isFile() && macBinary.isExecutable()) + return macBinary.absoluteFilePath(); + } + } + break; + } + return {}; +} + +static QString locateBinary(const QString &path, const QString &binary) +{ + // Absolute file? + const QFileInfo absInfo(binary); + if (absInfo.isAbsolute()) + return checkBinary(absInfo.dir(), absInfo.fileName()); + + // Windows finds binaries in the current directory + if (HostOsInfo::isWindowsHost()) { + const QString currentDirBinary = checkBinary(QDir::current(), binary); + if (!currentDirBinary.isEmpty()) + return currentDirBinary; + } + + const QStringList paths = path.split(HostOsInfo::pathListSeparator()); + if (paths.empty()) + return {}; + const QStringList::const_iterator cend = paths.constEnd(); + for (QStringList::const_iterator it = paths.constBegin(); it != cend; ++it) { + const QDir dir(*it); + const QString rc = checkBinary(dir, binary); + if (!rc.isEmpty()) + return rc; + } + return {}; +} + static QString msgStartFailed(const QString &binary, QStringList arguments) { arguments.push_front(binary); @@ -122,7 +199,7 @@ static bool getEditorLaunchData(const CommandForQtVersion &commandForQtVersion, // fallback if (data->binary.isEmpty()) { const QString path = qtcEnvironmentVariable("PATH"); - data->binary = QtcProcess::locateBinary(path, commandForQtVersion(nullptr)); + data->binary = locateBinary(path, commandForQtVersion(nullptr)); } if (data->binary.isEmpty()) { diff --git a/src/plugins/qtsupport/qtoptionspage.cpp b/src/plugins/qtsupport/qtoptionspage.cpp index 2854648549f..0cf089823ab 100644 --- a/src/plugins/qtsupport/qtoptionspage.cpp +++ b/src/plugins/qtsupport/qtoptionspage.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include diff --git a/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp b/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp index 270b95eb229..7e7e3d9a19a 100644 --- a/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp +++ b/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp @@ -96,11 +96,6 @@ void AbstractRemoteLinuxDeployService::setTarget(Target *target) d->deviceConfiguration = DeviceKitAspect::device(kit()); } -void AbstractRemoteLinuxDeployService::setDevice(const IDevice::ConstPtr &device) -{ - d->deviceConfiguration = device; -} - void AbstractRemoteLinuxDeployService::start() { QTC_ASSERT(!d->m_taskTree, return); diff --git a/src/plugins/remotelinux/abstractremotelinuxdeploystep.h b/src/plugins/remotelinux/abstractremotelinuxdeploystep.h index 258d4b9e7f9..67855064bb0 100644 --- a/src/plugins/remotelinux/abstractremotelinuxdeploystep.h +++ b/src/plugins/remotelinux/abstractremotelinuxdeploystep.h @@ -83,8 +83,7 @@ public: ~AbstractRemoteLinuxDeployService() override; void setTarget(ProjectExplorer::Target *bc); - // Only use setDevice() as fallback if no target is available - void setDevice(const ProjectExplorer::IDeviceConstPtr &device); + void start(); void stop(); diff --git a/src/plugins/squish/objectsmapdocument.cpp b/src/plugins/squish/objectsmapdocument.cpp index 23036dd6d72..eb7266ddc43 100644 --- a/src/plugins/squish/objectsmapdocument.cpp +++ b/src/plugins/squish/objectsmapdocument.cpp @@ -103,7 +103,7 @@ bool ObjectsMapDocument::buildObjectsMapTree(const QByteArray &contents) continue; const int tabPosition = line.indexOf(kPropertySeparator); - const QString objectName = QLatin1String(line.left(tabPosition).trimmed()); + const QString objectName = QString::fromUtf8(line.left(tabPosition).trimmed()); if (!objectName.startsWith(ObjectsMapTreeItem::COLON)) { qDeleteAll(itemForName); return false; diff --git a/src/plugins/squish/squishconstants.h b/src/plugins/squish/squishconstants.h index 45d35c27b49..cd495c68562 100644 --- a/src/plugins/squish/squishconstants.h +++ b/src/plugins/squish/squishconstants.h @@ -29,10 +29,9 @@ enum class RunnerState { RunRequested, Interrupted, InterruptRequested, - Canceling, - Canceled, CancelRequested, CancelRequestedWhileInterrupted, + Canceled, Finished }; diff --git a/src/plugins/squish/squishperspective.cpp b/src/plugins/squish/squishperspective.cpp index 85e2aadb37e..fca93ce35db 100644 --- a/src/plugins/squish/squishperspective.cpp +++ b/src/plugins/squish/squishperspective.cpp @@ -396,6 +396,7 @@ void SquishPerspective::setPerspectiveMode(PerspectiveMode mode) m_stepOutAction->setEnabled(true); m_stopAction->setEnabled(true); break; + case Configuring: case Querying: case NoMode: m_pausePlayAction->setIcon(iconForType(IconType::Pause)); diff --git a/src/plugins/squish/squishperspective.h b/src/plugins/squish/squishperspective.h index 0ef31a790c6..1e5c4aa515f 100644 --- a/src/plugins/squish/squishperspective.h +++ b/src/plugins/squish/squishperspective.h @@ -30,7 +30,7 @@ class SquishPerspective : public Utils::Perspective { Q_OBJECT public: - enum PerspectiveMode { NoMode, Interrupted, Running, Recording, Querying }; + enum PerspectiveMode { NoMode, Interrupted, Running, Recording, Querying, Configuring }; SquishPerspective(); void initPerspective(); diff --git a/src/plugins/squish/squishrunnerprocess.cpp b/src/plugins/squish/squishrunnerprocess.cpp index be7d3f7d75c..3fa6474e269 100644 --- a/src/plugins/squish/squishrunnerprocess.cpp +++ b/src/plugins/squish/squishrunnerprocess.cpp @@ -143,7 +143,6 @@ static QString cmdToString(SquishRunnerProcess::RunnerCommand cmd) case SquishRunnerProcess::Exit: return "exit\n"; case SquishRunnerProcess::Next: return "next\n"; case SquishRunnerProcess::PrintVariables: return "print variables\n"; - case SquishRunnerProcess::Quit: return "quit\n"; case SquishRunnerProcess::Return: return "return\n"; case SquishRunnerProcess::Step: return "step\n"; } diff --git a/src/plugins/squish/squishrunnerprocess.h b/src/plugins/squish/squishrunnerprocess.h index f3e85ca08af..b3200eb89d9 100644 --- a/src/plugins/squish/squishrunnerprocess.h +++ b/src/plugins/squish/squishrunnerprocess.h @@ -15,7 +15,7 @@ class SquishRunnerProcess : public SquishProcessBase { Q_OBJECT public: - enum RunnerCommand { Continue, EndRecord, Exit, Next, PrintVariables, Quit, Return, Step }; + enum RunnerCommand { Continue, EndRecord, Exit, Next, PrintVariables, Return, Step }; enum RunnerMode { Run, StartAut, QueryServer, Record }; enum RunnerError { InvalidSocket, MappedAutMissing }; diff --git a/src/plugins/squish/squishtools.cpp b/src/plugins/squish/squishtools.cpp index ca4340e5ec4..107a3c6bfab 100644 --- a/src/plugins/squish/squishtools.cpp +++ b/src/plugins/squish/squishtools.cpp @@ -50,10 +50,9 @@ static QString runnerStateName(RunnerState state) case RunnerState::RunRequested: return "RunRequested"; case RunnerState::Interrupted: return "Interrupted"; case RunnerState::InterruptRequested: return "InterruptedRequested"; - case RunnerState::Canceling: return "Canceling"; - case RunnerState::Canceled: return "Canceled"; case RunnerState::CancelRequested: return "CancelRequested"; case RunnerState::CancelRequestedWhileInterrupted: return "CancelRequestedWhileInterrupted"; + case RunnerState::Canceled: return "Canceled"; case RunnerState::Finished: return "Finished"; } return "ThouShallNotBeHere"; @@ -297,6 +296,7 @@ void SquishTools::writeServerSettingsChanges(const QList &changes) return; } m_serverConfigChanges = changes; + m_perspective.setPerspectiveMode(SquishPerspective::Configuring); startSquishServer(ServerConfigChangeRequested); } @@ -363,10 +363,12 @@ void SquishTools::onServerStopped() } m_serverConfigChanges.removeFirst(); - if (!m_serverConfigChanges.isEmpty()) + if (!m_serverConfigChanges.isEmpty()) { startSquishServer(ServerConfigChangeRequested); - else - emit configChangesWritten(); + return; + } + emit configChangesWritten(); + m_perspective.setPerspectiveMode(SquishPerspective::NoMode); } else if (m_request == ServerStopRequested) { m_request = None; if (m_perspective.perspectiveMode() == SquishPerspective::Running) @@ -603,7 +605,7 @@ void SquishTools::setupAndStartRecorder() void SquishTools::stopRecorder() { QTC_ASSERT(m_secondaryRunner && m_secondaryRunner->isRunning(), return); - if (m_squishRunnerState == RunnerState::CancelRequested) { + if (m_squishRunnerState == RunnerState::Canceled) { qCDebug(LOG) << "Stopping recorder (exit)"; m_secondaryRunner->writeCommand(SquishRunnerProcess::Exit); } else { @@ -663,7 +665,12 @@ void SquishTools::onRunnerFinished() { qCDebug(LOG) << "Runner finished"; if (!m_shutdownInitiated) { - logAndChangeRunnerState(RunnerState::Finished); + if (m_squishRunnerState == RunnerState::CancelRequested + || m_squishRunnerState == RunnerState::CancelRequestedWhileInterrupted) { + logAndChangeRunnerState(RunnerState::Canceled); + } else { + logAndChangeRunnerState(RunnerState::Finished); + } if (m_request == RunTestRequested) m_perspective.updateStatus(Tr::tr("Test run finished.")); else if (m_request == RecordTestRequested) @@ -814,8 +821,8 @@ void SquishTools::handlePrompt(const QString &fileName, int line, int column) break; case RunnerState::CancelRequested: case RunnerState::CancelRequestedWhileInterrupted: + logAndChangeRunnerState(RunnerState::Canceled); stopRecorder(); - logAndChangeRunnerState(RunnerState::Canceling); break; case RunnerState::Canceled: QTC_CHECK(false); @@ -846,13 +853,9 @@ void SquishTools::handlePrompt(const QString &fileName, int line, int column) } case RunnerState::CancelRequested: case RunnerState::CancelRequestedWhileInterrupted: + logAndChangeRunnerState(RunnerState::Canceled); m_primaryRunner->writeCommand(SquishRunnerProcess::Exit); clearLocationMarker(); - logAndChangeRunnerState(RunnerState::Canceling); - break; - case RunnerState::Canceling: - m_primaryRunner->writeCommand(SquishRunnerProcess::Quit); - logAndChangeRunnerState(RunnerState::Canceled); break; case RunnerState::Canceled: QTC_CHECK(false); diff --git a/src/plugins/texteditor/codeassist/documentcontentcompletion.cpp b/src/plugins/texteditor/codeassist/documentcontentcompletion.cpp index 162cba12d18..44506678e02 100644 --- a/src/plugins/texteditor/codeassist/documentcontentcompletion.cpp +++ b/src/plugins/texteditor/codeassist/documentcontentcompletion.cpp @@ -13,7 +13,6 @@ #include "../texteditorsettings.h" #include -#include #include #include diff --git a/src/plugins/vcsbase/vcsbaseclient.cpp b/src/plugins/vcsbase/vcsbaseclient.cpp index 3ec9763f0c4..d8dc91a888e 100644 --- a/src/plugins/vcsbase/vcsbaseclient.cpp +++ b/src/plugins/vcsbase/vcsbaseclient.cpp @@ -157,10 +157,11 @@ void VcsBaseClientImpl::vcsExecWithHandler(const FilePath &workingDirectory, const QStringList &arguments, const QObject *context, const CommandHandler &handler, - RunFlags additionalFlags) const + RunFlags additionalFlags, QTextCodec *codec) const { VcsCommand *command = createCommand(workingDirectory); command->addFlags(additionalFlags); + command->setCodec(codec); command->addJob({vcsBinary(), arguments}, vcsTimeoutS()); if (handler) { const QObject *actualContext = context ? context : this; diff --git a/src/plugins/vcsbase/vcsbaseclient.h b/src/plugins/vcsbase/vcsbaseclient.h index 87908f1f812..6703495c37f 100644 --- a/src/plugins/vcsbase/vcsbaseclient.h +++ b/src/plugins/vcsbase/vcsbaseclient.h @@ -83,7 +83,8 @@ public: const QStringList &arguments, const QObject *context, const CommandHandler &handler, - RunFlags additionalFlags = RunFlags::None) const; + RunFlags additionalFlags = RunFlags::None, + QTextCodec *codec = nullptr) const; void vcsExec(const Utils::FilePath &workingDirectory, const QStringList &arguments, RunFlags additionalFlags = RunFlags::None) const; diff --git a/src/tools/cplusplus-ast2png/cplusplus-ast2png.cpp b/src/tools/cplusplus-ast2png/cplusplus-ast2png.cpp index 45934b4f5b6..36fb7533e55 100644 --- a/src/tools/cplusplus-ast2png/cplusplus-ast2png.cpp +++ b/src/tools/cplusplus-ast2png/cplusplus-ast2png.cpp @@ -17,6 +17,7 @@ #include #include +#include #include "utils.h" @@ -425,7 +426,7 @@ static Document::Ptr parse(const QString &fileName, const QByteArray &source, if (verbose) std::cout << "Parsing as " << qPrintable(parseModeToString(parseMode)) << "..."; - Document::Ptr doc = Document::create(fileName); + Document::Ptr doc = Document::create(Utils::FilePath::fromString(fileName)); doc->control()->setDiagnosticClient(errorHandler); doc->setUtf8Source(source); const bool parsed = doc->parse(parseMode); diff --git a/src/tools/cplusplus-frontend/cplusplus-frontend.cpp b/src/tools/cplusplus-frontend/cplusplus-frontend.cpp index 92a319e86aa..467bb8ffa11 100644 --- a/src/tools/cplusplus-frontend/cplusplus-frontend.cpp +++ b/src/tools/cplusplus-frontend/cplusplus-frontend.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include "utils.h" #include @@ -74,7 +76,7 @@ int main(int argc, char *argv[]) const QByteArray source = file.readAll(); file.close(); - Document::Ptr doc = Document::create(fileName); + Document::Ptr doc = Document::create(Utils::FilePath::fromString(fileName)); doc->control()->setDiagnosticClient(0); doc->setUtf8Source(source); doc->parse(); diff --git a/src/tools/cplusplus-mkvisitor/cplusplus-mkvisitor.cpp b/src/tools/cplusplus-mkvisitor/cplusplus-mkvisitor.cpp index 1d1b5b1bcd2..253c85a6331 100644 --- a/src/tools/cplusplus-mkvisitor/cplusplus-mkvisitor.cpp +++ b/src/tools/cplusplus-mkvisitor/cplusplus-mkvisitor.cpp @@ -18,6 +18,8 @@ #include #include +#include + #include "utils.h" #include @@ -450,7 +452,7 @@ int main(int argc, char *argv[]) const QByteArray source = file.readAll(); file.close(); - Document::Ptr doc = Document::create(fileName); + Document::Ptr doc = Document::create(Utils::FilePath::fromString(fileName)); //doc->control()->setDiagnosticClient(0); doc->setUtf8Source(source); doc->parse(); diff --git a/tests/auto/utils/tasktree/tst_tasktree.cpp b/tests/auto/utils/tasktree/tst_tasktree.cpp index 4680c5e745c..f37f345590a 100644 --- a/tests/auto/utils/tasktree/tst_tasktree.cpp +++ b/tests/auto/utils/tasktree/tst_tasktree.cpp @@ -10,12 +10,45 @@ #include -#include -#include - using namespace Utils; using namespace Utils::Tasking; +enum class Handler { + Setup, + Done, + Error, + GroupSetup, + GroupDone, + GroupError +}; + +using Log = QList>; + +struct CustomStorage +{ + CustomStorage() { ++s_count; } + ~CustomStorage() { --s_count; } + Log m_log; + static int instanceCount() { return s_count; } +private: + static int s_count; +}; + +int CustomStorage::s_count = 0; +static const char s_processIdProperty[] = "__processId"; + +enum class OnStart { Running, NotRunning }; +enum class OnDone { Success, Failure }; + +struct TestData { + TreeStorage storage; + Group root; + Log expectedLog; + int taskCount = 0; + OnStart onStart = OnStart::Running; + OnDone onDone = OnDone::Success; +}; + class tst_TaskTree : public QObject { Q_OBJECT @@ -37,8 +70,8 @@ private: void tst_TaskTree::initTestCase() { - Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/" - + Core::Constants::IDE_CASED_ID + "-XXXXXX"); + TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/" + + Core::Constants::IDE_CASED_ID + "-XXXXXX"); const QString libExecPath(qApp->applicationDirPath() + '/' + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH)); LauncherInterface::setPathToLauncher(libExecPath); @@ -48,7 +81,7 @@ void tst_TaskTree::initTestCase() void tst_TaskTree::cleanupTestCase() { - Utils::Singleton::deleteAll(); + Singleton::deleteAll(); } void tst_TaskTree::validConstructs() @@ -80,7 +113,7 @@ void tst_TaskTree::validConstructs() Group { parallel, Process([](QtcProcess &) {}, [](const QtcProcess &) {}), - OnGroupDone([] {}), + OnGroupDone([] {}) } }, process, @@ -89,42 +122,9 @@ void tst_TaskTree::validConstructs() }; } -static const char s_processIdProperty[] = "__processId"; - -enum class Handler { - Setup, - Done, - Error, - GroupSetup, - GroupDone, - GroupError -}; - -using Log = QList>; - -struct CustomStorage -{ - CustomStorage() { ++s_count; } - ~CustomStorage() { --s_count; } - Log m_log; - static int instanceCount() { return s_count; } -private: - static int s_count; -}; - -int CustomStorage::s_count = 0; - - void tst_TaskTree::processTree_data() { - using namespace std::placeholders; - - QTest::addColumn("root"); - QTest::addColumn>("storage"); - QTest::addColumn("expectedLog"); - QTest::addColumn("runningAfterStart"); - QTest::addColumn("success"); - QTest::addColumn("taskCount"); + QTest::addColumn("testData"); TreeStorage storage; @@ -134,14 +134,21 @@ void tst_TaskTree::processTree_data() process.setProperty(s_processIdProperty, processId); storage->m_log.append({processId, Handler::Setup}); }; - const auto setupProcess = [setupProcessHelper](QtcProcess &process, int processId) { - setupProcessHelper(process, {"-return", "0"}, processId); + const auto setupProcess = [setupProcessHelper](int processId) { + return [=](QtcProcess &process) { + setupProcessHelper(process, {"-return", "0"}, processId); + }; }; - const auto setupCrashProcess = [setupProcessHelper](QtcProcess &process, int processId) { - setupProcessHelper(process, {"-crash"}, processId); + const auto setupCrashProcess = [setupProcessHelper](int processId) { + return [=](QtcProcess &process) { + setupProcessHelper(process, {"-crash"}, processId); + }; }; - const auto readResultAnonymous = [storage](const QtcProcess &) { - storage->m_log.append({-1, Handler::Done}); + const auto setupDynamicProcess = [setupProcessHelper](int processId, TaskAction action) { + return [=](QtcProcess &process) { + setupProcessHelper(process, {"-return", "0"}, processId); + return action; + }; }; const auto readResult = [storage](const QtcProcess &process) { const int processId = process.property(s_processIdProperty).toInt(); @@ -151,481 +158,744 @@ void tst_TaskTree::processTree_data() const int processId = process.property(s_processIdProperty).toInt(); storage->m_log.append({processId, Handler::Error}); }; - const auto groupSetup = [storage](int processId) { - storage->m_log.append({processId, Handler::GroupSetup}); + const auto groupSetup = [storage](int groupId) { + return [=] { storage->m_log.append({groupId, Handler::GroupSetup}); }; }; - const auto groupDone = [storage](int processId) { - storage->m_log.append({processId, Handler::GroupDone}); + const auto groupDone = [storage](int groupId) { + return [=] { storage->m_log.append({groupId, Handler::GroupDone}); }; }; - const auto rootDone = [storage] { - storage->m_log.append({-1, Handler::GroupDone}); + const auto groupError = [storage](int groupId) { + return [=] { storage->m_log.append({groupId, Handler::GroupError}); }; }; - const auto rootError = [storage] { - storage->m_log.append({-1, Handler::GroupError}); - }; - const auto setupDynamicProcess = [storage, setupProcess](QtcProcess &process, int processId, - TaskAction action) { - setupProcess(process, processId); - return action; - }; - - const Group emptyRoot { - Storage(storage), - OnGroupDone(rootDone) - }; - const Log emptyLog{{-1, Handler::GroupDone}}; - QTest::newRow("Empty") << emptyRoot << storage << emptyLog << false << true << 0; - - const Group dynamicTaskDoneRoot { - Storage(storage), - Process(std::bind(setupDynamicProcess, _1, 1, TaskAction::StopWithDone), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 2, TaskAction::StopWithDone), readResult, readError) - }; - const Log dynamicTaskDoneLog{{1, Handler::Setup}, - {2, Handler::Setup}}; - QTest::newRow("DynamicTaskDone") << dynamicTaskDoneRoot << storage << dynamicTaskDoneLog - << false << true << 2; - - const Group dynamicTaskErrorRoot { - Storage(storage), - Process(std::bind(setupDynamicProcess, _1, 1, TaskAction::StopWithError), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 2, TaskAction::StopWithError), readResult, readError) - }; - const Log dynamicTaskErrorLog{{1, Handler::Setup}}; - QTest::newRow("DynamicTaskError") << dynamicTaskErrorRoot << storage << dynamicTaskErrorLog - << false << false << 2; - - const Group dynamicMixedRoot { - Storage(storage), - Process(std::bind(setupDynamicProcess, _1, 1, TaskAction::Continue), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 2, TaskAction::Continue), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 3, TaskAction::StopWithError), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 4, TaskAction::Continue), readResult, readError) - }; - const Log dynamicMixedLog{{1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}}; - QTest::newRow("DynamicMixed") << dynamicMixedRoot << storage << dynamicMixedLog - << true << false << 4; - - const Group dynamicParallelRoot { - parallel, - Storage(storage), - Process(std::bind(setupDynamicProcess, _1, 1, TaskAction::Continue), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 2, TaskAction::Continue), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 3, TaskAction::StopWithError), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 4, TaskAction::Continue), readResult, readError) - }; - const Log dynamicParallelLog{{1, Handler::Setup}, - {2, Handler::Setup}, - {3, Handler::Setup}, - {1, Handler::Error}, - {2, Handler::Error}}; - QTest::newRow("DynamicParallel") << dynamicParallelRoot << storage << dynamicParallelLog - << false << false << 4; - - const Group dynamicParallelGroupRoot { - parallel, - Storage(storage), - Process(std::bind(setupDynamicProcess, _1, 1, TaskAction::Continue), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 2, TaskAction::Continue), readResult, readError), - Group { - Process(std::bind(setupDynamicProcess, _1, 3, TaskAction::StopWithError), readResult, readError) - }, - Process(std::bind(setupDynamicProcess, _1, 4, TaskAction::Continue), readResult, readError) - }; - const Log dynamicParallelGroupLog{{1, Handler::Setup}, - {2, Handler::Setup}, - {3, Handler::Setup}, - {1, Handler::Error}, - {2, Handler::Error}}; - QTest::newRow("DynamicParallelGroup") << dynamicParallelGroupRoot << storage - << dynamicParallelGroupLog << false << false << 4; - - const Group dynamicParallelGroupSetupRoot { - parallel, - Storage(storage), - Process(std::bind(setupDynamicProcess, _1, 1, TaskAction::Continue), readResult, readError), - Process(std::bind(setupDynamicProcess, _1, 2, TaskAction::Continue), readResult, readError), - Group { - OnGroupSetup([storage] { storage->m_log.append({0, Handler::GroupSetup}); - return TaskAction::StopWithError; }), - Process(std::bind(setupDynamicProcess, _1, 3, TaskAction::Continue), readResult, readError) - }, - Process(std::bind(setupDynamicProcess, _1, 4, TaskAction::Continue), readResult, readError) - }; - const Log dynamicParallelGroupSetupLog{{1, Handler::Setup}, - {2, Handler::Setup}, - {0, Handler::GroupSetup}, - {1, Handler::Error}, - {2, Handler::Error}}; - QTest::newRow("DynamicParallelGroupSetup") << dynamicParallelGroupSetupRoot << storage - << dynamicParallelGroupSetupLog << false << false << 4; - - const Group nestedRoot { - Storage(storage), - Group { - Group { - Group { - Group { - Group { - Process(std::bind(setupProcess, _1, 5), readResult), - OnGroupSetup(std::bind(groupSetup, 5)), - OnGroupDone(std::bind(groupDone, 5)) - }, - OnGroupSetup(std::bind(groupSetup, 4)), - OnGroupDone(std::bind(groupDone, 4)) - }, - OnGroupSetup(std::bind(groupSetup, 3)), - OnGroupDone(std::bind(groupDone, 3)) - }, - OnGroupSetup(std::bind(groupSetup, 2)), - OnGroupDone(std::bind(groupDone, 2)) - }, - OnGroupSetup(std::bind(groupSetup, 1)), - OnGroupDone(std::bind(groupDone, 1)) - }, - OnGroupDone(rootDone) - }; - const Log nestedLog{{1, Handler::GroupSetup}, - {2, Handler::GroupSetup}, - {3, Handler::GroupSetup}, - {4, Handler::GroupSetup}, - {5, Handler::GroupSetup}, - {5, Handler::Setup}, - {5, Handler::Done}, - {5, Handler::GroupDone}, - {4, Handler::GroupDone}, - {3, Handler::GroupDone}, - {2, Handler::GroupDone}, - {1, Handler::GroupDone}, - {-1, Handler::GroupDone}}; - QTest::newRow("Nested") << nestedRoot << storage << nestedLog << true << true << 1; - - const Group parallelRoot { - Storage(storage), - parallel, - Process(std::bind(setupProcess, _1, 1), readResultAnonymous), - Process(std::bind(setupProcess, _1, 2), readResultAnonymous), - Process(std::bind(setupProcess, _1, 3), readResultAnonymous), - Process(std::bind(setupProcess, _1, 4), readResultAnonymous), - Process(std::bind(setupProcess, _1, 5), readResultAnonymous), - OnGroupDone(rootDone) - }; - const Log parallelLog{{1, Handler::Setup}, // Setup order is determined in parallel mode - {2, Handler::Setup}, - {3, Handler::Setup}, - {4, Handler::Setup}, - {5, Handler::Setup}, - {-1, Handler::Done}, // Done order isn't determined in parallel mode - {-1, Handler::Done}, - {-1, Handler::Done}, - {-1, Handler::Done}, - {-1, Handler::Done}, - {-1, Handler::GroupDone}}; // Done handlers may come in different order - QTest::newRow("Parallel") << parallelRoot << storage << parallelLog << true << true << 5; - - const Group sequentialRoot { - Storage(storage), - Process(std::bind(setupProcess, _1, 1), readResult), - Process(std::bind(setupProcess, _1, 2), readResult), - Process(std::bind(setupProcess, _1, 3), readResult), - Process(std::bind(setupProcess, _1, 4), readResult), - Process(std::bind(setupProcess, _1, 5), readResult), - OnGroupDone(rootDone) - }; - const Group sequentialEncapsulatedRoot { - Storage(storage), - Group { - Process(std::bind(setupProcess, _1, 1), readResult) - }, - Group { - Process(std::bind(setupProcess, _1, 2), readResult) - }, - Group { - Process(std::bind(setupProcess, _1, 3), readResult) - }, - Group { - Process(std::bind(setupProcess, _1, 4), readResult) - }, - Group { - Process(std::bind(setupProcess, _1, 5), readResult) - }, - OnGroupDone(rootDone) - }; - auto setupSubTree = [=](TaskTree &taskTree) { - const Group nestedRoot { - Storage(storage), - Process(std::bind(setupProcess, _1, 2), readResult), - Process(std::bind(setupProcess, _1, 3), readResult), - Process(std::bind(setupProcess, _1, 4), readResult), - }; - taskTree.setupRoot(nestedRoot); - CustomStorage *activeStorage = storage.activeStorage(); - auto collectSubLog = [activeStorage](CustomStorage *subTreeStorage){ - activeStorage->m_log += subTreeStorage->m_log; - }; - taskTree.onStorageDone(storage, collectSubLog); - }; - const Group sequentialSubTreeRoot { - Storage(storage), - Process(std::bind(setupProcess, _1, 1), readResult), - Tree(setupSubTree), - Process(std::bind(setupProcess, _1, 5), readResult), - OnGroupDone(rootDone) - }; - const Log sequentialLog{{1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}, - {3, Handler::Done}, - {4, Handler::Setup}, - {4, Handler::Done}, - {5, Handler::Setup}, - {5, Handler::Done}, - {-1, Handler::GroupDone}}; - QTest::newRow("Sequential") << sequentialRoot << storage << sequentialLog << true << true << 5; - QTest::newRow("SequentialEncapsulated") << sequentialEncapsulatedRoot << storage - << sequentialLog << true << true << 5; - QTest::newRow("SequentialSubTree") << sequentialSubTreeRoot << storage << sequentialLog - << true << true << 3; // We don't inspect subtrees - - const Group sequentialNestedRoot { - Storage(storage), - Group { - Process(std::bind(setupProcess, _1, 1), readResult), - Group { - Process(std::bind(setupProcess, _1, 2), readResult), - Group { - Process(std::bind(setupProcess, _1, 3), readResult), - Group { - Process(std::bind(setupProcess, _1, 4), readResult), - Group { - Process(std::bind(setupProcess, _1, 5), readResult), - OnGroupDone(std::bind(groupDone, 5)) - }, - OnGroupDone(std::bind(groupDone, 4)) - }, - OnGroupDone(std::bind(groupDone, 3)) - }, - OnGroupDone(std::bind(groupDone, 2)) - }, - OnGroupDone(std::bind(groupDone, 1)) - }, - OnGroupDone(rootDone) - }; - const Log sequentialNestedLog{{1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}, - {3, Handler::Done}, - {4, Handler::Setup}, - {4, Handler::Done}, - {5, Handler::Setup}, - {5, Handler::Done}, - {5, Handler::GroupDone}, - {4, Handler::GroupDone}, - {3, Handler::GroupDone}, - {2, Handler::GroupDone}, - {1, Handler::GroupDone}, - {-1, Handler::GroupDone}}; - QTest::newRow("SequentialNested") << sequentialNestedRoot << storage << sequentialNestedLog - << true << true << 5; - - const Group sequentialErrorRoot { - Storage(storage), - Process(std::bind(setupProcess, _1, 1), readResult), - Process(std::bind(setupProcess, _1, 2), readResult), - Process(std::bind(setupCrashProcess, _1, 3), readResult, readError), - Process(std::bind(setupProcess, _1, 4), readResult), - Process(std::bind(setupProcess, _1, 5), readResult), - OnGroupDone(rootDone), - OnGroupError(rootError) - }; - const Log sequentialErrorLog{{1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}, - {3, Handler::Error}, - {-1, Handler::GroupError}}; - QTest::newRow("SequentialError") << sequentialErrorRoot << storage << sequentialErrorLog - << true << false << 5; const auto constructSimpleSequence = [=](const Workflow &policy) { return Group { Storage(storage), policy, - Process(std::bind(setupProcess, _1, 1), readResult), - Process(std::bind(setupCrashProcess, _1, 2), readResult, readError), - Process(std::bind(setupProcess, _1, 3), readResult), - OnGroupDone(rootDone), - OnGroupError(rootError) + Process(setupProcess(1), readResult), + Process(setupCrashProcess(2), readResult, readError), + Process(setupProcess(3), readResult), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) }; }; - - const Group stopOnErrorRoot = constructSimpleSequence(stopOnError); - const Log stopOnErrorLog{{1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Error}, - {-1, Handler::GroupError}}; - QTest::newRow("StopOnError") << stopOnErrorRoot << storage << stopOnErrorLog << true << false << 3; - - const Group continueOnErrorRoot = constructSimpleSequence(continueOnError); - const Log continueOnErrorLog{{1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Error}, - {3, Handler::Setup}, - {3, Handler::Done}, - {-1, Handler::GroupError}}; - QTest::newRow("ContinueOnError") << continueOnErrorRoot << storage << continueOnErrorLog - << true << false << 3; - - const Group stopOnDoneRoot = constructSimpleSequence(stopOnDone); - const Log stopOnDoneLog{{1, Handler::Setup}, - {1, Handler::Done}, - {-1, Handler::GroupDone}}; - QTest::newRow("StopOnDone") << stopOnDoneRoot << storage << stopOnDoneLog - << true << true << 3; - - const Group continueOnDoneRoot = constructSimpleSequence(continueOnDone); - const Log continueOnDoneLog{{1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Error}, - {3, Handler::Setup}, - {3, Handler::Done}, - {-1, Handler::GroupDone}}; - QTest::newRow("ContinueOnDone") << continueOnDoneRoot << storage << continueOnDoneLog - << true << true << 3; - - const Group optionalRoot { - Storage(storage), - optional, - Process(std::bind(setupCrashProcess, _1, 1), readResult, readError), - Process(std::bind(setupCrashProcess, _1, 2), readResult, readError), - OnGroupDone(rootDone), - OnGroupError(rootError) - }; - const Log optionalLog{{1, Handler::Setup}, - {1, Handler::Error}, - {2, Handler::Setup}, - {2, Handler::Error}, - {-1, Handler::GroupDone}}; - QTest::newRow("Optional") << optionalRoot << storage << optionalLog << true << true << 2; - - const auto stopWithDoneSetup = [] { return TaskAction::StopWithDone; }; - const auto stopWithErrorSetup = [] { return TaskAction::StopWithError; }; - const auto continueSetup = [] { return TaskAction::Continue; }; - const auto constructDynamicSetup = [=](const OnGroupSetup &dynamicSetup) { + const auto constructDynamicHierarchy = [=](TaskAction taskAction) { return Group { Storage(storage), Group { - Process(std::bind(setupProcess, _1, 1), readResult) + Process(setupProcess(1), readResult) }, Group { - dynamicSetup, - Process(std::bind(setupProcess, _1, 2), readResult), - Process(std::bind(setupProcess, _1, 3), readResult), - Process(std::bind(setupProcess, _1, 4), readResult) + OnGroupSetup([=] { return taskAction; }), + Process(setupProcess(2), readResult), + Process(setupProcess(3), readResult), + Process(setupProcess(4), readResult) }, - OnGroupDone(rootDone), - OnGroupError(rootError) + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) }; }; - const Group dynamicSetupDoneRoot = constructDynamicSetup({stopWithDoneSetup}); - const Log dynamicSetupDoneLog{{1, Handler::Setup}, - {1, Handler::Done}, - {-1, Handler::GroupDone}}; - QTest::newRow("DynamicSetupDone") << dynamicSetupDoneRoot << storage << dynamicSetupDoneLog - << true << true << 4; - const Group dynamicSetupErrorRoot = constructDynamicSetup({stopWithErrorSetup}); - const Log dynamicSetupErrorLog{{1, Handler::Setup}, - {1, Handler::Done}, - {-1, Handler::GroupError}}; - QTest::newRow("DynamicSetupError") << dynamicSetupErrorRoot << storage << dynamicSetupErrorLog - << true << false << 4; + { + const Group root { + Storage(storage), + OnGroupDone(groupDone(0)) + }; + const Log log {{0, Handler::GroupDone}}; + QTest::newRow("Empty") + << TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Success}; + } - const Group dynamicSetupContinueRoot = constructDynamicSetup({continueSetup}); - const Log dynamicSetupContinueLog{{1, Handler::Setup}, - {1, Handler::Done}, - {2, Handler::Setup}, - {2, Handler::Done}, - {3, Handler::Setup}, - {3, Handler::Done}, - {4, Handler::Setup}, - {4, Handler::Done}, - {-1, Handler::GroupDone}}; - QTest::newRow("DynamicSetupContinue") << dynamicSetupContinueRoot << storage - << dynamicSetupContinueLog << true << true << 4; + { + const Group root { + Storage(storage), + Process(setupDynamicProcess(1, TaskAction::StopWithDone), readResult, readError), + Process(setupDynamicProcess(2, TaskAction::StopWithDone), readResult, readError) + }; + const Log log {{1, Handler::Setup}, {2, Handler::Setup}}; + QTest::newRow("DynamicTaskDone") + << TestData{storage, root, log, 2, OnStart::NotRunning, OnDone::Success}; + } - const Group nestedParallelRoot { - ParallelLimit(2), - Storage(storage), - Group { - Storage(TreeStorage()), - OnGroupSetup(std::bind(groupSetup, 1)), + { + const Group root { + Storage(storage), + Process(setupDynamicProcess(1, TaskAction::StopWithError), readResult, readError), + Process(setupDynamicProcess(2, TaskAction::StopWithError), readResult, readError) + }; + const Log log {{1, Handler::Setup}}; + QTest::newRow("DynamicTaskError") + << TestData{storage, root, log, 2, OnStart::NotRunning, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), + Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError), + Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError), + Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError), + Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup} + }; + QTest::newRow("DynamicMixed") + << TestData{storage, root, log, 4, OnStart::Running, OnDone::Failure}; + } + + { + const Group root { + parallel, + Storage(storage), + Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError), + Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError), + Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError), + Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError) + }; + const Log log { + {1, Handler::Setup}, + {2, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Error} + }; + QTest::newRow("DynamicParallel") + << TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure}; + } + + { + const Group root { + parallel, + Storage(storage), + Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError), + Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError), Group { - parallel, - Process(std::bind(setupProcess, _1, 1)), - } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(std::bind(groupSetup, 2)), + Process(setupDynamicProcess(3, TaskAction::StopWithError), readResult, readError) + }, + Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError) + }; + const Log log { + {1, Handler::Setup}, + {2, Handler::Setup}, + {3, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Error} + }; + QTest::newRow("DynamicParallelGroup") + << TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure}; + } + + { + const Group root { + parallel, + Storage(storage), + Process(setupDynamicProcess(1, TaskAction::Continue), readResult, readError), + Process(setupDynamicProcess(2, TaskAction::Continue), readResult, readError), Group { - parallel, - Process(std::bind(setupProcess, _1, 2)), - } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(std::bind(groupSetup, 3)), + OnGroupSetup([storage] { + storage->m_log.append({0, Handler::GroupSetup}); + return TaskAction::StopWithError; + }), + Process(setupDynamicProcess(3, TaskAction::Continue), readResult, readError) + }, + Process(setupDynamicProcess(4, TaskAction::Continue), readResult, readError) + }; + const Log log { + {1, Handler::Setup}, + {2, Handler::Setup}, + {0, Handler::GroupSetup}, + {1, Handler::Error}, + {2, Handler::Error} + }; + QTest::newRow("DynamicParallelGroupSetup") + << TestData{storage, root, log, 4, OnStart::NotRunning, OnDone::Failure}; + } + + { + const Group root { + Storage(storage), Group { - parallel, - Process(std::bind(setupProcess, _1, 3)), - } - }, - Group { - Storage(TreeStorage()), - OnGroupSetup(std::bind(groupSetup, 4)), + Group { + Group { + Group { + Group { + Process(setupProcess(5), readResult, readError), + OnGroupSetup(groupSetup(5)), + OnGroupDone(groupDone(5)) + }, + OnGroupSetup(groupSetup(4)), + OnGroupDone(groupDone(4)) + }, + OnGroupSetup(groupSetup(3)), + OnGroupDone(groupDone(3)) + }, + OnGroupSetup(groupSetup(2)), + OnGroupDone(groupDone(2)) + }, + OnGroupSetup(groupSetup(1)), + OnGroupDone(groupDone(1)) + }, + OnGroupDone(groupDone(0)) + }; + const Log log { + {1, Handler::GroupSetup}, + {2, Handler::GroupSetup}, + {3, Handler::GroupSetup}, + {4, Handler::GroupSetup}, + {5, Handler::GroupSetup}, + {5, Handler::Setup}, + {5, Handler::Done}, + {5, Handler::GroupDone}, + {4, Handler::GroupDone}, + {3, Handler::GroupDone}, + {2, Handler::GroupDone}, + {1, Handler::GroupDone}, + {0, Handler::GroupDone} + }; + QTest::newRow("Nested") + << TestData{storage, root, log, 1, OnStart::Running, OnDone::Success}; + } + + { + const auto readResultAnonymous = [=](const QtcProcess &) { + storage->m_log.append({0, Handler::Done}); + }; + const Group root { + Storage(storage), + parallel, + Process(setupProcess(1), readResultAnonymous), + Process(setupProcess(2), readResultAnonymous), + Process(setupProcess(3), readResultAnonymous), + Process(setupProcess(4), readResultAnonymous), + Process(setupProcess(5), readResultAnonymous), + OnGroupDone(groupDone(0)) + }; + const Log log { + {1, Handler::Setup}, // Setup order is determined in parallel mode + {2, Handler::Setup}, + {3, Handler::Setup}, + {4, Handler::Setup}, + {5, Handler::Setup}, + {0, Handler::Done}, // Done order isn't determined in parallel mode + {0, Handler::Done}, + {0, Handler::Done}, + {0, Handler::Done}, + {0, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("Parallel") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; + } + + { + auto setupSubTree = [=](TaskTree &taskTree) { + const Group nestedRoot { + Storage(storage), + Process(setupProcess(2), readResult), + Process(setupProcess(3), readResult), + Process(setupProcess(4), readResult) + }; + taskTree.setupRoot(nestedRoot); + CustomStorage *activeStorage = storage.activeStorage(); + auto collectSubLog = [activeStorage](CustomStorage *subTreeStorage){ + activeStorage->m_log += subTreeStorage->m_log; + }; + taskTree.onStorageDone(storage, collectSubLog); + }; + const Group root1 { + Storage(storage), + Process(setupProcess(1), readResult), + Process(setupProcess(2), readResult), + Process(setupProcess(3), readResult), + Process(setupProcess(4), readResult), + Process(setupProcess(5), readResult), + OnGroupDone(groupDone(0)) + }; + const Group root2 { + Storage(storage), + Group { Process(setupProcess(1), readResult) }, + Group { Process(setupProcess(2), readResult) }, + Group { Process(setupProcess(3), readResult) }, + Group { Process(setupProcess(4), readResult) }, + Group { Process(setupProcess(5), readResult) }, + OnGroupDone(groupDone(0)) + }; + const Group root3 { + Storage(storage), + Process(setupProcess(1), readResult), + Tree(setupSubTree), + Process(setupProcess(5), readResult), + OnGroupDone(groupDone(0)) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Setup}, + {4, Handler::Done}, + {5, Handler::Setup}, + {5, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("Sequential") + << TestData{storage, root1, log, 5, OnStart::Running, OnDone::Success}; + QTest::newRow("SequentialEncapsulated") + << TestData{storage, root2, log, 5, OnStart::Running, OnDone::Success}; + QTest::newRow("SequentialSubTree") // We don't inspect subtrees, so taskCount is 3, not 5. + << TestData{storage, root3, log, 3, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + Storage(storage), Group { - parallel, - Process(std::bind(setupProcess, _1, 4)), + Process(setupProcess(1), readResult), + Group { + Process(setupProcess(2), readResult), + Group { + Process(setupProcess(3), readResult), + Group { + Process(setupProcess(4), readResult), + Group { + Process(setupProcess(5), readResult), + OnGroupDone(groupDone(5)) + }, + OnGroupDone(groupDone(4)) + }, + OnGroupDone(groupDone(3)) + }, + OnGroupDone(groupDone(2)) + }, + OnGroupDone(groupDone(1)) + }, + OnGroupDone(groupDone(0)) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Setup}, + {4, Handler::Done}, + {5, Handler::Setup}, + {5, Handler::Done}, + {5, Handler::GroupDone}, + {4, Handler::GroupDone}, + {3, Handler::GroupDone}, + {2, Handler::GroupDone}, + {1, Handler::GroupDone}, + {0, Handler::GroupDone} + }; + QTest::newRow("SequentialNested") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + Process(setupProcess(1), readResult), + Process(setupProcess(2), readResult), + Process(setupCrashProcess(3), readResult, readError), + Process(setupProcess(4), readResult), + Process(setupProcess(5), readResult), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Error}, + {0, Handler::GroupError} + }; + QTest::newRow("SequentialError") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Failure}; + } + + { + const Group root = constructSimpleSequence(stopOnError); + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Error}, + {0, Handler::GroupError} + }; + QTest::newRow("StopOnError") + << TestData{storage, root, log, 3, OnStart::Running, OnDone::Failure}; + } + + { + const Group root = constructSimpleSequence(continueOnError); + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Error}, + {3, Handler::Setup}, + {3, Handler::Done}, + {0, Handler::GroupError} + }; + QTest::newRow("ContinueOnError") + << TestData{storage, root, log, 3, OnStart::Running, OnDone::Failure}; + } + + { + const Group root = constructSimpleSequence(stopOnDone); + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("StopOnDone") + << TestData{storage, root, log, 3, OnStart::Running, OnDone::Success}; + } + + { + const Group root = constructSimpleSequence(continueOnDone); + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Error}, + {3, Handler::Setup}, + {3, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("ContinueOnDone") + << TestData{storage, root, log, 3, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + Storage(storage), + optional, + Process(setupCrashProcess(1), readResult, readError), + Process(setupCrashProcess(2), readResult, readError), + OnGroupDone(groupDone(0)), + OnGroupError(groupError(0)) + }; + const Log log { + {1, Handler::Setup}, + {1, Handler::Error}, + {2, Handler::Setup}, + {2, Handler::Error}, + {0, Handler::GroupDone} + }; + QTest::newRow("Optional") + << TestData{storage, root, log, 2, OnStart::Running, OnDone::Success}; + } + + { + const Group root = constructDynamicHierarchy(TaskAction::StopWithDone); + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("DynamicSetupDone") + << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; + } + + { + const Group root = constructDynamicHierarchy(TaskAction::StopWithError); + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {0, Handler::GroupError} + }; + QTest::newRow("DynamicSetupError") + << TestData{storage, root, log, 4, OnStart::Running, OnDone::Failure}; + } + + { + const Group root = constructDynamicHierarchy(TaskAction::Continue); + const Log log { + {1, Handler::Setup}, + {1, Handler::Done}, + {2, Handler::Setup}, + {2, Handler::Done}, + {3, Handler::Setup}, + {3, Handler::Done}, + {4, Handler::Setup}, + {4, Handler::Done}, + {0, Handler::GroupDone} + }; + QTest::newRow("DynamicSetupContinue") + << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + OnGroupSetup(groupSetup(1)), + Process(setupProcess(1)) + }, + Group { + OnGroupSetup(groupSetup(2)), + Process(setupProcess(2)) + }, + Group { + OnGroupSetup(groupSetup(3)), + Process(setupProcess(3)) + }, + Group { + OnGroupSetup(groupSetup(4)), + Process(setupProcess(4)) } - }, - }; - const Log nestedParallelLog{{1, Handler::GroupSetup}, - {1, Handler::Setup}, - {2, Handler::GroupSetup}, - {2, Handler::Setup}, - {3, Handler::GroupSetup}, - {3, Handler::Setup}, - {4, Handler::GroupSetup}, - {4, Handler::Setup}}; - QTest::newRow("NestedParallel") << nestedParallelRoot << storage << nestedParallelLog - << true << true << 4; + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {4, Handler::GroupSetup}, + {4, Handler::Setup} + }; + QTest::newRow("NestedParallel") + << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + OnGroupSetup(groupSetup(1)), + Process(setupProcess(1)) + }, + Group { + OnGroupSetup(groupSetup(2)), + Process(setupProcess(2)) + }, + Group { + OnGroupSetup(groupSetup(3)), + Process(setupDynamicProcess(3, TaskAction::StopWithDone)) + }, + Group { + OnGroupSetup(groupSetup(4)), + Process(setupProcess(4)) + }, + Group { + OnGroupSetup(groupSetup(5)), + Process(setupProcess(5)) + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {4, Handler::GroupSetup}, + {4, Handler::Setup}, + {5, Handler::GroupSetup}, + {5, Handler::Setup} + }; + QTest::newRow("NestedParallelDone") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + OnGroupSetup(groupSetup(1)), + Process(setupProcess(1)) + }, + Group { + OnGroupSetup(groupSetup(2)), + Process(setupProcess(2)) + }, + Group { + OnGroupSetup(groupSetup(3)), + Process(setupDynamicProcess(3, TaskAction::StopWithError)) + }, + Group { + OnGroupSetup(groupSetup(4)), + Process(setupProcess(4)) + }, + Group { + OnGroupSetup(groupSetup(5)), + Process(setupProcess(5)) + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::GroupSetup}, + {3, Handler::Setup} + }; + QTest::newRow("NestedParallelError") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Failure}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(1)), + Group { + parallel, + Process(setupProcess(1)) + } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(2)), + Group { + parallel, + Process(setupProcess(2)) + } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(3)), + Group { + parallel, + Process(setupProcess(3)) + } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(4)), + Group { + parallel, + Process(setupProcess(4)) + } + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {4, Handler::GroupSetup}, + {4, Handler::Setup} + }; + QTest::newRow("DeeplyNestedParallel") + << TestData{storage, root, log, 4, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(1)), + Group { Process(setupProcess(1)) } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(2)), + Group { Process(setupProcess(2)) } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(3)), + Group { Process(setupDynamicProcess(3, TaskAction::StopWithDone)) } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(4)), + Group { Process(setupProcess(4)) } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(5)), + Group { Process(setupProcess(5)) } + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::GroupSetup}, + {3, Handler::Setup}, + {4, Handler::GroupSetup}, + {4, Handler::Setup}, + {5, Handler::GroupSetup}, + {5, Handler::Setup} + }; + QTest::newRow("DeeplyNestedParallelDone") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Success}; + } + + { + const Group root { + ParallelLimit(2), + Storage(storage), + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(1)), + Group { Process(setupProcess(1)) } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(2)), + Group { Process(setupProcess(2)) } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(3)), + Group { Process(setupDynamicProcess(3, TaskAction::StopWithError)) } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(4)), + Group { Process(setupProcess(4)) } + }, + Group { + Storage(TreeStorage()), + OnGroupSetup(groupSetup(5)), + Group { Process(setupProcess(5)) } + } + }; + const Log log { + {1, Handler::GroupSetup}, + {1, Handler::Setup}, + {2, Handler::GroupSetup}, + {2, Handler::Setup}, + {3, Handler::GroupSetup}, + {3, Handler::Setup} + }; + QTest::newRow("DeeplyNestedParallelError") + << TestData{storage, root, log, 5, OnStart::Running, OnDone::Failure}; + } } void tst_TaskTree::processTree() { - QFETCH(Group, root); - QFETCH(TreeStorage, storage); - QFETCH(Log, expectedLog); - QFETCH(bool, runningAfterStart); - QFETCH(bool, success); - QFETCH(int, taskCount); + QFETCH(TestData, testData); QEventLoop eventLoop; - TaskTree taskTree(root); - QCOMPARE(taskTree.taskCount(), taskCount); + TaskTree taskTree(testData.root); + QCOMPARE(taskTree.taskCount(), testData.taskCount); int doneCount = 0; int errorCount = 0; connect(&taskTree, &TaskTree::done, this, [&doneCount, &eventLoop] { @@ -640,11 +910,12 @@ void tst_TaskTree::processTree() auto collectLog = [&actualLog](CustomStorage *storage){ actualLog = storage->m_log; }; - taskTree.onStorageDone(storage, collectLog); + taskTree.onStorageDone(testData.storage, collectLog); taskTree.start(); - QCOMPARE(taskTree.isRunning(), runningAfterStart); + const bool expectRunning = testData.onStart == OnStart::Running; + QCOMPARE(taskTree.isRunning(), expectRunning); - if (runningAfterStart) { + if (expectRunning) { QTimer timer; bool timedOut = false; connect(&timer, &QTimer::timeout, &eventLoop, [&eventLoop, &timedOut] { @@ -659,12 +930,13 @@ void tst_TaskTree::processTree() QCOMPARE(taskTree.isRunning(), false); } - QCOMPARE(taskTree.progressValue(), taskCount); - QCOMPARE(actualLog, expectedLog); + QCOMPARE(taskTree.progressValue(), testData.taskCount); + QCOMPARE(actualLog, testData.expectedLog); QCOMPARE(CustomStorage::instanceCount(), 0); - const int expectedDoneCount = success ? 1 : 0; - const int expectedErrorCount = success ? 0 : 1; + const bool expectSuccess = testData.onDone == OnDone::Success; + const int expectedDoneCount = expectSuccess ? 1 : 0; + const int expectedErrorCount = expectSuccess ? 0 : 1; QCOMPARE(doneCount, expectedDoneCount); QCOMPARE(errorCount, expectedErrorCount); }