From c8b19d507a303ee6cb960e12a2e0032891179e2c Mon Sep 17 00:00:00 2001 From: bruno Date: Mon, 27 Jul 2026 17:16:52 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20versioning=20SemVer=20MAJOR.MINOR.PATCH?= =?UTF-8?q?=20bas=C3=A9=20sur=20git=20describe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backend/version.py: get_version() lit git describe --tags (ex: 1.8.0-301-gabc1234) - main.py: FastAPI version = get_version() dynamique - build.rs: injecte GIT_VERSION, GIT_HASH, SEMVER depuis git describe - main.rs: ABOUT_MSG, get_version(), log utilisent GIT_VERSION - Format: MAJOR.MINOR.PATCH[-commits-gHASH] (ex: 1.8.0, 1.8.0-3-gabc1234) - Fallback: VERSION file ou 0.0.0-dev si Git indisponible --- backend/main.py | 4 ++- backend/version.py | 64 +++++++++++++++++++++++++++++++++++++++++++++ desktop/build.rs | 21 ++++++++++++--- desktop/src/main.rs | 9 +++---- 4 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 backend/version.py diff --git a/backend/main.py b/backend/main.py index 07f2fb9..6e0aabb 100644 --- a/backend/main.py +++ b/backend/main.py @@ -633,7 +633,9 @@ async def lifespan(app: FastAPI): _search_executor = None -app = FastAPI(title="ObsiGate", version="1.4.0", lifespan=lifespan) +from backend.version import get_version + +app = FastAPI(title="ObsiGate", version=get_version(), lifespan=lifespan) # GZip compression — reduces bandwidth by ~70% for text responses # Custom wrapper: skip compression for SSE streams (/api/events) diff --git a/backend/version.py b/backend/version.py new file mode 100644 index 0000000..eddf40d --- /dev/null +++ b/backend/version.py @@ -0,0 +1,64 @@ +""" +Version management for ObsiGate. + +Uses git describe to generate a SemVer-compatible version string. +Format: MAJOR.MINOR.PATCH[-commits-since-tag-gHASH] + +Examples: + v1.7.0 (exact tag) + v1.7.0-3-gabc1234 (3 commits after v1.7.0) + v1.7.0-dev (fallback when Git unavailable) +""" +from __future__ import annotations + +import subprocess +from pathlib import Path + +_ROOT = Path(__file__).resolve().parent.parent # ObsiGate repo root + + +def get_version() -> str: + """Get the current version from Git tags.""" + try: + result = subprocess.run( + ["git", "describe", "--tags", "--dirty=-dirty"], + cwd=str(_ROOT), + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode == 0: + tag = result.stdout.strip() + # git describe returns v1.7.0 or v1.7.0-3-gabc1234 + # Remove leading 'v' if present + if tag.startswith("v"): + tag = tag[1:] + return tag + except (FileNotFoundError, subprocess.TimeoutExpired, OSError): + pass + + # Fallback: try reading from a VERSION file + version_file = _ROOT / "VERSION" + if version_file.exists(): + return version_file.read_text().strip() + + return "0.0.0-dev" + + +def get_version_tuple() -> tuple[int, int, int]: + """Return (major, minor, patch) tuple for programmatic use.""" + raw = get_version() + # Strip -suffix for parsing + base = raw.split("-")[0] + try: + parts = base.split(".") + major = int(parts[0]) if len(parts) > 0 else 0 + minor = int(parts[1]) if len(parts) > 1 else 0 + patch = int(parts[2]) if len(parts) > 2 else 0 + return (major, minor, patch) + except (ValueError, IndexError): + return (0, 0, 0) + + +if __name__ == "__main__": + print(get_version()) diff --git a/desktop/build.rs b/desktop/build.rs index 3a4fa73..f656070 100644 --- a/desktop/build.rs +++ b/desktop/build.rs @@ -1,6 +1,15 @@ fn main() { - // Inject Git commit hash as version suffix - let git_hash = std::process::Command::new("git") + // Get version from git describe (same as backend/version.py) + let version = std::process::Command::new("git") + .args(["describe", "--tags", "--dirty=-dirty"]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .map(|s| s.trim().trim_start_matches('v').to_string()) + .unwrap_or_else(|| "0.0.0-dev".to_string()); + + // Extract short hash for display + let short_hash = std::process::Command::new("git") .args(["rev-parse", "--short", "HEAD"]) .output() .ok() @@ -8,9 +17,15 @@ fn main() { .map(|s| s.trim().to_string()) .unwrap_or_else(|| "unknown".to_string()); - println!("cargo:rustc-env=GIT_HASH={}", git_hash); + // SemVer base (strip -suffix) + let semver_base = version.split('-').next().unwrap_or("0.0.0"); + + println!("cargo:rustc-env=GIT_VERSION={}", version); + println!("cargo:rustc-env=GIT_HASH={}", short_hash); + println!("cargo:rustc-env=SEMVER={}", semver_base); println!("cargo:rerun-if-changed=.git/HEAD"); println!("cargo:rerun-if-changed=.git/refs/heads/main"); + println!("cargo:rerun-if-changed=.git/refs/tags"); tauri_build::build() } diff --git a/desktop/src/main.rs b/desktop/src/main.rs index 1dcd1ed..7ee0992 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -240,9 +240,7 @@ fn get_backend_url() -> String { #[tauri::command] fn get_version() -> String { - let cargo_version = env!("CARGO_PKG_VERSION"); - let git_hash = env!("GIT_HASH"); - format!("v{}-{}", cargo_version, git_hash) + env!("GIT_VERSION").to_string() } #[tauri::command] @@ -422,7 +420,7 @@ fn get_window_state() -> AppConfig { // Build About message once at compile time const ABOUT_MSG: &str = concat!( - "ObsiGate Desktop v", env!("CARGO_PKG_VERSION"), "-", env!("GIT_HASH"), + "ObsiGate Desktop ", env!("GIT_VERSION"), "\\n\\nPorte d\\'entrée native pour vos vaults Obsidian.", "\\nConstruit avec Tauri + Rust.", "\\n\\n© Bruno Charest — Licence MIT", @@ -447,8 +445,7 @@ fn main() { }) .try_init(); - let version_str = format!("v{}-{}", env!("CARGO_PKG_VERSION"), env!("GIT_HASH")); - info!("ObsiGate Desktop {} starting", version_str); + info!("ObsiGate Desktop {} starting", env!("GIT_VERSION")); // Resolve exe directory let exe_dir = std::env::current_exe()