2018-07-25 05:30:33 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2015-08-14 10:23:40 -04:00
|
|
|
require 'securerandom'
|
|
|
|
|
2013-03-31 16:45:38 -04:00
|
|
|
class Repository
|
2017-08-28 06:51:23 -04:00
|
|
|
REF_MERGE_REQUEST = 'merge-requests'.freeze
|
|
|
|
REF_KEEP_AROUND = 'keep-around'.freeze
|
|
|
|
REF_ENVIRONMENTS = 'environments'.freeze
|
2018-01-05 11:52:06 -05:00
|
|
|
MAX_DIVERGING_COUNT = 1000
|
2017-08-25 11:00:06 -04:00
|
|
|
|
|
|
|
RESERVED_REFS_NAMES = %W[
|
|
|
|
heads
|
|
|
|
tags
|
2017-09-22 04:18:59 -04:00
|
|
|
replace
|
2017-08-25 11:00:06 -04:00
|
|
|
#{REF_ENVIRONMENTS}
|
|
|
|
#{REF_KEEP_AROUND}
|
|
|
|
#{REF_ENVIRONMENTS}
|
|
|
|
].freeze
|
|
|
|
|
2018-03-06 19:12:29 -05:00
|
|
|
include Gitlab::RepositoryCacheAdapter
|
2013-07-16 15:19:07 -04:00
|
|
|
|
2019-03-18 12:51:11 -04:00
|
|
|
attr_accessor :full_path, :disk_path, :project, :repo_type
|
2013-03-31 16:45:38 -04:00
|
|
|
|
2017-03-28 13:14:48 -04:00
|
|
|
delegate :ref_name_for_sha, to: :raw_repository
|
2018-06-25 08:56:27 -04:00
|
|
|
delegate :bundle_to_disk, to: :raw_repository
|
2017-03-28 13:14:48 -04:00
|
|
|
|
2017-03-02 19:11:23 -05:00
|
|
|
CreateTreeError = Class.new(StandardError)
|
2018-11-28 09:43:58 -05:00
|
|
|
AmbiguousRefError = Class.new(StandardError)
|
2016-10-28 08:55:55 -04:00
|
|
|
|
2016-11-18 08:04:18 -05: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 10:47:52 -04: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 10:44:13 -05:00
|
|
|
CACHED_METHODS = %i(size commit_count rendered_readme readme_path contribution_guide
|
2018-10-12 20:54:08 -04:00
|
|
|
changelog license_blob license_key gitignore
|
2016-11-18 08:04:18 -05:00
|
|
|
gitlab_ci_yml branch_names tag_names branch_count
|
2018-03-06 20:01:12 -05:00
|
|
|
tag_count avatar exists? root_ref has_visible_content?
|
2019-03-19 05:36:05 -04:00
|
|
|
issue_template_names merge_request_template_names xcode_project?).freeze
|
2017-10-01 22:45:44 -04:00
|
|
|
|
|
|
|
# Methods that use cache_method but only memoize the value
|
2017-12-07 10:33:30 -05:00
|
|
|
MEMOIZED_CACHED_METHODS = %i(license).freeze
|
2016-11-18 08:04:18 -05: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 10:44:13 -05:00
|
|
|
readme: %i(rendered_readme readme_path),
|
2016-11-18 08:04:18 -05:00
|
|
|
changelog: :changelog,
|
2017-05-10 10:02:28 -04:00
|
|
|
license: %i(license_blob license_key license),
|
2016-11-18 08:04:18 -05:00
|
|
|
contributing: :contribution_guide,
|
|
|
|
gitignore: :gitignore,
|
|
|
|
gitlab_ci: :gitlab_ci_yml,
|
2017-10-11 11:48:43 -04:00
|
|
|
avatar: :avatar,
|
|
|
|
issue_template: :issue_template_names,
|
2018-05-09 10:54:47 -04:00
|
|
|
merge_request_template: :merge_request_template_names,
|
2019-03-19 05:36:05 -04:00
|
|
|
xcode_config: :xcode_project?
|
2017-02-21 18:32:18 -05:00
|
|
|
}.freeze
|
2016-11-18 08:04:18 -05:00
|
|
|
|
2019-03-18 12:51:11 -04:00
|
|
|
def initialize(full_path, project, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT)
|
2017-07-21 20:37:22 -04:00
|
|
|
@full_path = full_path
|
2017-07-23 03:05:34 -04:00
|
|
|
@disk_path = disk_path || full_path
|
2015-04-21 09:09:15 -04:00
|
|
|
@project = project
|
2017-10-13 04:37:31 -04:00
|
|
|
@commit_cache = {}
|
2019-03-18 12:51:11 -04:00
|
|
|
@repo_type = repo_type
|
2015-11-12 07:47:48 -05:00
|
|
|
end
|
2015-06-20 08:34:32 -04:00
|
|
|
|
2017-08-29 09:11:38 -04:00
|
|
|
def ==(other)
|
2018-11-23 06:27:08 -05:00
|
|
|
other.is_a?(self.class) && @disk_path == other.disk_path
|
|
|
|
end
|
|
|
|
|
|
|
|
alias_method :eql?, :==
|
|
|
|
|
|
|
|
def hash
|
|
|
|
[self.class, @disk_path].hash
|
2017-08-18 20:43:23 -04:00
|
|
|
end
|
|
|
|
|
2015-11-12 07:47:48 -05:00
|
|
|
def raw_repository
|
2019-02-08 07:19:53 -05:00
|
|
|
return unless full_path
|
2015-06-20 08:34:32 -04:00
|
|
|
|
2017-04-05 07:33:50 -04:00
|
|
|
@raw_repository ||= initialize_raw_repository
|
2016-02-17 10:49:16 -05:00
|
|
|
end
|
|
|
|
|
2017-07-25 16:48:17 -04:00
|
|
|
alias_method :raw, :raw_repository
|
|
|
|
|
2018-07-06 10:22:14 -04:00
|
|
|
# Don't use this! It's going away. Use Gitaly to read or write from repos.
|
2013-10-01 10:00:28 -04:00
|
|
|
def path_to_repo
|
2018-04-13 06:57:19 -04:00
|
|
|
@path_to_repo ||=
|
|
|
|
begin
|
|
|
|
storage = Gitlab.config.repositories.storages[@project.repository_storage]
|
|
|
|
|
|
|
|
File.expand_path(
|
|
|
|
File.join(storage.legacy_disk_path, disk_path + '.git')
|
|
|
|
)
|
|
|
|
end
|
2013-10-01 10:00:28 -04:00
|
|
|
end
|
|
|
|
|
2017-08-29 10:42:02 -04:00
|
|
|
def inspect
|
|
|
|
"#<#{self.class.name}:#{@disk_path}>"
|
|
|
|
end
|
|
|
|
|
2018-06-29 03:02:22 -04:00
|
|
|
def commit(ref = nil)
|
2019-02-08 07:19:53 -05:00
|
|
|
return unless exists?
|
2017-10-13 04:37:31 -04:00
|
|
|
return ref if ref.is_a?(::Commit)
|
2016-11-07 07:33:33 -05:00
|
|
|
|
2018-06-29 03:02:22 -04:00
|
|
|
find_commit(ref || root_ref)
|
2017-10-13 04:37:31 -04:00
|
|
|
end
|
2016-11-07 07:33:33 -05:00
|
|
|
|
2017-10-13 04:37:31 -04: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 16:45:38 -04:00
|
|
|
end
|
|
|
|
|
2017-12-05 08:15:30 -05:00
|
|
|
def commits_by(oids:)
|
|
|
|
return [] unless oids.present?
|
|
|
|
|
|
|
|
commits = Gitlab::Git::Commit.batch_by_oid(raw_repository, oids)
|
|
|
|
|
|
|
|
if commits.present?
|
|
|
|
Commit.decorate(commits, @project)
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-02-16 11:39:23 -05:00
|
|
|
def commits(ref = nil, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil, all: nil)
|
2015-10-23 07:13:22 -04:00
|
|
|
options = {
|
2013-08-05 09:51:04 -04:00
|
|
|
repo: raw_repository,
|
|
|
|
ref: ref,
|
|
|
|
path: path,
|
|
|
|
limit: limit,
|
|
|
|
offset: offset,
|
2016-04-22 08:07:25 -04:00
|
|
|
after: after,
|
|
|
|
before: before,
|
2017-04-17 08:58:07 -04:00
|
|
|
follow: Array(path).length == 1,
|
2018-02-16 11:39:23 -05:00
|
|
|
skip_merges: skip_merges,
|
|
|
|
all: all
|
2015-10-23 07:13:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
commits = Gitlab::Git::Commit.where(options)
|
2015-04-21 09:09:15 -04:00
|
|
|
commits = Commit.decorate(commits, @project) if commits.present?
|
2017-11-10 14:57:11 -05:00
|
|
|
|
|
|
|
CommitCollection.new(project, commits, ref)
|
2013-03-31 16:45:38 -04:00
|
|
|
end
|
|
|
|
|
2013-08-05 09:51:04 -04:00
|
|
|
def commits_between(from, to)
|
|
|
|
commits = Gitlab::Git::Commit.between(raw_repository, from, to)
|
2015-04-21 09:09:15 -04:00
|
|
|
commits = Commit.decorate(commits, @project) if commits.present?
|
2013-03-31 16:45:38 -04:00
|
|
|
commits
|
|
|
|
end
|
|
|
|
|
2018-02-07 08:00:53 -05:00
|
|
|
# Returns a list of commits that are not present in any reference
|
|
|
|
def new_commits(newrev)
|
2018-07-17 06:17:43 -04:00
|
|
|
commits = raw.new_commits(newrev)
|
2018-02-07 08:00:53 -05:00
|
|
|
|
2018-07-17 06:17:43 -04:00
|
|
|
::Commit.decorate(commits, project)
|
2018-02-07 08:00:53 -05:00
|
|
|
end
|
|
|
|
|
2017-07-13 11:58:45 -04:00
|
|
|
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
|
2016-01-07 07:00:47 -05:00
|
|
|
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
|
2016-10-19 12:43:04 -04:00
|
|
|
unless exists? && has_visible_content? && query.present?
|
|
|
|
return []
|
|
|
|
end
|
|
|
|
|
2018-01-22 11:08:00 -05:00
|
|
|
commits = raw_repository.find_commits_by_message(query, ref, path, limit, offset).map do |c|
|
|
|
|
commit(c)
|
2017-08-03 04:22:01 -04:00
|
|
|
end
|
2018-01-22 11:08:00 -05:00
|
|
|
CommitCollection.new(project, commits, ref)
|
2015-06-14 18:04:20 -04:00
|
|
|
end
|
|
|
|
|
2018-07-12 12:06:31 -04:00
|
|
|
def find_branch(name)
|
|
|
|
raw_repository.find_branch(name)
|
2013-07-17 08:11:03 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def find_tag(name)
|
2016-06-22 11:26:14 -04:00
|
|
|
tags.find { |tag| tag.name == name }
|
2013-07-17 08:11:03 -04:00
|
|
|
end
|
|
|
|
|
2018-11-22 18:11:42 -05:00
|
|
|
def ambiguous_ref?(ref)
|
|
|
|
tag_exists?(ref) && branch_exists?(ref)
|
|
|
|
end
|
2018-11-20 09:07:25 -05:00
|
|
|
|
2018-11-28 09:18:14 -05:00
|
|
|
def expand_ref(ref)
|
2018-11-22 18:11:42 -05:00
|
|
|
if tag_exists?(ref)
|
2018-11-20 09:07:25 -05:00
|
|
|
Gitlab::Git::TAG_REF_PREFIX + ref
|
2018-11-22 18:11:42 -05:00
|
|
|
elsif branch_exists?(ref)
|
2018-11-20 09:07:25 -05:00
|
|
|
Gitlab::Git::BRANCH_REF_PREFIX + ref
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-05 14:13:15 -05:00
|
|
|
def add_branch(user, branch_name, ref)
|
2017-09-13 12:16:56 -04:00
|
|
|
branch = raw_repository.add_branch(branch_name, user: user, target: ref)
|
2013-07-16 17:09:23 -04:00
|
|
|
|
2016-03-08 12:04:00 -05:00
|
|
|
after_create_branch
|
2017-09-01 10:16:42 -04:00
|
|
|
|
|
|
|
branch
|
|
|
|
rescue Gitlab::Git::Repository::InvalidRef
|
|
|
|
false
|
2013-07-16 17:09:23 -04:00
|
|
|
end
|
|
|
|
|
2016-04-25 10:31:45 -04:00
|
|
|
def add_tag(user, tag_name, target, message = nil)
|
2017-09-13 12:16:56 -04:00
|
|
|
raw_repository.add_tag(tag_name, user: user, target: target, message: message)
|
2017-09-01 10:16:42 -04:00
|
|
|
rescue Gitlab::Git::Repository::InvalidRef
|
|
|
|
false
|
2013-07-17 07:43:18 -04:00
|
|
|
end
|
|
|
|
|
2015-11-25 19:20:40 -05:00
|
|
|
def rm_branch(user, branch_name)
|
2016-03-08 11:53:00 -05:00
|
|
|
before_remove_branch
|
2015-11-25 19:20:40 -05:00
|
|
|
|
2017-09-13 12:16:56 -04:00
|
|
|
raw_repository.rm_branch(branch_name, user: user)
|
2013-07-16 17:09:23 -04:00
|
|
|
|
2016-03-08 11:53:00 -05:00
|
|
|
after_remove_branch
|
2015-11-25 19:20:40 -05:00
|
|
|
true
|
2013-07-16 15:19:07 -04:00
|
|
|
end
|
|
|
|
|
2017-01-04 13:53:45 -05:00
|
|
|
def rm_tag(user, tag_name)
|
2016-03-08 11:38:23 -05:00
|
|
|
before_remove_tag
|
2013-07-16 17:09:23 -04:00
|
|
|
|
2017-09-13 12:16:56 -04:00
|
|
|
raw_repository.rm_tag(tag_name, user: user)
|
2017-01-04 13:53:45 -05:00
|
|
|
|
|
|
|
after_remove_tag
|
|
|
|
true
|
2013-07-16 16:12:52 -04:00
|
|
|
end
|
|
|
|
|
2016-06-20 10:52:10 -04:00
|
|
|
def ref_names
|
|
|
|
branch_names + tag_names
|
|
|
|
end
|
|
|
|
|
2016-05-09 18:45:37 -04:00
|
|
|
def branch_exists?(branch_name)
|
2017-08-24 05:20:04 -04:00
|
|
|
return false unless raw_repository
|
|
|
|
|
2017-11-21 14:00:05 -05:00
|
|
|
branch_names.include?(branch_name)
|
2016-05-09 18:45:37 -04:00
|
|
|
end
|
|
|
|
|
2017-12-09 01:20:28 -05:00
|
|
|
def tag_exists?(tag_name)
|
|
|
|
return false unless raw_repository
|
|
|
|
|
|
|
|
tag_names.include?(tag_name)
|
|
|
|
end
|
|
|
|
|
2016-07-03 19:58:58 -04:00
|
|
|
def ref_exists?(ref)
|
2017-08-24 05:20:04 -04:00
|
|
|
!!raw_repository&.ref_exists?(ref)
|
|
|
|
rescue ArgumentError
|
2016-11-07 07:33:33 -05:00
|
|
|
false
|
2016-08-30 10:06:40 -04: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 07:10:59 -04:00
|
|
|
def languages
|
|
|
|
return [] if empty?
|
|
|
|
|
|
|
|
raw_repository.languages(root_ref)
|
|
|
|
end
|
|
|
|
|
2016-07-03 20:30:55 -04:00
|
|
|
# Makes sure a commit is kept around when Git garbage collection runs.
|
|
|
|
# Git GC will delete commits from the repository that are no longer in any
|
|
|
|
# branches or tags, but we want to keep some of these commits around, for
|
|
|
|
# example if they have comments or CI builds.
|
2018-08-16 19:55:00 -04:00
|
|
|
#
|
|
|
|
# For Geo's sake, pass in multiple shas rather than calling it multiple times,
|
|
|
|
# to avoid unnecessary syncing.
|
|
|
|
def keep_around(*shas)
|
|
|
|
shas.each do |sha|
|
2019-03-13 09:42:43 -04:00
|
|
|
next unless sha.present? && commit_by(oid: sha)
|
2016-07-03 19:58:58 -04:00
|
|
|
|
2019-03-13 09:42:43 -04:00
|
|
|
next if kept_around?(sha)
|
2016-07-03 19:58:58 -04:00
|
|
|
|
2019-03-13 09:42:43 -04:00
|
|
|
# This will still fail if the file is corrupted (e.g. 0 bytes)
|
|
|
|
raw_repository.write_ref(keep_around_ref_name(sha), sha)
|
|
|
|
rescue Gitlab::Git::CommandError => ex
|
|
|
|
Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
|
2018-08-16 19:55:00 -04:00
|
|
|
end
|
2016-07-03 19:58:58 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def kept_around?(sha)
|
2016-11-09 14:56:05 -05:00
|
|
|
ref_exists?(keep_around_ref_name(sha))
|
2013-04-28 16:04:56 -04:00
|
|
|
end
|
2016-01-07 07:00:47 -05:00
|
|
|
|
2015-07-20 21:34:19 -04:00
|
|
|
def diverging_commit_counts(branch)
|
2018-03-26 11:57:13 -04:00
|
|
|
@root_ref_hash ||= raw_repository.commit(root_ref).id
|
2015-11-11 17:29:29 -05:00
|
|
|
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
|
2015-12-15 17:23:52 -05:00
|
|
|
# Rugged seems to throw a `ReferenceError` when given branch_names rather
|
|
|
|
# than SHA-1 hashes
|
2018-01-05 11:52:06 -05:00
|
|
|
number_commits_behind, number_commits_ahead =
|
2019-01-09 21:45:47 -05:00
|
|
|
raw_repository.diverging_commit_count(
|
2018-03-26 11:57:13 -04:00
|
|
|
@root_ref_hash,
|
2018-01-05 11:52:06 -05:00
|
|
|
branch.dereferenced_target.sha,
|
|
|
|
max_count: MAX_DIVERGING_COUNT)
|
2016-01-07 07:00:47 -05:00
|
|
|
|
2019-01-09 21:45:47 -05:00
|
|
|
if number_commits_behind + number_commits_ahead >= MAX_DIVERGING_COUNT
|
|
|
|
{ distance: MAX_DIVERGING_COUNT }
|
|
|
|
else
|
|
|
|
{ behind: number_commits_behind, ahead: number_commits_ahead }
|
|
|
|
end
|
2015-07-20 21:34:19 -04:00
|
|
|
end
|
|
|
|
end
|
2013-04-28 16:04:56 -04:00
|
|
|
|
2019-04-11 19:49:53 -04:00
|
|
|
def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
|
2018-06-05 14:22:55 -04:00
|
|
|
raw_repository.archive_metadata(
|
|
|
|
ref,
|
|
|
|
storage_path,
|
|
|
|
project.path,
|
|
|
|
format,
|
2019-04-11 19:49:53 -04:00
|
|
|
append_sha: append_sha
|
2018-06-05 14:22:55 -04:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2018-06-22 08:59:36 -04:00
|
|
|
def cached_methods
|
|
|
|
CACHED_METHODS
|
|
|
|
end
|
|
|
|
|
2016-11-18 08:04:18 -05:00
|
|
|
def expire_tags_cache
|
|
|
|
expire_method_caches(%i(tag_names tag_count))
|
|
|
|
@tags = nil
|
2015-07-17 08:50:03 -04:00
|
|
|
end
|
2016-01-07 07:00:47 -05:00
|
|
|
|
2016-11-18 08:04:18 -05:00
|
|
|
def expire_branches_cache
|
2017-10-01 22:45:44 -04:00
|
|
|
expire_method_caches(%i(branch_names branch_count has_visible_content?))
|
2016-11-18 08:04:18 -05:00
|
|
|
@local_branches = nil
|
2017-08-24 05:20:04 -04:00
|
|
|
@branch_exists_memo = nil
|
2016-06-28 05:39:29 -04:00
|
|
|
end
|
|
|
|
|
2016-11-18 08:04:18 -05:00
|
|
|
def expire_statistics_caches
|
|
|
|
expire_method_caches(%i(size commit_count))
|
2015-07-17 08:50:03 -04:00
|
|
|
end
|
|
|
|
|
2016-11-18 08:04:18 -05:00
|
|
|
def expire_all_method_caches
|
|
|
|
expire_method_caches(CACHED_METHODS)
|
2015-11-11 10:28:31 -05:00
|
|
|
end
|
|
|
|
|
2016-11-18 08:04:18 -05: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 10:07:24 -04:00
|
|
|
return if types.empty?
|
|
|
|
|
2016-11-18 08:04:18 -05: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-28 23:08:28 -05:00
|
|
|
end
|
2016-01-07 07:00:47 -05:00
|
|
|
|
2016-11-18 08:04:18 -05:00
|
|
|
expire_method_caches(to_refresh)
|
2016-02-20 10:23:54 -05:00
|
|
|
|
2017-08-03 22:20:34 -04:00
|
|
|
to_refresh.each { |method| send(method) } # rubocop:disable GitlabSecurity/PublicSend
|
2015-07-20 21:34:19 -04:00
|
|
|
end
|
2016-01-07 07:00:47 -05:00
|
|
|
|
2016-02-09 08:59:11 -05: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 10:04:16 -05:00
|
|
|
cache.expire(:"commit_count_#{branch.name}")
|
2016-02-09 08:59:11 -05: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 10:04:16 -05:00
|
|
|
cache.expire(:"commit_count_#{branch_name}")
|
2015-07-20 21:34:19 -04:00
|
|
|
end
|
2013-06-25 06:55:03 -04:00
|
|
|
end
|
|
|
|
|
2016-02-08 06:50:55 -05:00
|
|
|
def expire_root_ref_cache
|
2016-11-18 08:04:18 -05:00
|
|
|
expire_method_caches(%i(root_ref))
|
2016-02-08 06:50:55 -05:00
|
|
|
end
|
|
|
|
|
2016-02-16 11:31:37 -05:00
|
|
|
# Expires the cache(s) used to determine if a repository is empty or not.
|
|
|
|
def expire_emptiness_caches
|
2016-11-18 08:04:18 -05:00
|
|
|
return unless empty?
|
2016-02-08 06:50:55 -05:00
|
|
|
|
2018-03-06 20:01:12 -05:00
|
|
|
expire_method_caches(%i(has_visible_content?))
|
2018-04-14 18:36:36 -04:00
|
|
|
raw_repository.expire_has_local_branches_cache
|
2016-03-08 11:38:23 -05:00
|
|
|
end
|
|
|
|
|
2015-03-21 16:44:51 -04:00
|
|
|
def lookup_cache
|
|
|
|
@lookup_cache ||= {}
|
|
|
|
end
|
|
|
|
|
2016-03-18 10:31:19 -04:00
|
|
|
def expire_exists_cache
|
2016-11-18 08:04:18 -05:00
|
|
|
expire_method_caches(%i(exists?))
|
2016-03-17 11:53:05 -04:00
|
|
|
end
|
|
|
|
|
2016-10-23 13:31:18 -04: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 08:04:18 -05:00
|
|
|
expire_statistics_caches
|
2016-03-18 10:31:19 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# Runs code after a repository has been created.
|
|
|
|
def after_create
|
|
|
|
expire_exists_cache
|
2016-04-07 03:23:02 -04:00
|
|
|
expire_root_ref_cache
|
|
|
|
expire_emptiness_caches
|
2016-08-16 10:18:48 -04:00
|
|
|
|
|
|
|
repository_event(:create_repository)
|
2016-03-18 10:31:19 -04:00
|
|
|
end
|
|
|
|
|
2016-02-23 06:02:59 -05:00
|
|
|
# Runs code just before a repository is deleted.
|
|
|
|
def before_delete
|
2016-03-27 08:54:12 -04:00
|
|
|
expire_exists_cache
|
2016-11-18 08:04:18 -05:00
|
|
|
expire_all_method_caches
|
|
|
|
expire_branch_cache if exists?
|
2016-10-23 13:45:08 -04:00
|
|
|
expire_content_cache
|
2016-08-16 10:18:48 -04:00
|
|
|
|
|
|
|
repository_event(:remove_repository)
|
2016-02-23 06:02:59 -05: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 10:18:48 -04:00
|
|
|
|
|
|
|
repository_event(:change_default_branch)
|
2016-02-23 06:02:59 -05:00
|
|
|
end
|
|
|
|
|
2016-03-08 11:38:23 -05:00
|
|
|
# Runs code before pushing (= creating or removing) a tag.
|
|
|
|
def before_push_tag
|
2016-11-18 08:04:18 -05:00
|
|
|
expire_statistics_caches
|
|
|
|
expire_emptiness_caches
|
2016-03-08 12:01:16 -05:00
|
|
|
expire_tags_cache
|
2016-08-16 10:18:48 -04:00
|
|
|
|
|
|
|
repository_event(:push_tag)
|
2016-03-08 11:38:23 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Runs code before removing a tag.
|
|
|
|
def before_remove_tag
|
|
|
|
expire_tags_cache
|
2016-11-18 08:04:18 -05:00
|
|
|
expire_statistics_caches
|
2016-08-16 10:18:48 -04:00
|
|
|
|
|
|
|
repository_event(:remove_tag)
|
2016-02-23 06:02:59 -05:00
|
|
|
end
|
|
|
|
|
2017-01-04 13:53:45 -05:00
|
|
|
# Runs code after removing a tag.
|
|
|
|
def after_remove_tag
|
|
|
|
expire_tags_cache
|
|
|
|
end
|
|
|
|
|
2016-04-26 23:32:28 -04:00
|
|
|
# Runs code after the HEAD of a repository is changed.
|
|
|
|
def after_change_head
|
2018-06-22 08:59:36 -04:00
|
|
|
expire_all_method_caches
|
2016-04-04 18:35:39 -04:00
|
|
|
end
|
|
|
|
|
2016-02-23 06:02:59 -05:00
|
|
|
# Runs code after a repository has been forked/imported.
|
|
|
|
def after_import
|
2016-10-23 13:31:18 -04:00
|
|
|
expire_content_cache
|
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 07:10:59 -04:00
|
|
|
|
|
|
|
DetectRepositoryLanguagesWorker.perform_async(project.id, project.owner.id)
|
2016-02-23 06:02:59 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Runs code after a new commit has been pushed.
|
2016-11-18 08:04:18 -05:00
|
|
|
def after_push_commit(branch_name)
|
|
|
|
expire_statistics_caches
|
|
|
|
expire_branch_cache(branch_name)
|
2016-08-16 10:18:48 -04:00
|
|
|
|
|
|
|
repository_event(:push_commit, branch: branch_name)
|
2016-02-23 06:02:59 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Runs code after a new branch has been created.
|
|
|
|
def after_create_branch
|
2016-03-08 12:04:00 -05:00
|
|
|
expire_branches_cache
|
2016-08-16 10:18:48 -04:00
|
|
|
|
|
|
|
repository_event(:push_branch)
|
2016-02-23 06:02:59 -05:00
|
|
|
end
|
|
|
|
|
2016-03-08 11:53:00 -05:00
|
|
|
# Runs code before removing an existing branch.
|
|
|
|
def before_remove_branch
|
|
|
|
expire_branches_cache
|
2016-08-16 10:18:48 -04:00
|
|
|
|
|
|
|
repository_event(:remove_branch)
|
2016-03-08 11:53:00 -05:00
|
|
|
end
|
|
|
|
|
2016-02-23 06:02:59 -05:00
|
|
|
# Runs code after an existing branch has been removed.
|
|
|
|
def after_remove_branch
|
2016-03-08 11:53:00 -05:00
|
|
|
expire_branches_cache
|
2016-02-23 06:02:59 -05:00
|
|
|
end
|
|
|
|
|
2018-07-04 10:02:01 -04: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 16:44:51 -04:00
|
|
|
else
|
2018-07-04 10:02:01 -04:00
|
|
|
raw_repository.__send__(msg, *args, &block) # rubocop:disable GitlabSecurity/PublicSend
|
2015-03-21 16:44:51 -04:00
|
|
|
end
|
2013-07-09 03:44:47 -04:00
|
|
|
end
|
|
|
|
|
2015-05-23 18:38:44 -04:00
|
|
|
def respond_to_missing?(method, include_private = false)
|
|
|
|
raw_repository.respond_to?(method, include_private) || super
|
2013-03-31 16:45:38 -04:00
|
|
|
end
|
2013-10-01 13:34:41 -04:00
|
|
|
|
|
|
|
def blob_at(sha, path)
|
2018-10-12 09:42:35 -04:00
|
|
|
blob = Blob.decorate(raw_repository.blob_at(sha, path), project)
|
|
|
|
|
|
|
|
# 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 16:49:20 -05:00
|
|
|
rescue Gitlab::Git::Repository::NoRepository
|
|
|
|
nil
|
2013-10-01 13:34:41 -04:00
|
|
|
end
|
2013-11-29 06:52:10 -05:00
|
|
|
|
2017-11-03 09:16:43 -04:00
|
|
|
# items is an Array like: [[oid, path], [oid1, path1]]
|
|
|
|
def blobs_at(items)
|
2019-01-29 23:51:10 -05:00
|
|
|
return [] unless exists?
|
|
|
|
|
2017-11-03 09:16:43 -04:00
|
|
|
raw_repository.batch_blobs(items).map { |blob| Blob.decorate(blob, project) }
|
|
|
|
end
|
|
|
|
|
2016-11-18 08:04:18 -05:00
|
|
|
def root_ref
|
2019-03-07 20:19:56 -05:00
|
|
|
raw_repository&.root_ref
|
2013-11-29 06:52:10 -05:00
|
|
|
end
|
2019-03-07 20:19:56 -05:00
|
|
|
cache_method_asymmetrically :root_ref
|
2014-02-04 07:46:15 -05:00
|
|
|
|
2017-07-21 03:36:31 -04:00
|
|
|
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314
|
2016-11-18 08:04:18 -05:00
|
|
|
def exists?
|
2017-07-21 20:37:22 -04:00
|
|
|
return false unless full_path
|
2017-07-21 03:36:31 -04:00
|
|
|
|
2017-09-26 14:07:27 -04:00
|
|
|
raw_repository.exists?
|
2016-11-18 08:04:18 -05:00
|
|
|
end
|
2018-09-25 13:12:51 -04:00
|
|
|
cache_method_asymmetrically :exists?
|
2016-11-18 08:04:18 -05:00
|
|
|
|
2018-03-06 20:01:12 -05: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 10:33:30 -05:00
|
|
|
def empty?
|
|
|
|
return true unless exists?
|
|
|
|
|
|
|
|
!has_visible_content?
|
|
|
|
end
|
2016-11-18 08:04:18 -05: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 10:04:16 -05:00
|
|
|
def commit_count_for_ref(ref)
|
2017-03-02 21:06:06 -05:00
|
|
|
return 0 unless exists?
|
2017-01-20 10:04:16 -05:00
|
|
|
|
2018-01-30 11:21:55 -05:00
|
|
|
cache.fetch(:"commit_count_#{ref}") { raw_repository.commit_count(ref) }
|
2014-06-25 05:07:06 -04:00
|
|
|
end
|
|
|
|
|
2017-02-15 21:08:30 -05:00
|
|
|
delegate :branch_names, to: :raw_repository
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :branch_names, fallback: []
|
|
|
|
|
2017-02-22 12:51:46 -05:00
|
|
|
delegate :tag_names, to: :raw_repository
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :tag_names, fallback: []
|
|
|
|
|
2017-09-29 09:08:44 -04:00
|
|
|
delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :branch_count, fallback: 0
|
|
|
|
cache_method :tag_count, fallback: 0
|
2017-09-29 09:08:44 -04:00
|
|
|
cache_method :has_visible_content?, fallback: false
|
2016-11-18 08:04:18 -05:00
|
|
|
|
|
|
|
def avatar
|
2017-09-25 06:22:07 -04:00
|
|
|
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38327
|
|
|
|
Gitlab::GitalyClient.allow_n_plus_1_calls do
|
|
|
|
if tree = file_on_head(:avatar)
|
|
|
|
tree.path
|
|
|
|
end
|
2015-03-17 05:28:50 -04:00
|
|
|
end
|
|
|
|
end
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :avatar
|
2015-03-17 05:29:17 -04:00
|
|
|
|
2017-10-11 11:48:43 -04: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: []
|
|
|
|
|
2013-11-29 06:52:10 -05:00
|
|
|
def readme
|
2018-10-12 09:42:35 -04:00
|
|
|
head_tree&.readme
|
2014-02-22 10:37:10 -05:00
|
|
|
end
|
|
|
|
|
2018-11-26 10:44:13 -05:00
|
|
|
def readme_path
|
|
|
|
readme&.path
|
|
|
|
end
|
|
|
|
cache_method :readme_path
|
|
|
|
|
2017-04-06 10:47:52 -04:00
|
|
|
def rendered_readme
|
2018-09-04 15:21:20 -04:00
|
|
|
return unless readme
|
|
|
|
|
|
|
|
context = { project: project }
|
|
|
|
|
|
|
|
MarkupHelper.markup_unsafe(readme.name, readme.data, context)
|
2017-04-06 10:47:52 -04:00
|
|
|
end
|
|
|
|
cache_method :rendered_readme
|
2015-11-12 11:00:39 -05:00
|
|
|
|
2014-02-22 10:37:10 -05:00
|
|
|
def contribution_guide
|
2016-11-18 08:04:18 -05:00
|
|
|
file_on_head(:contributing)
|
2015-03-17 05:28:50 -04:00
|
|
|
end
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :contribution_guide
|
2015-03-17 05:29:17 -04:00
|
|
|
|
|
|
|
def changelog
|
2016-11-18 08:04:18 -05:00
|
|
|
file_on_head(:changelog)
|
2016-04-11 09:49:25 -04:00
|
|
|
end
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :changelog
|
2014-02-22 10:37:10 -05:00
|
|
|
|
2016-04-11 09:49:25 -04:00
|
|
|
def license_blob
|
2016-11-18 08:04:18 -05:00
|
|
|
file_on_head(:license)
|
2016-04-11 09:49:25 -04:00
|
|
|
end
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :license_blob
|
2015-10-01 14:34:23 -04:00
|
|
|
|
2016-04-11 09:49:25 -04:00
|
|
|
def license_key
|
2016-11-18 08:04:18 -05:00
|
|
|
return unless exists?
|
2016-04-11 09:49:25 -04:00
|
|
|
|
2018-03-01 09:30:41 -05:00
|
|
|
raw_repository.license_short_name
|
2014-02-22 10:37:10 -05:00
|
|
|
end
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :license_key
|
2014-02-22 10:37:10 -05:00
|
|
|
|
2017-05-08 19:50:23 -04:00
|
|
|
def license
|
|
|
|
return unless license_key
|
2016-04-29 10:25:03 -04:00
|
|
|
|
2017-05-10 10:02:28 -04:00
|
|
|
Licensee::License.new(license_key)
|
2016-04-29 10:25:03 -04:00
|
|
|
end
|
2018-09-25 13:07:59 -04:00
|
|
|
memoize_method :license
|
2016-04-29 10:25:03 -04:00
|
|
|
|
|
|
|
def gitignore
|
2016-11-18 08:04:18 -05:00
|
|
|
file_on_head(:gitignore)
|
2016-04-29 10:25:03 -04:00
|
|
|
end
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :gitignore
|
2016-07-25 23:59:39 -04:00
|
|
|
|
2016-03-24 10:17:13 -04:00
|
|
|
def gitlab_ci_yml
|
2016-11-18 08:04:18 -05:00
|
|
|
file_on_head(:gitlab_ci)
|
2016-03-24 10:17:13 -04:00
|
|
|
end
|
2016-11-18 08:04:18 -05:00
|
|
|
cache_method :gitlab_ci_yml
|
2016-03-24 10:17:13 -04:00
|
|
|
|
2018-05-09 10:54:47 -04:00
|
|
|
def xcode_project?
|
2018-05-18 07:01:27 -04:00
|
|
|
file_on_head(:xcode_config, :tree).present?
|
2018-05-09 10:54:47 -04:00
|
|
|
end
|
|
|
|
cache_method :xcode_project?
|
|
|
|
|
2014-02-04 07:46:15 -05:00
|
|
|
def head_commit
|
2015-03-21 16:45:08 -04:00
|
|
|
@head_commit ||= commit(self.root_ref)
|
|
|
|
end
|
|
|
|
|
|
|
|
def head_tree
|
2016-11-18 08:04:18 -05:00
|
|
|
if head_commit
|
|
|
|
@head_tree ||= Tree.new(self, head_commit.sha, nil)
|
|
|
|
end
|
2014-02-04 07:46:15 -05:00
|
|
|
end
|
|
|
|
|
2016-08-29 11:23:40 -04:00
|
|
|
def tree(sha = :head, path = nil, recursive: false)
|
2014-02-04 07:46:15 -05:00
|
|
|
if sha == :head
|
2016-11-18 08:04:18 -05:00
|
|
|
return unless head_commit
|
|
|
|
|
2015-03-21 16:45:08 -04:00
|
|
|
if path.nil?
|
|
|
|
return head_tree
|
|
|
|
else
|
|
|
|
sha = head_commit.sha
|
|
|
|
end
|
2014-02-04 07:46:15 -05:00
|
|
|
end
|
|
|
|
|
2016-08-29 11:23:40 -04:00
|
|
|
Tree.new(self, sha, path, recursive: recursive)
|
2014-02-04 07:46:15 -05:00
|
|
|
end
|
2014-02-05 04:39:55 -05:00
|
|
|
|
|
|
|
def blob_at_branch(branch_name, path)
|
2014-02-18 05:27:02 -05:00
|
|
|
last_commit = commit(branch_name)
|
2014-02-05 04:39:55 -05:00
|
|
|
|
2014-02-18 05:27:02 -05:00
|
|
|
if last_commit
|
|
|
|
blob_at(last_commit.sha, path)
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
2014-02-05 04:39:55 -05:00
|
|
|
end
|
2014-02-10 06:02:26 -05:00
|
|
|
|
2018-10-01 08:29:47 -04:00
|
|
|
def list_last_commits_for_tree(sha, path, offset: 0, limit: 25)
|
|
|
|
commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit)
|
|
|
|
|
|
|
|
commits.each do |path, commit|
|
|
|
|
commits[path] = ::Commit.new(commit, @project)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-02-10 14:46:46 -05:00
|
|
|
def last_commit_for_path(sha, path)
|
2018-03-06 09:26:57 -05:00
|
|
|
commit = raw_repository.last_commit_for_path(sha, path)
|
|
|
|
::Commit.new(commit, @project) if commit
|
2014-02-10 14:46:46 -05:00
|
|
|
end
|
2014-04-09 09:09:11 -04:00
|
|
|
|
2016-12-15 00:40:15 -05:00
|
|
|
def last_commit_id_for_path(sha, path)
|
|
|
|
key = path.blank? ? "last_commit_id_for_path:#{sha}" : "last_commit_id_for_path:#{sha}:#{Digest::SHA1.hexdigest(path)}"
|
2016-12-20 08:43:59 -05:00
|
|
|
|
2016-12-14 23:29:51 -05:00
|
|
|
cache.fetch(key) do
|
2018-03-06 09:26:57 -05:00
|
|
|
last_commit_for_path(sha, path)&.id
|
2016-12-14 23:29:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-05 22:03:01 -04:00
|
|
|
def next_branch(name, opts = {})
|
2016-04-18 03:39:07 -04:00
|
|
|
branch_ids = self.branch_names.map do |n|
|
|
|
|
next 1 if n == name
|
2017-11-14 04:02:39 -05:00
|
|
|
|
2016-04-18 03:39:07 -04:00
|
|
|
result = n.match(/\A#{name}-([0-9]+)\z/)
|
2015-12-08 10:42:10 -05:00
|
|
|
result[1].to_i if result
|
|
|
|
end.compact
|
|
|
|
|
2016-04-18 03:39:07 -04:00
|
|
|
highest_branch_id = branch_ids.max || 0
|
2015-12-08 10:42:10 -05:00
|
|
|
|
2016-04-18 03:39:07 -04:00
|
|
|
return name if opts[:mild] && 0 == highest_branch_id
|
|
|
|
|
|
|
|
"#{name}-#{highest_branch_id + 1}"
|
2015-12-08 10:42:10 -05:00
|
|
|
end
|
|
|
|
|
2014-05-23 07:25:55 -04:00
|
|
|
def branches_sorted_by(value)
|
2017-03-17 15:36:46 -04:00
|
|
|
raw_repository.local_branches(sort_by: value)
|
2014-05-23 07:25:55 -04:00
|
|
|
end
|
2014-07-02 07:43:23 -04:00
|
|
|
|
2016-06-16 13:33:29 -04:00
|
|
|
def tags_sorted_by(value)
|
|
|
|
case value
|
2017-12-14 08:42:15 -05:00
|
|
|
when 'name_asc'
|
|
|
|
VersionSorter.sort(tags) { |tag| tag.name }
|
|
|
|
when 'name_desc'
|
2016-08-08 14:36:39 -04:00
|
|
|
VersionSorter.rsort(tags) { |tag| tag.name }
|
2016-06-16 13:33:29 -04: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 09:06:55 -05:00
|
|
|
# Params:
|
|
|
|
#
|
|
|
|
# order_by: name|email|commits
|
|
|
|
# sort: asc|desc default: 'asc'
|
|
|
|
def contributors(order_by: nil, sort: 'asc')
|
2016-04-22 08:07:25 -04:00
|
|
|
commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true)
|
2014-07-02 07:43:23 -04:00
|
|
|
|
2017-11-18 09:06:55 -05:00
|
|
|
commits = commits.group_by(&:author_email).map do |email, commits|
|
2014-07-02 08:09:06 -04:00
|
|
|
contributor = Gitlab::Contributor.new
|
|
|
|
contributor.email = email
|
2014-07-02 07:43:23 -04:00
|
|
|
|
2014-09-30 04:56:48 -04:00
|
|
|
commits.each do |commit|
|
2014-07-02 08:09:06 -04:00
|
|
|
if contributor.name.blank?
|
2014-09-30 04:56:48 -04:00
|
|
|
contributor.name = commit.author_name
|
2014-07-02 07:43:23 -04:00
|
|
|
end
|
|
|
|
|
2014-07-02 08:09:06 -04:00
|
|
|
contributor.commits += 1
|
2014-07-02 07:43:23 -04:00
|
|
|
end
|
|
|
|
|
2014-07-02 08:09:06 -04:00
|
|
|
contributor
|
|
|
|
end
|
2017-11-18 09:06:55 -05:00
|
|
|
Commit.order_by(collection: commits, order_by: order_by, sort: sort)
|
2014-07-02 07:43:23 -04:00
|
|
|
end
|
2014-07-28 13:54:40 -04:00
|
|
|
|
2015-11-14 13:43:48 -05:00
|
|
|
def branch_names_contains(sha)
|
2018-01-30 03:59:45 -05:00
|
|
|
raw_repository.branch_names_contains_sha(sha)
|
2015-11-14 13:43:48 -05:00
|
|
|
end
|
2015-01-17 08:12:49 -05:00
|
|
|
|
2015-11-14 13:43:48 -05:00
|
|
|
def tag_names_contains(sha)
|
2018-01-30 03:59:45 -05:00
|
|
|
raw_repository.tag_names_contains_sha(sha)
|
2015-01-17 08:12:49 -05:00
|
|
|
end
|
2015-01-28 23:08:28 -05:00
|
|
|
|
2016-04-01 15:21:08 -04:00
|
|
|
def local_branches
|
2016-07-27 11:26:29 -04:00
|
|
|
@local_branches ||= raw_repository.local_branches
|
2015-03-21 16:44:11 -04:00
|
|
|
end
|
|
|
|
|
2016-04-01 15:21:08 -04:00
|
|
|
alias_method :branches, :local_branches
|
|
|
|
|
2015-03-21 16:44:11 -04:00
|
|
|
def tags
|
|
|
|
@tags ||= raw_repository.tags
|
|
|
|
end
|
|
|
|
|
2017-02-24 15:11:10 -05:00
|
|
|
def create_dir(user, path, **options)
|
|
|
|
options[:actions] = [{ action: :create_dir, file_path: path }]
|
2015-03-21 16:44:11 -04:00
|
|
|
|
2018-01-03 20:47:25 -05:00
|
|
|
multi_action(user, **options)
|
2015-09-17 01:45:22 -04:00
|
|
|
end
|
|
|
|
|
2017-02-24 15:11:10 -05:00
|
|
|
def create_file(user, path, content, **options)
|
|
|
|
options[:actions] = [{ action: :create, file_path: path, content: content }]
|
2016-09-08 11:40:07 -04:00
|
|
|
|
2018-01-03 20:47:25 -05:00
|
|
|
multi_action(user, **options)
|
2015-09-17 01:45:22 -04:00
|
|
|
end
|
2015-08-11 08:33:31 -04:00
|
|
|
|
2017-02-24 15:11:10 -05: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 08:33:31 -04:00
|
|
|
|
2017-02-24 15:11:10 -05:00
|
|
|
options[:actions] = [{ action: action, file_path: path, previous_path: previous_path, content: content }]
|
2015-08-11 08:33:31 -04:00
|
|
|
|
2018-01-03 20:47:25 -05:00
|
|
|
multi_action(user, **options)
|
2015-08-11 08:33:31 -04:00
|
|
|
end
|
|
|
|
|
2017-02-24 15:11:10 -05:00
|
|
|
def delete_file(user, path, **options)
|
|
|
|
options[:actions] = [{ action: :delete, file_path: path }]
|
2016-07-04 06:32:57 -04:00
|
|
|
|
2018-01-03 20:47:25 -05:00
|
|
|
multi_action(user, **options)
|
2016-07-04 06:32:57 -04:00
|
|
|
end
|
|
|
|
|
2017-09-08 08:00:53 -04:00
|
|
|
def with_cache_hooks
|
|
|
|
result = yield
|
2017-08-23 11:14:21 -04:00
|
|
|
|
2017-09-08 08:00:53 -04:00
|
|
|
return unless result
|
2017-08-23 11:14:21 -04:00
|
|
|
|
2017-09-08 08:00:53 -04:00
|
|
|
after_create if result.repo_created?
|
|
|
|
after_create_branch if result.branch_created?
|
2017-08-23 11:14:21 -04:00
|
|
|
|
2017-09-08 08:00:53 -04:00
|
|
|
result.newrev
|
|
|
|
end
|
|
|
|
|
2018-01-03 20:47:25 -05:00
|
|
|
def multi_action(user, **options)
|
|
|
|
start_project = options.delete(:start_project)
|
2016-08-29 19:58:32 -04:00
|
|
|
|
2018-01-03 20:47:25 -05:00
|
|
|
if start_project
|
|
|
|
options[:start_repository] = start_project.repository.raw_repository
|
2016-08-29 19:58:32 -04:00
|
|
|
end
|
|
|
|
|
2018-01-03 20:47:25 -05:00
|
|
|
with_cache_hooks { raw.multi_action(user, **options) }
|
2015-08-11 08:33:31 -04:00
|
|
|
end
|
|
|
|
|
2017-09-08 08:00:53 -04: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 11:52:49 -04:00
|
|
|
end
|
2016-01-29 12:04:48 -05:00
|
|
|
end
|
|
|
|
|
2019-01-31 13:32:44 -05:00
|
|
|
def merge_to_ref(user, source_sha, merge_request, target_ref, message)
|
|
|
|
branch = merge_request.target_branch
|
|
|
|
|
|
|
|
raw.merge_to_ref(user, source_sha, branch, target_ref, message)
|
|
|
|
end
|
|
|
|
|
2017-09-13 10:11:10 -04:00
|
|
|
def ff_merge(user, source, target_branch, merge_request: nil)
|
2017-10-23 13:16:10 -04:00
|
|
|
their_commit_id = commit(source)&.id
|
|
|
|
raise 'Invalid merge source' if their_commit_id.nil?
|
2017-09-13 10:11:10 -04:00
|
|
|
|
2017-10-23 13:16:10 -04:00
|
|
|
merge_request&.update(in_progress_merge_commit_sha: their_commit_id)
|
2017-09-13 10:11:10 -04:00
|
|
|
|
2017-10-23 13:16:10 -04:00
|
|
|
with_cache_hooks { raw.ff_merge(user, their_commit_id, target_branch) }
|
2017-09-13 10:11:10 -04:00
|
|
|
end
|
|
|
|
|
2016-12-07 06:50:08 -05:00
|
|
|
def revert(
|
2017-09-19 13:09:10 -04:00
|
|
|
user, commit, branch_name, message,
|
2017-01-06 10:29:13 -05:00
|
|
|
start_branch_name: nil, start_project: project)
|
2016-02-03 18:28:40 -05:00
|
|
|
|
2017-09-19 13:09:10 -04: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,
|
|
|
|
start_repository: start_project.repository.raw_repository
|
|
|
|
)
|
2016-02-02 20:51:37 -05:00
|
|
|
end
|
2015-08-11 08:33:31 -04:00
|
|
|
end
|
|
|
|
|
2016-12-07 06:50:08 -05:00
|
|
|
def cherry_pick(
|
2017-09-19 13:09:10 -04:00
|
|
|
user, commit, branch_name, message,
|
2017-01-06 10:29:13 -05:00
|
|
|
start_branch_name: nil, start_project: project)
|
2016-04-18 03:39:07 -04:00
|
|
|
|
2017-09-19 13:09:10 -04: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,
|
|
|
|
start_repository: start_project.repository.raw_repository
|
|
|
|
)
|
2016-04-18 03:39:07 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-11-17 13:13:45 -05:00
|
|
|
def merged_to_root_ref?(branch_or_name)
|
2017-10-27 11:55:08 -04:00
|
|
|
branch = Gitlab::Git::Branch.find(self, branch_or_name)
|
|
|
|
|
|
|
|
if branch
|
2018-01-09 12:30:04 -05:00
|
|
|
same_head = branch.target == root_ref_sha
|
|
|
|
merged = ancestor?(branch.target, root_ref_sha)
|
2017-10-27 11:55:08 -04:00
|
|
|
!same_head && merged
|
2015-08-09 14:31:50 -04:00
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-09 12:30:04 -05:00
|
|
|
def root_ref_sha
|
|
|
|
@root_ref_sha ||= commit(root_ref).sha
|
|
|
|
end
|
|
|
|
|
2018-06-19 11:55:02 -04:00
|
|
|
delegate :merged_branch_names, to: :raw_repository
|
2017-10-27 11:55:08 -04:00
|
|
|
|
2018-10-11 10:27:04 -04: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 01:45:06 -04:00
|
|
|
end
|
|
|
|
|
2017-08-24 12:35:06 -04:00
|
|
|
def ancestor?(ancestor_id, descendant_id)
|
2017-06-07 20:47:10 -04:00
|
|
|
return false if ancestor_id.nil? || descendant_id.nil?
|
2017-06-28 03:53:12 -04:00
|
|
|
|
2018-01-30 11:21:55 -05:00
|
|
|
raw_repository.ancestor?(ancestor_id, descendant_id)
|
2015-11-11 10:27:55 -05:00
|
|
|
end
|
|
|
|
|
2018-03-02 08:50:17 -05:00
|
|
|
def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil, prune: true)
|
2017-11-15 10:46:08 -05:00
|
|
|
unless remote_name
|
|
|
|
remote_name = "tmp-#{SecureRandom.hex}"
|
|
|
|
tmp_remote_name = true
|
|
|
|
end
|
|
|
|
|
2017-12-06 17:08:29 -05:00
|
|
|
add_remote(remote_name, url, mirror_refmap: refmap)
|
2018-03-02 08:50:17 -05:00
|
|
|
fetch_remote(remote_name, forced: forced, prune: prune)
|
2017-11-15 10:46:08 -05:00
|
|
|
ensure
|
2018-05-03 10:19:21 -04:00
|
|
|
async_remove_remote(remote_name) if tmp_remote_name
|
2017-11-15 10:46:08 -05:00
|
|
|
end
|
|
|
|
|
2018-05-03 08:55:14 -04:00
|
|
|
def async_remove_remote(remote_name)
|
|
|
|
return unless remote_name
|
|
|
|
|
|
|
|
job_id = RepositoryRemoveRemoteWorker.perform_async(project.id, remote_name)
|
|
|
|
|
|
|
|
if job_id
|
|
|
|
Rails.logger.info("Remove remote job scheduled for #{project.id} with remote name: #{remote_name} job ID #{job_id}.")
|
|
|
|
else
|
|
|
|
Rails.logger.info("Remove remote job failed to create for #{project.id} with remote name #{remote_name}.")
|
|
|
|
end
|
|
|
|
|
|
|
|
job_id
|
|
|
|
end
|
|
|
|
|
2017-11-01 10:31:35 -04: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 11:14:21 -04:00
|
|
|
end
|
2017-08-11 08:12:17 -04:00
|
|
|
|
2017-08-23 11:14:21 -04: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 08:33:31 -04:00
|
|
|
end
|
2016-07-18 10:06:49 -04:00
|
|
|
|
2016-10-03 09:39:12 -04:00
|
|
|
def create_ref(ref, ref_path)
|
2017-12-20 13:29:52 -05:00
|
|
|
raw_repository.write_ref(ref_path, ref)
|
2015-08-14 10:23:40 -04:00
|
|
|
end
|
|
|
|
|
2016-01-07 06:56:18 -05:00
|
|
|
def ls_files(ref)
|
|
|
|
actual_ref = ref || root_ref
|
|
|
|
raw_repository.ls_files(actual_ref)
|
|
|
|
end
|
|
|
|
|
2018-01-22 11:08:00 -05:00
|
|
|
def search_files_by_content(query, ref)
|
|
|
|
return [] if empty? || query.blank?
|
|
|
|
|
|
|
|
raw_repository.search_files_by_content(query, ref)
|
|
|
|
end
|
|
|
|
|
|
|
|
def search_files_by_name(query, ref)
|
|
|
|
return [] if empty?
|
|
|
|
|
|
|
|
raw_repository.search_files_by_name(query, ref)
|
|
|
|
end
|
|
|
|
|
2016-03-14 19:33:00 -04: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 07:01:27 -04: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 11:53:05 -04:00
|
|
|
end
|
2018-05-18 07:01:27 -04:00
|
|
|
|
|
|
|
objects.find do |object|
|
|
|
|
Gitlab::FileDetector.type_of(object.path) == type
|
2016-03-17 11:53:05 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-06 19:06:46 -05:00
|
|
|
def route_map_for(sha)
|
|
|
|
blob_data_at(sha, '.gitlab/route-map.yml')
|
|
|
|
end
|
|
|
|
|
2017-07-05 08:04:53 -04:00
|
|
|
def gitlab_ci_yml_for(sha, path = '.gitlab-ci.yml')
|
|
|
|
blob_data_at(sha, path)
|
2017-02-06 19:06:46 -05:00
|
|
|
end
|
|
|
|
|
2018-06-06 12:42:18 -04:00
|
|
|
def lfsconfig_for(sha)
|
|
|
|
blob_data_at(sha, '.lfsconfig')
|
|
|
|
end
|
|
|
|
|
2017-11-10 09:45:23 -05: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
|
|
|
|
|
2017-12-20 04:01:21 -05:00
|
|
|
def rebase(user, merge_request)
|
|
|
|
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,
|
|
|
|
remote_branch: merge_request.target_branch)
|
|
|
|
end
|
|
|
|
|
2019-02-06 07:33:11 -05:00
|
|
|
def squash(user, merge_request, message)
|
2018-05-29 05:51:43 -04:00
|
|
|
raw.squash(user, merge_request.id, branch: merge_request.target_branch,
|
|
|
|
start_sha: merge_request.diff_start_sha,
|
|
|
|
end_sha: merge_request.diff_head_sha,
|
|
|
|
author: merge_request.author,
|
2019-02-06 07:33:11 -05:00
|
|
|
message: message)
|
2018-05-29 05:51:43 -04:00
|
|
|
end
|
|
|
|
|
2018-07-31 12:35:02 -04: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 15:37:44 -04:00
|
|
|
def blob_data_at(sha, path)
|
|
|
|
blob = blob_at(sha, path)
|
|
|
|
return unless blob
|
|
|
|
|
|
|
|
blob.load_all_data!
|
|
|
|
blob.data
|
|
|
|
end
|
|
|
|
|
2015-07-01 10:23:58 -04:00
|
|
|
private
|
|
|
|
|
2017-10-13 04:37:31 -04:00
|
|
|
# TODO Generice finder, later split this on finders by Ref or Oid
|
|
|
|
# gitlab-org/gitlab-ce#39239
|
|
|
|
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
|
|
|
|
|
|
|
|
::Commit.new(commit, @project) if commit
|
|
|
|
end
|
|
|
|
|
2015-01-28 23:08:28 -05:00
|
|
|
def cache
|
2019-01-08 14:37:54 -05:00
|
|
|
@cache ||= Gitlab::RepositoryCache.new(self)
|
2016-04-29 10:25:03 -04:00
|
|
|
end
|
2016-06-16 13:33:29 -04:00
|
|
|
|
2018-09-25 13:12:51 -04:00
|
|
|
def request_store_cache
|
2019-01-08 14:37:54 -05:00
|
|
|
@request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
|
2018-09-25 13:12:51 -04:00
|
|
|
end
|
|
|
|
|
2016-06-16 13:33:29 -04:00
|
|
|
def tags_sorted_by_committed_date
|
2017-01-26 02:13:09 -05: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
|
|
|
|
Time.now
|
|
|
|
end
|
|
|
|
end
|
2016-06-16 13:33:29 -04:00
|
|
|
end
|
2016-07-03 20:30:55 -04:00
|
|
|
|
|
|
|
def keep_around_ref_name(sha)
|
2017-08-25 11:00:06 -04:00
|
|
|
"refs/#{REF_KEEP_AROUND}/#{sha}"
|
2016-07-03 20:30:55 -04:00
|
|
|
end
|
2016-08-16 10:18:48 -04:00
|
|
|
|
|
|
|
def repository_event(event, tags = {})
|
2018-06-19 13:03:25 -04:00
|
|
|
Gitlab::Metrics.add_event(event, tags)
|
2016-08-16 10:18:48 -04:00
|
|
|
end
|
2017-04-03 13:46:00 -04:00
|
|
|
|
2017-04-05 07:33:50 -04:00
|
|
|
def initialize_raw_repository
|
2018-12-23 02:34:35 -05:00
|
|
|
Gitlab::Git::Repository.new(project.repository_storage,
|
|
|
|
disk_path + '.git',
|
2019-03-18 12:51:11 -04:00
|
|
|
repo_type.identifier_for_subject(project),
|
2018-12-23 02:34:35 -05:00
|
|
|
project.full_path)
|
2017-02-15 21:08:30 -05:00
|
|
|
end
|
2013-03-31 16:45:38 -04:00
|
|
|
end
|