jj-commit-cloud-poc / Every schema

Every schema

The two .proto files that define the wire between jj's client-side stores and the commit-cloud server: two services, fifteen RPCs and forty-three messages, each field with the jj-lib type it converts to and from.

On this page
  1. How the Rust code is generated
  2. The services
  3. BackendService
  4. OpStoreService
  5. backend.proto messages
  6. RegisterRepositoryRequest
  7. RegisterRepositoryResponse
  8. Timestamp
  9. Signature
  10. Commit
  11. ReadCommitRequest
  12. ReadCommitResponse
  13. WriteCommitRequest
  14. WriteCommitResponse
  15. File
  16. TreeValue
  17. TreeEntry
  18. ReadTreeRequest
  19. ReadTreeResponse
  20. WriteTreeRequest
  21. WriteTreeResponse
  22. ReadFileRequest
  23. ReadFileResponse
  24. WriteFileRequest
  25. WriteFileResponse
  26. ReadSymlinkRequest
  27. ReadSymlinkResponse
  28. WriteSymlinkRequest
  29. WriteSymlinkResponse
  30. op_store.proto messages
  31. OperationMetadata
  32. CommitPredecessors
  33. Operation
  34. ReadOperationRequest
  35. ReadOperationResponse
  36. WriteOperationRequest
  37. WriteOperationResponse
  38. RefTargetTerm
  39. RefTarget
  40. RemoteRef
  41. View
  42. ReadViewRequest
  43. ReadViewResponse
  44. WriteViewRequest
  45. WriteViewResponse
  46. GetOpHeadsRequest
  47. GetOpHeadsResponse
  48. UpdateOpHeadsRequest
  49. UpdateOpHeadsResponse
  50. What the wire doesn't carry

In short: BackendService carries the content-addressed objects jj-lib's Backend trait deals in (commits, trees, files, and on paper symlinks), and OpStoreService carries the operation log (operations, views and op heads). Every request names a repository by the UUID the server handed out at registration. On every write, the server computes the object's ID and sends it back. The messages are a trimmed copy of jj's own storage protos: they drop Git refs and tags, and they add the repo ID.

Both files are proto3, in packages commit_cloud.backend and commit_cloud.op_store. For the protos jj itself uses on disk, see /jj/protobufs. That page describes jj's own pinned version, which is newer than the jj-lib 0.43.0 used here.

How the Rust code is generated

The cc-common crate compiles both files at build time. common/build.rs calls tonic_build::configure().compile_protos(&["proto/backend.proto", "proto/op_store.proto"], &["proto"]), which generates prost message structs plus a tonic client and server for each service. common/src/lib.rs includes them as cc_common::backend and cc_common::op_store. That gives types such as cc_common::backend::Commit, the client backend_service_client::BackendServiceClient, and the server trait backend_service_server::BackendService. The same file defines the shared constants both sides rely on (source):

ItemType/SignatureValue
COMMIT_ID_LENGTHusize20
CHANGE_ID_LENGTHusize16
OPERATION_ID_LENGTH, VIEW_ID_LENGTHusize16
ROOT_COMMIT_ID_BYTES, ROOT_CHANGE_ID_BYTES, ROOT_OPERATION_ID_BYTES, ROOT_VIEW_ID_BYTESbyte arraysall zeros, of the lengths above
EMPTY_TREE_ID_HEX / EMPTY_TREE_ID_BYTES&str / [u8; 20]4b825dc642cb6eb9a060e54bf8d69288fbee4904, Git's empty-tree hash

The services

BackendService

backend.proto, lines 6–27. All RPCs are unary except ReadFile, which streams its response.

OpStoreService

op_store.proto, lines 7–22. All six RPCs are unary.

Every handler except RegisterRepository first checks the repo ID is registered and returns NOT_FOUND ("repository should have been registered before …") if it isn't (ensure_repo_registered_error). On the client, every call opens a fresh tonic connection to the configured server URL. The client Backend and server pages cover the calls themselves.

backend.proto messages

Fields are listed in proto order. "jj-lib type" is the jj-lib 0.43.0 type the client converts the field to and from. "—" means the field never becomes a jj-lib value.

RegisterRepositoryRequest

backend.proto, lines 31–33 · converted by client init, server

FieldTypeProto #Meaningjj-lib typeSource
nameoptional string1Optional display name. The client always sends None, and the server ignores the field.—L32

RegisterRepositoryResponse

