Files
2026-07-29 06:51:43 -04:00

532 lines
15 KiB
Protocol Buffer

// This is a faithful translation of the hand-written JSON-RPC method set in
// handlers.go (registerRepoMethods + registerWriteMethods) plus the gitcmd
// result types those handlers return verbatim. Every rpc below maps 1:1 to a
// "repo.<name>" JSON-RPC method (noted in each comment); every message field
// keeps the snake_case name of the corresponding Go struct json tag so the
// wire shape matches what gitrpc serializes today.
//
// The server is path-driven and stateless: every method takes the repository
// NAME (owner/name.git) in `repo`; the server resolves it to an on-disk path
// and has already authorized the call before the handler runs. Auth/ACL and
// name->path resolution live in the caller (simpleconsole), not here.
//
// Generate Go + TS with protoc-gen-es (TS) / protoc-gen-go + protoc-gen-connect-go:
// protoc -I . --go_out=. --go_opt=paths=source_relative \
// --connect_go_out=. --connect_go_opt=paths=source_relative \
// --es_out=./gen --es_opt=target=ts \
// --plugin=protoc-gen-es=./node_modules/.bin/protoc-gen-es \
// gitrpc.proto
syntax = "proto3";
package simplegit.v1;
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
option go_package = "simplegit/gitrpc/v1;v1";
// GitService is the git kernel facade: pure git operations over a resolved,
// already-authorized on-disk repo path. Bound localhost-only; no identity store.
service GitService {
// repo.listBranches
rpc ListBranches(RepoRequest) returns (ListBranchesResponse);
// repo.listTags
rpc ListTags(RepoRequest) returns (ListTagsResponse);
// repo.listCommits
rpc ListCommits(ListCommitsRequest) returns (ListCommitsResponse);
// repo.listCommitsByPath
rpc ListCommitsByPath(ListCommitsByPathRequest) returns (ListCommitsResponse);
// repo.getCommit
rpc GetCommit(GetCommitRequest) returns (CommitDetail);
// repo.getCommitDiff
rpc GetCommitDiff(GetCommitDiffRequest) returns (DiffResult);
// repo.getTree
rpc GetTree(GetTreeRequest) returns (Tree);
// repo.getBlob
rpc GetBlob(GetBlobRequest) returns (Blob);
// repo.compare
rpc Compare(CompareRequest) returns (CompareResult);
// repo.blame
rpc Blame(BlameRequest) returns (BlameResult);
// repo.getContributors
rpc GetContributors(GetContributorsRequest) returns (GetContributorsResponse);
// repo.getStats
rpc GetStats(RepoRequest) returns (RepoStats);
// repo.countObjects
rpc CountObjects(RepoRequest) returns (CountObjectsResult);
// repo.createFile
rpc CreateFile(CreateFileRequest) returns (FileCommitResult);
// repo.deleteFile
rpc DeleteFile(DeleteFileRequest) returns (FileCommitResult);
// repo.merge
rpc Merge(MergeRequest) returns (MergeResult);
}
// ManageService is the management surface backed by the self-contained state DB
// (state.IStateMut): repo lifecycle + ACL grants. Bound to a SEPARATE localhost
// port (-manage) from GitService; authenticated with a console system token
// (the Updater / console is the caller). Unlike GitService these mutate the
// state DB, not just git.
service ManageService {
// Execute a whitelisted state/ctrl database operation. Arguments are
// positional strings interpreted by the selected operation.
rpc Ctrl(CtrlRequest) returns (CtrlResponse);
// Create an empty namespace. Password login is configured separately.
rpc CreateNS(CreateNSRequest) returns (google.protobuf.Empty);
// Delete an empty namespace and its namespace-level auth data.
rpc DeleteNS(DeleteNSRequest) returns (google.protobuf.Empty);
// Create a repo when absent, or update its mutable properties when present.
rpc UpsertRepo(UpsertRepoRequest) returns (google.protobuf.Empty);
// OK if the repo is registered; NotFound otherwise.
rpc ExistRepo(ExistRepoRequest) returns (google.protobuf.Empty);
// Remove a repo (DB row + repo-targeted ACL grants + on-disk dir).
rpc DeleteRepo(DeleteRepoRequest) returns (google.protobuf.Empty);
// Move/rename a repo (transfer and/or rename).
rpc MoveRepo(MoveRepoRequest) returns (google.protobuf.Empty);
// Rename a namespace (all its repos); NamespaceID is stable.
rpc MoveNS(MoveNSRequest) returns (google.protobuf.Empty);
// Create or rotate the password-login projection for a namespace.
rpc UserPasswordUpsert(UserPasswordUpsertRequest) returns (google.protobuf.Empty);
// Remove a namespace's password-login projection. Repos and namespace remain.
rpc UserDelete(UserDeleteRequest) returns (google.protobuf.Empty);
// Grant an SSH key perm on a namespace; registers the key if new. Returns the ACL id.
rpc ACLUpsertSSHKeyOnNS(ACLUpsertSSHKeyOnNSRequest) returns (ACLIDResponse);
// Grant a PAT perm on a namespace; creates the PAT if new. Returns the ACL id.
rpc ACLUpsertPATOnNS(ACLUpsertPATOnNSRequest) returns (ACLIDResponse);
// Grant an SSH key perm on a repo; registers the key if new. Returns the ACL id.
rpc ACLUpsertSSHKeyOnRepo(ACLUpsertSSHKeyOnRepoRequest) returns (ACLIDResponse);
// Grant a PAT perm on a repo; creates the PAT if new. Returns the ACL id.
rpc ACLUpsertPATOnRepo(ACLUpsertPATOnRepoRequest) returns (ACLIDResponse);
// Remove a single ACL grant by id.
rpc ACLDelete(ACLDeleteRequest) returns (google.protobuf.Empty);
// Change a grant's permission without resubmitting its credential.
rpc ACLSetPerm(ACLSetPermRequest) returns (google.protobuf.Empty);
// Daemon status + config. Database connection details are intentionally not
// exposed; management reads use Ctrl.
rpc Status(google.protobuf.Empty) returns (StatusResponse);
}
// ---------- shared request messages ----------
// repoParams: the repository name (owner/name.git). JSON key is "repo" so it
// never collides with the in-repo file "path" param several methods also carry.
message RepoRequest {
string repo = 1;
}
message ListCommitsRequest {
string repo = 1;
string ref = 2; // "" -> HEAD (server-side default)
int32 limit = 3;
}
message ListCommitsByPathRequest {
string repo = 1;
string ref = 2;
string path = 3;
int32 limit = 4;
}
message GetCommitRequest {
string repo = 1;
string sha = 2;
}
message GetCommitDiffRequest {
string repo = 1;
string sha = 2;
}
message GetTreeRequest {
string repo = 1;
string ref = 2;
string path = 3; // "" for repo root
}
message GetBlobRequest {
string repo = 1;
string ref = 2;
string path = 3;
}
message CompareRequest {
string repo = 1;
string base = 2; // "" -> HEAD
string head = 3; // "" -> HEAD
}
message BlameRequest {
string repo = 1;
string ref = 2;
string path = 3;
}
message GetContributorsRequest {
string repo = 1;
string ref = 2; // "" -> default branch
}
// Write-path requests carry the caller-resolved commit identity
// (author_name / author_email): the server has no identity store. `strategy`
// selects the gitcmd implementation ("plumbing" default, "temprepo" for the
// Gitea-style temp-clone+push that runs hooks).
message CreateFileRequest {
string repo = 1;
string branch = 2;
string path = 3;
string content = 4;
string message = 5;
string author_name = 6;
string author_email = 7;
string expected_sha = 8; // atomic ref update against this branch tip
string strategy = 9; // "" / "plumbing" / "temprepo"
}
message DeleteFileRequest {
string repo = 1;
string branch = 2;
string path = 3;
string message = 4;
string author_name = 5;
string author_email = 6;
string expected_sha = 7;
string strategy = 8;
}
message MergeRequest {
string repo = 1;
string base = 2;
string head = 3;
string message = 4;
string author_name = 5;
string author_email = 6;
bool no_ff = 7;
string strategy = 8;
}
// ---- ManageService requests ----
message CreateNSRequest {
string ns = 1;
}
message DeleteNSRequest {
string ns = 1;
}
message UpsertRepoRequest {
string ns = 1;
string name = 2;
// is_private seeds the repo's privacy flag in simplegit's state DB. simplegit
// enforces it at the git-transport layer: a private repo denies read (clone)
// to anyone without an ACL grant, so the console's isPrivate must be
// propagated here (it can't be enforced console-side - simplegit is the git
// kernel that owns the bare repo + its access checks).
bool is_private = 3;
}
message ExistRepoRequest {
string ns = 1;
string name = 2;
}
message DeleteRepoRequest {
string ns = 1;
string name = 2;
}
message MoveRepoRequest {
string src = 1; // old ns/name
string dst = 2; // new ns/name
}
message MoveNSRequest {
string src = 1; // old namespace
string dst = 2; // new namespace
}
message UserPasswordUpsertRequest {
string ns = 1;
string password = 2;
}
message UserDeleteRequest {
string ns = 1;
}
message ACLUpsertSSHKeyOnNSRequest {
string ns = 1;
string key = 2; // authorized-keys text
string perm = 3; // read | write | admin
}
message ACLUpsertPATOnNSRequest {
string ns = 1;
string pat = 2; // plaintext PAT
string perm = 3;
}
message ACLUpsertSSHKeyOnRepoRequest {
string reponame = 1; // ns/name
string key = 2;
string perm = 3;
}
message ACLUpsertPATOnRepoRequest {
string reponame = 1;
string pat = 2;
string perm = 3;
}
message ACLDeleteRequest {
int64 acl_id = 1;
}
message ACLSetPermRequest {
int64 acl_id = 1;
string perm = 2;
}
// ---- ManageService Status (diagnostics) ----
// StatusResponse is the daemon's config + runtime snapshot.
message StatusResponse {
string root = 1; // -root (git root)
string rpc_addr = 2; // -rpc listen
string ssh_addr = 3; // -ssh listen
string manage_addr = 4; // -manage listen
string auth_url = 5; // -auth RBAC center ("" if skip-auth)
reserved 6; // formerly db_uri
bool skip_auth = 7; // -skip-auth
google.protobuf.Timestamp started_at = 8; // daemon start time
int64 uptime_seconds = 9; // seconds since started_at
}
// ---------- scalar/repeated response wrappers ----------
// (proto rpc returns must be a message; these wrap the non-message returns.)
message ACLIDResponse {
int64 acl_id = 1;
}
message CtrlRequest {
string op = 1;
repeated string args = 2;
}
message CtrlResponse {
bytes result = 1; // JSON encoded operation result
}
message ListBranchesResponse {
repeated Branch branches = 1;
}
message ListTagsResponse {
repeated Tag tags = 1;
}
message ListCommitsResponse {
repeated Commit commits = 1;
}
message GetContributorsResponse {
repeated Contributor contributors = 1;
}
// ---------- data messages (gitcmd result types) ----------
// gitcmd.Commit. Pinned snake_case json so renaming Go fields does not drift
// the wire contract.
message Commit {
string id = 1; // full 40-char SHA
string short_sha = 2; // 7-char abbreviated SHA
string message = 3; // full commit message
string author = 4; // author name
string author_email = 5; // author email
google.protobuf.Timestamp when = 6; // author timestamp
repeated string parents = 7; // parent SHAs
}
// gitcmd.CommitDetail extends Commit with file change statistics. Commit fields
// are nested (proto has no struct embedding) rather than flattened.
message CommitDetail {
Commit commit = 1;
DiffStats stats = 2;
repeated FileDiff files = 3;
}
message DiffStats {
int32 additions = 1;
int32 deletions = 2;
int32 total = 3;
int32 files = 4;
}
message FileDiff {
string path = 1;
int32 additions = 2;
int32 deletions = 3;
string status = 4; // A (added), M (modified), D (deleted), R (renamed)
}
message Branch {
string name = 1;
string sha = 2;
string short_sha = 3;
string message = 4;
string author = 5;
google.protobuf.Timestamp when = 6;
bool is_default = 7;
}
message Tag {
string name = 1;
string sha = 2;
string short_sha = 3;
string message = 4;
string tagger = 5;
google.protobuf.Timestamp tag_date = 6;
bool is_annotated = 7;
}
message TreeEntry {
string name = 1;
string path = 2;
string type = 3; // "blob" (file) or "tree" (directory)
string mode = 4; // git mode, e.g. "100644", "040000"
int64 size = 5; // file size in bytes (0 for directories)
string sha = 6;
}
message Tree {
string ref = 1;
string path = 2;
repeated TreeEntry entries = 3;
}
message Blob {
string ref = 1;
string path = 2;
string content = 3; // raw file content (text or base64-encoded binary)
string encoding = 4; // "text" or "base64"
int64 size = 5;
string sha = 6;
bool is_binary = 7;
}
message DiffLine {
string type = 1; // "context", "add", "delete"
string text = 2; // line content without the leading sigil
}
message Hunk {
int32 old_start = 1; // 0 if the old side is absent
int32 old_count = 2;
int32 new_start = 3; // 0 if the new side is absent
int32 new_count = 4;
repeated DiffLine lines = 5;
}
message FileChange {
string path = 6; // resulting path (new path; old path if deleted)
string old_path = 1; // set only on rename/copy
string status = 2; // A / M / D / R
bool is_binary = 3;
int32 additions = 4;
int32 deletions = 5;
repeated Hunk hunks = 7;
}
message DiffResult {
repeated FileChange files = 1;
}
message CompareResult {
string base = 1;
string head = 2;
int32 ahead = 3; // commits reachable from head but not base
int32 behind = 4; // commits reachable from base but not head
repeated Commit commits = 5; // base..head, newest first
DiffResult diff = 6; // combined file diff base..head
}
message BlameLine {
string sha = 1;
string short_sha = 2;
string author = 3;
string email = 4;
google.protobuf.Timestamp when = 5;
int32 line_no = 6; // 1-based line number in the blamed file
string content = 7;
string summary = 8; // commit subject line
}
message BlameResult {
string ref = 1;
string path = 2;
repeated BlameLine lines = 3;
}
message Contributor {
string author = 1;
string email = 2;
int32 commits = 3;
int32 additions = 4;
int32 deletions = 5;
int32 total_lines = 6;
}
message RepoStats {
int32 total_commits = 1;
int32 total_branches = 2;
int32 total_tags = 3;
int64 total_size = 4; // git object size in bytes (count-objects)
repeated Contributor contributors = 5;
google.protobuf.Timestamp first_commit = 6;
google.protobuf.Timestamp last_commit = 7;
}
message CountObjectsResult {
int64 count = 1; // loose objects
int64 size = 2; // loose objects size, bytes
int64 in_pack = 3; // objects inside packs
int64 packs = 4; // number of packs
int64 size_pack = 5; // packed objects size, bytes
int64 garbage = 6; // garbage objects
int64 size_garbage = 7; // garbage size, bytes
}
// MergeResult: exactly one of merge_commit (clean/FF) or conflicts (conflict)
// is meaningful.
message MergeResult {
string base = 1;
string head = 2;
Commit merge_commit = 3; // nil on conflict; on FF, head's tip
bool fast_forward = 4;
repeated string conflicts = 5; // conflicting paths (merge_commit nil)
}
message FileCommitResult {
Commit commit = 1; // the new commit that now tips the branch
}