summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2015-10-15 11:26:58 +0200
committerDouwe Maan <douwe@gitlab.com>2015-10-15 11:26:58 +0200
commit95f0440a7823a927ebba1557b091c12255e49ac4 (patch)
treefa6d130f2baff18e7a70d74dafe79e20da35babe /app/models
parent4a5b77188ec7525d1c3a1ee925c8791f841b040c (diff)
parent123669a55107514798ba531ba3a744b3ec8503ee (diff)
downloadgitlab-ce-95f0440a7823a927ebba1557b091c12255e49ac4.tar.gz
Merge branch 'master' into rs-redactor-filter
Diffstat (limited to 'app/models')
-rw-r--r--app/models/ability.rb2
-rw-r--r--app/models/ci/build.rb120
-rw-r--r--app/models/ci/commit.rb94
-rw-r--r--app/models/ci/project.rb6
-rw-r--r--app/models/ci/runner.rb17
-rw-r--r--app/models/commit.rb8
-rw-r--r--app/models/commit_status.rb95
-rw-r--r--app/models/concerns/case_sensitivity.rb28
-rw-r--r--app/models/concerns/mentionable.rb43
-rw-r--r--app/models/concerns/participable.rb16
-rw-r--r--app/models/generic_commit_status.rb15
-rw-r--r--app/models/group_milestone.rb2
-rw-r--r--app/models/label.rb5
-rw-r--r--app/models/merge_request.rb5
-rw-r--r--app/models/milestone.rb6
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/models/note.rb13
-rw-r--r--app/models/project.rb36
-rw-r--r--app/models/project_services/bamboo_service.rb7
-rw-r--r--app/models/project_services/ci/hip_chat_service.rb2
-rw-r--r--app/models/project_services/ci/mail_service.rb2
-rw-r--r--app/models/project_services/ci/slack_message.rb2
-rw-r--r--app/models/project_services/ci/slack_service.rb2
-rw-r--r--app/models/project_services/teamcity_service.rb7
-rw-r--r--app/models/repository.rb29
-rw-r--r--app/models/service.rb9
26 files changed, 384 insertions, 189 deletions
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a020b24a550..77c121ca5e8 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -135,6 +135,8 @@ class Ability
def project_report_rules
project_guest_rules + [
+ :create_commit_status,
+ :read_commit_statuses,
:download_code,
:fork_project,
:create_project_snippet,
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5d17f4418ed..5f8d44148ca 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -24,32 +24,19 @@
#
module Ci
- class Build < ActiveRecord::Base
- extend Ci::Model
-
+ class Build < CommitStatus
LAZY_ATTRIBUTES = ['trace']
- belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
- belongs_to :user
serialize :options
- validates :commit, presence: true
- validates :status, presence: true
validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref
- scope :running, ->() { where(status: "running") }
- scope :pending, ->() { where(status: "pending") }
- scope :success, ->() { where(status: "success") }
- scope :failed, ->() { where(status: "failed") }
scope :unstarted, ->() { where(runner_id: nil) }
- scope :running_or_pending, ->() { where(status:[:running, :pending]) }
- scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) }
scope :ignore_failures, ->() { where(allow_failure: false) }
- scope :for_ref, ->(ref) { where(ref: ref) }
scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
acts_as_taggable
@@ -74,13 +61,14 @@ module Ci
def create_from(build)
new_build = build.dup
- new_build.status = :pending
+ new_build.status = 'pending'
new_build.runner_id = nil
+ new_build.trigger_request_id = nil
new_build.save
end
def retry(build)
- new_build = Ci::Build.new(status: :pending)
+ new_build = Ci::Build.new(status: 'pending')
new_build.ref = build.ref
new_build.tag = build.tag
new_build.options = build.options
@@ -98,28 +86,7 @@ module Ci
end
state_machine :status, initial: :pending do
- event :run do
- transition pending: :running
- end
-
- event :drop do
- transition running: :failed
- end
-
- event :success do
- transition running: :success
- end
-
- event :cancel do
- transition [:pending, :running] => :canceled
- end
-
- after_transition pending: :running do |build, transition|
- build.update_attributes started_at: Time.now
- end
-
after_transition any => [:success, :failed, :canceled] do |build, transition|
- build.update_attributes finished_at: Time.now
project = build.project
if project.web_hooks?
@@ -136,19 +103,10 @@ module Ci
build.update_coverage
end
end
-
- state :pending, value: 'pending'
- state :running, value: 'running'
- state :failed, value: 'failed'
- state :success, value: 'success'
- state :canceled, value: 'canceled'
end
- delegate :sha, :short_sha, :project, :gl_project,
- to: :commit, prefix: false
-
- def before_sha
- Gitlab::Git::BLANK_SHA
+ def ignored?
+ failed? && allow_failure?
end
def trace_html
@@ -156,36 +114,12 @@ module Ci
html || ''
end
- def started?
- !pending? && !canceled? && started_at
- end
-
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
- end
-
- def ignored?
- failed? && allow_failure?
- end
-
def timeout
project.timeout
end
def variables
- yaml_variables + project_variables + trigger_variables
- end
-
- def duration
- if started_at && finished_at
- finished_at - started_at
- elsif started_at
- Time.now - started_at
- end
+ predefined_variables + yaml_variables + project_variables + trigger_variables
end
def project
@@ -278,6 +212,37 @@ module Ci
"#{dir_to_trace}/#{id}.log"
end
+ def target_url
+ Gitlab::Application.routes.url_helpers.
+ namespace_project_build_url(gl_project.namespace, gl_project, self)
+ end
+
+ def cancel_url
+ if active?
+ Gitlab::Application.routes.url_helpers.
+ cancel_namespace_project_build_path(gl_project.namespace, gl_project, self)
+ end
+ end
+
+ def retry_url
+ if commands.present?
+ Gitlab::Application.routes.url_helpers.
+ retry_namespace_project_build_path(gl_project.namespace, gl_project, self)
+ end
+ end
+
+ def can_be_served?(runner)
+ (tag_list - runner.tag_list).empty?
+ end
+
+ def any_runners_online?
+ project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
+ end
+
+ def show_warning?
+ pending? && !any_runners_online?
+ end
+
private
def yaml_variables
@@ -305,5 +270,14 @@ module Ci
[]
end
end
+
+ def predefined_variables
+ variables = []
+ variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag?
+ variables << { key: :CI_BUILD_NAME, value: name, public: true }
+ variables << { key: :CI_BUILD_STAGE, value: stage, public: true }
+ variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request
+ variables
+ end
end
end
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index fde754a92a1..68864edfbbf 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -20,7 +20,8 @@ module Ci
extend Ci::Model
belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
- has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
+ has_many :statuses, dependent: :destroy, class_name: 'CommitStatus'
+ has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :sha
@@ -47,7 +48,7 @@ module Ci
end
def retry
- builds_without_retry.each do |build|
+ latest_builds.each do |build|
Ci::Build.retry(build)
end
end
@@ -81,12 +82,11 @@ module Ci
end
def stage
- running_or_pending = builds_without_retry.running_or_pending
- running_or_pending.limit(1).pluck(:stage).first
+ running_or_pending = statuses.latest.running_or_pending.ordered
+ running_or_pending.first.try(:stage)
end
def create_builds(ref, tag, user, trigger_request = nil)
- return if skip_ci? && trigger_request.blank?
return unless config_processor
config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
@@ -94,7 +94,6 @@ module Ci
end
def create_next_builds(ref, tag, user, trigger_request)
- return if skip_ci? && trigger_request.blank?
return unless config_processor
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
@@ -107,61 +106,60 @@ module Ci
end
def refs
- builds.group(:ref).pluck(:ref)
+ statuses.order(:ref).pluck(:ref).uniq
end
- def last_ref
- builds.latest.first.try(:ref)
+ def latest_statuses
+ @latest_statuses ||= statuses.latest.to_a
end
- def builds_without_retry
- builds.latest
+ def latest_builds
+ @latest_builds ||= builds.latest.to_a
end
- def builds_without_retry_for_ref(ref)
- builds.for_ref(ref).latest
+ def latest_builds_for_ref(ref)
+ latest_builds.select { |build| build.ref == ref }
end
- def retried_builds
- @retried_builds ||= (builds.order(id: :desc) - builds_without_retry)
+ def retried
+ @retried ||= (statuses.order(id: :desc) - statuses.latest)
end
def status
- if skip_ci?
- return 'skipped'
- elsif yaml_errors.present?
+ if yaml_errors.present?
return 'failed'
- elsif builds.none?
- return 'skipped'
- elsif success?
- 'success'
- elsif pending?
- 'pending'
- elsif running?
- 'running'
- elsif canceled?
- 'canceled'
- else
- 'failed'
+ end
+
+ @status ||= begin
+ latest = latest_statuses
+ latest.reject! { |status| status.try(&:allow_failure?) }
+
+ if latest.none?
+ 'skipped'
+ elsif latest.all?(&:success?)
+ 'success'
+ elsif latest.all?(&:pending?)
+ 'pending'
+ elsif latest.any?(&:running?) || latest.any?(&:pending?)
+ 'running'
+ elsif latest.all?(&:canceled?)
+ 'canceled'
+ else
+ 'failed'
+ end
end
end
def pending?
- builds_without_retry.all? do |build|
- build.pending?
- end
+ status == 'pending'
end
def running?
- builds_without_retry.any? do |build|
- build.running? || build.pending?
- end
+ status == 'running'
end
def success?
- builds_without_retry.all? do |build|
- build.success? || build.ignored?
- end
+ status == 'success'
end
def failed?
@@ -169,26 +167,21 @@ module Ci
end
def canceled?
- builds_without_retry.all? do |build|
- build.canceled?
- end
+ status == 'canceled'
end
def duration
- @duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
- end
-
- def duration_for_ref(ref)
- builds_without_retry_for_ref(ref).select(&:duration).sum(&:duration).to_i
+ duration_array = latest_statuses.map(&:duration).compact
+ duration_array.reduce(:+).to_i
end
def finished_at
- @finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
+ @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end
def coverage
if project.coverage_enabled?
- coverage_array = builds_without_retry.map(&:coverage).compact
+ coverage_array = latest_builds.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
@@ -196,7 +189,7 @@ module Ci
end
def matrix_for_ref?(ref)
- builds_without_retry_for_ref(ref).pluck(:id).size > 1
+ latest_builds_for_ref(ref).size > 1
end
def config_processor
@@ -217,7 +210,6 @@ module Ci
end
def skip_ci?
- return false if builds.any?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end
diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb
index 88ba933a434..ef28353a30c 100644
--- a/app/models/ci/project.rb
+++ b/app/models/ci/project.rb
@@ -115,12 +115,12 @@ module Ci
web_url
end
- def any_runners?
- if runners.active.any?
+ def any_runners?(&block)
+ if runners.active.any?(&block)
return true
end
- shared_runners_enabled && Ci::Runner.shared.active.any?
+ shared_runners_enabled && Ci::Runner.shared.active.any?(&block)
end
def set_default_values
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 6838ccfaaab..02a3e9db1fa 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -20,6 +20,8 @@
module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
+
+ LAST_CONTACT_TIME = 5.minutes.ago
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -33,6 +35,7 @@ module Ci
scope :shared, ->() { where(is_shared: true) }
scope :active, ->() { where(active: true) }
scope :paused, ->() { where(active: false) }
+ scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
acts_as_taggable
@@ -65,6 +68,20 @@ module Ci
is_shared
end
+ def online?
+ contacted_at && contacted_at > LAST_CONTACT_TIME
+ end
+
+ def status
+ if contacted_at.nil?
+ :not_connected
+ elsif active?
+ online? ? :online : :offline
+ else
+ :paused
+ end
+ end
+
def belongs_to_one_project?
runner_projects.count == 1
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 95ac7156bed..23b5e38336c 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -184,4 +184,12 @@ class Commit
def parents
@parents ||= Commit.decorate(super, project)
end
+
+ def ci_commit
+ project.ci_commit(sha)
+ end
+
+ def status
+ ci_commit.try(:status) || :not_found
+ end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
new file mode 100644
index 00000000000..92905c618eb
--- /dev/null
+++ b/app/models/commit_status.rb
@@ -0,0 +1,95 @@
+class CommitStatus < ActiveRecord::Base
+ self.table_name = 'ci_builds'
+
+ belongs_to :commit, class_name: 'Ci::Commit'
+ belongs_to :user
+
+ validates :commit, presence: true
+ validates :status, inclusion: { in: %w(pending running failed success canceled) }
+
+ validates_presence_of :name
+
+ alias_attribute :author, :user
+
+ scope :running, -> { where(status: 'running') }
+ scope :pending, -> { where(status: 'pending') }
+ scope :success, -> { where(status: 'success') }
+ scope :failed, -> { where(status: 'failed') }
+ scope :running_or_pending, -> { where(status:[:running, :pending]) }
+ scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
+ scope :ordered, -> { order(:ref, :stage_idx, :name) }
+ scope :for_ref, ->(ref) { where(ref: ref) }
+ scope :running_or_pending, -> { where(status: [:running, :pending]) }
+
+ state_machine :status, initial: :pending do
+ event :run do
+ transition pending: :running
+ end
+
+ event :drop do
+ transition running: :failed
+ end
+
+ event :success do
+ transition [:pending, :running] => :success
+ end
+
+ event :cancel do
+ transition [:pending, :running] => :canceled
+ end
+
+ after_transition pending: :running do |build, transition|
+ build.update_attributes started_at: Time.now
+ end
+
+ after_transition any => [:success, :failed, :canceled] do |build, transition|
+ build.update_attributes finished_at: Time.now
+ end
+
+ state :pending, value: 'pending'
+ state :running, value: 'running'
+ state :failed, value: 'failed'
+ state :success, value: 'success'
+ state :canceled, value: 'canceled'
+ end
+
+ delegate :sha, :short_sha, :gl_project,
+ to: :commit, prefix: false
+
+ # TODO: this should be removed with all references
+ def before_sha
+ Gitlab::Git::BLANK_SHA
+ end
+
+ def started?
+ !pending? && !canceled? && started_at
+ end
+
+ def active?
+ running? || pending?
+ end
+
+ def complete?
+ canceled? || success? || failed?
+ end
+
+ def duration
+ if started_at && finished_at
+ finished_at - started_at
+ elsif started_at
+ Time.now - started_at
+ end
+ end
+
+ def cancel_url
+ nil
+ end
+
+ def retry_url
+ nil
+ end
+
+ def show_warning?
+ false
+ end
+end
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
new file mode 100644
index 00000000000..fe0cea8465f
--- /dev/null
+++ b/app/models/concerns/case_sensitivity.rb
@@ -0,0 +1,28 @@
+# Concern for querying columns with specific case sensitivity handling.
+module CaseSensitivity
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Queries the given columns regardless of the casing used.
+ #
+ # Unlike other ActiveRecord methods this method only operates on a Hash.
+ def iwhere(params)
+ criteria = self
+ cast_lower = Gitlab::Database.postgresql?
+
+ params.each do |key, value|
+ column = ActiveRecord::Base.connection.quote_table_name(key)
+
+ if cast_lower
+ condition = "LOWER(#{column}) = LOWER(:value)"
+ else
+ condition = "#{column} = :value"
+ end
+
+ criteria = criteria.where(condition, value: value)
+ end
+
+ criteria
+ end
+ end
+end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 715fc6f689d..193c91f1742 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -47,54 +47,49 @@ module Mentionable
self
end
- # Determine whether or not a cross-reference Note has already been created between this Mentionable and
- # the specified target.
- def has_mentioned?(target)
- SystemNoteService.cross_reference_exists?(target, local_reference)
+ def all_references(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
+ ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
+ ext.analyze(text)
+ ext
end
def mentioned_users(current_user = nil, load_lazy_references: true)
- return [] if mentionable_text.blank?
-
- ext = Gitlab::ReferenceExtractor.new(self.project, current_user, load_lazy_references: load_lazy_references)
- ext.analyze(mentionable_text)
- ext.users.uniq
+ all_references(current_user, load_lazy_references: load_lazy_references).users
end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
- def references(p = project, current_user = self.author, text = mentionable_text, load_lazy_references: true)
+ def referenced_mentionables(current_user = self.author, text = self.mentionable_text, load_lazy_references: true)
return [] if text.blank?
- ext = Gitlab::ReferenceExtractor.new(p, current_user, load_lazy_references: load_lazy_references)
- ext.analyze(text)
- (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference]
+ refs = all_references(current_user, text, load_lazy_references: load_lazy_references)
+ (refs.issues + refs.merge_requests + refs.commits) - [local_reference]
end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
- def create_cross_references!(p = project, a = author, without = [])
- refs = references(p)
-
+ def create_cross_references!(author = self.author, without = [], text = self.mentionable_text)
+ refs = referenced_mentionables(author, text)
+
# We're using this method instead of Array diffing because that requires
# both of the object's `hash` values to be the same, which may not be the
# case for otherwise identical Commit objects.
- refs.reject! { |ref| without.include?(ref) }
+ refs.reject! { |ref| without.include?(ref) || cross_reference_exists?(ref) }
refs.each do |ref|
- SystemNoteService.cross_reference(ref, local_reference, a)
+ SystemNoteService.cross_reference(ref, local_reference, author)
end
end
# When a mentionable field is changed, creates cross-reference notes that
# don't already exist
- def create_new_cross_references!(p = project, a = author)
+ def create_new_cross_references!(author = self.author)
changes = detect_mentionable_changes
return if changes.empty?
original_text = changes.collect { |_, vals| vals.first }.join(' ')
- preexisting = references(p, self.author, original_text)
- create_cross_references!(p, a, preexisting)
+ preexisting = referenced_mentionables(author, original_text)
+ create_cross_references!(author, preexisting)
end
private
@@ -116,4 +111,10 @@ module Mentionable
# Only include changed fields that are mentionable
source.select { |key, val| mentionable.include?(key) }
end
+
+ # Determine whether or not a cross-reference Note has already been created between this Mentionable and
+ # the specified target.
+ def cross_reference_exists?(target)
+ SystemNoteService.cross_reference_exists?(target, local_reference)
+ end
end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 0888de62f0a..85367f89f4f 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -37,7 +37,7 @@ module Participable
# Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request
- def participants(current_user = self.author, project = self.project, load_lazy_references: true)
+ def participants(current_user = self.author, load_lazy_references: true)
participants = self.class.participant_attrs.flat_map do |attr|
value =
if attr.respond_to?(:call)
@@ -46,16 +46,14 @@ module Participable
send(attr)
end
- participants_for(value, current_user, project)
+ participants_for(value, current_user)
end.compact.uniq
if load_lazy_references
participants = Gitlab::Markdown::ReferenceFilter::LazyReference.load(participants).uniq
- if project
- participants.select! do |user|
- user.can?(:read_project, project)
- end
+ participants.select! do |user|
+ user.can?(:read_project, project)
end
end
@@ -64,14 +62,14 @@ module Participable
private
- def participants_for(value, current_user = nil, project = nil)
+ def participants_for(value, current_user = nil)
case value
when User, Gitlab::Markdown::ReferenceFilter::LazyReference
[value]
when Enumerable, ActiveRecord::Relation
- value.flat_map { |v| participants_for(v, current_user, project) }
+ value.flat_map { |v| participants_for(v, current_user) }
when Participable
- value.participants(current_user, project, load_lazy_references: false)
+ value.participants(current_user, load_lazy_references: false)
end
end
end
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
new file mode 100644
index 00000000000..fa54e3540d0
--- /dev/null
+++ b/app/models/generic_commit_status.rb
@@ -0,0 +1,15 @@
+class GenericCommitStatus < CommitStatus
+ before_validation :set_default_values
+
+ # GitHub compatible API
+ alias_attribute :context, :name
+
+ def set_default_values
+ self.context ||= 'default'
+ self.stage ||= 'external'
+ end
+
+ def tags
+ [:external]
+ end
+end
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index ab055f6b80b..1dd2be68ebf 100644
--- a/app/models/group_milestone.rb
+++ b/app/models/group_milestone.rb
@@ -1,5 +1,7 @@
class GroupMilestone
+ alias_attribute :name, :title
+
def initialize(title, milestones)
@title = title
@milestones = milestones
diff --git a/app/models/label.rb b/app/models/label.rb
index 4a22bd53400..1bb4b5f55cf 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -12,6 +12,11 @@
class Label < ActiveRecord::Base
include Referable
+ # Represents a "No Label" state used for filtering Issues and Merge
+ # Requests that have no label assigned.
+ LabelStruct = Struct.new(:title, :name)
+ None = LabelStruct.new('No Label', 'No Label')
+ Any = LabelStruct.new('Any', '')
DEFAULT_COLOR = '#428BCA'
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index eb468c6cd53..c83b15c7d39 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -227,7 +227,7 @@ class MergeRequest < ActiveRecord::Base
end
def work_in_progress?
- title =~ /\A\[?WIP\]?:? /i
+ !!(title =~ /\A\[?WIP\]?:? /i)
end
def mergeable?
@@ -275,7 +275,8 @@ class MergeRequest < ActiveRecord::Base
attrs = {
source: source_project.hook_attrs,
target: target_project.hook_attrs,
- last_commit: nil
+ last_commit: nil,
+ work_in_progress: work_in_progress?
}
unless last_commit.nil?
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index d979a35084b..84acba30b6b 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -16,7 +16,9 @@
class Milestone < ActiveRecord::Base
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
- None = Struct.new(:title).new('No Milestone')
+ MilestoneStruct = Struct.new(:title, :name)
+ None = MilestoneStruct.new('No Milestone', 'No Milestone')
+ Any = MilestoneStruct.new('Any', '')
include InternalId
include Sortable
@@ -47,6 +49,8 @@ class Milestone < ActiveRecord::Base
state :active
end
+ alias_attribute :name, :title
+
class << self
def search(query)
query = "%#{query}%"
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index bc8525df5a5..5782e649f8b 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -118,6 +118,8 @@ class Namespace < ActiveRecord::Base
gitlab_shell.add_namespace(path_was)
if gitlab_shell.mv_namespace(path_was, path)
+ Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
+
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
diff --git a/app/models/note.rb b/app/models/note.rb
index 2fbe4784159..23b6f468e61 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -62,7 +62,6 @@ class Note < ActiveRecord::Base
serialize :st_diff
before_create :set_diff, if: ->(n) { n.line_code.present? }
- after_update :set_references
class << self
def discussions_from_notes(notes)
@@ -333,15 +332,13 @@ class Note < ActiveRecord::Base
end
def noteable_type_name
- if noteable_type.present?
- noteable_type.downcase
- end
+ noteable_type.downcase if noteable_type.present?
end
# FIXME: Hack for polymorphic associations with STI
# For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations
- def noteable_type=(sType)
- super(sType.to_s.classify.constantize.base_class.to_s)
+ def noteable_type=(noteable_type)
+ super(noteable_type.to_s.classify.constantize.base_class.to_s)
end
# Reset notes events cache
@@ -357,10 +354,6 @@ class Note < ActiveRecord::Base
Event.reset_event_cache_for(self)
end
- def set_references
- create_new_cross_references!(project, author)
- end
-
def system?
read_attribute(:system)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index bb47b9abb03..cd30467fae3 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -40,6 +40,7 @@ class Project < ActiveRecord::Base
include Referable
include Sortable
include AfterCommitQueue
+ include CaseSensitivity
extend Gitlab::ConfigHelper
extend Enumerize
@@ -235,13 +236,18 @@ class Project < ActiveRecord::Base
end
def find_with_namespace(id)
- return nil unless id.include?('/')
-
- id = id.split('/')
- namespace = Namespace.by_path(id.first)
- return nil unless namespace
-
- where(namespace_id: namespace.id).where("LOWER(projects.path) = :path", path: id.second.downcase).first
+ namespace_path, project_path = id.split('/')
+
+ 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).
+ unscoped.
+ joins(:namespace).
+ iwhere('namespaces.path' => namespace_path).
+ iwhere('projects.path' => project_path).
+ take
end
def visibility_levels
@@ -260,6 +266,20 @@ class Project < ActiveRecord::Base
name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
%r{(?<project>#{name_pattern}/#{name_pattern})}
end
+
+ 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
end
def team
@@ -636,6 +656,8 @@ class Project < ActiveRecord::Base
# 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
def hook_attrs
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d8aedbd2ab4..5f5255ab487 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -40,12 +40,19 @@ class BambooService < CiService
attr_accessor :response
after_save :compose_service_hook, if: :activated?
+ before_update :reset_password
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
+ def reset_password
+ if prop_updated?(:bamboo_url)
+ self.password = nil
+ end
+ end
+
def title
'Atlassian Bamboo CI'
end
diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb
index 0e6e97394bc..f17993d9f3b 100644
--- a/app/models/project_services/ci/hip_chat_service.rb
+++ b/app/models/project_services/ci/hip_chat_service.rb
@@ -49,7 +49,7 @@ module Ci
commit = build.commit
return unless commit
- return unless commit.builds_without_retry.include? build
+ return unless commit.latest_builds.include? build
case commit.status.to_sym
when :failed
diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb
index 11a2743f969..fd193301001 100644
--- a/app/models/project_services/ci/mail_service.rb
+++ b/app/models/project_services/ci/mail_service.rb
@@ -48,7 +48,7 @@ module Ci
# it doesn't make sense to send emails for retried builds
commit = build.commit
return unless commit
- return unless commit.builds_without_retry.include?(build)
+ return unless commit.latest_builds.include?(build)
case build.status.to_sym
when :failed
diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb
index 5ac8907ecd0..dc050a3fc59 100644
--- a/app/models/project_services/ci/slack_message.rb
+++ b/app/models/project_services/ci/slack_message.rb
@@ -23,7 +23,7 @@ module Ci
def attachments
fields = []
- commit.builds_without_retry.each do |build|
+ commit.latest_builds.each do |build|
next if build.allow_failure?
next unless build.failed?
fields << {
diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb
index 76db573dc17..ee8e4988826 100644
--- a/app/models/project_services/ci/slack_service.rb
+++ b/app/models/project_services/ci/slack_service.rb
@@ -48,7 +48,7 @@ module Ci
commit = build.commit
return unless commit
- return unless commit.builds_without_retry.include?(build)
+ return unless commit.latest_builds.include?(build)
case commit.status.to_sym
when :failed
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 3c002a1634b..fb11cad352e 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -37,12 +37,19 @@ class TeamcityService < CiService
attr_accessor :response
after_save :compose_service_hook, if: :activated?
+ before_update :reset_password
def compose_service_hook
hook = service_hook || build_service_hook
hook.save
end
+ def reset_password
+ if prop_updated?(:teamcity_url)
+ self.password = nil
+ end
+ end
+
def title
'JetBrains TeamCity CI'
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 2c5ab62d22c..8b51602bc23 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -373,11 +373,25 @@ class Repository
@root_ref ||= raw_repository.root_ref
end
- def commit_file(user, path, content, message, branch)
+ def commit_dir(user, path, message, branch)
commit_with_hooks(user, branch) do |ref|
- path[0] = '' if path[0] == '/'
+ committer = user_to_committer(user)
+ options = {}
+ options[:committer] = committer
+ options[:author] = committer
+
+ options[:commit] = {
+ message: message,
+ branch: ref,
+ }
+
+ raw_repository.mkdir(path, options)
+ end
+ end
- committer = user_to_comitter(user)
+ def commit_file(user, path, content, message, branch, update)
+ commit_with_hooks(user, branch) do |ref|
+ committer = user_to_committer(user)
options = {}
options[:committer] = committer
options[:author] = committer
@@ -388,7 +402,8 @@ class Repository
options[:file] = {
content: content,
- path: path
+ path: path,
+ update: update
}
Gitlab::Git::Blob.commit(raw_repository, options)
@@ -397,9 +412,7 @@ class Repository
def remove_file(user, path, message, branch)
commit_with_hooks(user, branch) do |ref|
- path[0] = '' if path[0] == '/'
-
- committer = user_to_comitter(user)
+ committer = user_to_committer(user)
options = {}
options[:committer] = committer
options[:author] = committer
@@ -416,7 +429,7 @@ class Repository
end
end
- def user_to_comitter(user)
+ def user_to_committer(user)
{
email: user.email,
name: user.name,
diff --git a/app/models/service.rb b/app/models/service.rb
index 60fcc9d2857..7e845d565b1 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -117,6 +117,15 @@ class Service < ActiveRecord::Base
end
end
+ # ActiveRecord does not provide a mechanism to track changes in serialized keys.
+ # This is why we need to perform extra query to do it mannually.
+ def prop_updated?(prop_name)
+ relation_name = self.type.underscore
+ previous_value = project.send(relation_name).send(prop_name)
+ return false if previous_value.nil?
+ previous_value != send(prop_name)
+ end
+
def async_execute(data)
return unless supported_events.include?(data[:object_kind])