backend.proto, lines 35–37 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1A random UUID v4 string the server generates. The client saves it in the store's config.toml and sends it on every later request.CommitCloudConfig.repo_idL36

Timestamp

backend.proto, lines 41–44 · converted by signature_to_proto / signature_from_proto

FieldTypeProto #Meaningjj-lib typeSource
millis_since_epochint641Milliseconds since the Unix epoch.MillisSinceEpochL42
tz_offsetint322Time-zone offset in minutes.Timestamp.tz_offsetL43

Signature

backend.proto, lines 46–50 · converted by signature_to_proto / signature_from_proto

FieldTypeProto #Meaningjj-lib typeSource
namestring1Author or committer name.Signature.nameL47
emailstring2Email address.Signature.emailL48
timestampTimestamp3When it was signed. A missing timestamp decodes as zero (unwrap_or_default).TimestampL49

Commit

backend.proto, lines 53–64 · converted by commit_to_proto / commit_from_proto

FieldTypeProto #Meaningjj-lib typeSource
commit_idbytes1Empty when the client writes, because commit_to_proto sets vec![]. The server fills it in with the ID it computes. If a request carries a non-empty ID, the server stores the commit under that ID without checking it.CommitId (the key, not a field)L54
change_idbytes2The change ID, 16 bytes.Commit.change_idL55
parent_commit_idsrepeated bytes3Parent commit IDs, in order.Commit.parentsL56
root_tree_idrepeated bytes4One tree ID per term of the root tree. A resolved tree has exactly one.Commit.root_tree (Merge<TreeId>)L57
descriptionstring5The commit message.Commit.descriptionL58
authorSignature6Author signature.Commit.authorL59
committerSignature7Committer signature.Commit.committerL60
predecessorsrepeated bytes8Commits this one rewrote (deprecated in jj-lib in favour of the operation's commit_predecessors).Commit.predecessorsL61
conflict_labelsrepeated string9Labels for each term of a conflicted tree. Converted with ConflictLabels::from_vec.Commit.conflict_labelsL62
secure_sigoptional bytes10Signature bytes, when the commit is signed. On read the client always sets secure_sig: None, so signatures don't survive a round trip, and write_commit ignores its signing function.SecureSigL63

ReadCommitRequest

backend.proto, lines 66–69 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L67
commit_idbytes2The 20-byte commit ID. The client answers the root commit locally and never asks for it.CommitIdL68

ReadCommitResponse

backend.proto, lines 71–73 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
commitCommit1The stored commit. A missing commit is a NOT_FOUND status, which the client maps to BackendError::ObjectNotFound.CommitL72

WriteCommitRequest

backend.proto, lines 75–78 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L76
commitCommit2The commit to store, with commit_id empty.CommitL77

WriteCommitResponse

backend.proto, lines 80–82 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
commit_idbytes1The ID the server computed with compute_git_commit_hash. The client uses it as the commit's ID.CommitIdL81

File

backend.proto, lines 86–90 · converted by tree_entry_to_proto / tree_entry_from_proto

FieldTypeProto #Meaningjj-lib typeSource
idbytes1Content ID of the file's blob.FileIdL87
executablebool2Executable bit.boolL88
copy_idbytes3Copy-tracking ID. On read it is kept only if it is exactly 20 bytes, and otherwise replaced with 20 zero bytes (the ROOT_COMMIT_ID_BYTES constant).CopyIdL89

TreeValue

backend.proto, lines 92–99 · converted by tree_entry_to_proto / tree_entry_from_proto

FieldTypeProto #Meaningjj-lib typeSource
fileFile (oneof value)1A regular file.TreeValue::FileL94
symlink_idbytes (oneof value)2A symlink. It can be stored in a tree, but the symlink content RPCs are not implemented.TreeValue::SymlinkL95
tree_idbytes (oneof value)3A subdirectory.TreeValue::TreeL96
conflict_idbytes (oneof value)4No counterpart in jj-lib 0.43's TreeValue. The client never writes it and rejects it on read ("tree entry should not have contained a ConflictId"). Git submodules have no field here, and the client rejects them on write with BackendError::Unsupported.—L97

TreeEntry

backend.proto, lines 101–104 · converted by tree_entry_to_proto / tree_entry_from_proto

FieldTypeProto #Meaningjj-lib typeSource
namestring1One path component.RepoPathComponentBufL102
valueTreeValue2What the name points at.TreeValueL103

ReadTreeRequest

backend.proto, lines 106–110 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L107
tree_idbytes2The tree's ID. The server answers the well-known empty-tree ID itself, without storage.TreeIdL108
pathstring3The directory's repo path. The client sends it, and the server ignores it.RepoPathL109

ReadTreeResponse

backend.proto, lines 112–115 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
tree_idbytes1Echo of the requested ID.TreeIdL113
entriesrepeated TreeEntry2The tree's entries.TreeL114

WriteTreeRequest

backend.proto, lines 117–121 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L118
pathstring2The directory's repo path. Sent, and ignored by the server.RepoPathL119
entriesrepeated TreeEntry3The tree's entries, in the order jj-lib gives them.TreeL120

WriteTreeResponse

backend.proto, lines 123–125 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
tree_idbytes1The ID the server computed with compute_git_tree_hash.TreeIdL124

ReadFileRequest

backend.proto, lines 129–132 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L130
file_idbytes2The blob's ID.FileIdL131

ReadFileResponse

backend.proto, lines 134–136 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
chunkbytes1File content. The RPC is server-streaming, but the server sends the whole file as one chunk.file bytesL135

WriteFileRequest

backend.proto, lines 138–141 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L139
contentbytes2The whole file in one message. Both the .proto and the server carry a TODO to make this streaming so files over 4MB don't hit gRPC's message size limit.file bytesL140

WriteFileResponse

backend.proto, lines 143–145 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
file_idbytes1The ID the server computed with compute_git_blob_hash.FileIdL144

ReadSymlinkRequest

backend.proto, lines 149–152 · converted by server (unimplemented)

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L150
symlink_idbytes2The symlink's ID.SymlinkIdL151

ReadSymlinkResponse

backend.proto, lines 154–156 · converted by server (unimplemented)

FieldTypeProto #Meaningjj-lib typeSource
targetstring1The link target.StringL155

WriteSymlinkRequest

backend.proto, lines 158–161 · converted by server (unimplemented)

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L159
targetstring2The link target.StringL160

WriteSymlinkResponse

backend.proto, lines 163–165 · converted by server (unimplemented)

FieldTypeProto #Meaningjj-lib typeSource
symlink_idbytes1The new symlink's ID.SymlinkIdL164

op_store.proto messages

The proto comments flag one subtlety that drives the server's hashing: map fields (attributes, wc_commit_ids, local_bookmarks, remote_bookmarks) serialise in no particular order, so "The server MUST perform key-sorted (or some other deterministic) hashing scheme to generate view_id" (source). The object IDs page shows how it does that.

OperationMetadata

op_store.proto, lines 88–97 · converted by operation_to_proto / operation_from_proto

FieldTypeProto #Meaningjj-lib typeSource
start_time_millisint641Start time. Only the milliseconds are sent: the time-zone offset is dropped and reads back as 0.TimestampRange.startL89
end_time_millisint642End time, likewise without its offset.TimestampRange.endL90
descriptionstring3What the operation did, for example describe commit ….OperationMetadata.descriptionL91
is_snapshotbool4Whether this operation only snapshotted the working copy.OperationMetadata.is_snapshotL92
workspace_nameoptional string5The workspace that ran it, if any.Option<WorkspaceNameBuf>L93
hostnamestring6Host that ran it.OperationMetadata.hostnameL94
usernamestring7User that ran it.OperationMetadata.usernameL95
attributesmap<string, string>8Free-form key/value tags. A proto map, so its wire order isn't deterministic. The proto comment says the server must hash it in a deterministic order.BTreeMap<String, String>L96

CommitPredecessors

op_store.proto, lines 99–102 · converted by operation_to_proto

FieldTypeProto #Meaningjj-lib typeSource
commit_idbytes1A commit created by the operation.CommitIdL100
predecessor_idsrepeated bytes2The commits it replaced.Vec<CommitId>L101

Operation

op_store.proto, lines 104–110 · converted by operation_to_proto / operation_from_proto

FieldTypeProto #Meaningjj-lib typeSource
view_idbytes1The view this operation produced.ViewIdL105
parentsrepeated bytes2Parent operations.Vec<OperationId>L106
metadataOperationMetadata3See OperationMetadata. A missing one decodes as all defaults.OperationMetadataL107
commit_predecessorsrepeated CommitPredecessors4One entry per rewritten commit.Option<BTreeMap<CommitId, Vec<CommitId>>>L108
commit_predecessors_setbool5Whether the map is present at all. A repeated field can't tell None from an empty map, so this flag does.Option::is_someL109

ReadOperationRequest

op_store.proto, lines 26–29 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L27
operation_idbytes2The 16-byte operation ID. The server synthesises the root operation if it isn't stored.OperationIdL28

ReadOperationResponse

op_store.proto, lines 31–33 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
operationOperation1The stored operation.OperationL32

WriteOperationRequest

op_store.proto, lines 35–38 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L36
operationOperation2The operation to store.OperationL37

WriteOperationResponse

op_store.proto, lines 40–42 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
operation_idbytes1The ID the server computed with hash_operation.OperationIdL41

RefTargetTerm

op_store.proto, lines 114–116 · converted by ref_target_to_proto / ref_target_from_proto

FieldTypeProto #Meaningjj-lib typeSource
commit_idbytes1One commit in a ref target's add or remove list.CommitIdL115

RefTarget

op_store.proto, lines 118–121 · converted by ref_target_to_proto / ref_target_from_proto, rebuilt with RefTarget::from_legacy_form

FieldTypeProto #Meaningjj-lib typeSource
removesrepeated RefTargetTerm1The removed terms of a possibly conflicted ref.RefTarget::removed_idsL119
addsrepeated RefTargetTerm2The added terms. A plain bookmark has one add and no removes.RefTarget::added_idsL120

RemoteRef

op_store.proto, lines 123–126 · converted by view_to_proto, view_from_proto

FieldTypeProto #Meaningjj-lib typeSource
targetRefTarget1Where the bookmark points on the remote. Missing reads as absent.RefTargetL124
is_trackedbool2Tracked or not.RemoteRefState::Tracked / NewL125

View

op_store.proto, lines 132–137 · converted by view_to_proto / view_from_proto

FieldTypeProto #Meaningjj-lib typeSource
head_idsrepeated bytes1Visible head commits.HashSet<CommitId>L133
wc_commit_idsmap<string, bytes>2Working-copy commit per workspace name.BTreeMap<WorkspaceNameBuf, CommitId>L134
local_bookmarksmap<string, RefTarget>3Local bookmarks by name.BTreeMap<RefNameBuf, RefTarget>L135
remote_bookmarksmap<string, RemoteRef>4Remote bookmarks, flattened into one map keyed "bookmark@remote". On read the key is split at the last @, so a remote name containing @ would not round-trip.remote_views[remote].bookmarks[bookmark]L136

ReadViewRequest

op_store.proto, lines 46–49 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L47
view_idbytes2The 16-byte view ID. The server synthesises the root view if it isn't stored.ViewIdL48

ReadViewResponse

op_store.proto, lines 51–54 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
view_idbytes1Echo of the requested ID.ViewIdL52
viewView2The stored view.ViewL53

WriteViewRequest

op_store.proto, lines 56–59 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L57
viewView2The view to store.ViewL58

WriteViewResponse

op_store.proto, lines 61–63 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
view_idbytes1The ID the server computed with hash_view.ViewIdL62

GetOpHeadsRequest

op_store.proto, lines 67–69 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L68

GetOpHeadsResponse

op_store.proto, lines 71–73 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
op_head_idsrepeated bytes1Current operation heads. A repository with none yet reports the root operation's ID.Vec<OperationId>L72

UpdateOpHeadsRequest

op_store.proto, lines 75–79 · converted by client, server

FieldTypeProto #Meaningjj-lib typeSource
repo_idstring1The repository UUID.—L76
old_op_head_idsrepeated bytes2Heads the new operation replaces.&[OperationId]L77
new_op_head_idbytes3The new head.OperationIdL78

UpdateOpHeadsResponse

op_store.proto, lines 81–83 · converted by server

FieldTypeProto #Meaningjj-lib typeSource
current_op_head_idsrepeated bytes1The heads after the update, as the store reports them.Vec<OperationId>L82

What the wire doesn't carry

Converting jj-lib's types to these messages loses some information. Each item below is visible in the conversion code:

  • Tags, Git refs and Git HEAD. View has no fields for them. view_from_proto fills local_tags and git_refs with empty maps and git_head with an absent target, and view_to_proto never reads a remote's tags (source). The proto comment says the Git fields were omitted on purpose.
  • Operation time zones. Only the millisecond timestamps are sent. Both offsets read back as 0 (source).
  • Commit signatures. Written if present, but always read back as None (source).
  • Copy IDs that aren't 20 bytes long are replaced with zeros on read (source). Copy history itself isn't supported (read_copy, write_copy and get_related_copies return Unsupported).
  • Symlink contents and Git submodules. Symlinks can appear in trees, but their targets can't be stored or read. Submodules are rejected outright.