2019-02-15 15:39:39 +05:30
# frozen_string_literal: true
2017-08-17 22:00:37 +05:30
require 'tempfile'
require 'forwardable'
require " rubygems/package "
module Gitlab
module Git
class Repository
2018-03-17 18:26:18 +05:30
include Gitlab :: Git :: RepositoryMirroring
2018-12-13 13:39:08 +05:30
include Gitlab :: Git :: WrapsGitalyErrors
2018-05-01 15:08:00 +05:30
include Gitlab :: EncodingHelper
2018-05-09 12:01:36 +05:30
include Gitlab :: Utils :: StrongMemoize
2019-05-03 19:53:19 +05:30
prepend Gitlab :: Git :: RuggedImpl :: Repository
2017-08-17 22:00:37 +05:30
SEARCH_CONTEXT_LINES = 3
2018-11-18 11:00:15 +05:30
REV_LIST_COMMIT_LIMIT = 2_000
2019-12-04 20:38:33 +05:30
GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'
2018-03-17 18:26:18 +05:30
GITLAB_PROJECTS_TIMEOUT = Gitlab . config . gitlab_shell . git_timeout
2019-12-04 20:38:33 +05:30
EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'
2017-08-17 22:00:37 +05:30
NoRepository = Class . new ( StandardError )
2018-10-15 14:42:47 +05:30
InvalidRepository = Class . new ( StandardError )
2017-08-17 22:00:37 +05:30
InvalidBlobName = Class . new ( StandardError )
InvalidRef = Class . new ( StandardError )
2018-03-17 18:26:18 +05:30
GitError = Class . new ( StandardError )
DeleteBranchError = Class . new ( StandardError )
TagExistsError = Class . new ( StandardError )
2018-05-09 12:01:36 +05:30
ChecksumError = Class . new ( StandardError )
2019-12-26 22:10:19 +05:30
class CreateTreeError < StandardError
attr_reader :error_code
def initialize ( error_code )
super ( self . class . name )
# The value coming from Gitaly is an uppercase String (e.g., "EMPTY")
@error_code = error_code . downcase . to_sym
end
end
2018-03-17 18:26:18 +05:30
2017-08-17 22:00:37 +05:30
# Directory name of repo
attr_reader :name
2018-03-17 18:26:18 +05:30
# Relative path of repo
attr_reader :relative_path
2020-10-24 23:57:45 +05:30
attr_reader :storage , :gl_repository , :gl_project_path
2017-08-17 22:00:37 +05:30
2019-02-15 15:39:39 +05:30
# This remote name has to be stable for all types of repositories that
# can join an object pool. If it's structure ever changes, a migration
# has to be performed on the object pools to update the remote names.
# Else the pool can't be updated anymore and is left in an inconsistent
# state.
alias_method :object_pool_remote_name , :gl_repository
2018-03-17 18:26:18 +05:30
# This initializer method is only used on the client side (gitlab-ce).
# Gitaly-ruby uses a different initializer.
2019-03-02 22:35:43 +05:30
def initialize ( storage , relative_path , gl_repository , gl_project_path )
2017-08-17 22:00:37 +05:30
@storage = storage
@relative_path = relative_path
2018-03-17 18:26:18 +05:30
@gl_repository = gl_repository
2019-03-02 22:35:43 +05:30
@gl_project_path = gl_project_path
2017-08-17 22:00:37 +05:30
@name = @relative_path . split ( " / " ) . last
end
2019-10-12 21:52:04 +05:30
def to_s
" < #{ self . class . name } : #{ self . gl_project_path } > "
end
2018-03-17 18:26:18 +05:30
def == ( other )
2019-02-15 15:39:39 +05:30
other . is_a? ( self . class ) && [ storage , relative_path ] == [ other . storage , other . relative_path ]
end
alias_method :eql? , :==
def hash
[ self . class , storage , relative_path ] . hash
2018-03-17 18:26:18 +05:30
end
2017-09-10 17:25:29 +05:30
2018-11-18 11:00:15 +05:30
# This method will be removed when Gitaly reaches v1.1.
2018-10-15 14:42:47 +05:30
def path
2018-11-18 11:00:15 +05:30
File . join (
2018-10-15 14:42:47 +05:30
Gitlab . config . repositories . storages [ @storage ] . legacy_disk_path , @relative_path
)
end
2017-08-17 22:00:37 +05:30
# Default branch in the repository
def root_ref
2018-11-08 19:23:39 +05:30
gitaly_ref_client . default_branch_name
rescue GRPC :: NotFound = > e
raise NoRepository . new ( e . message )
rescue GRPC :: Unknown = > e
raise Gitlab :: Git :: CommandError . new ( e . message )
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
def exists?
2018-10-15 14:42:47 +05:30
gitaly_repository_client . exists?
2018-03-17 18:26:18 +05:30
end
2019-07-31 22:56:46 +05:30
def create_repository
wrapped_gitaly_errors do
gitaly_repository_client . create_repository
end
end
2017-08-17 22:00:37 +05:30
# Returns an Array of branch names
# sorted by name ASC
def branch_names
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_ref_client . branch_names
2017-08-17 22:00:37 +05:30
end
end
# Returns an Array of Branches
def branches
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_ref_client . branches
2017-09-10 17:25:29 +05:30
end
2017-08-17 22:00:37 +05:30
end
# Directly find a branch with a simple name (e.g. master)
#
2018-11-18 11:00:15 +05:30
def find_branch ( name )
wrapped_gitaly_errors do
gitaly_ref_client . find_branch ( name )
2017-09-10 17:25:29 +05:30
end
2017-08-17 22:00:37 +05:30
end
2020-07-28 23:09:34 +05:30
def local_branches ( sort_by : nil , pagination_params : nil )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
2020-07-28 23:09:34 +05:30
gitaly_ref_client . local_branches ( sort_by : sort_by , pagination_params : pagination_params )
2017-08-17 22:00:37 +05:30
end
end
# Returns the number of valid branches
def branch_count
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_ref_client . count_branch_names
2017-08-17 22:00:37 +05:30
end
2019-12-21 20:55:43 +05:30
end
def rename ( new_relative_path )
wrapped_gitaly_errors do
gitaly_repository_client . rename ( new_relative_path )
end
end
def remove
wrapped_gitaly_errors do
gitaly_repository_client . remove
end
2017-08-17 22:00:37 +05:30
end
2020-04-08 14:13:33 +05:30
def replicate ( source_repository )
wrapped_gitaly_errors do
gitaly_repository_client . replicate ( source_repository )
end
end
2018-05-09 12:01:36 +05:30
def expire_has_local_branches_cache
clear_memoization ( :has_local_branches )
end
2018-03-17 18:26:18 +05:30
def has_local_branches?
2018-05-09 12:01:36 +05:30
strong_memoize ( :has_local_branches ) do
uncached_has_local_branches?
2018-03-17 18:26:18 +05:30
end
end
# Git repository can contains some hidden refs like:
# /refs/notes/*
# /refs/git-as-svn/*
# /refs/pulls/*
# This refs by default not visible in project page and not cloned to client side.
alias_method :has_visible_content? , :has_local_branches?
2017-08-17 22:00:37 +05:30
# Returns the number of valid tags
def tag_count
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_ref_client . count_tag_names
2017-08-17 22:00:37 +05:30
end
end
# Returns an Array of tag names
def tag_names
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_ref_client . tag_names
2017-08-17 22:00:37 +05:30
end
end
# Returns an Array of Tags
2017-09-10 17:25:29 +05:30
#
2017-08-17 22:00:37 +05:30
def tags
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_ref_client . tags
2017-09-10 17:25:29 +05:30
end
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
def ref_exists? ( ref_name )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_ref_exists? ( ref_name )
2018-03-17 18:26:18 +05:30
end
end
2017-08-17 22:00:37 +05:30
# Returns true if the given tag exists
#
# name - The name of the tag as a String.
def tag_exists? ( name )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_ref_exists? ( " refs/tags/ #{ name } " )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
end
# Returns true if the given branch exists
#
# name - The name of the branch as a String.
def branch_exists? ( name )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_ref_exists? ( " refs/heads/ #{ name } " )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
end
# Returns an Array of branch and tag names
def ref_names
branch_names + tag_names
end
2018-03-17 18:26:18 +05:30
def delete_all_refs_except ( prefixes )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_ref_client . delete_refs ( except_with_prefixes : prefixes )
2018-03-17 18:26:18 +05:30
end
end
2019-07-31 22:56:46 +05:30
def archive_metadata ( ref , storage_path , project_path , format = " tar.gz " , append_sha : , path : nil )
2017-08-17 22:00:37 +05:30
ref || = root_ref
commit = Gitlab :: Git :: Commit . find ( self , ref )
return { } if commit . nil?
2019-07-31 22:56:46 +05:30
prefix = archive_prefix ( ref , commit . id , project_path , append_sha : append_sha , path : path )
2017-08-17 22:00:37 +05:30
{
'ArchivePrefix' = > prefix ,
2018-05-09 12:01:36 +05:30
'ArchivePath' = > archive_file_path ( storage_path , commit . id , prefix , format ) ,
2018-11-08 19:23:39 +05:30
'CommitId' = > commit . id ,
'GitalyRepository' = > gitaly_repository . to_h
2017-08-17 22:00:37 +05:30
}
end
2018-05-09 12:01:36 +05:30
# This is both the filename of the archive (missing the extension) and the
# name of the top-level member of the archive under which all files go
2019-07-31 22:56:46 +05:30
def archive_prefix ( ref , sha , project_path , append_sha : , path : )
2018-05-09 12:01:36 +05:30
append_sha = ( ref != sha ) if append_sha . nil?
formatted_ref = ref . tr ( '/' , '-' )
2018-11-08 19:23:39 +05:30
prefix_segments = [ project_path , formatted_ref ]
2018-05-09 12:01:36 +05:30
prefix_segments << sha if append_sha
2019-07-31 22:56:46 +05:30
prefix_segments << path . tr ( '/' , '-' ) . gsub ( %r{ ^/|/$ } , '' ) if path
2018-05-09 12:01:36 +05:30
prefix_segments . join ( '-' )
end
private :archive_prefix
# The full path on disk where the archive should be stored. This is used
# to cache the archive between requests.
#
# The path is a global namespace, so needs to be globally unique. This is
# achieved by including `gl_repository` in the path.
#
# Archives relating to a particular ref when the SHA is not present in the
# filename must be invalidated when the ref is updated to point to a new
# SHA. This is achieved by including the SHA in the path.
#
# As this is a full path on disk, it is not "cloud native". This should
# be resolved by either removing the cache, or moving the implementation
# into Gitaly and removing the ArchivePath parameter from the git-archive
# senddata response.
def archive_file_path ( storage_path , sha , name , format = " tar.gz " )
2017-08-17 22:00:37 +05:30
# Build file path
2019-07-07 11:18:12 +05:30
return unless name
2017-08-17 22:00:37 +05:30
extension =
case format
when " tar.bz2 " , " tbz " , " tbz2 " , " tb2 " , " bz2 "
" tar.bz2 "
when " tar "
" tar "
when " zip "
" zip "
else
# everything else should fall back to tar.gz
" tar.gz "
end
file_name = " #{ name } . #{ extension } "
2018-05-09 12:01:36 +05:30
File . join ( storage_path , self . gl_repository , sha , file_name )
2017-08-17 22:00:37 +05:30
end
2018-05-09 12:01:36 +05:30
private :archive_file_path
2017-08-17 22:00:37 +05:30
# Return repo size in megabytes
def size
2018-11-08 19:23:39 +05:30
size = gitaly_repository_client . repository_size
2017-08-17 22:00:37 +05:30
2017-09-10 17:25:29 +05:30
( size . to_f / 1024 ) . round ( 2 )
2017-08-17 22:00:37 +05:30
end
2019-07-07 11:18:12 +05:30
# Return git object directory size in bytes
def object_directory_size
gitaly_repository_client . get_object_directory_size . to_f * 1024
end
2018-12-05 23:21:45 +05:30
# Build an array of commits.
2017-08-17 22:00:37 +05:30
#
# Usage.
# repo.log(
# ref: 'master',
# path: 'app/models',
# limit: 10,
# offset: 5,
# after: Time.new(2016, 4, 21, 14, 32, 10)
# )
def log ( options )
default_options = {
limit : 10 ,
offset : 0 ,
path : nil ,
2020-04-08 14:13:33 +05:30
author : nil ,
2017-08-17 22:00:37 +05:30
follow : false ,
skip_merges : false ,
after : nil ,
2018-03-27 19:54:05 +05:30
before : nil ,
all : false
2017-08-17 22:00:37 +05:30
}
options = default_options . merge ( options )
options [ :offset ] || = 0
2018-03-17 18:26:18 +05:30
limit = options [ :limit ]
if limit == 0 || ! limit . is_a? ( Integer )
raise ArgumentError . new ( " invalid Repository # log limit: #{ limit . inspect } " )
end
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_commit_client . find_commits ( options )
end
end
def new_commits ( newrev )
2018-11-20 20:47:30 +05:30
wrapped_gitaly_errors do
gitaly_ref_client . list_new_commits ( newrev )
2018-03-17 18:26:18 +05:30
end
end
2019-07-07 11:18:12 +05:30
def new_blobs ( newrev , dynamic_timeout : nil )
2018-11-18 11:00:15 +05:30
return [ ] if newrev . blank? || newrev == :: Gitlab :: Git :: BLANK_SHA
2018-03-17 18:26:18 +05:30
2018-11-18 11:00:15 +05:30
strong_memoize ( " new_blobs_ #{ newrev } " ) do
wrapped_gitaly_errors do
2019-07-07 11:18:12 +05:30
gitaly_ref_client . list_new_blobs ( newrev , REV_LIST_COMMIT_LIMIT , dynamic_timeout : dynamic_timeout )
2018-11-18 11:00:15 +05:30
end
end
2017-08-17 22:00:37 +05:30
end
def count_commits ( options )
2018-11-08 19:23:39 +05:30
options = process_count_commits_options ( options . dup )
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
if options [ :left_right ]
from = options [ :from ]
to = options [ :to ]
right_count = gitaly_commit_client
. commit_count ( " #{ from } .. #{ to } " , options )
left_count = gitaly_commit_client
. commit_count ( " #{ to } .. #{ from } " , options )
[ left_count , right_count ]
2017-09-10 17:25:29 +05:30
else
2018-11-08 19:23:39 +05:30
gitaly_commit_client . commit_count ( options [ :ref ] , options )
2017-09-10 17:25:29 +05:30
end
end
2017-08-17 22:00:37 +05:30
end
# Counts the amount of commits between `from` and `to`.
2018-03-17 18:26:18 +05:30
def count_commits_between ( from , to , options = { } )
count_commits ( from : from , to : to , ** options )
2017-08-17 22:00:37 +05:30
end
2018-10-15 14:42:47 +05:30
# old_rev and new_rev are commit ID's
# the result of this method is an array of Gitlab::Git::RawDiffChange
def raw_changes_between ( old_rev , new_rev )
@raw_changes_between || = { }
2018-11-08 19:23:39 +05:30
@raw_changes_between [ [ old_rev , new_rev ] ] || =
begin
return [ ] if new_rev . blank? || new_rev == Gitlab :: Git :: BLANK_SHA
2018-10-15 14:42:47 +05:30
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
2018-10-15 14:42:47 +05:30
gitaly_repository_client . raw_changes_between ( old_rev , new_rev )
. each_with_object ( [ ] ) do | msg , arr |
msg . raw_changes . each { | change | arr << :: Gitlab :: Git :: RawDiffChange . new ( change ) }
end
end
end
rescue ArgumentError = > e
raise Gitlab :: Git :: Repository :: GitError . new ( e )
end
2017-08-17 22:00:37 +05:30
# Returns the SHA of the most recent common ancestor of +from+ and +to+
2018-12-13 13:39:08 +05:30
def merge_base ( * commits )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
2018-12-13 13:39:08 +05:30
gitaly_repository_client . find_merge_base ( * commits )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
end
# Returns true is +from+ is direct ancestor to +to+, otherwise false
2018-03-17 18:26:18 +05:30
def ancestor? ( from , to )
2018-11-08 19:23:39 +05:30
gitaly_commit_client . ancestor? ( from , to )
2018-03-17 18:26:18 +05:30
end
def merged_branch_names ( branch_names = [ ] )
return [ ] unless root_ref
root_sha = find_branch ( root_ref ) & . target
return [ ] unless root_sha
2018-11-18 11:00:15 +05:30
branches = wrapped_gitaly_errors do
gitaly_merged_branch_names ( branch_names , root_sha )
2018-03-17 18:26:18 +05:30
end
Set . new ( branches )
2017-08-17 22:00:37 +05:30
end
# Return an array of Diff objects that represent the diff
# between +from+ and +to+. See Diff::filter_diff_options for the allowed
# diff options. The +options+ hash can also include :break_rewrites to
# split larger rewrites into delete/add pairs.
def diff ( from , to , options = { } , * paths )
2018-11-18 11:00:15 +05:30
iterator = gitaly_commit_client . diff ( from , to , options . merge ( paths : paths ) )
2018-03-17 18:26:18 +05:30
Gitlab :: Git :: DiffCollection . new ( iterator , options )
2017-08-17 22:00:37 +05:30
end
2018-12-05 23:21:45 +05:30
def diff_stats ( left_id , right_id )
2019-02-15 15:39:39 +05:30
if [ left_id , right_id ] . any? { | ref | ref . blank? || Gitlab :: Git . blank_ref? ( ref ) }
return empty_diff_stats
end
2018-12-05 23:21:45 +05:30
stats = wrapped_gitaly_errors do
gitaly_commit_client . diff_stats ( left_id , right_id )
end
Gitlab :: Git :: DiffStatsCollection . new ( stats )
rescue CommandError , TypeError
2019-02-15 15:39:39 +05:30
empty_diff_stats
2018-12-05 23:21:45 +05:30
end
2017-08-17 22:00:37 +05:30
# Returns a RefName for a given SHA
def ref_name_for_sha ( ref_path , sha )
raise ArgumentError , " sha can't be empty " unless sha . present?
2018-11-08 19:23:39 +05:30
gitaly_ref_client . find_ref_name ( sha , ref_path )
2017-08-17 22:00:37 +05:30
end
2019-02-15 15:39:39 +05:30
# Get refs hash which key is the commit id
2018-03-17 18:26:18 +05:30
# and value is a Gitlab::Git::Tag or Gitlab::Git::Branch
# Note that both inherit from Gitlab::Git::Ref
def refs_hash
return @refs_hash if @refs_hash
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
@refs_hash = Hash . new { | h , k | h [ k ] = [ ] }
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
( tags + branches ) . each do | ref |
2019-07-07 11:18:12 +05:30
next unless ref . target && ref . name && ref . dereferenced_target & . id
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
@refs_hash [ ref . dereferenced_target . id ] << ref . name
2017-08-17 22:00:37 +05:30
end
@refs_hash
end
2017-09-10 17:25:29 +05:30
# Returns url for submodule
2017-08-17 22:00:37 +05:30
#
# Ex.
2017-09-10 17:25:29 +05:30
# @repository.submodule_url_for('master', 'rack')
# # => git@localhost:rack.git
2017-08-17 22:00:37 +05:30
#
2017-09-10 17:25:29 +05:30
def submodule_url_for ( ref , path )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_submodule_url_for ( ref , path )
2017-08-17 22:00:37 +05:30
end
end
2019-09-30 21:07:59 +05:30
# Returns path to url mappings for submodules
#
# Ex.
# @repository.submodule_urls_for('master')
# # => { 'rack' => 'git@localhost:rack.git' }
#
def submodule_urls_for ( ref )
wrapped_gitaly_errors do
gitaly_submodule_urls_for ( ref )
end
end
2017-08-17 22:00:37 +05:30
# Return total commits count accessible from passed ref
def commit_count ( ref )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_commit_client . commit_count ( ref )
2017-09-10 17:25:29 +05:30
end
2017-08-17 22:00:37 +05:30
end
2019-07-07 11:18:12 +05:30
# Return total diverging commits count
2019-09-04 21:01:54 +05:30
def diverging_commit_count ( from , to , max_count : 0 )
2019-07-07 11:18:12 +05:30
wrapped_gitaly_errors do
gitaly_commit_client . diverging_commit_count ( from , to , max_count : max_count )
end
end
2017-08-17 22:00:37 +05:30
# Mimic the `git clean` command and recursively delete untracked files.
# Valid keys that can be passed in the +options+ hash are:
#
# :d - Remove untracked directories
# :f - Remove untracked directories that are managed by a different
# repository
# :x - Remove ignored files
#
# The value in +options+ must evaluate to true for an option to take
# effect.
#
# Examples:
#
# repo.clean(d: true, f: true) # Enable the -d and -f options
#
# repo.clean(d: false, x: true) # -x is enabled, -d is not
def clean ( options = { } )
strategies = [ :remove_untracked ]
strategies . push ( :force ) if options [ :f ]
strategies . push ( :remove_ignored ) if options [ :x ]
# TODO: implement this method
end
2018-03-17 18:26:18 +05:30
def add_branch ( branch_name , user : , target : )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . user_create_branch ( branch_name , user , target )
2018-03-17 18:26:18 +05:30
end
end
def add_tag ( tag_name , user : , target : , message : nil )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . add_tag ( tag_name , user , target , message )
2018-03-17 18:26:18 +05:30
end
end
2018-11-08 19:23:39 +05:30
def update_branch ( branch_name , user : , newrev : , oldrev : )
2018-11-20 20:47:30 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . user_update_branch ( branch_name , user , newrev , oldrev )
2018-11-18 11:00:15 +05:30
end
2018-11-08 19:23:39 +05:30
end
2018-03-17 18:26:18 +05:30
def rm_branch ( branch_name , user : )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . user_delete_branch ( branch_name , user )
2018-03-17 18:26:18 +05:30
end
end
def rm_tag ( tag_name , user : )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . rm_tag ( tag_name , user )
2018-03-17 18:26:18 +05:30
end
end
def find_tag ( name )
tags . find { | tag | tag . name == name }
end
2019-09-30 21:07:59 +05:30
def merge_to_ref ( user , source_sha , branch , target_ref , message , first_parent_ref )
2019-07-07 11:18:12 +05:30
wrapped_gitaly_errors do
2019-09-30 21:07:59 +05:30
gitaly_operation_client . user_merge_to_ref ( user , source_sha , branch , target_ref , message , first_parent_ref )
2019-07-07 11:18:12 +05:30
end
end
2018-03-17 18:26:18 +05:30
def merge ( user , source_sha , target_branch , message , & block )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . user_merge_branch ( user , source_sha , target_branch , message , & block )
2018-03-17 18:26:18 +05:30
end
end
def ff_merge ( user , source_sha , target_branch )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . user_ff_branch ( user , source_sha , target_branch )
2018-03-17 18:26:18 +05:30
end
end
2020-10-24 23:57:45 +05:30
def revert ( user : , commit : , branch_name : , message : , start_branch_name : , start_repository : , dry_run : false )
2018-11-08 19:23:39 +05:30
args = {
user : user ,
commit : commit ,
branch_name : branch_name ,
message : message ,
start_branch_name : start_branch_name ,
2020-10-24 23:57:45 +05:30
start_repository : start_repository ,
dry_run : dry_run
2018-11-08 19:23:39 +05:30
}
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . user_revert ( args )
2018-03-17 18:26:18 +05:30
end
end
2020-10-24 23:57:45 +05:30
def cherry_pick ( user : , commit : , branch_name : , message : , start_branch_name : , start_repository : , dry_run : false )
2018-11-08 19:23:39 +05:30
args = {
user : user ,
commit : commit ,
branch_name : branch_name ,
message : message ,
start_branch_name : start_branch_name ,
2020-10-24 23:57:45 +05:30
start_repository : start_repository ,
dry_run : dry_run
2018-11-08 19:23:39 +05:30
}
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . user_cherry_pick ( args )
2018-03-17 18:26:18 +05:30
end
end
2018-12-13 13:39:08 +05:30
def update_submodule ( user : , submodule : , commit_sha : , message : , branch : )
args = {
user : user ,
submodule : submodule ,
commit_sha : commit_sha ,
branch : branch ,
message : message
}
wrapped_gitaly_errors do
gitaly_operation_client . user_update_submodule ( args )
end
end
2017-08-17 22:00:37 +05:30
# Delete the specified branch from the repository
2020-03-13 15:44:24 +05:30
# Note: No Git hooks are executed for this action
2017-08-17 22:00:37 +05:30
def delete_branch ( branch_name )
2020-03-13 15:44:24 +05:30
write_ref ( branch_name , Gitlab :: Git :: BLANK_SHA )
2018-11-18 11:00:15 +05:30
rescue CommandError = > e
2018-03-17 18:26:18 +05:30
raise DeleteBranchError , e
end
def delete_refs ( * ref_names )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_delete_refs ( * ref_names )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
end
# Create a new branch named **ref+ based on **stat_point+, HEAD by default
2020-03-13 15:44:24 +05:30
# Note: No Git hooks are executed for this action
2017-08-17 22:00:37 +05:30
#
# Examples:
# create_branch("feature")
# create_branch("other-feature", "master")
def create_branch ( ref , start_point = " HEAD " )
2020-03-13 15:44:24 +05:30
write_ref ( ref , start_point )
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
# If `mirror_refmap` is present the remote is set as mirror with that mapping
def add_remote ( remote_name , url , mirror_refmap : nil )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_remote_client . add_remote ( remote_name , url , mirror_refmap )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
def remove_remote ( remote_name )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_remote_client . remove_remote ( remote_name )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
end
2018-11-20 20:47:30 +05:30
def find_remote_root_ref ( remote_name )
return unless remote_name . present?
wrapped_gitaly_errors do
gitaly_remote_client . find_remote_root_ref ( remote_name )
end
end
2017-08-17 22:00:37 +05:30
# Returns result like "git ls-files" , recursive and full file path
#
# Ex.
# repo.ls_files('master')
#
def ls_files ( ref )
2018-11-08 19:23:39 +05:30
gitaly_commit_client . ls_files ( ref )
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
def copy_gitattributes ( ref )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_repository_client . apply_gitattributes ( ref )
2017-08-17 22:00:37 +05:30
end
end
2018-10-15 14:42:47 +05:30
def info_attributes
return @info_attributes if @info_attributes
2018-11-08 19:23:39 +05:30
content = gitaly_repository_client . info_attributes
2018-10-15 14:42:47 +05:30
@info_attributes = AttributesParser . new ( content )
end
2017-08-17 22:00:37 +05:30
# Returns the Git attributes for the given file path.
#
# See `Gitlab::Git::Attributes` for more information.
def attributes ( path )
2018-10-15 14:42:47 +05:30
info_attributes . attributes ( path )
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
def gitattribute ( path , name )
attributes ( path ) [ name ]
end
2019-09-30 21:07:59 +05:30
# Returns parsed .gitattributes for a given ref
2018-03-17 18:26:18 +05:30
#
2019-09-30 21:07:59 +05:30
# This only parses the root .gitattributes file,
2018-03-17 18:26:18 +05:30
# it does not traverse subfolders to find additional .gitattributes files
#
2018-05-09 12:01:36 +05:30
# This method is around 30 times slower than `attributes`, which uses
# `$GIT_DIR/info/attributes`. Consider caching AttributesAtRefParser
# and reusing that for multiple calls instead of this method.
2019-09-30 21:07:59 +05:30
def attributes_at ( ref )
AttributesAtRefParser . new ( self , ref )
2018-03-17 18:26:18 +05:30
end
2017-09-10 17:25:29 +05:30
def languages ( ref = nil )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_commit_client . languages ( ref )
2017-09-10 17:25:29 +05:30
end
end
2018-03-27 19:54:05 +05:30
def license_short_name
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_repository_client . license_short_name
2018-03-27 19:54:05 +05:30
end
end
2018-03-17 18:26:18 +05:30
def fetch_source_branch! ( source_repository , source_branch , local_ref )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_repository_client . fetch_source_branch ( source_repository , source_branch , local_ref )
2018-03-17 18:26:18 +05:30
end
end
def compare_source_branch ( target_branch_name , source_repository , source_branch_name , straight : )
2020-02-01 01:16:34 +05:30
CrossRepoComparer
. new ( source_repository , self )
. compare ( source_branch_name , target_branch_name , straight : straight )
2018-03-17 18:26:18 +05:30
end
2019-02-15 15:39:39 +05:30
def write_ref ( ref_path , ref , old_ref : nil )
2018-03-17 18:26:18 +05:30
ref_path = " #{ Gitlab :: Git :: BRANCH_REF_PREFIX } #{ ref_path } " unless ref_path . start_with? ( " refs/ " ) || ref_path == " HEAD "
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
2019-02-15 15:39:39 +05:30
gitaly_repository_client . write_ref ( ref_path , ref , old_ref )
2018-03-17 18:26:18 +05:30
end
end
# Refactoring aid; allows us to copy code from app/models/repository.rb
def commit ( ref = 'HEAD' )
Gitlab :: Git :: Commit . find ( self , ref )
end
def empty?
! has_visible_content?
end
2018-12-13 13:39:08 +05:30
# Fetch remote for repository
#
# remote - remote name
# ssh_auth - SSH known_hosts data and a private key to use for public-key authentication
# forced - should we use --force flag?
# no_tags - should we use --no-tags flag?
# prune - should we use --prune flag?
def fetch_remote ( remote , ssh_auth : nil , forced : false , no_tags : false , prune : true )
wrapped_gitaly_errors do
gitaly_repository_client . fetch_remote (
remote ,
ssh_auth : ssh_auth ,
forced : forced ,
no_tags : no_tags ,
prune : prune ,
timeout : GITLAB_PROJECTS_TIMEOUT
)
end
end
2020-04-08 14:13:33 +05:30
def import_repository ( url )
raise ArgumentError , " don't use disk paths with import_repository: #{ url . inspect } " if url . start_with? ( '.' , '/' )
wrapped_gitaly_errors do
gitaly_repository_client . import_repository ( url )
end
end
2018-03-17 18:26:18 +05:30
def blob_at ( sha , path )
Gitlab :: Git :: Blob . find ( self , sha , path ) unless Gitlab :: Git . blank_ref? ( sha )
end
# Items should be of format [[commit_id, path], [commit_id1, path1]]
def batch_blobs ( items , blob_size_limit : Gitlab :: Git :: Blob :: MAX_DATA_DISPLAY_SIZE )
Gitlab :: Git :: Blob . batch ( self , items , blob_size_limit : blob_size_limit )
end
def fsck
2018-05-09 12:01:36 +05:30
msg , status = gitaly_repository_client . fsck
2018-03-17 18:26:18 +05:30
2020-10-24 23:57:45 +05:30
raise GitError . new ( " Could not fsck repository: #{ msg } " ) unless status == 0
2018-03-17 18:26:18 +05:30
end
def create_from_bundle ( bundle_path )
2019-01-20 21:35:32 +05:30
# It's important to check that the linked-to file is actually a valid
# .bundle file as it is passed to `git clone`, which may otherwise
# interpret it as a pointer to another repository
:: Gitlab :: Git :: BundleFile . check! ( bundle_path )
2018-11-08 19:23:39 +05:30
gitaly_repository_client . create_from_bundle ( bundle_path )
2018-03-17 18:26:18 +05:30
end
2018-05-09 12:01:36 +05:30
def create_from_snapshot ( url , auth )
gitaly_repository_client . create_from_snapshot ( url , auth )
end
2020-03-13 15:44:24 +05:30
def rebase ( user , rebase_id , branch : , branch_sha : , remote_repository : , remote_branch : , push_options : [ ] , & block )
2019-07-31 22:56:46 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . rebase (
user ,
rebase_id ,
branch : branch ,
branch_sha : branch_sha ,
remote_repository : remote_repository ,
remote_branch : remote_branch ,
2020-03-13 15:44:24 +05:30
push_options : push_options ,
2019-07-31 22:56:46 +05:30
& block
)
end
end
2018-03-17 18:26:18 +05:30
def rebase_in_progress? ( rebase_id )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_repository_client . rebase_in_progress? ( rebase_id )
2018-03-17 18:26:18 +05:30
end
end
2020-04-08 14:13:33 +05:30
def squash ( user , squash_id , start_sha : , end_sha : , author : , message : )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
2020-04-08 14:13:33 +05:30
gitaly_operation_client . user_squash ( user , squash_id , start_sha , end_sha , author , message )
2018-03-17 18:26:18 +05:30
end
end
def squash_in_progress? ( squash_id )
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_repository_client . squash_in_progress? ( squash_id )
2018-03-27 19:54:05 +05:30
end
2018-03-17 18:26:18 +05:30
end
def bundle_to_disk ( save_path )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_repository_client . create_bundle ( save_path )
2018-03-17 18:26:18 +05:30
end
true
end
2019-07-07 11:18:12 +05:30
# rubocop:disable Metrics/ParameterLists
2018-03-17 18:26:18 +05:30
def multi_action (
user , branch_name : , message : , actions : ,
author_email : nil , author_name : nil ,
2019-10-12 21:52:04 +05:30
start_branch_name : nil , start_sha : nil , start_repository : self ,
2019-07-07 11:18:12 +05:30
force : false )
2018-03-17 18:26:18 +05:30
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_operation_client . user_commit_files ( user , branch_name ,
2018-03-17 18:26:18 +05:30
message , actions , author_email , author_name ,
2019-10-12 21:52:04 +05:30
start_branch_name , start_repository , force , start_sha )
2018-03-17 18:26:18 +05:30
end
end
2019-07-07 11:18:12 +05:30
# rubocop:enable Metrics/ParameterLists
2018-03-17 18:26:18 +05:30
def write_config ( full_path : )
return unless full_path . present?
2018-11-08 19:23:39 +05:30
# This guard avoids Gitaly log/error spam
raise NoRepository , 'repository does not exist' unless exists?
set_config ( 'gitlab.fullpath' = > full_path )
end
def set_config ( entries )
wrapped_gitaly_errors do
gitaly_repository_client . set_config ( entries )
2018-03-17 18:26:18 +05:30
end
end
2018-11-08 19:23:39 +05:30
def delete_config ( * keys )
wrapped_gitaly_errors do
gitaly_repository_client . delete_config ( keys )
end
2018-03-17 18:26:18 +05:30
end
2019-07-31 22:56:46 +05:30
def disconnect_alternates
wrapped_gitaly_errors do
gitaly_repository_client . disconnect_alternates
end
end
2018-11-08 19:23:39 +05:30
def gitaly_repository
2019-03-02 22:35:43 +05:30
Gitlab :: GitalyClient :: Util . repository ( @storage , @relative_path , @gl_repository , @gl_project_path )
2017-08-17 22:00:37 +05:30
end
2017-09-10 17:25:29 +05:30
def gitaly_ref_client
@gitaly_ref_client || = Gitlab :: GitalyClient :: RefService . new ( self )
end
def gitaly_commit_client
@gitaly_commit_client || = Gitlab :: GitalyClient :: CommitService . new ( self )
end
def gitaly_repository_client
@gitaly_repository_client || = Gitlab :: GitalyClient :: RepositoryService . new ( self )
end
2018-03-17 18:26:18 +05:30
def gitaly_operation_client
@gitaly_operation_client || = Gitlab :: GitalyClient :: OperationService . new ( self )
end
def gitaly_remote_client
@gitaly_remote_client || = Gitlab :: GitalyClient :: RemoteService . new ( self )
end
def gitaly_blob_client
@gitaly_blob_client || = Gitlab :: GitalyClient :: BlobService . new ( self )
end
def gitaly_conflicts_client ( our_commit_oid , their_commit_oid )
Gitlab :: GitalyClient :: ConflictsService . new ( self , our_commit_oid , their_commit_oid )
end
2020-04-22 19:07:51 +05:30
def praefect_info_client
@praefect_info_client || = Gitlab :: GitalyClient :: PraefectInfoService . new ( self )
end
2018-05-09 12:01:36 +05:30
def clean_stale_repository_files
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_repository_client . cleanup if exists?
2018-05-09 12:01:36 +05:30
end
rescue Gitlab :: Git :: CommandError = > e # Don't fail if we can't cleanup
2019-09-30 21:07:59 +05:30
Rails . logger . error ( " Unable to clean repository on storage #{ storage } with relative path #{ relative_path } : #{ e . message } " ) # rubocop:disable Gitlab/RailsLogger
2018-05-09 12:01:36 +05:30
Gitlab :: Metrics . counter (
:failed_repository_cleanup_total ,
'Number of failed repository cleanup events'
) . increment
end
2018-03-17 18:26:18 +05:30
def branch_names_contains_sha ( sha )
2018-11-08 19:23:39 +05:30
gitaly_ref_client . branch_names_contains_sha ( sha )
2018-03-17 18:26:18 +05:30
end
def tag_names_contains_sha ( sha )
2018-11-08 19:23:39 +05:30
gitaly_ref_client . tag_names_contains_sha ( sha )
2018-03-17 18:26:18 +05:30
end
2020-03-13 15:44:24 +05:30
def search_files_by_content ( query , ref , options = { } )
2018-03-17 18:26:18 +05:30
return [ ] if empty? || query . blank?
2018-11-08 19:23:39 +05:30
safe_query = Regexp . escape ( query )
ref || = root_ref
2018-03-17 18:26:18 +05:30
2020-03-13 15:44:24 +05:30
gitaly_repository_client . search_files_by_content ( ref , safe_query , options )
2018-03-17 18:26:18 +05:30
end
def can_be_merged? ( source_sha , target_branch )
2018-11-18 11:00:15 +05:30
if target_sha = find_branch ( target_branch ) & . target
2018-11-08 19:23:39 +05:30
! gitaly_conflicts_client ( source_sha , target_sha ) . conflicts?
else
false
2018-03-17 18:26:18 +05:30
end
end
def search_files_by_name ( query , ref )
safe_query = Regexp . escape ( query . sub ( %r{ ^/* } , " " ) )
2018-11-08 19:23:39 +05:30
ref || = root_ref
2018-03-17 18:26:18 +05:30
return [ ] if empty? || safe_query . blank?
2018-11-08 19:23:39 +05:30
gitaly_repository_client . search_files_by_name ( ref , safe_query )
2018-03-17 18:26:18 +05:30
end
def find_commits_by_message ( query , ref , path , limit , offset )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
gitaly_commit_client
. commits_by_message ( query , revision : ref , path : path , limit : limit , offset : offset )
. map { | c | commit ( c ) }
2018-03-17 18:26:18 +05:30
end
end
2020-07-28 23:09:34 +05:30
def list_last_commits_for_tree ( sha , path , offset : 0 , limit : 25 , literal_pathspec : false )
2018-12-05 23:21:45 +05:30
wrapped_gitaly_errors do
2020-07-28 23:09:34 +05:30
gitaly_commit_client . list_last_commits_for_tree ( sha , path , offset : offset , limit : limit , literal_pathspec : literal_pathspec )
2018-12-05 23:21:45 +05:30
end
2018-03-17 18:26:18 +05:30
end
2020-07-28 23:09:34 +05:30
def list_commits_by_ref_name ( refs )
2018-11-18 11:00:15 +05:30
wrapped_gitaly_errors do
2020-07-28 23:09:34 +05:30
gitaly_commit_client . list_commits_by_ref_name ( refs )
end
end
def last_commit_for_path ( sha , path , literal_pathspec : false )
wrapped_gitaly_errors do
gitaly_commit_client . last_commit_for_path ( sha , path , literal_pathspec : literal_pathspec )
2018-03-17 18:26:18 +05:30
end
end
2018-05-09 12:01:36 +05:30
def checksum
2018-11-08 19:23:39 +05:30
# The exists? RPC is much cheaper, so we perform this request first
raise NoRepository , " Repository does not exists " unless exists?
gitaly_repository_client . calculate_checksum
rescue GRPC :: NotFound
raise NoRepository # Guard against data races.
2018-05-09 12:01:36 +05:30
end
2020-04-22 19:07:51 +05:30
def replicas
wrapped_gitaly_errors do
praefect_info_client . replicas
end
end
2017-08-17 22:00:37 +05:30
private
2019-02-15 15:39:39 +05:30
def empty_diff_stats
Gitlab :: Git :: DiffStatsCollection . new ( [ ] )
end
2018-05-09 12:01:36 +05:30
def uncached_has_local_branches?
2018-11-08 19:23:39 +05:30
wrapped_gitaly_errors do
gitaly_repository_client . has_local_branches?
2018-05-09 12:01:36 +05:30
end
end
2018-03-17 18:26:18 +05:30
def gitaly_merged_branch_names ( branch_names , root_sha )
qualified_branch_names = branch_names . map { | b | " refs/heads/ #{ b } " }
gitaly_ref_client . merged_branches ( qualified_branch_names )
. reject { | b | b . target == root_sha }
. map ( & :name )
2017-09-10 17:25:29 +05:30
end
2018-03-17 18:26:18 +05:30
def process_count_commits_options ( options )
if options [ :from ] || options [ :to ]
ref =
if options [ :left_right ] # Compare with merge-base for left-right
" #{ options [ :from ] } ... #{ options [ :to ] } "
else
" #{ options [ :from ] } .. #{ options [ :to ] } "
end
options . merge ( ref : ref )
elsif options [ :ref ] && options [ :left_right ]
from , to = options [ :ref ] . match ( / \ A([^ \ .]*) \ .{2,3}([^ \ .]*) \ z / ) [ 1 .. 2 ]
options . merge ( from : from , to : to )
else
options
end
2017-09-10 17:25:29 +05:30
end
def gitaly_submodule_url_for ( ref , path )
# We don't care about the contents so 1 byte is enough. Can't request 0 bytes, 0 means unlimited.
commit_object = gitaly_commit_client . tree_entry ( ref , path , 1 )
return unless commit_object && commit_object . type == :COMMIT
2019-09-30 21:07:59 +05:30
urls = gitaly_submodule_urls_for ( ref )
urls && urls [ path ]
end
def gitaly_submodule_urls_for ( ref )
2017-09-10 17:25:29 +05:30
gitmodules = gitaly_commit_client . tree_entry ( ref , '.gitmodules' , Gitlab :: Git :: Blob :: MAX_DATA_DISPLAY_SIZE )
return unless gitmodules
2019-09-30 21:07:59 +05:30
submodules = GitmodulesParser . new ( gitmodules . data ) . parse
submodules . transform_values { | submodule | submodule [ 'url' ] }
2017-09-10 17:25:29 +05:30
end
2018-03-17 18:26:18 +05:30
# Returns true if the given ref name exists
#
# Ref names must start with `refs/`.
def gitaly_ref_exists? ( ref_name )
gitaly_ref_client . ref_exists? ( ref_name )
end
def gitaly_copy_gitattributes ( revision )
gitaly_repository_client . apply_gitattributes ( revision )
end
def gitaly_delete_refs ( * ref_names )
2018-06-03 19:52:53 +05:30
gitaly_ref_client . delete_refs ( refs : ref_names ) if ref_names . any?
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
end
end
end