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