22 KiB
Version control — unity-cli command reference
Part of the unity-cli skill. See that skill's SKILL.md for CLI install, global flags,
environment variables, exit codes, and common workflows. All global flags (--format json,
--non-interactive, --yes, --proxy, …) apply to every command below.
unity vcs covers GitHub, GitLab, self-hosted git, and Unity Version Control (UVCS). It is
not Unity Collaboration — that is unity collaboration / unity collab, documented in
collaboration.md.
What this group is for. Generic git porcelain is not its job; you already have git. Every
verb here exists because it knows something about Unity that a generic tool cannot: that a scene
is YAML ordered by serialization rather than structure, that a missing .meta breaks references,
that switching branches with the Editor running invites a reimport storm, that a changed GUID
silently breaks every prefab pointing at it. When a verb below looks like a git command with a
Unity coat of paint, the Unity part is the point.
Map
| Verb | What it answers |
|---|---|
vcs setup |
Get this project into version control and push a first commit |
vcs status |
What changed, grouped by what it means to Unity |
vcs sync |
Pull safely, with the Editor and LFS accounted for |
vcs switch <branch> |
Change branch safely, reporting the reimport it causes |
vcs doctor |
Are this repository's Unity settings, ignores, LFS patterns and pinning right |
vcs providers |
Which provider can this machine reach, as whom |
vcs merge-setup |
Make scene and prefab merges work at all |
vcs conflicts |
Which conflicts exist, and which can merge automatically |
vcs explain <path> |
What did each branch actually change in this conflicted asset |
vcs resolve |
Resolve a conflicted asset, by merge or by taking a side |
vcs diff <path> |
What changed inside a scene or prefab, by object name |
vcs blame <path> |
Who last changed each object in a scene or prefab |
vcs summarize |
What did this branch change, ready to paste into a PR |
vcs affected |
Which assets, assemblies and tests does a change affect |
vcs hooks |
Run the Unity integrity checks at commit time |
vcs git … |
Git-only verbs: migrate-lfs, worktree add / remove |
vcs uvcs … |
UVCS reads that join cm data to your project |
Read the group's own help first when you are unsure — unity vcs --help names the providers in
its first line, and every verb takes --help.
Getting set up
vcs setup
One command, zero required flags. It detects everything inferable — whether you are in a Unity project, whether a repository or remote already exists, which providers you are signed in to — and asks only what it cannot work out. If a git repository with a remote is already there, it adopts it rather than failing.
# The whole happy path, from inside the project
unity vcs setup
# Non-interactive, naming the provider and the repository
unity vcs setup --vcs github --git-namespace my-org --git-repo my-game --git-visibility private
# UVCS, which needs no token: it uses your Unity sign-in
unity vcs setup --vcs uvcs --cloud-org my-org --vcs-region <region>
# See what UVCS setup would create, without creating it
unity vcs setup --vcs uvcs --dry-run
# Reuse a saved set of choices, or save this run's
unity vcs setup --preset team-default
unity vcs setup --vcs github --git-namespace my-org --save-preset team-default
--vcs takes github, gitlab, uvcs, or a self-hosted host name. Anything else fails
with VCS_PROVIDER_UNSUPPORTED — note the bare word git is not a valid value.
Credentials, in the order they are tried. You will usually not need to supply one:
--git-token-stdin(prefer this in CI — an argv token is visible in the process table) or--git-token <pat>- The provider's environment variable
- Your git credential helper / Git Credential Manager, which may itself open a browser
- A signed-in provider CLI (
gh/glab/tea) — the CLI can offer to run its login for you - A guided personal-access-token prompt that names the scopes needed and links the right page
LFS. --git-lfs initializes Git LFS before the first commit, for new repositories only. If
git-lfs is missing the CLI offers to install it through your platform's package manager
(brew / apt / dnf / winget / pacman / zypper / apk); it never vendors its own copy, because git
resolves lfs on PATH and a private copy would leave a repository only this binary could
satisfy. Set UNITY_INSTALL_MISSING_TOOLS=1 to accept that offer non-interactively.
vcs providers
Which provider this machine can reach, as whom, and what each host allows. Spawns the provider
CLIs and makes an authenticated request per host, so it is the opt-in, network-touching
counterpart to the offline VCS section of unity doctor.
unity vcs providers
unity vcs providers --host github.example.com --host gitlab.example.internal # repeatable
unity vcs providers --json
Reports binary presence and version, per-host auth state, credential-helper entries, and each host's capability tier. It deliberately does not report how a repository would get created: that depends on whether a token resolves, and resolving one can prompt — a read-only report must not ask for a credential.
The tools table also lists cm, the Unity Version Control client, taken from unity plugin list's
own registry lookup, so the two always agree on its path, version, and state (resolved,
not-installed, or unsupported-platform on linux-arm64). It never downloads cm. Inside a UVCS
workspace with no cm installed, it suggests unity plugin install uvcs. The offline VCS section of
unity doctor omits cm, which doctor already lists under external components.
vcs merge-setup
Scene and prefab merges do not work in a fresh Unity repository. Every editor install ships
UnityYAMLMerge, and this wires it up: the .gitattributes entries, the merge-driver config,
and a test merge that proves the tool actually runs.
unity vcs merge-setup # set it up, using the editor the project requires
unity vcs merge-setup --check # report only; exits 4 when work remains
unity vcs merge-setup --editor-version 6000.0.30f1
unity vcs merge-setup --skip-verify # skip the proving test merge
--check exits 4 when work remains, which is what makes it usable as a CI gate.
vcs doctor
The repository-side hygiene audit: git settings, ignore rules, LFS patterns, and package pinning that a Unity project needs under version control.
unity vcs doctor
unity vcs doctor --fix # repair everything repairable; idempotent
unity vcs doctor --check META,LFS # run only these checks
--fix is idempotent by contract — running it twice changes nothing the second time.
Distinct from unity projects verify, which is detection-only and never runs git. Different
subjects: projects verify checks the asset tree's integrity, vcs doctor checks the
repository's Unity configuration.
vcs hooks
Installs managed pre-commit, post-checkout and post-merge hooks so the Unity integrity
checks run without anyone remembering to.
unity vcs hooks install # install or upgrade
unity vcs hooks status # which managed hooks are installed, and at which version
unity vcs hooks uninstall # remove the managed hooks, leaving the rest of the file alone
Uninstall edits only the managed block, so hand-written hook content survives.
Day to day
vcs status
What changed, grouped by what it means to Unity rather than by path, and with meta-file
pairing problems called out — an asset added without its .meta, or a .meta left behind by
a move, is the single most common "works on my machine" bug.
unity vcs status
unity vcs status --json
On a UVCS workspace it additionally joins in lock state, which is a join no passthrough to cm
can do.
vcs sync
Pull safely.
unity vcs sync
unity vcs sync --rebase # replay local commits on top of incoming ones
unity vcs sync --allow-dirty # pull over uncommitted changes to tracked files
unity vcs sync --force # pull even while an Editor holds the project
unity vcs sync --verify # check the project afterwards without asking
It refuses while an Editor holds the project, and that refusal is the feature: pulling under
a live Editor invites a reimport storm or a corrupted Library. It pulls LFS objects too, then
reports the reimport the pull will cause.
vcs switch
unity vcs switch main
unity vcs switch feature/lighting --dry-run # every check, plus the reimport scope, no change
unity vcs switch main --allow-dirty # carry uncommitted changes across
unity vcs switch main --discard-changes # discard uncommitted changes to tracked files
unity vcs switch main --close-editor # ask the Editor to quit, then switch
unity vcs switch main --force -y
Same Editor gate as sync, and --dry-run reports the reimport the switch would cause — worth
running before a switch between branches that differ in assets.
Reading a change
These four are read-only and never write the working tree or the index.
vcs diff
A semantic diff of one scene or prefab, by GameObject and component name rather than by
fileID. A raw git diff of a scene is unreadable: everything is a numeric id and the file is
ordered by serialization, so a one-object edit shows up as hunks scattered through thousands of
lines with nothing naming what they belong to.
unity vcs diff Assets/Scenes/Level.unity # HEAD vs your working tree
unity vcs diff Assets/Scenes/Level.unity --from main --to HEAD
unity vcs diff Assets/Prefabs/Player.prefab --json
--from defaults to HEAD; --to defaults to your working tree, which is why it is not
defaulted to HEAD — absent and HEAD are different comparisons.
Reads like Changed Transform on Player at Level/Actors/Player: position {x: 0} → {x: 5}.
Identity is reported with a confidence. Matching is three tiers — fileID plus class tag,
then class plus name plus hierarchy path, then content similarity — and every row carries which
tier matched it. Treat anything below exact as a hypothesis: a diff that confidently reports a
rename as a delete plus an add is worse than one that admits it is unsure.
Prefab overrides are flagged, not attributed. An override lives in the referencing asset,
inside PrefabInstance.m_Modification, and resolving it means opening the source prefab. So the
change is reported against the instance with a prefabInstance flag and a note that attribution
stops at this asset — never as "the Player's mass changed".
vcs blame
Who last changed each object. git blame on a serialized scene answers "who last touched line
4,812", which is not a question anybody has; this answers "who last changed the Player's
Rigidbody".
unity vcs blame Assets/Scenes/Level.unity
# Just one object -- and, when it names a GameObject, its components too
unity vcs blame Assets/Scenes/Level.unity --object Player
unity vcs blame Assets/Scenes/Level.unity --object Level/Actors/Player
# Just one serialized field
unity vcs blame Assets/Scenes/Level.unity --object Player --field m_Mass
# As of a tag or release branch rather than HEAD
unity vcs blame Assets/Scenes/Level.unity --at v1.4.0
# Bound the history walk
unity vcs blame Assets/Scenes/Level.unity --max-revisions 50
unity vcs blame Assets/Scenes/Level.unity --json
One row per object: what it is, its hierarchy path, whether the commit changed or created it, the commit, author, date, the identity confidence, and which fields moved.
--objectmatches an object's own name, its hierarchy path, or its owning GameObject's name — which is what makes--object Playerreturn the Player and every component on it.--fieldnarrows each row to the newest commit that changed that field, and drops objects that do not carry it.--atblames as of another revision, so you can ask what a release branch shipped.--max-revisionsbounds the walk (default 200). It rations time, not memory: peak memory tracks the scene's object count and is flat in revision count, while each revision costs roughly a fifth of a second.
Three honest answers to expect, rather than a confident wrong one:
confidencebelowexactmeans the object's identity was matched across a revision, not established — a re-serialization that renumbersfileIDs lands here. The count is also reported as a warning.attribution: unattributed(unknownin the table) means the walk hit its bound with that object still unchanged. Its real answer is older; raise--max-revisions. In--jsonthis iswalkBounded: true, and a consumer acting on the output should branch on it.historyCrossedNonYamlmeans history reaches a revision where the asset was binary-serialized, so the walk stopped rather than attributing every object to the commit that switched Asset Serialization Mode.
A binary-serialized or non-Unity asset is reported as such, never as an empty result.
vcs summarize
What a branch changed, ready to paste into a pull request: counts by Unity category, plus the
risks worth a reviewer's attention — GUID changes that may break references, assets moved
without their .meta, binaries that cannot be reviewed as text, and package-manifest changes.
unity vcs summarize --since main
unity vcs summarize --since origin/dev --json
--since is required and takes a revision; the summary is measured from its merge base
with HEAD. This is the one verb in the group that is not gated on a Unity project root — the
range is a git range, so it works from a monorepo's root, one level above the project, which is
where PR bodies actually get written.
vcs affected
Which assets, assemblies and tests a change affects, by walking the GUID reference graph: changed asset → prefabs referencing its GUID → scenes → asmdefs → affected tests. A CI primitive no generic tool can build.
unity vcs affected # uncommitted work
unity vcs affected --since main
unity vcs affected --since main --json
--since takes a revision and uses its merge base with HEAD; it defaults to HEAD, which
reports uncommitted work.
The report is a lower bound, and every format says so. Addressables groups, resource-folder
lookups, load-by-name code and binary-serialized assets are real dependencies no static reader
finds. --json carries lowerBound and a count of holes. A consumer that skips work on
this is trading correctness for time — see unity test --affected in
build-run-test.md, which refuses rather than guessing when a diff carries
non-code changes.
Conflicts
The three verbs in the order a merge presents them: list, understand, resolve.
vcs conflicts
unity vcs conflicts
unity vcs conflicts --json
Every unresolved conflict, classified by Unity type, with whether each one can merge automatically — which is the column that tells you what needs a human.
vcs explain
unity vcs explain Assets/Scenes/Level.unity
What each branch changed in a conflicted asset, in plain language, by object and component name. Requires the path; there is no repository-wide form, because the output is per object inside one file.
vcs resolve
unity vcs resolve Assets/Scenes/Level.unity # merge with UnityYAMLMerge (the default)
unity vcs resolve Assets/Scenes/Level.unity --ours # take our side, discarding theirs
unity vcs resolve Assets/Scenes/Level.unity --theirs
unity vcs resolve --all # every auto-resolvable conflict
unity vcs resolve Assets/Scenes/Level.unity --editor-version 6000.0.30f1
--merge is the default and uses UnityYAMLMerge, so vcs merge-setup should have run first.
--all replaces the path operand, which is why the path is optional.
Git-only verbs
unity vcs git holds the things UVCS has no equivalent of.
vcs git migrate-lfs
Finds binaries already committed to history and prints the LFS migration command for them.
unity vcs git migrate-lfs
unity vcs git migrate-lfs --min-size 500KB
unity vcs git migrate-lfs --since "6 months ago"
⚠ --since here takes a git DATE, not a revision — unlike vcs summarize --since and
vcs affected --since, which take revisions. Same flag name, three leaves, two meanings.
Two things it deliberately does:
- The recommendation is narrower than the report. A size floor over a long history finds
generated bundles, sourcemaps and docs as readily as textures, and telling a team to put its
source into LFS stops it diffing and merging. So the include list is restricted to the asset
types the shipped
.gitattributestemplate already routes through LFS, plus what the repository already tracks. Everything else is reported and named as not offered. - A bounded scan still prints an unbounded migration.
--sincenarrows the diagnosis, but the printedgit lfs migrate importcarries--everything, because a rewrite that skips a branch leaves every blob on it reachable and the repository does not shrink.
It prints the command rather than running it: this rewrites history.
vcs git worktree
A git worktree with the right editor version, a seeded Library, and a Hub registry row — so a
second branch is usable in minutes instead of a full reimport.
unity vcs git worktree add feature/lighting
unity vcs git worktree add feature/lighting --into ../lighting --seed full
unity vcs git worktree add feature/lighting --install-editor
unity vcs git worktree add feature/lighting --dry-run
unity vcs git worktree add feature/lighting --no-register
unity vcs git worktree remove ../lighting
unity vcs git worktree remove ../lighting --discard-changes --force
--seed takes cache, full, or none. The default deliberately withholds
PackageCache/: it is three quarters of a real Library's bytes, and copying it finishes
later than withholding it, because UPM refills it from its own global store during the import
either way.
--no-register leaves the new worktree out of the Hub project registry, so it does not show up in
unity projects list or in the Hub. Use it for a short-lived checkout you do not want to keep
track of.
remove requires the path, so a bare run can never delete the checkout you are standing in.
UVCS
unity vcs uvcs holds the reads that join cm data to your project — the things a raw
passthrough cannot do. Everything else goes straight to cm.
# Wrapped reads: these join cm's data to your working tree
unity vcs uvcs locks # who holds a lock, AND which locks cover files you changed
unity vcs uvcs changesets --limit 50 # recent changesets in a stable envelope (default 25, max 1000)
# Code review
unity vcs uvcs review list --status pending
unity vcs uvcs review comments --review 42
unity vcs uvcs review comments --branch /main/feature --all-activity
unity vcs uvcs review reply --review 42 --comment 7 --body "Fixed in cs:118"
unity vcs uvcs review resolve --review 42 --comment 7 --changeset 118
# Everything else: the whole command line forwards verbatim to cm
unity uvcs lock list
unity uvcs shelve -c "wip: lighting pass"
unity uvcs partial update /Assets/Levels
unity uvcs and unity vcs uvcs are different commands, and the difference matters.
unity uvcs <args> is an opaque passthrough: the whole tail goes to cm in cm's own
vocabulary, with cm's own flags and output. unity vcs uvcs <verb> is Unity-aware wrapping
with a stable output envelope. Prefer the wrapped verbs when something parses the output;
prefer the passthrough when a human reads it, or when you need a cm verb we do not wrap.
Mutations in cm's vocabulary — shelves, partial checkout, lock acquire and release — are
deliberately not wrapped: cm versions that vocabulary, and a wrapper would pin a paraphrase
of it at build time and rot silently.
review reply and review resolve both require --review and --comment; resolve
additionally requires --changeset, the changeset the comment was applied in.
Machine output
Every read verb here supports --format json (and --ndjson) with the standard envelope:
success, command, data, errors, warnings. The data payload carries raw values —
unsanitized names, paths and field names exactly as the file holds them — because that is what a
consumer matches against the asset. Human and tsv output are sanitized for terminal display
instead.
tsv is the default whenever stdout is redirected, not human. So a piped run gets tabular
output, and the advisories (identity confidence, walk bounds, lower-bound caveats) go to
stderr rather than being dropped — which is exactly where a script author needs them.
Fields worth branching on rather than ignoring:
| Field | Verb | Why |
|---|---|---|
confidence |
diff, blame |
Below exact, identity was matched rather than established |
walkBounded |
blame |
The answer may be older than reported |
historyCrossedNonYaml |
blame |
The walk stopped at a binary-serialized revision |
prefabInstance |
diff |
Attribution stops at this asset |
lowerBound, holes |
affected |
Real dependencies exist that no static reader finds |
outcome |
diff, blame |
binary-serialized / not-a-serialized-asset are answers, not failures |
Common traps
--sinceis not one flag. A revision onvcs summarizeandvcs affected; a git date onvcs git migrate-lfs.--vcs gitis not a value.--vcstakesgithub,gitlab,uvcs, or a self-hosted host name.vcsis notcollab, andunity vcs uvcsis notunity uvcs. Three adjacent namespaces; each one's help says what it is not.- A binary-serialized scene defeats every semantic verb here.
diff,blame,explainandresolveall need text YAML. Set Asset Serialization Mode to Force Text in Editor settings; the commands say so when they hit it. syncandswitchrefuse while an Editor holds the project. That is the safety feature, not an obstacle — reach for--close-editorbefore--force.merge-setup --checkexits 4 when work remains, so aset -escript fails there by design.migrate-lfsrewrites history. It prints the command rather than running it. Read the warning.