How to think about TouchQuill
Before you type your first command, three ideas are worth internalizing. The rest of these docs keep coming back to them.
1. Content has an address, not a name
Every file, directory tree and commit is stored under an address computed from its content (a BLAKE3 hash). The consequences are practical: an identical texture used in two places is stored once; the server only sends objects you don't already have; and if anything gets corrupted in transit, the client notices because the hash won't match. You don't have to take integrity on faith: you can compute it.
2. You don't merge binaries: you take them
Textures, meshes and blueprints have no meaningful merge. Rather than pretend otherwise, TouchQuill builds locking into the core: whoever holds the lock commits; the server rejects a push touching a locked file from anyone else. The queue means "waiting for a file" no longer involves pinging people on Slack.
3. The change matters more than the commit
A commit is a technical record; the logical change (Change ID, ch-…) lives longer: it survives fix-ups (amend), message edits and rebases. Reviews, locks and history refer to something stable. On top of that sits the operation log: almost anything can be undone with tq undo, including recovering unsaved work after an accidental restore.
Install and first commit
The client is a single tq binary (Linux, Windows, macOS). You only need the server, tq-server, once you want to share work.
# Linux: rpm package · Windows: installer · or build from source: cargo build --release -p tq-cli
tq init MyGame echo "asset" > Content/hero.txt tq commit -m "first asset" tq log # ch-4f2a91 rev:1 first asset (you) tq status # what changed since the last commit
Made a mistake? Nothing is lost:
tq restore # back to the last committed state tq undo # ...and if restore itself was the mistake, get your work back
undo can bring back even uncommitted files.Working with a team
tq clone https://vcs.studio.com MyGame # fresh clone tq login --user ola # prompts for a password (set by your admin) tq push tq pull
You log in like a person: username and password. Under the hood the server exchanges the password for a session token you never see; explicit tokens (tq login tqt_…) remain for CI and scripts. Your identity on the server comes from the login, never from what the client claims, which is why nobody can impersonate a teammate in history or in the audit log. Push is always fast-forward: if someone got there first, pull first.
Artists: it's all clicks in Studio
An artist doesn't need to know a single command. The whole day is three clicks in the Studio app, and everything that happens "under the hood" below takes care of itself.
1. Sync
Open Studio and click Sync. It pulls the team's latest state. If the studio tracks builds, one toggle grabs the last stable revision instead of the very latest, one that's guaranteed to open in the editor.
2. Take the file you'll edit
Before you touch a file, select it and click Reserve. From that moment it's yours alone and no one can overwrite it. If someone's already holding it, you see who, and one click puts you in the queue; Studio notifies you the moment it frees up.
3. Send your changes
Done? Type a short description and click Send. Studio saves the change, uploads it to the server and releases the reservation, all in one move. The next person in the queue gets the file automatically.
Interrupted mid-work? Park the unfinished change with the Save for later button, no clutter in the history, and come back to it whenever you like. Every one of these clicks is, underneath, the same operation a programmer runs with tq commands (see below), the artist just never has to see them.
Programmers
A channel per task
A task channel inherits from main, but your commits don't affect anyone until you're done.
tq channel create task/GROM-447 --type task tq channel switch task/GROM-447
Commit freely, fix boldly
amend folds a fix into the last commit, reword edits the message. The Change ID stays the same, so nothing gets lost.
tq commit -m "dodge system"
tq amend # fix-up on the same changeStay current with main
integrate pulls in the parent's changes. A conflict won't stop you: it gets recorded, and you decide when and how to resolve it.
tq integrate
tq conflicts # what's pending
tq conflict resolve cfl-a1 --take theirsClose the task
complete merges the channel into its parent and closes it. The task's history stays readable.
tq complete
Managers: releases and order
A release channel with strict rules
No commits with unresolved conflicts land on a release, and only a chosen few can push. The policy lives in a versioned file: changing it goes through history like everything else.
tq channel create release/1.0 --type release
# tq-channel.toml:
[permissions]
write = ["lead", "server-admin"]
integrate = ["lead"]Lock disputes
Someone left a lock and went on vacation? Request a hand-over, or take it if you must. Every such action lands in the audit log, with the reason.
tq lock --request Content/Boss.uasset
tq steal Content/Boss.uasset # requires lock.stealReview before you close
A channel can require approvals before complete goes through. Reviewers comment and approve/reject, from the CLI or the web panel. An approval is tied to a specific state: a new push invalidates old approvals, so nobody sneaks changes past review.
# tq-channel.toml: [review] require-approvals = 2 # reviewer: tq review approve tq review comment "rename this variable" --path Combat.cpp --line 42
Conflicts under control
Conflicts can be assigned to people and given deadlines. Overdue ones escalate on their own: nothing drowns in the noise.
tq conflict assign cfl-a1 marta tq conflict defer cfl-b2 --deadline 48h
Who did what
The audit log records administrative actions and permission-denied attempts. Verifying its integrity is one command.
tq-server audit log --denied tq-server audit verify
CI and builds
An account with exactly the rights it needs
CI gets a PAT token limited to specific permissions: even if it leaks, it can't do anything beyond its scope.
tq-server user pat ci-build --user ci --scopes repo.read,build-flag.set
Build and mark
After a successful build, CI flags the revision. That flag means "this version works".
tq build-flag set --rev rev:128 --flag ci --state ok
The team takes stable
An artist never has to land on a programmer's broken commit: get stable fetches the newest revision with all required flags green.
tq get stable
Channels
A channel is a line of work: the counterpart of a branch, but with a type and a policy. The type carries sensible defaults: a release rejects conflicted commits, a task has an owner and a lifecycle (create → integrate → complete), personal is your sandbox.
The virtual channel is worth knowing: a view onto a slice of the repo. Your art team can get just Content/: as far as they're concerned the code doesn't exist, yet their commits land in shared history with the invisible files preserved.
tq channel create artview --type virtual --include "Content/..." --exclude "Source/..."
For working on a slice of a large repo there's also sparse checkout: you fetch and materialize only the matching paths, the rest never touches your disk. A character artist doesn't have to pull every map.
tq clone https://vcs.studio.com Game --sparse "Content/Characters/...,Content/Shared/..."
tq sparse set "Content/Characters/..." # narrow/widen in an existing clone
Locks
The model is simple: one owner, everyone else queues. Locks have an inactivity timeout (4 h by default). If you forget to release one and go home, the team isn't blocked overnight. You can ask the owner to hand a lock over (--request) or pass yours to a specific person (--give).
The server enforces locks at push: if the channel policy says *.uasset requires a lock, a push touching such a file without one is rejected with a clear message. Offline work is covered too: declare a lock locally, sync when you're back; if two people took the same file offline, a dispute blocks both sides until a lead decides. Nobody silently overwrites anyone's work.
Conflicts
In most systems a conflict is a wall: the merge stops and you drop everything to defuse it. In TouchQuill a conflict is a stored object (cfl-…) with full context: both versions, the common ancestor, who and when. Integration always completes; you resolve conflicts when you have room for them, or a lead delegates them.
tq conflict resolve cfl-a1 --take theirs # accept their version tq conflict resolve cfl-a1 --merge # three-way merge via plugin (if the format has a differ)
Change ID
A commit hash changes with every fix-up; a Change ID doesn't. The distinction sounds subtle, but in practice it means a review comment saying "fix this in ch-4f2a91" stays valid after your amend and rebase. The operation log (tq op log) completes the picture: every operation leaves a trace and most can be undone.
Lens
Classic blame answers "who changed line 40 of file X". Lens answers the question you actually have: "what happened to this function?", even when the file was renamed and the function moved between modules. That includes Unreal Blueprints (functions, events and macros from .uasset files). Cosmetic edits (formatting, comments) don't pollute the result.
tq lens blame --symbol calculate_damage
CREATED ch-6f9 rev:1 krzysztof "first version"
MODIFIED ch-a12 rev:3 marta "damage buff"
MOVED+RENAMED ch-c73 rev:5 krzysztof (compute_damage from Weapon.rs) [~86%]
Symbols come from a real parser (tree-sitter) for C++, C#, C, Rust, Python, Lua, Go, Java, JS/TS, with class methods and qualified names (Enemy::TakeDamage), plus a fallback extractor for Verse (UEFN), GDScript and shaders. For binary formats there is a plugin mechanism (a WASM host), and Blueprint support is on the development roadmap. Matching is fuzzy: a rename together with a body edit gets a confidence score ([~86%]), and a move spread across several commits within a week is stitched into one history. When the heuristic is unsure, it flags the result as ambiguous: you resolve it with tq lens link, or with one click in Studio. tq lens trace shows the full life of a symbol across every name it ever had. Full Lens writeup: languages, matching and limits →
tq lens blame --json. On the roadmap: Lens for Blueprints (symbols in graphs and .uasset files) and plugins for full Visual Studio and JetBrains Rider.Best practices
A few habits that make the biggest difference in practice:
- Lock before opening, not before saving. The most common way to lose work is an hour of editing a file someone else holds. Make it a reflex: binary file →
tq lockfirst. - Enforce locks with policy, not culture. Put
require-lock-for = ["*.uasset", "*.fbx"]intq-channel.toml. Human memory fails; the server doesn't. - Commit often: it costs nothing. With content-addressed storage a commit takes milliseconds even in a big repo, and every commit is a point you can return to.
- Shelve instead of "wip" commits. Unfinished work doesn't have to dirty the channel history. Shelves expire on their own and can be shared for review.
- One task: one task channel. Small channels integrate easily. A multi-week omnibus will hurt in any version control system.
- CI sets flags, the team runs
get stable. The cheapest way to make sure artists never land on a broken build. - Assign or defer conflicts: don't leave them hanging. With
assign/deferevery conflict has an owner and a deadline, and escalation kicks in by itself. - Turn on fsmonitor on dev machines.
tq fsmonitor startonce per session and status/commit stop depending on tree size. Works on Linux and Windows.
Migrating from Git and Perforce
Both migrations preserve history: authors, timestamps, messages. Change IDs are derived deterministically from the source, and Lens works on imported history from day one. A sensible order: migrate a copy, verify it, switch the team over, and keep the old repo read-only for a transition period.
tq migrate from-git --repo /path/to/repo --branch main tq migrate from-p4 --port perforce:1666 --user krzysztof --path //depot/... tq migrate verify --repo /path/to/repo
Moving a big studio doesn't have to be a single jump. After the first import, tq migrate p4-sync pulls only the new changelists from Perforce: during the migration P4 stays the source of truth and TouchQuill mirrors it.
Server and permissions
The server exposes gRPC (clients), REST (integrations) and SSE (a live event feed). Authentication switches on with the first user you add: before that it runs in open mode, handy for trying things out.
tq-server --data /var/tq --listen 0.0.0.0:7470 --tls-cert cert.pem --tls-key key.pem tq-server user add krzysztof --role developer
http://server:7480/admin. Log in with a password and manage users (add, roles, passwords, PATs), browse repositories (revision history, channels, locks with queues and conflicts) and the audit log with one-click hash-chain verification. There's also a repository browser (the file tree of any revision, content preview and diffs) available to anyone with read access, no cloning required. Same permissions as the CLI; every change lands in the audit log.Seven built-in roles (from viewer to server-admin), custom roles cloned with permissions subtracted, path restrictions for contractors ("only Content/Weapons/..."), a Perforce-style protections table (rules per repo × path, e.g. "artists can't write to Content/Code/..."). Read rules apply on the wire too: a file you're not allowed to read simply never reaches your disk. PATs with scopes for automation, per-channel overrides, and, for password login, a complexity policy, lockout after repeated failures and optional 2FA (TOTP).
Scale is handled gradually, without a rewrite. A small studio starts on a single file (SQLite) and a daily directory copy. A larger one switches metadata to PostgreSQL (one environment variable; CAS data stays put), and a remote office runs an Edge Proxy: a read-through object cache on the local network. Add regional replication (locks stay on the primary, one source of truth), server-to-server mTLS, one-command backups, and cryptographically signed releases.
Command reference
| Command | Description |
|---|---|
tq init / clone / setup | create / clone / configure a repo |
tq add / edit / status / diff | worktree changes |
tq commit / amend / reword | record a revision (stable Change ID) |
tq log / show / cat / restore | history and navigation |
tq rebase / revert | rebase / inverse revision |
tq op log / undo / redo | operation log and undo |
tq channel … | create / switch / list / policy |
tq integrate / complete | integrate and close a channel |
tq lock / unlock / locks | queue-based locks, request / give |
tq conflict(s) … | resolve / assign / defer |
tq shelf … | shelve work, share, TTL |
tq lens blame / trace / symbols | symbol history and full lineage |
tq lens link / reindex / index | manual stitch, rebuild and top-up of the index |
tq review comment / approve / reject | comments and approvals (gate on complete) |
tq sparse set / show / clear | work on a slice of the repo |
tq rename | rename the repository |
tq build-flag / get stable | build flags and the stable revision |
tq workspace … | save / sync / offline / mount / presence |
tq migrate from-git / from-p4 / p4-sync | history import and incremental P4 mirror |
tq fsmonitor start / stop / status | file-change daemon (Linux / Windows / macOS) |
tq notify … | inbox, DND, digests |
tq plugin … | WASM plugins: extractor / differ |
tq admin obliterate / channel | permanent content removal, permission overrides |
The full technical specification and architecture decision records (ADRs) are available on request.