Class: Project

Inherits:
ActiveRecord::Base
  • Object
show all
Extended by:
Gitlab::ConfigHelper
Includes:
AfterCommitQueue, CaseSensitivity, Gitlab::ConfigHelper, Gitlab::CurrentSettings, Gitlab::ShellAdapter, Gitlab::VisibilityLevel, Referable, Sortable, TokenAuthenticatable
Defined in:
app/models/project.rb

Constant Summary collapse

UNKNOWN_IMPORT_URL =
'http://unknown.git'

Constants included from Gitlab::VisibilityLevel

Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::PUBLIC

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::ConfigHelper

gitlab_config, gitlab_config_features

Methods included from Referable

#reference_link_text

Methods included from Gitlab::CurrentSettings

#current_application_settings, #fake_application_settings

Methods included from Gitlab::VisibilityLevel

allowed_for?, allowed_level?, #internal?, level_name, non_restricted_level?, options, #private?, #public?, valid_level?, values

Methods included from Gitlab::ShellAdapter

#gitlab_shell

Instance Attribute Details

#new_default_branchObject

Returns the value of attribute new_default_branch


94
95
96
# File 'app/models/project.rb', line 94

def new_default_branch
  @new_default_branch
end

#old_path_with_namespaceObject

Returns the value of attribute old_path_with_namespace


95
96
97
# File 'app/models/project.rb', line 95

def old_path_with_namespace
  @old_path_with_namespace
end

Class Method Details

.abandonedObject


254
255
256
# File 'app/models/project.rb', line 254

def abandoned
  where('projects.last_activity_at < ?', 6.months.ago)
end

.activeObject


262
263
264
# File 'app/models/project.rb', line 262

def active
  joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC')
end

.find_by_ci_id(id) ⇒ Object


327
328
329
# File 'app/models/project.rb', line 327

def find_by_ci_id(id)
  find_by(ci_id: id.to_i)
end

.find_with_namespace(id) ⇒ Object


311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'app/models/project.rb', line 311

def find_with_namespace(id)
  namespace_path, project_path = id.split('/', 2)

  return nil if !namespace_path || !project_path

  # Use of unscoped ensures we're not secretly adding any ORDER BYs, which
  # have a negative impact on performance (and aren't needed for this
  # query).
  projects = unscoped.
    joins(:namespace).
    iwhere('namespaces.path' => namespace_path)

  projects.find_by('projects.path' => project_path) ||
    projects.iwhere('projects.path' => project_path).take
end

.reference_patternObject


343
344
345
346
# File 'app/models/project.rb', line 343

def reference_pattern
  name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
  %r{(?<project>#{name_pattern}/#{name_pattern})}
end

.search(query) ⇒ Object

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.


273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'app/models/project.rb', line 273

def search(query)
  ptable  = arel_table
  ntable  = Namespace.arel_table
  pattern = "%#{query}%"

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

  # 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.
  namespaces = select(:id).
    except(:includes).
    joins(:namespace).
    where(ntable[:name].matches(pattern))

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

  where("projects.id IN (#{union.to_sql})")
end

.search_by_title(query) ⇒ Object


304
305
306
307
308
309
# File 'app/models/project.rb', line 304

def search_by_title(query)
  pattern = "%#{query}%"
  table   = Project.arel_table

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

.search_by_visibility(level) ⇒ Object


300
301
302
# File 'app/models/project.rb', line 300

def search_by_visibility(level)
  where(visibility_level: Gitlab::VisibilityLevel.const_get(level.upcase))
end

.sort(method) ⇒ Object


335
336
337
338
339
340
341
# File 'app/models/project.rb', line 335

def sort(method)
  if method == 'repository_size_desc'
    reorder(repository_size: :desc, id: :desc)
  else
    order_by(method)
  end
end

348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'app/models/project.rb', line 348

def trending(since = 1.month.ago)
  # By counting in the JOIN we don't expose the GROUP BY to the outer query.
  # This means that calls such as "any?" and "count" just return a number of
  # the total count, instead of the counts grouped per project as a Hash.
  join_body = "INNER JOIN (
    SELECT project_id, COUNT(*) AS amount
    FROM notes
    WHERE created_at >= #{sanitize(since)}
    GROUP BY project_id
  ) join_note_counts ON projects.id = join_note_counts.project_id"

  joins(join_body).reorder('join_note_counts.amount DESC')
end

.visibility_levelsObject


331
332
333
# File 'app/models/project.rb', line 331

def visibility_levels
  Gitlab::VisibilityLevel.options
end

.visible_to_user(user) ⇒ Object


362
363
364
# File 'app/models/project.rb', line 362

def visible_to_user(user)
  where(id: user.authorized_projects.select(:id).reorder(nil))
end

.with_pushObject


258
259
260
# File 'app/models/project.rb', line 258

def with_push
  joins(:events).where('events.action = ?', Event::PUSHED)
end

Instance Method Details

#add_import_jobObject


392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'app/models/project.rb', line 392

def add_import_job
  if forked?
    job_id = RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path)
  else
    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}"
  end
