diff options
| author | Ahmed <git@gumx.cc> | 2026-06-14 01:46:29 +0300 |
|---|---|---|
| committer | Ahmed <git@gumx.cc> | 2026-06-14 01:46:29 +0300 |
| commit | 5a8d568931d9b23ce0df1265d05259a7012081c9 (patch) | |
| tree | 4ea19b8bb1763a38246f342f172c285f6579e09b /alfred/git.py | |
init: mostly vibed
Diffstat (limited to 'alfred/git.py')
| -rw-r--r-- | alfred/git.py | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/alfred/git.py b/alfred/git.py new file mode 100644 index 0000000..107a3b1 --- /dev/null +++ b/alfred/git.py @@ -0,0 +1,148 @@ +""" +Sopel plugin: bare git repo management + + !git list list all repos + !git desc <repo> <text> set repo description + !git hook <repo> <type> install a named post-receive hook + !git remove <repo> 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 <list|desc|hook|remove>") + 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 <repo> <description>") + 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 <repo> <type> 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 <repo>") + 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 <list|desc|hook|remove>") |
