Addon Manager: Refactor to test git and eliminate GitPython

This commit is contained in:
Chris Hennes
2022-08-18 09:49:37 -05:00
parent b6da08ef8b
commit ee162ff3a8
6 changed files with 295 additions and 143 deletions

View File

@@ -26,6 +26,7 @@ import os
import shutil
import stat
import tempfile
import time
from zipfile import ZipFile
import FreeCAD
@@ -40,6 +41,7 @@ class TestGit(unittest.TestCase):
def setUp(self):
"""Set up the test case: called by the unit test system"""
self.cwd = os.getcwd()
test_data_dir = os.path.join(
FreeCAD.getHomePath(), "Mod", "AddonManager", "AddonManagerTest", "data"
)
@@ -49,7 +51,9 @@ class TestGit(unittest.TestCase):
)
os.makedirs(self.test_dir, exist_ok=True)
self.test_repo_remote = os.path.join(self.test_dir, "TEST_REPO_REMOTE")
self._rmdir(self.test_repo_remote)
if os.path.exists(self.test_repo_remote):
# Make sure any old copy that got left around is deleted
self._rmdir(self.test_repo_remote)
if not os.path.exists(git_repo_zip):
self.skipTest("Can't find test repo")
@@ -66,13 +70,16 @@ class TestGit(unittest.TestCase):
def tearDown(self):
"""Clean up after the test"""
# self._rmdir(self.test_dir)
os.chdir(self.cwd)
#self._rmdir(self.test_dir)
os.rename(self.test_dir, self.test_dir + ".old." + str(time.time()))
def test_clone(self):
"""Test git clone"""
checkout_dir = self._clone_test_repo()
self.assertTrue(os.path.exists(checkout_dir))
self.assertTrue(os.path.exists(os.path.join(checkout_dir, ".git")))
self.assertEqual(os.getcwd(),self.cwd, "We should be left in the same CWD we started")
def test_checkout(self):
"""Test git checkout"""
@@ -82,6 +89,7 @@ class TestGit(unittest.TestCase):
status = self.git.status(checkout_dir).strip()
expected_status = "## HEAD (no branch)"
self.assertEqual(status, expected_status)
self.assertEqual(os.getcwd(),self.cwd, "We should be left in the same CWD we started")
def test_update(self):
"""Test using git to update the local repo"""
@@ -91,6 +99,7 @@ class TestGit(unittest.TestCase):
self.assertTrue(self.git.update_available(checkout_dir))
self.git.update(checkout_dir)
self.assertFalse(self.git.update_available(checkout_dir))
self.assertEqual(os.getcwd(),self.cwd, "We should be left in the same CWD we started")
def test_tag_and_branch(self):
"""Test checking the currently checked-out tag"""
@@ -108,12 +117,40 @@ class TestGit(unittest.TestCase):
self.assertEqual(found_branch, expected_branch)
self.assertFalse(self.git.update_available(checkout_dir))
expected_branch = "master"
expected_branch = "main"
self.git.checkout(checkout_dir, expected_branch)
found_branch = self.git.current_branch(checkout_dir)
self.assertEqual(found_branch, expected_branch)
self.assertFalse(self.git.update_available(checkout_dir))
self.assertEqual(os.getcwd(),self.cwd, "We should be left in the same CWD we started")
def test_get_remote(self):
""" Test getting the remote location """
checkout_dir = self._clone_test_repo()
expected_remote = self.test_repo_remote
returned_remote = self.git.get_remote(checkout_dir)
self.assertEqual(expected_remote, returned_remote)
self.assertEqual(os.getcwd(),self.cwd, "We should be left in the same CWD we started")
def test_repair(self):
""" Test the repair feature (and some exception throwing) """
checkout_dir = self._clone_test_repo()
remote = self.git.get_remote(checkout_dir)
git_dir = os.path.join(checkout_dir,".git")
self.assertTrue(os.path.exists(git_dir))
self._rmdir(git_dir)
# Make sure that we've truly broken the install
with self.assertRaises(GitFailed):
self.git.status(checkout_dir)
self.git.repair(remote, checkout_dir)
status = self.git.status(checkout_dir)
self.assertEqual(status,"## main...origin/main\n")
self.assertEqual(os.getcwd(),self.cwd, "We should be left in the same CWD we started")
def _rmdir(self, path):
try:
shutil.rmtree(path, onerror=self._remove_readonly)

