2017-01-04 13:43:06 -05:00
|
|
|
require 'tempfile'
|
|
|
|
require 'forwardable'
|
|
|
|
require "rubygems/package"
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module Git
|
|
|
|
class Repository
|
2017-10-14 21:52:07 -04:00
|
|
|
include Gitlab::Git::RepositoryMirroring
|
2018-03-27 12:20:03 -04:00
|
|
|
include Gitlab::EncodingHelper
|
2018-04-14 17:34:39 -04:00
|
|
|
include Gitlab::Utils::StrongMemoize
|
2017-01-04 13:43:06 -05:00
|
|
|
|
2017-04-05 03:30:20 -04:00
|
|
|
ALLOWED_OBJECT_DIRECTORIES_VARIABLES = %w[
|
|
|
|
GIT_OBJECT_DIRECTORY
|
|
|
|
GIT_ALTERNATE_OBJECT_DIRECTORIES
|
|
|
|
].freeze
|
2017-10-05 10:51:31 -04:00
|
|
|
ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES = %w[
|
|
|
|
GIT_OBJECT_DIRECTORY_RELATIVE
|
|
|
|
GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE
|
|
|
|
].freeze
|
2017-01-04 13:43:06 -05:00
|
|
|
SEARCH_CONTEXT_LINES = 3
|
2018-08-07 07:13:48 -04:00
|
|
|
REV_LIST_COMMIT_LIMIT = 2_000
|
2018-05-04 06:45:20 -04:00
|
|
|
# In https://gitlab.com/gitlab-org/gitaly/merge_requests/698
|
|
|
|
# We copied these two prefixes into gitaly-go, so don't change these
|
|
|
|
# or things will break! (REBASE_WORKTREE_PREFIX and SQUASH_WORKTREE_PREFIX)
|
2017-12-04 10:58:24 -05:00
|
|
|
REBASE_WORKTREE_PREFIX = 'rebase'.freeze
|
|
|
|
SQUASH_WORKTREE_PREFIX = 'squash'.freeze
|
2017-11-20 13:00:38 -05:00
|
|
|
GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze
|
2017-12-18 21:27:21 -05:00
|
|
|
GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout
|
2018-04-06 12:14:05 -04:00
|
|
|
EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze
|
2017-01-04 13:43:06 -05:00
|
|
|
|
2017-03-01 06:00:37 -05:00
|
|
|
NoRepository = Class.new(StandardError)
|
2018-04-25 17:33:00 -04:00
|
|
|
InvalidRepository = Class.new(StandardError)
|
2017-03-01 06:00:37 -05:00
|
|
|
InvalidBlobName = Class.new(StandardError)
|
|
|
|
InvalidRef = Class.new(StandardError)
|
2017-08-30 09:15:06 -04:00
|
|
|
GitError = Class.new(StandardError)
|
2017-08-28 03:31:41 -04:00
|
|
|
DeleteBranchError = Class.new(StandardError)
|
2017-09-19 13:09:10 -04:00
|
|
|
CreateTreeError = Class.new(StandardError)
|
2017-09-25 16:01:04 -04:00
|
|
|
TagExistsError = Class.new(StandardError)
|
2018-04-06 12:14:05 -04:00
|
|
|
ChecksumError = Class.new(StandardError)
|
2017-01-04 13:43:06 -05:00
|
|
|
|
2017-08-14 07:29:47 -04:00
|
|
|
class << self
|
2017-12-11 12:52:07 -05:00
|
|
|
def create_hooks(repo_path, global_hooks_path)
|
|
|
|
local_hooks_path = File.join(repo_path, 'hooks')
|
|
|
|
real_local_hooks_path = :not_found
|
|
|
|
|
|
|
|
begin
|
|
|
|
real_local_hooks_path = File.realpath(local_hooks_path)
|
|
|
|
rescue Errno::ENOENT
|
|
|
|
# real_local_hooks_path == :not_found
|
|
|
|
end
|
|
|
|
|
|
|
|
# Do nothing if hooks already exist
|
|
|
|
unless real_local_hooks_path == File.realpath(global_hooks_path)
|
2017-09-19 11:25:42 -04:00
|
|
|
if File.exist?(local_hooks_path)
|
|
|
|
# Move the existing hooks somewhere safe
|
|
|
|
FileUtils.mv(
|
|
|
|
local_hooks_path,
|
|
|
|
"#{local_hooks_path}.old.#{Time.now.to_i}")
|
|
|
|
end
|
2017-12-11 12:52:07 -05:00
|
|
|
|
|
|
|
# Create the hooks symlink
|
|
|
|
FileUtils.ln_sf(global_hooks_path, local_hooks_path)
|
2017-08-14 07:29:47 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# Directory name of repo
|
|
|
|
attr_reader :name
|
|
|
|
|
2017-08-10 10:08:48 -04:00
|
|
|
# Relative path of repo
|
|
|
|
attr_reader :relative_path
|
|
|
|
|
2018-09-03 16:20:57 -04:00
|
|
|
attr_reader :storage, :gl_repository, :relative_path
|
2017-05-10 08:18:59 -04:00
|
|
|
|
2017-09-29 12:05:20 -04:00
|
|
|
# This initializer method is only used on the client side (gitlab-ce).
|
|
|
|
# Gitaly-ruby uses a different initializer.
|
2017-08-22 07:54:14 -04:00
|
|
|
def initialize(storage, relative_path, gl_repository)
|
2017-05-10 08:18:59 -04:00
|
|
|
@storage = storage
|
2017-02-15 21:08:30 -05:00
|
|
|
@relative_path = relative_path
|
2017-08-22 07:54:14 -04:00
|
|
|
@gl_repository = gl_repository
|
2017-02-15 21:08:30 -05:00
|
|
|
|
|
|
|
@name = @relative_path.split("/").last
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2017-09-01 05:40:07 -04:00
|
|
|
def ==(other)
|
2018-06-05 11:51:14 -04:00
|
|
|
[storage, relative_path] == [other.storage, other.relative_path]
|
2017-09-01 05:40:07 -04:00
|
|
|
end
|
|
|
|
|
2018-07-12 05:49:25 -04:00
|
|
|
# This method will be removed when Gitaly reaches v1.1.
|
2018-04-12 03:12:21 -04:00
|
|
|
def path
|
2018-07-12 05:49:25 -04:00
|
|
|
File.join(
|
2018-04-12 03:12:21 -04:00
|
|
|
Gitlab.config.repositories.storages[@storage].legacy_disk_path, @relative_path
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# Default branch in the repository
|
|
|
|
def root_ref
|
2018-06-14 03:56:16 -04:00
|
|
|
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-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2018-07-12 05:49:25 -04:00
|
|
|
# This method will be removed when Gitaly reaches v1.1.
|
2017-01-04 13:43:06 -05:00
|
|
|
def rugged
|
2018-07-12 05:49:25 -04:00
|
|
|
circuit_breaker.perform do
|
2017-05-17 12:17:15 -04:00
|
|
|
Rugged::Repository.new(path, alternates: alternate_object_directories)
|
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
rescue Rugged::RepositoryError, Rugged::OSError
|
|
|
|
raise NoRepository.new('no repository for such path')
|
|
|
|
end
|
|
|
|
|
2017-05-17 12:17:15 -04:00
|
|
|
def circuit_breaker
|
|
|
|
@circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
|
|
|
|
end
|
|
|
|
|
2017-09-26 14:07:27 -04:00
|
|
|
def exists?
|
2018-04-24 10:57:35 -04:00
|
|
|
gitaly_repository_client.exists?
|
2017-09-26 14:07:27 -04:00
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# Returns an Array of branch names
|
|
|
|
# sorted by name ASC
|
|
|
|
def branch_names
|
2018-06-14 04:13:15 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.branch_names
|
2017-04-18 22:47:35 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns an Array of Branches
|
2017-07-22 18:13:47 -04:00
|
|
|
def branches
|
2018-06-14 04:25:28 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.branches
|
2017-07-22 18:13:47 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Directly find a branch with a simple name (e.g. master)
|
|
|
|
#
|
2018-07-12 09:12:00 -04:00
|
|
|
def find_branch(name)
|
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.find_branch(name)
|
2017-07-14 09:30:58 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2017-03-17 15:36:46 -04:00
|
|
|
def local_branches(sort_by: nil)
|
2018-06-14 04:44:33 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.local_branches(sort_by: sort_by)
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the number of valid branches
|
|
|
|
def branch_count
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.count_branch_names
|
2017-04-19 05:06:20 -04:00
|
|
|
end
|
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
|
2018-04-14 18:36:36 -04:00
|
|
|
def expire_has_local_branches_cache
|
|
|
|
clear_memoization(:has_local_branches)
|
|
|
|
end
|
|
|
|
|
2017-09-29 09:08:44 -04:00
|
|
|
def has_local_branches?
|
2018-04-14 17:34:39 -04:00
|
|
|
strong_memoize(:has_local_branches) do
|
|
|
|
uncached_has_local_branches?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-12-07 10:33:30 -05:00
|
|
|
# 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-04-19 05:06:20 -04:00
|
|
|
# Returns the number of valid tags
|
|
|
|
def tag_count
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.count_tag_names
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns an Array of tag names
|
|
|
|
def tag_names
|
2018-06-14 04:13:15 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.tag_names
|
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns an Array of Tags
|
2017-07-14 09:51:31 -04:00
|
|
|
#
|
2017-01-04 13:43:06 -05:00
|
|
|
def tags
|
2018-06-14 04:18:45 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.tags
|
2017-07-25 15:49:12 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2017-08-24 05:20:04 -04:00
|
|
|
# Returns true if the given ref name exists
|
|
|
|
#
|
|
|
|
# Ref names must start with `refs/`.
|
|
|
|
def ref_exists?(ref_name)
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_exists?(ref_name)
|
2017-08-24 05:20:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# Returns true if the given tag exists
|
|
|
|
#
|
|
|
|
# name - The name of the tag as a String.
|
|
|
|
def tag_exists?(name)
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_exists?("refs/tags/#{name}")
|
2017-08-14 07:12:32 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns true if the given branch exists
|
|
|
|
#
|
|
|
|
# name - The name of the branch as a String.
|
|
|
|
def branch_exists?(name)
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_exists?("refs/heads/#{name}")
|
2017-08-14 07:12:32 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns an Array of branch and tag names
|
|
|
|
def ref_names
|
|
|
|
branch_names + tag_names
|
|
|
|
end
|
|
|
|
|
2017-09-01 08:01:50 -04:00
|
|
|
def delete_all_refs_except(prefixes)
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.delete_refs(except_with_prefixes: prefixes)
|
2017-11-17 11:54:48 -05:00
|
|
|
end
|
2017-09-01 08:01:50 -04:00
|
|
|
end
|
|
|
|
|
2018-06-05 14:22:55 -04:00
|
|
|
def archive_metadata(ref, storage_path, project_path, format = "tar.gz", append_sha:)
|
2017-01-04 13:43:06 -05:00
|
|
|
ref ||= root_ref
|
|
|
|
commit = Gitlab::Git::Commit.find(self, ref)
|
|
|
|
return {} if commit.nil?
|
|
|
|
|
2018-06-05 14:22:55 -04:00
|
|
|
prefix = archive_prefix(ref, commit.id, project_path, append_sha: append_sha)
|
2017-01-04 13:43:06 -05:00
|
|
|
|
|
|
|
{
|
|
|
|
'ArchivePrefix' => prefix,
|
2018-04-25 04:22:43 -04:00
|
|
|
'ArchivePath' => archive_file_path(storage_path, commit.id, prefix, format),
|
2018-05-16 10:17:55 -04:00
|
|
|
'CommitId' => commit.id,
|
|
|
|
'GitalyRepository' => gitaly_repository.to_h
|
2017-01-04 13:43:06 -05:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2018-04-25 04:22:43 -04:00
|
|
|
# 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
|
2018-06-05 14:22:55 -04:00
|
|
|
def archive_prefix(ref, sha, project_path, append_sha:)
|
2018-04-25 04:22:43 -04:00
|
|
|
append_sha = (ref != sha) if append_sha.nil?
|
|
|
|
|
|
|
|
formatted_ref = ref.tr('/', '-')
|
|
|
|
|
2018-06-05 14:22:55 -04:00
|
|
|
prefix_segments = [project_path, formatted_ref]
|
2018-04-25 04:22:43 -04:00
|
|
|
prefix_segments << sha if append_sha
|
|
|
|
|
|
|
|
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-01-04 13:43:06 -05:00
|
|
|
# Build file path
|
|
|
|
return nil unless name
|
|
|
|
|
|
|
|
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-04-25 04:22:43 -04:00
|
|
|
File.join(storage_path, self.gl_repository, sha, file_name)
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
2018-04-25 04:22:43 -04:00
|
|
|
private :archive_file_path
|
2017-01-04 13:43:06 -05:00
|
|
|
|
|
|
|
# Return repo size in megabytes
|
|
|
|
def size
|
2018-06-22 05:31:27 -04:00
|
|
|
size = gitaly_repository_client.repository_size
|
2017-08-02 14:59:44 -04:00
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
(size.to_f / 1024).round(2)
|
|
|
|
end
|
|
|
|
|
2018-09-03 16:20:57 -04:00
|
|
|
# Build an array of commits.
|
2017-01-04 13:43:06 -05:00
|
|
|
#
|
|
|
|
# Usage.
|
|
|
|
# repo.log(
|
|
|
|
# ref: 'master',
|
|
|
|
# path: 'app/models',
|
|
|
|
# limit: 10,
|
|
|
|
# offset: 5,
|
|
|
|
# after: Time.new(2016, 4, 21, 14, 32, 10)
|
|
|
|
# )
|
|
|
|
def log(options)
|
2017-08-07 12:05:06 -04:00
|
|
|
default_options = {
|
|
|
|
limit: 10,
|
|
|
|
offset: 0,
|
|
|
|
path: nil,
|
|
|
|
follow: false,
|
|
|
|
skip_merges: false,
|
|
|
|
after: nil,
|
2018-02-19 09:42:00 -05:00
|
|
|
before: nil,
|
|
|
|
all: false
|
2017-08-07 12:05:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
options = default_options.merge(options)
|
|
|
|
options[:offset] ||= 0
|
|
|
|
|
2018-01-29 10:27:46 -05:00
|
|
|
limit = options[:limit]
|
|
|
|
if limit == 0 || !limit.is_a?(Integer)
|
|
|
|
raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
|
|
|
|
end
|
|
|
|
|
2018-07-09 06:02:02 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_commit_client.find_commits(options)
|
2017-09-11 04:17:49 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2018-07-17 06:17:43 -04:00
|
|
|
def new_commits(newrev)
|
2018-08-14 04:34:15 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.list_new_commits(newrev)
|
2018-07-17 06:17:43 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-07 07:13:48 -04:00
|
|
|
def new_blobs(newrev)
|
2018-08-08 17:18:37 -04:00
|
|
|
return [] if newrev.blank? || newrev == ::Gitlab::Git::BLANK_SHA
|
2018-08-07 07:13:48 -04:00
|
|
|
|
|
|
|
strong_memoize("new_blobs_#{newrev}") do
|
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.list_new_blobs(newrev, REV_LIST_COMMIT_LIMIT)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-05 04:30:36 -05:00
|
|
|
def count_commits(options)
|
2018-06-14 07:01:55 -04:00
|
|
|
options = process_count_commits_options(options.dup)
|
2018-01-05 11:52:06 -05:00
|
|
|
|
2018-06-14 07:01:55 -04:00
|
|
|
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-07-27 02:45:04 -04:00
|
|
|
else
|
2018-06-14 07:01:55 -04:00
|
|
|
gitaly_commit_client.commit_count(options[:ref], options)
|
2017-07-27 02:45:04 -04:00
|
|
|
end
|
|
|
|
end
|
2017-02-05 04:30:36 -05:00
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# Counts the amount of commits between `from` and `to`.
|
2018-01-05 11:52:06 -05:00
|
|
|
def count_commits_between(from, to, options = {})
|
|
|
|
count_commits(from: from, to: to, **options)
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2018-04-11 23:05:07 -04:00
|
|
|
# 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)
|
2018-05-09 14:20:28 -04:00
|
|
|
@raw_changes_between ||= {}
|
|
|
|
|
2018-06-21 04:12:51 -04:00
|
|
|
@raw_changes_between[[old_rev, new_rev]] ||=
|
|
|
|
begin
|
|
|
|
return [] if new_rev.blank? || new_rev == Gitlab::Git::BLANK_SHA
|
2018-05-09 14:20:28 -04:00
|
|
|
|
2018-06-21 04:12:51 -04:00
|
|
|
wrapped_gitaly_errors do
|
2018-05-09 14:20:28 -04:00
|
|
|
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
|
2018-04-11 23:05:07 -04:00
|
|
|
end
|
2018-05-02 04:12:15 -04:00
|
|
|
rescue ArgumentError => e
|
|
|
|
raise Gitlab::Git::Repository::GitError.new(e)
|
2018-04-11 23:05:07 -04:00
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# Returns the SHA of the most recent common ancestor of +from+ and +to+
|
2018-01-30 11:21:55 -05:00
|
|
|
def merge_base(from, to)
|
2018-07-17 05:00:32 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.find_merge_base(from, to)
|
2017-12-06 10:54:57 -05:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2017-03-10 11:56:29 -05:00
|
|
|
# Returns true is +from+ is direct ancestor to +to+, otherwise false
|
2017-08-24 12:35:06 -04:00
|
|
|
def ancestor?(from, to)
|
2018-06-22 06:39:39 -04:00
|
|
|
gitaly_commit_client.ancestor?(from, to)
|
2017-03-10 11:56:29 -05:00
|
|
|
end
|
|
|
|
|
2017-10-27 11:55:08 -04:00
|
|
|
def merged_branch_names(branch_names = [])
|
2017-12-28 13:07:21 -05:00
|
|
|
return [] unless root_ref
|
|
|
|
|
|
|
|
root_sha = find_branch(root_ref)&.target
|
|
|
|
|
|
|
|
return [] unless root_sha
|
|
|
|
|
2018-07-17 05:00:32 -04:00
|
|
|
branches = wrapped_gitaly_errors do
|
|
|
|
gitaly_merged_branch_names(branch_names, root_sha)
|
2017-12-28 13:07:21 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
Set.new(branches)
|
2017-10-27 11:55:08 -04:00
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# 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-07-06 06:01:15 -04:00
|
|
|
iterator = gitaly_commit_client.diff(from, to, options.merge(paths: paths))
|
2017-09-11 08:52:27 -04:00
|
|
|
|
|
|
|
Gitlab::Git::DiffCollection.new(iterator, options)
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2018-09-13 21:11:35 -04:00
|
|
|
def diff_stats(left_id, right_id)
|
|
|
|
stats = wrapped_gitaly_errors do
|
|
|
|
gitaly_commit_client.diff_stats(left_id, right_id)
|
|
|
|
end
|
|
|
|
|
|
|
|
Gitlab::Git::DiffStatsCollection.new(stats)
|
2018-09-19 08:26:28 -04:00
|
|
|
rescue CommandError, TypeError
|
2018-09-13 21:11:35 -04:00
|
|
|
Gitlab::Git::DiffStatsCollection.new([])
|
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
|
2017-03-28 13:14:48 -04:00
|
|
|
# Returns a RefName for a given SHA
|
|
|
|
def ref_name_for_sha(ref_path, sha)
|
2017-05-11 16:26:51 -04:00
|
|
|
raise ArgumentError, "sha can't be empty" unless sha.present?
|
|
|
|
|
2018-06-22 05:41:07 -04:00
|
|
|
gitaly_ref_client.find_ref_name(sha, ref_path)
|
2017-03-28 13:14:48 -04:00
|
|
|
end
|
|
|
|
|
2018-01-31 05:03:13 -05:00
|
|
|
# Get refs hash which key is is the commit id
|
|
|
|
# and value is a Gitlab::Git::Tag or Gitlab::Git::Branch
|
|
|
|
# Note that both inherit from Gitlab::Git::Ref
|
2017-01-04 13:43:06 -05:00
|
|
|
def refs_hash
|
2018-01-31 05:03:13 -05:00
|
|
|
return @refs_hash if @refs_hash
|
|
|
|
|
|
|
|
@refs_hash = Hash.new { |h, k| h[k] = [] }
|
|
|
|
|
|
|
|
(tags + branches).each do |ref|
|
|
|
|
next unless ref.target && ref.name
|
|
|
|
|
|
|
|
@refs_hash[ref.dereferenced_target.id] << ref.name
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
2018-01-11 11:34:01 -05:00
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
@refs_hash
|
|
|
|
end
|
|
|
|
|
2017-06-27 08:20:26 -04:00
|
|
|
# Returns url for submodule
|
2017-01-04 13:43:06 -05:00
|
|
|
#
|
|
|
|
# Ex.
|
2017-06-27 08:20:26 -04:00
|
|
|
# @repository.submodule_url_for('master', 'rack')
|
|
|
|
# # => git@localhost:rack.git
|
2017-01-04 13:43:06 -05:00
|
|
|
#
|
2017-06-27 08:20:26 -04:00
|
|
|
def submodule_url_for(ref, path)
|
2018-07-03 11:39:08 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_submodule_url_for(ref, path)
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Return total commits count accessible from passed ref
|
|
|
|
def commit_count(ref)
|
2018-06-14 07:06:07 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_commit_client.commit_count(ref)
|
2017-07-06 14:34:25 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2017-09-13 12:16:56 -04:00
|
|
|
def add_branch(branch_name, user:, target:)
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_create_branch(branch_name, user, target)
|
|
|
|
end
|
2017-09-01 10:16:42 -04:00
|
|
|
end
|
|
|
|
|
2017-09-13 12:16:56 -04:00
|
|
|
def add_tag(tag_name, user:, target:, message: nil)
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.add_tag(tag_name, user, target, message)
|
2017-09-01 10:16:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-06-25 19:49:55 -04:00
|
|
|
def update_branch(branch_name, user:, newrev:, oldrev:)
|
2018-08-16 04:24:26 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_update_branch(branch_name, user, newrev, oldrev)
|
2018-06-27 17:55:40 -04:00
|
|
|
end
|
2018-06-25 19:49:55 -04:00
|
|
|
end
|
|
|
|
|
2017-09-13 12:16:56 -04:00
|
|
|
def rm_branch(branch_name, user:)
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_delete_branch(branch_name, user)
|
2017-09-30 14:09:36 -04:00
|
|
|
end
|
2017-09-01 10:16:42 -04:00
|
|
|
end
|
|
|
|
|
2017-09-13 12:16:56 -04:00
|
|
|
def rm_tag(tag_name, user:)
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.rm_tag(tag_name, user)
|
2017-09-20 06:11:51 -04:00
|
|
|
end
|
2017-09-01 10:16:42 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def find_tag(name)
|
|
|
|
tags.find { |tag| tag.name == name }
|
|
|
|
end
|
|
|
|
|
2017-10-10 08:15:21 -04:00
|
|
|
def merge(user, source_sha, target_branch, message, &block)
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_merge_branch(user, source_sha, target_branch, message, &block)
|
2017-09-08 08:00:53 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-23 13:16:10 -04:00
|
|
|
def ff_merge(user, source_sha, target_branch)
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_ff_branch(user, source_sha, target_branch)
|
2017-10-23 13:16:10 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-19 13:09:10 -04:00
|
|
|
def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
|
2018-07-03 05:12:03 -04:00
|
|
|
args = {
|
|
|
|
user: user,
|
|
|
|
commit: commit,
|
|
|
|
branch_name: branch_name,
|
|
|
|
message: message,
|
|
|
|
start_branch_name: start_branch_name,
|
|
|
|
start_repository: start_repository
|
|
|
|
}
|
2017-09-19 13:09:10 -04:00
|
|
|
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_revert(args)
|
2017-09-19 13:09:10 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def cherry_pick(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:)
|
2018-07-03 05:12:03 -04:00
|
|
|
args = {
|
|
|
|
user: user,
|
|
|
|
commit: commit,
|
|
|
|
branch_name: branch_name,
|
|
|
|
message: message,
|
|
|
|
start_branch_name: start_branch_name,
|
|
|
|
start_repository: start_repository
|
|
|
|
}
|
2017-09-19 13:09:10 -04:00
|
|
|
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_cherry_pick(args)
|
2017-09-19 13:09:10 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# Delete the specified branch from the repository
|
|
|
|
def delete_branch(branch_name)
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.delete_branch(branch_name)
|
2017-08-28 03:31:41 -04:00
|
|
|
end
|
2018-07-12 09:12:00 -04:00
|
|
|
rescue CommandError => e
|
2017-08-28 03:31:41 -04:00
|
|
|
raise DeleteBranchError, e
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
2017-08-25 10:38:07 -04:00
|
|
|
|
2017-08-30 09:15:06 -04:00
|
|
|
def delete_refs(*ref_names)
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_delete_refs(*ref_names)
|
2017-08-30 09:15:06 -04:00
|
|
|
end
|
2017-08-25 10:38:07 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
|
|
|
|
# Create a new branch named **ref+ based on **stat_point+, HEAD by default
|
|
|
|
#
|
|
|
|
# Examples:
|
|
|
|
# create_branch("feature")
|
|
|
|
# create_branch("other-feature", "master")
|
|
|
|
def create_branch(ref, start_point = "HEAD")
|
2018-07-12 09:12:00 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_ref_client.create_branch(ref, start_point)
|
2017-08-28 03:31:41 -04:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2017-12-06 17:08:29 -05:00
|
|
|
# If `mirror_refmap` is present the remote is set as mirror with that mapping
|
|
|
|
def add_remote(remote_name, url, mirror_refmap: nil)
|
2018-07-19 14:40:36 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_remote_client.add_remote(remote_name, url, mirror_refmap)
|
2017-12-20 15:17:28 -05:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2017-10-14 21:52:07 -04:00
|
|
|
def remove_remote(remote_name)
|
2018-07-19 14:40:36 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_remote_client.remove_remote(remote_name)
|
2017-10-14 21:52:07 -04:00
|
|
|
end
|
2017-10-13 12:50:36 -04:00
|
|
|
end
|
|
|
|
|
2018-09-04 16:04:10 -04:00
|
|
|
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-01-04 13:43:06 -05:00
|
|
|
AUTOCRLF_VALUES = {
|
|
|
|
"true" => true,
|
|
|
|
"false" => false,
|
|
|
|
"input" => :input
|
|
|
|
}.freeze
|
|
|
|
|
|
|
|
def autocrlf
|
|
|
|
AUTOCRLF_VALUES[rugged.config['core.autocrlf']]
|
|
|
|
end
|
|
|
|
|
|
|
|
def autocrlf=(value)
|
|
|
|
rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns result like "git ls-files" , recursive and full file path
|
|
|
|
#
|
|
|
|
# Ex.
|
|
|
|
# repo.ls_files('master')
|
|
|
|
#
|
|
|
|
def ls_files(ref)
|
2018-06-22 05:05:08 -04:00
|
|
|
gitaly_commit_client.ls_files(ref)
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def copy_gitattributes(ref)
|
2018-07-03 11:39:08 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.apply_gitattributes(ref)
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-27 08:31:15 -04:00
|
|
|
def info_attributes
|
|
|
|
return @info_attributes if @info_attributes
|
|
|
|
|
2018-06-14 09:01:29 -04:00
|
|
|
content = gitaly_repository_client.info_attributes
|
2018-03-27 08:31:15 -04:00
|
|
|
@info_attributes = AttributesParser.new(content)
|
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
# Returns the Git attributes for the given file path.
|
|
|
|
#
|
|
|
|
# See `Gitlab::Git::Attributes` for more information.
|
|
|
|
def attributes(path)
|
2018-03-27 08:31:15 -04:00
|
|
|
info_attributes.attributes(path)
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
|
2017-11-17 12:57:48 -05:00
|
|
|
def gitattribute(path, name)
|
|
|
|
attributes(path)[name]
|
|
|
|
end
|
|
|
|
|
2018-01-11 18:12:34 -05:00
|
|
|
# Check .gitattributes for a given ref
|
|
|
|
#
|
|
|
|
# This only checks the root .gitattributes file,
|
|
|
|
# it does not traverse subfolders to find additional .gitattributes files
|
|
|
|
#
|
2018-03-11 15:17:39 -04:00
|
|
|
# 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.
|
2018-01-11 18:12:34 -05:00
|
|
|
def attributes_at(ref, file_path)
|
|
|
|
parser = AttributesAtRefParser.new(self, ref)
|
|
|
|
parser.attributes(file_path)
|
|
|
|
end
|
|
|
|
|
2017-07-31 09:23:05 -04:00
|
|
|
def languages(ref = nil)
|
2018-06-25 05:50:41 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_commit_client.languages(ref)
|
2017-07-31 09:23:05 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-01 09:30:41 -05:00
|
|
|
def license_short_name
|
2018-06-14 07:24:17 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.license_short_name
|
2018-03-01 09:30:41 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-01 10:31:35 -04:00
|
|
|
def fetch_source_branch!(source_repository, source_branch, local_ref)
|
2018-07-09 06:50:17 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.fetch_source_branch(source_repository, source_branch, local_ref)
|
2017-08-23 11:14:21 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
|
2018-06-12 06:33:47 -04:00
|
|
|
tmp_ref = "refs/tmp/#{SecureRandom.hex}"
|
|
|
|
|
|
|
|
return unless fetch_source_branch!(source_repository, source_branch_name, tmp_ref)
|
|
|
|
|
|
|
|
Gitlab::Git::Compare.new(
|
|
|
|
self,
|
|
|
|
target_branch_name,
|
|
|
|
tmp_ref,
|
|
|
|
straight: straight
|
|
|
|
)
|
|
|
|
ensure
|
|
|
|
delete_refs(tmp_ref)
|
2017-08-23 11:14:21 -04:00
|
|
|
end
|
|
|
|
|
2018-01-08 10:24:59 -05:00
|
|
|
def write_ref(ref_path, ref, old_ref: nil, shell: true)
|
2018-01-17 00:43:30 -05:00
|
|
|
ref_path = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{ref_path}" unless ref_path.start_with?("refs/") || ref_path == "HEAD"
|
|
|
|
|
2018-07-17 05:00:32 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.write_ref(ref_path, ref, old_ref, shell)
|
2018-01-08 10:24:59 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-23 11:14:21 -04:00
|
|
|
# Refactoring aid; allows us to copy code from app/models/repository.rb
|
|
|
|
def commit(ref = 'HEAD')
|
|
|
|
Gitlab::Git::Commit.find(self, ref)
|
|
|
|
end
|
|
|
|
|
2017-12-07 10:33:30 -05:00
|
|
|
def empty?
|
|
|
|
!has_visible_content?
|
2017-08-23 11:14:21 -04:00
|
|
|
end
|
|
|
|
|
2017-11-20 13:00:38 -05:00
|
|
|
def fetch_repository_as_mirror(repository)
|
2018-07-19 14:40:36 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_remote_client.fetch_internal_remote(repository)
|
2017-11-20 13:00:38 -05:00
|
|
|
end
|
2017-10-09 17:25:24 -04:00
|
|
|
end
|
|
|
|
|
2017-10-06 19:47:06 -04:00
|
|
|
def blob_at(sha, path)
|
|
|
|
Gitlab::Git::Blob.find(self, sha, path) unless Gitlab::Git.blank_ref?(sha)
|
|
|
|
end
|
|
|
|
|
2017-11-03 09:16:43 -04:00
|
|
|
# Items should be of format [[commit_id, path], [commit_id1, path1]]
|
2018-01-07 04:30:30 -05:00
|
|
|
def batch_blobs(items, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
|
2017-11-03 09:16:43 -04:00
|
|
|
Gitlab::Git::Blob.batch(self, items, blob_size_limit: blob_size_limit)
|
|
|
|
end
|
|
|
|
|
2017-12-04 04:31:13 -05:00
|
|
|
def fsck
|
2018-03-08 10:27:31 -05:00
|
|
|
msg, status = gitaly_repository_client.fsck
|
2017-12-07 19:27:11 -05:00
|
|
|
|
2018-03-08 10:27:31 -05:00
|
|
|
raise GitError.new("Could not fsck repository: #{msg}") unless status.zero?
|
2017-12-04 04:31:13 -05:00
|
|
|
end
|
|
|
|
|
2018-01-22 10:46:02 -05:00
|
|
|
def create_from_bundle(bundle_path)
|
2018-06-25 08:56:27 -04:00
|
|
|
gitaly_repository_client.create_from_bundle(bundle_path)
|
2018-01-22 10:46:02 -05:00
|
|
|
end
|
|
|
|
|
2018-04-03 13:57:55 -04:00
|
|
|
def create_from_snapshot(url, auth)
|
|
|
|
gitaly_repository_client.create_from_snapshot(url, auth)
|
|
|
|
end
|
|
|
|
|
2017-12-04 10:58:24 -05:00
|
|
|
def rebase(user, rebase_id, branch:, branch_sha:, remote_repository:, remote_branch:)
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_rebase(user, rebase_id,
|
|
|
|
branch: branch,
|
|
|
|
branch_sha: branch_sha,
|
|
|
|
remote_repository: remote_repository,
|
|
|
|
remote_branch: remote_branch)
|
2017-12-04 10:58:24 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def rebase_in_progress?(rebase_id)
|
2018-06-14 07:36:53 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.rebase_in_progress?(rebase_id)
|
2018-01-08 10:08:25 -05:00
|
|
|
end
|
2017-12-04 10:58:24 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:)
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_squash(user, squash_id, branch,
|
2018-02-02 16:30:03 -05:00
|
|
|
start_sha, end_sha, author, message)
|
2017-12-04 10:58:24 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def squash_in_progress?(squash_id)
|
2018-06-14 07:36:53 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.squash_in_progress?(squash_id)
|
2018-02-07 16:26:35 -05:00
|
|
|
end
|
2017-12-04 10:58:24 -05:00
|
|
|
end
|
|
|
|
|
2018-01-18 12:33:35 -05:00
|
|
|
def bundle_to_disk(save_path)
|
2018-07-09 06:50:17 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.create_bundle(save_path)
|
2018-01-18 12:33:35 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2018-01-03 20:47:25 -05:00
|
|
|
def multi_action(
|
|
|
|
user, branch_name:, message:, actions:,
|
|
|
|
author_email: nil, author_name: nil,
|
|
|
|
start_branch_name: nil, start_repository: self)
|
|
|
|
|
2018-07-03 05:12:03 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_operation_client.user_commit_files(user, branch_name,
|
2018-01-08 22:51:05 -05:00
|
|
|
message, actions, author_email, author_name,
|
|
|
|
start_branch_name, start_repository)
|
2018-01-03 20:47:25 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-19 12:08:07 -05:00
|
|
|
def write_config(full_path:)
|
2018-01-26 03:00:22 -05:00
|
|
|
return unless full_path.present?
|
|
|
|
|
2018-06-05 08:55:41 -04:00
|
|
|
# This guard avoids Gitaly log/error spam
|
2018-06-25 09:08:20 -04:00
|
|
|
raise NoRepository, 'repository does not exist' unless exists?
|
2018-06-05 08:55:41 -04:00
|
|
|
|
2018-07-04 08:42:24 -04:00
|
|
|
set_config('gitlab.fullpath' => full_path)
|
|
|
|
end
|
|
|
|
|
|
|
|
def set_config(entries)
|
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.set_config(entries)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete_config(*keys)
|
2018-06-25 09:08:20 -04:00
|
|
|
wrapped_gitaly_errors do
|
2018-07-04 08:42:24 -04:00
|
|
|
gitaly_repository_client.delete_config(keys)
|
2018-01-26 03:00:22 -05:00
|
|
|
end
|
2018-01-19 01:29:33 -05:00
|
|
|
end
|
|
|
|
|
2017-04-06 10:54:15 -04:00
|
|
|
def gitaly_repository
|
2017-09-20 18:33:56 -04:00
|
|
|
Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
|
2017-04-06 10:54:15 -04:00
|
|
|
end
|
|
|
|
|
2017-06-23 17:52:51 -04:00
|
|
|
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)
|
2017-07-21 03:36:31 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def gitaly_repository_client
|
|
|
|
@gitaly_repository_client ||= Gitlab::GitalyClient::RepositoryService.new(self)
|
2017-06-23 17:52:51 -04:00
|
|
|
end
|
|
|
|
|
2017-09-20 18:34:30 -04:00
|
|
|
def gitaly_operation_client
|
|
|
|
@gitaly_operation_client ||= Gitlab::GitalyClient::OperationService.new(self)
|
|
|
|
end
|
|
|
|
|
2017-12-20 15:17:28 -05:00
|
|
|
def gitaly_remote_client
|
|
|
|
@gitaly_remote_client ||= Gitlab::GitalyClient::RemoteService.new(self)
|
|
|
|
end
|
|
|
|
|
2018-01-17 08:21:46 -05:00
|
|
|
def gitaly_blob_client
|
|
|
|
@gitaly_blob_client ||= Gitlab::GitalyClient::BlobService.new(self)
|
|
|
|
end
|
|
|
|
|
2017-12-05 17:18:20 -05:00
|
|
|
def gitaly_conflicts_client(our_commit_oid, their_commit_oid)
|
|
|
|
Gitlab::GitalyClient::ConflictsService.new(self, our_commit_oid, their_commit_oid)
|
2017-12-05 14:42:04 -05:00
|
|
|
end
|
|
|
|
|
2017-09-19 06:55:37 -04:00
|
|
|
def gitaly_migrate(method, status: Gitlab::GitalyClient::MigrationStatus::OPT_IN, &block)
|
|
|
|
Gitlab::GitalyClient.migrate(method, status: status, &block)
|
2017-07-31 13:38:36 -04:00
|
|
|
rescue GRPC::NotFound => e
|
|
|
|
raise NoRepository.new(e)
|
2017-09-12 06:26:59 -04:00
|
|
|
rescue GRPC::InvalidArgument => e
|
|
|
|
raise ArgumentError.new(e)
|
2017-10-25 18:00:19 -04:00
|
|
|
rescue GRPC::BadStatus => e
|
|
|
|
raise CommandError.new(e)
|
2018-06-14 04:13:15 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def wrapped_gitaly_errors(&block)
|
|
|
|
yield block
|
|
|
|
rescue GRPC::NotFound => e
|
|
|
|
raise NoRepository.new(e)
|
|
|
|
rescue GRPC::InvalidArgument => e
|
|
|
|
raise ArgumentError.new(e)
|
|
|
|
rescue GRPC::BadStatus => e
|
|
|
|
raise CommandError.new(e)
|
2017-07-31 13:38:36 -04:00
|
|
|
end
|
|
|
|
|
2018-03-29 19:10:43 -04:00
|
|
|
def clean_stale_repository_files
|
2018-07-24 07:25:27 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.cleanup if exists?
|
2018-03-29 19:10:43 -04:00
|
|
|
end
|
|
|
|
rescue Gitlab::Git::CommandError => e # Don't fail if we can't cleanup
|
2018-06-01 07:56:29 -04:00
|
|
|
Rails.logger.error("Unable to clean repository on storage #{storage} with relative path #{relative_path}: #{e.message}")
|
2018-03-29 19:10:43 -04:00
|
|
|
Gitlab::Metrics.counter(
|
|
|
|
:failed_repository_cleanup_total,
|
|
|
|
'Number of failed repository cleanup events'
|
|
|
|
).increment
|
|
|
|
end
|
|
|
|
|
2018-01-30 03:59:45 -05:00
|
|
|
def branch_names_contains_sha(sha)
|
2018-05-14 09:55:27 -04:00
|
|
|
gitaly_ref_client.branch_names_contains_sha(sha)
|
2018-01-30 03:59:45 -05:00
|
|
|
end
|
2018-01-22 11:08:00 -05:00
|
|
|
|
2018-01-30 03:59:45 -05:00
|
|
|
def tag_names_contains_sha(sha)
|
2018-05-14 09:55:27 -04:00
|
|
|
gitaly_ref_client.tag_names_contains_sha(sha)
|
2018-01-22 11:08:00 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def search_files_by_content(query, ref)
|
|
|
|
return [] if empty? || query.blank?
|
|
|
|
|
2018-05-04 09:53:01 -04:00
|
|
|
safe_query = Regexp.escape(query)
|
|
|
|
ref ||= root_ref
|
|
|
|
|
2018-06-27 07:46:51 -04:00
|
|
|
gitaly_repository_client.search_files_by_content(ref, safe_query)
|
2018-01-22 11:08:00 -05:00
|
|
|
end
|
|
|
|
|
2018-01-29 12:22:33 -05:00
|
|
|
def can_be_merged?(source_sha, target_branch)
|
2018-07-12 12:06:31 -04:00
|
|
|
if target_sha = find_branch(target_branch)&.target
|
2018-06-19 09:22:38 -04:00
|
|
|
!gitaly_conflicts_client(source_sha, target_sha).conflicts?
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
2018-01-29 12:22:33 -05:00
|
|
|
end
|
|
|
|
|
2018-01-22 11:08:00 -05:00
|
|
|
def search_files_by_name(query, ref)
|
2018-01-27 00:35:53 -05:00
|
|
|
safe_query = Regexp.escape(query.sub(%r{^/*}, ""))
|
2018-05-04 09:53:01 -04:00
|
|
|
ref ||= root_ref
|
2018-01-22 11:08:00 -05:00
|
|
|
|
|
|
|
return [] if empty? || safe_query.blank?
|
|
|
|
|
2018-06-27 07:46:51 -04:00
|
|
|
gitaly_repository_client.search_files_by_name(ref, safe_query)
|
2018-01-22 11:08:00 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def find_commits_by_message(query, ref, path, limit, offset)
|
2018-07-09 06:02:02 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_commit_client
|
|
|
|
.commits_by_message(query, revision: ref, path: path, limit: limit, offset: offset)
|
|
|
|
.map { |c| commit(c) }
|
2018-01-22 11:08:00 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-06 09:26:57 -05:00
|
|
|
def last_commit_for_path(sha, path)
|
2018-07-09 06:02:02 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_commit_client.last_commit_for_path(sha, path)
|
2018-01-30 11:21:55 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-06 12:14:05 -04:00
|
|
|
def checksum
|
2018-05-11 10:44:38 -04:00
|
|
|
# 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-04-06 12:14:05 -04:00
|
|
|
end
|
|
|
|
|
2017-01-04 13:43:06 -05:00
|
|
|
private
|
|
|
|
|
2018-04-14 18:38:53 -04:00
|
|
|
def uncached_has_local_branches?
|
2018-06-14 04:31:24 -04:00
|
|
|
wrapped_gitaly_errors do
|
|
|
|
gitaly_repository_client.has_local_branches?
|
2018-04-14 18:38:53 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-12-28 13:07:21 -05:00
|
|
|
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)
|
|
|
|
end
|
|
|
|
|
2018-01-05 11:52:06 -05:00
|
|
|
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
|
|
|
|
end
|
|
|
|
|
2017-07-04 09:14:13 -04:00
|
|
|
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
|
|
|
|
|
2017-07-11 17:06:38 -04:00
|
|
|
gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
|
2017-08-11 12:23:47 -04:00
|
|
|
return unless gitmodules
|
|
|
|
|
2017-07-04 09:14:13 -04:00
|
|
|
found_module = GitmodulesParser.new(gitmodules.data).parse[path]
|
|
|
|
|
|
|
|
found_module && found_module['url']
|
|
|
|
end
|
|
|
|
|
2017-04-05 03:30:20 -04:00
|
|
|
def alternate_object_directories
|
2018-03-30 05:19:46 -04:00
|
|
|
relative_object_directories.map { |d| File.join(path, d) }
|
2017-04-05 03:30:20 -04:00
|
|
|
end
|
|
|
|
|
2018-01-29 03:51:25 -05:00
|
|
|
def relative_object_directories
|
2018-03-30 05:19:46 -04:00
|
|
|
Gitlab::Git::HookEnv.all(gl_repository).values_at(*ALLOWED_OBJECT_RELATIVE_DIRECTORIES_VARIABLES).flatten.compact
|
2018-01-29 03:51:25 -05:00
|
|
|
end
|
|
|
|
|
2017-08-24 05:20:04 -04:00
|
|
|
# Returns true if the given ref name exists
|
|
|
|
#
|
|
|
|
# Ref names must start with `refs/`.
|
2017-08-14 07:12:32 -04:00
|
|
|
def gitaly_ref_exists?(ref_name)
|
|
|
|
gitaly_ref_client.ref_exists?(ref_name)
|
|
|
|
end
|
|
|
|
|
2017-08-11 18:44:25 -04:00
|
|
|
def gitaly_copy_gitattributes(revision)
|
|
|
|
gitaly_repository_client.apply_gitattributes(revision)
|
|
|
|
end
|
|
|
|
|
2018-01-25 14:59:33 -05:00
|
|
|
def gitaly_delete_refs(*ref_names)
|
2018-04-25 13:37:52 -04:00
|
|
|
gitaly_ref_client.delete_refs(refs: ref_names) if ref_names.any?
|
2018-01-25 14:59:33 -05:00
|
|
|
end
|
2017-01-04 13:43:06 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|