end

#allowed_to_share_with_group?Boolean


962
963
964
# File 'app/models/project.rb', line 962

def allowed_to_share_with_group?
  !namespace.share_with_group_lock
end

#any_runners?(&block) ⇒ Boolean


978
979
980
981
982
983
984
# File 'app/models/project.rb', line 978

def any_runners?(&block)
  if runners.active.any?(&block)
    return true
  end

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

#archive!Object


892
893
894
# File 'app/models/project.rb', line 892

def archive!
  update_attribute(:archived, true)
end

#avatar_in_gitObject


625
626
627
# File 'app/models/project.rb', line 625

def avatar_in_git
  repository.avatar
end

#avatar_typeObject


619
620
621
622
623
# File 'app/models/project.rb', line 619

def avatar_type
  unless self.avatar.image?
    self.errors.add :avatar, 'only images allowed'
  end
end

#avatar_urlObject


629
630
631
632
633
634
635
# File 'app/models/project.rb', line 629

def avatar_url
  if avatar.present?
    [gitlab_config.url, avatar.url].join
  elsif avatar_in_git
    Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
  end
end

#build_commit_note(commit) ⇒ Object


519
520
521
# File 'app/models/project.rb', line 519

def build_commit_note(commit)
  notes.new(commit_id: commit.id, noteable_type: 'Commit')
end

#build_coverage_enabled?Boolean


996
997
998
# File 'app/models/project.rb', line 996

def build_coverage_enabled?
  build_coverage_regex.present?
end

#build_missing_servicesObject


573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
# File 'app/models/project.rb', line 573

def build_missing_services
  services_templates = Service.where(template: true)

  Service.available_services_names.each do |service_name|
    service = find_service(services, service_name)

    # If service is available but missing in db
    if service.nil?
      # We should check if template for the service exists
      template = find_service(services_templates, service_name)

      if template.nil?
        # If no template, we should create an instance. Ex `create_gitlab_ci_service`
        self.send :"create_#{service_name}_service"
      else
        Service.create_from_template(self.id, template)
      end
    end
  end
end

#build_timeout_in_minutesObject


1000
1001
1002
# File 'app/models/project.rb', line 1000

def build_timeout_in_minutes
  build_timeout / 60
end

#build_timeout_in_minutes=(value) ⇒ Object


1004
1005
1006
# File 'app/models/project.rb', line 1004

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

#can_have_issues_tracker_id?Boolean


569
570
571
# File 'app/models/project.rb', line 569

def can_have_issues_tracker_id?
  self.issues_enabled && !self.default_issues_tracker?
end

#change_head(branch) ⇒ Object


900
901
902
903
904
905
906
907
# File 'app/models/project.rb', line 900

def change_head(branch)
  repository.before_change_head
  repository.rugged.references.create('HEAD',
                                      "refs/heads/#{branch}",
                                      force: true)
  repository.copy_gitattributes(branch)
  reload_default_branch