View File

@@ -26,6 +26,13 @@ import json
import unittest
import os
import tempfile
have_git = True
try:
import git
except ImportError:
have_git = False
import FreeCAD
from PySide2 import QtCore
@@ -36,6 +43,11 @@ from addonmanager_workers_startup import (
CreateAddonListWorker,
LoadPackagesFromCacheWorker,
LoadMacrosFromCacheWorker,
CheckSingleUpdateWorker,
)
from addonmanager_workers_installation import (
InstallWorkbenchWorker,
)
class TestWorkersStartup(unittest.TestCase):
@@ -159,8 +171,49 @@ class TestWorkersStartup(unittest.TestCase):
def test_update_checker(self):
""" Test the code that checks a single addon for available updates. """
# First, install a specific Addon of each kind
if not have_git:
return
# Populate the test's Addon List:
self.test_create_addon_list_worker()
# First, install a specific Addon of each kind into a temp location
location = os.path.join(tempfile.gettempdir(),"FreeCADTesting")
self._test_workbench_update_checker(location)
# Preference Pack
# Macro
# Arrange for those addons to be out-of-date
# Check for updates
# Check for updates
def _test_workbench_update_checker(self, location):
# Workbench: use the FreeCAD-Help workbench for testing purposes
help_addon = None
for addon in self.addon_list:
if addon.name == "Help":
help_addon = addon
break
if not help_addon:
print("Unable to locate the FreeCAD-Help addon to test with")
return
addon_location = os.path.join(location, help_addon.name)
worker = InstallWorkbenchWorker(addon, addon_location)
worker.run() # Synchronous call, blocks until complete
gitrepo = git.Git(addon_location)
gitrepo.reset("--hard", "HEAD^")
print (addon_location)
# At this point the addon should be "out of date", checked out to one revision behind
# the most recent.
worker = CheckSingleUpdateWorker(help_addon)
worker.do_work() # Synchronous call
self.assertEqual(help_addon.status(), Addon.Status.UPDATE_AVAILABLE)

View File

