uVersion
English
Download →

Wiki

CLI uversion

Complete uVersion CLI reference: every command, flag, example, JSON output, exit codes.

The uversion binary covers the same versioning operations as the desktop client, plus a --json output a program can consume, for automation: continuous integration, onboarding scripts, in-house tooling. This page is aimed at developers and studio administrators.

Installation

On Windows, the uversion.exe binary is included in the desktop client installer and added automatically to the user PATH (%LOCALAPPDATA%\uVersion). The PATH entry is deduplicated on every update and removed on uninstall. Open a new terminal and type:

uversion --help

On macOS, it is automatic: on first launch, the desktop client creates a link to the embedded binary at ~/.local/bin/uversion and makes sure that folder is on the PATH (via ~/.zprofile). Launch the app once, open a new terminal, and the uversion command is available. To do it by hand to another location on the PATH:

ln -s /Applications/uVersion.app/Contents/Resources/uversion /usr/local/bin/uversion

On Linux, it is automatic too, exactly as on macOS. The desktop client is distributed as an AppImage, and that AppImage contains the uversion binary. On first launch, the app places it at ~/.local/bin/uversion and makes sure that folder is on the PATH. Launch the app once, open a new terminal, and the command is available:

uversion --help

You have nothing to compile. On Linux, the desktop client is distributed only as an AppImage, and that is what brings the CLI. To place the binary elsewhere on the PATH, copy it from ~/.local/bin:

sudo cp ~/.local/bin/uversion /usr/local/bin/uversion

To check the installed version:

$ uversion --version

Cheatsheet: every command

The full set of available commands, in the order you typically encounter them:

CommandWhat it does
uversion login <url> -u <user>Authenticate against a server
uversion logoutClear the stored credentials
uversion reposList accessible repositories
uversion clone <repo> [path]Clone a repository
uversion infoShow the workspace state + current user
uversion status [paths...]See modified / new / deleted / locked files
uversion checkout <paths...>Reserve files for editing (acquires a lock)
uversion checkin <paths...> -m "..."Upload and commit changes
uversion revert <paths...>Discard local changes and release the locks
uversion syncFetch changes from the server (whole repository)
uversion content <path> --revision <n>Download a specific version of a file
uversion logCommit history
uversion lock listSee all locks in the repository
uversion lock release <paths...>Release a lock without touching the file
uversion lock heartbeatSignal that your locks are still in use (for monitoring)
uversion trust <url>Remember a server's self-signed certificate fingerprint (interactive; --yes to script it)
uversion mistrust <url>Forget a server's remembered fingerprint
uversion trustedList servers whose fingerprint is remembered

Every command except uversion content accepts --json, which replaces the human-readable display with output a program can consume (see JSON output). Every command accepts --help for the option details.

checkout, checkin and revert also accept --paths-file <fichier>, indispensable as soon as you work at the scale of an Unreal project. See Large path lists.

Authentication

uversion login

Authenticates against a uVersion server. The server hands back a session token (a JWT, for JSON Web Token), which the CLI stores in the system password manager: Credential Manager on Windows, Keychain on macOS, libsecret on Linux. This token is shared with the desktop client, the Unreal plugin and the Rider plugin: signing in on one side signs in the others, and signing out signs them all out.

uversion login <url_serveur> -u <utilisateur> [-p <mot_de_passe>]
OptionDescription
-u, --usernameUsername
-p, --passwordDiscouraged. See the box below. If the option is omitted, the CLI reads the UVERSION_PASSWORD environment variable, and failing that prompts for the password interactively, with no echo to the screen.
To script a login, use UVERSION_PASSWORD, never -p

On a multi-user system, each process's command line is readable by the other accounts on the machine: ps on Linux and macOS, Task Manager or wmic on Windows. A password passed as an argument shows up there in clear text, even when it comes from a variable, since the shell replaces it with its value before launching the program. It also ends up in the shell history and often in the continuous-integration logs.

The CLI reads UVERSION_PASSWORD precisely to avoid this, and prints a warning to standard error when -p is used.

Examples:

