aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-06-24 10:33:08 +0200
committerDanilo M. <danix@danix.xyz>2026-06-24 10:33:08 +0200
commit40cd61ce7e0c4a8d35d0e0a505994f9f9b5e4d67 (patch)
tree245aba80c137bea9e22662ea4a6363741d4f97fe
parent9c301a8eac944083556b3dce297764b4fca7d49a (diff)
downloadgitctl-40cd61ce7e0c4a8d35d0e0a505994f9f9b5e4d67.tar.gz
gitctl-40cd61ce7e0c4a8d35d0e0a505994f9f9b5e4d67.zip
feat: repo-name completion + label helper passthrough output
- Bash completion now completes repo NAMES for desc/delete/add-remote at the name position, sourced from `gitctl repo list` (first field, header row dropped). One ssh call, only at that position, so a stray Tab never hits the network; the leading -y is handled. - call_helper prefixes every passthrough line from the helper (and the sync script it runs) with "server: " via _server_prefixed, so output like "No changes." reads as remote output instead of interleaved local noise. quiet=True callers (repo list) still get raw stdout to parse. Client-only, no helper redeploy needed. Self-test pins _server_prefixed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--TODO.md28
-rw-r--r--completions/gitctl.bash14
-rwxr-xr-xgitctl15
3 files changed, 41 insertions, 16 deletions
diff --git a/TODO.md b/TODO.md
index 9bb4ad8..677217b 100644
--- a/TODO.md
+++ b/TODO.md
@@ -69,22 +69,22 @@ Done as part of items 1-3: both SKILL.md files updated (delete + `-y` limit),
self-test coverage for every new helper verb, completion actions for `list` and
`delete`, README and CLAUDE.md brought current.
-## Remaining (not started)
+## Remaining
-These are the leftovers after the three features shipped. None block anything.
+1. Repo-name bash completion -- DONE. `desc`, `delete`, and `add-remote`
+ complete repo NAMES at the name position from `gitctl repo list` (first
+ field, header dropped), one ssh call, only at that position so a stray Tab
+ never hits the network. `-y` before the subcommand is handled.
-1. Repo-name bash completion. Now that `repo list` exists, complete repo NAMES
- for `desc`, `delete`, and `add-remote` (the way `--section` is completed from
- the server today). One ssh call (`gitctl repo list`, cut field 1), only when
- completing the repo-name argument, so a stray Tab does not hit the network.
-
-2. Quiet the stray `No changes.` line. In `repo create` and `repo delete` the
- `list-sections` / `add-section` helper passthrough prints a `No changes.`
- line out of order, before the cgit-block message. Cosmetic only. Fix by
- calling those helper steps with `quiet=True` (the flag already exists) and
- printing a clean status, or by suppressing that specific echo.
+2. Stray sync-script echo -- DONE. The `No changes.` / `Wrote N change(s).`
+ lines come from the server sync script, passed through over ssh. Rather than
+ hide them, `call_helper` now prefixes every passthrough line with `server: `
+ (`_server_prefixed`), so they read clearly as remote output instead of
+ interleaved local noise.
3. FUTURE / YAGNI: `gc-trash --older-than <days>` helper verb to prune
`TRASH_DIR` entries past a retention window, runnable from cron. The
- timestamped trash layout already supports it; do not build until there is an
- actual need to reclaim space.
+ timestamped trash layout already supports it. It would be the ONLY real
+ `rm -rf` in the tool, so it needs hard guards (realpath-under-TRASH_DIR
+ check, no symlink follow, validated timestamp parse) and a dry-run default.
+ Do not build until trash actually accumulates and space needs reclaiming.
diff --git a/completions/gitctl.bash b/completions/gitctl.bash
index 929346d..321a917 100644
--- a/completions/gitctl.bash
+++ b/completions/gitctl.bash
@@ -29,6 +29,20 @@ _gitctl() {
return
fi
+ # complete repo names for the name argument of desc/delete/add-remote (one
+ # ssh call). Only at the name position and not while typing a flag, so a
+ # stray Tab elsewhere never hits the network. `repo list` prints an aligned
+ # table (no color when not a TTY, as here); the name is the first field, and
+ # we drop the NAME header row.
+ if [[ $cword -eq 3 && $group == repo && $cur != -* \
+ && ( $action == desc || $action == delete || $action == add-remote ) ]]; then
+ local repos
+ repos=$(gitctl repo list 2>/dev/null | awk 'NR>1 {print $1}')
+ local IFS=$'\n'
+ COMPREPLY=($(compgen -W "$repos" -- "$cur"))
+ return
+ fi
+
case $cword in
1)
COMPREPLY=($(compgen -W "-y --yes sections sync repo" -- "$cur"))
diff --git a/gitctl b/gitctl
index 45f54bb..1140b4c 100755
--- a/gitctl
+++ b/gitctl
@@ -50,6 +50,14 @@ def helper_command(args):
return " ".join(shlex.quote(a) for a in args)
+def _server_prefixed(text):
+ """Prefix each non-empty line with 'server: ' so passthrough output from the
+ helper (and the sync script it runs) reads as remote output, not interleaved
+ local noise."""
+ return "".join(("server: " + ln if ln.strip() else ln) + "\n"
+ for ln in text.splitlines())
+
+
def call_helper(cfg, args, check=True, quiet=False):
"""Run a helper verb. Returns CompletedProcess. ssh sends the verb string;
the restricted key forces gitctl-helper, which reads SSH_ORIGINAL_COMMAND.
@@ -57,9 +65,9 @@ def call_helper(cfg, args, check=True, quiet=False):
cmd = ["ssh", cfg["helper_ssh_alias"], helper_command(args)]
r = subprocess.run(cmd, text=True, capture_output=True)
if r.stdout and not quiet:
- sys.stdout.write(r.stdout)
+ sys.stdout.write(_server_prefixed(r.stdout))
if r.stderr:
- sys.stderr.write(r.stderr)
+ sys.stderr.write(_server_prefixed(r.stderr))
if check and r.returncode != 0:
sys.exit(f"error: helper '{args[0]}' failed (exit {r.returncode})")
return r
@@ -157,6 +165,9 @@ def _self_test():
# color on: public row green, private dim, header uncolored.
tc = format_repo_table([("cad", "PUB", "G", "d"), ("s", "PRIV", "", "")], color=True)
assert GREEN in tc and DIM in tc and tc.startswith("NAME")
+ # server passthrough: each non-empty line is prefixed, blanks left bare.
+ assert _server_prefixed("No changes.\n") == "server: No changes.\n"
+ assert _server_prefixed("a\n\nb") == "server: a\n\nserver: b\n"
# global -y/--yes: defaults off, set by either spelling, lands on the namespace
# regardless of subcommand (so cmd_create can read a.yes).
pr = build_parser()