File indexing completed on 2024-04-28 05:49:10

0001 /*  This file is part of the Kate project.
0002  *
0003  *  SPDX-FileCopyrightText: 2012 Christoph Cullmann <cullmann@kde.org>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "kateprojectworker.h"
0009 #include "kateprojectindex.h"
0010 #include "kateprojectitem.h"
0011 
0012 #include <bytearraysplitter.h>
0013 #include <gitprocess.h>
0014 
0015 #include "hostprocess.h"
0016 #include <QDir>
0017 #include <QDirIterator>
0018 #include <QFile>
0019 #include <QFileInfo>
0020 #include <QProcess>
0021 #include <QRegularExpression>
0022 #include <QSet>
0023 #include <QStandardPaths>
0024 #include <QThread>
0025 #include <QtConcurrent>
0026 
0027 #include <algorithm>
0028 #include <tuple>
0029 #include <vector>
0030 
0031 KateProjectWorker::KateProjectWorker(const QString &baseDir, const QString &indexDir, const QVariantMap &projectMap, bool force)
0032     : m_baseDir(baseDir)
0033     , m_indexDir(indexDir)
0034     , m_projectMap(projectMap)
0035     , m_force(force)
0036 {
0037     Q_ASSERT(!m_baseDir.isEmpty());
0038 }
0039 
0040 void KateProjectWorker::run()
0041 {
0042     /**
0043      * Create dummy top level parent item and empty map inside shared pointers
0044      * then load the project recursively
0045      */
0046     KateProjectSharedQStandardItem topLevel(new QStandardItem());
0047     KateProjectSharedQHashStringItem file2Item(new QHash<QString, KateProjectItem *>());
0048     loadProject(topLevel.get(), m_projectMap, file2Item.get(), m_baseDir);
0049 
0050     /**
0051      * sort the stuff once recursively, this is a LOT faster than once sorting the list
0052      * as we have normally not all stuff in on level of directory
0053      */
0054     topLevel->sortChildren(0);
0055 
0056     /**
0057      * decide if we need to create an index
0058      * if we need to do so, we will need to create a copy of the file list for later use
0059      * before this was default on, which is dangerous for large repositories, e.g. out-of-memory or out-of-disk
0060      * if specified in project map; use that setting, otherwise fall back to global setting
0061      */
0062     bool indexEnabled = !m_indexDir.isEmpty();
0063     const QVariantMap ctagsMap = m_projectMap[QStringLiteral("ctags")].toMap();
0064     auto indexValue = ctagsMap[QStringLiteral("enable")];
0065     if (!indexValue.isNull()) {
0066         indexEnabled = indexValue.toBool();
0067     }
0068 
0069     /**
0070      * create some local backup of some data we need for further processing!
0071      * this is expensive, therefore only really do this if required!
0072      */
0073     QStringList files;
0074     if (indexEnabled) {
0075         files = file2Item->keys();
0076     }
0077 
0078     /**
0079      * hand out our model item & mapping to the main thread
0080      * that will let Kate already show the project, even before index processing starts
0081      */
0082     Q_EMIT loadDone(topLevel, file2Item);
0083 
0084     /**
0085      * without indexing, we are even done with all stuff here
0086      */
0087     if (!indexEnabled) {
0088         Q_EMIT loadIndexDone(KateProjectSharedProjectIndex());
0089         return;
0090     }
0091 
0092     /**
0093      * create new index, this will do the loading in the constructor
0094      * wrap it into shared pointer for transfer to main thread
0095      */
0096     KateProjectSharedProjectIndex index(new KateProjectIndex(m_baseDir, m_indexDir, files, ctagsMap, m_force));
0097     Q_EMIT loadIndexDone(index);
0098 }
0099 
0100 void KateProjectWorker::loadProject(QStandardItem *parent, const QVariantMap &project, QHash<QString, KateProjectItem *> *file2Item, const QString &baseDir)
0101 {
0102     /**
0103      * recurse to sub-projects FIRST
0104      */
0105     const QVariantList subGroups = project[QStringLiteral("projects")].toList();
0106     for (const QVariant &subGroupVariant : subGroups) {
0107         /**
0108          * convert to map and get name, else skip
0109          */
0110         const QVariantMap subProject = subGroupVariant.toMap();
0111         const QString keyName = QStringLiteral("name");
0112         if (subProject[keyName].toString().isEmpty()) {
0113             continue;
0114         }
0115 
0116         /**
0117          * recurse
0118          */
0119         QStandardItem *subProjectItem = new KateProjectItem(KateProjectItem::Project, subProject[keyName].toString());
0120         loadProject(subProjectItem, subProject, file2Item, baseDir);
0121         parent->appendRow(subProjectItem);
0122     }
0123 
0124     /**
0125      * load all specified files
0126      */
0127     const QString keyFiles = QStringLiteral("files");
0128     const QVariantList files = project[keyFiles].toList();
0129     for (const QVariant &fileVariant : files) {
0130         loadFilesEntry(parent, fileVariant.toMap(), file2Item, baseDir);
0131     }
0132 }
0133 
0134 /**
0135  * small helper to construct directory parent items
0136  * @param dir2Item map for path => item
0137  * @param path current path we need item for
0138  * @return correct parent item for given path, will reuse existing ones
0139  */
0140 QStandardItem *KateProjectWorker::directoryParent(const QDir &base, QHash<QString, QStandardItem *> &dir2Item, QString path)
0141 {
0142     /**
0143      * throw away simple /
0144      */
0145     if (path == QLatin1String("/")) {
0146         path = QString();
0147     }
0148 
0149     /**
0150      * quick check: dir already seen?
0151      */
0152     const auto existingIt = dir2Item.find(path);
0153     if (existingIt != dir2Item.end()) {
0154         return existingIt.value();
0155     }
0156 
0157     /**
0158      * else: construct recursively
0159      */
0160     const int slashIndex = path.lastIndexOf(QLatin1Char('/'));
0161 
0162     /**
0163      * no slash?
0164      * simple, no recursion, append new item toplevel
0165      */
0166     if (slashIndex < 0) {
0167         const auto item = new KateProjectItem(KateProjectItem::Directory, path);
0168         item->setData(base.absoluteFilePath(path), Qt::UserRole);
0169         dir2Item[path] = item;
0170         dir2Item[QString()]->appendRow(item);
0171         return item;
0172     }
0173 
0174     /**
0175      * else, split and recurse
0176      */
0177     const QString leftPart = path.left(slashIndex);
0178     const QString rightPart = path.right(path.size() - (slashIndex + 1));
0179 
0180     /**
0181      * special handling if / with nothing on one side are found
0182      */
0183     if (leftPart.isEmpty() || rightPart.isEmpty()) {
0184         return directoryParent(base, dir2Item, leftPart.isEmpty() ? rightPart : leftPart);
0185     }
0186 
0187     /**
0188      * else: recurse on left side
0189      */
0190     const auto item = new KateProjectItem(KateProjectItem::Directory, rightPart);
0191     item->setData(base.absoluteFilePath(path), Qt::UserRole);
0192     dir2Item[path] = item;
0193     directoryParent(base, dir2Item, leftPart)->appendRow(item);
0194     return item;
0195 }
0196 
0197 void KateProjectWorker::loadFilesEntry(QStandardItem *parent,
0198                                        const QVariantMap &filesEntry,
0199                                        QHash<QString, KateProjectItem *> *file2Item,
0200                                        const QString &baseDir)
0201 {
0202     QDir dir(baseDir);
0203     if (!dir.cd(filesEntry[QStringLiteral("directory")].toString())) {
0204         return;
0205     }
0206 
0207     /**
0208      * handle linked projects, if any
0209      * one can reference other projects by specifying the path to them
0210      */
0211     QStringList linkedProjects = filesEntry[QStringLiteral("projects")].toStringList();
0212     if (!linkedProjects.empty()) {
0213         /**
0214          * ensure project files are made absolute in respect to correct base dir
0215          */
0216         for (auto &project : linkedProjects) {
0217             project = dir.absoluteFilePath(project);
0218         }
0219 
0220         /**
0221          * users might have specified duplicates, this can't happen for the other ways
0222          */
0223         linkedProjects.removeDuplicates();
0224 
0225         /**
0226          * filter out all directories that have no .kateproject inside!
0227          */
0228         linkedProjects.erase(std::remove_if(linkedProjects.begin(),
0229                                             linkedProjects.end(),
0230                                             [](const QString &item) {
0231                                                 const QFileInfo projectFile(item + QLatin1String("/.kateproject"));
0232                                                 return !projectFile.exists() || !projectFile.isFile();
0233                                             }),
0234                              linkedProjects.end());
0235 
0236         /**
0237          * we sort the projects, below we require that we walk them in order:
0238          * lala
0239          * lala/test
0240          * mow
0241          * mow/test2
0242          */
0243         std::sort(linkedProjects.begin(), linkedProjects.end());
0244 
0245         /**
0246          * now add our projects to the current item parent
0247          * later the tree view will e.g. allow to jump to the sub-projects
0248          */
0249         QHash<QString, QStandardItem *> dir2Item;
0250         dir2Item[QString()] = parent;
0251         for (const auto &filePath : linkedProjects) {
0252             /**
0253              * cheap file name computation
0254              * we do this A LOT, QFileInfo is very expensive just for this operation
0255              */
0256             const int slashIndex = filePath.lastIndexOf(QLatin1Char('/'));
0257             const QString fileName = (slashIndex < 0) ? filePath : filePath.mid(slashIndex + 1);
0258             const QString filePathName = (slashIndex < 0) ? QString() : filePath.left(slashIndex);
0259 
0260             /**
0261              * construct the item with right directory prefix
0262              * already hang in directories in tree
0263              */
0264             KateProjectItem *fileItem = new KateProjectItem(KateProjectItem::LinkedProject, fileName);
0265             fileItem->setData(filePath, Qt::UserRole);
0266 
0267             /**
0268              * projects are directories, register them, we walk in order over the projects
0269              * even if the nest, toplevel ones would have been done before!
0270              */
0271             dir2Item[dir.relativeFilePath(filePath)] = fileItem;
0272 
0273             // get the directory's relative path to the base directory
0274             QString dirRelPath = dir.relativeFilePath(filePathName);
0275             // if the relative path is ".", clean it up
0276             if (dirRelPath == QLatin1Char('.')) {
0277                 dirRelPath = QString();
0278             }
0279 
0280             // put in our item to the right directory parent
0281             directoryParent(dir, dir2Item, dirRelPath)->appendRow(fileItem);
0282         }
0283 
0284         /**
0285          * files with linked projects will ignore all other stuff inside
0286          */
0287         return;
0288     }
0289 
0290     /**
0291      * get list of files for this directory, might query the VCS
0292      */
0293     const QList<QString> files = findFiles(dir, filesEntry);
0294 
0295     QStringList excludeFolderPatterns = m_projectMap.value(QStringLiteral("exclude_patterns")).toStringList();
0296     std::vector<QRegularExpression> excludeRegexps;
0297     excludeRegexps.reserve(excludeFolderPatterns.size());
0298     for (const auto &pattern : excludeFolderPatterns) {
0299         excludeRegexps.push_back(QRegularExpression(pattern, QRegularExpression::DontCaptureOption));
0300     }
0301 
0302     /**
0303      * sort out non-files
0304      * even for git, that just reports non-directories, we need to filter out e.g. sym-links to directories
0305      * we use map, not filter, less locking!
0306      * we compute here already the KateProjectItem items we want to use later
0307      * this happens in the threads, we later skip all nullptr entries
0308      */
0309     std::vector<std::tuple<QString, QString, KateProjectItem *>> preparedItems;
0310     preparedItems.reserve(files.size());
0311     for (const auto &item : files)
0312         preparedItems.emplace_back(item, QString(), nullptr);
0313     QtConcurrent::blockingMap(preparedItems, [dir, excludeRegexps](std::tuple<QString, QString, KateProjectItem *> &item) {
0314         /**
0315          * cheap file name computation
0316          * we do this A LOT, QFileInfo is very expensive just for this operation
0317          * we remember fullFilePath for later use and overwrite filePath with the part without the filename for later use, too
0318          */
0319         auto &[filePath, fullFilePath, projectItem] = item;
0320         const QFileInfo info(dir, filePath);
0321         fullFilePath = info.absoluteFilePath();
0322 
0323         for (const auto &excludePattern : excludeRegexps) {
0324             if (excludePattern.match(filePath).hasMatch()) {
0325                 return;
0326             }
0327         }
0328 
0329         const int slashIndex = filePath.lastIndexOf(QLatin1Char('/'));
0330         const QString fileName = (slashIndex < 0) ? filePath : filePath.mid(slashIndex + 1);
0331         filePath = (slashIndex < 0) ? QString() : filePath.left(slashIndex);
0332 
0333         /**
0334          * construct the item with info about filename + full file path
0335          */
0336         if (info.isFile()) {
0337             projectItem = new KateProjectItem(KateProjectItem::File, fileName);
0338             projectItem->setData(fullFilePath, Qt::UserRole);
0339         }
0340         else if(info.isDir() && QDir(fullFilePath).isEmpty())
0341         {
0342             projectItem = new KateProjectItem(KateProjectItem::Directory, fileName);
0343             projectItem->setData(fullFilePath, Qt::UserRole);
0344         }
0345     });
0346 
0347     /**
0348      * put the pre-computed file items in our file2Item hash + create the needed directory items
0349      * all other stuff was already handled inside the worker threads to avoid main thread stalling
0350      */
0351     QHash<QString, QStandardItem *> dir2Item;
0352     dir2Item[QString()] = parent;
0353     file2Item->reserve(file2Item->size() + preparedItems.size());
0354     for (const auto &item : preparedItems) {
0355         /**
0356          * skip all entries without an item => that are filtered out non-files
0357          */
0358         const auto &[filePath, fullFilePath, projectItem] = item;
0359         if (!projectItem) {
0360             continue;
0361         }
0362 
0363         /**
0364          * register the item in the full file path => item hash
0365          * create needed directory parents
0366          */
0367         (*file2Item)[fullFilePath] = projectItem;
0368         directoryParent(dir, dir2Item, filePath)->appendRow(projectItem);
0369     }
0370 }
0371 
0372 QList<QString> KateProjectWorker::findFiles(const QDir &dir, const QVariantMap &filesEntry)
0373 {
0374     /**
0375      * shall we collect files recursively or not?
0376      */
0377     const bool recursive = !filesEntry.contains(QLatin1String("recursive")) || filesEntry[QStringLiteral("recursive")].toBool();
0378 
0379     /**
0380      * try the different version control systems first
0381      */
0382 
0383     if (filesEntry[QStringLiteral("git")].toBool()) {
0384         return filesFromGit(dir, recursive);
0385     }
0386 
0387     if (filesEntry[QStringLiteral("svn")].toBool()) {
0388         return filesFromSubversion(dir, recursive);
0389     }
0390 
0391     if (filesEntry[QStringLiteral("hg")].toBool()) {
0392         return filesFromMercurial(dir, recursive);
0393     }
0394 
0395     if (filesEntry[QStringLiteral("darcs")].toBool()) {
0396         return filesFromDarcs(dir, recursive);
0397     }
0398 
0399     if (filesEntry[QStringLiteral("fossil")].toBool()) {
0400         return filesFromFossil(dir, recursive);
0401     }
0402 
0403     /**
0404      * if we arrive here, we have some manual specification of files, no VCS
0405      */
0406 
0407     /**
0408      * try explicit list of stuff
0409      */
0410     QStringList userGivenFilesList = filesEntry[QStringLiteral("list")].toStringList();
0411     if (!userGivenFilesList.empty()) {
0412         /**
0413          * make the files absolute relative to current dir
0414          * all code later requires this and the filesFrom... routines do this, too, internally
0415          * even without this, the tree views will show them, but opening them will create new elements!
0416          */
0417         for (auto &file : userGivenFilesList) {
0418             file = dir.absoluteFilePath(file);
0419         }
0420 
0421         /**
0422          * users might have specified duplicates, this can't happen for the other ways
0423          */
0424         userGivenFilesList.removeDuplicates();
0425         return userGivenFilesList.toVector();
0426     }
0427 
0428     /**
0429      * shall we collect hidden files or not?
0430      */
0431     const bool hidden = filesEntry.contains(QLatin1String("hidden")) && filesEntry[QStringLiteral("hidden")].toBool();
0432 
0433     /**
0434      * if nothing found for that, try to use filters to scan the directory
0435      * here we only get files
0436      */
0437     return filesFromDirectory(dir, recursive, hidden, filesEntry[QStringLiteral("filters")].toStringList());
0438 }
0439 
0440 QList<QString> KateProjectWorker::filesFromGit(const QDir &dir, bool recursive)
0441 {
0442     /**
0443      * query files via ls-files and make them absolute afterwards
0444      */
0445 
0446     /**
0447      * git ls-files -z results a bytearray where each entry is \0-terminated.
0448      * NOTE: Without -z, Umlauts such as "Der Bäcker/Das Brötchen.txt" do not work (#389415)
0449      *
0450      * use --recurse-submodules, there since git 2.11 (released 2016)
0451      * our own submodules handling code leads to file duplicates
0452      */
0453 
0454     QStringList lsFilesArgs{QStringLiteral("ls-files"), QStringLiteral("-z"), QStringLiteral("--recurse-submodules"), QStringLiteral(".")};
0455 
0456     /**
0457      * ls-files untracked
0458      */
0459     QStringList lsFilesUntrackedArgs{QStringLiteral("ls-files"),
0460                                      QStringLiteral("-z"),
0461                                      QStringLiteral("--others"),
0462                                      QStringLiteral("--exclude-standard"),
0463                                      QStringLiteral(".")};
0464 
0465     /**
0466      * for recent enough git versions ensure we don't show duplicated files
0467      */
0468     const auto [major, minor] = getGitVersion(dir.absolutePath());
0469     if (major > 2 || (major == 2 && minor >= 31)) {
0470         lsFilesArgs.insert(3, QStringLiteral("--deduplicate"));
0471         lsFilesUntrackedArgs.insert(4, QStringLiteral("--deduplicate"));
0472     }
0473 
0474     // ls-files + ls-files untracked
0475     return gitFiles(dir, recursive, lsFilesArgs) << gitFiles(dir, recursive, lsFilesUntrackedArgs);
0476 }
0477 
0478 QList<QString> KateProjectWorker::gitFiles(const QDir &dir, bool recursive, const QStringList &args)
0479 {
0480     QList<QString> files;
0481     QProcess git;
0482     if (!setupGitProcess(git, dir.absolutePath(), args)) {
0483         return files;
0484     }
0485     startHostProcess(git, QProcess::ReadOnly);
0486     if (!git.waitForStarted() || !git.waitForFinished(-1)) {
0487         return files;
0488     }
0489 
0490     const QByteArray b = git.readAllStandardOutput();
0491     for (strview byteArray : ByteArraySplitter(b, '\0')) {
0492         if (byteArray.empty()) {
0493             continue;
0494         }
0495         if (!recursive && (byteArray.find('/') != std::string::npos)) {
0496             continue;
0497         }
0498         files.append(byteArray.toString());
0499     }
0500     return files;
0501 }
0502 
0503 QList<QString> KateProjectWorker::filesFromMercurial(const QDir &dir, bool recursive)
0504 {
0505     // only use version control from PATH
0506     QList<QString> files;
0507     static const auto fullExecutablePath = safeExecutableName(QStringLiteral("hg"));
0508     if (fullExecutablePath.isEmpty()) {
0509         return files;
0510     }
0511 
0512     QProcess hg;
0513     hg.setWorkingDirectory(dir.absolutePath());
0514     QStringList args;
0515     args << QStringLiteral("manifest") << QStringLiteral(".");
0516     startHostProcess(hg, fullExecutablePath, args, QProcess::ReadOnly);
0517     if (!hg.waitForStarted() || !hg.waitForFinished(-1)) {
0518         return files;
0519     }
0520 
0521     const QStringList relFiles = QString::fromLocal8Bit(hg.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), Qt::SkipEmptyParts);
0522 
0523     files.reserve(relFiles.size());
0524     for (const QString &relFile : relFiles) {
0525         if (!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) {
0526             continue;
0527         }
0528 
0529         files.append(relFile);
0530     }
0531 
0532     return files;
0533 }
0534 
0535 QList<QString> KateProjectWorker::filesFromSubversion(const QDir &dir, bool recursive)
0536 {
0537     // only use version control from PATH
0538     QList<QString> files;
0539     static const auto fullExecutablePath = safeExecutableName(QStringLiteral("svn"));
0540     if (fullExecutablePath.isEmpty()) {
0541         return files;
0542     }
0543 
0544     QProcess svn;
0545     svn.setWorkingDirectory(dir.absolutePath());
0546     QStringList args;
0547     args << QStringLiteral("status") << QStringLiteral("--verbose") << QStringLiteral(".");
0548     if (recursive) {
0549         args << QStringLiteral("--depth=infinity");
0550     } else {
0551         args << QStringLiteral("--depth=files");
0552     }
0553     startHostProcess(svn, fullExecutablePath, args, QProcess::ReadOnly);
0554     if (!svn.waitForStarted() || !svn.waitForFinished(-1)) {
0555         return files;
0556     }
0557 
0558     /**
0559      * get output and split up into lines
0560      */
0561     const QStringList lines = QString::fromLocal8Bit(svn.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), Qt::SkipEmptyParts);
0562 
0563     /**
0564      * remove start of line that is no filename, sort out unknown and ignore
0565      */
0566     bool first = true;
0567     int prefixLength = -1;
0568 
0569     files.reserve(lines.size());
0570     for (const QString &line : lines) {
0571         /**
0572          * get length of stuff to cut
0573          */
0574         if (first) {
0575             /**
0576              * try to find ., else fail
0577              */
0578             prefixLength = line.lastIndexOf(QLatin1Char('.'));
0579             if (prefixLength < 0) {
0580                 break;
0581             }
0582 
0583             /**
0584              * skip first
0585              */
0586             first = false;
0587             continue;
0588         }
0589 
0590         /**
0591          * get file, if not unknown or ignored
0592          * prepend directory path
0593          */
0594         if ((line.size() > prefixLength) && line[0] != QLatin1Char('?') && line[0] != QLatin1Char('I')) {
0595             files.append(line.right(line.size() - prefixLength));
0596         }
0597     }
0598 
0599     return files;
0600 }
0601 
0602 QList<QString> KateProjectWorker::filesFromDarcs(const QDir &dir, bool recursive)
0603 {
0604     // only use version control from PATH
0605     QList<QString> files;
0606     static const auto fullExecutablePath = safeExecutableName(QStringLiteral("darcs"));
0607     if (fullExecutablePath.isEmpty()) {
0608         return files;
0609     }
0610 
0611     QString root;
0612     {
0613         QProcess darcs;
0614         darcs.setWorkingDirectory(dir.absolutePath());
0615         QStringList args;
0616         args << QStringLiteral("list") << QStringLiteral("repo");
0617 
0618         startHostProcess(darcs, fullExecutablePath, args, QProcess::ReadOnly);
0619 
0620         if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) {
0621             return files;
0622         }
0623 
0624         auto str = QString::fromLocal8Bit(darcs.readAllStandardOutput());
0625         QRegularExpression exp(QStringLiteral("Root: ([^\\n\\r]*)"));
0626         auto match = exp.match(str);
0627 
0628         if (!match.hasMatch()) {
0629             return files;
0630         }
0631 
0632         root = match.captured(1);
0633     }
0634 
0635     QStringList relFiles;
0636     {
0637         QProcess darcs;
0638         QStringList args;
0639         darcs.setWorkingDirectory(dir.absolutePath());
0640         args << QStringLiteral("list") << QStringLiteral("files") << QStringLiteral("--no-directories") << QStringLiteral("--pending");
0641 
0642         startHostProcess(darcs, fullExecutablePath, args, QProcess::ReadOnly);
0643 
0644         if (!darcs.waitForStarted() || !darcs.waitForFinished(-1)) {
0645             return files;
0646         }
0647 
0648         relFiles = QString::fromLocal8Bit(darcs.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), Qt::SkipEmptyParts);
0649     }
0650 
0651     files.reserve(relFiles.size());
0652     for (const QString &relFile : qAsConst(relFiles)) {
0653         const QString path = dir.relativeFilePath(root + QLatin1String("/") + relFile);
0654 
0655         if ((!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) || (recursive && (relFile.indexOf(QLatin1String("..")) == 0))) {
0656             continue;
0657         }
0658 
0659         files.append(path);
0660     }
0661 
0662     return files;
0663 }
0664 
0665 QList<QString> KateProjectWorker::filesFromFossil(const QDir &dir, bool recursive)
0666 {
0667     // only use version control from PATH
0668     QList<QString> files;
0669     static const auto fullExecutablePath = safeExecutableName(QStringLiteral("fossil"));
0670     if (fullExecutablePath.isEmpty()) {
0671         return files;
0672     }
0673 
0674     QProcess fossil;
0675     fossil.setWorkingDirectory(dir.absolutePath());
0676     QStringList args;
0677     args << QStringLiteral("ls");
0678     startHostProcess(fossil, fullExecutablePath, args, QProcess::ReadOnly);
0679     if (!fossil.waitForStarted() || !fossil.waitForFinished(-1)) {
0680         return files;
0681     }
0682 
0683     const QStringList relFiles = QString::fromLocal8Bit(fossil.readAllStandardOutput()).split(QRegularExpression(QStringLiteral("[\n\r]")), Qt::SkipEmptyParts);
0684 
0685     files.reserve(relFiles.size());
0686     for (const QString &relFile : relFiles) {
0687         if (!recursive && (relFile.indexOf(QLatin1Char('/')) != -1)) {
0688             continue;
0689         }
0690 
0691         files.append(relFile);
0692     }
0693 
0694     return files;
0695 }
0696 
0697 QList<QString> KateProjectWorker::filesFromDirectory(QDir dir, bool recursive, bool hidden, const QStringList &filters)
0698 {
0699     /**
0700      * setup our filters
0701      */
0702     QDir::Filters filterFlags = QDir::Files | QDir::Dirs | QDir::NoDot | QDir::NoDotDot;
0703     if(hidden)
0704         filterFlags |= QDir::Hidden;
0705     dir.setFilter(filterFlags);
0706     if (!filters.isEmpty()) {
0707         dir.setNameFilters(filters);
0708     }
0709 
0710     /**
0711      * construct flags for iterator
0712      */
0713     QDirIterator::IteratorFlags flags = QDirIterator::NoIteratorFlags;
0714     if (recursive) {
0715         flags = flags | QDirIterator::Subdirectories | QDirIterator::FollowSymlinks;
0716     }
0717 
0718     /**
0719      * create iterator and collect all files
0720      */
0721     QList<QString> files;
0722     QDirIterator dirIterator(dir, flags);
0723     const QString dirPath = dir.path() + QLatin1Char('/');
0724     while (dirIterator.hasNext()) {
0725         dirIterator.next();
0726         // make it relative path
0727         files.append(dirIterator.filePath().remove(dirPath));
0728     }
0729     return files;
0730 }
0731 
0732 #include "moc_kateprojectworker.cpp"