project.rb 42.0 KB
Newer Older
1 2
require 'carrierwave/orm/activerecord'

G
gitlabhq 已提交
3
class Project < ActiveRecord::Base
4
  include Gitlab::ConfigHelper
5
  include Gitlab::ShellAdapter
6
  include Gitlab::VisibilityLevel
7
  include Gitlab::CurrentSettings
R
Rémy Coutable 已提交
8
  include AccessRequestable
9
  include CacheMarkdownField
10 11
  include Referable
  include Sortable
12
  include AfterCommitQueue
13
  include CaseSensitivity
14
  include TokenAuthenticatable
F
Felipe Artur 已提交
15
  include ProjectFeaturesCompatibility
R
Robert Speicher 已提交
16

17
  extend Gitlab::ConfigHelper
18

19 20
  class BoardLimitExceeded < StandardError; end

21
  NUMBER_OF_PERMITTED_BOARDS = 1
J
Jared Szechy 已提交
22 23
  UNKNOWN_IMPORT_URL = 'http://unknown.git'

24 25
  cache_markdown_field :description, pipeline: :description

26 27
  delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
           :merge_requests_enabled?, :issues_enabled?, to: :project_feature,
28
                                                       allow_nil: true
F
Felipe Artur 已提交
29

30
  default_value_for :archived, false
31
  default_value_for :visibility_level, gitlab_config_features.visibility_level
32
  default_value_for :container_registry_enabled, gitlab_config_features.container_registry
33
  default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
K
Kamil Trzcinski 已提交
34
  default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
F
Felipe Artur 已提交
35 36 37 38 39
  default_value_for :issues_enabled, gitlab_config_features.issues
  default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
  default_value_for :builds_enabled, gitlab_config_features.builds
  default_value_for :wiki_enabled, gitlab_config_features.wiki
  default_value_for :snippets_enabled, gitlab_config_features.snippets
40
  default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
41

42
  after_create :ensure_dir_exist
43
  after_create :create_project_feature, unless: :project_feature
44 45
  after_save :ensure_dir_exist, if: :namespace_id_changed?

46 47
  # set last_activity_at to the same as created_at
  after_create :set_last_activity_at
48
  def set_last_activity_at
49
    update_column(:last_activity_at, self.created_at)
50 51
  end

52
  # update visibility_level of forks
53 54 55 56 57 58 59 60 61 62 63 64
  after_update :update_forks_visibility_level
  def update_forks_visibility_level
    return unless visibility_level < visibility_level_was

    forks.each do |forked_project|
      if forked_project.visibility_level > visibility_level
        forked_project.visibility_level = visibility_level
        forked_project.save!
      end
    end
  end

65
  ActsAsTaggableOn.strict_case_match = true
66
  acts_as_taggable_on :tags
D
Dmitriy Zaporozhets 已提交
67

68
  attr_accessor :new_default_branch
69
  attr_accessor :old_path_with_namespace
70

71 72
  alias_attribute :title, :name

73
  # Relations
74
  belongs_to :creator, class_name: 'User'
75
  belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
76
  belongs_to :namespace
77

78
  has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event'
79
  has_many :boards, before_add: :validate_board_limit, dependent: :destroy
80
  has_many :chat_services
F
Felipe Artur 已提交
81

82
  # Project services
D
Dmitriy Zaporozhets 已提交
83
  has_one :campfire_service, dependent: :destroy
K
Kirilll Zaitsev 已提交
84
  has_one :drone_ci_service, dependent: :destroy
85
  has_one :emails_on_push_service, dependent: :destroy
86
  has_one :builds_email_service, dependent: :destroy
87
  has_one :pipelines_email_service, dependent: :destroy
A
Aorimn 已提交
88
  has_one :irker_service, dependent: :destroy
89
  has_one :pivotaltracker_service, dependent: :destroy
R
Ronald van Eede 已提交
90
  has_one :hipchat_service, dependent: :destroy
91
  has_one :flowdock_service, dependent: :destroy
C
Carlos Paramio 已提交
92
  has_one :assembla_service, dependent: :destroy
J
Jeremy 已提交
93
  has_one :asana_service, dependent: :destroy
94
  has_one :gemnasium_service, dependent: :destroy
95
  has_one :mattermost_command_service, dependent: :destroy
96
  has_one :slack_service, dependent: :destroy
97
  has_one :buildkite_service, dependent: :destroy
D
Drew Blessing 已提交
98
  has_one :bamboo_service, dependent: :destroy
99
  has_one :teamcity_service, dependent: :destroy
100
  has_one :pushover_service, dependent: :destroy
101 102
  has_one :jira_service, dependent: :destroy
  has_one :redmine_service, dependent: :destroy
M
Marin Jankovski 已提交
103
  has_one :custom_issue_tracker_service, dependent: :destroy
104
  has_one :bugzilla_service, dependent: :destroy
105
  has_one :gitlab_issue_tracker_service, dependent: :destroy, inverse_of: :project
D
Dmitriy Zaporozhets 已提交
106
  has_one :external_wiki_service, dependent: :destroy
107

108 109 110 111 112
  has_one  :forked_project_link,  dependent: :destroy, foreign_key: "forked_to_project_id"
  has_one  :forked_from_project,  through:   :forked_project_link

  has_many :forked_project_links, foreign_key: "forked_from_project_id"
  has_many :forks,                through:     :forked_project_links, source: :forked_to_project
113

114
  # Merge Requests for target project should be removed with it
115
  has_many :merge_requests,     dependent: :destroy, foreign_key: 'target_project_id'
116
  # Merge requests from source project should be kept when source project was removed
117
  has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
118
  has_many :issues,             dependent: :destroy
D
Douglas Barbosa Alexandre 已提交
119
  has_many :labels,             dependent: :destroy, class_name: 'ProjectLabel'
120 121
  has_many :services,           dependent: :destroy
  has_many :events,             dependent: :destroy
122 123
  has_many :milestones,         dependent: :destroy
  has_many :notes,              dependent: :destroy
124 125
  has_many :snippets,           dependent: :destroy, class_name: 'ProjectSnippet'
  has_many :hooks,              dependent: :destroy, class_name: 'ProjectHook'
126
  has_many :protected_branches, dependent: :destroy
