Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ const treeOid = await cas.createTree({ manifest });

## 7. The CLI

<img src="./docs/cli.gif" alt="git-cas CLI demo" />

`git-cas` installs as a Git subcommand. After installation, `git cas` is
available in any Git repository.

Expand Down Expand Up @@ -963,6 +965,8 @@ console.log(manifest.chunks.length); // Full chunk list, regardless of structur

## 13. Vault

<img src="./docs/vault.gif" alt="git-cas vault demo" />

When you call `createTree({ manifest })`, the resulting tree is a loose Git
object. If nothing references it -- no commit, no tag, no ref -- `git gc`
will garbage-collect it. This can silently lose stored data.
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ We use the object database.

**Use it for:** binary assets, build artifacts, model weights, data packs, secret bundles, weird experiments, etc.

<img src="./docs/demo.gif" alt="git-cas demo" />

## What's new in v2.0.0

**Compression** — `compression: { algorithm: 'gzip' }` on `store()`. Compression runs before encryption. Decompression on `restore()` is automatic.
Expand Down Expand Up @@ -122,10 +124,11 @@ git cas vault info my-image
git cas vault remove my-image
git cas vault history

# Encrypted vault round-trip
git cas vault init --vault-passphrase "secret"
git cas store ./secret.bin --slug vault-entry --tree --vault-passphrase "secret"
git cas restore --slug vault-entry --out ./decrypted.bin --vault-passphrase "secret"
# Encrypted vault round-trip (passphrase via env var or --vault-passphrase flag)
export GIT_CAS_PASSPHRASE="secret"
git cas vault init
git cas store ./secret.bin --slug vault-entry --tree
git cas restore --slug vault-entry --out ./decrypted.bin
```

## Why not Git LFS?
Expand Down
27 changes: 27 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -1673,3 +1673,30 @@ git-cas occupies a specific niche: **Git-native encrypted content-addressed stor
What it is: the only tool that lets you `git cas store ./model.bin --slug v3-weights --tree --vault-passphrase "secret"`, commit the tree OID, push to any Git remote, and restore it on any machine with `git cas restore --slug v3-weights --out ./model.bin --vault-passphrase "secret"` — no server, no external storage, no second system. Everything is Git objects, Git refs, Git transport.

If that's what you want, nothing else does it. If it's not, the right tool probably isn't git-cas.

---

## Backlog (unscheduled)

Ideas for future milestones. Not committed, not prioritized — just captured.

### Named Vaults
Multiple vaults instead of one. Refs move from `refs/cas/vault` to `refs/cas/vaults/<name>`. Default vault is `default`. CLI gets `--vault <name>` flag.

### Export
- **Export vault to archive** — `git cas vault export --format tar.gz` dumps all entries to a tarball/zip.
- **Export individual entry** — `git cas export --slug photos/vacation --format tar.gz` restores and archives a single entry.
- **Bulk export** — restore multiple slugs into a single archive.

### Vault Management
- **Move into vault** — `git cas vault add --slug <slug> --oid <tree-oid>` to adopt an existing CAS tree into the vault (the API `addToVault()` already supports this; just needs a CLI command).
- **Purge from CAS** — remove an entry from the vault and run `git gc` to reclaim storage. Tricky because git doesn't delete individual objects — you remove refs and let GC handle it.

### Publish / Mount
- **Publish to working tree** — `git cas publish --slug assets/hero --to docs/hero.gif` reconstitutes a vault entry into the repo's working tree so it's servable by GitHub (markdown images, Pages, etc.).
- **Publish to branch** — `git cas publish --branch gh-assets` materializes all vault entries onto a dedicated branch. Keeps the main branch clean while making assets accessible via GitHub raw URLs.
- **Auto-publish hook** — pre-commit or CI step that keeps published assets in sync with vault state.

### Repo Intelligence
- **Duplicate detection on store** — warn if a file being stored already exists as a tracked git blob (same content hash). "This file is already tracked by git — are you sure you want to store it in CAS too?"
- **Repo scan / dedup advisor** — `git cas scan` walks the git object database and recommends files that could benefit from CAS (large blobs, binary files, duplicated content across branches). Reports dedup opportunities and potential storage savings.
27 changes: 18 additions & 9 deletions bin/git-cas.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,28 @@ async function deriveVaultKey(cas, metadata, passphrase) {
}

/**
* Resolve encryption key from --key-file or --vault-passphrase.
* Resolve passphrase from --vault-passphrase flag or GIT_CAS_PASSPHRASE env var.
*/
function resolvePassphrase(opts) {
return opts.vaultPassphrase ?? process.env.GIT_CAS_PASSPHRASE;
}

/**
* Resolve encryption key from --key-file or --vault-passphrase / GIT_CAS_PASSPHRASE.
*/
async function resolveEncryptionKey(cas, opts) {
if (opts.keyFile) {
return readKeyFile(opts.keyFile);
}
if (!opts.vaultPassphrase) {
const passphrase = resolvePassphrase(opts);
if (!passphrase) {
return undefined;
}
const metadata = await cas.getVaultMetadata();
if (metadata?.encryption) {
return deriveVaultKey(cas, metadata, opts.vaultPassphrase);
return deriveVaultKey(cas, metadata, passphrase);
}
process.stderr.write('warning: --vault-passphrase ignored (vault is not encrypted)\n');
process.stderr.write('warning: passphrase ignored (vault is not encrypted)\n');
return undefined;
}

Expand Down Expand Up @@ -100,7 +108,7 @@ program
.option('--key-file <path>', 'Path to 32-byte raw encryption key file')
.option('--tree', 'Also create a Git tree and print its OID')
.option('--force', 'Overwrite existing vault entry')
.option('--vault-passphrase <pass>', 'Vault-level passphrase for encryption')
.option('--vault-passphrase <pass>', 'Vault-level passphrase for encryption (prefer GIT_CAS_PASSPHRASE env var)')
.option('--cwd <dir>', 'Git working directory', '.')
.action(async (file, opts) => {
try {
Expand Down Expand Up @@ -157,7 +165,7 @@ program
.option('--slug <slug>', 'Resolve tree OID from vault slug')
.option('--oid <tree-oid>', 'Direct tree OID')
.option('--key-file <path>', 'Path to 32-byte raw encryption key file')
.option('--vault-passphrase <pass>', 'Vault-level passphrase for decryption')
.option('--vault-passphrase <pass>', 'Vault-level passphrase for decryption (prefer GIT_CAS_PASSPHRASE env var)')
.option('--cwd <dir>', 'Git working directory', '.')
.action(async (opts) => {
try {
Expand Down Expand Up @@ -194,15 +202,16 @@ const vault = program
vault
.command('init')
.description('Initialize the vault')
.option('--vault-passphrase <pass>', 'Passphrase for vault-level encryption')
.option('--vault-passphrase <pass>', 'Passphrase for vault-level encryption (prefer GIT_CAS_PASSPHRASE env var)')
.option('--algorithm <alg>', 'KDF algorithm (pbkdf2 or scrypt)', 'pbkdf2')
.option('--cwd <dir>', 'Git working directory', '.')
.action(async (opts) => {
try {
const cas = createCas(opts.cwd);
const initOpts = {};
if (opts.vaultPassphrase) {
initOpts.passphrase = opts.vaultPassphrase;
const passphrase = resolvePassphrase(opts);
if (passphrase) {
initOpts.passphrase = passphrase;
initOpts.kdfOptions = { algorithm: opts.algorithm };
}
const { commitOid } = await cas.initVault(initOpts);
Expand Down
Binary file added docs/cli.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/vault.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.