Newer
Older
/**************************************************************************
**
** This file is part of Qt Creator
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** 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, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
**
**************************************************************************/
#include "branchmodel.h"
#include "gitclient.h"
#include <QtGui/QFont>
#include <QtCore/QRegExp>
#include <QtCore/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;
}
{
Q_ASSERT(isLeaf());
QStringList fn;
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('/')));
}
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)
Q_ASSERT(m_client);
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('/'));
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, &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);
return false;
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 << startPoint;
if (!m_client->synchronousBranchCmd(m_workingDirectory, args, &output, &errorMessage)) {
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
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("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));
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
569
570
571
572
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