@@ -29,8 +29,11 @@ import platform
import shutil
import subprocess
from typing import List
import time
import FreeCAD
translate = FreeCAD.Qt.translate
class NoGitFound(RuntimeError):
"""Could not locate the git executable on this system."""
@@ -80,15 +83,47 @@ class GitManager:
old_dir = os.getcwd()
os.chdir(local_path)
self._synchronous_call_git(["fetch"])
self._synchronous_call_git(["pull"])
self._synchronous_call_git(["submodule", "update", "--init", "--recursive"])
try:
self._synchronous_call_git(["pull"])
self._synchronous_call_git(["submodule", "update", "--init", "--recursive"])
except GitFailed as e:
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller",
"Basic git update failed with the following message:",
)
+ str(e)
+ "\n"
)
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller",
"Backing up the original directory and re-cloning",
)
+ "...\n"
)
remote = self.get_remote(local_path)
with open(os.path.join(local_path, "ADDON_DISABLED"), "w") as f:
f.write(
"This is a backup of an addon that failed to update cleanly so was re-cloned. "
+ "It was disabled by the Addon Manager's git update facility and can be safely "
+ "deleted if the addon is working properly."
)
os.chdir("..")
os.rename(local_path, local_path + ".backup" + str(time.time()))
self.clone(remote, local_path)
os.chdir(old_dir)
def status(self, local_path) -> str:
"""Gets the v1 porcelain status"""
old_dir = os.getcwd()
os.chdir(local_path)
status = self._synchronous_call_git(["status", "-sb", "--porcelain"])
try:
status = self._synchronous_call_git(["status", "-sb", "--porcelain"])
except GitFailed as e:
os.chdir(old_dir)
raise e
os.chdir(old_dir)
return status
@@ -99,7 +134,11 @@ class GitManager:
final_args = ["reset"]
if args:
final_args.extend(args)
self._synchronous_call_git(final_args)
try:
self._synchronous_call_git(final_args)
except GitFailed as e:
os.chdir(old_dir)
raise e
os.chdir(old_dir)
def async_fetch_and_update(self, local_path, progress_monitor, args=None):
@@ -109,8 +148,12 @@ class GitManager:
"""Returns True if an update is available from the remote, or false if not"""
old_dir = os.getcwd()
os.chdir(local_path)
self._synchronous_call_git(["fetch"])
status = self._synchronous_call_git(["status", "-sb", "--porcelain"])
try:
self._synchronous_call_git(["fetch"])
status = self._synchronous_call_git(["status", "-sb", "--porcelain"])
except GitFailed as e:
os.chdir(old_dir)
raise e
os.chdir(old_dir)
return "behind" in status
@@ -118,7 +161,11 @@ class GitManager:
"""Get the name of the currently checked-out tag if HEAD is detached"""
old_dir = os.getcwd()
os.chdir(local_path)
tag = self._synchronous_call_git(["describe", "--tags"]).strip()
try:
tag = self._synchronous_call_git(["describe", "--tags"]).strip()
except GitFailed as e:
os.chdir(old_dir)
raise e
os.chdir(old_dir)
return tag
@@ -126,7 +173,11 @@ class GitManager:
"""Get the name of the current branch"""
old_dir = os.getcwd()
os.chdir(local_path)
branch = self._synchronous_call_git(["branch", "--show-current"]).strip()
try:
branch = self._synchronous_call_git(["branch", "--show-current"]).strip()
except GitFailed as e:
os.chdir(old_dir)
raise e
os.chdir(old_dir)
return branch
@@ -135,14 +186,55 @@ class GitManager:
ensures that it is. Note that any local changes in local_path will be destroyed. This
is achieved by archiving the old path, cloning an entirely new copy, and then deleting
the old directory."""
old_path = local_path + ".bak"
os.rename(local_path, old_path)
original_cwd = os.getcwd()
# Make sure we are not currently in that directory, otherwise on Windows the rename
# will fail. To guarantee we aren't in it, change to it, then shift up one.
os.chdir(local_path)
os.chdir("..")
backup_path = local_path + ".backup" + str(time.time())
os.rename(local_path, backup_path)
try:
self.clone(remote, local_path)
except GitFailed as e:
shutil.rmtree(local_path)
os.rename(old_path, local_path)
FreeCAD.Console.PrintError(
translate(
"AddonsInstaller", "Failed to clone {} into {} using git"
).format(remote, local_path)
)
os.chdir(original_cwd)
raise e
os.chdir(original_cwd)
def get_remote(self, local_path) -> str:
"""Get the repository that this local path is set to fetch from"""
old_dir = os.getcwd()
os.chdir(local_path)
try:
response = self._synchronous_call_git(["remote", "-v", "show"])
except GitFailed as e:
os.chdir(old_dir)
raise e
lines = response.split("\n")
result = "(unknown remote)"
for line in lines:
if line.endswith("(fetch)"):
# The line looks like:
# origin https://some/sort/of/path (fetch)
segments = line.split()
if len(segments) == 3:
result = segments[1]
break
else:
FreeCAD.Console.PrintWarning(
"Error parsing the results from git remote -v show:\n"
)
FreeCAD.Console.PrintWarning(line + "\n")
os.chdir(old_dir)
return result
def _find_git(self):
# Find git. In preference order

View File

@@ -49,22 +49,17 @@ import addonmanager_utilities as utils
from addonmanager_macro import Macro
from Addon import Addon
import NetworkManager
from addonmanager_git import GitManager, GitFailed, NoGitFound
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
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:
pass # A log messsage was already printed by the startup code
translate = FreeCAD.Qt.translate
@@ -73,8 +68,6 @@ translate = FreeCAD.Qt.translate
# \brief Multithread workers for the addon manager
# @{
NOGIT = False # for debugging purposes, set this to True to always use http downloads
class InstallWorkbenchWorker(QtCore.QThread):
"This worker installs a workbench"
@@ -84,7 +77,7 @@ class InstallWorkbenchWorker(QtCore.QThread):
success = QtCore.Signal(Addon, str)
failure = QtCore.Signal(Addon, str)
def __init__(self, repo: Addon):
def __init__(self, repo: Addon, location=None):
QtCore.QThread.__init__(self)
self.repo = repo
@@ -93,13 +86,22 @@ class InstallWorkbenchWorker(QtCore.QThread):
self.update_timer.timeout.connect(self.update_status)
self.update_timer.start()
if location:
self.clone_directory = location
else:
basedir = FreeCAD.getUserAppDataDir()
self.clone_directory = os.path.join(basedir, "Mod", repo.name)
if not os.path.exists(self.clone_directory):
os.makedirs(self.clone_directory)
def run(self):
"installs or updates the selected addon"
if not self.repo:
return
if not have_git or NOGIT:
if not git_manager:
FreeCAD.Console.PrintLog(
translate(
"AddonsInstaller",
@@ -117,13 +119,9 @@ class InstallWorkbenchWorker(QtCore.QThread):
)
return
basedir = FreeCAD.getUserAppDataDir()
moddir = basedir + os.sep + "Mod"
if not os.path.exists(moddir):
os.makedirs(moddir)
target_dir = moddir + os.sep + self.repo.name
target_dir = self.clone_directory
if have_git and not NOGIT:
if git_manager:
# Do the git process...
self.run_git(target_dir)
else:
@@ -144,7 +142,7 @@ class InstallWorkbenchWorker(QtCore.QThread):
def run_git(self, clonedir: str) -> None:
if NOGIT or not have_git:
if not git_manager:
FreeCAD.Console.PrintLog(
translate(
"AddonsInstaller",
@@ -165,10 +163,9 @@ class InstallWorkbenchWorker(QtCore.QThread):
self.status_message.emit("Updating module...")
with self.repo.git_lock:
if not os.path.exists(clonedir + os.sep + ".git"):
utils.repair_git_repo(self.repo.url, clonedir)
repo = git.Git(clonedir)
git_manager.repair(self.repo.url, clonedir)
try:
repo.pull("--ff-only") # Refuses to take a progress object?
git_manager.update(clonedir)
if self.repo.contains_workbench():
answer = translate(
"AddonsInstaller",
@@ -179,7 +176,7 @@ class InstallWorkbenchWorker(QtCore.QThread):
"AddonsInstaller",
"Workbench successfully updated.",
)
except Exception as e:
except GitFailed as e:
answer = (
translate("AddonsInstaller", "Error updating module")
+ " "
@@ -190,14 +187,8 @@ class InstallWorkbenchWorker(QtCore.QThread):
)
answer += str(e)
self.failure.emit(self.repo, answer)
else:
# Update the submodules for this repository
repo_sms = git.Repo(clonedir)
self.status_message.emit("Updating submodules...")
for submodule in repo_sms.submodules:
submodule.update(init=True, recursive=True)
self.update_metadata()
self.success.emit(self.repo, answer)
self.update_metadata()
self.success.emit(self.repo, answer)
def run_git_clone(self, clonedir: str) -> None:
self.status_message.emit("Cloning module...")
@@ -216,27 +207,14 @@ class InstallWorkbenchWorker(QtCore.QThread):
with self.repo.git_lock:
FreeCAD.Console.PrintMessage("Lock acquired...\n")
# NOTE: There is no way to interrupt this process in GitPython: someday we should
# support pygit2/libgit2 so we can actually interrupt this properly.
repo = git.Repo.clone_from(
self.repo.url, clonedir, progress=self.git_progress
)
git_manager.clone(self.repo.url, clonedir)
FreeCAD.Console.PrintMessage("Initial clone complete...\n")
if current_thread.isInterruptionRequested():
return
# Make sure to clone all the submodules as well
if repo.submodules:
FreeCAD.Console.PrintMessage("Updating submodules...\n")
repo.submodule_update(recursive=True)
if current_thread.isInterruptionRequested():
return
if self.repo.branch in repo.heads:
FreeCAD.Console.PrintMessage("Checking out HEAD...\n")
repo.heads[self.repo.branch].checkout()
FreeCAD.Console.PrintMessage("Clone complete\n")
if self.repo.contains_workbench():
@@ -728,13 +706,12 @@ class UpdateMetadataCacheWorker(QtCore.QThread):
repo.cached_icon_filename = cache_file
if have_git and not NOGIT:
if git_manager:
class GitProgressMonitor(git.RemoteProgress):
class GitProgressMonitor:
"""An object that receives git progress updates and stores them for later display"""
def __init__(self):
super().__init__()
self.current = 0
self.total = 100
self.message = ""

View File

@@ -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...")