127

128
  has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
R
Rémy Coutable 已提交
129
  alias_method :members, :project_members
130 131 132 133
  has_many :users, through: :project_members

  has_many :requesters, -> { where.not(requested_at: nil) }, dependent: :destroy, as: :source, class_name: 'ProjectMember'

134 135
  has_many :deploy_keys_projects, dependent: :destroy
  has_many :deploy_keys, through: :deploy_keys_projects
C
Ciro Santilli 已提交
136 137
  has_many :users_star_projects, dependent: :destroy
  has_many :starrers, through: :users_star_projects, source: :user
138
  has_many :releases, dependent: :destroy
M
Marin Jankovski 已提交
139 140
  has_many :lfs_objects_projects, dependent: :destroy
  has_many :lfs_objects, through: :lfs_objects_projects
141 142
  has_many :project_group_links, dependent: :destroy
  has_many :invited_groups, through: :project_group_links, source: :group
143
  has_many :todos, dependent: :destroy
144
  has_many :notification_settings, dependent: :destroy, as: :source
145

146
  has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
F
Felipe Artur 已提交
147
  has_one :project_feature, dependent: :destroy
K
Kamil Trzcinski 已提交
148

149
  has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id
150
  has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
151 152 153 154 155
  has_many :builds, class_name: 'Ci::Build', foreign_key: :gl_project_id # the builds are created from the commit_statuses
  has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject', foreign_key: :gl_project_id
  has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
  has_many :variables, dependent: :destroy, class_name: 'Ci::Variable', foreign_key: :gl_project_id
  has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :gl_project_id
156 157
  has_many :environments, dependent: :destroy
  has_many :deployments, dependent: :destroy
K
Kamil Trzcinski 已提交
158

159
  accepts_nested_attributes_for :variables, allow_destroy: true
F
Felipe Artur 已提交
160
  accepts_nested_attributes_for :project_feature
161

162
  delegate :name, to: :owner, allow_nil: true, prefix: true
163
  delegate :members, to: :team, prefix: true
164
  delegate :add_user, to: :team
165

A
Andrey Kumanyaev 已提交
166
  # Validations
167
  validates :creator, presence: true, on: :create
168
  validates :description, length: { maximum: 2000 }, allow_blank: true
169 170 171 172
  validates :name,
    presence: true,
    length: { within: 0..255 },
    format: { with: Gitlab::Regex.project_name_regex,
D
Douwe Maan 已提交
173
              message: Gitlab::Regex.project_name_regex_message }
174 175 176
  validates :path,
    presence: true,
    length: { within: 0..255 },
D
Douwe Maan 已提交
177 178
    format: { with: Gitlab::Regex.project_path_regex,
              message: Gitlab::Regex.project_path_regex_message }
179
  validates :namespace, presence: true
180 181
  validates_uniqueness_of :name, scope: :namespace_id
  validates_uniqueness_of :path, scope: :namespace_id
J
James Lopez 已提交
182
  validates :import_url, addressable_url: true, if: :external_import?
183
  validates :star_count, numericality: { greater_than_or_equal_to: 0 }
184
  validate :check_limit, on: :create
185
  validate :avatar_type,
186
    if: ->(project) { project.avatar.present? && project.avatar_changed? }
187
  validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
D
Douwe Maan 已提交
188
  validate :visibility_level_allowed_by_group
D
Douwe Maan 已提交
189
  validate :visibility_level_allowed_as_fork
190
  validate :check_wiki_path_conflict
191 192 193
  validates :repository_storage,
    presence: true,
    inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
194

195
  add_authentication_token_field :runners_token
196
  before_save :ensure_runners_token
K
Kamil Trzcinski 已提交
197

D
Douwe Maan 已提交
198
  mount_uploader :avatar, AvatarUploader
A
Andrey Kumanyaev 已提交
199

200
  # Scopes
201 202
  default_scope { where(pending_delete: false) }

203
  scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
204 205
  scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }

206
  scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
D
Dmitriy Zaporozhets 已提交
207
  scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
208
  scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
R
Rémy Coutable 已提交
209
  scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
210
  scope :non_archived, -> { where(archived: false) }
R
Rubén Dávila 已提交
211
  scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
R
Rémy Coutable 已提交
212 213
  scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
  scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }

  # "enabled" here means "not disabled". It includes private features!
  scope :with_feature_enabled, ->(feature) {
    access_level_attribute = ProjectFeature.access_level_attribute(feature)
    with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] })
  }

  # Picks a feature where the level is exactly that given.
  scope :with_feature_access_level, ->(feature, level) {
    access_level_attribute = ProjectFeature.access_level_attribute(feature)
    with_project_feature.where(project_features: { access_level_attribute => level })
  }

  scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
  scope :with_issues_enabled, -> { with_feature_enabled(:issues) }

  # project features may be "disabled", "internal" or "enabled". If "internal",
  # they are only available to team members. This scope returns projects where
  # the feature is either enabled, or internal with permission for the user.
  def self.with_feature_available_for_user(feature, user)
    return with_feature_enabled(feature) if user.try(:admin?)

    unconditional = with_feature_access_level(feature, [nil, ProjectFeature::ENABLED])
    return unconditional if user.nil?

    conditional = with_feature_access_level(feature, ProjectFeature::PRIVATE)
    authorized = user.authorized_projects.merge(conditional.reorder(nil))

    union = Gitlab::SQL::Union.new([unconditional.select(:id), authorized.select(:id)])
    where(arel_table[:id].in(Arel::Nodes::SqlLiteral.new(union.to_sql)))
  end
F
Felipe Artur 已提交
246

R
Rémy Coutable 已提交
247 248
  scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
  scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
249

250 251
  scope :excluding_project, ->(project) { where.not(id: project) }

252 253
  state_machine :import_status, initial: :none do
    event :import_start do
254
      transition [:none, :finished] => :started
255 256 257
    end

    event :import_finish do
258
      transition started: :finished
259 260 261
    end

    event :import_fail do
262
      transition started: :failed
263 264 265
    end

    event :import_retry do
266
      transition failed: :started
267 268 269 270
    end

    state :started
    state :finished
271 272
    state :failed

273
    after_transition any => :finished, do: :reset_cache_and_import_attrs
