1
0
mirror of https://github.com/QRouland/UTPass.git synced 2025-01-24 07:36:39 +00:00

Add delete password store feature

This commit is contained in:
Quentin Rouland 2025-01-20 14:46:47 +01:00
parent 0eb8920856
commit ebfc6f500d
15 changed files with 329 additions and 65 deletions

View File

@ -11,8 +11,6 @@ extern "C" {
* @class CloneJob * @class CloneJob
* @brief A class to handle cloning Git repositories in a separate thread. * @brief A class to handle cloning Git repositories in a separate thread.
* *
* The CloneJob runs a cloning process in a separate thread and emits
* signals to indicate the success or failure of the operation.
*/ */
class CloneJob : public GitJob class CloneJob : public GitJob
{ {
@ -21,9 +19,8 @@ class CloneJob : public GitJob
/** /**
* @brief The main function that performs the cloning operation. * @brief The main function that performs the cloning operation.
* *
* This function overrides the `run()` method from the `QThread` class. It is * Handles the process of cloning a repository from the specified URL
* executed when the thread is started, and it handles the process of cloning * to the target path.
* a repository from the specified URL to the target path.
*/ */
void run() override; void run() override;
@ -31,8 +28,7 @@ signals:
/** /**
* @brief Signal emitted when the cloning operation is complete. * @brief Signal emitted when the cloning operation is complete.
* *
* This signal is emitted once the cloning operation finishes. It notifies * This signal is emitted once the cloning operation finishes.
* the caller whether an error occurred during the cloning process.
* *
* @param err A boolean indicating whether an error occurred during cloning. * @param err A boolean indicating whether an error occurred during cloning.
* `true` if an error occurred, `false` if the clone was successful. * `true` if an error occurred, `false` if the clone was successful.
@ -47,8 +43,6 @@ private:
* @brief Prepares the temporary directory for cloning. * @brief Prepares the temporary directory for cloning.
* *
* This method sets up the required directory structure for cloning a repository. * This method sets up the required directory structure for cloning a repository.
* It is called before the cloning process begins to ensure that a suitable
* location exists for the repository to be cloned into.
* *
* @return A `QDir` object representing the prepared temporary directory. * @return A `QDir` object representing the prepared temporary directory.
*/ */

View File

@ -30,10 +30,8 @@ typedef std::variant<HTTP, HTTPUserPass, SSHPass, SSHKey> cred_type;
* @class GitJob * @class GitJob
* @brief A class that manages Git-related tasks using libgit2. * @brief A class that manages Git-related tasks using libgit2.
* *
* The GitJob class is used to perform Git operations, such as cloning repositories, * The GitJob class is used abstraction class to perform Git operations, such as cloning repositories,
* in a separate thread. It utilizes libgit2 for interacting with Git repositories. * in a separate thread using libgit2 for interacting with Git repositories.
* The class handles credentials for repository access and allows the user to specify
* the type of credentials to use.
*/ */
class GitJob : public QThread class GitJob : public QThread
{ {

View File

@ -8,6 +8,7 @@ set(
gpg.cpp gpg.cpp
passkeymodel.h passkeymodel.h
passphraseprovider.h passphraseprovider.h
jobs/rmjob.cpp
) )
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)

View File

@ -25,7 +25,6 @@
#include "gpg.h" #include "gpg.h"
#include "passkeymodel.h"
#include "passphraseprovider.h" #include "passphraseprovider.h"

View File

@ -0,0 +1,24 @@
#include "rmjob.h"
RmJob::RmJob(QString path):
m_path(path)
{
this->setObjectName("RmJob");
}
void RmJob::run()
{
auto info = QFileInfo(this->m_path);
if (info.isFile()) {
auto file = QFile(this->m_path);
file.remove();
emit resultReady(false);
} else if (info.isDir()) {
auto dir = QDir(this->m_path);
dir.removeRecursively();
emit resultReady(false);
} else {
emit resultReady(true);
}
}

46
plugins/Pass/jobs/rmjob.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef RMJOB_H
#define RMJOB_H
#include <QThread>
#include <QDir>
/**
* @class RmJob
* @brief A class to handle removing recursively a path in a separate thread.
*
*/
class RmJob : public QThread
{
Q_OBJECT
/**
* @brief The main function that performs the rm operation.
*
* Handles the process of removing recursively a target path.
*/
void run() override;
signals:
/**
* @brief Signal emitted when the rm operation is complete.
*
* @param err A boolean indicating whether an error occurred during cloning.
* `true` if an error occurred, `false` if the clone was successful.
*/
void resultReady(const bool err);
private:
QString m_path; ///< The path to be removed.
public:
/**
* @brief Constructor for the RmJob class.
*
* Initializes the RmJob with the specified path to be removed.
*
* @param path Path to be remove.
*/
RmJob(QString path);
};
#endif // RMJOB_H

View File

@ -2,6 +2,7 @@
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QtCore/QDir> #include <QtCore/QDir>
#include "jobs/rmjob.h"
#include "pass.h" #include "pass.h"
#include "gpg.h" #include "gpg.h"
#include "passkeymodel.h" #include "passkeymodel.h"
@ -49,6 +50,7 @@ bool Pass::show(QUrl url)
return this->m_gpg->decryptFromFile(path); return this->m_gpg->decryptFromFile(path);
} }
void Pass::showResult(Error err, QString plain_text) void Pass::showResult(Error err, QString plain_text)
{ {
qDebug() << "Pass show Result"; qDebug() << "Pass show Result";
@ -67,6 +69,31 @@ void Pass::showResult(Error err, QString plain_text)
this->m_sem->release(1); this->m_sem->release(1);
} }
bool Pass::deletePasswordStore()
{
qInfo() << "Pass delete Password Store";
auto job = new RmJob(this->password_store());
qDebug() << "Delete Password Store at " << this->password_store();
connect(job, &RmJob::resultReady, this, &Pass::deletePasswordStoreResult);
connect(job, &RmJob::finished, job, &QObject::deleteLater);
job->start();
return true;
}
void Pass::deletePasswordStoreResult(bool err)
{
qDebug() << "Pass delete Password StoreResult";
if (err) { //dir.removeRecursively()) {
qInfo() << "Pass delete Password Store Failed";
emit deletePasswordStoreFailed("failed to delete password store");
} else {
qInfo() << "Pass delete Password Store Succeed";
emit deletePasswordStoreSucceed();
}
}
bool Pass::deleteGPGKey(PassKeyModel* key) bool Pass::deleteGPGKey(PassKeyModel* key)
{ {
if (!this->m_sem->tryAcquire(1, 500)) { if (!this->m_sem->tryAcquire(1, 500)) {

View File

@ -49,6 +49,12 @@ private slots:
*/ */
void getAllGPGKeysResult(Error err, std::vector<GpgME::Key> keys_info); void getAllGPGKeysResult(Error err, std::vector<GpgME::Key> keys_info);
/**
* @brief Slot to handle the result of a delete Password Store operation.
* @param err True if an error occurred during the operation.
*/
void deletePasswordStoreResult(bool err);
signals: signals:
// GPG-related signals // GPG-related signals
/** /**
@ -111,6 +117,18 @@ signals:
*/ */
void showCancelled(); void showCancelled();
/**
* @brief Emitted when the password store is successfully deleted.
*/
void deletePasswordStoreSucceed();
/**
* @brief Emitted when deleting the password store fails.
* @param message The error message describing the failure.
*/
void deletePasswordStoreFailed(QString message);
private: private:
QString m_password_store; /**< The path to the password store. */ QString m_password_store; /**< The path to the password store. */
std::unique_ptr<Gpg> m_gpg; /**< The GPG instance used for encryption/decryption. */ std::unique_ptr<Gpg> m_gpg; /**< The GPG instance used for encryption/decryption. */
@ -141,22 +159,22 @@ public:
// GPG-related methods // GPG-related methods
/** /**
* @brief Deletes the specified GPG key. * @brief Launch the job to delete the specified GPG key.
* @param key The PassKeyModel to delete. * @param key The PassKeyModel to delete.
* @return True if the operation was successful, false otherwise. * @return True if the job was start successfully, false otherwise.
*/ */
Q_INVOKABLE bool deleteGPGKey(PassKeyModel* key); Q_INVOKABLE bool deleteGPGKey(PassKeyModel* key);
/** /**
* @brief Imports a GPG key from the given URL. * @brief Launch the job to import a GPG key from the given URL.
* @param url The URL to import the GPG key from. * @param url The URL to import the GPG key from.
* @return True if the operation was successful, false otherwise. * @return True if the job was start was successfully, false otherwise.
*/ */
Q_INVOKABLE bool importGPGKey(QUrl url); Q_INVOKABLE bool importGPGKey(QUrl url);
/** /**
* @brief Retrieves all GPG keys. * @brief Launch the to retrieve all GPG keys.
* @return True if the operation was successful, false otherwise. * @return True if the job was start was successfully, false otherwise.
*/ */
Q_INVOKABLE bool getAllGPGKeys(); Q_INVOKABLE bool getAllGPGKeys();
@ -170,11 +188,17 @@ public:
// Password store-related methods // Password store-related methods
/** /**
* @brief Shows the password associated with the specified URL. * @brief Launch the job to shows the password associated with the specified URL.
* @param url The URL pointing to the password store entry. * @param url The URL pointing to the password store entry.
* @return True if the operation was successful, false otherwise. * @return True if the job was start successfully, false otherwise.
*/ */
Q_INVOKABLE bool show(QUrl url); Q_INVOKABLE bool show(QUrl url);
/**
* @brief Launch the job to delete the password store.
* @return True if if the job was start successfully, false otherwise.
*/
Q_INVOKABLE bool deletePasswordStore();
}; };
#endif #endif

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: utpass.qrouland\n" "Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-20 11:22+0100\n" "POT-Creation-Date: 2025-01-20 14:43+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -79,26 +79,32 @@ msgstr ""
msgid "Info" msgid "Info"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:37 #: ../qml/pages/PasswordList.qml:43
msgid "" msgid "No password found"
"No password found<br>You can import a password store by cloning or importing "
"a zip in the settings"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:71 ../qml/pages/settings/InfoKeys.qml:171 #: ../qml/pages/PasswordList.qml:54
msgid "You can import a password store by cloning or"
msgstr ""
#: ../qml/pages/PasswordList.qml:60
msgid "importing a password store zip in the settings"
msgstr ""
#: ../qml/pages/PasswordList.qml:93
msgid "Decryption failed !" msgid "Decryption failed !"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:85 #: ../qml/pages/PasswordList.qml:107
msgid "Back" msgid "Back"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:92 ../qml/pages/headers/MainHeader.qml:9 #: ../qml/pages/PasswordList.qml:114 ../qml/pages/headers/MainHeader.qml:9
#: ../qml/pages/headers/StackHeader.qml:9 UTPass.desktop.in.h:1 #: ../qml/pages/headers/StackHeader.qml:9 UTPass.desktop.in.h:1
msgid "UTPass" msgid "UTPass"
msgstr "" msgstr ""
#: ../qml/pages/headers/MainHeader.qml:26 ../qml/pages/settings/Settings.qml:70 #: ../qml/pages/headers/MainHeader.qml:26 ../qml/pages/settings/Settings.qml:75
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
@ -106,6 +112,35 @@ msgstr ""
msgid "Search" msgid "Search"
msgstr "" msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:41
#: ../qml/pages/settings/Settings.qml:58
msgid "Delete Password Store"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:57
msgid "You're are about to delete<br>the current Password Store.<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:58
#: ../qml/pages/settings/ImportZip.qml:62
#: ../qml/pages/settings/InfoKeys.qml:140
#: ../qml/pages/settings/git/ImportGitClone.qml:55
msgid "Yes"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:71
msgid "Password Store removal failed !"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:80
msgid "Password Store deleted !"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:92
#: ../qml/pages/settings/InfoKeys.qml:182
msgid "Info Keys"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:57 #: ../qml/pages/settings/ImportKeyFile.qml:57
msgid "Key import failed !" msgid "Key import failed !"
msgstr "" msgstr ""
@ -123,18 +158,12 @@ msgid ""
"Importing a new zip will delete<br>any existing password store!<br>Continue ?" "Importing a new zip will delete<br>any existing password store!<br>Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:62
#: ../qml/pages/settings/InfoKeys.qml:137
#: ../qml/pages/settings/git/ImportGitClone.qml:54
msgid "Yes"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:75 #: ../qml/pages/settings/ImportZip.qml:75
msgid "Password store import failed !" msgid "Password store import failed !"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:84 #: ../qml/pages/settings/ImportZip.qml:84
#: ../qml/pages/settings/git/ImportGitClone.qml:76 #: ../qml/pages/settings/git/ImportGitClone.qml:77
msgid "Password store sucessfully imported !" msgid "Password store sucessfully imported !"
msgstr "" msgstr ""
@ -142,32 +171,32 @@ msgstr ""
msgid "Zip Password Store Import" msgid "Zip Password Store Import"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:54 #: ../qml/pages/settings/InfoKeys.qml:56
msgid "Key ID :" msgid "Key ID :"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:88 #: ../qml/pages/settings/InfoKeys.qml:90
msgid "Users IDs : " msgid "Users IDs : "
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:114 #: ../qml/pages/settings/InfoKeys.qml:117
msgid "Delete this key" msgid "Delete this key"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:136 #: ../qml/pages/settings/InfoKeys.qml:139
msgid "You're are about to delete<br>%1<br>Continue ?" msgid "You're are about to delete<br>%1<br>.Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:150 #: ../qml/pages/settings/InfoKeys.qml:153
msgid "Key removal failed !" msgid "Key removal failed !"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:159 #: ../qml/pages/settings/InfoKeys.qml:162
msgid "Key successfully deleted !" msgid "Key successfully deleted !"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:179 #: ../qml/pages/settings/InfoKeys.qml:174
msgid "Info Keys" msgid "An Error occured getting GPG keys !"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:23 #: ../qml/pages/settings/Settings.qml:23
@ -194,7 +223,7 @@ msgstr ""
msgid "Import a Password Store Zip" msgid "Import a Password Store Zip"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:62 #: ../qml/pages/settings/Settings.qml:67
msgid "Warning: importing delete any exiting Password Store" msgid "Warning: importing delete any exiting Password Store"
msgstr "" msgstr ""
@ -212,16 +241,16 @@ msgstr ""
msgid "Password" msgid "Password"
msgstr "" msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:53 #: ../qml/pages/settings/git/ImportGitClone.qml:54
msgid "" msgid ""
"Importing a git repo will delete<br>any existing password store!" "Importing a git repo will delete<br>any existing password store!"
"<br>Continue ?" "<br>Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:67 #: ../qml/pages/settings/git/ImportGitClone.qml:68
msgid "An error occured during git clone !" msgid "An error occured during git clone !"
msgstr "" msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:88 #: ../qml/pages/settings/git/ImportGitClone.qml:89
msgid "Git Clone Import" msgid "Git Clone Import"
msgstr "" msgstr ""

View File

@ -26,17 +26,39 @@ Page {
}); });
} }
Rectangle { Column {
anchors.top: passwordListHeader.bottom anchors.top: passwordListHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
visible: folderModel.count == 0 anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
Rectangle {
width: parent.width
height: units.gu(1)
}
Text { Text {
text: i18n.tr("No password found<br>You can import a password store by cloning or importing a zip in the settings") text: i18n.tr("No password found")
anchors.horizontalCenter: parent.horizontalCenter width: parent.width
anchors.verticalCenter: parent.verticalCenter horizontalAlignment: Text.AlignHCenter
}
Rectangle {
width: parent.width
height: units.gu(1)
}
Text {
text: i18n.tr("You can import a password store by cloning or")
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
Text {
text: i18n.tr("importing a password store zip in the settings")
width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }

View File

@ -0,0 +1,92 @@
import "../../components"
import "../../dialogs"
import "../headers"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Pass 1.0
import QtQuick 2.4
Page {
id: deleteRepoPage
Component.onCompleted: {
Pass.deletePasswordStoreSucceed.connect(function(keys_info) {
PopupUtils.open(deleteRepoPagePageDeleteSuccess);
});
Pass.deletePasswordStoreFailed.connect(function(message) {
PopupUtils.open(deleteRepoPagePageDeleteError);
});
}
Column {
id: deleteRepoPageListView
anchors.top: deleteRepoPageHeader.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
spacing: units.gu(1)
Rectangle {
width: parent.width
height: units.gu(1)
}
Button {
id: buttonDeleteKey
width: parent.width
text: i18n.tr("Delete Password Store")
color: theme.palette.normal.negative
onClicked: {
PopupUtils.open(deleteRepoPagePageDeleteValidation, deleteRepoPage);
}
}
}
Component {
id: deleteRepoPagePageDeleteValidation
SimpleValidationDialog {
text: i18n.tr("You're are about to delete<br>the current Password Store.<br>Continue ?")
continueText: i18n.tr("Yes")
continueColor: theme.palette.normal.negative
onValidated: {
var status = Pass.deletePasswordStore();
}
}
}
Component {
id: deleteRepoPagePageDeleteError
ErrorDialog {
textError: i18n.tr("Password Store removal failed !")
}
}
Component {
id: deleteRepoPagePageDeleteSuccess
SuccessDialog {
textSuccess: i18n.tr("Password Store deleted !")
onDialogClosed: {
pageStack.pop();
pageStack.pop();
}
}
}
header: StackHeader {
id: deleteRepoPageHeader
title: i18n.tr('Info Keys')
}
}

View File

@ -12,7 +12,7 @@ Page {
property QtObject currentKey property QtObject currentKey
Component.onCompleted: { Component.onCompleted: {
Pass.onGetAllGPGKeysSucceed.connect(function(keys_info) { Pass.getAllGPGKeysSucceed.connect(function(keys_info) {
infoKeysListView.model = keys_info; infoKeysListView.model = keys_info;
}); });
Pass.getAllGPGKeysFailed.connect(function(message) { Pass.getAllGPGKeysFailed.connect(function(message) {
@ -34,6 +34,8 @@ Page {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
delegate: Grid { delegate: Grid {
columns: 1 columns: 1
@ -111,6 +113,7 @@ Page {
Button { Button {
id: buttonDeleteKey id: buttonDeleteKey
width: parent.width
text: i18n.tr("Delete this key") text: i18n.tr("Delete this key")
color: theme.palette.normal.negative color: theme.palette.normal.negative
onClicked: { onClicked: {
@ -133,7 +136,7 @@ Page {
id: infoKeysPageDeleteValidation id: infoKeysPageDeleteValidation
SimpleValidationDialog { SimpleValidationDialog {
text: i18n.tr("You're are about to delete<br>%1<br>Continue ?").arg(infoKeysPage.currentKey.uid) text: i18n.tr("You're are about to delete<br>%1<br>.Continue ?").arg(infoKeysPage.currentKey.uid)
continueText: i18n.tr("Yes") continueText: i18n.tr("Yes")
continueColor: theme.palette.normal.negative continueColor: theme.palette.normal.negative
onValidated: { onValidated: {
@ -168,7 +171,7 @@ Page {
id: infoKeysPageGetAllError id: infoKeysPageGetAllError
ErrorDialog { ErrorDialog {
textError: i18n.tr("Decryption failed !") textError: i18n.tr("An Error occured getting GPG keys !")
} }
} }

View File

@ -53,6 +53,11 @@ Page {
text: i18n.tr('Import a Password Store Zip') text: i18n.tr('Import a Password Store Zip')
} }
PageStackLink {
page: Qt.resolvedUrl("DeleteRepo.qml")
text: i18n.tr('Delete Password Store')
}
Text { Text {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View File

@ -11,12 +11,12 @@ OptionSelector {
onDelegateClicked: function(i) { onDelegateClicked: function(i) {
if (i === 0) if (i === 0)
timer.setTimeout(function() { timer.setTimeout(function() {
importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml"); importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml");
}, 500); }, 500);
else if (i === 1) else if (i === 1)
timer.setTimeout(function() { timer.setTimeout(function() {
importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttpAuth.qml"); importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttpAuth.qml");
}, 500); }, 500);
} }
Timer { Timer {

View File

@ -11,10 +11,10 @@ Page {
id: importGitClonePage id: importGitClonePage
Component.onCompleted: { Component.onCompleted: {
Git.onCloneSucceed.connect(function() { Git.cloneSucceed.connect(function() {
PopupUtils.open(dialogGitCloneSuccess); PopupUtils.open(dialogGitCloneSuccess);
}); });
Git.onCloneFailed.connect(function() { Git.cloneFailed.connect(function() {
PopupUtils.open(dialogGitCloneError); PopupUtils.open(dialogGitCloneError);
}); });
PopupUtils.open(importGitCloneValidation, importGitClonePage); PopupUtils.open(importGitCloneValidation, importGitClonePage);