- Rust 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| .github/workflows | ||
| src | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| README.md | ||
| vgit.example.toml | ||
vgit
A small Rust CLI that makes every commit anonymous, GPG-signed, and mirror-pushed to multiple git hosts simultaneously — from one encrypted, out-of-repo config file.
vgit only ever acts on repos you explicitly register. It never touches anything else.
Why
Maintaining a consistent pseudonymous identity across GitHub, Forgejo, Gitea, and GitLab requires remembering to:
- Override
user.name/user.emailper commit - Sign every commit with the right GPG key
- Push to N remotes without leaking tokens in the URL or global config
vgit makes this automatic and enforced — and stores all secrets encrypted outside every repo.
Requirements
- Rust toolchain (to build)
gitonPATHgpgonPATH(for signed commits)git-filter-repoonPATH(needed forvgit rewrite)
Install
cargo install --path .
Commands
| Command | Description |
|---|---|
vgit init |
Create and encrypt a fresh config template under the user config directory |
vgit register <slug> [--hosts h1,h2] |
Register the current directory as a tracked repo, bound to named hosts |
vgit commit -m <msg> [-a] |
Commit staged changes using the forced pseudonymous identity; GPG-signs if key is set |
vgit push |
Push the current branch to every configured mirror for this repo |
vgit sync -m <msg> [-a] |
Commit then push in one step |
vgit status |
Show the active identity, configured hosts, and whether the current dir is registered |
vgit doctor |
Safety self-check: verify the pseudonymous identity is not leaking into global git config |
vgit rewrite [--apply] |
Scan and display the identity rewrite map; apply history rewrite with --apply (dry-run by default) |
vgit release <tag> [--notes <text>] [--draft] [--prerelease] |
Create an annotated (or GPG-signed) tag, push it to all mirrors, and publish a release on each host (GitHub and Forgejo/Gitea only — GitLab push/mirroring works but release/issue/pr API is not yet supported) |
vgit issue create -t <title> [-b <body>] [--host <name>] |
Open a new issue on the target host |
vgit issue close <number> [-m <comment>] [--host <name>] |
Optionally comment, then close an issue |
vgit issue reopen <number> [--host <name>] |
Reopen a closed issue |
vgit issue comment <number> -m <text> [--host <name>] |
Post a comment on an issue |
vgit pr create -t <title> --from <branch> --to <base> [-b <body>] [--host <name>] |
Open a pull/merge request |
vgit pr merge <number> [--method merge|squash|rebase] [--host <name>] |
Merge a pull request |
vgit pr close <number> [--host <name>] |
Close a pull request without merging |
vgit unlock |
Prompt for the passphrase, verify it decrypts the config, and cache it in the OS keyring |
vgit lock |
Remove the cached passphrase from the OS keyring |
Forge API host scope:
release,issue *, andpr *work on GitHub (github_app/github_token) and Forgejo/Gitea hosts. GitLab git push and mirroring work normally; GitLab Forge API (release/issue/pr) is not yet supported and returns a clear error message.
Config
The config is TOML, encrypted at rest with age (scrypt/passphrase), and stored under the OS user-config directory — never inside any repo.
Typical location:
| OS | Path |
|---|---|
| Linux | ~/.config/vaiexia/vgit.age |
| macOS | ~/Library/Application Support/space.vai-rice.vaiexia/vgit.age |
| Windows | %APPDATA%\vai-rice\vaiexia\config\vgit.age |
Decrypt / re-encrypt happens in memory on every vgit invocation; the plaintext never touches disk.
The passphrase is resolved in order: VGIT_PASSPHRASE env var → OS keyring (cached via vgit unlock) → interactive prompt.
Full config example
[identity]
name = "VAIEXIA Team"
email = "vaiexia@vai-rice.space"
gpg_key_id = "<your-gpg-key-id>"
secret_salt = "<random-uuid>"
# Optional: real author emails checked during doctor leak scan
team_emails = []
# ── Hosts ────────────────────────────────────────────────────────────────────
[[hosts]]
name = "github"
type = "github_app"
owner = "<github-org-or-user>"
app_id = "<app-id>"
installation_id = "<installation-id>"
private_key_pem = "<rsa-private-key-pem>"
[[hosts]]
name = "github-token"
type = "github_token"
owner = "<github-org-or-user>"
token = "<personal-access-token>"
[[hosts]]
name = "codeberg"
type = "forgejo"
base_url = "https://codeberg.org"
owner = "<forgejo-user>"
token = "<forgejo-token>"
[[hosts]]
name = "gitea-self"
type = "gitea"
base_url = "https://gitea.example.com"
owner = "<gitea-user>"
token = "<gitea-token>"
[[hosts]]
name = "gitlab"
type = "gitlab"
base_url = "https://gitlab.com"
owner = "<gitlab-user-or-group>"
token = "<gitlab-token>"
# ── Repos ────────────────────────────────────────────────────────────────────
[[repos]]
path = "/absolute/path/to/your/repo"
slug = "repo-name"
hosts = ["github", "codeberg"]
Host types: github_app, github_token, forgejo, gitea, gitlab
Push authentication is handled via ephemeral HTTPS credential helpers — tokens are never stored in the repo's git config or remote URLs.
Safety model
- Per-invocation identity override —
GIT_AUTHOR_NAME,GIT_AUTHOR_EMAIL, andGIT_COMMITTER_*are set as environment variables for each git call. The global git config is never touched. - Registered-repos-only — every command that writes or pushes first verifies the current directory against the registered repo list. Unregistered repos are silently rejected.
- Encrypted secrets outside repos — the
.ageconfig file lives in the OS user-config directory, never inside a working tree.vgit doctorreports a warning if the file is ever found inside a git tree. - Leak check —
vgit doctorreads the global git config and fails loudly if the pseudonymous identity is found there, preventing it from bleeding into unrelated personal commits. - Agent/secret-file commit guard — before committing,
vgitscans staged paths against a blocklist (.agefiles, common secret file patterns). Matching files cause an immediate abort.
License
Licensed under either of
- MIT License (LICENSE-MIT)
- Apache License, Version 2.0 (LICENSE-APACHE)
at your option.