end

#check_limitObject


480
481
482
483
484
485
486
# File 'app/models/project.rb', line 480

def check_limit
  unless creator.can_create_project? or namespace.kind == 'group'
    self.errors.add(:limit_reached, "Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it")
  end
rescue
  self.errors.add(:base, "Can't check your ability to create project")
end

#ci_commit(sha, ref) ⇒ Object


966
967
968
# File 'app/models/project.rb', line 966

def ci_commit(sha, ref)
  ci_commits.order(id: :desc).find_by(sha: sha, ref: ref)
end

#ci_serviceObject


611
612
613
# File 'app/models/project.rb', line 611

def ci_service
  @ci_service ||= ci_services.reorder(nil).find_by(active: true)
end

#ci_servicesObject


607
608
609
# File 'app/models/project.rb', line 607

def ci_services
  services.where(category: :ci)
end

#clear_import_dataObject


406
407
408
409
410
411
412
# File 'app/models/project.rb', line 406

def clear_import_data
  update(import_error: nil)

  ProjectCacheWorker.perform_async(self.id)

  self.import_data.destroy if self.import_data
end

#codeObject

For compatibility with old code


638
639
640
# File 'app/models/project.rb', line 638

def code
  path
end

#commit(id = 'HEAD') ⇒ Object


375
376
377
# File 'app/models/project.rb', line 375

def commit(id = 'HEAD')
  repository.commit(id)
end

#create_labelsObject


594
595
596
597
598
599
600
601
# File 'app/models/project.rb', line 594

def create_labels
  Label.templates.each do |label|
    label = label.dup
    label.template = nil
    label.project_id = self.id
    label.save
  end
end

#create_or_update_import_data(data: nil, credentials: nil) ⇒ Object


429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'app/models/project.rb', line 429

def create_or_update_import_data(data: nil, credentials: nil)
  project_import_data = import_data || build_import_data
  if data
    project_import_data.data ||= {}
    project_import_data.data = project_import_data.data.merge(data)
  end
  if credentials
    project_import_data.credentials ||= {}
    project_import_data.credentials = project_import_data.credentials.merge(credentials)
  end

  project_import_data.save
end

#create_repositoryObject


933
934
935
936
937
938
939
940
941
942
943
944
# File 'app/models/project.rb', line 933

def create_repository
  # Forked import is handled asynchronously
  unless forked?
    if gitlab_shell.add_repository(path_with_namespace)
      repository.after_create
      true
    else
      errors.add(:base, 'Failed to create repository via gitlab-shell')
      false
    end
  end
end

#create_wikiObject


950
951
952
953
954
955
956
# File 'app/models/project.rb', line 950

def create_wiki
  ProjectWiki.new(self, self.owner).wiki
  true
rescue ProjectWiki::CouldNotCreateWikiError
  errors.add(:base, 'Failed create wiki')
  false
end

#default_branchObject


879
880
881
# File 'app/models/project.rb', line 879

def default_branch
  @default_branch ||= repository.root_ref if repository.exists?
end

#default_issue_trackerObject


547
548
549
# File 'app/models/project.rb', line 547

def default_issue_tracker
  gitlab_issue_tracker_service || create_gitlab_issue_tracker_service
end

#default_issues_tracker?Boolean


559
560
561
# File 'app/models/project.rb', line 559

def default_issues_tracker?
  !external_issue_tracker
end

#developers_can_push_to_protected_branch?(branch_name) ⇒ Boolean


771
772
773
# File 'app/models/project.rb', line 771

def developers_can_push_to_protected_branch?(branch_name)
  protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push }
end

#empty_repo?Boolean


718
719
720
# File 'app/models/project.rb', line 718

def empty_repo?
  !repository.exists? || !repository.has_visible_content?
end

#enable_ciObject


974
975
976
# File 'app/models/project.rb', line 974

def enable_ci
  self.builds_enabled = true
end

#ensure_ci_commit(sha, ref) ⇒ Object


970
971
972
# File 'app/models/project.rb', line 970

