mercurialclient.cpp 17.3 KB
Newer Older
dt's avatar
dt committed
1
2
/**************************************************************************
**
3
** Copyright (c) 2013 Brian McGillion
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
dt's avatar
dt committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
dt's avatar
dt committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
13
14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
dt's avatar
dt committed
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
23
24
25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
dt's avatar
dt committed
29

30
31
32
#include "mercurialclient.h"
#include "constants.h"

33
#include <vcsbase/command.h>
34
#include <vcsbase/vcsbaseoutputwindow.h>
cerf's avatar
cerf committed
35
#include <vcsbase/vcsbaseplugin.h>
36
37
#include <vcsbase/vcsbaseeditor.h>
#include <vcsbase/vcsbaseeditorparameterwidget.h>
cerf's avatar
cerf committed
38
#include <utils/synchronousprocess.h>
39
#include <utils/fileutils.h>
40
#include <utils/qtcassert.h>
41

42
43
44
45
46
#include <QDir>
#include <QFileInfo>
#include <QTextCodec>
#include <QTextStream>
#include <QVariant>
47

48
49
50
namespace Mercurial {
namespace Internal  {

51
MercurialClient::MercurialClient(MercurialSettings *settings) :
hjk's avatar
hjk committed
52
    VcsBase::VcsBaseClient(settings)
53
{
dt's avatar
dt committed
54
55
}

56
57
MercurialSettings *MercurialClient::settings() const
{
hjk's avatar
hjk committed
58
    return dynamic_cast<MercurialSettings *>(VcsBase::VcsBaseClient::settings());
59
60
}

61
bool MercurialClient::manifestSync(const QString &repository, const QString &relativeFilename)
62
{
63
64
    // This  only works when called from the repo and outputs paths relative to it.
    const QStringList args(QLatin1String("manifest"));
65
66

    QByteArray output;
cerf's avatar
cerf committed
67
    vcsFullySynchronousExec(repository, args, &output);
68
69
    const QDir repositoryDir(repository);
    const QFileInfo needle = QFileInfo(repositoryDir, relativeFilename);
70

71
72
    const QStringList files = QString::fromLocal8Bit(output).split(QLatin1Char('\n'));
    foreach (const QString &fileName, files) {
73
74
        const QFileInfo managedFile(repositoryDir, fileName);
        if (needle == managedFile)
75
76
77
78
79
            return true;
    }
    return false;
}

cerf's avatar
cerf committed
80
81
82
83
//bool MercurialClient::clone(const QString &directory, const QString &url)
bool MercurialClient::synchronousClone(const QString &workingDir,
                                       const QString &srcLocation,
                                       const QString &dstLocation,
84
                                       const QStringList &extraOptions)
85
{
cerf's avatar
cerf committed
86
87
88
89
    Q_UNUSED(workingDir);
    Q_UNUSED(extraOptions);
    QDir workingDirectory(srcLocation);
    QByteArray output;
hjk's avatar
hjk committed
90
91
92
    const unsigned flags = VcsBase::VcsBasePlugin::SshPasswordPrompt |
            VcsBase::VcsBasePlugin::ShowStdOutInLogWindow |
            VcsBase::VcsBasePlugin::ShowSuccessMessage;
93

cerf's avatar
cerf committed
94
95
96
    if (workingDirectory.exists()) {
        // Let's make first init
        QStringList arguments(QLatin1String("init"));
97
        if (!vcsFullySynchronousExec(workingDirectory.path(), arguments, &output))
cerf's avatar
cerf committed
98
            return false;
99

cerf's avatar
cerf committed
100
101
102
103
104
        // Then pull remote repository
        arguments.clear();
        arguments << QLatin1String("pull") << dstLocation;
        const Utils::SynchronousProcessResponse resp1 =
                vcsSynchronousExec(workingDirectory.path(), arguments, flags);
105
        if (resp1.result != Utils::SynchronousProcessResponse::Finished)
cerf's avatar
cerf committed
106
            return false;
107

cerf's avatar
cerf committed
108
        // By now, there is no hgrc file -> create it
109
110
111
        Utils::FileSaver saver(workingDirectory.path() + QLatin1String("/.hg/hgrc"));
        const QString hgrc = QLatin1String("[paths]\ndefault = ") + dstLocation + QLatin1Char('\n');
        saver.write(hgrc.toUtf8());
112
        if (!saver.finalize()) {
hjk's avatar
hjk committed
113
            VcsBase::VcsBaseOutputWindow::instance()->appendError(saver.errorString());
114
115
            return false;
        }
116

cerf's avatar
cerf committed
117
118
119
120
121
122
123
124
125
126
127
128
129
        // And last update repository
        arguments.clear();
        arguments << QLatin1String("update");
        const Utils::SynchronousProcessResponse resp2 =
                vcsSynchronousExec(workingDirectory.path(), arguments, flags);
        return resp2.result == Utils::SynchronousProcessResponse::Finished;
    } else {
        QStringList arguments(QLatin1String("clone"));
        arguments << dstLocation << workingDirectory.dirName();
        workingDirectory.cdUp();
        const Utils::SynchronousProcessResponse resp =
                vcsSynchronousExec(workingDirectory.path(), arguments, flags);
        return resp.result == Utils::SynchronousProcessResponse::Finished;
130
131
132
    }
}

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
bool MercurialClient::synchronousPull(const QString &workingDir, const QString &srcLocation, const QStringList &extraOptions)
{
    QStringList args;
    args << vcsCommandString(PullCommand) << extraOptions << srcLocation;
    // Disable UNIX terminals to suppress SSH prompting
    const unsigned flags =
            VcsBase::VcsBasePlugin::SshPasswordPrompt
            | VcsBase::VcsBasePlugin::ShowStdOutInLogWindow
            | VcsBase::VcsBasePlugin::ShowSuccessMessage;
    const QString binary = settings()->binaryPath();
    const int timeoutSec = settings()->value(settings()->timeoutKey).toInt();

    // cause mercurial doesn`t understand LANG
    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
    env.insert(QLatin1String("LANGUAGE"), QLatin1String("C"));
    const Utils::SynchronousProcessResponse resp = VcsBase::VcsBasePlugin::runVcs(
149
                workingDir, binary, args, timeoutSec * 1000, flags, 0, env);
150
151
152
153
154
155
    const bool ok = resp.result == Utils::SynchronousProcessResponse::Finished;

    parsePullOutput(resp.stdOut.trimmed());
    return ok;
}

156
QString MercurialClient::branchQuerySync(const QString &repositoryRoot)
157
158
{
    QByteArray output;
cerf's avatar
cerf committed
159
    if (vcsFullySynchronousExec(repositoryRoot, QStringList(QLatin1String("branch")), &output))
160
161
        return QTextCodec::codecForLocale()->toUnicode(output).trimmed();

162
    return QLatin1String("Unknown Branch");
163
164
}

165
166
167
static inline QString msgParentRevisionFailed(const QString &workingDirectory,
                                              const QString &revision,
                                              const QString &why)
168
{
169
170
    return MercurialClient::tr("Unable to find parent revisions of %1 in %2: %3").
            arg(revision, QDir::toNativeSeparators(workingDirectory), why);
171
172
173
174
175
176
177
}

static inline QString msgParseParentsOutputFailed(const QString &output)
{
    return MercurialClient::tr("Cannot parse output: %1").arg(output);
}

178
QStringList MercurialClient::parentRevisionsSync(const QString &workingDirectory,
179
                                          const QString &file /* = QString() */,