$ uversion login https://uversion.mygamestudio.com -u alice
Password:
Logged in as alice (artist)

# Sur un serveur de studio, en interne. Le port par défaut est 8443, en HTTPS.
$ uversion login https://192.168.1.100:8443 -u bob
Password:
Logged in as bob (programmer)

# Compte d'intégration continue : le mot de passe passe par l'environnement,
# jamais par la ligne de commande.
$ export UVERSION_PASSWORD="$SECRET_FROM_VAULT"
$ uversion login "$UV_SERVER" -u ci-nightly
Logged in as ci-nightly (programmer)

On the first connection to a server that presents a self-signed certificate, the CLI shows the certificate fingerprint and asks you to confirm it, then remembers it. This is the principle of trust on first use, the same as SSH: you accept an identity once, and any later presentation of a different identity is flagged. If the fingerprint changes, the CLI refuses the connection and warns you: it is either a legitimate certificate renewal, or an interception of your traffic by a third party. The deliberate move is then uversion mistrust <url>, followed by a new connection.

uversion logout

Erases the token from the system password manager and invalidates, on the server, every session for this account. Since the token is shared, this also signs out the desktop client, the Unreal plugin and the Rider plugin, on all your machines.

$ uversion logout
Logged out (alice)

$ uversion logout       # si aucune session n'était ouverte
Already logged out

Which account acts? The workspace's, not the last one signed in

This is the point that surprises people most, and you had better know it before scripting anything: the identity used is not that of the last login, it is that of the workspace you are in.

A workspace is a cloned folder, recognizable by its .uversion subfolder. The .uversion/config.toml file records the server and the owner account there:

[repository]
id = "1"
name = "hero-rpg"
server_url = "https://uversion.mygamestudio.com"

[workspace]
id = "..."
name = "alice-cli"
owner = "alice"
last_synced_revision = 42

As soon as you are inside a workspace, server_url and owner are authoritative: status, checkout, checkin, revert, sync, log, content, lock and info authenticate as owner, against server_url.

Only login, logout, repos and clone use the configuration shared across all workspaces (%APPDATA%/uversion/uVersion/config/config.toml on Windows), which follows the active account of the desktop client.

Why. One machine often serves several accounts, for instance a freelancer who works for two studios. Without this rule, every workspace would act under whichever account is active at the moment: a folder cloned by alice but used while bob is active would place its locks under bob's name, would see its own files as "locked by someone else", and would have its submissions refused.

To change a workspace's identity, edit the owner field in .uversion/config.toml, and make sure that account has signed in at least once on this machine (uversion login), so that its token is present. Then check with uversion info, which shows the account actually used.

Known limitation: the same username on two servers

The token is stored under the username alone, without the server. If the same name exists on two different uVersion servers, both share one and the same entry: signing in to the second replaces the first's token. Use distinct names, or use only one server at a time from a given machine.

Repositories

uversion repos

Lists the repositories the account has access to. This command uses the shared configuration, not a workspace's: it therefore answers for the account of your last login.

$ uversion repos
ID     Name                           Description
----------------------------------------------------------------------
1      hero-rpg                       Main RPG project
2      shared-assets                  Shared asset library
12     prototype-fps                  R&D prototype FPS

$ uversion repos          # si aucun dépôt n'est accessible
No repositories found

uversion clone

Fetches a repository locally. If the path is omitted, a folder named after the repository is created in the current directory. The clone also creates the .uversion subfolder, which turns the folder into a workspace and records the server and the owner account.

The transfer applies deduplication: the content is split into blocks, and a block already present is stored only once, even if it appears in several files. That is why the space used on disk is often noticeably smaller than the volume downloaded.

uversion clone <repo_name_or_id> [local_path]

Examples:

$ uversion clone hero-rpg
Cloning hero-rpg to ./hero-rpg...
✓ 8,432 files in 47s (14.2 GB downloaded, 6.1 GB on disk after dedup)

$ uversion clone hero-rpg D:\Projects\HeroRPG
$ uversion clone 1                              # par ID au lieu du nom