274 275
  end

A
Andrey Kumanyaev 已提交
276
  class << self
277 278 279 280 281 282 283
    # Searches for a list of projects based on the query given in `query`.
    #
    # On PostgreSQL this method uses "ILIKE" to perform a case-insensitive
    # search. On MySQL a regular "LIKE" is used as it's already
    # case-insensitive.
    #
    # query - The search query as a String.
284
    def search(query)
285
      ptable  = arel_table
286 287 288 289 290 291 292 293 294
      ntable  = Namespace.arel_table
      pattern = "%#{query}%"

      projects = select(:id).where(
        ptable[:path].matches(pattern).
          or(ptable[:name].matches(pattern)).
          or(ptable[:description].matches(pattern))
      )

295 296 297 298 299 300
      # We explicitly remove any eager loading clauses as they're:
      #
      # 1. Not needed by this query
      # 2. Combined with .joins(:namespace) lead to all columns from the
      #    projects & namespaces tables being selected, leading to a SQL error
      #    due to the columns of all UNION'd queries no longer being the same.
301
      namespaces = select(:id).
302
        except(:includes).
303 304 305 306 307 308
        joins(:namespace).
        where(ntable[:name].matches(pattern))

      union = Gitlab::SQL::Union.new([projects, namespaces])

      where("projects.id IN (#{union.to_sql})")
A
Andrey Kumanyaev 已提交
309
    end
310

311
    def search_by_visibility(level)
J
Josh Frye 已提交
312
      where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase))
313 314
    end

315
    def search_by_title(query)
316 317 318 319
      pattern = "%#{query}%"
      table   = Project.arel_table

      non_archived.where(table[:name].matches(pattern))
320 321
    end

Y
Yorick Peterse 已提交
322 323 324 325 326 327
    # Finds a single project for the given path.
    #
    # path - The full project path (including namespace path).
    #
    # Returns a Project, or nil if no project could be found.
    def find_with_namespace(path)
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
      namespace_path, project_path = path.split('/', 2)

      return unless namespace_path && project_path

      namespace_path = connection.quote(namespace_path)
      project_path = connection.quote(project_path)

      # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
      # any literal matches come first, for this we have to use "BINARY".
      # Without this there's still no guarantee in what order MySQL will return
      # rows.
      binary = Gitlab::Database.mysql? ? 'BINARY' : ''

      order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \
        "AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)"

      where_paths_in([path]).reorder(order_sql).take
Y
Yorick Peterse 已提交
345
    end
346

Y
Yorick Peterse 已提交
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
    # Builds a relation to find multiple projects by their full paths.
    #
    # Each path must be in the following format:
    #
    #     namespace_path/project_path
    #
    # For example:
    #
    #     gitlab-org/gitlab-ce
    #
    # Usage:
    #
    #     Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee})
    #
    # This would return the projects with the full paths matching the values
    # given.
    #
    # paths - An Array of full paths (namespace path + project path) for which
    #         to find the projects.
    #
    # Returns an ActiveRecord::Relation.
    def where_paths_in(paths)
      wheres = []
      cast_lower = Gitlab::Database.postgresql?
371

Y
Yorick Peterse 已提交
372 373
      paths.each do |path|
        namespace_path, project_path = path.split('/', 2)
374

Y
Yorick Peterse 已提交
375 376 377 378
        next unless namespace_path && project_path

        namespace_path = connection.quote(namespace_path)
        project_path = connection.quote(project_path)
379

Y
Yorick Peterse 已提交
380 381
        where = "(namespaces.path = #{namespace_path}
          AND projects.path = #{project_path})"
382

