Commit defc2708 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Fixes: Start a git branch dialog.

parent 12bcc113
#include "branchdialog.h"
#include "branchmodel.h"
#include "gitclient.h"
#include "ui_branchdialog.h"
#include <QtGui/QItemSelectionModel>
#include <QtGui/QPushButton>
#include <QtGui/QMessageBox>
// Single selection helper
static inline int selectedRow(const QAbstractItemView *listView)
{
const QModelIndexList indexList = listView->selectionModel()->selectedIndexes();
if (indexList.size() == 1)
return indexList.front().row();
return -1;
}
namespace Git {
namespace Internal {
BranchDialog::BranchDialog(QWidget *parent) :
QDialog(parent),
m_client(0),
m_ui(new Ui::BranchDialog),
m_checkoutButton(0),
m_deleteButton(0),
m_localModel(0),
m_remoteModel(0)
{
m_ui->setupUi(this);
m_checkoutButton = m_ui->buttonBox->addButton(tr("Checkout"), QDialogButtonBox::AcceptRole);
connect(m_checkoutButton, SIGNAL(clicked()), this, SLOT(slotCheckoutSelectedBranch()));
m_deleteButton = m_ui->buttonBox->addButton(tr("Delete"), QDialogButtonBox::ActionRole);
connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(slotDeleteSelectedBranch()));
connect(m_ui->localBranchListView, SIGNAL(doubleClicked(QModelIndex)), this,
SLOT(slotLocalBranchActivated()));
}
BranchDialog::~BranchDialog()
{
delete m_ui;
}
bool BranchDialog::init(GitClient *client, const QString &workingDirectory, QString *errorMessage)
{
// Find repository and populate models.
m_client = client;
m_repoDirectory = GitClient::findRepositoryForDirectory(workingDirectory);
if (m_repoDirectory.isEmpty()) {
*errorMessage = tr("Unable to find the repository directory for '%1'.").arg(workingDirectory);
return false;
}
m_ui->repositoryFieldLabel->setText(m_repoDirectory);
m_localModel = new BranchModel(client, BranchModel::LocalBranches, this);
m_remoteModel = new BranchModel(client, BranchModel::RemoteBranches, this);
if (!m_localModel->refresh(workingDirectory, errorMessage)
|| !m_remoteModel->refresh(workingDirectory, errorMessage))
return false;
m_ui->localBranchListView->setModel(m_localModel);
m_ui->remoteBranchListView->setModel(m_remoteModel);
// Selection model comes into existence only now
connect(m_ui->localBranchListView->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(slotEnableButtons()));
connect(m_ui->remoteBranchListView->selectionModel(),
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(slotEnableButtons()));
slotEnableButtons();
return true;
}
int BranchDialog::selectedLocalBranchIndex() const
{
return selectedRow(m_ui->localBranchListView);
}
int BranchDialog::selectedRemoteBranchIndex() const
{
return selectedRow(m_ui->remoteBranchListView);
}
void BranchDialog::slotEnableButtons()
{
// We can switch to or delete branches that are not current.
const int selectedLocalRow = selectedLocalBranchIndex();
const int currentLocalBranch = m_localModel->currentBranch();
const bool hasSelection = selectedLocalRow != -1;
const bool currentIsNotSelected = hasSelection && selectedLocalRow != currentLocalBranch;
m_checkoutButton->setEnabled(currentIsNotSelected);
m_deleteButton->setEnabled(currentIsNotSelected);
}
bool BranchDialog::ask(const QString &title, const QString &what, bool defaultButton)
{
return QMessageBox::question(this, title, what, QMessageBox::Yes|QMessageBox::No,
defaultButton ? QMessageBox::Yes : QMessageBox::No) == QMessageBox::Yes;
}
/* Prompt to delete a local branch and do so. */
void BranchDialog::slotDeleteSelectedBranch()
{
const int idx = selectedLocalBranchIndex();
if (idx == -1)
return;
const QString name = m_localModel->branchName(idx);
if (!ask(tr("Delete Branch"), tr("Would you like to delete the branch '%1'?").arg(name), true))
return;
QString errorMessage;
bool ok = false;
do {
QString output;
QStringList args(QLatin1String("-D"));
args << name;
if (!m_client->synchronousBranchCmd(m_repoDirectory, args, &output, &errorMessage))
break;
if (!m_localModel->refresh(m_repoDirectory, &errorMessage))
break;
ok = true;
} while (false);
slotEnableButtons();
if (!ok)
QMessageBox::warning(this, tr("Failed to delete branch"), errorMessage);
}
void BranchDialog::slotLocalBranchActivated()
{
if (m_checkoutButton->isEnabled())
m_checkoutButton->animateClick();
}
/* Ask to stash away changes and then close dialog and do an asynchronous
* checkout. */
void BranchDialog::slotCheckoutSelectedBranch()
{
const int idx = selectedLocalBranchIndex();
if (idx == -1)
return;
const QString name = m_localModel->branchName(idx);
QString errorMessage;
switch (m_client->ensureStash(m_repoDirectory, &errorMessage)) {
case GitClient::StashUnchanged:
case GitClient::Stashed:
case GitClient::NotStashed:
break;
case GitClient::StashCanceled:
return;
case GitClient::StashFailed:
QMessageBox::warning(this, tr("Failed to stash"), errorMessage);
return;
}
accept();
m_client->checkoutBranch(m_repoDirectory, name);
}
void BranchDialog::changeEvent(QEvent *e)
{
switch (e->type()) {
case QEvent::LanguageChange:
m_ui->retranslateUi(this);
break;
default:
break;
}
}
} // namespace Internal
} // namespace Git
#ifndef BRANCHDIALOG_H
#define BRANCHDIALOG_H
#include <QtGui/QDialog>
QT_BEGIN_NAMESPACE
class QPushButton;
QT_END_NAMESPACE
namespace Git {
namespace Internal {
namespace Ui {
class BranchDialog;
}
class GitClient;
class BranchModel;
/* Branch dialog: Display a list of local branches at the top
* and remote branches below. Offers to checkout/delete local
* branches.
* TODO: Add new branch (optionally tracking a remote one).
* How to find out that a local branch is a tracking one? */
class BranchDialog : public QDialog {
Q_OBJECT
Q_DISABLE_COPY(BranchDialog)
public:
explicit BranchDialog(QWidget *parent = 0);
bool init(GitClient *client, const QString &workingDirectory, QString *errorMessage);
virtual ~BranchDialog();
protected:
virtual void changeEvent(QEvent *e);
private slots:
void slotEnableButtons();
void slotCheckoutSelectedBranch();
void slotDeleteSelectedBranch();
void slotLocalBranchActivated();
private:
bool ask(const QString &title, const QString &what, bool defaultButton);
int selectedLocalBranchIndex() const;
int selectedRemoteBranchIndex() const;
GitClient *m_client;
Ui::BranchDialog *m_ui;
QPushButton *m_checkoutButton;
QPushButton *m_deleteButton;
BranchModel *m_localModel;
BranchModel *m_remoteModel;
QString m_repoDirectory;
};
} // namespace Internal
} // namespace Git
#endif // BRANCHDIALOG_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Git::Internal::BranchDialog</class>
<widget class="QDialog" name="Git::Internal::BranchDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>544</width>
<height>631</height>
</rect>
</property>
<property name="windowTitle">
<string>Branches</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="infoGroupBox">
<property name="title">
<string>General information</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="repositoryLabel">
<property name="text">
<string>Repository:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="repositoryFieldLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="localBranchGroupBox">
<property name="title">
<string>Branches</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListView" name="localBranchListView"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="remoteBranchGroupBox">
<property name="title">
<string>Remote branches</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListView" name="remoteBranchListView"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Git::Internal::BranchDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>466</x>
<y>614</y>
</hint>
<hint type="destinationlabel">
<x>544</x>
<y>23</y>
</hint>
</hints>
</connection>
</connections>
</ui>
#include "branchmodel.h"
#include "gitclient.h"
#include <QtCore/QDebug>
enum { debug = 0 };
namespace Git {
namespace Internal {
// Parse a branch line: " *name sha description". Return true if it is
// the current one
bool BranchModel::Branch::parse(const QString &lineIn, bool *isCurrent)
{
if (debug)
qDebug() << Q_FUNC_INFO << lineIn;
*isCurrent = lineIn.startsWith(QLatin1String("* "));
if (lineIn.size() < 3)
return false;
const QStringList tokens =lineIn.mid(2).split(QLatin1Char(' '), QString::SkipEmptyParts);
if (tokens.size() < 2)
return false;
name = tokens.at(0);
currentSHA= tokens.at(1);
toolTip.clear();
return true;
}
static inline Qt::ItemFlags typeToModelFlags(BranchModel::Type t)
{
Qt::ItemFlags rc = Qt::ItemIsSelectable|Qt::ItemIsEnabled;
if (t == BranchModel::BranchModel::LocalBranches)
rc |= Qt::ItemIsUserCheckable;
return rc;
}
// --- BranchModel
BranchModel::BranchModel(GitClient *client, Type type, QObject *parent) :
QAbstractListModel(parent),
m_type(type),
m_flags(typeToModelFlags(type)),
m_client(client),
m_currentBranch(-1)
{
}
int BranchModel::currentBranch() const
{
return m_currentBranch;
}
QString BranchModel::branchName(int row) const
{
return m_branches.at(row).name;
}
int BranchModel::rowCount(const QModelIndex & /* parent */) const
{
return m_branches.size();
}
QVariant BranchModel::data(const QModelIndex &index, int role) const
{
const int row = index.row();
switch (role) {
case Qt::DisplayRole:
return branchName(row);
case Qt::ToolTipRole:
if (m_branches.at(row).toolTip.isEmpty())
m_branches.at(row).toolTip = toolTip(m_branches.at(row).currentSHA);
return m_branches.at(row).toolTip;
break;
case Qt::CheckStateRole:
if (m_type == RemoteBranches)
return QVariant();
return row == m_currentBranch ? Qt::Checked : Qt::Unchecked;
default:
break;
}
return QVariant();
}
Qt::ItemFlags BranchModel::flags(const QModelIndex & /*index */) const
{
return m_flags;
}
bool BranchModel::refresh(const QString &workingDirectory, QString *errorMessage)
{
// Run branch command with verbose.
QStringList branchArgs(QLatin1String("-v"));
QString output;
if (m_type == RemoteBranches)
branchArgs.push_back(QLatin1String("-r"));
if (!m_client->synchronousBranchCmd(workingDirectory, branchArgs, &output, errorMessage))
return false;
if (debug)
qDebug() << Q_FUNC_INFO << workingDirectory << output;
// Parse output
m_workingDirectory = workingDirectory;
m_branches.clear();
m_currentBranch = -1;
const QStringList branches = output.split(QLatin1Char('\n'));
const int branchCount = branches.size();
bool isCurrent;
for (int b = 0; b < branchCount; b++) {
Branch newBranch;
if (newBranch.parse(branches.at(b), &isCurrent)) {
m_branches.push_back(newBranch);
if (isCurrent)
m_currentBranch = b;
}
}
reset();
return true;
}
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;
}
}
}
#ifndef BRANCHMODEL_H
#define BRANCHMODEL_H
#include <QtCore/QAbstractListModel>
#include <QtCore/QList>
namespace Git {
namespace Internal {
class GitClient;
/* A model to list git branches in a simple list of branch names. Local
* branches will have a read-only checkbox indicating the current one. The
* [delayed] tooltip describes the latest commit. */
class BranchModel : public QAbstractListModel {
public:
enum Type { LocalBranches, RemoteBranches };
explicit BranchModel(GitClient *client,
Type type = LocalBranches,
QObject *parent = 0);
bool refresh(const QString &workingDirectory, QString *errorMessage);
int currentBranch() const;
QString branchName(int row) const;
// QAbstractListModel
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
private:
QString toolTip(const QString &sha) const;
struct Branch {
bool parse(const QString &line, bool *isCurrent);
QString name;
QString currentSHA;
mutable QString toolTip;
};
typedef QList<Branch> BranchList;
const Type m_type;
const Qt::ItemFlags m_flags;
GitClient *m_client;
QString m_workingDirectory;
BranchList m_branches;
int m_currentBranch;
};
}
}
#endif // BRANCHMODEL_H
......@@ -6,7 +6,6 @@ include(../../plugins/texteditor/texteditor.pri)
include(../../plugins/coreplugin/coreplugin.pri)
include(../../plugins/vcsbase/vcsbase.pri)
include(../../libs/utils/utils.pri)
HEADERS += gitplugin.h \
gitconstants.h \
gitoutputwindow.h \
......@@ -19,8 +18,9 @@ HEADERS += gitplugin.h \
gitsubmiteditorwidget.h \
gitsubmiteditor.h \
gitversioncontrol.h \
gitsettings.h
gitsettings.h \
branchdialog.h \
branchmodel.h
SOURCES += gitplugin.cpp \
gitoutputwindow.cpp \
gitclient.cpp \
......@@ -32,8 +32,10 @@ SOURCES += gitplugin.cpp \
gitsubmiteditorwidget.cpp \
gitsubmiteditor.cpp \
gitversioncontrol.cpp \
gitsettings.cpp
gitsettings.cpp \
branchdialog.cpp \
branchmodel.cpp
FORMS += changeselectiondialog.ui \
settingspage.ui \
gitsubmitpanel.ui
gitsubmitpanel.ui \
branchdialog.ui
......@@ -276,6 +276,13 @@ void GitClient::blame(const QString &workingDirectory, const QString &fileName)
executeGit(workingDirectory, arguments, editor);
}
void GitClient::checkoutBranch(const QString &workingDirectory, const QString &branch)
{
QStringList arguments(QLatin1String("checkout"));
arguments << branch;
executeGit(workingDirectory, arguments, 0, true);
}
void GitClient::checkout(const QString &workingDirectory, const QString &fileName)
{
// Passing an empty argument as the file name is very dangereous, since this makes
......@@ -395,6 +402,41 @@ bool GitClient::synchronousStash(const QString &workingDirectory, QString *error
return true;
}
bool GitClient::synchronousBranchCmd(const QString &workingDirectory, QStringList branchArgs,
QString *output, QString *errorMessage)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << branchArgs;
branchArgs.push_front(QLatin1String("branch"));
QByteArray outputText;
QByteArray errorText;
const bool rc = synchronousGit(workingDirectory, branchArgs, &outputText, &errorText);
if (!rc) {
*errorMessage = tr("Unable to run branch command: %1: %2").arg(workingDirectory, QString::fromLocal8Bit(errorText));
return false;
}
*output = QString::fromLocal8Bit(outputText).remove(QLatin1Char('\r'));
return true;
}
bool GitClient::synchronousShow(const QString &workingDirectory, const QString &id,
QString *output, QString *errorMessage)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << id;
QStringList args(QLatin1String("show"));
args << id;
QByteArray outputText;
QByteArray errorText;
const bool rc = synchronousGit(workingDirectory, args, &outputText, &errorText);
if (!rc) {
*errorMessage = tr("Unable to run show: %1: %2").arg(workingDirectory, QString::fromLocal8Bit(errorText));
return false;
}
*output = QString::fromLocal8Bit(outputText).remove(QLatin1Char('\r'));
return true;
}