Files

uversion status

Shows the state of the files in the current workspace: modified, new (untracked), deleted, locked by others.

uversion status [paths...] [--json]

Examples:

$ uversion status
Modified:
  M  Content/Maps/MainLevel.umap (locked by alice)
New:
  A  Content/Textures/NewTexture.png
Deleted:
  D  Content/OldAsset.uasset
Locked by others:
  L  Content/Characters/Hero.uasset  (locked by bob)

1 modified, 1 new, 1 deleted, 1 locked by others

$ uversion status Content/Maps                  # filtre par dossier
$ uversion status --json | jq '.summary'        # extraction scriptable

uversion checkout

Places an exclusive lock on the target files and makes them writable on disk. Tracked files are read-only until they are reserved: this is what prevents two people from editing the same binary asset in parallel.

uversion checkout <paths...> [--paths-file <fichier>] [--force] [--add] [--json]
OptionDescription
--paths-fileRead additional paths from a file, one per line. See Large path lists.
--forceTakes the lock even if it is held by someone else. Restricted to accounts that hold the force_unlock capability, that is, the admin and lead roles. See below.
--addAllows reserving paths not yet present locally, for new files.
What --force really does

The option takes someone else's lock. It is guarded by the force_unlock capability, held by the admin and lead roles. An account that does not have it receives an explicit refusal, with the recourse to follow: ask an administrator, or use the "Request release" button in the desktop client, which notifies the person concerned.

When a takeover has actually happened, it is recorded in the audit log, with the paths concerned and the names of the people whose locks were taken. Nothing is written if the option was present but no one else's lock changed hands: in scripts, the flag is often applied systematically, and a log full of pointless events is a log nobody reads.

Examples:

$ uversion checkout Content/Maps/MainLevel.umap
✓ Lock acquired: Content/Maps/MainLevel.umap

$ uversion checkout Content/Characters/Hero.uasset Content/Characters/Villain.uasset
✓ Lock acquired: Content/Characters/Hero.uasset
✓ Lock acquired: Content/Characters/Villain.uasset

# Fichier déjà réservé par bob
$ uversion checkout Content/Maps/MainLevel.umap
✗ File is locked (bob)

# Compte sans la capacité force_unlock
$ uversion checkout --force Content/Maps/MainLevel.umap
Error: Taking a lock held by another user requires the force_unlock capability
(admin or lead). Ask an administrator, or use Request Release to ask the holder.

# Compte admin ou lead : le vol passe, et il est tracé
$ uversion checkout --force Content/Maps/MainLevel.umap
✓ Lock acquired: Content/Maps/MainLevel.umap

uversion checkin

Uploads the modified files and commits them to the server in a single transaction: either everything goes through, or nothing does. The locks are released automatically on success.

uversion checkin [paths...] [--paths-file <fichier>] -m <message> [--all] [--json]
OptionDescription
-m, --messageCommit message. Required.
-a, --allInclude every modified file in the workspace, not only the ones passed as arguments.
--paths-fileRead additional paths from a file, one per line. See Large path lists.

Examples:

