From c797b5a18b2d459d0e7f310fbb6180d86b2f896f Mon Sep 17 00:00:00 2001 From: Jan Wester Date: Tue, 15 Oct 2019 14:32:07 +0200 Subject: [PATCH] Log the git revision if applicable During development, the version number does not have much meaning... but when faced with a reported issue, you would still like to know in more detail what version of the Manuskript code was at work there. Knowing the exact git revision will hopefully make it easier to troubleshoot such issues in the future. Note: this code takes special care to not rely on external modules (we have enough dependencies) or even the git executable (invoking a program can be relatively slow on some operating systems). It might not handle all the edge cases, but I think it should cover our needs well enough. --- manuskript/functions/__init__.py | 47 ++++++++++++++++++++++++++++++++ manuskript/logging.py | 7 ++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/manuskript/functions/__init__.py b/manuskript/functions/__init__.py index 4acae75..7e4edd6 100644 --- a/manuskript/functions/__init__.py +++ b/manuskript/functions/__init__.py @@ -3,6 +3,8 @@ import os import re +import sys +import pathlib from random import * from PyQt5.QtCore import Qt, QRect, QStandardPaths, QObject, QRegExp, QDir @@ -13,6 +15,9 @@ from PyQt5.QtWidgets import qApp, QFileDialog from manuskript.enums import Outline +import logging +LOGGER = logging.getLogger(__name__) + # Used to detect multiple connections AUC = Qt.AutoConnection | Qt.UniqueConnection MW = None @@ -493,5 +498,47 @@ def getSearchResultContext(text, startPos, endPos): return context + +# Based on answer by jfs at: +# https://stackoverflow.com/questions/3718657/how-to-properly-determine-current-script-directory +def getManuskriptPath(follow_symlinks=True): + """Used to obtain the path Manuskript is located at.""" + if getattr(sys, 'frozen', False): # py2exe, PyInstaller, cx_Freeze + path = os.path.abspath(sys.executable) + else: + import inspect + path = inspect.getabsfile(getManuskriptPath) + "/../.." + if follow_symlinks: + path = os.path.realpath(path) + return os.path.dirname(path) + +# Based on answer by kagronik at: +# https://stackoverflow.com/questions/14989858/get-the-current-git-hash-in-a-python-script +def getGitRevision(base_path): + """Get git revision without relying on external processes or libraries.""" + git_dir = pathlib.Path(base_path) / '.git' + if not git_dir.exists(): + return None + + with (git_dir / 'HEAD').open('r') as head: + ref = head.readline().split(' ')[-1].strip() + + with (git_dir / ref).open('r') as git_hash: + return git_hash.readline().strip() + +def getGitRevisionAsString(base_path, short=False): + """Catches errors and presents a nice string.""" + try: + rev = getGitRevision(base_path) + if rev is not None: + if short: + rev = rev[:7] + return "#" + rev + else: + return "" # not a git repository + except Exception as e: + LOGGER.warning("Failed to obtain Git revision: %s", e) + return "#ERROR" + # Spellchecker loads writablePath from this file, so we need to load it after they get defined from manuskript.functions.spellchecker import Spellchecker diff --git a/manuskript/logging.py b/manuskript/logging.py index 1561463..5c33b9a 100644 --- a/manuskript/logging.py +++ b/manuskript/logging.py @@ -157,6 +157,8 @@ def attributesFromOptionalModule(module, *attributes): # The list is consumed as a part of the unpacking syntax. return v +import pathlib + def logVersionInformation(logger=None): """Logs all important runtime information neatly together. @@ -174,8 +176,11 @@ def logVersionInformation(logger=None): logger.info("Hardware: %s / %s", machine(), processor()) # Manuskript and Python info. + from manuskript.functions import getGitRevisionAsString, getManuskriptPath from manuskript.version import getVersion - logger.info("Manuskript %s (Python %s)", getVersion(), python_version()) + logger.info("Manuskript %s%s (Python %s)", getVersion(), + getGitRevisionAsString(getManuskriptPath(), short=True), + python_version()) # Installed Python packages.