Y
Yorick Peterse 已提交
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
        if cast_lower
          where = "(
            #{where}
            OR (
              LOWER(namespaces.path) = LOWER(#{namespace_path})
              AND LOWER(projects.path) = LOWER(#{project_path})
            )
          )"
        end

        wheres << where
      end

      if wheres.empty?
        none
      else
        joins(:namespace).where(wheres.join(' OR '))
      end
401
    end
402

403 404 405
    def visibility_levels
      Gitlab::VisibilityLevel.options
    end
406 407

    def sort(method)
408 409 410 411
      if method == 'repository_size_desc'
        reorder(repository_size: :desc, id: :desc)
      else
        order_by(method)
412 413
      end
    end
414 415 416 417 418

    def reference_pattern
      name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
      %r{(?<project>#{name_pattern}/#{name_pattern})}
    end
Y
Yorick Peterse 已提交
419

Y
Yorick Peterse 已提交
420 421 422
    def trending
      joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id').
        reorder('trending_projects.id ASC')
Y
Yorick Peterse 已提交
423
    end
424 425 426 427 428 429

    def cached_count
      Rails.cache.fetch('total_project_count', expires_in: 5.minutes) do
        Project.count
      end
    end
430 431

    def group_ids
432
      joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
433
    end
434 435
  end

436
  def lfs_enabled?
437
    return namespace.lfs_enabled? if self[:lfs_enabled].nil?
P
Patricio Cano 已提交
438

439
    self[:lfs_enabled] && Gitlab.config.lfs.enabled
440 441
  end

442 443
  def repository_storage_path
    Gitlab.config.repositories.storages[repository_storage]
444 445
  end

D
Dmitriy Zaporozhets 已提交
446
  def team
447
    @team ||= ProjectTeam.new(self)
D
Dmitriy Zaporozhets 已提交
448 449 450
  end

  def repository
451
    @repository ||= Repository.new(path_with_namespace, self)
452 453
  end

454 455 456 457
  def container_registry_path_with_namespace
    path_with_namespace.downcase
  end

458
  def container_registry_repository
K
Kamil Trzcinski 已提交
459 460
    return unless Gitlab.config.registry.enabled

461
    @container_registry_repository ||= begin
462
      token = Auth::ContainerRegistryAuthenticationService.full_access_token(container_registry_path_with_namespace)
463
      url = Gitlab.config.registry.api_url
464 465
      host_port = Gitlab.config.registry.host_port
      registry = ContainerRegistry::Registry.new(url, token: token, path: host_port)
466
      registry.repository(container_registry_path_with_namespace)
467
    end
468 469
  end

470
  def container_registry_repository_url
K
Kamil Trzcinski 已提交
471
    if Gitlab.config.registry.enabled
472
      "#{Gitlab.config.registry.host_port}/#{container_registry_path_with_namespace}"
473
    end
474 475
  end

476
  def has_container_registry_tags?
K
Kamil Trzcinski 已提交
477 478 479
    return unless container_registry_repository

    container_registry_repository.tags.any?
480 481
  end

482 483
  def commit(ref = 'HEAD')
    repository.commit(ref)
D
Dmitriy Zaporozhets 已提交
484 485
  end

486
  # ref can't be HEAD, can only be branch/tag name or SHA
487
  def latest_successful_builds_for(ref = default_branch)
488
    latest_pipeline = pipelines.latest_successful_for(ref)
489 490 491 492 493 494

    if latest_pipeline
      latest_pipeline.builds.latest.with_artifacts
    else
      builds.none
    end
495 496
  end

497
  def merge_base_commit(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
498 499
    sha = repository.merge_base(first_commit_id, second_commit_id)
    repository.commit(sha) if sha
500 501
  end

502
  def saved?
503
    id && persisted?
504 505
  end

506
  def add_import_job
507
    if forked?
508 509 510
      job_id = RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
                                                  forked_from_project.path_with_namespace,
                                                  self.namespace.path)
511
    else
512 513 514 515 516 517 518
      job_id = RepositoryImportWorker.perform_async(self.id)
    end

    if job_id
      Rails.logger.info "Import job started for #{path_with_namespace} with job ID #{job_id}"
    else
      Rails.logger.error "Import job failed to start for #{path_with_namespace}"
519
    end
520 521
  end

522
  def reset_cache_and_import_attrs
523 524
    ProjectCacheWorker.perform_async(self.id)

525
    self.import_data.destroy if self.import_data
526 527
  end

J
James Lopez 已提交
528
  def import_url=(value)
J
James Lopez 已提交
529 530
    return super(value) unless Gitlab::UrlSanitizer.valid?(value)

531
    import_url = Gitlab::UrlSanitizer.new(value)
J
James Lopez 已提交
532
    super(import_url.sanitized_url)
533
    create_or_update_import_data(credentials: import_url.credentials)
J
James Lopez 已提交
534 535 536
  end

  def import_url
J
James Lopez 已提交
537
    if import_data && super.present?
538
      import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials)
J
James Lopez 已提交
539 540 541
      import_url.full_url
    else
      super
J
James Lopez 已提交
542 543
    end
  end
J
James Lopez 已提交
544

J
James Lopez 已提交
545 546 547 548
  def valid_import_url?
    valid? || errors.messages[:import_url].nil?
  end

549
  def create_or_update_import_data(data: nil, credentials: nil)
550
    return unless import_url.present? && valid_import_url?
551

J
James Lopez 已提交
552
    project_import_data = import_data || build_import_data
J
James Lopez 已提交
553 554 555 556
    if data
      project_import_data.data ||= {}
      project_import_data.data = project_import_data.data.merge(data)
    end
557 558 559 560
    if credentials
      project_import_data.credentials ||= {}
      project_import_data.credentials = project_import_data.credentials.merge(credentials)
    end
561 562

    project_import_data.save
J
James Lopez 已提交
563
  end
J
James Lopez 已提交
564

565
  def import?
566
    external_import? || forked? || gitlab_project_import?
567 568
  end

569 570 571 572
  def no_import?
    import_status == 'none'
  end

573
  def external_import?
574 575 576
    import_url.present?
  end

577
  def imported?
578 579 580 581 582 583 584 585 586 587 588 589 590
    import_finished?
  end

  def import_in_progress?
    import? && import_status == 'started'
  end

  def import_failed?
    import_status == 'failed'
  end

  def import_finished?
    import_status == 'finished'
591 592
  end

D
Douwe Maan 已提交
593
  def safe_import_url
594
    Gitlab::UrlSanitizer.new(import_url).masked_url
D
Douwe Maan 已提交
595 596
  end

597 598 599 600
  def gitlab_project_import?
    import_type == 'gitlab_project'
  end

601
  def check_limit
602
    unless creator.can_create_project? or namespace.kind == 'group'
603 604 605
      projects_limit = creator.projects_limit

      if projects_limit == 0
P
Phil Hughes 已提交
606
        self.errors.add(:limit_reached, "Personal project creation is not allowed. Please contact your administrator with questions")
607
      else
P
Phil Hughes 已提交
608
        self.errors.add(:limit_reached, "Your project limit is #{projects_limit} projects! Please contact your administrator to increase it")
609
      end
610 611
    end
  rescue
D
Douwe Maan 已提交
612
    self.errors.add(:base, "Can't check your ability to create project")
G
gitlabhq 已提交
613 614
  end

D
Douwe Maan 已提交
615 616 617 618 619 620 621 622 623 624 625 626 627
  def visibility_level_allowed_by_group
    return if visibility_level_allowed_by_group?

    level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
    group_level_name = Gitlab::VisibilityLevel.level_name(self.group.visibility_level).downcase
    self.errors.add(:visibility_level, "#{level_name} is not allowed in a #{group_level_name} group.")
  end

  def visibility_level_allowed_as_fork
    return if visibility_level_allowed_as_fork?

    level_name = Gitlab::VisibilityLevel.level_name(self.visibility_level).downcase
    self.errors.add(:visibility_level, "#{level_name} is not allowed since the fork source project has lower visibility.")
628 629
  end

630 631 632 633 634 635 636 637 638 639
  def check_wiki_path_conflict
    return if path.blank?

    path_to_check = path.ends_with?('.wiki') ? path.chomp('.wiki') : "#{path}.wiki"

    if Project.where(namespace_id: namespace_id, path: path_to_check).exists?
      errors.add(:name, 'has already been taken')
    end
  end

640
  def to_param
641 642 643 644 645
    if persisted? && errors.include?(:path)
      path_was
    else
      path
    end
646 647
  end

648 649 650 651
  def to_reference(_from_project = nil)
    path_with_namespace
  end

652
  def web_url
653
    Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
654 655
  end

656
  def web_url_without_protocol
657
    web_url.split('://')[1]
658 659
  end

660
  def new_issue_address(author)
661
    return unless Gitlab::IncomingEmail.supports_issue_creation? && author
662

663 664 665 666
    author.ensure_incoming_email_token!

    Gitlab::IncomingEmail.reply_address(
      "#{path_with_namespace}+#{author.incoming_email_token}")
667 668
  end

669
  def build_commit_note(commit)
670
    notes.new(commit_id: commit.id, noteable_type: 'Commit')
G
gitlabhq 已提交
671
  end
N
Nihad Abbasov 已提交
672

N
Nihad Abbasov 已提交
673
  def last_activity
674
    last_event
G
gitlabhq 已提交
675 676 677
  end

  def last_activity_date
A
Andrey Kumanyaev 已提交
678
    last_activity_at || updated_at
D
Dmitriy Zaporozhets 已提交
679
  end
680

D
Dmitriy Zaporozhets 已提交
681 682 683
  def project_id
    self.id
  end
R
randx 已提交
684

R
Robert Speicher 已提交
685
  def get_issue(issue_id)
686
    if default_issues_tracker?
R
Robert Speicher 已提交
687
      issues.find_by(iid: issue_id)
688
    else
R
Robert Speicher 已提交
689
      ExternalIssue.new(issue_id, self)
690 691 692
    end
  end

R
Robert Speicher 已提交
693
  def issue_exists?(issue_id)
694
    get_issue(issue_id)
R
Robert Speicher 已提交
695 696
  end

697
  def default_issue_tracker
698
    gitlab_issue_tracker_service || create_gitlab_issue_tracker_service
699 700 701 702 703 704 705 706 707 708
  end

  def issues_tracker
    if external_issue_tracker
      external_issue_tracker
    else
      default_issue_tracker
    end
  end

709 710 711 712
  def issue_reference_pattern
    issues_tracker.reference_pattern
  end

713
  def default_issues_tracker?
714
    !external_issue_tracker
715 716 717
  end

  def external_issue_tracker
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
    if has_external_issue_tracker.nil? # To populate existing projects
      cache_has_external_issue_tracker
    end

    if has_external_issue_tracker?
      return @external_issue_tracker if defined?(@external_issue_tracker)

      @external_issue_tracker = services.external_issue_trackers.first
    else
      nil
    end
  end

  def cache_has_external_issue_tracker
    update_column(:has_external_issue_tracker, services.external_issue_trackers.any?)
733 734
  end

735 736 737 738
  def has_wiki?
    wiki_enabled? || has_external_wiki?
  end

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
  def external_wiki
    if has_external_wiki.nil?
      cache_has_external_wiki # Populate
    end

    if has_external_wiki
      @external_wiki ||= services.external_wikis.first
    else
      nil
    end
  end

  def cache_has_external_wiki
    update_column(:has_external_wiki, services.external_wikis.any?)
  end

755
  def find_or_initialize_services
M
Marin Jankovski 已提交
756 757
    services_templates = Service.where(template: true)

758
    Service.available_services_names.map do |service_name|
M
Marin Jankovski 已提交
759
      service = find_service(services, service_name)
760

761 762 763
      if service
        service
      else
M
Marin Jankovski 已提交
764 765 766 767
        # We should check if template for the service exists
        template = find_service(services_templates, service_name)

        if template.nil?
768 769
          # If no template, we should create an instance. Ex `build_gitlab_ci_service`
          public_send("build_#{service_name}_service")
M
Marin Jankovski 已提交
770
        else
771
          Service.build_from_template(id, template)
M
Marin Jankovski 已提交
772 773
        end
      end
774 775 776
    end
  end

777 778 779 780
  def find_or_initialize_service(name)
    find_or_initialize_services.find { |service| service.to_param == name }
  end

V
Valery Sizov 已提交
781 782
  def create_labels
    Label.templates.each do |label|
783
      params = label.attributes.except('id', 'template', 'created_at', 'updated_at')
784
      Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true)
V
Valery Sizov 已提交
785 786 787
    end
  end

M
Marin Jankovski 已提交
788 789 790
  def find_service(list, name)
    list.find { |service| service.to_param == name }
  end
D
Dmitriy Zaporozhets 已提交
791

792
  def ci_services
Y
Yorick Peterse 已提交
793
    services.where(category: :ci)
794 795 796
  end

  def ci_service
Y
Yorick Peterse 已提交
797
    @ci_service ||= ci_services.reorder(nil).find_by(active: true)
798 799
  end

D
Drew Blessing 已提交
800 801 802 803
  def jira_tracker?
    issues_tracker.to_param == 'jira'
  end

804
  def avatar_type
805 806
    unless self.avatar.image?
      self.errors.add :avatar, 'only images allowed'
807 808 809 810
    end
  end

  def avatar_in_git
811
    repository.avatar
812 813
  end

S
sue445 已提交
814
  def avatar_url
815
    if self[:avatar].present?
S
sue445 已提交
816 817
      [gitlab_config.url, avatar.url].join
    elsif avatar_in_git
818
      Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
S
sue445 已提交
819 820 821
    end
  end

822 823 824 825 826
  # For compatibility with old code
  def code
    path
  end

827
  def items_for(entity)
D
Dmitriy Zaporozhets 已提交
828 829 830 831 832 833 834
    case entity
    when 'issue' then
      issues
    when 'merge_request' then
      merge_requests
    end
  end
835

836
  def send_move_instructions(old_path_with_namespace)
837 838 839
    # New project path needs to be committed to the DB or notification will
    # retrieve stale information
    run_after_commit { NotificationService.new.project_was_moved(self, old_path_with_namespace) }
840
  end
841 842

  def owner
843 844
    if group
      group
845
    else
846
      namespace.try(:owner)
847 848
    end
  end
D
Dmitriy Zaporozhets 已提交
849 850 851 852

  def name_with_namespace
    @name_with_namespace ||= begin
                               if namespace
853
                                 namespace.human_name + ' / ' + name
D
Dmitriy Zaporozhets 已提交
854 855 856 857 858
                               else
                                 name
                               end
                             end
  end
R
Rémy Coutable 已提交
859
  alias_method :human_name, :name_with_namespace
D
Dmitriy Zaporozhets 已提交
860 861 862 863 864 865 866 867 868

  def path_with_namespace
    if namespace
      namespace.path + '/' + path
    else
      path
    end
  end

869 870
  def execute_hooks(data, hooks_scope = :push_hooks)
    hooks.send(hooks_scope).each do |hook|
871
      hook.async_execute(data, hooks_scope.to_s)
872
    end
D
Dmitriy Zaporozhets 已提交
873 874
  end

875 876 877
  def execute_services(data, hooks_scope = :push_hooks)
    # Call only service hooks that are active for this scope
    services.send(hooks_scope).each do |service|
878
      service.async_execute(data)
D
Dmitriy Zaporozhets 已提交
879 880 881 882
    end
  end

  def valid_repo?
883
    repository.exists?
D
Dmitriy Zaporozhets 已提交
884
  rescue
885
    errors.add(:path, 'Invalid repository path')
D
Dmitriy Zaporozhets 已提交
886 887 888 889
    false
  end

  def empty_repo?
V
Valery Sizov 已提交
890
    repository.empty_repo?
D
Dmitriy Zaporozhets 已提交
891 892 893
  end

  def repo
D
Dmitriy Zaporozhets 已提交
894
    repository.raw
D
Dmitriy Zaporozhets 已提交
895 896 897
  end

  def url_to_repo
898
    gitlab_shell.url_to_repo(path_with_namespace)
D
Dmitriy Zaporozhets 已提交
899 900 901 902 903 904 905
  end

  def namespace_dir
    namespace.try(:path) || ''
  end

  def repo_exists?
906
    @repo_exists ||= repository.exists?
D
Dmitriy Zaporozhets 已提交
907 908 909 910
  rescue
    @repo_exists = false
  end

911
  # Branches that are not _exactly_ matched by a protected branch.
D
Dmitriy Zaporozhets 已提交
912
  def open_branches
913 914 915 916
    exact_protected_branch_names = protected_branches.reject(&:wildcard?).map(&:name)
    branch_names = repository.branches.map(&:name)
    non_open_branch_names = Set.new(exact_protected_branch_names).intersection(Set.new(branch_names))
    repository.branches.reject { |branch| non_open_branch_names.include? branch.name }
D
Dmitriy Zaporozhets 已提交
917 918 919
  end

  def root_ref?(branch)
D
Dmitriy Zaporozhets 已提交
920
    repository.root_ref == branch
D
Dmitriy Zaporozhets 已提交
921 922 923 924 925 926 927
  end

  def ssh_url_to_repo
    url_to_repo
  end

  def http_url_to_repo
D
Douwe Maan 已提交
928
    "#{web_url}.git"
D
Dmitriy Zaporozhets 已提交
929 930 931
  end

  # Check if current branch name is marked as protected in the system
932
  def protected_branch?(branch_name)
933 934
    return true if empty_repo? && default_branch_protected?

935 936
    @protected_branches ||= self.protected_branches.to_a
    ProtectedBranch.matching(branch_name, protected_branches: @protected_branches).present?
D
Dmitriy Zaporozhets 已提交
937
  end
938

939 940
  def user_can_push_to_empty_repo?(user)
    !default_branch_protected? || team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
941 942
  end

943 944 945
  def forked?
    !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
  end
D
Dmitriy Zaporozhets 已提交
946

947 948 949 950
  def personal?
    !group
  end

D
Dmitriy Zaporozhets 已提交
951
  def rename_repo
952
    path_was = previous_changes['path'].first
D
Dmitriy Zaporozhets 已提交
953 954 955
    old_path_with_namespace = File.join(namespace_dir, path_was)
    new_path_with_namespace = File.join(namespace_dir, path)

956 957
    Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"

958 959
    expire_caches_before_rename(old_path_with_namespace)

960
    if has_container_registry_tags?
961 962
      Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present"

963
      # we currently doesn't support renaming repository if it contains tags in container registry
K
Kamil Trzcinski 已提交
964
      raise Exception.new('Project cannot be renamed, because tags are present in its container registry')
965 966
    end

967
    if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace)
