""" Sopel plugin: bare git repo management !git list list all repos !git desc set repo description !git hook install a named post-receive hook !git remove remove a repo (confirms first) """ import os import shutil import subprocess from sopel import plugin REPOS = "/home/git/repos" HOOKS = "/home/ahmed/scripts/git-hooks" _pending_remove = {} def _owner(bot, trigger): return trigger.nick == bot.settings.core.owner def _repos(): try: return sorted( e for e in os.listdir(REPOS) if os.path.isdir(os.path.join(REPOS, e)) ) except OSError: return [] def _desc(name): path = os.path.join(REPOS, name, "description") try: text = open(path).read().strip() if text.startswith("Unnamed repository"): return "" return text except OSError: return "" def _last_commit(name): try: r = subprocess.run( ["git", "-C", os.path.join(REPOS, name), "log", "-1", "--format=%cr"], capture_output=True, text=True, timeout=5, ) return r.stdout.strip() or "no commits" except Exception: return "?" @plugin.command("git") def cmd_git(bot, trigger): if not _owner(bot, trigger): return args = (trigger.group(2) or "").split() if not args: bot.say("usage: !git ") return action = args[0] if action == "list": repos = _repos() if not repos: bot.say("no repos") return for name in repos: desc = _desc(name) last = _last_commit(name) line = f"{name} ({last})" if desc: line += f" {desc}" bot.say(line) elif action == "desc": if len(args) < 3: bot.say("usage: !git desc ") return name, text = args[1], " ".join(args[2:]) path = os.path.join(REPOS, name) if not os.path.isdir(path): bot.say(f"{name}: not found") return with open(os.path.join(path, "description"), "w") as f: f.write(text + "\n") bot.say(f"{name}: description updated") elif action == "hook": if len(args) < 3: available = [ f[len("post-receive-"):-len(".sh")] for f in os.listdir(HOOKS) if f.startswith("post-receive-") and f.endswith(".sh") ] if os.path.isdir(HOOKS) else [] bot.say(f"usage: !git hook available: {', '.join(available) or 'none'}") return name, hooktype = args[1], args[2] repo_path = os.path.join(REPOS, name) if not os.path.isdir(repo_path): bot.say(f"{name}: not found") return src = os.path.join(HOOKS, f"post-receive-{hooktype}.sh") if not os.path.isfile(src): bot.say(f"{hooktype}: hook template not found") return dst = os.path.join(repo_path, "hooks", "post-receive") shutil.copy2(src, dst) os.chmod(dst, 0o755) bot.say(f"{name}: {hooktype} hook installed") elif action == "remove": if len(args) < 2: bot.say("usage: !git remove ") return name = args[1] path = os.path.join(REPOS, name) if not os.path.isdir(path): bot.say(f"{name}: not found") return channel = str(trigger.sender) _pending_remove[channel] = path bot.say(f"remove {name}? reply !git yes or !git no") elif action == "yes": channel = str(trigger.sender) path = _pending_remove.pop(channel, None) if not path: bot.say("nothing pending") return name = os.path.basename(path) shutil.rmtree(path) bot.say(f"{name}: removed") elif action == "no": channel = str(trigger.sender) _pending_remove.pop(channel, None) bot.say("cancelled") else: bot.say("usage: !git ")