02a05d34d0
Add info command, -c/--config, derived repo.wiki.git target, and README for submodule/root workflows.
138 lines
3.8 KiB
Python
138 lines
3.8 KiB
Python
"""Resolve configuration paths and Gitea/GitLab wiki clone URLs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
class ResolveError(ValueError):
|
|
pass
|
|
|
|
|
|
def resolve_src_root(config_or_src: Path) -> Path:
|
|
"""
|
|
Accept configuration root (…/crm3-26) or its src/ dump.
|
|
|
|
Returns absolute path to the directory that contains Catalogs/, Documents/, …
|
|
"""
|
|
path = Path(config_or_src).expanduser().resolve()
|
|
if not path.exists():
|
|
raise ResolveError(f"path does not exist: {path}")
|
|
|
|
if path.is_file():
|
|
raise ResolveError(f"expected directory, got file: {path}")
|
|
|
|
if _looks_like_src(path):
|
|
return path
|
|
|
|
src = path / "src"
|
|
if src.is_dir() and _looks_like_src(src):
|
|
return src.resolve()
|
|
|
|
raise ResolveError(
|
|
f"not a 1C configuration dump: {path} "
|
|
"(need …/src with Catalogs|Documents|Configuration.xml, or the src itself)"
|
|
)
|
|
|
|
|
|
def resolve_config_root(config_or_src: Path) -> Path:
|
|
"""Configuration root (parent of src when applicable)."""
|
|
src = resolve_src_root(config_or_src)
|
|
if src.name == "src":
|
|
return src.parent
|
|
return src
|
|
|
|
|
|
def _looks_like_src(path: Path) -> bool:
|
|
if (path / "Configuration.xml").is_file():
|
|
return True
|
|
markers = ("Catalogs", "Documents", "DataProcessors", "Reports", "CommonModules")
|
|
return any((path / name).is_dir() for name in markers)
|
|
|
|
|
|
def git_toplevel(path: Path) -> Path | None:
|
|
"""Nearest git work tree containing path (submodule or root project)."""
|
|
path = Path(path).resolve()
|
|
try:
|
|
proc = subprocess.run(
|
|
["git", "-C", str(path), "rev-parse", "--show-toplevel"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
except OSError:
|
|
return None
|
|
if proc.returncode != 0:
|
|
return None
|
|
top = proc.stdout.strip()
|
|
return Path(top) if top else None
|
|
|
|
|
|
def git_remote_url(path: Path, *, remote: str = "origin") -> str | None:
|
|
"""Clone URL of `remote` for the git repo that owns path."""
|
|
top = git_toplevel(path)
|
|
if top is None:
|
|
return None
|
|
try:
|
|
proc = subprocess.run(
|
|
["git", "-C", str(top), "remote", "get-url", remote],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
except OSError:
|
|
return None
|
|
if proc.returncode != 0:
|
|
return None
|
|
url = proc.stdout.strip()
|
|
return url or None
|
|
|
|
|
|
def to_wiki_clone_url(repo_url: str) -> str:
|
|
"""
|
|
Map configuration repository URL → wiki git URL.
|
|
|
|
Gitea / GitLab: https://host/org/repo.git → https://host/org/repo.wiki.git
|
|
git@host:org/repo.git → git@host:org/repo.wiki.git
|
|
"""
|
|
url = repo_url.strip().rstrip("/")
|
|
if not url:
|
|
raise ResolveError("empty repository URL")
|
|
|
|
if url.endswith(".wiki.git"):
|
|
return url
|
|
|
|
if url.endswith(".git"):
|
|
return url[: -len(".git")] + ".wiki.git"
|
|
|
|
return url + ".wiki.git"
|
|
|
|
|
|
def resolve_wiki_url(
|
|
config_or_src: Path,
|
|
*,
|
|
wiki_url: str | None = None,
|
|
remote: str = "origin",
|
|
) -> str:
|
|
"""Explicit --wiki-url, or derive from git remote of the configuration repo."""
|
|
if wiki_url:
|
|
return to_wiki_clone_url(wiki_url)
|
|
|
|
root = resolve_config_root(config_or_src)
|
|
repo = git_remote_url(root, remote=remote)
|
|
if not repo:
|
|
raise ResolveError(
|
|
f"cannot detect git remote '{remote}' for {root}; "
|
|
"pass --wiki-url explicitly (…/repo.wiki.git)"
|
|
)
|
|
return to_wiki_clone_url(repo)
|
|
|
|
|
|
def slug_for_path(path: Path) -> str:
|
|
"""Short filesystem-safe slug from config root name."""
|
|
name = resolve_config_root(path).name
|
|
slug = re.sub(r"[^\w.\-]+", "-", name, flags=re.UNICODE).strip("-")
|
|
return slug or "config"
|