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