forked from qt-creator/qt-creator
Git: Rework branch dialog
* Make adding new branches more discoverable * Make adding tracking branches more discoverable * Update UI Task-number: QTCREATORBUG-4943 Task-number: QTCREATORBUG-4944 Change-Id: Idcbf5f8321a3bd04c925e33d094bb479788a7d9b Reviewed-on: http://codereview.qt.nokia.com/588 Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com> Reviewed-by: Tobias Hunger <tobias.hunger@nokia.com>
This commit is contained in:
47
src/plugins/git/branchadddialog.cpp
Normal file
47
src/plugins/git/branchadddialog.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "branchadddialog.h"
|
||||
#include "ui_branchadddialog.h"
|
||||
|
||||
namespace Git {
|
||||
namespace Internal {
|
||||
|
||||
BranchAddDialog::BranchAddDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_ui(new Ui::BranchAddDialog)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
}
|
||||
|
||||
BranchAddDialog::~BranchAddDialog()
|
||||
{
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void BranchAddDialog::setBranchName(const QString &n)
|
||||
{
|
||||
m_ui->branchNameEdit->setText(n);
|
||||
m_ui->branchNameEdit->selectAll();
|
||||
}
|
||||
|
||||
QString BranchAddDialog::branchName() const
|
||||
{
|
||||
return m_ui->branchNameEdit->text();
|
||||
}
|
||||
|
||||
void BranchAddDialog::setTrackedBranchName(const QString &name, bool remote)
|
||||
{
|
||||
m_ui->trackingCheckBox->setVisible(true);
|
||||
if (!name.isEmpty())
|
||||
m_ui->trackingCheckBox->setText(remote ? tr("Track remote branch \'%1\'").arg(name) :
|
||||
tr("Track local branch \'%1\'").arg(name));
|
||||
else
|
||||
m_ui->trackingCheckBox->setVisible(false);
|
||||
m_ui->trackingCheckBox->setChecked(remote);
|
||||
}
|
||||
|
||||
bool BranchAddDialog::track()
|
||||
{
|
||||
return m_ui->trackingCheckBox->isVisible() && m_ui->trackingCheckBox->isChecked();
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Git
|
||||
36
src/plugins/git/branchadddialog.h
Normal file
36
src/plugins/git/branchadddialog.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef BRANCHADDDIALOG_H
|
||||
#define BRANCHADDDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Git {
|
||||
namespace Internal {
|
||||
|
||||
|
||||
namespace Ui {
|
||||
class BranchAddDialog;
|
||||
}
|
||||
|
||||
class BranchAddDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BranchAddDialog(QWidget *parent = 0);
|
||||
~BranchAddDialog();
|
||||
|
||||
void setBranchName(const QString &);
|
||||
QString branchName() const;
|
||||
|
||||
void setTrackedBranchName(const QString &name, bool remote);
|
||||
|
||||
bool track();
|
||||
|
||||
private:
|
||||
Ui::BranchAddDialog *m_ui;
|
||||
};
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Git
|
||||
|
||||
#endif // BRANCHADDDIALOG_H
|
||||
81
src/plugins/git/branchadddialog.ui
Normal file
81
src/plugins/git/branchadddialog.ui
Normal file
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Git::Internal::BranchAddDialog</class>
|
||||
<widget class="QDialog" name="Git::Internal::BranchAddDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>134</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="branchNameLabel">
|
||||
<property name="text">
|
||||
<string>Branch Name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="branchNameEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="trackingCheckBox">
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Git::Internal::BranchAddDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Git::Internal::BranchAddDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -31,12 +31,14 @@
|
||||
**************************************************************************/
|
||||
|
||||
#include "branchdialog.h"
|
||||
#include "branchadddialog.h"
|
||||
#include "branchmodel.h"
|
||||
#include "gitclient.h"
|
||||
#include "gitplugin.h"
|
||||
#include "ui_branchdialog.h"
|
||||
#include "stashdialog.h" // Label helpers
|
||||
|
||||
#include <utils/checkablemessagebox.h>
|
||||
#include <vcsbase/vcsbaseoutputwindow.h>
|
||||
|
||||
#include <QtGui/QItemSelectionModel>
|
||||
@@ -45,42 +47,13 @@
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
enum { debug = 0 };
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Helper to select a row. No sooner said then done
|
||||
static inline void selectListRow(QAbstractItemView *iv, int row)
|
||||
{
|
||||
const QModelIndex index = iv->model()->index(row, 0);
|
||||
iv->selectionModel()->select(index, QItemSelectionModel::Select);
|
||||
}
|
||||
|
||||
namespace Git {
|
||||
namespace Internal {
|
||||
|
||||
static inline GitClient *gitClient()
|
||||
{
|
||||
return GitPlugin::instance()->gitClient();
|
||||
}
|
||||
namespace Internal {
|
||||
|
||||
BranchDialog::BranchDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_ui(new Ui::BranchDialog),
|
||||
m_checkoutButton(0),
|
||||
m_diffButton(0),
|
||||
m_logButton(0),
|
||||
m_refreshButton(0),
|
||||
m_deleteButton(0),
|
||||
m_localModel(new LocalBranchModel(gitClient(), this)),
|
||||
m_remoteModel(new RemoteBranchModel(gitClient(), this))
|
||||
m_model(new BranchModel(GitPlugin::instance()->gitClient(), this))
|
||||
{
|
||||
setModal(false);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
@@ -88,273 +61,135 @@ BranchDialog::BranchDialog(QWidget *parent) :
|
||||
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_checkoutButton = m_ui->buttonBox->addButton(tr("Checkout"), QDialogButtonBox::ActionRole);
|
||||
connect(m_checkoutButton, SIGNAL(clicked()), this, SLOT(slotCheckoutSelectedBranch()));
|
||||
connect(m_ui->refreshButton, SIGNAL(clicked()), this, SLOT(refresh()));
|
||||
connect(m_ui->addButton, SIGNAL(clicked()), this, SLOT(add()));
|
||||
connect(m_ui->checkoutButton, SIGNAL(clicked()), this, SLOT(checkout()));
|
||||
connect(m_ui->removeButton, SIGNAL(clicked()), this, SLOT(remove()));
|
||||
connect(m_ui->diffButton, SIGNAL(clicked()), this, SLOT(diff()));
|
||||
connect(m_ui->logButton, SIGNAL(clicked()), this, SLOT(log()));
|
||||
|
||||
m_diffButton = m_ui->buttonBox->addButton(tr("Diff"), QDialogButtonBox::ActionRole);
|
||||
connect(m_diffButton, SIGNAL(clicked()), this, SLOT(slotDiffSelected()));
|
||||
m_ui->branchView->setModel(m_model);
|
||||
|
||||
m_logButton = m_ui->buttonBox->addButton(tr("Log"), QDialogButtonBox::ActionRole);
|
||||
connect(m_logButton, SIGNAL(clicked()), this, SLOT(slotLog()));
|
||||
connect(m_ui->branchView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
||||
this, SLOT(enableButtons()));
|
||||
|
||||
m_refreshButton = m_ui->buttonBox->addButton(tr("Refresh"), QDialogButtonBox::ActionRole);
|
||||
connect(m_refreshButton, SIGNAL(clicked()), this, SLOT(slotRefresh()));
|
||||
|
||||
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()));
|
||||
connect(m_ui->remoteBranchListView, SIGNAL(doubleClicked(QModelIndex)), this,
|
||||
SLOT(slotRemoteBranchActivated(QModelIndex)));
|
||||
|
||||
connect(m_localModel, SIGNAL(newBranchEntered(QString)), this, SLOT(slotCreateLocalBranch(QString)));
|
||||
m_ui->localBranchListView->setModel(m_localModel);
|
||||
m_ui->remoteBranchListView->setModel(m_remoteModel);
|
||||
|
||||
connect(m_ui->localBranchListView->selectionModel(),
|
||||
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
||||
this, SLOT(slotEnableButtons(QItemSelection)));
|
||||
connect(m_ui->remoteBranchListView->selectionModel(),
|
||||
SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
||||
this, SLOT(slotEnableButtons(QItemSelection)));
|
||||
|
||||
slotEnableButtons();
|
||||
enableButtons();
|
||||
}
|
||||
|
||||
BranchDialog::~BranchDialog()
|
||||
{
|
||||
delete m_ui;
|
||||
delete m_model;
|
||||
m_model = 0;
|
||||
}
|
||||
|
||||
void BranchDialog::refresh(const QString &repository, bool force)
|
||||
{
|
||||
if (m_repository == repository && !force)
|
||||
return;
|
||||
// Refresh
|
||||
return;
|
||||
|
||||
m_repository = repository;
|
||||
m_ui->repositoryLabel->setText(StashDialog::msgRepositoryLabel(m_repository));
|
||||
if (m_repository.isEmpty()) {
|
||||
m_localModel->clear();
|
||||
m_remoteModel->clear();
|
||||
} else {
|
||||
QString errorMessage;
|
||||
const bool success = m_localModel->refresh(m_repository, &errorMessage)
|
||||
&& m_remoteModel->refresh(m_repository, &errorMessage);
|
||||
if (!success)
|
||||
VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
|
||||
}
|
||||
slotEnableButtons();
|
||||
QString errorMessage;
|
||||
if (!m_model->refresh(m_repository, &errorMessage))
|
||||
VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
|
||||
|
||||
m_ui->branchView->expandAll();
|
||||
}
|
||||
|
||||
int BranchDialog::selectedLocalBranchIndex() const
|
||||
void BranchDialog::enableButtons()
|
||||
{
|
||||
return selectedRow(m_ui->localBranchListView);
|
||||
QModelIndex idx = selectedIndex();
|
||||
const bool hasSelection = idx.isValid();
|
||||
const bool currentSelected = hasSelection && idx == m_model->currentBranch();
|
||||
const bool isLocal = m_model->isLocal(idx);
|
||||
const bool isLeaf = m_model->isLeaf(idx);
|
||||
|
||||
m_ui->removeButton->setEnabled(hasSelection && !currentSelected && isLocal && isLeaf);
|
||||
m_ui->logButton->setEnabled(hasSelection && isLeaf);
|
||||
m_ui->diffButton->setEnabled(hasSelection && isLeaf);
|
||||
m_ui->checkoutButton->setEnabled(hasSelection && !currentSelected && isLocal && isLeaf);
|
||||
}
|
||||
|
||||
int BranchDialog::selectedRemoteBranchIndex() const
|
||||
{
|
||||
return selectedRow(m_ui->remoteBranchListView);
|
||||
}
|
||||
|
||||
void BranchDialog::slotEnableButtons(const QItemSelection &selected)
|
||||
{
|
||||
if (!selected.indexes().isEmpty()) {
|
||||
if (selected.indexes().at(0).model() == m_localModel)
|
||||
m_ui->remoteBranchListView->clearSelection();
|
||||
else
|
||||
m_ui->localBranchListView->clearSelection();
|
||||
}
|
||||
|
||||
// We can switch to or delete branches that are not current.
|
||||
const int selectedLocalRow = selectedLocalBranchIndex();
|
||||
const bool hasRepository = !m_repository.isEmpty();
|
||||
const bool hasLocalSelection = selectedLocalRow != -1 && !m_localModel->isNewBranchRow(selectedLocalRow);
|
||||
const bool otherLocalSelected = hasLocalSelection && selectedLocalRow != m_localModel->currentBranch();
|
||||
const bool branchSelected = hasLocalSelection || selectedRemoteBranchIndex() != -1;
|
||||
|
||||
m_checkoutButton->setEnabled(otherLocalSelected);
|
||||
m_diffButton->setEnabled(branchSelected);
|
||||
m_logButton->setEnabled(branchSelected);
|
||||
m_deleteButton->setEnabled(otherLocalSelected);
|
||||
m_refreshButton->setEnabled(hasRepository);
|
||||
// Also disable <New Branch> entry of list view
|
||||
m_ui->localBranchListView->setEnabled(hasRepository);
|
||||
m_ui->remoteBranchListView->setEnabled(hasRepository);
|
||||
}
|
||||
|
||||
void BranchDialog::slotRefresh()
|
||||
void BranchDialog::refresh()
|
||||
{
|
||||
refresh(m_repository, true);
|
||||
}
|
||||
|
||||
void BranchDialog::selectLocalBranch(const QString &b)
|
||||
void BranchDialog::add()
|
||||
{
|
||||
// Select the newly created branch
|
||||
const int row = m_localModel->findBranchByName(b);
|
||||
if (row != -1)
|
||||
selectListRow(m_ui->localBranchListView, row);
|
||||
QString trackedBranch = m_model->branchName(selectedIndex());
|
||||
bool isLocal = m_model->isLocal(selectedIndex());
|
||||
if (trackedBranch.isEmpty()) {
|
||||
trackedBranch = m_model->branchName(m_model->currentBranch());
|
||||
isLocal = true;
|
||||
}
|
||||
|
||||
QStringList localNames = m_model->localBranchNames();
|
||||
|
||||
QString suggestedNameBase = trackedBranch.mid(trackedBranch.lastIndexOf(QLatin1Char('/')) + 1);
|
||||
QString suggestedName = suggestedNameBase;
|
||||
int i = 2;
|
||||
while (localNames.contains(suggestedName)) {
|
||||
suggestedName = suggestedNameBase + QString::number(i);
|
||||
++i;
|
||||
}
|
||||
|
||||
BranchAddDialog branchAddDialog;
|
||||
branchAddDialog.setBranchName(suggestedName);
|
||||
branchAddDialog.setTrackedBranchName(trackedBranch, !isLocal);
|
||||
|
||||
if (branchAddDialog.exec() == QDialog::Accepted && m_model) {
|
||||
QModelIndex idx = m_model->addBranch(branchAddDialog.branchName(), branchAddDialog.track(), trackedBranch);
|
||||
m_ui->branchView->selectionModel()->select(idx, QItemSelectionModel::Clear
|
||||
| QItemSelectionModel::Select
|
||||
| QItemSelectionModel::Current);
|
||||
m_ui->branchView->scrollTo(idx);
|
||||
}
|
||||
}
|
||||
|
||||
bool BranchDialog::ask(const QString &title, const QString &what, bool defaultButton)
|
||||
void BranchDialog::checkout()
|
||||
{
|
||||
return QMessageBox::question(this, title, what, QMessageBox::Yes|QMessageBox::No,
|
||||
defaultButton ? QMessageBox::Yes : QMessageBox::No) == QMessageBox::Yes;
|
||||
QModelIndex idx = selectedIndex();
|
||||
Q_ASSERT(m_model->isLocal(idx));
|
||||
|
||||
m_model->checkoutBranch(idx);
|
||||
}
|
||||
|
||||
/* Prompt to delete a local branch and do so. */
|
||||
void BranchDialog::slotDeleteSelectedBranch()
|
||||
void BranchDialog::remove()
|
||||
{
|
||||
const int idx = selectedLocalBranchIndex();
|
||||
if (idx == -1)
|
||||
QModelIndex selected = selectedIndex();
|
||||
Q_ASSERT(selected != m_model->currentBranch()); // otherwise the button would not be enabled!
|
||||
|
||||
QString branchName = m_model->branchName(selected);
|
||||
if (branchName.isEmpty())
|
||||
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 (!gitClient()->synchronousBranchCmd(m_repository, args, &output, &errorMessage))
|
||||
break;
|
||||
if (!m_localModel->refresh(m_repository, &errorMessage))
|
||||
break;
|
||||
ok = true;
|
||||
} while (false);
|
||||
slotEnableButtons();
|
||||
if (!ok)
|
||||
QMessageBox::warning(this, tr("Failed to delete branch"), errorMessage);
|
||||
|
||||
QString message = tr("Would you like to delete the branch '%1'?").arg(branchName);
|
||||
bool wasMerged = m_model->branchIsMerged(selected);
|
||||
if (!wasMerged)
|
||||
message = tr("Would you like to delete the <b>unmerged</b> branch '%1'?").arg(branchName);
|
||||
|
||||
if (QMessageBox::question(this, tr("Delete Branch"), message, QMessageBox::Yes|QMessageBox::No,
|
||||
wasMerged ? QMessageBox::Yes : QMessageBox::No) == QMessageBox::Yes)
|
||||
m_model->removeBranch(selected);
|
||||
}
|
||||
|
||||
void BranchDialog::slotCreateLocalBranch(const QString &branchName)
|
||||
void BranchDialog::diff()
|
||||
{
|
||||
// Create
|
||||
QString output;
|
||||
QString errorMessage;
|
||||
bool ok = false;
|
||||
do {
|
||||
if (!gitClient()->synchronousBranchCmd(m_repository, QStringList(branchName), &output, &errorMessage))
|
||||
break;
|
||||
if (!m_localModel->refresh(m_repository, &errorMessage))
|
||||
break;
|
||||
ok = true;
|
||||
} while (false);
|
||||
if (!ok) {
|
||||
QMessageBox::warning(this, tr("Failed to create branch"), errorMessage);
|
||||
QString branchName = m_model->branchName(selectedIndex());
|
||||
if (branchName.isEmpty())
|
||||
return;
|
||||
}
|
||||
selectLocalBranch(branchName);
|
||||
GitPlugin::instance()->gitClient()->diffBranch(m_repository, QStringList(), branchName);
|
||||
}
|
||||
|
||||
void BranchDialog::slotLocalBranchActivated()
|
||||
void BranchDialog::log()
|
||||
{
|
||||
if (m_checkoutButton->isEnabled())
|
||||
m_checkoutButton->animateClick();
|
||||
}
|
||||
|
||||
void BranchDialog::slotDiffSelected()
|
||||
{
|
||||
int idx = selectedLocalBranchIndex();
|
||||
if (idx != -1) {
|
||||
gitClient()->diffBranch(m_repository, QStringList(), m_localModel->branchName(idx));
|
||||
QString branchName = m_model->branchName(selectedIndex());
|
||||
if (branchName.isEmpty())
|
||||
return;
|
||||
}
|
||||
idx = selectedRemoteBranchIndex();
|
||||
if (idx != -1)
|
||||
gitClient()->diffBranch(m_repository, QStringList(), m_remoteModel->branchName(idx));
|
||||
}
|
||||
|
||||
void BranchDialog::slotLog()
|
||||
{
|
||||
int idx = selectedLocalBranchIndex();
|
||||
if (idx != -1) {
|
||||
gitClient()->graphLog(m_repository, m_localModel->branchName(idx));
|
||||
return;
|
||||
}
|
||||
idx = selectedRemoteBranchIndex();
|
||||
if (idx != -1)
|
||||
gitClient()->graphLog(m_repository, m_remoteModel->branchName(idx));
|
||||
}
|
||||
|
||||
/* 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 (gitClient()->ensureStash(m_repository, &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;
|
||||
}
|
||||
if (gitClient()->synchronousCheckoutBranch(m_repository, name, &errorMessage)) {
|
||||
refresh(m_repository, true);
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Checkout failed"), errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void BranchDialog::slotRemoteBranchActivated(const QModelIndex &i)
|
||||
{
|
||||
// Double click on a remote branch (origin/foo): Switch to matching
|
||||
// local (foo) one or offer to create a tracking branch.
|
||||
const QString remoteName = m_remoteModel->branchName(i.row());
|
||||
// build the name of the corresponding local branch
|
||||
// and look for it in the local model.
|
||||
const int slashPos = remoteName.indexOf(QLatin1Char('/'));
|
||||
if (slashPos == -1)
|
||||
return;
|
||||
const QString localBranch = remoteName.mid(slashPos + 1);
|
||||
if (localBranch == QLatin1String("HEAD") || localBranch == QLatin1String("master"))
|
||||
return;
|
||||
const int localIndex = m_localModel->findBranchByName(localBranch);
|
||||
if (debug)
|
||||
qDebug() << Q_FUNC_INFO << remoteName << localBranch << localIndex;
|
||||
// There is a matching a local one!
|
||||
if (localIndex != -1) {
|
||||
// Is it the current one? Just close.
|
||||
if (m_localModel->currentBranch() == localIndex) {
|
||||
accept();
|
||||
return;
|
||||
}
|
||||
// Nope, select and trigger checkout
|
||||
selectListRow(m_ui->localBranchListView, localIndex);
|
||||
slotLocalBranchActivated();
|
||||
return;
|
||||
}
|
||||
// Does not exist yet. Ask to create.
|
||||
const QString msg = tr("Would you like to create a local branch '%1' tracking the remote branch '%2'?").arg(localBranch, remoteName);
|
||||
if (!ask(tr("Create branch"), msg, true))
|
||||
return;
|
||||
QStringList args(QLatin1String("--track"));
|
||||
args << localBranch << remoteName;
|
||||
QString errorMessage;
|
||||
bool ok = false;
|
||||
do {
|
||||
QString output;
|
||||
if (!gitClient()->synchronousBranchCmd(m_repository, args, &output, &errorMessage))
|
||||
break;
|
||||
if (!m_localModel->refresh(m_repository, &errorMessage))
|
||||
break;
|
||||
ok = true;
|
||||
} while (false);
|
||||
if (!ok) {
|
||||
QMessageBox::warning(this, tr("Failed to create a tracking branch"), errorMessage);
|
||||
return;
|
||||
}
|
||||
// Select it
|
||||
selectLocalBranch(localBranch);
|
||||
GitPlugin::instance()->gitClient()->graphLog(m_repository, branchName);
|
||||
}
|
||||
|
||||
void BranchDialog::changeEvent(QEvent *e)
|
||||
@@ -369,5 +204,13 @@ void BranchDialog::changeEvent(QEvent *e)
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex BranchDialog::selectedIndex()
|
||||
{
|
||||
QModelIndexList selected = m_ui->branchView->selectionModel()->selectedIndexes();
|
||||
if (selected.isEmpty())
|
||||
return QModelIndex();
|
||||
return selected.at(0);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Git
|
||||
|
||||
@@ -48,9 +48,8 @@ namespace Ui {
|
||||
class BranchDialog;
|
||||
}
|
||||
|
||||
class GitClient;
|
||||
class LocalBranchModel;
|
||||
class RemoteBranchModel;
|
||||
class BranchAddDialog;
|
||||
class BranchModel;
|
||||
|
||||
/**
|
||||
* Branch dialog. Displays a list of local branches at the top and remote
|
||||
@@ -59,44 +58,33 @@ class RemoteBranchModel;
|
||||
*/
|
||||
class BranchDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(BranchDialog)
|
||||
|
||||
public:
|
||||
explicit BranchDialog(QWidget *parent = 0);
|
||||
virtual ~BranchDialog();
|
||||
~BranchDialog();
|
||||
|
||||
public slots:
|
||||
void refresh(const QString &repository, bool force);
|
||||
|
||||
private slots:
|
||||
void slotEnableButtons(const QItemSelection &selected = QItemSelection());
|
||||
void slotCheckoutSelectedBranch();
|
||||
void slotDeleteSelectedBranch();
|
||||
void slotDiffSelected();
|
||||
void slotLog();
|
||||
void slotRefresh();
|
||||
void slotLocalBranchActivated();
|
||||
void slotRemoteBranchActivated(const QModelIndex &);
|
||||
void slotCreateLocalBranch(const QString &branchName);
|
||||
void enableButtons();
|
||||
void refresh();
|
||||
void add();
|
||||
void checkout();
|
||||
void remove();
|
||||
void diff();
|
||||
void log();
|
||||
|
||||
protected:
|
||||
virtual void changeEvent(QEvent *e);
|
||||
void changeEvent(QEvent *e);
|
||||
|
||||
private:
|
||||
bool ask(const QString &title, const QString &what, bool defaultButton);
|
||||
void selectLocalBranch(const QString &b);
|
||||
|
||||
int selectedLocalBranchIndex() const;
|
||||
int selectedRemoteBranchIndex() const;
|
||||
QModelIndex selectedIndex();
|
||||
|
||||
Ui::BranchDialog *m_ui;
|
||||
QPushButton *m_checkoutButton;
|
||||
QPushButton *m_diffButton;
|
||||
QPushButton *m_logButton;
|
||||
QPushButton *m_refreshButton;
|
||||
QPushButton *m_deleteButton;
|
||||
|
||||
LocalBranchModel *m_localModel;
|
||||
RemoteBranchModel *m_remoteModel;
|
||||
BranchModel *m_model;
|
||||
|
||||
QString m_repository;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,14 +16,33 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="infoGroupBox">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="repositoryLabel">
|
||||
<property name="text">
|
||||
<string notr="true">Repository: Dummy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Re&fresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -32,21 +51,63 @@
|
||||
<property name="title">
|
||||
<string>Branches</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="localBranchListView"/>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="topMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="3">
|
||||
<widget class="QTreeView" name="branchView">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</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 row="1" column="0">
|
||||
<widget class="QPushButton" name="addButton">
|
||||
<property name="text">
|
||||
<string>&Add...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="removeButton">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="diffButton">
|
||||
<property name="text">
|
||||
<string>&Diff</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="logButton">
|
||||
<property name="text">
|
||||
<string>&Log</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="checkoutButton">
|
||||
<property name="text">
|
||||
<string>&Checkout</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -63,6 +124,16 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>branchView</tabstop>
|
||||
<tabstop>addButton</tabstop>
|
||||
<tabstop>checkoutButton</tabstop>
|
||||
<tabstop>removeButton</tabstop>
|
||||
<tabstop>diffButton</tabstop>
|
||||
<tabstop>logButton</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
<tabstop>refreshButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
|
||||
@@ -33,97 +33,540 @@
|
||||
#include "branchmodel.h"
|
||||
#include "gitclient.h"
|
||||
|
||||
#include <vcsbase/vcsbaseoutputwindow.h>
|
||||
|
||||
#include <QtGui/QFont>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QRegExp>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
enum { debug = 0 };
|
||||
|
||||
namespace Git {
|
||||
namespace Internal {
|
||||
namespace Internal {
|
||||
|
||||
// Parse a branch line: " *name sha description".
|
||||
bool RemoteBranchModel::Branch::parse(const QString &lineIn, bool *isCurrent)
|
||||
// --------------------------------------------------------------------------
|
||||
// BranchNode:
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
class BranchNode
|
||||
{
|
||||
if (debug)
|
||||
qDebug() << Q_FUNC_INFO << lineIn;
|
||||
public:
|
||||
BranchNode() :
|
||||
parent(0), current(false)
|
||||
{ }
|
||||
|
||||
*isCurrent = lineIn.startsWith(QLatin1String("* "));
|
||||
if (lineIn.size() < 3)
|
||||
BranchNode(const QString &n, const QString &s = QString(), bool c = false) :
|
||||
parent(0), current(c), name(n), sha(s)
|
||||
{ }
|
||||
|
||||
~BranchNode()
|
||||
{
|
||||
qDeleteAll(children);
|
||||
}
|
||||
|
||||
BranchNode *rootNode()
|
||||
{
|
||||
return parent ? parent->rootNode() : this;
|
||||
}
|
||||
|
||||
int count()
|
||||
{
|
||||
return children.count();
|
||||
}
|
||||
|
||||
bool isLeaf()
|
||||
{
|
||||
return children.isEmpty();
|
||||
}
|
||||
|
||||
bool childOf(BranchNode *node)
|
||||
{
|
||||
if (this == node)
|
||||
return true;
|
||||
return parent ? parent->childOf(node) : false;
|
||||
}
|
||||
|
||||
bool isLocal()
|
||||
{
|
||||
BranchNode *rn = rootNode();
|
||||
if (rn->isLeaf())
|
||||
return false;
|
||||
return childOf(rn->children.at(0));
|
||||
}
|
||||
|
||||
BranchNode *childOfName(const QString &name)
|
||||
{
|
||||
for (int i = 0; i < children.count(); ++i) {
|
||||
if (children.at(i)->name == name)
|
||||
return children.at(i);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QStringList fullName()
|
||||
{
|
||||
Q_ASSERT(isLeaf());
|
||||
|
||||
QStringList fn;
|
||||
QList<BranchNode *> nodes;
|
||||
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 (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()
|
||||
{
|
||||
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")));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const QString branchInfo = lineIn.mid(2);
|
||||
QStringList tokens;
|
||||
if (*isCurrent && branchInfo.startsWith(QLatin1String("(no branch)")))
|
||||
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;
|
||||
else
|
||||
tokens = branchInfo.split(QLatin1Char(' '), QString::SkipEmptyParts);
|
||||
if (tokens.size() < 2)
|
||||
return false;
|
||||
name = tokens.at(0);
|
||||
currentSHA= tokens.at(1);
|
||||
toolTip.clear();
|
||||
}
|
||||
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------ RemoteBranchModel
|
||||
RemoteBranchModel::RemoteBranchModel(GitClient *client, QObject *parent) :
|
||||
QAbstractListModel(parent),
|
||||
m_flags(Qt::ItemIsSelectable|Qt::ItemIsEnabled),
|
||||
m_client(client)
|
||||
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;
|
||||
}
|
||||
|
||||
bool RemoteBranchModel::refresh(const QString &workingDirectory, QString *errorMessage)
|
||||
void BranchModel::clear()
|
||||
{
|
||||
int currentBranch;
|
||||
return refreshBranches(workingDirectory, true, ¤tBranch, errorMessage);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
QString RemoteBranchModel::branchName(int row) const
|
||||
bool BranchModel::refresh(const QString &workingDirectory, QString *errorMessage)
|
||||
{
|
||||
return m_branches.at(row).name;
|
||||
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;
|
||||
}
|
||||
|
||||
QString RemoteBranchModel::workingDirectory() const
|
||||
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;
|
||||
}
|
||||
|
||||
int RemoteBranchModel::branchCount() const
|
||||
GitClient *BranchModel::client() const
|
||||
{
|
||||
return m_branches.size();
|
||||
return m_client;
|
||||
}
|
||||
|
||||
int RemoteBranchModel::rowCount(const QModelIndex & /* parent */) const
|
||||
QModelIndex BranchModel::currentBranch() const
|
||||
{
|
||||
return branchCount();
|
||||
}
|
||||
|
||||
QVariant RemoteBranchModel::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;
|
||||
default:
|
||||
break;
|
||||
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 QVariant();
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
Qt::ItemFlags RemoteBranchModel::flags(const QModelIndex & /* index */) const
|
||||
QString BranchModel::branchName(const QModelIndex &idx) const
|
||||
{
|
||||
return m_flags;
|
||||
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('/'));
|
||||
}
|
||||
|
||||
QString RemoteBranchModel::toolTip(const QString &sha) const
|
||||
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()) {
|
||||
static_cast<BranchNode *>(currentBranch().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("--contains") << sha(idx);
|
||||
if (!m_client->synchronousBranchCmd(m_workingDirectory, args, &output, &errorMessage)) {
|
||||
VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList lines = output.split(QLatin1Char('/'), QString::SkipEmptyParts);
|
||||
foreach (const QString &l, lines) {
|
||||
if (l.startsWith(QLatin1String(" ")) && l.count() >= 3)
|
||||
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 << 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("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;
|
||||
@@ -137,156 +580,6 @@ QString RemoteBranchModel::toolTip(const QString &sha) const
|
||||
return output;
|
||||
}
|
||||
|
||||
bool RemoteBranchModel::runGitBranchCommand(const QString &workingDirectory, const QStringList &additionalArgs, QString *output, QString *errorMessage)
|
||||
{
|
||||
return m_client->synchronousBranchCmd(workingDirectory, additionalArgs, output, errorMessage);
|
||||
}
|
||||
|
||||
void RemoteBranchModel::clear()
|
||||
{
|
||||
if (!m_branches.isEmpty()) {
|
||||
m_branches.clear();
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool RemoteBranchModel::refreshBranches(const QString &workingDirectory, bool remoteBranches,
|
||||
int *currentBranch, QString *errorMessage)
|
||||
{
|
||||
// Run branch command with verbose.
|
||||
QStringList branchArgs;
|
||||
branchArgs << QLatin1String(GitClient::noColorOption) << QLatin1String("-v");
|
||||
QString output;
|
||||
*currentBranch = -1;
|
||||
if (remoteBranches)
|
||||
branchArgs.push_back(QLatin1String("-r"));
|
||||
if (!runGitBranchCommand(workingDirectory, branchArgs, &output, errorMessage))
|
||||
return false;
|
||||
if (debug)
|
||||
qDebug() << Q_FUNC_INFO << workingDirectory << output;
|
||||
// Parse output
|
||||
m_workingDirectory = workingDirectory;
|
||||
m_branches.clear();
|
||||
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)
|
||||
*currentBranch = b;
|
||||
}
|
||||
}
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
int RemoteBranchModel::findBranchByName(const QString &name) const
|
||||
{
|
||||
const int count = branchCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
if (branchName(i) == name)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// --- LocalBranchModel
|
||||
LocalBranchModel::LocalBranchModel(GitClient *client, QObject *parent) :
|
||||
RemoteBranchModel(client, parent),
|
||||
m_typeHere(tr("<New branch>")),
|
||||
m_typeHereToolTip(tr("Type to create a new branch")),
|
||||
m_currentBranch(-1)
|
||||
{
|
||||
}
|
||||
|
||||
int LocalBranchModel::currentBranch() const
|
||||
{
|
||||
return m_currentBranch;
|
||||
}
|
||||
|
||||
bool LocalBranchModel::isNewBranchRow(int row) const
|
||||
{
|
||||
return row >= branchCount();
|
||||
}
|
||||
|
||||
Qt::ItemFlags LocalBranchModel::flags(const QModelIndex & index) const
|
||||
{
|
||||
if (isNewBranchRow(index))
|
||||
return Qt::ItemIsEditable|Qt::ItemIsSelectable|Qt::ItemIsEnabled| Qt::ItemIsUserCheckable;
|
||||
return RemoteBranchModel::flags(index) | Qt::ItemIsUserCheckable;
|
||||
}
|
||||
|
||||
int LocalBranchModel::rowCount(const QModelIndex & /* parent */) const
|
||||
{
|
||||
return branchCount() + 1;
|
||||
}
|
||||
|
||||
QVariant LocalBranchModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (isNewBranchRow(index)) {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return m_typeHere;
|
||||
case Qt::ToolTipRole:
|
||||
return m_typeHereToolTip;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role == Qt::FontRole && index.row() == m_currentBranch) {
|
||||
QFont font = RemoteBranchModel::data(index, role).value<QFont>();
|
||||
font.setBold(true);
|
||||
font.setUnderline(true);
|
||||
return font;
|
||||
}
|
||||
return RemoteBranchModel::data(index, role);
|
||||
}
|
||||
|
||||
void LocalBranchModel::clear()
|
||||
{
|
||||
m_currentBranch = -1;
|
||||
m_newBranch.clear();
|
||||
RemoteBranchModel::clear();
|
||||
}
|
||||
|
||||
bool LocalBranchModel::refresh(const QString &workingDirectory, QString *errorMessage)
|
||||
{
|
||||
return refreshBranches(workingDirectory, false, &m_currentBranch, errorMessage);
|
||||
}
|
||||
|
||||
bool LocalBranchModel::checkNewBranchName(const QString &name) const
|
||||
{
|
||||
// Syntax
|
||||
const QRegExp pattern(QLatin1String("[a-zA-Z0-9-_]+"));
|
||||
if (!pattern.exactMatch(name))
|
||||
return false;
|
||||
// existing
|
||||
if (findBranchByName(name) != -1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalBranchModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
// Verify
|
||||
if (role != Qt::EditRole || index.row() < branchCount())
|
||||
return false;
|
||||
const QString branchName = value.toString();
|
||||
// Delay the signal as we don't want ourselves to be reset while
|
||||
// in setData().
|
||||
if (checkNewBranchName(branchName)) {
|
||||
m_newBranch = branchName;
|
||||
QTimer::singleShot(0, this, SLOT(slotNewBranchDelayedRefresh()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalBranchModel::slotNewBranchDelayedRefresh()
|
||||
{
|
||||
emit newBranchEntered(m_newBranch);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace Internal
|
||||
} // namespace Git
|
||||
|
||||
|
||||
@@ -38,105 +38,63 @@
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
namespace Git {
|
||||
namespace Internal {
|
||||
namespace Internal {
|
||||
|
||||
class GitClient;
|
||||
|
||||
/* A read-only model to list git remote branches in a simple list of branch names.
|
||||
* The tooltip describes the latest commit (delayed creation).
|
||||
* Provides virtuals to be able to derive a local branch model with the notion
|
||||
* of a "current branch". */
|
||||
class BranchNode;
|
||||
|
||||
class RemoteBranchModel : public QAbstractListModel {
|
||||
// --------------------------------------------------------------------------
|
||||
// BranchModel:
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
class BranchModel : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RemoteBranchModel(GitClient *client, QObject *parent = 0);
|
||||
explicit BranchModel(GitClient *client, QObject *parent = 0);
|
||||
~BranchModel();
|
||||
|
||||
virtual void clear();
|
||||
virtual bool refresh(const QString &workingDirectory, QString *errorMessage);
|
||||
// QAbstractItemModel
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
|
||||
QModelIndex parent(const QModelIndex &index) const;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
|
||||
QString branchName(int row) const;
|
||||
void clear();
|
||||
bool refresh(const QString &workingDirectory, QString *errorMessage);
|
||||
|
||||
// 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;
|
||||
|
||||
int branchCount() const;
|
||||
void renameBranch(const QString &oldName, const QString &newName);
|
||||
|
||||
QString workingDirectory() const;
|
||||
int findBranchByName(const QString &name) const;
|
||||
GitClient *client() const;
|
||||
|
||||
protected:
|
||||
struct Branch {
|
||||
bool parse(const QString &line, bool *isCurrent);
|
||||
QModelIndex currentBranch() const;
|
||||
QString branchName(const QModelIndex &idx) const;
|
||||
QStringList localBranchNames() const;
|
||||
QString sha(const QModelIndex &idx) const;
|
||||
bool isLocal(const QModelIndex &idx) const;
|
||||
bool isLeaf(const QModelIndex &idx) const;
|
||||
|
||||
QString name;
|
||||
QString currentSHA;
|
||||
mutable QString toolTip;
|
||||
};
|
||||
typedef QList<Branch> BranchList;
|
||||
|
||||
/* Parse git output and populate m_branches. */
|
||||
bool refreshBranches(const QString &workingDirectory, bool remoteBranches,
|
||||
int *currentBranch, QString *errorMessage);
|
||||
bool runGitBranchCommand(const QString &workingDirectory, const QStringList &additionalArgs, QString *output, QString *errorMessage);
|
||||
void removeBranch(const QModelIndex &idx);
|
||||
void checkoutBranch(const QModelIndex &idx);
|
||||
bool branchIsMerged(const QModelIndex &idx);
|
||||
QModelIndex addBranch(const QString &branchName, bool track, const QString &trackedBranch);
|
||||
|
||||
private:
|
||||
QString toolTip(const QString &sha) const;
|
||||
void parseOutputLine(const QString &line);
|
||||
|
||||
const Qt::ItemFlags m_flags;
|
||||
QString toolTip(const QString &sha) const;
|
||||
|
||||
GitClient *m_client;
|
||||
QString m_workingDirectory;
|
||||
BranchList m_branches;
|
||||
BranchNode *m_rootNode;
|
||||
};
|
||||
|
||||
/* LocalBranchModel: Extends RemoteBranchModel by a read-only
|
||||
* checkable column indicating the current branch. Provides an
|
||||
* editable "Type here" new-branch-row at the bottom to create
|
||||
* a new branch. */
|
||||
|
||||
class LocalBranchModel : public RemoteBranchModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
explicit LocalBranchModel(GitClient *client,
|
||||
QObject *parent = 0);
|
||||
|
||||
virtual void clear();
|
||||
virtual bool refresh(const QString &workingDirectory, QString *errorMessage);
|
||||
|
||||
// is this the "type here" row?
|
||||
bool isNewBranchRow(int row) const;
|
||||
bool isNewBranchRow(const QModelIndex & index) const { return isNewBranchRow(index.row()); }
|
||||
|
||||
int currentBranch() const;
|
||||
|
||||
// QAbstractListModel
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
||||
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
|
||||
signals:
|
||||
void newBranchCreated(const QString &);
|
||||
void newBranchEntered(const QString &);
|
||||
|
||||
private slots:
|
||||
void slotNewBranchDelayedRefresh();
|
||||
|
||||
private:
|
||||
bool checkNewBranchName(const QString &name) const;
|
||||
|
||||
const QVariant m_typeHere;
|
||||
const QVariant m_typeHereToolTip;
|
||||
|
||||
int m_currentBranch;
|
||||
QString m_newBranch;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
} // namespace Internal
|
||||
} // namespace Git
|
||||
|
||||
#endif // BRANCHMODEL_H
|
||||
|
||||
@@ -27,6 +27,7 @@ HEADERS += gitplugin.h \
|
||||
gitutils.h \
|
||||
remotemodel.h \
|
||||
remotedialog.h \
|
||||
branchadddialog.h
|
||||
|
||||
SOURCES += gitplugin.cpp \
|
||||
gitclient.cpp \
|
||||
@@ -48,6 +49,7 @@ SOURCES += gitplugin.cpp \
|
||||
gitutils.cpp \
|
||||
remotemodel.cpp \
|
||||
remotedialog.cpp \
|
||||
branchadddialog.cpp
|
||||
|
||||
FORMS += changeselectiondialog.ui \
|
||||
settingspage.ui \
|
||||
@@ -56,8 +58,12 @@ FORMS += changeselectiondialog.ui \
|
||||
stashdialog.ui \
|
||||
remotedialog.ui \
|
||||
remoteadditiondialog.ui \
|
||||
branchadddialog.ui
|
||||
|
||||
include(gitorious/gitorious.pri)
|
||||
|
||||
RESOURCES += \
|
||||
git.qrc
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user