diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 12e58d1d80..0f575c26e8 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -3,17 +3,27 @@ Provides information on the current InvenTree version """ +import datetime import os +import pathlib import re -import subprocess import django +from dulwich.repo import NotGitRepository, Repo + from .api_version import INVENTREE_API_VERSION # InvenTree software version INVENTREE_SW_VERSION = "0.12.0 dev" +# Discover git +try: + main_repo = Repo(pathlib.Path(__file__).parent.parent.parent) + main_commit = main_repo[main_repo.head()] +except NotGitRepository: + main_commit = None + def inventreeInstanceName(): """Returns the InstanceName settings for the current database.""" @@ -93,30 +103,6 @@ def inventreeDjangoVersion(): return django.get_version() -git_available: bool = None # is git available and used by the install? - - -def check_for_git(): - """Check if git is available on the install.""" - global git_available - - if git_available is not None: - return git_available - - try: - subprocess.check_output('git --version'.split(), stderr=subprocess.DEVNULL) - except Exception: - git_available = False - - if git_available is None: - try: - subprocess.check_output('git status'.split(), stderr=subprocess.DEVNULL) - git_available = True - return True - except Exception: - git_available = False - - def inventreeCommitHash(): """Returns the git commit hash for the running codebase.""" # First look in the environment variables, i.e. if running in docker @@ -125,13 +111,9 @@ def inventreeCommitHash(): if commit_hash: return commit_hash - if not check_for_git(): - return None - - try: - return str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip() - except Exception: # pragma: no cover + if main_commit is None: return None + return main_commit.sha().hexdigest()[0:7] def inventreeCommitDate(): @@ -142,11 +124,6 @@ def inventreeCommitDate(): if commit_date: return commit_date.split(' ')[0] - if not check_for_git(): - return None - - try: - d = str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8').strip() - return d.split(' ')[0] - except Exception: # pragma: no cover + if main_commit is None: return None + return str(datetime.datetime.fromtimestamp(main_commit.commit_time).date()) diff --git a/InvenTree/plugin/apps.py b/InvenTree/plugin/apps.py index 4686b77a9c..b3b1a4cb6a 100644 --- a/InvenTree/plugin/apps.py +++ b/InvenTree/plugin/apps.py @@ -7,13 +7,11 @@ The main code for plugin special sauce is in the plugin registry in `InvenTree/p import logging from django.apps import AppConfig -from django.utils.translation import gettext_lazy as _ from maintenance_mode.core import set_maintenance_mode from InvenTree.ready import canAppAccessDatabase from plugin import registry -from plugin.helpers import check_git_version, log_error logger = logging.getLogger('inventree') @@ -47,9 +45,3 @@ class PluginAppConfig(AppConfig): # drop out of maintenance # makes sure we did not have an error in reloading and maintenance is still active set_maintenance_mode(False) - - # check git version - registry.git_is_modern = check_git_version() - - if not registry.git_is_modern: # pragma: no cover # simulating old git seems not worth it for coverage - log_error(_('Your environment has an outdated git version. This prevents InvenTree from loading plugin details.'), 'load') diff --git a/InvenTree/plugin/helpers.py b/InvenTree/plugin/helpers.py index 2e760a988b..5277fc78de 100644 --- a/InvenTree/plugin/helpers.py +++ b/InvenTree/plugin/helpers.py @@ -1,10 +1,10 @@ """Helpers for plugin app.""" +import datetime import inspect import logging import pathlib import pkgutil -import subprocess import sysconfig import traceback from importlib.metadata import entry_points @@ -14,6 +14,8 @@ from django.conf import settings from django.core.exceptions import AppRegistryNotReady from django.db.utils import IntegrityError +from dulwich.repo import NotGitRepository, Repo + logger = logging.getLogger('inventree') @@ -109,21 +111,28 @@ def get_entrypoints(): # region git-helpers def get_git_log(path): """Get dict with info of the last commit to file named in path.""" - from plugin import registry output = None - if registry.git_is_modern: - path = path.replace(str(settings.BASE_DIR.parent), '')[1:] - command = ['git', 'log', '-n', '1', "--pretty=format:'%H%n%aN%n%aE%n%aI%n%f%n%G?%n%GK'", '--follow', '--', path] + path = path.replace(str(settings.BASE_DIR.parent), '')[1:] + + try: + walker = Repo.discover(path).get_walker(paths=[path.encode()], max_entries=1) try: - output = str(subprocess.check_output(command, cwd=settings.BASE_DIR.parent), 'utf-8')[1:-1] - if output: - output = output.split('\n') - except subprocess.CalledProcessError: # pragma: no cover - pass - except FileNotFoundError: # pragma: no cover - # Most likely the system does not have 'git' installed + commit = next(iter(walker)).commit + except StopIteration: pass + else: + output = [ + commit.sha().hexdigest(), + commit.author.decode().split('<')[0][:-1], + commit.author.decode().split('<')[1][:-1], + datetime.datetime.fromtimestamp(commit.author_time, ).isoformat(), + commit.message.decode().split('\n')[0], + 'E', + None + ] + except NotGitRepository: + pass if not output: output = 7 * [''] # pragma: no cover @@ -131,29 +140,6 @@ def get_git_log(path): return {'hash': output[0], 'author': output[1], 'mail': output[2], 'date': output[3], 'message': output[4], 'verified': output[5], 'key': output[6]} -def check_git_version(): - """Returns if the current git version supports modern features.""" - # get version string - try: - output = str(subprocess.check_output(['git', '--version'], cwd=settings.BASE_DIR.parent), 'utf-8') - except subprocess.CalledProcessError: # pragma: no cover - return False - except FileNotFoundError: # pragma: no cover - # Most likely the system does not have 'git' installed - return False - - # process version string - try: - version = output[12:-1].split(".") - if len(version) > 1 and version[0] == '2': - if len(version) > 2 and int(version[1]) >= 22: - return True - except ValueError: # pragma: no cover - pass - - return False # pragma: no cover - - class GitStatus: """Class for resolving git gpg singing state.""" diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 6403df6683..18f22559eb 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -60,7 +60,6 @@ class PluginsRegistry: # flags self.is_loading = False # Are plugins being loaded right now self.apps_loading = True # Marks if apps were reloaded yet - self.git_is_modern = True # Is a modern version of git available self.installed_apps = [] # Holds all added plugin_paths diff --git a/requirements.in b/requirements.in index ffdc26d782..cd01969dc5 100644 --- a/requirements.in +++ b/requirements.in @@ -28,6 +28,7 @@ django-user-sessions # user sessions in DB django-weasyprint # django weasyprint integration djangorestframework # DRF framework django-xforwardedfor-middleware # IP forwarding metadata +dulwich # pure Python git integration drf-spectacular # DRF API documentation feedparser # RSS newsfeed parser gunicorn # Gunicorn web server diff --git a/requirements.txt b/requirements.txt index cd3ac5dc05..487dcfeadf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -140,6 +140,8 @@ djangorestframework==3.14.0 # drf-spectacular drf-spectacular==0.26.2 # via -r requirements.in +dulwich==0.21.5 + # via -r requirements.in et-xmlfile==1.1.0 # via openpyxl feedparser==6.0.10 @@ -279,6 +281,7 @@ uritemplate==4.1.1 # drf-spectacular urllib3==2.0.2 # via + # dulwich # requests # sentry-sdk wcwidth==0.2.6