$ uversion checkin Content/Maps/MainLevel.umap -m "Fixed lighting in main level"
Validating 1 file...
✓ All validation rules passed
Uploading: [####################] 100% · 84 MB
✓ Committed as 7f3a9b1 (1 file, 84 MB uploaded, 0 deduped)

$ uversion checkin --all -m "Weekly art update"   # tout le workspace
$ uversion checkin Content/Characters/ -m "Updated character meshes"

uversion revert

Discards the local changes to one or more files, restores the server version, and releases the matching locks.

uversion revert <paths...> [--paths-file <fichier>] [--json]

Examples:

$ uversion revert Content/Maps/MainLevel.umap
✓ Reverted: Content/Maps/MainLevel.umap (lock released)

$ uversion revert Content/Characters/        # récursif par dossier

Large path lists: --paths-file

checkout, checkin and revert accept --paths-file <fichier>: a text file containing one path per line. The paths read this way are added to those passed as arguments, they do not replace them.

What it is for. On an Unreal project, an operation commonly covers several thousand files. Passing them all as arguments runs into a system limit: on Windows, a command line cannot exceed 32,767 characters, which is about 500 asset paths. Beyond that, the command fails before it even starts, with a system error message that says nothing about the real problem. --paths-file removes this limit: the file can contain as many as needed.

Reserve every modified file in a folder, whatever their number:

$ uversion status --json \
    | jq -r '.files[] | select(.status == "modified") | .path' > /tmp/changed.txt
$ wc -l /tmp/changed.txt
3184 /tmp/changed.txt

$ uversion checkout --paths-file /tmp/changed.txt

Then submit exactly the same batch:

$ uversion checkin --paths-file /tmp/changed.txt -m "Import de la passe d'éclairage"

On Windows, in PowerShell:

PS> (uversion status --json | ConvertFrom-Json).files |
      Where-Object { $_.status -eq "modified" } |
      ForEach-Object { $_.path } |
      Set-Content -Encoding utf8 changed.txt

PS> uversion checkout --paths-file changed.txt

This is also the mechanism the Rider plugin uses to pass a large changelist.

uversion sync

Downloads the latest changes from the server and applies them to the local workspace.

uversion sync [--force] [--json]
FlagDescription
-f, --forceFull sync: re-downloads every file, not only the delta since the last sync. Useful when the workspace is corrupted.

Examples:

$ uversion sync
Syncing from revision 41 → 47...
✓ 12 files updated, 3 added, 1 deleted (1.4 GB downloaded)

$ uversion sync --force                      # re-télécharge tout

uversion content

Downloads a specific version of a file without touching the local workspace. Useful to compare, archive, or retrieve a past state without doing a revert.

uversion content <path> [-r <numéro_de_révision>] [-o <fichier>]
--revision expects an integer, not a commit hash

It is the file's revision number: a counter that is 1 at its first version, 2 at the second, and so on. Passing a commit hash like 6e2b8a0 makes the command fail at argument parsing.

The number can be read in uversion log --path <fichier>, where each file line shows it in parentheses. Omitting --revision downloads the latest version.

Examples:

$ uversion content Content/Maps/MainLevel.umap --revision 12 --output ./snapshot.umap

$ uversion content Content/Characters/Hero.uasset -r 8 -o ./hero-v8.uasset

# Sans --output, le contenu est écrit sur la sortie standard
$ uversion content Config/DefaultEngine.ini -r 3 > DefaultEngine-v3.ini

History

uversion log

Commit history of the current repository, optionally filtered by file.

uversion log [-n <limit>] [-p <path>] [--json]
FlagDescription
-n, --limitNumber of entries to display (default: 20)
-p, --pathFilter by file path

Examples:

$ uversion log
commit 7f3a9b1c2d...
Author: alice
Date:   2026-05-15 08:30:00 UTC

    Fixed lighting in main level

    Content/Maps/MainLevel.umap (rev 12)

commit 6e2b8a0...
Author: bob
Date:   2026-05-14 17:22:00 UTC

    Hero pose pass

    Content/Characters/Hero.uasset (rev 8)
    Content/Characters/OldHero.uasset (deleted, rev 9)

$ uversion log -n 5                                  # 5 derniers commits
$ uversion log --path Content/Maps/MainLevel.umap    # historique d'un fichier

A line marked deleted is a deletion revision: it carries a number like the others, but has no content to download.

Locks

A lock never expires

It holds until it is explicitly released: by uversion checkin, by uversion revert, by uversion lock release, or by an administrator's force-unlock. No automatic expiry exists, neither after an hour nor after a month. A file reserved and forgotten stays that way until someone intervenes.

Consequently, uversion lock heartbeat extends nothing. This command only says "these locks are still useful to me", so that administrators can tell an active lock from an abandoned one.

uversion lock list

Shows all the locks in the current repository.

uversion lock list [--json]

Examples:

$ uversion lock list
File                                     User            Acquired
----------------------------------------------------------------------
Content/Maps/MainLevel.umap             alice           2026-05-15T08:42:11Z
Content/Characters/Hero.uasset          bob             2026-05-14T17:00:00Z
Content/UI/HUD.uasset                   alice           2026-05-15T09:15:00Z

If there is nothing to show, the command writes No active locks.

uversion lock release

Releases one or more locks without touching the local content of the file. Use it to "hand back" an asset you have not modified: reserved by mistake, or work abandoned without submitting.

uversion lock release <paths...> [--json]

Examples:

$ uversion lock release Content/Maps/MainLevel.umap
✓ Lock released: Content/Maps/MainLevel.umap

This command releases only your own locks. To remove someone else's, you have to go through administration, or through uversion checkout --force if you are admin or lead.

uversion lock heartbeat

Signals that the locks held by the current account are still in use. This does not extend them: nothing expires. It is a monitoring signal, so that an administrator inspecting the lock list can see which ones are still active. Useless in day-to-day work; useful for an automated job that keeps a file reserved for hours.

$ uversion lock heartbeat
3 lock(s) extended

$ uversion lock heartbeat        # si vous ne détenez aucun verrou
No locks to extend

Example in continuous integration:

$ while build_in_progress; do
    uversion lock heartbeat
    sleep 300
  done

Info

uversion info

Shows the account used and the state of the current workspace. It is the command to run first when something behaves unexpectedly: it shows under which identity the CLI actually acts, which is the workspace owner's and not necessarily that of your last login (see Which account acts?).

$ uversion info
User: alice (lead)

Repository: hero-rpg (id: 1)
Server:     https://uversion.mygamestudio.com
Workspace:  alice-cli (3f2a1c8e-...)
Local path: D:\Projects\HeroRPG
Last sync:  revision 42

Outside a workspace, or without a valid session:

$ uversion info
User: not logged in

Workspace: not in a uVersion workspace

The command does not count the files and does not show a summary of the changes: that is uversion status's job.

JSON output

Every command except content accepts --json, which replaces the human-readable display with structured output. This is what makes the CLI scriptable.

Several commands return an array at the root

log, lock list and repos produce a JSON array directly, with no wrapping object. So there is no commits key, no locks key, no repositories key: it is .[] you must write in jq, not .commits[]. An expression targeting a nonexistent key produces nothing at all, with no error message.

Example: uversion status --json

{
  "files": [
    {
      "path": "Content/Maps/MainLevel.umap",
      "status": "locked",
      "locked_by": "alice",
      "is_owned": true,
      "version": 12
    },
    {
      "path": "Content/Textures/NewTexture.png",
      "status": "new",
      "locked_by": null,
      "is_owned": false,
      "version": 0
    }
  ],
  "summary": {
    "modified": 1,
    "new": 1,
    "deleted": 0,
    "locked_by_others": 0
  }
}

The possible values of status:

ValueMeaning
modifiedThe file is writable on disk, with no lock placed
lockedReserved by you
locked_otherReserved by someone else, named in locked_by
newPresent locally, unknown to the server
deletedPresent on the server, absent locally
trackedTracked and intact. Appears only if you filtered by path

Note that summary.modified sums modified and locked, since both denote a file you are working on.

Example: uversion log --json -n 1

[
  {
    "commit_hash": "7f3a9b1c2d...",
    "message": "Fixed lighting in main level",
    "author": "alice",
    "created_at": "2026-05-15T08:30:00Z",
    "files": [
      {
        "path": "Content/Maps/MainLevel.umap",
        "revision_number": 12,
        "file_size": 84934656,
        "is_delete": false
      }
    ]
  }
]

Example: uversion lock list --json

[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "file_id": 12345,
    "file_path": "Content/Maps/MainLevel.umap",
    "user_id": 12,
    "username": "alice",
    "acquired_at": "2026-05-15T08:42:11Z",
    "expires_at": "2126-05-15T08:42:11Z"
  }
]