968
      # If repository moved successfully we need to send update instructions to users.
D
Dmitriy Zaporozhets 已提交
969 970 971
      # However we cannot allow rollback since we moved repository
      # So we basically we mute exceptions in next actions
      begin
972
        gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
973
        send_move_instructions(old_path_with_namespace)
974
        reset_events_cache
975 976 977 978 979

        @old_path_with_namespace = old_path_with_namespace

        SystemHooksService.new.execute_hooks_for(self, :rename)

980
        @repository = nil
981 982
      rescue => e
        Rails.logger.error "Exception renaming #{old_path_with_namespace} -> #{new_path_with_namespace}: #{e}"
J
Johannes Schleifenbaum 已提交
983
        # Returning false does not rollback after_* transaction but gives
D
Dmitriy Zaporozhets 已提交
984 985 986 987
        # us information about failing some of tasks
        false
      end
    else
988 989
      Rails.logger.error "Repository could not be renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"

D
Dmitriy Zaporozhets 已提交
990 991 992 993
      # if we cannot move namespace directory we should rollback
      # db changes in order to prevent out of sync between db and fs
      raise Exception.new('repository cannot be renamed')
    end
994

995 996
    Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"

997
    Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
D
Dmitriy Zaporozhets 已提交
998
  end
999

1000 1001 1002 1003 1004 1005
  # Expires various caches before a project is renamed.
  def expire_caches_before_rename(old_path)
    repo = Repository.new(old_path, self)
    wiki = Repository.new("#{old_path}.wiki", self)

    if repo.exists?
