aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-06-24 09:57:33 +0200
committerDanilo M. <danix@danix.xyz>2026-06-24 09:57:33 +0200
commit345968271dec38b33a32649d8cfb767c2806330d (patch)
treedf90e30c02189a38628bc10829fecfebec0b53d4
parenteeaba25642392532901cd4c95c23054e9a7ba2b3 (diff)
downloadgitctl-345968271dec38b33a32649d8cfb767c2806330d.tar.gz
gitctl-345968271dec38b33a32649d8cfb767c2806330d.zip
feat: global -y/--yes flag to skip additive confirmations
repo create's y/N push confirmation can now be skipped with -y. The flag is global (top-level parser), so it goes before the subcommand: gitctl -y repo create ... . The diff is still printed and an auto-confirmed note shown. -y does NOT bypass SSH key auth (the phase-1 push still needs the password / smartcard touch), so creation is never fully unattended. Hard wall: -y is never honored by a future destructive delete; the flag help, a client comment, and TODO item 1 all record this. Bash completion offers -y at the top level and transparently skips a leading -y when resolving the subcommand. Self-test pins the parse round-trip. Client-only change; the server helper is unchanged, no redeploy needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-rw-r--r--README.md9
-rw-r--r--TODO.md28
-rw-r--r--completions/gitctl.bash9
-rwxr-xr-xgitctl20
-rw-r--r--skills/gitctl/SKILL.md14
5 files changed, 57 insertions, 23 deletions
diff --git a/README.md b/README.md
index 96fdad3..43f852b 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/TODO.md b/TODO.md
index 6318eae..c457702 100644
--- a/TODO.md
+++ b/TODO.md
@@ -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
diff --git a/gitctl b/gitctl
index 11d9449..df0bb16 100755
--- a/gitctl
+++ b/gitctl
@@ -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