OSDN Git Service

ed1a1cdf8d96da1a88ba1e6e76e7e72209f40724
[qt-creator-jp/qt-creator-jp.git] / src / plugins / mercurial / mercurialclient.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2009 Brian McGillion
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "mercurialclient.h"
35 #include "constants.h"
36
37 #include <vcsbase/vcsbaseoutputwindow.h>
38 #include <vcsbase/vcsbaseplugin.h>
39 #include <vcsbase/vcsjobrunner.h>
40 #include <utils/synchronousprocess.h>
41
42 #include <QDir>
43 #include <QFileInfo>
44 #include <QTextCodec>
45 #include <QTextStream>
46 #include <QVariant>
47
48 namespace Mercurial {
49 namespace Internal  {
50
51 MercurialClient::MercurialClient(const VCSBase::VCSBaseClientSettings &settings) :
52     VCSBase::VCSBaseClient(settings)
53 {
54 }
55
56 bool MercurialClient::manifestSync(const QString &repository, const QString &relativeFilename)
57 {
58     // This  only works when called from the repo and outputs paths relative to it.
59     const QStringList args(QLatin1String("manifest"));
60
61     QByteArray output;
62     vcsFullySynchronousExec(repository, args, &output);
63     const QDir repositoryDir(repository);
64     const QFileInfo needle = QFileInfo(repositoryDir, relativeFilename);
65
66     const QStringList files = QString::fromLocal8Bit(output).split(QLatin1Char('\n'));
67     foreach (const QString &fileName, files) {
68         const QFileInfo managedFile(repositoryDir, fileName);
69         if (needle == managedFile)
70             return true;
71     }
72     return false;
73 }
74
75 //bool MercurialClient::clone(const QString &directory, const QString &url)
76 bool MercurialClient::synchronousClone(const QString &workingDir,
77                                        const QString &srcLocation,
78                                        const QString &dstLocation,
79                                        const ExtraCommandOptions &extraOptions)
80 {
81     Q_UNUSED(workingDir);
82     Q_UNUSED(extraOptions);
83     QDir workingDirectory(srcLocation);
84     QByteArray output;
85     const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt |
86             VCSBase::VCSBasePlugin::ShowStdOutInLogWindow |
87             VCSBase::VCSBasePlugin::ShowSuccessMessage;
88
89     if (workingDirectory.exists()) {
90         // Let's make first init
91         QStringList arguments(QLatin1String("init"));
92         if (!vcsFullySynchronousExec(workingDirectory.path(), arguments, &output)) {
93             return false;
94         }
95
96         // Then pull remote repository
97         arguments.clear();
98         arguments << QLatin1String("pull") << dstLocation;
99         const Utils::SynchronousProcessResponse resp1 =
100                 vcsSynchronousExec(workingDirectory.path(), arguments, flags);
101         if (resp1.result != Utils::SynchronousProcessResponse::Finished) {
102             return false;
103         }
104
105         // By now, there is no hgrc file -> create it
106         QFile hgrc(workingDirectory.path()+"/.hg/hgrc");
107         hgrc.open(QIODevice::WriteOnly);
108         hgrc.write(QString("[paths]\ndefault = %1\n").arg(dstLocation).toUtf8());
109         hgrc.close();
110
111         // And last update repository
112         arguments.clear();
113         arguments << QLatin1String("update");
114         const Utils::SynchronousProcessResponse resp2 =
115                 vcsSynchronousExec(workingDirectory.path(), arguments, flags);
116         return resp2.result == Utils::SynchronousProcessResponse::Finished;
117     } else {
118         QStringList arguments(QLatin1String("clone"));
119         arguments << dstLocation << workingDirectory.dirName();
120         workingDirectory.cdUp();
121         const Utils::SynchronousProcessResponse resp =
122                 vcsSynchronousExec(workingDirectory.path(), arguments, flags);
123         return resp.result == Utils::SynchronousProcessResponse::Finished;
124     }
125 }
126
127 QString MercurialClient::branchQuerySync(const QString &repositoryRoot)
128 {
129     QByteArray output;
130     if (vcsFullySynchronousExec(repositoryRoot, QStringList(QLatin1String("branch")), &output))
131         return QTextCodec::codecForLocale()->toUnicode(output).trimmed();
132
133     return QLatin1String("Unknown Branch");
134 }
135
136 static inline QString msgParentRevisionFailed(const QString &workingDirectory,
137                                               const QString &revision,
138                                               const QString &why)
139 {
140     return MercurialClient::tr("Unable to find parent revisions of %1 in %2: %3").
141             arg(revision, QDir::toNativeSeparators(workingDirectory), why);
142 }
143
144 static inline QString msgParseParentsOutputFailed(const QString &output)
145 {
146     return MercurialClient::tr("Cannot parse output: %1").arg(output);
147 }
148
149 bool MercurialClient::parentRevisionsSync(const QString &workingDirectory,
150                                           const QString &file /* = QString() */,
151                                           const QString &revision,
152                                           QStringList *parents)
153 {
154     parents->clear();
155     QStringList args;
156     args << QLatin1String("parents") <<  QLatin1String("-r") <<revision;
157     if (!file.isEmpty())
158         args << file;
159     QByteArray outputData;
160     if (!vcsFullySynchronousExec(workingDirectory, args, &outputData))
161         return false;
162     QString output = QString::fromLocal8Bit(outputData);
163     output.remove(QLatin1Char('\r'));
164     /* Looks like: \code
165 changeset:   0:031a48610fba
166 user: ...
167 \endcode   */
168     // Obtain first line and split by blank-delimited tokens
169     VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
170     const QStringList lines = output.split(QLatin1Char('\n'));
171     if (lines.size() < 1) {
172         outputWindow->appendSilently(msgParentRevisionFailed(workingDirectory, revision, msgParseParentsOutputFailed(output)));
173         return false;
174     }
175     QStringList changeSets = lines.front().simplified().split(QLatin1Char(' '));
176     if (changeSets.size() < 2) {
177         outputWindow->appendSilently(msgParentRevisionFailed(workingDirectory, revision, msgParseParentsOutputFailed(output)));
178         return false;
179     }
180     // Remove revision numbers
181     const QChar colon = QLatin1Char(':');
182     const QStringList::iterator end = changeSets.end();
183     QStringList::iterator it = changeSets.begin();
184     for (++it; it != end; ++it) {
185         const int colonIndex = it->indexOf(colon);
186         if (colonIndex != -1)
187             parents->push_back(it->mid(colonIndex + 1));
188     }
189     return true;
190 }
191
192 // Describe a change using an optional format
193 bool MercurialClient::shortDescriptionSync(const QString &workingDirectory,
194                                            const QString &revision,
195                                            const QString &format,
196                                            QString *description)
197 {
198     description->clear();
199     QStringList args;
200     args << QLatin1String("log") <<  QLatin1String("-r") <<revision;
201     if (!format.isEmpty())
202         args << QLatin1String("--template") << format;
203     QByteArray outputData;
204     if (!vcsFullySynchronousExec(workingDirectory, args, &outputData))
205         return false;
206     *description = QString::fromLocal8Bit(outputData);
207     description->remove(QLatin1Char('\r'));
208     if (description->endsWith(QLatin1Char('\n')))
209         description->truncate(description->size() - 1);
210     return true;
211 }
212
213 // Default format: "SHA1 (author summmary)"
214 static const char defaultFormatC[] = "{node} ({author|person} {desc|firstline})";
215
216 bool MercurialClient::shortDescriptionSync(const QString &workingDirectory,
217                                            const QString &revision,
218                                            QString *description)
219 {
220     if (!shortDescriptionSync(workingDirectory, revision, QLatin1String(defaultFormatC), description))
221         return false;
222     description->remove(QLatin1Char('\n'));
223     return true;
224 }
225
226 // Convenience to format a list of changes
227 bool MercurialClient::shortDescriptionsSync(const QString &workingDirectory, const QStringList &revisions,
228                                             QStringList *descriptions)
229 {
230     descriptions->clear();
231     foreach(const QString &revision, revisions) {
232         QString description;
233         if (!shortDescriptionSync(workingDirectory, revision, &description))
234             return false;
235         descriptions->push_back(description);
236     }
237     return true;
238 }
239
240 QString MercurialClient::vcsGetRepositoryURL(const QString &directory)
241 {
242     QByteArray output;
243
244     QStringList arguments(QLatin1String("showconfig"));
245     arguments << QLatin1String("paths.default");
246
247     if (vcsFullySynchronousExec(directory, arguments, &output))
248         return QString::fromLocal8Bit(output);
249     return QString();
250 }
251
252 void MercurialClient::incoming(const QString &repositoryRoot, const QString &repository)
253 {
254     QStringList args;
255     args << QLatin1String("incoming") << QLatin1String("-g") << QLatin1String("-p");
256     if (!repository.isEmpty())
257         args.append(repository);
258
259     QString id = repositoryRoot;
260     if (!repository.isEmpty()) {
261         id += QDir::separator();
262         id += repository;
263     }
264
265     const QString kind = QLatin1String(Constants::DIFFLOG);
266     const QString title = tr("Hg incoming %1").arg(id);
267
268     VCSBase::VCSBaseEditorWidget *editor = createVCSEditor(kind, title, repositoryRoot,
269                                                      true, "incoming", id);
270
271     QSharedPointer<VCSBase::VCSJob> job(new VCSBase::VCSJob(repositoryRoot, args, editor));
272     // Suppress SSH prompting.
273     if (!repository.isEmpty() && VCSBase::VCSBasePlugin::isSshPromptConfigured())
274         job->setUnixTerminalDisabled(true);
275     enqueueJob(job);
276 }
277
278 void MercurialClient::outgoing(const QString &repositoryRoot)
279 {
280     QStringList args;
281     args << QLatin1String("outgoing") << QLatin1String("-g") << QLatin1String("-p");
282
283     const QString kind = QLatin1String(Constants::DIFFLOG);
284     const QString title = tr("Hg outgoing %1").
285             arg(QDir::toNativeSeparators(repositoryRoot));
286
287     VCSBase::VCSBaseEditorWidget *editor = createVCSEditor(kind, title, repositoryRoot, true,
288                                                      "outgoing", repositoryRoot);
289
290     QSharedPointer<VCSBase::VCSJob> job(new VCSBase::VCSJob(repositoryRoot, args, editor));
291     // Suppress SSH prompting
292     job->setUnixTerminalDisabled(VCSBase::VCSBasePlugin::isSshPromptConfigured());
293     enqueueJob(job);
294 }
295
296 QString MercurialClient::findTopLevelForFile(const QFileInfo &file) const
297 {
298     const QString repositoryCheckFile = QLatin1String(Constants::MECURIALREPO) + QLatin1String("/requires");
299     return file.isDir() ?
300                 VCSBase::VCSBasePlugin::findRepositoryForDirectory(file.absoluteFilePath(), repositoryCheckFile) :
301                 VCSBase::VCSBasePlugin::findRepositoryForDirectory(file.absolutePath(), repositoryCheckFile);
302 }
303
304 QString MercurialClient::vcsEditorKind(VCSCommand cmd) const
305 {
306     switch (cmd)
307     {
308     case AnnotateCommand : return QLatin1String(Constants::ANNOTATELOG);
309     case DiffCommand : return QLatin1String(Constants::DIFFLOG);
310     case LogCommand : return QLatin1String(Constants::FILELOG);
311     default : return QLatin1String("");
312     }
313     return QLatin1String("");
314 }
315
316 QStringList MercurialClient::cloneArguments(const QString &srcLocation,
317                                             const QString &dstLocation,
318                                             const ExtraCommandOptions &extraOptions) const
319 {
320     Q_UNUSED(srcLocation);
321     Q_UNUSED(dstLocation);
322     Q_UNUSED(extraOptions);
323     QStringList args;
324     return args;
325 }
326
327 QStringList MercurialClient::pullArguments(const QString &srcLocation,
328                                            const ExtraCommandOptions &extraOptions) const
329 {
330     Q_UNUSED(extraOptions);
331     QStringList args;
332     // Add arguments for common options
333     if (!srcLocation.isEmpty())
334         args << srcLocation;
335     return args;
336 }
337
338 QStringList MercurialClient::pushArguments(const QString &dstLocation,
339                                            const ExtraCommandOptions &extraOptions) const
340 {
341     Q_UNUSED(extraOptions);
342     QStringList args;
343     // Add arguments for common options
344     if (!dstLocation.isEmpty())
345         args << dstLocation;
346     return args;
347 }
348
349 QStringList MercurialClient::commitArguments(const QStringList &files,
350                                              const QString &commitMessageFile,
351                                              const ExtraCommandOptions &extraOptions) const
352 {
353     QStringList args(QLatin1String("--noninteractive"));
354     // Fetch extra options
355     foreach (int iOption, extraOptions.keys())
356     {
357         const QVariant iOptValue = extraOptions[iOption];
358         switch (iOption)
359         {
360         case AuthorCommitOptionId :
361         {
362             Q_ASSERT(iOptValue.canConvert(QVariant::String));
363             const QString committerInfo = iOptValue.toString();
364             if (!committerInfo.isEmpty())
365                 args << QLatin1String("-u") << committerInfo;
366             break;
367         }
368         case AutoAddRemoveCommitOptionId :
369         {
370             Q_ASSERT(iOptValue.canConvert(QVariant::Bool));
371             const bool autoAddRemove = iOptValue.toBool();
372             if (autoAddRemove)
373                 args << QLatin1String("-A");
374             break;
375         }
376         default :
377             Q_ASSERT(false); // Invalid option !
378         }
379     } // end foreach ()
380     // Add arguments for common options
381     args << QLatin1String("-l") << commitMessageFile;
382     args << files;
383     return args;
384 }
385
386 QStringList MercurialClient::importArguments(const QStringList &files) const
387 {
388     QStringList args(QLatin1String("--no-commit"));
389     if (!files.isEmpty())
390         args.append(files);
391     return args;
392 }
393
394 QStringList MercurialClient::updateArguments(const QString &revision) const
395 {
396     QStringList args;
397     if (!revision.isEmpty())
398         args << QLatin1String("-r") << revision;
399     return args;
400 }
401
402 QStringList MercurialClient::revertArguments(const QString &file,
403                                              const QString &revision) const
404 {
405     QStringList args;
406     if (!revision.isEmpty())
407         args << QLatin1String("-r") << revision;
408     if (!file.isEmpty())
409         args << file;
410     return args;
411 }
412
413 QStringList MercurialClient::revertAllArguments(const QString &revision) const
414 {
415     QStringList args;
416     if (!revision.isEmpty())
417         args << QLatin1String("-r") << revision;
418     return args << QLatin1String("--all");
419 }
420
421 QStringList MercurialClient::annotateArguments(const QString &file,
422                                                const QString &revision,
423                                                int /*lineNumber*/) const
424 {
425     QStringList args;
426     args << QLatin1String("-u") << QLatin1String("-c") << QLatin1String("-d");
427     if (!revision.isEmpty())
428         args << QLatin1String("-r") << revision;
429     return args << file;
430 }
431
432 QStringList MercurialClient::diffArguments(const QStringList &files) const
433 {
434     QStringList args;
435     args << QLatin1String("-g") << QLatin1String("-p") << QLatin1String("-U 8");
436     if (!files.isEmpty())
437         args.append(files);
438     return args;
439 }
440
441 QStringList MercurialClient::logArguments(const QStringList &files) const
442 {
443     QStringList args;
444     if (!files.empty())
445         args.append(files);
446     return args;
447 }
448
449 QStringList MercurialClient::statusArguments(const QString &file) const
450 {
451     QStringList args;
452     if (!file.isEmpty())
453         args.append(file);
454     return args;
455 }
456
457 QStringList MercurialClient::viewArguments(const QString &revision) const
458 {
459     QStringList args;
460     args << QLatin1String("log") << QLatin1String("-p") << QLatin1String("-g")
461          << QLatin1String("-r") << revision;
462     return args;
463 }
464
465 QPair<QString, QString> MercurialClient::parseStatusLine(const QString &line) const
466 {
467     QPair<QString, QString> status;
468     if (!line.isEmpty())
469     {
470         if (line.startsWith(QLatin1Char('M')))
471             status.first = QLatin1String("Modified");
472         else if (line.startsWith(QLatin1Char('A')))
473             status.first = QLatin1String("Added");
474         else if (line.startsWith(QLatin1Char('R')))
475             status.first = QLatin1String("Removed");
476         else if (line.startsWith(QLatin1Char('!')))
477             status.first = QLatin1String("Deleted");
478         else if (line.startsWith(QLatin1Char('?')))
479             status.first = QLatin1String("Untracked");
480         else
481             return status;
482
483         //the status line should be similar to "M file_with_changes"
484         //so just should take the file name part and store it
485         status.second = line.mid(2);
486     }
487     return status;
488 }
489
490 } // namespace Internal
491 } // namespace Mercurial