1006
      repo.before_delete
1007 1008 1009
    end

    if wiki.exists?
1010
      wiki.before_delete
1011 1012 1013
    end
  end

1014 1015
  def hook_attrs(backward: true)
    attrs = {
K
Kirill Zaitsev 已提交
1016
      name: name,
1017
      description: description,
K
Kirilll Zaitsev 已提交
1018
      web_url: web_url,
1019 1020 1021
      avatar_url: avatar_url,
      git_ssh_url: ssh_url_to_repo,
      git_http_url: http_url_to_repo,
K
Kirill Zaitsev 已提交
1022
      namespace: namespace.name,
1023 1024 1025
      visibility_level: visibility_level,
      path_with_namespace: path_with_namespace,
      default_branch: default_branch,
K
Kirill Zaitsev 已提交
1026
    }
1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038

    # Backward compatibility
    if backward
      attrs.merge!({
                    homepage: web_url,
                    url: url_to_repo,
                    ssh_url: ssh_url_to_repo,
                    http_url: http_url_to_repo
                  })
    end

    attrs
K
Kirill Zaitsev 已提交
1039 1040
  end

1041 1042 1043 1044 1045
  # Reset events cache related to this project
  #
  # Since we do cache @event we need to reset cache in special cases:
  # * when project was moved
  # * when project was renamed
