Skip to content

Commit

Permalink
WIP: Create an example API.
Browse files Browse the repository at this point in the history
This PR is meant to trigger a discussion on what the specific structure of the API should look like.

This crate contains only the minimal set of code for an external library to start using it.
  • Loading branch information
matts1 committed Jun 17, 2024
1 parent 03a0921 commit cf0e303
Show file tree
Hide file tree
Showing 7 changed files with 288 additions and 0 deletions.
13 changes: 13 additions & 0 deletions api/proto/objects/options.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
syntax = "proto3";

package jj_api.objects;

import "objects/revision.proto";
import "objects/state.proto";

message Options {
bool ignore_immutable = 2;

// Formatting options.
RevisionMask revision_mask = 3;
}
70 changes: 70 additions & 0 deletions api/proto/objects/revision.proto
Original file line number Diff line number Diff line change
@@ -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

Check failure on line 26 in api/proto/objects/revision.proto

View workflow job for this annotation

GitHub Actions / Codespell

modfied ==> modified
// 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<string, File> files = 8;

// revision.parents[*].parents is always empty, to avoid recursion.
repeated Revision parents = 9;
}
72 changes: 72 additions & 0 deletions api/proto/objects/revset.proto
Original file line number Diff line number Diff line change
@@ -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;
}
28 changes: 28 additions & 0 deletions api/proto/objects/state.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 RepoState {
// 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;
}
54 changes: 54 additions & 0 deletions api/proto/rpc/read_revisions.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
syntax = "proto3";

package jj_api.rpc;

import "objects/options.proto";
import "objects/revset.proto";
import "objects/revision.proto";
import "objects/state.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.RepoState repo_state = 2;
}

jj_api.objects.Options options = 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;
}
25 changes: 25 additions & 0 deletions api/proto/rpc/transaction.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
syntax = "proto3";

package jj_api.rpc;

import "objects/state.proto";

message BeginTransactionRequest {
jj_api.objects.RepoState 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;
}
26 changes: 26 additions & 0 deletions api/proto/services/service.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
syntax = "proto3";

import "objects/state.proto";
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) {}
}

0 comments on commit cf0e303

Please sign in to comment.