project.rb 43.6 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 Avatarable
10
  include CacheMarkdownField
11 12
  include Referable
  include Sortable
13
  include AfterCommitQueue
14
  include CaseSensitivity
15
  include TokenAuthenticatable
16
  include ValidAttribute
F
Felipe Artur 已提交
17
  include ProjectFeaturesCompatibility
18
  include SelectForProjectAuthorization
19
  include Routable
R
Robert Speicher 已提交
20

21
  extend Gitlab::ConfigHelper
22

23
  BoardLimitExceeded = Class.new(StandardError)
24

25
  NUMBER_OF_PERMITTED_BOARDS = 1
D
Douwe Maan 已提交
26
  UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
J
Jared Szechy 已提交
27

28 29
  cache_markdown_field :description, pipeline: :description

30 31
  delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
           :merge_requests_enabled?, :issues_enabled?, to: :project_feature,
32
                                                       allow_nil: true
F
Felipe Artur 已提交
33

34
  default_value_for :archived, false
35
  default_value_for :visibility_level, gitlab_config_features.visibility_level
36
  default_value_for :container_registry_enabled, gitlab_config_features.container_registry
37
  default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
K
Kamil Trzcinski 已提交
38
  default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
F
Felipe Artur 已提交
39 40 41 42 43
  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
44
  default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
45

46
  after_create :ensure_dir_exist
47
  after_create :create_project_feature, unless: :project_feature
48
  after_save :ensure_dir_exist, if: :namespace_id_changed?
M
Markus Koller 已提交
49
  after_save :update_project_statistics, if: :namespace_id_changed?
50

51 52
  # set last_activity_at to the same as created_at
  after_create :set_last_activity_at
53
  def set_last_activity_at
54
    update_column(:last_activity_at, self.created_at)
55 56
  end

57 58 59 60 61
  after_create :set_last_repository_updated_at
  def set_last_repository_updated_at
    update_column(:last_repository_updated_at, self.created_at)
  end

62
  before_destroy :remove_private_deploy_keys
K
Kamil Trzcinski 已提交
63 64
  after_destroy :remove_pages

65
  # update visibility_level of forks
66 67
  after_update :update_forks_visibility_level

68 69
  after_validation :check_pending_delete

70
  acts_as_taggable
D
Dmitriy Zaporozhets 已提交
71

72
  attr_accessor :new_default_branch
73
  attr_accessor :old_path_with_namespace
74
  attr_writer :pipeline_status
75

76 77
  alias_attribute :title, :name

78
  # Relations
79
  belongs_to :creator, class_name: 'User'
80
  belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id'
81
  belongs_to :namespace
82

83
  has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event'
84
  has_many :boards, before_add: :validate_board_limit
F
Felipe Artur 已提交
85

86
  # Project services
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
  has_one :campfire_service
  has_one :drone_ci_service
  has_one :emails_on_push_service
  has_one :pipelines_email_service
  has_one :irker_service
  has_one :pivotaltracker_service
  has_one :hipchat_service
  has_one :flowdock_service
  has_one :assembla_service
  has_one :asana_service
  has_one :gemnasium_service
  has_one :mattermost_slash_commands_service
  has_one :mattermost_service
  has_one :slack_slash_commands_service
  has_one :slack_service
  has_one :buildkite_service
  has_one :bamboo_service
  has_one :teamcity_service
  has_one :pushover_service
  has_one :jira_service
  has_one :redmine_service
  has_one :custom_issue_tracker_service
  has_one :bugzilla_service
  has_one :gitlab_issue_tracker_service, inverse_of: :project
  has_one :external_wiki_service
  has_one :kubernetes_service, inverse_of: :project
  has_one :prometheus_service, inverse_of: :project
  has_one :mock_ci_service
  has_one :mock_deployment_service
  has_one :mock_monitoring_service
  has_one :microsoft_teams_service

  has_one  :forked_project_link,  foreign_key: "forked_to_project_id"
120 121 122 123
  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
124

125
  # Merge Requests for target project should be removed with it
126 127 128 129 130 131 132 133 134 135 136
  has_many :merge_requests, foreign_key: 'target_project_id'
  has_many :issues
  has_many :labels, class_name: 'ProjectLabel'
  has_many :services
  has_many :events
  has_many :milestones
  has_many :notes
  has_many :snippets, class_name: 'ProjectSnippet'
  has_many :hooks, class_name: 'ProjectHook'
  has_many :protected_branches
  has_many :protected_tags
137

138
  has_many :project_authorizations
139
  has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User'
140
  has_many :project_members, -> { where(requested_at: nil) },
141
    as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
142

R
Rémy Coutable 已提交
143
  alias_method :members, :project_members
144 145
  has_many :users, through: :project_members

146
  has_many :requesters, -> { where.not(requested_at: nil) },
147
    as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
148

149
  has_many :deploy_keys_projects
150
  has_many :deploy_keys, through: :deploy_keys_projects
151
  has_many :users_star_projects
C
Ciro Santilli 已提交
152
  has_many :starrers, through: :users_star_projects, source: :user
153
  has_many :releases
154
  has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
M
Marin Jankovski 已提交
155
  has_many :lfs_objects, through: :lfs_objects_projects
156
  has_many :project_group_links
157
  has_many :invited_groups, through: :project_group_links, source: :group
158 159
  has_many :pages_domains
  has_many :todos
160
  has_many :notification_settings, as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
161 162 163 164

  has_one :import_data, class_name: 'ProjectImportData'
  has_one :project_feature
  has_one :statistics, class_name: 'ProjectStatistics'
165