1046
  # * when the project avatar changes
1047 1048 1049 1050 1051 1052 1053 1054 1055
  # Events cache stored like  events/23-20130109142513.
  # The cache key includes updated_at timestamp.
  # Thus it will automatically generate a new fragment
  # when the event is updated because the key changes.
  def reset_events_cache
    Event.where(project_id: self.id).
      order('id DESC').limit(100).
      update_all(updated_at: Time.now)
  end
1056 1057

  def project_member(user)
G
Gabriel Mazetto 已提交
1058
    project_members.find_by(user_id: user)
1059
  end
1060 1061 1062 1063

  def default_branch
    @default_branch ||= repository.root_ref if repository.exists?
  end
1064 1065 1066 1067 1068

  def reload_default_branch
    @default_branch = nil
    default_branch
  end
1069

1070 1071 1072
  def visibility_level_field
    visibility_level
  end
1073 1074 1075 1076 1077 1078 1079 1080

  def archive!
    update_attribute(:archived, true)
  end

  def unarchive!
    update_attribute(:archived, false)
  end
1081

1082
  def change_head(branch)
1083
    repository.before_change_head
P
P.S.V.R 已提交
1084 1085 1086
    repository.rugged.references.create('HEAD',
                                        "refs/heads/#{branch}",
                                        force: true)
1087
    repository.copy_gitattributes(branch)
1088
    repository.expire_avatar_cache(branch)
1089 1090
    reload_default_branch
  end
1091 1092 1093 1094

  def forked_from?(project)
    forked? && project == forked_from_project
  end
1095 1096 1097 1098

  def update_repository_size
    update_attribute(:repository_size, repository.size)
  end
1099

1100 1101 1102 1103
  def update_commit_count
    update_attribute(:commit_count, repository.commit_count)
  end

1104
  def forks_count
1105
    forks.count
1106
  end
R
Robert Schilling 已提交
1107

1108 1109 1110
  def origin_merge_requests
    merge_requests.where(source_project_id: self.id)
  end
1111 1112

  def create_repository
1113 1114
    # Forked import is handled asynchronously
    unless forked?
1115
      if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
1116
        repository.after_create
1117 1118
        true
      else
1119
        errors.add(:base, 'Failed to create repository via gitlab-shell')
1120 1121
        false
      end
1122 1123 1124 1125 1126 1127 1128 1129 1130 1131
    end
  end

  def repository_exists?
    !!repository.exists?
  end

  def create_wiki
    ProjectWiki.new(self, self.owner).wiki
    true
G
Guilherme Garnier 已提交
1132
  rescue ProjectWiki::CouldNotCreateWikiError
1133
    errors.add(:base, 'Failed create wiki')
1134 1135
    false
  end
1136

D
Drew Blessing 已提交
1137 1138 1139 1140
  def jira_tracker_active?
    jira_tracker? && jira_service.active
  end

1141
  def allowed_to_share_with_group?
1142
    !namespace.share_with_group_lock
1143 1144
  end

1145 1146 1147
  def pipeline_for(ref, sha = nil)
    sha ||= commit(ref).try(:sha)

1148
    return unless sha
1149

1150
    pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
K
Kamil Trzcinski 已提交
1151 1152
  end

1153 1154 1155
  def ensure_pipeline(ref, sha, current_user = nil)
    pipeline_for(ref, sha) ||
      pipelines.create(sha: sha, ref: ref, user: current_user)
1156
  end
1157

1158
  def enable_ci
F
Felipe Artur 已提交
1159
    project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
1160
  end
M
Marin Jankovski 已提交
1161

K
Kamil Trzcinski 已提交
1162
  def any_runners?(&block)
1163
    if runners.active.any?(&block)
K
Kamil Trzcinski 已提交
1164 1165 1166 1167 1168 1169
      return true
    end

    shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
  end

1170
  def valid_runners_token?(token)
J
James Lopez 已提交
1171
    self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
K
Kamil Trzcinski 已提交
1172 1173
  end

K
Kamil Trzcinski 已提交
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184
  def build_coverage_enabled?
    build_coverage_regex.present?
  end

  def build_timeout_in_minutes
    build_timeout / 60
  end

  def build_timeout_in_minutes=(value)
    self.build_timeout = value.to_i * 60
  end
1185

S
Stan Hu 已提交
1186 1187 1188
  def open_issues_count
    issues.opened.count
  end
1189

D
Douwe Maan 已提交
1190
  def visibility_level_allowed_as_fork?(level = self.visibility_level)
D
Douwe Maan 已提交
1191
    return true unless forked?
D
Douwe Maan 已提交
1192

D
Douwe Maan 已提交
1193 1194 1195 1196 1197 1198
    # self.forked_from_project will be nil before the project is saved, so
    # we need to go through the relation
    original_project = forked_project_link.forked_from_project
    return true unless original_project

    level <= original_project.visibility_level
D
Douwe Maan 已提交
1199
  end
1200

D
Douwe Maan 已提交
1201 1202
  def visibility_level_allowed_by_group?(level = self.visibility_level)
    return true unless group
1203

D
Douwe Maan 已提交
1204
    level <= group.visibility_level
M
Marin Jankovski 已提交
1205
  end
1206

D
Douwe Maan 已提交
1207 1208
  def visibility_level_allowed?(level = self.visibility_level)
    visibility_level_allowed_as_fork?(level) && visibility_level_allowed_by_group?(level)
F
Felipe Artur 已提交
1209 1210
  end

1211 1212 1213
  def runners_token
    ensure_runners_token!
  end
V
Valery Sizov 已提交
1214 1215 1216 1217

  def wiki
    @wiki ||= ProjectWiki.new(self, self.owner)
  end
1218

