VCS[git]: Add branch combo to the checkout wizard.

Provide UI with manual refresh button in BaseCheckoutWizardPage.
Change ProcessCheckoutJob to be able to execute several steps.
Implement in git.
This commit is contained in:
Friedemann Kleint
2010-08-12 14:44:57 +02:00
parent 1ae9940b32
commit cb33332311
15 changed files with 274 additions and 49 deletions

View File

@@ -84,8 +84,9 @@ QSharedPointer<VCSBase::AbstractCheckoutJob> CheckoutWizard::createJob(const QLi
args << QLatin1String("checkout") << repository;
const QString workingDirectory = cwp->path();
*checkoutPath = workingDirectory + QLatin1Char('/') + repository;
VCSBase::AbstractCheckoutJob *job = new VCSBase::ProcessCheckoutJob(binary, settings.addOptions(args),
workingDirectory);
VCSBase::ProcessCheckoutJob *job = new VCSBase::ProcessCheckoutJob;
job->addStep(binary, settings.addOptions(args), workingDirectory);
return QSharedPointer<VCSBase::AbstractCheckoutJob>(job);
}

View File

@@ -39,6 +39,7 @@ CheckoutWizardPage::CheckoutWizardPage(QWidget *parent) :
setSubTitle(tr("Specify repository and path."));
setRepositoryLabel(tr("Repository:"));
setDirectoryVisible(false);
setBranchSelectorVisible(false);
}
} // namespace Internal

View File

