Skip to main content
The Gitaly Ruby client provides a Ruby interface for connecting to remote Gitaly servers. It’s primarily used by gitaly-ruby for operations that interact with multiple repositories, such as branch merging.

Overview

The Ruby client is implemented in the GitalyRemoteRepository class, which handles:
  • gRPC service client creation
  • Authentication with bearer tokens
  • SSL/TLS certificate management
  • Remote repository operations
Location: ruby/lib/gitlab/git/gitaly_remote_repository.rb

Basic Usage

Initialization

require 'gitlab-labkit'

module Gitlab
  module Git
    # Initialize a remote repository client
    remote = GitalyRemoteRepository.new(gitaly_repository, call)
  end
end

Constructor Parameters

  • gitaly_repository: A Gitaly repository protobuf message
  • call: The current gRPC call context

Authentication

The Ruby client uses HMAC-based bearer tokens for authentication:
def authorization_token
  issued_at = Time.now.to_i.to_s
  hmac = OpenSSL::HMAC.hexdigest(
    OpenSSL::Digest::SHA256.new,
    shared_secret,
    issued_at
  )
  
  "v2.#{hmac}.#{issued_at}"
end

Token Format

Tokens follow the format: v2.{hmac}.{timestamp}
  • v2: Token version
  • hmac: HMAC-SHA256 of the timestamp
  • timestamp: Unix timestamp when token was issued

Request Metadata

Tokens are sent in request metadata:
metadata = {
  'authorization' => "Bearer #{authorization_token}",
  'client_name' => 'gitaly-ruby'
}

{ metadata: metadata }

SSL/TLS Configuration

The client automatically handles SSL/TLS certificates:
def credentials
  if URI(gitaly_client.address(storage)).scheme == 'tls'
    GRPC::Core::ChannelCredentials.new(certs)
  else
    :this_channel_is_insecure
  end
end

Certificate Loading

Certificates are loaded from environment variables:
def certs
  raise 'SSL_CERT_DIR and/or SSL_CERT_FILE must be set' \
    unless ENV['SSL_CERT_DIR'] || ENV['SSL_CERT_FILE']
  
  files = []
  files += Dir["#{ENV['SSL_CERT_DIR']}/*"] if ENV['SSL_CERT_DIR']
  files += [ENV['SSL_CERT_FILE']] if ENV['SSL_CERT_FILE']
  
  files.flat_map do |cert_file|
    File.read(cert_file).scan(PEM_REXP).map do |cert|
      OpenSSL::X509::Certificate.new(cert).to_pem
    end.compact
  end.uniq.join("\n")
end

Environment Variables

  • SSL_CERT_DIR: Directory containing certificate files
  • SSL_CERT_FILE: Path to a single certificate file

Repository Operations

Check Branch Existence

def branch_exists?(branch_name)
  request = Gitaly::RefExistsRequest.new(
    repository: @gitaly_repository,
    ref: "refs/heads/#{branch_name}".b
  )
  
  stub = Gitaly::RefService::Stub.new(address, credentials)
  stub.ref_exists(request, request_kwargs).value
end

Get Commit ID

def commit_id(revision)
  request = Gitaly::FindCommitRequest.new(
    repository: @gitaly_repository,
    revision: revision.b
  )
  
  stub = Gitaly::CommitService::Stub.new(address, credentials)
  stub.find_commit(request, request_kwargs)&.commit&.id.presence
end

Check if Repository Exists

def exists?
  request = Gitaly::RepositoryExistsRequest.new(
    repository: @gitaly_repository
  )
  
  stub = Gitaly::RepositoryService::Stub.new(
    address,
    credentials,
    interceptors: @interceptors
  )
  
  stub.repository_exists(request, request_kwargs).exists
end

Check if Repository Has Branches

def has_visible_content?
  request = Gitaly::HasLocalBranchesRequest.new(
    repository: @gitaly_repository
  )
  
  stub = Gitaly::RepositoryService::Stub.new(
    address,
    credentials,
    interceptors: @interceptors
  )
  
  stub.has_local_branches(request, request_kwargs).value
end

Check if Repository is Empty