J
Josh Frye 已提交
1219 1220
  def running_or_pending_build_count(force: false)
    Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
1221 1222 1223
      builds.running_or_pending.count(:all)
    end
  end
J
James Lopez 已提交
1224 1225

  def mark_import_as_failed(error_message)
1226 1227 1228
    original_errors = errors.dup
    sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)

J
James Lopez 已提交
1229
    import_fail
1230 1231 1232 1233 1234
    update_column(:import_error, sanitized_message)
  rescue ActiveRecord::ActiveRecordError => e
    Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
  ensure
    @errors = original_errors
J
James Lopez 已提交
1235
  end
J
James Lopez 已提交
1236

1237 1238
  def add_export_job(current_user:)
    job_id = ProjectExportWorker.perform_async(current_user.id, self.id)
1239 1240 1241 1242 1243 1244 1245

    if job_id
      Rails.logger.info "Export job started for project ID #{self.id} with job ID #{job_id}"
    else
      Rails.logger.error "Export job failed to start for project ID #{self.id}"
    end
  end
J
James Lopez 已提交
1246 1247

  def export_path
1248
    File.join(Gitlab::ImportExport.storage_path, path_with_namespace)
J
James Lopez 已提交
1249
  end
1250 1251 1252 1253 1254 1255 1256 1257 1258

  def export_project_path
    Dir.glob("#{export_path}/*export.tar.gz").max_by { |f| File.ctime(f) }
  end

  def remove_exports
    _, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
    status.zero?
  end
1259 1260 1261 1262

  def ensure_dir_exist
    gitlab_shell.add_namespace(repository_storage_path, namespace.path)
  end
1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280

  def predefined_variables
    [
      { key: 'CI_PROJECT_ID', value: id.to_s, public: true },
      { key: 'CI_PROJECT_NAME', value: path, public: true },
      { key: 'CI_PROJECT_PATH', value: path_with_namespace, public: true },
      { key: 'CI_PROJECT_NAMESPACE', value: namespace.path, public: true },
      { key: 'CI_PROJECT_URL', value: web_url, public: true }
    ]
  end

  def container_registry_variables
    return [] unless Gitlab.config.registry.enabled

    variables = [
      { key: 'CI_REGISTRY', value: Gitlab.config.registry.host_port, public: true }
    ]

K
Kamil Trzcinski 已提交
1281 1282 1283 1284
    if container_registry_enabled?
      variables << { key: 'CI_REGISTRY_IMAGE', value: container_registry_repository_url, public: true }
    end

1285 1286 1287 1288 1289 1290 1291 1292
    variables
  end

  def secret_variables
    variables.map do |variable|
      { key: variable.key, value: variable.value, public: false }
    end
  end
1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307

  # Checks if `user` is authorized for this project, with at least the
  # `min_access_level` (if given).
  #
  # If you change the logic of this method, please also update `User#authorized_projects`
  def authorized_for_user?(user, min_access_level = nil)
    return false unless user

    return true if personal? && namespace_id == user.namespace_id

    authorized_for_user_by_group?(user, min_access_level) ||
      authorized_for_user_by_members?(user, min_access_level) ||
      authorized_for_user_by_shared_projects?(user, min_access_level)
  end

1308 1309 1310 1311 1312 1313 1314 1315 1316 1317
  def append_or_update_attribute(name, value)
    old_values = public_send(name.to_s)

    if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any?
      update_attribute(name, old_values + value)
    else
      update_attribute(name, value)
    end
  end

Y
Yorick Peterse 已提交
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329
  def pushes_since_gc
    Gitlab::Redis.with { |redis| redis.get(pushes_since_gc_redis_key).to_i }
  end

  def increment_pushes_since_gc
    Gitlab::Redis.with { |redis| redis.incr(pushes_since_gc_redis_key) }
  end

  def reset_pushes_since_gc
    Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) }
  end

1330
  def environments_for(ref, commit: nil, with_tags: false)
1331
    deployments_query = with_tags ? 'ref = ? OR tag IS TRUE' : 'ref = ?'
1332

1333
    environment_ids = deployments
1334
      .where(deployments_query, ref.to_s)
1335 1336
      .group(:environment_id)
      .select(:environment_id)
1337

1338 1339
    environments_found = environments.available
      .where(id: environment_ids).to_a
1340

1341
    return environments_found unless commit
1342

1343
    environments_found.select do |environment|
1344 1345 1346 1347
      environment.includes_commit?(commit)
    end
  end

1348 1349 1350
  def environments_recently_updated_on_branch(branch)
    environments_for(branch).select do |environment|
      environment.recently_updated_on_branch?(branch)
1351 1352 1353
    end
  end

1354 1355
  private

Y
Yorick Peterse 已提交
1356 1357 1358 1359
  def pushes_since_gc_redis_key
    "projects/#{id}/pushes_since_gc"
  end

1360
  def default_branch_protected?
1361 1362
    current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
      current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
1363 1364
  end

1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387
  def authorized_for_user_by_group?(user, min_access_level)
    member = user.group_members.find_by(source_id: group)

    member && (!min_access_level || member.access_level >= min_access_level)
  end

  def authorized_for_user_by_members?(user, min_access_level)
    member = members.find_by(user_id: user)

    member && (!min_access_level || member.access_level >= min_access_level)
  end

  def authorized_for_user_by_shared_projects?(user, min_access_level)
    shared_projects = user.group_members.joins(group: :shared_projects).
      where(project_group_links: { project_id: self })

    if min_access_level
      members_scope = { access_level: Gitlab::Access.values.select { |access| access >= min_access_level } }
      shared_projects = shared_projects.where(members: members_scope)
    end

    shared_projects.any?
  end
1388

1389 1390 1391 1392 1393 1394 1395
  # Similar to the normal callbacks that hook into the life cycle of an
  # Active Record object, you can also define callbacks that get triggered
  # when you add an object to an association collection. If any of these
  # callbacks throw an exception, the object will not be added to the
  # collection. Before you add a new board to the boards collection if you
  # already have 1, 2, or n it will fail, but it if you have 0 that is lower
  # than the number of permitted boards per project it won't fail.
1396
  def validate_board_limit(board)
1397
    raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
1398
  end
G
gitlabhq 已提交
1399
end