The 2126 expiry is not a typo: the column cannot stay empty in the database, so the server writes a value pushed a hundred years out. Locks do not expire. Do not show this field to a user and do not build anything on it.

Error handling

On failure, the CLI writes Error: <message> to standard error and exits with code 1. Errors are never emitted as JSON on standard output: with --json, only a success result is structured. In a script, test the exit code, not the output content.

Common patterns

Onboarding a new team member

uversion login https://uversion.mygamestudio.com -u newdev
uversion repos                                  # confirme l'accès
uversion clone hero-rpg ~/Projects/HeroRPG     # download initial

Daily workflow (artist / programmer)

# Début de journée
uversion sync

# Avant d'éditer
uversion checkout Content/Maps/MainLevel.umap

# ... édition dans Unreal Editor ou Rider ...

# Commit en fin de journée
uversion checkin --all -m "Updated main level + hero animations"

Audit script: who reserved what?

lock list --json returns an array at the root. So you iterate with .[], and the fields are username, file_path and acquired_at:

uversion lock list --json | jq -r '.[] | "\(.username)\t\(.file_path)\t\(.acquired_at)"'

The files reserved by a given person:

uversion lock list --json | jq -r '.[] | select(.username == "bob") | .file_path'

Extract the commit hashes

Here too the array is at the root, and the field is called commit_hash:

uversion log --json -n 50 | jq -r '.[].commit_hash'

One person's commits, with their message:

uversion log --json -n 200 \
  | jq -r '.[] | select(.author == "alice") | "\(.commit_hash[0:8])  \(.message)"'

Retrieve an asset at a past revision, without touching the workspace

uversion content expects a revision number, not a commit hash. Find it in the file's history, where it is shown in parentheses:

$ uversion log --path Content/Characters/Hero.uasset -n 10
commit 6e2b8a0...
Author: bob
Date:   2026-05-14 17:22:00 UTC

    Hero pose pass

    Content/Characters/Hero.uasset (rev 8)

$ uversion content Content/Characters/Hero.uasset --revision 8 --output ~/backup/Hero-v8.uasset

Or in one go, for the latest revision of a file:

REV=$(uversion log --json --path Content/Characters/Hero.uasset -n 1 \
  | jq -r '.[0].files[] | select(.path == "Content/Characters/Hero.uasset") | .revision_number')
uversion content Content/Characters/Hero.uasset --revision "$REV" --output ./Hero.uasset

Nightly build in continuous integration

The password goes through UVERSION_PASSWORD, never through -p: a process's command line is readable by the other accounts on the machine.

export UVERSION_PASSWORD="$SECRET_FROM_VAULT"
uversion login "$UV_SERVER" -u ci-nightly
unset UVERSION_PASSWORD

uversion clone hero-rpg ./project
cd project
uversion sync --json > sync.log

# Réserver un fichier pour la durée du cook, et signaler qu'il sert toujours
uversion checkout Content/Cooking/Distribution.uasset
( while pgrep RunUAT; do uversion lock heartbeat; sleep 300; done ) &

# ... build et cook ...

uversion lock release Content/Cooking/Distribution.uasset

Environment variables & exit codes

Environment variables

VariableDescription
UVERSION_PASSWORD Password used by uversion login when the -p option is absent. This is the recommended way to automate a login: unlike a command-line argument, an environment variable is not exposed to the other accounts on the machine. If it is empty or absent, the CLI prompts for the password interactively.
RUST_LOG Log verbosity, written to standard error. For example RUST_LOG=debug. Default level: warn.

No UV_* variable is read. The server and the account come from .uversion/config.toml when you are inside a workspace, and otherwise from the shared configuration (%APPDATA%/uversion/uVersion/config/config.toml on Windows). The session token comes from the system password manager. See Which account acts?.

Exit codes

CodeMeaning
0Success. It is also the code returned by --help and --version, which are not errors.
1Any application error: authentication, permission, network, server, disk write, outside workspace, conflict, validation. The CLI does not distinguish the causes by exit code; the detail is on standard error.
2Argument parsing error: unknown option, missing value, invalid subcommand.

Example in a shell script:

if ! uversion checkin --all -m "Nightly"; then
  echo "Checkin failed, see stderr"
  exit 1
fi