diff --git a/api/proto/objects/options.proto b/api/proto/objects/options.proto new file mode 100644 index 0000000000..08b7a1deff --- /dev/null +++ b/api/proto/objects/options.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package jj_api.objects; + +// Leaving this as a proto in case we come up with some sort of revset-style +// syntax for operations. +message OperationRef { + string id = 1; +} + +enum Snapshot { + SNAPSHOT_UNSPECIFIED = 0; + NO_SNAPSHOT = 1; + SNAPSHOT = 2; +} + +message GlobalOptions { + // Generally prefer providing working_dir over repo_path, since working_dir + // allows jj to infer the path if you're not at the repo root. + string working_dir = 1; + string repo_path = 2; + + // Technically this could be a boolean, but this forces users to choose either + // SNAPSHOT or NO_SNAPSHOT. + Snapshot snapshot = 3; + + OperationRef operation = 4; + + // If true, user configuration will be loaded. In particular: + // * User revset aliases will be used + // * User template aliases will be used + bool use_user_config = 5; + + // Path to additional config files. + // Will be applied after the user configuration, if use_user_config is set. + repeated string extra_configs = 6; + + bool ignore_immutable = 7; +} diff --git a/api/proto/objects/revision.proto b/api/proto/objects/revision.proto new file mode 100644 index 0000000000..297575dde4 --- /dev/null +++ b/api/proto/objects/revision.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +package jj_api.objects; + +// A RevisionMask controls how much of a revision is returned. +// For example, `jj log` will likely not care about files, while `jj status` +// will want to know what files changed in a revision, and `jj diff` will want +// to know both the content of the file and the content of the file in the +// parent revision. +message RevisionMask { + // Which files will be returned. + enum FilePathMask { + NONE = 0; + MODIFIED_FILES = 1; + ALL_FILES = 3; + // Include all files that were matched by the RevisionMask containing this + // RevisionMask. For example, if we had: + // RevisionMask( + // files_to_include = MODIFIED_FILES, + // files = FileMask(content = True), + // parents = RevisionMask( + // files_to_include = PARENT_FILES + // files = FileMask(content = True), + // ), + // ) + // And we returned the revision @, which modfied the file foo, then both + // r.files["foo"].content and r.parent.files["foo"].content would be filled. + PARENT_FILES = 4; + }; + FilePathMask files_to_include = 1; + repeated string additional_files = 2; + + bool rendered = 3; + + bool file_content = 4; + bool file_hash = 5; + bool file_metadata = 6; + + // How much of the parent revision to fill in. + RevisionMask parents = 7; +} + +message File { + // If you query for MODIFIED_FILES, you may get a file that's been deleted. + bool exists = 1; + string hash = 2; + bytes content = 3; + // TODO: file metadata (eg. permissions)? +} + + +// By default, +message Revision { + string change_id = 1; + string commit_id = 2; + string description = 3; + bool empty = 4; + bool conflicts = 5; + bool mutable = 6; + + // The rendered template. + // Open question: Is a template a first-class citizen of jj, or is it specific + // to jj-cli? + string rendered = 7; + + map files = 8; + + // revision.parents[*].parents is always empty, to avoid recursion. + repeated Revision parents = 9; +} \ No newline at end of file diff --git a/api/proto/objects/revset.proto b/api/proto/objects/revset.proto new file mode 100644 index 0000000000..78a6e4373d --- /dev/null +++ b/api/proto/objects/revset.proto @@ -0,0 +1,72 @@ +syntax = "proto3"; + +package jj_api.objects; + +message Revset { + oneof kind { + // Should ideally only be used when the user types in their own revset. + // Otherwise, construct it using this proto. + string revset = 1; + + // Primitives that we can look up directly. + string change_id = 2; + string commit_id = 3; + + // Operators + Union union = 4; + Intersection intersection = 5; + Revset not = 6; + Between between = 7; + + // Functions + Revset parents = 8; + Revset children = 9; + Ancestors ancestors = 10; + // Descendants is skipped (use Between(from=x, to=None, inclusive=True) + Reachable reachable = 12; + // Connected is skipped (use Between(from=x, to=x, inclusive=True) + bool all = 13; // Use bool for a function with no parameters + bool none = 14; // Use bool for a function with no parameters + Branches branches = 15; + RemoteBranches remote_branches = 16; + + // You get the point. Each builtin function is implemented as a oneof in + // this message. + } +} + +message Union { + repeated Revset srcs = 1; +} + +message Intersection { + repeated Revset srcs = 1; +} + +// Equivalent to from::to when inclusive is set, or from..to when not set. +message Between { + optional Revset from = 1; + optional Revset to = 2; + // If false, equivalent to .. + // If true, equivalent to :: + bool inclusive = 3; +} + +message Ancestors { + Revset srcs = 1; + int32 depth = 2; +} + +message Reachable { + Revset srcs = 1; + Revset domain = 2; +} + +message Branches { + string pattern = 1; +} + +message RemoteBranches { + string branch_pattern = 1; + string remote_pattern = 2; +} \ No newline at end of file diff --git a/api/proto/rpc/read_revisions.proto b/api/proto/rpc/read_revisions.proto new file mode 100644 index 0000000000..abb432191b --- /dev/null +++ b/api/proto/rpc/read_revisions.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +package jj_api.rpc; + +import "objects/options.proto"; +import "objects/revset.proto"; +import "objects/revision.proto"; + +message RevisionsRequest { + // Since this is a read-only request, we don't want to require the user to + // start a transaction. + oneof state { + string transaction_id = 1; + jj_api.objects.GlobalOptions repo_state = 2; + } + + jj_api.objects.RevisionMask revision_mask = 3; + + jj_api.objects.Revset revisions = 4; + + int32 limit = 5; + // TODO: We have three options here: + // 1) Make this a string, and return the string + // 2) Make this similar to Revset, where we can construct it + // 3) Remove this from the API and put it in jj-cli entirely. Templates would, + // instead of formatting a Commit object, format a Revision proto. + // I don't really like option 2, since it seems that the caller of this could + // always just do that themselves in whatever the language is calling this + // API. + // Option 1 and Option 3 both seem reasonable, and mainly depend on whether we + // think that jj-cli will be the only user of templates. + // I personally am leaning towards option 3, because I think that an extension + // may quite reasonably want to, for example, take advantage of a user's + // jj config file containing custom templates or template aliases. + string template = 6; +} + +message ListRevisionsResponse { + repeated jj_api.objects.Revision revisions = 1; + + // This is useful if you want to perform, for example, `jj diff` between two + // revisions. + string operation_id = 2; +} + +message GetRevisionResponse { + jj_api.objects.Revision revision = 1; + + // This is useful if you want to perform, for example, `jj diff` between two + // revisions. + string operation_id = 2; +} \ No newline at end of file diff --git a/api/proto/rpc/transaction.proto b/api/proto/rpc/transaction.proto new file mode 100644 index 0000000000..bb089d8e90 --- /dev/null +++ b/api/proto/rpc/transaction.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package jj_api.rpc; + +import "objects/options.proto"; + +message BeginTransactionRequest { + jj_api.objects.GlobalOptions state = 1; + + // If EndTransaction is not called within timeout_seconds, then the daemon + // will abandon the transaction. + uint32 timeout_seconds = 2; +} + +message BeginTransactionResponse { + string transaction_id = 1; +} + +message EndTransactionRequest { + string transaction_id = 1; +} + +message EndTransactionResponse { + string operation_id = 1; +} \ No newline at end of file diff --git a/api/proto/services/service.proto b/api/proto/services/service.proto new file mode 100644 index 0000000000..66515798a9 --- /dev/null +++ b/api/proto/services/service.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +import "rpc/read_revisions.proto"; +import "rpc/transaction.proto"; + +package jj_api.services; + +// All jj things will go into this service. +service JjService { + rpc BeginTransaction(jj_api.rpc.BeginTransactionRequest) returns (jj_api.rpc.BeginTransactionResponse) {} + rpc EndTransaction(jj_api.rpc.EndTransactionRequest) returns (jj_api.rpc.EndTransactionResponse) {} + + // This RPC will be used by `jj log` + rpc ListRevisions(jj_api.rpc.RevisionsRequest) returns (jj_api.rpc.ListRevisionsResponse) {} + + // This RPC will be used by: + // * cat + // * diff + // * files + // * show + // * status + // It only differs from `ListRevisions` in that it throws an error if there + // isn't exactly one match. + rpc GetRevision(jj_api.rpc.RevisionsRequest) returns (jj_api.rpc.GetRevisionResponse) {} +}