v0.4.0 — Track stages, Push publishes
Track no longer writes unilfs.manifest.json. It stages the paths on your machine and hides them from git; Push creates the manifest entry, after storage confirms the content.
The manifest is committed to git, and an entry in it is a promise that its blob is downloadable. Track was in no position to make that promise — and two separate failures came out of it making one anyway.
What went wrong before
A committed manifest could name a blob nobody can download. Commit between Track and Push and every teammate gets an entry pointing at an object that is not in the bucket. That is the case the CI verify gate exists to catch after the fact.
And it could lose data. Track also recorded a sync baseline — the manifest hash this machine last agreed with — which for freshly tracked content is an agreement with itself. So when a teammate's manifest arrived naming the same path, the three-way rule read "only the manifest moved" and answered outdated, and Pull replaced a file that existed on exactly one disk and in no bucket.
The split
| file | committed | written by | an entry means |
|---|---|---|---|
unilfs.manifest.json |
✅ | Push only | this content was in storage when the entry was written |
unilfs.staged.json |
❌ | Track / Push / Pull / Untrack | this machine asked to track this path; nothing is uploaded |
Staging holds intent only — no hashes, sizes or GUIDs. Push re-reads all three at push time, so anything else kept there would be a second copy of the truth with its own way of going stale, which is the bug being fixed one file over.
A tracked file still has to be invisible to git from the moment you track it, so staged paths are hidden through .git/info/exclude — per-checkout, never tracked by git. The committed .gitignore block stays derived from the manifest alone: deriving a committed file from a local one would make two machines produce different content for it and turn the most boring file in the repository into a source of merge conflicts. On Push the .gitignore line is added before the exclude line is removed.
Also in this release
- Track is instant. It no longer hashes a file it is not about to upload, so tracking a folder of multi-gigabyte assets no longer reads all of them first.
- New
stagedstate — tracked here, in no manifest, invisible to everyone else until you Push. - Keep Mine, next to Restore Modified: the recorded half of resolving a conflict ("keep mine" vs "take theirs"). Because it is recorded rather than passed to one Push call, Auto Push honours it too. Re-running Track Selected no longer resolves conflicts — that was a committed manifest being rewritten by a command that reads as "start tracking this".
- A path tracked separately on both sides now reads
conflicted, notoutdated. Neither Push nor Pull touches it until someone chooses. - GUIDs travel from Push, so a re-import that minted a new one reaches the team without anyone re-tracking.
- Outside a git checkout there is nowhere to write
info/exclude. Status reports staged paths that nothing is ignoring rather than pretending they are hidden.
Upgrading from 0.3.3
Nothing to do. unilfs.manifest.json is unchanged — same format, no bump — and the .gitignore block is generated exactly as before, so existing projects see no diff. unilfs.staged.json starts absent, which reads as empty; your first Track creates it.
Downgrading is safe too: an older UniLFS generates the same .gitignore block from the same manifest and leaves the info/exclude block alone, so staged files stay hidden from git. Only the intent to track them is forgotten, and re-running Track restores it.
Rationale, invariants, the full state table and the migration notes: Documentation~/staged-tracking.md.
Tested
110 EditMode tests on Unity 2022.3.62f3, including a Pull/Push suite that runs two clones and a stand-in file server through the state patterns they can end up in — one clone has the file and the other does not, the two disagree about a .meta GUID, the same path holds different bytes — plus the git directory lookup, down to a linked worktree's commondir.
Full changelog: v0.3.3...v0.4.0