166 167 168
  # Container repositories need to remove data from the container registry,
  # which is not managed by the DB. Hence we're still using dependent: :destroy
  # here.
169
  has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
K
Kamil Trzcinski 已提交
170

171 172 173 174 175 176 177
  has_many :commit_statuses
  has_many :pipelines, class_name: 'Ci::Pipeline'

  # Ci::Build objects store data on the file system such as artifact files and
  # build traces. Currently there's no efficient way of removing this data in
  # bulk that doesn't involve loading the rows into memory. As a result we're
  # still using `dependent: :destroy` here.
178
  has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
179
  has_many :runner_projects, class_name: 'Ci::RunnerProject'
180
  has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
181
  has_many :variables, class_name: 'Ci::Variable'
182 183 184 185
  has_many :triggers, class_name: 'Ci::Trigger'
  has_many :environments
  has_many :deployments
  has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule'
K
Kamil Trzcinski 已提交
186

K
Kamil Trzcinski 已提交
187 188
  has_many :active_runners, -> { active }, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'

189
  accepts_nested_attributes_for :variables, allow_destroy: true
F
Felipe Artur 已提交
190
  accepts_nested_attributes_for :project_feature
191

192
  delegate :name, to: :owner, allow_nil: true, prefix: true
D
Douwe Maan 已提交
193
  delegate :count, to: :forks, prefix: true
194
  delegate :members, to: :team, prefix: true
195
  delegate :add_user, :add_users, to: :team
196
  delegate :add_guest, :add_reporter, :add_developer, :add_master, to: :team
D
Douwe Maan 已提交
197
  delegate :empty_repo?, to: :repository
198

A
Andrey Kumanyaev 已提交
199
  # Validations
200
  validates :creator, presence: true, on: :create
201
  validates :description, length: { maximum: 2000 }, allow_blank: true
202
  validates :ci_config_path,
203
    format: { without: /\.{2}/,
204
              message: 'cannot include directory traversal.' },
205 206
    length: { maximum: 255 },
    allow_blank: true
207 208
  validates :name,
    presence: true,
209
    length: { maximum: 255 },
210
    format: { with: Gitlab::Regex.project_name_regex,
D
Douwe Maan 已提交
211
              message: Gitlab::Regex.project_name_regex_message }
212 213
  validates :path,
    presence: true,
214
    dynamic_path: true,
215
    length: { maximum: 255 },
216 217
    format: { with: Gitlab::PathRegex.project_path_format_regex,
              message: Gitlab::PathRegex.project_path_format_message },
218 219
    uniqueness: { scope: :namespace_id }

220
  validates :namespace, presence: true
D
Douwe Maan 已提交
221
  validates :name, uniqueness: { scope: :namespace_id }
J
James Lopez 已提交
222
  validates :import_url, addressable_url: true, if: :external_import?
D
Douwe Maan 已提交
223
  validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?]
224
  validates :star_count, numericality: { greater_than_or_equal_to: 0 }
225
  validate :check_limit, on: :create
226
  validate :avatar_type,
227
    if: ->(project) { project.avatar.present? && project.avatar_changed? }
228
  validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
D
Douwe Maan 已提交
229
  validate :visibility_level_allowed_by_group
D
Douwe Maan 已提交
230
  validate :visibility_level_allowed_as_fork
231
  validate :check_wiki_path_conflict
232 233 234
  validates :repository_storage,
    presence: true,
    inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
235

236
  add_authentication_token_field :runners_token
237
  before_save :ensure_runners_token
K
Kamil Trzcinski 已提交
238

D
Douwe Maan 已提交
239
  mount_uploader :avatar, AvatarUploader
240
  has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
A
Andrey Kumanyaev 已提交
241

242
  # Scopes
243
  scope :pending_delete, -> { where(pending_delete: true) }
244
  scope :without_deleted, -> { where(pending_delete: false) }
245

246
  scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) }
247 248
  scope :sorted_by_stars, -> { reorder('projects.star_count DESC') }

249
  scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
D
Dmitriy Zaporozhets 已提交
250
  scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
251
  scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) }
T
Toon Claes 已提交
252
  scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) }
R
Rémy Coutable 已提交
253
  scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
254
  scope :non_archived, -> { where(archived: false) }
R
Rubén Dávila 已提交
255
  scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
R
Rémy Coutable 已提交
256 257
  scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }

258
  scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
M
Markus Koller 已提交
259
  scope :with_statistics, -> { includes(:statistics) }
260
  scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
261 262 263
  scope :inside_path, ->(path) do
    # We need routes alias rs for JOIN so it does not conflict with
    # includes(:route) which we use in ProjectsFinder.
264 265
    joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'")
      .where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%")
266
  end
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281

  # "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) }
282
  scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) }
283

284
  enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
285

286 287 288 289
  # Returns a collection of projects that is either public or visible to the
  # logged in user.
  def self.public_or_visible_to_user(user = nil)
    if user
290 291 292 293
      authorized = user
        .project_authorizations
        .select(1)
        .where('project_authorizations.project_id = projects.id')
294 295 296 297 298 299 300 301 302

      levels = Gitlab::VisibilityLevel.levels_for_user(user)

      where('EXISTS (?) OR projects.visibility_level IN (?)', authorized, levels)
    else
      public_to_user
    end
  end

303 304 305
  # 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.
306 307 308 309
  #
  # This method uses an optimised version of `with_feature_access_level` for
  # logged in users to more efficiently get private projects with the given
  # feature.
310
  def self.with_feature_available_for_user(feature, user)