180
                                          const QString &revision)
181
{
182
    QStringList parents;
183
184
185
186
187
    QStringList args;
    args << QLatin1String("parents") <<  QLatin1String("-r") <<revision;
    if (!file.isEmpty())
        args << file;
    QByteArray outputData;
cerf's avatar
cerf committed
188
    if (!vcsFullySynchronousExec(workingDirectory, args, &outputData))
189
        return QStringList();
190
191
    const QString output = Utils::SynchronousProcess::normalizeNewlines(
                QString::fromLocal8Bit(outputData));
192
193
194
195
196
    /* Looks like: \code
changeset:   0:031a48610fba
user: ...
\endcode   */
    // Obtain first line and split by blank-delimited tokens
hjk's avatar
hjk committed
197
    VcsBase::VcsBaseOutputWindow *outputWindow = VcsBase::VcsBaseOutputWindow::instance();
198
199
200
    const QStringList lines = output.split(QLatin1Char('\n'));
    if (lines.size() < 1) {
        outputWindow->appendSilently(msgParentRevisionFailed(workingDirectory, revision, msgParseParentsOutputFailed(output)));
201
        return QStringList();
202
203
204
205
    }
    QStringList changeSets = lines.front().simplified().split(QLatin1Char(' '));
    if (changeSets.size() < 2) {
        outputWindow->appendSilently(msgParentRevisionFailed(workingDirectory, revision, msgParseParentsOutputFailed(output)));
206
        return QStringList();
207
208
209
210
211
212
213
214
    }
    // Remove revision numbers
    const QChar colon = QLatin1Char(':');
    const QStringList::iterator end = changeSets.end();
    QStringList::iterator it = changeSets.begin();
    for (++it; it != end; ++it) {
        const int colonIndex = it->indexOf(colon);
        if (colonIndex != -1)
215
            parents.push_back(it->mid(colonIndex + 1));
216
    }
217
    return parents;
218
219
220
}

