branchmodel.cpp 21 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
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.
15
16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
18
19
20
21
22
** 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.
23
**
hjk's avatar
hjk committed
24
25
** 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
****************************************************************************/
29

30
31
32
#include "branchmodel.h"
#include "gitclient.h"

33
#include <utils/qtcassert.h>
Tobias Hunger's avatar
Tobias Hunger committed
34
#include <vcsbase/vcsbaseoutputwindow.h>
35
#include <vcsbase/vcsbaseplugin.h>
Tobias Hunger's avatar
Tobias Hunger committed
36

37
#include <QFont>
38
39

namespace Git {
Tobias Hunger's avatar
Tobias Hunger committed
40
41
namespace Internal {

42
43
44
45
46
47
enum RootNodes {
    LocalBranches = 0,
    RemoteBranches = 1,
    Tags = 2
};

Tobias Hunger's avatar
Tobias Hunger committed
48
49
50
// --------------------------------------------------------------------------
// BranchNode:
// --------------------------------------------------------------------------
51

Tobias Hunger's avatar
Tobias Hunger committed
52
class BranchNode
53
{
Tobias Hunger's avatar
Tobias Hunger committed
54
55
public:
    BranchNode() :
56
57
        parent(0),
        name(QLatin1String("<ROOT>"))
Tobias Hunger's avatar
Tobias Hunger committed
58
59
    { }

60
61
    BranchNode(const QString &n, const QString &s = QString(), const QString &t = QString()) :
        parent(0), name(n), sha(s), tracking(t)
Tobias Hunger's avatar
Tobias Hunger committed
62
63
64
65
    { }

    ~BranchNode()
    {
66
67
68
69
        while (!children.isEmpty())
            delete children.first();
        if (parent)
            parent->children.removeAll(this);
Tobias Hunger's avatar
Tobias Hunger committed
70
    }
71

Tobias Hunger's avatar
Tobias Hunger committed
72
    BranchNode *rootNode() const
Tobias Hunger's avatar
Tobias Hunger committed
73
    {
Tobias Hunger's avatar
Tobias Hunger committed
74
        return parent ? parent->rootNode() : const_cast<BranchNode *>(this);
Tobias Hunger's avatar
Tobias Hunger committed
75
    }
76

Tobias Hunger's avatar
Tobias Hunger committed
77
    int count() const
Tobias Hunger's avatar
Tobias Hunger committed
78
79
80
81
    {
        return children.count();
    }

Tobias Hunger's avatar
Tobias Hunger committed
82
    bool isLeaf() const
Tobias Hunger's avatar
Tobias Hunger committed
83
    {
84
        return children.isEmpty() && parent && parent->parent;
Tobias Hunger's avatar
Tobias Hunger committed
85
86
    }

Tobias Hunger's avatar
Tobias Hunger committed
87
    bool childOf(BranchNode *node) const
Tobias Hunger's avatar
Tobias Hunger committed
88
89
90
91
92
93
    {
        if (this == node)
            return true;
        return parent ? parent->childOf(node) : false;
    }

94
    bool childOfRoot(RootNodes root) const
Tobias Hunger's avatar
Tobias Hunger committed
95
96
97
98
    {
        BranchNode *rn = rootNode();
        if (rn->isLeaf())
            return false;
99
100
        if (root >= rn->children.count())
            return false;
101
102
103
104
105
106
107
108
109
110
111
        return childOf(rn->children.at(root));
    }

    bool isTag() const
    {
        return childOfRoot(Tags);
    }

    bool isLocal() const
    {
        return childOfRoot(LocalBranches);
Tobias Hunger's avatar
Tobias Hunger committed
112
113
    }

Tobias Hunger's avatar
Tobias Hunger committed
114
    BranchNode *childOfName(const QString &name) const
Tobias Hunger's avatar
Tobias Hunger committed
115
116
117
118
119
120
121
122
    {
        for (int i = 0; i < children.count(); ++i) {
            if (children.at(i)->name == name)
                return children.at(i);
        }
        return 0;
    }

123
    QStringList fullName(bool includePrefix = false) const
Tobias Hunger's avatar
Tobias Hunger committed
124
    {
125
        QTC_ASSERT(isLeaf(), return QStringList());
Tobias Hunger's avatar
Tobias Hunger committed
126
127

        QStringList fn;
Tobias Hunger's avatar
Tobias Hunger committed
128
129
        QList<const BranchNode *> nodes;
        const BranchNode *current = this;
Tobias Hunger's avatar
Tobias Hunger committed
130
131
132
133
134
        while (current->parent) {
            nodes.prepend(current);
            current = current->parent;
        }

135
136
137
        if (includePrefix)
            fn.append(nodes.first()->sha);
        nodes.removeFirst();
Tobias Hunger's avatar
Tobias Hunger committed
138

Tobias Hunger's avatar
Tobias Hunger committed
139
        foreach (const BranchNode *n, nodes)
Tobias Hunger's avatar
Tobias Hunger committed
140
141
142
143
144
            fn.append(n->name);

        return fn;
    }

145
    void insert(const QStringList &path, BranchNode *n)
Tobias Hunger's avatar
Tobias Hunger committed
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
    {
        BranchNode *current = this;
        for (int i = 0; i < path.count(); ++i) {
            BranchNode *c = current->childOfName(path.at(i));
            if (c)
                current = c;
            else
                current = current->append(new BranchNode(path.at(i)));
        }
        current->append(n);
    }

    BranchNode *append(BranchNode *n)
    {
        n->parent = this;
        children.append(n);
        return n;
    }

Tobias Hunger's avatar
Tobias Hunger committed
165
    QStringList childrenNames() const
Tobias Hunger's avatar
Tobias Hunger committed
166
167
168
169
170
171
172
173
    {
        if (children.count() > 0) {
            QStringList names;
            foreach (BranchNode *n, children) {
                names.append(n->childrenNames());
            }
            return names;
        }
174
        return QStringList(fullName().join(QString(QLatin1Char('/'))));
Tobias Hunger's avatar
Tobias Hunger committed
175
    }
176

177
178
179
180
181
    int rowOf(BranchNode *node)
    {
        return children.indexOf(node);
    }

Tobias Hunger's avatar
Tobias Hunger committed
182
183
184
185
186
    BranchNode *parent;
    QList<BranchNode *> children;

    QString name;
    QString sha;
187
    QString tracking;
Tobias Hunger's avatar
Tobias Hunger committed
188
189
190
191
192
193
194
195
196
197
    mutable QString toolTip;
};

// --------------------------------------------------------------------------
// BranchModel:
// --------------------------------------------------------------------------

BranchModel::BranchModel(GitClient *client, QObject *parent) :
    QAbstractItemModel(parent),
    m_client(client),
198
199
    m_rootNode(new BranchNode),
    m_currentBranch(0)
200
{
201
    QTC_CHECK(m_client);
202
203
204
205

    // Abuse the sha field for ref prefix
    m_rootNode->append(new BranchNode(tr("Local Branches"), QLatin1String("refs/heads")));
    m_rootNode->append(new BranchNode(tr("Remote Branches"), QLatin1String("refs/remotes")));
206
207
}

Tobias Hunger's avatar
Tobias Hunger committed
208
BranchModel::~BranchModel()
209
{
Tobias Hunger's avatar
Tobias Hunger committed
210
    delete m_rootNode;
211
212
}

213
QModelIndex BranchModel::index(int row, int column, const QModelIndex &parentIdx) const
214
{
215
216
217
218
219
    if (column != 0)
        return QModelIndex();
    BranchNode *parentNode = indexToNode(parentIdx);

    if (row >= parentNode->count())
Tobias Hunger's avatar
Tobias Hunger committed
220
        return QModelIndex();
221
    return nodeToIndex(parentNode->children.at(row));
222
223
}

Tobias Hunger's avatar
Tobias Hunger committed
224
QModelIndex BranchModel::parent(const QModelIndex &index) const
225
{
226
227
228
229
    if (!index.isValid())
        return QModelIndex();

    BranchNode *node = indexToNode(index);
Tobias Hunger's avatar
Tobias Hunger committed
230
231
    if (node->parent == m_rootNode)
        return QModelIndex();
232
    return nodeToIndex(node->parent);
233
234
}

235
int BranchModel::rowCount(const QModelIndex &parentIdx) const
236
{
237
    if (parentIdx.column() > 0)
Tobias Hunger's avatar
Tobias Hunger committed
238
        return 0;
239
240

    return indexToNode(parentIdx)->count();
241
242
}

Tobias Hunger's avatar
Tobias Hunger committed
243
int BranchModel::columnCount(const QModelIndex &parent) const
244
{
Tobias Hunger's avatar
Tobias Hunger committed
245
246
    Q_UNUSED(parent);
    return 1;
247
248
}

Tobias Hunger's avatar
Tobias Hunger committed
249
QVariant BranchModel::data(const QModelIndex &index, int role) const
250
{
251
252
253
    BranchNode *node = indexToNode(index);
    if (!node)
        return QVariant();
Tobias Hunger's avatar
Tobias Hunger committed
254

255
    switch (role) {
256
257
258
259
260
261
    case Qt::DisplayRole: {
        QString res = node->name;
        if (!node->tracking.isEmpty())
            res += QLatin1String(" [") + node->tracking + QLatin1Char(']');
        return res;
    }
Tobias Hunger's avatar
Tobias Hunger committed
262
263
264
265
266
267
268
269
270
271
272
273
274
    case Qt::EditRole:
        return node->name;
    case Qt::ToolTipRole:
        if (!node->isLeaf())
            return QVariant();
        if (node->toolTip.isEmpty())
            node->toolTip = toolTip(node->sha);
        return node->toolTip;
    case Qt::FontRole:
    {
        QFont font;
        if (!node->isLeaf()) {
            font.setBold(true);
275
        } else if (node == m_currentBranch) {
Tobias Hunger's avatar
Tobias Hunger committed
276
277
278
279
280
281
282
            font.setBold(true);
            font.setUnderline(true);
        }
        return font;
    }
    default:
        return QVariant();
283
284
285
    }
}

Tobias Hunger's avatar
Tobias Hunger committed
286
bool BranchModel::setData(const QModelIndex &index, const QVariant &value, int role)
287
{
Tobias Hunger's avatar
Tobias Hunger committed
288
289
    if (role != Qt::EditRole)
        return false;
290
291
292
    BranchNode *node = indexToNode(index);
    if (!node)
        return false;
Tobias Hunger's avatar
Tobias Hunger committed
293
294
295
296
297
298
299
300
301
302
303

    const QString newName = value.toString();
    if (newName.isEmpty())
        return false;

    if (node->name == newName)
        return true;

    QStringList oldFullName = node->fullName();
    node->name = newName;
    QStringList newFullName = node->fullName();
304

305
306
    QString output;
    QString errorMessage;
Tobias Hunger's avatar
Tobias Hunger committed
307
308
309
310
311
312
    if (!m_client->synchronousBranchCmd(m_workingDirectory,
                                        QStringList() << QLatin1String("-m")
                                                      << oldFullName.last()
                                                      << newFullName.last(),
                                        &output, &errorMessage)) {
        node->name = oldFullName.last();
hjk's avatar
hjk committed
313
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
314
315
316
317
318
        return false;
    }

    emit dataChanged(index, index);
    return true;
319
320
}

Tobias Hunger's avatar
Tobias Hunger committed
321
Qt::ItemFlags BranchModel::flags(const QModelIndex &index) const
322
{
323
324
325
    BranchNode *node = indexToNode(index);
    if (!node)
        return Qt::NoItemFlags;
Tobias Hunger's avatar
Tobias Hunger committed
326
327
328
329
    if (node->isLeaf() && node->isLocal())
        return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
    else
        return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
330
331
}

Tobias Hunger's avatar
Tobias Hunger committed
332
void BranchModel::clear()
333
{
334
335
336
    foreach (BranchNode *root, m_rootNode->children)
        while (root->count())
            delete root->children.takeLast();
337
338
    if (hasTags())
        m_rootNode->children.takeLast();
339
340

    m_currentBranch = 0;
341
342
}

Tobias Hunger's avatar
Tobias Hunger committed
343
bool BranchModel::refresh(const QString &workingDirectory, QString *errorMessage)
344
{
345
346
347
348
    beginResetModel();
    clear();
    if (workingDirectory.isEmpty()) {
        endResetModel();
Tobias Hunger's avatar
Tobias Hunger committed
349
        return false;
350
    }
Tobias Hunger's avatar
Tobias Hunger committed
351

352
    m_currentSha = m_client->synchronousTopRevision(workingDirectory);
353
    QStringList args;
354
    args << QLatin1String("--format=%(objectname)\t%(refname)\t%(upstream:short)\t%(*objectname)");
355
    QString output;
356
    if (!m_client->synchronousForEachRefCmd(workingDirectory, args, &output, errorMessage))
hjk's avatar
hjk committed
357
        VcsBase::VcsBaseOutputWindow::instance()->appendError(*errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
358
359
360
361
362
363

    m_workingDirectory = workingDirectory;
    const QStringList lines = output.split(QLatin1Char('\n'));
    foreach (const QString &l, lines)
        parseOutputLine(l);

364
    if (m_currentBranch) {
365
        if (m_currentBranch->parent == m_rootNode->children.at(LocalBranches))
366
367
368
369
            m_currentBranch = 0;
        setCurrentBranch();
    }

Tobias Hunger's avatar
Tobias Hunger committed
370
    endResetModel();
371

372
373
374
    return true;
}

375
376
377
378
379
380
void BranchModel::setCurrentBranch()
{
    QString currentBranch = m_client->synchronousCurrentLocalBranch(m_workingDirectory);
    if (currentBranch.isEmpty())
        return;

381
    BranchNode *local = m_rootNode->children.at(LocalBranches);
382
383
    int pos = 0;
    for (pos = 0; pos < local->count(); ++pos) {
Orgad Shaneh's avatar
Orgad Shaneh committed
384
        if (local->children.at(pos)->name == currentBranch)
385
386
387
388
            m_currentBranch = local->children[pos];
    }
}

Tobias Hunger's avatar
Tobias Hunger committed
389
void BranchModel::renameBranch(const QString &oldName, const QString &newName)
390
{
Tobias Hunger's avatar
Tobias Hunger committed
391
392
393
394
395
    QString errorMessage;
    QString output;
    if (!m_client->synchronousBranchCmd(m_workingDirectory,
                                        QStringList() << QLatin1String("-m") << oldName << newName,
                                        &output, &errorMessage))
hjk's avatar
hjk committed
396
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
397
398
    else
        refresh(m_workingDirectory, &errorMessage);
399
400
}

401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
void BranchModel::renameTag(const QString &oldName, const QString &newName)
{
    QString errorMessage;
    QString output;
    if (!m_client->synchronousTagCmd(m_workingDirectory, QStringList() << newName << oldName,
                                     &output, &errorMessage)
     || !m_client->synchronousTagCmd(m_workingDirectory,
                                     QStringList() << QLatin1String("-d") << oldName,
                                     &output, &errorMessage)) {
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
    } else {
        refresh(m_workingDirectory, &errorMessage);
    }
}

Tobias Hunger's avatar
Tobias Hunger committed
416
QString BranchModel::workingDirectory() const
417
{
Tobias Hunger's avatar
Tobias Hunger committed
418
    return m_workingDirectory;
419
420
}

Tobias Hunger's avatar
Tobias Hunger committed
421
GitClient *BranchModel::client() const
422
{
Tobias Hunger's avatar
Tobias Hunger committed
423
    return m_client;
424
425
}

Tobias Hunger's avatar
Tobias Hunger committed
426
QModelIndex BranchModel::currentBranch() const
427
{
428
    if (!m_currentBranch)
Tobias Hunger's avatar
Tobias Hunger committed
429
        return QModelIndex();
430
    return nodeToIndex(m_currentBranch);
431
432
}

433
QString BranchModel::fullName(const QModelIndex &idx, bool includePrefix) const
434
{
Tobias Hunger's avatar
Tobias Hunger committed
435
436
    if (!idx.isValid())
        return QString();
437
438
    BranchNode *node = indexToNode(idx);
    if (!node || !node->isLeaf())
Tobias Hunger's avatar
Tobias Hunger committed
439
        return QString();
440
    QStringList path = node->fullName(includePrefix);
441
    return path.join(QString(QLatin1Char('/')));
442
443
}

Tobias Hunger's avatar
Tobias Hunger committed
444
QStringList BranchModel::localBranchNames() const
445
{
446
    if (!m_rootNode || !m_rootNode->count())
Tobias Hunger's avatar
Tobias Hunger committed
447
448
        return QStringList();

449
    return m_rootNode->children.at(LocalBranches)->childrenNames();
450
451
}

Tobias Hunger's avatar
Tobias Hunger committed
452
QString BranchModel::sha(const QModelIndex &idx) const
453
{
Tobias Hunger's avatar
Tobias Hunger committed
454
455
    if (!idx.isValid())
        return QString();
456
    BranchNode *node = indexToNode(idx);
Tobias Hunger's avatar
Tobias Hunger committed
457
458
    return node->sha;
}
459

460
461
462
463
464
bool BranchModel::hasTags() const
{
    return m_rootNode->children.count() > Tags;
}

Tobias Hunger's avatar
Tobias Hunger committed
465
466
467
468
bool BranchModel::isLocal(const QModelIndex &idx) const
{
    if (!idx.isValid())
        return false;
469
    BranchNode *node = indexToNode(idx);
Tobias Hunger's avatar
Tobias Hunger committed
470
    return node->isLocal();
471
472
}

Tobias Hunger's avatar
Tobias Hunger committed
473
bool BranchModel::isLeaf(const QModelIndex &idx) const
474
{
Tobias Hunger's avatar
Tobias Hunger committed
475
476
    if (!idx.isValid())
        return false;
477
    BranchNode *node = indexToNode(idx);
Tobias Hunger's avatar
Tobias Hunger committed
478
    return node->isLeaf();
479
480
}

Orgad Shaneh's avatar
Orgad Shaneh committed
481
482
bool BranchModel::isTag(const QModelIndex &idx) const
{
483
    if (!idx.isValid() || !hasTags())
Orgad Shaneh's avatar
Orgad Shaneh committed
484
485
486
487
        return false;
    return indexToNode(idx)->isTag();
}

Tobias Hunger's avatar
Tobias Hunger committed
488
void BranchModel::removeBranch(const QModelIndex &idx)
489
{
490
    QString branch = fullName(idx);
Tobias Hunger's avatar
Tobias Hunger committed
491
492
493
494
495
496
497
498
499
    if (branch.isEmpty())
        return;

    QString errorMessage;
    QString output;
    QStringList args;

    args << QLatin1String("-D") << branch;
    if (!m_client->synchronousBranchCmd(m_workingDirectory, args, &output, &errorMessage)) {
hjk's avatar
hjk committed
500
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
501
502
        return;
    }
503
504
    removeNode(idx);
}
Tobias Hunger's avatar
Tobias Hunger committed
505

506
507
void BranchModel::removeTag(const QModelIndex &idx)
{
508
    QString tag = fullName(idx);
509
510
511
512
513
514
515
516
517
518
519
    if (tag.isEmpty())
        return;

    QString errorMessage;
    QString output;
    QStringList args;

    args << QLatin1String("-d") << tag;
    if (!m_client->synchronousTagCmd(m_workingDirectory, args, &output, &errorMessage)) {
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
        return;
520
    }
521
    removeNode(idx);
522
523
}

Tobias Hunger's avatar
Tobias Hunger committed
524
void BranchModel::checkoutBranch(const QModelIndex &idx)
525
{
526
    QString branch = fullName(idx, !isLocal(idx));
Tobias Hunger's avatar
Tobias Hunger committed
527
528
529
    if (branch.isEmpty())
        return;

530
531
    // No StashGuard since this function for now is only used with clean working dir.
    // If it is ever used from another place, please add StashGuard here
532
    m_client->synchronousCheckout(m_workingDirectory, branch);
533
534
}

Tobias Hunger's avatar
Tobias Hunger committed
535
bool BranchModel::branchIsMerged(const QModelIndex &idx)
536
{
537
    QString branch = fullName(idx);
Tobias Hunger's avatar
Tobias Hunger committed
538
539
540
541
542
543
544
    if (branch.isEmpty())
        return false;

    QString errorMessage;
    QString output;
    QStringList args;

545
    args << QLatin1String("-a") << QLatin1String("--contains") << sha(idx);
546
    if (!m_client->synchronousBranchCmd(m_workingDirectory, args, &output, &errorMessage))
hjk's avatar
hjk committed
547
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
548

549
    QStringList lines = output.split(QLatin1Char('\n'), QString::SkipEmptyParts);
Tobias Hunger's avatar
Tobias Hunger committed
550
    foreach (const QString &l, lines) {
551
552
553
554
        QString currentBranch = l.mid(2); // remove first letters (those are either
                                          // "  " or "* " depending on whether it is
                                          // the currently checked out branch or not)
        if (currentBranch != branch)
Tobias Hunger's avatar
Tobias Hunger committed
555
556
557
            return true;
    }
    return false;
558
559
}

560
561
562
563
564
565
566
567
568
569
static int positionForName(BranchNode *node, const QString &name)
{
    int pos = 0;
    for (pos = 0; pos < node->count(); ++pos) {
        if (node->children.at(pos)->name >= name)
            break;
    }
    return pos;
}

570
QModelIndex BranchModel::addBranch(const QString &name, bool track, const QModelIndex &startPoint)
571
{
Tobias Hunger's avatar
Tobias Hunger committed
572
573
574
    if (!m_rootNode || !m_rootNode->count())
        return QModelIndex();

575
    const QString trackedBranch = fullName(startPoint);
576
    const QString fullTrackedBranch = fullName(startPoint, true);
577
    QString startSha;
Tobias Hunger's avatar
Tobias Hunger committed
578
579
580
581
582
    QString output;
    QString errorMessage;

    QStringList args;
    args << (track ? QLatin1String("--track") : QLatin1String("--no-track"));
583
    args << name;
584
    if (!fullTrackedBranch.isEmpty()) {
585
        args << fullTrackedBranch;
586
587
588
589
        startSha = sha(startPoint);
    } else {
        startSha = m_client->synchronousTopRevision(m_workingDirectory);
    }
Tobias Hunger's avatar
Tobias Hunger committed
590
591

    if (!m_client->synchronousBranchCmd(m_workingDirectory, args, &output, &errorMessage)) {
hjk's avatar
hjk committed
592
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
593
594
595
        return QModelIndex();
    }

596
    BranchNode *local = m_rootNode->children.at(LocalBranches);
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
    const int slash = name.indexOf(QLatin1Char('/'));
    const QString leafName = slash == -1 ? name : name.mid(slash + 1);
    bool added = false;
    if (slash != -1) {
        const QString nodeName = name.left(slash);
        int pos = positionForName(local, nodeName);
        BranchNode *child = (pos == local->count()) ? 0 : local->children.at(pos);
        if (!child || child->name != nodeName) {
            child = new BranchNode(nodeName);
            beginInsertRows(nodeToIndex(local), pos, pos);
            added = true;
            child->parent = local;
            local->children.insert(pos, child);
        }
        local = child;
Tobias Hunger's avatar
Tobias Hunger committed
612
    }
613
    int pos = positionForName(local, leafName);
614
    BranchNode *newNode = new BranchNode(leafName, startSha, track ? trackedBranch : QString());
615
616
    if (!added)
        beginInsertRows(nodeToIndex(local), pos, pos);
Tobias Hunger's avatar
Tobias Hunger committed
617
618
619
    newNode->parent = local;
    local->children.insert(pos, newNode);
    endInsertRows();
620
    return nodeToIndex(newNode);
621
622
}

Orgad Shaneh's avatar
Orgad Shaneh committed
623
624
625
626
627
628
629
630
631
632
633
634
void BranchModel::setRemoteTracking(const QModelIndex &trackingIndex)
{
    QModelIndex current = currentBranch();
    QTC_ASSERT(current.isValid(), return);
    const QString currentName = fullName(current);
    const QString shortTracking = fullName(trackingIndex);
    const QString tracking = fullName(trackingIndex, true);
    m_client->synchronousSetTrackingBranch(m_workingDirectory, currentName, tracking);
    m_currentBranch->tracking = shortTracking;
    emit dataChanged(current, current);
}

Tobias Hunger's avatar
Tobias Hunger committed
635
636
637
638
639
void BranchModel::parseOutputLine(const QString &line)
{
    if (line.size() < 3)
        return;

640
641
642
643
    QStringList lineParts = line.split(QLatin1Char('\t'));
    const QString shaDeref = lineParts.at(3);
    const QString sha = shaDeref.isEmpty() ? lineParts.at(0) : shaDeref;
    const QString fullName = lineParts.at(1);
Tobias Hunger's avatar
Tobias Hunger committed
644

645
    bool current = (sha == m_currentSha);
646
    bool showTags = m_client->settings()->boolValue(GitSettings::showTagsKey);
Tobias Hunger's avatar
Tobias Hunger committed
647
648

    // insert node into tree:
649
650
    QStringList nameParts = fullName.split(QLatin1Char('/'));
    nameParts.removeFirst(); // remove refs...
Tobias Hunger's avatar
Tobias Hunger committed
651

652
    BranchNode *root = 0;
653
    if (nameParts.first() == QLatin1String("heads")) {
654
        root = m_rootNode->children.at(LocalBranches);
655
    } else if (nameParts.first() == QLatin1String("remotes")) {
656
        root = m_rootNode->children.at(RemoteBranches);
657
658
659
    } else if (showTags && nameParts.first() == QLatin1String("tags")) {
        if (!hasTags()) // Tags is missing, add it
            m_rootNode->append(new BranchNode(tr("Tags"), QLatin1String("refs/tags")));
660
        root = m_rootNode->children.at(Tags);
661
    } else {
662
        return;
663
    }
664

665
666
    nameParts.removeFirst();

667
668
    // limit depth of list. Git basically only ever wants one / and considers the rest as part of
    // the name.
669
670
671
672
673
    while (nameParts.count() > 3) {
        nameParts[2] = nameParts.at(2) + QLatin1Char('/') + nameParts.at(3);
        nameParts.removeAt(3);
    }

674
    const QString name = nameParts.last();
Tobias Hunger's avatar
Tobias Hunger committed
675
676
    nameParts.removeLast();

677
    BranchNode *newNode = new BranchNode(name, sha, lineParts.at(2));
678
    root->insert(nameParts, newNode);
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
    if (current)
        m_currentBranch = newNode;
}

BranchNode *BranchModel::indexToNode(const QModelIndex &index) const
{
    if (index.column() > 0)
        return 0;
    if (!index.isValid())
        return m_rootNode;
    return static_cast<BranchNode *>(index.internalPointer());
}

QModelIndex BranchModel::nodeToIndex(BranchNode *node) const
{
    if (node == m_rootNode)
        return QModelIndex();
    return createIndex(node->parent->rowOf(node), 0, static_cast<void *>(node));
697
}
Tobias Hunger's avatar
Tobias Hunger committed
698

699
700
void BranchModel::removeNode(const QModelIndex &idx)
{
701
702
703
704
705
706
707
708
709
    QModelIndex nodeIndex = idx; // idx is a leaf, so count must be 0.
    BranchNode *node = indexToNode(nodeIndex);
    while (node->count() == 0 && node->parent != m_rootNode) {
        BranchNode *parentNode = node->parent;
        const QModelIndex parentIndex = nodeToIndex(parentNode);
        const int nodeRow = nodeIndex.row();
        beginRemoveRows(parentIndex, nodeRow, nodeRow);
        parentNode->children.removeAt(nodeRow);
        delete node;
710
        endRemoveRows();
711
712
        node = parentNode;
        nodeIndex = parentIndex;
713
714
715
    }
}

Tobias Hunger's avatar
Tobias Hunger committed
716
717
718
719
720
QString BranchModel::toolTip(const QString &sha) const
{
    // Show the sha description excluding diff as toolTip
    QString output;
    QString errorMessage;
721
722
    QStringList arguments(QLatin1String("-n1"));
    arguments << sha;
723
724
    if (!m_client->synchronousLog(m_workingDirectory, arguments, &output, &errorMessage,
                                  VcsBase::VcsBasePlugin::SuppressCommandLogging)) {
Tobias Hunger's avatar
Tobias Hunger committed
725
        return errorMessage;
726
    }
Tobias Hunger's avatar
Tobias Hunger committed
727
    return output;
728
729
}

Tobias Hunger's avatar
Tobias Hunger committed
730
731
732
} // namespace Internal
} // namespace Git