From a0a1c03011839bcbcb08d42b38e7c1a9a7ae0162 Mon Sep 17 00:00:00 2001 From: woblight Date: Tue, 3 Sep 2019 19:50:54 +0200 Subject: [PATCH] Add a repair option to automatically attempt to re-clone the repository --- AddonsPanel.qml | 37 +++++++++++++-- GitAddonsManager.pro | 3 +- addon.cpp | 105 ++++++++++++++++++++++++++++++++----------- addon.h | 10 ++++- control.h | 17 ------- utils.h | 45 +++++++++++++++++++ 6 files changed, 169 insertions(+), 48 deletions(-) create mode 100644 utils.h diff --git a/AddonsPanel.qml b/AddonsPanel.qml index 2b5b8c2..6058045 100644 --- a/AddonsPanel.qml +++ b/AddonsPanel.qml @@ -81,6 +81,12 @@ Page { implicitHeight: row.height hoverEnabled: true id: ma + Rectangle { + anchors.fill: row + color: "#ff8000" + visible: addon.status === Addon.Status.Error || addon.gitStatus === Addon.Error + } + RowLayout { width: parent.width id: row @@ -136,13 +142,38 @@ Page { readmeDialog.addon = addon readmeDialog.visible = true } + hoverEnabled: true } MenuItem { + enabled: addon.status == Addon.Status.Ready id: repairButton - text: qsTr("repair") - icon.name: "document-edit-decrypt" - onTriggered: addon.reset() + hoverEnabled: true + state: addon.gitStatus === Addon.Error ? "reclone" : "reset" + states: [ + State { + name: "reset" + PropertyChanges { + target: repairButton + text: qsTr("repair") + icon.name: "document-edit-decrypt" + ToolTip.visible: hovered + ToolTip.text: "Restore addon files" + onTriggered: addon.reset() + } + }, + State { + name: "reclone" + PropertyChanges { + target: repairButton + text: qsTr("repair repository") + icon.name: "view-refresh" + ToolTip.visible: hovered + ToolTip.text: "Attempts to re-download the repository" + onTriggered: addon.reclone() + } + } + ] } MenuItem { diff --git a/GitAddonsManager.pro b/GitAddonsManager.pro index 23cb4d5..b5bf8a1 100644 --- a/GitAddonsManager.pro +++ b/GitAddonsManager.pro @@ -32,7 +32,8 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin HEADERS += \ addon.h \ - control.h + control.h \ + utils.h LIBS += -lgit2 diff --git a/addon.cpp b/addon.cpp index 11599b3..3edef26 100644 --- a/addon.cpp +++ b/addon.cpp @@ -55,20 +55,16 @@ void Addon::update() { removeSubfolders(); delegate("Update", [this](){ - AutoPtr lbr(&git_reference_free); - AutoPtr tr(&git_reference_free); + auto lbr = branchRef(m_currentBranch.toLocal8Bit()); + auto tr = remoteRefForBranch(m_currentBranch.toLocal8Bit()); AutoPtr ubr(&git_reference_free); - int error = check_git_return(git_branch_lookup(&lbr, m_repo.get(), m_currentBranch.toLocal8Bit(), GIT_BRANCH_LOCAL)); - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - if (error == GIT_ENOTFOUND) - check_git_return(git_branch_lookup(&lbr, m_repo.get(), m_currentBranch.toLocal8Bit(), GIT_BRANCH_REMOTE)); - else - check_git_return(git_branch_upstream(&tr, lbr)); if (!git_branch_is_head(lbr)) - git_repository_set_head(m_repo.get(), git_reference_name(lbr)); + check_git_return(git_repository_set_head(m_repo.get(), git_reference_name(lbr))); + check_git_return(git_reference_set_target(&ubr, lbr, git_reference_target(!tr?lbr:tr), nullptr)); + + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; check_git_return(git_checkout_head(m_repo.get(), &opts)); }, [this](){updateGitStatus();}); unpackSubfolders(); @@ -110,7 +106,7 @@ void Addon::scanBranches() ref.reset(); } if (data.cb.isEmpty()) { - git_reflog *reflog; + AutoPtr reflog(&git_reflog_free); if (!git_reflog_read(&reflog, m_repo.get(), "HEAD")) { for (size_t i = 0; i < git_reflog_entrycount(reflog); i++) { auto entry = git_reflog_entry_byindex(reflog, i); @@ -265,16 +261,7 @@ void Addon::setGitStatus(Addon::GitStatus gitStatus) void Addon::updateGitStatus() { delegate("Git Status Update", [this](){ - AutoPtr tr(&git_reference_free); - check_git_return(git_branch_lookup(&tr, m_repo.get(), m_currentBranch.toLocal8Bit(), GIT_BRANCH_LOCAL)); - if (!tr) { - check_git_return(git_branch_lookup(&tr, m_repo.get(), m_currentBranch.toLocal8Bit(), GIT_BRANCH_REMOTE)); - } - else { - AutoPtr rbr(&git_reference_free); - check_git_return(git_branch_upstream(&rbr, tr)); - tr = std::move(rbr); - } + auto tr = remoteRefForBranch(m_currentBranch); if (!tr) return GitStatusFlag::UpToDate; AutoPtr lr(&git_reference_free); @@ -370,7 +357,7 @@ void walkFolders(const QFileInfo &info, auto f){ } -void Addon::removeFolders(QStringList paths, bool ask) { +bool Addon::removeFolders(QStringList paths, bool ask) { QStringList files; bool ok = ask; foreach (QString path, paths){ @@ -381,7 +368,7 @@ void Addon::removeFolders(QStringList paths, bool ask) { }); } } - if (files.isEmpty()) return; + if (files.isEmpty()) return true; if (ask) { m_mutex.lock(); m_result = &ok; @@ -403,11 +390,13 @@ void Addon::removeFolders(QStringList paths, bool ask) { } setProgress(i); } + return true; } + return false; } -void Addon::removeFolder(QString path, bool ask) { - removeFolders({path}, ask); +bool Addon::removeFolder(QString path, bool ask) { + return removeFolders({path}, ask); } void Addon::removeSubfolders() @@ -507,6 +496,48 @@ void Addon::reset() unpackSubfolders(); } +void Addon::reclone() +{ + delegate("Reclone", [this](){ + git_strarray remotes; + AutoPtr rr(&remotes, git_strarray_free); + check_git_return(git_remote_list(rr, m_repo.get())); + QStringList urls; + for (size_t i = 0; i < remotes.count; i++) { + AutoPtr remote(&git_remote_free); + check_git_return(git_remote_lookup(&remote, m_repo.get(), remotes.strings[i])); + urls << git_remote_url(remote); + } + const char *remote = nullptr; + check_git_return(git_branch_name(&remote, remoteRefForBranch(m_currentBranch))); + QString oldRemote(remote); + closeRepo(); + if (removeFolder(Control::instance()->addonsPath() + "/" + name() + "/.git")){ + git_repository *repo = nullptr; + check_git_return(git_repository_init(&repo, (Control::instance()->addonsPath() + "/" + m_name).toLocal8Bit(), false)); + m_repo.reset(repo); + for (size_t i = 0; i < remotes.count; i++) { + AutoPtr remote(&git_remote_free); + check_git_return(git_remote_create(&remote, m_repo.get(), remotes.strings[i], urls[i].toLocal8Bit())); + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + fetch_opts.callbacks.transfer_progress = &fetch_progress_cb; + fetch_opts.callbacks.payload = this; + AutoPtr r(git_remote_free); + check_git_return(git_remote_lookup(&r, m_repo.get(), git_remote_name(remote))); + check_git_return(git_remote_fetch(r,nullptr,&fetch_opts,nullptr)); + } + setCurrentBranch(oldRemote); + return true; + } + return false; + },[this](bool ok){ + if (ok) { + update(); + scanBranches(); + } + }); +} + void Addon::loadReadme() { delegate("Readme Loading",[this](){ @@ -604,6 +635,30 @@ void Addon::openRepo() m_repo.reset(repo); } +AutoPtr Addon::remoteRefForBranch(QString branch) +{ + AutoPtr lbr(&git_reference_free); + AutoPtr tr(&git_reference_free); + int error = check_git_return(git_branch_lookup(&lbr, m_repo.get(), branch.toLocal8Bit(), GIT_BRANCH_LOCAL)); + if (error == GIT_ENOTFOUND) + check_git_return(git_branch_lookup(&lbr, m_repo.get(), branch.toLocal8Bit(), GIT_BRANCH_REMOTE)); + else + check_git_return(git_branch_upstream(&tr, lbr)); + if (!tr) + return lbr; + else + return tr; +} + +AutoPtr Addon::branchRef(QString name) +{ + AutoPtr lbr(&git_reference_free); + int error = check_git_return(git_branch_lookup(&lbr, m_repo.get(), name.toLocal8Bit(), GIT_BRANCH_LOCAL)); + if (error == GIT_ENOTFOUND) + check_git_return(git_branch_lookup(&lbr, m_repo.get(), name.toLocal8Bit(), GIT_BRANCH_REMOTE)); + return lbr; +} + void Addon::delegate(QString taskname, auto work, auto callback) { Q_ASSERT_X(QThread::currentThread() == thread(), "delegate", "Attempt to delegate from another thread."); diff --git a/addon.h b/addon.h index 76feade..a58c579 100644 --- a/addon.h +++ b/addon.h @@ -24,8 +24,10 @@ #include #include #include +#include "utils.h" class git_repository; +class git_reference; using repo_p = std::unique_ptr; class QThreadPool; @@ -128,13 +130,16 @@ private: void openRepo(); + AutoPtr remoteRefForBranch(QString name); + AutoPtr branchRef(QString name); + void delegate(QString taskname, auto work, auto callback); void delegate(QString taskname, auto work); QThreadPool *m_pool; - void removeFolders(QStringList paths, bool ask = true); - void removeFolder(QString path, bool ask = true); + bool removeFolders(QStringList paths, bool ask = true); + bool removeFolder(QString path, bool ask = true); QString m_filesToRemove; QString m_readme; @@ -186,6 +191,7 @@ public slots: void setFilesToRemove(QString filesToRemove); void confirmFileRemove(bool confirmed); void reset(); + void reclone(); void loadReadme(); void setReadme(QString readme); }; diff --git a/control.h b/control.h index 7b982e4..5d4f0fc 100644 --- a/control.h +++ b/control.h @@ -24,23 +24,6 @@ #include #include -template -struct AutoPtr { - T pointer = nullptr; - void(*disposer)(T); - AutoPtr(void(*d)(T)) : disposer(d){ } - - void reset(T t = nullptr) { disposer(pointer); pointer = t; } - - ~AutoPtr(){ disposer(pointer); } - AutoPtr& operator =(AutoPtr &&other){ disposer(pointer); pointer = other.pointer; other.pointer = nullptr; return *this; } - AutoPtr& operator =(AutoPtr &other) = delete ; - - operator T&(){ return pointer; } - T* operator &(){ return &pointer; } - bool operator !(){ return !pointer; } -}; - class QThreadPool; class GitException : public QException { diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..699fabe --- /dev/null +++ b/utils.h @@ -0,0 +1,45 @@ +/* Copyright 2019 WobLight + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 UTILS_H +#define UTILS_H + +#include + +template +struct AutoPtr { + using disposer_t = void(*)(T*); + + T *pointer = nullptr; + disposer_t disposer; + AutoPtr(disposer_t d) noexcept : disposer(d){ } + AutoPtr(const AutoPtr &other) = delete; + AutoPtr(AutoPtr &&other) noexcept : pointer(std::move(other.pointer)), disposer(std::forward(other.disposer)) { other.pointer = nullptr; } + AutoPtr(T* p, disposer_t d) noexcept : pointer(p), disposer(d){ } + + void reset(T* t = nullptr) { disposer(pointer); pointer = t; } + + ~AutoPtr(){ disposer(pointer); } + AutoPtr& operator =(AutoPtr &&other){ disposer(pointer); pointer = std::move(other.pointer); disposer(std::move(other.disposer)); other.pointer = nullptr; return *this; } + AutoPtr& operator =(const AutoPtr &other) = delete ; + + operator T*&() noexcept { return pointer; } + T** operator &() noexcept { return &pointer; } + bool operator !() noexcept { return !pointer; } + T*& operator ->() noexcept { return pointer; } +}; + +#endif // UTILS_H