// Describe a change using an optional format
221
QString MercurialClient::shortDescriptionSync(const QString &workingDirectory,
222
                                           const QString &revision,
223
                                           const QString &format)
224
{
225
    QString description;
226
227
228
229
230
    QStringList args;
    args << QLatin1String("log") <<  QLatin1String("-r") <<revision;
    if (!format.isEmpty())
        args << QLatin1String("--template") << format;
    QByteArray outputData;
cerf's avatar
cerf committed
231
    if (!vcsFullySynchronousExec(workingDirectory, args, &outputData))
232
        return revision;
233
    description = Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(outputData));
234
235
236
    if (description.endsWith(QLatin1Char('\n')))
        description.truncate(description.size() - 1);
    return description;
237
238
239
240
241
}

// Default format: "SHA1 (author summmary)"
static const char defaultFormatC[] = "{node} ({author|person} {desc|firstline})";

242
243
QString MercurialClient::shortDescriptionSync(const QString &workingDirectory,
                                           const QString &revision)
244
{
245
    return shortDescriptionSync(workingDirectory, revision, QLatin1String(defaultFormatC));
246
247
}

248
249
250
251
252
253
254
QString MercurialClient::vcsGetRepositoryURL(const QString &directory)
{
    QByteArray output;

    QStringList arguments(QLatin1String("showconfig"));
    arguments << QLatin1String("paths.default");

cerf's avatar
cerf committed
255
256
    if (vcsFullySynchronousExec(directory, arguments, &output))
        return QString::fromLocal8Bit(output);
257
258
259
    return QString();
}

260
void MercurialClient::incoming(const QString &repositoryRoot, const QString &repository)
261
262
{
    QStringList args;
263
    args << QLatin1String("incoming") << QLatin1String("-g") << QLatin1String("-p");
264
265
266
    if (!repository.isEmpty())
        args.append(repository);

267
268
269
270
271
    QString id = repositoryRoot;
    if (!repository.isEmpty()) {
        id += QDir::separator();
        id += repository;
    }
272
273
274

    const QString title = tr("Hg incoming %1").arg(id);

hjk's avatar
hjk committed
275
    VcsBase::VcsBaseEditorWidget *editor = createVcsEditor(Constants::DIFFLOG, title, repositoryRoot,
276
                                                     true, "incoming", id);
hjk's avatar
hjk committed
277
    VcsBase::Command *cmd = createCommand(repository, editor);
278
    enqueueJob(cmd, args);
279
280
}

281
void MercurialClient::outgoing(const QString &repositoryRoot)
282
283
{
    QStringList args;
284
    args << QLatin1String("outgoing") << QLatin1String("-g") << QLatin1String("-p");
285

286
    const QString title = tr("Hg outgoing %1").
cerf's avatar
cerf committed
287
            arg(QDir::toNativeSeparators(repositoryRoot));
288

hjk's avatar
hjk committed
289
    VcsBase::VcsBaseEditorWidget *editor = createVcsEditor(Constants::DIFFLOG, title, repositoryRoot, true,
290
                                                     "outgoing", repositoryRoot);
291

hjk's avatar
hjk committed
292
    VcsBase::Command *cmd = createCommand(repositoryRoot, editor);
293
    enqueueJob(cmd, args);
294
295
}