311 312 313 314 315 316 317
    visible = [nil, ProjectFeature::ENABLED]

    if user&.admin?
      with_feature_enabled(feature)
    elsif user
      column = ProjectFeature.quoted_access_level_column(feature)

318 319
      authorized = user.project_authorizations.select(1)
        .where('project_authorizations.project_id = projects.id')
320

321 322
      with_project_feature
        .where("#{column} IN (?) OR (#{column} = ? AND EXISTS (?))",
323 324 325 326 327 328
              visible,
              ProjectFeature::PRIVATE,
              authorized)
    else
      with_feature_access_level(feature, visible)
    end
329
  end
F
Felipe Artur 已提交
330

R
Rémy Coutable 已提交
331 332
  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) }
333

334 335
  scope :excluding_project, ->(project) { where.not(id: project) }

336
  state_machine :import_status, initial: :none do
337 338 339 340 341 342 343 344
    event :import_schedule do
      transition [:none, :finished, :failed] => :scheduled
    end

    event :force_import_start do
      transition [:none, :finished, :failed] => :started
    end

345
    event :import_start do
346
      transition scheduled: :started
347 348 349
    end

    event :import_finish do
350
      transition started: :finished
351 352 353
    end

    event :import_fail do
354
      transition [:scheduled, :started] => :failed
355 356 357
    end

    event :import_retry do
358
      transition failed: :started
359 360
    end

361
    state :scheduled
362 363
    state :started
    state :finished
364 365
    state :failed

366 367 368 369
    after_transition [:none, :finished, :failed] => :scheduled do |project, _|
      project.run_after_commit { add_import_job }
    end

370 371
    after_transition started: :finished do |project, _|
      project.reset_cache_and_import_attrs
372 373 374 375 376 377 378 379 380 381

      if Gitlab::ImportSources.importer_names.include?(project.import_type) && project.repo_exists?
        project.run_after_commit do
          begin
            Projects::HousekeepingService.new(project).execute
          rescue Projects::HousekeepingService::LeaseTaken => e
            Rails.logger.info("Could not perform housekeeping for project #{project.path_with_namespace} (#{project.id}): #{e}")
          end
        end
      end
382
    end
383 384
  end

A
Andrey Kumanyaev 已提交
385
  class << self
386 387 388 389 390 391 392
    # 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.
393
    def search(query)
394
      ptable  = arel_table
395 396 397
      ntable  = Namespace.arel_table
      pattern = "%#{query}%"

M
mhasbini 已提交
398 399 400
      # unscoping unnecessary conditions that'll be applied
      # when executing `where("projects.id IN (#{union.to_sql})")`
      projects = unscoped.select(:id).where(
401 402 403
        ptable[:path].matches(pattern)
          .or(ptable[:name].matches(pattern))
          .or(ptable[:description].matches(pattern))
404 405
      )

406 407 408
      namespaces = unscoped.select(:id)
        .joins(:namespace)
        .where(ntable[:name].matches(pattern))
409 410 411 412

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

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

415
    def search_by_title(query)
416 417 418 419
      pattern = "%#{query}%"
      table   = Project.arel_table

      non_archived.where(table[:name].matches(pattern))
420 421
    end

422 423 424
    def visibility_levels
      Gitlab::VisibilityLevel.options
    end
425 426

    def sort(method)
427 428
      case method.to_s
      when 'storage_size_desc'
M
Markus Koller 已提交
429 430 431
        # storage_size is a joined column so we need to
        # pass a string to avoid AR adding the table name
        reorder('project_statistics.storage_size DESC, projects.id DESC')
432 433 434 435
      when 'latest_activity_desc'
        reorder(last_activity_at: :desc)
      when 'latest_activity_asc'
        reorder(last_activity_at: :asc)
436 437
      else
        order_by(method)
438 439
      end
    end
440 441

    def reference_pattern
442
      %r{
443 444
        ((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)?
        (?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX})
445
      }x
446
    end
Y
Yorick Peterse 已提交
447

Y
Yorick Peterse 已提交
448
    def trending
449 450
      joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id')
        .reorder('trending_projects.id ASC')
Y
Yorick Peterse 已提交
451
    end
452 453 454 455 456 457

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

    def group_ids
460
      joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
461
    end
462 463
  end

464
  def lfs_enabled?
465
    return namespace.lfs_enabled? if self[:lfs_enabled].nil?
P
Patricio Cano 已提交
466

467
    self[:lfs_enabled] && Gitlab.config.lfs.enabled
468 469
  end

470
  def repository_storage_path
471
    Gitlab.config.repositories.storages[repository_storage]['path']
472 473
  end

D
Dmitriy Zaporozhets 已提交
474
  def team
475
    @team ||= ProjectTeam.new(self)
D
Dmitriy Zaporozhets 已提交
476 477 478
  end

  def repository
479
    @repository ||= Repository.new(path_with_namespace, self)
480 481
  end

A
Andre Guedes 已提交
482
  def container_registry_url
K
Kamil Trzcinski 已提交
483
    if Gitlab.config.registry.enabled
484
      "#{Gitlab.config.registry.host_port}/#{path_with_namespace.downcase}"
485
    end
486 487
  end

488
  def has_container_registry_tags?
489 490
    container_repositories.to_a.any?(&:has_tags?) ||
      has_root_container_repository_tags?
491 492
  end

493 494
  def commit(ref = 'HEAD')
    repository.commit(ref)
D
Dmitriy Zaporozhets 已提交
495 496
  end

497
  # ref can't be HEAD, can only be branch/tag name or SHA
498
  def latest_successful_builds_for(ref = default_branch)
