syntax = "proto3";

package gitaly;

option go_package = "gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb";

import "lint.proto";
import "shared.proto";

service RepositoryService {
  rpc RepositoryExists(RepositoryExistsRequest) returns (RepositoryExistsResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }

  // RepackIncremental is deprecated in favor of OptimizeRepository.
  rpc RepackIncremental(RepackIncrementalRequest) returns (RepackIncrementalResponse) {
    option deprecated = true;
    option (op_type) = {
      op: MUTATOR
    };
  }

  // RepackFull is deprecated in favor of OptimizeRepository.
  rpc RepackFull(RepackFullRequest) returns (RepackFullResponse) {
    option deprecated = true;
    option (op_type) = {
      op: MUTATOR
    };
  }

  // MidxRepack is deprecated in favor of OptimizeRepository.
  rpc MidxRepack(MidxRepackRequest) returns (MidxRepackResponse) {
    option deprecated = true;
    option (op_type) = {
      op: MUTATOR
    };
  }

  // GarbageCollect is deprecated in favor of OptimizeRepository.
  rpc GarbageCollect(GarbageCollectRequest) returns (GarbageCollectResponse) {
    option deprecated = true;
    option (op_type) = {
      op: MUTATOR
    };
  }

  // WriteCommitGraph is deprecated in favor of OptimizeRepository.
  rpc WriteCommitGraph(WriteCommitGraphRequest) returns (WriteCommitGraphResponse) {
    option deprecated = true;
    option (op_type) = {
      op: MUTATOR
    };
  }

  rpc RepositorySize(RepositorySizeRequest) returns (RepositorySizeResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc ApplyGitattributes(ApplyGitattributesRequest) returns (ApplyGitattributesResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }

  // FetchRemote fetches references from a remote repository into the local
  // repository.
  rpc FetchRemote(FetchRemoteRequest) returns (FetchRemoteResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }
  rpc CreateRepository(CreateRepositoryRequest) returns (CreateRepositoryResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }
  rpc GetArchive(GetArchiveRequest) returns (stream GetArchiveResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc HasLocalBranches(HasLocalBranchesRequest) returns (HasLocalBranchesResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }

  // FetchSourceBranch fetches a branch from a second (potentially remote)
  // repository into the given repository.
  rpc FetchSourceBranch(FetchSourceBranchRequest) returns (FetchSourceBranchResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }
  rpc Fsck(FsckRequest) returns (FsckResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc WriteRef(WriteRefRequest) returns (WriteRefResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }
  rpc FindMergeBase(FindMergeBaseRequest) returns (FindMergeBaseResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc CreateFork(CreateForkRequest) returns (CreateForkResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }

  rpc CreateRepositoryFromURL(CreateRepositoryFromURLRequest) returns (CreateRepositoryFromURLResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }

  // CreateBundle creates a bundle from all refs
  rpc CreateBundle(CreateBundleRequest) returns (stream CreateBundleResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }

  // CreateBundleFromRefList creates a bundle from a stream of ref patterns.
  // When the bundle would be empty the FailedPrecondition error code is returned.
  rpc CreateBundleFromRefList(stream CreateBundleFromRefListRequest) returns (stream CreateBundleFromRefListResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }

  // FetchBundle fetches references from a bundle into the local repository.
  // Refs will be mirrored to the target repository with the refspec
  // "+refs/*:refs/*" and refs that do not exist in the bundle will be removed.
  rpc FetchBundle(stream FetchBundleRequest) returns (FetchBundleResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }

  rpc CreateRepositoryFromBundle(stream CreateRepositoryFromBundleRequest) returns (CreateRepositoryFromBundleResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }

  // GetConfig reads the target repository's gitconfig and streams its contents
  // back. Returns a NotFound error in case no gitconfig was found.
  rpc GetConfig(GetConfigRequest) returns (stream GetConfigResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }

  rpc FindLicense(FindLicenseRequest) returns (FindLicenseResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc GetInfoAttributes(GetInfoAttributesRequest) returns (stream GetInfoAttributesResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc CalculateChecksum(CalculateChecksumRequest) returns (CalculateChecksumResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }

  // Cleanup is deprecated in favor of OptimizeRepository.
  rpc Cleanup(CleanupRequest) returns (CleanupResponse) {
    option deprecated = true;
    option (op_type) = {
      op: MUTATOR
    };
  }

  rpc GetSnapshot(GetSnapshotRequest) returns (stream GetSnapshotResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc CreateRepositoryFromSnapshot(CreateRepositoryFromSnapshotRequest) returns (CreateRepositoryFromSnapshotResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }
  rpc GetRawChanges(GetRawChangesRequest) returns (stream GetRawChangesResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc SearchFilesByContent(SearchFilesByContentRequest) returns (stream SearchFilesByContentResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc SearchFilesByName(SearchFilesByNameRequest) returns (stream SearchFilesByNameResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc RestoreCustomHooks(stream RestoreCustomHooksRequest) returns (RestoreCustomHooksResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }
  rpc BackupCustomHooks(BackupCustomHooksRequest) returns (stream BackupCustomHooksResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  rpc GetObjectDirectorySize(GetObjectDirectorySizeRequest) returns (GetObjectDirectorySizeResponse) {
    option (op_type) = {
      op: ACCESSOR
    };
  }
  // RemoveRepository will move the repository to `+gitaly/tmp/<relative_path>_removed` and
  // eventually remove it. This ensures that even on networked filesystems the
  // data is actually removed even if there's someone still handling the data.
  rpc RemoveRepository(RemoveRepositoryRequest) returns (RemoveRepositoryResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }
  rpc RenameRepository(RenameRepositoryRequest) returns (RenameRepositoryResponse) {
    option (op_type) = {
      op: MUTATOR
    };
  }
  rpc ReplicateRepository(ReplicateRepositoryRequest) returns (ReplicateRepositoryResponse) {
    option (op_type)  = {
      op: MUTATOR
    };
  }

  // OptimizeRepository performs all maintenance tasks in a repository to keep
  // it in an efficient state. It cleans up stale data, repacks objects,
  // updates auxiliary caches like commit-graphs and packs references. The
  // optimizations performed are based on heuristics and will adapt to the
  // repository's size. This RPC call is designed as a black-box such that
  // Gitaly has complete control of the on-disk state of repositories.
  rpc OptimizeRepository(OptimizeRepositoryRequest) returns (OptimizeRepositoryResponse) {
    option (op_type)  = {
      op: MUTATOR
    };
  }

  // PruneUnreachableObjects will prune all objects which aren't reachable from
  // the repository's current set of references. Because pruning can only
  // happen for objects which aren't packed, you are required to first run
  // OptimizeRepository to explode any unreachable objects into loose objects.
  //
  // Furthermore, this RPC call has a grace period of 30 minutes: any
  // unreachable loose objects must not have been accessed or modified in the
  // last 30 minutes. This is a hard requirement to avoid repository corruption.
  //
  // To make proper use of this RPC you thus need to call OptimizeRepository,
  // wait 30 minutes, and then call PruneUnreachableObjects.
  rpc PruneUnreachableObjects(PruneUnreachableObjectsRequest) returns (PruneUnreachableObjectsResponse) {
    option (op_type)  = {
      op: MUTATOR
    };
  }

  // SetFullPath writes the "gitlab.fullpath" configuration into the
  // repository's gitconfig. This is mainly to help debugging purposes in case
  // an admin inspects the repository's gitconfig such that he can easily see
  // what the repository name is.
  rpc SetFullPath(SetFullPathRequest) returns (SetFullPathResponse) {
    option (op_type)  = {
      op: MUTATOR
    };
  }
}

message RepositoryExistsRequest {
  Repository repository = 1 [(target_repository)=true];
}

message RepositoryExistsResponse {
  bool exists = 1;
}

message RepackIncrementalRequest {
  Repository repository = 1 [(target_repository)=true];
}

message RepackIncrementalResponse {}

message RepackFullRequest {
  Repository repository = 1 [(target_repository)=true];
  bool create_bitmap = 2;
}

message RepackFullResponse {}

message MidxRepackRequest {
  Repository repository = 1 [(target_repository)=true];
}

message MidxRepackResponse {}

message GarbageCollectRequest {
  Repository repository = 1 [(target_repository)=true];
  bool create_bitmap = 2;
  // If set to 'true' the 'gc' will be triggered with '--prune=30.minutes.ago' flag.
  // This will remove dangling objects from the object storage that were not modified in the last 30 minutes.
  // If 'false' provided the 'gc' will rely on the default expiration period (2 weeks).
  // The window of 30 minutes exists because of possible concurrent operations running on the same
  // storage and removal of the objects may cause races and fail concurrent operations.
  bool prune = 3;
}

message GarbageCollectResponse {}

message WriteCommitGraphRequest {
  enum SplitStrategy {
    // SizeMultiple requires to use '--split --size-multiple=4' strategy to create/update commit graph.
    // https://git-scm.com/docs/git-commit-graph#Documentation/git-commit-graph.txt-emwriteem
    // It is a default, there is no need to explicitly set it in the request.
    SizeMultiple = 0;
  }
  Repository repository = 1 [(target_repository)=true];
  // SplitStrategy is a strategy used to create/update commit graph.
  SplitStrategy splitStrategy = 2;
}

message WriteCommitGraphResponse {}

message CleanupRequest {
  Repository repository = 1 [(target_repository)=true];
}

message CleanupResponse {}

message RepositorySizeRequest {
  Repository repository = 1 [(target_repository)=true];
}

message RepositorySizeResponse {
  // Repository size in kilobytes
  int64 size = 1;
}

message ApplyGitattributesRequest {
  Repository repository = 1 [(target_repository)=true];
  bytes revision = 2;
}

message ApplyGitattributesResponse {}

message FetchBundleRequest {
  // Repository into which the reference shall be fetched.
  Repository repository = 1 [(target_repository)=true];

  // Data is the bundle file stream.
  bytes data = 2;

  // UpdateHead will update HEAD if there is a HEAD reference listed in the bundle
  bool update_head = 3;
}

message FetchBundleResponse {}

message FetchRemoteRequest {
  Repository repository = 1 [(target_repository)=true];
  // force determines if references should be force-updated in case they have
  // diverged.
  bool force = 3;
  // no_tags determines whether tags should be fetched.
  bool no_tags = 4;
  // timeout specifies a timeout for the fetch.
  int32 timeout = 5;
  string ssh_key = 6;
  string known_hosts = 7;
  reserved 8;
  // no_prune will the fetch to not prune remote references which do not exist
  // in the remote repository anymore.
  bool no_prune = 9;
  // remote_params specifies the remote repository which should be fetched
  // from.
  Remote remote_params = 10;
  // If check_tags_changed is true, the FetchRemote RPC will check whether any
  // tags were modified, returning the result in the tags_changed field of
  // FetchRemoteResponse
  bool check_tags_changed = 11;

  reserved 2;
  reserved "remote";
}

message FetchRemoteResponse {
  // If check_tags_changed was set in the FetchRemoteRequest, the FetchRemote
  // RPC will return false when no tags were changed, and true if tags were
  // changed or answer cannot be determined.
  bool tags_changed = 1;
}

message CreateRepositoryRequest {
  Repository repository = 1 [(target_repository)=true];
  // Provide a branch name to set as the default branch of a newly created
  // repository. Note, this will be treated as the branch name and not a
  // fully qualified reference.
  bytes default_branch = 2;
}

message CreateRepositoryResponse {}

message GetArchiveRequest {
  enum Format {
    ZIP = 0;
    TAR = 1;
    TAR_GZ = 2;
    TAR_BZ2 = 3;
  }

  Repository repository = 1 [(target_repository)=true];
  string commit_id = 2;
  string prefix = 3;
  Format format = 4;
  bytes path = 5;
  repeated bytes exclude = 6;
  // If `elide_path` is true and `path` refers to a subdirectory, that
  // subdirectory will be elided from archive entries. For example, if `dir`
  // contains `README.md`, with `elide_path = false` the corresponding entry
  // will be `dir/README.md`; with `elide_path = true`, the entry will be
  // `README.md`. `elide_path` has no effect if `path` refers to the repository
  // root. `elide_path = true` is not supported if `path` refers to a file.
  bool elide_path = 7;
  bool include_lfs_blobs = 8;
}

message GetArchiveResponse {
  bytes data = 1;
}

message HasLocalBranchesRequest {
  Repository repository = 1 [(target_repository)=true];
}

message HasLocalBranchesResponse {
  bool value = 1;
}

message FetchSourceBranchRequest {
  // Repository into which the reference shall be fetched. After a successful
  // call, it should contain the target reference which points to the same
  // commit as the source repository's source branch.
  Repository repository = 1 [(target_repository)=true];
  // Repository from which to fetch the source branch from.
  Repository source_repository = 2;
  // Name of the branch in the source repository which should be fetched.
  bytes source_branch = 3;
  // Name of the reference which shall be newly created in the target
  // repository.
  bytes target_ref = 4;
}

message FetchSourceBranchResponse {
  // True if the source branch was successfully fetched into the target
  // repository, false if resolving the remote reference or fetching it failed.
  bool result = 1;
}

message FsckRequest {
  Repository repository = 1 [(target_repository)=true];
}

message FsckResponse {
  bytes error = 1;
}

message WriteRefRequest {
  Repository repository = 1 [(target_repository)=true];
  bytes ref = 2;
  bytes revision = 3;
  bytes old_revision = 4;
  bool force = 5;
  // This used to be a boolean indicating whether or not to shell out or use
  // the rugged implementation
  reserved 6;
}

message WriteRefResponse {
  // This used to contain an error message. Since we're shelling out
  // all exceptions are wrapped in GRPC errors.
  reserved 1;
}

message FindMergeBaseRequest {
  Repository repository = 1 [(target_repository)=true];
  // We use a repeated field because rugged supports finding a base
  // for more than 2 revisions, so if we needed that in the future we don't
  // need to change the protocol.
  repeated bytes revisions = 2;
}

message FindMergeBaseResponse {
  string base = 1;
}

message CreateForkRequest {
  Repository repository = 1 [(target_repository)=true];
  Repository source_repository = 2;
}

message CreateForkResponse {}

message CreateRepositoryFromURLRequest {
  Repository repository = 1 [(target_repository)=true];
  string url = 2;
  // HttpHost is the hostname of the remote repository. Use this when the
  // URL hostname has already been resolved to an IP address to prevent DNS
  // rebinding.
  string http_host = 3;
  // http_authorization_header is the HTTP header which can be added to
  // the request in order to authenticate against the repository.
  string http_authorization_header = 4;
  // Mirror defines whether to clone with `--mirror` flag or `--bare`. The default
  // value `false` will cause us to use `--bare`, which results in a clone that
  // contains only branches (`refs/heads/`) and tags (`refs/tags/`) of the remote
  // repository. If set to `true`, create a complete mirror-clone which maps all
  // remote references into the local repository.
  bool mirror = 5;
}

message CreateRepositoryFromURLResponse {}

message CreateBundleRequest {
  Repository repository = 1 [(target_repository)=true];
}

message CreateBundleResponse {
  bytes data = 1;
}

message CreateBundleFromRefListRequest {
  // Repository is the repository that the bundle is created from.
  Repository repository = 1 [(target_repository)=true];

  // Patterns contains all patterns which shall be bundled. Patterns should be
  // in the format accepted by git-rev-list(1). Patterns which don't match any
  // reference will be silently ignored.
  repeated bytes patterns = 2;
}

message CreateBundleFromRefListResponse {
  bytes data = 1;
}

// GetConfigRequest is a request for the GetConfig RPC.
message GetConfigRequest {
  // Repository is the repository from which the configuration should be read
  // from.
  Repository repository = 1 [(target_repository)=true];
}

// GetConfigResponse is a response for the GetConfig RPC.
message GetConfigResponse {
  // Data contains contents of the gitconfig.
  bytes data = 1;
}

message  RestoreCustomHooksRequest {
  Repository repository = 1 [(target_repository)=true];
  bytes data = 2;
}

message RestoreCustomHooksResponse {}

message BackupCustomHooksRequest {
  Repository repository = 1 [(target_repository)=true];
}

message BackupCustomHooksResponse {
  bytes data = 1;
}

message CreateRepositoryFromBundleRequest {
  // Only available on the first message
  Repository repository = 1 [(target_repository)=true];
  bytes data = 2;
}

message CreateRepositoryFromBundleResponse {}

message FindLicenseRequest {
  Repository repository = 1 [(target_repository)=true];
}

message FindLicenseResponse {
  string license_short_name = 1;
}

message GetInfoAttributesRequest {
  Repository repository = 1 [(target_repository)=true];
}

message GetInfoAttributesResponse {
  bytes attributes = 1;
}

message CalculateChecksumRequest {
  Repository repository = 1 [(target_repository)=true];
}

message CalculateChecksumResponse {
  string checksum = 1;
}

message GetSnapshotRequest {
  Repository repository = 1 [(target_repository)=true];
}

message GetSnapshotResponse {
  bytes data = 1;
}

message CreateRepositoryFromSnapshotRequest {
  Repository repository = 1 [(target_repository)=true];
  string http_url = 2;
  string http_auth = 3;
  // HttpHost is the hostname of the remote snapshot. Use this when the
  // URL hostname has already been resolved to an IP address to prevent DNS
  // rebinding.
  string http_host = 4;
}

message CreateRepositoryFromSnapshotResponse {}

message GetRawChangesRequest {
  Repository repository = 1 [(target_repository)=true];
  string from_revision = 2;
  string to_revision = 3;
}

message GetRawChangesResponse {
  message RawChange {
    enum Operation {
      UNKNOWN = 0;
      ADDED = 1;
      COPIED = 2;
      DELETED = 3;
      MODIFIED = 4;
      RENAMED = 5;
      TYPE_CHANGED = 6;
    }

    string blob_id = 1;
    int64 size= 2;

    // This used to be a string that is now represented by the field 9 as byte array.
    reserved 3;
    reserved "new_path";
    // This used to be a string that is now represented by the field 10 as byte array.
    reserved 4;
    reserved "old_path";

    Operation operation= 5;
    string raw_operation = 6;
    int32 old_mode = 7;
    int32 new_mode = 8;

    // the following fields, 9 and 10, will eventually replace 3 and 4
    bytes new_path_bytes = 9;
    bytes old_path_bytes = 10;
  }

  repeated RawChange raw_changes = 1;
}

message SearchFilesByNameRequest {
  Repository repository = 1 [(target_repository)=true];
  string query = 2;
  bytes ref = 3;
  // If `filter` is specified and non-empty, it will be parsed as a regular
  // expression and used to filter the result set before it is transmitted. It is
  // parsed using Go's `regexp` package, which is closely related to PCRE,
  // excluding backreferences, atomic/possesive operators, and some other
  // features. It has a maximum length of 1000 bytes.
  string filter = 4;
}

message SearchFilesByNameResponse {
  repeated bytes files = 1;
}

message SearchFilesByContentRequest {
  Repository repository = 1 [(target_repository)=true];
  string query = 2;
  bytes ref = 3;
  bool chunked_response = 4;
}

message SearchFilesByContentResponse {
  repeated bytes matches = 1;
  bytes match_data = 2;
  bool end_of_match = 3;
}

// Remote represents a git remote repository.
message Remote {
  // url is the URL of the remote repository.
  string url = 1;
  // http_authorization_header is the HTTP header which should be added to
  // the request in order to authenticate against the repository.
  string http_authorization_header = 3;
  // mirror_refmaps contains the refspecs which shall be fetched. Some special
  // refspecs are accepted:
  //
  // - "all_refs" gets translated to "+refs/*:refs/*", which mirrors all
  //   references of the source repository.
  // - "heads" gets translated to "+refs/heads/*:refs/heads/*", which mirrors
  //   all branches of the source repository.
  // - "tags" gets translated to "+refs/tags/*:refs/tags/*", which mirrors all
  //   tags of the source repository.
  //
  // If no refspecs are given, this defaults to "all_refs".
  repeated string mirror_refmaps = 4;
  // HttpHost is the hostname of the remote repository. Use this when the
  // URL hostname has already been resolved to an IP address to prevent DNS
  // rebinding.
  string http_host = 5;

  // Previously, it was possible to specify a remote name. This was quite a
  // dangerous field to set though: the name was simply used to create an ad-hoc
  // remote which got deleted afterwards again. So unexpectedly, the remote
  // isn't retained. And second, if the user chose the name of an existing
  // remote, then it would've been deleted after the call. So in effect, the
  // field was at best confusing and useless and at worst actively harmful.
  reserved 2;
  reserved "name";
}

message GetObjectDirectorySizeRequest {
  Repository repository = 1 [(target_repository)=true];
}

message GetObjectDirectorySizeResponse {
  // Object directory size in kilobytes
  int64 size = 1;
}

message RemoveRepositoryRequest {
  Repository repository = 1 [(target_repository)=true];
}

message RemoveRepositoryResponse {
}
message RenameRepositoryRequest {
  Repository repository = 1 [(target_repository)=true];
  string relative_path = 2;
}

message RenameRepositoryResponse{
}

message ReplicateRepositoryRequest {
  Repository repository = 1 [(target_repository)=true];
  Repository source = 2;
}

message ReplicateRepositoryResponse{}

message OptimizeRepositoryRequest {
  Repository repository = 1 [(target_repository)=true];
}

message OptimizeRepositoryResponse{}

// PruneUnreachableObjectsRequest is a request for the PruneUnreachableObjects
// RPC call.
message PruneUnreachableObjectsRequest {
  Repository repository = 1 [(target_repository)=true];
}

// PruneUnreachableObjectsResponse is a response for the
// PruneUnreachableObjects RPC call.
message PruneUnreachableObjectsResponse {
}

// SetFullPathRequest is a request for the SetFullPath RPC.
message SetFullPathRequest {
  // Repository is the repository whose gitconfig should be written to.
  Repository repository = 1 [(target_repository)=true];
  // Path is the path that shall be written into the "gitlab.fullpath" config key.
  string path = 2;
}

// SetFullPathResponse is a response fqor the SetFullPath RPC.
message SetFullPathResponse {}
