Skip to content
Snippets Groups Projects
branchmodel.cpp 20.6 KiB
Newer Older
hjk's avatar
hjk committed
/****************************************************************************
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
** Contact: http://www.qt-project.org/legal
hjk's avatar
hjk committed
** This file is part of Qt Creator.
hjk's avatar
hjk committed
** 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.
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
** 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.
hjk's avatar
hjk committed
** 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
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
****************************************************************************/
#include "branchmodel.h"
#include "gitclient.h"

#include <utils/qtcassert.h>
Tobias Hunger's avatar
Tobias Hunger committed
#include <vcsbase/vcsbaseoutputwindow.h>


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

enum RootNodes {
    LocalBranches = 0,
    RemoteBranches = 1,
    Tags = 2
};

Tobias Hunger's avatar
Tobias Hunger committed
// --------------------------------------------------------------------------
// BranchNode:
// --------------------------------------------------------------------------
Tobias Hunger's avatar
Tobias Hunger committed
class BranchNode
Tobias Hunger's avatar
Tobias Hunger committed
public:
    BranchNode() :
        parent(0),
        name(QLatin1String("<ROOT>"))
    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
    { }

    ~BranchNode()
    {
        while (!children.isEmpty())
            delete children.first();
        if (parent)
            parent->children.removeAll(this);
Tobias Hunger's avatar
Tobias Hunger committed
    BranchNode *rootNode() const
Tobias Hunger's avatar
Tobias Hunger committed
        return parent ? parent->rootNode() : const_cast<BranchNode *>(this);
Tobias Hunger's avatar
Tobias Hunger committed
    int count() const
Tobias Hunger's avatar
Tobias Hunger committed
    {
        return children.count();
    }

Tobias Hunger's avatar
Tobias Hunger committed
    bool isLeaf() const
        return children.isEmpty() && parent && parent->parent;
Tobias Hunger's avatar
Tobias Hunger committed
    bool childOf(BranchNode *node) const
Tobias Hunger's avatar
Tobias Hunger committed
    {
        if (this == node)
            return true;
        return parent ? parent->childOf(node) : false;
    }

    bool childOfRoot(RootNodes root) const
Tobias Hunger's avatar
Tobias Hunger committed
    {
        BranchNode *rn = rootNode();
        if (rn->isLeaf())
            return false;
        if (root >= rn->children.count())
            return false;
        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
    BranchNode *childOfName(const QString &name) const
Tobias Hunger's avatar
Tobias Hunger committed
    {
        for (int i = 0; i < children.count(); ++i) {
            if (children.at(i)->name == name)
                return children.at(i);
        }
        return 0;
    }

    QStringList fullName(bool includePrefix = false) const
        QTC_ASSERT(isLeaf(), return QStringList());
Tobias Hunger's avatar
Tobias Hunger committed

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

        if (includePrefix)
            fn.append(nodes.first()->sha);
        nodes.removeFirst();
Tobias Hunger's avatar
Tobias Hunger committed
        foreach (const BranchNode *n, nodes)
Tobias Hunger's avatar
Tobias Hunger committed
            fn.append(n->name);

        return fn;
    }

    void insert(const QStringList path, BranchNode *n)
    {
        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
    QStringList childrenNames() const
Tobias Hunger's avatar
Tobias Hunger committed
    {
        if (children.count() > 0) {
            QStringList names;
            foreach (BranchNode *n, children) {
                names.append(n->childrenNames());
            }
            return names;
        }
        return QStringList(fullName().join(QString(QLatin1Char('/'))));
    int rowOf(BranchNode *node)
    {
        return children.indexOf(node);
    }

Tobias Hunger's avatar
Tobias Hunger committed
    BranchNode *parent;
    QList<BranchNode *> children;

    QString name;
    QString sha;
    QString tracking;
Tobias Hunger's avatar
Tobias Hunger committed
    mutable QString toolTip;
};

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

BranchModel::BranchModel(GitClient *client, QObject *parent) :
    QAbstractItemModel(parent),
    m_client(client),
    m_rootNode(new BranchNode),
    m_currentBranch(0)
    QTC_CHECK(m_client);

    // 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")));
Tobias Hunger's avatar
Tobias Hunger committed
BranchModel::~BranchModel()
Tobias Hunger's avatar
Tobias Hunger committed
    delete m_rootNode;
QModelIndex BranchModel::index(int row, int column, const QModelIndex &parentIdx) const
    if (column != 0)
        return QModelIndex();
    BranchNode *parentNode = indexToNode(parentIdx);

    if (row >= parentNode->count())
Tobias Hunger's avatar
Tobias Hunger committed
        return QModelIndex();
    return nodeToIndex(parentNode->children.at(row));
Tobias Hunger's avatar
Tobias Hunger committed
QModelIndex BranchModel::parent(const QModelIndex &index) const
    if (!index.isValid())
        return QModelIndex();

    BranchNode *node = indexToNode(index);
Tobias Hunger's avatar
Tobias Hunger committed
    if (node->parent == m_rootNode)
        return QModelIndex();
    return nodeToIndex(node->parent);
int BranchModel::rowCount(const QModelIndex &parentIdx) const
    if (parentIdx.column() > 0)
Tobias Hunger's avatar
Tobias Hunger committed
        return 0;

    return indexToNode(parentIdx)->count();
Tobias Hunger's avatar
Tobias Hunger committed
int BranchModel::columnCount(const QModelIndex &parent) const
Tobias Hunger's avatar
Tobias Hunger committed
    Q_UNUSED(parent);
    return 1;
Tobias Hunger's avatar
Tobias Hunger committed
QVariant BranchModel::data(const QModelIndex &index, int role) const
    BranchNode *node = indexToNode(index);
    if (!node)
        return QVariant();
    switch (role) {
    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
    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);
        } else if (node == m_currentBranch) {
Tobias Hunger's avatar
Tobias Hunger committed
            font.setBold(true);
            font.setUnderline(true);
        }
        return font;
    }
    default:
        return QVariant();
