diff --git a/Libs/libMacGitverCore/App/MgvPrimaryWindowActions.hid b/Libs/libMacGitverCore/App/MgvPrimaryWindowActions.hid index 11a375b5..059a9ef6 100644 --- a/Libs/libMacGitverCore/App/MgvPrimaryWindowActions.hid +++ b/Libs/libMacGitverCore/App/MgvPrimaryWindowActions.hid @@ -26,7 +26,9 @@ Ui MgvPrimaryWindowActions { StatusToolTip "Repository related functions."; MergePlace RepositoryMenuMP; + Separator; + MergePlace CloneMP; Separator; Action RepositoryQuit { diff --git a/Libs/libMacGitverCore/CMakeLists.txt b/Libs/libMacGitverCore/CMakeLists.txt index c038c1a4..84c86ad9 100644 --- a/Libs/libMacGitverCore/CMakeLists.txt +++ b/Libs/libMacGitverCore/CMakeLists.txt @@ -61,6 +61,7 @@ SET( SRC_FILES Widgets/FlatTreeComboBox.cpp Widgets/FlatTreeModel.cpp Widgets/FlatTreeDelegate.cpp + Widgets/ProgressDlg.cpp Widgets/SHA1Input.cpp Widgets/ShortCommitModel.cpp Widgets/RepoStateWidget.cpp @@ -114,6 +115,7 @@ SET( PUB_HDR_FILES Widgets/FlatTreeComboBox.h Widgets/FlatTreeModel.h Widgets/FlatTreeDelegate.h + Widgets/ProgressDlg.hpp Widgets/SHA1Input.h Widgets/ShortCommitModel.h Widgets/TreeViewCtxMenu.hpp @@ -161,6 +163,9 @@ SET( UI_FILES RepoMan/Config/RepoManConfigPage.ui Widgets/ExpandableDlg.ui + Widgets/ProgressDlg.ui + + Widgets/ProgressWdgt.ui ) SET( HID_FILES diff --git a/Libs/libMacGitverCore/Widgets/ProgressDlg.cpp b/Libs/libMacGitverCore/Widgets/ProgressDlg.cpp new file mode 100644 index 00000000..e6679df8 --- /dev/null +++ b/Libs/libMacGitverCore/Widgets/ProgressDlg.cpp @@ -0,0 +1,206 @@ + +#include "ProgressDlg.hpp" +#include "ui_ProgressDlg.h" +#include "ui_ProgressWdgt.h" + +#include +#include +#include + +namespace Private +{ + + class ProgressWdgt : public QWidget, public Ui::ProgressWdgt + { + public: + typedef QMap< QString, QPointer > Steps; + + enum Status { Running = 0, Stopped }; + + public: + ProgressWdgt(const QString& description) + { + setupUi(this); + progressBar->setMinimum(0); + progressBar->setMaximum(100); + txtHeader->setText( description ); + } + + public: + Steps mSteps; + Status mStatus = Running; + + qreal mPercentage = 0.; + }; + +} + + +ProgressDlg::ProgressDlg() + : BlueSky::Dialog() + , ui( new Ui::ProgressDlg ) + , mDone( false ) +{ + ui->setupUi( this ); + + QPushButton* close = ui->buttonBox->button( QDialogButtonBox::Close ); + close->setEnabled( false ); + connect( close, &QPushButton::clicked, this, &ProgressDlg::close ); + + QPalette p; + p.setColor( QPalette::Base, p.color( QPalette::Window ) ); + p.setColor( QPalette::Text, p.color( QPalette::WindowText ) ); + ui->txtLog->setPalette( p ); + + connect(&mUpdater, &QTimer::timeout, this, &ProgressDlg::updateActivities); + mUpdater.start(250); +} + +ProgressDlg::~ProgressDlg() +{ + delete ui; +} + +int ProgressDlg::updateInterval() const +{ + return mUpdater.interval(); +} + +void ProgressDlg::setUpdateInterval(int msec) +{ + mUpdater.setInterval(msec); +} + +void ProgressDlg::addActivity(const QString& description, QObject* activity, + const StepInfo::List& steps) +{ + Q_ASSERT(activity); + + Private::ProgressWdgt* a( new Private::ProgressWdgt(description) ); + mActivities[activity] = a; + + QTreeWidgetItem* activityItem(new QTreeWidgetItem); + ui->treeProgress->addTopLevelItem(activityItem); + ui->treeProgress->setItemWidget(activityItem, 0, a); + + foreach (const StepInfo& sir, steps) { + Private::ProgressWdgt* s = new Private::ProgressWdgt(sir.desc); + a->mSteps[sir.name] = s; + + QTreeWidgetItem* stepItem(new QTreeWidgetItem); + activityItem->addChild(stepItem); + ui->treeProgress->setItemWidget(stepItem, 0, s); + } +} + +void ProgressDlg::setStatusInfo(QObject* activity, const QString& step, + const QString& text) +{ + Q_ASSERT(activity && !step.isEmpty()); + + Private::ProgressWdgt* s = findStep(activity, step); + s->txtStatusInfo->setText(text); +} + +void ProgressDlg::setPercentage(QObject* activity, const QString& step, + qreal percent) +{ + Q_ASSERT(activity && !step.isEmpty()); + + Private::ProgressWdgt* s = findStep(activity, step); + s->mPercentage = qMin( qMax(percent, 0.), 1. ) * 100.; +} + +void ProgressDlg::closeEvent( QCloseEvent* ev ) +{ + if( !mDone ) + { + ev->ignore(); + return; + } + + QDialog::closeEvent( ev ); +} + +void ProgressDlg::remoteMessage(const QString& msg) +{ + mRawRemoteMessage += msg; + + QString output; + QChar outputBuffer[ 256 ]; + int outBufPos = 0, outBufLen = 0; + + for( int i = 0; i < mRawRemoteMessage.length(); ++i ) + { + if( mRawRemoteMessage[ i ] == QChar( L'\r' ) ) + { + outBufPos = 0; + } + else if( mRawRemoteMessage[ i ] == QChar( L'\n' ) ) + { + if( outBufLen ) + output += QString( outputBuffer, outBufLen ); + output += QChar( L'\n' ); + outBufPos = outBufLen = 0; + } + else + { + outputBuffer[ outBufPos++] = mRawRemoteMessage[ i ]; + outBufLen = qMax( outBufLen, outBufPos ); + } + } + + if( outBufLen ) + output += QString( outputBuffer, outBufLen ); + + QString log = mBaseLog % QStringLiteral( "
" ) % + output.replace( QChar( L'\n' ), QStringLiteral("
") ).simplified(); + + ui->txtLog->setHtml( log ); +} + +void ProgressDlg::finished(QObject* activity) +{ + mActivities[activity]->mStatus = Private::ProgressWdgt::Stopped; + + bool done = true; + foreach (Private::ProgressWdgt* a, mActivities) { + done &= (a->mStatus == Private::ProgressWdgt::Stopped); + if (!done) { + break; + } + } + + if (done) { + mDone = true; + ui->buttonBox->button( QDialogButtonBox::Close )->setEnabled( true ); + } +} + +void ProgressDlg::finished(QObject* activity, const QString& step) +{ + Q_ASSERT(activity && !step.isEmpty()); + + Private::ProgressWdgt* a = mActivities[activity]; + a->mSteps[step]->mStatus = Private::ProgressWdgt::Stopped; +} + +void ProgressDlg::updateActivities() +{ + foreach(Private::ProgressWdgt* a, mActivities) { + a->mPercentage = 0; + foreach (Private::ProgressWdgt* s, a->mSteps) { + s->progressBar->setValue(qRound(s->mPercentage)); + qreal stepPercent = s->mPercentage / a->mSteps.size(); + a->mPercentage += qMin( qMax(stepPercent, 0.), 100.); + } + + a->progressBar->setValue(qRound(a->mPercentage)); + } +} + +Private::ProgressWdgt* ProgressDlg::findStep(QObject* activity, const QString& step) const +{ + Private::ProgressWdgt* a = mActivities[activity]; + return step.isEmpty() || !a ? nullptr : a->mSteps[step]; +} diff --git a/Libs/libMacGitverCore/Widgets/ProgressDlg.hpp b/Libs/libMacGitverCore/Widgets/ProgressDlg.hpp new file mode 100644 index 00000000..c1d458a0 --- /dev/null +++ b/Libs/libMacGitverCore/Widgets/ProgressDlg.hpp @@ -0,0 +1,74 @@ + +#pragma once + +#include "libMacGitverCore/MacGitverApi.hpp" +#include "libBlueSky/Dialog.hpp" + +#include +#include +#include + +namespace Private +{ + class ProgressWdgt; +}; + +namespace Ui +{ + class ProgressDlg; +}; + +class MGV_CORE_API ProgressDlg : public BlueSky::Dialog +{ + Q_OBJECT + +public: + struct StepInfo + { + typedef QVector List; + + QString name; + QString desc; + }; + + typedef QMap< QPointer, QPointer > Activities; + +public: + ProgressDlg(); + ~ProgressDlg(); + +public: + int updateInterval() const; + void setUpdateInterval(int msec); + + void addActivity(const QString& description, QObject* activity, + const StepInfo::List& steps); + + void setStatusInfo(QObject* activity, const QString& step, + const QString& text); + void setPercentage(QObject* activity, const QString& step, qreal percent); + void remoteMessage(const QString& msg); + + void finished(QObject* activity); + void finished(QObject* activity, const QString& step); + +private slots: + void updateActivities(); + +protected: + void closeEvent( QCloseEvent* ev ); + +private: + Private::ProgressWdgt* findStep(QObject* activity, const QString& step) const; + +private: + Ui::ProgressDlg* ui; + +private: + bool mDone; + QString mBaseLog; + QString mRawRemoteMessage; + Activities mActivities; + + QTimer mUpdater; +}; diff --git a/Libs/libMacGitverCore/Widgets/ProgressDlg.ui b/Libs/libMacGitverCore/Widgets/ProgressDlg.ui new file mode 100644 index 00000000..f5cce393 --- /dev/null +++ b/Libs/libMacGitverCore/Widgets/ProgressDlg.ui @@ -0,0 +1,116 @@ + + + ProgressDlg + + + Qt::ApplicationModal + + + + 0 + 0 + 603 + 332 + + + + Progress + + + true + + + + + + Qt::Vertical + + + + + 0 + 2 + + + + QAbstractItemView::ScrollPerPixel + + + true + + + true + + + false + + + + 1 + + + + + + + 0 + 1 + + + + + 0 + 60 + + + + Details: + + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + + + + + + QDialogButtonBox::Close + + + + + + + + diff --git a/Libs/libMacGitverCore/Widgets/ProgressWdgt.ui b/Libs/libMacGitverCore/Widgets/ProgressWdgt.ui new file mode 100644 index 00000000..3b780075 --- /dev/null +++ b/Libs/libMacGitverCore/Widgets/ProgressWdgt.ui @@ -0,0 +1,38 @@ + + + ProgressWdgt + + + + 0 + 0 + 448 + 83 + + + + Progress + + + + + + Progress: + + + + + + + + + + Not started... + + + + + + + + diff --git a/Modules/Remotes/CMakeLists.txt b/Modules/Remotes/CMakeLists.txt index f76cf72a..180ea75d 100644 --- a/Modules/Remotes/CMakeLists.txt +++ b/Modules/Remotes/CMakeLists.txt @@ -10,7 +10,7 @@ INCLUDE_DIRECTORIES( BEFORE SET( SRC_FILES - RemotesModule.cpp + CloneRepositoryDlg.cpp RemotesView.cpp RemotesViewContext.cpp RemoteCreateEditDlg.cpp @@ -18,6 +18,7 @@ SET( SRC_FILES SET( HDR_FILES + CloneRepositoryDlg.hpp RemotesModule.h RemotesView.h RemotesViewContext.h @@ -27,6 +28,8 @@ SET( HDR_FILES SET( UI_FILES RemoteCreateEditDlg.ui + CloneOptionsWdgt.ui + CloneWdgt.ui ) SET( HID_FILES diff --git a/Modules/Repository/CloneOptionsWdgt.ui b/Modules/Remotes/CloneOptionsWdgt.ui similarity index 100% rename from Modules/Repository/CloneOptionsWdgt.ui rename to Modules/Remotes/CloneOptionsWdgt.ui diff --git a/Modules/Remotes/CloneRepositoryDlg.cpp b/Modules/Remotes/CloneRepositoryDlg.cpp new file mode 100644 index 00000000..e0e27598 --- /dev/null +++ b/Modules/Remotes/CloneRepositoryDlg.cpp @@ -0,0 +1,281 @@ +/* + * MacGitver + * Copyright (C) 2015 The MacGitver-Developers + * + * (C) Sascha Cunz + * (C) Nils Fenner + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, see . + * + */ + +#include "CloneRepositoryDlg.hpp" + +#include "libMacGitverCore/Config/Config.h" + +#include "libGitWrap/Operations/CloneOperation.hpp" + +#include "libMacGitverCore/App/MacGitver.hpp" +#include "libMacGitverCore/Widgets/ProgressDlg.hpp" + +#include +#include + + +CloneOptionsWdgt::CloneOptionsWdgt() +{ + setupUi(this); +} + +void CloneOptionsWdgt::on_txtCloneMode_currentIndexChanged(int index) +{ + // Note: clone modes are fixed + mCloneMode = static_cast( index ); + grpSubmodules->setEnabled( mCloneMode == cmCheckout ); +} + +void CloneOptionsWdgt::on_chkInitSubmodules_toggled(bool checked) +{ + chkSubmodulesRecursive->setEnabled( checked ); + if( !checked ) + { + chkSubmodulesRecursive->setChecked( false ); + } +} + + +CloneWdgt::CloneWdgt() +{ + setupUi( this ); + + connect(btnBrowseTo, &QAbstractButton::clicked, + this, &CloneWdgt::onBrowse); +} + +void CloneWdgt::onBrowse() +{ + QString fn = txtPath->text(); + if( fn.isEmpty() ) + { + fn = Config::self().get( "Repository/lastUsedDir", QDir::homePath() ).toString(); + } + + QFileDialog* fd = new QFileDialog( this, trUtf8( "Select repository location" ) ); + + #ifdef Q_OS_MAC + fd->setFilter( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden ); + #else + fd->setFileMode( QFileDialog::Directory ); + #endif + + fd->setDirectory( fn ); + fd->setAcceptMode( QFileDialog::AcceptSave ); + fd->open( this, SLOT(onBrowseHelper(QString)) ); +} + +void CloneWdgt::onBrowseHelper( const QString& directory ) +{ + if( directory.isEmpty() ) + { + return; + } + + Config::self().set( "Repository/lastUsedDir", directory ); + txtPath->setText( directory ); +} + + +CloneDlg::CloneDlg() + : mCloneWdgt(new CloneWdgt) + , mCloneOptsWdgt(new CloneOptionsWdgt) +{ + setDialogWidgets(mCloneWdgt, mCloneOptsWdgt); + + connect(mCloneWdgt->txtPath, &QLineEdit::textChanged, + this, &CloneDlg::checkValid); + + checkValid(); +} + +void CloneDlg::checkValid() +{ + if (!mCloneWdgt) { + setAcceptable(false); + return; + } + + bool okay = !mCloneWdgt->txtPath->text().isEmpty() && + !mCloneWdgt->txtUrl->text().isEmpty(); + + QDir wanted( QDir::toNativeSeparators( mCloneWdgt->txtPath->text() ) ); + if( wanted.exists() ) + { + QStringList sl = wanted.entryList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot ); + if( sl.count() > 0 ) + { + okay = false; + } + } + + setAcceptable( okay ); +} + +void CloneDlg::accept() +{ + Git::CloneOperation* clone = new Git::CloneOperation( this ); + QString repoName = QUrl(mCloneWdgt->txtUrl->text() ) + .adjusted(QUrl::NormalizePathSegments | + QUrl::StripTrailingSlash ).toString(); + QString targetDir = QUrl(mCloneWdgt->txtPath->text() ) + .adjusted(QUrl::NormalizePathSegments | + QUrl::StripTrailingSlash ).toString(); + + if ( mCloneWdgt->chkAppendRepoName->isChecked() ) + { + targetDir += QString::fromUtf8("/%1") + .arg( QUrl( repoName ).fileName() ); + } + + clone->setBackgroundMode( true ); + clone->setUrl( repoName ); + clone->setPath( targetDir ); + clone->setRemoteAlias( mCloneWdgt->txtRemoteAlias->text() ); + + if ( mCloneOptsWdgt ) + { + clone->setBare( mCloneOptsWdgt->mCloneMode == CloneOptionsWdgt::cmBare ); + clone->setReference( mCloneOptsWdgt->txtBranch->text() ); + clone->setDepth( mCloneOptsWdgt->txtCloneDepth->value() ); + } + + mProgress = new ProgressDlg; + + // TODO: implement a ProgressDlg::minimumDuration + mProgress->show(); + + if( repoName.endsWith( QLatin1String( ".git" ) ) ) + repoName = repoName.left( repoName.length() - 4 ); + + if( repoName.lastIndexOf( QChar( L'/' ) ) != -1 ) + repoName.remove( 0, repoName.lastIndexOf( QChar( L'/' ) ) + 1 ); + + ProgressDlg::StepInfo::List steps; + steps << ProgressDlg::StepInfo{ QStringLiteral("transfer"), tr("Download Git objects.") } + << ProgressDlg::StepInfo{ QStringLiteral("index"), tr("Add objects to Git index.") }; + + if (!clone->bare()) { + steps << ProgressDlg::StepInfo{ QStringLiteral("checkout"), + tr("Checkout the worktree.") }; + connect( clone, &Git::CloneOperation::doneCheckout, + this, &CloneDlg::doneCheckout ); + } + + mProgress->addActivity(tr("Cloning %1 to %2") + .arg(repoName).arg(targetDir), clone, steps); + + connect( clone, &Git::CloneOperation::finished, + this, &CloneDlg::rootCloneFinished ); + connect( clone, &Git::CloneOperation::transportProgress, + this, &CloneDlg::onTransportProgress ); + connect( clone, &Git::CloneOperation::checkoutProgress, + this, &CloneDlg::onCheckoutProgress ); + connect( clone, &Git::CloneOperation::doneDownloading, + this, &CloneDlg::doneDownload ); + connect( clone, &Git::CloneOperation::doneIndexing, + this, &CloneDlg::doneIndexing ); + + clone->execute(); +} + +void CloneDlg::doneDownload() +{ + mProgress->finished(sender(), QStringLiteral("transfer")); +} + +void CloneDlg::doneIndexing() +{ + mProgress->finished(sender(), QStringLiteral("index")); +} + +void CloneDlg::doneCheckout() +{ + mProgress->finished(sender(), QStringLiteral("checkout")); +} + +void CloneDlg::rootCloneFinished() +{ + Git::BaseOperation* operation = static_cast( sender() ); + Q_ASSERT( operation ); + + if ( operation->result() ) + { + mProgress->finished(sender()); + return; + } + + QMessageBox::critical(mProgress, tr("Errors while cloning repository."), + tr("Cloning failed:\nGit Message: %1").arg(operation->result().errorText())); + mProgress->reject(); +} + +void CloneDlg::onCheckoutProgress(const QString& fileName, quint64 totalFiles, + quint64 completedFiles) +{ + QObject* s = sender(); + + const QString stepCheckout( QStringLiteral("checkout") ); + mProgress->setPercentage( s, stepCheckout, + (qreal)completedFiles / totalFiles ); + mProgress->setStatusInfo( s, stepCheckout, + tr("Checked out %1 of %2 files (last file: %3).") + .arg(completedFiles).arg(totalFiles).arg(fileName) ); +} + +void CloneDlg::onTransportProgress(quint32 totalObjects, + quint32 indexedObjects, + quint32 receivedObjects, + quint64 receivedBytes) +{ + QString recv; + if( receivedBytes > 1024 * 1024 * 950 ) /* 950 is so we get 0.9 gb */ + { + recv = QString::number( receivedBytes / (1024*1024*1024.0), 'f', 2 ) + QStringLiteral(" Gb"); + } + else if( receivedBytes > 1024 * 950 ) + { + recv = QString::number( receivedBytes / (1024*1024.0), 'f', 2 ) + QStringLiteral(" Mb"); + } + else if( receivedBytes > 950 ) + { + recv = QString::number( receivedBytes / 1024.0, 'f', 2 ) + QStringLiteral(" Kb"); + } + else + { + recv = QString::number( receivedBytes ); + } + + QObject* s = sender(); + const QString stepTransfer(QStringLiteral("transfer")); + mProgress->setPercentage( s, stepTransfer, + (qreal)receivedObjects / totalObjects ); + mProgress->setStatusInfo( s, stepTransfer, + tr("Received %1 of %2 objects (%3).") + .arg(receivedObjects) + .arg(totalObjects) + .arg(recv) ); + + const QString stepIndex(QStringLiteral("index")); + mProgress->setPercentage( s, stepIndex, + (qreal)indexedObjects / totalObjects ); + mProgress->setStatusInfo( s, stepIndex, tr("Indexed %1 of %2 objects.") + .arg(indexedObjects) + .arg(totalObjects) ); +} diff --git a/Modules/Remotes/CloneRepositoryDlg.hpp b/Modules/Remotes/CloneRepositoryDlg.hpp new file mode 100644 index 00000000..6f48ed0e --- /dev/null +++ b/Modules/Remotes/CloneRepositoryDlg.hpp @@ -0,0 +1,106 @@ +/* + * MacGitver + * Copyright (C) 2015 The MacGitver-Developers + * + * (C) Sascha Cunz + * (C) Nils Fenner + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License (Version 2) as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if + * not, see . + * + */ + +#pragma once + +#include "libMacGitverCore/Widgets/ExpandableDlg.hpp" + +#include "ui_CloneWdgt.h" +#include "ui_CloneOptionsWdgt.h" + +#include + +class ProgressDlg; + +class CloneOptionsWdgt : public QWidget, Ui::CloneOptionsWdgt +{ + Q_OBJECT + + friend class CloneDlg; + +public: + enum CloneMode { + cmCheckout = 0, + cmBare, + cmMirror + }; + +public: + CloneOptionsWdgt(); + +private slots: + void on_txtCloneMode_currentIndexChanged(int index); + void on_chkInitSubmodules_toggled(bool checked); + +private: + CloneMode mCloneMode; +}; + +class CloneWdgt : public QWidget, Ui::CloneWdgt +{ + Q_OBJECT + + friend class CloneDlg; + +public: + CloneWdgt(); + +private slots: + void onBrowse(); + void onBrowseHelper( const QString& directory ); +}; + +class CloneDlg : public ExpandableDlg +{ + Q_OBJECT + +private: + enum State { Unused, Open, Done, Current }; + enum Tasks { Prepare, Download, Index, Checkout }; + +public: + CloneDlg(); + +protected: + void accept(); + +private slots: + void checkValid(); + + void beginDownloading(); + void doneDownload(); + void doneIndexing(); + void doneCheckout(); + void rootCloneFinished(); + + void onCheckoutProgress(const QString& fileName, + quint64 totalFiles, + quint64 completedFiles); + + void onTransportProgress(quint32 totalObjects, + quint32 indexedObjects, + quint32 receivedObjects, + quint64 receivedBytes); + +private: + ProgressDlg* mProgress = nullptr; + + QPointer mCloneWdgt; + QPointer mCloneOptsWdgt; +}; diff --git a/Modules/Repository/CloneRepositoryDlg.ui b/Modules/Remotes/CloneWdgt.ui similarity index 51% rename from Modules/Repository/CloneRepositoryDlg.ui rename to Modules/Remotes/CloneWdgt.ui index 79b596c8..6e5b7d24 100644 --- a/Modules/Repository/CloneRepositoryDlg.ui +++ b/Modules/Remotes/CloneWdgt.ui @@ -1,44 +1,44 @@ - CloneRepositoryDlg - + CloneWdgt + 0 0 - 602 - 107 + 518 + 70 - Clone repository - - - true + Form - - + + + + + 0 + 0 + + - Clone Options + From: - - true + + txtUrl - - - - Append repository name - - + + + true - - + + 0 @@ -46,48 +46,34 @@ - To: + Alias: - txtPath + txtRemoteAlias - - + + - + 0 0 - - true - - - - - - ... + - - - - - - - 0 - 0 - + + origin true - - + + 0 @@ -95,78 +81,37 @@ - From: + To: - txtUrl + txtPath - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + true - - - - 0 - - - - - - - - 0 - 0 - - + + - Alias: - - - txtRemoteAlias + ... - - - - - 0 - 0 - - + + - - - - origin + Append repository name - + true - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -176,44 +121,6 @@
libMacGitverCore/Widgets/LineEdit.h
- - txtUrl - txtPath - chkAppendRepoName - - - - buttonBox - accepted() - CloneRepositoryDlg - accept() - - - 222 - 342 - - - 157 - 274 - - - - - buttonBox - rejected() - CloneRepositoryDlg - reject() - - - 290 - 348 - - - 286 - 274 - - - - +
diff --git a/Modules/Remotes/RemotesModule.cpp b/Modules/Remotes/RemotesModule.cpp index 05e7d295..1a9ff351 100644 --- a/Modules/Remotes/RemotesModule.cpp +++ b/Modules/Remotes/RemotesModule.cpp @@ -19,6 +19,7 @@ #include "libMacGitverCore/App/MacGitver.hpp" +#include "CloneRepositoryDlg.hpp" #include "RemoteCreateEditDlg.h" #include "RemotesModule.h" #include "RemotesView.h" @@ -35,6 +36,7 @@ BlueSky::View* RemotesModule::createRemotesView() void RemotesModule::initialize() { setupActions( this ); + acCloneAC->mergeInto( "CloneMP" ); acRemotesAC->mergeInto( "RemotesMP" ); MacGitver::self().registerView( "Remotes", tr( "Remotes" ), @@ -46,6 +48,11 @@ void RemotesModule::deinitialize() MacGitver::self().unregisterView( "Remotes" ); } +void RemotesModule::onClone() +{ + CloneDlg().exec(); +} + void RemotesModule::onRemoteCreate() { RemoteCreateEditDlg().exec(); diff --git a/Modules/Remotes/RemotesModule.h b/Modules/Remotes/RemotesModule.h index fd72b521..c1aefc09 100644 --- a/Modules/Remotes/RemotesModule.h +++ b/Modules/Remotes/RemotesModule.h @@ -37,6 +37,10 @@ class RemotesModule : public Module, public RemotesModuleActions private: static BlueSky::View* createRemotesView(); +private slots: + // CloneAC + void onClone(); + private slots: void onRemoteCreate(); }; diff --git a/Modules/Remotes/RemotesModuleActions.hid b/Modules/Remotes/RemotesModuleActions.hid index 4f7ffe4a..26987603 100644 --- a/Modules/Remotes/RemotesModuleActions.hid +++ b/Modules/Remotes/RemotesModuleActions.hid @@ -19,6 +19,15 @@ Ui RemotesModuleActions { Container RemotesAC { Menu Remotes { + Container CloneAC { + + Action Clone { + Text "&Clone..."; + _ConnectTo onClone(); + }; + + }; + Text "R&emotes"; diff --git a/Modules/Repository/CMakeLists.txt b/Modules/Repository/CMakeLists.txt index 4960338b..f7bb8142 100644 --- a/Modules/Repository/CMakeLists.txt +++ b/Modules/Repository/CMakeLists.txt @@ -17,9 +17,6 @@ SET( SRC_FILES RepositoryContext.cpp CreateRepositoryDlg.cpp - CloneRepositoryDlg.cpp - CloneOptionsWdgt.cpp - ProgressDlg.cpp ) SET( HDR_FILES @@ -31,17 +28,11 @@ SET( HDR_FILES RepositoryContext.hpp CreateRepositoryDlg.h - CloneRepositoryDlg.h - CloneOptionsWdgt.hpp - ProgressDlg.hpp ) SET( UI_FILES CreateRepositoryDlg.ui - CloneRepositoryDlg.ui - CloneOptionsWdgt.ui - ProgressDlg.ui ) SET( HID_FILES @@ -63,4 +54,3 @@ ADD_MGV_MODULE( ${UIC_FILES} ${UI_FILES} ${HIC_FILES} ${HID_FILES} ) - diff --git a/Modules/Repository/CloneOptionsWdgt.cpp b/Modules/Repository/CloneOptionsWdgt.cpp deleted file mode 100644 index f596a257..00000000 --- a/Modules/Repository/CloneOptionsWdgt.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "CloneOptionsWdgt.hpp" - -#include - - -CloneOptionsWdgt::CloneOptionsWdgt(QWidget *parent) : - QWidget(parent) -{ - setupUi(this); - - // margin is controlled by the parent widget's layout - layout()->setMargin(0); -} - -CloneOptionsWdgt::~CloneOptionsWdgt() -{ -} - -void CloneOptionsWdgt::on_txtCloneMode_currentIndexChanged(int index) -{ - // Note: clone modes are fixed - mCloneMode = static_cast( index ); - grpSubmodules->setEnabled( mCloneMode == cmCheckout ); -} - -void CloneOptionsWdgt::on_chkInitSubmodules_toggled(bool checked) -{ - chkSubmodulesRecursive->setEnabled( checked ); - if( !checked ) - { - chkSubmodulesRecursive->setChecked( false ); - } -} diff --git a/Modules/Repository/CloneOptionsWdgt.hpp b/Modules/Repository/CloneOptionsWdgt.hpp deleted file mode 100644 index c96d6594..00000000 --- a/Modules/Repository/CloneOptionsWdgt.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef UICLONEOPTIONS_HPP -#define UICLONEOPTIONS_HPP - -#include - -#include "ui_CloneOptionsWdgt.h" - - -class CloneOptionsWdgt : public QWidget, Ui::CloneOptionsWdgt -{ - Q_OBJECT - - friend class CloneRepositoryDlg; - -public: - enum CloneMode { - cmCheckout = 0, - cmBare, - cmMirror - }; - -public: - explicit CloneOptionsWdgt(QWidget *parent = 0); - ~CloneOptionsWdgt(); - -private slots: - void on_txtCloneMode_currentIndexChanged(int index); - - void on_chkInitSubmodules_toggled(bool checked); - -private: - CloneMode mCloneMode; -}; - -#endif // UICLONEOPTIONS_HPP diff --git a/Modules/Repository/CloneRepositoryDlg.cpp b/Modules/Repository/CloneRepositoryDlg.cpp deleted file mode 100644 index ed59a40e..00000000 --- a/Modules/Repository/CloneRepositoryDlg.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/* - * MacGitver - * Copyright (C) 2012 Sascha Cunz - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License (Version 2) as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without - * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; if - * not, see . - * - */ - -#include -#include - -#include "libMacGitverCore/Config/Config.h" - -#include "libGitWrap/Operations/CloneOperation.hpp" - -#include "libMacGitverCore/App/MacGitver.hpp" - -#include "CloneRepositoryDlg.h" -#include "CloneOptionsWdgt.hpp" -#include "ProgressDlg.hpp" - -CloneRepositoryDlg::CloneRepositoryDlg() - : mProgress( NULL ) -{ - setupUi( this ); - - connect( btnBrowseTo, SIGNAL(clicked()), SLOT(onBrowse()) ); - connect( txtPath, SIGNAL(textChanged(QString)), SLOT(checkValid()) ); - - checkValid(); -} - -void CloneRepositoryDlg::onBrowse() -{ - QString fn = txtPath->text(); - if( fn.isEmpty() ) - { - fn = Config::self().get( "Repository/lastUsedDir", QDir::homePath() ).toString(); - } - - QFileDialog* fd = new QFileDialog( this, trUtf8( "Select repository location" ) ); - - #ifdef Q_OS_MAC - fd->setFilter( QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden ); - #else - fd->setFileMode( QFileDialog::Directory ); - #endif - - fd->setDirectory( fn ); - fd->setAcceptMode( QFileDialog::AcceptSave ); - fd->open( this, SLOT(onBrowseHelper(QString)) ); -} - -void CloneRepositoryDlg::onBrowseHelper( const QString& directory ) -{ - if( directory.isEmpty() ) - { - return; - } - - Config::self().set( "Repository/lastUsedDir", directory ); - txtPath->setText( directory ); -} - -void CloneRepositoryDlg::checkValid() -{ - bool okay = !txtPath->text().isEmpty() && - !txtUrl->text().isEmpty(); - - QDir wanted( QDir::toNativeSeparators( txtPath->text() ) ); - if( wanted.exists() ) - { - QStringList sl = wanted.entryList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot ); - if( sl.count() > 0 ) - { - okay = false; - } - } - - buttonBox->button( QDialogButtonBox::Ok )->setEnabled( okay ); -} - -void CloneRepositoryDlg::accept() -{ - Git::CloneOperation* clone = new Git::CloneOperation( this ); - QString repoName = QUrl( txtUrl->text() ).adjusted( QUrl::NormalizePathSegments | QUrl::StripTrailingSlash ).toString(); - QString targetDir = QUrl( txtPath->text() ).adjusted( QUrl::NormalizePathSegments | QUrl::StripTrailingSlash ).toString(); - - if ( chkAppendRepoName->isChecked() ) - { - targetDir += QString::fromUtf8("/%1") - .arg( QUrl( repoName ).fileName() ); - } - - clone->setBackgroundMode( true ); - clone->setUrl( repoName ); - clone->setPath( targetDir ); - clone->setRemoteAlias( txtRemoteAlias->text() ); - - if ( mCloneOpts ) - { - clone->setBare( mCloneOpts->mCloneMode == CloneOptionsWdgt::cmBare ); - clone->setReference( mCloneOpts->txtBranch->text() ); - clone->setDepth( mCloneOpts->txtCloneDepth->value() ); - } - - mProgress = new ProgressDlg; - mProgress->show(); - mProgress->setCurrent( clone ); - - if( repoName.endsWith( QLatin1String( ".git" ) ) ) - repoName = repoName.left( repoName.length() - 4 ); - - if( repoName.lastIndexOf( QChar( L'/' ) ) != -1 ) - repoName.remove( 0, repoName.lastIndexOf( QChar( L'/' ) ) + 1 ); - - mAction = tr( "Cloning %1" ).arg( repoName ); - mProgress->beginStep( mAction ); - mStates.clear(); - mStates[ Prepare ] = Current; - mStates[ Download ] = Open; - mStates[ Index ] = Open; - mStates[ Checkout ] = Open; - updateAction(); - - connect( clone, SIGNAL(finished()), this, SLOT(rootCloneFinished()) ); - connect( clone, SIGNAL(transportProgress(quint32,quint32,quint32,quint64)), - this, SLOT(beginDownloading()) ); - connect( clone, SIGNAL(doneDownloading()), this, SLOT(doneDownload()) ); - connect( clone, SIGNAL(doneIndexing()), this, SLOT(doneIndexing()) ); - connect( clone, SIGNAL(doneCheckout()), this, SLOT(doneCheckout()) ); - clone->execute(); -} - -void CloneRepositoryDlg::beginDownloading() -{ - disconnect( sender(), SIGNAL(transportProgress(quint32,quint32,quint32,quint64)), - this, SLOT(beginDownloading()) ); - - mStates[ Prepare ] = Done; - mStates[ Download ] = Current; - mStates[ Index ] = Current; - updateAction(); -} - -void CloneRepositoryDlg::doneDownload() -{ - mStates[ Download ] = Done; - updateAction(); -} - -void CloneRepositoryDlg::doneIndexing() -{ - mStates[ Index ] = Done; - - if( mStates.contains( Checkout ) ) - mStates[ Checkout ] = Current; - - updateAction(); -} - -void CloneRepositoryDlg::doneCheckout() -{ - mStates[ Checkout ] = Done; - updateAction(); -} - -void CloneRepositoryDlg::rootCloneFinished() -{ - Git::BaseOperation* operation = static_cast( sender() ); - Q_ASSERT( operation ); - - if ( operation->result() ) - { - mProgress->setDone(); - return; - } - - QMessageBox::critical(mProgress, tr("Errors while cloning repository."), - tr("Cloning failed:\nGit Message: %1").arg(operation->result().errorText())); - mProgress->reject(); -} - -void CloneRepositoryDlg::updateAction() -{ - QStringList open, current, done; - - QHash< Tasks, QString > t; - t.insert( Prepare, tr( "Prepare" ) ); - t.insert( Index, tr( "Indexing" ) ); - t.insert( Download, tr( "Downloading" ) ); - t.insert( Checkout, tr( "Check out" ) ); - - foreach( Tasks task, mStates.keys() ) - { - QStringList* sl = NULL; - - if( mStates[ task ] == Open ) sl = &open; - else if( mStates[ task ] == Done ) sl = &done; - else if( mStates[ task ] == Current ) sl = ¤t; - - if( sl ) - sl->append( t[ task ] ); - } - - mProgress->setAction( mAction, open, current, done ); -} - -void CloneRepositoryDlg::on_btnCloneopts_toggled(bool checked) -{ - if ( checked ) - { - if ( !mCloneOpts ) - { - mCloneOpts = new CloneOptionsWdgt(); - } - - optsLayout->addWidget( mCloneOpts ); - mCloneOpts->show(); - } - else - { - optsLayout->removeWidget( mCloneOpts ); - if ( mCloneOpts ) - { - mCloneOpts->hide(); - } - layout()->activate(); - resize( width(), minimumSizeHint().height() ); - } -} diff --git a/Modules/Repository/CloneRepositoryDlg.h b/Modules/Repository/CloneRepositoryDlg.h deleted file mode 100644 index 0599c13e..00000000 --- a/Modules/Repository/CloneRepositoryDlg.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * MacGitver - * Copyright (C) 2012 Sascha Cunz - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License (Version 2) as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without - * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; if - * not, see . - * - */ - -#ifndef CLONE_REPOSITORY_DLG_H -#define CLONE_REPOSITORY_DLG_H - -#include "ui_CloneRepositoryDlg.h" - -#include - -class ProgressDlg; -class CloneOptionsWdgt; - -class CloneRepositoryDlg : public QDialog, Ui::CloneRepositoryDlg -{ - Q_OBJECT -public: - CloneRepositoryDlg(); - -protected: - void accept(); - -private slots: - void onBrowse(); - void onBrowseHelper( const QString& directory ); - void checkValid(); - - void beginDownloading(); - void doneDownload(); - void doneIndexing(); - void doneCheckout(); - void rootCloneFinished(); - - void on_btnCloneopts_toggled(bool checked); - -private: - enum State { Unused, Open, Done, Current }; - enum Tasks { Prepare, Download, Index, Checkout }; - -private: - void updateAction(); - -private: - QPointer mCloneOpts; - - ProgressDlg* mProgress; - QString mAction; - QHash< Tasks, State > mStates; - -}; - -#endif diff --git a/Modules/Repository/ProgressDlg.cpp b/Modules/Repository/ProgressDlg.cpp deleted file mode 100644 index 832e0862..00000000 --- a/Modules/Repository/ProgressDlg.cpp +++ /dev/null @@ -1,157 +0,0 @@ - -#include -#include -#include -#include -#include - -#include "ProgressDlg.hpp" - - -ProgressDlg::ProgressDlg() - : BlueSky::Dialog() - , mDone( false ) -{ - setupUi( this ); - - QPushButton* close = buttonBox->button( QDialogButtonBox::Close ); - close->setEnabled( false ); - connect( close, SIGNAL(clicked()), this, SLOT(close ()) ); - - QPalette p; - p.setColor( QPalette::Base, p.color( QPalette::Window ) ); - p.setColor( QPalette::Text, p.color( QPalette::WindowText ) ); - txtLog->setPalette( p ); - -} - -void ProgressDlg::setAction( const QString& action, - const QStringList& open, - const QStringList& current, - const QStringList& done ) -{ - QString act = action; - - foreach( QString s, done ) - { - act += QStringLiteral( " (" ) % s % QStringLiteral( ")" ); - } - - foreach( QString s, current ) - { - act += QStringLiteral( " (" ) % s % QStringLiteral( ")" ); - } - - foreach( QString s, open ) - { - act += QStringLiteral( " (" ) % s % QStringLiteral( ")" ); - } - - lblAction->setText( act ); -} - -void ProgressDlg::setCurrent(QObject* current) -{ - mCurrent = current; - - connect( mCurrent, SIGNAL(remoteMessage(QString)), - this, SLOT(remoteMessage(QString)) ); - connect( mCurrent, SIGNAL(transportProgress(quint32, quint32, quint32, quint64)), - this, SLOT(transportProgress(quint32, quint32, quint32, quint64)) ); -} - -void ProgressDlg::closeEvent( QCloseEvent* ev ) -{ - if( !mDone ) - { - ev->ignore(); - return; - } - - QDialog::closeEvent( ev ); -} - -void ProgressDlg::transportProgress( quint32 totalObjects, - quint32 indexedObjects, - quint32 receivedObjects, - quint64 receivedBytes ) -{ - QString recv; - if( receivedBytes > 1024 * 1024 * 950 ) /* 950 is so we get 0.9 gb */ - { - recv = QString::number( receivedBytes / (1024*1024*1024.0), 'f', 2 ) % QStringLiteral( " Gb" ); - } - else if( receivedBytes > 1024 * 950 ) - { - recv = QString::number( receivedBytes / (1024*1024.0), 'f', 2 ) % QStringLiteral( " Mb" ); - } - else if( receivedBytes > 950 ) - { - recv = QString::number( receivedBytes / 1024.0, 'f', 2 ) % QStringLiteral( " Kb" ); - } - else - { - recv = QString::number( receivedBytes ); - } - lblTransferSize->setText( recv ); - - progressBar->setRange( 0, totalObjects * 2 ); - progressBar->setValue( indexedObjects + receivedObjects ); -} - -void ProgressDlg::remoteMessage( const QString& msg ) -{ - mRawRemoteMessage += msg; - - QString output; - QChar outputBuffer[ 256 ]; - int outBufPos = 0, outBufLen = 0; - - for( int i = 0; i < mRawRemoteMessage.length(); ++i ) - { - if( mRawRemoteMessage[ i ] == QChar( L'\r' ) ) - { - outBufPos = 0; - } - else if( mRawRemoteMessage[ i ] == QChar( L'\n' ) ) - { - if( outBufLen ) - output += QString( outputBuffer, outBufLen ); - output += QChar( L'\n' ); - outBufPos = outBufLen = 0; - } - else - { - outputBuffer[ outBufPos++] = mRawRemoteMessage[ i ]; - outBufLen = qMax( outBufLen, outBufPos ); - } - } - - if( outBufLen ) - output += QString( outputBuffer, outBufLen ); - - QString log = mBaseLog % QStringLiteral( "
" ) % - output.replace( QChar( L'\n' ), QLatin1String("
") ).simplified(); - - txtLog->setHtml( log ); -} - -void ProgressDlg::beginStep( const QString& step ) -{ - mBaseLog += tr( "%1
" ).arg( step ); - txtLog->setHtml( mBaseLog ); -} - -void ProgressDlg::finalizeStep() -{ - mBaseLog = txtLog->toHtml() % QStringLiteral( "
" ); - mRawRemoteMessage = QString(); - - txtLog->setHtml( mBaseLog ); -} - -void ProgressDlg::setDone() -{ - mDone = true; - buttonBox->button( QDialogButtonBox::Close )->setEnabled( true ); -} diff --git a/Modules/Repository/ProgressDlg.hpp b/Modules/Repository/ProgressDlg.hpp deleted file mode 100644 index 7b1ecd69..00000000 --- a/Modules/Repository/ProgressDlg.hpp +++ /dev/null @@ -1,43 +0,0 @@ - -#ifndef MODREPO_PROGRESS_DLG_HPP -#define MODREPO_PROGRESS_DLG_HPP - -#include "libBlueSky/Dialog.hpp" - -#include "ui_ProgressDlg.h" - - -class ProgressDlg - : public BlueSky::Dialog - , private Ui::ProgressDlg -{ - Q_OBJECT -public: - ProgressDlg(); - -public: - void setAction( const QString& action, const QStringList& open, - const QStringList& current, const QStringList& done ); - void setCurrent(QObject* current); - -private slots: - void transportProgress( quint32 totalObjects, quint32 indexedObjects, - quint32 receivedObjects, quint64 receivedBytes ); - void remoteMessage( const QString& msg ); - -public: - void setDone(); - void beginStep( const QString& step ); - void finalizeStep(); - -protected: - void closeEvent( QCloseEvent* ev ); - -private: - bool mDone; - QString mBaseLog; - QObject* mCurrent; - QString mRawRemoteMessage; -}; - -#endif diff --git a/Modules/Repository/ProgressDlg.ui b/Modules/Repository/ProgressDlg.ui deleted file mode 100644 index c2670eb4..00000000 --- a/Modules/Repository/ProgressDlg.ui +++ /dev/null @@ -1,142 +0,0 @@ - - - ProgressDlg - - - Qt::ApplicationModal - - - - 0 - 0 - 484 - 291 - - - - - 0 - 0 - - - - Progress - - - false - - - true - - - - - - QDialogButtonBox::Close - - - - - - - - 0 - 0 - - - - - 0 - 60 - - - - Log - - - - QLayout::SetDefaultConstraint - - - 0 - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - - - - - - - - Current operation - - - - -1 - - - 2 - - - 3 - - - - - - 0 - 0 - - - - - - - - - - - - - - - <b>Progress</(b> - - - - - - - <b>Action</b> - - - - - - - - - - - - - - - - - - diff --git a/Modules/Repository/RepositoryModule.cpp b/Modules/Repository/RepositoryModule.cpp index a77bd736..df1b7e5e 100644 --- a/Modules/Repository/RepositoryModule.cpp +++ b/Modules/Repository/RepositoryModule.cpp @@ -28,7 +28,6 @@ #include "RepositoryModule.h" #include "RepoTreeView.hpp" -#include "CloneRepositoryDlg.h" #include "CreateRepositoryDlg.h" RepositoryModule::RepositoryModule() @@ -130,11 +129,6 @@ void RepositoryModule::onRepositoryOpenHelper() // repo.isBare() ? repoDir : repo.basePath() ); } -void RepositoryModule::onRepositoryClone() -{ - CloneRepositoryDlg().exec(); -} - void RepositoryModule::onRecentRepositoryOpen( const QVariant& path ) { QString repoPath = path.toString(); diff --git a/Modules/Repository/RepositoryModule.h b/Modules/Repository/RepositoryModule.h index ac7086b2..d0799ca4 100644 --- a/Modules/Repository/RepositoryModule.h +++ b/Modules/Repository/RepositoryModule.h @@ -57,7 +57,6 @@ class RepositoryModule private slots: void onRepositoryCreate(); - void onRepositoryClone(); void onRepositoryOpen(); void onRecentRepositoryOpen( const QVariant& path ); diff --git a/Modules/Repository/RepositoryModule.hid b/Modules/Repository/RepositoryModule.hid index 96b7a472..92778f6f 100644 --- a/Modules/Repository/RepositoryModule.hid +++ b/Modules/Repository/RepositoryModule.hid @@ -25,11 +25,6 @@ Ui RepositoryActions { ConnectTo onRepositoryOpen(); }; - Action RepositoryClone { - Text "&Clone..."; - ConnectTo onRepositoryClone(); - }; - Action RepositoryCreate { Text "&New..."; Shortcut "Ctrl+N"; @@ -45,11 +40,10 @@ Ui RepositoryActions { Container RepositoryMenuAC { + Action RepositoryCreate; Action RepositoryOpen; Menu RepoOpenRecent; Separator; - Action RepositoryCreate; - Action RepositoryClone; };