def ensure_ci_commit(sha, ref)
  ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
end

#execute_hooks(data, hooks_scope = :push_hooks) ⇒ Object


693
694
695
696
697
# File 'app/models/project.rb', line 693

def execute_hooks(data, hooks_scope = :push_hooks)
  hooks.send(hooks_scope).each do |hook|
    hook.async_execute(data, hooks_scope.to_s)
  end
end

#execute_services(data, hooks_scope = :push_hooks) ⇒ Object


699
700
701
702
703
704
# File 'app/models/project.rb', line 699

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|
    service.async_execute(data)
  end
end

#expire_caches_before_rename(old_path) ⇒ Object

Expires various caches before a project is renamed.


819
820
821
822
823
824
825
826
827
828
829
830
# File 'app/models/project.rb', line 819

def expire_caches_before_rename(old_path)
  repo = Repository.new(old_path, self)
  wiki = Repository.new("#{old_path}.wiki", self)

  if repo.exists?
    repo.before_delete
  end

  if wiki.exists?
    wiki.before_delete
  end
end

#external_import?Boolean


451
452
453
# File 'app/models/project.rb', line 451

def external_import?
  import_url.present?
end

#external_issue_trackerObject


563
564
565
566
567
# File 'app/models/project.rb', line 563

def external_issue_tracker
  return @external_issue_tracker if defined?(@external_issue_tracker)
  @external_issue_tracker ||=
    services.issue_trackers.active.without_defaults.first
end

#find_label(name) ⇒ Object


925
926
927
# File 'app/models/project.rb', line 925

def find_label(name)
  labels.find_by(name: name)
end

#find_service(list, name) ⇒ Object


603
604
605
# File 'app/models/project.rb', line 603

def find_service(list, name)
  list.find { |service| service.to_param == name }
end

#forked?Boolean


775
776
777
# File 'app/models/project.rb', line 775

def forked?
  !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
end

#forked_from?(project) ⇒ Boolean


909
910
911
# File 'app/models/project.rb', line 909

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

#forks_countObject


921
922
923
# File 'app/models/project.rb', line 921

def forks_count
  forks.count
end

#get_issue(issue_id) ⇒ Object


535
536
537
538
539
540
541
# File 'app/models/project.rb', line 535

def get_issue(issue_id)
  if default_issues_tracker?
    issues.find_by(iid: issue_id)
  else
    ExternalIssue.new(issue_id, self)
  end
end

#hook_attrs(backward: true) ⇒ Object


832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
# File 'app/models/project.rb', line 832

def hook_attrs(backward: true)
  attrs = {
    name: name,
    description: description,
    web_url: web_url,
    avatar_url: avatar_url,
    git_ssh_url: ssh_url_to_repo,
    git_http_url: http_url_to_repo,
    namespace: namespace.name,
    visibility_level: visibility_level,
    path_with_namespace: path_with_namespace,
    default_branch: default_branch,
  }

  # 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
end

#http_url_to_repoObject


762
763
764
# File 'app/models/project.rb', line 762

def http_url_to_repo
  "#{web_url}.git"
end

#import?Boolean


443
444
445
# File 'app/models/project.rb', line 443

def import?
  external_import? || forked?
end

#import_failed?Boolean


463
464
465
# File 'app/models/project.rb', line 463

def import_failed?
  import_status == 'failed'
end

#import_finished?Boolean


467
468
469
# File 'app/models/project.rb', line 467

def import_finished?
  import_status == 'finished'
end

#import_in_progress?Boolean


459
460
461
# File 'app/models/project.rb', line 459

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

#import_urlObject


420
421
422
423
424
425
426
427
# File 'app/models/project.rb', line 420

def import_url
  if import_data && super
    import_url = Gitlab::ImportUrl.new(super, credentials: import_data.credentials)
    import_url.full_url
  else
    super
  end
end

#import_url=(value) ⇒ Object


414
415
416
417
418
# File 'app/models/project.rb', line 414

