Command Reference
Global Flags
These flags are accepted by every subcommand and may appear before or after the subcommand name.
| Flag | Description |
|---|---|
-v, --verbose | Print every repo, even when no action is taken (adds the header for skipped repos; git status switches to per-file output) |
--terse | Minimal, machine-readable output: repo names only for data commands, just the N/total line for count commands, failing rule names only for the check commands |
--no-header | Suppress the [repo] header line printed before per-repo output |
--no-output | Suppress command output, keeping only the [repo] headers. Action commands run their subprocesses with output captured and discarded; a failing repo’s output is still attached to its error |
--print-not | Invert selection: print the repos that do NOT match |
--no-stop | On error, print error in <repo>: ... to stderr and continue with the next repo instead of stopping. The run still exits non-zero (N of M repos failed) if any repo failed |
--short-circuit | Stop at the first failing rule. Off by default; honoured by check same and check exists |
-j, --jobs <N> | Run up to N repos in parallel (default 1; 0 means one worker per CPU). Output is still printed in repo order |
--venv | Activate each repo’s local .venv (prepend .venv/bin to PATH, set VIRTUAL_ENV) before running tool subprocesses. On by default; honoured by run, build, cargo and npm; not by uv |
--no-venv | Turn --venv off: run tool subprocesses with the ambient environment |
Example:
rsmultigit --terse git count dirty # Just print "3/50"
rsmultigit --no-stop git pull # Pull all, report failures, keep going
rsmultigit -j 8 git fetch # Fetch eight repos at a time
rsmultigit --short-circuit check same # Stop at the first broken rule
Output conventions
Most commands print a [repo] header followed by that repo’s output, and
print nothing for repos where nothing happened (no data, nothing to do, or
the repo was skipped by a precondition such as a missing Cargo.toml).
--verbose prints the header for those repos too, --no-header drops the
header everywhere, and --terse reduces the output to bare repo paths.
Commands that take an operation word (git count dirty, gh clean-all,
cargo clippy, …) print their help, with every operation and its
description, when the word is left out - rsmultigit gh is the quickest way
to see what gh can do.
Git Commands
rsmultigit git <command>
Everything that inspects or operates on the repositories as git
repositories. rsmultigit git alone lists the subcommands. They come in
two kinds: the reports below (counts and per-repo data, computed with
libgit2) and the operations after them, each of which runs the git command
of the same name in every repo.
Counts
rsmultigit git count <what> tests each repo with libgit2 (no subprocess),
prints the path of every matching repo, then a final matched/total line.
rsmultigit git count dirty
Repos with changes in the working tree or index: modified, deleted, renamed, type-changed, newly staged, or conflicted (mid-merge) files. Untracked files do not count.
rsmultigit git count dirty
rsmultigit --terse git count dirty # Only the "N/total" line
rsmultigit --print-not git count dirty # Clean repos instead
rsmultigit git count untracked
Repos that have untracked files.
rsmultigit git count unsynchronized
Repos that are not synchronized with their upstream: ahead of or behind
it. The upstream is the branch’s configured tracking branch (as set by
clone or push -u); when none is configured, origin/<branch> is used.
A repo with no upstream at all has nothing to be out of sync with and is not
counted, which matches how git status and git push treat it.
rsmultigit git count unsynchronized
rsmultigit --print-not git count unsynchronized # Repos that ARE in sync
rsmultigit git count ahead / rsmultigit git count behind
The two halves of unsynchronized: repos with local commits not yet pushed
(ahead) or upstream commits not yet pulled (behind), against the same
upstream. The same tests as list ahead / list behind.
Reports
These commands compute a value per repo and print it under the [repo]
header. Repos with nothing to report are skipped unless --verbose is set.
With --terse, only the repo path is printed.
rsmultigit git status / rsmultigit status
One-line summary of every repo that needs attention: counts of conflicted,
staged, modified, deleted and untracked files, plus ahead N / behind N
when the branch has diverged from its upstream (see
git count unsynchronized for how the upstream is chosen). Clean, in-sync repos are skipped. Computed
with libgit2. rsmultigit status is a top-level shortcut for the same command.
rsmultigit status # Same as `rsmultigit git status`
# [/home/me/git/myrepo]
# 2 modified, 1 untracked, ahead 1
rsmultigit --verbose git status # Full per-file `git status -s` instead
rsmultigit git dirty
git diff --stat for repos with unstaged modifications; falls back to
git diff --cached --stat when only staged changes exist.
rsmultigit git age
The age of the last commit as a relative date (3 days ago).
rsmultigit git authors
git shortlog -sne HEAD: authors with commit counts and emails.
rsmultigit git size
Size of the .git directory per repo, in human-readable units. Symlinks are
not followed. A linked worktree, whose .git is only a pointer file, has no
git directory of its own and is skipped.
rsmultigit git last-tag
The most recent tag reachable from HEAD (git describe --tags --abbrev=0).
Repos without tags are skipped.
Operations
Action commands print the [repo] header for repos where the action ran;
with --verbose it is printed for skipped repos too.
rsmultigit git config <KEY>
A git config value per repo. Repos where the key is unset are skipped.
rsmultigit git config user.email
rsmultigit git config remote.origin.url
rsmultigit git pull [--quiet]
git pull in every repo. --quiet is forwarded to git.
rsmultigit git push
git push in every repo that is ahead of its upstream (the configured
tracking branch, else origin/<branch>). Repos with nothing to push, or
with neither, are skipped. A branch measured against origin/<branch> for
want of a configured upstream is pushed there explicitly
(git push origin HEAD), since plain git push refuses a branch without
an upstream.
rsmultigit git fetch
git fetch in every repo.
rsmultigit git commit -m <MESSAGE>
git add -A then git commit -m <MESSAGE> in every repo that has changes.
Clean repos are skipped.
rsmultigit git checkout <BRANCH>
git checkout <BRANCH> in every repo.
rsmultigit git stash push / rsmultigit git stash pop
push runs git stash push -m "rsmultigit stash" in every repo that has
changes to tracked files; clean repos are skipped. pop restores the most
recent stash carrying that message, wherever it sits in the stash list, and
skips repos that have none. A stash made by hand is never popped, so
push followed by pop round-trips exactly what push saved.
These three throw work away, so each one skips the repos where it would
change nothing (no [repo] header unless --verbose), and each takes
--dry-run, which lists per repo what would go and changes nothing. Run the
dry run first: across a fleet, one command can discard a lot.
rsmultigit git reset hard|mixed [--dry-run]
git reset --<mode> HEAD in every repo that has something to reset.
| What | Runs | Selects repos with | --dry-run shows |
|---|---|---|---|
hard | git reset --hard HEAD | any change to a tracked file, staged or not | git status --short --untracked-files=no |
mixed | git reset --mixed HEAD | staged changes | git diff --cached --name-status |
There is no soft: git reset --soft HEAD changes nothing, and a soft reset
to an older commit is not a fleet-wide operation.
rsmultigit git reset hard --dry-run # What would be discarded, per repo
rsmultigit git reset hard # Discard all changes to tracked files
rsmultigit git reset mixed # Unstage changes
rsmultigit git restore [--dry-run]
git restore . in every repo with unstaged changes to tracked files:
discards them, leaving staged changes and untracked files alone.
--dry-run shows git diff --name-status instead. (Formerly clean git;
it removes nothing untracked, so it was never a clean.)
rsmultigit git clean <what> [--dry-run]
| What | Runs | Notes |
|---|---|---|
hard | git clean -ffxd | Removes untracked and ignored files (.venv/, target/, .env, …) |
soft | git clean -fd | Removes untracked files only |
A repo is selected when git’s own dry run (git clean -nxd / -nd) lists
something, and --dry-run prints exactly that list.
cargo clean lives under the cargo command: rsmultigit cargo clean.
rsmultigit git diff
git diff in every repo.
rsmultigit git log [--count N]
git log --oneline -n N in every repo (default 10).
rsmultigit git log
rsmultigit git log --count 5
rsmultigit git blame <FILE>
git blame <FILE> in every repo that contains the file; others are skipped.
rsmultigit git grep [-l|--files] <REGEXP>
git grep -n -e <REGEXP> in every repo, so a pattern starting with - is
searched for rather than read as an option. Each output line is prefixed with the
repo name; -l prints matching filenames only. Repos with no match print
nothing; --terse lists just the repos that matched and --print-not the
ones that did not.
rsmultigit git grep "TODO"
rsmultigit git grep -l "TODO"
rsmultigit git branch local|remote|github
git branch, git branch -r, or the GitHub default branch via
gh repo view (requires the gh CLI).
rsmultigit git tag local|remote|has-local|has-remote
local and remote list tags (git tag, git ls-remote --tags origin).
has-local and has-remote behave like count commands: they print the
repos that have any tags, then a matched/total line.
rsmultigit git remote
git remote -v in every repo.
rsmultigit git remote-prune
git remote prune origin in every repo: drops remote-tracking branches
whose upstream branch is gone. Not called prune, since git prune is
git’s unrelated object pruning.
rsmultigit git gc
git gc in every repo.
rsmultigit git submodule-update
git submodule update --init --recursive in every repo.
Repo Lists
The list commands print the absolute path of every repo they select, one
per line, with no header and no summary line, so the output feeds straight
into xargs or a shell loop. --print-not inverts the selection of every
filter (list dirty --print-not prints the clean repos); -j inspects repos
in parallel without changing the output order. A bare rsmultigit list
lists the choices.
for repo in $(rsmultigit list dirty); do git -C "$repo" diff --stat; done
rsmultigit list ahead # the repos `git push` would send something from
rsmultigit list dirty --print-not # the clean repos
rsmultigit list repos
Every configured repo. --verbose adds the [repo] header for each entry.
rsmultigit list dirty
Repos with modified, deleted, staged or conflicted files (untracked files do
not count; see list untracked). The same test as git count dirty.
rsmultigit list untracked
Repos with untracked files. The same test as git count untracked.
rsmultigit list unsynchronized
Repos whose checked-out branch is ahead of or behind its upstream. A repo
with no upstream (or a detached HEAD) has nothing to be out of sync with and
is never listed. The same test as git count unsynchronized.
rsmultigit list ahead
Repos with local commits not yet pushed to the upstream.
rsmultigit list behind
Repos whose upstream has commits not yet pulled. This compares against the
remote-tracking refs as they are; run rsmultigit git fetch first for a
current answer.
Running Arbitrary Commands
rsmultigit run <COMMAND...> (alias: rsmultigit exec)
Run an arbitrary command in every repo. A single argument is run through the
shell (sh -c), so pipes, redirects and quoting work; several arguments are
executed directly without a shell.
By default (--venv), a repo that has a local .venv gets it activated
first, so the command and anything it spawns resolve from the repo’s own
venv. Pass --no-venv to run with the ambient environment everywhere.
rsmultigit run touch marker.txt
rsmultigit run "echo hello > greeting.txt"
rsmultigit exec cargo check
rsmultigit run pytest # each repo's own pytest, via its .venv
rsmultigit --no-venv run which python # ambient python everywhere
Consistency Checks
The check subcommands evaluate the [[check-same]], [[check-exists]] and
[[check-submodules]] rules in the config file; rsmultigit check alone lists them. See Configuration for the rule fields. Unlike every
other command, output is organised by rule, not by repo: each rule prints
a header line followed by its verdict. The header is [kind rule-name],
the kind being the stanza the rule came from and the command that runs it
(check-same, check-exists, check-submodules), followed
by the rule’s properties as key=value pairs (path and select always;
exclude, marker, marker_absent when set; enabled=false and
must_have=true only when they differ from the default), so a failure can
be read against its rule without opening the config. --no-header drops it.
rsmultigit check same
For each enabled [[check-same]] rule, hash path in every selected repo and
group the repos by content. A rule passes when there is at most one group
and, for must_have = true rules, no selected repo lacks the file.
[check-same gitignore] path=.gitignore select=*
ok (212 files)
[check-same workflow-build-yml] path=.github/workflows/build.yml select=* marker_absent=.noci must_have=true
34 files, 2 groups (1 missing, 3 skipped)
group A (31 files):
/home/me/git/alpha/.github/workflows/build.yml
...
group B (3 files):
/home/me/git/beta/.github/workflows/build.yml
...
missing in:
/home/me/git/gamma
Groups are ordered largest first, so group A is the presumed canonical
version. missing counts must_have violations; skipped counts repos
that lack the file under a rule without must_have.
Exit code is 1 when any rule fails, 0 otherwise. A rule that matches no
files at all is reported as no files matched and, after all rules have
run, turns into a hard error on stderr naming every such rule, since an
empty rule almost always means a stale select or path.
| Flag | Effect |
|---|---|
--checks <NAME>... | Run only these rules, in the given order. Named rules run even if enabled = false. An unknown name is an error |
--checks-re <REGEX>... | Also run every rule whose name matches a regex (unanchored). A regex matching nothing is an error. Combines with --checks |
--only-failed | Drop the ok (N files) lines; print failing rules only |
--allow-empty | Let a rule that matches no files pass as ok (0 files) instead of failing |
--diff | After reporting a failing rule, print a unified diff between representatives of the differing groups. With exactly two groups this is automatic; with more, it prompts for the pair and offers to diff another |
--copy | After reporting a failing rule, prompt for a “from” group and a “to” group, confirm, then overwrite every file in the “to” group with the “from” representative (preserving each destination’s file mode). Exit code is 0 even when rules still differ; a file that could not be written makes it non-zero |
--fix-missing | For rules with must_have violations, prompt for a group to seed from, confirm, then create the file in each violating repo (creating parent directories as needed). Exit code is 0 even when rules still differ; a file that could not be created makes it non-zero |
The interactive prompts accept a group letter (A, B, …), s to skip
the rule, or q to quit the whole run; confirmations default to no. EOF on
stdin is treated as quit, so the command never hangs when stdin is closed.
--terse prints only the names of failing rules, one per line, and implies
--only-failed. --short-circuit stops after the first failing rule.
rsmultigit check same # Report every rule
rsmultigit --terse --no-stop check same # Names of broken rules only, for scripts
rsmultigit check same --checks gitignore editorconfig
rsmultigit check same --checks-re '^rs-' # Every rule whose name starts with rs-
rsmultigit check same --diff --only-failed # Show what differs
rsmultigit check same --copy # Fix drift interactively
rsmultigit check same --fix-missing # Create files missing from must_have repos
rsmultigit check exists
For each enabled [[check-exists]] rule, assert that every selected repo contains
path. Content is never compared, which makes this the right rule type for
files that must exist but legitimately differ per repo (a README, say).
[check-exists readme-present] path=README.md select=*
ok (212 repos)
[check-exists license-present] path=LICENSE select=*
212 repos, 2 missing LICENSE
missing in:
/home/me/git/alpha
/home/me/git/beta
Exit code is 1 when any rule fails. A rule that selects no repos at all
fails with no repos selected and becomes a hard error at the end, unless
--allow-empty is passed.
--checks, --checks-re, --only-failed, --allow-empty, --terse and
--short-circuit behave exactly as for check same. There are no
interactive modes.
rsmultigit check submodules
For each enabled [[check-submodules]] rule, find the submodule the rule’s url
names in every selected repo and assert that all of them have it checked
out at the same commit. A submodule is the fleet’s way of sharing a repo,
and the pin is the part that drifts when one consumer bumps it and the
others do not. Only submodules with a rule are checked: one without a rule
is free to differ per repo, by decision rather than by accident.
The URL is matched after normalisation on both sides, so the rule may say
github.com/owner/repo while the repos added it over ssh or https, with or
without .git. The commit compared is the one checked out in the
submodule’s working directory, the same view check same takes of ordinary
files: what is on disk and what a build uses. A submodule that is not
checked out falls back to the commit recorded in the superproject’s index,
then in HEAD. A selected repo that does not carry the submodule is skipped,
not failed: the rule constrains the pin, not which repos use the submodule.
[check-submodules shared-themes] url=github.com/veltzer/shared-themes select=*
ok (4 repos @ aa5e688, 208 skipped)
[check-submodules shared-tags] url=github.com/veltzer/shared-tags select=teaching-*
3 repos, 2 commits
1f2e3d4 (2 repos):
/home/me/git/teaching-slides (shared/shared-tags)
/home/me/git/teaching-syllabi (shared/shared-tags)
9a8b7c6 (1 repo):
/home/me/git/teaching-animations (shared/shared-tags)
Groups are ordered largest first. Exit code is 1 when any rule’s submodule
is pinned to more than one commit, or to none. A rule whose submodule no
selected repo carries fails with no repos have this submodule and becomes
a hard error at the end, unless --allow-empty is passed, since finding it
nowhere almost always means a stale select or a misspelled url.
--checks, --checks-re, --only-failed, --allow-empty, --terse and
--short-circuit behave exactly as for check same. There are no
interactive modes.
rsmultigit check all [--only-failed] [--allow-empty]
Run every enabled [[check-same]] rule, then every enabled [[check-exists]] rule,
then every enabled [[check-submodules]] rule. All three always run, so a failure
in one never hides the state of the others. Exit code is non-zero if any of
them fails.
rsmultigit check all --only-failed
rsmultigit check list [same|exists|submodules]
Print the name of every [[check-same]] rule (the default), every
[[check-exists]] rule or every [[check-submodules]] rule, one per line,
including disabled ones. The kind is named after the subcommand that runs it.
The bash and zsh completion scripts call this to complete
check same --checks <TAB>, check exists --checks <TAB> and
check submodules --checks <TAB>.
Build Commands
rsmultigit build [<method>]
Run a build tool in each project directory. Projects with a .disable file
in their root are always skipped.
By default (the global --venv flag), a project that has a local .venv
gets it activated before the build tool runs, so the tools the build spawns
(pytest, mypy, ruff, …) resolve from the repo’s own venv, including the
build tool itself when the venv provides it. Projects without a .venv
build with the ambient environment. Pass --no-venv to disable the
activation everywhere.
| Method | What runs | Which projects |
|---|---|---|
bootstrap | python bootstrap.py | all |
make | make | all |
rsconstruct | rsconstruct --quiet build | those with rsconstruct.toml |
cargo | cargo build --profile dev then cargo build --release | those with Cargo.toml |
Publishing is not a build method: see rsmultigit cargo publish below.
Default build method
The method is optional when the config file sets default_build_method:
# ~/.config/rsmultigit/config.toml
default_build_method = "rsconstruct"
With that in place a bare rsmultigit build means
rsmultigit build rsconstruct. The key accepts exactly the spellings the
command line does (rsconstruct, not Rsconstruct), and a method given
on the command line always wins over the config file. Without the key, a
bare rsmultigit build is an error that says how to fix it.
rsmultigit build rsconstruct
rsmultigit build # same, given default_build_method = "rsconstruct"
rsmultigit build make # explicit method overrides the default
rsmultigit --no-venv build rsconstruct
Cargo Commands
These commands operate only on repositories that have a Cargo.toml file
and no .disable file. Other repositories are skipped.
rsmultigit cargo <what> [--release | --profile <name>] [--check]
Runs one cargo operation in each Rust project. All of them honour --venv.
what | Runs | Notes |
|---|---|---|
build | cargo build --profile dev then cargo build --release | Every profile, like cargo_build.sh; --release or --profile builds just that one |
check | cargo check | Type-check only, no artifacts |
clippy | cargo clippy --all-targets -- -D warnings | Same invocation as the fleet’s ci.yml: warnings fail the run |
fmt | cargo fmt --all | --check only verifies and fails on unformatted code |
test | cargo test | cargo’s built-in runner |
nextest | cargo nextest run | Requires cargo-nextest |
doc | cargo doc --no-deps | API docs for the crate itself |
deny | cargo deny check | Requires cargo-deny |
fetch | cargo fetch | Download dependencies without building |
update | cargo update | Refreshes Cargo.lock |
clean | cargo clean | Removes the target directory |
publish | cargo publish | Uploads the crate to crates.io; release is the full release flow |
release | cargo release <type> --execute --no-confirm | The full release flow, see below; --type picks the bump |
--release appends --release and --profile <name> appends
--profile <name> (dev, release, or any custom profile the crate’s
Cargo.toml defines). Both are accepted by the operations that compile under
a profile: build, check, clippy, test, nextest and doc. With
neither, build compiles every profile the fleet ships, dev then release,
and the other operations keep cargo’s own default. --check is accepted by
fmt only, and --type by release only. A flag on any other operation
is an error, and --release, --profile and --check are mutually
exclusive.
rsmultigit cargo build # Every Rust repo, dev and release
rsmultigit cargo build --profile dev # Only the default (dev) profile
rsmultigit cargo build --release # Only release
rsmultigit cargo fmt --check # Is every Rust repo formatted?
rsmultigit cargo clippy # Lint the fleet the way CI does
rsmultigit -j 0 cargo nextest --release # Release-mode tests, all cores
rsmultigit cargo update # Refresh every Cargo.lock
rsmultigit cargo release [--type <patch|minor|major>]
Release a new version of each Rust project by running
cargo release <type> --execute --no-confirm, which bumps the version in
Cargo.toml, commits, tags, pushes, and publishes to crates.io (the rest of
the policy is the repo’s release.toml). The default release type is
patch.
A repo is released only when its root Cargo.toml declares a [package]
that cargo would publish. This is cargo’s own vocabulary, so no
rsmultigit-specific marker is needed:
- A virtual workspace (a root
[workspace]listing members, with no[package]) has nothing to version or publish and is skipped. This is what a repo of examples and exercises looks like. - A package with
publish = false(orpublish = []) is a scratch crate that was never meant for crates.io; it is skipped here just ascargo publishitself would refuse it.publish.workspace = truedefers to[workspace.package].publish, as in cargo. - A repo with no
Cargo.tomlis not a rust project and is skipped.
Skipped repos are listed under --verbose, like every other skip.
Before the first repo is touched, two things are checked once for the whole run:
- cargo-release must be on
PATH. It is a separate crate, not part of the toolchain, so a rebuiltCARGO_HOMEloses it; the error names the fix (cargo install cargo-release) instead of cargo’s own “no such command: release”. - The crates.io token is fetched. If
CARGO_REGISTRY_TOKENis already set in the environment it is used as is. Otherwise the first line ofpass show <entry>is taken, where<entry>is the config file’scrates_io_pass_entry(defaultkeys/crates.io). The token is handed to everycargo releaseprocess through its environment only, so it never lands in~/.cargo/credentialsor any other file.
rsmultigit cargo release # Patch release (default)
rsmultigit cargo release --type minor # Minor release
rsmultigit cargo release --type major # Major release
uv Commands
Run uv operations across every repository that
has a pyproject.toml at its root; other repositories are skipped.
rsmultigit uv lock [--upgrade|--check]
Run uv lock in each Python project, re-resolving uv.lock from
pyproject.toml. Without --upgrade, versions that are already locked are
kept and only added or removed dependencies change; with --upgrade, locked
versions may move forward to the newest allowed releases.
With --check, nothing is written: uv lock --check only asserts that the
lockfile is up to date with pyproject.toml, and a stale lockfile is an
error (combine with the global --no-stop to survey all projects instead of
stopping at the first stale one). --check and --upgrade are mutually
exclusive.
rsmultigit uv lock # Bring every lockfile in sync with pyproject
rsmultigit uv lock --upgrade # Deliberately upgrade all locked versions
rsmultigit uv lock --check --no-stop # Report which lockfiles are stale
rsmultigit uv sync
Run uv sync in each Python project, syncing its environment from the
lockfile. Projects without a .venv get one created.
uv selects its own target environment from the project directory, so
rsmultigit runs it with VIRTUAL_ENV (and UV_PROJECT_ENVIRONMENT) unset
and leaves the choice to uv. This matters when you invoke rsmultigit from
an activated shell: the inherited VIRTUAL_ENV names your venv, never the
repo being synced, and passing it through makes uv sync warn
(does not match the project environment path, after which the value is
ignored) while the uv pip interface would silently target the wrong
environment. The global --venv/--no-venv flag therefore does not apply
to uv; the other uv operations behave the same way.
rsmultigit uv build
Run uv build in each Python project, producing the sdist and wheel under
dist/.
rsmultigit uv publish
Run uv publish in each Python project, uploading whatever uv build left
in dist/ to the package index. Credentials and the target index come from
uv’s own configuration (UV_PUBLISH_TOKEN, trusted publishing, keyring,
[[tool.uv.index]]); rsmultigit passes nothing through.
rsmultigit uv build # Build every distribution
rsmultigit uv publish # ... then upload them
npm Commands
Run npm operations across every repository
that has a package.json at its root; other repositories are skipped. All
of them honour --venv, so package.json scripts that shell out to tooling
living in the repo’s .venv find it on PATH.
rsmultigit npm <what> [--fix]
what | Runs | Notes |
|---|---|---|
install | npm install | Installs from package.json, writing package-lock.json |
ci | npm ci | Clean install of exactly what package-lock.json says; fails if it disagrees with package.json |
update | npm update | Moves dependencies to the newest versions their declared ranges allow and rewrites package-lock.json |
audit | npm audit | Reports known vulnerabilities; exits non-zero when any are found. --fix runs npm audit fix, which rewrites package.json and package-lock.json |
outdated | npm outdated | Lists dependencies with newer releases; exits non-zero when any are outdated |
test | npm test | Runs the project’s test script |
publish | npm publish | Publishes the package to the registry |
--fix is accepted by audit only; on any other operation it is an error.
audit and outdated report through their exit status, so combine them
with the global --no-stop to survey the whole fleet instead of stopping at
the first repo with findings.
rsmultigit npm update # Refresh every package-lock.json within ranges
rsmultigit npm ci # Reproduce every lockfile exactly
rsmultigit --no-stop npm outdated # Which repos have newer releases available?
rsmultigit npm audit --fix # Apply the automatic vulnerability fixes
GitHub Commands
These commands talk to GitHub through the gh CLI
(which must be installed and authenticated) and operate only on repositories
that have a remote whose URL contains github.com. Other repositories are
skipped.
rsmultigit gh clean-all [--keep <N>]
Clean up GitHub deployments, releases, and workflow runs for each repository.
Keeps only the --keep (default 4) most recent non-failed of each and
deletes the rest; failed deployments (latest status failure/error) and
failed workflow runs (failure, cancelled, timed_out, startup_failure,
action_required) are always deleted, even if recent.
Note that this deletes data on GitHub permanently: releases, deployment history, and workflow run logs are gone once removed.
rsmultigit gh clean-all # Keep the 4 most recent of each
rsmultigit gh clean-all --keep 10 # Keep the 10 most recent of each
rsmultigit gh artifacts
List the assets attached to each repository’s latest GitHub release as a NAME / SIZE / DOWNLOADS table. A data command: repositories without a release, or whose latest release has no assets, print nothing.
rsmultigit gh artifacts # Who is downloading which binaries?
rsmultigit gh last-workflow-state
Print the conclusion of each repository’s most recent workflow run
(success, failure, cancelled, …). A run that has not finished yet
shows its status instead (in_progress, queued). A data command:
repositories with no workflow runs print nothing.
rsmultigit gh last-workflow-state # One line per repo
rsmultigit --terse gh last-workflow-state # Just the repo names
rsmultigit gh open-site
Open each repository’s GitHub Pages site in the browser with xdg-open,
printing the URL first. A repository without a Pages site says
no GitHub Pages site and is otherwise left alone. Every repository with
a site gets a browser tab, so point the config at the repos you mean before
running it across a large fleet.
rsmultigit gh open-site
rsmultigit gh sync-metadata [--dry-run]
Sync each repository’s GitHub metadata from its config/project.lua,
printing only what actually differs. Repositories without that file are
skipped. The file is the source of truth for two fields:
project.lua | GitHub field |
|---|---|
DESCRIPTION_SHORT | description |
KEYWORDS (a lua list) | topics (order-independent) |
A field that is absent or empty locally is left alone on GitHub: “unset locally” is not the same as “clear it”. The repository features follow a fixed fleet policy that is not read from the file and is always enforced: wiki off, issues on, projects off.
The repository is addressed by its github.com remote, not by the file’s
NAME; a NAME that disagrees with the GitHub repository name is reported
but never acted on. Only the topics that differ are sent (--add-topic /
--remove-topic), so a topic present on both sides is never touched.
Only the plain assignment subset of lua that project.lua uses is
understood (X = "...", X = { "...", ... }, X = Y aliases, --
comments); anything else is an error naming the line.
rsmultigit gh sync-metadata --dry-run # Show the differences, change nothing
rsmultigit gh sync-metadata # Show them and update GitHub
Sample output for one repository with every field out of step:
[demos-os-linux]
description
local: demos-os-linux is a project to demo and explore the Linux API
github: Linux demos
topics
local: api c demos linux
github: c demos linux old-topic
features
want: wiki=false issues=true projects=false
github: wiki=true issues=true projects=false
--dry-run is accepted by sync-metadata only, and --keep by
clean-all only; on any other gh operation either is an error rather
than a silently ignored flag.
Utility Commands
These need no config file: setup is how a fresh install bootstraps one.
rsmultigit setup <command>
The two ways to get a first config file. rsmultigit setup interactive alone lists
them.
rsmultigit setup interactive
Write a first ~/.config/rsmultigit/config.toml by answering two questions
in the terminal:
- Which directory holds your git repositories. A text prompt with tab
completion over directories, defaulting to
~/gitwhen it exists. The answer is checked before it is accepted: it must be a directory with at least one git repository directly under it, the same test thereposglob applies later. - Which build tool a bare
rsmultigit buildruns. A menu of the supported methods (bootstrap,make,rsconstruct,cargo) plusnone, each row showing how many of the repos found carry that tool’s file (bootstrap.py,Makefile,rsconstruct.toml,Cargo.toml). The cursor starts on the most common one.
The config is written with repos = ["<dir>/*"] (the home directory
contracted to ~) and, unless none was picked, default_build_method.
An existing file is replaced only after a confirmation.
Each question is skipped when its answer is given as an option, and with every answer given nothing is asked at all, so the command also works in scripts and without a terminal:
rsmultigit setup interactive # fully interactive
rsmultigit setup interactive --repos-dir ~/src # asks only the build tool
rsmultigit setup interactive --repos-dir ~/src --build cargo # asks nothing
rsmultigit setup interactive --repos-dir ~/src --no-build # no default_build_method
rsmultigit setup interactive --repos-dir ~/src --build make --overwrite # replace without asking
| Option | Meaning |
|---|---|
--repos-dir <DIR> | Directory whose direct subdirectories are the repositories |
--build <METHOD> | Set default_build_method; excludes --no-build |
--no-build | Write no default_build_method |
--overwrite | Replace an existing config file without asking |
Without a terminal, a question that still needs asking is an error naming the option that answers it; nothing is written in that case.
rsmultigit setup config-sample
Print a fully commented sample config to stdout.
mkdir -p ~/.config/rsmultigit
rsmultigit setup config-sample > ~/.config/rsmultigit/config.toml
rsmultigit complete <bash|zsh|fish|elvish|powershell>
Print a shell completion script. The bash and zsh scripts additionally
complete check same --checks <TAB> and check exists --checks <TAB> with
the rule names from your config, by calling rsmultigit check list at
completion time.
rsmultigit complete bash >> ~/.bash_completion
rsmultigit complete zsh > ~/.zfunc/_rsmultigit
rsmultigit version
Print detailed version information: crate version, git describe, commit, branch, dirty state, rustc version, edition, and build timestamp.
rsmultigit version
rsmultigit --version # One-line "rsmultigit x.y.z by Author"