twshtd
Batch-sync multiple git repositories with a single command.
The problem: You have many repos (notes, dotfiles, configs, projects) scattered across your
machine. Keeping them all committed and pushed is tedious. twshtd push commits and pushes all of
them at once. twshtd dirty shows which ones need attention.
twshtd allows pre- and post-commit hooks to execute arbitrary actions.
Installation
# Install with uv
uv tool install twshtd
# Or install from source
uv tool install -e .
Usage
twshtd [OPTIONS] COMMAND [ARGS]
Commands
| Command | Description |
|---|---|
push |
Commit and push all configured repositories |
pull |
Commit local changes and pull from remotes |
info |
Show configuration and repository status |
edit |
Open configuration file in $EDITOR |
dirty |
Show dirty/behind/ahead status for repos in directories |
Options
| Option | Description |
|---|---|
-v, --verbose |
Enable debug logging |
-V, --version |
Show version |
Push Command
twshtd push [OPTIONS]
Commits all changes and pushes to remote for each configured repository.
| Option | Description |
|---|---|
-c, --config |
Config file path |
-w, --workers |
Parallel workers (number or 'auto') |
-n, --dry-run |
Show what would be done |
Pull Command
twshtd pull [OPTIONS]
Commits local changes and pulls from remotes. Supports two modes per repository:
pull: Full git pull (default)fetch: Only fetch, showing what changed
| Option | Description |
|---|---|
-c, --config |
Config file path |
-w, --workers |
Parallel workers (number or 'auto') |
-n, --dry-run |
Show what would be done |
Dirty Command
twshtd dirty [OPTIONS]
Scans configured directories for git repositories and shows their status.
| Option | Description |
|---|---|
-c, --config |
Config file path |
--no-fetch |
Skip git fetch before checking status |
Shows the "Untracked" number of files per repo.
Configuration
Config file location (in order of precedence):
TWSHTD_CONFIGenvironment variable~/.config/twshtd/repos.toml
Example Configuration
[settings]
workers = 1 # parallel workers (1 = sequential)
# Global hooks - run before/after ALL repos
pre_commands = [
"! pgrep -x anki", # Block push if Anki is running
]
post_commands = []
[[repos]]
path = "~/dev/project1"
pull_mode = "pull" # "pull" or "fetch"
pre_commands = ["make clean"] # run before commit on push/pull (all must pass)
post_commands = ["make build"] # run after pull (warn on failure)
enabled = true
[[repos]]
path = "$PROJECTS/project2"
pull_mode = "fetch"
pre_commands = ["git stash list"] # multiple commands supported
post_commands = ["make test", "make lint"]
[[repos]]
path = "~/dev/disabled-repo"
enabled = false
# Directories to scan with 'dirty' command
[[dirty]]
path = "~/dev"
enabled = true
[[dirty]]
path = "$WORK/repos"
Path Resolution
Paths support:
- Home directory expansion (
~) - Environment variables (
$VARor${VAR})
Features
Parallel Execution
Process multiple repositories simultaneously using the --workers option:
twshtd push --workers 4 # Use 4 parallel workers
twshtd push --workers auto # Use CPU count as worker count
Or set a default in your config:
[settings]
workers = 4 # 1 = sequential (default)
Hooks (Pre-Commands / Post-Commands)
Hooks allow you to run shell commands at specific points in the workflow.
Global Hooks
Define in [settings] to run for ALL repos:
[settings]
pre_commands = ["! pgrep -x anki"] # Block if Anki running
post_commands = ["echo 'All done'"]
- pre_commands: Run once before processing any repos. ALL must succeed (exit 0) or the entire operation stops.
- post_commands: Run once after all repos are processed. Failures show warnings but don't fail the operation.
Per-Repo Hooks
Define per repository for repo-specific actions:
[[repos]]
path = "~/dev/myproject"
pre_commands = ["make clean", "npm ci"] # before commit (push and pull)
post_commands = ["make build", "make test"] # after pull
- pre_commands: Run before the commit, on push and on pull/fetch (both commit local changes). ALL must succeed or the repo is skipped (other repos continue).
- post_commands: Run after pull. Failures show warnings but the repo is marked as successful.
Commands run in the repository directory (per-repo) or current directory (global).
Execution Order
PUSH:
1. Global pre_commands (all must pass, or exit 1)
2. For each repo:
a. Repo pre_commands (all must pass, or skip this repo)
b. Git commit + push
3. Global post_commands (warn on failure)
PULL:
1. Global pre_commands (all must pass, or exit 1)
2. For each repo:
a. Repo pre_commands (all must pass, or skip this repo)
b. Git commit + pull/fetch
c. Repo post_commands (warn on failure)
3. Global post_commands (warn on failure)
Commit Message
When committing changes, twshtd uses git status --porcelain output as the commit message,
providing a clear record of what changed.
Example: Blocking Push if Anki Running
To prevent pushing when Anki is running (e.g., to avoid sync conflicts with Anki repos), use a global pre_command:
[settings]
pre_commands = ["! pgrep -x anki"]
This command exits 1 (failure) if Anki is running, blocking the push.
rplc Integration
For repositories using rplc, twshtd automatically swaps out
files before committing. Use --skip-rplc to bypass.
Development
# Install dev dependencies
uv sync
# Run tests
make test
# Lint and format
make static-analysis
License
MIT
Metadata
Release files for twshtd 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| twshtd-1.2.0.tar.gz | 15.3 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| twshtd-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.1 kB
Release files / twshtd-1.2.0.tar.gz
| Download URL | twshtd-1.2.0.tar.gz |
|---|---|
| Size | 15.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
08c3b0346c794e5d5a2c17dad36e22578a529cad63626aa22a30b5ae8b9484b3
|
|
BLAKE2b-256 checksum How to use checksums |
363ea5953afc74ea400ae613f52e8c25b97133afd65be886e7f9a733ac47aeee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.11
|
Release files / twshtd-1.2.0-py3-none-any.whl
| Download URL | twshtd-1.2.0-py3-none-any.whl |
|---|---|
| Size | 13.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b165c88c73d067e8c8a2e677bd7b1bfccaaf1b1355c1ce8a085a19846f5bcd41
|
|
BLAKE2b-256 checksum How to use checksums |
f13c677cf17cd71f34fa0110649eedc24817e85cbff0f7caff019e5d89ea417b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.11
|
Security for twshtd 1.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.