File indexing completed on 2024-12-01 05:11:50

0001 // clang-format off
0002 /*
0003   class CvsIgnoreList from Cervisia cvsdir.cpp
0004      SPDX-FileCopyrightText: 1999-2002 Bernd Gehrmann <bernd at mail.berlios.de>
0005   with elements from class StringMatcher
0006      SPDX-FileCopyrightText: 2003 Andre Woebbeking <Woebbeking at web.de>
0007   Modifications for KDiff3 by Joachim Eibl
0008 
0009   SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de
0010   SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com
0011   SPDX-License-Identifier: GPL-2.0-or-later
0012  */
0013 // clang-format on
0014 #include "CvsIgnoreList.h"
0015 
0016 #include "fileaccess.h"        // for FileAccess
0017 
0018 #include <list>
0019 #include <utility>             // for pair
0020 
0021 #include <QByteArray>
0022 #include <QDir>
0023 #include <QFile>
0024 #include <QRegularExpression>
0025 #include <QStringList>
0026 #include <QTextStream>
0027 
0028 CvsIgnoreList::CvsIgnoreList() = default;
0029 
0030 CvsIgnoreList::~CvsIgnoreList() = default;
0031 
0032 void CvsIgnoreList::enterDir(const QString& dir, const DirectoryList& directoryList)
0033 {
0034     static const QString ignorestr = QString::fromLatin1(". .. core RCSLOG tags TAGS RCS SCCS .make.state "
0035                                    ".nse_depinfo #* .#* cvslog.* ,* CVS CVS.adm .del-* *.a *.olb *.o *.obj "
0036                                    "*.so *.Z *~ *.old *.elc *.ln *.bak *.BAK *.orig *.rej *.exe _$* *$");
0037     addEntriesFromString(dir, ignorestr);
0038     addEntriesFromFile(dir, QDir::homePath() + '/' + getGlobalIgnoreName());
0039     const char* varname = getVarName();
0040     if(qEnvironmentVariableIsSet(varname) && !qEnvironmentVariableIsEmpty(varname))
0041     {
0042         addEntriesFromString(dir, QString::fromLocal8Bit(qgetenv(varname)));
0043     }
0044     const bool bUseLocalCvsIgnore = ignoreExists(directoryList);
0045     if(bUseLocalCvsIgnore)
0046     {
0047         FileAccess file(dir);
0048         file.addPath(getIgnoreName());
0049         if(file.exists() && file.isLocal())
0050         {
0051             addEntriesFromFile(dir, file.absoluteFilePath());
0052         }
0053         else
0054         {
0055             file.createLocalCopy();
0056             addEntriesFromFile(dir, file.getTempName());
0057         }
0058     }
0059 }
0060 
0061 void CvsIgnoreList::addEntriesFromString(const QString& dir, const QString& str)
0062 {
0063     const QStringList patternList = str.split(' ');
0064     for(const QString& pattern: patternList)
0065     {
0066         addEntry(dir, pattern);
0067     }
0068 }
0069 
0070 /*
0071     We don't have a real file in AUTOTEST mode
0072 */
0073 void CvsIgnoreList::addEntriesFromFile(const QString& dir, const QString& name)
0074 { //want unused warning when not building autotest
0075 #ifdef AUTOTEST
0076     Q_UNUSED(name);
0077     Q_UNUSED(dir);
0078 #else
0079     QFile file(name);
0080 
0081     if(file.open(QIODevice::ReadOnly))
0082     {
0083         QTextStream stream(&file);
0084         while(!stream.atEnd())
0085         {
0086             addEntry(dir, stream.readLine());
0087         }
0088     }
0089 #endif
0090 }
0091 
0092 void CvsIgnoreList::addEntry(const QString& dir, const QString& pattern)
0093 {
0094     if(pattern != QString("!"))
0095     {
0096         if(pattern.isEmpty()) return;
0097 
0098         // The general match is general but slow.
0099         // Special tests for '*' and '?' at the beginning or end of a pattern
0100         // allow fast checks.
0101 
0102         // Count number of '*' and '?'
0103         quint32 nofMetaCharacters = 0;
0104 
0105         const QChar* pos;
0106         pos = pattern.unicode();
0107         const QChar* posEnd;
0108         posEnd = pos + pattern.length();
0109         while(pos < posEnd)
0110         {
0111             if(*pos == QChar('*') || *pos == QChar('?')) ++nofMetaCharacters;
0112             ++pos;
0113         }
0114 
0115         if(nofMetaCharacters == 0)
0116         {
0117             m_ignorePatterns[dir].m_exactPatterns.append(pattern);
0118         }
0119         else if(nofMetaCharacters == 1)
0120         {
0121             if(pattern.at(0) == QChar('*'))
0122             {
0123                 m_ignorePatterns[dir].m_endPatterns.append(pattern.right(pattern.length() - 1));
0124             }
0125             else if(pattern.at(pattern.length() - 1) == QChar('*'))
0126             {
0127                 m_ignorePatterns[dir].m_startPatterns.append(pattern.left(pattern.length() - 1));
0128             }
0129             else
0130             {
0131                 m_ignorePatterns[dir].m_generalPatterns.append(pattern);
0132             }
0133         }
0134         else
0135         {
0136             m_ignorePatterns[dir].m_generalPatterns.append(pattern);
0137         }
0138     }
0139     else
0140     {
0141         m_ignorePatterns.erase(dir);
0142     }
0143 }
0144 
0145 bool CvsIgnoreList::matches(const QString& dir, const QString& text, bool bCaseSensitive) const
0146 {
0147     const auto ignorePatternsIt = m_ignorePatterns.find(dir);
0148     if(ignorePatternsIt == m_ignorePatterns.end())
0149     {
0150         return false;
0151     }
0152     //Do not use QStringList::indexof here it has no case flag
0153     if(ignorePatternsIt->second.m_exactPatterns.contains(text, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive))
0154     {
0155         return true;
0156     }
0157 
0158     for(const QString& startPattern: ignorePatternsIt->second.m_startPatterns)
0159     {
0160         if(text.startsWith(startPattern, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive))
0161         {
0162             return true;
0163         }
0164     }
0165 
0166     for(const QString& endPattern: ignorePatternsIt->second.m_endPatterns)
0167     {
0168         if(text.endsWith(endPattern, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive))
0169         {
0170             return true;
0171         }
0172     }
0173 
0174     for(const QString& globStr: ignorePatternsIt->second.m_generalPatterns)
0175     {
0176         QRegularExpression pattern(QRegularExpression::wildcardToRegularExpression(globStr), bCaseSensitive ? QRegularExpression::UseUnicodePropertiesOption : QRegularExpression::UseUnicodePropertiesOption | QRegularExpression::CaseInsensitiveOption);
0177         if(pattern.match(text).hasMatch())
0178             return true;
0179     }
0180 
0181     return false;
0182 }
0183 
0184 bool CvsIgnoreList::ignoreExists(const DirectoryList& pDirList)
0185 {
0186     for(const FileAccess& dir: pDirList)
0187     {
0188         if(dir.fileName() == getIgnoreName())
0189             return true;
0190     }
0191     return false;
0192 }