← Files UnityARCHIVED FILE

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

21.9 KB · Oct 4, 2026 · 12:33 UTC

↓ Download file

# 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](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.

```bash
# 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`.

```bash
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. It also does not probe `cm`; that belongs to `unity plugin list`, and
two diagnostics disagreeing about a path is worse than one.

### 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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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

```bash
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.

```bash
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".

```bash
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 `fileID`s 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.

```bash
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.

```bash
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](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

```bash
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

```bash
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

```bash
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.

```bash
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.

```bash
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 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.

`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`.

```bash
# 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.

SHA-256: 6a2f16f3510b7ede8543109cec9b5991ca485459e6706c37ce59b0cf8859209f