1 /*
2 SPDX-FileCopyrightText: 2011 Vishesh Yadav <vishesh3y@gmail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "hgwrapper.h"
8
9 #include <QApplication>
10 #include <QTextCodec>
11 #include <QUrl>
12 #include <QDebug>
13
14 //TODO: Replace start() with executeCommand functions wherever possible.
15 //FIXME: Add/Remove/Revert argument length limit. Divide the list.
16 //FIXME: Cannot create thread for parent that is in different thread.
17
18 HgWrapper *HgWrapper::m_instance = nullptr;
19
HgWrapper(QObject * parent)20 HgWrapper::HgWrapper(QObject *parent) :
21 QObject(parent)
22 {
23 m_localCodec = QTextCodec::codecForLocale();
24
25 // re-emit QProcess signals
26 connect(&m_process, &QProcess::errorOccurred,
27 this, &HgWrapper::errorOccurred);
28 connect(&m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
29 this, &HgWrapper::finished);
30 connect(&m_process, &QProcess::stateChanged,
31 this, &HgWrapper::stateChanged);
32 connect(&m_process, &QProcess::started,
33 this, &HgWrapper::started);
34
35 connect(&m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
36 this, &HgWrapper::slotOperationCompleted);
37 connect(&m_process, &QProcess::errorOccurred,
38 this, &HgWrapper::slotOperationError);
39
40 }
41
instance()42 HgWrapper *HgWrapper::instance()
43 {
44 if (!m_instance) {
45 m_instance = new HgWrapper;
46 }
47 return m_instance;
48 }
49
freeInstance()50 void HgWrapper::freeInstance()
51 {
52 delete m_instance;
53 m_instance = nullptr;
54 }
55
slotOperationCompleted(int exitCode,QProcess::ExitStatus exitStatus)56 void HgWrapper::slotOperationCompleted(int exitCode,
57 QProcess::ExitStatus exitStatus)
58 {
59 qDebug() << "'hg' Exit Code: " << exitCode << " Exit Status: "
60 << exitStatus;
61 if (m_primaryOperation) {
62 Q_EMIT primaryOperationFinished(exitCode, exitStatus);
63 }
64 }
65
slotOperationError(QProcess::ProcessError error)66 void HgWrapper::slotOperationError(QProcess::ProcessError error)
67 {
68 qDebug() << "Error occurred while executing 'hg' with arguments ";
69 if (m_primaryOperation) {
70 Q_EMIT primaryOperationError(error);
71 }
72 }
73
executeCommand(const QString & hgCommand,const QStringList & arguments,QString & output,bool primaryOperation)74 bool HgWrapper::executeCommand(const QString &hgCommand,
75 const QStringList &arguments,
76 QString &output,
77 bool primaryOperation)
78 {
79 Q_ASSERT(m_process.state() == QProcess::NotRunning);
80
81 executeCommand(hgCommand, arguments, primaryOperation);
82 m_process.waitForFinished();
83 output = QTextCodec::codecForLocale()->toUnicode(m_process.readAllStandardOutput());
84
85 return (m_process.exitStatus() == QProcess::NormalExit &&
86 m_process.exitCode() == 0);
87 }
88
executeCommand(const QString & hgCommand,const QStringList & arguments,bool primaryOperation)89 void HgWrapper::executeCommand(const QString &hgCommand,
90 const QStringList &arguments,
91 bool primaryOperation)
92 {
93 Q_ASSERT(m_process.state() == QProcess::NotRunning);
94
95 m_primaryOperation = primaryOperation;
96 if (m_primaryOperation) {
97 qDebug() << "Primary operation";
98 }
99
100 QStringList args;
101 args << hgCommand;
102 args << arguments;
103 m_process.setWorkingDirectory(m_currentDir);
104 m_process.start(QLatin1String("hg"), args);
105 }
106
executeCommandTillFinished(const QString & hgCommand,const QStringList & arguments,bool primaryOperation)107 bool HgWrapper::executeCommandTillFinished(const QString &hgCommand,
108 const QStringList &arguments,
109 bool primaryOperation)
110 {
111 Q_ASSERT(m_process.state() == QProcess::NotRunning);
112
113 m_primaryOperation = primaryOperation;
114
115 QStringList args;
116 args << hgCommand;
117 args << arguments;
118 m_process.setWorkingDirectory(m_currentDir);
119 m_process.start(QLatin1String("hg"), args);
120 m_process.waitForFinished();
121
122 return (m_process.exitStatus() == QProcess::NormalExit &&
123 m_process.exitCode() == 0);
124 }
125
getBaseDir() const126 QString HgWrapper::getBaseDir() const
127 {
128 return m_hgBaseDir;
129 }
130
getCurrentDir() const131 QString HgWrapper::getCurrentDir() const
132 {
133 return m_currentDir;
134 }
135
updateBaseDir()136 void HgWrapper::updateBaseDir()
137 {
138 m_process.setWorkingDirectory(m_currentDir);
139 m_process.start(QStringLiteral("hg"), QStringList{QStringLiteral("root")});
140 m_process.waitForFinished();
141 m_hgBaseDir = QString(m_process.readAllStandardOutput()).trimmed();
142 }
143
setCurrentDir(const QString & directory)144 void HgWrapper::setCurrentDir(const QString &directory)
145 {
146 m_currentDir = directory;
147 updateBaseDir(); //now get root directory of repository
148 }
149
setBaseAsWorkingDir()150 void HgWrapper::setBaseAsWorkingDir()
151 {
152 m_process.setWorkingDirectory(getBaseDir());
153 }
154
addFiles(const KFileItemList & fileList)155 void HgWrapper::addFiles(const KFileItemList &fileList)
156 {
157 Q_ASSERT(m_process.state() == QProcess::NotRunning);
158
159 QStringList args;
160 args << QLatin1String("add");
161 for (const KFileItem &item : fileList) {
162 args << item.localPath();
163 }
164 m_process.start(QLatin1String("hg"), args);
165 }
166
renameFile(const QString & source,const QString & destination)167 bool HgWrapper::renameFile(const QString &source, const QString &destination)
168 {
169 Q_ASSERT(m_process.state() == QProcess::NotRunning);
170
171 QStringList args;
172 args << source << destination;
173 executeCommand(QLatin1String("rename"), args, true);
174
175 m_process.waitForFinished();
176 return (m_process.exitStatus() == QProcess::NormalExit &&
177 m_process.exitCode() == 0);
178 }
179
removeFiles(const KFileItemList & fileList)180 void HgWrapper::removeFiles(const KFileItemList &fileList)
181 {
182 Q_ASSERT(m_process.state() == QProcess::NotRunning);
183
184 QStringList args;
185 args << QLatin1String("remove");
186 args << QLatin1String("--force");
187 for (const KFileItem &item : fileList) {
188 args << item.localPath();
189 }
190 m_process.start(QLatin1String("hg"), args);
191 }
192
commit(const QString & message,const QStringList & files,bool closeCurrentBranch)193 bool HgWrapper::commit(const QString &message, const QStringList &files,
194 bool closeCurrentBranch)
195 {
196 QStringList args;
197 args << files;
198 args << QLatin1String("-m") << message;
199 if (closeCurrentBranch) {
200 args << "--close-branch";
201 }
202 executeCommand(QLatin1String("commit"), args, true);
203 m_process.waitForFinished();
204 return (m_process.exitCode() == 0 &&
205 m_process.exitStatus() == QProcess::NormalExit);
206 }
207
createBranch(const QString & name)208 bool HgWrapper::createBranch(const QString &name)
209 {
210 QStringList args;
211 args << name;
212 executeCommand(QLatin1String("branch"), args, true);
213 m_process.waitForFinished();
214 return (m_process.exitCode() == 0 &&
215 m_process.exitStatus() == QProcess::NormalExit);
216 }
217
switchBranch(const QString & name)218 bool HgWrapper::switchBranch(const QString &name)
219 {
220 QStringList args;
221 args << QLatin1String("-c") << name;
222 executeCommand(QLatin1String("update"), args, true);
223 m_process.waitForFinished();
224 return (m_process.exitCode() == 0 &&
225 m_process.exitStatus() == QProcess::NormalExit);
226 }
227
createTag(const QString & name)228 bool HgWrapper::createTag(const QString &name)
229 {
230 QStringList args;
231 args << name;
232 executeCommand(QLatin1String("tag"), args, true);
233 m_process.waitForFinished();
234 return (m_process.exitCode() == 0 &&
235 m_process.exitStatus() == QProcess::NormalExit);
236 }
237
revertAll()238 bool HgWrapper::revertAll()
239 {
240 QStringList args;
241 args << "--all";
242 return executeCommandTillFinished(QLatin1String("revert"), args, true);
243 }
244
245
revert(const KFileItemList & fileList)246 bool HgWrapper::revert(const KFileItemList &fileList)
247 {
248 QStringList arguments;
249 for (const KFileItem &item : fileList) {
250 arguments << item.localPath();
251 }
252 return executeCommandTillFinished(QLatin1String("revert"), arguments, true);
253 }
254
rollback(bool dryRun)255 bool HgWrapper::rollback(bool dryRun)
256 {
257 QStringList args;
258 if (dryRun) {
259 args << QLatin1String("-n");
260 }
261 return executeCommandTillFinished(QLatin1String("rollback"), args, true);
262 }
263
switchTag(const QString & name)264 bool HgWrapper::switchTag(const QString &name)
265 {
266 QStringList args;
267 args << QLatin1String("-c") << name;
268 executeCommand(QLatin1String("update"), args, true);
269 m_process.waitForFinished();
270 return (m_process.exitCode() == 0 &&
271 m_process.exitStatus() == QProcess::NormalExit);
272 }
273
274 //TODO: Make it return QStringList.
getParentsOfHead()275 QString HgWrapper::getParentsOfHead()
276 {
277 Q_ASSERT(m_process.state() == QProcess::NotRunning);
278
279 QString output;
280 QStringList args;
281 args << QLatin1String("--template");
282 args << QLatin1String("{rev}:{node|short} ");
283 executeCommand(QLatin1String("parents"), args, output);
284 return output;
285 }
286
getTags()287 QStringList HgWrapper::getTags()
288 {
289 QStringList result;
290 executeCommand(QLatin1String("tags"));
291 while (m_process.waitForReadyRead()) {
292 char buffer[1048];
293 while (m_process.readLine(buffer, sizeof(buffer)) > 0) {
294 result << QString(buffer).split(QRegExp("\\s+"),
295 Qt::SkipEmptyParts).first();
296 }
297 }
298 return result;
299 }
300
getBranches()301 QStringList HgWrapper::getBranches()
302 {
303 QStringList result;
304 executeCommand(QLatin1String("branches"));
305 while (m_process.waitForReadyRead()) {
306 char buffer[1048];
307 while (m_process.readLine(buffer, sizeof(buffer)) > 0) {
308 // 'hg branches' command lists the branches in following format
309 // <branchname> <revision:changeset_hash> [(inactive)]
310 // Extract just the branchname
311 result << QString(buffer).remove(QRegExp("[\\s]+[\\d:a-zA-Z\\(\\)]*"));
312 }
313 }
314 return result;
315 }
316
getItemVersions(QHash<QString,KVersionControlPlugin::ItemVersion> & result)317 void HgWrapper::getItemVersions(QHash<QString, KVersionControlPlugin::ItemVersion> &result)
318 {
319 /*int nTrimOutLeft = m_hgBaseDir.length();
320 QString relativePrefix = m_currentDir.right(m_currentDir.length() -
321 nTrimOutLeft - 1);
322 qDebug() << m_hgBaseDir << " " << relativePrefix;*/
323
324 // Get status of files
325 QStringList args;
326 args << QLatin1String("status");
327 args << QLatin1String("--modified");
328 args << QLatin1String("--added");
329 args << QLatin1String("--removed");
330 args << QLatin1String("--deleted");
331 args << QLatin1String("--unknown");
332 args << QLatin1String("--ignored");
333 m_process.setWorkingDirectory(m_currentDir);
334 m_process.start(QLatin1String("hg"), args);
335 while (m_process.waitForReadyRead()) {
336 char buffer[1024];
337 while (m_process.readLine(buffer, sizeof(buffer)) > 0) {
338 const QString currentLine(QTextCodec::codecForLocale()->toUnicode(buffer).trimmed());
339 char currentStatus = buffer[0];
340 QString currentFile = currentLine.mid(2);
341 KVersionControlPlugin::ItemVersion vs = KVersionControlPlugin::NormalVersion;
342 switch (currentStatus) {
343 case 'A':
344 vs = KVersionControlPlugin::AddedVersion;
345 break;
346 case 'M':
347 vs = KVersionControlPlugin::LocallyModifiedVersion;
348 break;
349 case '?':
350 vs = KVersionControlPlugin::UnversionedVersion;
351 break;
352 case 'R':
353 vs = KVersionControlPlugin::RemovedVersion;
354 break;
355 case 'I':
356 vs = KVersionControlPlugin::IgnoredVersion;
357 break;
358 case 'C':
359 vs = KVersionControlPlugin::NormalVersion;
360 break;
361 case '!':
362 vs = KVersionControlPlugin::MissingVersion;
363 break;
364 }
365 if (vs != KVersionControlPlugin::NormalVersion) {
366 // Get full path to file and insert it to result
367 QUrl url = QUrl::fromLocalFile(m_hgBaseDir);
368 url = url.adjusted(QUrl::StripTrailingSlash);
369 url.setPath(url.path() + '/' + currentFile);
370 QString filePath = url.path();
371 result.insert(filePath, vs);
372 }
373 }
374 }
375 }
376
terminateCurrentProcess()377 void HgWrapper::terminateCurrentProcess()
378 {
379 qDebug() << "terminating";
380 m_process.terminate();
381 }
382
isWorkingDirectoryClean()383 bool HgWrapper::isWorkingDirectoryClean()
384 {
385 QStringList args;
386 args << QLatin1String("--modified");
387 args << QLatin1String("--added");
388 args << QLatin1String("--removed");
389 args << QLatin1String("--deleted");
390
391 QString output;
392 executeCommand(QLatin1String("status"), args, output);
393
394 return output.trimmed().isEmpty();
395 }
396
397
398