296
297
298
void MercurialClient::annotate(const QString &workingDir, const QString &file,
                               const QString revision, int lineNumber,
                               const QStringList &extraOptions)
cerf's avatar
cerf committed
299
{
300
301
    QStringList args(extraOptions);
    args << QLatin1String("-u") << QLatin1String("-c") << QLatin1String("-d");
hjk's avatar
hjk committed
302
    VcsBaseClient::annotate(workingDir, file, revision, lineNumber, args);
cerf's avatar
cerf committed
303
}
304

305
306
307
void MercurialClient::commit(const QString &repositoryRoot, const QStringList &files,
                             const QString &commitMessageFile,
                             const QStringList &extraOptions)
cerf's avatar
cerf committed
308
{
309
    QStringList args(extraOptions);
Tobias Hunger's avatar
Tobias Hunger committed
310
    args << QLatin1String("--noninteractive") << QLatin1String("-l") << commitMessageFile << QLatin1String("-A");
hjk's avatar
hjk committed
311
    VcsBaseClient::commit(repositoryRoot, files, commitMessageFile, args);
cerf's avatar
cerf committed
312
}
313

314
315
void MercurialClient::diff(const QString &workingDir, const QStringList &files,
                           const QStringList &extraOptions)
cerf's avatar
cerf committed
316
{
317
318
    QStringList args(extraOptions);
    args << QLatin1String("-g") << QLatin1String("-p") << QLatin1String("-U 8");
hjk's avatar
hjk committed
319
    VcsBaseClient::diff(workingDir, files, args);
cerf's avatar
cerf committed
320
321
}

322
323
void MercurialClient::import(const QString &repositoryRoot, const QStringList &files,
                             const QStringList &extraOptions)
cerf's avatar
cerf committed
324
{
hjk's avatar
hjk committed
325
    VcsBaseClient::import(repositoryRoot, files,
326
                          QStringList(extraOptions) << QLatin1String("--no-commit"));
cerf's avatar
cerf committed
327
}
328

329
330
void MercurialClient::revertAll(const QString &workingDir, const QString &revision,
                                const QStringList &extraOptions)
cerf's avatar
cerf committed
331
{
hjk's avatar
hjk committed
332
    VcsBaseClient::revertAll(workingDir, revision,
333
                             QStringList(extraOptions) << QLatin1String("--all"));
334
335
}

336
337
void MercurialClient::view(const QString &source, const QString &id,
                           const QStringList &extraOptions)
338
{
cerf's avatar
cerf committed
339
    QStringList args;
340
    args << QLatin1String("log") << QLatin1String("-p") << QLatin1String("-g");
hjk's avatar
hjk committed
341
    VcsBaseClient::view(source, id, args << extraOptions);
cerf's avatar
cerf committed
342
}
343

344
QString MercurialClient::findTopLevelForFile(const QFileInfo &file) const
cerf's avatar
cerf committed
345
{
Nikita Baryshnikov's avatar
Nikita Baryshnikov committed
346
    const QString repositoryCheckFile = QLatin1String(Constants::MERCURIALREPO) + QLatin1String("/requires");
347
    return file.isDir() ?
hjk's avatar
hjk committed
348
349
                VcsBase::VcsBasePlugin::findRepositoryForDirectory(file.absoluteFilePath(), repositoryCheckFile) :
                VcsBase::VcsBasePlugin::findRepositoryForDirectory(file.absolutePath(), repositoryCheckFile);
350
351
}

hjk's avatar
hjk committed
352
Core::Id MercurialClient::vcsEditorKind(VcsCommand cmd) const
353
{
hjk's avatar
hjk committed
354
355
356
357
358
359
360
361
362
    switch (cmd) {
    case AnnotateCommand:
        return Constants::ANNOTATELOG;
    case DiffCommand:
        return Constants::DIFFLOG;
    case LogCommand:
        return Constants::FILELOG;
    default:
        return Core::Id();
363
    }
364
365
}

