In short: main.rs is 47 lines. It hands CliRunner a set of store factories named
commit_cloud and a cc subcommand. jj cc init runs jj-lib's ordinary
workspace initialisation with the commit-cloud stores swapped in. After that, every other command is
jj-cli's own: it finds commit_cloud in the repo's type files and reads and writes
through gRPC without knowing it.
main.rs
main builds the runner in three calls:
| Call | What it adds | Source |
|---|---|---|
CliRunner::init() | Stock jj-cli, including its default store factories (local, Git, simple). | cli_util.rs |
.add_store_factories(create_store_factories()) | A StoreFactories::empty() with only the commit-cloud backend, op store and op heads store, all named commit_cloud. jj-cli merges these into its defaults, so this binary still opens ordinary jj repositories. | L23–27, repo.rs, merge |
.add_subcommand(run_custom_command) | The cc command group, whose only member is init. The handler ignores the Ui and CommandHelper jj-cli passes it. | L9–21, L29–39 |
The index store, submodule store and working copy are jj-lib's defaults. Only commits, trees, files, operations, views and op heads live on the server.
jj cc init
Arguments (CcInitArgs):
| Argument | Default | Effect |
|---|---|---|
--server <URL> | required | The gRPC endpoint, for example http://localhost:8080. Stored in config.toml. |
--create | off | Ignored: let _ = args.create; (L34). A TODO asks for a server-side guard (L22). Every init registers a new repository, flag or not. |
[DESTINATION] | . | The workspace root. It must exist and must not already contain .jj. |
Step by step
-
Build
UserSettingsfromStackedConfig::with_defaults()and aSignerfrom them (L37–42). This is jj's built-in default config only. The user's config files aren't read by this command, so the first operation and working-copy commit don't carry the user's name or settings. -
Define three initialiser closures (L44–67). The backend one calls
CommitCloudBackend::init. The op store and op heads store ones call theirload, because they have nothing to create. -
Call jj-lib's
Workspace::init_with_factorieswith those closures and the default index, submodule and working-copy factories (L69–83). From here jj-lib drives:- Create
.jj/(create_jj_dir). If it already exists, init fails withDestinationExists. ReadonlyRepo::initcreates.jj/repo/store/and runs the backend closure. It callsRegisterRepository, receives a new UUID, and writesstore/config.tomlwithserver_urlandrepo_id. jj-lib then writesstore/type=commit_cloud.- It creates
op_store/andop_heads/, runs their closures and writes theirtypefiles. Both find the config through the../store/config.tomlfallback (util.rs), which works only because the backend closure has already run. The op heads store ignores the root operation ID it is given, and nothing is written for it on the server. An empty repository already reports the root as its head. - It creates the local
index/andsubmodule_store/, then loads the root operation and its view from the server. init_working_copystarts a transaction and checks out a new empty commit on top of the root. Committing the transaction writes the commit, the view and the operation "add workspace 'default'", then publishes it.- The local working-copy state is written under
.jj/working_copy/, and the workspace is recorded.
- Create
- Print "Initialized remote Commit Cloud repository" (L85).
Any error is turned into a user_error. If anything inside
init_with_factories fails, jj-lib deletes the new .jj/
(L338–341). The repository it may already have
registered on the server stays registered, and nothing ever unregisters it.
What crosses the wire
The sequence below shows what jj cc init sends, in order. Every RPC opens its own connection
(see client-backend). It leaves
out the backend's commit reads.
Commit, view and operation IDs are computed or accepted as described in
object-ids. The op-heads lock taken before
UpdateOpHeads is a no-op (L89–91).
Stock commands on commit-cloud stores
Once the workspace exists, jj describe, jj log, jj op log and the rest
are jj-cli's code, unchanged (see /jj/cli). Loading the workspace reads each
type file under .jj/repo/ and looks the name up in the merged factories. For
commit_cloud, each factory's load reads config.toml and makes no
network call (repo.rs). From then on, every
Backend, OpStore and OpHeadsStore call jj-lib makes becomes an
RPC. The working copy and index stay local.
A command that changes the repository ends the same way init does: a transaction writes its new
objects, then the view (write_view),
then the operation (write_operation),
then publishes with update_op_heads.
So WriteView, WriteOperation and UpdateOpHeads close every write, in that
order. A snapshot of a changed file adds WriteFile and WriteTree before the
commit. The upstream tests exercise this with describe, log and op log
(see testing).
Commands that need Git or other backends' features aren't covered by anything upstream.
What the binary doesn't do
--createdoes nothing, and there is no way to attach a second workspace to an existing remote repository:cc initalways registers a new one.cc inituses jj's default config, not the user's.- A failed init can leave an orphan registration on the server.
- Concurrent writers from two workspaces lose operations silently (client-op-store).
- The binary is named
jj, so installing it shadows a stock jj on thePATH.