Tobias Hunger's avatar
Tobias Hunger committed
bool BranchModel::setData(const QModelIndex &index, const QVariant &value, int role)
Tobias Hunger's avatar
Tobias Hunger committed
    if (role != Qt::EditRole)
        return false;
    BranchNode *node = indexToNode(index);
    if (!node)
        return false;
Tobias Hunger's avatar
Tobias Hunger committed

    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();
    QString output;
    QString errorMessage;
Tobias Hunger's avatar
Tobias Hunger committed
    if (!m_client->synchronousBranchCmd(m_workingDirectory,
                                        QStringList() << QLatin1String("-m")
                                                      << oldFullName.last()
                                                      << newFullName.last(),
                                        &output, &errorMessage)) {
        node->name = oldFullName.last();
hjk's avatar
hjk committed
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
        return false;
    }

    emit dataChanged(index, index);
    return true;
Tobias Hunger's avatar
Tobias Hunger committed
Qt::ItemFlags BranchModel::flags(const QModelIndex &index) const
    BranchNode *node = indexToNode(index);
    if (!node)
        return Qt::NoItemFlags;
Tobias Hunger's avatar
Tobias Hunger committed
    if (node->isLeaf() && node->isLocal())
        return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
    else
        return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
Tobias Hunger's avatar
Tobias Hunger committed
void BranchModel::clear()
    foreach (BranchNode *root, m_rootNode->children)
        while (root->count())
            delete root->children.takeLast();
    if (hasTags())
        m_rootNode->children.takeLast();

    m_currentBranch = 0;
