Changelog

Què ha anat passant

El registre de commits del CLI, del més recent al més antic, directe de GitLab. Cada versió queda marcada on apunta la seva etiqueta, amb les seves notes de CHANGELOG.md a sota.

actual: 0.13.0 · 100 commits · la història completa a GitLab

  1. 0.13.0

    Added

    • torii save --stage — stage paths without committing, so a change can be inspected (torii scan, torii diff --staged) before deciding to commit or back out. Reverses cleanly with the existing torii save --unstage. Closes the gap that previously forced git add -N as a workaround — which doesn't actually stage content, only records intent-to-add, so a scanner reading the index sees nothing there.

    Fixed

    • Secret scanner: several real detection gaps, found by testing actual glued-assignment shapes rather than assuming coverage.
    • GitHub/GitLab tokens (ghp_, gho_, ghs_, github_pat_, glpat-, glptt-), AWS access keys (AKIA…/ASIA…/AROA…), Stripe keys, and SendGrid keys (SG.…) were only detected when the token appeared as its own whitespace-delimited word. The extremely common KEY=token shape with no surrounding spaces (.env files, -e KEY=value container args) turned the whole assignment into one word that didn't *start with* the prefix, so it slipped through undetected.
    • Added detection for a literal Bearer <token> header value with no vendor-specific prefix — previously uncovered by any pattern.
    • The "Database connection string with credentials" pattern flagged a bare scheme://user@host (no password at all — SSH-style, nothing to leak) as if it carried a real credential. It now checks the actual userinfo segment for a user:password@ shape.
    • The connection-string scheme list was missing postgres:// (the short/libpq alias postgresql:// — Heroku, sqlx, psycopg, node-postgres all use it) and mongodb+srv:// (MongoDB Atlas' standard form), so a real credential under either spelling was invisible regardless of length. There was never an actual length threshold on the user, password, or host — verified by testing 1-character values against a scheme that already matched — the missing scheme aliases were the whole story. While in there: the word-based password-placeholder exclusions (password, pass, changeme, xxx, yourpassword) were removed — those are guessable strings a real weak password can collide with, so now only *syntactic* placeholders (${VAR}, $VAR, <placeholder>) are excluded. A doc example like user:pass@localhost will now be flagged; clear it with --yes or silence it for good via .toriignore's [secrets] allowlist.
    • False positive: typed field declarations no longer flagged as secrets. pub api_key: Arc<RwLock<String>> (and similar generic-wrapped type annotations) tripped the "Generic API key / token" rule even though no value is present.
    • torii status and torii diff --staged no longer crash before the first commit. A repo with staged content but no commits yet (an "unborn" HEAD) — exactly the state torii save --stage can now put you in — used to error with reference 'refs/heads/main' not found.
  2. 27da3e6e fix close missing DB-scheme aliases, stop excluding guessable passwords
  3. d8eb9a56 chore bump version to 0.13.0
  4. a43909df feat add --stage to prepare files without committing
  5. 66abeece fix status and diff --staged no longer crash on unborn HEAD
  6. 611e2a74 fix close secret-scanner detection gaps and false positives
  7. d6913d43 build vendor libgit2 (git2 vendored-libgit2 feature)
  8. 04594005 feat GitLab support for remote contents
  9. df924ba3 feat remote repo contents (tree + file) without cloning
  10. 2108ff4d fix record MERGE_HEAD parents when committing a merge resolution
  11. aae0d43f fix resolve remote-tracking branches when integrating
  12. e8d958c0 fix use the GitHub REST API instead of gh for repo settings
  13. 6d603f5c feat filter-branch/filter-repo suite; bump 0.12.0
  14. e3b9c0d6 feat history reword + date-preserving save/amend; bump 0.11.0
  15. 801c58e8 style clear all 325 clippy warnings across the workspace
  16. 0.10.0

    Added

    • torii-lib published as a standalone crate — the domain layer (VCS operations, platform clients, workspaces, config/auth) split out of the binary. gitorii now depends on it like any consumer; the upcoming torii-cloud plugin links against the same API.
    • torii save --yes (-y) — auto-confirm the secret-scanner prompt. Required to commit past findings when stdin is not a TTY.
    • GitHub clients accept a configurable API base URL (groundwork for GitHub Enterprise; previously hardcoded to api.github.com).
    • Test suite grew from 97 to 282 tests: per-platform parser fixtures, httpmock-backed client tests (auth headers, status mapping, error variants), TUI state tests, CLI dispatch tests, and process-level regression tests for SIGPIPE and the no-TTY save.

    Fixed

    • torii log | head no longer panics with "failed printing to stdout: Broken pipe" — SIGPIPE is restored to its default disposition at startup, so mid-pipe exits behave like git's.
    • torii rename works on directories — previously the move happened on disk but indexing failed (Index::add_path only accepts files). Tracked entries under the old prefix are now re-recorded recursively; untracked files stay untracked.
    • torii save no longer hangs without a TTY when the secret scanner finds something — it fails fast pointing at --yes instead of waiting forever on a [y/N] prompt nobody can answer.
    • TUI commit editor no longer panics on multibyte input (ñ, á, emoji) — cursor char-indices were used as byte offsets.

    Changed

    • Workspace split: crates/torii-lib (library, import name torii_lib) + crates/torii (CLI/TUI binary, package gitorii).
    • Modularised the three largest files: cli.rs (6.6k lines → clap surface + 24 domain modules), tui/app.rs and tui/events.rs (per-view modules), and platforms/ reorganised by platform (github/, gitlab/, gitea/, sourcehut/, radicle/, bitbucket/, azure/) with shared traits/types/factories per feature.
    • Error precision migration completed: ~415 catch-all InvalidConfig sites moved to precise variants — Network, PlatformApi (with real HTTP status), Auth, plus new MalformedResponse, Subprocess, Fs, RepoState, Workspace, Unsupported and Usage. The remaining InvalidConfig uses are genuine configuration errors.
    • Removed a stale TSAL license header from main.rs (the project relicensed to MIT OR Apache-2.0 in 0.9.0).
  17. 15bb7abe chore regenerate Cargo.lock for 0.10.0
  18. 504dfb08 chore bump to 0.10.0 — torii-lib split, precise errors, fixes
  19. 23fa73c5 refactor package torii-lib (import torii_lib) — nombre registrable
  20. 0c31828c style cargo fmt over the workspace
  21. b501380e fix fail fast on findings without a TTY; add --yes
  22. 00ca8428 fix reindex tracked entries when renaming a directory
  23. 79b9702e chore drop stale TSAL header — relicensed MIT/Apache in 0.9.0
  24. 1f41b1ea refactor workspace lib+bin — crates/torii-core + crates/torii
  25. 41af42f1 refactor WorkspaceManager devuelve datos, el CLI imprime
  26. b93fcd13 refactor sellar fugas git2 de la API pública + vcs/sign.rs
  27. e0e4878a refactor status() devuelve RepoStatus en vez de imprimir
  28. af72c535 chore regenerate Cargo.lock for 0.9.2
  29. 0.9.2

    Changed

    • README badges updated: license-customMIT OR Apache-2.0, Rust badge bumped to 1.95+.
    • rust-version in Cargo.toml bumped from 1.85 to 1.95 to reflect the actual minimum that builds the current dep tree.
  30. 52ff8c40 chore bump to 0.9.2 — refresh README badges (license, rust)
  31. a35d53a7 chore bump MSRV to rustc 1.95 (Cargo.toml + README badge)
  32. da581a52 chore regenerate Cargo.lock for 0.9.1
  33. 0.9.1

    Changed

    • Unpinned russh from =0.60.2 to 0.61. The upstream crypto chain has stabilised enough for the rustc 1.95 mono-item ICE to no longer trigger. Several previously-RC crates (ed25519, signature, ml-kem, password-hash, pkcs5, pkcs8, spki) reached stable releases, and ssh-key left the internal russh fork.
    • Removed rust-toolchain.toml. Stable rustc now builds the dep tree cleanly. Users no longer need rustup install 1.94.0 to cargo install gitorii.
    • README Troubleshooting section trimmed — the historical ICE note is gone; only the generic RUST_MIN_STACK advice remains for deep monomorphisation cases.
    • CI: GitHub release workflow switched from dtolnay/rust-toolchain@1.94.0 to @stable. GitLab CI comments updated.

    Verified

    • Full debug + release builds pass on stable rustc 1.95.0 (Arch host).
    • aws-lc-sys remains at 0.40.0 (Windows cross-compile via mingw + nasm still works; no regression to the 0.41 NASM-mandatory branch).
  34. 1fe58d90 chore unpin russh, drop rust-toolchain.toml
  35. e6b4c358 chore regenerate Cargo.lock for 0.9.0
  36. 0.9.0

    Changed

    • Relicensed from TSAL-1.0 to MIT OR Apache-2.0. Gitorii (the torii binary and core CLI/TUI) is now dual-licensed under the permissive open-source standard of the Rust ecosystem. Existing users are unaffected — TSAL-1.0 already allowed personal and non-production use; the new dual license simply removes the restrictions on production and organizational use without any additional cost or registration.
    • The previous LICENSE file (TSAL-1.0) has been replaced with two files: LICENSE-APACHE (Apache License 2.0) and LICENSE-MIT (MIT License). Recipients may choose either.
    • Cargo.toml license-file = "LICENSE" replaced by the SPDX expression license = "MIT OR Apache-2.0". This unblocks crates.io listings, awesome-rust inclusion, and contributions from companies whose CLAs prohibit non-OSS code.

    Why this change

    Contributing

    Trademarks

  37. 62c232f2 chore relicense to MIT OR Apache-2.0
  38. 0.8.3

    Added

    • TUI Log ops dropdown gained every history operation. The History view was fused into Log back in 0.7.2 but the dropdown inside Log only carried diff / copy hash / search until now. With this release, o over the log list opens a menu of: 1. diff — open the diff for the selected commit 2. copy hash — copy the short hash to the status bar 3. search — filter commits by message 4. cherry-pick — apply selected commit to current branch 5. rebase onto — rebase current branch onto selected commit (torii history rebase under the hood) 6. show signature — open the GPG armor + verify overlay for the selected commit (same modal S opens directly) 7. blame — line-by-line origin of a file (prompts for path) 8. remove file ⚠ — purge a path from every commit 9. rewrite dates ⚠ — shift author/committer dates in a range 10. scan history — find secrets across every commit 11. compactgc + reflog expire (recovers space)

    Internal

    • tui/views/log.rs::log_ops() exported as the single source of truth for the ops list. The renderer and the events handler both read it so the index → label mapping can't drift. Each entry is (label, description, danger); danger paints the row red and is used for the two history-rewriting ops.
    • Log ops dropdown title now reads ops — Enter run · Esc close with a white border, matching the chrome the Auth / Bisect / Worktree / Submodule / Platform views use.
    • Dropdown width grew from 16 to 50 cols so the descriptions can render inline.
  39. f0841edc feat Log ops dropdown gains history ops
  40. 0.8.2

    Changed

    • Cargo.toml description rewritten to mention the TUI, OAuth / refresh tokens, GPG, CI runners, and the self-hosted platform registry — features that landed since the original "snapshots + multi-platform mirrors + secret scanning" tagline.
    • Cargo.toml metadata: keywords now includes tui (was a slot short of mentioning the most distinctive surface); repository and documentation point at the GitLab mirror (the canonical home since the GitHub-radioactive memo); explicit readme = "README.md" for tooling that doesn't auto-detect.
    • README.md top-of-fold rewritten for crates.io / GitHub rendering: shields-style badges, a tagline that names the binary (torii), and a "What you get" bullet list that names every surface we actually ship — TUI, multi-platform, self-hosted, OAuth+refresh, GPG, CI runners, snapshots, secret scanner, pure-Rust transports. Replaces a wall of install caveats that used to live above the fold.
    • Install section trimmed: the prebuilt-binary path is one command up top; the long rustc ICE / SIGSEGV troubleshooting block moved to a ## Troubleshooting section near the end of the README so it doesn't scare new readers off.
    • Link list points at the GitLab mirror for releases / issues / changelog (was github.com).
  41. d83cf57c docs refresh crates.io / README presence
  42. b17da828 chore bump Cargo.lock for 0.8.1
  43. 0.8.1

    Added

    • TUI Runners sub-tab now combines online + local containers. The platform's runners (API-side) and any torii-spawned Docker container on this host (torii-runner-*) show up in the same list with a scope marker:
    • 🐳 local (yellow) — local-docker container
    • 🌐 online (brand) — registered against the platform Local entries land first so the "what's running on my machine" answer is at the top. When the platform call fails but local containers exist, the view still renders them instead of going empty.
    • Docker state mapped onto the existing status colours: runningonline (green), exitedoffline (dim), pausedpaused (dim), restartingonline (green).
    • Guard in the ops dropdown when the selected row is a local container: instead of firing the platform-API verbs (which would 404 against a docker name) the status line points at the right CLI verb — torii runner start/stop/logs/destroy <name>. In-TUI docker verbs land in v0.8.2 alongside the rest of the base_url override pass.

    Internal

    • New list_local_runner_containers() helper in tui::app. Calls docker ps -a --filter name=torii-runner- --format Names\tState\tImage and maps each row into a runner::Runner with runner_type = "local-docker". Silently returns an empty vec when docker isn't installed / the daemon's down — the Runners view degrades to platform-only instead of failing the load.
    • load_platform_runners now spawns a single worker that runs the platform API call + the docker query, concatenates the results (local first), and ships the combined vec through the existing platform_runners_rx.
  44. 7d7ac3d6 feat split online/local runners in Platform Runners sub-tab
  45. 0.8.0

    Added

    • platforms.toml registry at two locations:
    • ~/.config/torii/platforms.toml (global)
    • <repo>/.torii/platforms.toml (per-repo override) Local entries shadow global ones by name. Builtins (github.com, gitlab.com, codeberg.org, bitbucket.org) live in code and can be shadowed with torii platforms add if you need different URLs.
    • torii platforms list / add / remove / test CLI surface.
    • list prints builtins + custom side by side, with a * marker on customs.
    • add <name> --kind X --domain D --api URL --web URL [--client-id ID] [--local] writes the entry.
    • remove <name> [--local] drops a custom; nudges you toward add to shadow a builtin.
    • test <name> pings the API root with the stored token and surfaces the HTTP status.
    • Remote → platform detection now consults the registry. When a remote URL doesn't match any builtin domain, detect_platform_from_remote_named looks at platforms.toml (longest-domain match wins, so subdomains can shadow umbrellas) and returns the matching kind. New detect_platform_full helper also surfaces the API base URL alongside the platform / owner / repo, ready to thread into the right client.
    • GitLab pipeline client honours custom base URLs end to end via GitLabPipelineClient::new_with_base_url + get_pipeline_client_with_base_url. The CLI's torii pipeline … already calls through the new path, so a self-hosted GitLab declared in platforms.toml will list / cancel / retry / log exactly like gitlab.com does today.

    Internal

    • New crate::platforms::registry module with PlatformEntry, load_global / load_local / merged / all / find_by_host, and add_entry / remove_entry.
    • New URL helpers in crate::pr: extract_host, extract_owner_repo. Handle https://, ssh://, git@host: and the rare host/owner/repo shorthand.
    • Re-exported as crate::platforms_registry from main.rs.

    Notes for self-hosted

    • Self-hosted GitLab works fully: pipelines / jobs / logs / cancel / retry, plus everything that already passes through detect_platform_full.
    • Gitea / Forgejo / GitHub Enterprise / Bitbucket Data Center are detected and platforms list / add / remove / test will surface them, but their PR / issue / release / package / runner clients still hit their builtin SaaS endpoints. Extending the base-URL override across the rest of the platforms-side clients lands in 0.8.1 — that pass is mechanical (each *Client::new gets a _with_base_url sibling) and will use the same detect_platform_full plumbing this release introduced.
    • Backwards compat: no breaking changes. get_pipeline_client still exists as a thin shim over the new get_pipeline_client_with_base_url(p, None).
  46. 64dd0131 feat platforms.toml registry + GitLab self-hosted
  47. 0.7.40

    Added

    • Submodule TUI ops dropdown — closes the gap left in 0.7.39. o opens a context menu with Add new submodule (two-step input: URL → path), Update (submodule update), Update + init (--init), Init (register without cloning), Sync URLs (rewrite .git/config from .gitmodules), Foreach <cmd> (run a shell command in each submodule, input overlay), and Remove (confirm y/n). All entries route directly to crate::cmd::submodule::* — same wrapping pattern as Worktrees.
    • Submodule view's hint bar (render_hint in tui/ui.rs) gains state-aware arms for the four focus modes (List / OpsDropdown / InputArgs / ConfirmRemove), matching Worktrees / Bisect / Auth.

    Changed

    • Static CLI cheat-sheet removed from the Submodule info panel (was telling the user to drop to the shell). Hints flow through the global hint bar like every other view.
    • Palette aligned with the rest of the TUI: bc (brand) for the working OID column (was C_CYAN), C_DIM for path/url secondary text, C_GREEN/C_YELLOW for clean/dirty state.

    Internal

    • views/submodule.rs::ops_for(state) exported so events.rs can size the dropdown without duplicating the model — same export pattern as the rest of the ops-driven views.
    • handle_submodule + dispatch_submodule_op in tui/events.rs.
    • Esc closes overlays back to List, then defers to the global Esc path (sidebar focus).
  48. 58468424 feat submodule ops dropdown
  49. 0.7.39

    Fixed

    • Workspace view was empty when launched from inside a repo (and most other times). torii workspace add writes to ~/.config/torii/workspaces.toml (canonical XDG via dirs:: config_dir); the TUI loader was reading from ~/.torii/ workspaces.toml (legacy). Two different files, so nothing the user added from the shell ever showed up. Now we look at the canonical path first and fall back to the legacy path for installs that pre-date the move. New workspaces_toml_path() helper used by all four call sites (load_workspaces, the repo picker, the workspace status pump, the CLI's workspace lookup).
    • torii auth oauth now persists the refresh token + expiry so the next time the user opens Pipelines / PR / Issues, torii can renew the access token silently instead of returning 401 and forcing a manual auth oauth <provider> round.

    Added

    • Automatic OAuth refresh in auth::resolve_token. When a stored token is within 5 minutes of its [token_expires], the resolver calls the platform's refresh endpoint (grant_type=refresh_token via oauth::refresh_access_token), saves the new access / refresh / expiry, and drops the cache so the next call gets the fresh value. Failures are non-fatal: if the refresh endpoint is down or the refresh token was revoked, the resolver still returns the (possibly stale) cached token and lets the API call fail with its own 401 — which is the only point a manual re-auth is actually needed.
    • Worktree TUI ops dropdown. o opens a context menu with add / open / lock / unlock / move / remove / prune / repair, wired to crate::cmd::worktree::* directly. Add / Lock / Move open an input overlay; Remove / Prune gate behind a y/n confirm. Palette aligned with the rest (brand colour for branch, C_DIM for paths, C_GREEN for clean, C_YELLOW for dirty). Static "CLI:" cheat-sheet block removed — keybinds flow through the global hint bar like every other view.

    Internal

    • New auth::set_token_with_refresh (access + refresh + expiry in one call) used by the in-TUI OAuth worker.
    • New auth::refresh_if_needed(provider) — best-effort, idempotent; bails fast when no refresh token is stored or the access token isn't close to expiry.
    • New oauth::refresh_access_token(provider, refresh_token) -> (new_access, new_refresh, expires_in). GitLab device flow supports it end-to-end; GitHub OAuth Apps don't issue refresh tokens, so callers should check for Some(...) first.
    • DeviceFlowStep::Done is now a struct variant carrying the refresh token + expires_in alongside the access token.
    • New WorktreeFocus (List / OpsDropdown / InputArgs / ConfirmRemove / ConfirmPrune) + WorktreePendingOp (AddBranch / LockReason / MoveNewPath). Same shape as the Auth / Bisect ops scaffolds.

    Notes

    • The Submodules view still doesn't have its own ops dropdown — the state types are in place but the handler / dropdown / hints land in 0.7.40.
    • Existing OAuth installs need to re-run torii auth oauth <provider> once to start the refresh token flow. After that the renewal is silent.
  50. 148ffe38 fix+feat workspace path bug + OAuth auto-refresh + worktree TUI ops
  51. 0.7.38

    Added

    • torii runner exec <job> — run a single CI job locally without pushing anything. Wraps the platform's local-exec tool and inherits stdio so the user sees the build log live.
    • GitLab: by default invokes gitlab-runner exec <executor> <job> (still functional but deprecated in GitLab Runner 17.x; torii prints a one-line nudge toward gitlab-ci-local). Pass --use-gitlab-ci-local to switch to that binary instead — same .gitlab-ci.yml parsing, no deprecation tail.
    • GitHub: invokes act -j <job>. Install from <https://github.com/nektos/act>.
    • Other platforms: bails with a clear message — no local executor wired (Bitbucket Pipelines, Azure DevOps, sourcehut).
    • Flags: --ci-file <path> (override the default config file), --executor <docker|shell|…> (GitLab only, forwarded to gitlab-runner exec), --env KEY=VAL (repeatable; forwarded as --env to gitlab-runner / act, --variable to gitlab-ci-local).

    Internal

    • New run_runner_exec(platform, job, ci_file, executor, env, use_gitlab_ci_local) in cli.rs. Same pattern as the rest of the runner subcommands: which_binary first for a clear install-from-here error, then Command::status() with stdio inheritance.
  52. aa130e10 feat exec local job — gitlab-runner exec / gitlab-ci-local / act
  53. 0.7.37

    Added

    • torii runner spawn — bring up a Dockerized GitLab Runner against the current project. Two-phase: docker run -d --restart=unless-stopped -v /var/run/docker.sock:/var/run/ docker.sock -v <name>-config:/etc/gitlab-runner gitlab/gitlab-runner:latest, then docker exec <name> gitlab-runner register --non-interactive --url <reg-url> --registration-token <token> --executor <executor> [--docker-image <image>] [--description <desc>] [--tag-list <tags>]. Container name is torii-runner-<slug>, slug derived from owner/repo (override with --name).
    • torii runner statusdocker ps -a --filter name=torii-runner- with a coloured icon column (🟢/⚪/⏸/🔄). No local state file — everything lives in Docker.
    • torii runner start <name> / runner stop <name> — thin wrappers around docker start|stop against the torii-runner-<name> container.
    • torii runner logs <name> [-f] [--tail N]docker logs with stdio inheritance so -f streams the runner output live.
    • torii runner destroy <name> [-y]docker rm -f, with a confirmation prompt unless -y. Doesn't unregister the runner on the platform side; use torii runner remove <id> for that.

    Internal

    • New CLI helpers in cli.rs:
    • container_name(suffix) — single source of truth for the torii-runner-* naming.
    • run_runner_spawn / run_runner_status — the wrappers around docker run / docker ps.
    • run_runner_docker(args, label) — output-captured helper for start/stop/rm.
    • run_runner_docker_inherit(args) — stdio-inheriting variant used by spawn and logs -f.
    • GitHub Actions self-hosted runners deferred: the container shape (ephemeral tokens, JIT config) is different enough that we bail with a clear pointer at runner register --runner-dir instead.
  54. 81e7f54d feat spawn/status/start/stop/logs/destroy via docker CLI
  55. 0.7.36

    Added

    • TUI Log view — GPG signature column. G toggles a one-letter column ahead of the hash with the same vocabulary as torii log --signatures (G good = green, U unknown signer = yellow, B bad = red, ? other error = subtle, N none = dim). Populates a per-OID cache the first time the column turns on so scrolling doesn't re-run gpg --verify. Cache is keyed on the full hash, kept across navigation, and re-populated when the user toggles off and on again.
    • TUI Log view — armor overlay. S over the selected commit opens a centred modal that spawns a background worker (so the modal renders loading… while gpg --verify does its thing), then switches to the full ASCII-armored signature + the verification verdict in the same colours as the column. Any key closes; Ctrl-C still quits.
    • torii history reauthor --sign — force GPG signing of every rewritten commit, regardless of git.sign_commits. Lets you do reauthor + re-sign in one pass instead of chaining reauthor + torii sign A..B. Requires git.gpg_key like the other signing paths.
    • git.gpg_key and git.gpg_program shown + editable in the Config view. They were already round-trippable through the config layer; they just weren't in the TUI's ALL_KEYS list. Now they show alongside git.sign_commits with the existing in-view edit flow.

    Internal

    • LogState gained show_signatures: bool, signature_cache (HashMap<String, char>), and signature_overlay: Option<SignatureOverlay>. New SignatureOverlay enum (Loading / Done / Error) plus SignatureVerdictColor for the modal's verdict line.
    • New App::refresh_signature_cache (sequential verify across the loaded log slice) and App::start_signature_overlay (background worker via a fresh mpsc::Receiver).
    • Main loop drains log_signature_rx each tick into the modal state, same pattern as the OAuth modal.
    • vcs::core_extensions::signature_letter promoted from pub(super) to pub so the TUI helpers can reuse it without a copy-pasted verify pipeline.
  56. 74139534 feat Log column + armor overlay + reauthor --sign + Config keys
  57. 0.7.35

    Fixed

    • GPG no longer turns off silently when a repo has a local .torii/config.toml. The config merge replaced base.git with overlay.git wholesale, so a local file that only declared (say) default_branch = "master" reset sign_commits to false and gpg_key to None. Now the git section merges field-by-field: sign_commits / pull_rebase are OR'd so "true in either layer wins", gpg_key / gpg_program are only overridden when the overlay actually carries a value. (To explicitly *disable* signing for one repo, use torii config set --local git.sign_commits false, which goes through set and bypasses merge.)
    • TUI Commit view's message input no longer traps you inside. When typing == true and the sidebar was focused, the global dispatcher delegated keys only to PR/Issue/Branch/Tag — Commit (plus Snapshot/Log/History/Remote/Mirror/Workspace/Config) fell through to None, so Esc/Enter/Backspace/arrows did nothing while in the message field. All views whose typing predicate can be true are now routed to their own handlers.

    Added

    • git.gpg_program config key (alias: gpg.program). Mirrors git's own setting; lets you point at gpg2 or a vendor build when gpg isn't the right binary. Honoured by both the existing sign path and the new verify / sign-rewrite paths.
    • torii show --signature [target] — prints the commit's gpgsig ASCII armor plus a one-line verification verdict (good / unknown signer / bad / other), produced by parsing gpg's --status-fd output.
    • torii log --signatures — adds a single-letter column to the log (G good / U unknown signer / B bad / ? other gpg error / N none). Only spawns gpg --verify for commits that actually have an armor attached, so unsigned histories stay cheap.
    • torii save -S / --sign and --no-sign — per-commit signing override. Implemented as a process-scoped env var (TORII_SIGN_OVERRIDE) that the commit_inner_split reader honours; the CLI handler installs a guard that restores the previous value on drop so we don't leak into subprocess hooks.
    • torii sign [target] [--print-only] [-y] — rewrites one commit, or an A..B range, to include a fresh gpgsig header. Refuses to run with a dirty working tree, walks the range oldest-first, and moves any local-branch tip pointing at a rewritten OID onto the new signed commit. --print-only lets you sanity-check the armor without rewriting anything.

    Internal

    • New util::gpg::VerifyStatus + verify(armor, payload, program) on top of the existing sign_blob. Calls gpg --status-fd 1 --verify <sigfile> - and parses GOODSIG / BADSIG / NO_PUBKEY / ERRSIG to bucket the result.
    • New util::gpg::resolve_program(opt) — single source of truth for "which gpg binary do we run", used by both sign_blob and verify.
    • repo.log(...) gained a signatures: bool arg; the renderer calls vcs::core_extensions::signature_letter(repo, oid) (also new) which is what the future TUI log column will read.
  58. 42014738 fix+feat merge bug + verify + show/log/save/sign CLI + Commit-Esc bug
  59. 0.7.34

    Added

    • Bisect ref picker replaces the freeform "type bad good…" input from 0.7.33. Picking Start from the ops dropdown now opens a searchable overlay listing:
    • HEAD (with its shorthand + short OID),
    • local branches,
    • tags (newest-first by commit time, capped at 50),
    • remote branches (origin/*, github-*/*, …),
    • the last 30 commits reachable from HEAD, with their subject. Type any chars to filter case-insensitively against the display text; Up/Down navigates; j/k also work when the filter is empty. Esc closes.
    • Two-tab dance for Start: Tab toggles BadGood. Picking on the Bad tab stores the pick and auto-advances to Good; on the Good tab, Space multi-toggles the goods (✓ next to picked entries), and Enter runs crate::bisect::start with the collected refs. Hitting Enter on Good with nothing toggled takes the highlighted row as the single good (the most common shape).
    • Mark good <ref> / Mark bad <ref> / Skip <ref> added to the ops dropdown when a session is active. They open the same picker (single-select) and run crate::bisect::good|bad|skip with the chosen ref's name or OID.

    Internal

    • BisectFocus::RefPicker variant + RefPickerState (op / tab / all / idx / filter / bad_pick / good_picks). New RefPickerOp + RefPickerTab + RefEntry + RefKind in tui::app.
    • New views/bisect.rs::load_refs() (uses libgit2 via git2::*) and filter_indexes() exported so the events handler keeps the ordering in sync with what the renderer shows.
    • New handle_bisect_picker + commit_bisect_picker in tui/events.rs. The Start input overlay from 0.7.33 is now Run- only — Run is a command line, not a ref, so the freeform input fits.
  60. 2fd8a19a feat bisect ref picker (HEAD/branches/tags/remotes/log) + filter + tabs
  61. 0.7.33

    Changed

    • Bisect TUI view aligned with the rest of the chrome. Was a read-only status pane + a static "run these commands in shell" cheat-sheet block. Now:
    • Operable from inside the TUI. o opens an ops dropdown with the contextual actions (Start / Mark HEAD good / Mark HEAD bad / Skip HEAD / Run command / Reset) wired straight to the existing crate::bisect::* functions. Start and Run open an input overlay; Reset gates behind a confirm dialog.
    • Layout matches log / branch / platform: a 60/40 status + detail split, with the cheat-sheet moved into the detail panel when a session is active (and an explanation when it isn't).
    • Static "Commands" panel removed. Bottom-of-screen hints flow through render_hint in tui/ui.rs, the same arm pattern Auth and Platform use; nothing local stacks above it.
    • Palette aligned: bc for hashes (commit ids), C_DIM for secondary text, status-coloured marks (✓ good = C_GREEN, ✗ bad = C_RED, "bisecting…" headline = C_YELLOW). C_CYAN dropped in favour of the brand colour — same convention used in log/branch.

    Internal

    • New BisectFocus (List / OpsDropdown / InputArgs / ConfirmReset) + BisectPendingOp (Start / Run). BisectState gained dropdown_idx, input_buffer, input_prompt, pending_op — same shape as AuthState.
    • tui/views/bisect.rs::ops_for(state) exports the dropdown rows; events handler reads it the same way as Platform's ops_for / Auth's ops_for.
    • New handle_bisect + dispatch_bisect_op in tui/events.rs. Esc closes overlays back to List, then defers to the global Esc path (sidebar focus).
  62. 8d331a2d feat bisect rework — ops dropdown + palette + footer hints
  63. 0.7.32

    Changed

    • OAuth and rotate flows now run entirely inside the TUI. Until 0.7.31 we suspended the TUI and ran torii auth oauth|rotate <p> as a subprocess, which dropped the user into a plain terminal for the browser dance. From this release, picking "OAuth re-auth" / "Rotate (OAuth)" from the Auth ops menu opens a centred modal — same chrome family as the sync status panel — that shows the verification URL, the user code, and a bouncing progress bar while a background worker polls the platform's token endpoint. On success the modal switches to a green "token saved: <masked>" line and any keystroke closes it; Esc cancels mid-flight.
    • Rotate (OAuth) captures the old token before starting the flow so the worker can best-effort revoke it after the new one is saved (same revoke endpoint as the CLI's auth rotate).
    • Rotate as PAT (GitLab) stays in the TUI too, but doesn't need a modal — it's one HTTP call. Dispatched inline and surfaced via the app-wide status_msg.
    • In-process token cache is dropped after every successful in-TUI OAuth/rotate. Same fix as 0.7.31 but now baked into the worker itself rather than the dispatcher, so any future caller gets the invalidation for free.

    Internal

    • New oauth::start_device_flow(provider) -> DeviceFlowSession + oauth::poll_device_flow(session) -> DeviceFlowStep. The blocking run_device_flow could be rewritten on top of these but stays as the CLI entry point.
    • New AuthFocus::OauthFlow + OauthFlowState + OauthStatus variants on AuthState. Worker thread emits status updates over App::auth_oauth_rx; main loop drains them into the modal.
    • Removed Action::AuthOauthInPager / AuthRotateInPager and the run_torii_subprocess helper from 0.7.30 — no longer needed now that the flow stays in-process.
  64. 1f694b40 feat in-TUI OAuth / rotate modal
  65. 0.7.31

    Fixed

    • TUI re-auth no longer leaves the parent process serving the old token. 0.7.30 added the in-TUI OAuth / rotate flow by spawning torii auth oauth|rotate as a subprocess. The child wrote the new value to auth.toml and invalidated its own in-process token cache (0.7.22), but the parent TUI's cache was untouched — so every subsequent pipeline list / pr list / issue list kept handing the now-revoked token to the platform and getting 401 back. Added a public auth::drop_token_cache() that the TUI calls on return from the subprocess; the next resolve_token re-reads auth.toml and picks up the fresh value.
  66. 553485cd fix drop token cache after subprocess auth ops
  67. 0.7.30

    Added

    • Auth view operations from the TUI. The view used to be read-only (mirror of auth doctor / auth list); now o over the selected provider opens an ops dropdown with the contextual actions:
    • OAuth re-auth (when the provider has a flow wired) — suspends the TUI and runs torii auth oauth <provider> so the device-flow URL + paste prompt land on a real terminal, then refreshes the list on return.
    • Rotate (OAuth) — same path but torii auth rotate <provider>; the best-effort revoke from 0.7.25 still runs.
    • Rotate as PAT (GitLab)torii auth rotate --pat gitlab.
    • Set token (paste) — opens a masked input overlay (••••••• instead of the raw text) and writes via auth::set_token. Enter saves, Esc cancels.
    • Remove token — confirmation overlay; y deletes via auth::remove_token (and clears the matching [token_expires] entry, same as the CLI).
    • Hint bar arm for View::Auth with state-aware keys (list / dropdown / input / confirm), matching how Platform's hints work.

    Internal

    • New AuthFocus + AuthPendingOp on AuthState, with dropdown_idx, input_buffer, input_prompt, pending_provider.
    • tui/views/auth.rs::ops_for(state) exposes the contextual ops list to the events handler (same export pattern as Platform's ops_for / filters_for).
    • New run_torii_subprocess helper in tui/mod.rs — suspends raw mode + alt screen, runs current_exe() with the given args, restores chrome. Reused for OAuth + rotate; ready to wrap any future "needs a real terminal" flow (e.g. runner register).
    • New Action::AuthOauthInPager and Action::AuthRotateInPager { pat }. Dispatched in the main loop because they replace the TUI while running.
  68. 6eae9c9d feat auth view operations via ops dropdown
  69. 0.7.29

    Added

    • torii runner register --remote N — fetch a short-lived registration token from the platform's API and wrap the platform's native register CLI:
    • GitLab: gitlab-runner register --non-interactive --url … --registration-token … --executor <shell|docker|…> + --docker-image when the executor is docker, plus --description and --tag-list flags forwarded from the matching torii flags.
    • GitHub: <runner-dir>/config.sh --unattended --url … --token … --replace, with --name and --labels forwarded. The resolved command is printed before execution so the user can audit it; -y/--yes skips the prompt for scripts. The agent install itself (downloading the binary, systemd unit, etc.) is platform-specific and is not done by torii — install the runner first via your package manager / the platform's docs, then run this to attach the host to the project.
    • torii runner init — scaffold a starter ~/.gitlab-runner/ config.toml (chmod 600, minimal concurrent = 1 shell) when the file is absent, so gitlab-runner register has somewhere to land its [[runners]] block. GitHub variant is informational — points at the platform's actions/runners/new page since the runner tarball carries its own ./config.sh.

    Internal

    • New crate::runner::RegistrationToken (token + register URL + optional TTL hint).
    • RunnerClient trait grew registration_token(owner, repo). GitLab reads the project's runners_token field (needs Maintainer+); GitHub POSTs /actions/runners/registration-token (token valid ~1h, regenerate freely).
    • New which_binary(name) helper for PATH lookup; reused by the register flow to surface a clear "install gitlab-runner first" error when the binary is absent instead of an exec failure.
  70. 754e21f7 feat register + init
  71. 0.7.28

    Fixed

    • Platform detail panel: long values no longer read as concatenated with the next field. Each line_kv row was a single Line; Paragraph's block-level wrap broke the value mid-row but the continuation landed at column 0, where it visually merged with the next key/value pair (most visible on runners with a long description). Replaced with a kv() helper that word-wraps the value to subsequent lines indented to the value column, computed from the real panel width at render time. Same logic across pipelines / jobs / releases / packages / runners. Web URLs are also routed through kv() now (key url) so over-long URLs wrap with indent instead of running into the next entity.
  72. 95cd386b fix Platform detail panel — wrap long values with indent
  73. 0.7.27

    Fixed

    • Platform's local footer no longer overlaps the global hint bar. The view rendered its own 2-row hint+status panel inside its body, which sat on top of the app-wide render_hint row at the bottom of the screen. Both now share the same row — Platform's keys live in the global hint bar, like every other view does.
    • Colour palette aligned with the rest of the TUI. IDs and URLs use the brand colour (matching how commit hashes / refs render in log and branch) instead of C_CYAN. Stage / type / OS / runner type are C_DIM (secondary information) instead of C_YELLOW (which competes with warnings). running status keeps C_YELLOW (genuine attention) but pending drops to C_DIM so the warn semantics aren't diluted. Detail panel title uses bc (matching log's side panels).
    • Action result no longer renders inside the detail panel. It goes to the event log (e) — the canonical app-wide history of what just happened, same as workspace/mirror sync — and to the app-wide status_msg line. Detail panel goes back to being only entity data.
    • Tab divider switched from to · so the sub-tab labels read as a row of tags instead of a fenced gauge.

    Internal

    • Removed PlatformState::action_msg + action_msg_at. Their job is now done by App::status_msg (set with App::set_status) and the event log. Less local state, fewer places "what just happened?" can diverge.
    • Dropped the in-view render_footer function. Hints flowed into the View::Platform arm of render_hint in tui/ui.rs so the layout is uniform.
  74. 77a976b2 fix Platform palette + footer alignment
  75. 0.7.26

    Changed

    • Sidebar reordered by flow instead of the historical insertion order. Groups: entry (files) → local action (save, sync, snapshot) → navigation (log, branch, tags) → broadcast (pr/mr, issues, platform) → multi-platform layout (remote, workspace, worktrees, submodules) → admin (bisect, auth, config). View hotkeys (f/c/s/…) keep their mappings — only the visual order moves.
    • Platform header now uses a proper Tabs widget. The five sub-tabs render with the same chrome ratatui uses elsewhere (highlight bg on active, divider chars between), instead of a hand-rolled row of Spans. Active tab is unambiguous at a glance.
    • Platform interaction moved to dropdowns.
    • o opens an ops dropdown with the contextual actions for the current sub-tab (cancel/retry pipeline; cancel/retry/download artifacts for a job; pause/resume/reset-token/remove a runner). Replaces the per-action keys (c/x/a/t/d) that collided across sub-tabs and weren't discoverable.
    • f opens a filter dropdown (status: any/running/failed/ success/pending + branch-only toggle) with the active filter marked. Replaces the cycle key s and the toggle key b.
    • Dedicated 2-row footer at the bottom of the Platform view. Hints, filter indicators, live indicator, and action result line all live there instead of being grafted onto the detail panel. The detail panel is now exclusively the selected entity's data.

    Fixed

    • List columns no longer concatenate visually when an id overflows. GitHub workflow_run IDs are 11–14 digits; the old format!("{:<10}", id) only padded when the value was *shorter* than 10, so anything longer slammed straight into the next column ("#12345678901running" instead of "#12345678901 running"). A new col() helper truncates with so every column boundary holds regardless of input length. Applied to pipelines, jobs, releases, packages, and runners.

    Internal

    • New PlatformFocus::OpsDropdown / FilterDropdown variants, with PlatformState::dropdown_idx for selection state.
    • tui/views/platform.rs::ops_for and filters_for are now pub so the events handler can size the dropdown without duplicating the model.
  76. 865a6df3 feat sidebar reorder + Platform rework
  77. 0.7.25

    Added

    • torii runner — CLI subcommand to manage CI runners.
    • runner list [--remote N] — table of the project's runners (status, OS, type, tags).
    • runner show <id> — full detail (description, IP, version, tags, web URL).
    • runner remove <id> [-y] — delete the registration. The host-side agent still needs uninstalling separately.
    • runner reset-token <id> (GitLab) — generate a new authentication token; prints it to stdout for the operator to paste into the runner's config.toml.
    • runner pause <id> / runner resume <id> (GitLab) — temporarily stop / re-enable job pickup.
    • GitHub Actions supports list/show/remove only; the unsupported ops surface a clear error pointing at the documented workaround (label gating, agent restart).
    • TUI Platform — fifth sub-tab [5] runners. Same drill-down + refresh + filter machinery as the existing tabs. Per-runner actions:
    • c = pause, x = resume, t = reset-token, d = remove.
    • Reset-token's new credential is routed to the event log (open with e) so it never lands in the one-line status bar where it could leak via screenshots or scrollback.
    • torii auth rotate <provider> — rotate a stored token end to end.
    • Default flow (OAuth): re-runs the device or auth-code flow, swaps in the new access token, then POSTs to the platform's revoke endpoint so the old token stops working immediately. GitLab revoke is universal (RFC 7009, no client secret needed); GitHub revoke runs only when TORII_GITHUB_APP_SECRET is set (confidential app). Other platforms print a "revoke manually at …" hint.
    • --pat (GitLab only) — uses the native POST /personal_access_tokens/self/rotate endpoint, which generates a new PAT with the same scopes and invalidates the old one atomically (no browser round-trip).
    • --ttl flag on auth set / auth oauth / auth rotate — record an expiration timestamp alongside the token (30d, 2h, 7d12h, …). Stored under a new [token_expires] section in auth.toml. Purely advisory: torii doesn't auto-rotate, but auth doctor now prints ⛔ expired, ⚠ expires in 3d, or ⏳ expires in 28d next to each entry, with the warn band kicking in inside 7 days. Lets you treat short-lived bot tokens as a habit rather than a surprise.

    Fixed

    • Pipeline/job list cursor handling: when an auto-refresh poll returns, the index now clamps instead of jumping to 0. Auto-refresh no longer yanks the cursor away while you're reading.

    Internal

    • New crate::runner module with RunnerClient trait + GitLab and GitHub implementations, factored the same way as PipelineClient.
    • AuthStore gained expirations: BTreeMap<String, String> (ISO-8601 per provider). Parser and serializer extended; legacy auth.toml files keep loading unchanged.
    • set_token_with_expiry, token_expires_at public helpers in crate::auth. The old set_token is now a thin shim.
  78. 90d13ed3 feat runners + auth rotate + TTL
  79. 0.7.24

    Fixed

    • GitLab API clients now send Authorization: Bearer <token> instead of PRIVATE-TOKEN: <token>. The old header only works with personal access tokens; OAuth access tokens from torii auth oauth gitlab were being rejected with 401 across every endpoint (pipelines, jobs, releases, packages, issues, MRs, workspace remotes). Bearer is universal — it accepts both PATs and OAuth tokens — so this is a strict upgrade. ~30 call-sites migrated across platforms/{pipeline,issue,pr,release,package}.rs and workspace/remote.rs.

    Added

    • TUI Platform view — contextual actions.
    • c cancels the selected pipeline (in Pipelines) or job (in Jobs).
    • x retries the selected pipeline or job.
    • a downloads the selected job's artifacts to <repo>/artifacts/job-<id>.zip. Each action runs on a background thread, surfaces a green or red line in the detail panel, and reloads the active sub-tab so the new status shows up without Ctrl-R. A small in-flight guard prevents key-mashing from firing the same action three times.
    • TUI Platform view — auto-refresh polling. Press p while on Pipelines/Jobs/Releases/Packages to toggle live mode; the list re-fetches every 10s while in List focus. The header shows ⟳ live while it's on. Selection is preserved across reloads.
    • TUI Platform view — live tail of the job log. Drilling into a job that's still running or pending automatically enables tail mode; the log re-fetches every 3s and auto-scrolls to the latest output. Use the arrow keys to read past lines (manual mode), End to re-engage auto-follow, p to toggle live, and o to open the log in $PAGER (suspends the TUI cleanly).
    • TUI Platform view — status + branch filters. s cycles the status filter (none → running → failed → success → pending) and pushes it down to the platform API. b toggles "only current branch", applied client-side. Active filters render in the header.

    Internal

    • Job-log scroll now snaps to the tail (max(0, nlines − 20)) when a refresh arrives and the user hasn't scrolled manually. Same logic works for both initial drill-down and every poll while live tail is on.
    • Pipeline/job list cursors clamp instead of resetting to index 0 on reload, so auto-refresh doesn't yank the selection back to the top.
  80. 8a41ac8f fix+feat GitLab Bearer + Platform actions/polling/tail/filter
  81. 0.7.23

    Fixed

    • torii snapshot stash no longer reports success when libgit2 didn't actually save anything. After stash_save2 returns, we now verify the working tree is clean and surface a clear error (with torii snapshot create -n WIP as workaround) if it isn't. Matches the known issue documented in the project memory.
    • Tag push no longer prints a warning and returns success. push_all_tags_via_git2 now propagates the libgit2 error so the caller knows tags didn't reach the remote.
    • .git/info/exclude write errors no longer swallowed. sync_toriignore now returns the I/O error instead of pretending exclusions were synced (which could lead to private files getting staged on the next -a).
    • torii rm and torii mv now propagate index errors. The previous .ok() pattern silently dropped failures to update the index, leaving the staged state inconsistent with what the user saw. Both commands now check index.get_path first and surface any real error.
    • torii submodule deinit warns explicitly when the working-tree directory can't be removed. Index and .gitmodules are already updated at that point; the leftover dir is now flagged with a manual cleanup instruction instead of being silently ignored.
    • append_known_host propagates create_dir_all errors. Previously an unwritable ~/.ssh/ parent would silently swallow the directory creation and the subsequent host-key write would fail without context.
    • torii worktree move no longer corrupts gitdir metadata when canonicalize fails post-rename. Falls back to the raw new path so the .git/worktrees/<name>/gitdir admin file still gets patched to point at the new location.
    • Snapshot creation is now atomic against parallel torii save. Switched from exists() + create_dir_all (TOCTOU) to a create_dir retry loop, so two simultaneous saves can't both decide the same directory is free and overwrite each other's bundle.
    • torii mirror add no longer leaves mirrors.json referencing a remote that doesn't exist. Order reversed: add the git remote first, then persist the config; if config save fails, the remote is rolled back.
    • torii clean no longer silently swallows removal errors. Failures are collected, reported per-path at the end, and the command returns a non-zero exit so scripts can detect partial cleanups.
    • Secret scanner custom rules now skip comment lines the same way built-in rules do. Previously a // example: ghp_xxx… line in a staged file would false-positive against a user-configured [secrets] regex. scan_history aligned too: /* and * (block continuation) now skip alongside # and //.
    • TUI PR overlay no longer freezes when the terminal is very small. (overlay_height as usize - 3) underflowed to usize::MAX for heights below 3, spinning forever pushing empty lines. saturating_sub caps it at zero.
    • TUI workspace-name picker no longer panics on multi-byte characters. Slicing the input around ws_cursor now snaps to the nearest is_char_boundary, so cursoring through ñ, é, or emoji works instead of crashing the TUI.
  82. cfc02189 fix 15 silent failures, races, and panic risks
  83. 0.7.22

    Fixed

    • HTTP requests no longer hang forever. util::http::make_client now sets timeout=60s and connect_timeout=10s. Every platform client (7 of them, ~30 endpoints) inherits this — previously a hung API would freeze torii until Ctrl-C. cloud/ and transport/https already had timeouts; only the platform surface was missing them.
    • Self-shelling subprocesses in the TUI (save --revert, cherry-pick, etc.) now invoke the running binary via std::env::current_exe() instead of the literal name "torii". Avoids PATH-injection and works correctly when the binary is installed under a different name or invoked via absolute path. 33 call-sites in tui/mod.rs + tui/app.rs migrated through a new tui::torii_exe() helper.
    • self.repo.path().parent().unwrap() pattern (6 sites in vcs/core.rs + vcs/core_extensions.rs) now returns a clear InvalidConfig error instead of panicking on the bare-repo edge case.

    Performance

    • auth::resolve_token now caches per (provider, repo_path) in-process. CLI flows that resolve N tokens repeatedly (e.g. torii workspace status across M repos) previously re-read the global / local TOML on every platform-client constructor. Now one read per (provider, path) per torii invocation. Invalidated automatically by set_token / remove_token.

    Added

    • util::http::send_text(req, ctx) and send_bytes(req, ctx) — the same shape as send_json for endpoints that return plain text (job logs / build traces) or raw bytes (artifact zips). Replaces 5 inline .send() + status check + body read blocks in pipeline.rs (~50 lines collapsed).
    • ToriiError::Network, PlatformApi, Auth variants — intended to replace the catch-all InvalidConfig over time. Adopted in new code from this point on; migration of the ~430 existing InvalidConfig sites is tracked under "Validation and polish" in ROADMAP.

    Internal

    • Code audit identified four further areas of tech debt deferred to future releases:
    • The PlatformClient trait (workspace/remote.rs) has 7 methods but most platforms implement only set_visibility — the rest return "not yet wired". Worth splitting into VisibilityClient (minimum) + RepoManagementClient (full surface) so the trait stops lying about capability.
    • ~150 .unwrap() calls outside tests, mostly Mutex::lock patterns that are safe by construction; case-by-case audit needed.
    • The ~430 InvalidConfig sites that should migrate to the new typed variants.
    • TUI background loaders (platform_*_rx channels) drop threads on view-switch instead of cancelling them — work is wasted, not corrupted, but worth fixing.
    • Hardcoded base URLs (api.github.com, api.bitbucket.org, dev.azure.com) prevent self-hosted GitHub Enterprise / Bitbucket Data Center / Azure on-prem from working out of the box. Deferred to 0.8.0 where ~/.config/torii/platforms.toml will make per-host overrides trivial without per-client refactoring.
  84. f4b089fa fix HTTP timeout + TUI current_exe + token cache + parent() unwraps
  85. 0.7.22-rc.2

  86. 08d2d0c0 ci route ALL three builds to self-hosted runner
  87. 0.7.22-rc.1

  88. 40c237dd ci route build-linux-x86_64 to self-hosted runner `void-torii`
  89. 0.7.21

    Added

    • OAuth torii auth oauth works end-to-end on 4 platforms: GitHub, GitLab, Codeberg (device flow, RFC 8628) and Bitbucket Cloud (authorization-code grant with PKCE + localhost loopback). Every bundled client_id is now baked into the binary — no setup required for users to authenticate. PATs are still supported if you prefer.
    • Bitbucket auth-code flow (run_auth_code_flow):
    • Random code_verifier (43 base64url chars) + SHA-256 code_challenge per RFC 7636 PKCE.
    • Binds 127.0.0.1:8888 and serves a one-shot HTML "you can close this tab" page after the redirect.
    • Validates the state query param to prevent CSRF.
    • Sends client_secret as Basic auth when TORII_BITBUCKET_APP_SECRET is set (Bitbucket marks confidential consumers); falls back to PKCE-only otherwise.
    • New deps: sha2 = "0.10" (PKCE S256). Both are tiny pure-Rust.

    Internal

    • Bundled OAuth client IDs (all public, intentionally in the source):
    • GitHub: Ov23liDcA2Njn7eRWnYV
    • GitLab: b72a85262c309587f67591da8fed4f8e8f4ee7349e9ed06f6a2a99ee7caec4fe
    • Codeberg: d114c8aa-227d-453e-8f25-cdd727f49d42
    • Bitbucket: xQAkJEqx3LK4WtJ3KD
    • Env var names changed to match the convention the project's .env already uses: TORII_{GITHUB,GITLAB,CODEBERG,BITBUCKET}_APP_ID / _APP_SECRET. The env var still overrides the bundled id for self-hosted Gitea/Forgejo and for users with their own registered OAuth Apps.

    Requires (one-time setup by the user)

    • Bitbucket consumer: must have Callback URL set to http://localhost:8888/callback. The OAuth registration that shipped with the public URL https://gitorii.com needs to be updated to the loopback for auth-code with localhost to work.
  90. 589f245d feat OAuth end-to-end for 4 platforms
  91. 0.7.20

    Added

    • torii auth oauth <provider> — OAuth 2.0 Device Authorization Grant (RFC 8628) for authenticating against GitHub / GitLab / Codeberg without having to create a Personal Access Token in the web UI. Same UX pattern as gh auth login: 1. Torii prints a short user code + the verification URL. 2. You open the URL in any browser (no callback required), enter the code, authorise. 3. Torii polls the token endpoint and stores the resulting access token under ~/.config/torii/auth.toml (or --local for the per-repo store).
    • New src/util/oauth.rs implements the device flow with proper handling of authorization_pending / slow_down / expired_token / access_denied poll responses.

    Requires (one-time setup by the maintainer)

    • Each platform needs an OAuth App registered, providing torii with a public client_id. Until the registered ids ship bundled, the flow falls back to environment variables: TORII_GITHUB_CLIENT_ID, TORII_GITLAB_CLIENT_ID, TORII_CODEBERG_CLIENT_ID. If neither bundled nor env is available, the command errors with a clear fallback ("create a PAT and run torii auth set …").

    Notes

    • Bitbucket Cloud doesn't implement RFC 8628 — only the Authorization Code grant, which needs a localhost:PORT callback server. Tracked for the next release; torii auth set bitbucket USERNAME:APP_PASSWORD remains the path there.
    • Azure DevOps has Device Code support; wiring it just needs an Azure AD app registration with the right scopes. Tracked.
    • Sourcehut uses OAuth 1.0a + a token format with embedded scopes; not a fit for device flow either. PAT remains the path.
  92. f3ae16b2 feat OAuth device flow for github/gitlab/codeberg
  93. 0.7.19

    Added

    • torii remote visibility now works on every platform that has the concept, not just GitHub and GitLab. Before this release Gitea / Forgejo / Codeberg returned a "Gitea API not yet implemented" stub that's been there since 0.4.0, and Bitbucket / Sourcehut / Azure / Radicle weren't even registered in the platform-client factory.
    • Gitea / Forgejo / Codeberg (shared PATCH /api/v1/repos/{owner}/{repo} with private: true|false). The same client serves all three — Codeberg is a hardcoded https://codeberg.org, Forgejo and Gitea honour FORGEJO_URL / GITEA_URL env vars for self-hosted instances.
    • Bitbucket Cloud (PUT /2.0/repositories/{ws}/{repo} with is_private). Auth heuristic same as 0.7.17: : in the token → Basic with user:app_password, else Bearer.
    • Sourcehut (GraphQL mutation at https://git.sr.ht/query). Torii's (Public, Private, Internal) collapses to Sourcehut's (PUBLIC, PRIVATE, UNLISTED).
    • Azure DevOps: clear error — Azure controls visibility at the *project* level via https://dev.azure.com/{org}/{project}/_settings/, not per-repo. The error points the user there.
    • Radicle: clear error — peer-to-peer, no central visibility flag. Reachability is governed by seeding (rad node).

    Internal

    • Token resolution for Gitea / Forgejo / Codeberg now falls back through all three provider names — torii auth set codeberg ... works whether the factory built a Gitea, Forgejo or Codeberg client.
    • New free function gitea_set_visibility(base_url, token, owner, repo, visibility, label) shared by all three Gitea-API clients.

    Docs

    • ROADMAP.md brought up to date after a three-week gap (0.6.0 → 0.7.19). Released section covers 0.7.x, In progress notes 0.7.19 visibility expansion, 0.8.0 platforms.toml, plus Azure Artifacts and Bitbucket Data Center as platform follow-ups.
    • torii remote --help lists the 9 platforms supported by the factory plus a visibility availability matrix.
  94. 75038d6e feat `torii remote visibility` works across all platforms
  95. 0.7.18

    Added

    • Azure DevOps support (7th platform). Detection: dev.azure.com, ssh.dev.azure.com, and the legacy *.visualstudio.com URLs all auto-route through the new Azure client.
    • URL parsing unpacks Azure's three-level path (org/project/repo) and packs org/project into the owner slot of torii's standard (platform, owner, repo) triple — the AzureClient splits it back via split_azure_owner() at call time. Three URL shapes supported:
    • https://dev.azure.com/{org}/{project}/_git/{repo} (modern)
    • https://{org}.visualstudio.com/{project}/_git/{repo} (legacy)
    • git@ssh.dev.azure.com:v3/{org}/{project}/{repo} (SSH)
    • Auth: Personal Access Token via Basic auth with empty username (Authorization: Basic base64(":PAT")). Configure: torii auth set azure YOUR_PAT. Env fallbacks: AZURE_DEVOPS_TOKEN, AZURE_DEVOPS_EXT_PAT, AZDO_TOKEN.
    • PRs: full surface (list / create / get / merge / close / update / delete_branch) via _apis/git/repositories/{repo}/pullrequests. Merge methods map: mergenoFastForward, squashsquash, rebaserebase.
    • Work Items (≈ issues): list / create / close / comment via the WIQL (Work Item Query Language) query endpoint plus the JSON-Patch update API. Defaults to the Issue work-item type (Basic process); Agile / Scrum projects can extend later. Work items are *project-scoped*, not repo-scoped — the _repo arg is ignored.
    • Pipelines (Builds API): list / cancel / retry / delete + list_jobs / job_log / artifacts download. cancel PATCHes status: cancelling; retry POSTs a new build with the same definition.id. list_jobs reads the build's timeline and surfaces the Job records.
    • Releases (classic Release Management on vsrm.dev.azure.com): list / get / delete. edit returns a clear error — Azure Releases derive metadata from the definition template, not the release instance.
    • Artifacts: returns a clear error pointing to the web UI. Azure Artifacts feeds live at the *organisation* level, not per-repo — the addressing model doesn't fit cleanly into torii's owner/repo abstraction and needs a separate design pass (tracked for a future release).

    Notes

    • Supported platforms now: GitHub, GitLab, Gitea / Codeberg / Forgejo, Sourcehut, Radicle, Bitbucket Cloud, Azure DevOps — seven. The expansion arc that started with 0.7.13 (Gitea) is complete.
  96. 5e80ed74 feat Azure DevOps support — 7th platform
  97. 0.7.17

    Added

    • Bitbucket Cloud support (6th platform). Detection: bitbucket.org URLs auto-route. Auth heuristic: tokens containing : are treated as username:app_password (Basic header, base64-encoded); anything else is sent as a Bearer token. New dep: base64 = "0.22". Configure with: torii auth set bitbucket USERNAME:APP_PASSWORD (create the app password at https://bitbucket.org/account/settings/app-passwords).
    • PRs: full surface — list / create / get / merge / close / update / delete_branch. Merge methods map: torii merge → Bitbucket merge_commit, squashsquash, rebasefast_forward (Bitbucket's closest analog).
    • Issues: list / create / close / comment via the deprecated- but-functional Bitbucket Cloud issues API. Repos without issues enabled return 404 with a clear hint.
    • Pipelines: list / cancel / list_jobs / job_log work via Bitbucket Pipelines REST. retry / delete / per-step retry/cancel return clear "not exposed via REST" errors — Bitbucket's API doesn't surface those operations.
    • Releases: returns a clear error — Bitbucket Cloud has no Release-page object, only a "Downloads" tab (flat file list, no notes / tag binding). Use annotated tags + Downloads manually, or mirror to a host with native releases.
    • Packages: returns a clear error — no native Package Registry on Bitbucket Cloud.

    Notes

    • Supported platforms now: GitHub, GitLab, Gitea / Codeberg / Forgejo, Sourcehut, Radicle, Bitbucket Cloud — six. Azure DevOps arrives in 0.7.18.
    • Self-hosted Bitbucket Data Center has a different URL shape and a partially different API (REST /rest/api/1.0/) and will need a separate client. Tracked for after 0.8.0 (platforms.toml config).
  98. c3d6d621 feat 6th platform (Bitbucket Cloud)
  99. bbd06717 refactor organise modules into platforms/vcs/cmd/workspace/util/
  100. 0.7.16

    Added

    • Radicle support (5th platform, peer-to-peer). Detection: any remote URL starting with rad:// or rad@ routes through the Radicle client. RID is parsed into the owner slot; repo is left empty because Radicle projects are flat (no owner/repo split).
    • New src/radicle.rs module wraps the local rad binary via run_rad(args) and run_rad_json(args). Same shape as src/gpg.rs: subprocess + clear error if the binary is missing (link to https://radicle.xyz).
    • Issues: rad issue list / open work. close and comment return clear errors because Radicle identifies issues by hash, not by u64, and torii's IssueClient trait still takes number: u64. A future revision of the trait (string-id variant) will close that gap.
    • Patches (Radicle's PR equivalent): rad patch list / open work. get / merge / close / update / delete_branch return clear errors pointing at rad patch <op> <hash> for the same hash-vs- number reason.
    • Pipelines / Releases / Packages: clear errors — Radicle has no native CI, no Release-page object, no package registry. Mirror to a host that does, or run CI locally.

    Notes

    • Supported platforms now: GitHub, GitLab, Gitea / Codeberg / Forgejo, Sourcehut, Radicle — five. The roadmap target for the "0.7.13 → 0.7.16 multi-platform expansion" is complete.
  101. a9647ff2 feat 5th platform via `rad` subprocess
  102. 0.7.15

    Added

    • Sourcehut platform support (4th platform after GitHub / GitLab / Gitea). Detection: git.sr.ht URLs auto-route through the Sourcehut client. Auth: torii auth set sourcehut <oauth-token> (env fallback SOURCEHUT_TOKEN / SRHT_TOKEN). What's wired:
    • Issues (todo.sr.ht): list / create / close / comment via the REST tracker API. Assumes tracker_name == repo_name — if your project uses split trackers (e.g. ~user/repo-bugs, ~user/repo-features), the --remote flag of torii issue points at the correct one.
    • Pipelines (builds.sr.ht): list / cancel + log work. Other surface is honest about the limits:
    • retry / job_retry return an error pointing at the web UI — builds.sr.ht doesn't expose a "resubmit finished job" endpoint over REST.
    • delete returns an error — builds keep retention-policy-managed on the server, not user-deletable.
    • job_artifacts_download and job_erase return clear errors.
    • "Jobs in a pipeline" is a flat concept on builds.sr.ht — a job *is* the pipeline. list_jobs(pid) returns the run as a single job entry to keep the CLI surface uniform.
    • PRs: returns a clear error explaining sourcehut's email-patch workflow (*-devel@lists.sr.ht) and pointing the user at torii patch export.
    • Releases: returns an error explaining that sourcehut has no native release object — a release is just an annotated git tag.
    • Packages: same — no package registry exists on sourcehut.

    Changed

    • GPG signing now applies everywhere a commit is created, not just torii save and the TUI commit view. Routed commit_inner / commit_inner_split through:
    • cherry-pick (and --continue)
    • revert
    • merge (the merge commit itself)
    • history reauthor (rewritten commits)
    • history rewrite (date rewrites)
    • history remove-file (filter-branch replacement)

    Internal

    • New commit_inner_split(repo, ref, author, committer, msg, tree, parents) variant of commit_inner for callers that preserve the original author when rewriting the committer.
  103. dbff1707 feat 4th platform + extend GPG sign coverage
  104. 0.7.14

    Fixed

    • user.name / user.email from --local config are now actually used at commit time. Previously resolve_signature only read ~/.config/torii/config.toml (the global file), so torii config set user.email "X" --local would write .torii/config.toml successfully but torii save would still use the global value at commit creation. Per-repo identity now wins, as expected for a jerárquico config system. Bug introduced in 0.7.3 when resolve_signature was added; surfaced by the user trying to set a work-only email on a single repo.
    • GPG-signed commits actually sign now. git.sign_commits = true was accepted by the config layer since 0.6.x but never honoured at commit time — every commit went out as repo.commit(...) with no signature, leaving gpgsig absent from the object even though the flag was on. The fix:
    • New src/gpg.rs shells out to the system gpg binary (--detach-sign --armor -u <key>), reusing the user's existing keyring + agent + pinentry — same UX as git commit -S.
    • New commit_inner helper in core.rs routes both torii save and the TUI's commit view through the signed path when the flag is on (commit_create_buffer + commit_signed + manual ref update, since libgit2 doesn't update refs for signed commits).
    • Other commit sites (cherry-pick, revert, merge, tag annotated, history reauthor) still go through the unsigned path — extending commit_inner there is tracked for 0.7.15.
    • Requires gpg (or gpg2) on PATH. Clear error if missing.

    Added

    • user.signingkey and commit.gpgsign config aliases. They map to the existing git.gpg_key and git.sign_commits respectively. Either spelling works — pick the one you already use in git config. Reason: the previous git.gpg_key name was a torii-ism nobody guessed when migrating from git.

    Internal

    • HTTP boilerplate centralised in a new src/http.rs module with four helpers (make_client, send_json, send_empty, extract_array). The 15 platform clients (pr × issue × pipeline × release × package over GitHub × GitLab × Gitea) all go through it now. Net effect:
    • Dropped obsolete in-repo docs and a stray debug log (~1000 more lines): docs/BUG_COMMIT_AUTHOR_FALLBACK.md (FIXED in 0.7.3), docs/BUG_SNAPSHOT_LEAKS_INTO_COMMITS.md (FIXED in 0.7.7), docs/FEATURE_FETCH_SPECIFIC_REMOTE.md (shipped in 0.7.6), log_gitorii.txt, .pr-test.
  105. 38182f7d fix honour --local config and actually sign with GPG
  106. aff402dd refactor centralize HTTP boilerplate + drop obsolete docs
  107. 0.7.13

    Added

    • Gitea / Codeberg / Forgejo support — first new platform since 0.1.x. All four platform-side surfaces (pr, issue, pipeline, release) get a Gitea client; the four torii pipeline / job / package / release CLI commands and the corresponding TUI Platform view recognise codeberg.org remotes automatically and route through it. Self-hosted Gitea / Forgejo instances need explicit declaration via ~/.config/torii/platforms.toml — that arrives in 0.8.0; for now they fall through with "platform not detected".
    • torii auth set codeberg <token> (or gitea / forgejo) all share the same client — set the token under whichever name fits your mental model. Env-var fallbacks: CODEBERG_TOKEN, GITEA_TOKEN, FORGEJO_TOKEN.
    • Gitea Actions (CI runs in Gitea ≥ 1.19 / Forgejo) supported by torii pipeline list / cancel / retry / delete and torii job list / log / retry. job cancel / job artifacts download / job erase are not exposed by the Gitea v1 API and return a clear "use the run-level op instead" error.

    Notes

    • This is the first of the planned multi-host expansion (0.7.13 Gitea, 0.7.14 Sourcehut, 0.7.15 Radicle). The pattern lets the client surface grow without changing the CLI grammar.
  108. b015290e feat Gitea / Codeberg / Forgejo client
  109. 709a89b0 docs drop gitorii-experimental from AUR install section
  110. 0.7.12

    Added

    • TUI: unified Platform view. New platform entry in the sidebar (between auth and config) groups the four platform-side surfaces (pipeline / job / release / package) into one drill-down view with horizontal sub-tabs:
    • 1 Pipelines, 2 Jobs, 3 Releases, 4 Packages
    • Enter on a pipeline drills into its jobs (sub-tab auto-switches to Jobs, populated with that pipeline's jobs).
    • Enter on a job fetches and shows its log in a scrollable panel (PageUp / PageDown / Home for navigation; Esc walks back).
    • r opens a centred popup to switch the remote (auto-discovered from the repo); Enter selects, Esc cancels.
    • Ctrl-R reloads the active sub-tab.
    • All loads happen on background threads — the TUI stays responsive while pipelines / jobs / releases / packages are fetched.

    Fixed

    • TUI: add mirror was unreachable. The "add mirror" entry only appeared in the ops dropdown when a mirror was already selected, making it impossible to create the first mirror from the TUI. It's now also exposed from the git-remote ops dropdown, so users can bootstrap mirroring from scratch.

    Docs

    • README: refreshed the Views table (was missing worktrees / submodules / pr / issues / bisect / auth from earlier releases) and added the new platform row + short Platform-view description.
    • README: cross-linked the CI / platform management CLI section to the new TUI Platform view.
  111. ddb8386f feat unified Platform view + mirror-add fix + AUR install
  112. bf00652a docs --remote flag fully documented in README + after_help of pipeline/job/package/release
  113. 0.7.11

    Added

    • --remote NAME on every platform-management command. The four CLI surfaces added in 0.7.7 and 0.7.10 (torii pipeline, torii job, torii package, torii release) all auto-detected the platform from the origin remote URL. That works when there's only one platform — most projects — but breaks the multi-platform case where a repo is mirrored across e.g. GitLab (origin) and GitHub (github-paskidev). Each backend has its own releases, its own pipeline runs, its own packages; both should be reachable from the CLI.

    Why this release exists

  114. 0cdd933a feat --remote NAME flag on all platform-management commands
  115. 0.7.10

    Added — Platform Management Surface

    • torii job {list, log, retry, cancel, artifacts, erase} — individual CI job control. Sibling to torii pipeline (which manages whole pipelines / workflow runs); torii job drills into the jobs inside a pipeline.
    • torii job list --pipeline <id> [--status STATUS] — enumerate jobs of one pipeline, optionally filtered. Output is a one-line-per-job table with icon, raw status, name, stage, duration. The status filter is normalized (success | failed | running | canceled | pending) and applied client-side after the fetch so the same flag means the same thing on both backends.
    • torii job log <id> [--tail N]the killer feature. Fetches the raw trace and prints it. With --tail N only the last N lines are printed, which is the common case during failure post-mortems. Replaces the previous "open the UI, click into the job, scroll to the bottom of the log" round-trip. During the v0.7.9 saga this would have saved ~30 minutes of curl /jobs/<id>/trace | tail -20 repetition.
    • torii job retry <id> — GitLab only. Re-runs a single failed job without re-running the entire pipeline (which on a 25-minute matrix is the difference between "5 min retry" and "25 min retry"). GitHub Actions doesn't support per-job retry (only /runs/:run_id/rerun-failed-jobs at the run level); the GitHub backend returns a hint pointing at torii pipeline retry <run-id>.
    • torii job cancel <id> — GitLab only, same asymmetry as retry.
    • torii job artifacts <id> [-o <path>] — GitLab only. Downloads the per-job artifacts archive to disk. On GitHub artifacts are run-scoped, not job-scoped; the backend returns an error explaining this. Default output path is ./<job-id>-artifacts.zip.
    • torii job erase <id> [--yes] — GitLab only. Clears a job's log + artifacts but keeps the job entry visible in the UI (useful for storage cleanup when you want history). GitHub returns unsupported.
    • Trait impl in src/pipeline.rs — extends the existing PipelineClient trait rather than adding a parallel JobClient trait, since jobs are conceptually under pipelines and share the same auth + base URL. Asymmetric capabilities are handled by Err returns with self-explanatory hints (no silent fallback, no panics on unsupported ops).
    • torii package {list, files, delete} — GitLab Package Registry management. GitLab's Generic Package Registry stores release binaries between runs (gitorii's release pipeline uploads three cross-compiled binaries per tag — linux x86_64, linux aarch64, windows x86_64). Without cleanup, this accumulates against the namespace's 5 GB free-tier storage cap.
    • torii package list [--type TYPE] [--name SUBSTR] [--limit N] — enumerate packages. --type generic is the gitorii case; the surface also supports the other types GitLab exposes (npm, maven, conan, pypi, composer, nuget, helm) without code changes — the parser is type-agnostic.
    • torii package files <package-id> — list files inside a package with their sizes (in MB). Useful for understanding what's stored before deleting.
    • torii package delete <id> | --version vX.Y.Z | --older-than 90d --yes — three modes: single-id, by-exact-version, by-age. The filter modes are mutually exclusive with the explicit id (clap-enforced). Batch mode previews up to 10 entries before confirmation, then iterates one-by-one with per-id success/failure reporting — same pattern as torii pipeline delete.
    • Implementation in src/package.rs (~280 lines, follows the pipeline.rs shape). GitLab-only on purpose: GitHub's binary-distribution model is Release Assets attached to Releases, which is torii release's scope below.
    • torii release {list, show, edit, delete} — Release page management. Both backends.
    • torii release list [--limit N] — recent releases. One-line-per-release.
    • torii release show <tag> — full details (description body, web URL, created date). Useful for previewing what's published before editing or for grabbing the URL to paste somewhere.
    • torii release edit <tag> [--name X] [--notes notes.md | -] — patch the release name and/or description without re-tagging or re-running CI. --notes accepts either a path to a markdown file or - for stdin (so you can pipe in dynamically-generated notes). Fixes the workflow of "oops I had a typo in the CHANGELOG section that got copied to the GitLab Release" without forcing a re-release.
    • torii release delete <tag> [--yes] — removes the Release entity (the underlying tag stays — use torii tag delete <tag> if you also want the tag gone). Useful when CI auto-created a release with garbage in the description because of an interrupted run.
    • Both backends supported with appropriate asymmetries: GitHub's edit API needs the numeric release id (fetched via the get-by-tag call); GitLab's API uses the tag directly in the path. CLI surface is identical from the user's POV.
    • Implementation in src/release.rs (~340 lines).

    Tests added

    Why this release

  116. 0c075cd6 feat torii {job,package,release} — full CRUD over GitLab/GitHub platform side
  117. 0.7.9

    Changed (CI only — no source-level changes)

    • CI moved back to GitLab.com shared runners. A ~4h self-hosted runner experiment (registered as void-torii with tag gitorii, shell executor, Arch host) was abandoned after rustc 1.94.0 reproducibly SIGSEGV'd on this host even in trivial crates like libc, idna, fiat-crypto, num-traits — independent of RUST_MIN_STACK (tested up to 512 MB), CARGO_BUILD_JOBS=1, or LimitSTACK=infinity via systemd. The crash also reproduced in a standalone cargo build outside gitlab-runner, confirming it's a rustc-1.94/glibc/kernel interaction on that specific Arch host — unrelated to gitorii or the YAML.
    • .gitlab-ci.yml: removed default: tags: [gitorii] so jobs go to saas-linux-small-amd64 shared runners. The russh / rsa-rc / sec1 generic-tree stack pressure is covered by the RUST_MIN_STACK="33554432" already in the YAML's variables: block — shared runners satisfy the request without kernel-cap issues.
    • Removed leftover project-level CI/CD variables (RUST_MIN_STACK=536870912, CARGO_BUILD_JOBS=1) introduced during the self-hosted debug iteration; they're no longer needed.
  118. 4a84ce2c ci fix release-creation 403 — use the variable name that exists
  119. 8887373b fix cfg-gate AgentClient + split rebase fns by platform
  120. dd262938 build pin russh to =0.60.2 + restore Cargo.lock to v0.7.8 known-good state
  121. c3d16d43 ci install nasm for the windows-x86_64 cross-compile
  122. 2a5a6e32 ci restore image+apt+rustup directives for shared (Docker executor)
  123. a0f85a64 ci abandon self-hosted runner, back to gitlab.com shared
  124. b2ca24a0 ci bump RUST_MIN_STACK from 16 MB to 32 MB (rustc's own recommendation)
  125. dcce7e39 ci source ~/.cargo/env in before_script so shell-executor jobs find rustup
  126. eccb199b fix remove `cache:` block — runner has no cache backend configured
  127. 0.7.8

    Fixed

    • torii sync --push was re-pushing every local tag on every invocation, retriggering CI pipelines for every historical tag. Severity: medium (no data loss; wasted runner time, polluted pipeline list). Observed in production on the gitorii repo itself: every release (torii sync --push after a new torii tag create) created stale "canceled" pipelines for v0.7.0, v0.7.1, v0.7.2, v0.7.3 etc. They eventually got canceled by GitLab (concurrent tag pipelines hitting workflow:rules), but only after sitting queued and consuming runner attention — and they accumulated in the pipeline list like noise.
    • force=true (torii sync --push --force) still works — the comparison still happens, but tags that differ get the + prefix so rewritten OIDs (e.g. after torii history reauthor --since ...) still get pushed over the remote's ref.
    • Annotated tags' peeled refs/tags/<name>^{} entries that libgit2 sometimes surfaces in list() output are filtered out — only the tag object's own ref matters for the comparison.
    • First push of a brand-new tag still works (it's missing remotely → None != Some(oid) → included in the refspec list).
  128. fde3257d fix skip already-synced tags so sync --push stops retriggering CI for every historical tag
  129. 31ed8d2e ci adapt for self-hosted shell-executor runner with tag `gitorii`
  130. 0.7.7

    Fixed

    • Safety snapshots no longer leak into the next commit. Severity: high. History-rewriting commands (torii history reauthor, rebase, mailmap apply) wrote their pre-op backups to .torii/snapshots/<id>/git_backup/ *inside the working tree*. Nothing in torii init or in the snapshot-writing path added .torii/ to .gitignore, so the directory looked like ordinary untracked project files. The very next torii save -am ... then staged everything new — including the full .git clone the snapshot contains. In the wild on syrakon/tramuntana this produced a single commit carrying 10,269 unrelated objects (~681 MB); the push died with failed to finish zlib inflation: stream aborted prematurely. Git's own push --dry-run reported "fast-forward, 1 commit", hiding the payload (dry-run enumerates refs, not packfile contents) — making the cause non-obvious during debugging. Full report in docs/BUG_SNAPSHOT_LEAKS_INTO_COMMITS.md.
    • Auto-migration for pre-0.7.7 snapshots. When SnapshotManager::new finds an old .torii/snapshots/ directory in a working tree, it moves every snapshot under it into the new <gitdir>/torii/snapshots/ location, then removes the empty .torii/snapshots/ (and the .torii/ parent if nothing else lives there). Idempotent — destinations that already exist are preserved (the new location wins). Cross-filesystem rename failures fall back to a recursive copy + remove. Prints a one-line ℹ Migrating N snapshot(s) from … → … notice. Runs on every SnapshotManager instantiation but is a no-op when the legacy directory is empty or absent.

    Added

    • torii pipeline {list, cancel, retry, delete} — CI pipeline / workflow-run management for GitLab Pipelines and GitHub Actions. Symmetric surface across both platforms, auto-detects which one from the origin remote URL. Common shapes:

    Audit — non-changes

    • Fix #4 from the snapshot bug report (warn on suspicious commit size — > 50 MB or > 500 files) was deferred. The root cause is gone after fixes #1 + #3; a generic large-commit warning is general hardening worth doing on its own merit and not as a backstop for this bug. Tracked for a future release.
  131. f5199f34 fix+feat snapshots out of working tree + torii pipeline CLI
  132. e56f2461 fix parametrize Changelog URL via $CI_PROJECT_URL
  133. e297db2a fix set RUST_MIN_STACK so rustc doesn't SIGSEGV on the russh crypto tree
  134. 0.7.6

    Added

    • torii sync --fetch accepts a remote name and --all. The fork workflow — origin for our work, a separate upstream for read-only mirror sync — had no path through torii: sync --fetch always hit the tracking remote, and there was no way to point it elsewhere without dropping to git fetch upstream and bypassing the gitorii-skill invariant ("every VCS op goes through torii"). 0.7.6 closes this gap by overloading the existing positional argument on sync: when --fetch is present, the positional argument is the remote name, not a branch.
    • fetch_named(name) validates the remote exists up-front and surfaces a hint listing configured remotes if it doesn't (instead of libgit2's generic error).
    • fetch_all() iterates Repository::remotes(), prints one line per remote with the per-remote status, returns Err if any single remote failed (the others are still attempted before the error surfaces).
    • Both share fetch_one(name), which uses the default refspec from .git/config and the same auth callbacks + progress display the existing single-remote fetch() used.
    • --all is mutually exclusive with the positional remote (clap-enforced via conflicts_with = "branch").

    Audit — non-changes

    • The companion request "add torii remote add / remove" was audited and dropped: torii remote link / unlink already cover the gap (URL form and platform shorthand both work). Renaming a public CLI surface for mental-model alignment with git costs more than it buys.
  135. 3278f135 feat torii sync --fetch [remote] [--all] for fork workflow
  136. 0.7.5

    Fixed

    • PR / Issue views failed with the generic Invalid configuration: Unexpected GitLab response for every non-200 reply. Both pr::list and issue::list (GitHub + GitLab variants) parsed the response body straight into serde_json::Value::as_array() without inspecting the HTTP status, so a 401 Unauthorized, 404 Not Found, or 403 (the actual common failure modes) was indistinguishable from a malformed body. Now each list call: 1. Captures resp.status() before consuming the body. 2. Parses the body as serde_json::Value (works for both the array success case and the { "message": "…" } / { "error": "…" } error case). 3. On non-success, surfaces a real diagnostic: GitLab API 401 Unauthorized: <message> (url: <full url>) / GitHub API 404 Not Found: <message> (url: <full url>). The URL is included so misconfigured gitlab.host / github.owner are obvious from the error alone.
    • Auth config keys lived in two places at once. Since 0.7.2 the Auth view became the source of truth for tokens (~/.config/torii/auth.toml), but load_config() in src/tui/app.rs still listed auth.cloud_key, auth.github_token, auth.gitlab_token, auth.codeberg_token, auth.bitbucket_token in ALL_KEYS (and in SENSITIVE). Result: the auth section appeared in the Config view with [set] / [not set] placeholders that read from the old config.toml [auth] table — stale, and editing them did nothing because the resolver routes through auth.toml now. The five keys were removed from ALL_KEYS and SENSITIVE; the Auth view is now the only place credentials are managed. worktree.base_dir and worktree.inherit_paths were added in their place (real keys the Config view should expose).

    Changed — TUI

    • Config view "status" box removed; its hints live in the global hint bar. The view used to render a three-row layout (sections | entries | status), where the bottom box duplicated the bottom hint legend used by every other view. The status box is gone; render_hint for View::Config is now mode-aware:
    • Navigating: [↑↓/jk] navigate [Enter] edit [Tab] toggle scope
    • Editing: [Enter] save [Esc] cancel
  137. 075fa6c1 fix better GitLab/GitHub error msgs + Auth out of Config + hint bar consolidation
  138. 0.7.4

    Fixed

    • TUI sidebar couldn't reach the four new views added in 0.7.2 (Worktree, Submodule, Bisect, Auth). App::sidebar_down hard-coded if self.sidebar_idx < 13 — the pre-0.7.2 max — so navigating with / j capped at index 13 instead of 15 and the bottom four entries (Bisect, Auth, Config, and the slot they pushed Pr/Issue past) were unreachable through the sidebar. The mapping in view_for_idx was also stale: indexes 7–15 still pointed at the pre-0.7.2 layout (History, Remote, Workspace, …) so even when navigation worked the rendered view was wrong.
    • view_for_idx updated to the post-0.7.2 16-entry order, kept in sync with TABS in src/tui/ui.rs and the sidebar_idx assignments in App::go_to. A new constant App::SIDEBAR_LEN = 16 is used as the bound for sidebar_down.
    • App::go_back mapping rewritten with the same order; the deprecated View::History / Mirror / Settings variants map to their fused destinations (4, 9, 15 respectively).
    • torii publish printed a literal $(name) after a successful upload (View at https://crates.io/crates/$(name)) — the placeholder was never substituted. Now reads [package].name from Cargo.toml and prints the correct URL.

    Changed (CI)

    • .github/workflows/release.yml publish-crates job rebuilt around an OIDC-primary + PAT-secret fallback chain. The previous version layered rust-lang/crates-io-auth-action@v1 (which injects CARGO_REGISTRY_TOKEN via Trusted Publishing OIDC) and then immediately overwrote that token with ${{ env.CRATES_IO_TOKEN }} — a never-defined env var that resolved to empty string. Net effect: cargo publish always ran with no token and failed. Now: 1. The OIDC action runs with continue-on-error: true. If trusted publishing is set up on crates.io for PaskiDev/gitorii + workflow release.yml, it injects the token and cargo publish uses it. 2. If OIDC didn't fire, the publish step's env reads secrets.CARGO_REGISTRY_TOKEN (repository or org secret) as a fallback.
    • Note for users updating their GitHub allowlist: the workflow uses three external actions (actions/checkout, softprops/action-gh-release, dtolnay/rust-toolchain, rust-lang/crates-io-auth-action). The org-level "Actions permissions" must either allow Marketplace-verified creators or list these explicitly under "Allow specified actions".
  139. fd2eb44d fix sidebar bounds + view_for_idx + publish URL + CI auth chain
  140. 9ed5eda1 chore clean up the 21 warnings dragged by recent refactors
  141. 0.7.3

    Fixed

    • torii save no longer falls back to "Torii User" <user@torii.local> when the user's identity is configured via torii config but not in git's own config chain. The previous GitRepo::get_signature read only from repo.config() (libgit2's .git/config~/.gitconfig/etc/gitconfig) and substituted a hardcoded placeholder on miss. Tracked in docs/BUG_COMMIT_AUTHOR_FALLBACK.md (now annotated as FIXED). The fix introduces a single crate::core::resolve_signature(&repo) with documented precedence: 1. Torii config (~/.config/torii/config.toml [user]user.name / user.email). Treated as the source of truth for the user's identity. 2. Git's own config chain (kept as fallback so existing git config user.name setups keep working). 3. Hard error with a fix-it hint pointing at torii config set user.name "…". Bogus authorship is worse than failing fast.
    • Every commit-writing call site routes through the new resolver: GitRepo::get_signature (used by save / save --revert paths), core_tag::cherry_pick and its continue/conflict paths, tag::create_tag (annotated tags), core_extensions (rebase apply, revert commit, merge commit, generic-commit helper x2), snapshot::stash (which previously had its own "torii"/"torii@local" placeholder fallback — same anti-pattern, now removed), and the TUI's direct-commit path. Seven sites in total, all unified.
    • Empty strings treated as "not configured". torii config set user.name "" previously slipped past the lookup and passed an empty name straight to libgit2, which rejected the commit with the generic Signature cannot have an empty name or email error. Now the resolver filters empties and returns the same fix-it error as a missing key.

    Migration

    • Single bad commit: torii config set user.name "…" + torii config set user.email "…", then torii save --amend -m "<same message>".
    • Multiple bad commits: configure as above, then torii history reauthor --old "Torii User" --new "Real Name <real@email>" (committer pass-through with --committer if needed).
  142. 9fafa088 fix resolve signature from torii config, not just .git/config
  143. 0.7.2

    Added

    • Worktree view — lists every linked working copy with branch, clean/dirty count, and lock status. Sidebar key k. Refreshes on entry via libgit2's repo.worktrees() directly (no CLI shell-out).
    • Submodule view — every registered submodule with HEAD oid, working-tree oid, URL, and state string (clean/modified/staged/untracked/not initialised). Sidebar key m.
    • Bisect view — surfaces the state of an active git bisect session (current commit, good/bad refs from .git/BISECT_LOG). When no session is active, points to the CLI commands to start one. Sidebar key v.
    • Auth view — masked list of every credential torii knows about (cloud key + per-platform tokens) with the source of each (env: $VAR / local / global / (not set)). Mirrors torii auth doctor from the CLI. Sidebar key a.

    Changed — sidebar reorganisation

    • Log absorbs History. The natural flow is "browse log → modify the commit I see", and separating them forced sidebar hops. History rewriting ops will surface inside the log view in 0.7.3 (no functional regression — torii history … from the shell remains the canonical write path). Sidebar entry "history" removed.
    • Remote absorbs Mirror. The previous dispatcher already redirected View::Mirror → views::remote::render, so the separation was artificial. Mirrors become a panel/tab inside Remote in 0.7.3. Sidebar entry "mirror" removed.
    • Config absorbs Settings. Both end up presenting key-value editors; they'll share a single view with two tabs ("TUI prefs" / "Repo config") in 0.7.3. Sidebar entry "settings" removed.

    Notes

    • The four new views are informative in 0.7.2 — they show state and point at CLI commands for actions. Interactive ops dropdowns (add/remove/lock/start-bisect/set-token) will land in 0.7.3 once we've validated the layout. Same pattern other views (tag/snapshot) follow.
    • Deprecated View::Mirror, View::History, View::Settings variants still match in the dispatcher and go_to so any old code that constructs them keeps working — they just redirect to the fused view. Will be removed when the 0.8 deprecation cycle lands.
    • Old per-view event handlers (handle_history, handle_mirror, handle_settings) generate dead-code warnings for now; cleaning them up is bundled with the 0.7.3 interactive sweep so the diff stays focused.

    Out of scope, deferred to 0.7.3

    • Interactive keybinds on the four new views (n=new, d=delete, l=lock, …).
    • Log getting --tracked toggle + notes overlay + patch-export modal.
    • Snapshot view ops dropdown (apply / pop / drop / clear / show).
    • Tag view force-push toggle.
    • Remote view: mirrors panel + subtree subpanel.
    • Config view: TUI/Repo tabs absorbing the old Settings view.
  144. 32acbe44 feat 4 new views + 3 fusions to catch up with CLI surface
  145. 0.7.1

    Fixed

    • torii config set auth.X_token … --local had no effect at runtime. The local store was never read by transport/PR/issue/remote code — they all called ToriiConfig::load_global(), which ignores <repo>/.torii/config.toml. Tokens set with --local got persisted but never resolved when pushing or hitting platform APIs. Fixed by centralising every token read through crate::auth::resolve_token, which checks env > local > global in that order.
    • torii config set … --local was duplicating the global config into the local file. ToriiConfig::load_local() merges global+local and returns the result; the dispatcher then called save_local() on the *merged* config, writing every global setting (including tokens) into the local file. From that moment on the local clone shadowed any subsequent global change. Auth state now lives in a separate auth.toml and the new load_local_raw returns local-only without merging.
    • Mixed precedence between commands. pr and issue honoured env vars (GITHUB_TOKEN, GITLAB_TOKEN), but the HTTPS transport that actually does the pushing did not. So cargo install-style CI setups would create PRs fine but fail to push. Single resolver, same precedence everywhere.

    Added

    • torii auth becomes the single entry point for credentials.
    • torii auth set <provider> <token> (provider: github, gitlab, gitea, forgejo, codeberg, bitbucket, sourcehut, cargo). Use - as the token to read from stdin (CI-safe).
    • torii auth get <provider> prints the resolved token, masked (ghp_xxxx****). --unsafe-show for the raw value.
    • torii auth list shows every provider with masked value and source (env / local / global / not set).
    • torii auth remove <provider> deletes from global; --local for the per-repo store.
    • torii auth doctor prints exactly where each provider's token is being resolved from — the missing tool when "torii doesn't use my token" hits. Also surfaces a stale legacy [auth] block if it lingers in config.toml.
    • torii publish — thin wrapper over cargo publish that injects auth.cargo automatically. No more .env juggling. Flags --dry-run / --no-verify / --allow-dirty / --token <X> pass through.
    • New env vars recognised: BITBUCKET_TOKEN, SOURCEHUT_TOKEN / SRHT_TOKEN, GL_TOKEN. Already-supported ones (GITHUB_TOKEN, GH_TOKEN, GITLAB_TOKEN, GITEA_TOKEN, FORGEJO_TOKEN, CODEBERG_TOKEN, CARGO_REGISTRY_TOKEN, TORII_HTTPS_TOKEN) now apply uniformly across every code path.

    Changed

    • Storage moved: platform tokens migrate from ~/.config/torii/config.toml [auth] to ~/.config/torii/auth.toml [tokens]. Auto-migration: the first time auth is consulted, it reads the legacy [auth] block, rewrites it into the new file, and torii auth doctor reminds you the legacy section can be deleted from config.toml. No data loss; old configs keep working until you clean them up.
    • torii config set auth.<provider>_token … still works but is now a deprecated alias that redirects to torii auth set <provider> … and prints a one-line hint. Scheduled removal: 0.8.
    • All error messages and -h examples updated to point at torii auth set instead of torii config set auth.X_token.

    Storage format

    Out of scope

    • cargo-dist installer setup — still deferred to 0.7.2.
    • GPG re-sign in torii history reauthor — same.
  146. 59561cab feat unify credentials under torii auth; fix three real bugs
  147. 0.7.0

    Added (top-level)

    • torii bisect — binary-search the commit that introduced a regression. Subcommands start / bad / good / skip / reset / log / run <cmd>. State-machine wrapper over git bisect (libgit2 has no bisect primitives).
    • torii describe — pretty name for HEAD based on the nearest tag, e.g. v0.6.9-3-gabc1234. Flags --tags / --long / --dirty / --candidates N.
    • torii archive — export a tree or commit as tarball/zip. Wrapper over git archive to inherit decades of format edge-cases.
    • torii remove (alias rm) — remove tracked files from index and working tree. Flags --cached / -r / --force.
    • torii rename (alias mv) — rename/move tracked files; both filesystem and index updated atomically. --force to overwrite.
    • torii grep — search tracked content for a pattern. Wrapper over git grep (faster than ripgrep on tracked-only content; different concern from torii scan).
    • torii notes — annotations on commits stored in refs/notes/commits. Subcommands list / add / append / show / edit / copy / remove.
    • torii patch — export commit ranges as .patch files (export) and apply them as new commits (apply). Wrappers over git format-patch / git am with --3way / --continue / --abort / --skip plumbed through.
    • torii clean — remove untracked files (≡ git clean). Defaults to dry-run for safety. -f / -d / -x / -X flags.

    Added (extensions to existing commands)

    • torii tag push --force — finally a way to force-push a single tag (or all tags) from torii without falling back to git. Refspec gets the standard +oldref:newref prefix on the wire.
    • torii submodule update --recursive and torii submodule add --recursive — descend into nested submodules so update mirrors git submodule update --init --recursive when both flags are passed.
    • torii worktree lock / unlock / move / repair — fills in the rest of git worktree parity. move patches both the .git link inside the worktree and the .git/worktrees/<name>/gitdir admin file by hand because libgit2 has no worktree_move.
    • torii snapshot apply / pop / drop / clear / show — completes the git stash family on top of the existing stash / unstash. apply and pop are aliases of unstash --keep / unstash for users coming from git. clear deletes all snapshots (asks unless --yes); show prints metadata + bundle contents.
    • torii status --tracked (-z for NUL-separated)git ls-files equivalent. Walks the index and prints every tracked file.
    • torii remote refs <target> (--heads / --tags)git ls-remote equivalent. Hits the network using configured auth.

    Renamed (with aliases — old names still work, prints deprecation in some cases)

    • torii history cleantorii history compact (alias gc). "GC" is jargon, "compact" reads. clean (history) → deprecated alias with warning; will be removed in 0.8. Frees up the word clean for the new top-level untracked-cleanup command.
    • torii history fscktorii history orphans (alias fsck). "fsck" is hostile Unix-filesystem jargon; "orphans" describes exactly what the command finds.
    • torii rmtorii remove (alias rm). Plain English first, rm kept for muscle memory.
    • torii mvtorii rename (alias mv). "rename" is more accurate than "move" 95 % of the time and friendlier; mv kept for muscle memory.

    Deprecated

    • torii blame <file> → use torii show <file> --blame (already existed; was a duplicate top-level). Old form prints a warning and still works through 0.7.x; will be removed in 0.8.
    • torii history clean → use torii history compact (or alias gc). Old form prints a warning.

    Notes

    • torii notes and torii bisect intentionally wrap their git counterparts rather than reimplementing on top of libgit2. The state machines and edge-case handling involved (mailbox parsing, BISECT_* file ceremony, notes-tree merge semantics) are decades-refined upstream; reimplementing them would be 1k+ LOC of risk for behaviour already correct.
    • Out of 0.7.0, deferred to 0.7.1:
    • cargo-dist installer setup — the README still mentions a gitorii-installer.sh that no CI generates. Tracked.
    • GPG re-sign during torii history reauthor / mailmap apply — needs libgit2 commit_signed callback wiring + a real key in tests. Documented limitation since 0.6.7.
    • rust-toolchain.toml stays pinned at 1.94.0. rustc 1.96 (with the mono-partitioning ICE fix) is currently in beta with a stable release expected in ~11 days; we'll validate against it then and unpin.

    Porcelain coverage

    • sparse-checkout (edge case for monorepos),
    • mergetool / gui / citool (interactive UIs — torii tui occupies that space),
    • range-diff (rare; comparing commit series),
    • restore at file-level (parcial via save --reset; explicit form pending if asked),
    • shortlog (parcial via log --author).
  148. f717eca8 feat 0.7.0 — porcelain feature-complete + human-first naming polish
  149. 0.6.9

    Added

    • torii submodule — seven-subcommand MVP for embedding another git repo at a pinned commit inside this one. Mirrors git submodule with torii's UX layer on top.
    • torii submodule add <url> <path> [--branch <b>] [--name <n>] registers the entry in .gitmodules+.git/config, clones the contents, stages the result, and writes the optional tracking branch. The user finishes the operation with their own commit.
    • torii submodule status (or just torii submodule) lists every submodule with HEAD oid, working-tree oid, URL, and a state string (clean, modified, not initialised, dirty working tree, etc.).
    • torii submodule init [--force] copies .gitmodules URLs into .git/config so update knows where to fetch from. Idempotent.
    • torii submodule update [--init] fetches and checks out the commit each submodule is pinned at. --init runs init first for uninitialised entries (mirrors git submodule update --init).
    • torii submodule sync re-copies .gitmodules URLs into .git/config (useful after an upstream URL change).
    • torii submodule foreach <cmd> runs <cmd> via $SHELL -c in each submodule's working directory, exporting TORII_SUBMODULE_NAME and TORII_SUBMODULE_PATH. Stops at the first non-zero exit (matches git submodule foreach default).
    • torii submodule remove <path> scrubs all four places submodule state lives: .gitmodules section, .git/config section, .git/modules/<name>/ cached gitdir, and the super-repo's index (via libgit2 directly — git rm --cached refuses when .gitmodules already has staged changes; libgit2's index API doesn't care).
    • torii subtree — five-subcommand thin wrapper around git subtree for merging another project's history into a subdirectory of this repo. add/pull/push/split/merge, all forwarding to the upstream contrib script. --squash exposed on the operations that support it.
    • Why a wrapper, not a reimplementation: git subtree is ~800 lines of bash refined since 2009 with a long tail of edge cases (orphan commits, parent detection, --squash semantics, history rewrites through merge bases). Reimplementing those in Rust on top of libgit2 (no subtree primitives) would be 1k+ LOC of risk. Torii provides the UX skin and clear error message when git-subtree is missing.
    • Worktree polish — four follow-ups to 0.6.8:
    • torii worktree with no subcommand defaults to list (git/cargo/npm convention).
    • torii worktree list now shows ahead/behind vs upstream when the worktree's branch tracks one. Reads dirty · 2 ahead, 1 behind style; silently omits the second segment when there's no upstream (very common for fresh feature branches).
    • New config key worktree.inherit_paths (comma-separated): paths from the main repo to drop into every freshly-created worktree. Files are copied (real fresh writable copy); directories are symlinked (typically large build caches like target/ or node_modules/); missing entries are silent. Solves the #1 pain of worktrees in practice — no more rebuilding from scratch in every linked checkout.
    • Snapshot module now handles worktrees correctly. Previously the pre-remove safety snapshot in torii worktree remove failed silently with "Not a directory (os error 20)" because the module assumed .git was a directory; in a worktree it's a one-line link file pointing at a shared gitdir in the main repo's .git/modules/<name>/. The module now detects the file case, copies the link plus a RESOLVED-GITDIR marker, and leaves the shared metadata alone.

    Notes

    • Submodule recursion (--recursive) is intentionally not in 0.6.9; nested submodules need a manual loop for now. Tracked for follow-up.
    • Subtree depends on git-subtree being on PATH. On Arch/Fedora it ships with git; on Debian/Ubuntu it's a separate git-subtree package. Torii surfaces a precise error message if it's missing.
    • Index manipulation in submodule remove is now done via libgit2 directly (Index::remove_path/remove_dir + Index::write), not by shelling out to git rm --cached. The shell-out path stayed brittle in practice because git refuses to operate on the index when .gitmodules has uncommitted edits, which is precisely the state we're in mid-remove.
  150. 0b9b6765 feat three feature blocks + polish
  151. 0.6.8

    Added

    • torii worktree — five-subcommand MVP: linked working copies of the same repository, each on its own branch, sharing the underlying object database. Useful for "let me hot-fix without disturbing my in-progress branch" and similar workflows that git worktree covers — with torii ergonomics on top.
    • torii worktree add [<path>] [-b <new-branch>] [<existing-branch>] creates a new worktree. Path is optional: when omitted it's derived from worktree.base_dir (new config key, default ..) + <repo>-<branch-sanitized>/. -b creates a branch off HEAD; positional names an existing local branch.
    • torii worktree list prints every worktree (main + linked) with branch name and clean/dirty status in one shot. 📍 marks the current one; locked worktrees show their lock reason. Faster mental model than the per-worktree text dump from git worktree list.
    • torii worktree remove <path> [--force] [--no-snapshot] deletes a worktree's directory and prunes its libgit2 metadata. Refuses if the working tree is dirty unless --force. Always attempts a safety snapshot first (snapshot of the worktree itself, not the main repo). The snapshot may silently fail on worktrees because the existing snapshot module assumes .git is a directory and a worktree's .git is a link file — graceful warning, removal proceeds. Snapshot module fix tracked for a later release.
    • torii worktree prune clears metadata for worktrees whose directories were deleted out-of-band (e.g. via rm -rf). Only fires on already-invalid entries; never touches live worktrees.
    • torii worktree open <path> launches $SHELL (fallback /bin/bash) in the worktree directory and blocks until you exit — same gesture as (cd <path> && $SHELL) but rejected if the path isn't a known worktree of the current repo. git worktree has no equivalent.
    • New config key worktree.base_dir (default ..) controls where torii worktree add puts new worktrees when no path is provided. Honors ~ expansion. Set with torii config set worktree.base_dir ~/worktrees to centralise them.

    Notes

    • Lock / unlock / move / repair are intentionally not in 0.6.8; the design review picked an MVP plus open as the first cut. Filling them in is a straight-line addition on top of Worktree::lock/unlock from git2 + path manipulation; pull request welcome.
    • Unit tests cover branch-name sanitisation, ~ expansion, worktree-name derivation. Walker tested end-to-end against toy repos: add (new + existing branch), list (with status), remove (clean + dirty + force), prune (stale entries).
  152. 82df1533 feat MVP — add / list / remove / prune / open
  153. 0.6.7

    Added

    • torii history reauthor --old <id> --new <id> — rewrite author identity across reachable history with a single CLI pair. Auto-detects the --old format: "Name <email>" for full match, a bare email for email-only match, or a bare name for name-only match. The replacement --new must always be "Name <email>". Flags: --committer (also rewrite committer; default off), --since <rev> (limit to a range), --dry-run (preview without writing), --no-snapshot (skip the automatic safety snapshot), --allow-dirty (proceed past uncommitted changes).
    • torii history mailmap apply [--file <path>] — batch identity rewrite driven by a [standard git .mailmap](https://git-scm.com/docs/gitmailmap) at the repo root (or any path). Supports all four mailmap line shapes: Name <commit-email>, <proper-email> <commit-email>, Name <proper-email> <commit-email>, Name <proper-email> Commit Name <commit-email>. Shares every flag with reauthor (--since, --dry-run, --no-snapshot, --committer, --allow-dirty).
    • Shared behaviour for both commands:
    • Safety snapshot (pre-reauthor-<timestamp>) taken automatically; revert with torii snapshot restore <id>.
    • Annotated-tag taggers are rewritten to match the new identity (not preserved) so tag metadata stays consistent with the rewritten commit.
    • Original author/committer timestamps preserved — only *who* changes, never *when*. Use torii history rewrite for dates.
    • Refuses to run if the repository has a pending merge/rebase/cherry-pick or a dirty working tree (override with --allow-dirty).
    • HEAD and local branches re-point at the new OIDs; lightweight tags retarget; annotated tags get rebuilt.

    Documentation

    • COMMANDS.md adds an "Identity rewrite details" subsection under torii history covering snapshot behaviour, timestamp preservation, GPG-signature invalidation, mailmap format, and the --force push needed after rewriting shared branches.
    • README.md History section gains the new commands and a one-paragraph caveat block.
    • README "Install" expanded with a fallback to the GitLab Generic Package Registry direct URL (gitlab.com/api/v4/projects/paskidev%2Fgitorii/packages/generic/gitorii/<tag>/torii-<arch>). The gitorii-installer.sh wrapper referenced in the top install snippet doesn't exist yet — no CI generates it — so users currently hitting the 404 have an explicit working path. cargo install gitorii --locked is the new from-source recommendation.
    • README "Known issue" rewritten to separate the two failure modes (rustc 1.95 ICE vs. LLVM codegen SIGSEGV / stack overflow) and give the concrete flags that resolve each one: cargo +1.94.0 install gitorii --locked for the ICE, plus RUST_MIN_STACK=16777216 ... -j 2 for the codegen path. Adds a third "skip the compiler entirely" path with the GitLab binary URL.

    Build / toolchain

    • .github/workflows/release.yml pins dtolnay/rust-toolchain@1.94.0 for the cargo publish job (was @stable). Without this the CI's verify-build step would ICE on rustc 1.95.0 against the russh→rsa-rc chain and the publish would never reach crates.io. Also passes --locked, RUST_MIN_STACK=16777216 and CARGO_BUILD_JOBS=2 to mirror the README workarounds for the codegen-pressure path. Revert to @stable once upstream rustc fixes the regression.

    Known limitations

    • GPG-signed commits: signatures invalidate after rewrite because they're computed over the original author. Re-sign manually (or set up a key and re-run torii save --amend on each commit) — automatic re-signing during rewrite is not yet wired.
    • gitorii-installer.sh doesn't exist yet — README mentions GitHub Releases but no CI generates the wrapper script. Tracked for follow-up (cargo-dist or equivalent). Direct binary download from GitLab works in the meantime; see Install section.
  154. 0d921e3d chore pin CI toolchain to 1.94.0 + document install workarounds
  155. 4ba4dee7 feat reauthor + mailmap apply — rewrite author identity across history