M A N D A L I V I A
Obsidian Lab All Notes

Tracking Obsidian Vault Changes with Local-Only Git

The Problem

I use iCloud to sync my Obsidian vault. I trust Apple with my personal data — it’s where everything already lives — and I didn’t want to add Obsidian Sync or GitHub as another vendor with access to my notes. But iCloud gives you sync and backup, not history. If I rewrite a paragraph, the old version is gone. If I delete a file by accident, iCloud might propagate that deletion before I notice.

I wanted diffs. Not backup — I already have that — but the ability to see what changed, when, and roll back if needed.

The Solution

Run Git locally inside the vault. No remote repository, no pushing to GitHub. Just git init, a tight .gitignore, and a daily auto-commit. iCloud backs up the .git folder along with everything else, so the history is replicated without involving another service.

How To

Initialize the Repo

cd "/path/to/your/vault"
git init

That’s it. No git remote add, no SSH keys, no GitHub repo to create.

The .gitignore

A vault has markdown files you want to track, but also binary files, plugin code, and volatile state files that would bloat the repo or create noise.

Here’s what I use:

# Binary files — images, media, documents
*.png
*.jpg
*.jpeg
*.gif
*.webp
*.svg
*.mp4
*.mp3
*.pdf
*.epub
*.zip
*.tar.gz
 
# Obsidian internals — volatile state, not config
.obsidian/workspace.json
.obsidian/workspace-mobile.json
.obsidian/graph.json
.obsidian/vault_vectors.db
.obsidian/plugins/
.obsidian/themes/
.obsidian/icons/
.obsidian/.DS_Store
 
# Claude Code sessions (large, ephemeral)
.claude/sessions/
 
# System
.DS_Store
.trash/

This tracks all .md files, .canvas files, Obsidian config (appearance, hotkeys, plugin list, bookmarks, CSS snippets), Claude Code skills, hooks, and settings, plus CLAUDE.md. It skips images and PDFs (35 MB of attachments I don’t need diffs for), downloaded plugins (59 MB of third-party JavaScript, re-downloadable from community-plugins.json), themes (same logic), Omnisearch’s 52 MB vector database, and the workspace files Obsidian rewrites every time you switch tabs. My vault is about 2 GB total — the initial Git repo came out to 101 MB, just the text content of ~6,200 markdown files.

Why Track .claude/ and .obsidian/?

My vault increasingly has code in it — Claude Code skills (markdown files with embedded instructions), shell scripts in hook folders, and JSON configuration for MCP servers. These are things I wrote, things that break if I change them carelessly, and things I’d want to recover if something went wrong. The whole reason I reached for Git was that my vault stopped being purely notes and started having artifacts worth versioning.

I don’t track .obsidian/plugins/ or .obsidian/themes/ because those are downloaded code, not mine. The community-plugins.json file — which is tracked — records which plugins I have installed, so I can always reinstall them.

Daily Auto-Commit

I already had a nightly script that runs via launchd at 5:15 AM, pulling yesterday’s completed Things tasks and Toggl time entries into my daily note. I added a Git commit step at the end of that same script — no new scheduled job, no new plist to manage.

The commit function stages everything, checks if there are actual changes, and commits with a timestamped message:

Daily vault snapshot: 2026-03-11 05:15 — closing out the day

If nothing changed, it skips. If .git doesn’t exist (say, on a fresh vault clone), it skips gracefully. The commit runs whether or not the catchup portion found tasks to insert — it’s a separate concern bolted onto the same daily trigger.

Keeping the Repo Small

Without a remote, rewriting history is painless — there’s nobody else pulling from this repo. A couple of maintenance options:

  • Quarterly prune: Run git filter-repo --strip-blobs-bigger-than 1M to remove anything large that crept past the .gitignore. Follow up with git gc --prune=now --aggressive.
  • Nuclear option: If the repo gets unwieldy, rm -rf .git && git init && git add -A && git commit -m "fresh start". You lose history but keep all files. For a notes vault, recent history matters most anyway.

Caveats

Git Inside iCloud: The Known Risk

iCloud doesn’t understand .git/ as a database. It syncs individual files within .git/objects/ independently, which can leave the repo in an inconsistent state. The documented horror stories mostly involve multi-device setups where two machines write to .git/ simultaneously — iCloud creates conflict copies like HEAD 2 inside .git/refs/, and Git doesn’t know what to do with them.

My situation sidesteps the worst of this: I edit on one machine. Mobile access is read-mostly. There’s never a second writer racing against the Git database.

The safer alternative is to move .git outside iCloud using a gitdir pointer — create a .git file (not folder) in the vault that says gitdir: /some/path/outside/icloud. But that means managing and backing up a separate directory, which defeats part of the simplicity.

I chose to keep .git inside iCloud and accept the small risk. If corruption happens, git fsck will tell me, and the fix is just reinitializing. The history is nice to have, not mission-critical — this isn’t a code repo where losing commits means losing deployable work. It’s notes. The notes themselves are always intact thanks to iCloud. Git just adds a layer of “what changed when.”

Not a Substitute for Backups

Git gives you history, not redundancy. I still do periodic vault backups — a script that copies the whole vault (including binaries Git doesn’t track) and zips it. The Git repo and the zip backup serve different purposes.

Keep Exploring