Retroactively introduce git-crypt to existing repos by rewriting history.
git-recrypt replays every commit of an existing git repo into a fresh target repo where git-crypt's clean filter encrypts matched files from the very first commit. The source repo is never touched.
- Python 3.12+
gitgit-crypt(must be installed and inPATH)gpg(GPG mode only -- secret keys must be available in your keyring)diff(standard Unix tool, used for verification)
Install as a tool with uv:
uv tool install .
Or run directly without installing:
uv run git-recrypt
- Run
detectto see what files would be encrypted:
git-recrypt detect --repo /path/to/your/repo
- Run
initto generate a manifest interactively:
git-recrypt init --repo /path/to/your/repo --output git-recrypt.yaml
- Review and edit the manifest, then rewrite history:
git-recrypt run --manifest git-recrypt.yaml
The manifest is a YAML file (default name: git-recrypt.yaml) that describes how the rewrite should happen.
| Field | Required | Default | Description |
|---|---|---|---|
version |
yes | -- | Must be 1 |
key |
yes | -- | Exactly one of symmetric or gpg (see below) |
patterns |
yes | -- | List of gitattributes-style glob patterns for files to encrypt. At least one entry required. |
repo |
no | "./" |
Path to source repository. Relative paths are resolved against the manifest's parent directory. |
introduce_at |
no | "root" |
Where to introduce encryption. Currently only "root" (from first commit) is supported. "first-match" and SHA-based introduction points are rejected at validation time with a "not yet implemented" error. |
branches |
no | ["HEAD"] |
Branches to rewrite. HEAD resolves to the source repo's current branch via git symbolic-ref. Fails fast if unresolvable. |
exclude |
no | [] |
Patterns to exclude from encryption. |
detection_profile |
no | -- | Hint for the detect command. Values: "generic", "etckeeper", "kubernetes". |
Symmetric key:
| Field | Description |
|---|---|
key.symmetric.key_file |
Path to an existing key file, or "generate" to create one automatically. |
key.symmetric.export_to |
Where to write the generated key. Default: "./git-crypt.key". |
GPG key:
| Field | Description |
|---|---|
key.gpg.user_ids |
List of GPG user IDs (email addresses) to add as recipients. |
key.gpg.generate |
Not yet implemented. Auto-generate a GPG key. Planned sub-fields: name, email, algorithm (default: ed25519), expire (default: "0"), passphrase ("provided" or "random"). |
version: 1
key:
gpg:
user_ids:
- alice@example.com
- bob@example.com
patterns:
- '**/*.key'
- '**/*.key.pem'
- '**/*.private'
- '**/*privkey*'
- '**/.htpasswd'
- '**/secret.*'
- gshadow
- gshadow-
- shadow
- shadow-
- ssh/ssh_host_*_key
introduce_at: root
branches:
- HEAD
exclude: []
detection_profile: etckeeperversion: 1
key:
symmetric:
key_file: generate
export_to: ./git-crypt.key
patterns:
- 'secrets/**'
- '**/*.env'
- '**/*.pem'
introduce_at: root
branches:
- HEADScan a repo and suggest encryption patterns based on filenames and content.
git-recrypt detect [--repo PATH] [--profile PROFILE]
| Option | Default | Description |
|---|---|---|
--repo |
. |
Path to the repository to scan. |
--profile |
-- | Detection profile hint. Values: generic, etckeeper, kubernetes. |
Interactive wizard that produces a manifest file.
git-recrypt init [--repo PATH] [--output FILE]
| Option | Default | Description |
|---|---|---|
--repo |
. |
Path to the source repository. |
--output |
git-recrypt.yaml |
Where to write the generated manifest. |
Show which files would be encrypted without modifying anything.
git-recrypt dry-run [--manifest FILE] [--repo PATH]
| Option | Default | Description |
|---|---|---|
--manifest |
git-recrypt.yaml |
Path to the manifest file. |
--repo |
-- | Override the repo path from the manifest. |
Execute the history rewrite. This is the main command.
git-recrypt run [--manifest FILE] [--repo PATH] [--force] [--work-dir DIR] [--skip-verify]
| Option | Default | Description |
|---|---|---|
--manifest |
git-recrypt.yaml |
Path to the manifest file. |
--repo |
-- | Override the repo path from the manifest. |
--force |
false | Skip the confirmation prompt. |
--work-dir |
-- | Custom directory for the output (rewritten) repo. |
--skip-verify |
false | Skip post-rewrite verification. |
Verify that a rewritten repo matches the original content.
git-recrypt verify ORIGINAL REWRITTEN KEY_FILE [--manifest FILE] [--mode MODE]
| Argument / Option | Default | Description |
|---|---|---|
original |
-- | Path to the original (source) repo. |
rewritten |
-- | Path to the rewritten repo. |
key_file |
-- | Path to the git-crypt key file (required). For symmetric mode, use the exported key. For GPG mode, the value is unused -- pass any placeholder (e.g. /dev/null). |
--manifest |
git-recrypt.yaml |
Path to the manifest file. |
--mode |
fast |
Verification depth. fast or full. |
The rewrite happens entirely in a fresh target repo. The source repo is never modified.
- Create an empty target repo with
git init -b <branch>. - Run
git-crypt initand configure the key. For GPG mode,add-gpg-useris called for each user ID. For symmetric mode, the key file is copied in. - Commit
.gitattributeswith the encryption patterns. - Run
git-crypt unlockso the working tree is in decrypted state. - Pre-flight check: test a lock/unlock roundtrip to catch configuration issues before touching any history.
- Copy remotes from the source repo into the target repo.
- Replay each commit:
- Generate a patch with
git format-patchfrom the source repo. - Temporarily hide
.gitattributesin the target repo. - Apply the patch with
git apply. - Restore
.gitattributes. - Stage everything with
git add -A. The git-crypt clean filter encrypts matched files at this point. - Commit with the original author, date, and message.
- Generate a patch with
Rewrite state (commit map, config, setup commits) is persisted to ~/.cache/git-recrypt/<repo-hash>/ for post-rewrite verification and debugging. Resume from interrupted rewrites is a planned feature, not yet implemented.
After run completes, verification runs automatically in three phases (unless --skip-verify is passed).
Phase 1 -- git-crypt status. Checks that git-crypt status reports the expected files as encrypted in the rewritten repo.
Phase 2 -- lock/unlock roundtrip. Locks and unlocks the rewritten repo at the tip commit and at one commit sampled from the middle of history. Confirms that encrypted content survives a full cycle.
Phase 3 -- checkout and disk compare. Checks out roughly 10% of commits (sampled), then compares the full file tree against the corresponding commit in the source repo. Any content mismatch is reported as a failure.
You can also run verification independently with the verify command.
Set GIT_RECRYPT_DEBUG=1 before running any command to enable verbose debug output. Patches are dumped to:
~/.cache/git-recrypt/<repo-hash>/debug/
When the tool encounters an error, it prints a hint to re-run with this variable set.
GIT_RECRYPT_DEBUG=1 git-recrypt run --manifest git-recrypt.yaml
- Only branches listed in the manifest are rewritten. Tags are not rewritten.
- The rewrite produces a new repo with a different object graph. You'll need to force-push or replace the remote.
- GPG mode requires that all listed user IDs have public keys available in your local keyring before running.
The following features are not yet implemented but are on the roadmap:
introduce_at: first-matchand SHA-based introduction points. Currently onlyrootis fully supported.- Multi-branch rewriting. The
brancheslist accepts multiple entries in the manifest schema, but rewriting more than one branch is not yet implemented. - Octopus merge support. Merges with more than 2 parents are not handled.
- Named encryption keys (
named_patterns). Per-pattern key selection is not yet supported. - Resume interrupted rewrites. State is persisted after a successful run, but resuming a partially completed rewrite is not yet implemented.
- Per-GPG-identity verification. The verify command currently checks content correctness but does not validate per-recipient decryptability.