2018-07-25 09:30:33 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2015-08-14 14:23:40 +00:00
|
|
|
require 'securerandom'
|
|
|
|
|
2013-03-31 20:45:38 +00:00
|
|
|
class Repository
|
2019-08-31 19:57:00 +00:00
|
|
|
REF_MERGE_REQUEST = 'merge-requests'
|
|
|
|
REF_KEEP_AROUND = 'keep-around'
|
|
|
|
REF_ENVIRONMENTS = 'environments'
|
2019-09-27 18:06:20 +00:00
|
|
|
REF_PIPELINES = 'pipelines'
|
2017-08-25 15:00:06 +00:00
|
|
|
|
2019-07-10 17:34:05 +00:00
|
|
|
ARCHIVE_CACHE_TIME = 60 # Cache archives referred to by a (mutable) ref for 1 minute
|
|
|
|
ARCHIVE_CACHE_TIME_IMMUTABLE = 3600 # Cache archives referred to by an immutable reference for 1 hour
|
|
|
|
|
2017-08-25 15:00:06 +00:00
|
|
|
RESERVED_REFS_NAMES = %W[
|
|
|
|
heads
|
|
|
|
tags
|
2017-09-22 08:18:59 +00:00
|
|
|
replace
|
2017-08-25 15:00:06 +00:00
|
|
|
#{REF_ENVIRONMENTS}
|
|
|
|
#{REF_KEEP_AROUND}
|
2019-09-27 18:06:20 +00:00
|
|
|
#{REF_PIPELINES}
|
2017-08-25 15:00:06 +00:00
|
|
|
].freeze
|
|
|
|
|
2018-03-07 00:12:29 +00:00
|
|
|
include Gitlab::RepositoryCacheAdapter
|
2013-07-16 19:19:07 +00:00
|
|
|
|
2020-02-27 03:08:49 +00:00
|
|
|
attr_accessor :full_path, :shard, :disk_path, :container, :repo_type
|
2013-03-31 20:45:38 +00:00
|
|
|
|
2017-03-28 17:14:48 +00:00
|
|
|
delegate :ref_name_for_sha, to: :raw_repository
|
2018-06-25 12:56:27 +00:00
|
|
|
delegate :bundle_to_disk, to: :raw_repository
|
2017-03-28 17:14:48 +00:00
|
|
|
|
2017-03-03 00:11:23 +00:00
|
|
|
CreateTreeError = Class.new(StandardError)
|
2018-11-28 14:43:58 +00:00
|
|
|
AmbiguousRefError = Class.new(StandardError)
|
2016-10-28 12:55:55 +00:00
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
# Methods that cache data from the Git repository.
|
|
|
|
#
|
|
|
|
# Each entry in this Array should have a corresponding method with the exact
|
|
|
|
# same name. The cache key used by those methods must also match method's
|
|
|
|
# name.
|
|
|
|
#
|
2017-04-06 14:47:52 +00:00
|
|
|
# For example, for entry `:commit_count` there's a method called `commit_count` which
|
|
|
|
# stores its data in the `commit_count` cache key.
|
2018-11-26 15:44:13 +00:00
|
|
|
CACHED_METHODS = %i(size commit_count rendered_readme readme_path contribution_guide
|
2018-10-13 00:54:08 +00:00
|
|
|
changelog license_blob license_key gitignore
|
2016-11-18 13:04:18 +00:00
|
|
|
gitlab_ci_yml branch_names tag_names branch_count
|
2020-02-03 18:08:46 +00:00
|
|
|
tag_count avatar exists? root_ref merged_branch_names
|
|
|
|
has_visible_content? issue_template_names merge_request_template_names
|
2020-08-07 18:09:53 +00:00
|
|
|
user_defined_metrics_dashboard_paths xcode_project? has_ambiguous_refs?).freeze
|
2017-10-02 02:45:44 +00:00
|
|
|
|
|
|
|
# Methods that use cache_method but only memoize the value
|
2017-12-07 15:33:30 +00:00
|
|
|
MEMOIZED_CACHED_METHODS = %i(license).freeze
|
2016-11-18 13:04:18 +00:00
|
|
|
|
|
|
|
# Certain method caches should be refreshed when certain types of files are
|
|
|
|
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
|
|
|
|
# the corresponding methods to call for refreshing caches.
|
|
|
|
METHOD_CACHES_FOR_FILE_TYPES = {
|
2018-11-26 15:44:13 +00:00
|
|
|
readme: %i(rendered_readme readme_path),
|
2016-11-18 13:04:18 +00:00
|
|
|
changelog: :changelog,
|
2017-05-10 14:02:28 +00:00
|
|
|
license: %i(license_blob license_key license),
|
2016-11-18 13:04:18 +00:00
|
|
|
contributing: :contribution_guide,
|
|
|
|
gitignore: :gitignore,
|
|
|
|
gitlab_ci: :gitlab_ci_yml,
|
2017-10-11 15:48:43 +00:00
|
|
|
avatar: :avatar,
|
|
|
|
issue_template: :issue_template_names,
|
2018-05-09 14:54:47 +00:00
|
|
|
merge_request_template: :merge_request_template_names,
|
2020-07-31 00:09:37 +00:00
|
|
|
metrics_dashboard: :user_defined_metrics_dashboard_paths,
|
2019-03-19 09:36:05 +00:00
|
|
|
xcode_config: :xcode_project?
|
2017-02-21 23:32:18 +00:00
|
|
|
}.freeze
|
2016-11-18 13:04:18 +00:00
|
|
|
|
2020-02-27 03:08:49 +00:00
|
|
|
def initialize(full_path, container, shard:, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT)
|
2017-07-22 00:37:22 +00:00
|
|
|
@full_path = full_path
|
2020-02-27 03:08:49 +00:00
|
|
|
@shard = shard
|
2017-07-23 07:05:34 +00:00
|
|
|
@disk_path = disk_path || full_path
|
2020-02-04 15:08:40 +00:00
|
|
|
@container = container
|
2017-10-13 08:37:31 +00:00
|
|
|
@commit_cache = {}
|
2019-03-18 16:51:11 +00:00
|
|
|
@repo_type = repo_type
|
2015-11-12 12:47:48 +00:00
|
|
|
end
|
2015-06-20 12:34:32 +00:00
|
|
|
|
2017-08-29 13:11:38 +00:00
|
|
|
def ==(other)
|
2018-11-23 11:27:08 +00:00
|
|
|
other.is_a?(self.class) && @disk_path == other.disk_path
|
|
|
|
end
|
|
|
|
|
|
|
|
alias_method :eql?, :==
|
|
|
|
|
|
|
|
def hash
|
|
|
|
[self.class, @disk_path].hash
|
2017-08-19 00:43:23 +00:00
|
|
|
end
|
|
|
|
|
2015-11-12 12:47:48 +00:00
|
|
|
def raw_repository
|
2019-02-08 12:19:53 +00:00
|
|
|
return unless full_path
|
2015-06-20 12:34:32 +00:00
|
|
|
|
2017-04-05 11:33:50 +00:00
|
|
|
@raw_repository ||= initialize_raw_repository
|
2016-02-17 15:49:16 +00:00
|
|
|
end
|
|
|
|
|
2017-07-25 20:48:17 +00:00
|
|
|
alias_method :raw, :raw_repository
|
|
|
|
|
2018-07-06 14:22:14 +00:00
|
|
|
# Don't use this! It's going away. Use Gitaly to read or write from repos.
|
2013-10-01 14:00:28 +00:00
|
|
|
def path_to_repo
|
2018-04-13 10:57:19 +00:00
|
|
|
@path_to_repo ||=
|
|
|
|
begin
|
2020-02-27 03:08:49 +00:00
|
|
|
storage = Gitlab.config.repositories.storages[shard]
|
2018-04-13 10:57:19 +00:00
|
|
|
|
|
|
|
File.expand_path(
|
|
|
|
File.join(storage.legacy_disk_path, disk_path + '.git')
|
|
|
|
)
|
|
|
|
end
|
2013-10-01 14:00:28 +00:00
|
|
|
end
|
|
|
|
|
2017-08-29 14:42:02 +00:00
|
|
|
def inspect
|
|
|
|
"#<#{self.class.name}:#{@disk_path}>"
|
|
|
|
end
|
|
|
|
|
2018-06-29 07:02:22 +00:00
|
|
|
def commit(ref = nil)
|
2019-02-08 12:19:53 +00:00
|
|
|
return unless exists?
|
2017-10-13 08:37:31 +00:00
|
|
|
return ref if ref.is_a?(::Commit)
|
2016-11-07 12:33:33 +00:00
|
|
|
|
2018-06-29 07:02:22 +00:00
|
|
|
find_commit(ref || root_ref)
|
2017-10-13 08:37:31 +00:00
|
|
|
end
|
2016-11-07 12:33:33 +00:00
|
|
|
|
2017-10-13 08:37:31 +00:00
|
|
|
# Finding a commit by the passed SHA
|
|
|
|
# Also takes care of caching, based on the SHA
|
|
|
|
def commit_by(oid:)
|
|
|
|
return @commit_cache[oid] if @commit_cache.key?(oid)
|
|
|
|
|
|
|
|
@commit_cache[oid] = find_commit(oid)
|
2013-03-31 20:45:38 +00:00
|
|
|
end
|
|
|
|
|
2017-12-05 13:15:30 +00:00
|
|
|
def commits_by(oids:)
|
|
|
|
return [] unless oids.present?
|
|
|
|
|
|
|
|
commits = Gitlab::Git::Commit.batch_by_oid(raw_repository, oids)
|
|
|
|
|
|
|
|
if commits.present?
|
2020-02-04 15:08:40 +00:00
|
|
|
Commit.decorate(commits, container)
|
2017-12-05 13:15:30 +00:00
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-09-19 21:06:29 +00:00
|
|
|
def commits(ref = nil, opts = {})
|
2015-10-23 11:13:22 +00:00
|
|
|
options = {
|
2013-08-05 13:51:04 +00:00
|
|
|
repo: raw_repository,
|
|
|
|
ref: ref,
|
2019-09-19 21:06:29 +00:00
|
|
|
path: opts[:path],
|
2020-02-26 18:09:24 +00:00
|
|
|
author: opts[:author],
|
2019-09-19 21:06:29 +00:00
|
|
|
follow: Array(opts[:path]).length == 1,
|
|
|
|
limit: opts[:limit],
|
|
|
|
offset: opts[:offset],
|
|
|
|
skip_merges: !!opts[:skip_merges],
|
|
|
|
after: opts[:after],
|
|
|
|
before: opts[:before],
|
|
|
|
all: !!opts[:all],
|
2020-02-13 21:08:59 +00:00
|
|
|
first_parent: !!opts[:first_parent],
|
2020-06-26 21:08:51 +00:00
|
|
|
order: opts[:order],
|
|
|
|
literal_pathspec: opts.fetch(:literal_pathspec, true)
|
2015-10-23 11:13:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
commits = Gitlab::Git::Commit.where(options)
|
2020-02-04 15:08:40 +00:00
|
|
|
commits = Commit.decorate(commits, container) if commits.present?
|
2017-11-10 19:57:11 +00:00
|
|
|
|
2020-02-04 15:08:40 +00:00
|
|
|
CommitCollection.new(container, commits, ref)
|
2013-03-31 20:45:38 +00:00
|
|
|
end
|
|
|
|
|
2013-08-05 13:51:04 +00:00
|
|
|
def commits_between(from, to)
|
|
|
|
commits = Gitlab::Git::Commit.between(raw_repository, from, to)
|
2020-02-04 15:08:40 +00:00
|
|
|
commits = Commit.decorate(commits, container) if commits.present?
|
2013-03-31 20:45:38 +00:00
|
|
|
commits
|
|
|
|
end
|
|
|
|
|
2018-02-07 13:00:53 +00:00
|
|
|
# Returns a list of commits that are not present in any reference
|
|
|
|
def new_commits(newrev)
|
2018-07-17 10:17:43 +00:00
|
|
|
commits = raw.new_commits(newrev)
|
2018-02-07 13:00:53 +00:00
|
|
|
|
2020-02-04 15:08:40 +00:00
|
|
|
::Commit.decorate(commits, container)
|
2018-02-07 13:00:53 +00:00
|
|
|
end
|
|
|
|
|
2017-07-13 15:58:45 +00:00
|
|
|
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
|
2016-01-07 12:00:47 +00:00
|
|
|
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
|
2016-10-19 16:43:04 +00:00
|
|
|
unless exists? && has_visible_content? && query.present?
|
|
|
|
return []
|
|
|
|
end
|
|
|
|
|
2018-01-22 16:08:00 +00:00
|
|
|
commits = raw_repository.find_commits_by_message(query, ref, path, limit, offset).map do |c|
|
|
|
|
commit(c)
|
2017-08-03 08:22:01 +00:00
|
|
|
end
|
2020-02-04 15:08:40 +00:00
|
|
|
CommitCollection.new(container, commits, ref)
|
2015-06-14 22:04:20 +00:00
|
|
|
end
|
|
|
|
|
2018-07-12 16:06:31 +00:00
|
|
|
def find_branch(name)
|
|
|
|
raw_repository.find_branch(name)
|
2013-07-17 12:11:03 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def find_tag(name)
|
2016-06-22 15:26:14 +00:00
|
|
|
tags.find { |tag| tag.name == name }
|
2013-07-17 12:11:03 +00:00
|
|
|
end
|
|
|
|
|
2018-11-22 23:11:42 +00:00
|
|
|
def ambiguous_ref?(ref)
|
|
|
|
tag_exists?(ref) && branch_exists?(ref)
|
|
|
|
end
|
2018-11-20 14:07:25 +00:00
|
|
|
|
2020-08-07 18:09:53 +00:00
|
|
|
# It's possible for a tag name to be a prefix (including slash) of a branch
|
|
|
|
# name, or vice versa. For instance, a tag named `foo` means we can't create a
|
|
|
|
# tag `foo/bar`, but we _can_ create a branch `foo/bar`.
|
|
|
|
#
|
|
|
|
# If we know a repository has no refs of this type (which is the common case)
|
|
|
|
# then separating refs from paths - as in ExtractsRef - can be faster.
|
|
|
|
#
|
|
|
|
# This method only checks one level deep, so only prefixes that contain no
|
|
|
|
# slashes are considered. If a repository has a tag `foo/bar` and a branch
|
|
|
|
# `foo/bar/baz`, it will return false.
|
|
|
|
def has_ambiguous_refs?
|
|
|
|
return false unless branch_names.present? && tag_names.present?
|
|
|
|
|
|
|
|
with_slash, no_slash = (branch_names + tag_names).partition { |ref| ref.include?('/') }
|
|
|
|
|
|
|
|
return false if with_slash.empty?
|
|
|
|
|
|
|
|
prefixes = no_slash.map { |ref| Regexp.escape(ref) }.join('|')
|
2020-08-20 12:10:27 +00:00
|
|
|
prefix_regex = %r{^(#{prefixes})/}
|
2020-08-07 18:09:53 +00:00
|
|
|
|
|
|
|
with_slash.any? do |ref|
|
|
|
|
prefix_regex.match?(ref)
|
|
|
|
end
|
|
|
|
end
|
2020-08-18 21:09:57 +00:00
|
|
|
cache_method :has_ambiguous_refs?
|
2020-08-07 18:09:53 +00:00
|
|
|
|
2018-11-28 14:18:14 +00:00
|
|
|
def expand_ref(ref)
|
2018-11-22 23:11:42 +00:00
|
|
|
if tag_exists?(ref)
|
2018-11-20 14:07:25 +00:00
|
|
|
Gitlab::Git::TAG_REF_PREFIX + ref
|
2018-11-22 23:11:42 +00:00
|
|
|
elsif branch_exists?(ref)
|
2018-11-20 14:07:25 +00:00
|
|
|
Gitlab::Git::BRANCH_REF_PREFIX + ref
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-05 19:13:15 +00:00
|
|
|
def add_branch(user, branch_name, ref)
|
2017-09-13 16:16:56 +00:00
|
|
|
branch = raw_repository.add_branch(branch_name, user: user, target: ref)
|
2013-07-16 21:09:23 +00:00
|
|
|
|
2016-03-08 17:04:00 +00:00
|
|
|
after_create_branch
|
2017-09-01 14:16:42 +00:00
|
|
|
|
|
|
|
branch
|
|
|
|
rescue Gitlab::Git::Repository::InvalidRef
|
|
|
|
false
|
2013-07-16 21:09:23 +00:00
|
|
|
end
|
|
|
|
|
2016-04-25 14:31:45 +00:00
|
|
|
def add_tag(user, tag_name, target, message = nil)
|
2017-09-13 16:16:56 +00:00
|
|
|
raw_repository.add_tag(tag_name, user: user, target: target, message: message)
|
2017-09-01 14:16:42 +00:00
|
|
|
rescue Gitlab::Git::Repository::InvalidRef
|
|
|
|
false
|
2013-07-17 11:43:18 +00:00
|
|
|
end
|
|
|
|
|
2015-11-26 00:20:40 +00:00
|
|
|
def rm_branch(user, branch_name)
|
2016-03-08 16:53:00 +00:00
|
|
|
before_remove_branch
|
2015-11-26 00:20:40 +00:00
|
|
|
|
2017-09-13 16:16:56 +00:00
|
|
|
raw_repository.rm_branch(branch_name, user: user)
|
2013-07-16 21:09:23 +00:00
|
|
|
|
2016-03-08 16:53:00 +00:00
|
|
|
after_remove_branch
|
2015-11-26 00:20:40 +00:00
|
|
|
true
|
2013-07-16 19:19:07 +00:00
|
|
|
end
|
|
|
|
|
2017-01-04 18:53:45 +00:00
|
|
|
def rm_tag(user, tag_name)
|
2016-03-08 16:38:23 +00:00
|
|
|
before_remove_tag
|
2013-07-16 21:09:23 +00:00
|
|
|
|
2017-09-13 16:16:56 +00:00
|
|
|
raw_repository.rm_tag(tag_name, user: user)
|
2017-01-04 18:53:45 +00:00
|
|
|
|
|
|
|
after_remove_tag
|
|
|
|
true
|
2013-07-16 20:12:52 +00:00
|
|
|
end
|
|
|
|
|
2016-06-20 14:52:10 +00:00
|
|
|
def ref_names
|
|
|
|
branch_names + tag_names
|
|
|
|
end
|
|
|
|
|
2016-05-09 22:45:37 +00:00
|
|
|
def branch_exists?(branch_name)
|
2017-08-24 09:20:04 +00:00
|
|
|
return false unless raw_repository
|
|
|
|
|
2019-09-23 18:06:14 +00:00
|
|
|
branch_names_include?(branch_name)
|
2016-05-09 22:45:37 +00:00
|
|
|
end
|
|
|
|
|
2017-12-09 06:20:28 +00:00
|
|
|
def tag_exists?(tag_name)
|
|
|
|
return false unless raw_repository
|
|
|
|
|
2019-09-23 18:06:14 +00:00
|
|
|
tag_names_include?(tag_name)
|
2017-12-09 06:20:28 +00:00
|
|
|
end
|
|
|
|
|
2016-07-03 23:58:58 +00:00
|
|
|
def ref_exists?(ref)
|
2017-08-24 09:20:04 +00:00
|
|
|
!!raw_repository&.ref_exists?(ref)
|
|
|
|
rescue ArgumentError
|
2016-11-07 12:33:33 +00:00
|
|
|
false
|
2016-08-30 14:06:40 +00:00
|
|
|
end
|
|
|
|
|
Add repository languages for projects
Our friends at GitHub show the programming languages for a long time,
and inspired by that this commit means to create about the same
functionality.
Language detection is done through Linguist, as before, where the
difference is that we cache the result in the database. Also, Gitaly can
incrementaly scan a repository. This is done through a shell out, which
creates overhead of about 3s each run. For now this won't be improved.
Scans are triggered by pushed to the default branch, usually `master`.
However, one exception to this rule the charts page. If we're requesting
this expensive data anyway, we just cache it in the database.
Edge cases where there is no repository, or its empty are caught in the
Repository model. This makes use of Redis caching, which is probably
already loaded.
The added model is called RepositoryLanguage, which will make it harder
if/when GitLab supports multiple repositories per project. However, for
now I think this shouldn't be a concern. Also, Language could be
confused with the i18n languages and felt like the current name was
suiteable too.
Design of the Project#Show page is done with help from @dimitrieh. This
change is not visible to the end user unless detections are done.
2018-06-06 11:10:59 +00:00
|
|
|
def languages
|
|
|
|
return [] if empty?
|
|
|
|
|
|
|
|
raw_repository.languages(root_ref)
|
|
|
|
end
|
|
|
|
|
2018-08-16 23:55:00 +00:00
|
|
|
def keep_around(*shas)
|
2019-09-18 14:02:45 +00:00
|
|
|
Gitlab::Git::KeepAround.execute(self, shas)
|
2013-04-28 20:04:56 +00:00
|
|
|
end
|
2016-01-07 12:00:47 +00:00
|
|
|
|
2019-04-11 14:03:02 +00:00
|
|
|
def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:, path: nil)
|
2018-06-05 18:22:55 +00:00
|
|
|
raw_repository.archive_metadata(
|
|
|
|
ref,
|
|
|
|
storage_path,
|
2020-02-04 15:08:40 +00:00
|
|
|
project&.path,
|
2018-06-05 18:22:55 +00:00
|
|
|
format,
|
2019-04-11 14:03:02 +00:00
|
|
|
append_sha: append_sha,
|
|
|
|
path: path
|
2018-06-05 18:22:55 +00:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2018-06-22 12:59:36 +00:00
|
|
|
def cached_methods
|
|
|
|
CACHED_METHODS
|
|
|
|
end
|
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
def expire_tags_cache
|
2020-08-18 21:09:57 +00:00
|
|
|
expire_method_caches(%i(tag_names tag_count has_ambiguous_refs?))
|
2016-11-18 13:04:18 +00:00
|
|
|
@tags = nil
|
2020-08-19 18:10:34 +00:00
|
|
|
@tag_names_include = nil
|
2015-07-17 12:50:03 +00:00
|
|
|
end
|
2016-01-07 12:00:47 +00:00
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
def expire_branches_cache
|
2020-08-18 21:09:57 +00:00
|
|
|
expire_method_caches(%i(branch_names merged_branch_names branch_count has_visible_content? has_ambiguous_refs?))
|
2016-11-18 13:04:18 +00:00
|
|
|
@local_branches = nil
|
2017-08-24 09:20:04 +00:00
|
|
|
@branch_exists_memo = nil
|
2020-08-19 18:10:34 +00:00
|
|
|
@branch_names_include = nil
|
2016-06-28 09:39:29 +00:00
|
|
|
end
|
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
def expire_statistics_caches
|
|
|
|
expire_method_caches(%i(size commit_count))
|
2015-07-17 12:50:03 +00:00
|
|
|
end
|
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
def expire_all_method_caches
|
|
|
|
expire_method_caches(CACHED_METHODS)
|
2015-11-11 15:28:31 +00:00
|
|
|
end
|
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
def expire_avatar_cache
|
|
|
|
expire_method_caches(%i(avatar))
|
|
|
|
end
|
|
|
|
|
|
|
|
# Refreshes the method caches of this repository.
|
|
|
|
#
|
|
|
|
# types - An Array of file types (e.g. `:readme`) used to refresh extra
|
|
|
|
# caches.
|
|
|
|
def refresh_method_caches(types)
|
2018-07-31 14:07:24 +00:00
|
|
|
return if types.empty?
|
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
to_refresh = []
|
|
|
|
|
|
|
|
types.each do |type|
|
|
|
|
methods = METHOD_CACHES_FOR_FILE_TYPES[type.to_sym]
|
|
|
|
|
|
|
|
to_refresh.concat(Array(methods)) if methods
|
2015-01-29 04:08:28 +00:00
|
|
|
end
|
2016-01-07 12:00:47 +00:00
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
expire_method_caches(to_refresh)
|
2016-02-20 15:23:54 +00:00
|
|
|
|
2017-08-04 02:20:34 +00:00
|
|
|
to_refresh.each { |method| send(method) } # rubocop:disable GitlabSecurity/PublicSend
|
2015-07-21 01:34:19 +00:00
|
|
|
end
|
2016-01-07 12:00:47 +00:00
|
|
|
|
2016-02-09 13:59:11 +00:00
|
|
|
def expire_branch_cache(branch_name = nil)
|
|
|
|
# When we push to the root branch we have to flush the cache for all other
|
|
|
|
# branches as their statistics are based on the commits relative to the
|
|
|
|
# root branch.
|
|
|
|
if !branch_name || branch_name == root_ref
|
|
|
|
branches.each do |branch|
|
|
|
|
cache.expire(:"diverging_commit_counts_#{branch.name}")
|
2017-01-20 15:04:16 +00:00
|
|
|
cache.expire(:"commit_count_#{branch.name}")
|
2016-02-09 13:59:11 +00:00
|
|
|
end
|
|
|
|
# In case a commit is pushed to a non-root branch we only have to flush the
|
|
|
|
# cache for said branch.
|
|
|
|
else
|
|
|
|
cache.expire(:"diverging_commit_counts_#{branch_name}")
|
2017-01-20 15:04:16 +00:00
|
|
|
cache.expire(:"commit_count_#{branch_name}")
|
2015-07-21 01:34:19 +00:00
|
|
|
end
|
2013-06-25 10:55:03 +00:00
|
|
|
end
|
|
|
|
|
2016-02-08 11:50:55 +00:00
|
|
|
def expire_root_ref_cache
|
2016-11-18 13:04:18 +00:00
|
|
|
expire_method_caches(%i(root_ref))
|
2016-02-08 11:50:55 +00:00
|
|
|
end
|
|
|
|
|
2016-02-16 16:31:37 +00:00
|
|
|
# Expires the cache(s) used to determine if a repository is empty or not.
|
|
|
|
def expire_emptiness_caches
|
2016-11-18 13:04:18 +00:00
|
|
|
return unless empty?
|
2016-02-08 11:50:55 +00:00
|
|
|
|
2018-03-07 01:01:12 +00:00
|
|
|
expire_method_caches(%i(has_visible_content?))
|
2018-04-14 22:36:36 +00:00
|
|
|
raw_repository.expire_has_local_branches_cache
|
2016-03-08 16:38:23 +00:00
|
|
|
end
|
|
|
|
|
2015-03-21 20:44:51 +00:00
|
|
|
def lookup_cache
|
|
|
|
@lookup_cache ||= {}
|
|
|
|
end
|
|
|
|
|
2016-03-18 14:31:19 +00:00
|
|
|
def expire_exists_cache
|
2016-11-18 13:04:18 +00:00
|
|
|
expire_method_caches(%i(exists?))
|
2016-03-17 15:53:05 +00:00
|
|
|
end
|
|
|
|
|
2016-10-23 17:31:18 +00:00
|
|
|
# expire cache that doesn't depend on repository data (when expiring)
|
|
|
|
def expire_content_cache
|
|
|
|
expire_tags_cache
|
|
|
|
expire_branches_cache
|
|
|
|
expire_root_ref_cache
|
|
|
|
expire_emptiness_caches
|
|
|
|
expire_exists_cache
|
2016-11-18 13:04:18 +00:00
|
|
|
expire_statistics_caches
|
2016-03-18 14:31:19 +00:00
|
|
|
end
|
|
|
|
|
2019-08-16 19:53:56 +00:00
|
|
|
def expire_status_cache
|
2016-03-18 14:31:19 +00:00
|
|
|
expire_exists_cache
|
2016-04-07 07:23:02 +00:00
|
|
|
expire_root_ref_cache
|
|
|
|
expire_emptiness_caches
|
2019-08-16 19:53:56 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Runs code after a repository has been created.
|
|
|
|
def after_create
|
|
|
|
expire_status_cache
|
2016-08-16 14:18:48 +00:00
|
|
|
|
|
|
|
repository_event(:create_repository)
|
2016-03-18 14:31:19 +00:00
|
|
|
end
|
|
|
|
|
2016-02-23 11:02:59 +00:00
|
|
|
# Runs code just before a repository is deleted.
|
|
|
|
def before_delete
|
2016-03-27 12:54:12 +00:00
|
|
|
expire_exists_cache
|
2016-11-18 13:04:18 +00:00
|
|
|
expire_all_method_caches
|
|
|
|
expire_branch_cache if exists?
|
2016-10-23 17:45:08 +00:00
|
|
|
expire_content_cache
|
2016-08-16 14:18:48 +00:00
|
|
|
|
|
|
|
repository_event(:remove_repository)
|
2016-02-23 11:02:59 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Runs code just before the HEAD of a repository is changed.
|
|
|
|
def before_change_head
|
|
|
|
# Cached divergent commit counts are based on repository head
|
|
|
|
expire_branch_cache
|
|
|
|
expire_root_ref_cache
|
2016-08-16 14:18:48 +00:00
|
|
|
|
|
|
|
repository_event(:change_default_branch)
|
2016-02-23 11:02:59 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 16:38:23 +00:00
|
|
|
# Runs code before pushing (= creating or removing) a tag.
|
2019-08-13 16:04:30 +00:00
|
|
|
#
|
|
|
|
# Note that this doesn't expire the tags. You may need to call
|
|
|
|
# expire_caches_for_tags or expire_tags_cache.
|
2016-03-08 16:38:23 +00:00
|
|
|
def before_push_tag
|
2019-08-13 16:04:30 +00:00
|
|
|
repository_event(:push_tag)
|
|
|
|
end
|
|
|
|
|
|
|
|
def expire_caches_for_tags
|
2016-11-18 13:04:18 +00:00
|
|
|
expire_statistics_caches
|
|
|
|
expire_emptiness_caches
|
2016-03-08 17:01:16 +00:00
|
|
|
expire_tags_cache
|
2016-03-08 16:38:23 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Runs code before removing a tag.
|
|
|
|
def before_remove_tag
|
2019-08-13 16:04:30 +00:00
|
|
|
expire_caches_for_tags
|
2016-08-16 14:18:48 +00:00
|
|
|
|
|
|
|
repository_event(:remove_tag)
|
2016-02-23 11:02:59 +00:00
|
|
|
end
|
|
|
|
|
2017-01-04 18:53:45 +00:00
|
|
|
# Runs code after removing a tag.
|
|
|
|
def after_remove_tag
|
2019-08-13 16:04:30 +00:00
|
|
|
expire_caches_for_tags
|
2017-01-04 18:53:45 +00:00
|
|
|
end
|
|
|
|
|
2016-04-27 03:32:28 +00:00
|
|
|
# Runs code after the HEAD of a repository is changed.
|
|
|
|
def after_change_head
|
2018-06-22 12:59:36 +00:00
|
|
|
expire_all_method_caches
|
2016-04-04 22:35:39 +00:00
|
|
|
end
|
|
|
|
|
2016-02-23 11:02:59 +00:00
|
|
|
# Runs code after a new commit has been pushed.
|
2016-11-18 13:04:18 +00:00
|
|
|
def after_push_commit(branch_name)
|
|
|
|
expire_statistics_caches
|
|
|
|
expire_branch_cache(branch_name)
|
2016-08-16 14:18:48 +00:00
|
|
|
|
|
|
|
repository_event(:push_commit, branch: branch_name)
|
2016-02-23 11:02:59 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Runs code after a new branch has been created.
|
2019-08-09 10:09:06 +00:00
|
|
|
def after_create_branch(expire_cache: true)
|
|
|
|
expire_branches_cache if expire_cache
|
2016-08-16 14:18:48 +00:00
|
|
|
|
|
|
|
repository_event(:push_branch)
|
2016-02-23 11:02:59 +00:00
|
|
|
end
|
|
|
|
|
2016-03-08 16:53:00 +00:00
|
|
|
# Runs code before removing an existing branch.
|
|
|
|
def before_remove_branch
|
|
|
|
expire_branches_cache
|
2016-08-16 14:18:48 +00:00
|
|
|
|
|
|
|
repository_event(:remove_branch)
|
2016-03-08 16:53:00 +00:00
|
|
|
end
|
|
|
|
|
2016-02-23 11:02:59 +00:00
|
|
|
# Runs code after an existing branch has been removed.
|
2019-08-09 10:09:06 +00:00
|
|
|
def after_remove_branch(expire_cache: true)
|
|
|
|
expire_branches_cache if expire_cache
|
2016-02-23 11:02:59 +00:00
|
|
|
end
|
|
|
|
|
2018-07-04 14:02:01 +00:00
|
|
|
def method_missing(msg, *args, &block)
|
|
|
|
if msg == :lookup && !block_given?
|
|
|
|
lookup_cache[msg] ||= {}
|
|
|
|
lookup_cache[msg][args.join(":")] ||= raw_repository.__send__(msg, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
|
2015-03-21 20:44:51 +00:00
|
|
|
else
|
2018-07-04 14:02:01 +00:00
|
|
|
raw_repository.__send__(msg, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
|
2015-03-21 20:44:51 +00:00
|
|
|
end
|
2013-07-09 07:44:47 +00:00
|
|
|
end
|
|
|
|
|
2015-05-23 22:38:44 +00:00
|
|
|
def respond_to_missing?(method, include_private = false)
|
|
|
|
raw_repository.respond_to?(method, include_private) || super
|
2013-03-31 20:45:38 +00:00
|
|
|
end
|
2013-10-01 17:34:41 +00:00
|
|
|
|
|
|
|
def blob_at(sha, path)
|
2020-02-04 15:08:40 +00:00
|
|
|
blob = Blob.decorate(raw_repository.blob_at(sha, path), container)
|
2018-10-12 13:42:35 +00:00
|
|
|
|
|
|
|
# Don't attempt to return a special result if there is no blob at all
|
|
|
|
return unless blob
|
|
|
|
|
|
|
|
# Don't attempt to return a special result unless we're looking at HEAD
|
|
|
|
return blob unless head_commit&.sha == sha
|
|
|
|
|
|
|
|
case path
|
|
|
|
when head_tree&.readme_path
|
|
|
|
ReadmeBlob.new(blob, self)
|
|
|
|
else
|
|
|
|
blob
|
|
|
|
end
|
2017-02-01 21:49:20 +00:00
|
|
|
rescue Gitlab::Git::Repository::NoRepository
|
|
|
|
nil
|
2013-10-01 17:34:41 +00:00
|
|
|
end
|
2013-11-29 11:52:10 +00:00
|
|
|
|
2017-11-03 13:16:43 +00:00
|
|
|
# items is an Array like: [[oid, path], [oid1, path1]]
|
2020-02-02 06:08:56 +00:00
|
|
|
def blobs_at(items, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
|
2019-01-30 04:51:10 +00:00
|
|
|
return [] unless exists?
|
|
|
|
|
2020-02-02 06:08:56 +00:00
|
|
|
raw_repository.batch_blobs(items, blob_size_limit: blob_size_limit).map do |blob|
|
2020-02-04 15:08:40 +00:00
|
|
|
Blob.decorate(blob, container)
|
2020-02-02 06:08:56 +00:00
|
|
|
end
|
2017-11-03 13:16:43 +00:00
|
|
|
end
|
|
|
|
|
2016-11-18 13:04:18 +00:00
|
|
|
def root_ref
|
2019-03-08 01:19:56 +00:00
|
|
|
raw_repository&.root_ref
|
2013-11-29 11:52:10 +00:00
|
|
|
end
|
2019-03-08 01:19:56 +00:00
|
|
|
cache_method_asymmetrically :root_ref
|
2014-02-04 12:46:15 +00:00
|
|
|
|
2017-07-21 07:36:31 +00:00
|
|
|
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314
|
2016-11-18 13:04:18 +00:00
|
|
|
def exists?
|
2017-07-22 00:37:22 +00:00
|
|
|
return false unless full_path
|
2017-07-21 07:36:31 +00:00
|
|
|
|
2017-09-26 18:07:27 +00:00
|
|
|
raw_repository.exists?
|
2016-11-18 13:04:18 +00:00
|
|
|
end
|
2018-09-25 17:12:51 +00:00
|
|
|
cache_method_asymmetrically :exists?
|
2016-11-18 13:04:18 +00:00
|
|
|
|
2018-03-07 01:01:12 +00:00
|
|
|
# We don't need to cache the output of this method because both exists? and
|
|
|
|
# has_visible_content? are already memoized and cached. There's no guarantee
|
|
|
|
# that the values are expired and loaded atomically.
|
2017-12-07 15:33:30 +00:00
|
|
|
def empty?
|
|
|
|
return true unless exists?
|
|
|
|
|
|
|
|
!has_visible_content?
|
|
|
|
end
|
2016-11-18 13:04:18 +00:00
|
|
|
|
|
|
|
# The size of this repository in megabytes.
|
|
|
|
def size
|
|
|
|
exists? ? raw_repository.size : 0.0
|
|
|
|
end
|
|
|
|
cache_method :size, fallback: 0.0
|
|
|
|
|
|
|
|
def commit_count
|
|
|
|
root_ref ? raw_repository.commit_count(root_ref) : 0
|
|
|
|
end
|
|
|
|
cache_method :commit_count, fallback: 0
|
|
|
|
|
2017-01-20 15:04:16 +00:00
|
|
|
def commit_count_for_ref(ref)
|
2017-03-03 02:06:06 +00:00
|
|
|
return 0 unless exists?
|
2017-01-20 15:04:16 +00:00
|
|
|
|
2018-01-30 16:21:55 +00:00
|
|
|
cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
|
2014-06-25 09:07:06 +00:00
|
|
|
end
|
|
|
|
|
2017-02-16 02:08:30 +00:00
|
|
|
delegate :branch_names, to: :raw_repository
|
2019-09-23 18:06:14 +00:00
|
|
|
cache_method_as_redis_set :branch_names, fallback: []
|
2016-11-18 13:04:18 +00:00
|
|
|
|
2017-02-22 17:51:46 +00:00
|
|
|
delegate :tag_names, to: :raw_repository
|
2019-09-23 18:06:14 +00:00
|
|
|
cache_method_as_redis_set :tag_names, fallback: []
|
2016-11-18 13:04:18 +00:00
|
|
|
|
2017-09-29 13:08:44 +00:00
|
|
|
delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
|
2016-11-18 13:04:18 +00:00
|
|
|
cache_method :branch_count, fallback: 0
|
|
|
|
cache_method :tag_count, fallback: 0
|
2019-10-06 06:06:24 +00:00
|
|
|
cache_method_asymmetrically :has_visible_content?
|
2016-11-18 13:04:18 +00:00
|
|
|
|
|
|
|
def avatar
|
2019-09-18 14:02:45 +00:00
|
|
|
# n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/38327
|
2017-09-25 10:22:07 +00:00
|
|
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
|
|
|
if tree = file_on_head(:avatar)
|
|
|
|
tree.path
|
|
|
|
end
|
2015-03-17 09:28:50 +00:00
|
|
|
end
|
|
|
|
end
|
2016-11-18 13:04:18 +00:00
|
|
|
cache_method :avatar
|
2015-03-17 09:29:17 +00:00
|
|
|
|
2017-10-11 15:48:43 +00:00
|
|
|
def issue_template_names
|
|
|
|
Gitlab::Template::IssueTemplate.dropdown_names(project)
|
|
|
|
end
|
|
|
|
cache_method :issue_template_names, fallback: []
|
|
|
|
|
|
|
|
def merge_request_template_names
|
|
|
|
Gitlab::Template::MergeRequestTemplate.dropdown_names(project)
|
|
|
|
end
|
|
|
|
cache_method :merge_request_template_names, fallback: []
|
|
|
|
|
2020-07-31 00:09:37 +00:00
|
|
|
def user_defined_metrics_dashboard_paths
|
|
|
|
Gitlab::Metrics::Dashboard::RepoDashboardFinder.list_dashboards(project)
|
2019-05-01 10:16:03 +00:00
|
|
|
end
|
2020-07-31 00:09:37 +00:00
|
|
|
cache_method :user_defined_metrics_dashboard_paths, fallback: []
|
2019-05-01 10:16:03 +00:00
|
|
|
|
2013-11-29 11:52:10 +00:00
|
|
|
def readme
|
2018-10-12 13:42:35 +00:00
|
|
|
head_tree&.readme
|
2014-02-22 15:37:10 +00:00
|
|
|
end
|
|
|
|
|
2018-11-26 15:44:13 +00:00
|
|
|
def readme_path
|
|
|
|
readme&.path
|
|
|
|
end
|
|
|
|
cache_method :readme_path
|
|
|
|
|
2017-04-06 14:47:52 +00:00
|
|
|
def rendered_readme
|
2018-09-04 19:21:20 +00:00
|
|
|
return unless readme
|
|
|
|
|
|
|
|
context = { project: project }
|
|
|
|
|
|
|
|
MarkupHelper.markup_unsafe(readme.name, readme.data, context)
|
2017-04-06 14:47:52 +00:00
|
|
|
end
|
|
|
|
cache_method :rendered_readme
|
2015-11-12 16:00:39 +00:00
|
|
|
|
2014-02-22 15:37:10 +00:00
|
|
|
def contribution_guide
|
2016-11-18 13:04:18 +00:00
|
|
|
file_on_head(:contributing)
|
2015-03-17 09:28:50 +00:00
|
|
|
end
|
2016-11-18 13:04:18 +00:00
|
|
|
cache_method :contribution_guide
|
2015-03-17 09:29:17 +00:00
|
|
|
|
|
|
|
def changelog
|
2016-11-18 13:04:18 +00:00
|
|
|
file_on_head(:changelog)
|
2016-04-11 13:49:25 +00:00
|
|
|
end
|
2016-11-18 13:04:18 +00:00
|
|
|
cache_method :changelog
|
2014-02-22 15:37:10 +00:00
|
|
|
|
2016-04-11 13:49:25 +00:00
|
|
|
def license_blob
|
2016-11-18 13:04:18 +00:00
|
|
|
file_on_head(:license)
|
2016-04-11 13:49:25 +00:00
|
|
|
end
|
2016-11-18 13:04:18 +00:00
|
|
|
cache_method :license_blob
|
2015-10-01 18:34:23 +00:00
|
|
|
|
2016-04-11 13:49:25 +00:00
|
|
|
def license_key
|
2016-11-18 13:04:18 +00:00
|
|
|
return unless exists?
|
2016-04-11 13:49:25 +00:00
|
|
|
|
2018-03-01 14:30:41 +00:00
|
|
|
raw_repository.license_short_name
|
2014-02-22 15:37:10 +00:00
|
|
|
end
|
2016-11-18 13:04:18 +00:00
|
|
|
cache_method :license_key
|
2014-02-22 15:37:10 +00:00
|
|
|
|
2017-05-08 23:50:23 +00:00
|
|
|
def license
|
|
|
|
return unless license_key
|
2016-04-29 14:25:03 +00:00
|
|
|
|
2017-05-10 14:02:28 +00:00
|
|
|
Licensee::License.new(license_key)
|
2016-04-29 14:25:03 +00:00
|
|
|
end
|
2018-09-25 17:07:59 +00:00
|
|
|
memoize_method :license
|
2016-04-29 14:25:03 +00:00
|
|
|
|
|
|
|
def gitignore
|
2016-11-18 13:04:18 +00:00
|
|
|
file_on_head(:gitignore)
|
2016-04-29 14:25:03 +00:00
|
|
|
end
|
2016-11-18 13:04:18 +00:00
|
|
|
cache_method :gitignore
|
2016-07-26 03:59:39 +00:00
|
|
|
|
2016-03-24 14:17:13 +00:00
|
|
|
def gitlab_ci_yml
|
2016-11-18 13:04:18 +00:00
|
|
|
file_on_head(:gitlab_ci)
|
2016-03-24 14:17:13 +00:00
|
|
|
end
|
2016-11-18 13:04:18 +00:00
|
|
|
cache_method :gitlab_ci_yml
|
2016-03-24 14:17:13 +00:00
|
|
|
|
2018-05-09 14:54:47 +00:00
|
|
|
def xcode_project?
|
2018-05-18 11:01:27 +00:00
|
|
|
file_on_head(:xcode_config, :tree).present?
|
2018-05-09 14:54:47 +00:00
|
|
|
end
|
|
|
|
cache_method :xcode_project?
|
|
|
|
|
2014-02-04 12:46:15 +00:00
|
|
|
def head_commit
|
2015-03-21 20:45:08 +00:00
|
|
|
@head_commit ||= commit(self.root_ref)
|
|
|
|
end
|
|
|
|
|
|
|
|
def head_tree
|
2016-11-18 13:04:18 +00:00
|
|
|
if head_commit
|
|
|
|
@head_tree ||= Tree.new(self, head_commit.sha, nil)
|
|
|
|
end
|
2014-02-04 12:46:15 +00:00
|
|
|
end
|
|
|
|
|
2016-08-29 15:23:40 +00:00
|
|
|
def tree(sha = :head, path = nil, recursive: false)
|
2014-02-04 12:46:15 +00:00
|
|
|
if sha == :head
|
2016-11-18 13:04:18 +00:00
|
|
|
return unless head_commit
|
|
|
|
|
2015-03-21 20:45:08 +00:00
|
|
|
if path.nil?
|
|
|
|
return head_tree
|
|
|
|
else
|
|
|
|
sha = head_commit.sha
|
|
|
|
end
|
2014-02-04 12:46:15 +00:00
|
|
|
end
|
|
|
|
|
2016-08-29 15:23:40 +00:00
|
|
|
Tree.new(self, sha, path, recursive: recursive)
|
2014-02-04 12:46:15 +00:00
|
|
|
end
|
2014-02-05 09:39:55 +00:00
|
|
|
|
|
|
|
def blob_at_branch(branch_name, path)
|
2014-02-18 10:27:02 +00:00
|
|
|
last_commit = commit(branch_name)
|
2014-02-05 09:39:55 +00:00
|
|
|
|
2014-02-18 10:27:02 +00:00
|
|
|
if last_commit
|
|
|
|
blob_at(last_commit.sha, path)
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
2014-02-05 09:39:55 +00:00
|
|
|
end
|
2014-02-10 11:02:26 +00:00
|
|
|
|
2020-06-26 21:08:51 +00:00
|
|
|
def list_last_commits_for_tree(sha, path, offset: 0, limit: 25, literal_pathspec: false)
|
|
|
|
commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit, literal_pathspec: literal_pathspec)
|
2018-10-01 12:29:47 +00:00
|
|
|
|
|
|
|
commits.each do |path, commit|
|
2020-02-04 15:08:40 +00:00
|
|
|
commits[path] = ::Commit.new(commit, container)
|
2018-10-01 12:29:47 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-22 03:08:17 +00:00
|
|
|
def last_commit_for_path(sha, path, literal_pathspec: false)
|
|
|
|
commit = raw_repository.last_commit_for_path(sha, path, literal_pathspec: literal_pathspec)
|
2020-02-04 15:08:40 +00:00
|
|
|
::Commit.new(commit, container) if commit
|
2014-02-10 19:46:46 +00:00
|
|
|
end
|
2014-04-09 13:09:11 +00:00
|
|
|
|
2020-06-22 03:08:17 +00:00
|
|
|
def last_commit_id_for_path(sha, path, literal_pathspec: false)
|
2016-12-15 05:40:15 +00:00
|
|
|
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
|
2016-12-20 13:43:59 +00:00
|
|
|
|
2016-12-15 04:29:51 +00:00
|
|
|
cache.fetch(key) do
|
2020-06-22 03:08:17 +00:00
|
|
|
last_commit_for_path(sha, path, literal_pathspec: literal_pathspec)&.id
|
2016-12-15 04:29:51 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-06 02:03:01 +00:00
|
|
|
def next_branch(name, opts = {})
|
2016-04-18 07:39:07 +00:00
|
|
|
branch_ids = self.branch_names.map do |n|
|
|
|
|
next 1 if n == name
|
2017-11-14 09:02:39 +00:00
|
|
|
|
2016-04-18 07:39:07 +00:00
|
|
|
result = n.match(/\A#{name}-([0-9]+)\z/)
|
2015-12-08 15:42:10 +00:00
|
|
|
result[1].to_i if result
|
|
|
|
end.compact
|
|
|
|
|
2016-04-18 07:39:07 +00:00
|
|
|
highest_branch_id = branch_ids.max || 0
|
2015-12-08 15:42:10 +00:00
|
|
|
|
2016-04-18 07:39:07 +00:00
|
|
|
return name if opts[:mild] && 0 == highest_branch_id
|
|
|
|
|
|
|
|
"#{name}-#{highest_branch_id + 1}"
|
2015-12-08 15:42:10 +00:00
|
|
|
end
|
|
|
|
|
2020-07-17 09:09:43 +00:00
|
|
|
def branches_sorted_by(sort_by, pagination_params = nil)
|
|
|
|
raw_repository.local_branches(sort_by: sort_by, pagination_params: pagination_params)
|
2014-05-23 11:25:55 +00:00
|
|
|
end
|
2014-07-02 11:43:23 +00:00
|
|
|
|
2016-06-16 17:33:29 +00:00
|
|
|
def tags_sorted_by(value)
|
|
|
|
case value
|
2017-12-14 13:42:15 +00:00
|
|
|
when 'name_asc'
|
|
|
|
VersionSorter.sort(tags) { |tag| tag.name }
|
|
|
|
when 'name_desc'
|
2016-08-08 18:36:39 +00:00
|
|
|
VersionSorter.rsort(tags) { |tag| tag.name }
|
2016-06-16 17:33:29 +00:00
|
|
|
when 'updated_desc'
|
|
|
|
tags_sorted_by_committed_date.reverse
|
|
|
|
when 'updated_asc'
|
|
|
|
tags_sorted_by_committed_date
|
|
|
|
else
|
|
|
|
tags
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-18 14:06:55 +00:00
|
|
|
# Params:
|
|
|
|
#
|
|
|
|
# order_by: name|email|commits
|
|
|
|
# sort: asc|desc default: 'asc'
|
|
|
|
def contributors(order_by: nil, sort: 'asc')
|
2016-04-22 12:07:25 +00:00
|
|
|
commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
|
2014-07-02 11:43:23 +00:00
|
|
|
|
2017-11-18 14:06:55 +00:00
|
|
|
commits = commits.group_by(&:author_email).map do |email, commits|
|
2014-07-02 12:09:06 +00:00
|
|
|
contributor = Gitlab::Contributor.new
|
|
|
|
contributor.email = email
|
2014-07-02 11:43:23 +00:00
|
|
|
|
2014-09-30 08:56:48 +00:00
|
|
|
commits.each do |commit|
|
2014-07-02 12:09:06 +00:00
|
|
|
if contributor.name.blank?
|
2014-09-30 08:56:48 +00:00
|
|
|
contributor.name = commit.author_name
|
2014-07-02 11:43:23 +00:00
|
|
|
end
|
|
|
|
|
2014-07-02 12:09:06 +00:00
|
|
|
contributor.commits += 1
|
2014-07-02 11:43:23 +00:00
|
|
|
end
|
|
|
|
|
2014-07-02 12:09:06 +00:00
|
|
|
contributor
|
|
|
|
end
|
2017-11-18 14:06:55 +00:00
|
|
|
Commit.order_by(collection: commits, order_by: order_by, sort: sort)
|
2014-07-02 11:43:23 +00:00
|
|
|
end
|
2014-07-28 17:54:40 +00:00
|
|
|
|
2015-11-14 18:43:48 +00:00
|
|
|
def branch_names_contains(sha)
|
2018-01-30 08:59:45 +00:00
|
|
|
raw_repository.branch_names_contains_sha(sha)
|
2015-11-14 18:43:48 +00:00
|
|
|
end
|
2015-01-17 13:12:49 +00:00
|
|
|
|
2015-11-14 18:43:48 +00:00
|
|
|
def tag_names_contains(sha)
|
2018-01-30 08:59:45 +00:00
|
|
|
raw_repository.tag_names_contains_sha(sha)
|
2015-01-17 13:12:49 +00:00
|
|
|
end
|
2015-01-29 04:08:28 +00:00
|
|
|
|
2016-04-01 19:21:08 +00:00
|
|
|
def local_branches
|
2016-07-27 15:26:29 +00:00
|
|
|
@local_branches ||= raw_repository.local_branches
|
2015-03-21 20:44:11 +00:00
|
|
|
end
|
|
|
|
|
2016-04-01 19:21:08 +00:00
|
|
|
alias_method :branches, :local_branches
|
|
|
|
|
2015-03-21 20:44:11 +00:00
|
|
|
def tags
|
|
|
|
@tags ||= raw_repository.tags
|
|
|
|
end
|
|
|
|
|
2017-02-24 20:11:10 +00:00
|
|
|
def create_dir(user, path, **options)
|
|
|
|
options[:actions] = [{ action: :create_dir, file_path: path }]
|
2015-03-21 20:44:11 +00:00
|
|
|
|
2018-01-04 01:47:25 +00:00
|
|
|
multi_action(user, **options)
|
2015-09-17 05:45:22 +00:00
|
|
|
end
|
|
|
|
|
2017-02-24 20:11:10 +00:00
|
|
|
def create_file(user, path, content, **options)
|
|
|
|
options[:actions] = [{ action: :create, file_path: path, content: content }]
|
2016-09-08 15:40:07 +00:00
|
|
|
|
2018-01-04 01:47:25 +00:00
|
|
|
multi_action(user, **options)
|
2015-09-17 05:45:22 +00:00
|
|
|
end
|
2015-08-11 12:33:31 +00:00
|
|
|
|
2017-02-24 20:11:10 +00:00
|
|
|
def update_file(user, path, content, **options)
|
|
|
|
previous_path = options.delete(:previous_path)
|
|
|
|
action = previous_path && previous_path != path ? :move : :update
|
2015-08-11 12:33:31 +00:00
|
|
|
|
2017-02-24 20:11:10 +00:00
|
|
|
options[:actions] = [{ action: action, file_path: path, previous_path: previous_path, content: content }]
|
2015-08-11 12:33:31 +00:00
|
|
|
|
2018-01-04 01:47:25 +00:00
|
|
|
multi_action(user, **options)
|
2015-08-11 12:33:31 +00:00
|
|
|
end
|
|
|
|
|
2017-02-24 20:11:10 +00:00
|
|
|
def delete_file(user, path, **options)
|
|
|
|
options[:actions] = [{ action: :delete, file_path: path }]
|
2016-07-04 10:32:57 +00:00
|
|
|
|
2018-01-04 01:47:25 +00:00
|
|
|
multi_action(user, **options)
|
2016-07-04 10:32:57 +00:00
|
|
|
end
|
|
|
|
|
2017-09-08 12:00:53 +00:00
|
|
|
def with_cache_hooks
|
|
|
|
result = yield
|
2017-08-23 15:14:21 +00:00
|
|
|
|
2017-09-08 12:00:53 +00:00
|
|
|
return unless result
|
2017-08-23 15:14:21 +00:00
|
|
|
|
2017-09-08 12:00:53 +00:00
|
|
|
after_create if result.repo_created?
|
|
|
|
after_create_branch if result.branch_created?
|
2017-08-23 15:14:21 +00:00
|
|
|
|
2017-09-08 12:00:53 +00:00
|
|
|
result.newrev
|
|
|
|
end
|
|
|
|
|
2018-01-04 01:47:25 +00:00
|
|
|
def multi_action(user, **options)
|
|
|
|
start_project = options.delete(:start_project)
|
2016-08-29 23:58:32 +00:00
|
|
|
|
2018-01-04 01:47:25 +00:00
|
|
|
if start_project
|
|
|
|
options[:start_repository] = start_project.repository.raw_repository
|
2016-08-29 23:58:32 +00:00
|
|
|
end
|
|
|
|
|
2018-01-04 01:47:25 +00:00
|
|
|
with_cache_hooks { raw.multi_action(user, **options) }
|
2015-08-11 12:33:31 +00:00
|
|
|
end
|
|
|
|
|
2017-09-08 12:00:53 +00:00
|
|
|
def merge(user, source_sha, merge_request, message)
|
|
|
|
with_cache_hooks do
|
|
|
|
raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id|
|
|
|
|
merge_request.update(in_progress_merge_commit_sha: commit_id)
|
|
|
|
nil # Return value does not matter.
|
|
|
|
end
|
2015-08-14 15:52:49 +00:00
|
|
|
end
|
2016-01-29 17:04:48 +00:00
|
|
|
end
|
|
|
|
|
2019-06-20 12:45:46 +00:00
|
|
|
def merge_to_ref(user, source_sha, merge_request, target_ref, message, first_parent_ref)
|
2019-01-31 18:32:44 +00:00
|
|
|
branch = merge_request.target_branch
|
|
|
|
|
2019-06-20 12:45:46 +00:00
|
|
|
raw.merge_to_ref(user, source_sha, branch, target_ref, message, first_parent_ref)
|
2019-01-31 18:32:44 +00:00
|
|
|
end
|
|
|
|
|
2019-07-02 02:01:36 +00:00
|
|
|
def delete_refs(*ref_names)
|
|
|
|
raw.delete_refs(*ref_names)
|
|
|
|
end
|
|
|
|
|
2017-09-13 14:11:10 +00:00
|
|
|
def ff_merge(user, source, target_branch, merge_request: nil)
|
2017-10-23 17:16:10 +00:00
|
|
|
their_commit_id = commit(source)&.id
|
|
|
|
raise 'Invalid merge source' if their_commit_id.nil?
|
2017-09-13 14:11:10 +00:00
|
|
|
|
2017-10-23 17:16:10 +00:00
|
|
|
merge_request&.update(in_progress_merge_commit_sha: their_commit_id)
|
2017-09-13 14:11:10 +00:00
|
|
|
|
2017-10-23 17:16:10 +00:00
|
|
|
with_cache_hooks { raw.ff_merge(user, their_commit_id, target_branch) }
|
2017-09-13 14:11:10 +00:00
|
|
|
end
|
|
|
|
|
2016-12-07 11:50:08 +00:00
|
|
|
def revert(
|
2017-09-19 17:09:10 +00:00
|
|
|
user, commit, branch_name, message,
|
2020-07-30 18:09:39 +00:00
|
|
|
start_branch_name: nil, start_project: project, dry_run: false)
|
2016-02-03 23:28:40 +00:00
|
|
|
|
2017-09-19 17:09:10 +00:00
|
|
|
with_cache_hooks do
|
|
|
|
raw_repository.revert(
|
|
|
|
user: user,
|
|
|
|
commit: commit.raw,
|
|
|
|
branch_name: branch_name,
|
|
|
|
message: message,
|
|
|
|
start_branch_name: start_branch_name,
|
2020-07-30 18:09:39 +00:00
|
|
|
start_repository: start_project.repository.raw_repository,
|
|
|
|
dry_run: dry_run
|
2017-09-19 17:09:10 +00:00
|
|
|
)
|
2016-02-03 01:51:37 +00:00
|
|
|
end
|
2015-08-11 12:33:31 +00:00
|
|
|
end
|
|
|
|
|
2016-12-07 11:50:08 +00:00
|
|
|
def cherry_pick(
|
2017-09-19 17:09:10 +00:00
|
|
|
user, commit, branch_name, message,
|
2020-07-30 18:09:39 +00:00
|
|
|
start_branch_name: nil, start_project: project, dry_run: false)
|
2016-04-18 07:39:07 +00:00
|
|
|
|
2017-09-19 17:09:10 +00:00
|
|
|
with_cache_hooks do
|
|
|
|
raw_repository.cherry_pick(
|
|
|
|
user: user,
|
|
|
|
commit: commit.raw,
|
|
|
|
branch_name: branch_name,
|
|
|
|
message: message,
|
|
|
|
start_branch_name: start_branch_name,
|
2020-07-30 18:09:39 +00:00
|
|
|
start_repository: start_project.repository.raw_repository,
|
|
|
|
dry_run: dry_run
|
2017-09-19 17:09:10 +00:00
|
|
|
)
|
2016-04-18 07:39:07 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-17 18:13:45 +00:00
|
|
|
def merged_to_root_ref?(branch_or_name)
|
2017-10-27 15:55:08 +00:00
|
|
|
branch = Gitlab::Git::Branch.find(self, branch_or_name)
|
|
|
|
|
|
|
|
if branch
|
2018-01-09 17:30:04 +00:00
|
|
|
same_head = branch.target == root_ref_sha
|
|
|
|
merged = ancestor?(branch.target, root_ref_sha)
|
2017-10-27 15:55:08 +00:00
|
|
|
!same_head && merged
|
2015-08-09 18:31:50 +00:00
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-09 17:30:04 +00:00
|
|
|
def root_ref_sha
|
|
|
|
@root_ref_sha ||= commit(root_ref).sha
|
|
|
|
end
|
|
|
|
|
2020-02-12 18:09:21 +00:00
|
|
|
# If this method is not provided a set of branch names to check merge status,
|
|
|
|
# it fetches all branches.
|
2020-02-03 18:08:46 +00:00
|
|
|
def merged_branch_names(branch_names = [])
|
|
|
|
# Currently we should skip caching if requesting all branch names
|
|
|
|
# This is only used in a few places, notably app/services/branches/delete_merged_service.rb,
|
2020-03-05 15:07:52 +00:00
|
|
|
# and it could potentially result in a very large cache.
|
|
|
|
return raw_repository.merged_branch_names(branch_names) if branch_names.empty?
|
2020-02-03 18:08:46 +00:00
|
|
|
|
2020-02-12 18:09:21 +00:00
|
|
|
cache = redis_hash_cache
|
2020-02-03 18:08:46 +00:00
|
|
|
|
2020-02-12 18:09:21 +00:00
|
|
|
merged_branch_names_hash = cache.fetch_and_add_missing(:merged_branch_names, branch_names) do |missing_branch_names, hash|
|
2020-02-03 18:08:46 +00:00
|
|
|
merged = raw_repository.merged_branch_names(missing_branch_names)
|
|
|
|
|
|
|
|
missing_branch_names.each do |bn|
|
2020-02-12 18:09:21 +00:00
|
|
|
# Redis only stores strings in hset keys, use a fancy encoder
|
|
|
|
hash[bn] = Gitlab::Redis::Boolean.new(merged.include?(bn))
|
2020-02-03 18:08:46 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-12 18:09:21 +00:00
|
|
|
Set.new(merged_branch_names_hash.select { |_, v| Gitlab::Redis::Boolean.true?(v) }.keys)
|
2020-02-03 18:08:46 +00:00
|
|
|
end
|
2017-10-27 15:55:08 +00:00
|
|
|
|
2018-10-11 14:27:04 +00:00
|
|
|
def merge_base(*commits_or_ids)
|
|
|
|
commit_ids = commits_or_ids.map do |commit_or_id|
|
|
|
|
commit_or_id.is_a?(::Commit) ? commit_or_id.id : commit_or_id
|
|
|
|
end
|
|
|
|
|
|
|
|
raw_repository.merge_base(*commit_ids)
|
2015-10-16 05:45:06 +00:00
|
|
|
end
|
|
|
|
|
2017-08-24 16:35:06 +00:00
|
|
|
def ancestor?(ancestor_id, descendant_id)
|
2017-06-08 00:47:10 +00:00
|
|
|
return false if ancestor_id.nil? || descendant_id.nil?
|
2017-06-28 07:53:12 +00:00
|
|
|
|
2019-11-29 15:06:43 +00:00
|
|
|
cache_key = "ancestor:#{ancestor_id}:#{descendant_id}"
|
2020-01-28 18:08:35 +00:00
|
|
|
request_store_cache.fetch(cache_key) do
|
2019-11-29 15:06:43 +00:00
|
|
|
cache.fetch(cache_key) do
|
|
|
|
raw_repository.ancestor?(ancestor_id, descendant_id)
|
|
|
|
end
|
|
|
|
end
|
2015-11-11 15:27:55 +00:00
|
|
|
end
|
|
|
|
|
2018-03-02 13:50:17 +00:00
|
|
|
def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil, prune: true)
|
2017-11-15 15:46:08 +00:00
|
|
|
unless remote_name
|
|
|
|
remote_name = "tmp-#{SecureRandom.hex}"
|
|
|
|
tmp_remote_name = true
|
|
|
|
end
|
|
|
|
|
2017-12-06 22:08:29 +00:00
|
|
|
add_remote(remote_name, url, mirror_refmap: refmap)
|
2018-03-02 13:50:17 +00:00
|
|
|
fetch_remote(remote_name, forced: forced, prune: prune)
|
2017-11-15 15:46:08 +00:00
|
|
|
ensure
|
2018-05-03 14:19:21 +00:00
|
|
|
async_remove_remote(remote_name) if tmp_remote_name
|
2017-11-15 15:46:08 +00:00
|
|
|
end
|
|
|
|
|
2018-05-03 12:55:14 +00:00
|
|
|
def async_remove_remote(remote_name)
|
|
|
|
return unless remote_name
|
2020-02-04 15:08:40 +00:00
|
|
|
return unless project
|
2018-05-03 12:55:14 +00:00
|
|
|
|
|
|
|
job_id = RepositoryRemoveRemoteWorker.perform_async(project.id, remote_name)
|
|
|
|
|
|
|
|
if job_id
|
2020-05-24 12:08:20 +00:00
|
|
|
Gitlab::AppLogger.info("Remove remote job scheduled for #{project.id} with remote name: #{remote_name} job ID #{job_id}.")
|
2018-05-03 12:55:14 +00:00
|
|
|
else
|
2020-05-24 12:08:20 +00:00
|
|
|
Gitlab::AppLogger.info("Remove remote job failed to create for #{project.id} with remote name #{remote_name}.")
|
2018-05-03 12:55:14 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
job_id
|
|
|
|
end
|
|
|
|
|
2017-11-01 14:31:35 +00:00
|
|
|
def fetch_source_branch!(source_repository, source_branch, local_ref)
|
|
|
|
raw_repository.fetch_source_branch!(source_repository.raw_repository, source_branch, local_ref)
|
2017-08-23 15:14:21 +00:00
|
|
|
end
|
2017-08-11 12:12:17 +00:00
|
|
|
|
2017-08-23 15:14:21 +00:00
|
|
|
def compare_source_branch(target_branch_name, source_repository, source_branch_name, straight:)
|
|
|
|
raw_repository.compare_source_branch(target_branch_name, source_repository.raw_repository, source_branch_name, straight: straight)
|
2015-08-11 12:33:31 +00:00
|
|
|
end
|
2016-07-18 14:06:49 +00:00
|
|
|
|
2016-10-03 13:39:12 +00:00
|
|
|
def create_ref(ref, ref_path)
|
2017-12-20 18:29:52 +00:00
|
|
|
raw_repository.write_ref(ref_path, ref)
|
2015-08-14 14:23:40 +00:00
|
|
|
end
|
|
|
|
|
2016-01-07 11:56:18 +00:00
|
|
|
def ls_files(ref)
|
|
|
|
actual_ref = ref || root_ref
|
|
|
|
raw_repository.ls_files(actual_ref)
|
|
|
|
end
|
|
|
|
|
2020-02-16 21:08:53 +00:00
|
|
|
def search_files_by_content(query, ref, options = {})
|
2018-01-22 16:08:00 +00:00
|
|
|
return [] if empty? || query.blank?
|
|
|
|
|
2020-02-16 21:08:53 +00:00
|
|
|
raw_repository.search_files_by_content(query, ref, options)
|
2018-01-22 16:08:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def search_files_by_name(query, ref)
|
|
|
|
return [] if empty?
|
|
|
|
|
|
|
|
raw_repository.search_files_by_name(query, ref)
|
|
|
|
end
|
|
|
|
|
2016-03-14 23:33:00 +00:00
|
|
|
def copy_gitattributes(ref)
|
|
|
|
actual_ref = ref || root_ref
|
|
|
|
begin
|
|
|
|
raw_repository.copy_gitattributes(actual_ref)
|
|
|
|
true
|
|
|
|
rescue Gitlab::Git::Repository::InvalidRef
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-05-18 11:01:27 +00:00
|
|
|
def file_on_head(type, object_type = :blob)
|
|
|
|
return unless head = tree(:head)
|
|
|
|
|
|
|
|
objects =
|
|
|
|
case object_type
|
|
|
|
when :blob
|
|
|
|
head.blobs
|
|
|
|
when :tree
|
|
|
|
head.trees
|
|
|
|
else
|
|
|
|
raise ArgumentError, "Object type #{object_type} is not supported"
|
2016-03-17 15:53:05 +00:00
|
|
|
end
|
2018-05-18 11:01:27 +00:00
|
|
|
|
|
|
|
objects.find do |object|
|
|
|
|
Gitlab::FileDetector.type_of(object.path) == type
|
2016-03-17 15:53:05 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-07 00:06:46 +00:00
|
|
|
def route_map_for(sha)
|
|
|
|
blob_data_at(sha, '.gitlab/route-map.yml')
|
|
|
|
end
|
|
|
|
|
2017-07-05 12:04:53 +00:00
|
|
|
def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml')
|
|
|
|
blob_data_at(sha, path)
|
2017-02-07 00:06:46 +00:00
|
|
|
end
|
|
|
|
|
2018-06-06 16:42:18 +00:00
|
|
|
def lfsconfig_for(sha)
|
|
|
|
blob_data_at(sha, '.lfsconfig')
|
|
|
|
end
|
|
|
|
|
2017-11-10 14:45:23 +00:00
|
|
|
def fetch_ref(source_repository, source_ref:, target_ref:)
|
|
|
|
raw_repository.fetch_ref(source_repository.raw_repository, source_ref: source_ref, target_ref: target_ref)
|
|
|
|
end
|
|
|
|
|
2020-01-16 21:08:24 +00:00
|
|
|
def rebase(user, merge_request, skip_ci: false)
|
|
|
|
push_options = []
|
|
|
|
push_options << Gitlab::PushOptions::CI_SKIP if skip_ci
|
|
|
|
|
2019-11-26 18:06:33 +00:00
|
|
|
raw.rebase(
|
|
|
|
user,
|
|
|
|
merge_request.id,
|
|
|
|
branch: merge_request.source_branch,
|
|
|
|
branch_sha: merge_request.source_branch_sha,
|
|
|
|
remote_repository: merge_request.target_project.repository.raw,
|
2020-01-16 21:08:24 +00:00
|
|
|
remote_branch: merge_request.target_branch,
|
|
|
|
push_options: push_options
|
2019-11-26 18:06:33 +00:00
|
|
|
) do |commit_id|
|
|
|
|
merge_request.update!(rebase_commit_sha: commit_id, merge_error: nil)
|
2019-05-02 17:30:07 +00:00
|
|
|
end
|
2019-11-26 18:06:33 +00:00
|
|
|
rescue StandardError => error
|
|
|
|
merge_request.update!(rebase_commit_sha: nil)
|
|
|
|
raise error
|
2017-12-20 09:01:21 +00:00
|
|
|
end
|
|
|
|
|
2019-02-06 12:33:11 +00:00
|
|
|
def squash(user, merge_request, message)
|
2020-03-17 03:09:27 +00:00
|
|
|
raw.squash(user, merge_request.id, start_sha: merge_request.diff_start_sha,
|
2018-05-29 09:51:43 +00:00
|
|
|
end_sha: merge_request.diff_head_sha,
|
|
|
|
author: merge_request.author,
|
2019-02-06 12:33:11 +00:00
|
|
|
message: message)
|
2018-05-29 09:51:43 +00:00
|
|
|
end
|
|
|
|
|
2020-01-20 21:08:46 +00:00
|
|
|
def submodule_links
|
|
|
|
@submodule_links ||= ::Gitlab::SubmoduleLinks.new(self)
|
|
|
|
end
|
|
|
|
|
2018-07-31 16:35:02 +00:00
|
|
|
def update_submodule(user, submodule, commit_sha, message:, branch:)
|
|
|
|
with_cache_hooks do
|
|
|
|
raw.update_submodule(
|
|
|
|
user: user,
|
|
|
|
submodule: submodule,
|
|
|
|
commit_sha: commit_sha,
|
|
|
|
branch: branch,
|
|
|
|
message: message
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-07 19:37:44 +00:00
|
|
|
def blob_data_at(sha, path)
|
|
|
|
blob = blob_at(sha, path)
|
|
|
|
return unless blob
|
|
|
|
|
|
|
|
blob.load_all_data!
|
|
|
|
blob.data
|
|
|
|
end
|
|
|
|
|
2019-04-22 16:07:19 +00:00
|
|
|
def create_if_not_exists
|
|
|
|
return if exists?
|
|
|
|
|
|
|
|
raw.create_repository
|
|
|
|
after_create
|
2019-09-27 09:06:26 +00:00
|
|
|
|
|
|
|
true
|
2019-04-22 16:07:19 +00:00
|
|
|
end
|
|
|
|
|
2020-02-11 00:09:06 +00:00
|
|
|
def create_from_bundle(bundle_path)
|
|
|
|
raw.create_from_bundle(bundle_path).tap do |result|
|
|
|
|
after_create if result
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-22 16:07:19 +00:00
|
|
|
def blobs_metadata(paths, ref = 'HEAD')
|
|
|
|
references = Array.wrap(paths).map { |path| [ref, path] }
|
|
|
|
|
|
|
|
Gitlab::Git::Blob.batch_metadata(raw, references).map { |raw_blob| Blob.decorate(raw_blob) }
|
|
|
|
end
|
|
|
|
|
2020-02-04 15:08:40 +00:00
|
|
|
def project
|
2020-02-13 15:08:52 +00:00
|
|
|
if repo_type.snippet?
|
|
|
|
container.project
|
2020-06-24 03:08:44 +00:00
|
|
|
elsif container.is_a?(Project)
|
2020-02-13 15:08:52 +00:00
|
|
|
container
|
|
|
|
end
|
2020-02-04 15:08:40 +00:00
|
|
|
end
|
|
|
|
|
2020-04-22 12:09:29 +00:00
|
|
|
# TODO: pass this in directly to `Blob` rather than delegating it to here
|
|
|
|
#
|
|
|
|
# https://gitlab.com/gitlab-org/gitlab/-/issues/201886
|
|
|
|
def lfs_enabled?
|
|
|
|
if container.is_a?(Project)
|
|
|
|
container.lfs_enabled?
|
|
|
|
else
|
|
|
|
false # LFS is not supported for snippet or group repositories
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-01 14:23:58 +00:00
|
|
|
private
|
|
|
|
|
2020-01-07 18:07:34 +00:00
|
|
|
# TODO Genericize finder, later split this on finders by Ref or Oid
|
|
|
|
# https://gitlab.com/gitlab-org/gitlab/issues/19877
|
2017-10-13 08:37:31 +00:00
|
|
|
def find_commit(oid_or_ref)
|
|
|
|
commit = if oid_or_ref.is_a?(Gitlab::Git::Commit)
|
|
|
|
oid_or_ref
|
|
|
|
else
|
|
|
|
Gitlab::Git::Commit.find(raw_repository, oid_or_ref)
|
|
|
|
end
|
|
|
|
|
2020-02-13 15:08:52 +00:00
|
|
|
::Commit.new(commit, container) if commit
|
2017-10-13 08:37:31 +00:00
|
|
|
end
|
|
|
|
|
2015-01-29 04:08:28 +00:00
|
|
|
def cache
|
2019-01-08 19:37:54 +00:00
|
|
|
@cache ||= Gitlab::RepositoryCache.new(self)
|
2016-04-29 14:25:03 +00:00
|
|
|
end
|
2016-06-16 17:33:29 +00:00
|
|
|
|
2019-08-29 17:17:30 +00:00
|
|
|
def redis_set_cache
|
|
|
|
@redis_set_cache ||= Gitlab::RepositorySetCache.new(self)
|
|
|
|
end
|
|
|
|
|
2020-02-12 18:09:21 +00:00
|
|
|
def redis_hash_cache
|
|
|
|
@redis_hash_cache ||= Gitlab::RepositoryHashCache.new(self)
|
|
|
|
end
|
|
|
|
|
2018-09-25 17:12:51 +00:00
|
|
|
def request_store_cache
|
2019-01-08 19:37:54 +00:00
|
|
|
@request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
|
2018-09-25 17:12:51 +00:00
|
|
|
end
|
|
|
|
|
2016-06-16 17:33:29 +00:00
|
|
|
def tags_sorted_by_committed_date
|
2017-01-26 07:13:09 +00:00
|
|
|
tags.sort_by do |tag|
|
|
|
|
# Annotated tags can point to any object (e.g. a blob), but generally
|
|
|
|
# tags point to a commit. If we don't have a commit, then just default
|
|
|
|
# to putting the tag at the end of the list.
|
|
|
|
target = tag.dereferenced_target
|
|
|
|
|
|
|
|
if target
|
|
|
|
target.committed_date
|
|
|
|
else
|
2020-05-22 09:08:09 +00:00
|
|
|
Time.current
|
2017-01-26 07:13:09 +00:00
|
|
|
end
|
|
|
|
end
|
2016-06-16 17:33:29 +00:00
|
|
|
end
|
2016-07-04 00:30:55 +00:00
|
|
|
|
2016-08-16 14:18:48 +00:00
|
|
|
def repository_event(event, tags = {})
|
2018-06-19 17:03:25 +00:00
|
|
|
Gitlab::Metrics.add_event(event, tags)
|
2016-08-16 14:18:48 +00:00
|
|
|
end
|
2017-04-03 17:46:00 +00:00
|
|
|
|
2017-04-05 11:33:50 +00:00
|
|
|
def initialize_raw_repository
|
2020-02-27 03:08:49 +00:00
|
|
|
Gitlab::Git::Repository.new(shard,
|
2018-12-23 07:34:35 +00:00
|
|
|
disk_path + '.git',
|
2020-02-04 15:08:40 +00:00
|
|
|
repo_type.identifier_for_container(container),
|
|
|
|
container.full_path)
|
2017-02-16 02:08:30 +00:00
|
|
|
end
|
2013-03-31 20:45:38 +00:00
|
|
|
end
|
2019-09-13 13:26:31 +00:00
|
|
|
|
|
|
|
Repository.prepend_if_ee('EE::Repository')
|