Changelog
What has been happening
The commit record of the CLI, newest first, straight from GitLab. Each release is marked where its tag points, with its notes from CHANGELOG.md underneath.
current: 0.13.0 · 100 commits · the full history on GitLab
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 existingtorii save --unstage. Closes the gap that previously forcedgit add -Nas 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 commonKEY=tokenshape with no surrounding spaces (.envfiles,-e KEY=valuecontainer 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 auser:password@shape. - The connection-string scheme list was missing
postgres://(the short/libpq aliaspostgresql://— Heroku, sqlx, psycopg, node-postgres all use it) andmongodb+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 likeuser:pass@localhostwill now be flagged; clear it with--yesor 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 statusandtorii diff --stagedno longer crash before the first commit. A repo with staged content but no commits yet (an "unborn" HEAD) — exactly the statetorii save --stagecan now put you in — used to error withreference 'refs/heads/main' not found.
- 27da3e6e fix close missing DB-scheme aliases, stop excluding guessable passwords
- d8eb9a56 chore bump version to 0.13.0
- a43909df feat add --stage to prepare files without committing
- 66abeece fix status and diff --staged no longer crash on unborn HEAD
- 611e2a74 fix close secret-scanner detection gaps and false positives
- d6913d43 build vendor libgit2 (git2 vendored-libgit2 feature)
- 04594005 feat GitLab support for remote contents
- df924ba3 feat remote repo contents (tree + file) without cloning
- 2108ff4d fix record MERGE_HEAD parents when committing a merge resolution
- aae0d43f fix resolve remote-tracking branches when integrating
- e8d958c0 fix use the GitHub REST API instead of gh for repo settings
- 6d603f5c feat filter-branch/filter-repo suite; bump 0.12.0
- e3b9c0d6 feat history reword + date-preserving save/amend; bump 0.11.0
- 801c58e8 style clear all 325 clippy warnings across the workspace
0.10.0
Added
torii-libpublished as a standalone crate — the domain layer (VCS operations, platform clients, workspaces, config/auth) split out of the binary.gitoriinow depends on it like any consumer; the upcomingtorii-cloudplugin 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 | headno 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 renameworks on directories — previously the move happened on disk but indexing failed (Index::add_pathonly accepts files). Tracked entries under the old prefix are now re-recorded recursively; untracked files stay untracked.torii saveno longer hangs without a TTY when the secret scanner finds something — it fails fast pointing at--yesinstead 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 nametorii_lib) +crates/torii(CLI/TUI binary, packagegitorii). - Modularised the three largest files:
cli.rs(6.6k lines → clap surface + 24 domain modules),tui/app.rsandtui/events.rs(per-view modules), andplatforms/reorganised by platform (github/, gitlab/, gitea/, sourcehut/, radicle/, bitbucket/, azure/) with shared traits/types/factories per feature. - Error precision migration completed: ~415 catch-all
InvalidConfigsites moved to precise variants —Network,PlatformApi(with real HTTP status),Auth, plus newMalformedResponse,Subprocess,Fs,RepoState,Workspace,UnsupportedandUsage. The remainingInvalidConfiguses are genuine configuration errors. - Removed a stale TSAL license header from
main.rs(the project relicensed toMIT OR Apache-2.0in 0.9.0).
- 15bb7abe chore regenerate Cargo.lock for 0.10.0
- 504dfb08 chore bump to 0.10.0 — torii-lib split, precise errors, fixes
- 23fa73c5 refactor package torii-lib (import torii_lib) — nombre registrable
- 0c31828c style cargo fmt over the workspace
- b501380e fix fail fast on findings without a TTY; add --yes
- 00ca8428 fix reindex tracked entries when renaming a directory
- 79b9702e chore drop stale TSAL header — relicensed MIT/Apache in 0.9.0
- 1f41b1ea refactor workspace lib+bin — crates/torii-core + crates/torii
- 41af42f1 refactor WorkspaceManager devuelve datos, el CLI imprime
- b93fcd13 refactor sellar fugas git2 de la API pública + vcs/sign.rs
- e0e4878a refactor status() devuelve RepoStatus en vez de imprimir
- af72c535 chore regenerate Cargo.lock for 0.9.2
0.9.2
Changed
- README badges updated:
license-custom→MIT OR Apache-2.0, Rust badge bumped to1.95+. rust-versioninCargo.tomlbumped from1.85to1.95to reflect the actual minimum that builds the current dep tree.
- README badges updated:
- 52ff8c40 chore bump to 0.9.2 — refresh README badges (license, rust)
- a35d53a7 chore bump MSRV to rustc 1.95 (Cargo.toml + README badge)
- da581a52 chore regenerate Cargo.lock for 0.9.1
0.9.1
Changed
- Unpinned
russhfrom=0.60.2to0.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, andssh-keyleft the internal russh fork. - Removed
rust-toolchain.toml. Stable rustc now builds the dep tree cleanly. Users no longer needrustup install 1.94.0tocargo install gitorii. - README
Troubleshootingsection trimmed — the historical ICE note is gone; only the genericRUST_MIN_STACKadvice remains for deep monomorphisation cases. - CI: GitHub release workflow switched from
dtolnay/rust-toolchain@1.94.0to@stable. GitLab CI comments updated.
Verified
- Full debug + release builds pass on stable rustc 1.95.0 (Arch host).
aws-lc-sysremains at0.40.0(Windows cross-compile via mingw + nasm still works; no regression to the 0.41 NASM-mandatory branch).
- Unpinned
- 1fe58d90 chore unpin russh, drop rust-toolchain.toml
- e6b4c358 chore regenerate Cargo.lock for 0.9.0
0.9.0
Changed
- Relicensed from TSAL-1.0 to
MIT OR Apache-2.0. Gitorii (thetoriibinary 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
LICENSEfile (TSAL-1.0) has been replaced with two files:LICENSE-APACHE(Apache License 2.0) andLICENSE-MIT(MIT License). Recipients may choose either. Cargo.tomllicense-file = "LICENSE"replaced by the SPDX expressionlicense = "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
- Relicensed from TSAL-1.0 to
- 62c232f2 chore relicense to MIT OR Apache-2.0
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/searchuntil now. With this release,oover 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 rebaseunder the hood) 6. show signature — open the GPG armor + verify overlay for the selected commit (same modalSopens 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. compact —gc+ 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);dangerpaints the row red and is used for the two history-rewriting ops.- Log ops dropdown title now reads
ops — Enter run · Esc closewith 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.
- 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
- f0841edc feat Log ops dropdown gains history ops
0.8.2
Changed
Cargo.tomldescription 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:
keywordsnow includestui(was a slot short of mentioning the most distinctive surface);repositoryanddocumentationpoint at the GitLab mirror (the canonical home since the GitHub-radioactive memo); explicitreadme = "README.md"for tooling that doesn't auto-detect. README.mdtop-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
rustcICE / SIGSEGV troubleshooting block moved to a## Troubleshootingsection 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).
- d83cf57c docs refresh crates.io / README presence
- b17da828 chore bump Cargo.lock for 0.8.1
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:
running→online(green),exited→offline(dim),paused→paused(dim),restarting→online(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 intui::app. Callsdocker ps -a --filter name=torii-runner- --format Names\tState\tImageand maps each row into arunner::Runnerwithrunner_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_runnersnow 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 existingplatform_runners_rx.
- TUI Runners sub-tab now combines online + local containers. The platform's runners (API-side) and any torii-spawned Docker container on this host (
- 7d7ac3d6 feat split online/local runners in Platform Runners sub-tab
0.8.0
Added
platforms.tomlregistry at two locations:~/.config/torii/platforms.toml(global)<repo>/.torii/platforms.toml(per-repo override) Local entries shadow global ones byname. Builtins (github.com, gitlab.com, codeberg.org, bitbucket.org) live in code and can be shadowed withtorii platforms addif you need different URLs.torii platforms list / add / remove / testCLI surface.listprints 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 towardaddto 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_namedlooks atplatforms.toml(longest-domain match wins, so subdomains can shadow umbrellas) and returns the matching kind. Newdetect_platform_fullhelper 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'storii pipeline …already calls through the new path, so a self-hosted GitLab declared inplatforms.tomlwill list / cancel / retry / log exactly like gitlab.com does today.
Internal
- New
crate::platforms::registrymodule withPlatformEntry,load_global/load_local/merged/all/find_by_host, andadd_entry/remove_entry. - New URL helpers in
crate::pr:extract_host,extract_owner_repo. Handlehttps://,ssh://,git@host:and the rarehost/owner/reposhorthand. - Re-exported as
crate::platforms_registryfrom 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 / testwill 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::newgets a_with_base_urlsibling) and will use the samedetect_platform_fullplumbing this release introduced. - Backwards compat: no breaking changes.
get_pipeline_clientstill exists as a thin shim over the newget_pipeline_client_with_base_url(p, None).
- 64dd0131 feat platforms.toml registry + GitLab self-hosted
0.7.40
Added
- Submodule TUI ops dropdown — closes the gap left in 0.7.39.
oopens 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/configfrom.gitmodules), Foreach<cmd>(run a shell command in each submodule, input overlay), and Remove (confirmy/n). All entries route directly tocrate::cmd::submodule::*— same wrapping pattern as Worktrees. - Submodule view's hint bar (
render_hintintui/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 (wasC_CYAN),C_DIMfor path/url secondary text,C_GREEN/C_YELLOWfor 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_opintui/events.rs.- Esc closes overlays back to List, then defers to the global Esc path (sidebar focus).
- Submodule TUI ops dropdown — closes the gap left in 0.7.39.
- 58468424 feat submodule ops dropdown
0.7.39
Fixed
- Workspace view was empty when launched from inside a repo (and most other times).
torii workspace addwrites to~/.config/torii/workspaces.toml(canonical XDG viadirs:: 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. Newworkspaces_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 oauthnow 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 manualauth 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_tokenviaoauth::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.
oopens a context menu with add / open / lock / unlock / move / remove / prune / repair, wired tocrate::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_DIMfor paths,C_GREENfor clean,C_YELLOWfor 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 forSome(...)first. DeviceFlowStep::Doneis 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.
- Workspace view was empty when launched from inside a repo (and most other times).
- 148ffe38 fix+feat workspace path bug + OAuth auto-refresh + worktree TUI ops
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 towardgitlab-ci-local). Pass--use-gitlab-ci-localto switch to that binary instead — same.gitlab-ci.ymlparsing, 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 togitlab-runner exec),--env KEY=VAL(repeatable; forwarded as--envto gitlab-runner / act,--variableto gitlab-ci-local).
Internal
- New
run_runner_exec(platform, job, ci_file, executor, env, use_gitlab_ci_local)incli.rs. Same pattern as the rest of the runner subcommands:which_binaryfirst for a clear install-from-here error, thenCommand::status()with stdio inheritance.
- aa130e10 feat exec local job — gitlab-runner exec / gitlab-ci-local / act
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, thendocker 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 istorii-runner-<slug>, slug derived from owner/repo (override with--name).torii runner status—docker 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 arounddocker start|stopagainst thetorii-runner-<name>container.torii runner logs <name> [-f] [--tail N]—docker logswith stdio inheritance so-fstreams 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; usetorii runner remove <id>for that.
Internal
- New CLI helpers in
cli.rs: container_name(suffix)— single source of truth for thetorii-runner-*naming.run_runner_spawn/run_runner_status— the wrappers arounddocker run/docker ps.run_runner_docker(args, label)— output-captured helper for start/stop/rm.run_runner_docker_inherit(args)— stdio-inheriting variant used byspawnandlogs -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-dirinstead.
- 81e7f54d feat spawn/status/start/stop/logs/destroy via docker CLI
0.7.36
Added
- TUI Log view — GPG signature column.
Gtoggles a one-letter column ahead of the hash with the same vocabulary astorii log --signatures(Ggood = green,Uunknown signer = yellow,Bbad = red,?other error = subtle,Nnone = dim). Populates a per-OID cache the first time the column turns on so scrolling doesn't re-rungpg --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.
Sover the selected commit opens a centred modal that spawns a background worker (so the modal rendersloading…whilegpg --verifydoes 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 ofgit.sign_commits. Lets you do reauthor + re-sign in one pass instead of chainingreauthor+torii sign A..B. Requiresgit.gpg_keylike the other signing paths.git.gpg_keyandgit.gpg_programshown + editable in the Config view. They were already round-trippable through the config layer; they just weren't in the TUI'sALL_KEYSlist. Now they show alongsidegit.sign_commitswith the existing in-view edit flow.
Internal
LogStategainedshow_signatures: bool,signature_cache(HashMap<String, char>), andsignature_overlay: Option<SignatureOverlay>. NewSignatureOverlayenum (Loading/Done/Error) plusSignatureVerdictColorfor the modal's verdict line.- New
App::refresh_signature_cache(sequential verify across the loaded log slice) andApp::start_signature_overlay(background worker via a freshmpsc::Receiver). - Main loop drains
log_signature_rxeach tick into the modal state, same pattern as the OAuth modal. vcs::core_extensions::signature_letterpromoted frompub(super)topubso the TUI helpers can reuse it without a copy-pasted verify pipeline.
- TUI Log view — GPG signature column.
- 74139534 feat Log column + armor overlay + reauthor --sign + Config keys
0.7.35
Fixed
- GPG no longer turns off silently when a repo has a local
.torii/config.toml. The config merge replacedbase.gitwithoverlay.gitwholesale, so a local file that only declared (say)default_branch = "master"resetsign_commitstofalseandgpg_keytoNone. Now thegitsection merges field-by-field:sign_commits/pull_rebaseare OR'd so "true in either layer wins",gpg_key/gpg_programare only overridden when the overlay actually carries a value. (To explicitly *disable* signing for one repo, usetorii config set --local git.sign_commits false, which goes throughsetand bypasses merge.) - TUI Commit view's message input no longer traps you inside. When
typing == trueand 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 toNone, so Esc/Enter/Backspace/arrows did nothing while in the message field. All views whosetypingpredicate can be true are now routed to their own handlers.
Added
git.gpg_programconfig key (alias:gpg.program). Mirrors git's own setting; lets you point atgpg2or a vendor build whengpgisn'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'sgpgsigASCII armor plus a one-line verification verdict (good / unknown signer / bad / other), produced by parsing gpg's--status-fdoutput.torii log --signatures— adds a single-letter column to the log (Ggood /Uunknown signer /Bbad /?other gpg error /Nnone). Only spawnsgpg --verifyfor commits that actually have an armor attached, so unsigned histories stay cheap.torii save -S/--signand--no-sign— per-commit signing override. Implemented as a process-scoped env var (TORII_SIGN_OVERRIDE) that thecommit_inner_splitreader 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 anA..Brange, to include a freshgpgsigheader. 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-onlylets you sanity-check the armor without rewriting anything.
Internal
- New
util::gpg::VerifyStatus+verify(armor, payload, program)on top of the existingsign_blob. Callsgpg --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 bothsign_blobandverify. repo.log(...)gained asignatures: boolarg; the renderer callsvcs::core_extensions::signature_letter(repo, oid)(also new) which is what the future TUI log column will read.
- GPG no longer turns off silently when a repo has a local
- 42014738 fix+feat merge bug + verify + show/log/save/sign CLI + Commit-Esc bug
0.7.34
Added
- Bisect ref picker replaces the freeform "type
bad good…" input from 0.7.33. PickingStartfrom 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/Downnavigates;j/kalso work when the filter is empty.Esccloses. - Two-tab dance for Start:
Tabtoggles Bad ↔ Good. Picking on the Bad tab stores the pick and auto-advances to Good; on the Good tab,Spacemulti-toggles the goods (✓ next to picked entries), andEnterrunscrate::bisect::startwith 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 runcrate::bisect::good|bad|skipwith the chosen ref's name or OID.
Internal
BisectFocus::RefPickervariant +RefPickerState(op/tab/all/idx/filter/bad_pick/good_picks). NewRefPickerOp+RefPickerTab+RefEntry+RefKindintui::app.- New
views/bisect.rs::load_refs()(uses libgit2 viagit2::*) andfilter_indexes()exported so the events handler keeps the ordering in sync with what the renderer shows. - New
handle_bisect_picker+commit_bisect_pickerintui/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.
- Bisect ref picker replaces the freeform "type
- 2fd8a19a feat bisect ref picker (HEAD/branches/tags/remotes/log) + filter + tabs
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.
oopens an ops dropdown with the contextual actions (Start / Mark HEAD good / Mark HEAD bad / Skip HEAD / Run command / Reset) wired straight to the existingcrate::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_hintintui/ui.rs, the same arm pattern Auth and Platform use; nothing local stacks above it. - Palette aligned: bc for hashes (commit ids),
C_DIMfor secondary text, status-coloured marks (✓ good =C_GREEN, ✗ bad =C_RED, "bisecting…" headline =C_YELLOW).C_CYANdropped in favour of the brand colour — same convention used in log/branch.
Internal
- New
BisectFocus(List/OpsDropdown/InputArgs/ConfirmReset) +BisectPendingOp(Start/Run).BisectStategaineddropdown_idx,input_buffer,input_prompt,pending_op— same shape asAuthState. tui/views/bisect.rs::ops_for(state)exports the dropdown rows; events handler reads it the same way as Platform'sops_for/ Auth'sops_for.- New
handle_bisect+dispatch_bisect_opintui/events.rs. Esc closes overlays back toList, then defers to the global Esc path (sidebar focus).
- 8d331a2d feat bisect rework — ops dropdown + palette + footer hints
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 thesyncstatus 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 blockingrun_device_flowcould be rewritten on top of these but stays as the CLI entry point. - New
AuthFocus::OauthFlow+OauthFlowState+OauthStatusvariants onAuthState. Worker thread emits status updates overApp::auth_oauth_rx; main loop drains them into the modal. - Removed
Action::AuthOauthInPager/AuthRotateInPagerand therun_torii_subprocesshelper from 0.7.30 — no longer needed now that the flow stays in-process.
- OAuth and rotate flows now run entirely inside the TUI. Until 0.7.31 we suspended the TUI and ran
- 1f694b40 feat in-TUI OAuth / rotate modal
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|rotateas a subprocess. The child wrote the new value toauth.tomland invalidated its own in-process token cache (0.7.22), but the parent TUI's cache was untouched — so every subsequentpipeline list/pr list/issue listkept handing the now-revoked token to the platform and getting 401 back. Added a publicauth::drop_token_cache()that the TUI calls on return from the subprocess; the nextresolve_tokenre-readsauth.tomland picks up the fresh value.
- 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
- 553485cd fix drop token cache after subprocess auth ops
0.7.30
Added
- Auth view operations from the TUI. The view used to be read-only (mirror of
auth doctor/auth list); nowoover 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 viaauth::set_token. Enter saves, Esc cancels. - Remove token — confirmation overlay;
ydeletes viaauth::remove_token(and clears the matching[token_expires]entry, same as the CLI). - Hint bar arm for
View::Authwith state-aware keys (list / dropdown / input / confirm), matching how Platform's hints work.
Internal
- New
AuthFocus+AuthPendingOponAuthState, withdropdown_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'sops_for/filters_for).- New
run_torii_subprocesshelper intui/mod.rs— suspends raw mode + alt screen, runscurrent_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::AuthOauthInPagerandAction::AuthRotateInPager { pat }. Dispatched in the main loop because they replace the TUI while running.
- Auth view operations from the TUI. The view used to be read-only (mirror of
- 6eae9c9d feat auth view operations via ops dropdown
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-imagewhen the executor is docker, plus--descriptionand--tag-listflags forwarded from the matching torii flags. - GitHub:
<runner-dir>/config.sh --unattended --url … --token … --replace, with--nameand--labelsforwarded. The resolved command is printed before execution so the user can audit it;-y/--yesskips 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, minimalconcurrent = 1shell) when the file is absent, sogitlab-runner registerhas somewhere to land its[[runners]]block. GitHub variant is informational — points at the platform'sactions/runners/newpage since the runner tarball carries its own./config.sh.
Internal
- New
crate::runner::RegistrationToken(token + register URL + optional TTL hint). RunnerClienttrait grewregistration_token(owner, repo). GitLab reads the project'srunners_tokenfield (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.
- 754e21f7 feat register + init
0.7.28
Fixed
- Platform detail panel: long values no longer read as concatenated with the next field. Each
line_kvrow was a singleLine; 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 longdescription). Replaced with akv()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 throughkv()now (keyurl) so over-long URLs wrap with indent instead of running into the next entity.
- Platform detail panel: long values no longer read as concatenated with the next field. Each
- 95cd386b fix Platform detail panel — wrap long values with indent
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_hintrow 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
logandbranch) instead ofC_CYAN. Stage / type / OS / runner type areC_DIM(secondary information) instead ofC_YELLOW(which competes with warnings).runningstatus keepsC_YELLOW(genuine attention) butpendingdrops toC_DIMso the warn semantics aren't diluted. Detail panel title usesbc(matchinglog'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-widestatus_msgline. 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 byApp::status_msg(set withApp::set_status) and the event log. Less local state, fewer places "what just happened?" can diverge. - Dropped the in-view
render_footerfunction. Hints flowed into theView::Platformarm ofrender_hintintui/ui.rsso the layout is uniform.
- 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
- 77a976b2 fix Platform palette + footer alignment
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.
oopens 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.fopens a filter dropdown (status: any/running/failed/ success/pending + branch-only toggle) with the active filter marked. Replaces the cycle keysand the toggle keyb.- 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 newcol()helper truncates with…so every column boundary holds regardless of input length. Applied to pipelines, jobs, releases, packages, and runners.
Internal
- New
PlatformFocus::OpsDropdown/FilterDropdownvariants, withPlatformState::dropdown_idxfor selection state. tui/views/platform.rs::ops_forandfilters_forare nowpubso the events handler can size the dropdown without duplicating the model.
- 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 (
- 865a6df3 feat sidebar reorder + Platform rework
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'sconfig.toml.runner pause <id>/runner resume <id>(GitLab) — temporarily stop / re-enable job pickup.- GitHub Actions supports
list/show/removeonly; 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_SECRETis set (confidential app). Other platforms print a "revoke manually at …" hint. --pat(GitLab only) — uses the nativePOST /personal_access_tokens/self/rotateendpoint, which generates a new PAT with the same scopes and invalidates the old one atomically (no browser round-trip).--ttlflag onauth set/auth oauth/auth rotate— record an expiration timestamp alongside the token (30d,2h,7d12h, …). Stored under a new[token_expires]section inauth.toml. Purely advisory: torii doesn't auto-rotate, butauth doctornow prints⛔ expired,⚠ expires in 3d, or⏳ expires in 28dnext 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::runnermodule withRunnerClienttrait + GitLab and GitHub implementations, factored the same way asPipelineClient. AuthStoregainedexpirations: BTreeMap<String, String>(ISO-8601 per provider). Parser and serializer extended; legacyauth.tomlfiles keep loading unchanged.set_token_with_expiry,token_expires_atpublic helpers incrate::auth. The oldset_tokenis now a thin shim.
- 90d13ed3 feat runners + auth rotate + TTL
0.7.24
Fixed
- GitLab API clients now send
Authorization: Bearer <token>instead ofPRIVATE-TOKEN: <token>. The old header only works with personal access tokens; OAuth access tokens fromtorii auth oauth gitlabwere 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 acrossplatforms/{pipeline,issue,pr,release,package}.rsandworkspace/remote.rs.
Added
- TUI Platform view — contextual actions.
ccancels the selected pipeline (in Pipelines) or job (in Jobs).xretries the selected pipeline or job.adownloads 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 withoutCtrl-R. A small in-flight guard prevents key-mashing from firing the same action three times.- TUI Platform view — auto-refresh polling. Press
pwhile on Pipelines/Jobs/Releases/Packages to toggle live mode; the list re-fetches every 10s while inListfocus. The header shows⟳ livewhile it's on. Selection is preserved across reloads. - TUI Platform view — live tail of the job log. Drilling into a job that's still
runningorpendingautomatically 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),Endto re-engage auto-follow,pto toggle live, andoto open the log in$PAGER(suspends the TUI cleanly). - TUI Platform view — status + branch filters.
scycles the status filter (none → running → failed → success → pending) and pushes it down to the platform API.btoggles "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.
- GitLab API clients now send
- 8a41ac8f fix+feat GitLab Bearer + Platform actions/polling/tail/filter
0.7.23
Fixed
torii snapshot stashno longer reports success when libgit2 didn't actually save anything. Afterstash_save2returns, we now verify the working tree is clean and surface a clear error (withtorii snapshot create -n WIPas 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_git2now propagates the libgit2 error so the caller knows tags didn't reach the remote. .git/info/excludewrite errors no longer swallowed.sync_toriignorenow returns the I/O error instead of pretending exclusions were synced (which could lead to private files getting staged on the next-a).torii rmandtorii mvnow 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 checkindex.get_pathfirst and surface any real error.torii submodule deinitwarns explicitly when the working-tree directory can't be removed. Index and.gitmodulesare already updated at that point; the leftover dir is now flagged with a manual cleanup instruction instead of being silently ignored.append_known_hostpropagatescreate_dir_allerrors. Previously an unwritable~/.ssh/parent would silently swallow the directory creation and the subsequent host-key write would fail without context.torii worktree moveno longer corrupts gitdir metadata when canonicalize fails post-rename. Falls back to the raw new path so the.git/worktrees/<name>/gitdiradmin file still gets patched to point at the new location.- Snapshot creation is now atomic against parallel
torii save. Switched fromexists() + create_dir_all(TOCTOU) to acreate_dirretry loop, so two simultaneous saves can't both decide the same directory is free and overwrite each other's bundle. torii mirror addno longer leavesmirrors.jsonreferencing 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 cleanno 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_historyaligned 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 tousize::MAXfor heights below 3, spinning forever pushing empty lines.saturating_subcaps it at zero. - TUI workspace-name picker no longer panics on multi-byte characters. Slicing the input around
ws_cursornow snaps to the nearestis_char_boundary, so cursoring through ñ, é, or emoji works instead of crashing the TUI.
- cfc02189 fix 15 silent failures, races, and panic risks
0.7.22
Fixed
- HTTP requests no longer hang forever.
util::http::make_clientnow setstimeout=60sandconnect_timeout=10s. Every platform client (7 of them, ~30 endpoints) inherits this — previously a hung API would freeze torii until Ctrl-C.cloud/andtransport/httpsalready 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 viastd::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 intui/mod.rs+tui/app.rsmigrated through a newtui::torii_exe()helper. self.repo.path().parent().unwrap()pattern (6 sites invcs/core.rs+vcs/core_extensions.rs) now returns a clearInvalidConfigerror instead of panicking on the bare-repo edge case.
Performance
auth::resolve_tokennow caches per(provider, repo_path)in-process. CLI flows that resolve N tokens repeatedly (e.g.torii workspace statusacross M repos) previously re-read the global / local TOML on every platform-client constructor. Now one read per (provider, path) pertoriiinvocation. Invalidated automatically byset_token/remove_token.
Added
util::http::send_text(req, ctx)andsend_bytes(req, ctx)— the same shape assend_jsonfor endpoints that return plain text (job logs / build traces) or raw bytes (artifact zips). Replaces 5 inline.send() + status check + body readblocks inpipeline.rs(~50 lines collapsed).ToriiError::Network,PlatformApi,Authvariants — intended to replace the catch-allInvalidConfigover time. Adopted in new code from this point on; migration of the ~430 existingInvalidConfigsites is tracked under "Validation and polish" in ROADMAP.
Internal
- Code audit identified four further areas of tech debt deferred to future releases:
- The
PlatformClienttrait (workspace/remote.rs) has 7 methods but most platforms implement onlyset_visibility— the rest return"not yet wired". Worth splitting intoVisibilityClient(minimum) +RepoManagementClient(full surface) so the trait stops lying about capability. - ~150
.unwrap()calls outside tests, mostlyMutex::lockpatterns that are safe by construction; case-by-case audit needed. - The ~430
InvalidConfigsites that should migrate to the new typed variants. - TUI background loaders (
platform_*_rxchannels) 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.tomlwill make per-host overrides trivial without per-client refactoring.
- HTTP requests no longer hang forever.
- f4b089fa fix HTTP timeout + TUI current_exe + token cache + parent() unwraps
0.7.22-rc.2
- 08d2d0c0 ci route ALL three builds to self-hosted runner
0.7.22-rc.1
- 40c237dd ci route build-linux-x86_64 to self-hosted runner `void-torii`
0.7.21
Added
- OAuth
torii auth oauthworks end-to-end on 4 platforms: GitHub, GitLab, Codeberg (device flow, RFC 8628) and Bitbucket Cloud (authorization-code grant with PKCE + localhost loopback). Every bundledclient_idis 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:8888and serves a one-shot HTML "you can close this tab" page after the redirect. - Validates the
statequery param to prevent CSRF. - Sends
client_secretas Basic auth whenTORII_BITBUCKET_APP_SECRETis 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
.envalready 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 URLhttps://gitorii.comneeds to be updated to the loopback for auth-code with localhost to work.
- OAuth
- 589f245d feat OAuth end-to-end for 4 platforms
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 asgh 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--localfor the per-repo store).- New
src/util/oauth.rsimplements the device flow with proper handling ofauthorization_pending/slow_down/expired_token/access_deniedpoll 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 runtorii auth set …").
Notes
- Bitbucket Cloud doesn't implement RFC 8628 — only the Authorization Code grant, which needs a
localhost:PORTcallback server. Tracked for the next release;torii auth set bitbucket USERNAME:APP_PASSWORDremains 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.
- f3ae16b2 feat OAuth device flow for github/gitlab/codeberg
0.7.19
Added
torii remote visibilitynow 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}withprivate: true|false). The same client serves all three —Codebergis a hardcodedhttps://codeberg.org,ForgejoandGiteahonourFORGEJO_URL/GITEA_URLenv vars for self-hosted instances. - Bitbucket Cloud (
PUT /2.0/repositories/{ws}/{repo}withis_private). Auth heuristic same as 0.7.17::in the token → Basic withuser: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.mdbrought 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.0platforms.toml, plus Azure Artifacts and Bitbucket Data Center as platform follow-ups.torii remote --helplists the 9 platforms supported by the factory plus a visibility availability matrix.
- 75038d6e feat `torii remote visibility` works across all platforms
0.7.18
Added
- Azure DevOps support (7th platform). Detection:
dev.azure.com,ssh.dev.azure.com, and the legacy*.visualstudio.comURLs all auto-route through the new Azure client. - URL parsing unpacks Azure's three-level path (
org/project/repo) and packsorg/projectinto theownerslot of torii's standard(platform, owner, repo)triple — theAzureClientsplits it back viasplit_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:merge→noFastForward,squash→squash,rebase→rebase. - Work Items (≈ issues):
list / create / close / commentvia the WIQL (Work Item Query Language) query endpoint plus the JSON-Patch update API. Defaults to theIssuework-item type (Basic process); Agile / Scrum projects can extend later. Work items are *project-scoped*, not repo-scoped — the_repoarg is ignored. - Pipelines (Builds API):
list / cancel / retry / delete + list_jobs / job_log / artifacts download.cancelPATCHesstatus: cancelling;retryPOSTs a new build with the samedefinition.id.list_jobsreads the build's timeline and surfaces theJobrecords. - Releases (classic Release Management on
vsrm.dev.azure.com):list / get / delete.editreturns 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.
- Azure DevOps support (7th platform). Detection:
- 5e80ed74 feat Azure DevOps support — 7th platform
0.7.17
Added
- Bitbucket Cloud support (6th platform). Detection:
bitbucket.orgURLs auto-route. Auth heuristic: tokens containing:are treated asusername:app_password(Basic header, base64-encoded); anything else is sent as aBearertoken. 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: toriimerge→ Bitbucketmerge_commit,squash→squash,rebase→fast_forward(Bitbucket's closest analog). - Issues:
list / create / close / commentvia the deprecated- but-functional Bitbucket Cloud issues API. Repos without issues enabled return 404 with a clear hint. - Pipelines:
list / cancel / list_jobs / job_logwork 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.tomlconfig).
- Bitbucket Cloud support (6th platform). Detection:
- c3d6d621 feat 6th platform (Bitbucket Cloud)
- bbd06717 refactor organise modules into platforms/vcs/cmd/workspace/util/
0.7.16
Added
- Radicle support (5th platform, peer-to-peer). Detection: any remote URL starting with
rad://orrad@routes through the Radicle client. RID is parsed into theownerslot;repois left empty because Radicle projects are flat (no owner/repo split). - New
src/radicle.rsmodule wraps the localradbinary viarun_rad(args)andrun_rad_json(args). Same shape assrc/gpg.rs: subprocess + clear error if the binary is missing (link to https://radicle.xyz). - Issues:
rad issue list / openwork.closeandcommentreturn clear errors because Radicle identifies issues by hash, not byu64, and torii'sIssueClienttrait still takesnumber: u64. A future revision of the trait (string-id variant) will close that gap. - Patches (Radicle's PR equivalent):
rad patch list / openwork.get / merge / close / update / delete_branchreturn clear errors pointing atrad 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.
- Radicle support (5th platform, peer-to-peer). Detection: any remote URL starting with
- a9647ff2 feat 5th platform via `rad` subprocess
0.7.15
Added
- Sourcehut platform support (4th platform after GitHub / GitLab / Gitea). Detection:
git.sr.htURLs auto-route through the Sourcehut client. Auth:torii auth set sourcehut <oauth-token>(env fallbackSOURCEHUT_TOKEN/SRHT_TOKEN). What's wired: - Issues (
todo.sr.ht):list / create / close / commentvia the REST tracker API. Assumestracker_name == repo_name— if your project uses split trackers (e.g.~user/repo-bugs,~user/repo-features), the--remoteflag oftorii issuepoints at the correct one. - Pipelines (
builds.sr.ht):list / cancel+logwork. Other surface is honest about the limits: retry/job_retryreturn an error pointing at the web UI — builds.sr.ht doesn't expose a "resubmit finished job" endpoint over REST.deletereturns an error — builds keep retention-policy-managed on the server, not user-deletable.job_artifacts_downloadandjob_erasereturn 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 attorii 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 saveand the TUI commit view. Routedcommit_inner/commit_inner_splitthrough: cherry-pick(and--continue)revertmerge(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 ofcommit_innerfor callers that preserve the original author when rewriting the committer.
- Sourcehut platform support (4th platform after GitHub / GitLab / Gitea). Detection:
- dbff1707 feat 4th platform + extend GPG sign coverage
0.7.14
Fixed
user.name/user.emailfrom--localconfig are now actually used at commit time. Previouslyresolve_signatureonly read~/.config/torii/config.toml(the global file), sotorii config set user.email "X" --localwould write.torii/config.tomlsuccessfully buttorii savewould 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 whenresolve_signaturewas 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 = truewas accepted by the config layer since 0.6.x but never honoured at commit time — every commit went out asrepo.commit(...)with no signature, leavinggpgsigabsent from the object even though the flag was on. The fix: - New
src/gpg.rsshells out to the systemgpgbinary (--detach-sign --armor -u <key>), reusing the user's existing keyring + agent + pinentry — same UX asgit commit -S. - New
commit_innerhelper incore.rsroutes bothtorii saveand 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 — extendingcommit_innerthere is tracked for 0.7.15. - Requires
gpg(orgpg2) onPATH. Clear error if missing.
Added
user.signingkeyandcommit.gpgsignconfig aliases. They map to the existinggit.gpg_keyandgit.sign_commitsrespectively. Either spelling works — pick the one you already use ingit config. Reason: the previousgit.gpg_keyname was a torii-ism nobody guessed when migrating from git.
Internal
- HTTP boilerplate centralised in a new
src/http.rsmodule with four helpers (make_client,send_json,send_empty,extract_array). The 15 platform clients (pr×issue×pipeline×release×packageover 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.
- 38182f7d fix honour --local config and actually sign with GPG
- aff402dd refactor centralize HTTP boilerplate + drop obsolete docs
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 fourtorii pipeline / job / package / releaseCLI commands and the corresponding TUI Platform view recognisecodeberg.orgremotes 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>(orgitea/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 / deleteandtorii job list / log / retry.job cancel/job artifacts download/job eraseare 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.
- Gitea / Codeberg / Forgejo support — first new platform since 0.1.x. All four platform-side surfaces (
- b015290e feat Gitea / Codeberg / Forgejo client
- 709a89b0 docs drop gitorii-experimental from AUR install section
0.7.12
Added
- TUI: unified Platform view. New
platformentry in the sidebar (betweenauthandconfig) groups the four platform-side surfaces (pipeline/job/release/package) into one drill-down view with horizontal sub-tabs: 1Pipelines,2Jobs,3Releases,4Packages- 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).
ropens a centred popup to switch the remote (auto-discovered from the repo);Enterselects,Esccancels.Ctrl-Rreloads the active sub-tab.- All loads happen on background threads — the TUI stays responsive while pipelines / jobs / releases / packages are fetched.
Fixed
- TUI:
add mirrorwas 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
platformrow + short Platform-view description. - README: cross-linked the CI / platform management CLI section to the new TUI Platform view.
- TUI: unified Platform view. New
- ddb8386f feat unified Platform view + mirror-add fix + AUR install
- bf00652a docs --remote flag fully documented in README + after_help of pipeline/job/package/release
0.7.11
Added
--remote NAMEon 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 theoriginremote 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
- 0cdd933a feat --remote NAME flag on all platform-management commands
0.7.10
Added — Platform Management Surface
torii job {list, log, retry, cancel, artifacts, erase}— individual CI job control. Sibling totorii pipeline(which manages whole pipelines / workflow runs);torii jobdrills 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 Nonly 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 ofcurl /jobs/<id>/trace | tail -20repetition.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-jobsat the run level); the GitHub backend returns a hint pointing attorii pipeline retry <run-id>.torii job cancel <id>— GitLab only, same asymmetry asretry.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 existingPipelineClienttrait rather than adding a parallelJobClienttrait, since jobs are conceptually under pipelines and share the same auth + base URL. Asymmetric capabilities are handled byErrreturns 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 genericis 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 astorii pipeline delete.- Implementation in
src/package.rs(~280 lines, follows thepipeline.rsshape). GitLab-only on purpose: GitHub's binary-distribution model is Release Assets attached to Releases, which istorii 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.--notesaccepts 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 — usetorii 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
- 0c075cd6 feat torii {job,package,release} — full CRUD over GitLab/GitHub platform side
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-toriiwith taggitorii, shell executor, Arch host) was abandoned after rustc 1.94.0 reproducibly SIGSEGV'd on this host even in trivial crates likelibc,idna,fiat-crypto,num-traits— independent ofRUST_MIN_STACK(tested up to 512 MB),CARGO_BUILD_JOBS=1, orLimitSTACK=infinityvia systemd. The crash also reproduced in a standalonecargo buildoutside 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: removeddefault: tags: [gitorii]so jobs go tosaas-linux-small-amd64shared runners. The russh / rsa-rc / sec1 generic-tree stack pressure is covered by theRUST_MIN_STACK="33554432"already in the YAML'svariables: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.
- CI moved back to GitLab.com shared runners. A ~4h self-hosted runner experiment (registered as
- 4a84ce2c ci fix release-creation 403 — use the variable name that exists
- 8887373b fix cfg-gate AgentClient + split rebase fns by platform
- dd262938 build pin russh to =0.60.2 + restore Cargo.lock to v0.7.8 known-good state
- c3d16d43 ci install nasm for the windows-x86_64 cross-compile
- 2a5a6e32 ci restore image+apt+rustup directives for shared (Docker executor)
- a0f85a64 ci abandon self-hosted runner, back to gitlab.com shared
- b2ca24a0 ci bump RUST_MIN_STACK from 16 MB to 32 MB (rustc's own recommendation)
- dcce7e39 ci source ~/.cargo/env in before_script so shell-executor jobs find rustup
- eccb199b fix remove `cache:` block — runner has no cache backend configured
0.7.8
Fixed
torii sync --pushwas 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 --pushafter a newtorii tag create) created stale "canceled" pipelines forv0.7.0,v0.7.1,v0.7.2,v0.7.3etc. 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. aftertorii history reauthor --since ...) still get pushed over the remote's ref.- Annotated tags' peeled
refs/tags/<name>^{}entries that libgit2 sometimes surfaces inlist()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).
- fde3257d fix skip already-synced tags so sync --push stops retriggering CI for every historical tag
- 31ed8d2e ci adapt for self-hosted shell-executor runner with tag `gitorii`
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 intorii initor in the snapshot-writing path added.torii/to.gitignore, so the directory looked like ordinary untracked project files. The very nexttorii save -am ...then staged everything new — including the full.gitclone the snapshot contains. In the wild onsyrakon/tramuntanathis produced a single commit carrying 10,269 unrelated objects (~681 MB); the push died withfailed to finish zlib inflation: stream aborted prematurely. Git's ownpush --dry-runreported "fast-forward, 1 commit", hiding the payload (dry-run enumerates refs, not packfile contents) — making the cause non-obvious during debugging. Full report indocs/BUG_SNAPSHOT_LEAKS_INTO_COMMITS.md. - Auto-migration for pre-0.7.7 snapshots. When
SnapshotManager::newfinds 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 everySnapshotManagerinstantiation 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 theoriginremote URL. Common shapes:
Audit — non-changes
- Fix #4 from the snapshot bug report (warn on suspicious commit size —
> 50 MBor> 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.
- Safety snapshots no longer leak into the next commit. Severity: high. History-rewriting commands (
- f5199f34 fix+feat snapshots out of working tree + torii pipeline CLI
- e56f2461 fix parametrize Changelog URL via $CI_PROJECT_URL
- e297db2a fix set RUST_MIN_STACK so rustc doesn't SIGSEGV on the russh crypto tree
0.7.6
Added
torii sync --fetchaccepts a remote name and--all. The fork workflow —originfor our work, a separateupstreamfor read-only mirror sync — had no path through torii:sync --fetchalways hit the tracking remote, and there was no way to point it elsewhere without dropping togit fetch upstreamand bypassing the gitorii-skill invariant ("every VCS op goes through torii"). 0.7.6 closes this gap by overloading the existing positional argument onsync: when--fetchis 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()iteratesRepository::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/configand the same auth callbacks + progress display the existing single-remotefetch()used. --allis mutually exclusive with the positional remote (clap-enforced viaconflicts_with = "branch").
Audit — non-changes
- The companion request "add
torii remote add/remove" was audited and dropped:torii remote link/unlinkalready 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.
- 3278f135 feat torii sync --fetch [remote] [--all] for fork workflow
0.7.5
Fixed
- PR / Issue views failed with the generic
Invalid configuration: Unexpected GitLab responsefor every non-200 reply. Bothpr::listandissue::list(GitHub + GitLab variants) parsed the response body straight intoserde_json::Value::as_array()without inspecting the HTTP status, so a401 Unauthorized,404 Not Found, or403(the actual common failure modes) was indistinguishable from a malformed body. Now each list call: 1. Capturesresp.status()before consuming the body. 2. Parses the body asserde_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 misconfiguredgitlab.host/github.ownerare obvious from the error alone. Authconfig keys lived in two places at once. Since 0.7.2 theAuthview became the source of truth for tokens (~/.config/torii/auth.toml), butload_config()insrc/tui/app.rsstill listedauth.cloud_key,auth.github_token,auth.gitlab_token,auth.codeberg_token,auth.bitbucket_tokeninALL_KEYS(and inSENSITIVE). Result: theauthsection appeared in the Config view with[set]/[not set]placeholders that read from the oldconfig.toml [auth]table — stale, and editing them did nothing because the resolver routes throughauth.tomlnow. The five keys were removed fromALL_KEYSandSENSITIVE; theAuthview is now the only place credentials are managed.worktree.base_dirandworktree.inherit_pathswere 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_hintforView::Configis now mode-aware: - Navigating:
[↑↓/jk] navigate [Enter] edit [Tab] toggle scope - Editing:
[Enter] save [Esc] cancel
- PR / Issue views failed with the generic
- 075fa6c1 fix better GitLab/GitHub error msgs + Auth out of Config + hint bar consolidation
0.7.4
Fixed
- TUI sidebar couldn't reach the four new views added in 0.7.2 (Worktree, Submodule, Bisect, Auth).
App::sidebar_downhard-codedif self.sidebar_idx < 13— the pre-0.7.2 max — so navigating with↓/jcapped 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 inview_for_idxwas 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_idxupdated to the post-0.7.2 16-entry order, kept in sync withTABSinsrc/tui/ui.rsand thesidebar_idxassignments inApp::go_to. A new constantApp::SIDEBAR_LEN = 16is used as the bound forsidebar_down.App::go_backmapping rewritten with the same order; the deprecatedView::History/Mirror/Settingsvariants map to their fused destinations (4, 9, 15 respectively).torii publishprinted a literal$(name)after a successful upload (View at https://crates.io/crates/$(name)) — the placeholder was never substituted. Now reads[package].namefromCargo.tomland prints the correct URL.
Changed (CI)
.github/workflows/release.ymlpublish-crates job rebuilt around an OIDC-primary + PAT-secret fallback chain. The previous version layeredrust-lang/crates-io-auth-action@v1(which injectsCARGO_REGISTRY_TOKENvia 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 publishalways ran with no token and failed. Now: 1. The OIDC action runs withcontinue-on-error: true. If trusted publishing is set up on crates.io forPaskiDev/gitorii+ workflowrelease.yml, it injects the token andcargo publishuses it. 2. If OIDC didn't fire, the publish step's env readssecrets.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".
- TUI sidebar couldn't reach the four new views added in 0.7.2 (Worktree, Submodule, Bisect, Auth).
- fd2eb44d fix sidebar bounds + view_for_idx + publish URL + CI auth chain
- 9ed5eda1 chore clean up the 21 warnings dragged by recent refactors
0.7.3
Fixed
torii saveno longer falls back to"Torii User" <user@torii.local>when the user's identity is configured viatorii configbut not in git's own config chain. The previousGitRepo::get_signatureread only fromrepo.config()(libgit2's.git/config→~/.gitconfig→/etc/gitconfig) and substituted a hardcoded placeholder on miss. Tracked indocs/BUG_COMMIT_AUTHOR_FALLBACK.md(now annotated as FIXED). The fix introduces a singlecrate::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 existinggit config user.namesetups keep working). 3. Hard error with a fix-it hint pointing attorii 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 bysave/save --revertpaths),core_tag::cherry_pickand 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 genericSignature cannot have an empty name or emailerror. 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 "…", thentorii 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--committerif needed).
- 9fafa088 fix resolve signature from torii config, not just .git/config
0.7.2
Added
Worktreeview — lists every linked working copy with branch, clean/dirty count, and lock status. Sidebar keyk. Refreshes on entry via libgit2'srepo.worktrees()directly (no CLI shell-out).Submoduleview — every registered submodule with HEAD oid, working-tree oid, URL, and state string (clean/modified/staged/untracked/not initialised). Sidebar keym.Bisectview — surfaces the state of an activegit bisectsession (current commit, good/bad refs from.git/BISECT_LOG). When no session is active, points to the CLI commands to start one. Sidebar keyv.Authview — masked list of every credential torii knows about (cloud key + per-platform tokens) with the source of each (env: $VAR/local/global/(not set)). Mirrorstorii auth doctorfrom the CLI. Sidebar keya.
Changed — sidebar reorganisation
LogabsorbsHistory. 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.RemoteabsorbsMirror. The previous dispatcher already redirectedView::Mirror → views::remote::render, so the separation was artificial. Mirrors become a panel/tab inside Remote in 0.7.3. Sidebar entry "mirror" removed.ConfigabsorbsSettings. 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::Settingsvariants still match in the dispatcher andgo_toso 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, …).
Loggetting--trackedtoggle + notes overlay + patch-export modal.Snapshotview ops dropdown (apply/pop/drop/clear/show).Tagview force-push toggle.Remoteview: mirrors panel + subtree subpanel.Configview: TUI/Repo tabs absorbing the old Settings view.
- 32acbe44 feat 4 new views + 3 fusions to catch up with CLI surface
0.7.1
Fixed
torii config set auth.X_token … --localhad no effect at runtime. The local store was never read by transport/PR/issue/remote code — they all calledToriiConfig::load_global(), which ignores<repo>/.torii/config.toml. Tokens set with--localgot persisted but never resolved when pushing or hitting platform APIs. Fixed by centralising every token read throughcrate::auth::resolve_token, which checks env > local > global in that order.torii config set … --localwas duplicating the global config into the local file.ToriiConfig::load_local()merges global+local and returns the result; the dispatcher then calledsave_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 separateauth.tomland the newload_local_rawreturns local-only without merging.- Mixed precedence between commands.
prandissuehonoured env vars (GITHUB_TOKEN,GITLAB_TOKEN), but the HTTPS transport that actually does the pushing did not. Socargo install-style CI setups would create PRs fine but fail to push. Single resolver, same precedence everywhere.
Added
torii authbecomes 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-showfor the raw value.torii auth listshows every provider with masked value and source (env / local / global / not set).torii auth remove <provider>deletes from global;--localfor the per-repo store.torii auth doctorprints 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 inconfig.toml.torii publish— thin wrapper overcargo publishthat injectsauth.cargoautomatically. No more.envjuggling. 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 timeauthis consulted, it reads the legacy[auth]block, rewrites it into the new file, andtorii auth doctorreminds you the legacy section can be deleted fromconfig.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 totorii auth set <provider> …and prints a one-line hint. Scheduled removal: 0.8.- All error messages and
-hexamples updated to point attorii auth setinstead oftorii config set auth.X_token.
Storage format
Out of scope
cargo-distinstaller setup — still deferred to 0.7.2.- GPG re-sign in
torii history reauthor— same.
- 59561cab feat unify credentials under torii auth; fix three real bugs
0.7.0
Added (top-level)
torii bisect— binary-search the commit that introduced a regression. Subcommandsstart / bad / good / skip / reset / log / run <cmd>. State-machine wrapper overgit 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 overgit archiveto inherit decades of format edge-cases.torii remove(aliasrm) — remove tracked files from index and working tree. Flags--cached / -r / --force.torii rename(aliasmv) — rename/move tracked files; both filesystem and index updated atomically.--forceto overwrite.torii grep— search tracked content for a pattern. Wrapper overgit grep(faster than ripgrep on tracked-only content; different concern fromtorii scan).torii notes— annotations on commits stored inrefs/notes/commits. Subcommandslist / add / append / show / edit / copy / remove.torii patch— export commit ranges as.patchfiles (export) and apply them as new commits (apply). Wrappers overgit format-patch/git amwith--3way / --continue / --abort / --skipplumbed through.torii clean— remove untracked files (≡git clean). Defaults to dry-run for safety.-f / -d / -x / -Xflags.
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 togit. Refspec gets the standard+oldref:newrefprefix on the wire.torii submodule update --recursiveandtorii submodule add --recursive— descend into nested submodules soupdatemirrorsgit submodule update --init --recursivewhen both flags are passed.torii worktree lock/unlock/move/repair— fills in the rest ofgit worktreeparity.movepatches both the.gitlink inside the worktree and the.git/worktrees/<name>/gitdiradmin file by hand because libgit2 has noworktree_move.torii snapshot apply / pop / drop / clear / show— completes thegit stashfamily on top of the existingstash/unstash.applyandpopare aliases ofunstash --keep/unstashfor users coming from git.cleardeletes all snapshots (asks unless--yes);showprints metadata + bundle contents.torii status --tracked(-zfor NUL-separated) —git ls-filesequivalent. Walks the index and prints every tracked file.torii remote refs <target>(--heads / --tags) —git ls-remoteequivalent. Hits the network using configured auth.
Renamed (with aliases — old names still work, prints deprecation in some cases)
torii history clean→torii history compact(aliasgc). "GC" is jargon, "compact" reads.clean(history) → deprecated alias with warning; will be removed in 0.8. Frees up the wordcleanfor the new top-level untracked-cleanup command.torii history fsck→torii history orphans(aliasfsck). "fsck" is hostile Unix-filesystem jargon; "orphans" describes exactly what the command finds.torii rm→torii remove(aliasrm). Plain English first,rmkept for muscle memory.torii mv→torii rename(aliasmv). "rename" is more accurate than "move" 95 % of the time and friendlier;mvkept for muscle memory.
Deprecated
torii blame <file>→ usetorii 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→ usetorii history compact(or aliasgc). Old form prints a warning.
Notes
torii notesandtorii bisectintentionally 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-distinstaller setup — the README still mentions agitorii-installer.shthat no CI generates. Tracked.- GPG re-sign during
torii history reauthor/mailmap apply— needs libgit2commit_signedcallback wiring + a real key in tests. Documented limitation since 0.6.7. rust-toolchain.tomlstays 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 tuioccupies that space),range-diff(rare; comparing commit series),restoreat file-level (parcial viasave --reset; explicit form pending if asked),shortlog(parcial vialog --author).
- f717eca8 feat 0.7.0 — porcelain feature-complete + human-first naming polish
0.6.9
Added
torii submodule— seven-subcommand MVP for embedding another git repo at a pinned commit inside this one. Mirrorsgit submodulewith 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 justtorii 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.gitmodulesURLs into.git/configsoupdateknows where to fetch from. Idempotent.torii submodule update [--init]fetches and checks out the commit each submodule is pinned at.--initrunsinitfirst for uninitialised entries (mirrorsgit submodule update --init).torii submodule syncre-copies.gitmodulesURLs into.git/config(useful after an upstream URL change).torii submodule foreach <cmd>runs<cmd>via$SHELL -cin each submodule's working directory, exportingTORII_SUBMODULE_NAMEandTORII_SUBMODULE_PATH. Stops at the first non-zero exit (matchesgit submodule foreachdefault).torii submodule remove <path>scrubs all four places submodule state lives:.gitmodulessection,.git/configsection,.git/modules/<name>/cached gitdir, and the super-repo's index (via libgit2 directly —git rm --cachedrefuses when.gitmodulesalready has staged changes; libgit2's index API doesn't care).torii subtree— five-subcommand thin wrapper aroundgit subtreefor merging another project's history into a subdirectory of this repo.add/pull/push/split/merge, all forwarding to the upstream contrib script.--squashexposed on the operations that support it.- Why a wrapper, not a reimplementation:
git subtreeis ~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 whengit-subtreeis missing. - Worktree polish — four follow-ups to 0.6.8:
torii worktreewith no subcommand defaults tolist(git/cargo/npm convention).torii worktree listnow shows ahead/behind vs upstream when the worktree's branch tracks one. Readsdirty · 2 ahead, 1 behindstyle; 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 liketarget/ornode_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 removefailed silently with "Not a directory (os error 20)" because the module assumed.gitwas 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 aRESOLVED-GITDIRmarker, 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-subtreebeing on PATH. On Arch/Fedora it ships withgit; on Debian/Ubuntu it's a separategit-subtreepackage. Torii surfaces a precise error message if it's missing. - Index manipulation in
submodule removeis now done via libgit2 directly (Index::remove_path/remove_dir+Index::write), not by shelling out togit rm --cached. The shell-out path stayed brittle in practice because git refuses to operate on the index when.gitmoduleshas uncommitted edits, which is precisely the state we're in mid-remove.
- 0b9b6765 feat three feature blocks + polish
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 thatgit worktreecovers — 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 fromworktree.base_dir(new config key, default..) +<repo>-<branch-sanitized>/.-bcreates a branch off HEAD; positional names an existing local branch.torii worktree listprints 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 fromgit 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.gitis a directory and a worktree's.gitis a link file — graceful warning, removal proceeds. Snapshot module fix tracked for a later release.torii worktree pruneclears metadata for worktrees whose directories were deleted out-of-band (e.g. viarm -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 worktreehas no equivalent.- New config key
worktree.base_dir(default..) controls wheretorii worktree addputs new worktrees when no path is provided. Honors~expansion. Set withtorii config set worktree.base_dir ~/worktreesto centralise them.
Notes
- Lock / unlock / move / repair are intentionally not in 0.6.8; the design review picked an MVP plus
openas the first cut. Filling them in is a straight-line addition on top ofWorktree::lock/unlockfrom 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).
- 82df1533 feat MVP — add / list / remove / prune / open
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--oldformat:"Name <email>"for full match, a bare email for email-only match, or a bare name for name-only match. The replacement--newmust 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 withreauthor(--since,--dry-run,--no-snapshot,--committer,--allow-dirty).- Shared behaviour for both commands:
- Safety snapshot (
pre-reauthor-<timestamp>) taken automatically; revert withtorii 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 rewritefor 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.mdadds an "Identity rewrite details" subsection undertorii historycovering snapshot behaviour, timestamp preservation, GPG-signature invalidation, mailmap format, and the--forcepush needed after rewriting shared branches.README.mdHistory 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>). Thegitorii-installer.shwrapper 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 --lockedis 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 --lockedfor the ICE, plusRUST_MIN_STACK=16777216 ... -j 2for the codegen path. Adds a third "skip the compiler entirely" path with the GitLab binary URL.
Build / toolchain
.github/workflows/release.ymlpinsdtolnay/rust-toolchain@1.94.0for thecargo publishjob (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=16777216andCARGO_BUILD_JOBS=2to mirror the README workarounds for the codegen-pressure path. Revert to@stableonce 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 --amendon each commit) — automatic re-signing during rewrite is not yet wired. gitorii-installer.shdoesn'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.
- 0d921e3d chore pin CI toolchain to 1.94.0 + document install workarounds
- 4ba4dee7 feat reauthor + mailmap apply — rewrite author identity across history