Addon Manager: Refactor to test git and eliminate GitPython
This commit is contained in:
@@ -40,29 +40,25 @@ import addonmanager_utilities as utils
|
||||
from addonmanager_macro import Macro
|
||||
from Addon import Addon
|
||||
import NetworkManager
|
||||
|
||||
have_git = False
|
||||
try:
|
||||
import git
|
||||
|
||||
# Some types of Python installation will fall back to finding a directory called "git"
|
||||
# in certain locations instead of a Python package called git: that directory is unlikely
|
||||
# to have the "Repo" attribute unless it is a real installation, however, so this check
|
||||
# should catch that. (Bug #4072)
|
||||
have_git = hasattr(git, "Repo")
|
||||
if not have_git:
|
||||
FreeCAD.Console.PrintMessage(
|
||||
"Unable to locate a viable GitPython installation: falling back to ZIP installation."
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
NOGIT = False # for debugging purposes, set this to True to always use http downloads
|
||||
from addonmanager_git import GitManager, GitFailed, NoGitFound
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
git_manager = None
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
disable_git = pref.GetBool("disableGit", False)
|
||||
if not disable_git:
|
||||
try:
|
||||
git_manager = GitManager()
|
||||
except NoGitFound:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
translate("AddonsInstaller", "Could not locate a suitable git executable")
|
||||
)
|
||||
|
||||
# Workers only have one public method by design
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
|
||||
class CreateAddonListWorker(QtCore.QThread):
|
||||
"""This worker updates the list of available workbenches, emitting an "addon_repo"
|
||||
signal for each Addon as they are processed."""
|
||||
@@ -133,7 +129,7 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
raise ConnectionError
|
||||
|
||||
def _process_deprecated(self, deprecated_addons):
|
||||
""" Parse the section on deprecated addons """
|
||||
"""Parse the section on deprecated addons"""
|
||||
|
||||
fc_major = int(FreeCAD.Version()[0])
|
||||
fc_minor = int(FreeCAD.Version()[1])
|
||||
@@ -146,9 +142,7 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
minor = int(version_components[1])
|
||||
else:
|
||||
minor = 0
|
||||
if major < fc_major or (
|
||||
major == fc_major and minor <= fc_minor
|
||||
):
|
||||
if major < fc_major or (major == fc_major and minor <= fc_minor):
|
||||
if "kind" not in item or item["kind"] == "mod":
|
||||
self.obsolete.append(item["name"])
|
||||
elif item["kind"] == "macro":
|
||||
@@ -268,10 +262,10 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
|
||||
macro_cache_location = utils.get_cache_file_name("Macros")
|
||||
|
||||
if not have_git or NOGIT:
|
||||
if not git_manager:
|
||||
message = translate(
|
||||
"AddonsInstaller",
|
||||
"Failed to execute Git Python command: check installation of GitPython and/or git",
|
||||
"Failed to execute git command: check installation of git",
|
||||
)
|
||||
self.status_message_signal.emit(message)
|
||||
FreeCAD.Console.PrintWarning(message + "\n")
|
||||
@@ -316,16 +310,17 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
"Attempting to change non-git Macro setup to use git\n",
|
||||
)
|
||||
)
|
||||
utils.repair_git_repo(
|
||||
"https://github.com/FreeCAD/FreeCAD-macros.git", macro_cache_location
|
||||
git_manager.repair(
|
||||
"https://github.com/FreeCAD/FreeCAD-macros.git",
|
||||
macro_cache_location,
|
||||
)
|
||||
gitrepo = git.Git(macro_cache_location)
|
||||
gitrepo.pull("--ff-only")
|
||||
git_manager.update(macro_cache_location)
|
||||
else:
|
||||
git.Repo.clone_from(
|
||||
"https://github.com/FreeCAD/FreeCAD-macros.git", macro_cache_location
|
||||
git_manager.clone(
|
||||
"https://github.com/FreeCAD/FreeCAD-macros.git",
|
||||
macro_cache_location,
|
||||
)
|
||||
except git.exc.GitError as e:
|
||||
except GitFailed as e:
|
||||
FreeCAD.Console.PrintMessage(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
@@ -340,13 +335,14 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
)
|
||||
try:
|
||||
shutil.rmtree(macro_cache_location, onerror=self._remove_readonly)
|
||||
git.Repo.clone_from(
|
||||
"https://github.com/FreeCAD/FreeCAD-macros.git", macro_cache_location
|
||||
git_manager.clone(
|
||||
"https://github.com/FreeCAD/FreeCAD-macros.git",
|
||||
macro_cache_location,
|
||||
)
|
||||
FreeCAD.Console.PrintMessage(
|
||||
translate("AddonsInstaller", "Clean checkout succeeded") + "\n"
|
||||
)
|
||||
except git.exc.GitError as e2:
|
||||
except GitFailed as e2:
|
||||
# The Qt Python translation extractor doesn't support splitting this string (yet)
|
||||
# pylint: disable=line-too-long
|
||||
FreeCAD.Console.PrintWarning(
|
||||
@@ -413,7 +409,8 @@ class CreateAddonListWorker(QtCore.QThread):
|
||||
|
||||
|
||||
class LoadPackagesFromCacheWorker(QtCore.QThread):
|
||||
""" A subthread worker that loads package information from its cache file. """
|
||||
"""A subthread worker that loads package information from its cache file."""
|
||||
|
||||
addon_repo = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, cache_file: str):
|
||||
@@ -424,12 +421,12 @@ class LoadPackagesFromCacheWorker(QtCore.QThread):
|
||||
)
|
||||
|
||||
def override_metadata_cache_path(self, path):
|
||||
""" For testing purposes, override the location to fetch the package metadata from. """
|
||||
"""For testing purposes, override the location to fetch the package metadata from."""
|
||||
self.metadata_cache_path = path
|
||||
|
||||
def run(self):
|
||||
""" Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread """
|
||||
"""Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread"""
|
||||
with open(self.cache_file, "r", encoding="utf-8") as f:
|
||||
data = f.read()
|
||||
if data:
|
||||
@@ -456,7 +453,7 @@ class LoadPackagesFromCacheWorker(QtCore.QThread):
|
||||
|
||||
|
||||
class LoadMacrosFromCacheWorker(QtCore.QThread):
|
||||
""" A worker object to load macros from a cache file """
|
||||
"""A worker object to load macros from a cache file"""
|
||||
|
||||
add_macro_signal = QtCore.Signal(object)
|
||||
|
||||
@@ -465,8 +462,8 @@ class LoadMacrosFromCacheWorker(QtCore.QThread):
|
||||
self.cache_file = cache_file
|
||||
|
||||
def run(self):
|
||||
""" Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread """
|
||||
"""Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread"""
|
||||
|
||||
with open(self.cache_file, "r", encoding="utf-8") as f:
|
||||
data = f.read()
|
||||
@@ -491,8 +488,8 @@ class CheckSingleUpdateWorker(QtCore.QObject):
|
||||
self.repo = repo
|
||||
|
||||
def do_work(self):
|
||||
""" Use the UpdateChecker class to do the work of this function, depending on the
|
||||
type of Addon """
|
||||
"""Use the UpdateChecker class to do the work of this function, depending on the
|
||||
type of Addon"""
|
||||
|
||||
checker = UpdateChecker()
|
||||
if self.repo.repo_type == Addon.Kind.WORKBENCH:
|
||||
@@ -520,8 +517,8 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
self.moddir = os.path.join(self.basedir, "Mod")
|
||||
|
||||
def run(self):
|
||||
""" Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread """
|
||||
"""Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread"""
|
||||
|
||||
self.current_thread = QtCore.QThread.currentThread()
|
||||
checker = UpdateChecker()
|
||||
@@ -544,22 +541,22 @@ class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
|
||||
|
||||
|
||||
class UpdateChecker:
|
||||
""" A utility class used by the CheckWorkbenchesForUpdatesWorker class. Each function is
|
||||
"""A utility class used by the CheckWorkbenchesForUpdatesWorker class. Each function is
|
||||
designed for a specific Addon type, and modifies the passed-in Addon with the determined
|
||||
update status. """
|
||||
update status."""
|
||||
|
||||
def __init__(self):
|
||||
self.basedir = FreeCAD.getUserAppDataDir()
|
||||
self.moddir = os.path.join(self.basedir, "Mod")
|
||||
|
||||
def override_mod_directory(self, moddir):
|
||||
""" Primarily for use when testing, sets an alternate directory to use for mods """
|
||||
"""Primarily for use when testing, sets an alternate directory to use for mods"""
|
||||
self.moddir = moddir
|
||||
|
||||
def check_workbench(self, wb):
|
||||
""" Given a workbench Addon wb, check it for updates using git. If git is not
|
||||
available, does nothing. """
|
||||
if not have_git or NOGIT:
|
||||
"""Given a workbench Addon wb, check it for updates using git. If git is not
|
||||
available, does nothing."""
|
||||
if not git_manager:
|
||||
wb.set_status(Addon.Status.CANNOT_CHECK)
|
||||
return
|
||||
clonedir = os.path.join(self.moddir, wb.name)
|
||||
@@ -567,21 +564,17 @@ class UpdateChecker:
|
||||
# mark as already installed AND already checked for updates
|
||||
if not os.path.exists(os.path.join(clonedir, ".git")):
|
||||
with wb.git_lock:
|
||||
utils.repair_git_repo(wb.url, clonedir)
|
||||
git_manager.repair(wb.url, clonedir)
|
||||
with wb.git_lock:
|
||||
gitrepo = git.Repo(clonedir)
|
||||
try:
|
||||
if gitrepo.head.is_detached:
|
||||
status = git_manager.status(clonedir)
|
||||
if "(no branch)" in git_manager.status(clonedir):
|
||||
# By definition, in a detached-head state we cannot
|
||||
# update, so don't even bother checking.
|
||||
wb.set_status(Addon.Status.NO_UPDATE_AVAILABLE)
|
||||
if hasattr(gitrepo.head, "ref"):
|
||||
wb.branch = gitrepo.head.ref.name
|
||||
else:
|
||||
wb.branch = gitrepo.head.name
|
||||
wb.branch = git_manager.current_branch(clonedir)
|
||||
return
|
||||
gitrepo.git.fetch()
|
||||
except git.exc.GitError as e:
|
||||
except GitFailed as e:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
"AddonManager: "
|
||||
+ translate(
|
||||
@@ -594,11 +587,11 @@ class UpdateChecker:
|
||||
wb.set_status(Addon.Status.CANNOT_CHECK)
|
||||
else:
|
||||
try:
|
||||
if "git pull" in gitrepo.git.status():
|
||||
if git_manager.update_available(clonedir):
|
||||
wb.set_status(Addon.Status.UPDATE_AVAILABLE)
|
||||
else:
|
||||
wb.set_status(Addon.Status.NO_UPDATE_AVAILABLE)
|
||||
except git.exc.GitError:
|
||||
except GitFailed:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
translate(
|
||||
"AddonsInstaller", "git status failed for {}"
|
||||
@@ -608,7 +601,7 @@ class UpdateChecker:
|
||||
wb.set_status(Addon.Status.CANNOT_CHECK)
|
||||
|
||||
def check_package(self, package: Addon) -> None:
|
||||
""" Given a packaged Addon package, check it for updates. If git is available that is
|
||||
"""Given a packaged Addon package, check it for updates. If git is available that is
|
||||
used. If not, the package's metadata is examined, and if the metadata file has changed
|
||||
compared to the installed copy, an update is flagged."""
|
||||
|
||||
@@ -616,7 +609,7 @@ class UpdateChecker:
|
||||
if os.path.exists(clonedir):
|
||||
|
||||
# First, try to just do a git-based update, which will give the most accurate results:
|
||||
if have_git and not NOGIT:
|
||||
if git_manager:
|
||||
self.check_workbench(package)
|
||||
if package.status() != Addon.Status.CANNOT_CHECK:
|
||||
# It worked, just exit now
|
||||
@@ -653,7 +646,7 @@ class UpdateChecker:
|
||||
package.set_status(Addon.Status.CANNOT_CHECK)
|
||||
|
||||
def check_macro(self, macro_wrapper: Addon) -> None:
|
||||
""" Check to see if the online copy of the macro's code differs from the local copy. """
|
||||
"""Check to see if the online copy of the macro's code differs from the local copy."""
|
||||
|
||||
# Make sure this macro has its code downloaded:
|
||||
try:
|
||||
@@ -724,8 +717,8 @@ class CacheMacroCodeWorker(QtCore.QThread):
|
||||
self.repo_queue = None
|
||||
|
||||
def run(self):
|
||||
""" Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread """
|
||||
"""Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread"""
|
||||
|
||||
self.status_message.emit(translate("AddonsInstaller", "Caching macro code..."))
|
||||
|
||||
@@ -769,8 +762,8 @@ class CacheMacroCodeWorker(QtCore.QThread):
|
||||
)
|
||||
|
||||
def _process_queue(self, num_macros) -> bool:
|
||||
""" Spools up six network connections and downloads the macro code. Returns True if
|
||||
it was interrupted by user request, or False if it ran to completion. """
|
||||
"""Spools up six network connections and downloads the macro code. Returns True if
|
||||
it was interrupted by user request, or False if it ran to completion."""
|
||||
|
||||
# Emulate QNetworkAccessManager and spool up six connections:
|
||||
for _ in range(6):
|
||||
@@ -800,7 +793,7 @@ class CacheMacroCodeWorker(QtCore.QThread):
|
||||
return False
|
||||
|
||||
def update_and_advance(self, repo: Addon) -> None:
|
||||
""" Emit the updated signal and launch the next item from the queue. """
|
||||
"""Emit the updated signal and launch the next item from the queue."""
|
||||
if repo is not None:
|
||||
if repo.macro.name not in self.failed:
|
||||
self.update_macro.emit(repo)
|
||||
@@ -835,7 +828,7 @@ class CacheMacroCodeWorker(QtCore.QThread):
|
||||
pass
|
||||
|
||||
def terminate(self, worker) -> None:
|
||||
""" Shut down all running workers and exit the thread """
|
||||
"""Shut down all running workers and exit the thread"""
|
||||
if not worker.isFinished():
|
||||
macro_name = worker.macro.name
|
||||
FreeCAD.Console.PrintWarning(
|
||||
@@ -871,8 +864,8 @@ class GetMacroDetailsWorker(QtCore.QThread):
|
||||
self.macro = repo.macro
|
||||
|
||||
def run(self):
|
||||
""" Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread """
|
||||
"""Rarely called directly: create an instance and call start() on it instead to
|
||||
launch in a new thread"""
|
||||
|
||||
self.status_message.emit(
|
||||
translate("AddonsInstaller", "Retrieving macro description...")
|
||||
|
||||
Reference in New Issue
Block a user