def empty?
  !exists? || !has_visible_content?
end

Address Resolution

The client normalizes Gitaly addresses:
def address
  addr = gitaly_client.address(storage)
  # Remove protocol prefix for tcp:// and tls://
  addr = addr.sub(%r{^tcp://|^tls://}, '') if %w[tcp tls].include?(URI(addr).scheme)
  addr
end

Supported Address Formats

  • unix:/path/to/socket - Unix domain socket
  • tcp://hostname:port - Insecure TCP
  • tls://hostname:port - TCP with TLS

Distributed Tracing

The client supports GitLab LabKit tracing:
def initialize(gitaly_repository, call)
  @gitaly_repository = gitaly_repository
  @storage = gitaly_repository.storage_name
  @gitaly_client = GitalyServer.client(call)
  
  @interceptors = []
  if Labkit::Tracing.enabled?
    @interceptors << Labkit::Tracing::GRPC::ClientInterceptor.instance
  end
end
When tracing is enabled, interceptors automatically propagate trace context.

Creating Service Stubs

Pattern for creating gRPC service stubs:
# Create stub
stub = Gitaly::{ServiceName}::Stub.new(
  address,
  credentials,
  interceptors: @interceptors  # Optional
)

# Make RPC call
response = stub.rpc_method(request, request_kwargs)

Available Services

  • Gitaly::RefService::Stub - Reference operations
  • Gitaly::CommitService::Stub - Commit operations
  • Gitaly::RepositoryService::Stub - Repository operations

Complete Example

Here’s a complete example of using the Ruby client:
require 'gitlab-labkit'
require 'gitaly'

module Gitlab
  module Git
    # Create repository message
    repository = Gitaly::Repository.new(
      storage_name: 'default',
      relative_path: '@hashed/ab/cd/abcd1234.git'
    )
    
    # Initialize remote repository client
    remote = GitalyRemoteRepository.new(repository, call)
    
    # Check if branch exists
    if remote.branch_exists?('main')
      puts "Branch 'main' exists"
    end
    
    # Get commit ID
    commit_id = remote.commit_id('HEAD')
    puts "HEAD is at: #{commit_id}"
    
    # Check if repository is empty
    if remote.empty?
      puts "Repository is empty"
    else
      puts "Repository has content"
    end
  end
end

Configuration Requirements

Environment Variables

For TLS connections, set one or both:
export SSL_CERT_DIR=/etc/ssl/certs
export SSL_CERT_FILE=/etc/ssl/cert.pem

Dependencies

Required gems:
gem 'gitlab-labkit'  # For tracing support
gem 'grpc'           # gRPC library

Error Handling

Path Access Error

Remote repositories cannot be accessed by path:
def path
  raise 'gitaly-ruby cannot access remote repositories by path'
end

Certificate Errors

If SSL certificates are not configured:
def certs
  raise 'SSL_CERT_DIR and/or SSL_CERT_FILE environment variable must be set' \
    unless ENV['SSL_CERT_DIR'] || ENV['SSL_CERT_FILE']
  # ...
end

Best Practices

  1. Environment Configuration: Always set SSL certificate environment variables for TLS connections
  2. Error Handling: Wrap RPC calls in error handling for network failures
  3. Token Security: Keep shared secrets secure and rotate them regularly
  4. Interceptors: Enable tracing interceptors for observability
  5. Connection Reuse: The gRPC stub handles connection pooling automatically

API Patterns

All RPC calls follow this pattern:
# 1. Create request message
request = Gitaly::SomeRequest.new(params)

# 2. Create service stub
stub = Gitaly::SomeService::Stub.new(address, credentials)

# 3. Call RPC method with metadata
response = stub.some_method(request, request_kwargs)

# 4. Process response
result = response.some_field

Client Constants

CLIENT_NAME = 'gitaly-ruby'.freeze
PEM_REXP = /[-]+BEGIN CERTIFICATE[-]+.+?[-]+END CERTIFICATE[-]+/m.freeze
  • CLIENT_NAME: Identifies this client in request metadata
  • PEM_REXP: Regex for parsing PEM-encoded certificates

Build docs developers (and LLMs) love