366
QStringList MercurialClient::revisionSpec(const QString &revision) const
367
{
cerf's avatar
cerf committed
368
369
370
371
    QStringList args;
    if (!revision.isEmpty())
        args << QLatin1String("-r") << revision;
    return args;
372
}
373

374
MercurialClient::StatusItem MercurialClient::parseStatusLine(const QString &line) const
375
{
376
    StatusItem item;
cerf's avatar
cerf committed
377
378
379
    if (!line.isEmpty())
    {
        if (line.startsWith(QLatin1Char('M')))
380
            item.flags = QLatin1String("Modified");
cerf's avatar
cerf committed
381
        else if (line.startsWith(QLatin1Char('A')))
382
            item.flags = QLatin1String("Added");
cerf's avatar
cerf committed
383
        else if (line.startsWith(QLatin1Char('R')))
384
            item.flags = QLatin1String("Removed");
cerf's avatar
cerf committed
385
        else if (line.startsWith(QLatin1Char('!')))
386
            item.flags = QLatin1String("Deleted");
cerf's avatar
cerf committed
387
        else if (line.startsWith(QLatin1Char('?')))
388
            item.flags = QLatin1String("Untracked");
cerf's avatar
cerf committed
389
        else
390
            return item;
cerf's avatar
cerf committed
391
392
393

        //the status line should be similar to "M file_with_changes"
        //so just should take the file name part and store it
394
        item.file = line.mid(2);
395
    }
396
    return item;
397
}
398

399
400
401
402
403
404
405
406
407
408
409
410
411
412
void MercurialClient::parsePullOutput(const QString &output)
{
    if (output.endsWith(QLatin1String("no changes found")))
        return;

    if (output.endsWith(QLatin1String("(run 'hg update' to get a working copy)"))) {
        emit needUpdate();
        return;
    }

    if (output.endsWith(QLatin1String("'hg merge' to merge)")))
        emit needMerge();
}

413
414
415
416
417
418
// Collect all parameters required for a diff to be able to associate them
// with a diff editor and re-run the diff with parameters.
struct MercurialDiffParameters
{
    QString workingDir;
    QStringList files;
419
    QStringList extraOptions;
420
421
422
};

// Parameter widget controlling whitespace diff mode, associated with a parameter
hjk's avatar
hjk committed
423
class MercurialDiffParameterWidget : public VcsBase::VcsBaseEditorParameterWidget
424
425
426
{
    Q_OBJECT
public:
427
428
    MercurialDiffParameterWidget(MercurialClient *client,
                                 const MercurialDiffParameters &p, QWidget *parent = 0) :
hjk's avatar
hjk committed
429
        VcsBase::VcsBaseEditorParameterWidget(parent), m_client(client), m_params(p)
430
    {
431
        mapSetting(addToggleButton(QLatin1String("-w"), tr("Ignore Whitespace")),
432
                   client->settings()->boolPointer(MercurialSettings::diffIgnoreWhiteSpaceKey));
433
        mapSetting(addToggleButton(QLatin1String("-B"), tr("Ignore Blank Lines")),
434
                   client->settings()->boolPointer(MercurialSettings::diffIgnoreBlankLinesKey));
435
    }
436

437
438
439
440
    void executeCommand()
    {
        m_client->diff(m_params.workingDir, m_params.files, m_params.extraOptions);
    }
441
442

private:
443
444
    MercurialClient *m_client;
    const MercurialDiffParameters m_params;
445
446
};

hjk's avatar
hjk committed
447
VcsBase::VcsBaseEditorParameterWidget *MercurialClient::createDiffEditor(
448
    const QString &workingDir, const QStringList &files, const QStringList &extraOptions)
449
450
451
452
{
    MercurialDiffParameters parameters;
    parameters.workingDir = workingDir;
    parameters.files = files;
453
    parameters.extraOptions = extraOptions;
454
    return new MercurialDiffParameterWidget(this, parameters);
455
456
}

457
458
} // namespace Internal
} // namespace Mercurial
459
460

#include "mercurialclient.moc"