@@ -34,6 +34,8 @@
#include <vcsbase/checkoutjobs.h>
#include <utils/qtcassert.h>
#include <QtGui/QCheckBox>
namespace Git {
struct CloneWizardPagePrivate {
@@ -42,12 +44,14 @@ struct CloneWizardPagePrivate {
const QString mainLinePostfix;
const QString gitPostFix;
const QString protocolDelimiter;
QCheckBox *deleteMasterCheckBox;
};
CloneWizardPagePrivate::CloneWizardPagePrivate() :
mainLinePostfix(QLatin1String("/mainline.git")),
gitPostFix(QLatin1String(".git")),
protocolDelimiter(QLatin1String("://"))
protocolDelimiter(QLatin1String("://")),
deleteMasterCheckBox(0)
{
}
@@ -58,6 +62,9 @@ CloneWizardPage::CloneWizardPage(QWidget *parent) :
setTitle(tr("Location"));
setSubTitle(tr("Specify repository URL, checkout directory and path."));
setRepositoryLabel(tr("Clone URL:"));
d->deleteMasterCheckBox = new QCheckBox(tr("Delete master branch"));
addControl(d->deleteMasterCheckBox);
setDeleteMasterBranch(true);
}
CloneWizardPage::~CloneWizardPage()
@@ -65,6 +72,16 @@ CloneWizardPage::~CloneWizardPage()
delete d;
}
bool CloneWizardPage::deleteMasterBranch() const
{
return d->deleteMasterCheckBox->isChecked();
}
void CloneWizardPage::setDeleteMasterBranch(bool v)
{
d->deleteMasterCheckBox->setChecked(v);
}
QString CloneWizardPage::directoryFromRepository(const QString &urlIn) const
{
/* Try to figure out a good directory name from something like:
@@ -105,17 +122,55 @@ QString CloneWizardPage::directoryFromRepository(const QString &urlIn) const
QSharedPointer<VCSBase::AbstractCheckoutJob> CloneWizardPage::createCheckoutJob(QString *checkoutPath) const
{
const Internal::GitClient *client = Internal::GitPlugin::instance()->gitClient();
QStringList args = client->binary();
const QString workingDirectory = path();
const QString checkoutDir = directory();
*checkoutPath = workingDirectory + QLatin1Char('/') + checkoutDir;
QStringList baseArgs = client->binary();
const QString binary = baseArgs.front();
baseArgs.pop_front();
QStringList args;
args << QLatin1String("clone") << repository() << checkoutDir;
const QString binary = args.front();
args.pop_front();
VCSBase::AbstractCheckoutJob *job =
new VCSBase::ProcessCheckoutJob(binary, args, workingDirectory,
client->processEnvironment());
VCSBase::ProcessCheckoutJob *job = new VCSBase::ProcessCheckoutJob;
const QProcessEnvironment env = client->processEnvironment();
// 1) Basic checkout step
job->addStep(binary, baseArgs + args, workingDirectory, env);
const QString checkoutBranch = branch();
// 2) Checkout branch, change to checkoutDir
const QString masterBranch = QLatin1String("master");
if (!checkoutBranch.isEmpty() && checkoutBranch != masterBranch) {
// Create branch
args.clear();
args << QLatin1String("branch") << QLatin1String("--track")
<< checkoutBranch << (QLatin1String("origin/") + checkoutBranch);
job->addStep(binary, baseArgs + args, checkoutDir, env);
// Checkout branch
args.clear();
args << QLatin1String("checkout") << checkoutBranch;
job->addStep(binary, baseArgs + args, checkoutDir, env);
// Delete master if desired
if (deleteMasterBranch()) {
args.clear();
args << QLatin1String("branch") << QLatin1String("-D") << masterBranch;
job->addStep(binary, baseArgs + args, checkoutDir, env);
}
}
return QSharedPointer<VCSBase::AbstractCheckoutJob>(job);
}
QStringList CloneWizardPage::branches(const QString &repository, int *current)
{
// Run git on remote repository if URL is complete
*current = 0;
if (!repository.endsWith(d->gitPostFix))
return QStringList();
const QStringList branches = Internal::GitPlugin::instance()->gitClient()->synchronousRepositoryBranches(repository);
*current = branches.indexOf(QLatin1String("master"));
return branches;
}
} // namespace Git

View File

@@ -46,6 +46,7 @@ struct CloneWizardPagePrivate;
class CloneWizardPage : public VCSBase::BaseCheckoutWizardPage
{
Q_OBJECT
Q_PROPERTY(bool deleteMasterBranch READ deleteMasterBranch WRITE setDeleteMasterBranch)
public:
explicit CloneWizardPage(QWidget *parent = 0);
virtual ~CloneWizardPage();
@@ -54,6 +55,10 @@ public:
protected:
virtual QString directoryFromRepository(const QString &r) const;
virtual QStringList branches(const QString &repository, int *current);
bool deleteMasterBranch() const;
void setDeleteMasterBranch(bool v);
private:
CloneWizardPagePrivate *d;

View File

@@ -1265,6 +1265,28 @@ GitClient::StatusResult GitClient::gitStatus(const QString &workingDirectory,
return StatusChanged;
}
// Quietly retrieve branch list of remote repository URL
QStringList GitClient::synchronousRepositoryBranches(const QString &repositoryURL)
{
QStringList arguments(QLatin1String("ls-remote"));
arguments << QLatin1String("--heads") << repositoryURL;
const unsigned flags =
VCSBase::VCSBasePlugin::SshPasswordPrompt|
VCSBase::VCSBasePlugin::SuppressStdErrInLogWindow|
VCSBase::VCSBasePlugin::SuppressFailMessageInLogWindow;
const Utils::SynchronousProcessResponse resp = synchronousGit(QString(), arguments, flags);
QStringList branches;
if (resp.result == Utils::SynchronousProcessResponse::Finished) {
// split "82bfad2f51d34e98b18982211c82220b8db049b<tab>refs/heads/master"
foreach(const QString &line, resp.stdOut.split(QLatin1Char('\n'))) {
const int slashPos = line.lastIndexOf(QLatin1Char('/'));
if (slashPos != -1)
branches.push_back(line.mid(slashPos + 1));
}
}
return branches;
}
void GitClient::launchGitK(const QString &workingDirectory)
{
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();

View File

@@ -205,6 +205,7 @@ public:
bool *onBranch = 0);
void launchGitK(const QString &workingDirectory);
QStringList synchronousRepositoryBranches(const QString &repositoryURL);
GitSettings settings() const;
void setSettings(const GitSettings &s);

View File

@@ -86,7 +86,7 @@ QSharedPointer<VCSBase::AbstractCheckoutJob> CloneWizard::createJob(const QList<
args << QLatin1String("clone") << page->repository() << directory;
*checkoutPath = path + QLatin1Char('/') + directory;
return QSharedPointer<VCSBase::AbstractCheckoutJob>(new VCSBase::ProcessCheckoutJob(settings.binary(),
args, path));
VCSBase::ProcessCheckoutJob *job = new VCSBase::ProcessCheckoutJob;
job->addStep(settings.binary(), args, path);
return QSharedPointer<VCSBase::AbstractCheckoutJob>(job);
}

View File

@@ -37,6 +37,7 @@ CloneWizardPage::CloneWizardPage(QWidget *parent)
setTitle(tr("Location"));
setSubTitle(tr("Specify repository URL, checkout directory and path."));
setRepositoryLabel(tr("Clone URL:"));
setBranchSelectorVisible(false);
}
QString CloneWizardPage::directoryFromRepository(const QString &repository) const

View File

@@ -86,8 +86,8 @@ QSharedPointer<VCSBase::AbstractCheckoutJob> CheckoutWizard::createJob(const QLi
const QStringList completeArgs = settings.hasAuthentication() ?
SubversionPlugin::addAuthenticationOptions(args, settings.user, settings.password) :
args;
VCSBase::AbstractCheckoutJob *job = new VCSBase::ProcessCheckoutJob(binary, completeArgs,
workingDirectory);
VCSBase::ProcessCheckoutJob *job = new VCSBase::ProcessCheckoutJob;
job->addStep(binary, completeArgs, workingDirectory);
return QSharedPointer<VCSBase::AbstractCheckoutJob>(job);
}

View File

@@ -38,6 +38,7 @@ CheckoutWizardPage::CheckoutWizardPage(QWidget *parent) :
setTitle(tr("Location"));
setSubTitle(tr("Specify repository URL, checkout directory and path."));
setRepositoryLabel(tr("Repository:"));
setBranchSelectorVisible(false);
}
QString CheckoutWizardPage::directoryFromRepository(const QString &repoIn) const

View File

@@ -30,6 +30,8 @@
#include "basecheckoutwizardpage.h"
#include "ui_basecheckoutwizardpage.h"
#include <QtGui/QIcon>
namespace VCSBase {
struct BaseCheckoutWizardPagePrivate {
@@ -54,6 +56,10 @@ BaseCheckoutWizardPage::BaseCheckoutWizardPage(QWidget *parent) :
d->ui.pathChooser->setExpectedKind(Utils::PathChooser::Directory);
connect(d->ui.pathChooser, SIGNAL(validChanged()), this, SLOT(slotChanged()));
d->ui.branchComboBox->setEnabled(false);
d->ui.branchRefreshToolButton->setIcon(QIcon(QLatin1String(":/locator/images/reload.png")));
connect(d->ui.branchRefreshToolButton, SIGNAL(clicked()), this, SLOT(slotRefreshBranches()));
}
BaseCheckoutWizardPage::~BaseCheckoutWizardPage()
@@ -61,6 +67,28 @@ BaseCheckoutWizardPage::~BaseCheckoutWizardPage()
delete d;
}
void BaseCheckoutWizardPage::addControl(QWidget *w)
{
d->ui.formLayout->addRow(w);
}
void BaseCheckoutWizardPage::addControl(QString &description, QWidget *w)
{
d->ui.formLayout->addRow(description, w);
}
bool BaseCheckoutWizardPage::isBranchSelectorVisible() const
{
return d->ui.branchComboBox->isVisible();
}
void BaseCheckoutWizardPage::setBranchSelectorVisible(bool v)
{
d->ui.branchComboBox->setVisible(v);
d->ui.branchRefreshToolButton->setVisible(v);
d->ui.branchLabel->setVisible(v);
}
void BaseCheckoutWizardPage::setRepositoryLabel(const QString &l)
{
d->ui.repositoryLabel->setText(l);
@@ -112,6 +140,35 @@ void BaseCheckoutWizardPage::setRepository(const QString &r)
d->ui.repositoryLineEdit->setText(r);
}
QString BaseCheckoutWizardPage::branch() const
{
return d->ui.branchComboBox->currentText();
}
void BaseCheckoutWizardPage::setBranch(const QString &b)
{
const int index = d->ui.branchComboBox->findText(b);
if (index != -1)
d->ui.branchComboBox->setCurrentIndex(index);
}
void BaseCheckoutWizardPage::slotRefreshBranches()
{
if (!isBranchSelectorVisible())
return;
// Refresh branch list on demand. This is hard to make
// automagically since there can be network slowness/timeouts, etc.
int current;
const QStringList branchList = branches(repository(), &current);
d->ui.branchComboBox->clear();
d->ui.branchComboBox->setEnabled(branchList.size() > 1);
if (!branchList.isEmpty()) {
d->ui.branchComboBox->addItems(branchList);
if (current >= 0 && current < branchList.size())
d->ui.branchComboBox->setCurrentIndex(current);
}
}
void BaseCheckoutWizardPage::slotRepositoryChanged(const QString &repo)
{
// Derive directory name from repository unless user manually edited it.
@@ -125,6 +182,11 @@ QString BaseCheckoutWizardPage::directoryFromRepository(const QString &r) const
return r;
}
QStringList BaseCheckoutWizardPage::branches(const QString &, int *)
{
return QStringList();
}
void BaseCheckoutWizardPage::slotDirectoryEdited()
{
d->m_directoryEdited = true;

View File

@@ -49,6 +49,7 @@ struct BaseCheckoutWizardPagePrivate;
class VCSBASE_EXPORT BaseCheckoutWizardPage : public QWizardPage {
Q_OBJECT
Q_PROPERTY(bool isBranchSelectorVisible READ isBranchSelectorVisible WRITE setBranchSelectorVisible)
public:
BaseCheckoutWizardPage(QWidget *parent = 0);
~BaseCheckoutWizardPage();
@@ -65,22 +66,36 @@ public:
bool isRepositoryReadOnly() const;
void setRepositoryReadOnly(bool v);
QString branch() const;
void setBranch(const QString &);
virtual bool isComplete() const;
bool isBranchSelectorVisible() const ;
protected:
void changeEvent(QEvent *e);
void setRepositoryLabel(const QString &l);
void setDirectoryVisible(bool v);
void setBranchSelectorVisible(bool v);
/* Determine a checkout directory name from
* repository URL, that is, "protocol:/project" -> "project". */
virtual QString directoryFromRepository(const QString &r) const;
/* Return list of branches of that repository, defaults to empty. */
virtual QStringList branches(const QString &repository, int *current);
/* Add additional controls */
void addControl(QWidget *w);
void addControl(QString &description, QWidget *w);
private slots:
void slotRepositoryChanged(const QString &url);
void slotDirectoryEdited();
void slotChanged();
void slotRefreshBranches();
private:
BaseCheckoutWizardPagePrivate *d;

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>464</width>
<height>302</height>
<width>483</width>
<height>237</height>
</rect>
</property>
<property name="windowTitle">
@@ -40,7 +40,35 @@
</widget>
</item>
<item row="2" column="1">
<widget class="Utils::PathChooser" name="pathChooser"/>
<widget class="Utils::PathChooser" name="pathChooser" native="true"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="branchLabel">
<property name="text">
<string>Branches:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="branchHorizontalLayout">
<item>
<widget class="QComboBox" name="branchComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="branchRefreshToolButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>

View File

@@ -30,10 +30,13 @@
#include "checkoutjobs.h"
#include <vcsbaseplugin.h>
#include <vcsbaseoutputwindow.h>
#include <QtCore/QDebug>
#include <QtCore/QQueue>
#include <QtCore/QDir>
#include <utils/synchronousprocess.h>
#include <utils/qtcassert.h>
enum { debug = 0 };
namespace VCSBase {
@@ -43,15 +46,27 @@ AbstractCheckoutJob::AbstractCheckoutJob(QObject *parent) :
{
}
struct ProcessCheckoutJobStep
{
ProcessCheckoutJobStep() {}
explicit ProcessCheckoutJobStep(const QString &bin,
const QStringList &args,
const QString &workingDir,
QProcessEnvironment env) :
binary(bin), arguments(args), workingDirectory(workingDir), environment(env) {}
QString binary;
QStringList arguments;
QString workingDirectory;
QProcessEnvironment environment;
};
struct ProcessCheckoutJobPrivate {
ProcessCheckoutJobPrivate(const QString &binary,
const QStringList &args,
const QString &workingDirectory,
QProcessEnvironment env);
ProcessCheckoutJobPrivate();
QSharedPointer<QProcess> process;
const QString binary;
const QStringList args;
QQueue<ProcessCheckoutJobStep> stepQueue;
QString binary;
};
// Use a terminal-less process to suppress SSH prompts.
@@ -63,30 +78,15 @@ static inline QSharedPointer<QProcess> createProcess()
return Utils::SynchronousProcess::createProcess(flags);
}
ProcessCheckoutJobPrivate::ProcessCheckoutJobPrivate(const QString &b,
const QStringList &a,
const QString &workingDirectory,
QProcessEnvironment processEnv) :
process(createProcess()),
binary(b),
args(a)
ProcessCheckoutJobPrivate::ProcessCheckoutJobPrivate() :
process(createProcess())
{
if (!workingDirectory.isEmpty())
process->setWorkingDirectory(workingDirectory);
VCSBasePlugin::setProcessEnvironment(&processEnv, false);
process->setProcessEnvironment(processEnv);
}
ProcessCheckoutJob::ProcessCheckoutJob(const QString &binary,
const QStringList &args,
const QString &workingDirectory,
const QProcessEnvironment &env,
QObject *parent) :
ProcessCheckoutJob::ProcessCheckoutJob(QObject *parent) :
AbstractCheckoutJob(parent),
d(new ProcessCheckoutJobPrivate(binary, args, workingDirectory, env))
d(new ProcessCheckoutJobPrivate)
{
if (debug)
qDebug() << "ProcessCheckoutJob" << binary << args << workingDirectory;
connect(d->process.data(), SIGNAL(error(QProcess::ProcessError)), this, SLOT(slotError(QProcess::ProcessError)));
connect(d->process.data(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotFinished(int,QProcess::ExitStatus)));
connect(d->process.data(), SIGNAL(readyReadStandardOutput()), this, SLOT(slotOutput()));
@@ -99,6 +99,16 @@ ProcessCheckoutJob::~ProcessCheckoutJob()
delete d;
}
void ProcessCheckoutJob::addStep(const QString &binary,
const QStringList &args,
const QString &workingDirectory,
const QProcessEnvironment &env)
{
if (debug)
qDebug() << "ProcessCheckoutJob::addStep" << binary << args << workingDirectory;
d->stepQueue.enqueue(ProcessCheckoutJobStep(binary, args, workingDirectory, env));
}
void ProcessCheckoutJob::slotOutput()
{
const QByteArray data = d->process->readAllStandardOutput();
@@ -130,7 +140,7 @@ void ProcessCheckoutJob::slotFinished (int exitCode, QProcess::ExitStatus exitSt
case QProcess::NormalExit:
emit output(tr("The process terminated with exit code %1.").arg(exitCode));
if (exitCode == 0) {
emit succeeded();
slotNext();
} else {
emit failed(tr("The process returned exit code %1.").arg(exitCode));
}
@@ -143,7 +153,28 @@ void ProcessCheckoutJob::slotFinished (int exitCode, QProcess::ExitStatus exitSt
void ProcessCheckoutJob::start()
{
d->process->start(d->binary, d->args);
QTC_ASSERT(!d->stepQueue.empty(), return)
slotNext();
}
void ProcessCheckoutJob::slotNext()
{
if (d->stepQueue.isEmpty()) {
emit succeeded();
return;
}
// Launch next
const ProcessCheckoutJobStep step = d->stepQueue.dequeue();
d->process->setWorkingDirectory(step.workingDirectory);
// Set up SSH correctly.
QProcessEnvironment processEnv = step.environment;
VCSBasePlugin::setProcessEnvironment(&processEnv, false);
d->process->setProcessEnvironment(processEnv);
d->binary = step.binary;
emit output(VCSBaseOutputWindow::msgExecutionLogEntry(step.workingDirectory, d->binary, step.arguments));
d->process->start(d->binary, step.arguments);
}
void ProcessCheckoutJob::cancel()

View File

@@ -72,13 +72,14 @@ class VCSBASE_EXPORT ProcessCheckoutJob : public AbstractCheckoutJob
{
Q_OBJECT
public:
explicit ProcessCheckoutJob(const QString &binary,
const QStringList &args,
const QString &workingDirectory = QString(),
const QProcessEnvironment &env = QProcessEnvironment::systemEnvironment(),
QObject *parent = 0);
explicit ProcessCheckoutJob(QObject *parent = 0);
virtual ~ProcessCheckoutJob();
void addStep(const QString &binary,
const QStringList &args,
const QString &workingDirectory = QString(),
const QProcessEnvironment &env = QProcessEnvironment::systemEnvironment());
virtual void start();
virtual void cancel();
@@ -86,6 +87,7 @@ private slots:
void slotError(QProcess::ProcessError error);
void slotFinished (int exitCode, QProcess::ExitStatus exitStatus);
void slotOutput();
void slotNext();
private:
ProcessCheckoutJobPrivate *d;