499
    latest_pipeline = pipelines.latest_successful_for(ref)
500 501 502 503 504 505

    if latest_pipeline
      latest_pipeline.builds.latest.with_artifacts
    else
      builds.none
    end
506 507
  end

508
  def merge_base_commit(first_commit_id, second_commit_id)
D
Douwe Maan 已提交
509 510
    sha = repository.merge_base(first_commit_id, second_commit_id)
    repository.commit(sha) if sha
511 512
  end

513
  def saved?
514
    id && persisted?
515 516
  end

517
  def add_import_job
D
Douwe Maan 已提交
518 519 520 521 522 523 524 525
    job_id =
      if forked?
        RepositoryForkWorker.perform_async(id, forked_from_project.repository_storage_path,
          forked_from_project.path_with_namespace,
          self.namespace.full_path)
      else
        RepositoryImportWorker.perform_async(self.id)
      end
526 527 528 529 530

    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}"
531
    end
532 533
  end

534 535 536 537 538
  def reset_cache_and_import_attrs
    run_after_commit do
      ProjectCacheWorker.perform_async(self.id)
    end

539
    import_data&.destroy
540 541
  end

542
  def ci_config_path=(value)
543
    # Strip all leading slashes so that //foo -> foo
L
Lin Jen-Shin 已提交
544
    super(value&.sub(%r{\A/+}, '')&.delete("\0"))
545 546
  end

J
James Lopez 已提交
547
  def import_url=(value)
J
James Lopez 已提交
548 549
    return super(value) unless Gitlab::UrlSanitizer.valid?(value)

550
    import_url = Gitlab::UrlSanitizer.new(value)
J
James Lopez 已提交
551
    super(import_url.sanitized_url)
552
    create_or_update_import_data(credentials: import_url.credentials)
J
James Lopez 已提交
553 554 555
  end

  def import_url
J
James Lopez 已提交
556
    if import_data && super.present?
557
      import_url = Gitlab::UrlSanitizer.new(super, credentials: import_data.credentials)
J
James Lopez 已提交
558 559 560
      import_url.full_url
    else
      super
J
James Lopez 已提交
561 562
    end
  end
J
James Lopez 已提交
563

J
James Lopez 已提交
564 565 566 567
  def valid_import_url?
    valid? || errors.messages[:import_url].nil?
  end

568
  def create_or_update_import_data(data: nil, credentials: nil)
569
    return unless import_url.present? && valid_import_url?
570

J
James Lopez 已提交
571
    project_import_data = import_data || build_import_data
J
James Lopez 已提交
572 573 574 575
    if data
      project_import_data.data ||= {}
      project_import_data.data = project_import_data.data.merge(data)
    end
576 577 578 579
    if credentials
      project_import_data.credentials ||= {}
      project_import_data.credentials = project_import_data.credentials.merge(credentials)
    end
580 581

    project_import_data.save
J
James Lopez 已提交
582
  end
J
James Lopez 已提交
583

584
  def import?
585
    external_import? || forked? || gitlab_project_import?
586 587
  end

588 589 590 591
  def no_import?
    import_status == 'none'
  end

592
  def external_import?
593 594 595
    import_url.present?
  end

596
  def imported?
597 598 599 600
    import_finished?
  end

  def import_in_progress?
601 602 603 604
    import_started? || import_scheduled?
  end

  def import_started?
605 606 607
    import? && import_status == 'started'
  end

608 609 610 611
  def import_scheduled?
    import_status == 'scheduled'
  end

612 613 614 615 616 617
  def import_failed?
    import_status == 'failed'
  end

  def import_finished?
    import_status == 'finished'
618 619
  end

D
Douwe Maan 已提交
620
  def safe_import_url
621
    Gitlab::UrlSanitizer.new(import_url).masked_url
D
Douwe Maan 已提交
622 623
  end

624 625 626 627
  def gitlab_project_import?
    import_type == 'gitlab_project'
  end

R
Rémy Coutable 已提交
628 629 630 631
  def gitea_import?
    import_type == 'gitea'
  end

632 633 634 635
  def github_import?
    import_type == 'github'
  end

636
  def check_limit
D
Douwe Maan 已提交
637
    unless creator.can_create_project? || namespace.kind == 'group'
638 639 640
      projects_limit = creator.projects_limit

      if projects_limit == 0
P
Phil Hughes 已提交
641
        self.errors.add(:limit_reached, "Personal project creation is not allowed. Please contact your administrator with questions")
642
      else
P
Phil Hughes 已提交
643
        self.errors.add(:limit_reached, "Your project limit is #{projects_limit} projects! Please contact your administrator to increase it")
644
      end
645 646
    end
  rescue
D
Douwe Maan 已提交
647
    self.errors.add(:base, "Can't check your ability to create project")
G
gitlabhq 已提交
648 649
  end

D
Douwe Maan 已提交
650 651 652 653 654 655 656 657 658 659 660 661 662
  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.")
663 664
  end

665 666 667 668 669 670 671 672 673 674
  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

675
  def to_param
676 677 678 679 680
    if persisted? && errors.include?(:path)
      path_was
    else
      path
    end
681 682
  end

683
  # `from` argument can be a Namespace or Project.
684 685 686 687 688 689
  def to_reference(from = nil, full: false)
    if full || cross_namespace_reference?(from)
      path_with_namespace
    elsif cross_project_reference?(from)
      path
    end
690 691 692 693 694 695 696 697
  end

  def to_human_reference(from_project = nil)
    if cross_namespace_reference?(from_project)
      name_with_namespace
    elsif cross_project_reference?(from_project)
      name
    end
