diff options
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | TODO.md | 28 | ||||
| -rw-r--r-- | completions/gitctl.bash | 9 | ||||
| -rwxr-xr-x | gitctl | 20 | ||||
| -rw-r--r-- | skills/gitctl/SKILL.md | 14 |
5 files changed, 57 insertions, 23 deletions
@@ -177,15 +177,22 @@ gitctl sync End-to-end agent flow: ``` -gitctl repo create publisher --section SlackBuilds --desc "..." +gitctl -y repo create publisher --section SlackBuilds --desc "..." gitctl repo add-remote publisher --remote-name origin git push -u origin master ``` +`-y` skips the y/N so an agent can drive create; the push itself still prompts +for the SSH key, so it is not fully unattended. ## Notes - `repo create` shows the gitolite.conf diff and asks before committing. On decline it reverts the conf edit, leaving a clean tree. +- `-y` / `--yes` (a global flag, so it goes BEFORE the subcommand: + `gitctl -y repo create ...`) skips that y/N confirmation. The diff is still + printed. It does NOT make creation unattended: the phase-1 push still needs + the SSH key password or smartcard touch, which `-y` cannot supply. It is + never honored by a destructive delete. - `repo list` lists every bare repo on disk, not just the cgit-exposed ones, so private repos show too. A `VIS` column marks each `PUB` (present in cgitrc) or `PRIV`; on a terminal public rows are green and private dim. The description @@ -66,22 +66,22 @@ Still open from the original plan: extend bash completion to complete repo NAMES (for `desc` / `delete` / `add-remote`) now that a list verb exists. The `repo list` action itself is already in completion. -## 3. `-y` / `--yes` flag on `repo create` +## 3. `-y` / `--yes` flag -- DONE -Let an agent drive `repo create` without the interactive y/N, so the only human -step left is supplying the SSH-key password / smartcard PIN for the push. +`-y` / `--yes` is implemented as a GLOBAL flag (on the top-level parser), so it +goes before the subcommand: `gitctl -y repo create ...`. Today the only y/N it +skips is the phase-1 push confirmation in `repo create`; the diff is still +printed and an "auto-confirmed" note shown. It does NOT bypass SSH key auth +(password / smartcard touch), so creation is never fully unattended. Section +auto-create was already silent, so behavior stays consistent. -- Add `--yes` (`-y`) to `repo create`; when set, skip the `_confirm` prompt and - proceed to commit + push. -- IMPORTANT: `-y` does NOT remove the need for a human at the SSH key. The - phase-1 `git push` to gitolite-admin still requires the key's password or the - card touch/PIN, which an agent cannot supply. `-y` removes only the y/N - diff-confirmation; the push auth is still interactive. Document this clearly - so nobody expects fully unattended creation. -- Still print the diff before pushing even with `-y` (visibility), just do not - block on it. -- Consider whether `-y` should also auto-create a missing section (it already - does that without asking) - keep behavior consistent. +Hard wall for delete (item 1): `-y` must NEVER be honored by a destructive +delete. The flag help says so, this note records it, and the global -y comment +in the client points at item 1. When delete is built, it reads its own +confirmation and ignores `a.yes`. + +Bash completion offers `-y`/`--yes` at the top level and transparently skips a +leading `-y` when resolving group/action. ## Cross-cutting diff --git a/completions/gitctl.bash b/completions/gitctl.bash index 6fead65..3d072fe 100644 --- a/completions/gitctl.bash +++ b/completions/gitctl.bash @@ -12,7 +12,12 @@ _gitctl() { cword=$COMP_CWORD } - local group=${words[1]} action=${words[2]} + # global -y/--yes may sit before the subcommand; skip it so group/action and + # the cword positions below line up whether or not it is present. + local off=0 + [[ ${words[1]} == "-y" || ${words[1]} == "--yes" ]] && off=1 + local group=${words[1+off]} action=${words[2+off]} + local cword=$((cword - off)) # complete section names from the live server (one ssh call). Only when the # previous word is --section, so we do not pay it on every Tab. @@ -26,7 +31,7 @@ _gitctl() { case $cword in 1) - COMPREPLY=($(compgen -W "sections sync repo" -- "$cur")) + COMPREPLY=($(compgen -W "-y --yes sections sync repo" -- "$cur")) return ;; 2) case $group in @@ -119,6 +119,12 @@ 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") + # 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() + assert pr.parse_args(["repo", "create", "r", "--section", "S"]).yes is False + assert pr.parse_args(["-y", "repo", "create", "r", "--section", "S"]).yes is True + assert pr.parse_args(["--yes", "sections", "list"]).yes is True print("self-test OK") @@ -189,8 +195,10 @@ def cmd_create(cfg, a): with open(conf_path, "w") as f: f.write(add_stanza(conf, a.name, cfg["rw_user"])) diff = git(cfg, "diff", "--", "conf/gitolite.conf", capture=True).stdout - print(diff) - if not _confirm("Commit and push this gitolite.conf change?"): + print(diff) # always shown, even with -y, for visibility + if a.yes: + print("-y: auto-confirmed (the push may still prompt for the SSH key)") + elif not _confirm("Commit and push this gitolite.conf change?"): git(cfg, "checkout", "--", "conf/gitolite.conf") sys.exit("aborted: reverted conf/gitolite.conf, no changes pushed") git(cfg, "add", "conf/gitolite.conf") @@ -225,6 +233,14 @@ def build_parser(): "calls a server helper (run as the git user) over a restricted SSH key for the " "privileged cgit edits. Reads ~/.config/gitctl/config.toml; pass " "--self-test to run the built-in assertions.") + # Global -y. Skips additive y/N confirmations (today: the repo-create + # gitolite.conf push). It does NOT make creation unattended: the phase-1 + # push still needs the SSH key password / smartcard touch, which -y cannot + # supply. NEVER honored by a future `repo delete` (a destructive move must + # always confirm on its own); see TODO item 1. + p.add_argument("-y", "--yes", action="store_true", + help="skip y/N confirmation of additive changes (still prints the diff; " + "does NOT bypass SSH key auth, and is never honored by repo delete)") sub = p.add_subparsers(dest="group", required=True, help="command group") sections = sub.add_parser( diff --git a/skills/gitctl/SKILL.md b/skills/gitctl/SKILL.md index 9d6ac17..8660669 100644 --- a/skills/gitctl/SKILL.md +++ b/skills/gitctl/SKILL.md @@ -36,6 +36,11 @@ Phase 1 edits the local gitolite-admin clone, shows the diff, **asks y/N**, commits, pushes, then polls until gitolite builds the bare repo. Phase 2 adds it to cgit under the section and (if given) sets the description. +Pass global **`-y` BEFORE the subcommand** (`gitctl -y repo create ...`) to skip +the y/N. The diff still prints. `-y` does NOT remove the SSH-key step: the +phase-1 push still needs the key password / smartcard touch, so creation is +still not fully unattended. + Then wire up the new repo for the user: ``` gitctl repo add-remote <name> --remote-name origin @@ -44,10 +49,11 @@ git push -u origin master ## Gotchas -- **`repo create` prompts y/N -> it needs a TTY.** Do not run it through a - non-interactive pipe expecting it to just work; let the user confirm, or have - them run it. The phase-1 push may also require a hardware-key touch/PIN, which - only works interactively. +- **`repo create` prompts y/N -> use `gitctl -y repo create ...` to skip it** + (the `-y` is global, so it goes before `repo`). Even with `-y` the phase-1 + push may require the SSH key password or a hardware-key touch/PIN, which only + works interactively, so creation is never fully unattended. Without `-y` it + needs a TTY for the y/N; do not pipe it non-interactively expecting success. - **If the push fails after the local commit** (key refused, auth failure): the gitolite-admin commit is made but unpushed. The user pushes manually (`git -C <admin-clone> push origin <branch>`), then you RE-RUN the same |
