syntax = "proto3";

package gitmirror.v1;

import "google/protobuf/timestamp.proto";

option go_package = "tangled.org/core/gitmirror/proto/gen;gitmirrorv1";

service GitMirrorService {
  rpc CommitLog(CommitLogRequest) returns (stream CommitLogResponse);
  rpc GetBlob(GetBlobRequest) returns (stream BlobChunk);
  rpc Diff(DiffRequest) returns (stream FileDiff);
  rpc Interdiff(InterdiffRequest) returns (stream FileDiff);
  rpc MergeCheck(MergeCheckRequest) returns (MergeCheckResponse);
  rpc ResolveRev(ResolveRevRequest) returns (ResolveRevResponse);
}

message ResolveRevRequest {
  // repo carries the DID as a string.
  string repo = 1;
  // rev is a SIMPLE revspec: a branch name, tag name, or short/full commit oid.
  // HEAD and revspec navigation (HEAD~2, foo^, ref@{...}, a..b, path:file) are rejected.
  bytes rev = 2;
}
message ResolveRevResponse {
  // commit is the resolved commit object id, hex-encoded.
  string commit = 1;
}

// A commit in a specific repo, addressed by the repo's DID.
message RepoCommit {
  // repo carries the DID as a string.
  string repo = 1;
  // commit is a full commit object id, hex-encoded (NOT a revspec/branch/tag).
  bytes commit = 2;
}

message MergeCheckRequest {
  // Branch/commit being merged into (the "ours" side of the 3-way merge).
  RepoCommit target = 1;
  // PR head being merged (the "theirs" side of the 3-way merge).
  RepoCommit source = 2;
}
message MergeCheckResponse {
  // Whether the 3-way merge has unresolved conflicts.
  bool is_conflicted = 1;
  // One entry per conflicted file.
  repeated MergeConflict conflicts = 2;
  // Optional human-readable summary of the merge check.
  optional string message = 3;
}
message MergeConflict {
  string filename = 1;
  string reason = 2;
}

message CommitLogRequest {
  // repo DID
  string repo = 1;
  // Ranges to include in the git log (revspec, "A..B", "A...B", etc.).
  // At least one range, or all_refs must be specified.
  repeated bytes ranges = 2;
  // If true, all refs are searched for commits.
  // Must not be true when ranges are given.
  bool all_refs = 3;
  // After is an optional parameter to specify the earliest commit to consider.
  google.protobuf.Timestamp after = 4;
  // Before is an optional parameter to specify the latest commit to consider.
  google.protobuf.Timestamp before = 5;
  // MaxCommits is an optional parameter to specify the maximum number of commits
  // to return. If max_commits is 0, all commits that match the criteria will be
  // returned.
  uint32 max_commits = 6;
  // Skip is an optional parameter to specify the number of commits to skip.
  // This can be used to implement a poor mans pagination.
  uint32 skip = 7;
}
message CommitLogResponse {
  repeated GitCommit commits = 1;
}

message GetBlobRequest {
  // repo carries the DID as a string.
  string repo = 1;
  // oid is the blob object id (hex).
  string oid = 2;
}
message BlobChunk {
  bytes data = 1;
}

message DiffRequest {
  RepoCommit base = 1;
  RepoCommit head = 2;
  ComparisonType comparison_type = 3;

  enum ComparisonType {
    COMPARISON_TYPE_UNSPECIFIED = 0;
    // Corresponds to the BASE..HEAD syntax that only returns any commits that
    // are in HEAD but not in BASE.
    COMPARISON_TYPE_ONLY_IN_HEAD = 1;
    // Corresponds to the BASE...HEAD syntax that returns any commits that are
    // not in both BASE and HEAD.
    COMPARISON_TYPE_INTERSECTION = 2;
  }
}

message InterdiffRequest {
  // repo carries the DID as a string.
  string repo = 1;
  // Old patch range (from_base..from_head) and new patch range (to_base..to_head).
  bytes from_base = 2;
  bytes from_head = 3;
  bytes to_base = 4;
  bytes to_head = 5;
}

// Diff and Interdiff stream one FileDiff per changed file.

message FileContent {
  string path = 1;
  string oid = 2;
  uint64 size = 3;
  bool is_binary = 4;
  bool is_submodule = 5;
  // Raw file bytes, inlined when the oid is not fetchable by the client
  // (e.g. interdiff)
  optional bytes content = 6;
}

// One aligned line pair; a side is absent (None) when unset.
message LinePair {
  optional uint32 lhs = 1;
  optional uint32 rhs = 2;
}

message Hunk {
  // LineNumber (u32) sets containing novel content per side.
  repeated uint32 novel_lhs = 1;
  repeated uint32 novel_rhs = 2;
  repeated LinePair lines = 3;
}

// Number of bytes per side when the files differ (Rust Option<(usize, usize)>).
message ByteChanges {
  uint64 lhs = 1;
  uint64 rhs = 2;
}

message FileDiff {
  FileContent lhs_src = 1;
  FileContent rhs_src = 2;
  repeated Hunk hunks = 3;
  optional ByteChanges has_byte_changes = 4;
  bool has_syntactic_changes = 5;
  // TODO: lhs_positions & rhs_positions
}

message GitCommit {
  string oid = 1;
  GitSignature author = 2;
  GitSignature committer = 3;
  bytes message = 4;
  repeated string parents = 5;
  map<string, string> extra_headers = 6;
}

message GitSignature {
  bytes name = 1;
  bytes email = 2;
  google.protobuf.Timestamp date = 3;
}
