From 5f6a91e0095989b6f978cd2b347890110a101d33 Mon Sep 17 00:00:00 2001 From: Tobias Hunger Date: Mon, 30 May 2011 12:14:49 +0000 Subject: [PATCH] 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 Reviewed-by: Tobias Hunger --- src/plugins/git/branchadddialog.cpp | 47 +++ src/plugins/git/branchadddialog.h | 36 +++ src/plugins/git/branchadddialog.ui | 81 +++++ src/plugins/git/branchdialog.cpp | 345 ++++++-------------- src/plugins/git/branchdialog.h | 44 +-- src/plugins/git/branchdialog.ui | 103 +++++- src/plugins/git/branchmodel.cpp | 615 ++++++++++++++++++++++++++---------- src/plugins/git/branchmodel.h | 116 +++---- src/plugins/git/git.pro | 6 + 9 files changed, 858 insertions(+), 535 deletions(-) create mode 100644 src/plugins/git/branchadddialog.cpp create mode 100644 src/plugins/git/branchadddialog.h create mode 100644 src/plugins/git/branchadddialog.ui diff --git a/src/plugins/git/branchadddialog.cpp b/src/plugins/git/branchadddialog.cpp new file mode 100644 index 0000000000..64e2d32b2e --- /dev/null +++ b/src/plugins/git/branchadddialog.cpp @@ -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 diff --git a/src/plugins/git/branchadddialog.h b/src/plugins/git/branchadddialog.h new file mode 100644 index 0000000000..9fec0966a3 --- /dev/null +++ b/src/plugins/git/branchadddialog.h @@ -0,0 +1,36 @@ +#ifndef BRANCHADDDIALOG_H +#define BRANCHADDDIALOG_H + +#include + +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 diff --git a/src/plugins/git/branchadddialog.ui b/src/plugins/git/branchadddialog.ui new file mode 100644 index 0000000000..38298fca2d --- /dev/null +++ b/src/plugins/git/branchadddialog.ui @@ -0,0 +1,81 @@ + + + Git::Internal::BranchAddDialog + + + + 0 + 0 + 400 + 134 + + + + Dialog + + + + + + Branch Name: + + + + + + + + + + CheckBox + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Git::Internal::BranchAddDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Git::Internal::BranchAddDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/plugins/git/branchdialog.cpp b/src/plugins/git/branchdialog.cpp index 175325b377..cb57a34417 100644 --- a/src/plugins/git/branchdialog.cpp +++ b/src/plugins/git/branchdialog.cpp @@ -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 #include #include @@ -45,42 +47,13 @@ #include -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())); - - m_diffButton = m_ui->buttonBox->addButton(tr("Diff"), QDialogButtonBox::ActionRole); - connect(m_diffButton, SIGNAL(clicked()), this, SLOT(slotDiffSelected())); - - m_logButton = m_ui->buttonBox->addButton(tr("Log"), QDialogButtonBox::ActionRole); - connect(m_logButton, SIGNAL(clicked()), this, SLOT(slotLog())); + 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_refreshButton = m_ui->buttonBox->addButton(tr("Refresh"), QDialogButtonBox::ActionRole); - connect(m_refreshButton, SIGNAL(clicked()), this, SLOT(slotRefresh())); + m_ui->branchView->setModel(m_model); - m_deleteButton = m_ui->buttonBox->addButton(tr("Delete..."), QDialogButtonBox::ActionRole); - connect(m_deleteButton, SIGNAL(clicked()), this, SLOT(slotDeleteSelectedBranch())); + connect(m_ui->branchView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(enableButtons())); - 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(); -} - -int BranchDialog::selectedLocalBranchIndex() const -{ - return selectedRow(m_ui->localBranchListView); -} + QString errorMessage; + if (!m_model->refresh(m_repository, &errorMessage)) + VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage); -int BranchDialog::selectedRemoteBranchIndex() const -{ - return selectedRow(m_ui->remoteBranchListView); + m_ui->branchView->expandAll(); } -void BranchDialog::slotEnableButtons(const QItemSelection &selected) +void BranchDialog::enableButtons() { - if (!selected.indexes().isEmpty()) { - if (selected.indexes().at(0).model() == m_localModel) - m_ui->remoteBranchListView->clearSelection(); - else - m_ui->localBranchListView->clearSelection(); - } + 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); - // 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 entry of list view - m_ui->localBranchListView->setEnabled(hasRepository); - m_ui->remoteBranchListView->setEnabled(hasRepository); + 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); } -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; + } -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; -} + QStringList localNames = m_model->localBranchNames(); -/* 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 (!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 suggestedNameBase = trackedBranch.mid(trackedBranch.lastIndexOf(QLatin1Char('/')) + 1); + QString suggestedName = suggestedNameBase; + int i = 2; + while (localNames.contains(suggestedName)) { + suggestedName = suggestedNameBase + QString::number(i); + ++i; + } -void BranchDialog::slotCreateLocalBranch(const QString &branchName) -{ - // 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); - return; + 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); } - selectLocalBranch(branchName); } -void BranchDialog::slotLocalBranchActivated() +void BranchDialog::checkout() { - if (m_checkoutButton->isEnabled()) - m_checkoutButton->animateClick(); -} + QModelIndex idx = selectedIndex(); + Q_ASSERT(m_model->isLocal(idx)); -void BranchDialog::slotDiffSelected() -{ - int idx = selectedLocalBranchIndex(); - if (idx != -1) { - gitClient()->diffBranch(m_repository, QStringList(), m_localModel->branchName(idx)); - return; - } - idx = selectedRemoteBranchIndex(); - if (idx != -1) - gitClient()->diffBranch(m_repository, QStringList(), m_remoteModel->branchName(idx)); + m_model->checkoutBranch(idx); } -void BranchDialog::slotLog() +/* Prompt to delete a local branch and do so. */ +void BranchDialog::remove() { - int idx = selectedLocalBranchIndex(); - if (idx != -1) { - gitClient()->graphLog(m_repository, m_localModel->branchName(idx)); + 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; - } - idx = selectedRemoteBranchIndex(); - if (idx != -1) - gitClient()->graphLog(m_repository, m_remoteModel->branchName(idx)); + + 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 unmerged 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); } -/* Ask to stash away changes and then close dialog and do an asynchronous - * checkout. */ -void BranchDialog::slotCheckoutSelectedBranch() +void BranchDialog::diff() { - 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: + QString branchName = m_model->branchName(selectedIndex()); + if (branchName.isEmpty()) 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); - } + GitPlugin::instance()->gitClient()->diffBranch(m_repository, QStringList(), branchName); } -void BranchDialog::slotRemoteBranchActivated(const QModelIndex &i) +void BranchDialog::log() { - // 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")) + QString branchName = m_model->branchName(selectedIndex()); + if (branchName.isEmpty()) 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 diff --git a/src/plugins/git/branchdialog.h b/src/plugins/git/branchdialog.h index 2fc9c7b031..e1f240e5e0 100644 --- a/src/plugins/git/branchdialog.h +++ b/src/plugins/git/branchdialog.h @@ -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; }; diff --git a/src/plugins/git/branchdialog.ui b/src/plugins/git/branchdialog.ui index 872f7b19fa..1689119b66 100644 --- a/src/plugins/git/branchdialog.ui +++ b/src/plugins/git/branchdialog.ui @@ -16,14 +16,33 @@ - - + + + 4 + + + 4 + + Repository: Dummy + + + + + 0 + 0 + + + + Re&fresh + + + @@ -32,21 +51,63 @@ Branches - - - + + + 4 + + + 4 + + + 9 + + + + + false + + + true + + + false + + - - - - - - - Remote Branches - - - - + + + + &Add... + + + + + + + &Remove + + + + + + + &Diff + + + + + + + &Log + + + + + + + &Checkout + + @@ -63,6 +124,16 @@ + + branchView + addButton + checkoutButton + removeButton + diffButton + logButton + buttonBox + refreshButton + diff --git a/src/plugins/git/branchmodel.cpp b/src/plugins/git/branchmodel.cpp index c209caf4e0..428ddf3e6a 100644 --- a/src/plugins/git/branchmodel.cpp +++ b/src/plugins/git/branchmodel.cpp @@ -33,260 +33,553 @@ #include "branchmodel.h" #include "gitclient.h" +#include + #include -#include #include #include -enum { debug = 0 }; - namespace Git { - namespace Internal { +namespace Internal { + +// -------------------------------------------------------------------------- +// BranchNode: +// -------------------------------------------------------------------------- -// Parse a branch line: " *name sha description". -bool RemoteBranchModel::Branch::parse(const QString &lineIn, bool *isCurrent) +class BranchNode { - if (debug) - qDebug() << Q_FUNC_INFO << lineIn; +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); + } - *isCurrent = lineIn.startsWith(QLatin1String("* ")); - if (lineIn.size() < 3) - return false; + BranchNode *rootNode() + { + return parent ? parent->rootNode() : this; + } - const QString branchInfo = lineIn.mid(2); - QStringList tokens; - if (*isCurrent && branchInfo.startsWith(QLatin1String("(no branch)"))) - 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(); - return true; -} + 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 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('/'))); + } -// ------ RemoteBranchModel -RemoteBranchModel::RemoteBranchModel(GitClient *client, QObject *parent) : - QAbstractListModel(parent), - m_flags(Qt::ItemIsSelectable|Qt::ItemIsEnabled), - m_client(client) + BranchNode *parent; + QList 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"))); } -bool RemoteBranchModel::refresh(const QString &workingDirectory, QString *errorMessage) +BranchModel::~BranchModel() { - int currentBranch; - return refreshBranches(workingDirectory, true, ¤tBranch, errorMessage); + delete m_rootNode; } -QString RemoteBranchModel::branchName(int row) const +QModelIndex BranchModel::index(int row, int column, const QModelIndex &parent) const { - return m_branches.at(row).name; + BranchNode *node = m_rootNode; + if (parent.isValid()) + node = static_cast(parent.internalPointer()); + if (row >= node->count()) + return QModelIndex(); + return createIndex(row, column, static_cast(node->children.at(row))); } -QString RemoteBranchModel::workingDirectory() const +QModelIndex BranchModel::parent(const QModelIndex &index) const { - return m_workingDirectory; + BranchNode *node = static_cast(index.internalPointer()); + if (node->parent == m_rootNode) + return QModelIndex(); + int row = node->parent->children.indexOf(node); + return createIndex(row, 0, static_cast(node->parent)); } -int RemoteBranchModel::branchCount() const +int BranchModel::rowCount(const QModelIndex &parent) const { - return m_branches.size(); + if (!parent.isValid()) + return m_rootNode->count(); + if (parent.column() != 0) + return 0; + return static_cast(parent.internalPointer())->count(); } -int RemoteBranchModel::rowCount(const QModelIndex & /* parent */) const +int BranchModel::columnCount(const QModelIndex &parent) const { - return branchCount(); + Q_UNUSED(parent); + return 1; } -QVariant RemoteBranchModel::data(const QModelIndex &index, int role) const +QVariant BranchModel::data(const QModelIndex &index, int role) const { - const int row = index.row(); + BranchNode *node = static_cast(index.internalPointer()); + 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; + 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(); } - return QVariant(); } -Qt::ItemFlags RemoteBranchModel::flags(const QModelIndex & /* index */) const +bool BranchModel::setData(const QModelIndex &index, const QVariant &value, int role) { - return m_flags; -} + if (role != Qt::EditRole) + return false; + BranchNode *node = static_cast(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 RemoteBranchModel::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; + 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; } -bool RemoteBranchModel::runGitBranchCommand(const QString &workingDirectory, const QStringList &additionalArgs, QString *output, QString *errorMessage) +Qt::ItemFlags BranchModel::flags(const QModelIndex &index) const { - return m_client->synchronousBranchCmd(workingDirectory, additionalArgs, output, errorMessage); + BranchNode *node = static_cast(index.internalPointer()); + if (node->isLeaf() && node->isLocal()) + return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; + else + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } -void RemoteBranchModel::clear() +void BranchModel::clear() { - if (!m_branches.isEmpty()) { - m_branches.clear(); - reset(); + 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 RemoteBranchModel::refreshBranches(const QString &workingDirectory, bool remoteBranches, - int *currentBranch, QString *errorMessage) +bool BranchModel::refresh(const QString &workingDirectory, QString *errorMessage) { - // Run branch command with verbose. + if (workingDirectory.isEmpty()) + return false; + QStringList branchArgs; - branchArgs << QLatin1String(GitClient::noColorOption) << QLatin1String("-v"); + branchArgs << QLatin1String(GitClient::noColorOption) + << QLatin1String("-v") << QLatin1String("-a"); QString output; - *currentBranch = -1; - if (remoteBranches) - branchArgs.push_back(QLatin1String("-r")); - if (!runGitBranchCommand(workingDirectory, branchArgs, &output, errorMessage)) + if (!m_client->synchronousBranchCmd(workingDirectory, branchArgs, &output, errorMessage)) { + VCSBase::VCSBaseOutputWindow::instance()->appendError(*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(); + + beginResetModel(); + + clear(); + + m_workingDirectory = workingDirectory; + const QStringList lines = output.split(QLatin1Char('\n')); + foreach (const QString &l, lines) + parseOutputLine(l); + + endResetModel(); return true; } -int RemoteBranchModel::findBranchByName(const QString &name) const +void BranchModel::renameBranch(const QString &oldName, const QString &newName) { - const int count = branchCount(); - for (int i = 0; i < count; i++) - if (branchName(i) == name) - return i; - return -1; + 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); } -// --- LocalBranchModel -LocalBranchModel::LocalBranchModel(GitClient *client, QObject *parent) : - RemoteBranchModel(client, parent), - m_typeHere(tr("")), - m_typeHereToolTip(tr("Type to create a new branch")), - m_currentBranch(-1) +QString BranchModel::workingDirectory() const { + return m_workingDirectory; } -int LocalBranchModel::currentBranch() const +GitClient *BranchModel::client() const { - return m_currentBranch; + return m_client; } -bool LocalBranchModel::isNewBranchRow(int row) const +QModelIndex BranchModel::currentBranch() const { - return row >= branchCount(); + 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(); } -Qt::ItemFlags LocalBranchModel::flags(const QModelIndex & index) const +QString BranchModel::branchName(const QModelIndex &idx) const { - if (isNewBranchRow(index)) - return Qt::ItemIsEditable|Qt::ItemIsSelectable|Qt::ItemIsEnabled| Qt::ItemIsUserCheckable; - return RemoteBranchModel::flags(index) | Qt::ItemIsUserCheckable; + if (!idx.isValid()) + return QString(); + BranchNode *node = static_cast(idx.internalPointer()); + if (!node->isLeaf()) + return QString(); + QStringList path = node->fullName(); + return path.join(QString('/')); } -int LocalBranchModel::rowCount(const QModelIndex & /* parent */) const +QStringList BranchModel::localBranchNames() const { - return branchCount() + 1; + if (!m_rootNode || m_rootNode->children.isEmpty()) + return QStringList(); + + return m_rootNode->children.at(0)->childrenNames(); } -QVariant LocalBranchModel::data(const QModelIndex &index, int role) const +QString BranchModel::sha(const QModelIndex &idx) const { - if (isNewBranchRow(index)) { - switch (role) { - case Qt::DisplayRole: - return m_typeHere; - case Qt::ToolTipRole: - return m_typeHereToolTip; - } - return QVariant(); - } + if (!idx.isValid()) + return QString(); + BranchNode *node = static_cast(idx.internalPointer()); + return node->sha; +} - if (role == Qt::FontRole && index.row() == m_currentBranch) { - QFont font = RemoteBranchModel::data(index, role).value(); - font.setBold(true); - font.setUnderline(true); - return font; - } - return RemoteBranchModel::data(index, role); +bool BranchModel::isLocal(const QModelIndex &idx) const +{ + if (!idx.isValid()) + return false; + BranchNode *node = static_cast(idx.internalPointer()); + return node->isLocal(); } -void LocalBranchModel::clear() +bool BranchModel::isLeaf(const QModelIndex &idx) const { - m_currentBranch = -1; - m_newBranch.clear(); - RemoteBranchModel::clear(); + if (!idx.isValid()) + return false; + BranchNode *node = static_cast(idx.internalPointer()); + return node->isLeaf(); } -bool LocalBranchModel::refresh(const QString &workingDirectory, QString *errorMessage) +void BranchModel::removeBranch(const QModelIndex &idx) { - return refreshBranches(workingDirectory, false, &m_currentBranch, errorMessage); + 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(parentIdx.internalPointer())->children.removeAt(parentIdx.row()); + delete static_cast(idx.internalPointer()); + endRemoveRows(); } -bool LocalBranchModel::checkNewBranchName(const QString &name) const +void BranchModel::checkoutBranch(const QModelIndex &idx) { - // Syntax - const QRegExp pattern(QLatin1String("[a-zA-Z0-9-_]+")); - if (!pattern.exactMatch(name)) - return false; - // existing - if (findBranchByName(name) != -1) - return false; - return true; + 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(currentBranch().internalPointer())->current = false; + emit dataChanged(currentBranch(), currentBranch()); + static_cast(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 LocalBranchModel::setData(const QModelIndex &index, const QVariant &value, int role) +bool BranchModel::branchIsMerged(const QModelIndex &idx) { - // Verify - if (role != Qt::EditRole || index.row() < branchCount()) + 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; - 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; + + QStringList lines = output.split(QLatin1Char('/'), QString::SkipEmptyParts); + foreach (const QString &l, lines) { + if (l.startsWith(QLatin1String(" ")) && l.count() >= 3) + return true; + } + return false; } -void LocalBranchModel::slotNewBranchDelayedRefresh() +QModelIndex BranchModel::addBranch(const QString &branchName, bool track, const QString &startPoint) { - emit newBranchEntered(m_newBranch); + 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; + 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 + diff --git a/src/plugins/git/branchmodel.h b/src/plugins/git/branchmodel.h index 0516baf1eb..c56d209138 100644 --- a/src/plugins/git/branchmodel.h +++ b/src/plugins/git/branchmodel.h @@ -38,105 +38,63 @@ #include 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); - virtual void clear(); - virtual bool refresh(const QString &workingDirectory, QString *errorMessage); +public: + explicit BranchModel(GitClient *client, QObject *parent = 0); + ~BranchModel(); - QString branchName(int row) const; + // 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; - // 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; + void clear(); + bool refresh(const QString &workingDirectory, QString *errorMessage); - 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 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; -}; - -/* 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; + BranchNode *m_rootNode; }; -} -} +} // namespace Internal +} // namespace Git #endif // BRANCHMODEL_H diff --git a/src/plugins/git/git.pro b/src/plugins/git/git.pro index f0ee631596..4b4bb0bf7a 100644 --- a/src/plugins/git/git.pro +++ b/src/plugins/git/git.pro @@ -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 + + + -- 2.11.0