-
Orgad Shaneh authored
Change-Id: Icf278e4969ca98a8081d6a12be08e61670c8dbaa Reviewed-by:
Tobias Hunger <tobias.hunger@nokia.com>
Orgad Shaneh authoredChange-Id: Icf278e4969ca98a8081d6a12be08e61670c8dbaa Reviewed-by:
Tobias Hunger <tobias.hunger@nokia.com>
branchmodel.cpp 17.02 KiB
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
**
** 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
** Nokia at qt-info@nokia.com.
**
**************************************************************************/
#include "branchmodel.h"
#include "gitclient.h"
#include <utils/qtcassert.h>
#include <vcsbase/vcsbaseoutputwindow.h>
#include <QFont>
#include <QRegExp>
#include <QTimer>
namespace Git {
namespace Internal {
// --------------------------------------------------------------------------
// BranchNode:
// --------------------------------------------------------------------------
class 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);
}
BranchNode *rootNode() const
{
return parent ? parent->rootNode() : const_cast<BranchNode *>(this);
}
int count() const
{
return children.count();
}
bool isLeaf() const
{
return children.isEmpty();
}
bool childOf(BranchNode *node) const
{
if (this == node)
return true;
return parent ? parent->childOf(node) : false;
}
bool isLocal() const
{
BranchNode *rn = rootNode();
if (rn->isLeaf())
return false;
return childOf(rn->children.at(0));
}
BranchNode *childOfName(const QString &name) const
{
for (int i = 0; i < children.count(); ++i) {
if (children.at(i)->name == name)
return children.at(i);
}
return 0;
}
QStringList fullName() const
{
QTC_ASSERT(isLeaf(), return QStringList());
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
foreach (const BranchNode *n, nodes)
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;
}
QStringList childrenNames() const
{
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)
{
QTC_CHECK(m_client);
m_rootNode->append(new BranchNode(tr("Local Branches")));
}
BranchModel::~BranchModel()
{
delete m_rootNode;
}
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
{
Q_UNUSED(parent);
return 1;
}
QVariant BranchModel::data(const QModelIndex &index, int role) const
{
BranchNode *node = static_cast<BranchNode *>(index.internalPointer());
switch (role) {
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;
}
void BranchModel::clear()
{
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;
QStringList branchArgs;
branchArgs << QLatin1String(GitClient::noColorOption)
<< QLatin1String("-v") << QLatin1String("-a");
QString output;
if (!m_client->synchronousBranchCmd(workingDirectory, branchArgs, &output, errorMessage)) {
VcsBase::VcsBaseOutputWindow::instance()->appendError(*errorMessage);
return false;
}
beginResetModel();
clear();
m_workingDirectory = workingDirectory;
const QStringList lines = output.split(QLatin1Char('\n'));
foreach (const QString &l, lines)
parseOutputLine(l);
endResetModel();
return true;
}
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);
}
QString BranchModel::workingDirectory() const
{
return m_workingDirectory;
}
GitClient *BranchModel::client() const
{
return m_client;
}
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, &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);
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)
return true;
}
return false;
}
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));
}
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