diff options
| author | Danilo M. <danix@danix.xyz> | 2026-06-29 18:23:14 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-06-29 18:23:14 +0200 |
| commit | 9359ec0eaedf8b8f8b68a33b96507589e0e2c651 (patch) | |
| tree | 689eee26c804c95a90e84657d76add8568652679 | |
| parent | ffe0864406cd67f82071becf494f0dd9e701e149 (diff) | |
| download | gitctl-9359ec0eaedf8b8f8b68a33b96507589e0e2c651.tar.gz gitctl-9359ec0eaedf8b8f8b68a33b96507589e0e2c651.zip | |
feat: repo create --private - Phase 1 only, never added to cgit
--private makes the gitolite stanza and bare repo but skips the helper
call, so the repo is never written to cgitrc and stays off the public
cgit index (shows PRIV in repo list). --section is required unless
--private. Bash completion offers --private; README and CLAUDE.md
document the flag and the local /usr/local/bin install.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| -rw-r--r-- | CLAUDE.md | 7 | ||||
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | completions/gitctl.bash | 2 | ||||
| -rwxr-xr-x | gitctl | 21 |
4 files changed, 31 insertions, 4 deletions
@@ -10,6 +10,8 @@ Two single-file scripts, no shared module: - `gitctl`: client, runs on the laptop. Dumb caller: loads TOML config, edits the local gitolite-admin clone and pushes, shells out to the helper over SSH. + Installed locally at `/usr/local/bin/gitctl` (the user copies it there by + hand; not done by this repo). - `gitctl-helper`: server helper, runs as the git user over a `command=`-restricted SSH key. Holds all parsing-heavy logic. Dispatches on `SSH_ORIGINAL_COMMAND`. @@ -57,6 +59,11 @@ separate `AuthorizedKeysFile` (`~git/.ssh/gitctl_keys`) wired via a - **`description` file is owned `git:gitolite3`** (system user git, group gitolite3, no gitolite3 user). `set-desc` writes in place (truncate-write) to preserve owner, with `os.chown` back as insurance. +- **`repo create --private` runs Phase 1 only.** It makes the gitolite stanza + and bare repo but never calls the helper, so the repo is never written to + cgitrc and stays off the public cgit index (shows `PRIV` in `repo list`). + `--section` is required unless `--private` (enforced in `cmd_create`, not + argparse, since the rule is conditional). - Every operation is idempotent and safe to re-run. - No em dashes in user-facing text. - Work on `master`, no feature branches. @@ -173,6 +173,7 @@ ssh git_push info # gitolite access check gitctl sections list gitctl sections add "Generic Projects" gitctl repo create publisher --section SlackBuilds --desc "..." +gitctl repo create secrets --private gitctl repo list gitctl repo desc publisher "a new description" gitctl repo add-remote publisher --remote-name origin @@ -193,6 +194,10 @@ for the SSH key, so it is not fully unattended. - `repo create` shows the gitolite.conf diff and asks before committing. On decline it reverts the conf edit, leaving a clean tree. +- `--private` creates a Phase 1 only repo: the gitolite stanza and bare repo + are made, but the repo is never added to cgitrc, so it stays off the public + cgit index. `--section` is not required (and is ignored) with `--private`; + the repo shows as `PRIV` in `repo list`. - `-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 diff --git a/completions/gitctl.bash b/completions/gitctl.bash index 321a917..497d0a8 100644 --- a/completions/gitctl.bash +++ b/completions/gitctl.bash @@ -58,7 +58,7 @@ _gitctl() { # flags, by subcommand if [[ $cur == -* ]]; then case "$group $action" in - "repo create") COMPREPLY=($(compgen -W "--section --desc --owner" -- "$cur")) ;; + "repo create") COMPREPLY=($(compgen -W "--section --private --desc --owner" -- "$cur")) ;; "repo add-remote") COMPREPLY=($(compgen -W "--remote-name" -- "$cur")) ;; esac return @@ -177,6 +177,10 @@ def _self_test(): # repo delete takes a name; -y parses but cmd_delete ignores it (always confirms) da = pr.parse_args(["-y", "repo", "delete", "doomed"]) assert da.action == "delete" and da.name == "doomed" and da.yes is True + # --private: defaults off, --section no longer hard-required at the parser level + assert pr.parse_args(["repo", "create", "r", "--section", "S"]).private is False + pa = pr.parse_args(["repo", "create", "r", "--private"]) + assert pa.private is True and pa.section is None print("self-test OK") @@ -232,6 +236,8 @@ def _confirm(prompt): def cmd_create(cfg, a): + if not a.private and not a.section: + sys.exit("error: --section is required unless --private is given") owner = a.owner or cfg["default_owner"] conf_path = os.path.join(cfg["admin_clone_path"], "conf", "gitolite.conf") @@ -266,7 +272,11 @@ def cmd_create(cfg, a): else: sys.exit(f"error: timed out waiting for {a.name}.git on the server") - # PHASE 2: server-side via helper + # PHASE 2: server-side via helper. Skipped for private repos: they are + # never written to cgitrc, so they stay off the public cgit index. + if a.private: + print(f"done: {a.name} created (private, not in cgit)") + return 0 sections = call_helper(cfg, ["list-sections"], check=False).stdout.split("\n") if a.section not in [s.strip() for s in sections if s.strip()]: call_helper(cfg, ["add-section", "--name", a.section]) @@ -361,10 +371,15 @@ def build_parser(): "pushing (declining reverts the edit), then polls until gitolite " "has built the bare repo. Phase 2 ensures the cgit section exists, " "files the repo under it, and optionally sets its description. " + "With --private, Phase 2 is skipped: the repo is never added to " + "cgitrc, so it stays off the public cgit index. " "Every step is idempotent and safe to re-run.") rc.add_argument("name", help="repo name; used for the gitolite stanza, the bare repo, and cgit") - rc.add_argument("--section", required=True, - help="cgit section to file the repo under; created automatically if missing") + rc.add_argument("--section", + help="cgit section to file the repo under; created automatically if " + "missing. Required unless --private.") + rc.add_argument("--private", action="store_true", + help="create a private repo: Phase 1 only, never added to cgit") rc.add_argument("--desc", help="optional initial cgit description for the repo") rc.add_argument("--owner", help="cgit repo.owner string (default: default_owner from config)") |
