From 5d69f5b46d475f34fb71dfb4e8b683e90897f1da Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 31 Mar 2016 19:51:28 +0200 Subject: [PATCH 001/113] Use Ci::Commit as Pipeline --- app/controllers/projects/commit_controller.rb | 7 +- .../projects/merge_requests_controller.rb | 3 + .../projects/pipelines_controller.rb | 0 app/helpers/ci_status_helper.rb | 19 +-- app/helpers/gitlab_routing_helper.rb | 12 ++ app/models/ci/build.rb | 11 +- app/models/ci/commit.rb | 124 +++++++++--------- app/models/commit.rb | 7 +- app/models/commit_status.rb | 40 ++---- app/models/concerns/ci_status.rb | 58 ++++++++ app/models/merge_request.rb | 2 +- app/models/project.rb | 8 +- app/services/ci/create_builds_service.rb | 27 ++-- .../ci/create_trigger_request_service.rb | 2 +- app/services/create_commit_builds_service.rb | 9 +- app/views/admin/runners/show.html.haml | 2 +- app/views/projects/_last_commit.html.haml | 9 +- app/views/projects/builds/show.html.haml | 8 +- app/views/projects/ci/builds/_build.html.haml | 13 +- .../projects/ci/commits/_commit.html.haml | 0 .../ci_commits/_header_title.html.haml | 0 app/views/projects/ci_commits/index.html.haml | 0 app/views/projects/ci_commits/new.html.haml | 0 app/views/projects/commit/_builds.html.haml | 69 +--------- .../projects/commit/_ci_commit.html.haml | 69 ++++++++++ .../projects/commit/_commit_box.html.haml | 8 +- app/views/projects/commit/show.html.haml | 2 +- app/views/projects/commits/_commit.html.haml | 7 +- .../issues/_related_branches.html.haml | 2 +- app/views/shared/projects/_project.html.haml | 7 +- .../20160331153918_add_fields_to_ci_commit.rb | 7 + .../20160331204039_add_action_to_ci_commit.rb | 5 + ...0160411122626_add_duration_to_ci_commit.rb | 5 + db/schema.rb | 20 ++- lib/api/commit_statuses.rb | 2 +- 35 files changed, 326 insertions(+), 238 deletions(-) create mode 100644 app/controllers/projects/pipelines_controller.rb create mode 100644 app/models/concerns/ci_status.rb create mode 100644 app/views/projects/ci/commits/_commit.html.haml create mode 100644 app/views/projects/ci_commits/_header_title.html.haml create mode 100644 app/views/projects/ci_commits/index.html.haml create mode 100644 app/views/projects/ci_commits/new.html.haml create mode 100644 app/views/projects/commit/_ci_commit.html.haml create mode 100644 db/migrate/20160331153918_add_fields_to_ci_commit.rb create mode 100644 db/migrate/20160331204039_add_action_to_ci_commit.rb create mode 100644 db/migrate/20160411122626_add_duration_to_ci_commit.rb diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 576fa3cedb..f9a4aeaa62 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -94,8 +94,8 @@ class Projects::CommitController < Projects::ApplicationController @commit ||= @project.commit(params[:id]) end - def ci_commit - @ci_commit ||= project.ci_commit(commit.sha) + def ci_commits + @ci_commits ||= project.ci_commits.where(sha: commit.sha) end def define_show_vars @@ -108,7 +108,8 @@ class Projects::CommitController < Projects::ApplicationController @diff_refs = [commit.parent || commit, commit] @notes_count = commit.notes.count - @statuses = ci_commit.statuses if ci_commit + @statuses = CommitStatus.where(commit: ci_commits) + @builds = Ci::Build.where(commit: ci_commits) end def assign_revert_commit_vars diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 49064f5d50..ed5a190105 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -118,6 +118,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit @note_counts = Note.where(commit_id: @commits.map(&:id)). @@ -308,6 +309,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request_diff = @merge_request.merge_request_diff @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit if @merge_request.locked_long_ago? @@ -318,6 +320,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_widget_vars @ci_commit = @merge_request.ci_commit + @ci_commits = [@ci_commit].compact closes_issues end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 8b1575d5e0..fd2179c7af 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -1,17 +1,4 @@ module CiStatusHelper - def ci_status_path(ci_commit) - project = ci_commit.project - builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) - end - - def ci_status_icon(ci_commit) - ci_icon_for_status(ci_commit.status) - end - - def ci_status_label(ci_commit) - ci_label_for_status(ci_commit.status) - end - def ci_status_with_icon(status, target = nil) content = ci_icon_for_status(status) + ' '.html_safe + ci_label_for_status(status) klass = "ci-status ci-#{status}" @@ -47,10 +34,10 @@ module CiStatusHelper end def render_ci_status(ci_commit, tooltip_placement: 'auto left') - link_to ci_status_icon(ci_commit), - ci_status_path(ci_commit), + link_to ci_icon_for_status(ci_commit.status), + project_ci_commit_path(ci_commit.project, ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", - title: "Build #{ci_status_label(ci_commit)}", + title: "Build #{ci_label_for_status(ci_commit.status)}", data: { toggle: 'tooltip', placement: tooltip_placement } end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f3fddef01c..f1af8e163c 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -25,10 +25,22 @@ module GitlabRoutingHelper namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref) end + def project_pipelines_path(project, *args) + namespace_project_pipelines_path(project.namespace, project, *args) + end + def project_builds_path(project, *args) namespace_project_builds_path(project.namespace, project, *args) end + def project_commit_path(project, commit) + builds_namespace_project_commit_path(project.namespace, project, commit.id) + end + + def project_ci_commit_path(project, ci_commit) + builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) + end + def activity_project_path(project, *args) activity_namespace_project_path(project.namespace, project, *args) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 7d33838044..15fc714b53 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -50,7 +50,6 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } - scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) } mount_uploader :artifacts_file, ArtifactUploader mount_uploader :artifacts_metadata, ArtifactUploader @@ -62,6 +61,8 @@ module Ci before_destroy { project } + after_create :execute_hooks + class << self def columns_without_lazy (column_names - LAZY_ATTRIBUTES).map do |column_name| @@ -126,12 +127,16 @@ module Ci end def retried? - !self.commit.latest_statuses_for_ref(self.ref).include?(self) + !self.commit.latest.include?(self) + end + + def retry + Ci::Build.retry(self) end def depends_on_builds # Get builds of the same type - latest_builds = self.commit.builds.similar(self).latest + latest_builds = self.commit.builds.latest # Return builds from previous stages latest_builds.where('stage_idx < ?', stage_idx) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index f4cf7034b1..70fe63877c 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -19,6 +19,7 @@ module Ci class Commit < ActiveRecord::Base extend Ci::Model + include CiStatus belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id has_many :statuses, class_name: 'CommitStatus' @@ -28,12 +29,18 @@ module Ci validates_presence_of :sha validate :valid_commit_sha + # Make sure that status is saved + before_save :status + before_save :started_at + before_save :finished_at + before_save :duration + def self.truncate_sha(sha) sha[0...8] end - def to_param - sha + def stages + statuses.group(:stage).order(:stage_idx).pluck(:stage) end def project_id @@ -68,15 +75,26 @@ module Ci nil end - def stage - running_or_pending = statuses.latest.running_or_pending.ordered - running_or_pending.first.try(:stage) + def branch? + !tag? end - def create_builds(ref, tag, user, trigger_request = nil) + def retryable? + builds.latest.any? do |build| + build.failed? || build.retryable? + end + end + + def invalidate + status = nil + started_at = nil + finished_at = nil + end + + def create_builds(user, trigger_request = nil) return unless config_processor config_processor.stages.any? do |stage| - CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present? + CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present? end end @@ -84,7 +102,7 @@ module Ci return unless config_processor # don't create other builds if this one is retried - latest_builds = builds.similar(build).latest + latest_builds = builds.latest return unless latest_builds.exists?(build.id) # get list of stages after this build @@ -97,26 +115,12 @@ module Ci # create builds for next stages based next_stages.any? do |stage| - CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present? + CreateBuildsService.new(self).execute(stage, build.user, status, build.trigger_request).present? end end - def refs - statuses.order(:ref).pluck(:ref).uniq - end - - def latest_statuses - @latest_statuses ||= statuses.latest.to_a - end - - def latest_statuses_for_ref(ref) - latest_statuses.select { |status| status.ref == ref } - end - - def matrix_builds(build = nil) - matrix_builds = builds.latest.ordered - matrix_builds = matrix_builds.similar(build) if build - matrix_builds.to_a + def latest + statuses.latest end def retried @@ -124,56 +128,23 @@ module Ci end def status - if yaml_errors.present? - return 'failed' - end - - @status ||= Ci::Status.get_status(latest_statuses) - end - - def pending? - status == 'pending' - end - - def running? - status == 'running' - end - - def success? - status == 'success' - end - - def failed? - status == 'failed' - end - - def canceled? - status == 'canceled' - end - - def active? - running? || pending? - end - - def complete? - canceled? || success? || failed? + read_attribute(:status) || update_status end def duration - duration_array = statuses.map(&:duration).compact - duration_array.reduce(:+).to_i + read_attribute(:duration) || update_duration end def started_at - @started_at ||= statuses.order('started_at ASC').first.try(:started_at) + read_attribute(:started_at) || update_started_at end def finished_at - @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at) + read_attribute(:finished_at) || update_finished_at end def coverage - coverage_array = latest_statuses.map(&:coverage).compact + coverage_array = latest.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end @@ -191,6 +162,7 @@ module Ci end def ci_yaml_file + return nil if defined?(@ci_yaml_file) @ci_yaml_file ||= begin blob = project.repository.blob_at(sha, '.gitlab-ci.yml') blob.load_all_data!(project.repository) @@ -206,6 +178,32 @@ module Ci private + def update_status + status = + if yaml_errors.present? + 'failed' + else + latest.status + end + end + + def update_started_at + started_at = + statuses.order(:id).first.try(:started_at) + end + + def update_finished_at + finished_at = + statuses.order(id: :desc).first.try(:finished_at) + end + + def update_duration + duration = begin + duration_array = latest.map(&:duration).compact + duration_array.reduce(:+).to_i + end + end + def save_yaml_error(error) return if self.yaml_errors? self.yaml_errors = error diff --git a/app/models/commit.rb b/app/models/commit.rb index d09876a07d..a898f7ba33 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -209,12 +209,13 @@ class Commit @raw.short_id(7) end - def ci_commit - project.ci_commit(sha) + def ci_commits + @ci_commits ||= project.ci_commits.where(sha: sha) end def status - ci_commit.try(:status) || :not_found + return @status if defined?(@status) + @status ||= ci_commits.status end def revert_branch_name diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 3377a85a55..da7d6ea6b9 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -33,6 +33,8 @@ # class CommitStatus < ActiveRecord::Base + include CiStatus + self.table_name = 'ci_builds' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id @@ -40,21 +42,13 @@ class CommitStatus < ActiveRecord::Base 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 :finished, -> { where(status: [:success, :failed, :canceled]) } - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } + scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } - scope :for_ref, ->(ref) { where(ref: ref) } AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] @@ -87,31 +81,13 @@ class CommitStatus < ActiveRecord::Base MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) end - state :pending, value: 'pending' - state :running, value: 'running' - state :failed, value: 'failed' - state :success, value: 'success' - state :canceled, value: 'canceled' + after_transition any => any do |commit_status| + commit_status.commit.invalidate + commit_status.save + end end - delegate :sha, :short_sha, 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 + delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false def ignored? allow_failure? && (failed? || canceled?) diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb new file mode 100644 index 0000000000..9fe20bc9d7 --- /dev/null +++ b/app/models/concerns/ci_status.rb @@ -0,0 +1,58 @@ +module CiStatus + extend ActiveSupport::Concern + + module ClassMethods + def status + objs = all.to_a + if objs.none? + nil + elsif objs.all? { |status| status.success? || status.try(:ignored?) } + 'success' + elsif objs.all?(&:pending?) + 'pending' + elsif objs.any?(&:running?) || all.any?(&:pending?) + 'running' + elsif objs.all?(&:canceled?) + 'canceled' + else + 'failed' + end + end + + def duration + duration_array = all.map(&:duration).compact + duration_array.reduce(:+).to_i + end + end + + included do + validates :status, inclusion: { in: %w(pending running failed success canceled) } + + state_machine :status, initial: :pending do + state :pending, value: 'pending' + state :running, value: 'running' + state :failed, value: 'failed' + state :success, value: 'success' + state :canceled, value: 'canceled' + end + + 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 :finished, -> { where(status: [:success, :failed, :canceled]) } + end + + def started? + !pending? && !canceled? && started_at + end + + def active? + running? || pending? + end + + def complete? + canceled? || success? || failed? + end +end \ No newline at end of file diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bf185cb5dd..33869e215c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -589,7 +589,7 @@ class MergeRequest < ActiveRecord::Base end def ci_commit - @ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project + @ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project end def diff_refs diff --git a/app/models/project.rb b/app/models/project.rb index 3e1f04b415..b9d589a459 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -919,12 +919,12 @@ class Project < ActiveRecord::Base !namespace.share_with_group_lock end - def ci_commit(sha) - ci_commits.find_by(sha: sha) + def ci_commit(sha, ref) + ci_commits.find_by(sha: sha, ref: ref) end - def ensure_ci_commit(sha) - ci_commit(sha) || ci_commits.create(sha: sha) + def ensure_ci_commit(sha, ref) + ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref) end def enable_ci diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index 2cd51a7610..3b6e045d69 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -1,7 +1,11 @@ module Ci class CreateBuildsService - def execute(commit, stage, ref, tag, user, trigger_request, status) - builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request) + def initialize(commit) + @commit = commit + end + + def execute(stage, user, status, trigger_request = nil) + builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request) # check when to create next build builds_attrs = builds_attrs.select do |build_attrs| @@ -17,7 +21,8 @@ module Ci builds_attrs.map do |build_attrs| # don't create the same build twice - unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name]) + unless commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, + trigger_request: trigger_request, name: build_attrs[:name]) build_attrs.slice!(:name, :commands, :tag_list, @@ -26,17 +31,21 @@ module Ci :stage, :stage_idx) - build_attrs.merge!(ref: ref, - tag: tag, + build_attrs.merge!(ref: @commit.ref, + tag: @commit.tag, trigger_request: trigger_request, user: user, - project: commit.project) + project: @commit.project) - build = commit.builds.create!(build_attrs) - build.execute_hooks - build + @commit.builds.create!(build_attrs) end end end + + private + + def config_processor + @config_processor ||= @commit.config_processor + end end end diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb index b3dfc70722..d3745c770e 100644 --- a/app/services/ci/create_trigger_request_service.rb +++ b/app/services/ci/create_trigger_request_service.rb @@ -7,7 +7,7 @@ module Ci # check if ref is tag tag = project.repository.find_tag(ref).present? - ci_commit = project.ensure_ci_commit(commit.sha) + ci_commit = project.ci_commits.create(commit.sha, ref) trigger_request = trigger.trigger_requests.create!( variables: variables, diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb index 69d5c42a87..e7e1134ce4 100644 --- a/app/services/create_commit_builds_service.rb +++ b/app/services/create_commit_builds_service.rb @@ -2,6 +2,7 @@ class CreateCommitBuildsService def execute(project, user, params) return false unless project.builds_enabled? + before_sha = params[:checkout_sha] || params[:before] sha = params[:checkout_sha] || params[:after] origin_ref = params[:ref] @@ -10,15 +11,16 @@ class CreateCommitBuildsService end ref = Gitlab::Git.ref_name(origin_ref) + tag = Gitlab::Git.tag_ref?(origin_ref) # Skip branch removal if sha == Gitlab::Git::BLANK_SHA return false end - commit = project.ci_commit(sha) + commit = project.ci_commit(sha, ref) unless commit - commit = project.ci_commits.new(sha: sha) + commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag) # Skip creating ci_commit when no gitlab-ci.yml is found unless commit.ci_yaml_file @@ -32,8 +34,7 @@ class CreateCommitBuildsService # Skip creating builds for commits that have [ci skip] unless commit.skip_ci? # Create builds for commit - tag = Gitlab::Git.tag_ref?(origin_ref) - commit.create_builds(ref, tag, user) + commit.create_builds(user) end commit diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 8700b4820c..50d0005140 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -117,7 +117,7 @@ %td.build-link - if project - = link_to ci_status_path(build.commit) do + = link_to builds_namespace_project_commit_path(project.namespace, project, build.sha) do %strong #{build.commit.short_sha} %td.timestamp diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 386d72e778..6eccbaa4bf 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -1,9 +1,8 @@ .project-last-commit - - ci_commit = project.ci_commit(commit.sha) - - if ci_commit - = link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do - = ci_status_icon(ci_commit) - = ci_status_label(ci_commit) + - if commit.status + = link_to project_commit_path(commit.project, commit), class: "ci-status ci-#{commit.status}" do + = ci_icon_for_status(commit.status) + = ci_label_for_status(commit.status) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message" diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index b02aee3db2..41b1ca9f9e 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -4,7 +4,7 @@ .build-page .gray-content-block.top-block Build ##{@build.id} for commit - %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) + %strong.monospace= link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha) from = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) - merge_request = @build.merge_request @@ -13,7 +13,7 @@ = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request) #up-build-trace - - builds = @build.commit.matrix_builds(@build) + - builds = @build.commit.builds.latest.to_a - if builds.size > 1 %ul.nav-links.no-top.no-bottom - builds.each do |build| @@ -173,7 +173,7 @@ Commit .pull-right %small - = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" + = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace" %p %span.attr-name Branch: = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) @@ -196,7 +196,7 @@ .build-widget %h4.title #{pluralize(@builds.count(:id), "other build")} for = succeed ":" do - = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" + = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, build.sha), class: "monospace" %table.table.builds - @builds.each_with_index do |build, i| %tr.build diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 2cf9115e4d..218d396b89 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -19,11 +19,12 @@ %td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" - %td - - if build.ref - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) - - else - .light none + - if !defined?(ref) || ref + %td + - if build.ref + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) + - else + .light none - if defined?(runner) && runner %td @@ -48,6 +49,8 @@ %span.label.label-info triggered - if build.try(:allow_failure) %span.label.label-danger allowed to fail + - if defined?(retried) && retried + %span.label.label-warning retried %td.duration - if build.duration diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/projects/commit/_builds.html.haml b/app/views/projects/commit/_builds.html.haml index 003b7c18d0..5c9a319ede 100644 --- a/app/views/projects/commit/_builds.html.haml +++ b/app/views/projects/commit/_builds.html.haml @@ -1,67 +1,2 @@ -.gray-content-block.middle-block - .pull-right - - if can?(current_user, :update_build, @ci_commit.project) - - if @ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post - - - if @ci_commit.builds.running_or_pending.any? - = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - - .oneline - = pluralize @statuses.count(:id), "build" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to @ci_commit.short_sha, namespace_project_commit_path(@ci_commit.project.namespace, @ci_commit.project, @ci_commit.sha), class: "monospace" - - if @ci_commit.duration > 0 - in - = time_interval_in_words @ci_commit.duration - -- if @ci_commit.yaml_errors.present? - .bs-callout.bs-callout-danger - %h4 Found errors in your .gitlab-ci.yml: - %ul - - @ci_commit.yaml_errors.split(",").each do |error| - %li= error - You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} - -- if @ci_commit.project.builds_enabled? && !@ci_commit.ci_yaml_file - .bs-callout.bs-callout-warning - \.gitlab-ci.yml not found in this commit - -.table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_commit.project.build_coverage_enabled? - %th Coverage - %th - - @ci_commit.refs.each do |ref| - - builds = @ci_commit.statuses.for_ref(ref).latest.ordered - = render builds, coverage: @ci_commit.project.build_coverage_enabled?, stage: true, allow_retry: true - -- if @ci_commit.retried.any? - .gray-content-block.second-block - Retried builds - - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Build ID - %th Ref - %th Stage - %th Name - %th Duration - %th Finished at - - if @ci_commit.project.build_coverage_enabled? - %th Coverage - %th - = render @ci_commit.retried, coverage: @ci_commit.project.build_coverage_enabled?, stage: true +- @ci_commits.each do |ci_commit| + = render "ci_commit", ci_commit: ci_commit diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml new file mode 100644 index 0000000000..06520e40bd --- /dev/null +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -0,0 +1,69 @@ +.gray-content-block.middle-block + .pull-right + - if can?(current_user, :update_build, @project) + - if ci_commit.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: 'btn btn-grouped btn-primary', method: :post + + - if ci_commit.builds.running_or_pending.any? + = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + + .oneline + = pluralize ci_commit.statuses.count(:id), "build" + - if ci_commit.ref + for + %span.label.label-info + = ci_commit.ref + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" + - if ci_commit.duration > 0 + in + = time_interval_in_words ci_commit.duration + +- if ci_commit.yaml_errors.present? + .bs-callout.bs-callout-danger + %h4 Found errors in your .gitlab-ci.yml: + %ul + - ci_commit.yaml_errors.split(",").each do |error| + %li= error + You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path} + +- if @project.builds_enabled? && !ci_commit.ci_yaml_file + .bs-callout.bs-callout-warning + \.gitlab-ci.yml not found in this commit + +.table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Stage + %th Name + %th Duration + %th Finished at + - if @project.build_coverage_enabled? + %th Coverage + %th + - builds = ci_commit.statuses.latest.ordered + = render builds, coverage: @project.build_coverage_enabled?, stage: true, ref: false, allow_retry: true + +- if ci_commit.retried.any? + .gray-content-block.second-block + Retried builds + + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Build ID + %th Ref + %th Stage + %th Name + %th Duration + %th Finished at + - if @project.build_coverage_enabled? + %th Coverage + %th + = render ci_commit.retried, coverage: @project.build_coverage_enabled?, stage: true, ref: false diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 71995fcc48..0908e830f8 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -42,12 +42,12 @@ - @commit.parents.each do |parent| = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace" -- if @ci_commit +- if @commit.status .pull-right - = link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do - = ci_status_icon(@ci_commit) + = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "ci-status ci-#{@commit.status}" do + = ci_icon_for_status(@commit.status) build: - = ci_status_label(@ci_commit) + = ci_label_for_status(@commit.status) .commit-info-row.branches %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 21e186120c..096f7058bd 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -5,7 +5,7 @@ .prepend-top-default = render "commit_box" -- if @ci_commit +- if @commit.status = render "ci_menu" - else %div.block-connector diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 7f2903589a..fa34f7b7d6 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -4,9 +4,8 @@ - notes = commit.notes - note_count = notes.user.count -- ci_commit = project.ci_commit(commit.sha) - cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count] -- cache_key.push(ci_commit.status) if ci_commit +- cache_key.push(commit.status) if commit.status = cache(cache_key) do %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" } @@ -17,8 +16,8 @@ %a.text-expander.js-toggle-button ... .pull-right - - if ci_commit - = render_ci_status(ci_commit) + - if commit.status + = render_ci_status(commit)   = clipboard_button(clipboard_text: commit.id) = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index b10cd03515..bdfa0c7009 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -5,7 +5,7 @@ - @related_branches.each do |branch| %li - sha = @project.repository.find_branch(branch).target - - ci_commit = @project.ci_commit(sha) if sha + - ci_commit = @project.ci_commit(sha, branch) if sha - if ci_commit %span.related-branch-ci-status = render_ci_status(ci_commit) diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 53ff8959bc..53261fcace 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -6,9 +6,8 @@ - css_class = '' unless local_assigns[:css_class] - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description -- ci_commit = project.ci_commit(project.commit.sha) if ci && !project.empty_repo? && project.commit - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3'] -- cache_key.push(ci_commit.status) if ci_commit +- cache_key.push(project.commit.status) if project.commit.status %li.project-row{ class: css_class } = cache(cache_key) do @@ -16,9 +15,9 @@ - if project.main_language %span = project.main_language - - if ci_commit + - if project.commit.status %span - = render_ci_status(ci_commit) + = render_ci_status(project.commit) - if forks %span = icon('code-fork') diff --git a/db/migrate/20160331153918_add_fields_to_ci_commit.rb b/db/migrate/20160331153918_add_fields_to_ci_commit.rb new file mode 100644 index 0000000000..03eb9ba4e5 --- /dev/null +++ b/db/migrate/20160331153918_add_fields_to_ci_commit.rb @@ -0,0 +1,7 @@ +class AddFieldsToCiCommit < ActiveRecord::Migration + def change + add_column :ci_commits, :status, :string + add_column :ci_commits, :started_at, :timestamp + add_column :ci_commits, :finished_at, :timestamp + end +end diff --git a/db/migrate/20160331204039_add_action_to_ci_commit.rb b/db/migrate/20160331204039_add_action_to_ci_commit.rb new file mode 100644 index 0000000000..e9f8eb624d --- /dev/null +++ b/db/migrate/20160331204039_add_action_to_ci_commit.rb @@ -0,0 +1,5 @@ +class AddActionToCiCommit < ActiveRecord::Migration + def change + add_column :ci_commits, :action, :string + end +end diff --git a/db/migrate/20160411122626_add_duration_to_ci_commit.rb b/db/migrate/20160411122626_add_duration_to_ci_commit.rb new file mode 100644 index 0000000000..7def7a48cd --- /dev/null +++ b/db/migrate/20160411122626_add_duration_to_ci_commit.rb @@ -0,0 +1,5 @@ +class AddDurationToCiCommit < ActiveRecord::Migration + def change + add_column :ci_commits, :duration, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index ec235c1913..72d6391338 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160331223143) do +ActiveRecord::Schema.define(version: 20160411122626) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -141,6 +141,7 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" + t.string "plugin" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree @@ -168,6 +169,11 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" + t.string "status" + t.datetime "started_at" + t.datetime "finished_at" + t.string "action" + t.integer "duration" end add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree @@ -306,11 +312,20 @@ ActiveRecord::Schema.define(version: 20160331223143) do add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree create_table "ci_trigger_requests", force: :cascade do |t| - t.integer "trigger_id", null: false + t.integer "trigger_id" t.text "variables" t.datetime "created_at" t.datetime "updated_at" t.integer "commit_id" + t.string "sha" + t.string "before_sha" + t.string "ref" + t.string "action" + t.string "status" + t.datetime "started_at" + t.datetime "finished_at" + t.integer "project_id" + t.string "origin_ref" end create_table "ci_triggers", force: :cascade do |t| @@ -731,6 +746,7 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.boolean "public_builds", default: true, null: false t.string "main_language" t.integer "pushes_since_gc", default: 0 + t.boolean "images_enabled" end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 8e74e177ea..e7d76764ff 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -21,7 +21,7 @@ module API authorize!(:read_commit_status, user_project) not_found!('Commit') unless user_project.commit(params[:sha]) - ci_commit = user_project.ci_commit(params[:sha]) + ci_commit = user_project.ci_commit(params[:sha], params[:ref]) return [] unless ci_commit statuses = ci_commit.statuses From af7214d0f077f738ed57194feb0cd468c43d4310 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 11 Apr 2016 16:55:40 +0200 Subject: [PATCH 002/113] Fix specs --- app/controllers/projects/commit_controller.rb | 8 +- app/helpers/ci_status_helper.rb | 2 + app/models/ci/commit.rb | 27 +++- app/models/commit_status.rb | 9 +- app/models/concerns/ci_status.rb | 3 +- app/services/ci/create_builds_service.rb | 2 +- .../ci/create_trigger_request_service.rb | 4 +- app/services/ci/image_for_build_service.rb | 11 +- app/services/create_commit_builds_service.rb | 1 + app/views/projects/builds/show.html.haml | 2 +- app/views/shared/projects/_project.html.haml | 4 +- db/fixtures/development/14_builds.rb | 2 +- features/steps/project/merge_requests.rb | 2 +- features/steps/shared/project.rb | 2 +- lib/api/commit_statuses.rb | 14 ++- spec/features/commits_spec.rb | 5 + spec/helpers/ci_status_helper_spec.rb | 4 +- spec/lib/gitlab/badge/build_spec.rb | 6 +- spec/models/ci/commit_spec.rb | 117 +----------------- spec/models/commit_status_spec.rb | 16 +-- spec/models/merge_request_spec.rb | 4 +- spec/models/project_spec.rb | 4 +- spec/requests/api/builds_spec.rb | 2 +- spec/requests/api/commit_status_spec.rb | 19 +-- spec/requests/api/commits_spec.rb | 4 +- spec/requests/ci/api/builds_spec.rb | 20 +-- .../services/ci/create_builds_service_spec.rb | 4 +- .../ci/image_for_build_service_spec.rb | 2 +- 28 files changed, 106 insertions(+), 194 deletions(-) diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index f9a4aeaa62..72078c3cc6 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -38,13 +38,13 @@ class Projects::CommitController < Projects::ApplicationController end def cancel_builds - ci_commit.builds.running_or_pending.each(&:cancel) + ci_builds.running_or_pending.each(&:cancel) redirect_back_or_default default: builds_namespace_project_commit_path(project.namespace, project, commit.sha) end def retry_builds - ci_commit.builds.latest.failed.each do |build| + ci_builds.latest.failed.each do |build| if build.retryable? Ci::Build.retry(build) end @@ -98,6 +98,10 @@ class Projects::CommitController < Projects::ApplicationController @ci_commits ||= project.ci_commits.where(sha: commit.sha) end + def ci_builds + @ci_builds ||= Ci::Build.where(commit: ci_commits) + end + def define_show_vars return git_not_found! unless commit diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index fd2179c7af..effa7ce77e 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -34,6 +34,8 @@ module CiStatusHelper end def render_ci_status(ci_commit, tooltip_placement: 'auto left') + return unless ci_commit.is_a?(Commit) || ci_commit.is_a?(Ci::Commit) + link_to ci_icon_for_status(ci_commit.status), project_ci_commit_path(ci_commit.project, ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 70fe63877c..d09866a4bd 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -35,6 +35,11 @@ module Ci before_save :finished_at before_save :duration + # Invalidate object and save if when touched + after_touch :reload + after_touch :invalidate + after_touch :save + def self.truncate_sha(sha) sha[0...8] end @@ -86,9 +91,10 @@ module Ci end def invalidate - status = nil - started_at = nil - finished_at = nil + write_attribute(:status, nil) + write_attribute(:started_at, nil) + write_attribute(:finished_at, nil) + write_attribute(:duration, nil) end def create_builds(user, trigger_request = nil) @@ -183,18 +189,18 @@ module Ci if yaml_errors.present? 'failed' else - latest.status + latest.status || 'skipped' end end def update_started_at started_at = - statuses.order(:id).first.try(:started_at) + statuses.minimum(:started_at) end def update_finished_at finished_at = - statuses.order(id: :desc).first.try(:finished_at) + statuses.maximum(:finished_at) end def update_duration @@ -204,9 +210,18 @@ module Ci end end + def update_statuses + update_status + update_started_at + update_finished_at + update_duration + save + end + def save_yaml_error(error) return if self.yaml_errors? self.yaml_errors = error + update_status save end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index da7d6ea6b9..e8a331e720 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -38,7 +38,7 @@ class CommitStatus < ActiveRecord::Base self.table_name = 'ci_builds' belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id - belongs_to :commit, class_name: 'Ci::Commit' + belongs_to :commit, class_name: 'Ci::Commit', touch: true belongs_to :user validates :commit, presence: true @@ -47,7 +47,7 @@ class CommitStatus < ActiveRecord::Base alias_attribute :author, :user - scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name)) } + scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] @@ -80,11 +80,6 @@ class CommitStatus < ActiveRecord::Base after_transition [:pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status) end - - after_transition any => any do |commit_status| - commit_status.commit.invalidate - commit_status.save - end end delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 9fe20bc9d7..25bee601f4 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -26,7 +26,7 @@ module CiStatus end included do - validates :status, inclusion: { in: %w(pending running failed success canceled) } + validates :status, inclusion: { in: %w(pending running failed success canceled skipped) } state_machine :status, initial: :pending do state :pending, value: 'pending' @@ -34,6 +34,7 @@ module CiStatus state :failed, value: 'failed' state :success, value: 'success' state :canceled, value: 'canceled' + state :skipped, value: 'skipped' end scope :running, -> { where(status: 'running') } diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index 3b6e045d69..bbc8251a2d 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -21,7 +21,7 @@ module Ci builds_attrs.map do |build_attrs| # don't create the same build twice - unless commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, + unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, trigger_request: trigger_request, name: build_attrs[:name]) build_attrs.slice!(:name, :commands, diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb index d3745c770e..993acf11db 100644 --- a/app/services/ci/create_trigger_request_service.rb +++ b/app/services/ci/create_trigger_request_service.rb @@ -7,14 +7,14 @@ module Ci # check if ref is tag tag = project.repository.find_tag(ref).present? - ci_commit = project.ci_commits.create(commit.sha, ref) + ci_commit = project.ci_commits.create(sha: commit.sha, ref: ref, tag: tag) trigger_request = trigger.trigger_requests.create!( variables: variables, commit: ci_commit, ) - if ci_commit.create_builds(ref, tag, nil, trigger_request) + if ci_commit.create_builds(nil, trigger_request) trigger_request end end diff --git a/app/services/ci/image_for_build_service.rb b/app/services/ci/image_for_build_service.rb index 50c95ced8a..3018f27ec0 100644 --- a/app/services/ci/image_for_build_service.rb +++ b/app/services/ci/image_for_build_service.rb @@ -3,8 +3,9 @@ module Ci def execute(project, opts) sha = opts[:sha] || ref_sha(project, opts[:ref]) - commit = project.ci_commits.find_by(sha: sha) - image_name = image_for_commit(commit) + ci_commits = project.ci_commits.where(sha: sha) + ci_commits = ci_commits.where(ref: opts[:ref]) if opts[:ref] + image_name = image_for_status(ci_commits.status) image_path = Rails.root.join('public/ci', image_name) OpenStruct.new(path: image_path, name: image_name) @@ -16,9 +17,9 @@ module Ci project.commit(ref).try(:sha) if ref end - def image_for_commit(commit) - return 'build-unknown.svg' unless commit - 'build-' + commit.status + ".svg" + def image_for_status(status) + status ||= 'unknown' + 'build-' + status + ".svg" end end end diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb index e7e1134ce4..0d2aa1ff03 100644 --- a/app/services/create_commit_builds_service.rb +++ b/app/services/create_commit_builds_service.rb @@ -37,6 +37,7 @@ class CreateCommitBuildsService commit.create_builds(user) end + commit.touch commit end end diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 41b1ca9f9e..20160a718b 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -196,7 +196,7 @@ .build-widget %h4.title #{pluralize(@builds.count(:id), "other build")} for = succeed ":" do - = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, build.sha), class: "monospace" + = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace" %table.table.builds - @builds.each_with_index do |build, i| %tr.build diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 53261fcace..ab8b022411 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -7,7 +7,7 @@ - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && project.commit - css_class += " no-description" if project.description.blank? && !show_last_commit_as_description - cache_key = [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.3'] -- cache_key.push(project.commit.status) if project.commit.status +- cache_key.push(project.commit.status) if project.commit.try(:status) %li.project-row{ class: css_class } = cache(cache_key) do @@ -15,7 +15,7 @@ - if project.main_language %span = project.main_language - - if project.commit.status + - if project.commit.try(:status) %span = render_ci_status(project.commit) - if forks diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb index e3ca2b4eea..b99d24a03c 100644 --- a/db/fixtures/development/14_builds.rb +++ b/db/fixtures/development/14_builds.rb @@ -19,7 +19,7 @@ class Gitlab::Seeder::Builds commits = @project.repository.commits('master', nil, 5) commits_sha = commits.map { |commit| commit.raw.id } commits_sha.map do |sha| - @project.ensure_ci_commit(sha) + @project.ensure_ci_commit(sha, 'master') end rescue [] diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index a4f02b590e..9887bf80a8 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -515,7 +515,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step '"Bug NS-05" has CI status' do project = merge_request.source_project project.enable_ci - ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id + ci_commit = create :ci_commit, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch create :ci_build, commit: ci_commit end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index b13e82f276..3eb0cf00e6 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -230,7 +230,7 @@ module SharedProject step 'project "Shop" has CI build' do project = Project.find_by(name: "Shop") - create :ci_commit, project: project, sha: project.commit.sha + create :ci_commit, project: project, sha: project.commit.sha, ref: 'master' end step 'I should see last commit with CI status' do diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index e7d76764ff..0b52dd8a74 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -21,10 +21,9 @@ module API authorize!(:read_commit_status, user_project) not_found!('Commit') unless user_project.commit(params[:sha]) - ci_commit = user_project.ci_commit(params[:sha], params[:ref]) - return [] unless ci_commit - statuses = ci_commit.statuses + ci_commits = user_project.ci_commits.where(sha: params[:sha]) + statuses = ::CommitStatus.where(commit: ci_commits) statuses = statuses.latest unless parse_boolean(params[:all]) statuses = statuses.where(ref: params[:ref]) if params[:ref].present? statuses = statuses.where(stage: params[:stage]) if params[:stage].present? @@ -51,7 +50,14 @@ module API commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit - ci_commit = @project.ensure_ci_commit(commit.sha) + ref = params[:ref] || + begin + branches = @project.repository.branch_names_contains(commit.sha) + not_found! 'Reference for commit' if branches.none? + branches.first + end + + ci_commit = @project.ensure_ci_commit(commit.sha, ref) name = params[:name] || params[:context] status = GenericCommitStatus.running_or_pending.find_by(commit: ci_commit, name: name, ref: params[:ref]) diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index dacaa96d76..80e80ea665 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -162,4 +162,9 @@ describe 'Commits' do end end end + + def ci_status_path(ci_commit) + project = ci_commit.project + builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) + end end diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb index 4f8d9c6726..906db0ea82 100644 --- a/spec/helpers/ci_status_helper_spec.rb +++ b/spec/helpers/ci_status_helper_spec.rb @@ -7,7 +7,7 @@ describe CiStatusHelper do let(:failed_commit) { double("Ci::Commit", status: 'failed') } describe 'ci_status_icon' do - it { expect(helper.ci_status_icon(success_commit)).to include('fa-check') } - it { expect(helper.ci_status_icon(failed_commit)).to include('fa-close') } + it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') } + it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') } end end diff --git a/spec/lib/gitlab/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb index 329792bb68..b6f7a2e7ec 100644 --- a/spec/lib/gitlab/badge/build_spec.rb +++ b/spec/lib/gitlab/badge/build_spec.rb @@ -42,7 +42,7 @@ describe Gitlab::Badge::Build do end context 'build exists' do - let(:ci_commit) { create(:ci_commit, project: project, sha: sha) } + let(:ci_commit) { create(:ci_commit, project: project, sha: sha, ref: branch) } let!(:build) { create(:ci_build, commit: ci_commit) } @@ -57,7 +57,7 @@ describe Gitlab::Badge::Build do describe '#data' do let(:data) { badge.data } - it 'contains infromation about success' do + it 'contains information about success' do expect(status_node(data, 'success')).to be_truthy end end @@ -74,7 +74,7 @@ describe Gitlab::Badge::Build do describe '#data' do let(:data) { badge.data } - it 'contains infromation about failure' do + it 'contains information about failure' do expect(status_node(data, 'failed')).to be_truthy end end diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 412842337b..fb3b61ad7c 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -52,57 +52,9 @@ describe Ci::Commit, models: true do it { expect(commit.sha).to start_with(subject) } end - describe :stage do - subject { commit.stage } - - before do - @second = FactoryGirl.create :commit_status, commit: commit, name: 'deploy', stage: 'deploy', stage_idx: 1, status: 'pending' - @first = FactoryGirl.create :commit_status, commit: commit, name: 'test', stage: 'test', stage_idx: 0, status: 'pending' - end - - it 'returns first running stage' do - is_expected.to eq('test') - end - - context 'first build succeeded' do - before do - @first.success - end - - it 'returns last running stage' do - is_expected.to eq('deploy') - end - end - - context 'all builds succeeded' do - before do - @first.success - @second.success - end - - it 'returns nil' do - is_expected.to be_nil - end - end - end - describe :create_next_builds do end - describe :refs do - subject { commit.refs } - - before do - FactoryGirl.create :commit_status, commit: commit, name: 'deploy' - FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'develop' - FactoryGirl.create :commit_status, commit: commit, name: 'deploy', ref: 'master' - end - - it 'returns all refs' do - is_expected.to contain_exactly('master', 'develop', nil) - end - end - describe :retried do subject { commit.retried } @@ -117,10 +69,10 @@ describe Ci::Commit, models: true do end describe :create_builds do - let!(:commit) { FactoryGirl.create :ci_commit, project: project } + let!(:commit) { FactoryGirl.create :ci_commit, project: project, ref: 'master', tag: false } def create_builds(trigger_request = nil) - commit.create_builds('master', false, nil, trigger_request) + commit.create_builds(nil, trigger_request) end def create_next_builds @@ -143,67 +95,6 @@ describe Ci::Commit, models: true do expect(create_next_builds).to be_falsey end - context 'for different ref' do - def create_develop_builds - commit.create_builds('develop', false, nil, nil) - end - - it 'creates builds' do - expect(create_builds).to be_truthy - commit.builds.update_all(status: "success") - expect(commit.builds.count(:all)).to eq(2) - - expect(create_develop_builds).to be_truthy - commit.builds.update_all(status: "success") - expect(commit.builds.count(:all)).to eq(4) - expect(commit.refs.size).to eq(2) - expect(commit.builds.pluck(:name).uniq.size).to eq(2) - end - end - - context 'for build triggers' do - let(:trigger) { FactoryGirl.create :ci_trigger, project: project } - let(:trigger_request) { FactoryGirl.create :ci_trigger_request, commit: commit, trigger: trigger } - - it 'creates builds' do - expect(create_builds(trigger_request)).to be_truthy - expect(commit.builds.count(:all)).to eq(2) - end - - it 'rebuilds commit' do - expect(create_builds).to be_truthy - expect(commit.builds.count(:all)).to eq(2) - - expect(create_builds(trigger_request)).to be_truthy - expect(commit.builds.count(:all)).to eq(4) - end - - it 'creates next builds' do - expect(create_builds(trigger_request)).to be_truthy - expect(commit.builds.count(:all)).to eq(2) - commit.builds.update_all(status: "success") - - expect(create_next_builds).to be_truthy - expect(commit.builds.count(:all)).to eq(4) - end - - context 'for [ci skip]' do - before do - allow(commit).to receive(:git_commit_message) { 'message [ci skip]' } - end - - it 'rebuilds commit' do - expect(commit.status).to eq('skipped') - expect(create_builds).to be_truthy - - # since everything in Ci::Commit is cached we need to fetch a new object - new_commit = Ci::Commit.find_by_id(commit.id) - expect(new_commit.status).to eq('pending') - end - end - end - - context 'custom stage with first job allowed to fail' do let(:yaml) do { @@ -284,6 +175,7 @@ describe Ci::Commit, models: true do commit.builds.running_or_pending.each(&:success) expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success') + commit.reload expect(commit.status).to eq('success') end @@ -306,6 +198,7 @@ describe Ci::Commit, models: true do commit.builds.running_or_pending.each(&:success) expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success') + commit.reload expect(commit.status).to eq('failed') end @@ -329,6 +222,7 @@ describe Ci::Commit, models: true do expect(commit.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup') expect(commit.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success') + commit.reload expect(commit.status).to eq('failed') end @@ -351,6 +245,7 @@ describe Ci::Commit, models: true do commit.builds.running_or_pending.each(&:success) expect(commit.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success') + commit.reload expect(commit.status).to eq('failed') end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 82c68ff6cb..1c64947f1f 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -163,21 +163,7 @@ describe CommitStatus, models: true do end it 'return unique statuses' do - is_expected.to eq([@commit2, @commit3, @commit4, @commit5]) - end - end - - describe :for_ref do - subject { CommitStatus.for_ref('bb').order(:id) } - - before do - @commit1 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: 'bb', status: 'running' - @commit2 = FactoryGirl.create :commit_status, commit: commit, name: 'cc', ref: 'cc', status: 'pending' - @commit3 = FactoryGirl.create :commit_status, commit: commit, name: 'aa', ref: nil, status: 'success' - end - - it 'return statuses with equal and nil ref set' do - is_expected.to eq([@commit1]) + is_expected.to eq([@commit4, @commit5]) end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6f5d912fe5..d7884cea33 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -404,12 +404,12 @@ describe MergeRequest, models: true do describe 'when the source project exists' do it 'returns the latest commit' do commit = double(:commit, id: '123abc') - ci_commit = double(:ci_commit) + ci_commit = double(:ci_commit, ref: 'master') allow(subject).to receive(:last_commit).and_return(commit) expect(subject.source_project).to receive(:ci_commit). - with('123abc'). + with('123abc', 'master'). and_return(ci_commit) expect(subject.ci_commit).to eq(ci_commit) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f29c389e09..1688e91ca6 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -441,9 +441,9 @@ describe Project, models: true do describe :ci_commit do let(:project) { create :project } - let(:commit) { create :ci_commit, project: project } + let(:commit) { create :ci_commit, project: project, ref: 'master' } - it { expect(project.ci_commit(commit.sha)).to eq(commit) } + it { expect(project.ci_commit(commit.sha, 'master')).to eq(commit) } end describe :builds_enabled do diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 967c34800d..5ead735be4 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -59,7 +59,7 @@ describe API::API, api: true do describe 'GET /projects/:id/repository/commits/:sha/builds' do before do - project.ensure_ci_commit(commit.sha) + project.ensure_ci_commit(commit.sha, 'master') get api("/projects/#{project.id}/repository/commits/#{commit.sha}/builds", api_user) end diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_status_spec.rb index 429a24109f..f3785b1936 100644 --- a/spec/requests/api/commit_status_spec.rb +++ b/spec/requests/api/commit_status_spec.rb @@ -16,7 +16,8 @@ describe API::CommitStatus, api: true do let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" } context 'ci commit exists' do - let!(:ci_commit) { project.ensure_ci_commit(commit.id) } + let!(:master) { project.ci_commits.create(sha: commit.id, ref: 'master') } + let!(:develop) { project.ci_commits.create(sha: commit.id, ref: 'develop') } it_behaves_like 'a paginated resources' do let(:request) { get api(get_url, reporter) } @@ -25,16 +26,16 @@ describe API::CommitStatus, api: true do context "reporter user" do let(:statuses_id) { json_response.map { |status| status['id'] } } - def create_status(opts = {}) - create(:commit_status, { commit: ci_commit }.merge(opts)) + def create_status(commit, opts = {}) + create(:commit_status, { commit: commit, ref: commit.ref }.merge(opts)) end - let!(:status1) { create_status(status: 'running') } - let!(:status2) { create_status(name: 'coverage', status: 'pending') } - let!(:status3) { create_status(ref: 'develop', status: 'running', allow_failure: true) } - let!(:status4) { create_status(name: 'coverage', status: 'success') } - let!(:status5) { create_status(name: 'coverage', ref: 'develop', status: 'success') } - let!(:status6) { create_status(status: 'success') } + let!(:status1) { create_status(master, status: 'running') } + let!(:status2) { create_status(master, name: 'coverage', status: 'pending') } + let!(:status3) { create_status(develop, status: 'running', allow_failure: true) } + let!(:status4) { create_status(master, name: 'coverage', status: 'success') } + let!(:status5) { create_status(develop, name: 'coverage', status: 'success') } + let!(:status6) { create_status(master, status: 'success') } context 'latest commit statuses' do before { get api(get_url, reporter) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 7ff21175c1..25377a4044 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -51,11 +51,11 @@ describe API::API, api: true do it "should return not_found for CI status" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response.status).to eq(200) - expect(json_response['status']).to eq('not_found') + expect(json_response['status']).to be_nil end it "should return status for CI" do - ci_commit = project.ensure_ci_commit(project.repository.commit.sha) + ci_commit = project.ensure_ci_commit(project.repository.commit.sha, 'master') get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response.status).to eq(200) expect(json_response['status']).to eq(ci_commit.status) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 57d7eb927f..b652b488b5 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -20,8 +20,8 @@ describe Ci::API::API do describe "POST /builds/register" do it "should start a build" do - commit = FactoryGirl.create(:ci_commit, project: project) - commit.create_builds('master', false, nil) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + commit.create_builds(nil) build = commit.builds.first post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -56,8 +56,8 @@ describe Ci::API::API do end it "returns options" do - commit = FactoryGirl.create(:ci_commit, project: project) - commit.create_builds('master', false, nil) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + commit.create_builds(nil) post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -66,8 +66,8 @@ describe Ci::API::API do end it "returns variables" do - commit = FactoryGirl.create(:ci_commit, project: project) - commit.create_builds('master', false, nil) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + commit.create_builds(nil) project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -83,10 +83,10 @@ describe Ci::API::API do it "returns variables for triggers" do trigger = FactoryGirl.create(:ci_trigger, project: project) - commit = FactoryGirl.create(:ci_commit, project: project) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') trigger_request = FactoryGirl.create(:ci_trigger_request_with_variables, commit: commit, trigger: trigger) - commit.create_builds('master', false, nil, trigger_request) + commit.create_builds(nil, trigger_request) project.variables << Ci::Variable.new(key: "SECRET_KEY", value: "secret_value") post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } @@ -103,8 +103,8 @@ describe Ci::API::API do end it "returns dependent builds" do - commit = FactoryGirl.create(:ci_commit, project: project) - commit.create_builds('master', false, nil, nil) + commit = FactoryGirl.create(:ci_commit, project: project, ref: 'master') + commit.create_builds(nil, nil) commit.builds.where(stage: 'test').each(&:success) post ci_api("/builds/register"), token: runner.token, info: { platform: :darwin } diff --git a/spec/services/ci/create_builds_service_spec.rb b/spec/services/ci/create_builds_service_spec.rb index 1fca362868..ecc3a88a26 100644 --- a/spec/services/ci/create_builds_service_spec.rb +++ b/spec/services/ci/create_builds_service_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::CreateBuildsService, services: true do - let(:commit) { create(:ci_commit) } + let(:commit) { create(:ci_commit, ref: 'master') } let(:user) { create(:user) } describe '#execute' do @@ -9,7 +9,7 @@ describe Ci::CreateBuildsService, services: true do # subject do - described_class.new.execute(commit, 'test', 'master', nil, user, nil, status) + described_class.new(commit).execute(commit, nil, user, status) end context 'next builds available' do diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb index 870861ad20..4cc4b3870d 100644 --- a/spec/services/ci/image_for_build_service_spec.rb +++ b/spec/services/ci/image_for_build_service_spec.rb @@ -5,7 +5,7 @@ module Ci let(:service) { ImageForBuildService.new } let(:project) { FactoryGirl.create(:empty_project) } let(:commit_sha) { '01234567890123456789' } - let(:commit) { project.ensure_ci_commit(commit_sha) } + let(:commit) { project.ensure_ci_commit(commit_sha, 'master') } let(:build) { FactoryGirl.create(:ci_build, commit: commit) } describe :execute do From ac50f9dddfb4555a2c356454c1cbfc1cc0b37d6d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 10:23:31 +0200 Subject: [PATCH 003/113] Fix rest of rspec and spinach tests --- app/models/ci/commit.rb | 10 +++++----- app/models/concerns/ci_status.rb | 6 ++++-- app/services/ci/create_builds_service.rb | 2 +- features/steps/shared/builds.rb | 6 +++--- features/steps/shared/project.rb | 2 +- spec/lib/ci/status_spec.rb | 15 ++++++++++++--- 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index d09866a4bd..2ab1fefe8e 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -117,7 +117,7 @@ module Ci # get status for all prior builds prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } - status = Ci::Status.get_status(prior_builds) + status = prior_builds.status # create builds for next stages based next_stages.any? do |stage| @@ -185,7 +185,7 @@ module Ci private def update_status - status = + self.status = if yaml_errors.present? 'failed' else @@ -194,17 +194,17 @@ module Ci end def update_started_at - started_at = + self.started_at = statuses.minimum(:started_at) end def update_finished_at - finished_at = + self.finished_at = statuses.maximum(:finished_at) end def update_duration - duration = begin + self.duration = begin duration_array = latest.map(&:duration).compact duration_array.reduce(:+).to_i end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 25bee601f4..6450b6dd20 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -5,7 +5,7 @@ module CiStatus def status objs = all.to_a if objs.none? - nil + nil elsif objs.all? { |status| status.success? || status.try(:ignored?) } 'success' elsif objs.all?(&:pending?) @@ -14,6 +14,8 @@ module CiStatus 'running' elsif objs.all?(&:canceled?) 'canceled' + elsif objs.all?(&:skipped?) + 'skipped' else 'failed' end @@ -56,4 +58,4 @@ module CiStatus def complete? canceled? || success? || failed? end -end \ No newline at end of file +end diff --git a/app/services/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb index bbc8251a2d..18274ce24e 100644 --- a/app/services/ci/create_builds_service.rb +++ b/app/services/ci/create_builds_service.rb @@ -22,7 +22,7 @@ module Ci builds_attrs.map do |build_attrs| # don't create the same build twice unless @commit.builds.find_by(ref: @commit.ref, tag: @commit.tag, - trigger_request: trigger_request, name: build_attrs[:name]) + trigger_request: trigger_request, name: build_attrs[:name]) build_attrs.slice!(:name, :commands, :tag_list, diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb index c4c7672a43..cf30e23b6b 100644 --- a/features/steps/shared/builds.rb +++ b/features/steps/shared/builds.rb @@ -10,16 +10,16 @@ module SharedBuilds end step 'project has a recent build' do - @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha) + @ci_commit = create(:ci_commit, project: @project, sha: @project.commit.sha, ref: 'master') @build = create(:ci_build_with_coverage, commit: @ci_commit) end step 'recent build is successful' do - @build.update_column(:status, 'success') + @build.update(status: 'success') end step 'recent build failed' do - @build.update_column(:status, 'failed') + @build.update(status: 'failed') end step 'project has another build that is running' do diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 3eb0cf00e6..084338f30d 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -230,7 +230,7 @@ module SharedProject step 'project "Shop" has CI build' do project = Project.find_by(name: "Shop") - create :ci_commit, project: project, sha: project.commit.sha, ref: 'master' + commit = create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped' end step 'I should see last commit with CI status' do diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb index 47f3df6e3c..1c37921cd7 100644 --- a/spec/lib/ci/status_spec.rb +++ b/spec/lib/ci/status_spec.rb @@ -1,8 +1,17 @@ require 'spec_helper' -describe Ci::Status do - describe '.get_status' do - subject { described_class.get_status(statuses) } +describe CiStatus do + before do + @object = Object.new + @object.extend(CiStatus::ClassMethods) + end + + describe '.status' do + before do + allow(@object).to receive(:all).and_return(statuses) + end + + subject { @object.status } shared_examples 'build status summary' do context 'all successful' do From 1ae797c22900a6e32d63883f6a2d76c498e20d9b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 11:15:19 +0200 Subject: [PATCH 004/113] Fix create_next_builds method --- app/models/ci/commit.rb | 6 +++--- lib/ci/status.rb | 19 ------------------- 2 files changed, 3 insertions(+), 22 deletions(-) delete mode 100644 lib/ci/status.rb diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 2ab1fefe8e..94951ced1f 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -116,12 +116,12 @@ module Ci next_stages.delete(build.stage) # get status for all prior builds - prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } - status = prior_builds.status + prior_builds = latest_builds.where.not(stage: next_stages) + prior_status = prior_builds.status # create builds for next stages based next_stages.any? do |stage| - CreateBuildsService.new(self).execute(stage, build.user, status, build.trigger_request).present? + CreateBuildsService.new(self).execute(stage, build.user, prior_status, build.trigger_request).present? end end diff --git a/lib/ci/status.rb b/lib/ci/status.rb deleted file mode 100644 index 3fb1fe2949..0000000000 --- a/lib/ci/status.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Ci - class Status - def self.get_status(statuses) - if statuses.none? - 'skipped' - elsif statuses.all? { |status| status.success? || status.ignored? } - 'success' - elsif statuses.all?(&:pending?) - 'pending' - elsif statuses.any?(&:running?) || statuses.any?(&:pending?) - 'running' - elsif statuses.all?(&:canceled?) - 'canceled' - else - 'failed' - end - end - end -end From 89f0dc713ca07fe935fa9ce2c31d0ca6febb5d6c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 12:33:04 +0200 Subject: [PATCH 005/113] Fix rubocop --- features/steps/shared/project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 084338f30d..ea5f958030 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -230,7 +230,7 @@ module SharedProject step 'project "Shop" has CI build' do project = Project.find_by(name: "Shop") - commit = create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped' + create :ci_commit, project: project, sha: project.commit.sha, ref: 'master', status: 'skipped' end step 'I should see last commit with CI status' do From 8e84acbf2e7e306ba937aaae87cfed35d2632e10 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 19:57:54 +0200 Subject: [PATCH 006/113] Optimise CI status accessor --- app/models/ci/commit.rb | 6 +++- app/models/commit_status.rb | 35 +++++++++++++++++++ app/models/concerns/ci_status.rb | 1 + .../20160412174954_add_ci_commit_indexes.rb | 7 ++++ db/migrate/20160412175417_update_ci_commit.rb | 31 ++++++++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160412174954_add_ci_commit_indexes.rb create mode 100644 db/migrate/20160412175417_update_ci_commit.rb diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 94951ced1f..8865bd76bd 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -44,8 +44,12 @@ module Ci sha[0...8] end + def self.stages + CommitStatus.where(commit: all).stages + end + def stages - statuses.group(:stage).order(:stage_idx).pluck(:stage) + statuses.stages end def project_id diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index e8a331e720..8a7b1f00cd 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -49,6 +49,7 @@ class CommitStatus < ActiveRecord::Base scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :commit_id)) } scope :ordered, -> { order(:ref, :stage_idx, :name) } + scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] @@ -84,6 +85,40 @@ class CommitStatus < ActiveRecord::Base delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false + def self.stages + order_by = 'max(stage_idx)' + group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact + end + + def self.status_sql + builds = all.select('count(id)').to_sql + success = all.success.select('count(id)').to_sql + ignored = all.failed.where(allow_failure: true).select('count(id)').to_sql if all.try(:ignored) + ignored ||= '0' + pending = all.pending.select('count(id)').to_sql + running = all.running.select('count(id)').to_sql + canceled = all.canceled.select('count(id)').to_sql + + deduce_status = "(CASE + WHEN (#{builds})=0 THEN 'skipped' + WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' + WHEN (#{builds})=(#{pending}) THEN 'pending' + WHEN (#{builds})=(#{canceled}) THEN 'canceled' + WHEN (#{running})+(#{pending})>0 THEN 'running' + ELSE 'failed' + END)" + + deduce_status + end + + def self.status + pluck(self.status_sql).first + end + + def self.stages_status + Hash[group(:stage).pluck(:stage, self.status_sql)] + end + def ignored? allow_failure? && (failed? || canceled?) end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 6450b6dd20..88b890376c 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -43,6 +43,7 @@ module CiStatus scope :pending, -> { where(status: 'pending') } scope :success, -> { where(status: 'success') } scope :failed, -> { where(status: 'failed') } + scope :canceled, -> { where(status: 'canceled') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } end diff --git a/db/migrate/20160412174954_add_ci_commit_indexes.rb b/db/migrate/20160412174954_add_ci_commit_indexes.rb new file mode 100644 index 0000000000..49fbb3e9bd --- /dev/null +++ b/db/migrate/20160412174954_add_ci_commit_indexes.rb @@ -0,0 +1,7 @@ +class AddCiCommitIndexes < ActiveRecord::Migration + def change + add_index :ci_commits, [:gl_project_id, :sha] + add_index :ci_commits, [:gl_project_id, :status] + add_index :ci_commits, [:status] + end +end diff --git a/db/migrate/20160412175417_update_ci_commit.rb b/db/migrate/20160412175417_update_ci_commit.rb new file mode 100644 index 0000000000..ebe1d143b1 --- /dev/null +++ b/db/migrate/20160412175417_update_ci_commit.rb @@ -0,0 +1,31 @@ +class UpdateCiCommit < ActiveRecord::Migration + def change + execute("UPDATE ci_commits SET status=#{status}, ref=#{ref}, tag=#{tag} WHERE status IS NULL") + end + + def status + builds = '(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)' + success = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')" + ignored = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND (status='failed' OR status='canceled') AND allow_failure)" + pending = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='pending')" + running = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='running')" + canceled = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='canceled')" + + "(CASE + WHEN #{builds}=0 THEN 'skipped' + WHEN #{builds}=#{success}+#{ignored} THEN 'success' + WHEN #{builds}=#{pending} THEN 'pending' + WHEN #{builds}=#{canceled} THEN 'canceled' + WHEN #{running}+#{pending}>0 THEN 'running' + ELSE 'failed' + END)" + end + + def ref + '(SELECT ref FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)' + end + + def tag + '(SELECT tag FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id ORDER BY id DESC LIMIT 1)' + end +end From 234be12e4ee24cda6f6b7963d54af00ceb5e26c8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 12 Apr 2016 19:59:44 +0200 Subject: [PATCH 007/113] Optimise CI status accessor --- app/models/commit_status.rb | 25 --------------------- app/models/concerns/ci_status.rb | 38 ++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 8a7b1f00cd..882595ca21 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -90,31 +90,6 @@ class CommitStatus < ActiveRecord::Base group('stage').order(order_by).pluck(:stage, order_by).map(&:first).compact end - def self.status_sql - builds = all.select('count(id)').to_sql - success = all.success.select('count(id)').to_sql - ignored = all.failed.where(allow_failure: true).select('count(id)').to_sql if all.try(:ignored) - ignored ||= '0' - pending = all.pending.select('count(id)').to_sql - running = all.running.select('count(id)').to_sql - canceled = all.canceled.select('count(id)').to_sql - - deduce_status = "(CASE - WHEN (#{builds})=0 THEN 'skipped' - WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' - WHEN (#{builds})=(#{pending}) THEN 'pending' - WHEN (#{builds})=(#{canceled}) THEN 'canceled' - WHEN (#{running})+(#{pending})>0 THEN 'running' - ELSE 'failed' - END)" - - deduce_status - end - - def self.status - pluck(self.status_sql).first - end - def self.stages_status Hash[group(:stage).pluck(:stage, self.status_sql)] end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 88b890376c..4c7089bc36 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -2,23 +2,29 @@ module CiStatus extend ActiveSupport::Concern module ClassMethods + def status_sql + builds = all.select('count(id)').to_sql + success = all.success.select('count(id)').to_sql + ignored = all.failed.where(allow_failure: true).select('count(id)').to_sql if all.try(:ignored) + ignored ||= '0' + pending = all.pending.select('count(id)').to_sql + running = all.running.select('count(id)').to_sql + canceled = all.canceled.select('count(id)').to_sql + + deduce_status = "(CASE + WHEN (#{builds})=0 THEN 'skipped' + WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' + WHEN (#{builds})=(#{pending}) THEN 'pending' + WHEN (#{builds})=(#{canceled}) THEN 'canceled' + WHEN (#{running})+(#{pending})>0 THEN 'running' + ELSE 'failed' + END)" + + deduce_status + end + def status - objs = all.to_a - if objs.none? - nil - elsif objs.all? { |status| status.success? || status.try(:ignored?) } - 'success' - elsif objs.all?(&:pending?) - 'pending' - elsif objs.any?(&:running?) || all.any?(&:pending?) - 'running' - elsif objs.all?(&:canceled?) - 'canceled' - elsif objs.all?(&:skipped?) - 'skipped' - else - 'failed' - end + pluck(self.status_sql).first end def duration From 4d72ca39803615850267d034e9dc59540fe657b7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 11:01:10 +0200 Subject: [PATCH 008/113] Remove the use of default scope for Builds --- app/models/ci/build.rb | 11 - db/schema.rb | 598 ++++++++++++++++++++--------------------- 2 files changed, 296 insertions(+), 313 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 15fc714b53..085ecc6951 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -37,8 +37,6 @@ module Ci class Build < CommitStatus - LAZY_ATTRIBUTES = ['trace'] - belongs_to :runner, class_name: 'Ci::Runner' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :erased_by, class_name: 'User' @@ -56,20 +54,11 @@ module Ci acts_as_taggable - # To prevent db load megabytes of data from trace - default_scope -> { select(Ci::Build.columns_without_lazy) } - before_destroy { project } after_create :execute_hooks class << self - def columns_without_lazy - (column_names - LAZY_ATTRIBUTES).map do |column_name| - "#{table_name}.#{column_name}" - end - end - def last_month where('created_at > ?', Date.today - 1.month) end diff --git a/db/schema.rb b/db/schema.rb index 72d6391338..e000e35fca 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160411122626) do +ActiveRecord::Schema.define(version: 20160412175417) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -42,48 +42,48 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.text "sign_in_text" t.datetime "created_at" t.datetime "updated_at" - t.string "home_page_url" - t.integer "default_branch_protection", default: 2 + t.string "home_page_url", limit: 255 + t.integer "default_branch_protection", default: 2 t.text "restricted_visibility_levels" - t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false + t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" - t.boolean "user_oauth_applications", default: true - t.string "after_sign_out_path" - t.integer "session_expire_delay", default: 10080, null: false + t.boolean "version_check_enabled", default: true + t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path", limit: 255 + t.integer "session_expire_delay", default: 10080, null: false t.text "import_sources" t.text "help_page_text" - t.string "admin_notification_email" - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false + t.string "admin_notification_email", limit: 255 + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false t.string "recaptcha_site_key" t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 - t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false + t.integer "metrics_port", default: 8089 + t.integer "metrics_sample_interval", default: 15 + t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "akismet_enabled", default: false + t.boolean "akismet_enabled", default: false t.string "akismet_api_key" - t.boolean "email_author_in_body", default: false + t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" end create_table "audit_events", force: :cascade do |t| - t.integer "author_id", null: false - t.string "type", null: false - t.integer "entity_id", null: false - t.string "entity_type", null: false + t.integer "author_id", null: false + t.string "type", limit: 255, null: false + t.integer "entity_id", null: false + t.string "entity_type", limit: 255, null: false t.text "details" t.datetime "created_at" t.datetime "updated_at" @@ -94,13 +94,13 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" - t.datetime "created_at" - t.datetime "updated_at" - t.string "color" - t.string "font" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "color", limit: 510 + t.string "font", limit: 510 end create_table "ci_application_settings", force: :cascade do |t| @@ -112,7 +112,7 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "ci_builds", force: :cascade do |t| t.integer "project_id" - t.string "status" + t.string "status", limit: 255 t.datetime "finished_at" t.text "trace" t.datetime "created_at" @@ -123,29 +123,29 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.integer "commit_id" t.text "commands" t.integer "job_id" - t.string "name" - t.boolean "deploy", default: false + t.string "name", limit: 255 + t.boolean "deploy", default: false t.text "options" - t.boolean "allow_failure", default: false, null: false - t.string "stage" + t.boolean "allow_failure", default: false, null: false + t.string "stage", limit: 255 t.integer "trigger_request_id" t.integer "stage_idx" t.boolean "tag" - t.string "ref" + t.string "ref", limit: 255 t.integer "user_id" - t.string "type" - t.string "target_url" - t.string "description" + t.string "type", limit: 255 + t.string "target_url", limit: 255 + t.string "description", limit: 255 t.text "artifacts_file" t.integer "gl_project_id" t.text "artifacts_metadata" t.integer "erased_by_id" t.datetime "erased_at" - t.string "plugin" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree + add_index "ci_builds", ["commit_id", "status"], name: "index_ci_builds_on_commit_id_and_status", using: :btree add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree @@ -155,17 +155,18 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree + add_index "ci_builds", ["type", "status", "runner_id"], name: "index_ci_builds_on_test2", using: :btree add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree create_table "ci_commits", force: :cascade do |t| t.integer "project_id" - t.string "ref" - t.string "sha" - t.string "before_sha" + t.string "ref", limit: 255 + t.string "sha", limit: 255 + t.string "before_sha", limit: 255 t.text "push_data" t.datetime "created_at" t.datetime "updated_at" - t.boolean "tag", default: false + t.boolean "tag", default: false t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" @@ -176,12 +177,15 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.integer "duration" end + add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree + add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree + add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree create_table "ci_events", force: :cascade do |t| t.integer "project_id" @@ -197,16 +201,16 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree create_table "ci_jobs", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id", null: false t.text "commands" - t.boolean "active", default: true, null: false + t.boolean "active", default: true, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "build_branches", default: true, null: false - t.boolean "build_tags", default: false, null: false - t.string "job_type", default: "parallel" - t.string "refs" + t.string "name", limit: 255 + t.boolean "build_branches", default: true, null: false + t.boolean "build_tags", default: false, null: false + t.string "job_type", limit: 255, default: "parallel" + t.string "refs", limit: 255 t.datetime "deleted_at" end @@ -214,25 +218,25 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree create_table "ci_projects", force: :cascade do |t| - t.string "name" - t.integer "timeout", default: 3600, null: false + t.string "name", limit: 255 + t.integer "timeout", default: 3600, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "token" - t.string "default_ref" - t.string "path" - t.boolean "always_build", default: false, null: false + t.string "token", limit: 255 + t.string "default_ref", limit: 255 + t.string "path", limit: 255 + t.boolean "always_build", default: false, null: false t.integer "polling_interval" - t.boolean "public", default: false, null: false - t.string "ssh_url_to_repo" + t.boolean "public", default: false, null: false + t.string "ssh_url_to_repo", limit: 255 t.integer "gitlab_id" - t.boolean "allow_git_fetch", default: true, null: false - t.string "email_recipients", default: "", null: false - t.boolean "email_add_pusher", default: true, null: false - t.boolean "email_only_broken_builds", default: true, null: false - t.string "skip_refs" - t.string "coverage_regex" - t.boolean "shared_runners_enabled", default: false + t.boolean "allow_git_fetch", default: true, null: false + t.string "email_recipients", limit: 255, default: "", null: false + t.boolean "email_add_pusher", default: true, null: false + t.boolean "email_only_broken_builds", default: true, null: false + t.string "skip_refs", limit: 255 + t.string "coverage_regex", limit: 255 + t.boolean "shared_runners_enabled", default: false t.text "generated_yaml_config" end @@ -251,18 +255,18 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree create_table "ci_runners", force: :cascade do |t| - t.string "token" + t.string "token", limit: 255 t.datetime "created_at" t.datetime "updated_at" - t.string "description" + t.string "description", limit: 255 t.datetime "contacted_at" - t.boolean "active", default: true, null: false - t.boolean "is_shared", default: false - t.string "name" - t.string "version" - t.string "revision" - t.string "platform" - t.string "architecture" + t.boolean "active", default: true, null: false + t.boolean "is_shared", default: false + t.string "name", limit: 255 + t.string "version", limit: 255 + t.string "revision", limit: 255 + t.string "platform", limit: 255 + t.string "architecture", limit: 255 end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} @@ -270,19 +274,19 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} create_table "ci_services", force: :cascade do |t| - t.string "type" - t.string "title" - t.integer "project_id", null: false + t.string "type", limit: 255 + t.string "title", limit: 255 + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.boolean "active", default: false, null: false t.text "properties" end add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree create_table "ci_sessions", force: :cascade do |t| - t.string "session_id", null: false + t.string "session_id", limit: 255, null: false t.text "data" t.datetime "created_at" t.datetime "updated_at" @@ -294,9 +298,9 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "ci_taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type" + t.string "taggable_type", limit: 255 t.integer "tagger_id" - t.string "tagger_type" + t.string "tagger_type", limit: 255 t.string "context", limit: 128 t.datetime "created_at" end @@ -305,31 +309,22 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "ci_tags", force: :cascade do |t| - t.string "name" - t.integer "taggings_count", default: 0 + t.string "name", limit: 255 + t.integer "taggings_count", default: 0 end add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree create_table "ci_trigger_requests", force: :cascade do |t| - t.integer "trigger_id" + t.integer "trigger_id", null: false t.text "variables" t.datetime "created_at" t.datetime "updated_at" t.integer "commit_id" - t.string "sha" - t.string "before_sha" - t.string "ref" - t.string "action" - t.string "status" - t.datetime "started_at" - t.datetime "finished_at" - t.integer "project_id" - t.string "origin_ref" end create_table "ci_triggers", force: :cascade do |t| - t.string "token" + t.string "token", limit: 255 t.integer "project_id" t.datetime "deleted_at" t.datetime "created_at" @@ -342,19 +337,19 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "ci_variables", force: :cascade do |t| t.integer "project_id" - t.string "key" + t.string "key", limit: 255 t.text "value" t.text "encrypted_value" - t.string "encrypted_value_salt" - t.string "encrypted_value_iv" + t.string "encrypted_value_salt", limit: 255 + t.string "encrypted_value_iv", limit: 255 t.integer "gl_project_id" end add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree create_table "ci_web_hooks", force: :cascade do |t| - t.string "url", null: false - t.integer "project_id", null: false + t.string "url", limit: 255, null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -362,30 +357,30 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "deploy_keys_projects", force: :cascade do |t| t.integer "deploy_key_id", null: false t.integer "project_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree create_table "emails", force: :cascade do |t| - t.integer "user_id", null: false - t.string "email", null: false + t.integer "user_id", null: false + t.string "email", limit: 510, null: false t.datetime "created_at" t.datetime "updated_at" end - add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree + add_index "emails", ["email"], name: "emails_email_key", unique: true, using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "events", force: :cascade do |t| - t.string "target_type" + t.string "target_type", limit: 510 t.integer "target_id" - t.string "title" + t.string "title", limit: 510 t.text "data" t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.integer "action" t.integer "author_id" end @@ -400,15 +395,15 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "forked_project_links", force: :cascade do |t| t.integer "forked_to_project_id", null: false t.integer "forked_from_project_id", null: false - t.datetime "created_at" - t.datetime "updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree + add_index "forked_project_links", ["forked_to_project_id"], name: "forked_project_links_forked_to_project_id_key", unique: true, using: :btree create_table "identities", force: :cascade do |t| - t.string "extern_uid" - t.string "provider" + t.string "extern_uid", limit: 255 + t.string "provider", limit: 255 t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" @@ -418,21 +413,21 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issues", force: :cascade do |t| - t.string "title" + t.string "title", limit: 510 t.integer "assignee_id" t.integer "author_id" t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 - t.string "branch_name" + t.integer "position", default: 0 + t.string "branch_name", limit: 510 t.text "description" t.integer "milestone_id" - t.string "state" + t.string "state", limit: 510 t.integer "iid" t.integer "updated_by_id" + t.boolean "confidential", default: false t.integer "moved_to_id" - t.boolean "confidential", default: false t.datetime "deleted_at" end @@ -455,10 +450,10 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.datetime "created_at" t.datetime "updated_at" t.text "key" - t.string "title" - t.string "type" - t.string "fingerprint" - t.boolean "public", default: false, null: false + t.string "title", limit: 510 + t.string "type", limit: 510 + t.string "fingerprint", limit: 510 + t.boolean "public", default: false, null: false end add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree @@ -467,7 +462,7 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "label_links", force: :cascade do |t| t.integer "label_id" t.integer "target_id" - t.string "target_type" + t.string "target_type", limit: 255 t.datetime "created_at" t.datetime "updated_at" end @@ -476,23 +471,23 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree create_table "labels", force: :cascade do |t| - t.string "title" - t.string "color" + t.string "title", limit: 255 + t.string "color", limit: 255 t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false + t.boolean "template", default: false t.string "description" end add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree create_table "lfs_objects", force: :cascade do |t| - t.string "oid", null: false - t.integer "size", limit: 8, null: false + t.string "oid", limit: 255, null: false + t.integer "size", limit: 8, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "file" + t.string "file", limit: 255 end add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree @@ -507,17 +502,17 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree create_table "members", force: :cascade do |t| - t.integer "access_level", null: false - t.integer "source_id", null: false - t.string "source_type", null: false + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", limit: 255, null: false t.integer "user_id" - t.integer "notification_level", null: false - t.string "type" + t.integer "notification_level", null: false + t.string "type", limit: 255 t.datetime "created_at" t.datetime "updated_at" t.integer "created_by_id" - t.string "invite_email" - t.string "invite_token" + t.string "invite_email", limit: 255 + t.string "invite_token", limit: 255 t.datetime "invite_accepted_at" end @@ -529,10 +524,10 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree create_table "merge_request_diffs", force: :cascade do |t| - t.string "state" + t.string "state", limit: 255 t.text "st_commits" t.text "st_diffs" - t.integer "merge_request_id", null: false + t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" t.string "base_commit_sha" @@ -542,26 +537,26 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", null: false - t.string "source_branch", null: false - t.integer "source_project_id", null: false + t.string "target_branch", limit: 510, null: false + t.string "source_branch", limit: 510, null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" - t.string "title" + t.string "title", limit: 510 t.datetime "created_at" t.datetime "updated_at" t.integer "milestone_id" - t.string "state" - t.string "merge_status" - t.integer "target_project_id", null: false + t.string "state", limit: 510 + t.string "merge_status", limit: 510 + t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.integer "position", default: 0 + t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" - t.string "merge_error" + t.string "merge_error", limit: 255 t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false + t.boolean "merge_when_build_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" t.datetime "deleted_at" @@ -582,13 +577,13 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "milestones", force: :cascade do |t| - t.string "title", null: false - t.integer "project_id", null: false + t.string "title", limit: 510, null: false + t.integer "project_id", null: false t.text "description" t.date "due_date" - t.datetime "created_at" - t.datetime "updated_at" - t.string "state" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "state", limit: 510 t.integer "iid" end @@ -601,16 +596,16 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false + t.string "name", limit: 510, null: false + t.string "path", limit: 510, null: false t.integer "owner_id" - t.datetime "created_at" - t.datetime "updated_at" - t.string "type" - t.string "description", default: "", null: false - t.string "avatar" - t.boolean "share_with_group_lock", default: false - t.integer "visibility_level", default: 20, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "type", limit: 510 + t.string "description", limit: 510, default: "", null: false + t.string "avatar", limit: 510 + t.boolean "share_with_group_lock", default: false + t.integer "visibility_level", default: 20, null: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree @@ -624,19 +619,19 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "notes", force: :cascade do |t| t.text "note" - t.string "noteable_type" + t.string "noteable_type", limit: 510 t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" t.integer "project_id" - t.string "attachment" - t.string "line_code" - t.string "commit_id" + t.string "attachment", limit: 510 + t.string "line_code", limit: 510 + t.string "commit_id", limit: 510 t.integer "noteable_id" - t.boolean "system", default: false, null: false t.text "st_diff" + t.boolean "system", null: false t.integer "updated_by_id" - t.boolean "is_award", default: false, null: false + t.boolean "is_award", default: false, null: false end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -653,14 +648,14 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", limit: 255, null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false t.datetime "revoked_at" - t.string "scopes" + t.string "scopes", limit: 255 end add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree @@ -668,12 +663,12 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "oauth_access_tokens", force: :cascade do |t| t.integer "resource_owner_id" t.integer "application_id" - t.string "token", null: false - t.string "refresh_token" + t.string "token", limit: 255, null: false + t.string "refresh_token", limit: 255 t.integer "expires_in" t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes" + t.datetime "created_at", null: false + t.string "scopes", limit: 255 end add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree @@ -681,15 +676,15 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree create_table "oauth_applications", force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false + t.string "name", limit: 255, null: false + t.string "uid", limit: 255, null: false + t.string "secret", limit: 255, null: false + t.text "redirect_uri", null: false + t.string "scopes", limit: 255, default: "", null: false t.datetime "created_at" t.datetime "updated_at" t.integer "owner_id" - t.string "owner_type" + t.string "owner_type", limit: 255 end add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree @@ -709,44 +704,43 @@ ActiveRecord::Schema.define(version: 20160411122626) do end create_table "projects", force: :cascade do |t| - t.string "name" - t.string "path" + t.string "name", limit: 510 + t.string "path", limit: 510 t.text "description" t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", default: true, null: false - t.boolean "wall_enabled", default: true, null: false - t.boolean "merge_requests_enabled", default: true, null: false - t.boolean "wiki_enabled", default: true, null: false + t.boolean "issues_enabled", null: false + t.boolean "wall_enabled", null: false + t.boolean "merge_requests_enabled", null: false + t.boolean "wiki_enabled", null: false t.integer "namespace_id" - t.string "issues_tracker", default: "gitlab", null: false - t.string "issues_tracker_id" - t.boolean "snippets_enabled", default: true, null: false + t.string "issues_tracker", limit: 510, default: "gitlab", null: false + t.string "issues_tracker_id", limit: 510 + t.boolean "snippets_enabled", null: false t.datetime "last_activity_at" - t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false - t.string "avatar" - t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type" - t.string "import_source" - t.integer "commit_count", default: 0 + t.string "import_url", limit: 510 + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", null: false + t.string "import_status", limit: 255 + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type", limit: 255 + t.string "import_source", limit: 255 + t.string "avatar", limit: 255 + t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false t.string "main_language" - t.integer "pushes_since_gc", default: 0 - t.boolean "images_enabled" + t.integer "pushes_since_gc", default: 0 end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree @@ -766,17 +760,17 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "developers_can_push", default: false, null: false + t.integer "project_id", null: false + t.string "name", limit: 510, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "developers_can_push", default: false, null: false end add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree create_table "releases", force: :cascade do |t| - t.string "tag" + t.string "tag", limit: 255 t.text "description" t.integer "project_id" t.datetime "created_at" @@ -789,32 +783,32 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" - t.string "noteable_type" + t.string "noteable_type", limit: 255 t.integer "recipient_id" - t.string "commit_id" - t.string "reply_key", null: false - t.string "line_code" + t.string "commit_id", limit: 255 + t.string "reply_key", limit: 255, null: false + t.string "line_code", limit: 255 end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree create_table "services", force: :cascade do |t| - t.string "type" - t.string "title" + t.string "type", limit: 510 + t.string "title", limit: 510 t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", null: false t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false end add_index "services", ["category"], name: "index_services_on_category", using: :btree @@ -824,15 +818,15 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "services", ["template"], name: "index_services_on_template", using: :btree create_table "snippets", force: :cascade do |t| - t.string "title" + t.string "title", limit: 510 t.text "content" - t.integer "author_id", null: false + t.integer "author_id", null: false t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "file_name" - t.string "type" - t.integer "visibility_level", default: 0, null: false + t.string "file_name", limit: 510 + t.string "type", limit: 510 + t.integer "visibility_level", default: 0, null: false end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -860,7 +854,7 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "subscriptions", force: :cascade do |t| t.integer "user_id" t.integer "subscribable_id" - t.string "subscribable_type" + t.string "subscribable_type", limit: 255 t.boolean "subscribed" t.datetime "created_at" t.datetime "updated_at" @@ -871,10 +865,10 @@ ActiveRecord::Schema.define(version: 20160411122626) do create_table "taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type" + t.string "taggable_type", limit: 510 t.integer "tagger_id" - t.string "tagger_type" - t.string "context" + t.string "tagger_type", limit: 510 + t.string "context", limit: 510 t.datetime "created_at" end @@ -882,8 +876,8 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name" - t.integer "taggings_count", default: 0 + t.string "name", limit: 510 + t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree @@ -911,76 +905,76 @@ ActiveRecord::Schema.define(version: 20160411122626) do add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", limit: 510, default: "", null: false + t.string "encrypted_password", limit: 256, default: "", null: false + t.string "reset_password_token", limit: 510 t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" + t.string "current_sign_in_ip", limit: 510 + t.string "last_sign_in_ip", limit: 510 t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false - t.string "authentication_token" - t.integer "theme_id", default: 1, null: false - t.string "bio" - t.integer "failed_attempts", default: 0 + t.string "name", limit: 510 + t.boolean "admin", null: false + t.integer "projects_limit", default: 10 + t.string "skype", limit: 510, default: "", null: false + t.string "linkedin", limit: 510, default: "", null: false + t.string "twitter", limit: 510, default: "", null: false + t.string "authentication_token", limit: 510 + t.integer "theme_id", default: 1, null: false + t.string "bio", limit: 510 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false - t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.string "username", limit: 510 + t.boolean "can_create_group", null: false + t.boolean "can_create_team", null: false + t.string "state", limit: 510 + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" - t.datetime "last_credential_check_at" - t.string "avatar" - t.string "confirmation_token" + t.string "avatar", limit: 510 + t.string "confirmation_token", limit: 510 t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location" - t.string "encrypted_otp_secret" - t.string "encrypted_otp_secret_iv" - t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false + t.string "unconfirmed_email", limit: 510 + t.boolean "hide_no_ssh_key" + t.string "website_url", limit: 510, default: "", null: false + t.datetime "last_credential_check_at" + t.string "notification_email", limit: 255 + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location", limit: 255 + t.string "public_email", limit: 255, default: "", null: false + t.string "encrypted_otp_secret", limit: 255 + t.string "encrypted_otp_secret_iv", limit: 255 + t.string "encrypted_otp_secret_salt", limit: 255 + t.boolean "otp_required_for_login", default: false, null: false t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false t.string "unlock_token" t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false + t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree - add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree - add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + add_index "users", ["authentication_token"], name: "users_authentication_token_key", unique: true, using: :btree + add_index "users", ["confirmation_token"], name: "users_confirmation_token_key", unique: true, using: :btree add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree - add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} + add_index "users", ["email"], name: "users_email_key", unique: true, using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} - add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + add_index "users", ["reset_password_token"], name: "users_reset_password_token_key", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} @@ -1000,11 +994,11 @@ ActiveRecord::Schema.define(version: 20160411122626) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", default: "ProjectHook" + t.string "type", limit: 510, default: "ProjectHook" t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false + t.boolean "push_events", null: false + t.boolean "issues_events", null: false + t.boolean "merge_requests_events", null: false t.boolean "tag_push_events", default: false t.boolean "note_events", default: false, null: false t.boolean "enable_ssl_verification", default: true From 102537072bd56bac0e66533b8fa7166938687592 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 15:40:12 +0200 Subject: [PATCH 009/113] Fix CiStatus implementation and tests --- app/models/commit_status.rb | 2 -- app/models/concerns/ci_status.rb | 20 +++++++++++--------- spec/lib/ci/status_spec.rb | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 882595ca21..66eb5dcecf 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -51,8 +51,6 @@ class CommitStatus < ActiveRecord::Base scope :ordered, -> { order(:ref, :stage_idx, :name) } scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) } - AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled'] - state_machine :status, initial: :pending do event :run do transition pending: :running diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 4c7089bc36..67e15b2d55 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -1,15 +1,17 @@ module CiStatus extend ActiveSupport::Concern - module ClassMethods + AVAILABLE_STATUSES = %w(pending running success failed canceled skipped) + + class_methods do def status_sql - builds = all.select('count(id)').to_sql - success = all.success.select('count(id)').to_sql - ignored = all.failed.where(allow_failure: true).select('count(id)').to_sql if all.try(:ignored) + builds = all.select('count(*)').to_sql + success = all.success.select('count(*)').to_sql + ignored = all.ignored.select('count(*)').to_sql if all.try(:ignored) ignored ||= '0' - pending = all.pending.select('count(id)').to_sql - running = all.running.select('count(id)').to_sql - canceled = all.canceled.select('count(id)').to_sql + pending = all.pending.select('count(*)').to_sql + running = all.running.select('count(*)').to_sql + canceled = all.canceled.select('count(*)').to_sql deduce_status = "(CASE WHEN (#{builds})=0 THEN 'skipped' @@ -24,7 +26,7 @@ module CiStatus end def status - pluck(self.status_sql).first + all.pluck(self.status_sql).first end def duration @@ -34,7 +36,7 @@ module CiStatus end included do - validates :status, inclusion: { in: %w(pending running failed success canceled skipped) } + validates :status, inclusion: { in: AVAILABLE_STATUSES } state_machine :status, initial: :pending do state :pending, value: 'pending' diff --git a/spec/lib/ci/status_spec.rb b/spec/lib/ci/status_spec.rb index 1c37921cd7..886b82a7af 100644 --- a/spec/lib/ci/status_spec.rb +++ b/spec/lib/ci/status_spec.rb @@ -8,7 +8,7 @@ describe CiStatus do describe '.status' do before do - allow(@object).to receive(:all).and_return(statuses) + allow(@object).to receive(:all).and_return(CommitStatus.where(id: statuses)) end subject { @object.status } From daa29729cc64c4c5ca150993bb2375f4c838b9f4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 15:40:26 +0200 Subject: [PATCH 010/113] Add indexes concurrently on PostgreSQL --- db/migrate/20160412174954_add_ci_commit_indexes.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/db/migrate/20160412174954_add_ci_commit_indexes.rb b/db/migrate/20160412174954_add_ci_commit_indexes.rb index 49fbb3e9bd..4cb6133317 100644 --- a/db/migrate/20160412174954_add_ci_commit_indexes.rb +++ b/db/migrate/20160412174954_add_ci_commit_indexes.rb @@ -1,7 +1,13 @@ class AddCiCommitIndexes < ActiveRecord::Migration + disable_ddl_transaction! + def change - add_index :ci_commits, [:gl_project_id, :sha] - add_index :ci_commits, [:gl_project_id, :status] - add_index :ci_commits, [:status] + add_index :ci_commits, [:gl_project_id, :sha], index_options + add_index :ci_commits, [:gl_project_id, :status], index_options + add_index :ci_commits, [:status], index_options + end + + def index_options + { algorithm: :concurrently } if Gitlab::Database.postgresql? end end From 872bbb9fe2a5fbb5195aa448b615191baae960d7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 15:46:35 +0200 Subject: [PATCH 011/113] Cleanup required migrations --- db/migrate/20160331204039_add_action_to_ci_commit.rb | 5 ----- db/migrate/20160411122626_add_duration_to_ci_commit.rb | 5 ----- ...i_commit.rb => 20160412173416_add_fields_to_ci_commit.rb} | 1 + ...pdate_ci_commit.rb => 20160412173417_update_ci_commit.rb} | 4 ++++ ...it_indexes.rb => 20160412173418_add_ci_commit_indexes.rb} | 2 ++ 5 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 db/migrate/20160331204039_add_action_to_ci_commit.rb delete mode 100644 db/migrate/20160411122626_add_duration_to_ci_commit.rb rename db/migrate/{20160331153918_add_fields_to_ci_commit.rb => 20160412173416_add_fields_to_ci_commit.rb} (82%) rename db/migrate/{20160412175417_update_ci_commit.rb => 20160412173417_update_ci_commit.rb} (88%) rename db/migrate/{20160412174954_add_ci_commit_indexes.rb => 20160412173418_add_ci_commit_indexes.rb} (97%) diff --git a/db/migrate/20160331204039_add_action_to_ci_commit.rb b/db/migrate/20160331204039_add_action_to_ci_commit.rb deleted file mode 100644 index e9f8eb624d..0000000000 --- a/db/migrate/20160331204039_add_action_to_ci_commit.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddActionToCiCommit < ActiveRecord::Migration - def change - add_column :ci_commits, :action, :string - end -end diff --git a/db/migrate/20160411122626_add_duration_to_ci_commit.rb b/db/migrate/20160411122626_add_duration_to_ci_commit.rb deleted file mode 100644 index 7def7a48cd..0000000000 --- a/db/migrate/20160411122626_add_duration_to_ci_commit.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddDurationToCiCommit < ActiveRecord::Migration - def change - add_column :ci_commits, :duration, :integer - end -end diff --git a/db/migrate/20160331153918_add_fields_to_ci_commit.rb b/db/migrate/20160412173416_add_fields_to_ci_commit.rb similarity index 82% rename from db/migrate/20160331153918_add_fields_to_ci_commit.rb rename to db/migrate/20160412173416_add_fields_to_ci_commit.rb index 03eb9ba4e5..125956a3dd 100644 --- a/db/migrate/20160331153918_add_fields_to_ci_commit.rb +++ b/db/migrate/20160412173416_add_fields_to_ci_commit.rb @@ -3,5 +3,6 @@ class AddFieldsToCiCommit < ActiveRecord::Migration add_column :ci_commits, :status, :string add_column :ci_commits, :started_at, :timestamp add_column :ci_commits, :finished_at, :timestamp + add_column :ci_commits, :duration, :integer end end diff --git a/db/migrate/20160412175417_update_ci_commit.rb b/db/migrate/20160412173417_update_ci_commit.rb similarity index 88% rename from db/migrate/20160412175417_update_ci_commit.rb rename to db/migrate/20160412173417_update_ci_commit.rb index ebe1d143b1..fd92444dba 100644 --- a/db/migrate/20160412175417_update_ci_commit.rb +++ b/db/migrate/20160412173417_update_ci_commit.rb @@ -1,8 +1,12 @@ class UpdateCiCommit < ActiveRecord::Migration + # This migration can be run online, but needs to be executed for the second time after restarting Unicorn workers + # Otherwise Offline migration should be used. def change execute("UPDATE ci_commits SET status=#{status}, ref=#{ref}, tag=#{tag} WHERE status IS NULL") end + private + def status builds = '(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id)' success = "(SELECT COUNT(*) FROM ci_builds WHERE ci_builds.commit_id=ci_commits.id AND status='success')" diff --git a/db/migrate/20160412174954_add_ci_commit_indexes.rb b/db/migrate/20160412173418_add_ci_commit_indexes.rb similarity index 97% rename from db/migrate/20160412174954_add_ci_commit_indexes.rb rename to db/migrate/20160412173418_add_ci_commit_indexes.rb index 4cb6133317..c1e238bc02 100644 --- a/db/migrate/20160412174954_add_ci_commit_indexes.rb +++ b/db/migrate/20160412173418_add_ci_commit_indexes.rb @@ -7,6 +7,8 @@ class AddCiCommitIndexes < ActiveRecord::Migration add_index :ci_commits, [:status], index_options end + private + def index_options { algorithm: :concurrently } if Gitlab::Database.postgresql? end From 251a78022d12c62ff738540d4104bbf0730ef234 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 15:58:22 +0200 Subject: [PATCH 012/113] Cleanup changes --- .../projects/pipelines_controller.rb | 0 app/helpers/ci_status_helper.rb | 12 +- app/helpers/gitlab_routing_helper.rb | 8 - app/views/admin/runners/show.html.haml | 2 +- app/views/projects/_last_commit.html.haml | 2 +- app/views/projects/builds/show.html.haml | 6 +- .../projects/ci/commits/_commit.html.haml | 0 .../ci_commits/_header_title.html.haml | 0 app/views/projects/ci_commits/index.html.haml | 0 app/views/projects/ci_commits/new.html.haml | 0 db/schema.rb | 590 +++++++++--------- spec/features/commits_spec.rb | 5 - spec/helpers/ci_status_helper_spec.rb | 2 +- 13 files changed, 305 insertions(+), 322 deletions(-) delete mode 100644 app/controllers/projects/pipelines_controller.rb delete mode 100644 app/views/projects/ci/commits/_commit.html.haml delete mode 100644 app/views/projects/ci_commits/_header_title.html.haml delete mode 100644 app/views/projects/ci_commits/index.html.haml delete mode 100644 app/views/projects/ci_commits/new.html.haml diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index effa7ce77e..417050b413 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -1,4 +1,9 @@ module CiStatusHelper + def ci_status_path(ci_commit) + project = ci_commit.project + builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) + end + def ci_status_with_icon(status, target = nil) content = ci_icon_for_status(status) + ' '.html_safe + ci_label_for_status(status) klass = "ci-status ci-#{status}" @@ -34,10 +39,11 @@ module CiStatusHelper end def render_ci_status(ci_commit, tooltip_placement: 'auto left') - return unless ci_commit.is_a?(Commit) || ci_commit.is_a?(Ci::Commit) - + # TODO: split this method into + # - render_commit_status + # - render_pipeline_status link_to ci_icon_for_status(ci_commit.status), - project_ci_commit_path(ci_commit.project, ci_commit), + ci_status_path(ci_commit), class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}", title: "Build #{ci_label_for_status(ci_commit.status)}", data: { toggle: 'tooltip', placement: tooltip_placement } diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index f1af8e163c..f07eff3fb5 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -33,14 +33,6 @@ module GitlabRoutingHelper namespace_project_builds_path(project.namespace, project, *args) end - def project_commit_path(project, commit) - builds_namespace_project_commit_path(project.namespace, project, commit.id) - end - - def project_ci_commit_path(project, ci_commit) - builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) - end - def activity_project_path(project, *args) activity_namespace_project_path(project.namespace, project, *args) end diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 50d0005140..8700b4820c 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -117,7 +117,7 @@ %td.build-link - if project - = link_to builds_namespace_project_commit_path(project.namespace, project, build.sha) do + = link_to ci_status_path(build.commit) do %strong #{build.commit.short_sha} %td.timestamp diff --git a/app/views/projects/_last_commit.html.haml b/app/views/projects/_last_commit.html.haml index 6eccbaa4bf..66c30283c7 100644 --- a/app/views/projects/_last_commit.html.haml +++ b/app/views/projects/_last_commit.html.haml @@ -1,6 +1,6 @@ .project-last-commit - if commit.status - = link_to project_commit_path(commit.project, commit), class: "ci-status ci-#{commit.status}" do + = link_to builds_namespace_project_commit_path(commit.project.namespace, commit.project, commit), class: "ci-status ci-#{commit.status}" do = ci_icon_for_status(commit.status) = ci_label_for_status(commit.status) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 20160a718b..b50b2cf376 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -4,7 +4,7 @@ .build-page .gray-content-block.top-block Build ##{@build.id} for commit - %strong.monospace= link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha) + %strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit) from = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) - merge_request = @build.merge_request @@ -173,7 +173,7 @@ Commit .pull-right %small - = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace" + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" %p %span.attr-name Branch: = link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref) @@ -196,7 +196,7 @@ .build-widget %h4.title #{pluralize(@builds.count(:id), "other build")} for = succeed ":" do - = link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace" + = link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace" %table.table.builds - @builds.each_with_index do |build, i| %tr.build diff --git a/app/views/projects/ci/commits/_commit.html.haml b/app/views/projects/ci/commits/_commit.html.haml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/views/projects/ci_commits/_header_title.html.haml b/app/views/projects/ci_commits/_header_title.html.haml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/views/projects/ci_commits/index.html.haml b/app/views/projects/ci_commits/index.html.haml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/views/projects/ci_commits/new.html.haml b/app/views/projects/ci_commits/new.html.haml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/db/schema.rb b/db/schema.rb index 90e238fcfe..44482de467 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160412175417) do +ActiveRecord::Schema.define(version: 20160331223143) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -42,48 +42,48 @@ ActiveRecord::Schema.define(version: 20160412175417) do t.text "sign_in_text" t.datetime "created_at" t.datetime "updated_at" - t.string "home_page_url", limit: 255 - t.integer "default_branch_protection", default: 2 + t.string "home_page_url" + t.integer "default_branch_protection", default: 2 t.text "restricted_visibility_levels" - t.integer "max_attachment_size", default: 10, null: false + t.boolean "version_check_enabled", default: true + t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" - t.boolean "version_check_enabled", default: true - t.boolean "user_oauth_applications", default: true - t.string "after_sign_out_path", limit: 255 - t.integer "session_expire_delay", default: 10080, null: false + t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path" + t.integer "session_expire_delay", default: 10080, null: false t.text "import_sources" t.text "help_page_text" - t.string "admin_notification_email", limit: 255 - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false + t.string "admin_notification_email" + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false t.string "recaptcha_site_key" t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 - t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false + t.integer "metrics_port", default: 8089 + t.integer "metrics_sample_interval", default: 15 + t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "akismet_enabled", default: false + t.boolean "akismet_enabled", default: false t.string "akismet_api_key" - t.boolean "email_author_in_body", default: false + t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" end create_table "audit_events", force: :cascade do |t| - t.integer "author_id", null: false - t.string "type", limit: 255, null: false - t.integer "entity_id", null: false - t.string "entity_type", limit: 255, null: false + t.integer "author_id", null: false + t.string "type", null: false + t.integer "entity_id", null: false + t.string "entity_type", null: false t.text "details" t.datetime "created_at" t.datetime "updated_at" @@ -94,13 +94,13 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "color", limit: 510 - t.string "font", limit: 510 + t.datetime "created_at" + t.datetime "updated_at" + t.string "color" + t.string "font" end create_table "ci_application_settings", force: :cascade do |t| @@ -112,7 +112,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "ci_builds", force: :cascade do |t| t.integer "project_id" - t.string "status", limit: 255 + t.string "status" t.datetime "finished_at" t.text "trace" t.datetime "created_at" @@ -123,19 +123,19 @@ ActiveRecord::Schema.define(version: 20160412175417) do t.integer "commit_id" t.text "commands" t.integer "job_id" - t.string "name", limit: 255 - t.boolean "deploy", default: false + t.string "name" + t.boolean "deploy", default: false t.text "options" - t.boolean "allow_failure", default: false, null: false - t.string "stage", limit: 255 + t.boolean "allow_failure", default: false, null: false + t.string "stage" t.integer "trigger_request_id" t.integer "stage_idx" t.boolean "tag" - t.string "ref", limit: 255 + t.string "ref" t.integer "user_id" - t.string "type", limit: 255 - t.string "target_url", limit: 255 - t.string "description", limit: 255 + t.string "type" + t.string "target_url" + t.string "description" t.text "artifacts_file" t.integer "gl_project_id" t.text "artifacts_metadata" @@ -145,7 +145,6 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree add_index "ci_builds", ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree - add_index "ci_builds", ["commit_id", "status"], name: "index_ci_builds_on_commit_id_and_status", using: :btree add_index "ci_builds", ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree add_index "ci_builds", ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree add_index "ci_builds", ["commit_id"], name: "index_ci_builds_on_commit_id", using: :btree @@ -155,37 +154,28 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_builds", ["project_id"], name: "index_ci_builds_on_project_id", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree - add_index "ci_builds", ["type", "status", "runner_id"], name: "index_ci_builds_on_test2", using: :btree add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree create_table "ci_commits", force: :cascade do |t| t.integer "project_id" - t.string "ref", limit: 255 - t.string "sha", limit: 255 - t.string "before_sha", limit: 255 + t.string "ref" + t.string "sha" + t.string "before_sha" t.text "push_data" t.datetime "created_at" t.datetime "updated_at" - t.boolean "tag", default: false + t.boolean "tag", default: false t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" - t.string "status" - t.datetime "started_at" - t.datetime "finished_at" - t.string "action" - t.integer "duration" end - add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree - add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree - add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree create_table "ci_events", force: :cascade do |t| t.integer "project_id" @@ -201,16 +191,16 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree create_table "ci_jobs", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id", null: false t.text "commands" - t.boolean "active", default: true, null: false + t.boolean "active", default: true, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "name", limit: 255 - t.boolean "build_branches", default: true, null: false - t.boolean "build_tags", default: false, null: false - t.string "job_type", limit: 255, default: "parallel" - t.string "refs", limit: 255 + t.string "name" + t.boolean "build_branches", default: true, null: false + t.boolean "build_tags", default: false, null: false + t.string "job_type", default: "parallel" + t.string "refs" t.datetime "deleted_at" end @@ -218,25 +208,25 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree create_table "ci_projects", force: :cascade do |t| - t.string "name", limit: 255 - t.integer "timeout", default: 3600, null: false + t.string "name" + t.integer "timeout", default: 3600, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "token", limit: 255 - t.string "default_ref", limit: 255 - t.string "path", limit: 255 - t.boolean "always_build", default: false, null: false + t.string "token" + t.string "default_ref" + t.string "path" + t.boolean "always_build", default: false, null: false t.integer "polling_interval" - t.boolean "public", default: false, null: false - t.string "ssh_url_to_repo", limit: 255 + t.boolean "public", default: false, null: false + t.string "ssh_url_to_repo" t.integer "gitlab_id" - t.boolean "allow_git_fetch", default: true, null: false - t.string "email_recipients", limit: 255, default: "", null: false - t.boolean "email_add_pusher", default: true, null: false - t.boolean "email_only_broken_builds", default: true, null: false - t.string "skip_refs", limit: 255 - t.string "coverage_regex", limit: 255 - t.boolean "shared_runners_enabled", default: false + t.boolean "allow_git_fetch", default: true, null: false + t.string "email_recipients", default: "", null: false + t.boolean "email_add_pusher", default: true, null: false + t.boolean "email_only_broken_builds", default: true, null: false + t.string "skip_refs" + t.string "coverage_regex" + t.boolean "shared_runners_enabled", default: false t.text "generated_yaml_config" end @@ -255,18 +245,18 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree create_table "ci_runners", force: :cascade do |t| - t.string "token", limit: 255 + t.string "token" t.datetime "created_at" t.datetime "updated_at" - t.string "description", limit: 255 + t.string "description" t.datetime "contacted_at" - t.boolean "active", default: true, null: false - t.boolean "is_shared", default: false - t.string "name", limit: 255 - t.string "version", limit: 255 - t.string "revision", limit: 255 - t.string "platform", limit: 255 - t.string "architecture", limit: 255 + t.boolean "active", default: true, null: false + t.boolean "is_shared", default: false + t.string "name" + t.string "version" + t.string "revision" + t.string "platform" + t.string "architecture" end add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} @@ -274,19 +264,19 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"} create_table "ci_services", force: :cascade do |t| - t.string "type", limit: 255 - t.string "title", limit: 255 - t.integer "project_id", null: false + t.string "type" + t.string "title" + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.boolean "active", default: false, null: false t.text "properties" end add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree create_table "ci_sessions", force: :cascade do |t| - t.string "session_id", limit: 255, null: false + t.string "session_id", null: false t.text "data" t.datetime "created_at" t.datetime "updated_at" @@ -298,9 +288,9 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "ci_taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type", limit: 255 + t.string "taggable_type" t.integer "tagger_id" - t.string "tagger_type", limit: 255 + t.string "tagger_type" t.string "context", limit: 128 t.datetime "created_at" end @@ -309,8 +299,8 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "ci_tags", force: :cascade do |t| - t.string "name", limit: 255 - t.integer "taggings_count", default: 0 + t.string "name" + t.integer "taggings_count", default: 0 end add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree @@ -324,7 +314,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do end create_table "ci_triggers", force: :cascade do |t| - t.string "token", limit: 255 + t.string "token" t.integer "project_id" t.datetime "deleted_at" t.datetime "created_at" @@ -337,19 +327,19 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "ci_variables", force: :cascade do |t| t.integer "project_id" - t.string "key", limit: 255 + t.string "key" t.text "value" t.text "encrypted_value" - t.string "encrypted_value_salt", limit: 255 - t.string "encrypted_value_iv", limit: 255 + t.string "encrypted_value_salt" + t.string "encrypted_value_iv" t.integer "gl_project_id" end add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree create_table "ci_web_hooks", force: :cascade do |t| - t.string "url", limit: 255, null: false - t.integer "project_id", null: false + t.string "url", null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -357,30 +347,30 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "deploy_keys_projects", force: :cascade do |t| t.integer "deploy_key_id", null: false t.integer "project_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree create_table "emails", force: :cascade do |t| - t.integer "user_id", null: false - t.string "email", limit: 510, null: false + t.integer "user_id", null: false + t.string "email", null: false t.datetime "created_at" t.datetime "updated_at" end - add_index "emails", ["email"], name: "emails_email_key", unique: true, using: :btree + add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "events", force: :cascade do |t| - t.string "target_type", limit: 510 + t.string "target_type" t.integer "target_id" - t.string "title", limit: 510 + t.string "title" t.text "data" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" t.integer "action" t.integer "author_id" end @@ -395,15 +385,15 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "forked_project_links", force: :cascade do |t| t.integer "forked_to_project_id", null: false t.integer "forked_from_project_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at" + t.datetime "updated_at" end - add_index "forked_project_links", ["forked_to_project_id"], name: "forked_project_links_forked_to_project_id_key", unique: true, using: :btree + add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree create_table "identities", force: :cascade do |t| - t.string "extern_uid", limit: 255 - t.string "provider", limit: 255 + t.string "extern_uid" + t.string "provider" t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" @@ -413,21 +403,21 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issues", force: :cascade do |t| - t.string "title", limit: 510 + t.string "title" t.integer "assignee_id" t.integer "author_id" t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 - t.string "branch_name", limit: 510 + t.integer "position", default: 0 + t.string "branch_name" t.text "description" t.integer "milestone_id" - t.string "state", limit: 510 + t.string "state" t.integer "iid" t.integer "updated_by_id" - t.boolean "confidential", default: false t.integer "moved_to_id" + t.boolean "confidential", default: false t.datetime "deleted_at" end @@ -450,10 +440,10 @@ ActiveRecord::Schema.define(version: 20160412175417) do t.datetime "created_at" t.datetime "updated_at" t.text "key" - t.string "title", limit: 510 - t.string "type", limit: 510 - t.string "fingerprint", limit: 510 - t.boolean "public", default: false, null: false + t.string "title" + t.string "type" + t.string "fingerprint" + t.boolean "public", default: false, null: false end add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree @@ -462,7 +452,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "label_links", force: :cascade do |t| t.integer "label_id" t.integer "target_id" - t.string "target_type", limit: 255 + t.string "target_type" t.datetime "created_at" t.datetime "updated_at" end @@ -471,23 +461,23 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree create_table "labels", force: :cascade do |t| - t.string "title", limit: 255 - t.string "color", limit: 255 + t.string "title" + t.string "color" t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false + t.boolean "template", default: false t.string "description" end add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree create_table "lfs_objects", force: :cascade do |t| - t.string "oid", limit: 255, null: false - t.integer "size", limit: 8, null: false + t.string "oid", null: false + t.integer "size", limit: 8, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "file", limit: 255 + t.string "file" end add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree @@ -502,17 +492,17 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree create_table "members", force: :cascade do |t| - t.integer "access_level", null: false - t.integer "source_id", null: false - t.string "source_type", limit: 255, null: false + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", null: false t.integer "user_id" - t.integer "notification_level", null: false - t.string "type", limit: 255 + t.integer "notification_level", null: false + t.string "type" t.datetime "created_at" t.datetime "updated_at" t.integer "created_by_id" - t.string "invite_email", limit: 255 - t.string "invite_token", limit: 255 + t.string "invite_email" + t.string "invite_token" t.datetime "invite_accepted_at" end @@ -524,10 +514,10 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree create_table "merge_request_diffs", force: :cascade do |t| - t.string "state", limit: 255 + t.string "state" t.text "st_commits" t.text "st_diffs" - t.integer "merge_request_id", null: false + t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" t.string "base_commit_sha" @@ -537,26 +527,26 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", limit: 510, null: false - t.string "source_branch", limit: 510, null: false - t.integer "source_project_id", null: false + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false t.integer "author_id" t.integer "assignee_id" - t.string "title", limit: 510 + t.string "title" t.datetime "created_at" t.datetime "updated_at" t.integer "milestone_id" - t.string "state", limit: 510 - t.string "merge_status", limit: 510 - t.integer "target_project_id", null: false + t.string "state" + t.string "merge_status" + t.integer "target_project_id", null: false t.integer "iid" t.text "description" - t.integer "position", default: 0 + t.integer "position", default: 0 t.datetime "locked_at" t.integer "updated_by_id" - t.string "merge_error", limit: 255 + t.string "merge_error" t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false + t.boolean "merge_when_build_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" t.datetime "deleted_at" @@ -577,13 +567,13 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "milestones", force: :cascade do |t| - t.string "title", limit: 510, null: false - t.integer "project_id", null: false + t.string "title", null: false + t.integer "project_id", null: false t.text "description" t.date "due_date" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "state", limit: 510 + t.datetime "created_at" + t.datetime "updated_at" + t.string "state" t.integer "iid" end @@ -596,16 +586,16 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "namespaces", force: :cascade do |t| - t.string "name", limit: 510, null: false - t.string "path", limit: 510, null: false + t.string "name", null: false + t.string "path", null: false t.integer "owner_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "type", limit: 510 - t.string "description", limit: 510, default: "", null: false - t.string "avatar", limit: 510 - t.boolean "share_with_group_lock", default: false - t.integer "visibility_level", default: 20, null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "type" + t.string "description", default: "", null: false + t.string "avatar" + t.boolean "share_with_group_lock", default: false + t.integer "visibility_level", default: 20, null: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree @@ -619,19 +609,19 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "notes", force: :cascade do |t| t.text "note" - t.string "noteable_type", limit: 510 + t.string "noteable_type" t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" t.integer "project_id" - t.string "attachment", limit: 510 - t.string "line_code", limit: 510 - t.string "commit_id", limit: 510 + t.string "attachment" + t.string "line_code" + t.string "commit_id" t.integer "noteable_id" + t.boolean "system", default: false, null: false t.text "st_diff" - t.boolean "system", null: false t.integer "updated_by_id" - t.boolean "is_award", default: false, null: false + t.boolean "is_award", default: false, null: false end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -660,14 +650,14 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", limit: 255, null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false t.datetime "revoked_at" - t.string "scopes", limit: 255 + t.string "scopes" end add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree @@ -675,12 +665,12 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "oauth_access_tokens", force: :cascade do |t| t.integer "resource_owner_id" t.integer "application_id" - t.string "token", limit: 255, null: false - t.string "refresh_token", limit: 255 + t.string "token", null: false + t.string "refresh_token" t.integer "expires_in" t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes", limit: 255 + t.datetime "created_at", null: false + t.string "scopes" end add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree @@ -688,15 +678,15 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree create_table "oauth_applications", force: :cascade do |t| - t.string "name", limit: 255, null: false - t.string "uid", limit: 255, null: false - t.string "secret", limit: 255, null: false - t.text "redirect_uri", null: false - t.string "scopes", limit: 255, default: "", null: false + t.string "name", null: false + t.string "uid", null: false + t.string "secret", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false t.datetime "created_at" t.datetime "updated_at" t.integer "owner_id" - t.string "owner_type", limit: 255 + t.string "owner_type" end add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree @@ -716,43 +706,43 @@ ActiveRecord::Schema.define(version: 20160412175417) do end create_table "projects", force: :cascade do |t| - t.string "name", limit: 510 - t.string "path", limit: 510 + t.string "name" + t.string "path" t.text "description" t.datetime "created_at" t.datetime "updated_at" t.integer "creator_id" - t.boolean "issues_enabled", null: false - t.boolean "wall_enabled", null: false - t.boolean "merge_requests_enabled", null: false - t.boolean "wiki_enabled", null: false + t.boolean "issues_enabled", default: true, null: false + t.boolean "wall_enabled", default: true, null: false + t.boolean "merge_requests_enabled", default: true, null: false + t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" - t.string "issues_tracker", limit: 510, default: "gitlab", null: false - t.string "issues_tracker_id", limit: 510 - t.boolean "snippets_enabled", null: false + t.string "issues_tracker", default: "gitlab", null: false + t.string "issues_tracker_id" + t.boolean "snippets_enabled", default: true, null: false t.datetime "last_activity_at" - t.string "import_url", limit: 510 - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", null: false - t.string "import_status", limit: 255 - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type", limit: 255 - t.string "import_source", limit: 255 - t.string "avatar", limit: 255 - t.integer "commit_count", default: 0 + t.string "import_url" + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false + t.string "avatar" + t.string "import_status" + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type" + t.string "import_source" + t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" - t.boolean "builds_enabled", default: true, null: false - t.boolean "shared_runners_enabled", default: true, null: false + t.boolean "builds_enabled", default: true, null: false + t.boolean "shared_runners_enabled", default: true, null: false t.string "runners_token" t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false t.string "main_language" - t.integer "pushes_since_gc", default: 0 + t.integer "pushes_since_gc", default: 0 end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree @@ -772,17 +762,17 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", limit: 510, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "developers_can_push", default: false, null: false + t.integer "project_id", null: false + t.string "name", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "developers_can_push", default: false, null: false end add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree create_table "releases", force: :cascade do |t| - t.string "tag", limit: 255 + t.string "tag" t.text "description" t.integer "project_id" t.datetime "created_at" @@ -795,32 +785,32 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" - t.string "noteable_type", limit: 255 + t.string "noteable_type" t.integer "recipient_id" - t.string "commit_id", limit: 255 - t.string "reply_key", limit: 255, null: false - t.string "line_code", limit: 255 + t.string "commit_id" + t.string "reply_key", null: false + t.string "line_code" end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree create_table "services", force: :cascade do |t| - t.string "type", limit: 510 - t.string "title", limit: 510 + t.string "type" + t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", null: false t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false end add_index "services", ["category"], name: "index_services_on_category", using: :btree @@ -830,15 +820,15 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "services", ["template"], name: "index_services_on_template", using: :btree create_table "snippets", force: :cascade do |t| - t.string "title", limit: 510 + t.string "title" t.text "content" - t.integer "author_id", null: false + t.integer "author_id", null: false t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "file_name", limit: 510 - t.string "type", limit: 510 - t.integer "visibility_level", default: 0, null: false + t.string "file_name" + t.string "type" + t.integer "visibility_level", default: 0, null: false end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -866,7 +856,7 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "subscriptions", force: :cascade do |t| t.integer "user_id" t.integer "subscribable_id" - t.string "subscribable_type", limit: 255 + t.string "subscribable_type" t.boolean "subscribed" t.datetime "created_at" t.datetime "updated_at" @@ -877,10 +867,10 @@ ActiveRecord::Schema.define(version: 20160412175417) do create_table "taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" - t.string "taggable_type", limit: 510 + t.string "taggable_type" t.integer "tagger_id" - t.string "tagger_type", limit: 510 - t.string "context", limit: 510 + t.string "tagger_type" + t.string "context" t.datetime "created_at" end @@ -888,8 +878,8 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name", limit: 510 - t.integer "taggings_count", default: 0 + t.string "name" + t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree @@ -917,76 +907,76 @@ ActiveRecord::Schema.define(version: 20160412175417) do add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree create_table "users", force: :cascade do |t| - t.string "email", limit: 510, default: "", null: false - t.string "encrypted_password", limit: 256, default: "", null: false - t.string "reset_password_token", limit: 510 + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip", limit: 510 - t.string "last_sign_in_ip", limit: 510 + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" t.datetime "created_at" t.datetime "updated_at" - t.string "name", limit: 510 - t.boolean "admin", null: false - t.integer "projects_limit", default: 10 - t.string "skype", limit: 510, default: "", null: false - t.string "linkedin", limit: 510, default: "", null: false - t.string "twitter", limit: 510, default: "", null: false - t.string "authentication_token", limit: 510 - t.integer "theme_id", default: 1, null: false - t.string "bio", limit: 510 - t.integer "failed_attempts", default: 0 + t.string "name" + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false + t.string "authentication_token" + t.integer "theme_id", default: 1, null: false + t.string "bio" + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "username", limit: 510 - t.boolean "can_create_group", null: false - t.boolean "can_create_team", null: false - t.string "state", limit: 510 - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.string "username" + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false + t.string "state" + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" - t.string "avatar", limit: 510 - t.string "confirmation_token", limit: 510 + t.datetime "last_credential_check_at" + t.string "avatar" + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email", limit: 510 - t.boolean "hide_no_ssh_key" - t.string "website_url", limit: 510, default: "", null: false - t.datetime "last_credential_check_at" - t.string "notification_email", limit: 255 - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location", limit: 255 - t.string "public_email", limit: 255, default: "", null: false - t.string "encrypted_otp_secret", limit: 255 - t.string "encrypted_otp_secret_iv", limit: 255 - t.string "encrypted_otp_secret_salt", limit: 255 - t.boolean "otp_required_for_login", default: false, null: false + t.string "unconfirmed_email" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false + t.string "notification_email" + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location" + t.string "encrypted_otp_secret" + t.string "encrypted_otp_secret_iv" + t.string "encrypted_otp_secret_salt" + t.boolean "otp_required_for_login", default: false, null: false t.text "otp_backup_codes" - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false t.string "unlock_token" t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false + t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree - add_index "users", ["authentication_token"], name: "users_authentication_token_key", unique: true, using: :btree - add_index "users", ["confirmation_token"], name: "users_confirmation_token_key", unique: true, using: :btree + add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree + add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree + add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} - add_index "users", ["email"], name: "users_email_key", unique: true, using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} - add_index "users", ["reset_password_token"], name: "users_reset_password_token_key", unique: true, using: :btree + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} @@ -1006,11 +996,11 @@ ActiveRecord::Schema.define(version: 20160412175417) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", limit: 510, default: "ProjectHook" + t.string "type", default: "ProjectHook" t.integer "service_id" - t.boolean "push_events", null: false - t.boolean "issues_events", null: false - t.boolean "merge_requests_events", null: false + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false t.boolean "tag_push_events", default: false t.boolean "note_events", default: false, null: false t.boolean "enable_ssl_verification", default: true diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 80e80ea665..dacaa96d76 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -162,9 +162,4 @@ describe 'Commits' do end end end - - def ci_status_path(ci_commit) - project = ci_commit.project - builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha) - end end diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb index 906db0ea82..f942695b6f 100644 --- a/spec/helpers/ci_status_helper_spec.rb +++ b/spec/helpers/ci_status_helper_spec.rb @@ -6,7 +6,7 @@ describe CiStatusHelper do let(:success_commit) { double("Ci::Commit", status: 'success') } let(:failed_commit) { double("Ci::Commit", status: 'failed') } - describe 'ci_status_icon' do + describe 'ci_icon_for_status' do it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') } it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') } end From f3a4f854494963edeeae87a5f9b2a483f0c6dbfd Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:07:29 +0200 Subject: [PATCH 013/113] Update db/schema.rb --- db/schema.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 44482de467..f53478a859 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160331223143) do +ActiveRecord::Schema.define(version: 20160412175417) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -168,14 +168,21 @@ ActiveRecord::Schema.define(version: 20160331223143) do t.text "yaml_errors" t.datetime "committed_at" t.integer "gl_project_id" + t.string "status" + t.datetime "started_at" + t.datetime "finished_at" + t.integer "duration" end + add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree + add_index "ci_commits", ["gl_project_id", "status"], name: "index_ci_commits_on_gl_project_id_and_status", using: :btree add_index "ci_commits", ["gl_project_id"], name: "index_ci_commits_on_gl_project_id", using: :btree add_index "ci_commits", ["project_id", "committed_at", "id"], name: "index_ci_commits_on_project_id_and_committed_at_and_id", using: :btree add_index "ci_commits", ["project_id", "committed_at"], name: "index_ci_commits_on_project_id_and_committed_at", using: :btree add_index "ci_commits", ["project_id", "sha"], name: "index_ci_commits_on_project_id_and_sha", using: :btree add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree + add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree create_table "ci_events", force: :cascade do |t| t.integer "project_id" From 8fb976a3ff5fa05ae35cb03de6ca34e8c2139841 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:14:27 +0200 Subject: [PATCH 014/113] Fix migrations on MySQL --- db/migrate/20160412173418_add_ci_commit_indexes.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db/migrate/20160412173418_add_ci_commit_indexes.rb b/db/migrate/20160412173418_add_ci_commit_indexes.rb index c1e238bc02..603d4a4161 100644 --- a/db/migrate/20160412173418_add_ci_commit_indexes.rb +++ b/db/migrate/20160412173418_add_ci_commit_indexes.rb @@ -10,6 +10,10 @@ class AddCiCommitIndexes < ActiveRecord::Migration private def index_options - { algorithm: :concurrently } if Gitlab::Database.postgresql? + if Gitlab::Database.postgresql? + { algorithm: :concurrently } + else + { } + end end end From 9e68109f2d454dd05cf42a03a41a2e858e1e11bc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 16:32:42 +0200 Subject: [PATCH 015/113] Optimise Merge Request builds rendering --- app/controllers/projects/merge_requests_controller.rb | 2 -- app/views/projects/commit/_ci_commit.html.haml | 6 +++--- app/views/projects/merge_requests/show/_builds.html.haml | 3 ++- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2fcef6c430..4b8fc04904 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -118,7 +118,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare @ci_commit = @merge_request.ci_commit - @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit @note_counts = Note.where(commit_id: @commits.map(&:id)). @@ -311,7 +310,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request_diff = @merge_request.merge_request_diff @ci_commit = @merge_request.ci_commit - @ci_commits = [@ci_commit].compact @statuses = @ci_commit.statuses if @ci_commit if @merge_request.locked_long_ago? diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 06520e40bd..25714e6cb4 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -2,10 +2,10 @@ .pull-right - if can?(current_user, :update_build, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: 'btn btn-grouped btn-primary', method: :post + = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post - if ci_commit.builds.running_or_pending.any? - = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, @commit.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + = link_to "Cancel running", cancel_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post .oneline = pluralize ci_commit.statuses.count(:id), "build" @@ -15,7 +15,7 @@ = ci_commit.ref - if defined?(link_to_commit) && link_to_commit for commit - = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" + = link_to ci_commit.short_sha, namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: "monospace" - if ci_commit.duration > 0 in = time_interval_in_words ci_commit.duration diff --git a/app/views/projects/merge_requests/show/_builds.html.haml b/app/views/projects/merge_requests/show/_builds.html.haml index 307a75d02c..a116ffe2e1 100644 --- a/app/views/projects/merge_requests/show/_builds.html.haml +++ b/app/views/projects/merge_requests/show/_builds.html.haml @@ -1 +1,2 @@ -= render "projects/commit/builds", link_to_commit: true += render "projects/commit/ci_commit", ci_commit: @ci_commit, link_to_commit: true + From 3d38e461918e75e719938801ae2d63aae680a85c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 17:26:22 +0200 Subject: [PATCH 016/113] Support skipped status --- app/models/concerns/ci_status.rb | 3 +++ features/steps/dashboard/dashboard.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index 67e15b2d55..fd86d2f755 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -12,12 +12,14 @@ module CiStatus pending = all.pending.select('count(*)').to_sql running = all.running.select('count(*)').to_sql canceled = all.canceled.select('count(*)').to_sql + skipped = all.skipped.select('count(*)').to_sql deduce_status = "(CASE WHEN (#{builds})=0 THEN 'skipped' WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' WHEN (#{builds})=(#{pending}) THEN 'pending' WHEN (#{builds})=(#{canceled}) THEN 'canceled' + WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{running})+(#{pending})>0 THEN 'running' ELSE 'failed' END)" @@ -52,6 +54,7 @@ module CiStatus scope :success, -> { where(status: 'success') } scope :failed, -> { where(status: 'failed') } scope :canceled, -> { where(status: 'canceled') } + scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } end diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index b5980b3510..ba0e829dc0 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -13,7 +13,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps end step 'I should see "Shop" project CI status' do - expect(page).to have_link "Build skipped" + expect(page).to have_link "Build: skipped" end step 'I should see last push widget' do From ae24b257189be758da6b5189f3a836654fe72f7e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 17:30:31 +0200 Subject: [PATCH 017/113] Simplify state update of Ci::Commit object --- app/models/ci/commit.rb | 72 +++++++---------------------------------- 1 file changed, 11 insertions(+), 61 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 8865bd76bd..07cd489223 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -29,16 +29,8 @@ module Ci validates_presence_of :sha validate :valid_commit_sha - # Make sure that status is saved - before_save :status - before_save :started_at - before_save :finished_at - before_save :duration - # Invalidate object and save if when touched - after_touch :reload - after_touch :invalidate - after_touch :save + after_touch :update_state def self.truncate_sha(sha) sha[0...8] @@ -94,13 +86,6 @@ module Ci end end - def invalidate - write_attribute(:status, nil) - write_attribute(:started_at, nil) - write_attribute(:finished_at, nil) - write_attribute(:duration, nil) - end - def create_builds(user, trigger_request = nil) return unless config_processor config_processor.stages.any? do |stage| @@ -137,22 +122,6 @@ module Ci @retried ||= (statuses.order(id: :desc) - statuses.latest) end - def status - read_attribute(:status) || update_status - end - - def duration - read_attribute(:duration) || update_duration - end - - def started_at - read_attribute(:started_at) || update_started_at - end - - def finished_at - read_attribute(:finished_at) || update_finished_at - end - def coverage coverage_array = latest.map(&:coverage).compact if coverage_array.size >= 1 @@ -188,45 +157,26 @@ module Ci private - def update_status - self.status = - if yaml_errors.present? - 'failed' - else - latest.status || 'skipped' - end - end - - def update_started_at - self.started_at = - statuses.minimum(:started_at) - end - - def update_finished_at - self.finished_at = - statuses.maximum(:finished_at) - end - - def update_duration + def update_state + reload + self.status = if yaml_errors.present? + 'failed' + else + latest.status + end + self.started_at = statuses.minimum(:started_at) + self.finished_at = statuses.maximum(:finished_at) self.duration = begin duration_array = latest.map(&:duration).compact duration_array.reduce(:+).to_i end - end - - def update_statuses - update_status - update_started_at - update_finished_at - update_duration save end def save_yaml_error(error) return if self.yaml_errors? self.yaml_errors = error - update_status - save + update_state end end end From 2a7f7f75285e3818ac9c90b3e6c7893f4999e33e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 20:51:03 +0200 Subject: [PATCH 018/113] Update handling of skipped status --- app/models/ci/build.rb | 2 +- app/models/ci/commit.rb | 23 ++++++++--------------- app/models/concerns/ci_status.rb | 10 +++++++++- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 085ecc6951..c0b334d360 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -116,7 +116,7 @@ module Ci end def retried? - !self.commit.latest.include?(self) + !self.commit.statuses.latest.include?(self) end def retry diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 07cd489223..334d3c7bc4 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -114,16 +114,12 @@ module Ci end end - def latest - statuses.latest - end - def retried @retried ||= (statuses.order(id: :desc) - statuses.latest) end def coverage - coverage_array = latest.map(&:coverage).compact + coverage_array = statuses.latest.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end @@ -158,18 +154,15 @@ module Ci private def update_state - reload - self.status = if yaml_errors.present? - 'failed' + statuses.reload + self.status = if yaml_errors.blank? + statuses.latest.status || 'skipped' else - latest.status + 'failed' end - self.started_at = statuses.minimum(:started_at) - self.finished_at = statuses.maximum(:finished_at) - self.duration = begin - duration_array = latest.map(&:duration).compact - duration_array.reduce(:+).to_i - end + self.started_at = statuses.started_at + self.finished_at = statuses.finished_at + self.duration = statuses.latest.duration save end diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/ci_status.rb index fd86d2f755..8190b2a20c 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/ci_status.rb @@ -15,7 +15,7 @@ module CiStatus skipped = all.skipped.select('count(*)').to_sql deduce_status = "(CASE - WHEN (#{builds})=0 THEN 'skipped' + WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success' WHEN (#{builds})=(#{pending}) THEN 'pending' WHEN (#{builds})=(#{canceled}) THEN 'canceled' @@ -35,6 +35,14 @@ module CiStatus duration_array = all.map(&:duration).compact duration_array.reduce(:+).to_i end + + def started_at + all.minimum(:started_at) + end + + def finished_at + all.minimum(:finished_at) + end end included do From 5117412e33821f8eaf50befd2e00e431bfc74738 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 13 Apr 2016 20:54:21 +0200 Subject: [PATCH 019/113] Fix implementation of config_processor and ci_yaml_file --- app/models/ci/commit.rb | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 334d3c7bc4..ae30407bca 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -127,24 +127,29 @@ module Ci def config_processor return nil unless ci_yaml_file - @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) - rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e - save_yaml_error(e.message) - nil - rescue - save_yaml_error("Undefined error") - nil + return @config_processor if defined?(@config_processor) + + @config_processor ||= begin + Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) + rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e + save_yaml_error(e.message) + nil + rescue + save_yaml_error("Undefined error") + nil + end end def ci_yaml_file - return nil if defined?(@ci_yaml_file) + return @ci_yaml_file if defined?(@ci_yaml_file) + @ci_yaml_file ||= begin blob = project.repository.blob_at(sha, '.gitlab-ci.yml') blob.load_all_data!(project.repository) blob.data + rescue + nil end - rescue - nil end def skip_ci? From 90a67a76d5b852c62b59dd52b9dafd58722f2237 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 14 Apr 2016 17:32:46 -0400 Subject: [PATCH 020/113] Always read diff_view setting from the cookie Prior, when the user had their view set to "parallel" and then visited a merge request's changes tab _without_ passing the `view` parameter via query string, the view would be parallel but the `Notes` class was always instantiated with the default value from `diff_view` ("inline"), resulting in broken markup when the form to add a line note was dynamically inserted. The cookie is set whenever the view is changed, so this value should always be up-to-date. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/14557 --- app/helpers/diff_helper.rb | 8 +++++++- .../projects/merge_requests/_show.html.haml | 2 +- spec/helpers/diff_helper_spec.rb | 20 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 6a3ec83b8c..97466d532f 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -9,7 +9,13 @@ module DiffHelper end def diff_view - params[:view] == 'parallel' ? 'parallel' : 'inline' + diff_views = %w(inline parallel) + + if diff_views.include?(cookies[:diff_view]) + cookies[:diff_view] + else + diff_views.first + end end def diff_hard_limit_enabled? diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 1dd8f721f7..1733622e9b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -4,7 +4,7 @@ = render "header_title" -- if params[:view] == 'parallel' +- if diff_view == 'parallel' - fluid_layout true .merge-request{'data-url' => merge_request_path(@merge_request)} diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 982c113e84..b7810185d1 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -11,6 +11,26 @@ describe DiffHelper do let(:diff_refs) { [commit.parent, commit] } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) } + describe 'diff_view' do + it 'returns a valid value when cookie is set' do + helper.request.cookies[:diff_view] = 'parallel' + + expect(helper.diff_view).to eq 'parallel' + end + + it 'returns a default value when cookie is invalid' do + helper.request.cookies[:diff_view] = 'invalid' + + expect(helper.diff_view).to eq 'inline' + end + + it 'returns a default value when cookie is nil' do + expect(helper.request.cookies).to be_empty + + expect(helper.diff_view).to eq 'inline' + end + end + describe 'diff_hard_limit_enabled?' do it 'should return true if param is provided' do allow(controller).to receive(:params) { { force_show_diff: true } } From 50998e69bf743bda5d053262237145bd240bca8b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 23:05:49 -0500 Subject: [PATCH 021/113] Restore broken code --- app/assets/javascripts/labels_select.js.coffee | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index bc80980acb..c1fcd764a6 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -20,15 +20,6 @@ class @LabelsSelect $value = $block.find('.value') $loading = $block.find('.block-loading').fadeOut() - if newLabelField.length - $newLabelCreateButton = $('.js-new-label-btn') - $colorPreview = $('.js-dropdown-label-color-preview') - $newLabelError = $dropdown.parent().find('.js-label-error') - $newLabelError.hide() - - # Suggested colors in the dropdown to chose from pre-chosen colors - $('.suggest-colors-dropdown a').on 'click', (e) -> - issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL? if issueUpdateURL labelHTMLTemplate = _.template( @@ -43,6 +34,12 @@ class @LabelsSelect labelNoneHTMLTemplate = _.template('
None
') if newLabelField.length and $dropdown.hasClass 'js-extra-options' + $newLabelCreateButton = $('.js-new-label-btn') + $colorPreview = $('.js-dropdown-label-color-preview') + $newLabelError = $dropdown.parent().find('.js-label-error') + $newLabelError.hide() + + # Suggested colors in the dropdown to chose from pre-chosen colors $('.suggest-colors-dropdown a').on "click", (e) -> e.preventDefault() e.stopPropagation() From f396b2e6d09fbfe47201ceb0f897c508dabb1b1a Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 11 Apr 2016 23:19:47 -0500 Subject: [PATCH 022/113] Reuse partial for Create Label page --- .../shared/issuable/_label_dropdown.html.haml | 18 +----------------- .../issuable/_label_page_create.html.haml | 17 +++++++++++++++++ app/views/shared/issuable/_sidebar.html.haml | 4 +++- 3 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 app/views/shared/issuable/_label_page_create.html.haml diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index f722e61eea..371acc46c5 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -24,21 +24,5 @@ - else View labels - if can? current_user, :admin_label, @project and @project - .dropdown-page-two.dropdown-new-label - = dropdown_title("Create new label", back: true) - = dropdown_content do - .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} - .suggest-colors.suggest-colors-dropdown - - suggested_colors.each do |color| - = link_to '#', style: "background-color: #{color}", data: { color: color } do -   - .dropdown-label-color-input - .dropdown-label-color-preview.js-dropdown-label-color-preview - %input#new_label_color.dropdown-input-field{ type: "text" } - .clearfix - %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"} - Create - %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"} - Cancel + = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml new file mode 100644 index 0000000000..89bc836826 --- /dev/null +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -0,0 +1,17 @@ +.dropdown-page-two.dropdown-new-label + = dropdown_title("Create new label", back: true) + = dropdown_content do + .dropdown-labels-error.js-label-error + %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} + .suggest-colors.suggest-colors-dropdown + - suggested_colors.each do |color| + = link_to '#', style: "background-color: #{color}", data: { color: color } do +   + .dropdown-label-color-input + .dropdown-label-color-preview.js-dropdown-label-color-preview + %input#new_label_color.dropdown-input-field{ type: "text" } + .clearfix + %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"} + Create + %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"} + Cancel diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 08bfd93f4e..2dc5c0c19e 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -96,7 +96,7 @@ - issuable.labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text Label = icon('chevron-down') @@ -118,6 +118,8 @@ Manage labels - else View labels + - if can? current_user, :admin_label, @project and @project + = render "shared/issuable/milestone_dropdown" = render "shared/issuable/participants", participants: issuable.participants(current_user) - if current_user From a2550392100422a00db8f9baabfded7e2e71d046 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 13 Apr 2016 14:12:40 -0500 Subject: [PATCH 023/113] Add spaces --- app/views/shared/issuable/_label_page_create.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 89bc836826..9179069f78 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -2,7 +2,7 @@ = dropdown_title("Create new label", back: true) = dropdown_content do .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{type: "text", placeholder: "Name new label"} + %input#new_label_name.dropdown-input-field{ type: "text", placeholder: "Name new label"} .suggest-colors.suggest-colors-dropdown - suggested_colors.each do |color| = link_to '#', style: "background-color: #{color}", data: { color: color } do @@ -11,7 +11,7 @@ .dropdown-label-color-preview.js-dropdown-label-color-preview %input#new_label_color.dropdown-input-field{ type: "text" } .clearfix - %button.btn.btn-primary.pull-left.js-new-label-btn{type: "button"} + %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" } Create - %button.btn.btn-default.pull-right.js-cancel-label-btn{type: "button"} + %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" } Cancel From 88ddce257d90114c3981c5c07d621331a683bd05 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 14 Apr 2016 12:57:54 -0500 Subject: [PATCH 024/113] Render proper partial --- app/views/shared/issuable/_label_page_create.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 9179069f78..26ccade425 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -2,7 +2,7 @@ = dropdown_title("Create new label", back: true) = dropdown_content do .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{ type: "text", placeholder: "Name new label"} + %input#new_label_name.dropdown-input-field{ type: "text", placeholder: "Name new label" } .suggest-colors.suggest-colors-dropdown - suggested_colors.each do |color| = link_to '#', style: "background-color: #{color}", data: { color: color } do diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 2dc5c0c19e..6108239aed 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -119,7 +119,7 @@ - else View labels - if can? current_user, :admin_label, @project and @project - = render "shared/issuable/milestone_dropdown" + = render partial: "shared/issuable/label_page_create" = render "shared/issuable/participants", participants: issuable.participants(current_user) - if current_user From 0556d661e0027068aa7bee45a5c61017344505f3 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 14 Apr 2016 19:47:53 -0500 Subject: [PATCH 025/113] Fix Label dropdown and organize code # Conflicts: # app/assets/javascripts/labels_select.js.coffee --- app/assets/javascripts/gl_dropdown.js.coffee | 7 +++++-- app/assets/stylesheets/framework/dropdowns.scss | 2 +- app/assets/stylesheets/pages/issuable.scss | 4 ++++ app/views/shared/issuable/_label_page_create.html.haml | 4 ++-- app/views/shared/issuable/_sidebar.html.haml | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 2dc37257e2..fa1f765095 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -224,6 +224,9 @@ class GitLabDropdown menu.toggleClass PAGE_TWO_CLASS + # Focus first visible input on active page + @dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus() + parseData: (data) -> @renderedData = data @@ -243,7 +246,8 @@ class GitLabDropdown shouldPropagate: (e) => if @options.multiSelect $target = $(e.target) - if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') + + if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and ($target.attr('href') is '#') e.stopPropagation() return false else @@ -378,7 +382,6 @@ class GitLabDropdown selectedObject = @renderedData[selectedIndex] value = if @options.id then @options.id(selectedObject, el) else selectedObject.id field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']") - if el.hasClass(ACTIVE_CLASS) el.removeClass(ACTIVE_CLASS) field.remove() diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index ba6c7930cd..719703b1af 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -320,7 +320,7 @@ } } -.dropdown-input-field { +.dropdown-input-field, .default-dropdown-input { width: 100%; padding: 0 7px; color: $dropdown-input-color; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 6bd90a2362..152b60d1e8 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -248,6 +248,10 @@ background: $gray-dark; border: 1px solid $border-gray-dark; } + + &.btn-primary { + @extend .btn-primary + } } a:not(.btn) { diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 26ccade425..3bc57d3d2a 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -2,14 +2,14 @@ = dropdown_title("Create new label", back: true) = dropdown_content do .dropdown-labels-error.js-label-error - %input#new_label_name.dropdown-input-field{ type: "text", placeholder: "Name new label" } + %input#new_label_name.default-dropdown-input{ type: "text", placeholder: "Name new label" } .suggest-colors.suggest-colors-dropdown - suggested_colors.each do |color| = link_to '#', style: "background-color: #{color}", data: { color: color } do   .dropdown-label-color-input .dropdown-label-color-preview.js-dropdown-label-color-preview - %input#new_label_color.dropdown-input-field{ type: "text" } + %input#new_label_color.default-dropdown-input{ type: "text" } .clearfix %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" } Create diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 6108239aed..ef670bc9ba 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -96,7 +96,7 @@ - issuable.labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-extra-options{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} + %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} %span.dropdown-toggle-text Label = icon('chevron-down') From 9066ac0da51bd2b74249185c79ca278942a40241 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 15 Apr 2016 13:01:46 -0500 Subject: [PATCH 026/113] Fix create label functionality --- .../javascripts/labels_select.js.coffee | 66 +++++++------------ 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index c1fcd764a6..ddf7d84a38 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -2,14 +2,15 @@ class @LabelsSelect constructor: -> $('.js-label-select').each (i, dropdown) -> $dropdown = $(dropdown) + $parent = $dropdown.parent() projectId = $dropdown.data('project-id') labelUrl = $dropdown.data('labels') issueUpdateURL = $dropdown.data('issueUpdate') selectedLabel = $dropdown.data('selected') if selectedLabel? selectedLabel = selectedLabel.split(',') - newLabelField = $('#new_label_name') - newColorField = $('#new_label_color') + newLabelField = $('#new_label_name', $parent) + newColorField = $('#new_label_color', $parent) showNo = $dropdown.data('show-no') showAny = $dropdown.data('show-any') defaultLabel = $dropdown.data('default-label') @@ -18,6 +19,11 @@ class @LabelsSelect $block = $selectbox.closest('.block') $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span') $value = $block.find('.value') + $newLabelError = $('.js-label-error', $parent) + $colorPreview = $('.js-dropdown-label-color-preview', $parent) + $newLabelCreateButton = $('.js-new-label-btn', $parent) + + $newLabelError.hide() $loading = $block.find('.block-loading').fadeOut() issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL? @@ -33,11 +39,7 @@ class @LabelsSelect ); labelNoneHTMLTemplate = _.template('
None
') - if newLabelField.length and $dropdown.hasClass 'js-extra-options' - $newLabelCreateButton = $('.js-new-label-btn') - $colorPreview = $('.js-dropdown-label-color-preview') - $newLabelError = $dropdown.parent().find('.js-label-error') - $newLabelError.hide() + if newLabelField.length # Suggested colors in the dropdown to chose from pre-chosen colors $('.suggest-colors-dropdown a').on "click", (e) -> @@ -78,26 +80,25 @@ class @LabelsSelect enableLabelCreateButton = -> if newLabelField.val() isnt '' and newColorField.val() isnt '' $newLabelError.hide() - $('.js-new-label-btn').disable() - - # Create new label with API - Api.newLabel projectId, { - name: newLabelField.val() - color: newColorField.val() - }, (label) -> - $('.js-new-label-btn').enable() - - if label.message? - $newLabelError - .text label.message - .show() - else - $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' - $newLabelCreateButton.enable() else $newLabelCreateButton.disable() + saveLabel = -> + # Create new label with API + Api.newLabel projectId, { + name: newLabelField.val() + color: newColorField.val() + }, (label) -> + $newLabelCreateButton.enable() + + if label.message? + $newLabelError + .text label.message + .show() + else + $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' + newLabelField.on 'keyup change', enableLabelCreateButton newColorField.on 'keyup change', enableLabelCreateButton @@ -108,24 +109,7 @@ class @LabelsSelect .on 'click', (e) -> e.preventDefault() e.stopPropagation() - - if newLabelField.val() isnt '' and newColorField.val() isnt '' - $newLabelError.hide() - $('.js-new-label-btn').disable() - - # Create new label with API - Api.newLabel projectId, { - name: newLabelField.val() - color: newColorField.val() - }, (label) -> - $('.js-new-label-btn').enable() - - if label.message? - $newLabelError - .text label.message - .show() - else - $('.dropdown-menu-back', $dropdown.parent()).trigger 'click' + saveLabel() saveLabelData = -> selected = $dropdown From 6c29e1ef69d4ee7e656d0b3b203cf43ff68c637b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 15 Apr 2016 14:25:36 -0500 Subject: [PATCH 027/113] Add tests to check if a new label is added --- spec/features/issues/issue_sidebar_spec.rb | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 spec/features/issues/issue_sidebar_spec.rb diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb new file mode 100644 index 0000000000..5739bc64df --- /dev/null +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -0,0 +1,79 @@ +require 'rails_helper' + +feature 'Issue Sidebar', feature: true do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + let!(:user) { create(:user)} + + before do + create(:label, project: project, title: 'bug') + login_as(user) + end + + context 'as a allowed user' do + before do + project.team << [user, :developer] + visit_issue(project, issue) + end + + describe 'when clicking on edit labels', js: true do + it 'dropdown has an option to create a new label' do + find('.block.labels .edit-link').click + + page.within('.block.labels') do + expect(page).to have_content 'Create new' + end + end + end + + context 'creating a new label', js: true do + it 'option to crate a new label is present' do + page.within('.block.labels') do + find('.edit-link').click + + expect(page).to have_content 'Create new' + end + end + + it 'dropdown switches to "create label" section' do + page.within('.block.labels') do + find('.edit-link').click + click_link 'Create new' + + expect(page).to have_content 'Create new label' + end + end + + it 'new label is added' do + page.within('.block.labels') do + find('.edit-link').click + sleep 1 + click_link 'Create new' + + fill_in 'new_label_name', with: 'wontfix' + page.find(".suggest-colors a", match: :first).click + click_button 'Create' + + page.within('.dropdown-page-one') do + expect(page).to have_content 'wontfix' + end + end + end + end + end + + context 'as a guest' do + before do + project.team << [user, :guest] + visit_issue(project, issue) + end + + it 'does not have a option to edit labels' do + expect(page).not_to have_selector('.block.labels .edit-link') + end + end + + def visit_issue(project, issue) + visit namespace_project_issue_path(project.namespace, project, issue) + end +end From 0b1655e7b2e2aa57cb7ea8401743d709bf246074 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 21:46:26 +0200 Subject: [PATCH 028/113] Rename CiStatus to Statusable --- app/models/ci/commit.rb | 2 +- app/models/commit_status.rb | 8 ++++++-- app/models/concerns/{ci_status.rb => statuseable.rb} | 4 ++-- app/models/project.rb | 2 +- app/views/projects/commit/_ci_commit.html.haml | 2 +- spec/models/commit_spec.rb | 8 ++++++++ .../concerns/statuseable_spec.rb} | 4 ++-- 7 files changed, 21 insertions(+), 9 deletions(-) rename app/models/concerns/{ci_status.rb => statuseable.rb} (97%) rename spec/{lib/ci/status_spec.rb => models/concerns/statuseable_spec.rb} (97%) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index ae30407bca..412ab44aaf 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -19,7 +19,7 @@ module Ci class Commit < ActiveRecord::Base extend Ci::Model - include CiStatus + include Statuseable belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id has_many :statuses, class_name: 'CommitStatus' diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 66eb5dcecf..06d296eef0 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -33,7 +33,7 @@ # class CommitStatus < ActiveRecord::Base - include CiStatus + include Statuseable self.table_name = 'ci_builds' @@ -81,7 +81,11 @@ class CommitStatus < ActiveRecord::Base end end - delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false + delegate :sha, :short_sha, to: :commit + + def before_sha + commit.before_sha || Gitlab::Git::BLANK_SHA + end def self.stages order_by = 'max(stage_idx)' diff --git a/app/models/concerns/ci_status.rb b/app/models/concerns/statuseable.rb similarity index 97% rename from app/models/concerns/ci_status.rb rename to app/models/concerns/statuseable.rb index 8190b2a20c..f34dca2912 100644 --- a/app/models/concerns/ci_status.rb +++ b/app/models/concerns/statuseable.rb @@ -1,4 +1,4 @@ -module CiStatus +module Statuseable extend ActiveSupport::Concern AVAILABLE_STATUSES = %w(pending running success failed canceled skipped) @@ -41,7 +41,7 @@ module CiStatus end def finished_at - all.minimum(:finished_at) + all.maximum(:finished_at) end end diff --git a/app/models/project.rb b/app/models/project.rb index 95eb7c51b8..3a55e6c5dd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -958,7 +958,7 @@ class Project < ActiveRecord::Base end def ci_commit(sha, ref) - ci_commits.find_by(sha: sha, ref: ref) + ci_commits.order(id: :desc).find_by(sha: sha, ref: ref) end def ensure_ci_commit(sha, ref) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 25714e6cb4..0c01f2ed95 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,6 +1,6 @@ .gray-content-block.middle-block .pull-right - - if can?(current_user, :update_build, @project) + - if can?(current_user, :update_build, @ci_commit.project) - if ci_commit.builds.latest.failed.any?(&:retryable?) = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 0e9111c802..ad47e338a3 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -163,4 +163,12 @@ eos it { expect(commit.reverts_commit?(another_commit)).to be_truthy } end end + + describe '#ci_commits' do + # TODO: kamil + end + + describe '#status' do + # TODO: kamil + end end diff --git a/spec/lib/ci/status_spec.rb b/spec/models/concerns/statuseable_spec.rb similarity index 97% rename from spec/lib/ci/status_spec.rb rename to spec/models/concerns/statuseable_spec.rb index 886b82a7af..dacbd3034c 100644 --- a/spec/lib/ci/status_spec.rb +++ b/spec/models/concerns/statuseable_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' -describe CiStatus do +describe Statuseable do before do @object = Object.new - @object.extend(CiStatus::ClassMethods) + @object.extend(Statuseable::ClassMethods) end describe '.status' do From dc0d7f1a9b4018541596680c643cc5489fd8e625 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 21:47:10 +0200 Subject: [PATCH 029/113] Revert unneeded changes --- app/views/projects/commit/_ci_commit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/commit/_ci_commit.html.haml b/app/views/projects/commit/_ci_commit.html.haml index 0c01f2ed95..25714e6cb4 100644 --- a/app/views/projects/commit/_ci_commit.html.haml +++ b/app/views/projects/commit/_ci_commit.html.haml @@ -1,6 +1,6 @@ .gray-content-block.middle-block .pull-right - - if can?(current_user, :update_build, @ci_commit.project) + - if can?(current_user, :update_build, @project) - if ci_commit.builds.latest.failed.any?(&:retryable?) = link_to "Retry failed", retry_builds_namespace_project_commit_path(@project.namespace, @project, ci_commit.sha), class: 'btn btn-grouped btn-primary', method: :post From 1c5b172abb1279a25731d35ee913daa91738606d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 16 Apr 2016 22:43:40 +0200 Subject: [PATCH 030/113] Write specs for this feature --- app/models/ci/commit.rb | 9 ++-- app/models/commit_status.rb | 17 ++++--- app/models/concerns/statuseable.rb | 2 +- lib/api/commit_statuses.rb | 19 +++++--- spec/models/ci/commit_spec.rb | 71 ++++++++++++++++++++++++++++ spec/models/commit_status_spec.rb | 74 ++++++++++++++++++++++++++++++ spec/models/project_spec.rb | 15 +++++- 7 files changed, 188 insertions(+), 19 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 412ab44aaf..f2667e5476 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -26,7 +26,10 @@ module Ci has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' + delegate :stages, to: :statuses + validates_presence_of :sha + validates_presence_of :status validate :valid_commit_sha # Invalidate object and save if when touched @@ -40,10 +43,6 @@ module Ci CommitStatus.where(commit: all).stages end - def stages - statuses.stages - end - def project_id project.id end @@ -82,7 +81,7 @@ module Ci def retryable? builds.latest.any? do |build| - build.failed? || build.retryable? + build.failed? && build.retryable? end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 06d296eef0..24a26b4be8 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -93,7 +93,10 @@ class CommitStatus < ActiveRecord::Base end def self.stages_status - Hash[group(:stage).pluck(:stage, self.status_sql)] + all.stages.inject({}) do |h, stage| + h[stage] = all.where(stage: stage).status + h + end end def ignored? @@ -101,11 +104,13 @@ class CommitStatus < ActiveRecord::Base end def duration - if started_at && finished_at - finished_at - started_at - elsif started_at - Time.now - started_at - end + duration = + if started_at && finished_at + finished_at - started_at + elsif started_at + Time.now - started_at + end + duration.to_i end def stuck? diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb index f34dca2912..7b0c9789fb 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/statuseable.rb @@ -33,7 +33,7 @@ module Statuseable def duration duration_array = all.map(&:duration).compact - duration_array.reduce(:+).to_i + duration_array.reduce(:+) end def started_at diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 0b52dd8a74..7388ed2f4e 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -50,12 +50,19 @@ module API commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit - ref = params[:ref] || - begin - branches = @project.repository.branch_names_contains(commit.sha) - not_found! 'Reference for commit' if branches.none? - branches.first - end + # Since the CommitStatus is attached to Ci::Commit (in the future Pipeline) + # We need to always have the pipeline object + # To have a valid pipeline object that can be attached to specific MR + # Other CI service needs to send `ref` + # If we don't receive it, we will attach the CommitStatus to + # the first found branch on that commit + + ref = params[:ref] + unless ref + branches = @project.repository.branch_names_contains(commit.sha) + not_found! 'References for commit' if branches.none? + ref = branches.first + end ci_commit = @project.ensure_ci_commit(commit.sha, ref) diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index fb3b61ad7c..aef4f00720 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -27,6 +27,8 @@ describe Ci::Commit, models: true do it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:builds) } it { is_expected.to validate_presence_of :sha } + it { is_expected.to validate_presence_of :status } + it { is_expected.to delegate_method(:stages).to(:statuses) } it { is_expected.to respond_to :git_author_name } it { is_expected.to respond_to :git_author_email } @@ -297,4 +299,73 @@ describe Ci::Commit, models: true do expect(commit.coverage).to be_nil end end + + describe '#retryable?' do + subject { commit.retryable? } + + context 'no failed builds' do + before do + FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'success' + end + + it 'be not retryable' do + is_expected.to be_falsey + end + end + + context 'with failed builds' do + before do + FactoryGirl.create :ci_build, name: "rspec", commit: commit, status: 'running' + FactoryGirl.create :ci_build, name: "rubocop", commit: commit, status: 'failed' + end + + it 'be retryable' do + is_expected.to be_truthy + end + end + end + + describe '#stages' do + let(:commit2) { FactoryGirl.create :ci_commit, project: project } + subject { CommitStatus.where(commit: [commit, commit2]).stages } + + before do + FactoryGirl.create :ci_build, commit: commit2, stage: 'test', stage_idx: 1 + FactoryGirl.create :ci_build, commit: commit, stage: 'build', stage_idx: 0 + end + + it 'return all stages' do + is_expected.to eq(%w(build test)) + end + end + + describe '#update_state' do + it 'execute update_state after touching object' do + expect(commit).to receive(:update_state).and_return(true) + commit.touch + end + + context 'dependent objects' do + let(:commit_status) { build :commit_status, commit: commit } + + it 'execute update_state after saving dependent object' do + expect(commit).to receive(:update_state).and_return(true) + commit_status.save + end + end + + context 'update state' do + let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: Time.now - 120, finished_at: Time.now - 60 } + + before do + build + end + + [:status, :started_at, :finished_at, :duration].each do |param| + it "update #{param}" do + expect(commit.send(param)).to eq(build.send(param)) + end + end + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 1c64947f1f..31d546820c 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -182,4 +182,78 @@ describe CommitStatus, models: true do is_expected.to eq([@commit1, @commit2]) end end + + describe '#before_sha' do + subject { commit_status.before_sha } + + context 'when no before_sha is set for ci::commit' do + before { commit.before_sha = nil } + + it 'return blank sha' do + is_expected.to eq(Gitlab::Git::BLANK_SHA) + end + end + + context 'for before_sha set for ci::commit' do + let(:value) { '1234' } + before { commit.before_sha = value } + + it 'return the set value' do + is_expected.to eq(value) + end + end + end + + describe '#stages' do + before do + FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'success' + FactoryGirl.create :commit_status, commit: commit, stage: 'build', stage_idx: 0, status: 'failed' + FactoryGirl.create :commit_status, commit: commit, stage: 'deploy', stage_idx: 2, status: 'running' + FactoryGirl.create :commit_status, commit: commit, stage: 'test', stage_idx: 1, status: 'success' + end + + context 'stages list' do + subject { CommitStatus.where(commit: commit).stages } + + it 'return ordered list of stages' do + is_expected.to eq(%w(build test deploy)) + end + end + + context 'stages with statuses' do + subject { CommitStatus.where(commit: commit).stages_status } + + it 'return list of stages with statuses' do + is_expected.to eq({ + 'build' => 'failed', + 'test' => 'success', + 'deploy' => 'running' + }) + end + end + end + + describe '#branch?' do + subject { commit_status.branch? } + + context 'is not a tag' do + before do + commit_status.tag = false + end + + it 'return true when tag is set to false' do + is_expected.to be_truthy + end + end + + context 'is not a tag' do + before do + commit_status.tag = true + end + + it 'return false when tag is set to true' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1688e91ca6..becc743de3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -443,7 +443,20 @@ describe Project, models: true do let(:project) { create :project } let(:commit) { create :ci_commit, project: project, ref: 'master' } - it { expect(project.ci_commit(commit.sha, 'master')).to eq(commit) } + subject { project.ci_commit(commit.sha, 'master') } + + it { is_expected.to eq(commit) } + + context 'return latest' do + let(:commit2) { create :ci_commit, project: project, ref: 'master' } + + before do + commit + commit2 + end + + it { is_expected.to eq(commit2) } + end end describe :builds_enabled do From 433ca7390d800a2fda3e3c5eb29272d0ba2d9df6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 17 Apr 2016 09:32:49 -0400 Subject: [PATCH 031/113] Make some logic less twistable --- CHANGELOG | 1 + app/models/concerns/statuseable.rb | 2 +- app/views/projects/builds/index.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 2 +- .../_generic_commit_status.html.haml | 13 +++++++------ spec/requests/api/commits_spec.rb | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5c375fcdb3..4b5564223f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,6 +52,7 @@ v 8.7.0 (unreleased) - Hide `Create a group` help block when creating a new project in a group - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Allow issues and merge requests to be assigned to the author !2765 + - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) (Kamil Trzciński) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Decouple membership and notifications - Fix creation of merge requests for orphaned branches (Stan Hu) diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb index 7b0c9789fb..8a293b7b76 100644 --- a/app/models/concerns/statuseable.rb +++ b/app/models/concerns/statuseable.rb @@ -7,7 +7,7 @@ module Statuseable def status_sql builds = all.select('count(*)').to_sql success = all.success.select('count(*)').to_sql - ignored = all.ignored.select('count(*)').to_sql if all.try(:ignored) + ignored = all.ignored.select('count(*)').to_sql if all.respond_to?(:ignored) ignored ||= '0' pending = all.pending.select('count(*)').to_sql running = all.running.select('count(*)').to_sql diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index aa85f495e3..0406fc21d7 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -58,6 +58,6 @@ %th Coverage %th - = render @builds, commit_sha: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? + = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? = paginate @builds, theme: 'gitlab' diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 218d396b89..e123eb1cc7 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -19,7 +19,7 @@ %td = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace" - - if !defined?(ref) || ref + - if defined?(ref) && ref %td - if build.ref = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index c15386b488..f21c864e35 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -15,12 +15,13 @@ - if defined?(commit_sha) && commit_sha %td = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" - - %td - - if generic_commit_status.ref - = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - - else - .light none + + - if defined?(ref) && ref + %td + - if generic_commit_status.ref + = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) + - else + .light none - if defined?(runner) && runner %td diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 25377a4044..e28998d51b 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -48,7 +48,7 @@ describe API::API, api: true do expect(response.status).to eq(404) end - it "should return not_found for CI status" do + it "should return nil for commit without CI" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response.status).to eq(200) expect(json_response['status']).to be_nil From 4b025ba02bb19c72a6872d0c6821fa0b10c58800 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 13 Apr 2016 12:30:42 +0200 Subject: [PATCH 032/113] Document the feature freeze of stable branches Fixes gitlab-org/gitlab-ce#15183 --- PROCESS.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/PROCESS.md b/PROCESS.md index cad45d23df..e34f59c6bc 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -105,6 +105,25 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide]. +## Feature Freeze + +5 working days before the 22nd the stable branches for the upcoming release will +be frozen for major changes. Merge requests may still be merged into master +during this period. By freezing the stable branches prior to a release there's +no need to worry about last minute merge requests potentially breaking a lot of +things. + +What is considered to be a major change is determined on a case by case basis as +this definition depends very much on the context of changes. For example, a 5 +line change might have a big impact on the entire application. Ultimately the +decision will be made by those reviewing a merge request and the release +manager. + +During the feature freeze all merge requests that are meant to go into the next +release should have the correct milestone assigned _and_ have the label +~"Pick into Stable" set. Merge requests without a milestone and this label will +not be merged into any stable branches. + ## Copy & paste responses ### Improperly formatted issue From 28ce41c00ebba5b60c69f75d9bce97c11210779a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 07:35:43 -0400 Subject: [PATCH 033/113] Fix tests --- app/models/commit_status.rb | 2 +- features/steps/dashboard/dashboard.rb | 2 +- spec/models/ci/commit_spec.rb | 27 ++++++++++++++++++++++++++- spec/models/commit_status_spec.rb | 24 ------------------------ 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 24a26b4be8..aa56314aa1 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -110,7 +110,7 @@ class CommitStatus < ActiveRecord::Base elsif started_at Time.now - started_at end - duration.to_i + duration end def stuck? diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index ba0e829dc0..b5980b3510 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -13,7 +13,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps end step 'I should see "Shop" project CI status' do - expect(page).to have_link "Build: skipped" + expect(page).to have_link "Build skipped" end step 'I should see last push widget' do diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index aef4f00720..c12327c2a7 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -355,7 +355,8 @@ describe Ci::Commit, models: true do end context 'update state' do - let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: Time.now - 120, finished_at: Time.now - 60 } + let(:current) { Time.now.change(:usec => 0) } + let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 } before do build @@ -368,4 +369,28 @@ describe Ci::Commit, models: true do end end end + + describe '#branch?' do + subject { commit.branch? } + + context 'is not a tag' do + before do + commit.tag = false + end + + it 'return true when tag is set to false' do + is_expected.to be_truthy + end + end + + context 'is not a tag' do + before do + commit.tag = true + end + + it 'return false when tag is set to true' do + is_expected.to be_falsey + end + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 31d546820c..971e675037 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -232,28 +232,4 @@ describe CommitStatus, models: true do end end end - - describe '#branch?' do - subject { commit_status.branch? } - - context 'is not a tag' do - before do - commit_status.tag = false - end - - it 'return true when tag is set to false' do - is_expected.to be_truthy - end - end - - context 'is not a tag' do - before do - commit_status.tag = true - end - - it 'return false when tag is set to true' do - is_expected.to be_falsey - end - end - end end From e5ff37c1fcd384f417518c8cca6a2a2dcc3c2767 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 07:45:23 -0400 Subject: [PATCH 034/113] Fix rubocop --- spec/models/ci/commit_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index c12327c2a7..82c18aaa01 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -355,7 +355,7 @@ describe Ci::Commit, models: true do end context 'update state' do - let(:current) { Time.now.change(:usec => 0) } + let(:current) { Time.now.change(usec: 0) } let(:build) { FactoryGirl.create :ci_build, :success, commit: commit, started_at: current - 120, finished_at: current - 60 } before do From 9a99fccb9717d4f1b0221de84094bb8db4ed280f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Apr 2016 12:49:23 -0500 Subject: [PATCH 035/113] Remove $parent --- app/assets/javascripts/labels_select.js.coffee | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index ddf7d84a38..602fc4be07 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -2,15 +2,14 @@ class @LabelsSelect constructor: -> $('.js-label-select').each (i, dropdown) -> $dropdown = $(dropdown) - $parent = $dropdown.parent() projectId = $dropdown.data('project-id') labelUrl = $dropdown.data('labels') issueUpdateURL = $dropdown.data('issueUpdate') selectedLabel = $dropdown.data('selected') if selectedLabel? selectedLabel = selectedLabel.split(',') - newLabelField = $('#new_label_name', $parent) - newColorField = $('#new_label_color', $parent) + newLabelField = $('#new_label_name') + newColorField = $('#new_label_color') showNo = $dropdown.data('show-no') showAny = $dropdown.data('show-any') defaultLabel = $dropdown.data('default-label') @@ -19,9 +18,9 @@ class @LabelsSelect $block = $selectbox.closest('.block') $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span') $value = $block.find('.value') - $newLabelError = $('.js-label-error', $parent) - $colorPreview = $('.js-dropdown-label-color-preview', $parent) - $newLabelCreateButton = $('.js-new-label-btn', $parent) + $newLabelError = $('.js-label-error') + $colorPreview = $('.js-dropdown-label-color-preview') + $newLabelCreateButton = $('.js-new-label-btn') $newLabelError.hide() $loading = $block.find('.block-loading').fadeOut() From ee2dc0624093a0fe8665778fd0cc734754e7cdc9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 18 Apr 2016 15:08:24 -0400 Subject: [PATCH 036/113] Fix CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4b5564223f..29df53f689 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,7 +52,7 @@ v 8.7.0 (unreleased) - Hide `Create a group` help block when creating a new project in a group - Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.) - Allow issues and merge requests to be assigned to the author !2765 - - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) (Kamil Trzciński) + - Make Ci::Commit to group only similar builds and make it stateful (ref, tag) - Gracefully handle notes on deleted commits in merge requests (Stan Hu) - Decouple membership and notifications - Fix creation of merge requests for orphaned branches (Stan Hu) From 5c7181b51492bd3ee13c5b498ac117ba08576ea8 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Apr 2016 14:37:18 -0500 Subject: [PATCH 037/113] Add is-link attribute to know if a menu item should not be stopped --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index fa1f765095..398d3ba4dc 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -247,7 +247,7 @@ class GitLabDropdown if @options.multiSelect $target = $(e.target) - if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and ($target.attr('href') is '#') + if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link') e.stopPropagation() return false else diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index ef670bc9ba..62b95824ba 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -113,7 +113,7 @@ %a.dropdown-toggle-page{href: "#"} Create new %li - = link_to namespace_project_labels_path(@project.namespace, @project) do + = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do - if can? current_user, :admin_label, @project Manage labels - else From e784958cb5396d745e43797988bacf9c9a27e9f4 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Apr 2016 14:56:02 -0500 Subject: [PATCH 038/113] Move shared html to a partial --- .../shared/issuable/_label_dropdown.html.haml | 18 +----------------- .../issuable/_label_page_default.html.haml | 19 +++++++++++++++++++ app/views/shared/issuable/_sidebar.html.haml | 18 +----------------- 3 files changed, 21 insertions(+), 34 deletions(-) create mode 100644 app/views/shared/issuable/_label_page_default.html.haml diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 371acc46c5..ec0cb78fac 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -6,23 +6,7 @@ = h(params[:label_name].presence || "Label") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - .dropdown-page-one - = dropdown_title("Filter by label") - = dropdown_filter("Search labels") - = dropdown_content - - if @project - = dropdown_footer do - %ul.dropdown-footer-list - - if can? current_user, :admin_label, @project - %li - %a.dropdown-toggle-page{href: "#"} - Create new - %li - = link_to namespace_project_labels_path(@project.namespace, @project) do - - if can? current_user, :admin_label, @project - Manage labels - - else - View labels + = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label" } - if can? current_user, :admin_label, @project and @project = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml new file mode 100644 index 0000000000..80a8fb82e0 --- /dev/null +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -0,0 +1,19 @@ +- title = local_assigns.fetch(:title, 'Assign labels') +- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels') +.dropdown-page-one + = dropdown_title(title) + = dropdown_filter(filter_placeholder) + = dropdown_content + - if @project + = dropdown_footer do + %ul.dropdown-footer-list + - if can? current_user, :admin_label, @project + %li + %a.dropdown-toggle-page{href: "#"} + Create new + %li + = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do + - if can? current_user, :admin_label, @project + Manage labels + - else + View labels \ No newline at end of file diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 62b95824ba..225737b9ec 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -101,23 +101,7 @@ Label = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - .dropdown-page-one - = dropdown_title("Assign labels") - = dropdown_filter("Search labels") - = dropdown_content - - if @project - = dropdown_footer do - %ul.dropdown-footer-list - - if can? current_user, :admin_label, @project - %li - %a.dropdown-toggle-page{href: "#"} - Create new - %li - = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do - - if can? current_user, :admin_label, @project - Manage labels - - else - View labels + = render partial: "shared/issuable/label_page_default" - if can? current_user, :admin_label, @project and @project = render partial: "shared/issuable/label_page_create" From 62f6601c598d59781137109c0eee5c5ea1792e13 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 15 Apr 2016 12:04:07 -0300 Subject: [PATCH 039/113] Show project members only for members --- .../projects/project_members_controller.rb | 7 ++++- app/helpers/projects_helper.rb | 4 +++ app/models/ability.rb | 12 ++++++++- app/views/layouts/nav/_project.html.haml | 2 +- .../project_members_controller_spec.rb | 27 +++++++++++++++++++ 5 files changed, 49 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index e457db2f0b..f8c9ff657d 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,6 +1,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize - before_action :authorize_admin_project_member!, except: :leave + before_action :authorize_admin_project_member!, except: [:leave, :index] + before_action :authorize_read_members_list!, only: [:index] def index @project_members = @project.project_members @@ -112,4 +113,8 @@ class Projects::ProjectMembersController < Projects::ApplicationController def member_params params.require(:project_member).permit(:user_id, :access_level) end + + def authorize_read_members_list! + render_403 unless can?(current_user, :read_members_list , @project) + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7e00aaccea..fc3662bc09 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -144,6 +144,10 @@ module ProjectsHelper nav_tabs << :settings end + if can?(current_user, :read_members_list, project) + nav_tabs << :team + end + if can?(current_user, :read_issue, project) nav_tabs << :issues end diff --git a/app/models/ability.rb b/app/models/ability.rb index 6103a2947e..a037aee6d5 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -154,9 +154,17 @@ class Ability end end + def project_member_rules(team, user) + all_members_rules = [] + + #Rules only for members which does not include public behavior + all_members_rules << :read_members_list if team.members.include?(user) + all_members_rules + end + def project_team_rules(team, user) # Rules based on role in project - if team.master?(user) + filtered_rules = if team.master?(user) project_master_rules elsif team.developer?(user) project_dev_rules @@ -165,6 +173,8 @@ class Ability elsif team.guest?(user) project_guest_rules end + + Array(filtered_rules) + project_member_rules(team, user) end def public_project_rules diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 86b46e8c75..a15b7758c4 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -77,7 +77,7 @@ Merge Requests %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) - - if project_nav_tab? :settings + - if project_nav_tab? :team = nav_link(controller: [:project_members, :teams]) do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do = icon('users fw') diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index d47e4ab9a4..c52c586cc9 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -46,4 +46,31 @@ describe Projects::ProjectMembersController do end end end + + describe 'index' do + let(:project) { create(:project, :internal) } + + context 'when user is member' do + let(:member) { create(:user) } + + before do + project.team << [member, :guest] + sign_in(member) + get :index, namespace_id: project.namespace.to_param, project_id: project.to_param + end + + it { expect(response.status).to eq(200) } + end + + context 'when user is not member' do + let(:not_member) { create(:user) } + + before do + sign_in(not_member) + get :index, namespace_id: project.namespace.to_param, project_id: project.to_param + end + + it { expect(response.status).to eq(403) } + end + end end From 0b91ff287d12d59bb4193fff4c8e605f8a1a6e69 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 18 Apr 2016 17:52:10 -0300 Subject: [PATCH 040/113] Projects members tab should follow visibility levels --- app/models/ability.rb | 15 +++------------ .../projects/project_members_controller_spec.rb | 15 ++------------- .../security/project/internal_access_spec.rb | 10 +++++----- .../security/project/private_access_spec.rb | 6 +++--- .../security/project/public_access_spec.rb | 12 ++++++------ 5 files changed, 19 insertions(+), 39 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index a037aee6d5..386c3d82d2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -154,17 +154,9 @@ class Ability end end - def project_member_rules(team, user) - all_members_rules = [] - - #Rules only for members which does not include public behavior - all_members_rules << :read_members_list if team.members.include?(user) - all_members_rules - end - def project_team_rules(team, user) # Rules based on role in project - filtered_rules = if team.master?(user) + if team.master?(user) project_master_rules elsif team.developer?(user) project_dev_rules @@ -173,8 +165,6 @@ class Ability elsif team.guest?(user) project_guest_rules end - - Array(filtered_rules) + project_member_rules(team, user) end def public_project_rules @@ -199,7 +189,8 @@ class Ability :create_project, :create_issue, :create_note, - :upload_file + :upload_file, + :read_members_list ] end diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index c52c586cc9..1bc5ad4706 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -48,7 +48,7 @@ describe Projects::ProjectMembersController do end describe 'index' do - let(:project) { create(:project, :internal) } + let(:project) { create(:project, :private) } context 'when user is member' do let(:member) { create(:user) } @@ -59,18 +59,7 @@ describe Projects::ProjectMembersController do get :index, namespace_id: project.namespace.to_param, project_id: project.to_param end - it { expect(response.status).to eq(200) } - end - - context 'when user is not member' do - let(:not_member) { create(:user) } - - before do - sign_in(not_member) - get :index, namespace_id: project.namespace.to_param, project_id: project.to_param - end - - it { expect(response.status).to eq(403) } + it { expect(response.status).to eq(200) } end end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 79d5bf4cf0..8625ea6bc1 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -101,12 +101,12 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_denied_for :external } end describe "GET /:project_path/blob" do diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 0a89193eb6..544270b403 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -101,9 +101,9 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 40daac89d4..4def4f99bc 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -101,12 +101,12 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for master } - it { is_expected.to be_denied_for developer } - it { is_expected.to be_denied_for reporter } - it { is_expected.to be_denied_for guest } - it { is_expected.to be_denied_for :user } - it { is_expected.to be_denied_for :external } - it { is_expected.to be_denied_for :visitor } + it { is_expected.to be_allowed_for developer } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } + it { is_expected.to be_allowed_for :external } end describe "GET /:project_path/builds" do From ded5efad1ffddb5f1bf8585802bb25e588eee6ea Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 15 Apr 2016 21:35:48 -0500 Subject: [PATCH 041/113] Allow filtering by Owned projects --- app/controllers/concerns/filter_projects.rb | 5 +++++ app/views/shared/projects/_dropdown.html.haml | 10 +++++++++- features/dashboard/dashboard.feature | 5 +++++ features/steps/shared/issuable.rb | 14 ++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/controllers/concerns/filter_projects.rb b/app/controllers/concerns/filter_projects.rb index f63b703d10..091412a7c9 100644 --- a/app/controllers/concerns/filter_projects.rb +++ b/app/controllers/concerns/filter_projects.rb @@ -10,6 +10,11 @@ module FilterProjects def filter_projects(projects) projects = projects.search(params[:filter_projects]) if params[:filter_projects].present? projects = projects.non_archived if params[:archived].blank? + + if params[:personal].present? and current_user + projects = projects.personal(current_user) + end + projects end end diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml index e7e04621ff..681d182dae 100644 --- a/app/views/shared/projects/_dropdown.html.haml +++ b/app/views/shared/projects/_dropdown.html.haml @@ -1,4 +1,5 @@ - @sort ||= sort_value_recently_updated +- personal = params[:personal] - archived = params[:archived] .dropdown.inline %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} @@ -10,7 +11,7 @@ Sort by - projects_sort_options_hash.each do |value, title| %li - = link_to filter_projects_path(sort: value, archived: archived), class: ("is-active" if @sort == value) do + = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do = title %li.divider @@ -20,3 +21,10 @@ %li = link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do Show archived projects + %li.divider + %li + = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do + Owned by anyone + %li + = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do + Owned by me diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index db73309804..c88cd0c9c2 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -79,3 +79,8 @@ Feature: Dashboard And I sort the list by "Oldest updated" And I visit project "Shop" merge requests page Then The list should be sorted by "Oldest updated" + + @javascript @focus + Scenario: Sort owned Projects + Then I filter the list by "Owned by me" + Then The list should be filtered by "Owned by me" diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index a58b3cb7e1..c09be48080 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -138,6 +138,20 @@ module SharedIssuable end end + step 'I filter the list by "Owned by me"' do + find('button.dropdown-toggle.btn').click + + page.within('ul.dropdown-menu.dropdown-menu-align-right') do + click_link "Owned by me" + end + end + + step 'The list should be filtered by "Owned by me"' do + page.within('ul.dropdown-menu.dropdown-menu-align-right') do + expect(page).to have_selector('a', text: 'Owned by me') + end + end + step 'I should see "1 of 1" in the sidebar' do expect_sidebar_content('1 of 1') end From 9bd29a772604d9ea9aa7f9a5e6d4482e6fc81d6b Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Apr 2016 19:53:49 -0500 Subject: [PATCH 042/113] Remove Spinach feature --- features/dashboard/dashboard.feature | 5 ----- features/steps/shared/issuable.rb | 14 -------------- 2 files changed, 19 deletions(-) diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index c88cd0c9c2..db73309804 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -79,8 +79,3 @@ Feature: Dashboard And I sort the list by "Oldest updated" And I visit project "Shop" merge requests page Then The list should be sorted by "Oldest updated" - - @javascript @focus - Scenario: Sort owned Projects - Then I filter the list by "Owned by me" - Then The list should be filtered by "Owned by me" diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index c09be48080..a58b3cb7e1 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -138,20 +138,6 @@ module SharedIssuable end end - step 'I filter the list by "Owned by me"' do - find('button.dropdown-toggle.btn').click - - page.within('ul.dropdown-menu.dropdown-menu-align-right') do - click_link "Owned by me" - end - end - - step 'The list should be filtered by "Owned by me"' do - page.within('ul.dropdown-menu.dropdown-menu-align-right') do - expect(page).to have_selector('a', text: 'Owned by me') - end - end - step 'I should see "1 of 1" in the sidebar' do expect_sidebar_content('1 of 1') end From c0ce49861510e73194760a4251858437abaedc03 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Apr 2016 19:54:53 -0500 Subject: [PATCH 043/113] Add tests --- spec/features/dashboard_filter_spec.rb | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 spec/features/dashboard_filter_spec.rb diff --git a/spec/features/dashboard_filter_spec.rb b/spec/features/dashboard_filter_spec.rb new file mode 100644 index 0000000000..3d20813161 --- /dev/null +++ b/spec/features/dashboard_filter_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe "Dashboard projects filters", feature: true, js: true do + + context 'filtering personal projects' do + before do + user = create(:user) + project = create(:project, name: "Victorialand", namespace: user.namespace) + project.team << [user, :master] + + login_as(user) + visit dashboard_projects_path + + open_filter_dropdown + click_link "Owned by me" + end + + it 'filters by projects "Owned by me"' do + sleep 1 + open_filter_dropdown + page.within('ul.dropdown-menu.dropdown-menu-align-right') do + expect(page).to have_css('.is-active', text: 'Owned by me') + end + end + end + + def open_filter_dropdown + find('button.dropdown-toggle.btn').click + end +end From 136887da4b1c1184630ca2242055fe35bf04a69b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 19 Apr 2016 10:54:27 +0100 Subject: [PATCH 044/113] Removed JS update templates --- app/controllers/projects/issues_controller.rb | 1 - app/controllers/projects/merge_requests_controller.rb | 1 - app/views/projects/issues/update.js.haml | 0 app/views/projects/merge_requests/update.js.haml | 0 app/views/shared/issuable/_sidebar.html.haml | 2 +- 5 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 app/views/projects/issues/update.js.haml delete mode 100644 app/views/projects/merge_requests/update.js.haml diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 38214f0479..05f12dad6a 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -100,7 +100,6 @@ class Projects::IssuesController < Projects::ApplicationController end respond_to do |format| - format.js format.html do if @issue.valid? redirect_to issue_path(@issue) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3e0cfc6aa6..debe0475a2 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -148,7 +148,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController if @merge_request.valid? respond_to do |format| - format.js format.html do redirect_to([@merge_request.target_project.namespace.becomes(Namespace), @merge_request.target_project, @merge_request]) diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 08bfd93f4e..4560667d91 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -20,7 +20,7 @@ %a.btn.btn-default.disabled{href: '#'} Next - = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| + = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| .block.assignee .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)} - if issuable.assignee From 6fdde5fabd9defed06a3308da7788c9a7f4632cf Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 19 Apr 2016 12:56:58 +0200 Subject: [PATCH 045/113] filter labels by including all filter titles as part of the query --- app/finders/issuable_finder.rb | 2 +- app/models/concerns/issuable.rb | 2 +- spec/features/issues/filter_by_labels_spec.rb | 170 ++++++++++++++++++ 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 spec/features/issues/filter_by_labels_spec.rb diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index f1df6832bf..f8e2062d11 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -270,7 +270,7 @@ class IssuableFinder if filter_by_no_label? items = items.without_label else - items = items.with_label(label_names) + items = items.with_label(label_names.flatten) if projects items = items.where(labels: { project_id: projects }) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index afa2ca039a..e2a2899941 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -37,7 +37,7 @@ module Issuable 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 :with_label, ->(title) { joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}") } 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) } diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb new file mode 100644 index 0000000000..27d9ea7c62 --- /dev/null +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -0,0 +1,170 @@ +# Uncomment once this is merged with multi-filter-labels +# Changes are related to using AND in label filters instead of OR + +# require 'rails_helper' +# +# feature 'Issue filtering by Labels', feature: true do +# let(:project) { create(:project, :public) } +# let!(:user) { create(:user)} +# let!(:label) { create(:label, project: project) } +# +# before do +# ['bug', 'feature', 'enhancement'].each do |title| +# create(:label, +# project: project, +# title: title) +# end +# +# issue1 = create(:issue, title: "Bugfix1", project: project) +# issue1.labels << project.labels.find_by(title: 'bug') +# +# issue2 = create(:issue, title: "Bugfix2", project: project) +# issue2.labels << project.labels.find_by(title: 'bug') +# issue2.labels << project.labels.find_by(title: 'enhancement') +# +# issue3 = create(:issue, title: "Feature1", project: project) +# issue3.labels << project.labels.find_by(title: 'feature') +# +# project.team << [user, :master] +# login_as(user) +# +# visit namespace_project_issues_path(project.namespace, project) +# end +# +# context 'filter by label bug', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do +# expect(page).to have_content "Bugfix1" +# expect(page).to have_content "Bugfix2" +# end +# +# it 'should not show "Feature1" in issues list' do +# expect(page).not_to have_content "Feature1" +# end +# +# it 'should show label "bug" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "bug" +# end +# +# it 'should not show label "feature" and "enhancement" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "feature" +# expect(find('.filtered-labels')).not_to have_content "enhancement" +# end +# end +# +# context 'filter by label feature', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should show issue "Feature1" in issues list' do +# expect(page).to have_content "Feature1" +# end +# +# it 'should not show "Bugfix1" and "Bugfix2" in issues list' do +# expect(page).not_to have_content "Bugfix2" +# expect(page).not_to have_content "Bugfix1" +# end +# +# it 'should show label "feature" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "feature" +# end +# +# it 'should not show label "bug" and "enhancement" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "bug" +# expect(find('.filtered-labels')).not_to have_content "enhancement" +# end +# end +# +# context 'filter by label enhancement', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should show issue "Bugfix2" in issues list' do +# expect(page).to have_content "Bugfix2" +# end +# +# it 'should not show "Feature1" and "Bugfix1" in issues list' do +# expect(page).not_to have_content "Feature1" +# expect(page).not_to have_content "Bugfix1" +# end +# +# it 'should show label "enhancement" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "enhancement" +# end +# +# it 'should not show label "feature" and "bug" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "bug" +# expect(find('.filtered-labels')).not_to have_content "feature" +# end +# end +# +# context 'filter by label enhancement or feature', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") +# execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should not show "Bugfix1" or "Feature1" in issues list' do +# expect(page).not_to have_content "Bugfix1" +# expect(page).not_to have_content "Feature1" +# end +# +# it 'should show label "enhancement" and "feature" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "enhancement" +# expect(find('.filtered-labels')).to have_content "feature" +# end +# +# it 'should not show label "bug" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "bug" +# end +# end +# +# context 'filter by label enhancement and bug in issues list', js: true do +# before do +# page.find('.js-label-select').click +# sleep 0.5 +# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") +# execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") +# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click +# sleep 2 +# end +# +# it 'should show issue "Bugfix2" in issues list' do +# expect(page).to have_content "Bugfix2" +# end +# +# it 'should not show "Feature1"' do +# expect(page).not_to have_content "Feature1" +# end +# +# it 'should show label "bug" and "enhancement" in filtered-labels' do +# expect(find('.filtered-labels')).to have_content "bug" +# expect(find('.filtered-labels')).to have_content "enhancement" +# end +# +# it 'should not show label "feature" in filtered-labels' do +# expect(find('.filtered-labels')).not_to have_content "feature" +# end +# end +# end From 60b5b22241046498c53a0ddb8509b4ebbf2b9315 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 19 Apr 2016 05:44:44 -0700 Subject: [PATCH 046/113] Update Sidekiq and related gems (bundle update sidekiq) Closes #15079 --- Gemfile.lock | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 958824f7ed..1589f226d3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,7 +134,7 @@ GEM execjs coffee-script-source (1.10.0) colorize (0.7.7) - concurrent-ruby (1.0.0) + concurrent-ruby (1.0.1) connection_pool (2.2.0) coveralls (0.8.13) json (~> 1.8) @@ -627,7 +627,7 @@ GEM recaptcha (1.0.2) json redcarpet (3.3.3) - redis (3.2.2) + redis (3.3.0) redis-actionpack (4.0.1) actionpack (~> 4) redis-rack (~> 1.5.0) @@ -734,10 +734,9 @@ GEM rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (4.0.1) + sidekiq (4.1.1) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) - json (~> 1.0) redis (~> 3.2, >= 3.2.1) sidekiq-cron (0.4.0) redis-namespace (>= 1.5.2) From 8619208b644f2acdc30ccf36209ac56527bb4188 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 19 Apr 2016 15:36:28 +0200 Subject: [PATCH 047/113] fix other spec failures --- app/helpers/milestones_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 87fc2db690..e2d01632df 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -32,7 +32,7 @@ module MilestonesHelper end def milestone_issues_by_label_count(milestone, label, state:) - milestone.issues.with_label(label.title).send(state).size + milestone.issues.with_label([label.title]).send(state).size end def milestone_progress_bar(milestone) From d7f1e041d9df1121b550fbdb7682d25e404b04c2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 19 Apr 2016 19:12:12 +0300 Subject: [PATCH 048/113] Fix links in update README [ci skip] --- doc/update/README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/update/README.md b/doc/update/README.md index 0241f03683..d4197e2cfc 100644 --- a/doc/update/README.md +++ b/doc/update/README.md @@ -2,17 +2,20 @@ Depending on the installation method and your GitLab version, there are multiple ## Omnibus Packages -- [Omnibus update guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md) contains the steps needed to update a GitLab [package](https://about.gitlab.com/downloads/). +- [Omnibus update guide](http://doc.gitlab.com/omnibus/update/README.html) + contains the steps needed to update a GitLab + [package](https://about.gitlab.com/downloads/). ## Installation from source - [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab from source. -- [The CE to EE update guides](https://gitlab.com/subscribers/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status. -- [Upgrader](upgrader.md) is an automatic ruby script that performs the update for installations from source. -- [Patch versions](patch_versions.md) guide includes the steps needed for a patch version, eg. 6.2.0 to 6.2.1. +- [The CE to EE update guides](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status. +- [Patch versions](patch_versions.md) guide includes the steps needed for a + patch version, eg. 6.2.0 to 6.2.1. ## Miscellaneous -- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL. +- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating + your database from MySQL to PostgreSQL. - [MySQL installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/database_mysql.md) contains additional information about configuring GitLab to work with a MySQL database. - [Restoring from backup after a failed upgrade](restore_after_failure.md) From d6ab11a1ee043e0cacc1994219d3cc4d037dce5d Mon Sep 17 00:00:00 2001 From: Evan Lucas Date: Thu, 7 Apr 2016 10:03:22 -0500 Subject: [PATCH 049/113] add tap coverage example to project settings Previously, there were examples for Simplecov, pytest-cov, and phpunit. This adds an example for using the tap cli tool (https://github.com/tapjs/node-tap) --- app/views/projects/_builds_settings.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml index b6074373e2..0de019983c 100644 --- a/app/views/projects/_builds_settings.html.haml +++ b/app/views/projects/_builds_settings.html.haml @@ -55,6 +55,9 @@ %li gcovr (C/C++) - %code ^TOTAL.*\s+(\d+\%)$ + %li + tap --coverage-report=text-summary (Node.js) - + %code ^Statements\s*:\s*([^%]+) .form-group .col-sm-offset-2.col-sm-10 From 324e57693b21339273a9b9cafd9d27348fcd0488 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 19 Apr 2016 20:05:30 +0200 Subject: [PATCH 050/113] Implement top navigation concept for profile area Main idea is to keep left sidebar static so user is not confused by changing context. Instead we put changing navigation with changing content in one main block Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/nav.scss | 8 ++++++++ app/helpers/page_layout_helper.rb | 8 ++++++++ app/helpers/tab_helper.rb | 8 ++++++++ app/views/doorkeeper/applications/index.html.haml | 1 - app/views/layouts/_page.html.haml | 3 +++ app/views/layouts/application.html.haml | 2 +- app/views/layouts/nav/_dashboard.html.haml | 3 +-- app/views/layouts/nav/_profile.html.haml | 12 ++---------- app/views/layouts/profile.html.haml | 3 ++- app/views/profiles/accounts/show.html.haml | 1 - app/views/profiles/audit_log.html.haml | 1 - app/views/profiles/emails/index.html.haml | 1 - app/views/profiles/keys/index.html.haml | 1 - app/views/profiles/notifications/show.html.haml | 1 - app/views/profiles/passwords/edit.html.haml | 1 - app/views/profiles/preferences/show.html.haml | 1 - 16 files changed, 33 insertions(+), 22 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 192d53b048..e3f9825fe1 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -185,3 +185,11 @@ } } } + +.layout-nav { + margin-bottom: 5px; + + .nav-links { + padding: 0 $gl-padding; + } +} diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb index 82f805fa44..e4e8b934bc 100644 --- a/app/helpers/page_layout_helper.rb +++ b/app/helpers/page_layout_helper.rb @@ -84,6 +84,14 @@ module PageLayoutHelper end end + def nav(name = nil) + if name + @nav = name + else + @nav + end + end + def fluid_layout(enabled = false) if @fluid_layout.nil? @fluid_layout = (current_user && current_user.layout == "fluid") || enabled diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 04e53fe7c6..96a8367100 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -110,4 +110,12 @@ module TabHelper 'active' end end + + def profile_tab_class + if controller.controller_path =~ /\Aprofiles/ + return 'active' + end + + 'active' if current_controller?('oauth/applications') + end end diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 0aff79749e..79df17ba61 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -1,5 +1,4 @@ - page_title "Applications" -- header_title page_title, applications_profile_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index c799e9c588..d4e5d5e4bf 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -25,6 +25,9 @@ .content-wrapper = render "layouts/flash" = yield :flash_message + - if defined?(nav) && nav + .layout-nav + = render "layouts/nav/#{nav}" %div{ class: (container_class unless @no_container) } .content .clearfix diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index babfb03223..e4d1c773d0 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -6,6 +6,6 @@ = yield :scripts_body_top = render "layouts/header/default", title: header_title - = render 'layouts/page', sidebar: sidebar + = render 'layouts/page', sidebar: sidebar, nav: nav = yield :scripts_body diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 5cef652da1..ca49c313ff 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -48,8 +48,7 @@ %span Help - %li.separate-item - = nav_link(controller: :profile) do + = nav_link(html_options: {class: profile_tab_class}) do = link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do = icon('user fw') %span diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 3b9d31a6fc..d90dd3e248 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,17 +1,9 @@ -%ul.nav.nav-sidebar - = nav_link do - = link_to root_path, title: 'Go to dashboard', class: 'back-link' do - = icon('caret-square-o-left fw') - %span - Go to dashboard - - %li.separate-item - +%ul.nav-links = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: 'Profile Settings' do = icon('user fw') %span - Profile Settings + Profile = nav_link(controller: [:accounts, :two_factor_auths]) do = link_to profile_account_path, title: 'Account' do = icon('gear fw') diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index dfa6cc5702..b77d3402a2 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,5 +1,6 @@ - page_title "Profile Settings" - header_title "Profile Settings", profile_path unless header_title -- sidebar "profile" +- sidebar "dashboard" +- nav "profile" = render template: "layouts/application" diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 6efd119f26..afd3d79321 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,5 +1,4 @@ - page_title "Account" -- header_title page_title, profile_account_path - if current_user.ldap_user? .alert.alert-info diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml index f630c03e5f..9c404b6935 100644 --- a/app/views/profiles/audit_log.html.haml +++ b/app/views/profiles/audit_log.html.haml @@ -1,5 +1,4 @@ - page_title "Audit Log" -- header_title page_title, audit_log_profile_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 3f328f96ce..57527361eb 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,5 +1,4 @@ - page_title "Emails" -- header_title page_title, profile_emails_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index e0f8c9a573..6a067a0353 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,5 +1,4 @@ - page_title "SSH Keys" -- header_title page_title, profile_keys_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index a2a505c082..7696f112bb 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,5 +1,4 @@ - page_title "Notifications" -- header_title page_title, profile_notifications_path %div - if @user.errors.any? diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 5ac8a8b9d0..243428b690 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,5 +1,4 @@ - page_title "Password" -- header_title page_title, edit_profile_password_path .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index f80211669f..bfe53be685 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,5 +1,4 @@ - page_title 'Preferences' -- header_title page_title, profile_preferences_path = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f| .col-lg-3.profile-settings-sidebar From 52268d1d9a7c64886dcc99a1a82c02af1ccbe7a1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 19 Apr 2016 21:11:33 +0200 Subject: [PATCH 051/113] Fix tests and remove counters Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/nav/_profile.html.haml | 2 -- features/steps/profile/active_tab.rb | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index d90dd3e248..d730840d63 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -19,7 +19,6 @@ = icon('envelope-o fw') %span Emails - %span.count= number_with_delimiter(current_user.emails.count + 1) - unless current_user.ldap_user? = nav_link(controller: :passwords) do = link_to edit_profile_password_path, title: 'Password' do @@ -37,7 +36,6 @@ = icon('key fw') %span SSH Keys - %span.count= number_with_delimiter(current_user.keys.count) = nav_link(controller: :preferences) do = link_to profile_preferences_path, title: 'Preferences' do -# TODO (rspeicher): Better icon? diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb index 4724a32627..3b59089a09 100644 --- a/features/steps/profile/active_tab.rb +++ b/features/steps/profile/active_tab.rb @@ -22,4 +22,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps step 'the active main tab should be Audit Log' do ensure_active_main_tab('Audit Log') end + + def ensure_active_main_tab(content) + expect(find('.layout-nav li.active')).to have_content(content) + end end From 8530ce4c6fbc411e00cf426f3b3baca74a4370f7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 18 Apr 2016 14:13:38 -0400 Subject: [PATCH 052/113] Clarify that the diff view setting always comes from the cookie This invalidates one test, which we've removed. --- app/controllers/projects/application_controller.rb | 3 +-- .../projects/merge_requests_controller_spec.rb | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 74150ad606..be872a93fe 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -83,8 +83,7 @@ class Projects::ApplicationController < ApplicationController end def apply_diff_view_cookie! - view = params[:view] || cookies[:diff_view] - cookies.permanent[:diff_view] = params[:view] = view if view + cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present? end def builds_enabled diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c54e83339a..c0a1f45195 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -300,14 +300,6 @@ describe Projects::MergeRequestsController do expect(response.cookies['diff_view']).to eq('parallel') end - - it 'assigns :view param based on cookie' do - request.cookies['diff_view'] = 'parallel' - - go - - expect(controller.params[:view]).to eq 'parallel' - end end describe 'GET commits' do From af18cddddf4ecb402ac613ddcc1f313f6adb7aad Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 20 Apr 2016 10:56:28 +0200 Subject: [PATCH 053/113] udpated a few things based on MR feedback. Also added model spec --- app/finders/issuable_finder.rb | 5 +- app/helpers/milestones_helper.rb | 2 +- app/models/concerns/issuable.rb | 9 +- spec/features/issues/filter_by_labels_spec.rb | 335 +++++++++--------- spec/models/concerns/issuable_spec.rb | 26 +- 5 files changed, 202 insertions(+), 175 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index f8e2062d11..ca71acb65d 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -270,8 +270,7 @@ class IssuableFinder if filter_by_no_label? items = items.without_label else - items = items.with_label(label_names.flatten) - + items = items.with_label(label_names) if projects items = items.where(labels: { project_id: projects }) end @@ -282,7 +281,7 @@ class IssuableFinder end def label_names - params[:label_name].split(',') + params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name] end def current_user_related? diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index e2d01632df..87fc2db690 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -32,7 +32,7 @@ module MilestonesHelper end def milestone_issues_by_label_count(milestone, label, state:) - milestone.issues.with_label([label.title]).send(state).size + milestone.issues.with_label(label.title).send(state).size end def milestone_progress_bar(milestone) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index e2a2899941..d5166e8147 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -37,7 +37,6 @@ module Issuable 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 }).group('issues.id').having("count(distinct labels.title) = #{title.count}") } 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) } @@ -122,6 +121,14 @@ module Issuable joins(join_clause).group(issuable_table[:id]).reorder("COUNT(notes.id) DESC") end + + def with_label(title) + if title.is_a?(Array) && title.count > 1 + joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}") + else + joins(:labels).where(labels: { title: title }) + end + end end def today? diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 27d9ea7c62..b189a97fbc 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -1,170 +1,167 @@ -# Uncomment once this is merged with multi-filter-labels -# Changes are related to using AND in label filters instead of OR +require 'rails_helper' -# require 'rails_helper' -# -# feature 'Issue filtering by Labels', feature: true do -# let(:project) { create(:project, :public) } -# let!(:user) { create(:user)} -# let!(:label) { create(:label, project: project) } -# -# before do -# ['bug', 'feature', 'enhancement'].each do |title| -# create(:label, -# project: project, -# title: title) -# end -# -# issue1 = create(:issue, title: "Bugfix1", project: project) -# issue1.labels << project.labels.find_by(title: 'bug') -# -# issue2 = create(:issue, title: "Bugfix2", project: project) -# issue2.labels << project.labels.find_by(title: 'bug') -# issue2.labels << project.labels.find_by(title: 'enhancement') -# -# issue3 = create(:issue, title: "Feature1", project: project) -# issue3.labels << project.labels.find_by(title: 'feature') -# -# project.team << [user, :master] -# login_as(user) -# -# visit namespace_project_issues_path(project.namespace, project) -# end -# -# context 'filter by label bug', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do -# expect(page).to have_content "Bugfix1" -# expect(page).to have_content "Bugfix2" -# end -# -# it 'should not show "Feature1" in issues list' do -# expect(page).not_to have_content "Feature1" -# end -# -# it 'should show label "bug" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "bug" -# end -# -# it 'should not show label "feature" and "enhancement" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "feature" -# expect(find('.filtered-labels')).not_to have_content "enhancement" -# end -# end -# -# context 'filter by label feature', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should show issue "Feature1" in issues list' do -# expect(page).to have_content "Feature1" -# end -# -# it 'should not show "Bugfix1" and "Bugfix2" in issues list' do -# expect(page).not_to have_content "Bugfix2" -# expect(page).not_to have_content "Bugfix1" -# end -# -# it 'should show label "feature" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "feature" -# end -# -# it 'should not show label "bug" and "enhancement" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "bug" -# expect(find('.filtered-labels')).not_to have_content "enhancement" -# end -# end -# -# context 'filter by label enhancement', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should show issue "Bugfix2" in issues list' do -# expect(page).to have_content "Bugfix2" -# end -# -# it 'should not show "Feature1" and "Bugfix1" in issues list' do -# expect(page).not_to have_content "Feature1" -# expect(page).not_to have_content "Bugfix1" -# end -# -# it 'should show label "enhancement" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "enhancement" -# end -# -# it 'should not show label "feature" and "bug" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "bug" -# expect(find('.filtered-labels')).not_to have_content "feature" -# end -# end -# -# context 'filter by label enhancement or feature', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") -# execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should not show "Bugfix1" or "Feature1" in issues list' do -# expect(page).not_to have_content "Bugfix1" -# expect(page).not_to have_content "Feature1" -# end -# -# it 'should show label "enhancement" and "feature" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "enhancement" -# expect(find('.filtered-labels')).to have_content "feature" -# end -# -# it 'should not show label "bug" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "bug" -# end -# end -# -# context 'filter by label enhancement and bug in issues list', js: true do -# before do -# page.find('.js-label-select').click -# sleep 0.5 -# execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") -# execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") -# page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click -# sleep 2 -# end -# -# it 'should show issue "Bugfix2" in issues list' do -# expect(page).to have_content "Bugfix2" -# end -# -# it 'should not show "Feature1"' do -# expect(page).not_to have_content "Feature1" -# end -# -# it 'should show label "bug" and "enhancement" in filtered-labels' do -# expect(find('.filtered-labels')).to have_content "bug" -# expect(find('.filtered-labels')).to have_content "enhancement" -# end -# -# it 'should not show label "feature" in filtered-labels' do -# expect(find('.filtered-labels')).not_to have_content "feature" -# end -# end -# end +feature 'Issue filtering by Labels', feature: true do + let(:project) { create(:project, :public) } + let!(:user) { create(:user)} + let!(:label) { create(:label, project: project) } + + before do + ['bug', 'feature', 'enhancement'].each do |title| + create(:label, + project: project, + title: title) + end + + issue1 = create(:issue, title: "Bugfix1", project: project) + issue1.labels << project.labels.find_by(title: 'bug') + + issue2 = create(:issue, title: "Bugfix2", project: project) + issue2.labels << project.labels.find_by(title: 'bug') + issue2.labels << project.labels.find_by(title: 'enhancement') + + issue3 = create(:issue, title: "Feature1", project: project) + issue3.labels << project.labels.find_by(title: 'feature') + + project.team << [user, :master] + login_as(user) + + visit namespace_project_issues_path(project.namespace, project) + end + + context 'filter by label bug', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do + expect(page).to have_content "Bugfix1" + expect(page).to have_content "Bugfix2" + end + + it 'should not show "Feature1" in issues list' do + expect(page).not_to have_content "Feature1" + end + + it 'should show label "bug" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "bug" + end + + it 'should not show label "feature" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "feature" + expect(find('.filtered-labels')).not_to have_content "enhancement" + end + end + + context 'filter by label feature', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Feature1" in issues list' do + expect(page).to have_content "Feature1" + end + + it 'should not show "Bugfix1" and "Bugfix2" in issues list' do + expect(page).not_to have_content "Bugfix2" + expect(page).not_to have_content "Bugfix1" + end + + it 'should show label "feature" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "feature" + end + + it 'should not show label "bug" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + expect(find('.filtered-labels')).not_to have_content "enhancement" + end + end + + context 'filter by label enhancement', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix2" in issues list' do + expect(page).to have_content "Bugfix2" + end + + it 'should not show "Feature1" and "Bugfix1" in issues list' do + expect(page).not_to have_content "Feature1" + expect(page).not_to have_content "Bugfix1" + end + + it 'should show label "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "enhancement" + end + + it 'should not show label "feature" and "bug" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + expect(find('.filtered-labels')).not_to have_content "feature" + end + end + + context 'filter by label enhancement or feature', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should not show "Bugfix1" or "Feature1" in issues list' do + expect(page).not_to have_content "Bugfix1" + expect(page).not_to have_content "Feature1" + end + + it 'should show label "enhancement" and "feature" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "enhancement" + expect(find('.filtered-labels')).to have_content "feature" + end + + it 'should not show label "bug" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "bug" + end + end + + context 'filter by label enhancement and bug in issues list', js: true do + before do + page.find('.js-label-select').click + sleep 0.5 + execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") + execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") + page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click + sleep 2 + end + + it 'should show issue "Bugfix2" in issues list' do + expect(page).to have_content "Bugfix2" + end + + it 'should not show "Feature1"' do + expect(page).not_to have_content "Feature1" + end + + it 'should show label "bug" and "enhancement" in filtered-labels' do + expect(find('.filtered-labels')).to have_content "bug" + expect(find('.filtered-labels')).to have_content "enhancement" + end + + it 'should not show label "feature" in filtered-labels' do + expect(find('.filtered-labels')).not_to have_content "feature" + end + end +end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index b16ccc6e30..dc7a9a1089 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -212,4 +212,28 @@ describe Issue, "Issuable" do expect(issue.downvotes).to eq(1) end end -end + + describe ".with_label" do + let(:example_label) { 'test1' } + let(:example_labels) { ['test1', 'test2'] } + + it 'finds issue with 1 label' do + setup_labels([example_label]) + + expect(Issue.with_label(example_label).count).to eq(1) + end + + it 'finds issue with 2 labels' do + setup_labels(example_labels) + + expect(Issue.with_label(example_labels).to_a.count).to eq(1) + end + + def setup_labels(label_names) + labels = label_names.map do |label| + create(:label, project: issue.project, title: label) + end + issue.labels << labels + end + end +end \ No newline at end of file From c0948396d1fd8bf4cbd187f33583b2f6db5d2178 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 20 Apr 2016 11:25:33 +0200 Subject: [PATCH 054/113] fix rubocop warning --- spec/models/concerns/issuable_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index dc7a9a1089..6b61bbb2fa 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -220,13 +220,13 @@ describe Issue, "Issuable" do it 'finds issue with 1 label' do setup_labels([example_label]) - expect(Issue.with_label(example_label).count).to eq(1) + expect(Issue.with_label(example_label).size).to eq(1) end it 'finds issue with 2 labels' do setup_labels(example_labels) - expect(Issue.with_label(example_labels).to_a.count).to eq(1) + expect(Issue.with_label(example_labels).to_a.size).to eq(1) end def setup_labels(label_names) From 976593fa7fd657437c68ade72f2061260318e7a9 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 20 Apr 2016 11:50:07 +0200 Subject: [PATCH 055/113] final line missing --- spec/models/concerns/issuable_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 6b61bbb2fa..63a0a47e92 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -236,4 +236,4 @@ describe Issue, "Issuable" do issue.labels << labels end end -end \ No newline at end of file +end From eb99e5f5c1cb2dfd2e8d058c3866f08aba5f2a4c Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 20 Apr 2016 10:57:31 -0300 Subject: [PATCH 056/113] Remove unused authorization from controller --- app/controllers/projects/project_members_controller.rb | 5 ----- app/helpers/projects_helper.rb | 2 +- app/models/ability.rb | 3 +-- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index f8c9ff657d..33b2625c0a 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,7 +1,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize before_action :authorize_admin_project_member!, except: [:leave, :index] - before_action :authorize_read_members_list!, only: [:index] def index @project_members = @project.project_members @@ -113,8 +112,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController def member_params params.require(:project_member).permit(:user_id, :access_level) end - - def authorize_read_members_list! - render_403 unless can?(current_user, :read_members_list , @project) - end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index fc3662bc09..82f362b3b3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -144,7 +144,7 @@ module ProjectsHelper nav_tabs << :settings end - if can?(current_user, :read_members_list, project) + if can?(current_user, :read_project_member, project) nav_tabs << :team end diff --git a/app/models/ability.rb b/app/models/ability.rb index 386c3d82d2..6103a2947e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -189,8 +189,7 @@ class Ability :create_project, :create_issue, :create_note, - :upload_file, - :read_members_list + :upload_file ] end From 9581aba4d46f4f31e0ae1f4b76726d5ad01041e7 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 20 Apr 2016 12:41:35 -0300 Subject: [PATCH 057/113] Add changelog entry and fix convention in a spec --- CHANGELOG | 1 + spec/controllers/projects/project_members_controller_spec.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 290c956814..ee2ffc7035 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.7.0 (unreleased) - Add endpoints to archive or unarchive a project !3372 - Fix a bug whith trailing slash in bamboo_url - Add links to CI setup documentation from project settings and builds pages + - Display project members page to all members - Handle nil descriptions in Slack issue messages (Stan Hu) - Add automated repository integrity checks - API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling) diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb index 1bc5ad4706..ed64e7cf9a 100644 --- a/spec/controllers/projects/project_members_controller_spec.rb +++ b/spec/controllers/projects/project_members_controller_spec.rb @@ -47,7 +47,7 @@ describe Projects::ProjectMembersController do end end - describe 'index' do + describe '#index' do let(:project) { create(:project, :private) } context 'when user is member' do From 7bcacafc8a601896f8750f29fcde71f8f8ffb910 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 16:52:01 +0100 Subject: [PATCH 058/113] Fixed issue with incorrect line being highlighted if line is in discussion view --- app/assets/javascripts/merge_request_tabs.js.coffee | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index e8613cab72..55e09c6acb 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -176,12 +176,12 @@ class @MergeRequestTabs if locationHash isnt '' hashClassString = ".#{locationHash.replace('#', '')}" - $diffLine = $(locationHash) + $diffLine = $("#{locationHash}:not(.match)", $('#diffs')) - if $diffLine.is ':not(tr)' - $diffLine = $("td#{locationHash}, td#{hashClassString}") + if not $diffLine.is 'tr' + $diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}") else - $diffLine = $('td', $diffLine) + $diffLine = $diffLine.find('td') if $diffLine.length $diffLine.addClass 'hll' From 6495c13d70cfd26cb46fb8bd0b7bc138cb939ff0 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 17:00:05 +0100 Subject: [PATCH 059/113] Fixed issue with lines not being selectable when expanding diff --- app/views/projects/blob/diff.html.haml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index 38e62c81fe..88d51b00e3 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -1,17 +1,17 @@ - if @lines.present? - if @form.unfold? && @form.since != 1 && !@form.bottom? - %tr.line_holder{ id: @form.since } + %tr.line_holder = render "projects/diffs/match_line", { line: @match_line, line_old: @form.since, line_new: @form.since, bottom: false, new_file: false } - @lines.each_with_index do |line, index| - line_new = index + @form.since - line_old = line_new - @form.offset - %tr.line_holder + %tr.line_holder{ id: line_old } %td.old_line.diff-line-num{ data: { linenumber: line_old } } - = link_to raw(line_old), "#" - %td.new_line.diff-line-num{ data: { linenumber: line_old } } - = link_to raw(line_new) , "#" + = link_to raw(line_old), "##{line_old}" + %td.new_line.diff-line-num + = link_to raw(line_new) , "##{line_old}" %td.line_content.noteable_line==#{' ' * @form.indent}#{line} - if @form.unfold? && @form.bottom? && @form.to < @blob.loc From 28faa9719d70867f6ca0b0fb6b9fbf125812eb47 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Apr 2016 17:07:44 +0100 Subject: [PATCH 060/113] Fixed issue when scrolling to element and there is an expander --- app/assets/javascripts/merge_request_tabs.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee index 55e09c6acb..372732d0aa 100644 --- a/app/assets/javascripts/merge_request_tabs.js.coffee +++ b/app/assets/javascripts/merge_request_tabs.js.coffee @@ -87,8 +87,8 @@ class @MergeRequestTabs if window.location.hash navBarHeight = $('.navbar-gitlab').outerHeight() - $el = $("#{container} #{window.location.hash}") - $.scrollTo("#{container} #{window.location.hash}", offset: -navBarHeight) if $el.length + $el = $("#{container} #{window.location.hash}:not(.match)") + $.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length # Activate a tab based on the current action activateTab: (action) -> From 2dbfa1de27f5123b4be75b46cdebfae1f3fdbdeb Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 20 Apr 2016 17:30:21 +0100 Subject: [PATCH 061/113] Put back removed line number --- app/views/projects/blob/diff.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index 88d51b00e3..5926d181ba 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -10,7 +10,7 @@ %tr.line_holder{ id: line_old } %td.old_line.diff-line-num{ data: { linenumber: line_old } } = link_to raw(line_old), "##{line_old}" - %td.new_line.diff-line-num + %td.new_line.diff-line-num{ data: { linenumber: line_old } } = link_to raw(line_new) , "##{line_old}" %td.line_content.noteable_line==#{' ' * @form.indent}#{line} From f93b0f3ea25af48f9f165cf6c0e3c2fb359bca35 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 20 Apr 2016 18:30:53 +0200 Subject: [PATCH 062/113] Layout navigation should respect layout width settings Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/nav.scss | 8 -------- app/views/layouts/_page.html.haml | 3 ++- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index e3f9825fe1..192d53b048 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -185,11 +185,3 @@ } } } - -.layout-nav { - margin-bottom: 5px; - - .nav-links { - padding: 0 $gl-padding; - } -} diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index d4e5d5e4bf..ca9c2a0bf2 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -27,7 +27,8 @@ = yield :flash_message - if defined?(nav) && nav .layout-nav - = render "layouts/nav/#{nav}" + %div{ class: container_class } + = render "layouts/nav/#{nav}" %div{ class: (container_class unless @no_container) } .content .clearfix From e38958d1d51b5f5fd695f09600d29b9b455757a1 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 21 Apr 2016 00:20:13 +0000 Subject: [PATCH 063/113] Remove unused param --- app/assets/javascripts/commits.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee index ffd3627b1b..0acb4c1955 100644 --- a/app/assets/javascripts/commits.js.coffee +++ b/app/assets/javascripts/commits.js.coffee @@ -1,7 +1,7 @@ class @CommitsList @timer = null - @init: (ref, limit) -> + @init: (limit) -> $("body").on "click", ".day-commits-table li.commit", (event) -> if event.target.nodeName != "A" location.href = $(this).attr("url") From fd5b158765ff46617e1e00642bb266177f6d5e32 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Thu, 21 Apr 2016 00:21:28 +0000 Subject: [PATCH 064/113] Remove unnecessary provided param --- app/views/projects/commits/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index c52cf25d40..bcdb09208a 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -39,4 +39,4 @@ = spinner :javascript - CommitsList.init("#{@ref}", #{@limit}); + CommitsList.init(#{@limit}); From dcd54f63451ab689ec03653b525b5f218a9f420a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 18 Apr 2016 16:00:30 -0700 Subject: [PATCH 065/113] Attempt to create pg_trgm extension in migration for test/dev environments Closes #15210 --- ...0160226114608_add_trigram_indexes_for_searching.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb index 003169c13c..d7b00e3d6e 100644 --- a/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb +++ b/db/migrate/20160226114608_add_trigram_indexes_for_searching.rb @@ -4,6 +4,8 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration def up return unless Gitlab::Database.postgresql? + create_trigrams_extension + unless trigrams_enabled? raise 'You must enable the pg_trgm extension. You can do so by running ' \ '"CREATE EXTENSION pg_trgm;" as a PostgreSQL super user, this must be ' \ @@ -37,6 +39,15 @@ class AddTrigramIndexesForSearching < ActiveRecord::Migration row && row['enabled'] == 't' ? true : false end + def create_trigrams_extension + # This may not work if the user doesn't have permission. We attempt in + # case we do have permission, particularly for test/dev environments. + begin + enable_extension 'pg_trgm' + rescue + end + end + def to_index { ci_runners: [:token, :description], From dbe06ac790befe2d49dbb6edecb79d82abfe46e7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 20 Apr 2016 23:55:06 -0500 Subject: [PATCH 066/113] Add noreferrer value to rel attribute for external links --- lib/banzai/filter/external_link_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index d179bea181..699de56fd4 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -15,7 +15,7 @@ module Banzai # Skip internal links next if link.start_with?(internal_url) - node.set_attribute('rel', 'nofollow') + node.set_attribute('rel', 'nofollow noreferrer') end doc From 6865bc16f8974e80b1ae657d7330be30c12c21ad Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 21 Apr 2016 09:12:03 +0200 Subject: [PATCH 067/113] refactored specs, adding more scenarios --- spec/features/issues/filter_by_labels_spec.rb | 33 +++++++++---------- spec/models/concerns/issuable_spec.rb | 27 +++++++++++---- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 7944403f87..9696af4b00 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -4,23 +4,25 @@ feature 'Issue filtering by Labels', feature: true do let(:project) { create(:project, :public) } let!(:user) { create(:user)} let!(:label) { create(:label, project: project) } + let(:bug) { create(:label, project: project, title: 'bug') } + let(:feature) { create(:label, project: project, title: 'feature') } + let(:enhancement) { create(:label, project: project, title: 'enhancement') } before do - ['bug', 'feature', 'enhancement'].each do |title| - create(:label, - project: project, - title: title) - end + + bug = create(:label, project: project, title: 'bug') + feature = create(:label, project: project, title: 'feature') + enhancement = create(:label, project: project, title: 'enhancement') issue1 = create(:issue, title: "Bugfix1", project: project) - issue1.labels << project.labels.find_by(title: 'bug') + issue1.labels << bug issue2 = create(:issue, title: "Bugfix2", project: project) - issue2.labels << project.labels.find_by(title: 'bug') - issue2.labels << project.labels.find_by(title: 'enhancement') + issue2.labels << bug + issue2.labels << enhancement issue3 = create(:issue, title: "Feature1", project: project) - issue3.labels << project.labels.find_by(title: 'feature') + issue3.labels << feature project.team << [user, :master] login_as(user) @@ -122,13 +124,9 @@ feature 'Issue filtering by Labels', feature: true do sleep 2 end - it 'should show issue "Bugfix2" or "Feature1" in issues list' do - expect(page).to have_content "Bugfix2" - expect(page).to have_content "Feature1" - end - - it 'should not show "Bugfix1" in issues list' do + it 'should not show "Bugfix1" or "Feature1" in issues list' do expect(page).not_to have_content "Bugfix1" + expect(page).not_to have_content "Feature1" end it 'should show label "enhancement" and "feature" in filtered-labels' do @@ -141,7 +139,7 @@ feature 'Issue filtering by Labels', feature: true do end end - context 'filter by label enhancement or bug in issues list', js: true do + context 'filter by label enhancement and bug in issues list', js: true do before do page.find('.js-label-select').click sleep 0.5 @@ -151,9 +149,8 @@ feature 'Issue filtering by Labels', feature: true do sleep 2 end - it 'should show issue "Bugfix2" or "Bugfix1" in issues list' do + it 'should show issue "Bugfix2" in issues list' do expect(page).to have_content "Bugfix2" - expect(page).to have_content "Bugfix1" end it 'should not show "Feature1"' do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 63a0a47e92..cf9c1cc1fb 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -217,16 +217,26 @@ describe Issue, "Issuable" do let(:example_label) { 'test1' } let(:example_labels) { ['test1', 'test2'] } - it 'finds issue with 1 label' do - setup_labels([example_label]) - - expect(Issue.with_label(example_label).size).to eq(1) + before(:each) do + setup_other_issue end - it 'finds issue with 2 labels' do + it 'finds the correct issue with 1 label' do + setup_labels([example_label]) + + expect(Issue.with_label(example_label)).to eq([issue]) + end + + it 'finds the correct issue with 2 labels' do setup_labels(example_labels) - expect(Issue.with_label(example_labels).to_a.size).to eq(1) + expect(Issue.with_label(example_labels)).to eq([issue]) + end + + it 'finds the correct issue with 1 of 2 labels' do + setup_labels(example_labels) + + expect(Issue.with_label(example_label)).to eq([issue]) end def setup_labels(label_names) @@ -235,5 +245,10 @@ describe Issue, "Issuable" do end issue.labels << labels end + + def setup_other_issue + issue2 = create(:issue) + issue2.labels << create(:label, project: issue2.project, title: 'other_label') + end end end From f7348cd348ad8f4a18d74dd668283a4e236f5790 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Apr 2016 10:41:51 +0200 Subject: [PATCH 068/113] Fix cross-project label ref with invalid project Closes #15168 --- lib/banzai/filter/label_reference_filter.rb | 2 +- .../filter/label_reference_filter_spec.rb | 42 ++++++++++++------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index a2987850d0..db4d60c019 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -20,7 +20,7 @@ module Banzai text.gsub(pattern) do |match| project = project_from_ref($~[:project]) params = label_params($~[:label_id].to_i, $~[:label_name]) - label = project.labels.find_by(params) + label = project.labels.find_by(params) if project if label yield match, label.id, $~[:project], $~ diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 94468abcbb..b0a38e7c25 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -178,27 +178,37 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do end describe 'cross project label references' do - let(:another_project) { create(:empty_project, :public) } - let(:project_name) { another_project.name_with_namespace } - let(:label) { create(:label, project: another_project, color: '#00ff00') } - let(:reference) { label.to_reference(project) } + context 'valid project referenced' do + let(:another_project) { create(:empty_project, :public) } + let(:project_name) { another_project.name_with_namespace } + let(:label) { create(:label, project: another_project, color: '#00ff00') } + let(:reference) { label.to_reference(project) } - let!(:result) { reference_filter("See #{reference}") } + let!(:result) { reference_filter("See #{reference}") } - it 'points to referenced project issues page' do - expect(result.css('a').first.attr('href')) - .to eq urls.namespace_project_issues_url(another_project.namespace, - another_project, - label_name: label.name) + it 'points to referenced project issues page' do + expect(result.css('a').first.attr('href')) + .to eq urls.namespace_project_issues_url(another_project.namespace, + another_project, + label_name: label.name) + end + + it 'has valid color' do + expect(result.css('a span').first.attr('style')) + .to match /background-color: #00ff00/ + end + + it 'contains cross project content' do + expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}" + end end - it 'has valid color' do - expect(result.css('a span').first.attr('style')) - .to match /background-color: #00ff00/ - end + context 'project that does not exist referenced' do + let(:result) { reference_filter('aaa/bbb~ccc') } - it 'contains cross project content' do - expect(result.css('a').first.text).to eq "#{label.name} in #{project_name}" + it 'does not link reference' do + expect(result.to_html).to eq 'aaa/bbb~ccc' + end end end end From dcc45eda8887e9af20e43ec449bff76fd52f8ebf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Apr 2016 10:44:30 +0200 Subject: [PATCH 069/113] Add Changelog entry for cross-project label fix --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d4b8a50926..3e63c2c453 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.8.0 (unreleased) v 8.7.0 (unreleased) - The number of InfluxDB points stored per UDP packet can now be configured + - Fix error when cross-project label reference used with non-existent project - Transactions for /internal/allowed now have an "action" tag set - Method instrumentation now uses Module#prepend instead of aliasing methods - Repository.clean_old_archives is now instrumented From 35d8bc4485f3151dcf9ea5964b9cc18bdfd7d3d4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Apr 2016 12:07:13 +0200 Subject: [PATCH 070/113] Refactor banzai code that finds cross-project labels --- lib/banzai/filter/label_reference_filter.rb | 42 ++++++++++++--------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index db4d60c019..8488a493b5 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -18,9 +18,7 @@ module Banzai def references_in(text, pattern = Label.reference_pattern) text.gsub(pattern) do |match| - project = project_from_ref($~[:project]) - params = label_params($~[:label_id].to_i, $~[:label_name]) - label = project.labels.find_by(params) if project + label = find_label($~[:project], $~[:label_id], $~[:label_name]) if label yield match, label.id, $~[:project], $~ @@ -30,6 +28,29 @@ module Banzai end end + def find_label(project_ref, label_id, label_name) + project = project_from_ref(project_ref) + return unless project + + label_params = label_params(label_id, label_name) + project.labels.find_by(label_params) + end + + # Parameters to pass to `Label.find_by` based on the given arguments + # + # id - Integer ID to pass. If present, returns {id: id} + # name - String name to pass. If `id` is absent, finds by name without + # surrounding quotes. + # + # Returns a Hash. + def label_params(id, name) + if name + { name: name.tr('"', '') } + else + { id: id.to_i } + end + end + def url_for_object(label, project) h = Gitlab::Routing.url_helpers h.namespace_project_issues_url(project.namespace, project, label_name: label.name, @@ -43,21 +64,6 @@ module Banzai LabelsHelper.render_colored_cross_project_label(object) end end - - # Parameters to pass to `Label.find_by` based on the given arguments - # - # id - Integer ID to pass. If present, returns {id: id} - # name - String name to pass. If `id` is absent, finds by name without - # surrounding quotes. - # - # Returns a Hash. - def label_params(id, name) - if name - { name: name.tr('"', '') } - else - { id: id } - end - end end end end From 7f23e0b8c051da34c8587e4096476939a57fee24 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 21 Apr 2016 08:50:15 +0100 Subject: [PATCH 071/113] Fixes text color on labels in sidebar --- app/assets/javascripts/labels_select.js.coffee | 5 +++-- app/controllers/projects/issues_controller.rb | 2 +- app/controllers/projects/merge_requests_controller.rb | 2 +- app/helpers/labels_helper.rb | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee index 021ade73d4..5cb225c9ba 100644 --- a/app/assets/javascripts/labels_select.js.coffee +++ b/app/assets/javascripts/labels_select.js.coffee @@ -34,8 +34,8 @@ class @LabelsSelect if issueUpdateURL labelHTMLTemplate = _.template( '<% _.each(labels, function(label){ %> - issues?label_name=<%= _.escape(label.title) %>"> - + issues?label_name[]=<%= _.escape(label.title) %>"> + <%= _.escape(label.title) %> @@ -157,6 +157,7 @@ class @LabelsSelect data.issueURLSplit = issueURLSplit labelCount = 0 if data.labels.length + console.log data.labels template = labelHTMLTemplate(data) labelCount = data.labels.length else diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e86428147e..346b055615 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -110,7 +110,7 @@ class Projects::IssuesController < Projects::ApplicationController end end format.json do - render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }]) + render json: @issue.to_json(include: [:milestone, labels: { methods: :text_color }, assignee: { methods: :avatar_url }]) end end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 1388ea9d66..d82a4290ed 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -155,7 +155,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.target_project, @merge_request]) end format.json do - render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }]) + render json: @merge_request.to_json(include: [:milestone, labels: { methods: :text_color }, assignee: { methods: :avatar_url }]) end end else diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 3dded7c2f2..c99b137cda 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -37,7 +37,7 @@ module LabelsHelper link = send("namespace_project_#{type.to_s.pluralize}_path", project.namespace, project, - label_name: label.name) + label_name: [label.name]) if block_given? link_to link, &block From 2e0182b27821edc3c61b3aac5e8b214f7d08d8c2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 21 Apr 2016 08:54:24 +0100 Subject: [PATCH 072/113] Fixed issue with author link color on dark diffs --- app/assets/stylesheets/pages/notes.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index aacb12532c..e308f01f91 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -196,6 +196,9 @@ ul.notes { } } + .author_link { + color: $gl-gray; + } } .note-headline-light, From 2d2b73ae4954a96d060fdf21ce8e95ccbdebe68f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 21 Apr 2016 10:30:48 +0200 Subject: [PATCH 073/113] use wait_for_ajax instead of sleeping for 2 days! --- spec/features/issues/filter_by_labels_spec.rb | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 9696af4b00..29c3f9eaad 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' feature 'Issue filtering by Labels', feature: true do + include WaitForAjax + let(:project) { create(:project, :public) } let!(:user) { create(:user)} let!(:label) { create(:label, project: project) } @@ -33,10 +35,10 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label bug', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should show issue "Bugfix1" and "Bugfix2" in issues list' do @@ -61,10 +63,10 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label feature', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should show issue "Feature1" in issues list' do @@ -89,10 +91,10 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label enhancement', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should show issue "Bugfix2" in issues list' do @@ -117,11 +119,11 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label enhancement or feature', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"feature\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should not show "Bugfix1" or "Feature1" in issues list' do @@ -142,11 +144,11 @@ feature 'Issue filtering by Labels', feature: true do context 'filter by label enhancement and bug in issues list', js: true do before do page.find('.js-label-select').click - sleep 0.5 + wait_for_ajax execute_script("$('.dropdown-menu-labels li:contains(\"enhancement\") a').click()") execute_script("$('.dropdown-menu-labels li:contains(\"bug\") a').click()") page.first('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - sleep 2 + wait_for_ajax end it 'should show issue "Bugfix2" in issues list' do From 15d1804d9f6a2c37255150f43c164fec0af8371f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 21 Apr 2016 10:54:03 +0100 Subject: [PATCH 074/113] Updated tests --- spec/helpers/labels_helper_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 39042ff7e9..501f150cfd 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -11,13 +11,13 @@ describe LabelsHelper do end it 'uses the instance variable' do - expect(link_to_label(label)).to match %r{} + expect(link_to_label(label)).to match %r{} end end context 'without @project set' do it "uses the label's project" do - expect(link_to_label(label)).to match %r{.*} + expect(link_to_label(label)).to match %r{.*} end end @@ -25,7 +25,7 @@ describe LabelsHelper do let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') } it 'links to merge requests page' do - expect(link_to_label(label, project: another_project)).to match %r{.*} + expect(link_to_label(label, project: another_project)).to match %r{.*} end end @@ -33,7 +33,7 @@ describe LabelsHelper do ['issue', :issue, 'merge_request', :merge_request].each do |type| context "set to #{type}" do it 'links to correct page' do - expect(link_to_label(label, type: type)).to match %r{.*} + expect(link_to_label(label, type: type)).to match %r{.*} end end end From 67c990be654eafae3bcaa2229e97b38acd98f200 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 21 Apr 2016 13:07:33 +0300 Subject: [PATCH 075/113] Split into sections, port `downgrade_ee_to_ce` doc from EE [ci skip] --- doc/downgrade_ee_to_ce/README.md | 82 +++++++++++++++++++++++++++++ doc/update/README.md | 90 +++++++++++++++++++++++++++++--- 2 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 doc/downgrade_ee_to_ce/README.md diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md new file mode 100644 index 0000000000..3625c4191b --- /dev/null +++ b/doc/downgrade_ee_to_ce/README.md @@ -0,0 +1,82 @@ +# Downgrading from EE to CE + +If you ever decide to downgrade your Enterprise Edition back to the Community +Edition, there are a few steps you need take before installing the CE package +on top of the current EE package, or, if you are in an installation from source, +before you change remotes and fetch the latest CE code. + +## Disable Enterprise-only features + +First thing to do is to disable the following features. + +### Authentication mechanisms + +Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so +you should disable these mechanisms before downgrading and you should provide +alternative authentication methods to your users. + +### Git Annex + +Git Annex is also only available on the Enterprise Edition. This means that if +you have repositories that use Git Annex to store large files, these files will +no longer be easily available via Git. You should consider migrating these +repositories to use Git LFS before downgrading to the Community Edition. + +### Remove Jenkins CI Service entries from the database + +The `JenkinsService` class is only available on the Enterprise Edition codebase, +so if you downgrade to the Community Edition, you'll come across the following +error: + +``` +Completed 500 Internal Server Error in 497ms (ActiveRecord: 32.2ms) + +ActionView::Template::Error (The single-table inheritance mechanism failed to locate the subclass: 'JenkinsService'. This +error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this +column if you didn't intend it to be used for storing the inheritance class or overwrite Service.inheritance_column to +use another column for that information.) +``` + +All services are created automatically for every project you have, so in order +to avoid getting this error, you need to remove all instances of the +`JenkinsService` from your database: + +**Omnibus Installation** + +``` +$ sudo gitlab-rails runner "Service.where(type: 'JenkinsService').delete_all" +``` + +**Source Installation** + +``` +$ bundle exec rails runner "Service.where(type: 'JenkinsService').delete_all" production +``` + +## Downgrade to CE + +After performing the above mentioned steps, you are now ready to downgrade your +GitLab installation to the Community Edition. + +**Omnibus Installation** + +To downgrade an Omnibus installation, it is sufficient to install the Community +Edition package on top of the currently installed one. You can do this manually, +by directly [downloading the package](https://packages.gitlab.com/gitlab/gitlab-ce) +you need, or by adding our CE package repository and following the +[CE installation instructions](https://about.gitlab.com/downloads/). + +**Source Installation** + +To downgrade a source installation, you need to replace the current remote of +your GitLab installation with the Community Edition's remote, fetch the latest +changes, and checkout the latest stable branch: + +``` +$ git remote set-url origin git@gitlab.com:gitlab-org/gitlab-ce.git +$ git fetch --all +$ git checkout 8-x-stable +``` + +Remember to follow the correct [update guides](../update/README.md) to make +sure all dependencies are up to date. diff --git a/doc/update/README.md b/doc/update/README.md index d4197e2cfc..a770633c9b 100644 --- a/doc/update/README.md +++ b/doc/update/README.md @@ -1,21 +1,95 @@ -Depending on the installation method and your GitLab version, there are multiple update guides. Choose one that fits your needs. +# Updating GitLab + +Depending on the installation method and your GitLab version, there are multiple +update guides. + +There are currently 3 official ways to install GitLab: + +- Omnibus packages +- Source installation +- Docker installation + +Based on your installation, choose a section below that fits your needs. + +--- + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Omnibus Packages](#omnibus-packages) +- [Installation from source](#installation-from-source) +- [Installation using Docker](#installation-using-docker) +- [Upgrading between editions](#upgrading-between-editions) + - [Community to Enterprise Edition](#community-to-enterprise-edition) + - [Enterprise to Community Edition](#enterprise-to-community-edition) +- [Miscellaneous](#miscellaneous) + + ## Omnibus Packages -- [Omnibus update guide](http://doc.gitlab.com/omnibus/update/README.html) - contains the steps needed to update a GitLab - [package](https://about.gitlab.com/downloads/). +- The [Omnibus update guide](http://doc.gitlab.com/omnibus/update/README.html) + contains the steps needed to update an Omnibus GitLab package. ## Installation from source -- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab from source. -- [The CE to EE update guides](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status. +- [Upgrading Community Edition from source][source-ce] - The individual + upgrade guides are for those who have installed GitLab CE from source. +- [Upgrading Enterprise Edition from source][source-ee] - The individual + upgrade guides are for those who have installed GitLab EE from source. - [Patch versions](patch_versions.md) guide includes the steps needed for a - patch version, eg. 6.2.0 to 6.2.1. + patch version, eg. 6.2.0 to 6.2.1, and apply to both Community and Enterprise + Editions. + +## Installation using Docker + +GitLab provides official Docker images for both Community and Enterprise +editions. They are based on the Omnibus package and instructions on how to +update them are in [a separate document][omnidocker]. + +## Upgrading between editions + +GitLab comes in two flavors: [Community Edition][ce] which is MIT licensed, +and [Enterprise Edition][ee] which builds on top of the Community Edition and +includes extra features mainly aimed at organizations with more than 100 users. + +Below you can find some guides to help you change editions easily. + +### Community to Enterprise Edition + +>**Note:** +The following guides are for subscribers of the Enterprise Edition only. + +If you wish to upgrade your GitLab installation from Community to Enterprise +Edition, follow the guides below based on the installation method: + +- [Source CE to EE update guides][source-ee] - Find your version, and follow the + `-ce-to-ee.md` guide. The steps are very similar to a version upgrade: stop + the server, get the code, update config files for the new functionality, + install libraries and do migrations, update the init script, start the + application and check its status. +- [Omnibus CE to EE][omni-ce-ee] - Follow this guide to update your Omnibus + GitLab Community Edition to the Enterprise Edition. + +### Enterprise to Community Edition + +If you need to downgrade your Enterprise Edition installation back to Community +Edition, you can follow [this guide][ee-ce] to make the process as smooth as +possible. ## Miscellaneous - [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL. -- [MySQL installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/database_mysql.md) contains additional information about configuring GitLab to work with a MySQL database. +- [MySQL installation guide](../install/database_mysql.md) contains additional + information about configuring GitLab to work with a MySQL database. - [Restoring from backup after a failed upgrade](restore_after_failure.md) + +[omnidocker]: http://doc.gitlab.com/omnibus/docker/README.html +[source-ee]: https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc/update +[source-ce]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update +[ee-ce]: ../downgrade_ee_to_ce/README.md +[ce]: https://about.gitlab.com/features/#community +[ee]: https://about.gitlab.com/features/#enterprise +[omni-ce-ee]: http://doc.gitlab.com/omnibus/update/README.html#from-community-edition-to-enterprise-edition From b09b175def7c66487d4571d90f23f613d868f25c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 21 Apr 2016 13:20:00 +0200 Subject: [PATCH 076/113] refactored specs based on feedback --- spec/features/issues/filter_by_labels_spec.rb | 4 -- spec/models/concerns/issuable_spec.rb | 45 ++++++++----------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/issues/filter_by_labels_spec.rb index 29c3f9eaad..7f65468414 100644 --- a/spec/features/issues/filter_by_labels_spec.rb +++ b/spec/features/issues/filter_by_labels_spec.rb @@ -6,12 +6,8 @@ feature 'Issue filtering by Labels', feature: true do let(:project) { create(:project, :public) } let!(:user) { create(:user)} let!(:label) { create(:label, project: project) } - let(:bug) { create(:label, project: project, title: 'bug') } - let(:feature) { create(:label, project: project, title: 'feature') } - let(:enhancement) { create(:label, project: project, title: 'enhancement') } before do - bug = create(:label, project: project, title: 'bug') feature = create(:label, project: project, title: 'feature') enhancement = create(:label, project: project, title: 'enhancement') diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index cf9c1cc1fb..4a4cd09343 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -214,41 +214,32 @@ describe Issue, "Issuable" do end describe ".with_label" do - let(:example_label) { 'test1' } - let(:example_labels) { ['test1', 'test2'] } + let(:project) { create(:project, :public) } + let(:bug) { create(:label, project: project, title: 'bug') } + let(:feature) { create(:label, project: project, title: 'feature') } + let(:enhancement) { create(:label, project: project, title: 'enhancement') } + let(:issue1) { create(:issue, title: "Bugfix1", project: project) } + let(:issue2) { create(:issue, title: "Bugfix2", project: project) } + let(:issue3) { create(:issue, title: "Feature1", project: project) } before(:each) do - setup_other_issue + issue1.labels << bug + issue1.labels << feature + issue2.labels << bug + issue2.labels << enhancement + issue3.labels << feature end - it 'finds the correct issue with 1 label' do - setup_labels([example_label]) - - expect(Issue.with_label(example_label)).to eq([issue]) + it 'finds the correct issue containing just enhancement label' do + expect(Issue.with_label(enhancement.title)).to match_array([issue2]) end - it 'finds the correct issue with 2 labels' do - setup_labels(example_labels) - - expect(Issue.with_label(example_labels)).to eq([issue]) + it 'finds the correct issues containing the same label' do + expect(Issue.with_label(bug.title)).to match_array([issue1, issue2]) end - it 'finds the correct issue with 1 of 2 labels' do - setup_labels(example_labels) - - expect(Issue.with_label(example_label)).to eq([issue]) - end - - def setup_labels(label_names) - labels = label_names.map do |label| - create(:label, project: issue.project, title: label) - end - issue.labels << labels - end - - def setup_other_issue - issue2 = create(:issue) - issue2.labels << create(:label, project: issue2.project, title: 'other_label') + it 'finds the correct issues containing only both labels' do + expect(Issue.with_label([bug.title, enhancement.title])).to match_array([issue2]) end end end From b3f4e8b218c380cc68213d1e59a6d992cd541099 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 21 Apr 2016 07:27:27 -0400 Subject: [PATCH 077/113] Revert "Merge branch 'sentry-js' into 'master' " This reverts commit 0f309794e78243b1ee16ba6f1451dbb0752956c5, reversing changes made to 1e596fef1c42a1dd925636c48fea01be444dc3ab. --- app/assets/javascripts/application.js.coffee | 1 - app/assets/javascripts/raven_config.js.coffee | 44 - lib/gitlab/gon_helper.rb | 1 - vendor/assets/javascripts/raven.js | 2435 ----------------- 4 files changed, 2481 deletions(-) delete mode 100644 app/assets/javascripts/raven_config.js.coffee delete mode 100644 vendor/assets/javascripts/raven.js diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 642e7429ac..5bac8eef1c 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -55,7 +55,6 @@ #= require_tree . #= require fuzzaldrin-plus #= require cropper -#= require raven window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() diff --git a/app/assets/javascripts/raven_config.js.coffee b/app/assets/javascripts/raven_config.js.coffee deleted file mode 100644 index d031a655ab..0000000000 --- a/app/assets/javascripts/raven_config.js.coffee +++ /dev/null @@ -1,44 +0,0 @@ -@raven = - init: -> - if gon.sentry_dsn? - Raven.config(gon.sentry_dsn, { - includePaths: [/gon.relative_url_root/] - ignoreErrors: [ - # Random plugins/extensions - 'top.GLOBALS', - # See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error. html - 'originalCreateNotification', - 'canvas.contentDocument', - 'MyApp_RemoveAllHighlights', - 'http://tt.epicplay.com', - 'Can\'t find variable: ZiteReader', - 'jigsaw is not defined', - 'ComboSearch is not defined', - 'http://loading.retry.widdit.com/', - 'atomicFindClose', - # ISP "optimizing" proxy - `Cache-Control: no-transform` seems to - # reduce this. (thanks @acdha) - # See http://stackoverflow.com/questions/4113268 - 'bmi_SafeAddOnload', - 'EBCallBackMessageReceived', - # See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx - 'conduitPage' - ], - ignoreUrls: [ - # Chrome extensions - /extensions\//i, - /^chrome:\/\//i, - # Other plugins - /127\.0\.0\.1:4001\/isrunning/i, # Cacaoweb - /webappstoolbarba\.texthelp\.com\//i, - /metrics\.itunes\.apple\.com\.edgesuite\.net\//i - ] - }).install() - - if gon.current_user_id - Raven.setUserContext({ - id: gon.current_user_id - }) - -$ -> - raven.init() diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index eb27d82f11..5ebaad6ca6 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -7,7 +7,6 @@ module Gitlab gon.max_file_size = current_application_settings.max_attachment_size gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - gon.sentry_dsn = ApplicationSetting.current.sentry_dsn if Rails.env.production? if current_user gon.current_user_id = current_user.id diff --git a/vendor/assets/javascripts/raven.js b/vendor/assets/javascripts/raven.js deleted file mode 100644 index d99c6f1c2c..0000000000 --- a/vendor/assets/javascripts/raven.js +++ /dev/null @@ -1,2435 +0,0 @@ -/*! Raven.js 2.3.0 (b09d766) | github.com/getsentry/raven-js */ - -/* - * Includes TraceKit - * https://github.com/getsentry/TraceKit - * - * Copyright 2016 Matt Robenolt and other contributors - * Released under the BSD license - * https://github.com/getsentry/raven-js/blob/master/LICENSE - * - */ - -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Raven = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 300) { - isMinified = true; - break; - } - } - - if (isMinified) { - // The source is minified and we don't know which column. Fuck it. - if (isUndefined(frame.column)) return; - - // If the source is minified and has a frame column - // we take a chunk of the offending line to hopefully shed some light - return [ - [], // no pre_context - context[pivot].substr(frame.column, 50), // grab 50 characters, starting at the offending column - [] // no post_context - ]; - } - - return [ - context.slice(0, pivot), // pre_context - context[pivot], // context_line - context.slice(pivot + 1) // post_context - ]; - }, - - _processException: function(type, message, fileurl, lineno, frames, options) { - var stacktrace, fullMessage; - - if (!!this._globalOptions.ignoreErrors.test && this._globalOptions.ignoreErrors.test(message)) return; - - message += ''; - message = truncate(message, this._globalOptions.maxMessageLength); - - fullMessage = (type ? type + ': ' : '') + message; - fullMessage = truncate(fullMessage, this._globalOptions.maxMessageLength); - - if (frames && frames.length) { - fileurl = frames[0].filename || fileurl; - // Sentry expects frames oldest to newest - // and JS sends them as newest to oldest - frames.reverse(); - stacktrace = {frames: frames}; - } else if (fileurl) { - stacktrace = { - frames: [{ - filename: fileurl, - lineno: lineno, - in_app: true - }] - }; - } - - if (!!this._globalOptions.ignoreUrls.test && this._globalOptions.ignoreUrls.test(fileurl)) return; - if (!!this._globalOptions.whitelistUrls.test && !this._globalOptions.whitelistUrls.test(fileurl)) return; - - var data = objectMerge({ - // sentry.interfaces.Exception - exception: { - values: [{ - type: type, - value: message, - stacktrace: stacktrace - }] - }, - culprit: fileurl, - message: fullMessage - }, options); - - // Fire away! - this._send(data); - }, - - _trimPacket: function(data) { - // For now, we only want to truncate the two different messages - // but this could/should be expanded to just trim everything - var max = this._globalOptions.maxMessageLength; - data.message = truncate(data.message, max); - if (data.exception) { - var exception = data.exception.values[0]; - exception.value = truncate(exception.value, max); - } - - return data; - }, - - _getHttpData: function() { - if (!this._hasDocument || !document.location || !document.location.href) { - return; - } - - var httpData = { - headers: { - 'User-Agent': navigator.userAgent - } - }; - - httpData.url = document.location.href; - - if (document.referrer) { - httpData.headers.Referer = document.referrer; - } - - return httpData; - }, - - - _send: function(data) { - var self = this; - - var globalOptions = this._globalOptions; - - var baseData = { - project: this._globalProject, - logger: globalOptions.logger, - platform: 'javascript' - }, httpData = this._getHttpData(); - - if (httpData) { - baseData.request = httpData; - } - - data = objectMerge(baseData, data); - - // Merge in the tags and extra separately since objectMerge doesn't handle a deep merge - data.tags = objectMerge(objectMerge({}, this._globalContext.tags), data.tags); - data.extra = objectMerge(objectMerge({}, this._globalContext.extra), data.extra); - - // Send along our own collected metadata with extra - data.extra['session:duration'] = now() - this._startTime; - - // If there are no tags/extra, strip the key from the payload alltogther. - if (isEmptyObject(data.tags)) delete data.tags; - - if (this._globalContext.user) { - // sentry.interfaces.User - data.user = this._globalContext.user; - } - - // Include the release if it's defined in globalOptions - if (globalOptions.release) data.release = globalOptions.release; - - // Include server_name if it's defined in globalOptions - if (globalOptions.serverName) data.server_name = globalOptions.serverName; - - if (isFunction(globalOptions.dataCallback)) { - data = globalOptions.dataCallback(data) || data; - } - - // Why?????????? - if (!data || isEmptyObject(data)) { - return; - } - - // Check if the request should be filtered or not - if (isFunction(globalOptions.shouldSendCallback) && !globalOptions.shouldSendCallback(data)) { - return; - } - - // Send along an event_id if not explicitly passed. - // This event_id can be used to reference the error within Sentry itself. - // Set lastEventId after we know the error should actually be sent - this._lastEventId = data.event_id || (data.event_id = uuid4()); - - // Try and clean up the packet before sending by truncating long values - data = this._trimPacket(data); - - this._logDebug('debug', 'Raven about to send:', data); - - if (!this.isSetup()) return; - - var auth = { - sentry_version: '7', - sentry_client: 'raven-js/' + this.VERSION, - sentry_key: this._globalKey - }; - if (this._globalSecret) { - auth.sentry_secret = this._globalSecret; - } - - var url = this._globalEndpoint; - (globalOptions.transport || this._makeRequest).call(this, { - url: url, - auth: auth, - data: data, - options: globalOptions, - onSuccess: function success() { - self._triggerEvent('success', { - data: data, - src: url - }); - }, - onError: function failure() { - self._triggerEvent('failure', { - data: data, - src: url - }); - } - }); - }, - - _makeImageRequest: function(opts) { - // Tack on sentry_data to auth options, which get urlencoded - opts.auth.sentry_data = JSON.stringify(opts.data); - - var img = this._newImage(), - src = opts.url + '?' + urlencode(opts.auth), - crossOrigin = opts.options.crossOrigin; - - if (crossOrigin || crossOrigin === '') { - img.crossOrigin = crossOrigin; - } - img.onload = opts.onSuccess; - img.onerror = img.onabort = opts.onError; - img.src = src; - }, - - _makeXhrRequest: function(opts) { - var request; - - var url = opts.url; - function handler() { - if (request.status === 200) { - if (opts.onSuccess) { - opts.onSuccess(); - } - } else if (opts.onError) { - opts.onError(); - } - } - - request = new XMLHttpRequest(); - if ('withCredentials' in request) { - request.onreadystatechange = function () { - if (request.readyState !== 4) { - return; - } - handler(); - }; - } else { - request = new XDomainRequest(); - // xdomainrequest cannot go http -> https (or vice versa), - // so always use protocol relative - url = url.replace(/^https?:/, ''); - - // onreadystatechange not supported by XDomainRequest - request.onload = handler; - } - - // NOTE: auth is intentionally sent as part of query string (NOT as custom - // HTTP header) so as to avoid preflight CORS requests - request.open('POST', url + '?' + urlencode(opts.auth)); - request.send(JSON.stringify(opts.data)); - }, - - _makeRequest: function(opts) { - var hasCORS = - 'withCredentials' in new XMLHttpRequest() || - typeof XDomainRequest !== 'undefined'; - - return (hasCORS ? this._makeXhrRequest : this._makeImageRequest)(opts); - }, - - // Note: this is shitty, but I can't figure out how to get - // sinon to stub document.createElement without breaking everything - // so this wrapper is just so I can stub it for tests. - _newImage: function() { - return document.createElement('img'); - }, - - _logDebug: function(level) { - if (this._originalConsoleMethods[level] && this.debug) { - // In IE<10 console methods do not have their own 'apply' method - Function.prototype.apply.call( - this._originalConsoleMethods[level], - this._originalConsole, - [].slice.call(arguments, 1) - ); - } - }, - - _mergeContext: function(key, context) { - if (isUndefined(context)) { - delete this._globalContext[key]; - } else { - this._globalContext[key] = objectMerge(this._globalContext[key] || {}, context); - } - } -}; - -// Deprecations -Raven.prototype.setUser = Raven.prototype.setUserContext; -Raven.prototype.setReleaseContext = Raven.prototype.setRelease; - -module.exports = Raven; - -},{"1":1,"4":4,"5":5}],3:[function(_dereq_,module,exports){ -/** - * Enforces a single instance of the Raven client, and the - * main entry point for Raven. If you are a consumer of the - * Raven library, you SHOULD load this file (vs raven.js). - **/ - -'use strict'; - -var RavenConstructor = _dereq_(2); - -var _Raven = window.Raven; - -var Raven = new RavenConstructor(); - -/* - * Allow multiple versions of Raven to be installed. - * Strip Raven from the global context and returns the instance. - * - * @return {Raven} - */ -Raven.noConflict = function () { - window.Raven = _Raven; - return Raven; -}; - -Raven.afterLoad(); - -module.exports = Raven; - -},{"2":2}],4:[function(_dereq_,module,exports){ -'use strict'; - -var objectPrototype = Object.prototype; - -function isUndefined(what) { - return what === void 0; -} - -function isFunction(what) { - return typeof what === 'function'; -} - -function isString(what) { - return objectPrototype.toString.call(what) === '[object String]'; -} - -function isObject(what) { - return typeof what === 'object' && what !== null; -} - -function isEmptyObject(what) { - for (var _ in what) return false; // eslint-disable-line guard-for-in, no-unused-vars - return true; -} - -// Sorta yanked from https://github.com/joyent/node/blob/aa3b4b4/lib/util.js#L560 -// with some tiny modifications -function isError(what) { - var toString = objectPrototype.toString.call(what); - return isObject(what) && - toString === '[object Error]' || - toString === '[object Exception]' || // Firefox NS_ERROR_FAILURE Exceptions - what instanceof Error; -} - -function each(obj, callback) { - var i, j; - - if (isUndefined(obj.length)) { - for (i in obj) { - if (hasKey(obj, i)) { - callback.call(null, i, obj[i]); - } - } - } else { - j = obj.length; - if (j) { - for (i = 0; i < j; i++) { - callback.call(null, i, obj[i]); - } - } - } -} - -function objectMerge(obj1, obj2) { - if (!obj2) { - return obj1; - } - each(obj2, function(key, value){ - obj1[key] = value; - }); - return obj1; -} - -function truncate(str, max) { - return !max || str.length <= max ? str : str.substr(0, max) + '\u2026'; -} - -/** - * hasKey, a better form of hasOwnProperty - * Example: hasKey(MainHostObject, property) === true/false - * - * @param {Object} host object to check property - * @param {string} key to check - */ -function hasKey(object, key) { - return objectPrototype.hasOwnProperty.call(object, key); -} - -function joinRegExp(patterns) { - // Combine an array of regular expressions and strings into one large regexp - // Be mad. - var sources = [], - i = 0, len = patterns.length, - pattern; - - for (; i < len; i++) { - pattern = patterns[i]; - if (isString(pattern)) { - // If it's a string, we need to escape it - // Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions - sources.push(pattern.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1')); - } else if (pattern && pattern.source) { - // If it's a regexp already, we want to extract the source - sources.push(pattern.source); - } - // Intentionally skip other cases - } - return new RegExp(sources.join('|'), 'i'); -} - -function urlencode(o) { - var pairs = []; - each(o, function(key, value) { - pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); - }); - return pairs.join('&'); -} - -function uuid4() { - var crypto = window.crypto || window.msCrypto; - - if (!isUndefined(crypto) && crypto.getRandomValues) { - // Use window.crypto API if available - var arr = new Uint16Array(8); - crypto.getRandomValues(arr); - - // set 4 in byte 7 - arr[3] = arr[3] & 0xFFF | 0x4000; - // set 2 most significant bits of byte 9 to '10' - arr[4] = arr[4] & 0x3FFF | 0x8000; - - var pad = function(num) { - var v = num.toString(16); - while (v.length < 4) { - v = '0' + v; - } - return v; - }; - - return pad(arr[0]) + pad(arr[1]) + pad(arr[2]) + pad(arr[3]) + pad(arr[4]) + - pad(arr[5]) + pad(arr[6]) + pad(arr[7]); - } else { - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#2117523 - return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = Math.random()*16|0, - v = c === 'x' ? r : r&0x3|0x8; - return v.toString(16); - }); - } -} - -module.exports = { - isUndefined: isUndefined, - isFunction: isFunction, - isString: isString, - isObject: isObject, - isEmptyObject: isEmptyObject, - isError: isError, - each: each, - objectMerge: objectMerge, - truncate: truncate, - hasKey: hasKey, - joinRegExp: joinRegExp, - urlencode: urlencode, - uuid4: uuid4 -}; - -},{}],5:[function(_dereq_,module,exports){ -'use strict'; - -var utils = _dereq_(4); - -var hasKey = utils.hasKey; -var isString = utils.isString; -var isUndefined = utils.isUndefined; - -/* - TraceKit - Cross brower stack traces - github.com/occ/TraceKit - MIT license -*/ - -var TraceKit = { - remoteFetching: false, - collectWindowErrors: true, - // 3 lines before, the offending line, 3 lines after - linesOfContext: 7, - debug: false -}; - -// global reference to slice -var _slice = [].slice; -var UNKNOWN_FUNCTION = '?'; - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types -var ERROR_TYPES_RE = /^(?:Uncaught )?((?:Eval|Internal|Range|Reference|Syntax|Type|URI)Error)\: ?(.*)$/; - -function getLocationHref() { - if (typeof document === 'undefined') - return ''; - - return document.location.href; -} - -/** - * TraceKit.report: cross-browser processing of unhandled exceptions - * - * Syntax: - * TraceKit.report.subscribe(function(stackInfo) { ... }) - * TraceKit.report.unsubscribe(function(stackInfo) { ... }) - * TraceKit.report(exception) - * try { ...code... } catch(ex) { TraceKit.report(ex); } - * - * Supports: - * - Firefox: full stack trace with line numbers, plus column number - * on top frame; column number is not guaranteed - * - Opera: full stack trace with line and column numbers - * - Chrome: full stack trace with line and column numbers - * - Safari: line and column number for the top frame only; some frames - * may be missing, and column number is not guaranteed - * - IE: line and column number for the top frame only; some frames - * may be missing, and column number is not guaranteed - * - * In theory, TraceKit should work on all of the following versions: - * - IE5.5+ (only 8.0 tested) - * - Firefox 0.9+ (only 3.5+ tested) - * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require - * Exceptions Have Stacktrace to be enabled in opera:config) - * - Safari 3+ (only 4+ tested) - * - Chrome 1+ (only 5+ tested) - * - Konqueror 3.5+ (untested) - * - * Requires TraceKit.computeStackTrace. - * - * Tries to catch all unhandled exceptions and report them to the - * subscribed handlers. Please note that TraceKit.report will rethrow the - * exception. This is REQUIRED in order to get a useful stack trace in IE. - * If the exception does not reach the top of the browser, you will only - * get a stack trace from the point where TraceKit.report was called. - * - * Handlers receive a stackInfo object as described in the - * TraceKit.computeStackTrace docs. - */ -TraceKit.report = (function reportModuleWrapper() { - var handlers = [], - lastArgs = null, - lastException = null, - lastExceptionStack = null; - - /** - * Add a crash handler. - * @param {Function} handler - */ - function subscribe(handler) { - installGlobalHandler(); - handlers.push(handler); - } - - /** - * Remove a crash handler. - * @param {Function} handler - */ - function unsubscribe(handler) { - for (var i = handlers.length - 1; i >= 0; --i) { - if (handlers[i] === handler) { - handlers.splice(i, 1); - } - } - } - - /** - * Remove all crash handlers. - */ - function unsubscribeAll() { - uninstallGlobalHandler(); - handlers = []; - } - - /** - * Dispatch stack information to all handlers. - * @param {Object.} stack - */ - function notifyHandlers(stack, isWindowError) { - var exception = null; - if (isWindowError && !TraceKit.collectWindowErrors) { - return; - } - for (var i in handlers) { - if (hasKey(handlers, i)) { - try { - handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2))); - } catch (inner) { - exception = inner; - } - } - } - - if (exception) { - throw exception; - } - } - - var _oldOnerrorHandler, _onErrorHandlerInstalled; - - /** - * Ensures all global unhandled exceptions are recorded. - * Supported by Gecko and IE. - * @param {string} message Error message. - * @param {string} url URL of script that generated the exception. - * @param {(number|string)} lineNo The line number at which the error - * occurred. - * @param {?(number|string)} colNo The column number at which the error - * occurred. - * @param {?Error} ex The actual Error object. - */ - function traceKitWindowOnError(message, url, lineNo, colNo, ex) { - var stack = null; - - if (lastExceptionStack) { - TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message); - processLastException(); - } else if (ex) { - // New chrome and blink send along a real error object - // Let's just report that like a normal error. - // See: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror - stack = TraceKit.computeStackTrace(ex); - notifyHandlers(stack, true); - } else { - var location = { - 'url': url, - 'line': lineNo, - 'column': colNo - }; - location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line); - location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line); - - var name = undefined; - var msg = message; // must be new var or will modify original `arguments` - var groups; - if (isString(message)) { - var groups = message.match(ERROR_TYPES_RE); - if (groups) { - name = groups[1]; - msg = groups[2]; - } - } - - stack = { - 'name': name, - 'message': msg, - 'url': getLocationHref(), - 'stack': [location] - }; - notifyHandlers(stack, true); - } - - if (_oldOnerrorHandler) { - return _oldOnerrorHandler.apply(this, arguments); - } - - return false; - } - - function installGlobalHandler () - { - if (_onErrorHandlerInstalled) { - return; - } - _oldOnerrorHandler = window.onerror; - window.onerror = traceKitWindowOnError; - _onErrorHandlerInstalled = true; - } - - function uninstallGlobalHandler () - { - if (!_onErrorHandlerInstalled) { - return; - } - window.onerror = _oldOnerrorHandler; - _onErrorHandlerInstalled = false; - _oldOnerrorHandler = undefined; - } - - function processLastException() { - var _lastExceptionStack = lastExceptionStack, - _lastArgs = lastArgs; - lastArgs = null; - lastExceptionStack = null; - lastException = null; - notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs)); - } - - /** - * Reports an unhandled Error to TraceKit. - * @param {Error} ex - * @param {?boolean} rethrow If false, do not re-throw the exception. - * Only used for window.onerror to not cause an infinite loop of - * rethrowing. - */ - function report(ex, rethrow) { - var args = _slice.call(arguments, 1); - if (lastExceptionStack) { - if (lastException === ex) { - return; // already caught by an inner catch block, ignore - } else { - processLastException(); - } - } - - var stack = TraceKit.computeStackTrace(ex); - lastExceptionStack = stack; - lastException = ex; - lastArgs = args; - - // If the stack trace is incomplete, wait for 2 seconds for - // slow slow IE to see if onerror occurs or not before reporting - // this exception; otherwise, we will end up with an incomplete - // stack trace - window.setTimeout(function () { - if (lastException === ex) { - processLastException(); - } - }, (stack.incomplete ? 2000 : 0)); - - if (rethrow !== false) { - throw ex; // re-throw to propagate to the top level (and cause window.onerror) - } - } - - report.subscribe = subscribe; - report.unsubscribe = unsubscribe; - report.uninstall = unsubscribeAll; - return report; -}()); - -/** - * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript - * - * Syntax: - * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below) - * Returns: - * s.name - exception name - * s.message - exception message - * s.stack[i].url - JavaScript or HTML file URL - * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work) - * s.stack[i].args - arguments passed to the function, if known - * s.stack[i].line - line number, if known - * s.stack[i].column - column number, if known - * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line# - * - * Supports: - * - Firefox: full stack trace with line numbers and unreliable column - * number on top frame - * - Opera 10: full stack trace with line and column numbers - * - Opera 9-: full stack trace with line numbers - * - Chrome: full stack trace with line and column numbers - * - Safari: line and column number for the topmost stacktrace element - * only - * - IE: no line numbers whatsoever - * - * Tries to guess names of anonymous functions by looking for assignments - * in the source code. In IE and Safari, we have to guess source file names - * by searching for function bodies inside all page scripts. This will not - * work for scripts that are loaded cross-domain. - * Here be dragons: some function names may be guessed incorrectly, and - * duplicate functions may be mismatched. - * - * TraceKit.computeStackTrace should only be used for tracing purposes. - * Logging of unhandled exceptions should be done with TraceKit.report, - * which builds on top of TraceKit.computeStackTrace and provides better - * IE support by utilizing the window.onerror event to retrieve information - * about the top of the stack. - * - * Note: In IE and Safari, no stack trace is recorded on the Error object, - * so computeStackTrace instead walks its *own* chain of callers. - * This means that: - * * in Safari, some methods may be missing from the stack trace; - * * in IE, the topmost function in the stack trace will always be the - * caller of computeStackTrace. - * - * This is okay for tracing (because you are likely to be calling - * computeStackTrace from the function you want to be the topmost element - * of the stack trace anyway), but not okay for logging unhandled - * exceptions (because your catch block will likely be far away from the - * inner function that actually caused the exception). - * - */ -TraceKit.computeStackTrace = (function computeStackTraceWrapper() { - var sourceCache = {}; - - /** - * Attempts to retrieve source code via XMLHttpRequest, which is used - * to look up anonymous function names. - * @param {string} url URL of source code. - * @return {string} Source contents. - */ - function loadSource(url) { - if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on. - return ''; - } - try { - var getXHR = function() { - try { - return new window.XMLHttpRequest(); - } catch (e) { - // explicitly bubble up the exception if not found - return new window.ActiveXObject('Microsoft.XMLHTTP'); - } - }; - - var request = getXHR(); - request.open('GET', url, false); - request.send(''); - return request.responseText; - } catch (e) { - return ''; - } - } - - /** - * Retrieves source code from the source code cache. - * @param {string} url URL of source code. - * @return {Array.} Source contents. - */ - function getSource(url) { - if (!isString(url)) return []; - if (!hasKey(sourceCache, url)) { - // URL needs to be able to fetched within the acceptable domain. Otherwise, - // cross-domain errors will be triggered. - var source = ''; - var domain = ''; - try { domain = document.domain; } catch (e) {} - if (url.indexOf(domain) !== -1) { - source = loadSource(url); - } - sourceCache[url] = source ? source.split('\n') : []; - } - - return sourceCache[url]; - } - - /** - * Tries to use an externally loaded copy of source code to determine - * the name of a function by looking at the name of the variable it was - * assigned to, if any. - * @param {string} url URL of source code. - * @param {(string|number)} lineNo Line number in source code. - * @return {string} The function name, if discoverable. - */ - function guessFunctionName(url, lineNo) { - var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/, - reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/, - line = '', - maxLines = 10, - source = getSource(url), - m; - - if (!source.length) { - return UNKNOWN_FUNCTION; - } - - // Walk backwards from the first line in the function until we find the line which - // matches the pattern above, which is the function definition - for (var i = 0; i < maxLines; ++i) { - line = source[lineNo - i] + line; - - if (!isUndefined(line)) { - if ((m = reGuessFunction.exec(line))) { - return m[1]; - } else if ((m = reFunctionArgNames.exec(line))) { - return m[1]; - } - } - } - - return UNKNOWN_FUNCTION; - } - - /** - * Retrieves the surrounding lines from where an exception occurred. - * @param {string} url URL of source code. - * @param {(string|number)} line Line number in source code to centre - * around for context. - * @return {?Array.} Lines of source code. - */ - function gatherContext(url, line) { - var source = getSource(url); - - if (!source.length) { - return null; - } - - var context = [], - // linesBefore & linesAfter are inclusive with the offending line. - // if linesOfContext is even, there will be one extra line - // *before* the offending line. - linesBefore = Math.floor(TraceKit.linesOfContext / 2), - // Add one extra line if linesOfContext is odd - linesAfter = linesBefore + (TraceKit.linesOfContext % 2), - start = Math.max(0, line - linesBefore - 1), - end = Math.min(source.length, line + linesAfter - 1); - - line -= 1; // convert to 0-based index - - for (var i = start; i < end; ++i) { - if (!isUndefined(source[i])) { - context.push(source[i]); - } - } - - return context.length > 0 ? context : null; - } - - /** - * Escapes special characters, except for whitespace, in a string to be - * used inside a regular expression as a string literal. - * @param {string} text The string. - * @return {string} The escaped string literal. - */ - function escapeRegExp(text) { - return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&'); - } - - /** - * Escapes special characters in a string to be used inside a regular - * expression as a string literal. Also ensures that HTML entities will - * be matched the same as their literal friends. - * @param {string} body The string. - * @return {string} The escaped string. - */ - function escapeCodeAsRegExpForMatchingInsideHTML(body) { - return escapeRegExp(body).replace('<', '(?:<|<)').replace('>', '(?:>|>)').replace('&', '(?:&|&)').replace('"', '(?:"|")').replace(/\s+/g, '\\s+'); - } - - /** - * Determines where a code fragment occurs in the source code. - * @param {RegExp} re The function definition. - * @param {Array.} urls A list of URLs to search. - * @return {?Object.} An object containing - * the url, line, and column number of the defined function. - */ - function findSourceInUrls(re, urls) { - var source, m; - for (var i = 0, j = urls.length; i < j; ++i) { - // console.log('searching', urls[i]); - if ((source = getSource(urls[i])).length) { - source = source.join('\n'); - if ((m = re.exec(source))) { - // console.log('Found function in ' + urls[i]); - - return { - 'url': urls[i], - 'line': source.substring(0, m.index).split('\n').length, - 'column': m.index - source.lastIndexOf('\n', m.index) - 1 - }; - } - } - } - - // console.log('no match'); - - return null; - } - - /** - * Determines at which column a code fragment occurs on a line of the - * source code. - * @param {string} fragment The code fragment. - * @param {string} url The URL to search. - * @param {(string|number)} line The line number to examine. - * @return {?number} The column number. - */ - function findSourceInLine(fragment, url, line) { - var source = getSource(url), - re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'), - m; - - line -= 1; - - if (source && source.length > line && (m = re.exec(source[line]))) { - return m.index; - } - - return null; - } - - /** - * Determines where a function was defined within the source code. - * @param {(Function|string)} func A function reference or serialized - * function definition. - * @return {?Object.} An object containing - * the url, line, and column number of the defined function. - */ - function findSourceByFunctionBody(func) { - if (typeof document === 'undefined') - return; - - var urls = [window.location.href], - scripts = document.getElementsByTagName('script'), - body, - code = '' + func, - codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, - eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/, - re, - parts, - result; - - for (var i = 0; i < scripts.length; ++i) { - var script = scripts[i]; - if (script.src) { - urls.push(script.src); - } - } - - if (!(parts = codeRE.exec(code))) { - re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+')); - } - - // not sure if this is really necessary, but I don’t have a test - // corpus large enough to confirm that and it was in the original. - else { - var name = parts[1] ? '\\s+' + parts[1] : '', - args = parts[2].split(',').join('\\s*,\\s*'); - - body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+'); - re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}'); - } - - // look for a normal function definition - if ((result = findSourceInUrls(re, urls))) { - return result; - } - - // look for an old-school event handler function - if ((parts = eventRE.exec(code))) { - var event = parts[1]; - body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]); - - // look for a function defined in HTML as an onXXX handler - re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i'); - - if ((result = findSourceInUrls(re, urls[0]))) { - return result; - } - - // look for ??? - re = new RegExp(body); - - if ((result = findSourceInUrls(re, urls))) { - return result; - } - } - - return null; - } - - // Contents of Exception in various browsers. - // - // SAFARI: - // ex.message = Can't find variable: qq - // ex.line = 59 - // ex.sourceId = 580238192 - // ex.sourceURL = http://... - // ex.expressionBeginOffset = 96 - // ex.expressionCaretOffset = 98 - // ex.expressionEndOffset = 98 - // ex.name = ReferenceError - // - // FIREFOX: - // ex.message = qq is not defined - // ex.fileName = http://... - // ex.lineNumber = 59 - // ex.columnNumber = 69 - // ex.stack = ...stack trace... (see the example below) - // ex.name = ReferenceError - // - // CHROME: - // ex.message = qq is not defined - // ex.name = ReferenceError - // ex.type = not_defined - // ex.arguments = ['aa'] - // ex.stack = ...stack trace... - // - // INTERNET EXPLORER: - // ex.message = ... - // ex.name = ReferenceError - // - // OPERA: - // ex.message = ...message... (see the example below) - // ex.name = ReferenceError - // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message) - // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace' - - /** - * Computes stack trace information from the stack property. - * Chrome and Gecko use this property. - * @param {Error} ex - * @return {?Object.} Stack trace information. - */ - function computeStackTraceFromStackProp(ex) { - if (isUndefined(ex.stack) || !ex.stack) return; - - var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, - gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|\[native).*?)(?::(\d+))?(?::(\d+))?\s*$/i, - winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|https?|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i, - lines = ex.stack.split('\n'), - stack = [], - parts, - element, - reference = /^(.*) is undefined$/.exec(ex.message); - - for (var i = 0, j = lines.length; i < j; ++i) { - if ((parts = chrome.exec(lines[i]))) { - var isNative = parts[2] && parts[2].indexOf('native') !== -1; - element = { - 'url': !isNative ? parts[2] : null, - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': isNative ? [parts[2]] : [], - 'line': parts[3] ? +parts[3] : null, - 'column': parts[4] ? +parts[4] : null - }; - } else if ( parts = winjs.exec(lines[i]) ) { - element = { - 'url': parts[2], - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': [], - 'line': +parts[3], - 'column': parts[4] ? +parts[4] : null - }; - } else if ((parts = gecko.exec(lines[i]))) { - element = { - 'url': parts[3], - 'func': parts[1] || UNKNOWN_FUNCTION, - 'args': parts[2] ? parts[2].split(',') : [], - 'line': parts[4] ? +parts[4] : null, - 'column': parts[5] ? +parts[5] : null - }; - } else { - continue; - } - - if (!element.func && element.line) { - element.func = guessFunctionName(element.url, element.line); - } - - if (element.line) { - element.context = gatherContext(element.url, element.line); - } - - stack.push(element); - } - - if (!stack.length) { - return null; - } - - if (stack[0].line && !stack[0].column && reference) { - stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line); - } else if (!stack[0].column && !isUndefined(ex.columnNumber)) { - // FireFox uses this awesome columnNumber property for its top frame - // Also note, Firefox's column number is 0-based and everything else expects 1-based, - // so adding 1 - stack[0].column = ex.columnNumber + 1; - } - - return { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref(), - 'stack': stack - }; - } - - /** - * Computes stack trace information from the stacktrace property. - * Opera 10 uses this property. - * @param {Error} ex - * @return {?Object.} Stack trace information. - */ - function computeStackTraceFromStacktraceProp(ex) { - // Access and store the stacktrace property before doing ANYTHING - // else to it because Opera is not very good at providing it - // reliably in other circumstances. - var stacktrace = ex.stacktrace; - if (isUndefined(ex.stacktrace) || !ex.stacktrace) return; - - var opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i, - opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i, - lines = stacktrace.split('\n'), - stack = [], - parts; - - for (var line = 0; line < lines.length; line += 2) { - var element = null; - if ((parts = opera10Regex.exec(lines[line]))) { - element = { - 'url': parts[2], - 'line': +parts[1], - 'column': null, - 'func': parts[3], - 'args':[] - }; - } else if ((parts = opera11Regex.exec(lines[line]))) { - element = { - 'url': parts[6], - 'line': +parts[1], - 'column': +parts[2], - 'func': parts[3] || parts[4], - 'args': parts[5] ? parts[5].split(',') : [] - }; - } - - if (element) { - if (!element.func && element.line) { - element.func = guessFunctionName(element.url, element.line); - } - if (element.line) { - try { - element.context = gatherContext(element.url, element.line); - } catch (exc) {} - } - - if (!element.context) { - element.context = [lines[line + 1]]; - } - - stack.push(element); - } - } - - if (!stack.length) { - return null; - } - - return { - 'name': ex.name, - 'message': ex.message, - 'url': getLocationHref(), - 'stack': stack - }; - } - - /** - * NOT TESTED. - * Computes stack trace information from an error message that includes - * the stack trace. - * Opera 9 and earlier use this method if the option to show stack - * traces is turned on in opera:config. - * @param {Error} ex - * @return {?Object.} Stack information. - */ - function computeStackTraceFromOperaMultiLineMessage(ex) { - // Opera includes a stack trace into the exception message. An example is: - // - // Statement on line 3: Undefined variable: undefinedFunc - // Backtrace: - // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz - // undefinedFunc(a); - // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy - // zzz(x, y, z); - // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx - // yyy(a, a, a); - // Line 1 of function script - // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); } - // ... - - var lines = ex.message.split('\n'); - if (lines.length < 4) { - return null; - } - - var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i, - lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i, - lineRE3 = /^\s*Line (\d+) of function script\s*$/i, - stack = [], - scripts = document.getElementsByTagName('script'), - inlineScriptBlocks = [], - parts; - - for (var s in scripts) { - if (hasKey(scripts, s) && !scripts[s].src) { - inlineScriptBlocks.push(scripts[s]); - } - } - - for (var line = 2; line < lines.length; line += 2) { - var item = null; - if ((parts = lineRE1.exec(lines[line]))) { - item = { - 'url': parts[2], - 'func': parts[3], - 'args': [], - 'line': +parts[1], - 'column': null - }; - } else if ((parts = lineRE2.exec(lines[line]))) { - item = { - 'url': parts[3], - 'func': parts[4], - 'args': [], - 'line': +parts[1], - 'column': null // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number. - }; - var relativeLine = (+parts[1]); // relative to the start of the