def import_url=(value)
  import_url = Gitlab::ImportUrl.new(value)
  create_or_update_import_data(credentials: import_url.credentials)
  super(import_url.sanitized_url)
end

#imported?Boolean


455
456
457
# File 'app/models/project.rb', line 455

def imported?
  import_finished?
end

#issue_exists?(issue_id) ⇒ Boolean


543
544
545
# File 'app/models/project.rb', line 543

def issue_exists?(issue_id)
  get_issue(issue_id)
end

#issues_trackerObject


551
552
553
554
555
556
557
# File 'app/models/project.rb', line 551

def issues_tracker
  if external_issue_tracker
    external_issue_tracker
  else
    default_issue_tracker
  end
end

#items_for(entity) ⇒ Object


642
643
644
645
646
647
648
649
# File 'app/models/project.rb', line 642

def items_for(entity)
  case entity
  when 'issue' then
    issues
  when 'merge_request' then
    merge_requests
  end
end

#jira_tracker?Boolean


615
616
617
# File 'app/models/project.rb', line 615

def jira_tracker?
  issues_tracker.to_param == 'jira'
end

#jira_tracker_active?Boolean


958
959
960
# File 'app/models/project.rb', line 958

def jira_tracker_active?
  jira_tracker? && jira_service.active
end

#last_activityObject


523
524
525
# File 'app/models/project.rb', line 523

def last_activity
  last_event
end

#last_activity_dateObject


527
528
529
# File 'app/models/project.rb', line 527

def last_activity_date
  last_activity_at || updated_at
end

#merge_base_commit(first_commit_id, second_commit_id) ⇒ Object


379
380
381
382
# File 'app/models/project.rb', line 379

def merge_base_commit(first_commit_id, second_commit_id)
  sha = repository.merge_base(first_commit_id, second_commit_id)
  repository.commit(sha) if sha
end

#name_with_namespaceObject


675
676
677
678
679
680
681
682
683
# File 'app/models/project.rb', line 675

def name_with_namespace
  @name_with_namespace ||= begin
                             if namespace
                               namespace.human_name + ' / ' + name
                             else
                               name
                             end
                           end
end

#namespace_dirObject


730
731
732
# File 'app/models/project.rb', line 730

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

#no_import?Boolean


447
448
449
# File 'app/models/project.rb', line 447

def no_import?
  import_status == 'none'
end

#open_branchesObject


740
741
742
743
744
745
746
747
748
# File 'app/models/project.rb', line 740

def open_branches
  # We're using a Set here as checking values in a large Set is faster than
  # checking values in a large Array.
  protected_set = Set.new(protected_branch_names)

  repository.branches.reject do |branch|
    protected_set.include?(branch.name)
  end
end

#open_issues_countObject


1008
1009
1010
# File 'app/models/project.rb', line 1008

def open_issues_count
  issues.opened.count
end

#origin_merge_requestsObject


929
930
931
# File 'app/models/project.rb', line 929

def origin_merge_requests
  merge_requests.where(source_project_id: self.id)
end

#ownerObject


657
658
659
660
661
662
663
# File 'app/models/project.rb', line 657

def owner
  if group
    group
  else
    namespace.try(:owner)
  end
end

#path_with_namespaceObject


685
686
687
688
689
690
691
# File 'app/models/project.rb', line 685

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

#personal?Boolean


779
780
781
# File 'app/models/project.rb', line 779

def personal?
  !group
end

#project_idObject


531
532
533
# File 'app/models/project.rb', line 531

def project_id
  self.id
end

#project_member(user) ⇒ Object


875
876
877
# File 'app/models/project.rb', line 875

def project_member(user)
  project_members.find_by(user_id: user)
end

#project_member_by_id(user_id) ⇒ Object

Get Team Member record by user id


671
672
673
# File 'app/models/project.rb', line 671

def project_member_by_id(user_id)
  project_members.find_by(user_id: user_id)
end

#project_member_by_name_or_email(name = nil, email = nil) ⇒ Object


665
666
667
668
# File 'app/models/project.rb', line 665

