aboutsummaryrefslogtreecommitdiffstats
path: root/TODO.md
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-06-24 10:19:30 +0200
committerDanilo M. <danix@danix.xyz>2026-06-24 10:19:30 +0200
commit02a5ae2adc2df39fc78fd7e29904ff473e9266a0 (patch)
treebbe270a6302526476147430ce7614e17ac7cc1df /TODO.md
parent345968271dec38b33a32649d8cfb767c2806330d (diff)
downloadgitctl-02a5ae2adc2df39fc78fd7e29904ff473e9266a0.tar.gz
gitctl-02a5ae2adc2df39fc78fd7e29904ff473e9266a0.zip
feat: repo delete - remove repo, move bare repo to trash
gitctl repo delete <name> removes a repo from all three places it lives, the inverse of repo create: - gitolite stanza via remove_stanza (inverse of add_stanza), committed and pushed FIRST so gitolite stops serving the repo before its bytes move. - cgit block via the new delete-repo helper verb (remove_repo_block, inverse of insert_repo_block), backed up and synced. - the bare repo is MOVED to TRASH_DIR (/var/lib/gitolite3/trash, timestamped) with a .trashinfo breadcrumb, not rm, so it is recoverable. A realpath check keeps the move one level under REPO_BASE. Always confirms; the global -y is deliberately not honored (cmd_delete never reads a.yes). Idempotent: a piece already gone is skipped. Partial failure is resumable by re-running, like create. Both inverse functions are pinned by add/insert-then-remove round-trip self-tests plus middle/last/missing cases; trash_repo and do_delete_repo run on temp dirs. Verified live: create then delete round-trips against the real gitolite.conf, bare repo lands in the trash dir intact. Helper changed: redeploy gitctl-helper to the server. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'TODO.md')
-rw-r--r--TODO.md64
1 files changed, 22 insertions, 42 deletions
diff --git a/TODO.md b/TODO.md
index c457702..7889779 100644
--- a/TODO.md
+++ b/TODO.md
@@ -4,51 +4,31 @@ Planned features. Each note captures the non-obvious bits so the work does not
re-discover them. Keep the hard rules from CLAUDE.md (stdlib only, single-file
scripts, single-writer repo.desc, idempotent, no em dashes, work on master).
-## 1. delete repo
+## 1. delete repo -- DONE
-`gitctl repo delete <name>` - remove a repo from all three places it lives.
+`gitctl repo delete <name>` is implemented. It removes a repo from all three
+places it lives, the inverse of `repo create`:
+- gitolite stanza -> client `remove_stanza` (inverse of `add_stanza`), commit +
+ push (needs the SSH key / card, like create). PUSHED FIRST so gitolite stops
+ serving the repo before its bytes move.
+- cgit block -> helper verb `delete-repo`, `remove_repo_block` (inverse of
+ `insert_repo_block`); deletes the repo.url/path/owner block + its spacer
+ blanks, leaves the section header. Backed up by write_cgitrc_lines, sync after.
+- bare repo -> `trash_repo` MOVES it to `TRASH_DIR` (`/var/lib/gitolite3/trash`,
+ outside REPO_BASE, timestamped name) instead of rm, with a `.trashinfo`
+ breadcrumb. shutil.move, with a realpath check that the source is one level
+ under REPO_BASE (defense in depth on top of validate_url).
-Touches three writers, so it is the inverse of `repo create`:
-- gitolite.conf stanza -> client edits the admin clone, pushes (needs the SSH
- key / card, like create).
-- cgit block in /etc/cgitrc -> new helper verb, the inverse of
- `insert_repo_block`: find the repo's `repo.url=` line and delete its block up
- to the next `repo.url=`, `section=`, banner, or EOF. Back up first
- (write_cgitrc_lines already does). Run sync after.
-- the bare repo on disk -> gitolite does NOT delete it when the stanza goes
- away. The helper does NOT rm it. Instead it MOVES the repo to a trash dir
- (see below), so deletion is reversible and you review before anything is
- really gone.
+The helper exposes ONE verb (`delete-repo`) doing cgit-block removal + move +
+.trashinfo. Always confirms (the global `-y` is deliberately NOT read by
+cmd_delete). Idempotent: a piece already gone is skipped, not an error. Partial
+failure (push ok, helper fails, or commit ok but push fails) is resumable by
+re-running, same as create. Self-tests: remove_repo_block and remove_stanza both
+pinned by an insert/add-then-remove round-trip plus middle/last/missing cases;
+trash_repo and do_delete_repo exercised on temp dirs.
-Trash dir (the chosen design, not rm -rf):
-- New constant `TRASH_DIR`, OUTSIDE `REPO_BASE` so gitolite and cgit never see
- it (e.g. `/var/lib/gitolite3/trash`). git-writable.
-- Delete = `mv REPO_BASE/<name>.git TRASH_DIR/<name>.git.<timestamp>`. Atomic
- within one filesystem; if TRASH_DIR is on a different mount, fall back to
- copy+remove or just require same-fs.
-- validate_url first; double-check the resolved source path is under REPO_BASE
- before moving (defense in depth, even though validate_url blocks `..`/`/`).
-- Leave a note for the user: print where it went and that it needs manual
- review/removal. Optionally drop a short TRASH_DIR/README or a per-entry
- `.trashinfo` (origin path, timestamp, who) so a later cleanup tool has
- context.
-- FUTURE: a helper verb like `gc-trash --older-than <days>` that prunes
- TRASH_DIR entries past a retention window, runnable from cron. The move-based
- design makes this trivial later; do not build it now (YAGNI), just keep
- TRASH_DIR layout cron-friendly (timestamp in the name).
-
-Design notes:
-- DESTRUCTIVE (even as a move - it disappears from the live tree). Always
- confirm, show what will be removed and where it goes. The `-y` flag from
- item 3 must NOT bypass the delete confirmation (or gate it behind a separate,
- louder opt-in). Deleting is not the same as auto-confirming an additive diff.
-- Idempotent-ish: if the repo is already gone from a given place, skip that
- step and report, do not error.
-- Recoverable: the bare repo sits in TRASH_DIR until you remove it; the cgitrc
- backup covers the cgit block. Nothing is irreversibly destroyed by gitctl.
-- Order: remove cgit block + move bare repo via helper first, then the gitolite
- stanza push? Or stanza first? Decide and document - partial failure (push
- fails after the move) should leave a clear, resumable state like create does.
+FUTURE (not built, YAGNI): a `gc-trash --older-than <days>` prune verb for cron.
+The timestamped TRASH_DIR layout already supports it.
## 2. list repos -- DONE