698 699
  end

700
  def web_url
701
    Gitlab::Routing.url_helpers.project_url(self)
702 703
  end

704
  def new_issue_address(author)
705
    return unless Gitlab::IncomingEmail.supports_issue_creation? && author
706

707 708 709 710
    author.ensure_incoming_email_token!

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

713
  def build_commit_note(commit)
714
    notes.new(commit_id: commit.id, noteable_type: 'Commit')
G
gitlabhq 已提交
715
  end
N
Nihad Abbasov 已提交
716

N
Nihad Abbasov 已提交
717
  def last_activity
718
    last_event
G
gitlabhq 已提交
719 720 721
  end

  def last_activity_date
722
    last_repository_updated_at || last_activity_at || updated_at
D
Dmitriy Zaporozhets 已提交
723
  end
724

D
Dmitriy Zaporozhets 已提交
725 726 727
  def project_id
    self.id
  end
R
randx 已提交
728

729
  def get_issue(issue_id, current_user)
730
    if default_issues_tracker?
731
      IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id)
732
    else
R
Robert Speicher 已提交
733
      ExternalIssue.new(issue_id, self)
734 735 736
    end
  end

R
Robert Speicher 已提交
737
  def issue_exists?(issue_id)
738
    get_issue(issue_id)
R
Robert Speicher 已提交
739 740
  end

741
  def default_issue_tracker
742
    gitlab_issue_tracker_service || create_gitlab_issue_tracker_service
743 744 745 746 747 748 749 750 751 752
  end

  def issues_tracker
    if external_issue_tracker
      external_issue_tracker
    else
      default_issue_tracker
    end
  end

753 754
  def external_issue_reference_pattern
    external_issue_tracker.class.reference_pattern
755 756
  end

757
  def default_issues_tracker?
758
    !external_issue_tracker
759 760 761
  end

  def external_issue_tracker
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
    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?)
777 778
  end

779 780 781 782
  def has_wiki?
    wiki_enabled? || has_external_wiki?
  end

783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798
  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

799
  def find_or_initialize_services
M
Marin Jankovski 已提交
800 801
    services_templates = Service.where(template: true)

802
    Service.available_services_names.map do |service_name|
M
Marin Jankovski 已提交
803
      service = find_service(services, service_name)
804

805 806 807
      if service
        service
      else
M
Marin Jankovski 已提交
808 809 810 811
        # We should check if template for the service exists
        template = find_service(services_templates, service_name)

        if template.nil?
812 813
          # If no template, we should create an instance. Ex `build_gitlab_ci_service`
          public_send("build_#{service_name}_service")
M
Marin Jankovski 已提交
814
        else
815
          Service.build_from_template(id, template)
M
Marin Jankovski 已提交
816 817
        end
      end
818 819 820
    end
  end

821 822 823 824
  def find_or_initialize_service(name)
    find_or_initialize_services.find { |service| service.to_param == name }
  end

V
Valery Sizov 已提交
825 826
  def create_labels
    Label.templates.each do |label|
827
      params = label.attributes.except('id', 'template', 'created_at', 'updated_at')
828
      Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true)
V
Valery Sizov 已提交
829 830 831
    end
  end

M
Marin Jankovski 已提交
832 833 834
  def find_service(list, name)
    list.find { |service| service.to_param == name }
  end
D
Dmitriy Zaporozhets 已提交
835

836
  def ci_services
Y
Yorick Peterse 已提交
837
    services.where(category: :ci)
838 839 840
  end

  def ci_service
841
    @ci_service ||= ci_services.find_by(active: true)
842 843
  end

844 845 846 847 848
  def deployment_services
    services.where(category: :deployment)
  end

  def deployment_service
849
    @deployment_service ||= deployment_services.find_by(active: true)
850 851
  end

852 853 854 855 856
  def monitoring_services
    services.where(category: :monitoring)
  end

  def monitoring_service
857
    @monitoring_service ||= monitoring_services.find_by(active: true)
858 859
  end

D
Drew Blessing 已提交
860 861 862 863
  def jira_tracker?
    issues_tracker.to_param == 'jira'
  end

864
  def avatar_type
865 866
    unless self.avatar.image?
      self.errors.add :avatar, 'only images allowed'
867 868 869 870
    end
  end

  def avatar_in_git
871
    repository.avatar
872 873
  end

874 875 876
  def avatar_url(**args)
    # We use avatar_path instead of overriding avatar_url because of carrierwave.
    # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11001/diffs#note_28659864
877
    avatar_path(args) || (Gitlab::Routing.url_helpers.project_avatar_url(self) if avatar_in_git)
S
sue445 已提交
878 879
  end

880 881 882 883 884
  # For compatibility with old code
  def code
    path
  end

885
  def items_for(entity)
D
Dmitriy Zaporozhets 已提交
886 887 888 889 890 891 892
    case entity
    when 'issue' then
      issues
    when 'merge_request' then
      merge_requests
    end
  end
893

894
  def send_move_instructions(old_path_with_namespace)
895 896 897
    # 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) }
898
  end
899 900

  def owner
901 902
    if group
      group
903
    else
904
      namespace.try(:owner)
905 906
    end
  end
D
Dmitriy Zaporozhets 已提交
907

908 909
  def execute_hooks(data, hooks_scope = :push_hooks)
    hooks.send(hooks_scope).each do |hook|
910
      hook.async_execute(data, hooks_scope.to_s)
911
    end
D
Dmitriy Zaporozhets 已提交
912 913
  end

914 915 916
  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|
917
      service.async_execute(data)
