Releases: BangRocket/mcagit
Release list
mcagit v20260607-e772a39
mcagit build from commit e772a39c1920b3304ebd3196b78dd0867fa09477.
Dispatched from PR #44.
v0.7.0 — network remotes (HTTP + SSH)
mcadiff is now a distributed VCS — push / fetch / clone over the network, not just the local filesystem. A remote URL can be a path, http(s)://, or ssh://. Because objects are content-addressed, transfer copies only what the other side lacks.
HTTP (built-in daemon)
git's model: anonymous read, authenticated push.
mcadiff -C <repo> serve --port 8421 --allow-push --token s3cret
mcadiff clone http://host:8421 ./world.mcagit # anonymous read
mcadiff push origin main --token s3cret # authenticated pushPush is rejected (401) without the token; --allow-push is required for any write. Uploaded objects are integrity-checked (decompress + SHA must match the name), so a bad peer can't poison the store.
SSH (no daemon)
Runs mcadiff serve-stdio on the remote over your ssh session; auth & encryption are ssh's job (keys/agent). Requires mcadiff installed on the remote.
mcadiff clone ssh://user@host/path/to/world.mcagit ./world.mcagit
mcadiff push ssh://user@host/path/to/world.mcagit mainUnder the hood
A single transport abstraction (IRemoteTransport) means clone/fetch/push are written once; filesystem, HTTP, and ssh are just transports. The filesystem path was refactored onto it with no behavior change.
75 tests (was 72): live HTTP push/clone + token auth against an in-process server, and the ssh stdio protocol (clone + push) over in-memory pipes. Verified end-to-end on the example worlds — HTTP push of 4,389 objects then a 0-object re-push, anonymous clone, 401 without a token, and the cloned-over-HTTP world checks out byte-clean.
Limitations
Per-object transfer (no packfiles/delta yet); the HTTP daemon is simple (single token, no TLS — front it with a reverse proxy for https). No staging index.
v0.6.0 — git-likeness Tier 2 (remotes, reflog, cherry-pick, gc)
Takes mcadiff from a local VCS toward a distributed one — sync world history between repositories (e.g. push backups to another drive/NAS). No network yet; remotes are repository directories, and because objects are content-addressed, transfer copies only what the other side lacks.
New
- Remotes (filesystem):
clone,remote add,fetch,push.pushis fast-forward-checked (--forceoverrides);fetchpopulatesrefs/remotes/<remote>/*(resolvable asorigin/main). reflog— HEAD movement history (logged on commit).cherry-pick <commit>— apply one commit onto HEAD via the 3-way engine.gc— prune objects unreachable from any ref.
Fixes (found via Tier-2 testing)
- Merge dropped a one-sided region: a region present on only one side (including an empty region like a fresh
poi/) was lost by the manifest 3-way merge. Region existence is now resolved per-region (added / deleted / delete-modify conflict). - Nondeterministic classification hardened: a rare transient parse failure during the parallel commit could silently downgrade a valid NBT
.datto an opaque blob, making manifests nondeterministic. Parsing now retries before falling back.
72 tests (was 67): clone+push fast-forward, fetch remote-tracking refs, gc prunes unreachable objects, reflog records commits, merge carries a one-sided/empty region.
Deferred (by design)
A staging index and an in-place conflict-resolution workflow don't map cleanly to whole-world snapshots; network transport (ssh/http) and delta/packfiles remain future work.
v0.5.0 — git-likeness Tier 1
Rounds out the local VCS so it feels git-native, reusing the existing diff/patch/merge engines.
- Revision syntax —
HEAD, branch/tag names, abbreviated hashes, and~n/^nancestor suffixes everywhere (diff HEAD~2 HEAD,checkout main~1,show <short>). tag— create/list/-d, resolvable as revisions.show <ref>— a commit's metadata + its diff vs the first parent.reset <ref> [--hard]— move the current branch (and update the worktree with--hard).restore <ref> <path>...— materialize selected files/regions from a snapshot.revert <commit>— a new commit that undoes one, via the 3-way merge engine (conflicts reported).log—--oneline,-p(patch),--stat,-n..mcaignore— gitignore-lite (*.ext,dir/,name,/anchored/path) honored on commit.
67 tests (was 61). Next: Tier 2 — filesystem remotes (clone/fetch/push), reflog, cherry-pick, gc.
v0.4.0 — git-aligned CLI + repo-aware diff
The CLI now works like git, and diff understands the repository.
Git-aligned CLI
- Repo discovery: the repository is the current directory (or nearest ancestor) — or pass
-C <repo>before any command, exactly likegit -C. The old<repo>first-positional is gone from every command. - Bound worktree: bind a world to the repo (
mcadiff init <repo> --worktree <world>, ormcadiff config worktree <path>). Thencommit/status/diff/checkoutneed no path — just like git's working tree. A path can still be passed to override.
mcadiff init <repo> --worktree <world>
cd <repo> # or prefix any command with -C <repo>
mcadiff commit -m "before raid" # snapshots the bound worktree
mcadiff status # changes vs HEAD
mcadiff diff # worktree vs HEAD
mcadiff log / branch / merge / checkout …Repo-aware diff
diff now connects to history:
mcadiff diff→ worktree vs HEADmcadiff diff <ref>→ ref vs worktreemcadiff diff <refA> <refB>→ any two snapshots (branch / commit / HEAD / or a path to a working world)
It compares chunk hashes from the manifests first, so unchanged chunks are skipped instantly and only differing chunks are decoded — then reuses the same colored/JSON output as the file diff. Outside a repository, mcadiff diff <A> <B> still diffs two worlds/files as before.
Tests
61 (was 58): RepoDiffer across two commits and commit-vs-working-tree. Verified end-to-end on the example worlds — -C and cwd discovery, bound-worktree commit/status/diff, and all three diff forms.
v0.3.1 — repository polish
Polish pass on the repository/VCS layer (v0.3.0).
Fixed
- Correctness — detached HEAD: committing on a detached HEAD no longer silently clobbers
main. It advances HEAD itself and leaves all branches untouched;commit/checkoutnow report when you're detached. - Robustness — dedup: the canonical form now sorts NBT compound keys recursively (lists preserved). Two semantically-identical chunks that differ only in key order now dedup to a single object instead of being stored twice.
- Performance — re-commit cache: a per-repo cache maps a chunk's compressed-payload hash to its stored object hash, so re-committing a mostly-unchanged world skips decoding the unchanged chunks. Measured ~7× faster re-commit on the example world (0.54s → 0.08s). Commit still verifies the object exists before trusting a cache hit;
statusreads the cache but never writes it.
Tests
58 (was 55): detached commit keeps main put; reordered-key compounds hash identically; re-commit adds no objects and matches HEAD. Verified end-to-end on the real world — re-commit is a fast no-op and checkout still round-trips clean.
Deliberately deferred (documented in README)
hardlink/reflink for the apply copy; delta-packed objects; remotes; in-place conflict resolution; criss-cross merge-base; and addressing NBT keys that contain literal ./[.
v0.3.0 — semantic version control (history + 3-way merge)
Turns mcadiff into a git-like, semantic version-control system for Minecraft worlds — on top of the existing diff and patch/restore.
New: repository
A content-addressed, deduplicating object store (external repo). Each chunk is hashed by its decoded NBT, so an unchanged chunk is stored once no matter how many snapshots reference it — the thing fastback + git-LFS fundamentally can't do (they store whole region blobs per snapshot).
mcadiff init <repo>
mcadiff commit <repo> <world> -m "msg"
mcadiff log <repo>
mcadiff status <repo> <world>
mcadiff checkout <repo> <ref> <world-out>
mcadiff branch <repo> [name]
mcadiff merge <repo> <other-branch> [--theirs]- Whole-world snapshots: region/entities/poi as deduped per-chunk objects, loose NBT canonical, all other files (datapacks, stats, advancements) as raw blobs → faithful, playable
checkout. - True 3-way merge: finds the common ancestor and merges per NBT node — changes from both sides touching different nodes both land; only a genuine same-node clash is a conflict (kept ours, or theirs with
--theirs, and reported). Far finer than line-based merge.
Quality
55 tests (was 48): object-store dedup, canonical-form determinism, commit→checkout reproduces a world, merge-base, and 3-way merge (combine / conflict ours+theirs / fast-forward). Verified on the example worlds — committing Older then Newer reproduces each exactly on checkout, and shared chunks are stored once (~4,389 objects for ~5,250 chunks across both commits).
Requires .NET 9. See the README's Version control section.
v0.2.0 — diff + patch/restore
First tagged release of mcadiff — a semantic, git-style diff and patch/restore tool for Anvil-format Minecraft (Java Edition) worlds.
Diff
- Element-by-element diff of two worlds:
region/+entities/+poi/across all dimensions, plus loose NBT (level.dat,playerdata/*.dat,data/*.dat). - Recurses the full NBT tree to leaf level; compounds matched by key, set-like lists matched by identity (block coords, entity UUID, inventory slot, string
id) else by index; packed arrays summarized (--expandfor per-index). - Colored unified output (auto-off when piped) or
--json;--only,--summary. Exit0/1/2. - Fast paths skip byte-identical files and byte-identical compressed chunks; a ~2,900-file self-diff runs in seconds.
Patch & restore (new)
mcadiff extract <old> <new> -o changes.mcapatch— portable, bidirectional patch (each op stores old + new, losslessly type-encoded).mcadiff apply <patch> <target> -o <out>— non-destructive: copies the target to a fresh output world and rewrites only the patched nodes, each 3-way guarded (mismatch → conflict, skipped, never clobbered).--reverserestores old state;--force,--dry-run,--only.- New write path (
RegionWriter+ChunkCodec.Encode),Nbt/utilities (NbtJson,NbtPath,NbtEquality,NbtIdentity), and a singleIDiffSinkwalk driving both diff and extract.
Quality
- 48 xUnit tests. Forward/reverse round-trips verified end-to-end on real worlds:
extractOlder→Newer +applyreproduces Newer exactly;apply --reversereproduces Older.
Requires .NET 9. See the README for usage and a worked example.