Tobias Hunger's avatar
Tobias Hunger committed
bool BranchModel::refresh(const QString &workingDirectory, QString *errorMessage)
    beginResetModel();
    clear();
    if (workingDirectory.isEmpty()) {
        endResetModel();
Tobias Hunger's avatar
Tobias Hunger committed
        return false;
    m_currentSha = m_client->synchronousTopRevision(workingDirectory);
    QStringList args;
    args << QLatin1String("--format=%(objectname)\t%(refname)\t%(upstream:short)\t%(*objectname)");
    QString output;
    if (!m_client->synchronousForEachRefCmd(workingDirectory, args, &output, errorMessage))
hjk's avatar
hjk committed
        VcsBase::VcsBaseOutputWindow::instance()->appendError(*errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed

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

    if (m_currentBranch) {
        if (m_currentBranch->parent == m_rootNode->children.at(LocalBranches))
            m_currentBranch = 0;
        setCurrentBranch();
    }

Tobias Hunger's avatar
Tobias Hunger committed
    endResetModel();
void BranchModel::setCurrentBranch()
{
    QString currentBranch = m_client->synchronousCurrentLocalBranch(m_workingDirectory);
    if (currentBranch.isEmpty())
        return;

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

Tobias Hunger's avatar
Tobias Hunger committed
void BranchModel::renameBranch(const QString &oldName, const QString &newName)
Tobias Hunger's avatar
Tobias Hunger committed
    QString errorMessage;
    QString output;
    if (!m_client->synchronousBranchCmd(m_workingDirectory,
                                        QStringList() << QLatin1String("-m") << oldName << newName,
                                        &output, &errorMessage))
hjk's avatar
hjk committed
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
    else
        refresh(m_workingDirectory, &errorMessage);
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
QString BranchModel::workingDirectory() const
Tobias Hunger's avatar
Tobias Hunger committed
    return m_workingDirectory;
Tobias Hunger's avatar
Tobias Hunger committed
GitClient *BranchModel::client() const
Tobias Hunger's avatar
Tobias Hunger committed
    return m_client;
Tobias Hunger's avatar
Tobias Hunger committed
QModelIndex BranchModel::currentBranch() const
    if (!m_currentBranch)
Tobias Hunger's avatar
Tobias Hunger committed
        return QModelIndex();
    return nodeToIndex(m_currentBranch);
QString BranchModel::fullName(const QModelIndex &idx, bool includePrefix) const
Tobias Hunger's avatar
Tobias Hunger committed
    if (!idx.isValid())
        return QString();
    BranchNode *node = indexToNode(idx);
    if (!node || !node->isLeaf())
Tobias Hunger's avatar
Tobias Hunger committed
        return QString();
    QStringList path = node->fullName(includePrefix);
    return path.join(QString(QLatin1Char('/')));
Tobias Hunger's avatar
Tobias Hunger committed
QStringList BranchModel::localBranchNames() const
    if (!m_rootNode || !m_rootNode->count())
Tobias Hunger's avatar
Tobias Hunger committed
        return QStringList();

    return m_rootNode->children.at(LocalBranches)->childrenNames();
Tobias Hunger's avatar
Tobias Hunger committed
QString BranchModel::sha(const QModelIndex &idx) const
Tobias Hunger's avatar
Tobias Hunger committed
    if (!idx.isValid())
        return QString();
    BranchNode *node = indexToNode(idx);
Tobias Hunger's avatar
Tobias Hunger committed
    return node->sha;
}
bool BranchModel::hasTags() const
{
    return m_rootNode->children.count() > Tags;
}

Tobias Hunger's avatar
Tobias Hunger committed
bool BranchModel::isLocal(const QModelIndex &idx) const
{
    if (!idx.isValid())
        return false;
    BranchNode *node = indexToNode(idx);
Tobias Hunger's avatar
Tobias Hunger committed
    return node->isLocal();
Tobias Hunger's avatar
Tobias Hunger committed
bool BranchModel::isLeaf(const QModelIndex &idx) const
Tobias Hunger's avatar
Tobias Hunger committed
    if (!idx.isValid())
        return false;
    BranchNode *node = indexToNode(idx);
Tobias Hunger's avatar
Tobias Hunger committed
    return node->isLeaf();
Orgad Shaneh's avatar
Orgad Shaneh committed
bool BranchModel::isTag(const QModelIndex &idx) const
{
    if (!idx.isValid() || !hasTags())
Orgad Shaneh's avatar
Orgad Shaneh committed
        return false;
    return indexToNode(idx)->isTag();
}

Tobias Hunger's avatar
Tobias Hunger committed
void BranchModel::removeBranch(const QModelIndex &idx)
    QString branch = fullName(idx);
Tobias Hunger's avatar
Tobias Hunger committed
    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
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
Tobias Hunger's avatar
Tobias Hunger committed
        return;
    }
    removeNode(idx);
}
void BranchModel::removeTag(const QModelIndex &idx)
{
    QString tag = fullName(idx);
    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;
    removeNode(idx);
Tobias Hunger's avatar
Tobias Hunger committed
void BranchModel::checkoutBranch(const QModelIndex &idx)
    QString branch = fullName(idx, !isLocal(idx));
Tobias Hunger's avatar
Tobias Hunger committed
    if (branch.isEmpty())
        return;

    // 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
    m_client->synchronousCheckout(m_workingDirectory, branch);
Tobias Hunger's avatar
Tobias Hunger committed
bool BranchModel::branchIsMerged(const QModelIndex &idx)
    QString branch = fullName(idx);
Tobias Hunger's avatar
Tobias Hunger committed
    if (branch.isEmpty())
        return false;

    QString errorMessage;
    QString output;
    QStringList args;

    args << QLatin1String("-a") << QLatin1String("--contains") << sha(idx);
    if (!m_client->synchronousBranchCmd(m_workingDirectory, args, &output, &errorMessage))
hjk's avatar
hjk committed
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
    QStringList lines = output.split(QLatin1Char('\n'), QString::SkipEmptyParts);
Tobias Hunger's avatar
Tobias Hunger committed
    foreach (const QString &l, lines) {
        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
            return true;
    }
    return false;
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;
}

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

    const QString trackedBranch = fullName(startPoint);
    const QString fullTrackedBranch = fullName(startPoint, true);
Tobias Hunger's avatar
Tobias Hunger committed
    QString output;
    QString errorMessage;

    QStringList args;
    args << (track ? QLatin1String("--track") : QLatin1String("--no-track"));
    if (!fullTrackedBranch.isEmpty())
        args << fullTrackedBranch;
Tobias Hunger's avatar
Tobias Hunger committed

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

    BranchNode *local = m_rootNode->children.at(LocalBranches);
    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;
    int pos = positionForName(local, leafName);
    BranchNode *newNode = new BranchNode(leafName, sha(startPoint), track ? trackedBranch : QString());
    if (!added)
        beginInsertRows(nodeToIndex(local), pos, pos);
Tobias Hunger's avatar
Tobias Hunger committed
    newNode->parent = local;
    local->children.insert(pos, newNode);
    endInsertRows();
    return nodeToIndex(newNode);
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
void BranchModel::parseOutputLine(const QString &line)
{
    if (line.size() < 3)
        return;

    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);
    bool current = (sha == m_currentSha);
    bool showTags = m_client->settings()->boolValue(GitSettings::showTagsKey);
Tobias Hunger's avatar
Tobias Hunger committed

    // insert node into tree:
    QStringList nameParts = fullName.split(QLatin1Char('/'));
    nameParts.removeFirst(); // remove refs...
    BranchNode *root = 0;
    if (nameParts.first() == QLatin1String("heads")) {
        root = m_rootNode->children.at(LocalBranches);
    } else if (nameParts.first() == QLatin1String("remotes")) {
        root = m_rootNode->children.at(RemoteBranches);
    } else if (showTags && nameParts.first() == QLatin1String("tags")) {
        if (!hasTags()) // Tags is missing, add it
            m_rootNode->append(new BranchNode(tr("Tags"), QLatin1String("refs/tags")));
        root = m_rootNode->children.at(Tags);
    nameParts.removeFirst();

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

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

    BranchNode *newNode = new BranchNode(name, sha, lineParts.at(2));
    root->insert(nameParts, newNode);
    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));
void BranchModel::removeNode(const QModelIndex &idx)
{
    QModelIndex tmp = idx; // tmp is a leaf, so count must be 0.
    while (indexToNode(tmp)->count() == 0) {
        QModelIndex tmpParent = parent(tmp);
        beginRemoveRows(tmpParent, tmp.row(), tmp.row());
        indexToNode(tmpParent)->children.removeAt(tmp.row());
        delete indexToNode(tmp);
        endRemoveRows();
        tmp = tmpParent;
    }
}

Tobias Hunger's avatar
Tobias Hunger committed
QString BranchModel::toolTip(const QString &sha) const
{
    // Show the sha description excluding diff as toolTip
    QString output;
    QString errorMessage;
    QStringList arguments(QLatin1String("-n1"));
    arguments << sha;
    if (!m_client->synchronousLog(m_workingDirectory, arguments, &output, &errorMessage))
Tobias Hunger's avatar
Tobias Hunger committed
        return errorMessage;
    return output;
Tobias Hunger's avatar
Tobias Hunger committed
} // namespace Internal
} // namespace Git