D
Dmitriy Zaporozhets 已提交
918 919 920 921
    end
  end

  def valid_repo?
922
    repository.exists?
D
Dmitriy Zaporozhets 已提交
923
  rescue
924
    errors.add(:path, 'Invalid repository path')
D
Dmitriy Zaporozhets 已提交
925 926 927 928
    false
  end

  def repo
D
Dmitriy Zaporozhets 已提交
929
    repository.raw
D
Dmitriy Zaporozhets 已提交
930 931 932
  end

  def url_to_repo
933
    gitlab_shell.url_to_repo(path_with_namespace)
D
Dmitriy Zaporozhets 已提交
934 935 936
  end

  def repo_exists?
937
    @repo_exists ||= repository.exists?
D
Dmitriy Zaporozhets 已提交
938 939 940 941 942
  rescue
    @repo_exists = false
  end

  def root_ref?(branch)
D
Dmitriy Zaporozhets 已提交
943
    repository.root_ref == branch
D
Dmitriy Zaporozhets 已提交
944 945 946 947 948 949
  end

  def ssh_url_to_repo
    url_to_repo
  end

950 951
  def http_url_to_repo
    "#{web_url}.git"
D
Dmitriy Zaporozhets 已提交
952 953
  end

954
  def user_can_push_to_empty_repo?(user)
955
    !ProtectedBranch.default_branch_protected? || team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
956 957
  end

958 959 960
  def forked?
    !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
  end
D
Dmitriy Zaporozhets 已提交
961

962 963 964 965
  def personal?
    !group
  end

D
Dmitriy Zaporozhets 已提交
966
  def rename_repo
967
    path_was = previous_changes['path'].first
968 969
    old_path_with_namespace = File.join(namespace.full_path, path_was)
    new_path_with_namespace = File.join(namespace.full_path, path)
D
Dmitriy Zaporozhets 已提交
970

971 972
    Rails.logger.error "Attempting to rename #{old_path_with_namespace} -> #{new_path_with_namespace}"

973 974
    expire_caches_before_rename(old_path_with_namespace)

975
    if has_container_registry_tags?
976
      Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present!"
977

A
Andre Guedes 已提交
978 979
      # we currently doesn't support renaming repository if it contains images in container registry
      raise StandardError.new('Project cannot be renamed, because images are present in its container registry')
980 981
    end

982
    if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace)
983
      # If repository moved successfully we need to send update instructions to users.
D
Dmitriy Zaporozhets 已提交
984 985 986
      # However we cannot allow rollback since we moved repository
      # So we basically we mute exceptions in next actions
      begin
987
        gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
988
        send_move_instructions(old_path_with_namespace)
989
        expires_full_path_cache
990 991 992 993 994

        @old_path_with_namespace = old_path_with_namespace

        SystemHooksService.new.execute_hooks_for(self, :rename)

995
        @repository = nil
996 997
      rescue => e
        Rails.logger.error "Exception renaming #{old_path_with_namespace} -> #{new_path_with_namespace}: #{e}"
J
Johannes Schleifenbaum 已提交
998
        # Returning false does not rollback after_* transaction but gives
D
Dmitriy Zaporozhets 已提交
999 1000 1001 1002
        # us information about failing some of tasks
        false
      end
    else
1003 1004
      Rails.logger.error "Repository could not be renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}"

D
Dmitriy Zaporozhets 已提交
1005 1006
      # if we cannot move namespace directory we should rollback
      # db changes in order to prevent out of sync between db and fs
1007
      raise StandardError.new('repository cannot be renamed')
D
Dmitriy Zaporozhets 已提交
1008
    end
1009

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

1012 1013
    Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.full_path)
    Gitlab::PagesTransfer.new.rename_project(path_was, path, namespace.full_path)
D
Dmitriy Zaporozhets 已提交
1014
  end
1015

1016 1017 1018 1019 1020 1021
  # 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?
1022
      repo.before_delete
1023 1024 1025
    end

    if wiki.exists?
1026
      wiki.before_delete
1027 1028 1029
    end
  end

1030 1031
  def hook_attrs(backward: true)
    attrs = {
K
Kirill Zaitsev 已提交
1032
      name: name,
1033
      description: description,
K
Kirilll Zaitsev 已提交
1034
      web_url: web_url,
1035 1036 1037
      avatar_url: avatar_url,
      git_ssh_url: ssh_url_to_repo,
      git_http_url: http_url_to_repo,
K
Kirill Zaitsev 已提交
1038
      namespace: namespace.name,
1039 1040 1041
      visibility_level: visibility_level,
      path_with_namespace: path_with_namespace,
      default_branch: default_branch,
1042
      ci_config_path: ci_config_path
K
Kirill Zaitsev 已提交
1043
    }
1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055

    # 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 已提交
1056 1057
  end

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

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

  def reload_default_branch
    @default_branch = nil
    default_branch
  end
1070

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

  def archive!
    update_attribute(:archived, true)
  end

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

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

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

1097 1098 1099
  def origin_merge_requests
    merge_requests.where(source_project_id: self.id)
  end
1100

1101
  def create_repository(force: false)
1102
    # Forked import is handled asynchronously
1103 1104 1105 1106 1107 1108 1109 1110
    return if forked? && !force

    if gitlab_shell.add_repository(repository_storage_path, path_with_namespace)
      repository.after_create
      true
    else
      errors.add(:base, 'Failed to create repository via gitlab-shell')
      false
1111 1112 1113
    end
  end

1114
  def ensure_repository
1115
    create_repository(force: true) unless repository_exists?
1116 1117
  end

1118 1119 1120 1121
  def repository_exists?
    !!repository.exists?
  end

