ThreeKingdomsSlingshot/.claude/skills/unity-cli/references/version-control.md

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:

  1. --git-token-stdin (prefer this in CI — an argv token is visible in the process table) or --git-token <pat>
  2. The provider's environment variable
  3. Your git credential helper / Git Credential Manager, which may itself open a browser
  4. A signed-in provider CLI (gh / glab / tea) — the CLI can offer to run its login for you
  5. 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.

  • --object matches an object's own name, its hierarchy path, or its owning GameObject's name — which is what makes --object Player return the Player and every component on it.
  • --field narrows each row to the newest commit that changed that field, and drops objects that do not carry it.
  • --at blames as of another revision, so you can ask what a release branch shipped.
  • --max-revisions bounds 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:

  • confidence below exact means the object's identity was matched across a revision, not established — a re-serialization that renumbers fileIDs lands here. The count is also reported as a warning.
  • attribution: unattributed (unknown in the table) means the walk hit its bound with that object still unchanged. Its real answer is older; raise --max-revisions. In --json this is walkBounded: true, and a consumer acting on the output should branch on it.
  • historyCrossedNonYaml means 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 .gitattributes template 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. --since narrows the diagnosis, but the printed git lfs migrate import carries --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

  • --since is not one flag. A revision on vcs summarize and vcs affected; a git date on vcs git migrate-lfs.
  • --vcs git is not a value. --vcs takes github, gitlab, uvcs, or a self-hosted host name.
  • vcs is not collab, and unity vcs uvcs is not unity uvcs. Three adjacent namespaces; each one's help says what it is not.
  • A binary-serialized scene defeats every semantic verb here. diff, blame, explain and resolve all need text YAML. Set Asset Serialization Mode to Force Text in Editor settings; the commands say so when they hit it.
  • sync and switch refuse while an Editor holds the project. That is the safety feature, not an obstacle — reach for --close-editor before --force.
  • merge-setup --check exits 4 when work remains, so a set -e script fails there by design.
  • migrate-lfs rewrites history. It prints the command rather than running it. Read the warning.