def project_member_by_name_or_email(name = nil, email = nil)
  user = users.find_by('name like ? or email like ?', name, email)
  project_members.where(user: user) if user
end

#protected_branch?(branch_name) ⇒ Boolean

Check if current branch name is marked as protected in the system


767
768
769
# File 'app/models/project.rb', line 767

def protected_branch?(branch_name)
  protected_branch_names.include?(branch_name)
end

#protected_branch_namesObject


750
751
752
# File 'app/models/project.rb', line 750

def protected_branch_names
  @protected_branch_names ||= protected_branches.pluck(:name)
end

#reload_default_branchObject


883
884
885
886
# File 'app/models/project.rb', line 883

def reload_default_branch
  @default_branch = nil
  default_branch
end

#rename_repoObject


783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'app/models/project.rb', line 783

def rename_repo
  path_was = previous_changes['path'].first
  old_path_with_namespace = File.join(namespace_dir, path_was)
  new_path_with_namespace = File.join(namespace_dir, path)

  expire_caches_before_rename(old_path_with_namespace)

  if gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
    # If repository moved successfully we need to send update instructions to users.
    # However we cannot allow rollback since we moved repository
    # So we basically we mute exceptions in next actions
    begin
      gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
      send_move_instructions(old_path_with_namespace)
      reset_events_cache

      @old_path_with_namespace = old_path_with_namespace

      SystemHooksService.new.execute_hooks_for(self, :rename)

      @repository = nil
    rescue
      # Returning false does not rollback after_* transaction but gives
      # us information about failing some of tasks
      false
    end
  else
    # 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

  Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path)
end

#repoObject


722
723
724
# File 'app/models/project.rb', line 722

def repo
  repository.raw
end

#repo_exists?Boolean


734
735
736
737
738
# File 'app/models/project.rb', line 734

def repo_exists?
  @repo_exists ||= repository.exists?
rescue
  @repo_exists = false
end

#repositoryObject


371
372
373
# File 'app/models/project.rb', line 371

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

#repository_exists?Boolean


946
947
948
# File 'app/models/project.rb', line 946

def repository_exists?
  !!repository.exists?
end

#reset_events_cacheObject

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

  • when the project avatar changes

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.


869
870
871
872
873
# File 'app/models/project.rb', line 869

def reset_events_cache
  Event.where(project_id: self.id).
    order('id DESC').limit(100).
    update_all(updated_at: Time.now)
end

#root_ref?(branch) ⇒ Boolean


754
755
756
# File 'app/models/project.rb', line 754

def root_ref?(branch)
  repository.root_ref == branch
end

#runners_tokenObject


1033
1034
1035
# File 'app/models/project.rb', line 1033

def runners_token
  ensure_runners_token!
end

#safe_import_urlObject


471
472
473
474
475
476
477
478
# File 'app/models/project.rb', line 471

def safe_import_url
  result = URI.parse(self.import_url)
  result.password = '*****' unless result.password.nil?
  result.user = '*****' unless result.user.nil? || result.user == "git" #tokens or other data may be saved as user
  result.to_s
rescue
  self.import_url
end

#saved?Boolean


384
385
386
# File 'app/models/project.rb', line 384

def saved?
  id && persisted?
end

#schedule_add_import_jobObject


388
389
390
# File 'app/models/project.rb', line 388

def schedule_add_import_job
  run_after_commit(:add_import_job)
end

#schedule_delete!(user_id, params) ⇒ Object


1041
1042
1043
1044
1045
1046
# File 'app/models/project.rb', line 1041

def schedule_delete!(user_id, params)
  # Queue this task for after the commit, so once we mark pending_delete it will run
  run_after_commit { ProjectDestroyWorker.perform_async(id, user_id, params) }

  update_attribute(:pending_delete, true)
end

#send_move_instructions(old_path_with_namespace) ⇒ Object


651
652
653
654
655
# File 'app/models/project.rb', line 651

def send_move_instructions(old_path_with_namespace)
  # 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) }
