Newer
Older
/****************************************************************************
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
** 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
** 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
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#include "branchmodel.h"
#include "gitclient.h"
#include <QFont>
#include <QRegExp>
#include <QTimer>
namespace Internal {
// --------------------------------------------------------------------------
// BranchNode:
// --------------------------------------------------------------------------
public:
BranchNode() :
parent(0), current(false)
{ }
BranchNode(const QString &n, const QString &s = QString(), bool c = false) :
parent(0), current(c), name(n), sha(s)
{ }
~BranchNode()
{
qDeleteAll(children);
}
return parent ? parent->rootNode() : const_cast<BranchNode *>(this);
{
if (this == node)
return true;
return parent ? parent->childOf(node) : false;
}
{
BranchNode *rn = rootNode();
if (rn->isLeaf())
return false;
return childOf(rn->children.at(0));
}
{
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->name == name)
return children.at(i);
}
return 0;
}
QTC_ASSERT(isLeaf(), return QStringList());
QList<const BranchNode *> nodes;
const BranchNode *current = this;
while (current->parent) {
nodes.prepend(current);
current = current->parent;
}
if (current->children.at(0) == nodes.at(0))
nodes.removeFirst(); // remove local branch designation
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;
}
{
if (children.count() > 0) {
QStringList names;
foreach (BranchNode *n, children) {
names.append(n->childrenNames());
}
return names;
}
return QStringList(fullName().join(QString(QLatin1Char('/'))));
BranchNode *parent;
QList<BranchNode *> children;
bool current;
QString name;
QString sha;
mutable QString toolTip;
};
// --------------------------------------------------------------------------
// BranchModel:
// --------------------------------------------------------------------------
BranchModel::BranchModel(GitClient *client, QObject *parent) :
QAbstractItemModel(parent),
m_client(client),
m_rootNode(new BranchNode)
m_rootNode->append(new BranchNode(tr("Local Branches")));
QModelIndex BranchModel::index(int row, int column, const QModelIndex &parent) const
BranchNode *node = m_rootNode;
if (parent.isValid())
node = static_cast<BranchNode *>(parent.internalPointer());
if (row >= node->count())
return QModelIndex();
return createIndex(row, column, static_cast<void *>(node->children.at(row)));
QModelIndex BranchModel::parent(const QModelIndex &index) const
BranchNode *node = static_cast<BranchNode *>(index.internalPointer());
if (node->parent == m_rootNode)
return QModelIndex();
int row = node->parent->children.indexOf(node);
return createIndex(row, 0, static_cast<void *>(node->parent));
int BranchModel::rowCount(const QModelIndex &parent) const
if (!parent.isValid())
return m_rootNode->count();
if (parent.column() != 0)
return 0;
return static_cast<BranchNode *>(parent.internalPointer())->count();
int BranchModel::columnCount(const QModelIndex &parent) const
QVariant BranchModel::data(const QModelIndex &index, int role) const
BranchNode *node = static_cast<BranchNode *>(index.internalPointer());
case Qt::DisplayRole:
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->current) {
font.setBold(true);
font.setUnderline(true);
}
return font;
}
default:
return QVariant();
bool BranchModel::setData(const QModelIndex &index, const QVariant &value, int role)
if (role != Qt::EditRole)
return false;
BranchNode *node = static_cast<BranchNode *>(index.internalPointer());
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;
if (!m_client->synchronousBranchCmd(m_workingDirectory,
QStringList() << QLatin1String("-m")
<< oldFullName.last()
<< newFullName.last(),
&output, &errorMessage)) {
node->name = oldFullName.last();
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
return false;
}
emit dataChanged(index, index);
return true;
Qt::ItemFlags BranchModel::flags(const QModelIndex &index) const
BranchNode *node = static_cast<BranchNode *>(index.internalPointer());
if (node->isLeaf() && node->isLocal())
return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
else
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
while (m_rootNode->count() > 1) {
BranchNode *n = m_rootNode->children.takeLast();
delete n;
}
BranchNode *locals = m_rootNode->children.at(0);
while (locals->count()) {
BranchNode *n = locals->children.takeLast();
delete n;
}
}
bool BranchModel::refresh(const QString &workingDirectory, QString *errorMessage)
if (workingDirectory.isEmpty())
return false;

Friedemann Kleint
committed
QStringList branchArgs;
branchArgs << QLatin1String(GitClient::noColorOption)
<< QLatin1String("-v") << QLatin1String("-a");
if (!m_client->synchronousBranchCmd(workingDirectory, branchArgs, &output, errorMessage))
VcsBase::VcsBaseOutputWindow::instance()->appendError(*errorMessage);
beginResetModel();
clear();
m_workingDirectory = workingDirectory;
const QStringList lines = output.split(QLatin1Char('\n'));
foreach (const QString &l, lines)
parseOutputLine(l);
endResetModel();
void BranchModel::renameBranch(const QString &oldName, const QString &newName)
QString errorMessage;
QString output;
if (!m_client->synchronousBranchCmd(m_workingDirectory,
QStringList() << QLatin1String("-m") << oldName << newName,
&output, &errorMessage))
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
else
refresh(m_workingDirectory, &errorMessage);
QModelIndex BranchModel::currentBranch() const
if (!m_rootNode || !m_rootNode->count())
return QModelIndex();
BranchNode *localBranches = m_rootNode->children.at(0);
QModelIndex localIdx = index(0, 0, QModelIndex());
for (int i = 0; i < localBranches->count(); ++i) {
if (localBranches->children.at(i)->current)
return index(i, 0, localIdx);
}
return QModelIndex();
QString BranchModel::branchName(const QModelIndex &idx) const
if (!idx.isValid())
return QString();
BranchNode *node = static_cast<BranchNode *>(idx.internalPointer());
if (!node->isLeaf())
return QString();
QStringList path = node->fullName();
return path.join(QString(QLatin1Char('/')));
QStringList BranchModel::localBranchNames() const
if (!m_rootNode || m_rootNode->children.isEmpty())
return QStringList();
return m_rootNode->children.at(0)->childrenNames();
QString BranchModel::sha(const QModelIndex &idx) const
if (!idx.isValid())
return QString();
BranchNode *node = static_cast<BranchNode *>(idx.internalPointer());
return node->sha;
}
bool BranchModel::isLocal(const QModelIndex &idx) const
{
if (!idx.isValid())
return false;
BranchNode *node = static_cast<BranchNode *>(idx.internalPointer());
return node->isLocal();
bool BranchModel::isLeaf(const QModelIndex &idx) const
if (!idx.isValid())
return false;
BranchNode *node = static_cast<BranchNode *>(idx.internalPointer());
return node->isLeaf();
void BranchModel::removeBranch(const QModelIndex &idx)
QString branch = branchName(idx);
if (branch.isEmpty())
return;
QString errorMessage;
QString output;
QStringList args;
args << QLatin1String("-D") << branch;
if (!m_client->synchronousBranchCmd(m_workingDirectory, args, &output, &errorMessage)) {
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
return;
}
QModelIndex parentIdx = parent(idx);
beginRemoveRows(parentIdx, idx.row(), idx.row());
static_cast<BranchNode *>(parentIdx.internalPointer())->children.removeAt(parentIdx.row());
delete static_cast<BranchNode *>(idx.internalPointer());
endRemoveRows();
void BranchModel::checkoutBranch(const QModelIndex &idx)
QString branch = branchName(idx);
if (branch.isEmpty())
return;
QString errorMessage;
switch (m_client->ensureStash(m_workingDirectory, QLatin1String("Branch-Checkout"), 0, &errorMessage)) {
case GitClient::StashUnchanged:
case GitClient::Stashed:
case GitClient::NotStashed:
break;
case GitClient::StashCanceled:
return;
case GitClient::StashFailed:
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
return;
}
if (m_client->synchronousCheckoutBranch(m_workingDirectory, branch, &errorMessage)) {
if (errorMessage.isEmpty()) {
QModelIndex currentIdx = currentBranch();
if (currentIdx.isValid()) {
static_cast<BranchNode *>(currentIdx.internalPointer())->current = false;
emit dataChanged(currentBranch(), currentBranch());
}
static_cast<BranchNode *>(idx.internalPointer())->current = true;
emit dataChanged(idx, idx);
} else {
refresh(m_workingDirectory, &errorMessage); // not sure all went well... better refresh!
}
}
if (!errorMessage.isEmpty())
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
bool BranchModel::branchIsMerged(const QModelIndex &idx)
QString branch = branchName(idx);
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))
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
QStringList lines = output.split(QLatin1Char('\n'), QString::SkipEmptyParts);
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)
QModelIndex BranchModel::addBranch(const QString &branchName, bool track, const QString &startPoint)
if (!m_rootNode || !m_rootNode->count())
return QModelIndex();
QString output;
QString errorMessage;
QStringList args;
args << (track ? QLatin1String("--track") : QLatin1String("--no-track"));
args << branchName;
if (!startPoint.isEmpty())
args << startPoint;
if (!m_client->synchronousBranchCmd(m_workingDirectory, args, &output, &errorMessage)) {
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
return QModelIndex();
}
BranchNode *local = m_rootNode->children.at(0);
int pos = 0;
for (pos = 0; pos < local->count(); ++pos) {
if (local->children.at(pos)->name > branchName)
break;
}
BranchNode *newNode = new BranchNode(branchName);
// find the sha of the new branch:
output = toolTip(branchName); // abuse toolTip to get the data;-)
QStringList lines = output.split(QLatin1Char('\n'));
foreach (const QString &l, lines) {
if (l.startsWith(QLatin1String("commit "))) {
newNode->sha = l.mid(7, 8);
break;
}
}
beginInsertRows(index(0, 0), pos, pos);
newNode->parent = local;
local->children.insert(pos, newNode);
endInsertRows();
return index(pos, 0, index(0, 0));
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
void BranchModel::parseOutputLine(const QString &line)
{
if (line.size() < 3)
return;
bool current = line.startsWith(QLatin1String("* "));
const QString branchInfo = line.mid(2);
if (current && branchInfo.startsWith(QLatin1String("(no branch)")))
return;
QStringList tokens = branchInfo.split(QLatin1Char(' '), QString::SkipEmptyParts);
if (tokens.size() < 2)
return;
QString sha = tokens.at(1);
// insert node into tree:
QStringList nameParts = tokens.at(0).split(QLatin1Char('/'));
if (nameParts.count() < 1)
return;
QString name = nameParts.last();
nameParts.removeLast();
if (nameParts.isEmpty() || nameParts.at(0) != QLatin1String("remotes")) {
// local branch:
while (nameParts.count() > 2)
nameParts[1] = nameParts.at(1) + QLatin1Char('/') + nameParts.at(2);
m_rootNode->children.at(0)->insert(nameParts, new BranchNode(name, sha, current));
} else {
// remote branch:
nameParts.removeFirst(); // remove "remotes"
while (nameParts.count() > 2)
nameParts[1] = nameParts.at(1) + QLatin1Char('/') + nameParts.at(2);
m_rootNode->insert(nameParts, new BranchNode(name, sha, current));
}
QString BranchModel::toolTip(const QString &sha) const
{
// Show the sha description excluding diff as toolTip
QString output;
QString errorMessage;
if (!m_client->synchronousShow(m_workingDirectory, sha, &output, &errorMessage))
return errorMessage;
// Remove 'diff' output
const int diffPos = output.indexOf(QLatin1String("\ndiff --"));
if (diffPos != -1)
output.remove(diffPos, output.size() - diffPos);
return output;
} // namespace Internal
} // namespace Git