summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'app/models')
-rw-r--r--app/models/ability.rb5
-rw-r--r--app/models/blob.rb3
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/concerns/milestoneish.rb25
-rw-r--r--app/models/global_label.rb7
-rw-r--r--app/models/global_milestone.rb55
-rw-r--r--app/models/label.rb44
-rw-r--r--app/models/merge_request.rb25
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/milestone.rb27
-rw-r--r--app/models/personal_snippet.rb1
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_snippet.rb3
-rw-r--r--app/models/repository.rb55
-rw-r--r--app/models/snippet.rb7
15 files changed, 159 insertions, 105 deletions
diff --git a/app/models/ability.rb b/app/models/ability.rb
index f34554d557c..fe9e0aab717 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -9,6 +9,7 @@ class Ability
when CommitStatus then commit_status_abilities(user, subject)
when Project then project_abilities(user, subject)
when Issue then issue_abilities(user, subject)
+ when ExternalIssue then external_issue_abilities(user, subject)
when Note then note_abilities(user, subject)
when ProjectSnippet then project_snippet_abilities(user, subject)
when PersonalSnippet then personal_snippet_abilities(user, subject)
@@ -424,6 +425,10 @@ class Ability
end
end
+ def external_issue_abilities(user, subject)
+ project_abilities(user, subject.project)
+ end
+
private
def named_abilities(name)
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 8ee9f3006b2..72e6c5fa3fd 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -1,5 +1,8 @@
# Blob is a Rails-specific wrapper around Gitlab::Git::Blob objects
class Blob < SimpleDelegator
+ CACHE_TIME = 60 # Cache raw blobs referred to by a (mutable) ref for 1 minute
+ CACHE_TIME_IMMUTABLE = 3600 # Cache blobs referred to by an immutable reference for 1 hour
+
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
#
# This method prevents the decorated object from evaluating to "truthy" when
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 286d6655861..27b97944e38 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -29,12 +29,15 @@ module Issuable
scope :assigned, -> { where("assignee_id IS NOT NULL") }
scope :unassigned, -> { where("assignee_id IS NULL") }
scope :of_projects, ->(ids) { where(project_id: ids) }
+ scope :of_milestones, ->(ids) { where(milestone_id: ids) }
scope :opened, -> { with_state(:opened, :reopened) }
scope :only_opened, -> { with_state(:opened) }
scope :only_reopened, -> { with_state(:reopened) }
scope :closed, -> { with_state(:closed) }
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
+ scope :with_label, ->(title) { joins(:labels).where(labels: { title: title }) }
+ scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
scope :references_project, -> { references(:project) }
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
new file mode 100644
index 00000000000..d67df7c1d9c
--- /dev/null
+++ b/app/models/concerns/milestoneish.rb
@@ -0,0 +1,25 @@
+module Milestoneish
+ def closed_items_count
+ issues.closed.size + merge_requests.closed_and_merged.size
+ end
+
+ def total_items_count
+ issues.size + merge_requests.size
+ end
+
+ def complete?
+ total_items_count == closed_items_count
+ end
+
+ def percent_complete
+ ((closed_items_count * 100) / total_items_count).abs
+ rescue ZeroDivisionError
+ 0
+ end
+
+ def remaining_days
+ return 0 if !due_date || expired?
+
+ (due_date - Date.today).to_i
+ end
+end
diff --git a/app/models/global_label.rb b/app/models/global_label.rb
index 0171f7d54b7..ddd4bad5c21 100644
--- a/app/models/global_label.rb
+++ b/app/models/global_label.rb
@@ -2,16 +2,19 @@ class GlobalLabel
attr_accessor :title, :labels
alias_attribute :name, :title
+ delegate :color, :description, to: :@first_label
+
def self.build_collection(labels)
labels = labels.group_by(&:title)
- labels.map do |title, label|
- new(title, label)
+ labels.map do |title, labels|
+ new(title, labels)
end
end
def initialize(title, labels)
@title = title
@labels = labels
+ @first_label = labels.find { |lbl| lbl.description.present? } || labels.first
end
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 7ee276255a0..97bd79af083 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -1,4 +1,6 @@
class GlobalMilestone
+ include Milestoneish
+
attr_accessor :title, :milestones
alias_attribute :name, :title
@@ -28,33 +30,7 @@ class GlobalMilestone
end
def projects
- milestones.map { |milestone| milestone.project }
- end
-
- def issue_count
- milestones.map { |milestone| milestone.issues.count }.sum
- end
-
- def merge_requests_count
- milestones.map { |milestone| milestone.merge_requests.count }.sum
- end
-
- def open_items_count
- milestones.map { |milestone| milestone.open_items_count }.sum
- end
-
- def closed_items_count
- milestones.map { |milestone| milestone.closed_items_count }.sum
- end
-
- def total_items_count
- milestones.map { |milestone| milestone.total_items_count }.sum
- end
-
- def percent_complete
- ((closed_items_count * 100) / total_items_count).abs
- rescue ZeroDivisionError
- 0
+ @projects ||= Project.for_milestones(milestones.map(&:id))
end
def state
@@ -76,35 +52,20 @@ class GlobalMilestone
end
def issues
- @issues ||= milestones.map(&:issues).flatten.group_by(&:state)
+ @issues ||= Issue.of_milestones(milestones.map(&:id)).includes(:project)
end
def merge_requests
- @merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state)
+ @merge_requests ||= MergeRequest.of_milestones(milestones.map(&:id)).includes(:target_project)
end
def participants
@participants ||= milestones.map(&:participants).flatten.compact.uniq
end
- def opened_issues
- issues.values_at("opened", "reopened").compact.flatten
- end
-
- def closed_issues
- issues['closed']
- end
-
- def opened_merge_requests
- merge_requests.values_at("opened", "reopened").compact.flatten
- end
-
- def closed_merge_requests
- merge_requests.values_at("closed", "merged", "locked").compact.flatten
- end
-
- def complete?
- total_items_count == closed_items_count
+ def labels
+ @labels ||= GlobalLabel.build_collection(milestones.map(&:labels).flatten)
+ .sort_by!(&:title)
end
def due_date
diff --git a/app/models/label.rb b/app/models/label.rb
index c34f4e4ba60..5ff644b8426 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -48,10 +48,15 @@ class Label < ActiveRecord::Base
'~'
end
+ ##
# Pattern used to extract label references from text
+ #
+ # This pattern supports cross-project references.
+ #
def self.reference_pattern
%r{
- #{reference_prefix}
+ (#{Project.reference_pattern})?
+ #{Regexp.escape(reference_prefix)}
(?:
(?<label_id>\d+) | # Integer-based label ID, or
(?<label_name>
@@ -62,24 +67,31 @@ class Label < ActiveRecord::Base
}x
end
+ def self.link_reference_pattern
+ nil
+ end
+
+ ##
# Returns the String necessary to reference this Label in Markdown
#
# format - Symbol format to use (default: :id, optional: :name)
#
- # Note that its argument differs from other objects implementing Referable. If
- # a non-Symbol argument is given (such as a Project), it will default to :id.
- #
# Examples:
#
- # Label.first.to_reference # => "~1"
- # Label.first.to_reference(:name) # => "~\"bug\""
+ # Label.first.to_reference # => "~1"
+ # Label.first.to_reference(format: :name) # => "~\"bug\""
+ # Label.first.to_reference(project) # => "gitlab-org/gitlab-ce~1"
#
# Returns a String
- def to_reference(format = :id)
- if format == :name && !name.include?('"')
- %(#{self.class.reference_prefix}"#{name}")
+ #
+ def to_reference(from_project = nil, format: :id)
+ format_reference = label_format_reference(format)
+ reference = "#{self.class.reference_prefix}#{format_reference}"
+
+ if cross_project_reference?(from_project)
+ project.to_reference + reference
else
- "#{self.class.reference_prefix}#{id}"
+ reference
end
end
@@ -98,4 +110,16 @@ class Label < ActiveRecord::Base
def template?
template
end
+
+ private
+
+ def label_format_reference(format = :id)
+ raise StandardError, 'Unknown format' unless [:id, :name].include?(format)
+
+ if format == :name && !name.include?('"')
+ %("#{name}")
+ else
+ id
+ end
+ end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index cf26183c254..c1e18bb3cc5 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -137,9 +137,7 @@ class MergeRequest < ActiveRecord::Base
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
- scope :opened, -> { with_states(:opened, :reopened) }
scope :merged, -> { with_state(:merged) }
- scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
scope :join_project, -> { joins(:target_project) }
@@ -537,6 +535,29 @@ class MergeRequest < ActiveRecord::Base
end
end
+ def diverged_commits_count
+ cache = Rails.cache.read(:"merge_request_#{id}_diverged_commits")
+
+ if cache.blank? || cache[:source_sha] != source_sha || cache[:target_sha] != target_sha
+ cache = {
+ source_sha: source_sha,
+ target_sha: target_sha,
+ diverged_commits_count: compute_diverged_commits_count
+ }
+ Rails.cache.write(:"merge_request_#{id}_diverged_commits", cache)
+ end
+
+ cache[:diverged_commits_count]
+ end
+
+ def compute_diverged_commits_count
+ Gitlab::Git::Commit.between(target_project.repository.raw_repository, source_sha, target_sha).size
+ end
+
+ def diverged_from_target_branch?
+ diverged_commits_count > 0
+ end
+
def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index df08d3a6dfb..33884118595 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -17,7 +17,7 @@ class MergeRequestDiff < ActiveRecord::Base
include Sortable
# Prevent store of diff if commits amount more then 500
- COMMITS_SAFE_SIZE = 500
+ COMMITS_SAFE_SIZE = 100
belongs_to :merge_request
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 7dc2f909b2f..e3969f32dd6 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -24,12 +24,13 @@ class Milestone < ActiveRecord::Base
include Sortable
include Referable
include StripAttribute
+ include Milestoneish
belongs_to :project
has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
- has_many :participants, through: :issues, source: :assignee
+ has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
@@ -92,30 +93,6 @@ class Milestone < ActiveRecord::Base
end
end
- def open_items_count
- self.issues.opened.count + self.merge_requests.opened.count
- end
-
- def closed_items_count
- self.issues.closed.count + self.merge_requests.closed_and_merged.count
- end
-
- def total_items_count
- self.issues.count + self.merge_requests.count
- end
-
- def percent_complete
- ((closed_items_count * 100) / total_items_count).abs
- rescue ZeroDivisionError
- 0
- end
-
- def remaining_days
- return 0 if !due_date || expired?
-
- (due_date - Date.today).to_i
- end
-
def expires_at
if due_date
if due_date.past?
diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb
index 9cee3b70cb3..452f3913eef 100644
--- a/app/models/personal_snippet.rb
+++ b/app/models/personal_snippet.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
diff --git a/app/models/project.rb b/app/models/project.rb
index 148eab692ff..426464dee81 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -151,6 +151,7 @@ class Project < ActiveRecord::Base
has_many :releases, dependent: :destroy
has_many :lfs_objects_projects, dependent: :destroy
has_many :lfs_objects, through: :lfs_objects_projects
+ has_many :todos, dependent: :destroy
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
@@ -215,6 +216,7 @@ class Project < ActiveRecord::Base
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
scope :non_archived, -> { where(archived: false) }
+ scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
state_machine :import_status, initial: :none do
event :import_start do
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 9e2c1b0e18e..1f7d85a5f3d 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
@@ -23,6 +22,4 @@ class ProjectSnippet < Snippet
# Scopes
scope :fresh, -> { order("created_at DESC") }
- scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
- scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c135ab61f6a..6441cd87e87 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -133,18 +133,18 @@ class Repository
rugged.branches.create(branch_name, target)
end
- expire_branches_cache
+ after_create_branch
find_branch(branch_name)
end
def add_tag(tag_name, ref, message = nil)
- expire_tags_cache
+ before_push_tag
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
def rm_branch(user, branch_name)
- expire_branches_cache
+ before_remove_branch
branch = find_branch(branch_name)
oldrev = branch.try(:target)
@@ -155,12 +155,12 @@ class Repository
rugged.branches.delete(branch_name)
end
- expire_branches_cache
+ after_remove_branch
true
end
def rm_tag(tag_name)
- expire_tags_cache
+ before_remove_tag
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
@@ -183,6 +183,14 @@ class Repository
end
end
+ def branch_count
+ @branch_count ||= cache.fetch(:branch_count) { raw_repository.branch_count }
+ end
+
+ def tag_count
+ @tag_count ||= cache.fetch(:tag_count) { raw_repository.rugged.tags.count }
+ end
+
# Return repo size in megabytes
# Cached in redis
def size
@@ -278,6 +286,16 @@ class Repository
@has_visible_content = nil
end
+ def expire_branch_count_cache
+ cache.expire(:branch_count)
+ @branch_count = nil
+ end
+
+ def expire_tag_count_cache
+ cache.expire(:tag_count)
+ @tag_count = nil
+ end
+
def rebuild_cache
cache_keys.each do |key|
cache.expire(key)
@@ -313,9 +331,17 @@ class Repository
expire_root_ref_cache
end
- # Runs code before creating a new tag.
- def before_create_tag
+ # Runs code before pushing (= creating or removing) a tag.
+ def before_push_tag
expire_cache
+ expire_tags_cache
+ expire_tag_count_cache
+ end
+
+ # Runs code before removing a tag.
+ def before_remove_tag
+ expire_tags_cache
+ expire_tag_count_cache
end
# Runs code after a repository has been forked/imported.
@@ -330,12 +356,21 @@ class Repository
# Runs code after a new branch has been created.
def after_create_branch
+ expire_branches_cache
expire_has_visible_content_cache
+ expire_branch_count_cache
+ end
+
+ # Runs code before removing an existing branch.
+ def before_remove_branch
+ expire_branches_cache
end
# Runs code after an existing branch has been removed.
def after_remove_branch
expire_has_visible_content_cache
+ expire_branch_count_cache
+ expire_branches_cache
end
def method_missing(m, *args, &block)
@@ -812,6 +847,12 @@ class Repository
raw_repository.ls_files(actual_ref)
end
+ def main_language
+ unless empty?
+ Linguist::Repository.new(rugged, rugged.head.target_id).language
+ end
+ end
+
private
def cache
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index f876be7a4c8..dd3925c7a7d 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -10,7 +10,6 @@
# created_at :datetime
# updated_at :datetime
# file_name :string(255)
-# expires_at :datetime
# type :string(255)
# visibility_level :integer default(0), not null
#
@@ -46,8 +45,6 @@ class Snippet < ActiveRecord::Base
scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") }
- scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) }
- scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) }
participant :author, :notes
@@ -111,10 +108,6 @@ class Snippet < ActiveRecord::Base
nil
end
- def expired?
- expires_at && expires_at < Time.current
- end
-
def visibility_level_field
visibility_level
end