end

#set_last_activity_atObject


74
75
76
# File 'app/models/project.rb', line 74

def set_last_activity_at
  update_column(:last_activity_at, self.created_at)
end

#ssh_url_to_repoObject


758
759
760
# File 'app/models/project.rb', line 758

def ssh_url_to_repo
  url_to_repo
end

#teamObject


367
368
369
# File 'app/models/project.rb', line 367

def team
  @team ||= ProjectTeam.new(self)
end

#to_paramObject


503
504
505
# File 'app/models/project.rb', line 503

def to_param
  path
end

#to_reference(_from_project = nil) ⇒ Object


507
508
509
# File 'app/models/project.rb', line 507

def to_reference(_from_project = nil)
  path_with_namespace
end

#unarchive!Object


896
897
898
# File 'app/models/project.rb', line 896

def unarchive!
  update_attribute(:archived, false)
end

#update_commit_countObject


917
918
919
# File 'app/models/project.rb', line 917

def update_commit_count
  update_attribute(:commit_count, repository.commit_count)
end

#update_forks_visibility_levelObject


80
81
82
83
84
85
86
87
88
89
# File 'app/models/project.rb', line 80

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

#update_merge_requests(oldrev, newrev, ref, user) ⇒ Object


706
707
708
709
# File 'app/models/project.rb', line 706

def update_merge_requests(oldrev, newrev, ref, user)
  MergeRequests::RefreshService.new(self, user).
    execute(oldrev, newrev, ref)
end

#update_repository_sizeObject


913
914
915
# File 'app/models/project.rb', line 913

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

#url_to_repoObject


726
727
728
# File 'app/models/project.rb', line 726

def url_to_repo
  gitlab_shell.url_to_repo(path_with_namespace)
end

#valid_build_token?(token) ⇒ Boolean

TODO (ayufan): For now we use runners_token (backward compatibility) In 8.4 every build will have its own individual token valid for time of build


992
993
994
# File 'app/models/project.rb', line 992

def valid_build_token? token
  self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end

#valid_repo?Boolean


711
712
713
714
715
716
# File 'app/models/project.rb', line 711

def valid_repo?
  repository.exists?
rescue
  errors.add(:path, 'Invalid repository path')
  false
end

#valid_runners_token?(token) ⇒ Boolean


986
987
988
# File 'app/models/project.rb', line 986

def valid_runners_token? token
  self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end

#visibility_level_allowed?(level = self.visibility_level) ⇒ Boolean


1029
1030
1031
# File 'app/models/project.rb', line 1029

def visibility_level_allowed?(level = self.visibility_level)
  visibility_level_allowed_as_fork?(level) && visibility_level_allowed_by_group?(level)
end

#visibility_level_allowed_as_forkObject


496
497
498
499
500
501
# File 'app/models/project.rb', line 496

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.")
end

#visibility_level_allowed_as_fork?(level = self.visibility_level) ⇒ Boolean


1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
# File 'app/models/project.rb', line 1012

def visibility_level_allowed_as_fork?(level = self.visibility_level)
  return true unless forked?

  # 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
end

#visibility_level_allowed_by_groupObject


488
489
490
491
492
493
494
# File 'app/models/project.rb', line 488

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

#visibility_level_allowed_by_group?(level = self.visibility_level) ⇒ Boolean


1023
1024
1025
1026
1027
# File 'app/models/project.rb', line 1023

def visibility_level_allowed_by_group?(level = self.visibility_level)
  return true unless group

  level <= group.visibility_level
end

#visibility_level_fieldObject


888
889
890
# File 'app/models/project.rb', line 888

def visibility_level_field
  visibility_level
end

#web_urlObject


511
512
513
# File 'app/models/project.rb', line 511

def web_url
  Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
end

#web_url_without_protocolObject


515
516
517
# File 'app/models/project.rb', line 515

def web_url_without_protocol
  web_url.split('://')[1]
end

#wikiObject


1037
1038
1039
# File 'app/models/project.rb', line 1037

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