1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "patchtool.h"
27 #include "messagemanager.h"
28 #include "icore.h"
29 
30 #include <utils/environment.h>
31 #include <utils/qtcprocess.h>
32 
33 #include <QDir>
34 #include <QApplication>
35 
36 static const char settingsGroupC[] = "General";
37 static const char patchCommandKeyC[] = "PatchCommand";
38 static const char patchCommandDefaultC[] = "patch";
39 
40 using namespace Utils;
41 
42 namespace Core {
43 
patchCommand()44 QString PatchTool::patchCommand()
45 {
46     QSettings *s = ICore::settings();
47 
48     s->beginGroup(settingsGroupC);
49     const QString command = s->value(patchCommandKeyC, patchCommandDefaultC).toString();
50     s->endGroup();
51 
52     return command;
53 }
54 
setPatchCommand(const QString & newCommand)55 void PatchTool::setPatchCommand(const QString &newCommand)
56 {
57     Utils::QtcSettings *s = ICore::settings();
58     s->beginGroup(QLatin1String(settingsGroupC));
59     s->setValueWithDefault(QLatin1String(patchCommandKeyC), newCommand, QString(patchCommandKeyC));
60     s->endGroup();
61 }
62 
runPatchHelper(const QByteArray & input,const QString & workingDirectory,int strip,bool reverse,bool withCrlf)63 static bool runPatchHelper(const QByteArray &input, const QString &workingDirectory,
64                            int strip, bool reverse, bool withCrlf)
65 {
66     const QString patch = PatchTool::patchCommand();
67     if (patch.isEmpty()) {
68         MessageManager::writeDisrupting(QApplication::translate(
69             "Core::PatchTool",
70             "There is no patch-command configured in the general \"Environment\" settings."));
71         return false;
72     }
73 
74     if (!Utils::FilePath::fromString(patch).exists()
75             && !Utils::Environment::systemEnvironment().searchInPath(patch).exists()) {
76         MessageManager::writeDisrupting(
77             QApplication::translate("Core::PatchTool",
78                                     "The patch-command configured in the general \"Environment\" "
79                                     "settings does not exist."));
80         return false;
81     }
82 
83     QtcProcess patchProcess;
84     if (!workingDirectory.isEmpty())
85         patchProcess.setWorkingDirectory(workingDirectory);
86     Environment env = Environment::systemEnvironment();
87     env.setupEnglishOutput();
88     patchProcess.setEnvironment(env);
89     QStringList args;
90     // Add argument 'apply' when git is used as patch command since git 2.5/Windows
91     // no longer ships patch.exe.
92     if (patch.endsWith(QLatin1String("git"), Qt::CaseInsensitive)
93         || patch.endsWith(QLatin1String("git.exe"), Qt::CaseInsensitive)) {
94         args << QLatin1String("apply");
95     }
96     if (strip >= 0)
97         args << (QLatin1String("-p") + QString::number(strip));
98     if (reverse)
99         args << QLatin1String("-R");
100     if (withCrlf)
101         args << QLatin1String("--binary");
102     MessageManager::writeDisrupting(
103         QApplication::translate("Core::PatchTool", "Running in %1: %2 %3")
104             .arg(QDir::toNativeSeparators(workingDirectory),
105                  QDir::toNativeSeparators(patch),
106                  args.join(QLatin1Char(' '))));
107     patchProcess.setCommand({patch, args});
108     patchProcess.start();
109     if (!patchProcess.waitForStarted()) {
110         MessageManager::writeFlashing(
111             QApplication::translate("Core::PatchTool", "Unable to launch \"%1\": %2")
112                 .arg(patch, patchProcess.errorString()));
113         return false;
114     }
115 
116 
117     patchProcess.write(input);
118     patchProcess.closeWriteChannel();
119 
120     QByteArray stdOut;
121     QByteArray stdErr;
122     if (!patchProcess.readDataFromProcess(30, &stdOut, &stdErr, true)) {
123         patchProcess.stopProcess();
124         MessageManager::writeFlashing(
125             QApplication::translate("Core::PatchTool", "A timeout occurred running \"%1\"")
126                 .arg(patch));
127         return false;
128 
129     }
130     if (!stdOut.isEmpty()) {
131         if (stdOut.contains("(different line endings)") && !withCrlf) {
132             QByteArray crlfInput = input;
133             crlfInput.replace('\n', "\r\n");
134             return runPatchHelper(crlfInput, workingDirectory, strip, reverse, true);
135         } else {
136             MessageManager::writeFlashing(QString::fromLocal8Bit(stdOut));
137         }
138     }
139     if (!stdErr.isEmpty())
140         MessageManager::writeFlashing(QString::fromLocal8Bit(stdErr));
141 
142     if (patchProcess.exitStatus() != QProcess::NormalExit) {
143         MessageManager::writeFlashing(
144             QApplication::translate("Core::PatchTool", "\"%1\" crashed.").arg(patch));
145         return false;
146     }
147     if (patchProcess.exitCode() != 0) {
148         MessageManager::writeFlashing(
149             QApplication::translate("Core::PatchTool", "\"%1\" failed (exit code %2).")
150                 .arg(patch)
151                 .arg(patchProcess.exitCode()));
152         return false;
153     }
154     return true;
155 }
156 
runPatch(const QByteArray & input,const QString & workingDirectory,int strip,bool reverse)157 bool PatchTool::runPatch(const QByteArray &input, const QString &workingDirectory,
158                          int strip, bool reverse)
159 {
160     return runPatchHelper(input, workingDirectory, strip, reverse, false);
161 }
162 
163 } // namespace Core
164