Skip to main content

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):

  1. TWSHTD_CONFIG environment variable
  2. ~/.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 ($VAR or ${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)

Source distribution for twshtd 1.2.0
File Size Uploaded Provenance
twshtd-1.2.0.tar.gz 15.3 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for twshtd 1.2.0
File Interpreter ABI Platform
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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page