1 /*
2 SPDX-FileCopyrightText: 2001-2005,2009 Otto Bruggeman <bruggie@gmail.com>
3 SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org>
4 SPDX-FileCopyrightText: 2007-2008 Kevin Kofler <kevin.kofler@chello.at>
5 
6 SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "kompareprocess.h"
10 
11 #include <QDir>
12 #include <QStringList>
13 #include <QTextCodec>
14 
15 #include <KCharsets>
16 #include <KIO/Global>
17 
18 #include <komparediffdebug.h>
19 #include "diffsettings.h"
20 
21 namespace {
22 /// TODO: This should be replaced to QDir::relativeFilePath
constructRelativePath(const QString & from,const QString & to)23 static QString constructRelativePath(const QString& from, const QString& to)
24 {
25     QUrl fromURL(from);
26     QUrl toURL(to);
27     QUrl root;
28     int upLevels = 0;
29 
30     // Find a common root.
31     root = fromURL;
32     while (root.isValid() && !root.isParentOf(toURL)) {
33         root = KIO::upUrl(root);
34         ++upLevels;
35     }
36 
37     if (!root.isValid()) return to;
38 
39     QString relative;
40     for (; upLevels > 0; --upLevels) {
41         relative += QStringLiteral("../");
42     }
43 
44     relative += QString(to).remove(0, root.path().length());
45     return relative;
46 }
47 }
48 
KompareProcess(DiffSettings * diffSettings,Kompare::DiffMode diffMode,const QString & source,const QString & destination,const QString & dir,Kompare::Mode mode)49 KompareProcess::KompareProcess(DiffSettings* diffSettings, Kompare::DiffMode diffMode, const QString& source, const QString& destination, const QString& dir, Kompare::Mode mode)
50     : KProcess(),
51       m_diffSettings(diffSettings),
52       m_mode(diffMode),
53       m_customString(nullptr),
54       m_textDecoder(nullptr)
55 {
56     // connect the signal that indicates that the process has exited
57     connect(this, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
58             this, &KompareProcess::slotFinished);
59 
60     setEnv(QStringLiteral("LANG"), QStringLiteral("C"));
61 
62     // Write command and options
63     if (m_mode == Kompare::Default)
64     {
65         writeDefaultCommandLine();
66     }
67     else
68     {
69         writeCommandLine();
70     }
71 
72     if (!dir.isEmpty()) {
73         setWorkingDirectory(dir);
74     }
75 
76     // Write file names
77     *this << QStringLiteral("--");
78 
79     //Add the option for diff to read from stdin(QIODevice::write), and save a pointer to the string
80     if (mode == Kompare::ComparingStringFile)
81     {
82         *this << QStringLiteral("-");
83         m_customString = &source;
84     }
85     else
86     {
87         *this << constructRelativePath(dir, source);
88     }
89 
90     if (mode == Kompare::ComparingFileString)
91     {
92         *this << QStringLiteral("-");
93         m_customString = &destination;
94     }
95     else
96     {
97         *this << constructRelativePath(dir, destination);
98     }
99 }
100 
writeDefaultCommandLine()101 void KompareProcess::writeDefaultCommandLine()
102 {
103     if (!m_diffSettings || m_diffSettings->m_diffProgram.isEmpty())
104     {
105         *this << QStringLiteral("diff") << QStringLiteral("-dr");
106     }
107     else
108     {
109         *this << m_diffSettings->m_diffProgram << QStringLiteral("-dr");
110     }
111 
112     *this << QStringLiteral("-U") << QString::number(m_diffSettings->m_linesOfContext);
113 }
114 
writeCommandLine()115 void KompareProcess::writeCommandLine()
116 {
117     // load the executable into the KProcess
118     if (m_diffSettings->m_diffProgram.isEmpty())
119     {
120         qCDebug(LIBKOMPAREDIFF2) << "Using the first diff in the path...";
121         *this << QStringLiteral("diff");
122     }
123     else
124     {
125         qCDebug(LIBKOMPAREDIFF2) << "Using a user specified diff, namely: " << m_diffSettings->m_diffProgram;
126         *this << m_diffSettings->m_diffProgram;
127     }
128 
129     switch (m_diffSettings->m_format) {
130     case Kompare::Unified :
131         *this << QStringLiteral("-U") << QString::number(m_diffSettings->m_linesOfContext);
132         break;
133     case Kompare::Context :
134         *this << QStringLiteral("-C") << QString::number(m_diffSettings->m_linesOfContext);
135         break;
136     case Kompare::RCS :
137         *this << QStringLiteral("-n");
138         break;
139     case Kompare::Ed :
140         *this << QStringLiteral("-e");
141         break;
142     case Kompare::SideBySide:
143         *this << QStringLiteral("-y");
144         break;
145     case Kompare::Normal :
146     case Kompare::UnknownFormat :
147     default:
148         break;
149     }
150 
151     if (m_diffSettings->m_largeFiles
152 // default diff does not have -H on OpenBSD
153 // so don't pass this option unless the user overrode the default program
154 #if defined(__OpenBSD__)
155             && !m_diffSettings->m_diffProgram.isEmpty()
156 #endif
157        )
158     {
159         *this << QStringLiteral("-H");
160     }
161 
162     if (m_diffSettings->m_ignoreWhiteSpace)
163     {
164         *this << QStringLiteral("-b");
165     }
166 
167     if (m_diffSettings->m_ignoreAllWhiteSpace)
168     {
169         *this << QStringLiteral("-w");
170     }
171 
172     if (m_diffSettings->m_ignoreEmptyLines)
173     {
174         *this << QStringLiteral("-B");
175     }
176 
177     if (m_diffSettings->m_ignoreChangesDueToTabExpansion)
178     {
179         *this << QStringLiteral("-E");
180     }
181 
182     if (m_diffSettings->m_createSmallerDiff)
183     {
184         *this << QStringLiteral("-d");
185     }
186 
187     if (m_diffSettings->m_ignoreChangesInCase)
188     {
189         *this << QStringLiteral("-i");
190     }
191 
192     if (m_diffSettings->m_ignoreRegExp && !m_diffSettings->m_ignoreRegExpText.isEmpty())
193     {
194         *this << QStringLiteral("-I") << m_diffSettings->m_ignoreRegExpText;
195     }
196 
197     if (m_diffSettings->m_showCFunctionChange)
198     {
199         *this << QStringLiteral("-p");
200     }
201 
202     if (m_diffSettings->m_convertTabsToSpaces)
203     {
204         *this << QStringLiteral("-t");
205     }
206 
207     if (m_diffSettings->m_recursive)
208     {
209         *this << QStringLiteral("-r");
210     }
211 
212     if (m_diffSettings->m_newFiles)
213     {
214         *this << QStringLiteral("-N");
215     }
216 
217 // This option is more trouble than it is worth... please do not ever enable it unless you want really weird crashes
218 //  if ( m_diffSettings->m_allText )
219 //  {
220 //      *this << QStringLiteral("-a");
221 //  }
222 
223     if (m_diffSettings->m_excludeFilePattern)
224     {
225         for (const QString& it : qAsConst(m_diffSettings->m_excludeFilePatternList)) {
226             *this << QStringLiteral("-x") << it;
227         }
228     }
229 
230     if (m_diffSettings->m_excludeFilesFile && !m_diffSettings->m_excludeFilesFileURL.isEmpty())
231     {
232         *this << QStringLiteral("-X") << m_diffSettings->m_excludeFilesFileURL;
233     }
234 }
235 
~KompareProcess()236 KompareProcess::~KompareProcess()
237 {
238     delete m_textDecoder;
239 }
240 
setEncoding(const QString & encoding)241 void KompareProcess::setEncoding(const QString& encoding)
242 {
243     if (!encoding.compare(QLatin1String("default"), Qt::CaseInsensitive))
244     {
245         m_textDecoder = QTextCodec::codecForLocale()->makeDecoder();
246     }
247     else
248     {
249         m_codec = KCharsets::charsets()->codecForName(encoding);
250         if (m_codec)
251             m_textDecoder = m_codec->makeDecoder();
252         else
253         {
254             qCDebug(LIBKOMPAREDIFF2) << "Using locale codec as backup...";
255             m_codec = QTextCodec::codecForLocale();
256             m_textDecoder = m_codec->makeDecoder();
257         }
258     }
259 }
260 
start()261 void KompareProcess::start()
262 {
263 #ifndef NDEBUG
264     QString cmdLine;
265     QStringList program = KProcess::program();
266     QStringList::ConstIterator it = program.constBegin();
267     QStringList::ConstIterator end = program.constEnd();
268     for (; it != end; ++it)
269         cmdLine += QLatin1Char('\"') + (*it) + QLatin1String("\" ");
270     qCDebug(LIBKOMPAREDIFF2) << cmdLine;
271 #endif
272     setOutputChannelMode(SeparateChannels);
273     setNextOpenMode(QIODevice::ReadWrite);
274     KProcess::start();
275 
276     //If we have a string to compare against input it now
277     if (m_customString)
278         write(m_codec->fromUnicode(*m_customString));
279     closeWriteChannel();
280 }
281 
slotFinished(int exitCode,QProcess::ExitStatus exitStatus)282 void KompareProcess::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
283 {
284     // add all output to m_stdout/m_stderr
285     if (m_textDecoder)
286     {
287         m_stdout = m_textDecoder->toUnicode(readAllStandardOutput());
288         m_stderr = m_textDecoder->toUnicode(readAllStandardError());
289     }
290     else
291         qCDebug(LIBKOMPAREDIFF2) << "KompareProcess::slotFinished : No decoder !!!";
292 
293     // exit code of 0: no differences
294     //              1: some differences
295     //              2: error but there may be differences !
296     qCDebug(LIBKOMPAREDIFF2) << "Exited with exit code : " << exitCode;
297     Q_EMIT diffHasFinished(exitStatus == NormalExit && exitCode != 0);
298 }
299 
300 
301