Anonymous, GPG-signed, multi-host mirrored Git CLI powering the VAIEXIA workflow.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-17 21:51:39 +00:00
.forgejo/workflows ci: github + forgejo build/test workflows and example config 2026-07-17 01:41:10 +03:00
.github/workflows ci: github + forgejo build/test workflows and example config 2026-07-17 01:41:10 +03:00
src fix: embed credentials in push URL to bypass hijacked git credential helpers; scrub token from errors 2026-07-17 21:31:38 +00:00
tests test: force tagger identity in push_tag test so CI (no ambient git identity) passes 2026-07-17 21:51:39 +00:00
.gitattributes chore: fail release on partial error; enforce LF line endings 2026-07-16 23:59:17 +03:00
.gitignore chore: scaffold vgit crate 2026-07-16 21:56:40 +03:00
Cargo.lock fix: enable jsonwebtoken rust_crypto provider + regression test 2026-07-17 18:52:39 +03:00
Cargo.toml feat: vgit repo create, push-all, sync-all, config edit; stage_all_excluding; doctor tracked-forbidden 2026-07-17 19:20:56 +03:00
LICENSE-APACHE docs: vgit readme and licenses 2026-07-16 22:31:34 +03:00
LICENSE-MIT docs: vgit readme and licenses 2026-07-16 22:31:34 +03:00
README.md feat: cache config passphrase in OS keyring (unlock/lock) 2026-07-17 18:52:52 +03:00
vgit.example.toml docs(config): add commented exclude examples to CONFIG_TEMPLATE and vgit.example.toml 2026-07-17 19:22:13 +03:00

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.email per 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)
  • git on PATH
  • gpg on PATH (for signed commits)
  • git-filter-repo on PATH (needed for vgit 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 *, and pr * 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 overrideGIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and GIT_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 .age config file lives in the OS user-config directory, never inside a working tree. vgit doctor reports a warning if the file is ever found inside a git tree.
  • Leak checkvgit doctor reads 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, vgit scans staged paths against a blocklist (.age files, common secret file patterns). Matching files cause an immediate abort.

License

Licensed under either of

at your option.