From 1b63c1cf811312e0593adde95ccf8369c9f6ade2 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 8 May 2026 11:06:20 +0200 Subject: feat: add sync-cgit-descs.py to sync repo descriptions from gitolite Reads /var/lib/gitolite3/repositories/*/description and updates repo.desc= in /etc/cgitrc for existing repo blocks only. Supports --dry-run, --cgitrc, and --repo-base overrides. Co-Authored-By: Claude Sonnet 4.6 --- sync-cgit-descs.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100755 sync-cgit-descs.py diff --git a/sync-cgit-descs.py b/sync-cgit-descs.py new file mode 100755 index 0000000..da7ae6d --- /dev/null +++ b/sync-cgit-descs.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +""" +sync-cgit-descs.py — update repo.desc= in /etc/cgitrc from gitolite description files. + +Only updates existing repo blocks. Never adds new blocks or removes repos. +Skips repos whose description file is missing or contains the gitolite default. +""" + +import argparse +import os +import re +import sys + +CGITRC = "/etc/cgitrc" +REPO_BASE = "/var/lib/gitolite3/repositories" +GITOLITE_DEFAULT = "Unnamed repository" + + +def read_description(repo_url): + path = os.path.join(REPO_BASE, repo_url + ".git", "description") + if not os.path.exists(path): + return None + desc = open(path).read().strip() + if not desc or GITOLITE_DEFAULT in desc: + return None + return desc + + +def sync(cgitrc_path, dry_run=False): + with open(cgitrc_path) as f: + lines = f.readlines() + + out = [] + i = 0 + changes = 0 + + while i < len(lines): + line = lines[i] + m = re.match(r"^repo\.url=(.+)", line.rstrip()) + if m: + repo_url = m.group(1) + desc = read_description(repo_url) + out.append(line) + i += 1 + # consume lines until next repo block or EOF, updating repo.desc= if found + desc_written = False + block = [] + while i < len(lines): + next_line = lines[i] + if re.match(r"^repo\.url=", next_line): + break + if re.match(r"^#?repo\.desc=", next_line): + if desc: + new_line = f"repo.desc={desc}\n" + if next_line.rstrip() != new_line.rstrip(): + print(f" {repo_url}: '{next_line.rstrip()}' -> '{new_line.rstrip()}'") + changes += 1 + block.append(new_line) + else: + block.append(next_line) + desc_written = True + else: + block.append(next_line) + i += 1 + # insert repo.desc= after repo.url= if not found in block + if not desc_written and desc: + block.insert(0, f"repo.desc={desc}\n") + print(f" {repo_url}: inserted repo.desc={desc}") + changes += 1 + out.extend(block) + else: + out.append(line) + i += 1 + + if changes == 0: + print("No changes.") + return + + if dry_run: + print(f"\n-- dry run: {changes} change(s), not writing --") + else: + with open(cgitrc_path, "w") as f: + f.writelines(out) + print(f"\nWrote {changes} change(s) to {cgitrc_path}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--cgitrc", default=CGITRC, help=f"cgitrc path (default: {CGITRC})") + parser.add_argument("--repo-base", default=REPO_BASE, help=f"gitolite repos dir (default: {REPO_BASE})") + parser.add_argument("--dry-run", action="store_true", help="print changes without writing") + args = parser.parse_args() + + REPO_BASE = args.repo_base + + if not os.path.exists(args.cgitrc): + print(f"error: {args.cgitrc} not found", file=sys.stderr) + sys.exit(1) + + sync(args.cgitrc, dry_run=args.dry_run) -- cgit v1.2.3