1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
  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

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

1141 1142 1143 1144
  def wiki
    @wiki ||= ProjectWiki.new(self, self.owner)
  end

D
Drew Blessing 已提交
1145 1146 1147 1148
  def jira_tracker_active?
    jira_tracker? && jira_service.active
  end

1149
  def allowed_to_share_with_group?
1150
    !namespace.share_with_group_lock
1151 1152
  end

1153 1154 1155
  def pipeline_for(ref, sha = nil)
    sha ||= commit(ref).try(:sha)

1156
    return unless sha
1157

1158
    pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
K
Kamil Trzcinski 已提交
1159 1160
  end

1161
  def enable_ci
F
Felipe Artur 已提交
1162
    project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
1163
  end
M
Marin Jankovski 已提交
1164

1165 1166 1167 1168 1169
  def shared_runners_available?
    shared_runners_enabled?
  end

  def shared_runners
1170
    @shared_runners ||= shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
1171 1172
  end

1173 1174
  def active_shared_runners
    @active_shared_runners ||= shared_runners.active
1175
  end
K
Kamil Trzcinski 已提交
1176 1177

  def any_runners?(&block)
K
Kamil Trzcinski 已提交
1178
    active_runners.any?(&block) || active_shared_runners.any?(&block)
K
Kamil Trzcinski 已提交
1179 1180
  end

1181
  def valid_runners_token?(token)
J
James Lopez 已提交
1182
    self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
K
Kamil Trzcinski 已提交
1183 1184
  end

K
Kamil Trzcinski 已提交
1185 1186 1187 1188 1189 1190 1191
  def build_timeout_in_minutes
    build_timeout / 60
  end

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

S
Stan Hu 已提交
1193 1194 1195
  def open_issues_count
    issues.opened.count
  end
1196

D
Douwe Maan 已提交
1197
  def visibility_level_allowed_as_fork?(level = self.visibility_level)
D
Douwe Maan 已提交
1198
    return true unless forked?
D
Douwe Maan 已提交
1199

D
Douwe Maan 已提交
1200 1201 1202 1203 1204 1205
    # 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 已提交
1206
  end
1207

D
Douwe Maan 已提交
1208 1209
  def visibility_level_allowed_by_group?(level = self.visibility_level)
    return true unless group
1210

D
Douwe Maan 已提交
1211
    level <= group.visibility_level
M
Marin Jankovski 已提交
1212
  end
1213

D
Douwe Maan 已提交
1214 1215
  def visibility_level_allowed?(level = self.visibility_level)
    visibility_level_allowed_as_fork?(level) && visibility_level_allowed_by_group?(level)
F
Felipe Artur 已提交
1216 1217
  end

1218 1219 1220
  def runners_token
    ensure_runners_token!
  end
V
Valery Sizov 已提交
1221

1222 1223 1224
  def pages_deployed?
    Dir.exist?(public_pages_path)
  end
1225

1226
  def pages_url
1227 1228
    subdomain, _, url_path = full_path.partition('/')

K
Kamil Trzcinski 已提交
1229 1230
    # The hostname always needs to be in downcased
    # All web servers convert hostname to lowercase
1231
    host = "#{subdomain}.#{Settings.pages.host}".downcase
K
Kamil Trzcinski 已提交
1232 1233

    # The host in URL always needs to be downcased
1234
    url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix|
1235
      "#{prefix}#{subdomain}."
K
Kamil Trzcinski 已提交
1236
    end.downcase
1237

K
Kamil Trzcinski 已提交
1238
    # If the project path is the same as host, we serve it as group page
1239 1240 1241 1242
    return url if host == url_path

    "#{url}/#{url_path}"
  end
1243

1244 1245
  def pages_subdomain
    full_path.partition('/').first
1246
  end
K
Kamil Trzcinski 已提交
1247 1248

  def pages_path
1249
    File.join(Settings.pages.path, path_with_namespace)
K
Kamil Trzcinski 已提交
1250 1251 1252 1253 1254 1255
  end

  def public_pages_path
    File.join(pages_path, 'public')
  end

1256 1257 1258 1259
  def remove_private_deploy_keys
    deploy_keys.where(public: false).delete_all
  end

K
Kamil Trzcinski 已提交
1260
  def remove_pages
1261 1262
    ::Projects::UpdatePagesConfigurationService.new(self).execute

1263 1264 1265
    # 1. We rename pages to temporary directory
    # 2. We wait 5 minutes, due to NFS caching
    # 3. We asynchronously remove pages with force
K
Kamil Trzcinski 已提交
1266
    temp_path = "#{path}.#{SecureRandom.hex}.deleted"
K
Kamil Trzcinski 已提交
1267

1268 1269
    if Gitlab::PagesTransfer.new.rename_project(path, temp_path, namespace.full_path)
      PagesWorker.perform_in(5.minutes, :remove, namespace.full_path, temp_path)
K
Kamil Trzcinski 已提交
1270
    end
K
Kamil Trzcinski 已提交
1271 1272
  end

J
Josh Frye 已提交
1273 1274
  def running_or_pending_build_count(force: false)
    Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do
1275 1276 1277
      builds.running_or_pending.count(:all)
    end
  end
J
James Lopez 已提交
1278

1279
  # Lazy loading of the `pipeline_status` attribute
1280
  def pipeline_status
1281
    @pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self)
1282 1283
  end

J
James Lopez 已提交
1284
  def mark_import_as_failed(error_message)
1285 1286 1287
    original_errors = errors.dup
    sanitized_message = Gitlab::UrlSanitizer.sanitize(error_message)

J
James Lopez 已提交
1288
    import_fail
1289 1290 1291 1292 1293
    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 已提交
1294
  end
J
James Lopez 已提交
1295

1296 1297
  def add_export_job(current_user:)
    job_id = ProjectExportWorker.perform_async(current_user.id, self.id)
1298 1299 1300 1301 1302 1303 1304

    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 已提交
1305 1306

  def export_path
1307
    File.join(Gitlab::ImportExport.storage_path, path_with_namespace)
J
James Lopez 已提交
1308
  end
1309 1310 1311 1312 1313 1314 1315 1316 1317

  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
1318 1319

  def ensure_dir_exist
1320
    gitlab_shell.add_namespace(repository_storage_path, namespace.full_path)
1321
  end
1322 1323 1324 1325 1326 1327

  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 },
1328
      { key: 'CI_PROJECT_PATH_SLUG', value: path_with_namespace.parameterize, public: true },
1329
      { key: 'CI_PROJECT_NAMESPACE', value: namespace.full_path, public: true },
1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340
      { 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 已提交
1341
    if container_registry_enabled?
A
Andre Guedes 已提交
1342
      variables << { key: 'CI_REGISTRY_IMAGE', value: container_registry_url, public: true }
K
Kamil Trzcinski 已提交
1343 1344
    end

1345 1346 1347
    variables
  end

1348 1349 1350 1351 1352 1353 1354
  def secret_variables_for(ref)
    if protected_for?(ref)
      variables
    else
      variables.unprotected
    end
  end
1355

1356 1357 1358
  def protected_for?(ref)
    ProtectedBranch.protected?(self, ref) ||
      ProtectedTag.protected?(self, ref)
1359
  end
1360

1361 1362 1363 1364 1365 1366
  def deployment_variables
    return [] unless deployment_service

    deployment_service.predefined_variables
  end

1367 1368 1369 1370 1371 1372 1373 1374
  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
1375 1376 1377

  rescue ActiveRecord::RecordNotSaved => e
    handle_update_attribute_error(e, value)
1378 1379
  end

Y
Yorick Peterse 已提交
1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391
  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

D
Douwe Maan 已提交
1392
  def route_map_for(commit_sha)
1393 1394
    @route_maps_by_commit ||= Hash.new do |h, sha|
      h[sha] = begin
D
Douwe Maan 已提交
1395
        data = repository.route_map_for(sha)
1396 1397
        next unless data

D
Douwe Maan 已提交
1398 1399 1400
        Gitlab::RouteMap.new(data)
      rescue Gitlab::RouteMap::FormatError
        nil
1401 1402 1403 1404 1405 1406 1407
      end
    end

    @route_maps_by_commit[commit_sha]
  end

  def public_path_for_source_path(path, commit_sha)
D
Douwe Maan 已提交
1408
    map = route_map_for(commit_sha)
1409 1410
    return unless map

D
Douwe Maan 已提交
1411
    map.public_path_for_source_path(path)
1412 1413
  end

1414 1415 1416 1417 1418 1419 1420 1421
  def parent
    namespace
  end

  def parent_changed?
    namespace_id_changed?
  end

1422 1423 1424 1425 1426 1427 1428 1429
  def default_merge_request_target
    if forked_from_project&.merge_requests_enabled?
      forked_from_project
    else
      self
    end
  end

1430 1431 1432 1433
  alias_method :name_with_namespace, :full_name
  alias_method :human_name, :full_name
  alias_method :path_with_namespace, :full_path

1434 1435
  private

1436
  def cross_namespace_reference?(from)
1437 1438 1439 1440 1441
    case from
    when Project
      namespace != from.namespace
    when Namespace
      namespace != from
1442 1443 1444
    end
  end

1445
  # Check if a reference is being done cross-project
1446 1447 1448 1449
  def cross_project_reference?(from)
    return true if from.is_a?(Namespace)

    from && self != from
1450 1451
  end

Y
Yorick Peterse 已提交
1452 1453 1454 1455
  def pushes_since_gc_redis_key
    "projects/#{id}/pushes_since_gc"
  end

1456 1457 1458 1459 1460 1461 1462
  # 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.
1463
  def validate_board_limit(board)
1464
    raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
1465
  end
1466

M
Markus Koller 已提交
1467 1468 1469 1470
  def update_project_statistics
    stats = statistics || build_statistics
    stats.update(namespace_id: namespace_id)
  end
1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485

  def check_pending_delete
    return if valid_attribute?(:name) && valid_attribute?(:path)
    return unless pending_delete_twin

    %i[route route.path name path].each do |error|
      errors.delete(error)
    end

    errors.add(:base, "The project is still being deleted. Please try again later.")
  end

  def pending_delete_twin
    return false unless path

1486
    Project.pending_delete.find_by_full_path(path_with_namespace)
1487
  end
1488 1489 1490 1491 1492 1493 1494 1495 1496

  ##
  # This method is here because of support for legacy container repository
  # which has exactly the same path like project does, but which might not be
  # persisted in `container_repositories` table.
  #
  def has_root_container_repository_tags?
    return false unless Gitlab.config.registry.enabled

1497
    ContainerRepository.build_root_repository(self).has_tags?
1498
  end
1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510

  def handle_update_attribute_error(ex, value)
    if ex.message.start_with?('Failed to replace')
      if value.respond_to?(:each)
        invalid = value.detect(&:invalid?)

        raise ex, ([ex.message] + invalid.errors.full_messages).join(' ') if invalid
      end
    end

    raise ex
  end
G
gitlabhq 已提交
1511
end