From ae5f124e96504f20500df282d34dce7b0627c212 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 28 Jul 2016 01:16:31 +0800 Subject: WIP, initial work to implement pipeline hooks: I might be squashing this commit in the future. --- app/controllers/projects/hooks_controller.rb | 1 + app/models/ci/pipeline.rb | 6 ++ app/models/hooks/project_hook.rb | 1 + app/models/hooks/web_hook.rb | 1 + app/models/service.rb | 1 + ...60728081025_add_pipeline_events_to_web_hooks.rb | 16 ++++++ ...160728103734_add_pipeline_events_to_services.rb | 16 ++++++ lib/gitlab/data_builder/pipeline_data_builder.rb | 65 ++++++++++++++++++++++ 8 files changed, 107 insertions(+) create mode 100644 db/migrate/20160728081025_add_pipeline_events_to_web_hooks.rb create mode 100644 db/migrate/20160728103734_add_pipeline_events_to_services.rb create mode 100644 lib/gitlab/data_builder/pipeline_data_builder.rb diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index a60027ff477..b5624046387 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -56,6 +56,7 @@ class Projects::HooksController < Projects::ApplicationController def hook_params params.require(:hook).permit( :build_events, + :pipeline_events, :enable_ssl_verification, :issues_events, :merge_requests_events, diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index bce6a992af6..bf6d872dece 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -213,6 +213,12 @@ module Ci ] end + def execute_hooks + pipeline_data = Gitlab::DataBuilder::PipelineDataBuilder.build(self) + project.execute_hooks(pipeline_data.dup, :pipeline_hooks) + project.execute_services(pipeline_data.dup, :pipeline_hooks) + end + private def build_builds_for_stages(stages, user, status, trigger_request) diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index ba42a8eeb70..836a75b0608 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -5,5 +5,6 @@ class ProjectHook < WebHook scope :note_hooks, -> { where(note_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) } scope :build_hooks, -> { where(build_events: true) } + scope :pipeline_hooks, -> { where(pipeline_events: true) } scope :wiki_page_hooks, -> { where(wiki_page_events: true) } end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 8b87b6c3d64..f365dee3141 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -8,6 +8,7 @@ class WebHook < ActiveRecord::Base default_value_for :merge_requests_events, false default_value_for :tag_push_events, false default_value_for :build_events, false + default_value_for :pipeline_events, false default_value_for :enable_ssl_verification, true scope :push_hooks, -> { where(push_events: true) } diff --git a/app/models/service.rb b/app/models/service.rb index 40cd9b861f0..4a63abb2724 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -36,6 +36,7 @@ class Service < ActiveRecord::Base scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } scope :note_hooks, -> { where(note_events: true, active: true) } scope :build_hooks, -> { where(build_events: true, active: true) } + scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) } scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) } scope :external_issue_trackers, -> { issue_trackers.active.without_defaults } diff --git a/db/migrate/20160728081025_add_pipeline_events_to_web_hooks.rb b/db/migrate/20160728081025_add_pipeline_events_to_web_hooks.rb new file mode 100644 index 00000000000..b800e6d7283 --- /dev/null +++ b/db/migrate/20160728081025_add_pipeline_events_to_web_hooks.rb @@ -0,0 +1,16 @@ +class AddPipelineEventsToWebHooks < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:web_hooks, :pipeline_events, :boolean, + default: false, allow_null: false) + end + + def down + remove_column(:web_hooks, :pipeline_events) + end +end diff --git a/db/migrate/20160728103734_add_pipeline_events_to_services.rb b/db/migrate/20160728103734_add_pipeline_events_to_services.rb new file mode 100644 index 00000000000..bcd24fe1566 --- /dev/null +++ b/db/migrate/20160728103734_add_pipeline_events_to_services.rb @@ -0,0 +1,16 @@ +class AddPipelineEventsToServices < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:services, :pipeline_events, :boolean, + default: false, allow_null: false) + end + + def down + remove_column(:services, :pipeline_events) + end +end diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb new file mode 100644 index 00000000000..a9c1bc7acee --- /dev/null +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -0,0 +1,65 @@ +module Gitlab + module DataBuilder + module PipelineDataBuilder + module_function + + def build(pipeline) + { + object_kind: 'pipeline', + user: pipeline.user.hook_attrs, + project: pipeline.project.hook_attrs(backward: false), + commit: pipeline.commit.hook_attrs, + object_attributes: hook_attrs(pipeline), + builds: pipeline.builds.map(&method(:build_hook_attrs)) + } + end + + def hook_attrs(pipeline) + first_pending_build = pipeline.builds.first_pending + config_processor = pipeline.config_processor + + { + id: pipeline.id, + ref: pipeline.ref, + tag: pipeline.tag, + sha: pipeline.sha, + before_sha: pipeline.before_sha, + status: pipeline.status, + stage: first_pending_build.try(:stage), + stages: config_processor.try(:stages), + created_at: pipeline.created_at, + finished_at: pipeline.finished_at + } + end + + def build_hook_attrs(build) + { + id: build.id, + stage: build.stage, + name: build.name, + status: build.status, + created_at: build.created_at, + started_at: build.started_at, + finished_at: build.finished_at, + when: build.when, + manual: build.manual?, + user: build.user.hook_attrs, + runner: runner_hook_attrs(build.runner), + artifacts_file: { + filename: build.artifacts_file.filename, + size: build.artifacts_size + } + } + end + + def runner_hook_attrs(runner) + { + id: runner.id, + description: runner.description, + active: runner.active?, + is_shared: runner.is_shared? + } + end + end + end +end -- cgit v1.2.3 From 751be82ee189cad83c59516881edfac51f1ce379 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 28 Jul 2016 19:33:40 +0800 Subject: Add views for pipeline events --- app/views/projects/hooks/_project_hook.html.haml | 2 +- app/views/shared/web_hooks/_form.html.haml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/hooks/_project_hook.html.haml index 8151187d499..3fcf1692e09 100644 --- a/app/views/projects/hooks/_project_hook.html.haml +++ b/app/views/projects/hooks/_project_hook.html.haml @@ -3,7 +3,7 @@ .col-md-8.col-lg-7 %strong.light-header= hook.url %div - - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events wiki_page_events).each do |trigger| + - %w(push_events tag_push_events issues_events note_events merge_requests_events build_events pipeline_events wiki_page_events).each do |trigger| - if hook.send(trigger) %span.label.label-gray.deploy-project-label= trigger.titleize .col-md-4.col-lg-5.text-right-lg.prepend-top-5 diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index 2585ed9360b..106161d6515 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -65,6 +65,13 @@ %strong Build events %p.light This url will be triggered when the build status changes + %li + = f.check_box :pipeline_events, class: 'pull-left' + .prepend-left-20 + = f.label :pipeline_events, class: 'list-label' do + %strong Pipeline events + %p.light + This url will be triggered when the pipeline status changes %li = f.check_box :wiki_page_events, class: 'pull-left' .prepend-left-20 -- cgit v1.2.3 From 6d82e3f15f8de3bae316cdc2f204b3cdea88b55f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 28 Jul 2016 19:43:36 +0800 Subject: We're not using original hash anyway, we could use it --- app/models/ci/pipeline.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index bf6d872dece..7d23456cdac 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -215,7 +215,7 @@ module Ci def execute_hooks pipeline_data = Gitlab::DataBuilder::PipelineDataBuilder.build(self) - project.execute_hooks(pipeline_data.dup, :pipeline_hooks) + project.execute_hooks(pipeline_data, :pipeline_hooks) project.execute_services(pipeline_data.dup, :pipeline_hooks) end -- cgit v1.2.3 From 755301a2ad1d307c727e3c2642c9e234a7ddb05d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 29 Jul 2016 00:37:35 +0800 Subject: Also touch the pipeline so we could just hook into update_state --- app/models/ci/pipeline.rb | 13 +++++++------ app/services/ci/create_pipeline_service.rb | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 7d23456cdac..cd6ead4ded2 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -213,12 +213,6 @@ module Ci ] end - def execute_hooks - pipeline_data = Gitlab::DataBuilder::PipelineDataBuilder.build(self) - project.execute_hooks(pipeline_data, :pipeline_hooks) - project.execute_services(pipeline_data.dup, :pipeline_hooks) - end - private def build_builds_for_stages(stages, user, status, trigger_request) @@ -244,6 +238,13 @@ module Ci self.finished_at = statuses.finished_at self.duration = statuses.latest.duration save + execute_hooks + end + + def execute_hooks + pipeline_data = Gitlab::DataBuilder::PipelineDataBuilder.build(self) + project.execute_hooks(pipeline_data, :pipeline_hooks) + project.execute_services(pipeline_data.dup, :pipeline_hooks) end def keep_around_commits diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index be91bf0db85..7a8b0683acb 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -27,6 +27,7 @@ module Ci end pipeline.save! + pipeline.touch unless pipeline.create_builds(current_user) pipeline.errors.add(:base, 'No builds for this pipeline.') -- cgit v1.2.3 From b831ef716b088fa5f0892ececd00d4a383267979 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 29 Jul 2016 02:05:50 +0800 Subject: They could be nil --- lib/gitlab/data_builder/pipeline_data_builder.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb index a9c1bc7acee..fed9bd92ba4 100644 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -6,9 +6,9 @@ module Gitlab def build(pipeline) { object_kind: 'pipeline', - user: pipeline.user.hook_attrs, + user: pipeline.user.try(:hook_attrs), project: pipeline.project.hook_attrs(backward: false), - commit: pipeline.commit.hook_attrs, + commit: pipeline.commit.try(:hook_attrs), object_attributes: hook_attrs(pipeline), builds: pipeline.builds.map(&method(:build_hook_attrs)) } @@ -43,8 +43,8 @@ module Gitlab finished_at: build.finished_at, when: build.when, manual: build.manual?, - user: build.user.hook_attrs, - runner: runner_hook_attrs(build.runner), + user: build.user.try(:hook_attrs), + runner: build.runner && runner_hook_attrs(build.runner), artifacts_file: { filename: build.artifacts_file.filename, size: build.artifacts_size -- cgit v1.2.3 From d41e83e91a2b2c90ab51feda61e105818e1713be Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 29 Jul 2016 17:06:09 +0800 Subject: Don't execute hooks if ci was supposed to be skipped And we should preserve the return value --- app/models/ci/pipeline.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index cd6ead4ded2..4e6ccf48c68 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -237,8 +237,9 @@ module Ci self.started_at = statuses.started_at self.finished_at = statuses.finished_at self.duration = statuses.latest.duration - save - execute_hooks + saved = save + execute_hooks if saved && !skip_ci? + saved end def execute_hooks -- cgit v1.2.3 From 5fee7ec1a0016fb8789abf2f3b7e2faaf24518f5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 29 Jul 2016 17:58:26 +0800 Subject: It was never used --- app/models/project_services/slack_service/build_message.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_services/slack_service/build_message.rb b/app/models/project_services/slack_service/build_message.rb index 69c21b3fc38..0fca4267bad 100644 --- a/app/models/project_services/slack_service/build_message.rb +++ b/app/models/project_services/slack_service/build_message.rb @@ -9,7 +9,7 @@ class SlackService attr_reader :user_name attr_reader :duration - def initialize(params, commit = true) + def initialize(params) @sha = params[:sha] @ref_type = params[:tag] ? 'tag' : 'branch' @ref = params[:ref] @@ -36,7 +36,7 @@ class SlackService def message "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}" - end + end def format(string) Slack::Notifier::LinkFormatter.format(string) -- cgit v1.2.3 From 4a431a266f8773c54a0f47292d4b9470a7d2acd3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 29 Jul 2016 20:20:04 +0800 Subject: Fix that tricky side-effect issue in the test --- app/models/ci/build.rb | 2 +- spec/models/build_spec.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index aac78d75f57..e5523c42a3b 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -460,7 +460,7 @@ module Ci def build_attributes_from_config return {} unless pipeline.config_processor - + pipeline.config_processor.build_attributes(name) end end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index dc88697199b..47c489e6af1 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -275,7 +275,8 @@ describe Ci::Build, models: true do context 'when yaml_variables are undefined' do before do - build.yaml_variables = nil + build.update(yaml_variables: nil) + build.reload # reload pipeline so that it resets config_processor end context 'use from gitlab-ci.yml' do @@ -854,7 +855,8 @@ describe Ci::Build, models: true do context 'if is undefined' do before do - build.when = nil + build.update(when: nil) + build.reload # reload pipeline so that it resets config_processor end context 'use from gitlab-ci.yml' do -- cgit v1.2.3 From d27095a36a5d8c83182771d7548a13fcc9188e3c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 30 Jul 2016 00:59:54 +0800 Subject: Add pipeline_events to Slack service: Also add Service#event_names so that we don't have to hard code all event names in ServiceParams. Note that I don't know why we want to call plural on issue_event and merge_request_event so that we still need to hard code them for issues_event and merge_requests_event. See app/helpers/services_helper.rb for those special rules. --- app/controllers/concerns/service_params.rb | 15 ++--- app/models/project_services/slack_service.rb | 20 +++++- .../slack_service/pipeline_message.rb | 77 ++++++++++++++++++++++ app/models/service.rb | 4 ++ lib/gitlab/data_builder/pipeline_data_builder.rb | 3 +- .../slack_service/pipeline_message_spec.rb | 58 ++++++++++++++++ 6 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 app/models/project_services/slack_service/pipeline_message.rb create mode 100644 spec/models/project_services/slack_service/pipeline_message_spec.rb diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index 471d15af913..58877c5ad5d 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -7,11 +7,12 @@ module ServiceParams :build_key, :server, :teamcity_url, :drone_url, :build_type, :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, :colorize_messages, :channels, - :push_events, :issues_events, :merge_requests_events, :tag_push_events, - :note_events, :build_events, :wiki_page_events, - :notify_only_broken_builds, :add_pusher, - :send_from_committer_email, :disable_diffs, :external_wiki_url, - :notify, :color, + # See app/helpers/services_helper.rb + # for why we need issues_events and merge_requests_events. + :issues_events, :merge_requests_events, + :notify_only_broken_builds, :notify_only_broken_pipelines, + :add_pusher, :send_from_committer_email, :disable_diffs, + :external_wiki_url, :notify, :color, :server_host, :server_port, :default_irc_uri, :enable_ssl_verification, :jira_issue_transition_id] @@ -19,9 +20,7 @@ module ServiceParams FILTER_BLANK_PARAMS = [:password] def service_params - dynamic_params = [] - dynamic_params.concat(@service.event_channel_names) - + dynamic_params = @service.event_channel_names + @service.event_names service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params) if service_params[:service].is_a?(Hash) diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index abbc780dc1a..6584646e998 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -1,6 +1,6 @@ class SlackService < Service prop_accessor :webhook, :username, :channel - boolean_accessor :notify_only_broken_builds + boolean_accessor :notify_only_broken_builds, :notify_only_broken_pipelines validates :webhook, presence: true, url: true, if: :activated? def initialize_properties @@ -10,6 +10,7 @@ class SlackService < Service if properties.nil? self.properties = {} self.notify_only_broken_builds = true + self.notify_only_broken_pipelines = true end end @@ -38,13 +39,14 @@ class SlackService < Service { type: 'text', name: 'username', placeholder: 'username' }, { type: 'text', name: 'channel', placeholder: "#general" }, { type: 'checkbox', name: 'notify_only_broken_builds' }, + { type: 'checkbox', name: 'notify_only_broken_pipelines' }, ] default_fields + build_event_channels end def supported_events - %w(push issue merge_request note tag_push build wiki_page) + %w(push issue merge_request note tag_push build pipeline wiki_page) end def execute(data) @@ -74,6 +76,8 @@ class SlackService < Service NoteMessage.new(data) when "build" BuildMessage.new(data) if should_build_be_notified?(data) + when "pipeline" + PipelineMessage.new(data) if should_pipeline_be_notified?(data) when "wiki_page" WikiPageMessage.new(data) end @@ -142,6 +146,17 @@ class SlackService < Service false end end + + def should_pipeline_be_notified?(data) + case data[:object_attributes][:status] + when 'success' + !notify_only_broken_builds? + when 'failed' + true + else + false + end + end end require "slack_service/issue_message" @@ -149,4 +164,5 @@ require "slack_service/push_message" require "slack_service/merge_message" require "slack_service/note_message" require "slack_service/build_message" +require "slack_service/pipeline_message" require "slack_service/wiki_page_message" diff --git a/app/models/project_services/slack_service/pipeline_message.rb b/app/models/project_services/slack_service/pipeline_message.rb new file mode 100644 index 00000000000..3d12af3fa7e --- /dev/null +++ b/app/models/project_services/slack_service/pipeline_message.rb @@ -0,0 +1,77 @@ +class SlackService + class PipelineMessage < BaseMessage + attr_reader :sha, :ref_type, :ref, :status, :project_name, :project_url, + :user_name, :duration, :pipeline_id + + def initialize(data) + @sha = data[:sha] + @ref_type = data[:tag] ? 'tag' : 'branch' + @ref = data[:ref] + @status = data[:status] + @project_name = data[:project][:path_with_namespace] + @project_url = data[:project][:web_url] + @user_name = data[:commit] && data[:commit][:author_name] + @duration = data[:object_attributes][:duration] + @pipeline_id = data[:object_attributes][:id] + end + + def pretext + '' + end + + def fallback + format(message) + end + + def attachments + [{ text: format(message), color: attachment_color }] + end + + private + + def message + "#{project_link}: Pipeline #{pipeline_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}" + end + + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def humanized_status + case status + when 'success' + 'passed' + else + status + end + end + + def attachment_color + if status == 'success' + 'good' + else + 'danger' + end + end + + def branch_url + "#{project_url}/commits/#{ref}" + end + + def branch_link + "[#{ref}](#{branch_url})" + end + + def project_link + "[#{project_name}](#{project_url})" + end + + def pipeline_url + "#{project_url}/pipelines/#{pipeline_id}" + end + + def pipeline_link + "[#{Commit.truncate_sha(sha)}](#{pipeline_url})" + end + end +end diff --git a/app/models/service.rb b/app/models/service.rb index 4a63abb2724..e4cd44f542a 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -87,6 +87,10 @@ class Service < ActiveRecord::Base [] end + def event_names + supported_events.map { |event| "#{event}_events" } + end + def event_field(event) nil end diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb index fed9bd92ba4..46f8a1857b4 100644 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -28,7 +28,8 @@ module Gitlab stage: first_pending_build.try(:stage), stages: config_processor.try(:stages), created_at: pipeline.created_at, - finished_at: pipeline.finished_at + finished_at: pipeline.finished_at, + duration: pipeline.duration } end diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb new file mode 100644 index 00000000000..2960a200e9d --- /dev/null +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe SlackService::PipelineMessage do + subject { SlackService::PipelineMessage.new(args) } + + let(:args) do + { + sha: '97de212e80737a608d939f648d959671fb0a0142', + tag: false, + ref: 'develop', + status: status, + project: { path_with_namespace: 'project_name', + web_url: 'somewhere.com' }, + commit: { author_name: 'hacker' }, + object_attributes: { duration: duration, + id: 123 } + } + end + + context 'succeeded' do + let(:status) { 'success' } + let(:color) { 'good' } + let(:duration) { 10 } + + it 'returns a message with information about succeeded build' do + message = ': Pipeline of branch by hacker passed in 10 seconds' + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + end + + context 'failed' do + let(:status) { 'failed' } + let(:color) { 'danger' } + let(:duration) { 10 } + + it 'returns a message with information about failed build' do + message = ': Pipeline of branch by hacker failed in 10 seconds' + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + end + + describe '#seconds_name' do + let(:status) { 'failed' } + let(:color) { 'danger' } + let(:duration) { 1 } + + it 'returns seconds as singular when there is only one' do + message = ': Pipeline of branch by hacker failed in 1 second' + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) + end + end +end -- cgit v1.2.3 From 3caf7bffdc9cffb0ff60af25cb97b19f672cdb63 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 30 Jul 2016 01:18:35 +0800 Subject: Reduce complexity by extracting it to a method --- app/models/project_services/slack_service.rb | 37 +++++++++++++++------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 6584646e998..4e14a979833 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -64,23 +64,7 @@ class SlackService < Service # 'close' action. Ignore update events for now to prevent duplicate # messages from arriving. - message = \ - case object_kind - when "push", "tag_push" - PushMessage.new(data) - when "issue" - IssueMessage.new(data) unless is_update?(data) - when "merge_request" - MergeMessage.new(data) unless is_update?(data) - when "note" - NoteMessage.new(data) - when "build" - BuildMessage.new(data) if should_build_be_notified?(data) - when "pipeline" - PipelineMessage.new(data) if should_pipeline_be_notified?(data) - when "wiki_page" - WikiPageMessage.new(data) - end + message = get_message(object_kind, data) opt = {} @@ -109,6 +93,25 @@ class SlackService < Service private + def get_message(object_kind, data) + case object_kind + when "push", "tag_push" + PushMessage.new(data) + when "issue" + IssueMessage.new(data) unless is_update?(data) + when "merge_request" + MergeMessage.new(data) unless is_update?(data) + when "note" + NoteMessage.new(data) + when "build" + BuildMessage.new(data) if should_build_be_notified?(data) + when "pipeline" + PipelineMessage.new(data) if should_pipeline_be_notified?(data) + when "wiki_page" + WikiPageMessage.new(data) + end + end + def get_channel_field(event) field_name = event_channel_name(event) self.public_send(field_name) -- cgit v1.2.3 From cc4b0a55bd4a2ef5992c8b5ea0102b0dc40cf616 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 30 Jul 2016 01:24:03 +0800 Subject: Fix test for checking everything for Slack service --- features/steps/admin/settings.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb index 03f87df7a60..11dc7f580f0 100644 --- a/features/steps/admin/settings.rb +++ b/features/steps/admin/settings.rb @@ -33,6 +33,7 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps page.check('Issue') page.check('Merge request') page.check('Build') + page.check('Pipeline') click_on 'Save' end -- cgit v1.2.3 From 7209ea4fd7aac3517472851d107567e89eccb7e8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 1 Aug 2016 16:06:57 +0800 Subject: Slack pipeline events test and fix some issues along the way --- app/models/project_services/slack_service.rb | 16 ++++-- .../slack_service/pipeline_message.rb | 14 +++-- spec/models/project_services/slack_service_spec.rb | 66 ++++++++++++++++++++-- 3 files changed, 80 insertions(+), 16 deletions(-) diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 4e14a979833..a46ff475750 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -66,16 +66,20 @@ class SlackService < Service message = get_message(object_kind, data) - opt = {} + if message + opt = {} - event_channel = get_channel_field(object_kind) || channel + event_channel = get_channel_field(object_kind) || channel - opt[:channel] = event_channel if event_channel - opt[:username] = username if username + opt[:channel] = event_channel if event_channel + opt[:username] = username if username - if message notifier = Slack::Notifier.new(webhook, opt) notifier.ping(message.pretext, attachments: message.attachments, fallback: message.fallback) + + true + else + false end end @@ -153,7 +157,7 @@ class SlackService < Service def should_pipeline_be_notified?(data) case data[:object_attributes][:status] when 'success' - !notify_only_broken_builds? + !notify_only_broken_pipelines? when 'failed' true else diff --git a/app/models/project_services/slack_service/pipeline_message.rb b/app/models/project_services/slack_service/pipeline_message.rb index 3d12af3fa7e..f06b3562965 100644 --- a/app/models/project_services/slack_service/pipeline_message.rb +++ b/app/models/project_services/slack_service/pipeline_message.rb @@ -4,15 +4,17 @@ class SlackService :user_name, :duration, :pipeline_id def initialize(data) - @sha = data[:sha] - @ref_type = data[:tag] ? 'tag' : 'branch' - @ref = data[:ref] - @status = data[:status] + pipeline_attributes = data[:object_attributes] + @sha = pipeline_attributes[:sha] + @ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch' + @ref = pipeline_attributes[:ref] + @status = pipeline_attributes[:status] + @duration = pipeline_attributes[:duration] + @pipeline_id = pipeline_attributes[:id] + @project_name = data[:project][:path_with_namespace] @project_url = data[:project][:web_url] @user_name = data[:commit] && data[:commit][:author_name] - @duration = data[:object_attributes][:duration] - @pipeline_id = data[:object_attributes][:id] end def pretext diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index df511b1bc4c..9fc097f2ce0 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -21,6 +21,9 @@ require 'spec_helper' describe SlackService, models: true do + let(:slack) { SlackService.new } + let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } + describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } @@ -42,11 +45,9 @@ describe SlackService, models: true do end describe "Execute" do - let(:slack) { SlackService.new } let(:user) { create(:user) } let(:project) { create(:project) } let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } - let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } let(:username) { 'slack_username' } let(:channel) { 'slack_channel' } @@ -210,10 +211,8 @@ describe SlackService, models: true do end describe "Note events" do - let(:slack) { SlackService.new } let(:user) { create(:user) } let(:project) { create(:project, creator_id: user.id) } - let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } before do allow(slack).to receive_messages( @@ -283,4 +282,63 @@ describe SlackService, models: true do end end end + + describe 'Pipeline events' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:pipeline) do + create(:ci_pipeline, + project: project, status: status, + sha: project.commit.sha, ref: project.default_branch) + end + let(:status) { raise NotImplementedError } + + before do + allow(slack).to receive_messages( + project: project, + service_hook: true, + webhook: webhook_url + ) + end + + shared_examples 'call Slack API' do + before do + WebMock.stub_request(:post, webhook_url) + end + + it 'calls Slack API for pipeline events' do + data = Gitlab::DataBuilder::PipelineDataBuilder.build(pipeline) + slack.execute(data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + end + + context 'with failed pipeline' do + let(:status) { 'failed' } + + it_behaves_like 'call Slack API' + end + + context 'with succeeded pipeline' do + let(:status) { 'success' } + + context 'with default to notify_only_broken_pipelines' do + it 'does not call Slack API for pipeline events' do + data = Gitlab::DataBuilder::PipelineDataBuilder.build(pipeline) + result = slack.execute(data) + + expect(result).to be_falsy + end + end + + context 'with setting notify_only_broken_pipelines to false' do + before do + slack.notify_only_broken_pipelines = false + end + + it_behaves_like 'call Slack API' + end + end + end end -- cgit v1.2.3 From 3b943fbb9603dd5babe7f9348aef79a52ac10c45 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 1 Aug 2016 17:09:26 +0800 Subject: Fix the test due to the format changed --- .../slack_service/pipeline_message_spec.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb index 2960a200e9d..a292defee66 100644 --- a/spec/models/project_services/slack_service/pipeline_message_spec.rb +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -5,15 +5,17 @@ describe SlackService::PipelineMessage do let(:args) do { - sha: '97de212e80737a608d939f648d959671fb0a0142', - tag: false, - ref: 'develop', - status: status, + object_attributes: { + id: 123, + sha: '97de212e80737a608d939f648d959671fb0a0142', + tag: false, + ref: 'develop', + status: status, + duration: duration + }, project: { path_with_namespace: 'project_name', web_url: 'somewhere.com' }, - commit: { author_name: 'hacker' }, - object_attributes: { duration: duration, - id: 123 } + commit: { author_name: 'hacker' } } end -- cgit v1.2.3 From ef0613716c3e2a3cbfedb9b6652e501dafd6804b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 1 Aug 2016 18:02:53 +0800 Subject: Add test for PipelineDataBuilder --- lib/gitlab/data_builder/pipeline_data_builder.rb | 2 +- .../data_builder/pipeline_data_builder_spec.rb | 32 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb index 46f8a1857b4..13417ba09eb 100644 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ b/lib/gitlab/data_builder/pipeline_data_builder.rb @@ -6,10 +6,10 @@ module Gitlab def build(pipeline) { object_kind: 'pipeline', + object_attributes: hook_attrs(pipeline), user: pipeline.user.try(:hook_attrs), project: pipeline.project.hook_attrs(backward: false), commit: pipeline.commit.try(:hook_attrs), - object_attributes: hook_attrs(pipeline), builds: pipeline.builds.map(&method(:build_hook_attrs)) } end diff --git a/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb b/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb new file mode 100644 index 00000000000..24d39b318c0 --- /dev/null +++ b/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Gitlab::DataBuilder::PipelineDataBuilder do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:pipeline) do + create(:ci_pipeline, + project: project, status: 'success', + sha: project.commit.sha, ref: project.default_branch) + end + let!(:build) { create(:ci_build, pipeline: pipeline) } + + describe '.build' do + let(:data) { Gitlab::DataBuilder::PipelineDataBuilder.build(pipeline) } + let(:attributes) { data[:object_attributes] } + let(:build_data) { data[:builds].first } + let(:project_data) { data[:project] } + + it { expect(attributes).to be_a(Hash) } + it { expect(attributes[:ref]).to eq(pipeline.ref) } + it { expect(attributes[:sha]).to eq(pipeline.sha) } + it { expect(attributes[:tag]).to eq(pipeline.tag) } + it { expect(attributes[:id]).to eq(pipeline.id) } + it { expect(attributes[:status]).to eq(pipeline.status) } + + it { expect(build_data).to be_a(Hash) } + it { expect(build_data[:id]).to eq(build.id) } + it { expect(build_data[:status]).to eq(build.status) } + + it { expect(project_data).to eq(project.hook_attrs(backward: false)) } + end +end -- cgit v1.2.3 From db0cdee6d59f86fe196e4a10c2d1195a45b25cce Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 1 Aug 2016 18:03:05 +0800 Subject: Expose pipeline_events in the API --- lib/api/entities.rb | 6 ++++-- lib/api/project_hooks.rb | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e76e7304674..06e94d953fe 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -48,7 +48,8 @@ module API class ProjectHook < Hook expose :project_id, :push_events - expose :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events + expose :issues_events, :merge_requests_events, :tag_push_events + expose :note_events, :build_events, :pipeline_events expose :enable_ssl_verification end @@ -340,7 +341,8 @@ module API class ProjectService < Grape::Entity expose :id, :title, :created_at, :updated_at, :active - expose :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events, :build_events + expose :push_events, :issues_events, :merge_requests_events + expose :tag_push_events, :note_events, :build_events, :pipeline_events # Expose serialized properties expose :properties do |service, options| field_names = service.fields. diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 6bb70bc8bc3..3f63cd678e8 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -45,6 +45,7 @@ module API :tag_push_events, :note_events, :build_events, + :pipeline_events, :enable_ssl_verification ] @hook = user_project.hooks.new(attrs) @@ -78,6 +79,7 @@ module API :tag_push_events, :note_events, :build_events, + :pipeline_events, :enable_ssl_verification ] -- cgit v1.2.3 From 016d4f6b03b5afab34ef93b205ecd78dc8697497 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 1 Aug 2016 18:11:59 +0800 Subject: Add API tests for pipeline_events --- spec/requests/api/project_hooks_spec.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index fd1fffa6223..504deed81f9 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -8,8 +8,9 @@ describe API::API, 'ProjectHooks', api: true do let!(:hook) do create(:project_hook, project: project, url: "http://example.com", - push_events: true, merge_requests_events: true, tag_push_events: true, - issues_events: true, note_events: true, build_events: true, + push_events: true, merge_requests_events: true, + tag_push_events: true, issues_events: true, note_events: true, + build_events: true, pipeline_events: true, enable_ssl_verification: true) end @@ -33,6 +34,7 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response.first['tag_push_events']).to eq(true) expect(json_response.first['note_events']).to eq(true) expect(json_response.first['build_events']).to eq(true) + expect(json_response.first['pipeline_events']).to eq(true) expect(json_response.first['enable_ssl_verification']).to eq(true) end end @@ -91,6 +93,7 @@ describe API::API, 'ProjectHooks', api: true do expect(json_response['tag_push_events']).to eq(false) expect(json_response['note_events']).to eq(false) expect(json_response['build_events']).to eq(false) + expect(json_response['pipeline_events']).to eq(false) expect(json_response['enable_ssl_verification']).to eq(true) end -- cgit v1.2.3 From f9075eb04083030b70b6b58b6c4042d7d74c701c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 1 Aug 2016 19:16:59 +0800 Subject: Add test for running hooks for pipeline after touched --- spec/models/ci/pipeline_spec.rb | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 0d4c86955ce..aa05fc78f94 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -513,7 +513,7 @@ describe Ci::Pipeline, models: true do create :ci_build, :success, pipeline: pipeline, name: 'rspec' create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop' end - + it 'returns true' do is_expected.to be_truthy end @@ -524,7 +524,7 @@ describe Ci::Pipeline, models: true do create :ci_build, :success, pipeline: pipeline, name: 'rspec' create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop' end - + it 'returns false' do is_expected.to be_falsey end @@ -542,4 +542,33 @@ describe Ci::Pipeline, models: true do end end end + + describe '#execute_hooks' do + let!(:hook) do + create(:project_hook, project: project, pipeline_events: enabled) + end + let(:enabled) { raise NotImplementedError } + + before do + WebMock.stub_request(:post, hook.url) + pipeline.touch + ProjectWebHookWorker.drain + end + + context 'with pipeline hooks enabled' do + let(:enabled) { true } + + it 'executes pipeline_hook after touched' do + expect(WebMock).to have_requested(:post, hook.url).once + end + end + + context 'with pipeline hooks disabled' do + let(:enabled) { false } + + it 'did not execute pipeline_hook after touched' do + expect(WebMock).not_to have_requested(:post, hook.url) + end + end + end end -- cgit v1.2.3 From 6fb25bef8e55958c6b75f9c699645969d1b1a8f7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 1 Aug 2016 19:23:40 +0800 Subject: Add a CHANGELOG entry for pipeline events --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d555d23860d..3b1c83fb841 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ v 8.11.0 (unreleased) - Fix renaming repository when name contains invalid chararacters under project settings - Nokogiri's various parsing methods are now instrumented - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 + - Add pipeline events and a corresponding Slack integration !5525 - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 -- cgit v1.2.3 From 4384e53ec56fa361014fc8e49a84608536596949 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 2 Aug 2016 19:08:40 +0800 Subject: Update CHANGELOG due to the introduction of !5620 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 65283f93ed7..85396cb4e42 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,7 +21,7 @@ v 8.11.0 (unreleased) - Optimize checking if a user has read access to a list of issues !5370 - Nokogiri's various parsing methods are now instrumented - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 - - Add pipeline events and a corresponding Slack integration !5525 + - Add pipeline events to Slack integration !5525 - Include old revision in merge request update hooks (Ben Boeckel) - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) -- cgit v1.2.3 From e0b1b68c3aecbb4b335d99e5493ff0da6d77c78d Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Fri, 19 Aug 2016 12:51:49 +0000 Subject: Include region in S3 configuration --- doc/administration/container_registry.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index 28c4c7c86ca..5eb5626cef5 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -406,7 +406,8 @@ To configure the storage driver in Omnibus: 's3' => { 'accesskey' => 's3-access-key', 'secretkey' => 's3-secret-key-for-access-key', - 'bucket' => 'your-s3-bucket' + 'bucket' => 'your-s3-bucket', + 'region' => 'your-s3-region' } } ``` -- cgit v1.2.3 From cd12d759582c45a1a41fe5733cc3358698916e07 Mon Sep 17 00:00:00 2001 From: Benjamin Schwarze Date: Fri, 19 Aug 2016 14:05:26 +0000 Subject: docs: fix typo, it should refer to `domain_blacklist_enabled` setting --- doc/api/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/settings.md b/doc/api/settings.md index a76dad0ebd4..aaa2c99642b 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -67,7 +67,7 @@ PUT /application/settings | `default_snippet_visibility` | integer | no | What visibility level new snippets receive. Can take `0` _(Private)_, `1` _(Internal)_ and `2` _(Public)_ as a parameter. Default is `0`.| | `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is null, meaning there is no restriction. | | `domain_blacklist_enabled` | boolean | no | Enable/disable the `domain_blacklist` | -| `domain_blacklist` | array of strings | yes (if `domain_whitelist_enabled` is `true` | People trying to sign-up with emails from this domain will not be allowed to do so. | +| `domain_blacklist` | array of strings | yes (if `domain_blacklist_enabled` is `true`) | People trying to sign-up with emails from this domain will not be allowed to do so. | | `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider | | `after_sign_out_path` | string | no | Where to redirect users after logout | | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes | -- cgit v1.2.3 From cb767d87c6835cbba631fd4eb68d0b0b905d0e23 Mon Sep 17 00:00:00 2001 From: Geoff Webster Date: Fri, 19 Aug 2016 18:09:38 +0000 Subject: Document IAM Profile AWS S3 configuration key. --- doc/raketasks/backup_restore.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 835af5443a3..68d946cfc63 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -79,6 +79,9 @@ gitlab_rails['backup_upload_connection'] = { 'region' => 'eu-west-1', 'aws_access_key_id' => 'AKIAKIAKI', 'aws_secret_access_key' => 'secret123' + # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty + # ie. 'aws_access_key_id' => '', + # 'use_iam_profile' => 'true' } gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket' ``` @@ -95,12 +98,16 @@ For installations from source: region: eu-west-1 aws_access_key_id: AKIAKIAKI aws_secret_access_key: 'secret123' + # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty + # ie. aws_access_key_id: '' + # use_iam_profile: 'true' # The remote 'directory' to store your backups. For S3, this would be the bucket name. remote_directory: 'my.s3.bucket' # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional # encryption: 'AES256' ``` + If you are uploading your backups to S3 you will probably want to create a new IAM user with restricted access rights. To give the upload user access only for uploading backups create the following IAM profile, replacing `my.s3.bucket` -- cgit v1.2.3 From 86b8d3d0f719a499d1c91878726e37d290be1147 Mon Sep 17 00:00:00 2001 From: Geoff Webster Date: Fri, 19 Aug 2016 20:04:48 +0000 Subject: Remove whitespace. --- doc/raketasks/backup_restore.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 68d946cfc63..3f4056dc440 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -107,7 +107,6 @@ For installations from source: # encryption: 'AES256' ``` - If you are uploading your backups to S3 you will probably want to create a new IAM user with restricted access rights. To give the upload user access only for uploading backups create the following IAM profile, replacing `my.s3.bucket` -- cgit v1.2.3 From 1a6385ba28cd8837f225fd29758fd5893ed13768 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 16 Aug 2016 06:24:24 -0700 Subject: Enable rspec documentation formatter --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index be5614520a5..073f94e09fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -82,7 +82,7 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH} - - knapsack rspec + - knapsack rspec "--format documentation" artifacts: expire_in: 31d paths: -- cgit v1.2.3 From 0611866f106a1d0b798ae3d1398f430fc6ec668f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 20 Aug 2016 09:46:29 -0700 Subject: Add color to rspec output --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 073f94e09fe..a5db5ba80d1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -82,7 +82,7 @@ update-knapsack: - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_GENERATE_REPORT=true - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH} - - knapsack rspec "--format documentation" + - knapsack rspec "--color --format documentation" artifacts: expire_in: 31d paths: -- cgit v1.2.3 From 59b84b6ef5cdf2904840dfa583ddd5642a231a21 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 21:07:55 +0800 Subject: Fix CHANGELOG --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c4d2f79b1d9..ce2fc5b194f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.12.0 (unreleased) - Change merge_error column from string to text type - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Added tests for diff notes + - Add pipeline events to Slack integration !5525 v 8.11.1 (unreleased) - Fix file links on project page when default view is Files !5933 @@ -95,8 +96,6 @@ v 8.11.0 - Fix syntax highlighting in file editor - Support slash commands in issue and merge request descriptions as well as comments. !5021 - Nokogiri's various parsing methods are now instrumented - - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 - - Add pipeline events to Slack integration !5525 - Add archived badge to project list !5798 - Add simple identifier to public SSH keys (muteor) - Admin page now references docs instead of a specific file !5600 (AnAverageHuman) -- cgit v1.2.3 From 386bffb8ca2ea471432e0a57e27044f871610845 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 21:08:01 +0800 Subject: Fix caption --- app/views/shared/web_hooks/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index f99fbe56277..8189e0b645d 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -78,7 +78,7 @@ = f.label :pipeline_events, class: 'list-label' do %strong Pipeline events %p.light - This url will be triggered when the pipeline status changes + This URL will be triggered when the pipeline status changes %li = f.check_box :wiki_page_events, class: 'pull-left' .prepend-left-20 -- cgit v1.2.3 From c6f09f4d88b0fcd11d8b8c2b4fdd48d0c7e3cd61 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 21:08:42 +0800 Subject: Fix spacing --- spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb b/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb index 24d39b318c0..fb5d07bb563 100644 --- a/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb +++ b/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb @@ -3,12 +3,13 @@ require 'spec_helper' describe Gitlab::DataBuilder::PipelineDataBuilder do let(:user) { create(:user) } let(:project) { create(:project) } + let!(:build) { create(:ci_build, pipeline: pipeline) } + let(:pipeline) do create(:ci_pipeline, project: project, status: 'success', sha: project.commit.sha, ref: project.default_branch) end - let!(:build) { create(:ci_build, pipeline: pipeline) } describe '.build' do let(:data) { Gitlab::DataBuilder::PipelineDataBuilder.build(pipeline) } -- cgit v1.2.3 From a79cd2a275893635d9ee719495fbb33aa7649497 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 21:09:53 +0800 Subject: No longer needed --- spec/models/build_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index f63183f5df1..ee2c3d04984 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -275,8 +275,7 @@ describe Ci::Build, models: true do context 'when yaml_variables are undefined' do before do - build.update(yaml_variables: nil) - build.reload # reload pipeline so that it resets config_processor + build.yaml_variables = nil end context 'use from gitlab-ci.yml' do @@ -902,8 +901,7 @@ describe Ci::Build, models: true do context 'when `when` is undefined' do before do - build.update(when: nil) - build.reload # reload pipeline so that it resets config_processor + build.when = nil end context 'use from gitlab-ci.yml' do -- cgit v1.2.3 From 4323d24abf78a33f8d53b62fe976a6b4a486de8d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 24 Aug 2016 22:01:34 +0800 Subject: Remove old stuffs --- lib/gitlab/data_builder/pipeline_data_builder.rb | 66 ---------------------- .../data_builder/pipeline_data_builder_spec.rb | 33 ----------- spec/models/project_services/slack_service_spec.rb | 4 +- 3 files changed, 2 insertions(+), 101 deletions(-) delete mode 100644 lib/gitlab/data_builder/pipeline_data_builder.rb delete mode 100644 spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb diff --git a/lib/gitlab/data_builder/pipeline_data_builder.rb b/lib/gitlab/data_builder/pipeline_data_builder.rb deleted file mode 100644 index 13417ba09eb..00000000000 --- a/lib/gitlab/data_builder/pipeline_data_builder.rb +++ /dev/null @@ -1,66 +0,0 @@ -module Gitlab - module DataBuilder - module PipelineDataBuilder - module_function - - def build(pipeline) - { - object_kind: 'pipeline', - object_attributes: hook_attrs(pipeline), - user: pipeline.user.try(:hook_attrs), - project: pipeline.project.hook_attrs(backward: false), - commit: pipeline.commit.try(:hook_attrs), - builds: pipeline.builds.map(&method(:build_hook_attrs)) - } - end - - def hook_attrs(pipeline) - first_pending_build = pipeline.builds.first_pending - config_processor = pipeline.config_processor - - { - id: pipeline.id, - ref: pipeline.ref, - tag: pipeline.tag, - sha: pipeline.sha, - before_sha: pipeline.before_sha, - status: pipeline.status, - stage: first_pending_build.try(:stage), - stages: config_processor.try(:stages), - created_at: pipeline.created_at, - finished_at: pipeline.finished_at, - duration: pipeline.duration - } - end - - def build_hook_attrs(build) - { - id: build.id, - stage: build.stage, - name: build.name, - status: build.status, - created_at: build.created_at, - started_at: build.started_at, - finished_at: build.finished_at, - when: build.when, - manual: build.manual?, - user: build.user.try(:hook_attrs), - runner: build.runner && runner_hook_attrs(build.runner), - artifacts_file: { - filename: build.artifacts_file.filename, - size: build.artifacts_size - } - } - end - - def runner_hook_attrs(runner) - { - id: runner.id, - description: runner.description, - active: runner.active?, - is_shared: runner.is_shared? - } - end - end - end -end diff --git a/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb b/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb deleted file mode 100644 index fb5d07bb563..00000000000 --- a/spec/lib/gitlab/data_builder/pipeline_data_builder_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'spec_helper' - -describe Gitlab::DataBuilder::PipelineDataBuilder do - let(:user) { create(:user) } - let(:project) { create(:project) } - let!(:build) { create(:ci_build, pipeline: pipeline) } - - let(:pipeline) do - create(:ci_pipeline, - project: project, status: 'success', - sha: project.commit.sha, ref: project.default_branch) - end - - describe '.build' do - let(:data) { Gitlab::DataBuilder::PipelineDataBuilder.build(pipeline) } - let(:attributes) { data[:object_attributes] } - let(:build_data) { data[:builds].first } - let(:project_data) { data[:project] } - - it { expect(attributes).to be_a(Hash) } - it { expect(attributes[:ref]).to eq(pipeline.ref) } - it { expect(attributes[:sha]).to eq(pipeline.sha) } - it { expect(attributes[:tag]).to eq(pipeline.tag) } - it { expect(attributes[:id]).to eq(pipeline.id) } - it { expect(attributes[:status]).to eq(pipeline.status) } - - it { expect(build_data).to be_a(Hash) } - it { expect(build_data[:id]).to eq(build.id) } - it { expect(build_data[:status]).to eq(build.status) } - - it { expect(project_data).to eq(project.hook_attrs(backward: false)) } - end -end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 9b7899270d0..975c18fee1b 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -310,7 +310,7 @@ describe SlackService, models: true do end it 'calls Slack API for pipeline events' do - data = Gitlab::DataBuilder::PipelineDataBuilder.build(pipeline) + data = Gitlab::DataBuilder::Pipeline.build(pipeline) slack.execute(data) expect(WebMock).to have_requested(:post, webhook_url).once @@ -328,7 +328,7 @@ describe SlackService, models: true do context 'with default to notify_only_broken_pipelines' do it 'does not call Slack API for pipeline events' do - data = Gitlab::DataBuilder::PipelineDataBuilder.build(pipeline) + data = Gitlab::DataBuilder::Pipeline.build(pipeline) result = slack.execute(data) expect(result).to be_falsy -- cgit v1.2.3 From 714aeb7d377277ae8ccaab68c22c41e6bdbf910a Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Thu, 25 Aug 2016 01:05:39 +0000 Subject: improve wording on build and push images using GitLab CI --- doc/container_registry/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md index 047a0b08406..d7740647a91 100644 --- a/doc/container_registry/README.md +++ b/doc/container_registry/README.md @@ -78,9 +78,9 @@ delete them. > **Note:** This feature requires GitLab 8.8 and GitLab Runner 1.2. -Make sure that your GitLab Runner is configured to allow building docker images. -You have to check the [Using Docker Build documentation](../ci/docker/using_docker_build.md). -Then see the CI documentation on [Using the GitLab Container Registry](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry). +Make sure that your GitLab Runner is configured to allow building Docker images by +following the [Using Docker Build](../ci/docker/using_docker_build.md) +and [Using the GitLab Container Registry documentation](../ci/docker/using_docker_build.md#using-the-gitlab-container-registry). ## Limitations -- cgit v1.2.3 From 6c704fd99e1fd5a86dd2eccc8b192a009546cc96 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 6 Aug 2016 16:45:02 +0200 Subject: Inject dependencies into each CI config entry node --- lib/gitlab/ci/config/node/configurable.rb | 2 +- lib/gitlab/ci/config/node/entry.rb | 11 +++++++---- lib/gitlab/ci/config/node/global.rb | 6 +++++- lib/gitlab/ci/config/node/job.rb | 2 +- lib/gitlab/ci/config/node/jobs.rb | 2 +- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 2de82d40c9d..fb3604edac2 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -25,7 +25,7 @@ module Gitlab private - def compose! + def compose!(_deps) self.class.nodes.each do |key, factory| factory .value(@config[key]) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 0c782c422b5..40ffa85d6da 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -20,11 +20,14 @@ module Gitlab @validator.validate(:new) end - def process! + def process!(deps = nil) return unless valid? - compose! - descendants.each(&:process!) + compose!(deps) + + descendants.each do |entry| + entry.process!(deps) + end end def leaf? @@ -76,7 +79,7 @@ module Gitlab private - def compose! + def compose!(_deps) end end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index ccd539fb003..4d65309cf44 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -36,9 +36,13 @@ module Gitlab helpers :before_script, :image, :services, :after_script, :variables, :stages, :types, :cache, :jobs + def process!(_deps = nil) + super(self) + end + private - def compose! + def compose!(_deps) super compose_jobs! diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index e84737acbb9..55f34b446e5 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -107,7 +107,7 @@ module Gitlab after_script: after_script } end - def compose! + def compose!(_deps) super if type_defined? && !stage_defined? diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 51683c82ceb..a2a8554ed19 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -28,7 +28,7 @@ module Gitlab private - def compose! + def compose!(_deps) @config.each do |name, config| node = hidden?(name) ? Node::HiddenJob : Node::Job -- cgit v1.2.3 From 700078e8e43626a9be4fd752e35cd27ac526b410 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 6 Aug 2016 17:12:59 +0200 Subject: Rename unspecified and undefined CI config nodes --- lib/gitlab/ci/config/node/factory.rb | 8 ++--- lib/gitlab/ci/config/node/null.rb | 34 ------------------ lib/gitlab/ci/config/node/undefined.rb | 27 +++++++++++--- lib/gitlab/ci/config/node/unspecified.rb | 19 ++++++++++ spec/lib/gitlab/ci/config/node/factory_spec.rb | 3 +- spec/lib/gitlab/ci/config/node/global_spec.rb | 4 +-- spec/lib/gitlab/ci/config/node/null_spec.rb | 41 ---------------------- spec/lib/gitlab/ci/config/node/undefined_spec.rb | 33 ++++++++++------- spec/lib/gitlab/ci/config/node/unspecified_spec.rb | 32 +++++++++++++++++ 9 files changed, 103 insertions(+), 98 deletions(-) delete mode 100644 lib/gitlab/ci/config/node/null.rb create mode 100644 lib/gitlab/ci/config/node/unspecified.rb delete mode 100644 spec/lib/gitlab/ci/config/node/null_spec.rb create mode 100644 spec/lib/gitlab/ci/config/node/unspecified_spec.rb diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 707b052e6a8..5387f29ad59 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -37,8 +37,8 @@ module Gitlab # See issue #18775. # if @value.nil? - Node::Undefined.new( - fabricate_undefined + Node::Unspecified.new( + fabricate_unspecified ) else fabricate(@node, @value) @@ -47,13 +47,13 @@ module Gitlab private - def fabricate_undefined + def fabricate_unspecified ## # If node has a default value we fabricate concrete node # with default value. # if @node.default.nil? - fabricate(Node::Null) + fabricate(Node::Undefined) else fabricate(@node, @node.default) end diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb deleted file mode 100644 index 88a5f53f13c..00000000000 --- a/lib/gitlab/ci/config/node/null.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # This class represents an undefined node. - # - # Implements the Null Object pattern. - # - class Null < Entry - def value - nil - end - - def valid? - true - end - - def errors - [] - end - - def specified? - false - end - - def relevant? - false - end - end - end - end - end -end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index 45fef8c3ae5..33e78023539 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -3,15 +3,34 @@ module Gitlab class Config module Node ## - # This class represents an unspecified entry node. + # This class represents an undefined node. # - # It decorates original entry adding method that indicates it is - # unspecified. + # Implements the Null Object pattern. # - class Undefined < SimpleDelegator + class Undefined < Entry + def initialize(*) + super(nil) + end + + def value + nil + end + + def valid? + true + end + + def errors + [] + end + def specified? false end + + def relevant? + false + end end end end diff --git a/lib/gitlab/ci/config/node/unspecified.rb b/lib/gitlab/ci/config/node/unspecified.rb new file mode 100644 index 00000000000..a7d1f6131b8 --- /dev/null +++ b/lib/gitlab/ci/config/node/unspecified.rb @@ -0,0 +1,19 @@ +module Gitlab + module Ci + class Config + module Node + ## + # This class represents an unspecified entry node. + # + # It decorates original entry adding method that indicates it is + # unspecified. + # + class Unspecified < SimpleDelegator + def specified? + false + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index d26185ba585..a699089c563 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -65,7 +65,8 @@ describe Gitlab::Ci::Config::Node::Factory do .value(nil) .create! - expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined + expect(entry) + .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 2f87d270b36..dfe75bd09e5 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -156,9 +156,9 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.descendants.count).to eq 8 end - it 'contains undefined nodes' do + it 'contains unspecified nodes' do expect(global.descendants.first) - .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined + .to be_an_instance_of Gitlab::Ci::Config::Node::Unspecified end end diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb deleted file mode 100644 index 1ab5478dcfa..00000000000 --- a/spec/lib/gitlab/ci/config/node/null_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::Null do - let(:null) { described_class.new(nil) } - - describe '#leaf?' do - it 'is leaf node' do - expect(null).to be_leaf - end - end - - describe '#valid?' do - it 'is always valid' do - expect(null).to be_valid - end - end - - describe '#errors' do - it 'is does not contain errors' do - expect(null.errors).to be_empty - end - end - - describe '#value' do - it 'returns nil' do - expect(null.value).to eq nil - end - end - - describe '#relevant?' do - it 'is not relevant' do - expect(null.relevant?).to eq false - end - end - - describe '#specified?' do - it 'is not defined' do - expect(null.specified?).to eq false - end - end -end diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb index 2d43e1c1a9d..6bde8602963 100644 --- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb +++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb @@ -1,32 +1,41 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Undefined do - let(:undefined) { described_class.new(entry) } - let(:entry) { spy('Entry') } + let(:entry) { described_class.new } + + describe '#leaf?' do + it 'is leaf node' do + expect(entry).to be_leaf + end + end describe '#valid?' do - it 'delegates method to entry' do - expect(undefined.valid).to eq entry + it 'is always valid' do + expect(entry).to be_valid end end describe '#errors' do - it 'delegates method to entry' do - expect(undefined.errors).to eq entry + it 'is does not contain errors' do + expect(entry.errors).to be_empty end end describe '#value' do - it 'delegates method to entry' do - expect(undefined.value).to eq entry + it 'returns nil' do + expect(entry.value).to eq nil end end - describe '#specified?' do - it 'is always false' do - allow(entry).to receive(:specified?).and_return(true) + describe '#relevant?' do + it 'is not relevant' do + expect(entry.relevant?).to eq false + end + end - expect(undefined.specified?).to be false + describe '#specified?' do + it 'is not defined' do + expect(entry.specified?).to eq false end end end diff --git a/spec/lib/gitlab/ci/config/node/unspecified_spec.rb b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb new file mode 100644 index 00000000000..ba3ceef24ce --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/unspecified_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Unspecified do + let(:unspecified) { described_class.new(entry) } + let(:entry) { spy('Entry') } + + describe '#valid?' do + it 'delegates method to entry' do + expect(unspecified.valid?).to eq entry + end + end + + describe '#errors' do + it 'delegates method to entry' do + expect(unspecified.errors).to eq entry + end + end + + describe '#value' do + it 'delegates method to entry' do + expect(unspecified.value).to eq entry + end + end + + describe '#specified?' do + it 'is always false' do + allow(entry).to receive(:specified?).and_return(true) + + expect(unspecified.specified?).to be false + end + end +end -- cgit v1.2.3 From eaf211c2e393a2eeb5999b9ffec17112cdd52a0c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 25 Aug 2016 13:49:15 +0200 Subject: Expose compose method in the ci config entry nodes --- lib/gitlab/ci/config.rb | 2 +- lib/gitlab/ci/config/node/configurable.rb | 10 ++++++++-- lib/gitlab/ci/config/node/entry.rb | 17 +++++++---------- lib/gitlab/ci/config/node/global.rb | 14 +++++--------- lib/gitlab/ci/config/node/job.rb | 20 ++++++++++---------- lib/gitlab/ci/config/node/jobs.rb | 28 ++++++++++++++++------------ 6 files changed, 47 insertions(+), 44 deletions(-) diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index ae82c0db3f1..bbfa6cf7d05 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -14,7 +14,7 @@ module Gitlab @config = Loader.new(config).load! @global = Node::Global.new(@config) - @global.process! + @global.compose! end def valid? diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index fb3604edac2..530f95a6251 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -23,9 +23,9 @@ module Gitlab end end - private + def compose!(deps) + return unless valid? - def compose!(_deps) self.class.nodes.each do |key, factory| factory .value(@config[key]) @@ -33,6 +33,12 @@ module Gitlab @entries[key] = factory.create! end + + yield if block_given? + + @entries.each_value do |entry| + entry.compose!(deps) + end end class_methods do diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 40ffa85d6da..e02f4bf0a05 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -20,14 +20,16 @@ module Gitlab @validator.validate(:new) end + # Temporary method + # def process!(deps = nil) - return unless valid? - compose!(deps) + end - descendants.each do |entry| - entry.process!(deps) - end + def compose!(deps = nil) + return unless valid? + + yield if block_given? end def leaf? @@ -76,11 +78,6 @@ module Gitlab def self.validator Validator end - - private - - def compose!(_deps) - end end end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 4d65309cf44..2a2943c9288 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -36,19 +36,15 @@ module Gitlab helpers :before_script, :image, :services, :after_script, :variables, :stages, :types, :cache, :jobs - def process!(_deps = nil) - super(self) + def compose!(_deps = nil) + super(self) do + compose_jobs! + compose_deprecated_entries! + end end private - def compose!(_deps) - super - - compose_jobs! - compose_deprecated_entries! - end - def compose_jobs! factory = Node::Factory.new(Node::Jobs) .value(@config.except(*self.class.nodes.keys)) diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 55f34b446e5..9317bb40f50 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -82,6 +82,16 @@ module Gitlab :cache, :image, :services, :only, :except, :variables, :artifacts + def compose!(deps) + super do + if type_defined? && !stage_defined? + @entries[:stage] = @entries[:type] + end + + @entries.delete(:type) + end + end + def name @metadata[:name] end @@ -106,16 +116,6 @@ module Gitlab artifacts: artifacts, after_script: after_script } end - - def compose!(_deps) - super - - if type_defined? && !stage_defined? - @entries[:stage] = @entries[:type] - end - - @entries.delete(:type) - end end end end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index a2a8554ed19..45865825e46 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -26,19 +26,23 @@ module Gitlab name.to_s.start_with?('.') end - private - - def compose!(_deps) - @config.each do |name, config| - node = hidden?(name) ? Node::HiddenJob : Node::Job - - factory = Node::Factory.new(node) - .value(config || {}) - .metadata(name: name) - .with(key: name, parent: self, - description: "#{name} job definition.") + def compose!(deps = nil) + super do + @config.each do |name, config| + node = hidden?(name) ? Node::HiddenJob : Node::Job + + factory = Node::Factory.new(node) + .value(config || {}) + .metadata(name: name) + .with(key: name, parent: self, + description: "#{name} job definition.") + + @entries[name] = factory.create! + end - @entries[name] = factory.create! + @entries.each_value do |entry| + entry.compose!(deps) + end end end end -- cgit v1.2.3 From 4f837f6690970bde315b8c9d76190ed45f15e49e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 25 Aug 2016 13:58:46 +0200 Subject: Remove temporary stub method from ci config nodes --- lib/gitlab/ci/config/node/configurable.rb | 2 +- lib/gitlab/ci/config/node/entry.rb | 6 ------ lib/gitlab/ci/config/node/job.rb | 2 +- spec/lib/gitlab/ci/config/node/cache_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/global_spec.rb | 26 ++++++++++++++++---------- spec/lib/gitlab/ci/config/node/job_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 6 +++--- spec/lib/gitlab/ci/config/node/script_spec.rb | 4 +--- 8 files changed, 24 insertions(+), 26 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 530f95a6251..6b7ab2fdaf2 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -23,7 +23,7 @@ module Gitlab end end - def compose!(deps) + def compose!(deps = nil) return unless valid? self.class.nodes.each do |key, factory| diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index e02f4bf0a05..907d25a8c49 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -20,12 +20,6 @@ module Gitlab @validator.validate(:new) end - # Temporary method - # - def process!(deps = nil) - compose!(deps) - end - def compose!(deps = nil) return unless valid? diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 9317bb40f50..3e6666d751f 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -82,7 +82,7 @@ module Gitlab :cache, :image, :services, :only, :except, :variables, :artifacts - def compose!(deps) + def compose!(deps = nil) super do if type_defined? && !stage_defined? @entries[:stage] = @entries[:type] diff --git a/spec/lib/gitlab/ci/config/node/cache_spec.rb b/spec/lib/gitlab/ci/config/node/cache_spec.rb index 50f619ce26e..e251210949c 100644 --- a/spec/lib/gitlab/ci/config/node/cache_spec.rb +++ b/spec/lib/gitlab/ci/config/node/cache_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Cache do let(:entry) { described_class.new(config) } describe 'validations' do - before { entry.process! } + before { entry.compose! } context 'when entry config value is correct' do let(:config) do diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index dfe75bd09e5..e033b423fdd 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -27,8 +27,8 @@ describe Gitlab::Ci::Config::Node::Global do spinach: { script: 'spinach' } } end - describe '#process!' do - before { global.process! } + describe '#compose!' do + before { global.compose! } it 'creates nodes hash' do expect(global.descendants).to be_an Array @@ -59,7 +59,7 @@ describe Gitlab::Ci::Config::Node::Global do end end - context 'when not processed' do + context 'when not composed' do describe '#before_script' do it 'returns nil' do expect(global.before_script).to be nil @@ -73,8 +73,8 @@ describe Gitlab::Ci::Config::Node::Global do end end - context 'when processed' do - before { global.process! } + context 'when composed' do + before { global.compose! } describe '#before_script' do it 'returns correct script' do @@ -148,8 +148,11 @@ describe Gitlab::Ci::Config::Node::Global do end context 'when most of entires not defined' do - let(:hash) { { cache: { key: 'a' }, rspec: { script: %w[ls] } } } - before { global.process! } + before { global.compose! } + + let(:hash) do + { cache: { key: 'a' }, rspec: { script: %w[ls] } } + end describe '#nodes' do it 'instantizes all nodes' do @@ -188,8 +191,11 @@ describe Gitlab::Ci::Config::Node::Global do # details. # context 'when entires specified but not defined' do - let(:hash) { { variables: nil, rspec: { script: 'rspec' } } } - before { global.process! } + before { global.compose! } + + let(:hash) do + { variables: nil, rspec: { script: 'rspec' } } + end describe '#variables' do it 'undefined entry returns a default value' do @@ -200,7 +206,7 @@ describe Gitlab::Ci::Config::Node::Global do end context 'when hash is not valid' do - before { global.process! } + before { global.compose! } let(:hash) do { before_script: 'ls' } diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 1484fb60dd8..74aa616720e 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Job do let(:entry) { described_class.new(config, name: :rspec) } - before { entry.process! } + before { entry.compose! } describe 'validations' do context 'when entry config value is correct' do diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index b8d9c70479c..4bf01835c0a 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Ci::Config::Node::Jobs do let(:entry) { described_class.new(config) } describe 'validations' do - before { entry.process! } + before { entry.compose! } context 'when entry config value is correct' do let(:config) { { rspec: { script: 'rspec' } } } @@ -47,8 +47,8 @@ describe Gitlab::Ci::Config::Node::Jobs do end end - context 'when valid job entries processed' do - before { entry.process! } + context 'when valid job entries composed' do + before { entry.compose! } let(:config) do { rspec: { script: 'rspec' }, diff --git a/spec/lib/gitlab/ci/config/node/script_spec.rb b/spec/lib/gitlab/ci/config/node/script_spec.rb index ee7395362a9..219a7e981d3 100644 --- a/spec/lib/gitlab/ci/config/node/script_spec.rb +++ b/spec/lib/gitlab/ci/config/node/script_spec.rb @@ -3,9 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Script do let(:entry) { described_class.new(config) } - describe '#process!' do - before { entry.process! } - + describe 'validations' do context 'when entry config value is correct' do let(:config) { ['ls', 'pwd'] } -- cgit v1.2.3 From 30f58cf3924610564ca95b0ac17b69d272e74f5f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 25 Aug 2016 14:31:06 +0200 Subject: Add [] method for accessing ci entry dependencies --- lib/gitlab/ci/config/node/entry.rb | 4 ++++ spec/lib/gitlab/ci/config/node/global_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 907d25a8c49..8717eabf81e 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -20,6 +20,10 @@ module Gitlab @validator.validate(:new) end + def [](key) + @entries[key] || Node::Undefined.new + end + def compose!(deps = nil) return unless valid? diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index e033b423fdd..4ff2320f1bf 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -253,4 +253,27 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.specified?).to be true end end + + describe '#[]' do + before { global.compose! } + + let(:hash) do + { cache: { key: 'a' }, rspec: { script: 'ls' } } + end + + context 'when node exists' do + it 'returns correct entry' do + expect(global[:cache]) + .to be_an_instance_of Gitlab::Ci::Config::Node::Cache + expect(global[:jobs][:rspec][:script].value).to eq ['ls'] + end + end + + context 'when node does not exist' do + it 'always return unspecified node' do + expect(global[:some][:unknown][:node]) + .not_to be_specified + end + end + end end -- cgit v1.2.3 From b99263fbd120dcfea4159e800d8949fb6ad453e2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 26 Aug 2016 09:35:42 +0000 Subject: Fix CHANGELOG --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e317b1f6ce0..a59c45a0fda 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,8 +15,6 @@ v 8.12.0 (unreleased) - Fix groups sort dropdown alignment (ClemMakesApps) - Added tests for diff notes - Add pipeline events to Slack integration !5525 - -v 8.11.1 (unreleased) - Add delimiter to project stars and forks count (ClemMakesApps) - Fix badge count alignment (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps) -- cgit v1.2.3 From 34ea6802940627df70926a1a22e798f9dae800ec Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sun, 28 Aug 2016 22:32:26 -0600 Subject: Add CII badge to README [ci skip] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3df8bfa04c7..d70653eb91e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) +![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) ## Canonical source -- cgit v1.2.3 From 5067d9d388589da39416d818358ec3e1211c6ed7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 15:21:50 +0800 Subject: Empty line between message =, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5525#note_14664714 --- spec/models/project_services/slack_service/build_message_spec.rb | 9 ++++++--- .../project_services/slack_service/pipeline_message_spec.rb | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb index 7fcfdf0eacd..a3ef121a093 100644 --- a/spec/models/project_services/slack_service/build_message_spec.rb +++ b/spec/models/project_services/slack_service/build_message_spec.rb @@ -24,9 +24,10 @@ describe SlackService::BuildMessage do let(:status) { 'success' } let(:color) { 'good' } let(:duration) { 10 } - + it 'returns a message with information about succeeded build' do message = ': Commit of branch by hacker passed in 10 seconds' + expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) @@ -40,12 +41,13 @@ describe SlackService::BuildMessage do it 'returns a message with information about failed build' do message = ': Commit of branch by hacker failed in 10 seconds' + expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) end - end - + end + describe '#seconds_name' do let(:status) { 'failed' } let(:color) { 'danger' } @@ -53,6 +55,7 @@ describe SlackService::BuildMessage do it 'returns seconds as singular when there is only one' do message = ': Commit of branch by hacker failed in 1 second' + expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb index a292defee66..c27b7c82dd6 100644 --- a/spec/models/project_services/slack_service/pipeline_message_spec.rb +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -26,6 +26,7 @@ describe SlackService::PipelineMessage do it 'returns a message with information about succeeded build' do message = ': Pipeline of branch by hacker passed in 10 seconds' + expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) @@ -39,6 +40,7 @@ describe SlackService::PipelineMessage do it 'returns a message with information about failed build' do message = ': Pipeline of branch by hacker failed in 10 seconds' + expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) @@ -52,6 +54,7 @@ describe SlackService::PipelineMessage do it 'returns seconds as singular when there is only one' do message = ': Pipeline of branch by hacker failed in 1 second' + expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) -- cgit v1.2.3 From 4fc478a4e0cb88dc72db627cd6081c1456f7ddf8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 15:23:49 +0800 Subject: somewhere -> example.gitlab, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5525#note_14664661 --- spec/models/project_services/slack_service/build_message_spec.rb | 8 ++++---- .../project_services/slack_service/pipeline_message_spec.rb | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb index a3ef121a093..5ffa724e7a4 100644 --- a/spec/models/project_services/slack_service/build_message_spec.rb +++ b/spec/models/project_services/slack_service/build_message_spec.rb @@ -10,7 +10,7 @@ describe SlackService::BuildMessage do tag: false, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'example.gitlab.com', commit: { status: status, @@ -26,7 +26,7 @@ describe SlackService::BuildMessage do let(:duration) { 10 } it 'returns a message with information about succeeded build' do - message = ': Commit of branch by hacker passed in 10 seconds' + message = ': Commit of branch by hacker passed in 10 seconds' expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) @@ -40,7 +40,7 @@ describe SlackService::BuildMessage do let(:duration) { 10 } it 'returns a message with information about failed build' do - message = ': Commit of branch by hacker failed in 10 seconds' + message = ': Commit of branch by hacker failed in 10 seconds' expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) @@ -54,7 +54,7 @@ describe SlackService::BuildMessage do let(:duration) { 1 } it 'returns seconds as singular when there is only one' do - message = ': Commit of branch by hacker failed in 1 second' + message = ': Commit of branch by hacker failed in 1 second' expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb index c27b7c82dd6..8258f118c13 100644 --- a/spec/models/project_services/slack_service/pipeline_message_spec.rb +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -14,7 +14,7 @@ describe SlackService::PipelineMessage do duration: duration }, project: { path_with_namespace: 'project_name', - web_url: 'somewhere.com' }, + web_url: 'example.gitlab.com' }, commit: { author_name: 'hacker' } } end @@ -25,7 +25,7 @@ describe SlackService::PipelineMessage do let(:duration) { 10 } it 'returns a message with information about succeeded build' do - message = ': Pipeline of branch by hacker passed in 10 seconds' + message = ': Pipeline of branch by hacker passed in 10 seconds' expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) @@ -39,7 +39,7 @@ describe SlackService::PipelineMessage do let(:duration) { 10 } it 'returns a message with information about failed build' do - message = ': Pipeline of branch by hacker failed in 10 seconds' + message = ': Pipeline of branch by hacker failed in 10 seconds' expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) @@ -53,7 +53,7 @@ describe SlackService::PipelineMessage do let(:duration) { 1 } it 'returns seconds as singular when there is only one' do - message = ': Pipeline of branch by hacker failed in 1 second' + message = ': Pipeline of branch by hacker failed in 1 second' expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) -- cgit v1.2.3 From a97133a6b13b0357f4037d2678569b738046acdb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 15:33:36 +0800 Subject: Shorten the line and use methods, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5525#note_14664637 --- .../slack_service/build_message_spec.rb | 17 +++++++++++------ .../slack_service/pipeline_message_spec.rb | 16 ++++++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb index 5ffa724e7a4..16fe1c93039 100644 --- a/spec/models/project_services/slack_service/build_message_spec.rb +++ b/spec/models/project_services/slack_service/build_message_spec.rb @@ -20,14 +20,15 @@ describe SlackService::BuildMessage do } end + let(:message) { build_message } + context 'succeeded' do let(:status) { 'success' } let(:color) { 'good' } let(:duration) { 10 } + let(:message) { build_message('passed') } it 'returns a message with information about succeeded build' do - message = ': Commit of branch by hacker passed in 10 seconds' - expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) @@ -40,8 +41,6 @@ describe SlackService::BuildMessage do let(:duration) { 10 } it 'returns a message with information about failed build' do - message = ': Commit of branch by hacker failed in 10 seconds' - expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) @@ -54,11 +53,17 @@ describe SlackService::BuildMessage do let(:duration) { 1 } it 'returns seconds as singular when there is only one' do - message = ': Commit of branch by hacker failed in 1 second' - expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) end end + + def build_message(status_text=status) + ":" \ + " Commit " \ + " of branch" \ + " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}" + end end diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb index 8258f118c13..1f1701202b4 100644 --- a/spec/models/project_services/slack_service/pipeline_message_spec.rb +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -19,14 +19,15 @@ describe SlackService::PipelineMessage do } end + let(:message) { build_message } + context 'succeeded' do let(:status) { 'success' } let(:color) { 'good' } let(:duration) { 10 } + let(:message) { build_message('passed') } it 'returns a message with information about succeeded build' do - message = ': Pipeline of branch by hacker passed in 10 seconds' - expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) @@ -39,8 +40,6 @@ describe SlackService::PipelineMessage do let(:duration) { 10 } it 'returns a message with information about failed build' do - message = ': Pipeline of branch by hacker failed in 10 seconds' - expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) @@ -53,11 +52,16 @@ describe SlackService::PipelineMessage do let(:duration) { 1 } it 'returns seconds as singular when there is only one' do - message = ': Pipeline of branch by hacker failed in 1 second' - expect(subject.pretext).to be_empty expect(subject.fallback).to eq(message) expect(subject.attachments).to eq([text: message, color: color]) end end + + def build_message(status_text=status) + ":" \ + " Pipeline " \ + " of branch" \ + " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}" + end end -- cgit v1.2.3 From 08c3e9e7849c9f303d3df5f4afb9986eae7a9973 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 15:35:04 +0800 Subject: pipeline/build succeeded/failed, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5525#note_14664613 --- spec/models/project_services/slack_service/build_message_spec.rb | 4 ++-- spec/models/project_services/slack_service/pipeline_message_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb index 16fe1c93039..f690b6c7b3b 100644 --- a/spec/models/project_services/slack_service/build_message_spec.rb +++ b/spec/models/project_services/slack_service/build_message_spec.rb @@ -22,7 +22,7 @@ describe SlackService::BuildMessage do let(:message) { build_message } - context 'succeeded' do + context 'build succeeded' do let(:status) { 'success' } let(:color) { 'good' } let(:duration) { 10 } @@ -35,7 +35,7 @@ describe SlackService::BuildMessage do end end - context 'failed' do + context 'build failed' do let(:status) { 'failed' } let(:color) { 'danger' } let(:duration) { 10 } diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb index 1f1701202b4..fedf6eb1317 100644 --- a/spec/models/project_services/slack_service/pipeline_message_spec.rb +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -21,7 +21,7 @@ describe SlackService::PipelineMessage do let(:message) { build_message } - context 'succeeded' do + context 'pipeline succeeded' do let(:status) { 'success' } let(:color) { 'good' } let(:duration) { 10 } @@ -34,7 +34,7 @@ describe SlackService::PipelineMessage do end end - context 'failed' do + context 'pipeline failed' do let(:status) { 'failed' } let(:color) { 'danger' } let(:duration) { 10 } -- cgit v1.2.3 From 71981151f817712f6f6dcd30651d32fb1bdb0abc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 15:37:15 +0800 Subject: empty lines between blocks --- spec/models/project_services/slack_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 975c18fee1b..f920dc263ef 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -289,12 +289,12 @@ describe SlackService, models: true do describe 'Pipeline events' do let(:user) { create(:user) } let(:project) { create(:project) } + let(:pipeline) do create(:ci_pipeline, project: project, status: status, sha: project.commit.sha, ref: project.default_branch) end - let(:status) { raise NotImplementedError } before do allow(slack).to receive_messages( -- cgit v1.2.3 From 41384db1c6a267b344259cd350b6161fe79605e8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 15:45:43 +0800 Subject: Use a completely fake webhook URL, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5525#note_14665966 --- spec/models/project_services/slack_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index f920dc263ef..5afdc4b2f7b 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -22,7 +22,7 @@ require 'spec_helper' describe SlackService, models: true do let(:slack) { SlackService.new } - let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } + let(:webhook_url) { 'https://example.gitlab.com/' } describe "Associations" do it { is_expected.to belong_to :project } -- cgit v1.2.3 From 867b64c87dc1e5e9d43a91e9cd7ba48c1025683e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 16:03:03 +0800 Subject: I am too used to have no spaces around default args --- spec/models/project_services/slack_service/build_message_spec.rb | 2 +- spec/models/project_services/slack_service/pipeline_message_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb index f690b6c7b3b..0b876f4fd26 100644 --- a/spec/models/project_services/slack_service/build_message_spec.rb +++ b/spec/models/project_services/slack_service/build_message_spec.rb @@ -59,7 +59,7 @@ describe SlackService::BuildMessage do end end - def build_message(status_text=status) + def build_message(status_text = status) ":" \ " Commit " \ diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb index fedf6eb1317..384b22b7103 100644 --- a/spec/models/project_services/slack_service/pipeline_message_spec.rb +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -58,7 +58,7 @@ describe SlackService::PipelineMessage do end end - def build_message(status_text=status) + def build_message(status_text = status) ":" \ " Pipeline " \ " of branch" \ -- cgit v1.2.3 From da1acf04cef4cd7ee53f9da7bb870ba226ed1441 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 17:14:16 +0800 Subject: Not sure why there's an extra one!? --- app/views/shared/web_hooks/_form.html.haml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index 8189e0b645d..d2ec6c3ddef 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -72,13 +72,6 @@ %strong Pipeline events %p.light This URL will be triggered when the pipeline status changes - %li - = f.check_box :pipeline_events, class: 'pull-left' - .prepend-left-20 - = f.label :pipeline_events, class: 'list-label' do - %strong Pipeline events - %p.light - This URL will be triggered when the pipeline status changes %li = f.check_box :wiki_page_events, class: 'pull-left' .prepend-left-20 -- cgit v1.2.3 From 62f704c5ad421a538257917d75f68b3c698b9be0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 29 Aug 2016 12:44:14 +0200 Subject: Make it possible to inherit global ci config nodes --- lib/gitlab/ci/config/node/job.rb | 15 ++++++++++ spec/lib/gitlab/ci/config/node/global_spec.rb | 30 +++++++++++++++---- spec/lib/gitlab/ci/config/node/job_spec.rb | 43 +++++++++++++++++++++++++-- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 3e6666d751f..745f03ae4d5 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -90,6 +90,8 @@ module Gitlab @entries.delete(:type) end + + inherit!(deps) end def name @@ -102,6 +104,19 @@ module Gitlab private + def inherit!(deps) + return unless deps + + self.class.nodes.each_key do |key| + global_entry = deps[key] + job_entry = @entries[key] + + if global_entry.specified? && !job_entry.specified? + @entries[key] = global_entry + end + end + end + def to_hash { name: name, before_script: before_script, diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 4ff2320f1bf..951f5f956d8 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Ci::Config::Node::Global do end context 'when hash is valid' do - context 'when all entries defined' do + context 'when some entries defined' do let(:hash) do { before_script: ['ls', 'pwd'], image: 'ruby:2.2', @@ -24,7 +24,7 @@ describe Gitlab::Ci::Config::Node::Global do stages: ['build', 'pages'], cache: { key: 'k', untracked: true, paths: ['public/'] }, rspec: { script: %w[rspec ls] }, - spinach: { script: 'spinach' } } + spinach: { before_script: [], variables: {}, script: 'spinach' } } end describe '#compose!' do @@ -76,6 +76,12 @@ describe Gitlab::Ci::Config::Node::Global do context 'when composed' do before { global.compose! } + describe '#errors' do + it 'has no errors' do + expect(global.errors).to be_empty + end + end + describe '#before_script' do it 'returns correct script' do expect(global.before_script).to eq ['ls', 'pwd'] @@ -135,12 +141,24 @@ describe Gitlab::Ci::Config::Node::Global do describe '#jobs' do it 'returns jobs configuration' do expect(global.jobs).to eq( - rspec: { name: :rspec, - script: %w[rspec ls], - stage: 'test' }, + rspec: { script: %w[rspec ls], + name: :rspec, + before_script: ['ls', 'pwd'], + image: 'ruby:2.2', + services: ['postgres:9.1', 'mysql:5.5'], + stage: 'test', + cache: { key: 'k', untracked: true, paths: ['public/'] }, + variables: { VAR: 'value' }, + after_script: ['make clean'] }, spinach: { name: :spinach, script: %w[spinach], - stage: 'test' } + before_script: [], + image: 'ruby:2.2', + services: ['postgres:9.1', 'mysql:5.5'], + stage: 'test', + cache: { key: 'k', untracked: true, paths: ['public/'] }, + variables: {}, + after_script: ['make clean'] }, ) end end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 74aa616720e..f34a3786a0d 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Job do let(:entry) { described_class.new(config, name: :rspec) } - before { entry.compose! } - describe 'validations' do + before { entry.compose! } + context 'when entry config value is correct' do let(:config) { { script: 'rspec' } } @@ -60,6 +60,8 @@ describe Gitlab::Ci::Config::Node::Job do end describe '#value' do + before { entry.compose! } + context 'when entry is correct' do let(:config) do { before_script: %w[ls pwd], @@ -83,4 +85,41 @@ describe Gitlab::Ci::Config::Node::Job do expect(entry).to be_relevant end end + + describe '#compose!' do + let(:unspecified) { double('unspecified', 'specified?' => false) } + + let(:specified) do + double('specified', 'specified?' => true, value: 'specified') + end + + let(:deps) { spy('deps', '[]' => unspecified) } + + context 'when job config overrides global config' do + before { entry.compose!(deps) } + + let(:config) do + { image: 'some_image', cache: { key: 'test' } } + end + + it 'overrides global config' do + expect(entry[:image].value).to eq 'some_image' + expect(entry[:cache].value).to eq(key: 'test') + end + end + + context 'when job config does not override global config' do + before do + allow(deps).to receive('[]').with(:image).and_return(specified) + entry.compose!(deps) + end + + let(:config) { { script: 'ls', cache: { key: 'test' } } } + + it 'uses config from global entry' do + expect(entry[:image].value).to eq 'specified' + expect(entry[:cache].value).to eq(key: 'test') + end + end + end end -- cgit v1.2.3 From bd807503d14f2592e2c89c361c6967866580e977 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 29 Aug 2016 13:10:57 +0200 Subject: Do not override job nodes in legacy ci config code --- lib/ci/gitlab_ci_yaml_processor.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 47efd5bd9f2..5f1ba97214e 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -60,7 +60,7 @@ module Ci # - before script behaves differently than after script # - after script returns an array of commands # - before script should be a concatenated command - commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"), + commands: [job[:before_script], job[:script]].flatten.compact.join("\n"), tag_list: job[:tags] || [], name: job[:name].to_s, allow_failure: job[:allow_failure] || false, @@ -68,12 +68,12 @@ module Ci environment: job[:environment], yaml_variables: yaml_variables(name), options: { - image: job[:image] || @image, - services: job[:services] || @services, + image: job[:image], + services: job[:services], artifacts: job[:artifacts], - cache: job[:cache] || @cache, + cache: job[:cache], dependencies: job[:dependencies], - after_script: job[:after_script] || @after_script, + after_script: job[:after_script], }.compact } end -- cgit v1.2.3 From 1255205d896d070a6d8bcefa3774116263db84be Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 29 Aug 2016 13:11:35 +0200 Subject: Add method that returns commands for ci job entry --- lib/ci/gitlab_ci_yaml_processor.rb | 7 +--- lib/gitlab/ci/config/node/job.rb | 7 +++- spec/lib/gitlab/ci/config/node/global_spec.rb | 8 ++-- spec/lib/gitlab/ci/config/node/job_spec.rb | 57 +++++++++++++++++---------- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 2 + 5 files changed, 50 insertions(+), 31 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 5f1ba97214e..c00c2020b4f 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -55,12 +55,7 @@ module Ci { stage_idx: @stages.index(job[:stage]), stage: job[:stage], - ## - # Refactoring note: - # - before script behaves differently than after script - # - after script returns an array of commands - # - before script should be a concatenated command - commands: [job[:before_script], job[:script]].flatten.compact.join("\n"), + commands: job[:commands], tag_list: job[:tags] || [], name: job[:name].to_s, allow_failure: job[:allow_failure] || false, diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 745f03ae4d5..0cbdf7619c0 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -80,7 +80,7 @@ module Gitlab helpers :before_script, :script, :stage, :type, :after_script, :cache, :image, :services, :only, :except, :variables, - :artifacts + :artifacts, :commands def compose!(deps = nil) super do @@ -102,6 +102,10 @@ module Gitlab @config.merge(to_hash.compact) end + def commands + (before_script_value.to_a + script_value.to_a).join("\n") + end + private def inherit!(deps) @@ -121,6 +125,7 @@ module Gitlab { name: name, before_script: before_script, script: script, + commands: commands, image: image, services: services, stage: stage, diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 951f5f956d8..12232ff7e2f 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -141,9 +141,10 @@ describe Gitlab::Ci::Config::Node::Global do describe '#jobs' do it 'returns jobs configuration' do expect(global.jobs).to eq( - rspec: { script: %w[rspec ls], - name: :rspec, + rspec: { name: :rspec, + script: %w[rspec ls], before_script: ['ls', 'pwd'], + commands: "ls\npwd\nrspec\nls", image: 'ruby:2.2', services: ['postgres:9.1', 'mysql:5.5'], stage: 'test', @@ -151,8 +152,9 @@ describe Gitlab::Ci::Config::Node::Global do variables: { VAR: 'value' }, after_script: ['make clean'] }, spinach: { name: :spinach, - script: %w[spinach], before_script: [], + script: %w[spinach], + commands: 'spinach', image: 'ruby:2.2', services: ['postgres:9.1', 'mysql:5.5'], stage: 'test', diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index f34a3786a0d..c660ce39c46 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -59,27 +59,6 @@ describe Gitlab::Ci::Config::Node::Job do end end - describe '#value' do - before { entry.compose! } - - context 'when entry is correct' do - let(:config) do - { before_script: %w[ls pwd], - script: 'rspec', - after_script: %w[cleanup] } - end - - it 'returns correct value' do - expect(entry.value) - .to eq(name: :rspec, - before_script: %w[ls pwd], - script: %w[rspec], - stage: 'test', - after_script: %w[cleanup]) - end - end - end - describe '#relevant?' do it 'is a relevant entry' do expect(entry).to be_relevant @@ -122,4 +101,40 @@ describe Gitlab::Ci::Config::Node::Job do end end end + + context 'when composed' do + before { entry.compose! } + + describe '#value' do + before { entry.compose! } + + context 'when entry is correct' do + let(:config) do + { before_script: %w[ls pwd], + script: 'rspec', + after_script: %w[cleanup] } + end + + it 'returns correct value' do + expect(entry.value) + .to eq(name: :rspec, + before_script: %w[ls pwd], + script: %w[rspec], + commands: "ls\npwd\nrspec", + stage: 'test', + after_script: %w[cleanup]) + end + end + end + + describe '#commands' do + let(:config) do + { before_script: %w[ls pwd], script: 'rspec' } + end + + it 'returns a string of commands concatenated with new line character' do + expect(entry.commands).to eq "ls\npwd\nrspec" + end + end + end end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 4bf01835c0a..420d137270a 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -61,9 +61,11 @@ describe Gitlab::Ci::Config::Node::Jobs do expect(entry.value).to eq( rspec: { name: :rspec, script: %w[rspec], + commands: 'rspec', stage: 'test' }, spinach: { name: :spinach, script: %w[spinach], + commands: 'spinach', stage: 'test' }) end end -- cgit v1.2.3 From ace0a005b8bda05db224c21ac5ea691c3ffb6fb6 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 20:24:48 +0800 Subject: Smartly calculate real running time and pending time --- app/models/ci/pipeline.rb | 8 +- ...0829122117_add_pending_duration_to_pipelines.rb | 8 ++ lib/gitlab/ci/pipeline_duration.rb | 90 ++++++++++++++++++++ spec/lib/gitlab/ci/pipeline_duration_spec.rb | 95 ++++++++++++++++++++++ 4 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160829122117_add_pending_duration_to_pipelines.rb create mode 100644 lib/gitlab/ci/pipeline_duration.rb create mode 100644 spec/lib/gitlab/ci/pipeline_duration_spec.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 03812cd195f..e59c90e7e0c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -258,7 +258,13 @@ module Ci end def update_duration - self.duration = calculate_duration + calculated_status = %w[success failed running canceled] + calculated_builds = builds.latest.where(status: calculated_status) + calculator = PipelineDuration.from_builds(calculated_builds) + + self.duration = calculator.duration + self.pending_duration = + started_at - created_at + calculator.pending_duration end def execute_hooks diff --git a/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb b/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb new file mode 100644 index 00000000000..f056f45c840 --- /dev/null +++ b/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb @@ -0,0 +1,8 @@ +class AddPendingDurationToPipelines < ActiveRecord::Migration + + DOWNTIME = false + + def change + add_column :ci_commits, :pending_duration, :integer + end +end diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb new file mode 100644 index 00000000000..e4c0be3b640 --- /dev/null +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -0,0 +1,90 @@ +module Gitlab + module Ci + class PipelineDuration + SegmentStruct = Struct.new(:first, :last) + class Segment < SegmentStruct + def duration + last - first + end + end + + def self.from_builds(builds) + now = Time.now + + segments = builds.map do |b| + Segment.new(b.started_at, b.finished_at || now) + end + + new(segments) + end + + attr_reader :duration, :pending_duration + + def initialize(segments) + process(segments.sort_by(&:first)) + end + + private + + def process(segments) + merged = process_segments(segments) + + @duration = process_duration(merged) + @pending_duration = process_pending_duration(merged, @duration) + end + + def process_segments(segments) + if segments.empty? + segments + else + segments[1..-1].inject([segments.first]) do |current, target| + left, result = insert_segment(current, target) + + if left # left is the latest one + result << left + else + result + end + end + end + end + + def insert_segment(segments, init) + segments.inject([init, []]) do |target_result, member| + target, result = target_result + + if target.nil? # done + result << member + [nil, result] + elsif merged = try_merge_segment(target, member) # overlapped + [merged, result] # merge and keep finding the hole + elsif target.last < member.first # found the hole + result << target << member + [nil, result] + else + result << member + target_result + end + end + end + + def try_merge_segment(target, member) + if target.first <= member.last && target.last >= member.first + Segment.new([target.first, member.first].min, + [target.last, member.last].max) + end + end + + def process_duration(segments) + segments.inject(0) do |result, seg| + result + seg.duration + end + end + + def process_pending_duration(segments, duration) + total = segments.last.last - segments.first.first + total - duration + end + end + end +end diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb new file mode 100644 index 00000000000..7ac9a3bd6bc --- /dev/null +++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper' + +describe Gitlab::Ci::PipelineDuration do + let(:calculator) { create_calculator(data) } + + shared_examples 'calculating duration' do + it do + expect(calculator.duration).to eq(duration) + expect(calculator.pending_duration).to eq(pending_duration) + end + end + + context 'test sample A' do + let(:data) do + [[0, 1], + [1, 2], + [3, 4], + [5, 6]] + end + + let(:duration) { 4 } + let(:pending_duration) { 2 } + + it_behaves_like 'calculating duration' + end + + context 'test sample B' do + let(:data) do + [[0, 1], + [1, 2], + [2, 3], + [3, 4], + [0, 4]] + end + + let(:duration) { 4 } + let(:pending_duration) { 0 } + + it_behaves_like 'calculating duration' + end + + context 'test sample C' do + let(:data) do + [[0, 4], + [2, 6], + [5, 7], + [8, 9]] + end + + let(:duration) { 8 } + let(:pending_duration) { 1 } + + it_behaves_like 'calculating duration' + end + + context 'test sample D' do + let(:data) do + [[0, 1], + [2, 3], + [4, 5], + [6, 7]] + end + + let(:duration) { 4 } + let(:pending_duration) { 3 } + + it_behaves_like 'calculating duration' + end + + context 'test sample E' do + let(:data) do + [[0, 1], + [3, 9], + [3, 4], + [3, 5], + [3, 8], + [4, 5], + [4, 7], + [5, 8]] + end + + let(:duration) { 7 } + let(:pending_duration) { 2 } + + it_behaves_like 'calculating duration' + end + + def create_calculator(data) + segments = data.shuffle.map do |(first, last)| + Gitlab::Ci::PipelineDuration::Segment.new(first, last) + end + + Gitlab::Ci::PipelineDuration.new(segments) + end +end -- cgit v1.2.3 From 1be5af31b086a79029b91d0bed5366b31fc6de1a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 20:41:00 +0800 Subject: I prefer to have empty lines around constants though --- db/migrate/20160829122117_add_pending_duration_to_pipelines.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb b/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb index f056f45c840..6f238a2f65c 100644 --- a/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb +++ b/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb @@ -1,5 +1,4 @@ class AddPendingDurationToPipelines < ActiveRecord::Migration - DOWNTIME = false def change -- cgit v1.2.3 From e5d022c813d476383cc9889f8631d38ac8a85f62 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 22:22:03 +0800 Subject: Fix constant name --- app/models/ci/pipeline.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index e59c90e7e0c..9db0f139162 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -260,7 +260,7 @@ module Ci def update_duration calculated_status = %w[success failed running canceled] calculated_builds = builds.latest.where(status: calculated_status) - calculator = PipelineDuration.from_builds(calculated_builds) + calculator = Gitlab::Ci::PipelineDuration.from_builds(calculated_builds) self.duration = calculator.duration self.pending_duration = -- cgit v1.2.3 From f6051d71d62dd5b98daad44422867345685ed427 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 23:37:15 +0800 Subject: no point to set duration while not started yet --- app/models/ci/pipeline.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 9db0f139162..9799c06c19f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -258,6 +258,8 @@ module Ci end def update_duration + return unless started_at + calculated_status = %w[success failed running canceled] calculated_builds = builds.latest.where(status: calculated_status) calculator = Gitlab::Ci::PipelineDuration.from_builds(calculated_builds) -- cgit v1.2.3 From e9e7c3788d1b2061e321032db4c72178f21ca6bd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 29 Aug 2016 23:40:15 +0800 Subject: no builds no pending --- lib/gitlab/ci/pipeline_duration.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index e4c0be3b640..b9d0006182e 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -82,6 +82,8 @@ module Gitlab end def process_pending_duration(segments, duration) + return 0 if segments.empty? + total = segments.last.last - segments.first.first total - duration end -- cgit v1.2.3 From 7e32e2ef20feb9c2bbc1b1504cc56c9185621c86 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 30 Aug 2016 02:53:53 +0800 Subject: build might not start yet --- lib/gitlab/ci/pipeline_duration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index b9d0006182e..03bf7fabc99 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -12,7 +12,7 @@ module Gitlab now = Time.now segments = builds.map do |b| - Segment.new(b.started_at, b.finished_at || now) + Segment.new(b.started_at || now, b.finished_at || now) end new(segments) -- cgit v1.2.3 From 031b162392c074b0bff1a5d4239acd8026592fdd Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 30 Aug 2016 02:54:07 +0800 Subject: Fix test for Pipeline#duration --- spec/models/ci/pipeline_spec.rb | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 721b20e0cb2..a34b9cb4461 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -124,17 +124,22 @@ describe Ci::Pipeline, models: true do describe 'state machine' do let(:current) { Time.now.change(usec: 0) } - let(:build) { create :ci_build, name: 'build1', pipeline: pipeline } + let(:build) { create_build('build1', current - 60) } + let(:another_build) { create_build('build2', current + 20) } describe '#duration' do before do - travel_to(current - 120) do - pipeline.run - end + pipeline.run travel_to(current) do - pipeline.succeed + build.success + end + + travel_to(current + 80) do + another_build.drop end + + pipeline.drop end it 'matches sum of builds duration' do @@ -169,6 +174,13 @@ describe Ci::Pipeline, models: true do expect(pipeline.reload.finished_at).to be_nil end end + + def create_build(name, started_at = current) + create(:ci_build, + name: name, + pipeline: pipeline, + started_at: started_at) + end end describe '#branch?' do -- cgit v1.2.3 From bd78e6af297c59d220090a037891003ec2571e22 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 30 Aug 2016 23:02:39 +0800 Subject: Use algorithm from Kamil: Excluding sorting, this is O(n) which should be much faster and much simpler and easier to understand. --- lib/gitlab/ci/pipeline_duration.rb | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 03bf7fabc99..ff6dcdbfa31 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -37,41 +37,22 @@ module Gitlab if segments.empty? segments else - segments[1..-1].inject([segments.first]) do |current, target| - left, result = insert_segment(current, target) + segments.drop(1).inject([segments.first]) do |result, current| + merged = try_merge_segment(result.last, current) - if left # left is the latest one - result << left - else + if merged + result[-1] = merged result + else + result << current end end end end - def insert_segment(segments, init) - segments.inject([init, []]) do |target_result, member| - target, result = target_result - - if target.nil? # done - result << member - [nil, result] - elsif merged = try_merge_segment(target, member) # overlapped - [merged, result] # merge and keep finding the hole - elsif target.last < member.first # found the hole - result << target << member - [nil, result] - else - result << member - target_result - end - end - end - - def try_merge_segment(target, member) - if target.first <= member.last && target.last >= member.first - Segment.new([target.first, member.first].min, - [target.last, member.last].max) + def try_merge_segment(previous, current) + if current.first <= previous.last + Segment.new(previous.first, [previous.last, current.last].max) end end -- cgit v1.2.3 From 1f6efa352ce8cabc1d733cc54420daa86a7d22c0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 30 Aug 2016 23:06:29 +0800 Subject: Rename to periods since it's easier to understand --- lib/gitlab/ci/pipeline_duration.rb | 44 ++++++++++++++-------------- spec/lib/gitlab/ci/pipeline_duration_spec.rb | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index ff6dcdbfa31..490e8f34cff 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -1,8 +1,8 @@ module Gitlab module Ci class PipelineDuration - SegmentStruct = Struct.new(:first, :last) - class Segment < SegmentStruct + PeriodStruct = Struct.new(:first, :last) + class Period < SegmentStruct def duration last - first end @@ -11,34 +11,34 @@ module Gitlab def self.from_builds(builds) now = Time.now - segments = builds.map do |b| - Segment.new(b.started_at || now, b.finished_at || now) + periods = builds.map do |b| + Period.new(b.started_at || now, b.finished_at || now) end - new(segments) + new(periods) end attr_reader :duration, :pending_duration - def initialize(segments) - process(segments.sort_by(&:first)) + def initialize(periods) + process(periods.sort_by(&:first)) end private - def process(segments) - merged = process_segments(segments) + def process(periods) + merged = process_periods(periods) @duration = process_duration(merged) @pending_duration = process_pending_duration(merged, @duration) end - def process_segments(segments) - if segments.empty? - segments + def process_periods(periods) + if periods.empty? + periods else - segments.drop(1).inject([segments.first]) do |result, current| - merged = try_merge_segment(result.last, current) + periods.drop(1).inject([periods.first]) do |result, current| + merged = try_merge_period(result.last, current) if merged result[-1] = merged @@ -50,22 +50,22 @@ module Gitlab end end - def try_merge_segment(previous, current) + def try_merge_period(previous, current) if current.first <= previous.last - Segment.new(previous.first, [previous.last, current.last].max) + Period.new(previous.first, [previous.last, current.last].max) end end - def process_duration(segments) - segments.inject(0) do |result, seg| - result + seg.duration + def process_duration(periods) + periods.inject(0) do |result, per| + result + per.duration end end - def process_pending_duration(segments, duration) - return 0 if segments.empty? + def process_pending_duration(periods, duration) + return 0 if periods.empty? - total = segments.last.last - segments.first.first + total = periods.last.last - periods.first.first total - duration end end diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb index 7ac9a3bd6bc..5cd0727f46b 100644 --- a/spec/lib/gitlab/ci/pipeline_duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb @@ -87,7 +87,7 @@ describe Gitlab::Ci::PipelineDuration do def create_calculator(data) segments = data.shuffle.map do |(first, last)| - Gitlab::Ci::PipelineDuration::Segment.new(first, last) + Gitlab::Ci::PipelineDuration::Period.new(first, last) end Gitlab::Ci::PipelineDuration.new(segments) -- cgit v1.2.3 From 2af2b78e49c2089386919bc7a9d155fed244ec80 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 30 Aug 2016 23:08:25 +0800 Subject: Avoid shadowing method name. Just use existing method --- lib/gitlab/ci/pipeline_duration.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 490e8f34cff..d8fb05724bc 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -30,7 +30,7 @@ module Gitlab merged = process_periods(periods) @duration = process_duration(merged) - @pending_duration = process_pending_duration(merged, @duration) + @pending_duration = process_pending_duration(merged) end def process_periods(periods) @@ -62,7 +62,7 @@ module Gitlab end end - def process_pending_duration(periods, duration) + def process_pending_duration(periods) return 0 if periods.empty? total = periods.last.last - periods.first.first -- cgit v1.2.3 From f10a1e331dc83fe3c973283d9f9a50f75c59d2d5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 30 Aug 2016 23:11:57 +0800 Subject: Fix renaming --- lib/gitlab/ci/pipeline_duration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index d8fb05724bc..55d870e2e9d 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -2,7 +2,7 @@ module Gitlab module Ci class PipelineDuration PeriodStruct = Struct.new(:first, :last) - class Period < SegmentStruct + class Period < PeriodStruct def duration last - first end -- cgit v1.2.3 From 4789adc565b08ed4979ef3b876b2fbd6224fa084 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 30 Aug 2016 23:12:55 +0800 Subject: Add test cases from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14709761 --- spec/lib/gitlab/ci/pipeline_duration_spec.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb index 5cd0727f46b..21d5a5c96a2 100644 --- a/spec/lib/gitlab/ci/pipeline_duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb @@ -85,6 +85,34 @@ describe Gitlab::Ci::PipelineDuration do it_behaves_like 'calculating duration' end + context 'test sample F' do + let(:data) do + [[1, 3], + [2, 4], + [2, 4], + [2, 4], + [5, 8]] + end + + let(:duration) { 6 } + let(:pending_duration) { 1 } + + it_behaves_like 'calculating duration' + end + + context 'test sample G' do + let(:data) do + [[1, 3], + [2, 4], + [6, 7]] + end + + let(:duration) { 4 } + let(:pending_duration) { 2 } + + it_behaves_like 'calculating duration' + end + def create_calculator(data) segments = data.shuffle.map do |(first, last)| Gitlab::Ci::PipelineDuration::Period.new(first, last) -- cgit v1.2.3 From 7dcc2446077084c0cbce92bc5b1279984cc75920 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 31 Aug 2016 00:49:10 +0800 Subject: It's renamed to periods --- spec/lib/gitlab/ci/pipeline_duration_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb index 21d5a5c96a2..acb690581d6 100644 --- a/spec/lib/gitlab/ci/pipeline_duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb @@ -114,10 +114,10 @@ describe Gitlab::Ci::PipelineDuration do end def create_calculator(data) - segments = data.shuffle.map do |(first, last)| + periods = data.shuffle.map do |(first, last)| Gitlab::Ci::PipelineDuration::Period.new(first, last) end - Gitlab::Ci::PipelineDuration.new(segments) + Gitlab::Ci::PipelineDuration.new(periods) end end -- cgit v1.2.3 From 09a53359e4847dfd146141cc3672d48f6b1df233 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 31 Aug 2016 00:51:24 +0800 Subject: Add CHANGELOG entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index d06fc24d40a..7aab8715801 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.12.0 (unreleased) - Remove Gitorious import - Add Sentry logging to API calls - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 - Fix groups sort dropdown alignment (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Fix markdown help references (ClemMakesApps) -- cgit v1.2.3 From 1e49a8bc6cd593910577a0f09ea13f0c933de1e9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 31 Aug 2016 03:09:16 +0800 Subject: Introduction to PipelineDuration --- lib/gitlab/ci/pipeline_duration.rb | 98 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 55d870e2e9d..f97727e8548 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -1,5 +1,103 @@ module Gitlab module Ci + # The problem this class is trying to solve is finding the total running + # time amongst all the jobs, excluding retries and pending (queue) time. + # We could reduce this problem down to finding the union of periods. + # + # So each job would be represented as a `Period`, which consists of + # `Period#first` and `Period#last`. A simple example here would be: + # + # * A (1, 3) + # * B (2, 4) + # * C (6, 7) + # + # Here A begins from 1, and ends to 3. B begins from 2, and ends to 4. + # C begins from 6, and ends to 7. Visually it could be viewed as: + # + # 0 1 2 3 4 5 6 7 + # AAAAAAA + # BBBBBBB + # CCCC + # + # The union of A, B, and C would be (1, 4) and (6, 7), therefore the + # total running time should be: + # + # (4 - 1) + (7 - 6) => 4 + # + # And the pending (queue) time would be (4, 6) like this: (marked as X) + # + # 0 1 2 3 4 5 6 7 + # AAAAAAA + # BBBBBBB + # CCCC + # XXXXX + # + # Which could be calculated by having (1, 7) as total time, minus + # the running time we have above, 4. The full calculation would be: + # + # total = (7 - 1) + # duration = (4 - 1) + (7 - 6) + # pending = total - duration # 6 - 4 => 2 + # + # Which the answer to pending would be 2 in this example. + # + # The algorithm used here for union would be described as follow. + # First we make sure that all periods are sorted by `Period#first`. + # Then we try to merge periods by iterating through the first period + # to the last period. The goal would be merging all overlapped periods + # so that in the end all the periods are discrete. When all periods + # are discrete, we're free to just sum all the periods to get real + # running time. + # + # Here we begin from A, and compare it to B. We could find that + # before A ends, B already started. That is `B.first <= A.last` + # that is `2 <= 3` which means A and B are overlapping! + # + # When we found that two periods are overlapping, we would need to merge + # them into a new period and disregard the old periods. To make a new + # period, we take `A.first` as the new first because remember? we sorted + # them, so `A.first` must be smaller or equal to `B.first`. And we take + # `[A.last, B.last].max` as the new last because we want whoever ended + # later. This could be broken into two cases: + # + # 0 1 2 3 4 + # AAAAAAA + # BBBBBBB + # + # Or: + # + # 0 1 2 3 4 + # AAAAAAAAAA + # BBBB + # + # So that we need to take whoever ends later. Back to our example, + # after merging and discard A and B it could be visually viewed as: + # + # 0 1 2 3 4 5 6 7 + # DDDDDDDDDD + # CCCC + # + # Now we could go on and compare the newly created D and the old C. + # We could figure out that D and C are not overlapping by checking + # `C.first <= D.last` is `false`. Therefore we need to keep both C + # and D. The example would end here because there are no more jobs. + # + # After having the union of all periods, the rest is simple and + # described in the beginning. To summarise: + # + # duration = (4 - 1) + (7 - 6) + # total = (7 - 1) + # pending = total - duration # 6 - 4 => 2 + # + # Note that the pending time is actually not the final pending time + # for pipelines, because we still need to accumulate the pending time + # before the first job (A in this example) even started! That is: + # + # total_pending = pipeline.started_at - pipeline.created_at + pending + # + # Would be the final answer. We deal with that in pipeline itself + # but not here because here we try not to be depending on pipeline + # and it's trivial enough to get that information. class PipelineDuration PeriodStruct = Struct.new(:first, :last) class Period < PeriodStruct -- cgit v1.2.3 From d2cfcb3ec1327cd9dd901dcbe8c927e3c43cfb38 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 31 Aug 2016 16:48:29 +0800 Subject: Use guard clause, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14754681 --- lib/gitlab/ci/pipeline_duration.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index f97727e8548..e37ba19bca9 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -132,18 +132,16 @@ module Gitlab end def process_periods(periods) - if periods.empty? - periods - else - periods.drop(1).inject([periods.first]) do |result, current| - merged = try_merge_period(result.last, current) + return periods if periods.empty? - if merged - result[-1] = merged - result - else - result << current - end + periods.drop(1).inject([periods.first]) do |result, current| + merged = try_merge_period(result.last, current) + + if merged + result[-1] = merged + result + else + result << current end end end -- cgit v1.2.3 From e7b0334cfb13d42c20f704cd02eafc0f97c9d7ed Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 31 Aug 2016 09:32:48 -0500 Subject: Add textarea autoresize after comment --- CHANGELOG | 1 + app/assets/javascripts/notes.js | 7 +++- spec/javascripts/fixtures/comments.html.haml | 21 ++++++++++ spec/javascripts/notes_spec.js | 57 ++++++++++++++++++++++------ 4 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 spec/javascripts/fixtures/comments.html.haml diff --git a/CHANGELOG b/CHANGELOG index 9837b2edb9d..a56138986e6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.12.0 (unreleased) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps) + - Add textarea autoresize after comment (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) - Add white background for no readme container (ClemMakesApps) - API: Expose issue confidentiality flag. (Robert Schilling) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index d0d5cad813a..ebe9ff2b8c4 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -331,7 +331,12 @@ form.find(".js-md-write-button").click(); form.find(".js-note-text").val("").trigger("input"); form.find(".js-note-text").data("autosave").reset(); - return this.updateTargetButtons(e); + + var event = document.createEvent('Event'); + event.initEvent('autosize:update', true, false); + form.find('.js-autosize')[0].dispatchEvent(event); + + this.updateTargetButtons(e); }; Notes.prototype.reenableTargetFormSubmitButton = function() { diff --git a/spec/javascripts/fixtures/comments.html.haml b/spec/javascripts/fixtures/comments.html.haml new file mode 100644 index 00000000000..cc1f8f15c21 --- /dev/null +++ b/spec/javascripts/fixtures/comments.html.haml @@ -0,0 +1,21 @@ +.flash-container.timeline-content +.timeline-icon.hidden-xs.hidden-sm + %a.author_link + %img +.timeline-content.timeline-content-form + %form.new-note.js-quick-submit.common-note-form.gfm-form.js-main-target-form + .md-area + .md-header + .md-write-holder + .zen-backdrop.div-dropzone-wrapper + .div-dropzone-wrapper + .div-dropzone.dz-clickable + %textarea.note-textarea.js-note-text.js-gfm-input.js-autosize.markdown-area + .note-form-actions.clearfix + %input.btn.btn-nr.btn-create.append-right-10.comment-btn.js-comment-button{ type: 'submit' } + %a.btn.btn-nr.btn-reopen.btn-comment.js-note-target-reopen + Reopen issue + %a.btn.btn-nr.btn-close.btn-comment.js-note-target-close + Close issue + %a.btn.btn-cancel.js-note-discard + Discard draft \ No newline at end of file diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 14dc6bfdfde..a588f403dd5 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -1,8 +1,7 @@ - /*= require notes */ - - +/*= require autosize */ /*= require gl_form */ +/*= require lib/utils/text_utility */ (function() { window.gon || (window.gon = {}); @@ -12,29 +11,63 @@ }; describe('Notes', function() { - return describe('task lists', function() { + describe('task lists', function() { fixture.preload('issue_note.html'); + beforeEach(function() { fixture.load('issue_note.html'); $('form').on('submit', function(e) { - return e.preventDefault(); + e.preventDefault(); }); - return this.notes = new Notes(); + this.notes = new Notes(); }); + it('modifies the Markdown field', function() { $('input[type=checkbox]').attr('checked', true).trigger('change'); - return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item'); + expect($('.js-task-list-field').val()).toBe('- [x] Task List Item'); }); - return it('submits the form on tasklist:changed', function() { - var submitted; - submitted = false; + + it('submits the form on tasklist:changed', function() { + var submitted = false; $('form').on('submit', function(e) { submitted = true; - return e.preventDefault(); + e.preventDefault(); }); + $('.js-task-list-field').trigger('tasklist:changed'); - return expect(submitted).toBe(true); + expect(submitted).toBe(true); + }); + }); + + describe('comments', function() { + var commentsTemplate = 'comments.html'; + var textarea = '.js-note-text'; + fixture.preload(commentsTemplate); + + beforeEach(function() { + fixture.load(commentsTemplate); + this.notes = new Notes(); + + this.autoSizeSpy = spyOnEvent($(textarea), 'autosize:update'); + spyOn(this.notes, 'renderNote').and.stub(); + + $(textarea).data('autosave', { + reset: function() {} + }); + + $('form').on('submit', function(e) { + e.preventDefault(); + $('.js-main-target-form').trigger('ajax:success'); + }); }); + + it('autosizes after comment submission', function() { + $(textarea).text('This is an example comment note'); + expect(this.autoSizeSpy).not.toHaveBeenTriggered(); + + $('.js-comment-button').click(); + expect(this.autoSizeSpy).toHaveBeenTriggered(); + }) }); }); -- cgit v1.2.3 From 0c36469538631ed7729d1faae9c610d3453869b8 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 1 Sep 2016 18:20:40 -0500 Subject: Update templates. --- vendor/gitignore/Global/NetBeans.gitignore | 1 - vendor/gitignore/Global/OSX.gitignore | 25 ------------------------- vendor/gitignore/Global/Tags.gitignore | 1 + vendor/gitignore/Global/macOS.gitignore | 26 ++++++++++++++++++++++++++ vendor/gitignore/Haskell.gitignore | 1 + vendor/gitignore/Joomla.gitignore | 16 ++++++++++++++++ vendor/gitignore/Node.gitignore | 3 +++ vendor/gitignore/Objective-C.gitignore | 2 ++ vendor/gitignore/Python.gitignore | 1 + vendor/gitignore/Rails.gitignore | 6 ++++-- vendor/gitignore/VisualStudio.gitignore | 7 +++++++ vendor/gitlab-ci-yml/Docker.gitlab-ci.yml | 7 ++++++- vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml | 9 +++++++++ vendor/gitlab-ci-yml/Swift.gitlab-ci.yml | 30 ++++++++++++++++++++++++++++++ 14 files changed, 106 insertions(+), 29 deletions(-) delete mode 100644 vendor/gitignore/Global/OSX.gitignore create mode 100644 vendor/gitignore/Global/macOS.gitignore create mode 100644 vendor/gitlab-ci-yml/Swift.gitlab-ci.yml diff --git a/vendor/gitignore/Global/NetBeans.gitignore b/vendor/gitignore/Global/NetBeans.gitignore index 520d91ff584..254108cd23b 100644 --- a/vendor/gitignore/Global/NetBeans.gitignore +++ b/vendor/gitignore/Global/NetBeans.gitignore @@ -3,5 +3,4 @@ build/ nbbuild/ dist/ nbdist/ -nbactions.xml .nb-gradle/ diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/OSX.gitignore deleted file mode 100644 index 5972fe50f66..00000000000 --- a/vendor/gitignore/Global/OSX.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk diff --git a/vendor/gitignore/Global/Tags.gitignore b/vendor/gitignore/Global/Tags.gitignore index c0318165a27..91927af4cd6 100644 --- a/vendor/gitignore/Global/Tags.gitignore +++ b/vendor/gitignore/Global/Tags.gitignore @@ -9,6 +9,7 @@ gtags.files GTAGS GRTAGS GPATH +GSYMS cscope.files cscope.out cscope.in.out diff --git a/vendor/gitignore/Global/macOS.gitignore b/vendor/gitignore/Global/macOS.gitignore new file mode 100644 index 00000000000..828a509a137 --- /dev/null +++ b/vendor/gitignore/Global/macOS.gitignore @@ -0,0 +1,26 @@ +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore index a4ee41ab62b..450f32ec40c 100644 --- a/vendor/gitignore/Haskell.gitignore +++ b/vendor/gitignore/Haskell.gitignore @@ -17,3 +17,4 @@ cabal.sandbox.config *.eventlog .stack-work/ cabal.project.local +.HTF/ diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore index 0d7a0de298f..93103fdbe77 100644 --- a/vendor/gitignore/Joomla.gitignore +++ b/vendor/gitignore/Joomla.gitignore @@ -52,6 +52,7 @@ /administrator/language/en-GB/en-GB.plg_content_contact.sys.ini /administrator/language/en-GB/en-GB.plg_content_finder.ini /administrator/language/en-GB/en-GB.plg_content_finder.sys.ini +/administrator/language/en-GB/en-GB.plg_editors-xtd_module* /administrator/language/en-GB/en-GB.plg_finder_categories.ini /administrator/language/en-GB/en-GB.plg_finder_categories.sys.ini /administrator/language/en-GB/en-GB.plg_finder_contacts.ini @@ -64,6 +65,10 @@ /administrator/language/en-GB/en-GB.plg_finder_tags.sys.ini /administrator/language/en-GB/en-GB.plg_finder_weblinks.ini /administrator/language/en-GB/en-GB.plg_finder_weblinks.sys.ini +/administrator/language/en-GB/en-GB.plg_installer_folderinstaller* +/administrator/language/en-GB/en-GB.plg_installer_packageinstaller* +/administrator/language/en-GB/en-GB.plg_installer_packageinstaller +/administrator/language/en-GB/en-GB.plg_installer_urlinstaller* /administrator/language/en-GB/en-GB.plg_installer_webinstaller.ini /administrator/language/en-GB/en-GB.plg_installer_webinstaller.sys.ini /administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.ini @@ -72,6 +77,8 @@ /administrator/language/en-GB/en-GB.plg_search_tags.sys.ini /administrator/language/en-GB/en-GB.plg_system_languagecode.ini /administrator/language/en-GB/en-GB.plg_system_languagecode.sys.ini +/administrator/language/en-GB/en-GB.plg_system_stats* +/administrator/language/en-GB/en-GB.plg_system_updatenotification* /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.ini /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.sys.ini /administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.ini @@ -249,8 +256,10 @@ /administrator/language/en-GB/en-GB.tpl_hathor.sys.ini /administrator/language/en-GB/en-GB.xml /administrator/language/en-GB/index.html +/administrator/language/ru-RU/index.html /administrator/language/overrides/* /administrator/language/index.html +/administrator/logs/index.html /administrator/manifests/* /administrator/modules/mod_custom/* /administrator/modules/mod_feed/* @@ -289,6 +298,7 @@ /components/com_finder/* /components/com_mailto/* /components/com_media/* +/components/com_modules/* /components/com_newsfeeds/* /components/com_search/* /components/com_users/* @@ -407,6 +417,7 @@ /libraries/idna_convert/* /libraries/joomla/* /libraries/legacy/* +/libraries/php-encryption/* /libraries/phpass/* /libraries/phpmailer/* /libraries/phputf8/* @@ -431,9 +442,11 @@ /media/media/* /media/mod_languages/* /media/overrider/* +/media/plg_captcha_recaptcha/* /media/plg_quickicon_extensionupdate/* /media/plg_quickicon_joomlaupdate/* /media/plg_system_highlight/* +/media/plg_system_stats/* /media/system/* /media/index.html /modules/mod_articles_archive/* @@ -486,6 +499,7 @@ /plugins/editors/none/* /plugins/editors/tinymce/* /plugins/editors/index.html +/plugins/editors-xtd/module/* /plugins/editors-xtd/article/* /plugins/editors-xtd/image/* /plugins/editors-xtd/pagebreak/* @@ -523,6 +537,8 @@ /plugins/system/redirect/* /plugins/system/remember/* /plugins/system/sef/* +/plugins/system/stats/* +/plugins/system/updatenotification/* /plugins/system/index.html /plugins/twofactorauth/* /plugins/user/contactcreator/* diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index aea5294de9d..bf7525f9912 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -34,5 +34,8 @@ jspm_packages # Optional npm cache directory .npm +# Optional eslint cache +.eslintcache + # Optional REPL history .node_repl_history diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore index 20592083931..58c51ecaed4 100644 --- a/vendor/gitignore/Objective-C.gitignore +++ b/vendor/gitignore/Objective-C.gitignore @@ -50,7 +50,9 @@ Carthage/Build # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md fastlane/report.xml +fastlane/Preview.html fastlane/screenshots +fastlane/test_output # Code Injection # diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore index 72364f99fe4..37fc9d40817 100644 --- a/vendor/gitignore/Python.gitignore +++ b/vendor/gitignore/Python.gitignore @@ -79,6 +79,7 @@ celerybeat-schedule .env # virtualenv +.venv/ venv/ ENV/ diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore index d8c256c1925..e97427608c1 100644 --- a/vendor/gitignore/Rails.gitignore +++ b/vendor/gitignore/Rails.gitignore @@ -12,9 +12,11 @@ capybara-*.html rerun.txt pickle-email-*.html -# TODO Comment out these rules if you are OK with secrets being uploaded to the repo +# TODO Comment out this rule if you are OK with secrets being uploaded to the repo config/initializers/secret_token.rb -config/secrets.yml + +# Only include if you have production secrets in this file, which is no longer a Rails default +# config/secrets.yml # dotenv # TODO Comment out this rule if environment variables can be committed diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index 67acbf42f5e..d56f8b53288 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -251,3 +251,10 @@ paket-files/ # JetBrains Rider .idea/ *.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml index 396d3f1b042..f3fa3949656 100644 --- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml @@ -1,7 +1,12 @@ # Official docker image. image: docker:latest +services: + - docker:dind + build: stage: build script: - - docker build -t test . + - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" $CI_REGISTRY + - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME" . + - docker push "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME" diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml index 166f146ee05..08b57c8c0ac 100644 --- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -43,3 +43,12 @@ rails: - bundle exec rake db:migrate - bundle exec rake db:seed - bundle exec rake test + +# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk +# are supported too: https://github.com/travis-ci/dpl +deploy: + type: deploy + environment: production + script: + - gem install dpl + - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY diff --git a/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml new file mode 100644 index 00000000000..c9c35906d1c --- /dev/null +++ b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml @@ -0,0 +1,30 @@ +# Lifted from: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/ +# This file assumes an own GitLab CI runner, setup on an OS X system. +stages: + - build + - archive + +build_project: + stage: build + script: + - xcodebuild clean -project ProjectName.xcodeproj -scheme SchemeName | xcpretty + - xcodebuild test -project ProjectName.xcodeproj -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.2' | xcpretty -s + tags: + - ios_9-2 + - xcode_7-2 + - osx_10-11 + +archive_project: + stage: archive + script: + - xcodebuild clean archive -archivePath build/ProjectName -scheme SchemeName + - xcodebuild -exportArchive -exportFormat ipa -archivePath "build/ProjectName.xcarchive" -exportPath "build/ProjectName.ipa" -exportProvisioningProfile "ProvisioningProfileName" + only: + - master + artifacts: + paths: + - build/ProjectName.ipa + tags: + - ios_9-2 + - xcode_7-2 + - osx_10-11 -- cgit v1.2.3 From 9224f031cfcb8283566f9d1dcc336ae644faf062 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Fri, 19 Aug 2016 18:51:56 -0700 Subject: Pass the remember_me option into the u2f form and support it while authenticating Matches the changes done for non-u2f two-factor auth --- CHANGELOG | 1 + .../concerns/authenticates_with_two_factor.rb | 1 + app/views/devise/sessions/two_factor.html.haml | 3 +-- app/views/u2f/_authenticate.html.haml | 2 ++ spec/controllers/sessions_controller_spec.rb | 23 ++++++++++++++++++++++ spec/features/u2f_spec.rb | 14 +++++++++++++ .../fixtures/u2f/authenticate.html.haml | 2 +- 7 files changed, 43 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..98aac94917e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.12.0 (unreleased) - Filter tags by name !6121 - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 + - Pass the "Remember me" value to the U2F authentication form - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) - Add font color contrast to external label in admin area (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps) diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb index ba07cea569c..d5a8a962662 100644 --- a/app/controllers/concerns/authenticates_with_two_factor.rb +++ b/app/controllers/concerns/authenticates_with_two_factor.rb @@ -62,6 +62,7 @@ module AuthenticatesWithTwoFactor session.delete(:otp_user_id) session.delete(:challenges) + remember_me(user) if user_params[:remember_me] == '1' sign_in(user) else flash.now[:alert] = 'Authentication via U2F device failed.' diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 4debd3d608f..e623f7cff88 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -18,6 +18,5 @@ = f.submit "Verify code", class: "btn btn-save" - if @user.two_factor_u2f_enabled? - %hr - = render "u2f/authenticate" + = render "u2f/authenticate", locals: { params: params, resource: resource, resource_name: resource_name } diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml index 75fb0e303ad..9657101ace5 100644 --- a/app/views/u2f/_authenticate.html.haml +++ b/app/views/u2f/_authenticate.html.haml @@ -20,6 +20,8 @@ %div %p We heard back from your U2F device. Click this button to authenticate with the GitLab server. = form_tag(new_user_session_path, method: :post) do |f| + - resource_params = params[resource_name].presence || params + = hidden_field_tag 'user[remember_me]', resource_params.fetch(:remember_me, 0) = hidden_field_tag 'user[device_response]', nil, class: 'form-control', required: true, id: "js-device-response" = submit_tag "Authenticate via U2F Device", class: "btn btn-success" diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index 4e9bfb0c69b..8f27e616c3e 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -136,6 +136,29 @@ describe SessionsController do post(:create, { user: user_params }, { otp_user_id: user.id }) end + context 'remember_me field' do + it 'sets a remember_user_token cookie when enabled' do + allow(U2fRegistration).to receive(:authenticate).and_return(true) + allow(controller).to receive(:find_user).and_return(user) + expect(controller). + to receive(:remember_me).with(user).and_call_original + + authenticate_2fa_u2f(remember_me: '1', login: user.username, device_response: "{}") + + expect(response.cookies['remember_user_token']).to be_present + end + + it 'does nothing when disabled' do + allow(U2fRegistration).to receive(:authenticate).and_return(true) + allow(controller).to receive(:find_user).and_return(user) + expect(controller).not_to receive(:remember_me) + + authenticate_2fa_u2f(remember_me: '0', login: user.username, device_response: "{}") + + expect(response.cookies['remember_user_token']).to be_nil + end + end + it "creates an audit log record" do allow(U2fRegistration).to receive(:authenticate).and_return(true) expect { authenticate_2fa_u2f(login: user.username, device_response: "{}") }.to change { SecurityEvent.count }.by(1) diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index a46e48c76ed..ff6933dc8d9 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -156,6 +156,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: describe "when 2FA via OTP is disabled" do it "allows logging in with the U2F device" do + user.update_attribute(:otp_required_for_login, false) login_with(user) @u2f_device.respond_to_u2f_authentication @@ -181,6 +182,19 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: end end + it 'persists remember_me value via hidden field' do + login_with(user, remember: true) + + @u2f_device.respond_to_u2f_authentication + click_on "Login Via U2F Device" + expect(page.body).to match('We heard back from your U2F device') + + within 'div#js-authenticate-u2f' do + field = first('input#user_remember_me', visible: false) + expect(field.value).to eq '1' + end + end + describe "when a given U2F device has already been registered by another user" do describe "but not the current user" do it "does not allow logging in with that particular device" do diff --git a/spec/javascripts/fixtures/u2f/authenticate.html.haml b/spec/javascripts/fixtures/u2f/authenticate.html.haml index 859e79a6c9e..779d6429a5f 100644 --- a/spec/javascripts/fixtures/u2f/authenticate.html.haml +++ b/spec/javascripts/fixtures/u2f/authenticate.html.haml @@ -1 +1 @@ -= render partial: "u2f/authenticate", locals: { new_user_session_path: "/users/sign_in" } += render partial: "u2f/authenticate", locals: { new_user_session_path: "/users/sign_in", params: {}, resource_name: "user" } -- cgit v1.2.3 From 39c090fecbdce0031d789a2da5826d25a59c73a4 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Sep 2016 19:15:42 +0800 Subject: Calculate real queueing time to exclude gaps from: retries and probably also manual actions! Feedback: * https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14735478 * https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14737804 --- app/models/ci/pipeline.rb | 9 ++------- lib/gitlab/ci/pipeline_duration.rb | 28 ++++++++++++++-------------- spec/lib/gitlab/ci/pipeline_duration_spec.rb | 8 -------- spec/models/ci/pipeline_spec.rb | 27 ++++++++++++++++++--------- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 9799c06c19f..b7d4b492a75 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -260,13 +260,8 @@ module Ci def update_duration return unless started_at - calculated_status = %w[success failed running canceled] - calculated_builds = builds.latest.where(status: calculated_status) - calculator = Gitlab::Ci::PipelineDuration.from_builds(calculated_builds) - - self.duration = calculator.duration - self.pending_duration = - started_at - created_at + calculator.pending_duration + self.duration, self.pending_duration = + Gitlab::Ci::PipelineDuration.from_pipeline(self) end def execute_hooks diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index e37ba19bca9..baf84954b7e 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -106,17 +106,27 @@ module Gitlab end end - def self.from_builds(builds) + def self.from_pipeline(pipeline) + status = %w[success failed running canceled] + builds = pipeline.builds.latest.where(status: status) + + duration = from_builds(builds, :started_at, :finished_at).duration + queued = from_builds(builds, :queued_at, :started_at).duration + + [duration, pipeline.started_at - pipeline.created_at + queued] + end + + def self.from_builds(builds, from, to) now = Time.now periods = builds.map do |b| - Period.new(b.started_at || now, b.finished_at || now) + Period.new(b.public_send(from) || now, b.public_send(to) || now) end new(periods) end - attr_reader :duration, :pending_duration + attr_reader :duration def initialize(periods) process(periods.sort_by(&:first)) @@ -125,10 +135,7 @@ module Gitlab private def process(periods) - merged = process_periods(periods) - - @duration = process_duration(merged) - @pending_duration = process_pending_duration(merged) + @duration = process_duration(process_periods(periods)) end def process_periods(periods) @@ -157,13 +164,6 @@ module Gitlab result + per.duration end end - - def process_pending_duration(periods) - return 0 if periods.empty? - - total = periods.last.last - periods.first.first - total - duration - end end end end diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb index acb690581d6..a160c0a6e39 100644 --- a/spec/lib/gitlab/ci/pipeline_duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb @@ -6,7 +6,6 @@ describe Gitlab::Ci::PipelineDuration do shared_examples 'calculating duration' do it do expect(calculator.duration).to eq(duration) - expect(calculator.pending_duration).to eq(pending_duration) end end @@ -19,7 +18,6 @@ describe Gitlab::Ci::PipelineDuration do end let(:duration) { 4 } - let(:pending_duration) { 2 } it_behaves_like 'calculating duration' end @@ -34,7 +32,6 @@ describe Gitlab::Ci::PipelineDuration do end let(:duration) { 4 } - let(:pending_duration) { 0 } it_behaves_like 'calculating duration' end @@ -48,7 +45,6 @@ describe Gitlab::Ci::PipelineDuration do end let(:duration) { 8 } - let(:pending_duration) { 1 } it_behaves_like 'calculating duration' end @@ -62,7 +58,6 @@ describe Gitlab::Ci::PipelineDuration do end let(:duration) { 4 } - let(:pending_duration) { 3 } it_behaves_like 'calculating duration' end @@ -80,7 +75,6 @@ describe Gitlab::Ci::PipelineDuration do end let(:duration) { 7 } - let(:pending_duration) { 2 } it_behaves_like 'calculating duration' end @@ -95,7 +89,6 @@ describe Gitlab::Ci::PipelineDuration do end let(:duration) { 6 } - let(:pending_duration) { 1 } it_behaves_like 'calculating duration' end @@ -108,7 +101,6 @@ describe Gitlab::Ci::PipelineDuration do end let(:duration) { 4 } - let(:pending_duration) { 2 } it_behaves_like 'calculating duration' end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index a34b9cb4461..2d239caab64 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -124,18 +124,23 @@ describe Ci::Pipeline, models: true do describe 'state machine' do let(:current) { Time.now.change(usec: 0) } - let(:build) { create_build('build1', current - 60) } - let(:another_build) { create_build('build2', current + 20) } + let(:build) { create_build('build1', current, 10) } + let(:another_build) { create_build('build2', current + 80, 5) } - describe '#duration' do + describe '#duration and #pending_duration' do before do - pipeline.run + pipeline.update(created_at: current) - travel_to(current) do + travel_to(current + 5) do + pipeline.run + pipeline.save + end + + travel_to(current + 70) do build.success end - travel_to(current + 80) do + travel_to(current + 145) do another_build.drop end @@ -143,7 +148,10 @@ describe Ci::Pipeline, models: true do end it 'matches sum of builds duration' do - expect(pipeline.reload.duration).to eq(120) + pipeline.reload + + expect(pipeline.duration).to eq(70 - 10 + 145 - 85) + expect(pipeline.pending_duration).to eq(5 + 10 + 5) end end @@ -175,11 +183,12 @@ describe Ci::Pipeline, models: true do end end - def create_build(name, started_at = current) + def create_build(name, queued_at = current, started_from = 0) create(:ci_build, name: name, pipeline: pipeline, - started_at: started_at) + queued_at: queued_at, + started_at: queued_at + started_from) end end -- cgit v1.2.3 From 30b2eec09aa2a57955dbbf99c0e0c1cac82c6386 Mon Sep 17 00:00:00 2001 From: Bryce Date: Tue, 30 Aug 2016 14:41:38 +0200 Subject: Give project selection dropdowns responsive width, long-names wrap. --- CHANGELOG | 1 + app/assets/stylesheets/pages/projects.scss | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 934dabe743a..c0bb991ec47 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Filter tags by name !6121 + - Give project selection dropdowns responsive width, make non-wrapping. - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index f2db373da52..3e6e50375f6 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -723,9 +723,15 @@ pre.light-well { } } -.project-refs-form { - .dropdown-menu { - width: 300px; +.project-refs-form .dropdown-menu, .dropdown-menu-projects { + width: 300px; + + @media (min-width: $screen-sm-min) { + width: 500px; + } + + a { + white-space: normal; } } -- cgit v1.2.3 From 1ccb578ec89df5d69c997920fd512a1db44e309d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Sep 2016 13:00:56 +0000 Subject: Update schema for pending_duration --- db/schema.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/schema.rb b/db/schema.rb index af6e74a4e25..f57c4149e6c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -210,6 +210,7 @@ ActiveRecord::Schema.define(version: 20160831223750) do t.datetime "finished_at" t.integer "duration" t.integer "user_id" + t.integer "pending_duration" end add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree -- cgit v1.2.3 From defd8899056c26593939243b05c365605ac06fdc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Sep 2016 21:08:26 +0800 Subject: Actually we still need to use total - running to get: real pending time, because that's actually by definition, (the time that it's not running!) or we could end up with awfully complicated algorithm :( --- lib/gitlab/ci/pipeline_duration.rb | 7 ++++--- spec/models/ci/pipeline_spec.rb | 17 +++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index baf84954b7e..0c7c38e2b67 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -110,10 +110,11 @@ module Gitlab status = %w[success failed running canceled] builds = pipeline.builds.latest.where(status: status) - duration = from_builds(builds, :started_at, :finished_at).duration - queued = from_builds(builds, :queued_at, :started_at).duration + running = from_builds(builds, :started_at, :finished_at).duration + total = from_builds(builds, :queued_at, :finished_at).duration + pending = pipeline.started_at - pipeline.created_at - [duration, pipeline.started_at - pipeline.created_at + queued] + [running, pending + total - running] end def self.from_builds(builds, from, to) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 68a88217afe..4615daa6551 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -125,7 +125,8 @@ describe Ci::Pipeline, models: true do describe 'state machine' do let(:current) { Time.now.change(usec: 0) } let(:build) { create_build('build1', current, 10) } - let(:another_build) { create_build('build2', current + 80, 5) } + let(:build_b) { create_build('build2', current, 20) } + let(:build_c) { create_build('build3', current + 40, 20) } describe '#duration and #pending_duration' do before do @@ -136,12 +137,16 @@ describe Ci::Pipeline, models: true do pipeline.save end - travel_to(current + 70) do + travel_to(current + 30) do build.success end - travel_to(current + 145) do - another_build.drop + travel_to(current + 40) do + build_b.drop + end + + travel_to(current + 70) do + build_c.success end pipeline.drop @@ -150,8 +155,8 @@ describe Ci::Pipeline, models: true do it 'matches sum of builds duration' do pipeline.reload - expect(pipeline.duration).to eq(70 - 10 + 145 - 85) - expect(pipeline.pending_duration).to eq(5 + 10 + 5) + expect(pipeline.duration).to eq(40) + expect(pipeline.pending_duration).to eq(35) end end -- cgit v1.2.3 From 8d96062f2386a4e9c30631c35cb41f827cc25beb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Sep 2016 21:16:40 +0800 Subject: Make sure the algorithm did exclude gaps --- spec/models/ci/pipeline_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 4615daa6551..2c04ef298bc 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -126,7 +126,7 @@ describe Ci::Pipeline, models: true do let(:current) { Time.now.change(usec: 0) } let(:build) { create_build('build1', current, 10) } let(:build_b) { create_build('build2', current, 20) } - let(:build_c) { create_build('build3', current + 40, 20) } + let(:build_c) { create_build('build3', current + 50, 10) } describe '#duration and #pending_duration' do before do @@ -156,7 +156,7 @@ describe Ci::Pipeline, models: true do pipeline.reload expect(pipeline.duration).to eq(40) - expect(pipeline.pending_duration).to eq(35) + expect(pipeline.pending_duration).to eq(25) end end -- cgit v1.2.3 From 315e6392cd5f4e35fd44d0d24637ea377604b754 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Sep 2016 14:33:24 +0100 Subject: Hides merge request button on branches page If user does not have the correct permissions, the merge request button is hidden Closes #21805 --- CHANGELOG | 1 + app/views/projects/branches/_branch.html.haml | 5 +-- spec/features/projects/branches_spec.rb | 48 +++++++++++++++++---------- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0eb076fd62f..e91bc0b9b17 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.12.0 (unreleased) - Shorten task status phrase (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) + - Hides merge request button on branches page is user doesn't have permissions - Add white background for no readme container (ClemMakesApps) - API: Expose issue confidentiality flag. (Robert Schilling) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 808e6b95746..5217b8bf028 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -3,6 +3,7 @@ - diverging_commit_counts = @repository.diverging_commit_counts(branch) - number_commits_behind = diverging_commit_counts[:behind] - number_commits_ahead = diverging_commit_counts[:ahead] +- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) %li(class="js-branch-#{branch.name}") %div = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do @@ -19,12 +20,12 @@ %i.fa.fa-lock protected .controls.hidden-xs - - if create_mr_button?(@repository.root_ref, branch.name) + - if merge_project && create_mr_button?(@repository.root_ref, branch.name) = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do Merge Request - if branch.name != @repository.root_ref - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-default', method: :post, title: "Compare" do + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: "btn btn-default #{'prepend-left-10' unless merge_project}", method: :post, title: "Compare" do Compare = render 'projects/buttons/download', project: @project, ref: branch.name diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 1b14945bf0a..d26a0caf036 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -1,32 +1,46 @@ require 'spec_helper' describe 'Branches', feature: true do - let(:project) { create(:project) } + let(:project) { create(:project, :public) } let(:repository) { project.repository } - before do - login_as :user - project.team << [@user, :developer] - end + context 'logged in' do + before do + login_as :user + project.team << [@user, :developer] + end - describe 'Initial branches page' do - it 'shows all the branches' do - visit namespace_project_branches_path(project.namespace, project) + describe 'Initial branches page' do + it 'shows all the branches' do + visit namespace_project_branches_path(project.namespace, project) - repository.branches { |branch| expect(page).to have_content("#{branch.name}") } - expect(page).to have_content("Protected branches can be managed in project settings") + repository.branches { |branch| expect(page).to have_content("#{branch.name}") } + expect(page).to have_content("Protected branches can be managed in project settings") + end + end + + describe 'Find branches' do + it 'shows filtered branches', js: true do + visit namespace_project_branches_path(project.namespace, project) + + fill_in 'branch-search', with: 'fix' + find('#branch-search').native.send_keys(:enter) + + expect(page).to have_content('fix') + expect(find('.all-branches')).to have_selector('li', count: 1) + end end end - describe 'Find branches' do - it 'shows filtered branches', js: true do + context 'logged out' do + before do visit namespace_project_branches_path(project.namespace, project) + end - fill_in 'branch-search', with: 'fix' - find('#branch-search').native.send_keys(:enter) - - expect(page).to have_content('fix') - expect(find('.all-branches')).to have_selector('li', count: 1) + it 'does not show merge request button' do + page.within first('.all-branches li') do + expect(page).not_to have_content 'Merge Request' + end end end end -- cgit v1.2.3 From 7aaed299eb744e524a1597ceed923c14b556ef20 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Sep 2016 23:43:10 +0800 Subject: Just sum all the queuing time, indication for needing more runners --- lib/gitlab/ci/pipeline_duration.rb | 13 +++++++------ spec/models/ci/pipeline_spec.rb | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 0c7c38e2b67..ac68fd0272f 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -107,19 +107,20 @@ module Gitlab end def self.from_pipeline(pipeline) + now = Time.now status = %w[success failed running canceled] builds = pipeline.builds.latest.where(status: status) - running = from_builds(builds, :started_at, :finished_at).duration - total = from_builds(builds, :queued_at, :finished_at).duration + running = from_builds(builds, :started_at, :finished_at, now).duration pending = pipeline.started_at - pipeline.created_at + queuing = builds.inject(0) do |result, job| + result + ((job.started_at || now) - (job.queued_at || now)) + end - [running, pending + total - running] + [running, pending + queuing] end - def self.from_builds(builds, from, to) - now = Time.now - + def self.from_builds(builds, from, to, now = Time.now) periods = builds.map do |b| Period.new(b.public_send(from) || now, b.public_send(to) || now) end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 2c04ef298bc..194c3fc01f1 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -156,7 +156,7 @@ describe Ci::Pipeline, models: true do pipeline.reload expect(pipeline.duration).to eq(40) - expect(pipeline.pending_duration).to eq(25) + expect(pipeline.pending_duration).to eq(45) end end -- cgit v1.2.3 From 1750fe321052c96fb896bad3e51c1bbee74d6600 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 24 Aug 2016 13:46:07 +0200 Subject: Add issues filters reset btn (ES6-ified). (Also refactored checkChanged slightly.) --- CHANGELOG | 1 + app/assets/javascripts/issuable.js | 86 ---------------------- app/assets/javascripts/issuable.js.es6 | 101 ++++++++++++++++++++++++++ app/assets/stylesheets/framework/filters.scss | 4 + app/views/shared/issuable/_filter.html.haml | 3 + 5 files changed, 109 insertions(+), 86 deletions(-) delete mode 100644 app/assets/javascripts/issuable.js create mode 100644 app/assets/javascripts/issuable.js.es6 diff --git a/CHANGELOG b/CHANGELOG index e86eae09330..e48d0327f52 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -107,6 +107,7 @@ v 8.11.3 - Fix external issue tracker "Issues" link leading to 404s - Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Issues filters reset button v 8.11.2 - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js deleted file mode 100644 index d0305c6c6a1..00000000000 --- a/app/assets/javascripts/issuable.js +++ /dev/null @@ -1,86 +0,0 @@ -(function() { - var issuable_created; - - issuable_created = false; - - this.Issuable = { - init: function() { - Issuable.initTemplates(); - Issuable.initSearch(); - Issuable.initChecks(); - return Issuable.initLabelFilterRemove(); - }, - initTemplates: function() { - return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <%- label.title %> <% }); %>'); - }, - initSearch: function() { - this.timer = null; - return $('#issue_search').off('keyup').on('keyup', function() { - clearTimeout(this.timer); - return this.timer = setTimeout(function() { - var $form, $input, $search; - $search = $('#issue_search'); - $form = $('.js-filter-form'); - $input = $("input[name='" + ($search.attr('name')) + "']", $form); - if ($input.length === 0) { - $form.append(""); - } else { - $input.val($search.val()); - } - if ($search.val() !== '') { - return Issuable.filterResults($form); - } - }, 500); - }); - }, - initLabelFilterRemove: function() { - return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) { - var $button; - $button = $(this); - $('input[name="label_name[]"]').filter(function() { - return this.value === $button.data('label'); - }).remove(); - Issuable.filterResults($('.filter-form')); - return $('.js-label-select').trigger('update.label'); - }); - }, - filterResults: (function(_this) { - return function(form) { - var formAction, formData, issuesUrl; - formData = form.serialize(); - formAction = form.attr('action'); - issuesUrl = formAction; - issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&'); - issuesUrl += formData; - return Turbolinks.visit(issuesUrl); - }; - })(this), - initChecks: function() { - this.issuableBulkActions = $('.bulk-update').data('bulkActions'); - $('.check_all_issues').off('click').on('click', function() { - $('.selected_issue').prop('checked', this.checked); - return Issuable.checkChanged(); - }); - return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this)); - }, - checkChanged: function() { - var checked_issues, ids; - checked_issues = $('.selected_issue:checked'); - if (checked_issues.length > 0) { - ids = $.map(checked_issues, function(value) { - return $(value).data('id'); - }); - $('#update_issues_ids').val(ids); - $('.issues-other-filters').hide(); - $('.issues_bulk_update').show(); - } else { - $('#update_issues_ids').val([]); - $('.issues_bulk_update').hide(); - $('.issues-other-filters').show(); - this.issuableBulkActions.willUpdateLabels = false; - } - return true; - } - }; - -}).call(this); diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6 new file mode 100644 index 00000000000..4006ac740b2 --- /dev/null +++ b/app/assets/javascripts/issuable.js.es6 @@ -0,0 +1,101 @@ +(function() { + var issuable_created; + + issuable_created = false; + + this.Issuable = { + init: function() { + Issuable.initTemplates(); + Issuable.initSearch(); + Issuable.initChecks(); + Issuable.initResetFilters(); + return Issuable.initLabelFilterRemove(); + }, + initTemplates: function() { + return Issuable.labelRow = _.template('<% _.each(labels, function(label){ %> <%- label.title %> <% }); %>'); + }, + initSearch: function() { + this.timer = null; + return $('#issue_search').off('keyup').on('keyup', function() { + clearTimeout(this.timer); + return this.timer = setTimeout(function() { + var $form, $input, $search; + $search = $('#issue_search'); + $form = $('.js-filter-form'); + $input = $("input[name='" + ($search.attr('name')) + "']", $form); + if ($input.length === 0) { + $form.append(""); + } else { + $input.val($search.val()); + } + if ($search.val() !== '') { + return Issuable.filterResults($form); + } + }, 500); + }); + }, + initLabelFilterRemove: function() { + return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) { + var $button; + $button = $(this); + $('input[name="label_name[]"]').filter(function() { + return this.value === $button.data('label'); + }).remove(); + Issuable.filterResults($('.filter-form')); + return $('.js-label-select').trigger('update.label'); + }); + }, + filterResults: (function(_this) { + return function(form) { + var formAction, formData, issuesUrl; + formData = form.serialize(); + formAction = form.attr('action'); + issuesUrl = formAction; + issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&'); + issuesUrl += formData; + return Turbolinks.visit(issuesUrl); + }; + })(this), + initResetFilters: function() { + $('.reset-filters').on('click', function(e) { + e.preventDefault(); + const target = e.target; + const $form = $(target).parents('.js-filter-form'); + const baseIssuesUrl = target.href; + + $form.attr('action', baseIssuesUrl); + Turbolinks.visit(baseIssuesUrl); + }); + }, + initChecks: function() { + this.issuableBulkActions = $('.bulk-update').data('bulkActions'); + $('.check_all_issues').off('click').on('click', function() { + $('.selected_issue').prop('checked', this.checked); + return Issuable.checkChanged(); + }); + return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this)); + }, + checkChanged: function() { + const $checkedIssues = $('.selected_issue:checked'); + const $updateIssuesIds = $('#update_issues_ids'); + const $issuesOtherFilters = $('.issues-other-filters'); + const $issuesBulkUpdate = $('.issues_bulk_update'); + + if ($checkedIssues.length > 0) { + let ids = $.map($checkedIssues, function(value) { + return $(value).data('id'); + }); + $updateIssuesIds.val(ids); + $issuesOtherFilters.hide(); + $issuesBulkUpdate.show(); + } else { + $updateIssuesIds.val([]); + $issuesBulkUpdate.hide(); + $issuesOtherFilters.show(); + this.issuableBulkActions.willUpdateLabels = false; + } + return true; + } + }; + +}).call(this); diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 9209347f9bc..19827943385 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -1,6 +1,10 @@ .filter-item { margin-right: 6px; vertical-align: top; + + &.reset-filters { + padding: 7px; + } } @media (min-width: $screen-sm-min) { diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 0f4f744a71f..fabf6d74392 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -26,6 +26,9 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown" + .filter-item.inline.reset-filters + %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search])} Reset filters + .pull-right - if controller.controller_name == 'boards' #js-boards-seach.issue-boards-search -- cgit v1.2.3 From e1f2ff91eb1df285ac1ef07a2a93d479971084d4 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Fri, 2 Sep 2016 20:04:55 +0200 Subject: Add tests for reset filters button. --- spec/features/issues/reset_filters_spec.rb | 97 ++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 spec/features/issues/reset_filters_spec.rb diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb new file mode 100644 index 00000000000..6d08a30c7ad --- /dev/null +++ b/spec/features/issues/reset_filters_spec.rb @@ -0,0 +1,97 @@ +require 'rails_helper' + +feature 'Filter Resetter', feature: true do + include WaitForAjax + + let(:project) { create(:project, :public) } + let(:milestone) { create(:milestone, project: project) } + + context 'resets the milestone filter', js: true do + it 'shows all issues when reset' do + create(:issue, project: project, milestone: milestone) + create(:issue, project: project) + + visit_issues(project) + + filter_by_milestone(milestone.title) + expect(page).to have_css(".issue", count: 1) + + reset_filters + expect(page).to have_css(".issue", count: 2) + end + end + + context 'resets labels filter', js:true do + it 'shows all issues when reset' do + bug = create(:label, project: project, title: 'bug') + issue1 = create(:issue, title: "Bugfix1", project: project) + issue1.labels << bug + issue2 = create(:issue, title: "Feature", project: project) + + visit_issues(project) + + filter_by_label(bug.title) + expect(page).to have_css(".issue", count: 1) + + reset_filters + expect(page).to have_css(".issue", count: 2) + end + end + + context 'resets text filter', js:true do + it 'shows all issues when reset' do + issue1 = create(:issue, title: "Bugfix1", project: project) + issue2 = create(:issue, title: "Feature", project: project) + + visit_issues(project) + fill_in 'issue_search', with: 'Bug' + expect(page).to have_css(".issue", count: 1) + + reset_filters + expect(page).to have_css(".issue", count: 2) + end + end + + context 'resets label and text dually applied', js:true do + it 'shows all issues when reset' do + bug = create(:label, project: project, title: 'bug') + issue1 = create(:issue, title: "Bugfix1", project: project) + issue1.labels << bug + issue2 = create(:issue, project: project, title: 'Feature1') + + visit_issues(project) + expect(page).to have_css('.issue', count: 2) + + fill_in 'issue_search', with: 'Feat' + expect(page).to have_css(".issue", count: 1) + + wait_for_ajax + + filter_by_label(bug.title) + expect(page).to have_css(".issue", count: 0) + + reset_filters + expect(page).to have_css(".issue", count: 2) + end + end + + def filter_by_milestone(title) + find(".js-milestone-select").click + find(".milestone-filter .dropdown-content a", text: title).click + end + + def filter_by_label(title) + find(".js-label-select").click + find(".labels-filter .dropdown-content a", text: title).click + find(".labels-filter .dropdown-title .dropdown-menu-close-icon").click + end + + def reset_filters() + find(".reset-filters").click + wait_for_ajax + end + + def visit_issues(project) + visit namespace_project_issues_path(project.namespace, project) + end +end -- cgit v1.2.3 From b3cd41b4014fa96780218f0f086de239731ec91a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 3 Sep 2016 09:31:02 +0200 Subject: Use double instead of spy in job config node specs --- spec/lib/gitlab/ci/config/node/job_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index c660ce39c46..91f676dae03 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -72,7 +72,7 @@ describe Gitlab::Ci::Config::Node::Job do double('specified', 'specified?' => true, value: 'specified') end - let(:deps) { spy('deps', '[]' => unspecified) } + let(:deps) { double('deps', '[]' => unspecified) } context 'when job config overrides global config' do before { entry.compose!(deps) } -- cgit v1.2.3 From 49ea6ac76fc2f3db018abcf624825c1fb5256fd7 Mon Sep 17 00:00:00 2001 From: Airat Shigapov Date: Sun, 4 Sep 2016 19:59:00 +0300 Subject: Make sure DatabaseCleaner.clean runs AFTER Capybara's cleanup --- spec/support/db_cleaner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb index e0dbc9aa84c..ac38e31b77e 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -15,7 +15,7 @@ RSpec.configure do |config| DatabaseCleaner.start end - config.after(:each) do + config.append_after(:each) do DatabaseCleaner.clean end end -- cgit v1.2.3 From 7f440b92d5483f346a79e3e9e49d991d46c75cf6 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 5 Sep 2016 11:12:40 +0200 Subject: Clean reset filters test. --- spec/features/issues/reset_filters_spec.rb | 53 +++++++++++++++--------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index 6d08a30c7ad..8be46864b0b 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -14,80 +14,81 @@ feature 'Filter Resetter', feature: true do visit_issues(project) filter_by_milestone(milestone.title) - expect(page).to have_css(".issue", count: 1) + expect(page).to have_css('.issue', count: 1) reset_filters - expect(page).to have_css(".issue", count: 2) + expect(page).to have_css('.issue', count: 2) end end - context 'resets labels filter', js:true do + context 'resets labels filter', js: true do it 'shows all issues when reset' do bug = create(:label, project: project, title: 'bug') - issue1 = create(:issue, title: "Bugfix1", project: project) + issue1 = create(:issue, title: 'Bugfix1', project: project) issue1.labels << bug - issue2 = create(:issue, title: "Feature", project: project) + + create(:issue, title: 'Feature', project: project) visit_issues(project) filter_by_label(bug.title) - expect(page).to have_css(".issue", count: 1) + expect(page).to have_css('.issue', count: 1) reset_filters - expect(page).to have_css(".issue", count: 2) + expect(page).to have_css('.issue', count: 2) end end - context 'resets text filter', js:true do + context 'resets text filter', js: true do it 'shows all issues when reset' do - issue1 = create(:issue, title: "Bugfix1", project: project) - issue2 = create(:issue, title: "Feature", project: project) + create(:issue, title: 'Bugfix1', project: project) + create(:issue, title: 'Feature', project: project) visit_issues(project) + fill_in 'issue_search', with: 'Bug' - expect(page).to have_css(".issue", count: 1) + expect(page).to have_css('.issue', count: 1) reset_filters - expect(page).to have_css(".issue", count: 2) + expect(page).to have_css('.issue', count: 2) end end - context 'resets label and text dually applied', js:true do + context 'resets label and text dually applied', js: true do it 'shows all issues when reset' do bug = create(:label, project: project, title: 'bug') - issue1 = create(:issue, title: "Bugfix1", project: project) + issue1 = create(:issue, title: 'Bugfix1', project: project) issue1.labels << bug - issue2 = create(:issue, project: project, title: 'Feature1') + create(:issue, project: project, title: 'Feature1') visit_issues(project) - expect(page).to have_css('.issue', count: 2) fill_in 'issue_search', with: 'Feat' - expect(page).to have_css(".issue", count: 1) + expect(page).to have_css('.issue', count: 1) wait_for_ajax filter_by_label(bug.title) - expect(page).to have_css(".issue", count: 0) + expect(page).to have_css('.issue', count: 0) reset_filters - expect(page).to have_css(".issue", count: 2) + expect(page).to have_css('.issue', count: 2) end end def filter_by_milestone(title) - find(".js-milestone-select").click - find(".milestone-filter .dropdown-content a", text: title).click + find('.js-milestone-select').click + find('.milestone-filter .dropdown-content a', text: title).click end def filter_by_label(title) - find(".js-label-select").click - find(".labels-filter .dropdown-content a", text: title).click - find(".labels-filter .dropdown-title .dropdown-menu-close-icon").click + find('.js-label-select').click + find('.labels-filter .dropdown-content a', text: title).click + find('.labels-filter .dropdown-title .dropdown-menu-close-icon').click end - def reset_filters() - find(".reset-filters").click + def reset_filters + find('.reset-filters').click wait_for_ajax end -- cgit v1.2.3 From 1a23b74d9346265fa88f87224e35b01b81934405 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 5 Sep 2016 11:34:45 +0200 Subject: Use broader js flag. --- spec/features/issues/reset_filters_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index 8be46864b0b..fab772d0385 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -1,12 +1,12 @@ require 'rails_helper' -feature 'Filter Resetter', feature: true do +feature 'Issues filter resetter', feature: true, js: true do include WaitForAjax let(:project) { create(:project, :public) } let(:milestone) { create(:milestone, project: project) } - context 'resets the milestone filter', js: true do + context 'resets the milestone filter' do it 'shows all issues when reset' do create(:issue, project: project, milestone: milestone) create(:issue, project: project) @@ -21,7 +21,7 @@ feature 'Filter Resetter', feature: true do end end - context 'resets labels filter', js: true do + context 'resets labels filter' do it 'shows all issues when reset' do bug = create(:label, project: project, title: 'bug') issue1 = create(:issue, title: 'Bugfix1', project: project) @@ -39,7 +39,7 @@ feature 'Filter Resetter', feature: true do end end - context 'resets text filter', js: true do + context 'resets text filter' do it 'shows all issues when reset' do create(:issue, title: 'Bugfix1', project: project) create(:issue, title: 'Feature', project: project) @@ -54,7 +54,7 @@ feature 'Filter Resetter', feature: true do end end - context 'resets label and text dually applied', js: true do + context 'resets label and text dually applied' do it 'shows all issues when reset' do bug = create(:label, project: project, title: 'bug') issue1 = create(:issue, title: 'Bugfix1', project: project) -- cgit v1.2.3 From d071f61b0de12a57012ed1c77634b54fa83615a7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 5 Sep 2016 17:55:30 +0800 Subject: Forget about pending duration for now, need more discussion --- app/models/ci/pipeline.rb | 3 +- ...0829122117_add_pending_duration_to_pipelines.rb | 7 ---- db/schema.rb | 1 - lib/gitlab/ci/pipeline_duration.rb | 49 +++++----------------- 4 files changed, 11 insertions(+), 49 deletions(-) delete mode 100644 db/migrate/20160829122117_add_pending_duration_to_pipelines.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index de7709c36c4..4092205578e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -260,8 +260,7 @@ module Ci def update_duration return unless started_at - self.duration, self.pending_duration = - Gitlab::Ci::PipelineDuration.from_pipeline(self) + self.duration = Gitlab::Ci::PipelineDuration.from_pipeline(self) end def execute_hooks diff --git a/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb b/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb deleted file mode 100644 index 6f238a2f65c..00000000000 --- a/db/migrate/20160829122117_add_pending_duration_to_pipelines.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AddPendingDurationToPipelines < ActiveRecord::Migration - DOWNTIME = false - - def change - add_column :ci_commits, :pending_duration, :integer - end -end diff --git a/db/schema.rb b/db/schema.rb index f57c4149e6c..af6e74a4e25 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -210,7 +210,6 @@ ActiveRecord::Schema.define(version: 20160831223750) do t.datetime "finished_at" t.integer "duration" t.integer "user_id" - t.integer "pending_duration" end add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index ac68fd0272f..cd782934302 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -1,11 +1,14 @@ module Gitlab module Ci + # # Introduction - total running time + # # The problem this class is trying to solve is finding the total running # time amongst all the jobs, excluding retries and pending (queue) time. # We could reduce this problem down to finding the union of periods. # # So each job would be represented as a `Period`, which consists of - # `Period#first` and `Period#last`. A simple example here would be: + # `Period#first` as when the job started and `Period#last` as when the + # job was finished. A simple example here would be: # # * A (1, 3) # * B (2, 4) @@ -24,22 +27,7 @@ module Gitlab # # (4 - 1) + (7 - 6) => 4 # - # And the pending (queue) time would be (4, 6) like this: (marked as X) - # - # 0 1 2 3 4 5 6 7 - # AAAAAAA - # BBBBBBB - # CCCC - # XXXXX - # - # Which could be calculated by having (1, 7) as total time, minus - # the running time we have above, 4. The full calculation would be: - # - # total = (7 - 1) - # duration = (4 - 1) + (7 - 6) - # pending = total - duration # 6 - 4 => 2 - # - # Which the answer to pending would be 2 in this example. + # # The Algorithm # # The algorithm used here for union would be described as follow. # First we make sure that all periods are sorted by `Period#first`. @@ -82,22 +70,12 @@ module Gitlab # `C.first <= D.last` is `false`. Therefore we need to keep both C # and D. The example would end here because there are no more jobs. # - # After having the union of all periods, the rest is simple and - # described in the beginning. To summarise: - # - # duration = (4 - 1) + (7 - 6) - # total = (7 - 1) - # pending = total - duration # 6 - 4 => 2 - # - # Note that the pending time is actually not the final pending time - # for pipelines, because we still need to accumulate the pending time - # before the first job (A in this example) even started! That is: + # After having the union of all periods, we just need to sum the length + # of all periods to get total time. # - # total_pending = pipeline.started_at - pipeline.created_at + pending + # (4 - 1) + (7 - 6) => 4 # - # Would be the final answer. We deal with that in pipeline itself - # but not here because here we try not to be depending on pipeline - # and it's trivial enough to get that information. + # That is 4 is the answer in the example. class PipelineDuration PeriodStruct = Struct.new(:first, :last) class Period < PeriodStruct @@ -107,17 +85,10 @@ module Gitlab end def self.from_pipeline(pipeline) - now = Time.now status = %w[success failed running canceled] builds = pipeline.builds.latest.where(status: status) - running = from_builds(builds, :started_at, :finished_at, now).duration - pending = pipeline.started_at - pipeline.created_at - queuing = builds.inject(0) do |result, job| - result + ((job.started_at || now) - (job.queued_at || now)) - end - - [running, pending + queuing] + from_builds(builds, :started_at, :finished_at).duration end def self.from_builds(builds, from, to, now = Time.now) -- cgit v1.2.3 From 6e1a06d8cd761692596696ab4e17ccaf0c68519d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 5 Sep 2016 18:49:27 +0800 Subject: Show how long this pipeline was queued Closes #19804 --- app/models/ci/pipeline.rb | 7 +++++++ app/views/projects/pipelines/_info.html.haml | 3 +++ 2 files changed, 10 insertions(+) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 4092205578e..0b1df9f4294 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -257,6 +257,13 @@ module Ci ] end + def queued_duration + return unless started_at + + seconds = (started_at - created_at).to_i + seconds unless seconds.zero? + end + def update_duration return unless started_at diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 063e83a407a..03f5f55e336 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -10,6 +10,9 @@ - if @pipeline.duration in = time_interval_in_words(@pipeline.duration) + - if @pipeline.queued_duration + (queued for + = "#{time_interval_in_words(@pipeline.queued_duration)})" .pull-right = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do -- cgit v1.2.3 From d354d185ff8e6355d03457d61fd8424b4494e5cb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 5 Sep 2016 19:18:14 +0800 Subject: Remove tests for pending_duration --- spec/models/ci/pipeline_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 194c3fc01f1..fbf945c757c 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -128,7 +128,7 @@ describe Ci::Pipeline, models: true do let(:build_b) { create_build('build2', current, 20) } let(:build_c) { create_build('build3', current + 50, 10) } - describe '#duration and #pending_duration' do + describe '#duration' do before do pipeline.update(created_at: current) @@ -156,7 +156,6 @@ describe Ci::Pipeline, models: true do pipeline.reload expect(pipeline.duration).to eq(40) - expect(pipeline.pending_duration).to eq(45) end end -- cgit v1.2.3 From c87540ed46ba8756154f767be99f80be75c27a43 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 19 Aug 2016 19:10:41 +0200 Subject: Verify JWT messages from gitlab-workhorse --- .gitignore | 1 + .../projects/git_http_client_controller.rb | 4 + app/controllers/projects/git_http_controller.rb | 3 + app/controllers/projects/lfs_storage_controller.rb | 11 +-- app/helpers/workhorse_helper.rb | 4 + config/initializers/gitlab_workhorse_secret.rb | 8 ++ lib/ci/api/builds.rb | 4 +- lib/gitlab/workhorse.rb | 52 ++++++++++++- spec/lib/gitlab/workhorse_spec.rb | 86 +++++++++++++++++++++- spec/requests/ci/api/builds_spec.rb | 11 ++- spec/requests/git_http_spec.rb | 18 ++++- spec/requests/lfs_http_spec.rb | 19 ++++- spec/support/workhorse_helpers.rb | 5 ++ 13 files changed, 210 insertions(+), 16 deletions(-) create mode 100644 config/initializers/gitlab_workhorse_secret.rb diff --git a/.gitignore b/.gitignore index 1bf9a47aef6..9166512606d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ /vendor/bundle/* /builds/* /shared/* +/.gitlab_workhorse_secret diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index a5b4031c30f..f5ce63fdfed 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -117,4 +117,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController def ci? @ci.present? end + + def verify_workhorse_api! + Gitlab::Workhorse.verify_api_request!(request.headers) + end end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index b4373ef89ef..9805705c4e3 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,6 +1,8 @@ # This file should be identical in GitLab Community Edition and Enterprise Edition class Projects::GitHttpController < Projects::GitHttpClientController + before_action :verify_workhorse_api! + # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) def info_refs @@ -56,6 +58,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def render_ok + set_workhorse_internal_api_content_type render json: Gitlab::Workhorse.git_http_ok(repository, user) end diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb index 69066cb40e6..9005b104e90 100644 --- a/app/controllers/projects/lfs_storage_controller.rb +++ b/app/controllers/projects/lfs_storage_controller.rb @@ -3,6 +3,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController before_action :require_lfs_enabled! before_action :lfs_check_access! + before_action :verify_workhorse_api!, only: [:upload_authorize] def download lfs_object = LfsObject.find_by_oid(oid) @@ -15,14 +16,8 @@ class Projects::LfsStorageController < Projects::GitHttpClientController end def upload_authorize - render( - json: { - StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload", - LfsOid: oid, - LfsSize: size, - }, - content_type: 'application/json; charset=utf-8' - ) + set_workhorse_internal_api_content_type + render json: Gitlab::Workhorse.lfs_upload_ok(oid, size) end def upload_finalize diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb index d887cdadc34..88f374be1e5 100644 --- a/app/helpers/workhorse_helper.rb +++ b/app/helpers/workhorse_helper.rb @@ -34,4 +34,8 @@ module WorkhorseHelper headers.store(*Gitlab::Workhorse.send_artifacts_entry(build, entry)) head :ok end + + def set_workhorse_internal_api_content_type + headers['Content-Type'] = Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE + end end diff --git a/config/initializers/gitlab_workhorse_secret.rb b/config/initializers/gitlab_workhorse_secret.rb new file mode 100644 index 00000000000..ed54dc11098 --- /dev/null +++ b/config/initializers/gitlab_workhorse_secret.rb @@ -0,0 +1,8 @@ +begin + Gitlab::Workhorse.secret +rescue + Gitlab::Workhorse.write_secret +end + +# Try a second time. If it does not work this will raise. +Gitlab::Workhorse.secret diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 9f3b582a263..2b18ecef8ba 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -101,6 +101,7 @@ module Ci # POST /builds/:id/artifacts/authorize post ":id/artifacts/authorize" do require_gitlab_workhorse! + Gitlab::Workhorse.verify_api_request!(headers) not_allowed! unless Gitlab.config.artifacts.enabled build = Ci::Build.find_by_id(params[:id]) not_found! unless build @@ -113,7 +114,8 @@ module Ci end status 200 - { TempPath: ArtifactUploader.artifacts_upload_path } + content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE + Gitlab::Workhorse.artifact_upload_ok end # Upload artifacts to build - Runners only diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index c6826a09bd2..efe4aeb399d 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -1,19 +1,38 @@ require 'base64' require 'json' +require 'securerandom' module Gitlab class Workhorse SEND_DATA_HEADER = 'Gitlab-Workhorse-Send-Data' VERSION_FILE = 'GITLAB_WORKHORSE_VERSION' + INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json' + INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request' + + # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32 + # bytes https://tools.ietf.org/html/rfc4868#section-2.6 + SECRET_LENGTH = 32 class << self def git_http_ok(repository, user) { - 'GL_ID' => Gitlab::GlId.gl_id(user), - 'RepoPath' => repository.path_to_repo, + GL_ID: Gitlab::GlId.gl_id(user), + RepoPath: repository.path_to_repo, } end + def lfs_upload_ok(oid, size) + { + StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload", + LfsOid: oid, + LfsSize: size, + } + end + + def artifact_upload_ok + { TempPath: ArtifactUploader.artifacts_upload_path } + end + def send_git_blob(repository, blob) params = { 'RepoPath' => repository.path_to_repo, @@ -81,6 +100,35 @@ module Gitlab path.readable? ? path.read.chomp : 'unknown' end + def secret + @secret ||= begin + bytes = Base64.strict_decode64(File.read(secret_path)) + raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH + bytes + end + end + + def write_secret + bytes = SecureRandom.random_bytes(SECRET_LENGTH) + File.open(secret_path, 'w:BINARY', 0600) do |f| + f.chmod(0600) + f.write(Base64.strict_encode64(bytes)) + end + end + + def verify_api_request!(request_headers) + JWT.decode( + request_headers[INTERNAL_API_REQUEST_HEADER], + secret, + true, + { iss: 'gitlab-workhorse', verify_iss: true, algorithm: 'HS256' }, + ) + end + + def secret_path + Rails.root.join('.gitlab_workhorse_secret') + end + protected def encode(hash) diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index c5c1402e8fc..63032d43027 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::Workhorse, lib: true do let(:project) { create(:project) } let(:subject) { Gitlab::Workhorse } - describe "#send_git_archive" do + describe ".send_git_archive" do context "when the repository doesn't have an archive file path" do before do allow(project.repository).to receive(:archive_metadata).and_return(Hash.new) @@ -15,4 +15,88 @@ describe Gitlab::Workhorse, lib: true do end end end + + describe ".secret" do + subject { described_class.secret } + + before do + described_class.instance_variable_set(:@secret, nil) + described_class.write_secret + end + + it 'returns 32 bytes' do + expect(subject).to be_a(String) + expect(subject.length).to eq(32) + expect(subject.encoding).to eq(Encoding::ASCII_8BIT) + end + + it 'raises an exception if the secret file cannot be read' do + File.delete(described_class.secret_path) + expect { subject }.to raise_exception(Errno::ENOENT) + end + + it 'raises an exception if the secret file contains the wrong number of bytes' do + File.truncate(described_class.secret_path, 0) + expect { subject }.to raise_exception(RuntimeError) + end + end + + describe ".write_secret" do + let(:secret_path) { described_class.secret_path } + before do + begin + File.delete(secret_path) + rescue Errno::ENOENT + end + + described_class.write_secret + end + + it 'uses mode 0600' do + expect(File.stat(secret_path).mode & 0777).to eq(0600) + end + + it 'writes base64 data' do + bytes = Base64.strict_decode64(File.read(secret_path)) + expect(bytes).not_to be_empty + end + end + + describe '#verify_api_request!' do + let(:header_key) { described_class.const_get('INTERNAL_API_REQUEST_HEADER') } + let(:payload) { { 'iss' => 'gitlab-workhorse' } } + + it 'accepts a correct header' do + headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') } + expect { call_verify(headers) }.not_to raise_error + end + + it 'raises an error when the header is not set' do + expect { call_verify({}) }.to raise_jwt_error + end + + it 'raises an error when the header is not signed' do + headers = { header_key => JWT.encode(payload, nil, 'none') } + expect { call_verify(headers) }.to raise_jwt_error + end + + it 'raises an error when the header is signed with the wrong key' do + headers = { header_key => JWT.encode(payload, 'wrongkey', 'HS256') } + expect { call_verify(headers) }.to raise_jwt_error + end + + it 'raises an error when the issuer is incorrect' do + payload['iss'] = 'somebody else' + headers = { header_key => JWT.encode(payload, described_class.secret, 'HS256') } + expect { call_verify(headers) }.to raise_jwt_error + end + + def raise_jwt_error + raise_error(JWT::DecodeError) + end + + def call_verify(headers) + described_class.verify_api_request!(headers) + end + end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index ca7932dc5da..29a194b31f6 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -230,7 +230,8 @@ describe Ci::API::API do let(:post_url) { ci_api("/builds/#{build.id}/artifacts") } let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") } let(:get_url) { ci_api("/builds/#{build.id}/artifacts") } - let(:headers) { { "GitLab-Workhorse" => "1.0" } } + let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } + let(:headers) { { "GitLab-Workhorse" => "1.0", Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } } let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) } before { build.run! } @@ -240,14 +241,22 @@ describe Ci::API::API do it "using token as parameter" do post authorize_url, { token: build.token }, headers expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(json_response["TempPath"]).not_to be_nil end it "using token as header" do post authorize_url, {}, headers_with_token expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(json_response["TempPath"]).not_to be_nil end + + it "reject requests that did not go through gitlab-workhorse" do + headers.delete('Gitlab-Workhorse-Api-Request') + post authorize_url, { token: build.token }, headers + expect(response).to have_http_status(500) + end end context "should fail to post too large artifact" do diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 9ca3b021aa2..b7001fede40 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -1,6 +1,8 @@ require "spec_helper" describe 'Git HTTP requests', lib: true do + include WorkhorseHelpers + let(:user) { create(:user) } let(:project) { create(:project, path: 'project.git-project') } @@ -48,6 +50,7 @@ describe 'Git HTTP requests', lib: true do expect(response).to have_http_status(200) expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end end @@ -63,6 +66,7 @@ describe 'Git HTTP requests', lib: true do it "downloads get status 200" do download(path, {}) do |response| expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end @@ -101,6 +105,14 @@ describe 'Git HTTP requests', lib: true do end end end + + context 'when the request is not from gitlab-workhorse' do + it 'raises an exception' do + expect do + get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack") + end.to raise_error(JWT::DecodeError) + end + end end context "when the project is private" do @@ -170,11 +182,13 @@ describe 'Git HTTP requests', lib: true do clone_get(path, env) expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end it "uploads get status 200" do upload(path, env) do |response| expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end end end @@ -189,6 +203,7 @@ describe 'Git HTTP requests', lib: true do clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end it "uploads get status 401 (no project existence information leak)" do @@ -297,6 +312,7 @@ describe 'Git HTTP requests', lib: true do clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end it "uploads get status 401 (no project existence information leak)" do @@ -426,7 +442,7 @@ describe 'Git HTTP requests', lib: true do end def auth_env(user, password, spnego_request_token) - env = {} + env = workhorse_internal_api_request_header if user && password env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password) elsif spnego_request_token diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index fcd6521317a..6e551bb65fa 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe 'Git LFS API and storage' do + include WorkhorseHelpers + let(:user) { create(:user) } let!(:lfs_object) { create(:lfs_object, :with_file) } @@ -715,6 +717,12 @@ describe 'Git LFS API and storage' do project.team << [user, :developer] end + context 'and the request bypassed workhorse' do + it 'raises an exception' do + expect { put_authorize(verified: false) }.to raise_error JWT::DecodeError + end + end + context 'and request is sent by gitlab-workhorse to authorize the request' do before do put_authorize @@ -724,6 +732,10 @@ describe 'Git LFS API and storage' do expect(response).to have_http_status(200) end + it 'uses the gitlab-workhorse content type' do + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + end + it 'responds with status 200, location of lfs store and object details' do expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") expect(json_response['LfsOid']).to eq(sample_oid) @@ -863,8 +875,11 @@ describe 'Git LFS API and storage' do end end - def put_authorize - put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers + def put_authorize(verified: true) + authorize_headers = headers + authorize_headers.merge!(workhorse_internal_api_request_header) if verified + + put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, authorize_headers end def put_finalize(lfs_tmp = lfs_tmp_file) diff --git a/spec/support/workhorse_helpers.rb b/spec/support/workhorse_helpers.rb index 107b6e30924..47673cd4c3a 100644 --- a/spec/support/workhorse_helpers.rb +++ b/spec/support/workhorse_helpers.rb @@ -13,4 +13,9 @@ module WorkhorseHelpers ] end end + + def workhorse_internal_api_request_header + jwt_token = JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') + { 'HTTP_' + Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER.upcase.tr('-', '_') => jwt_token } + end end -- cgit v1.2.3 From 413516081b91f3e64305fa3f4dffd84a564596a7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 5 Sep 2016 21:13:13 +0800 Subject: Also add an entry for showing queued time [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 0e7972c2704..1e286cca135 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.12.0 (unreleased) - Add BroadcastMessage API - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 + - Show queued time when showing a pipeline !6084 - Remove unused mixins (ClemMakesApps) - Add search to all issue board lists - Fix groups sort dropdown alignment (ClemMakesApps) -- cgit v1.2.3 From 0ffacb8e7fe7fe59648452cc19c05b4c5055a217 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 5 Sep 2016 15:29:49 +0100 Subject: Fixed commit search UI Closes #21868 --- app/views/search/_results.html.haml | 16 ++++++++++------ app/views/search/results/_commit.html.haml | 3 +-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 252c37532e1..7fe2bce3e7c 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -10,12 +10,16 @@ in group #{link_to @group.name, @group} .results.prepend-top-10 - .search-results - - if @scope == 'projects' - .term - = render 'shared/projects/list', projects: @search_objects - - else - = render partial: "search/results/#{@scope.singularize}", collection: @search_objects + - if @scope == 'commits' + %ul.list-unstyled + = render partial: "search/results/commit", collection: @search_objects + - else + .search-results + - if @scope == 'projects' + .term + = render 'shared/projects/list', projects: @search_objects + - else + = render partial: "search/results/#{@scope.singularize}", collection: @search_objects - if @scope != 'projects' = paginate(@search_objects, theme: 'gitlab') diff --git a/app/views/search/results/_commit.html.haml b/app/views/search/results/_commit.html.haml index 4e6c3965dc6..5b2d83d6b92 100644 --- a/app/views/search/results/_commit.html.haml +++ b/app/views/search/results/_commit.html.haml @@ -1,2 +1 @@ -.search-result-row - = render 'projects/commits/commit', project: @project, commit: commit += render 'projects/commits/commit', project: @project, commit: commit -- cgit v1.2.3 From 87f6209ca502ae9b7c3e6f346681f7be4304e385 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 5 Sep 2016 16:32:38 +0200 Subject: Revise test error messages to be more reaadble. --- spec/features/issues/reset_filters_spec.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index fab772d0385..e3dd460d9fc 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -1,13 +1,13 @@ require 'rails_helper' -feature 'Issues filter resetter', feature: true, js: true do +feature 'Issues filter reset button', feature: true, js: true do include WaitForAjax let(:project) { create(:project, :public) } let(:milestone) { create(:milestone, project: project) } - context 'resets the milestone filter' do - it 'shows all issues when reset' do + context 'when a milestone filter has been applied' do + it 'resets the milestone filter' do create(:issue, project: project, milestone: milestone) create(:issue, project: project) @@ -21,8 +21,8 @@ feature 'Issues filter resetter', feature: true, js: true do end end - context 'resets labels filter' do - it 'shows all issues when reset' do + context 'when a label filter has been applied' do + it 'resets the label filter' do bug = create(:label, project: project, title: 'bug') issue1 = create(:issue, title: 'Bugfix1', project: project) issue1.labels << bug @@ -39,8 +39,8 @@ feature 'Issues filter resetter', feature: true, js: true do end end - context 'resets text filter' do - it 'shows all issues when reset' do + context 'when a text search has been conducted' do + it 'resets the text search filter' do create(:issue, title: 'Bugfix1', project: project) create(:issue, title: 'Feature', project: project) @@ -54,8 +54,8 @@ feature 'Issues filter resetter', feature: true, js: true do end end - context 'resets label and text dually applied' do - it 'shows all issues when reset' do + context 'when label and text filters have been dually applied' do + it 'resets both filters' do bug = create(:label, project: project, title: 'bug') issue1 = create(:issue, title: 'Bugfix1', project: project) issue1.labels << bug -- cgit v1.2.3 From 3111d6a9cc814e43a0ca232f586c244ef79dc8bd Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 2 Sep 2016 16:52:11 +0200 Subject: Fixed gitlab.com importer missing confidential attribute --- CHANGELOG | 1 + lib/gitlab/gitlab_import/importer.rb | 3 ++- spec/lib/gitlab/gitlab_import/importer_spec.rb | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index bc18171d091..29a8d8bba84 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -87,6 +87,7 @@ v 8.11.5 (unreleased) - Fix member expiration date picker after update - Fix suggested colors options for new labels in the admin area. !6138 - Fix GitLab import button + - Fix confidential issues being exposed as public using gitlab.com export v 8.11.4 - Fix resolving conflicts on forks. !6082 diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index 46d40f75be6..cc49d6aac5c 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -41,7 +41,8 @@ module Gitlab title: issue["title"], state: issue["state"], updated_at: issue["updated_at"], - author_id: gl_user_id(project, issue["author"]["id"]) + author_id: gl_user_id(project, issue["author"]["id"]), + confidential: issue["confidential"] ) end end diff --git a/spec/lib/gitlab/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb index d3f1deb3837..9b499b593d3 100644 --- a/spec/lib/gitlab/gitlab_import/importer_spec.rb +++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb @@ -13,6 +13,7 @@ describe Gitlab::GitlabImport::Importer, lib: true do 'title' => 'Issue', 'description' => 'Lorem ipsum', 'state' => 'opened', + 'confidential' => true, 'author' => { 'id' => 283999, 'name' => 'John Doe' @@ -34,6 +35,7 @@ describe Gitlab::GitlabImport::Importer, lib: true do title: 'Issue', description: "*Created by: John Doe*\n\nLorem ipsum", state: 'opened', + confidential: true, author_id: project.creator_id } -- cgit v1.2.3 From 08b05d0c335e75e681dc3227a2eca4e42ee530c4 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Mon, 5 Sep 2016 18:36:38 +0200 Subject: Refactor filters reset test to re-use issues. --- spec/features/issues/reset_filters_spec.rb | 66 ++++++++++++++++++------------ 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index e3dd460d9fc..17311509619 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -3,16 +3,19 @@ require 'rails_helper' feature 'Issues filter reset button', feature: true, js: true do include WaitForAjax - let(:project) { create(:project, :public) } - let(:milestone) { create(:milestone, project: project) } + let!(:project) { create(:project, :public) } + let!(:user) { create(:user)} + let!(:milestone) { create(:milestone, project: project) } + let!(:bug) { create(:label, project: project, name: 'bug')} # maybe switch back to title + let!(:issue1) { create(:issue, project: project, milestone: milestone, author: user, assignee: user, title: 'Feature')} + let!(:issue2) { create(:labeled_issue, project: project, labels: [bug], title: 'Bugfix1')} + + before do + visit_issues(project) + end context 'when a milestone filter has been applied' do it 'resets the milestone filter' do - create(:issue, project: project, milestone: milestone) - create(:issue, project: project) - - visit_issues(project) - filter_by_milestone(milestone.title) expect(page).to have_css('.issue', count: 1) @@ -23,14 +26,6 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when a label filter has been applied' do it 'resets the label filter' do - bug = create(:label, project: project, title: 'bug') - issue1 = create(:issue, title: 'Bugfix1', project: project) - issue1.labels << bug - - create(:issue, title: 'Feature', project: project) - - visit_issues(project) - filter_by_label(bug.title) expect(page).to have_css('.issue', count: 1) @@ -41,10 +36,6 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when a text search has been conducted' do it 'resets the text search filter' do - create(:issue, title: 'Bugfix1', project: project) - create(:issue, title: 'Feature', project: project) - - visit_issues(project) fill_in 'issue_search', with: 'Bug' expect(page).to have_css('.issue', count: 1) @@ -54,15 +45,28 @@ feature 'Issues filter reset button', feature: true, js: true do end end - context 'when label and text filters have been dually applied' do - it 'resets both filters' do - bug = create(:label, project: project, title: 'bug') - issue1 = create(:issue, title: 'Bugfix1', project: project) - issue1.labels << bug - create(:issue, project: project, title: 'Feature1') + context 'when author filter has been applied' do + it 'resets the author filter' do + filter_by_author(user.name) + expect(page).to have_css('.issue', count: 1) - visit_issues(project) + reset_filters + expect(page).to have_css('.issue', count: 2) + end + end + context 'when assignee filter has been applied' do + it 'resets the assignee filter' do + filter_by_assignee(user.name) + expect(page).to have_css('.issue', count: 1) + + reset_filters + expect(page).to have_css('.issue', count: 2) + end + end + + context 'when label and text filters have been dually applied' do + it 'resets both filters' do fill_in 'issue_search', with: 'Feat' expect(page).to have_css('.issue', count: 1) @@ -87,6 +91,16 @@ feature 'Issues filter reset button', feature: true, js: true do find('.labels-filter .dropdown-title .dropdown-menu-close-icon').click end + def filter_by_author(name) + find('.js-author-search').click + find('.dropdown-menu-author .dropdown-content a', text: name).click + end + + def filter_by_assignee(name) + find('.js-assignee-search').click + find('.dropdown-menu-assignee .dropdown-content a', text: name).click + end + def reset_filters find('.reset-filters').click wait_for_ajax -- cgit v1.2.3 From c7d717cf6a452afbf71158bddce78fdd29368d5b Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 5 Sep 2016 20:58:27 -0600 Subject: Fix two problematic bits of code that will be deprecated or broken in Rails 5. --- app/models/project.rb | 2 +- config/initializers/mime_types.rb | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index a6de2c48071..4017cabe9f0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -58,7 +58,7 @@ class Project < ActiveRecord::Base # Relations belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' - belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' + belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id' belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index f498732feca..350388fedbe 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -13,9 +13,11 @@ Mime::Type.register "video/mp4", :mp4, [], [:m4v, :mov] Mime::Type.register "video/webm", :webm Mime::Type.register "video/ogg", :ogv -middlewares = Gitlab::Application.config.middleware -middlewares.swap(ActionDispatch::ParamsParser, ActionDispatch::ParamsParser, { - Mime::Type.lookup('application/vnd.git-lfs+json') => lambda do |body| - ActiveSupport::JSON.decode(body) - end -}) +lfs_mime_types = %w( + application/vnd.git-lfs+json + text/x-json + application/json +) + +Mime::Type.unregister :json +Mime::Type.register 'application/json', :json, lfs_mime_types -- cgit v1.2.3 From 117893eee75edc568be51c0a2cc79c2ac65981f2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 6 Sep 2016 10:36:09 +0100 Subject: Fix layout issues with blame table Closes #21216 --- CHANGELOG | 1 + app/assets/stylesheets/framework/files.scss | 22 +++------------------- app/helpers/avatars_helper.rb | 3 ++- app/views/projects/blame/show.html.haml | 3 ++- 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f54a17e1ea2..efb6cd8d670 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.12.0 (unreleased) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) + - Fix blame table layout width - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Center build stage columns in pipeline overview (ClemMakesApps) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index e3be45ba1dc..76a3c083697 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -63,7 +63,7 @@ &.image_file { background: #eee; text-align: center; - + img { padding: 20px; max-width: 80%; @@ -94,7 +94,6 @@ &.blame { table { border: none; - box-shadow: none; margin: 0; } tr { @@ -108,19 +107,10 @@ border-right: none; } } - img.avatar { - border: 0 none; - float: none; - margin: 0; - padding: 0; - } td.blame-commit { + padding: 0 10px; + min-width: 400px; background: $gray-light; - min-width: 350px; - - .commit-author-link { - color: #888; - } } td.line-numbers { float: none; @@ -133,12 +123,6 @@ } td.lines { padding: 0; - code { - font-family: $monospace_font; - } - pre { - margin: 0; - } } } diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index aa8acbe7567..df41473543b 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -14,7 +14,8 @@ module AvatarsHelper avatar_icon(options[:user] || options[:user_email], avatar_size), class: "avatar has-tooltip hidden-xs s#{avatar_size}", alt: "#{user_name}'s avatar", - title: user_name + title: user_name, + data: { container: 'body' } ) if options[:user] diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 377665b096f..5a98e258b22 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -11,7 +11,7 @@ %small= number_to_human_size @blob.size .file-actions = render "projects/blob/actions" - .file-content.blame.code.js-syntax-highlight + .table-responsive.file-content.blame.code.js-syntax-highlight %table - current_line = 1 - @blame_groups.each do |blame_group| @@ -19,6 +19,7 @@ %td.blame-commit .commit - commit = blame_group[:commit] + = author_avatar(commit, size: 36) .commit-row-title %strong = link_to_gfm truncate(commit.title, length: 35), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "cdark" -- cgit v1.2.3 From 5a62d811551ed523d71719fa59dad3575ad1eb27 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Tue, 6 Sep 2016 12:08:32 +0200 Subject: Add user to project. --- spec/features/issues/reset_filters_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index 17311509619..6ce3d919646 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -11,6 +11,7 @@ feature 'Issues filter reset button', feature: true, js: true do let!(:issue2) { create(:labeled_issue, project: project, labels: [bug], title: 'Bugfix1')} before do + project.team << [user, :developer] visit_issues(project) end -- cgit v1.2.3 From b92c75ab982e123e2e1efc189e35b84c0ffd1c27 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Sep 2016 18:12:46 +0800 Subject: Use sum, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14991392 --- lib/gitlab/ci/pipeline_duration.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index cd782934302..9fe4996fc09 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -133,9 +133,7 @@ module Gitlab end def process_duration(periods) - periods.inject(0) do |result, per| - result + per.duration - end + periods.sum(&:duration) end end end -- cgit v1.2.3 From 3a68c98973c2c17e0c6e57184c4b2605a2dd274e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Sep 2016 19:14:28 +0800 Subject: Just use module because there's nothing to save, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14992064 --- lib/gitlab/ci/pipeline_duration.rb | 24 ++++++++++-------------- spec/lib/gitlab/ci/pipeline_duration_spec.rb | 8 ++++---- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 9fe4996fc09..0333807263f 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -2,7 +2,7 @@ module Gitlab module Ci # # Introduction - total running time # - # The problem this class is trying to solve is finding the total running + # The problem this module is trying to solve is finding the total running # time amongst all the jobs, excluding retries and pending (queue) time. # We could reduce this problem down to finding the union of periods. # @@ -76,7 +76,9 @@ module Gitlab # (4 - 1) + (7 - 6) => 4 # # That is 4 is the answer in the example. - class PipelineDuration + module PipelineDuration + extend self + PeriodStruct = Struct.new(:first, :last) class Period < PeriodStruct def duration @@ -84,33 +86,27 @@ module Gitlab end end - def self.from_pipeline(pipeline) + def from_pipeline(pipeline) status = %w[success failed running canceled] builds = pipeline.builds.latest.where(status: status) - from_builds(builds, :started_at, :finished_at).duration + from_builds(builds, :started_at, :finished_at) end - def self.from_builds(builds, from, to, now = Time.now) + def from_builds(builds, from, to, now = Time.now) periods = builds.map do |b| Period.new(b.public_send(from) || now, b.public_send(to) || now) end - new(periods) + from_periods(periods) end - attr_reader :duration - - def initialize(periods) - process(periods.sort_by(&:first)) + def from_periods(periods) + process_duration(process_periods(periods.sort_by(&:first))) end private - def process(periods) - @duration = process_duration(process_periods(periods)) - end - def process_periods(periods) return periods if periods.empty? diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb index a160c0a6e39..580af97bea7 100644 --- a/spec/lib/gitlab/ci/pipeline_duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' describe Gitlab::Ci::PipelineDuration do - let(:calculator) { create_calculator(data) } + let(:calculated_duration) { calculate(data) } shared_examples 'calculating duration' do it do - expect(calculator.duration).to eq(duration) + expect(calculated_duration).to eq(duration) end end @@ -105,11 +105,11 @@ describe Gitlab::Ci::PipelineDuration do it_behaves_like 'calculating duration' end - def create_calculator(data) + def calculate(data) periods = data.shuffle.map do |(first, last)| Gitlab::Ci::PipelineDuration::Period.new(first, last) end - Gitlab::Ci::PipelineDuration.new(periods) + Gitlab::Ci::PipelineDuration.from_periods(periods) end end -- cgit v1.2.3 From 7351c269deae2c28c63e793df11155e0f6c75d04 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Sep 2016 19:36:07 +0800 Subject: Sort by database, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14991226 and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14994233 --- lib/gitlab/ci/pipeline_duration.rb | 6 ++++-- spec/lib/gitlab/ci/pipeline_duration_spec.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 0333807263f..711f911346c 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -88,7 +88,8 @@ module Gitlab def from_pipeline(pipeline) status = %w[success failed running canceled] - builds = pipeline.builds.latest.where(status: status) + builds = pipeline.builds.latest. + where(status: status).where.not(started_at: nil).order(:started_at) from_builds(builds, :started_at, :finished_at) end @@ -101,8 +102,9 @@ module Gitlab from_periods(periods) end + # periods should be sorted by `first` def from_periods(periods) - process_duration(process_periods(periods.sort_by(&:first))) + process_duration(process_periods(periods)) end private diff --git a/spec/lib/gitlab/ci/pipeline_duration_spec.rb b/spec/lib/gitlab/ci/pipeline_duration_spec.rb index 580af97bea7..b26728a843c 100644 --- a/spec/lib/gitlab/ci/pipeline_duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline_duration_spec.rb @@ -110,6 +110,6 @@ describe Gitlab::Ci::PipelineDuration do Gitlab::Ci::PipelineDuration::Period.new(first, last) end - Gitlab::Ci::PipelineDuration.from_periods(periods) + Gitlab::Ci::PipelineDuration.from_periods(periods.sort_by(&:first)) end end -- cgit v1.2.3 From c2bcfab18af1cf9253a47d4ffd3ea48e43cd19be Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Sep 2016 19:53:02 +0800 Subject: Remove redundant tests, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5525#note_14993999 --- .../project_services/slack_service/build_message_spec.rb | 12 ------------ .../project_services/slack_service/pipeline_message_spec.rb | 12 ------------ 2 files changed, 24 deletions(-) diff --git a/spec/models/project_services/slack_service/build_message_spec.rb b/spec/models/project_services/slack_service/build_message_spec.rb index 0b876f4fd26..452f4e2782c 100644 --- a/spec/models/project_services/slack_service/build_message_spec.rb +++ b/spec/models/project_services/slack_service/build_message_spec.rb @@ -47,18 +47,6 @@ describe SlackService::BuildMessage do end end - describe '#seconds_name' do - let(:status) { 'failed' } - let(:color) { 'danger' } - let(:duration) { 1 } - - it 'returns seconds as singular when there is only one' do - expect(subject.pretext).to be_empty - expect(subject.fallback).to eq(message) - expect(subject.attachments).to eq([text: message, color: color]) - end - end - def build_message(status_text = status) ":" \ " Commit :" \ " Pipeline " \ -- cgit v1.2.3 From 61bc90af0ea247c5f561d8b71348ab028566033d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Sep 2016 19:57:51 +0800 Subject: Be more specific since it's not needed to be generic now, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_14995016 --- lib/gitlab/ci/pipeline_duration.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 711f911346c..10ad70f14fa 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -91,12 +91,14 @@ module Gitlab builds = pipeline.builds.latest. where(status: status).where.not(started_at: nil).order(:started_at) - from_builds(builds, :started_at, :finished_at) + from_builds(builds) end - def from_builds(builds, from, to, now = Time.now) + def from_builds(builds) + now = Time.now + periods = builds.map do |b| - Period.new(b.public_send(from) || now, b.public_send(to) || now) + Period.new(b.started_at, b.finished_at || now) end from_periods(periods) -- cgit v1.2.3 From 0986fe23bc8f55de07a5220add33275c3c73952e Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Fri, 5 Aug 2016 16:42:25 -0500 Subject: Replace animateEmoji timeout with eventListener --- CHANGELOG | 1 + app/assets/javascripts/awards_handler.js | 10 ++-- app/assets/stylesheets/framework/animations.scss | 67 ++++++++---------------- app/assets/stylesheets/framework/mixins.scss | 10 ++++ 4 files changed, 39 insertions(+), 49 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 982b4dd5e5a..a0f295653d4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.12.0 (unreleased) - API: Expose issue confidentiality flag. (Robert Schilling) - Fix markdown anchor icon interaction (ClemMakesApps) - Test migration paths from 8.5 until current release !4874 + - Replace animateEmoji timeout with eventListener (ClemMakesApps) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Remove Gitorious import diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index ad12cb906e1..5ea18ea8b7a 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -255,12 +255,12 @@ }; AwardsHandler.prototype.animateEmoji = function($emoji) { - var className; - className = 'pulse animated'; + var className = 'pulse animated once short'; $emoji.addClass(className); - return setTimeout((function() { - return $emoji.removeClass(className); - }), 321); + + $emoji.on('webkitAnimationEnd animationEnd', function() { + $(this).removeClass(className); + }); }; AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) { diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 1fec61bdba1..1e9a45c19b8 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -8,65 +8,44 @@ // Copyright (c) 2016 Daniel Eden .animated { - -webkit-animation-duration: 1s; - animation-duration: 1s; - -webkit-animation-fill-mode: both; - animation-fill-mode: both; -} - -.animated.infinite { - -webkit-animation-iteration-count: infinite; - animation-iteration-count: infinite; -} + @include webkit-prefix(animation-duration, 1s); + @include webkit-prefix(animation-fill-mode, both); -.animated.hinge { - -webkit-animation-duration: 2s; - animation-duration: 2s; -} + &.infinite { + @include webkit-prefix(animation-iteration-count, infinite); + } -.animated.flipOutX, -.animated.flipOutY, -.animated.bounceIn, -.animated.bounceOut { - -webkit-animation-duration: .75s; - animation-duration: .75s; -} + &.once { + @include webkit-prefix(animation-iteration-count, 1); + } -@-webkit-keyframes pulse { - from { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); + &.hinge { + @include webkit-prefix(animation-duration, 2s); } - 50% { - -webkit-transform: scale3d(1.05, 1.05, 1.05); - transform: scale3d(1.05, 1.05, 1.05); + &.flipOutX, + &.flipOutY, + &.bounceIn, + &.bounceOut { + @include webkit-prefix(animation-duration, .75s); } - to { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); + &.short { + @include webkit-prefix(animation-duration, 321ms); + @include webkit-prefix(animation-fill-mode, none); } } -@keyframes pulse { - from { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); +@include keyframes(pulse) { + from, to { + @include webkit-prefix(transform, scale3d(1, 1, 1)); } 50% { - -webkit-transform: scale3d(1.05, 1.05, 1.05); - transform: scale3d(1.05, 1.05, 1.05); - } - - to { - -webkit-transform: scale3d(1, 1, 1); - transform: scale3d(1, 1, 1); + @include webkit-prefix(transform, scale3d(1.05, 1.05, 1.05)); } } .pulse { - -webkit-animation-name: pulse; - animation-name: pulse; + @include webkit-prefix(animation-name, pulse); } diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 00f92cef9a4..1ec08cdef23 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -85,3 +85,13 @@ #{'-webkit-' + $property}: $value; #{$property}: $value; } + +@mixin keyframes($animation-name) { + @-webkit-keyframes #{$animation-name} { + @content; + } + + @keyframes #{$animation-name} { + @content; + } +} -- cgit v1.2.3 From ea155ccc3edd071a0ca5adfd774513892b19ab6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Fri, 2 Sep 2016 17:29:47 -0300 Subject: Optimize discussion notes resolving and unresolving Use `update_all` to only require one query per discussion to update the notes resolved status. Some changes had to be made to the discussion spec to accout for the fact that notes are not individually updated now --- CHANGELOG | 1 + app/models/diff_note.rb | 18 ++++++++ app/models/discussion.rb | 39 +++++++++++------ spec/factories/notes.rb | 5 +++ spec/models/diff_note_spec.rb | 37 ++++++++++++++++ spec/models/discussion_spec.rb | 96 ++++++++++++++++-------------------------- 6 files changed, 125 insertions(+), 71 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 982b4dd5e5a..880f85bf57a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -94,6 +94,7 @@ v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch - Fix member expiration date picker after update - Fix suggested colors options for new labels in the admin area. !6138 + - Optimize discussion notes resolving and unresolving - Fix GitLab import button - Remove gitorious from import_sources diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 4442cefc7e9..559b3075905 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -13,6 +13,11 @@ class DiffNote < Note validate :positions_complete validate :verify_supported + # Keep this scope in sync with the logic in `#resolvable?` + scope :resolvable, -> { user.where(noteable_type: 'MergeRequest') } + scope :resolved, -> { resolvable.where.not(resolved_at: nil) } + scope :unresolved, -> { resolvable.where(resolved_at: nil) } + after_initialize :ensure_original_discussion_id before_validation :set_original_position, :update_position, on: :create before_validation :set_line_code, :set_original_discussion_id @@ -25,6 +30,16 @@ class DiffNote < Note def build_discussion_id(noteable_type, noteable_id, position) [super(noteable_type, noteable_id), *position.key].join("-") end + + # This method must be kept in sync with `#resolve!` + def resolve!(current_user) + unresolved.update_all(resolved_at: Time.now, resolved_by_id: current_user.id) + end + + # This method must be kept in sync with `#unresolve!` + def unresolve! + resolved.update_all(resolved_at: nil, resolved_by_id: nil) + end end def new_diff_note? @@ -73,6 +88,7 @@ class DiffNote < Note self.position.diff_refs == diff_refs end + # If you update this method remember to also update the scope `resolvable` def resolvable? !system? && for_merge_request? end @@ -83,6 +99,7 @@ class DiffNote < Note self.resolved_at.present? end + # If you update this method remember to also update `.resolve!` def resolve!(current_user) return unless resolvable? return if resolved? @@ -92,6 +109,7 @@ class DiffNote < Note save! end + # If you update this method remember to also update `.unresolve!` def unresolve! return unless resolvable? return unless resolved? diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 9676bc03470..de06c13481a 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -1,7 +1,7 @@ class Discussion NUMBER_OF_TRUNCATED_DIFF_LINES = 16 - attr_reader :first_note, :last_note, :notes + attr_reader :notes delegate :created_at, :project, @@ -36,8 +36,6 @@ class Discussion end def initialize(notes) - @first_note = notes.first - @last_note = notes.last @notes = notes end @@ -70,17 +68,25 @@ class Discussion end def resolvable? - return @resolvable if defined?(@resolvable) + return @resolvable if @resolvable.present? @resolvable = diff_discussion? && notes.any?(&:resolvable?) end def resolved? - return @resolved if defined?(@resolved) + return @resolved if @resolved.present? @resolved = resolvable? && notes.none?(&:to_be_resolved?) end + def first_note + @first_note ||= @notes.first + end + + def last_note + @last_note ||= @notes.last + end + def resolved_notes notes.select(&:resolved?) end @@ -100,17 +106,13 @@ class Discussion def resolve!(current_user) return unless resolvable? - notes.each do |note| - note.resolve!(current_user) if note.resolvable? - end + update { |notes| notes.resolve!(current_user) } end def unresolve! return unless resolvable? - notes.each do |note| - note.unresolve! if note.resolvable? - end + update { |notes| notes.unresolve! } end def for_target?(target) @@ -118,7 +120,7 @@ class Discussion end def active? - return @active if defined?(@active) + return @active if @active.present? @active = first_note.active? end @@ -174,4 +176,17 @@ class Discussion prev_lines end + + private + + def update + notes_relation = DiffNote.where(id: notes.map(&:id)).fresh + yield(notes_relation) + + # Set the notes array to the updated notes + @notes = notes_relation.to_a + + # Reset the memoized values + @last_resolved_note = @resolvable = @resolved = @first_note = @last_note = nil + end end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 83e38095feb..6919002dedc 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -28,6 +28,11 @@ FactoryGirl.define do diff_refs: noteable.diff_refs ) end + + trait :resolved do + resolved_at { Time.now } + resolved_by { create(:user) } + end end factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 6a640474cfe..3db5937a4f3 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -31,6 +31,43 @@ describe DiffNote, models: true do subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) } + describe ".resolve!" do + let(:current_user) { create(:user) } + let!(:commit_note) { create(:diff_note_on_commit) } + let!(:resolved_note) { create(:diff_note_on_merge_request, :resolved) } + let!(:unresolved_note) { create(:diff_note_on_merge_request) } + + before do + described_class.resolve!(current_user) + + commit_note.reload + resolved_note.reload + unresolved_note.reload + end + + it 'resolves only the resolvable, not yet resolved notes' do + expect(commit_note.resolved_at).to be_nil + expect(resolved_note.resolved_by).not_to eq(current_user) + expect(unresolved_note.resolved_at).not_to be_nil + expect(unresolved_note.resolved_by).to eq(current_user) + end + end + + describe ".unresolve!" do + let!(:resolved_note) { create(:diff_note_on_merge_request, :resolved) } + + before do + described_class.unresolve! + + resolved_note.reload + end + + it 'unresolves the resolved notes' do + expect(resolved_note.resolved_by).to be_nil + expect(resolved_note.resolved_at).to be_nil + end + end + describe "#position=" do context "when provided a string" do it "sets the position" do diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb index 179f2e73662..0142706d140 100644 --- a/spec/models/discussion_spec.rb +++ b/spec/models/discussion_spec.rb @@ -238,27 +238,19 @@ describe Discussion, model: true do context "when resolvable" do let(:user) { create(:user) } + let(:second_note) { create(:diff_note_on_commit) } # unresolvable before do allow(subject).to receive(:resolvable?).and_return(true) - - allow(first_note).to receive(:resolvable?).and_return(true) - allow(second_note).to receive(:resolvable?).and_return(false) - allow(third_note).to receive(:resolvable?).and_return(true) end context "when all resolvable notes are resolved" do before do first_note.resolve!(user) third_note.resolve!(user) - end - it "calls resolve! on every resolvable note" do - expect(first_note).to receive(:resolve!).with(current_user) - expect(second_note).not_to receive(:resolve!) - expect(third_note).to receive(:resolve!).with(current_user) - - subject.resolve!(current_user) + first_note.reload + third_note.reload end it "doesn't change resolved_at on the resolved notes" do @@ -309,46 +301,44 @@ describe Discussion, model: true do first_note.resolve!(user) end - it "calls resolve! on every resolvable note" do - expect(first_note).to receive(:resolve!).with(current_user) - expect(second_note).not_to receive(:resolve!) - expect(third_note).to receive(:resolve!).with(current_user) - - subject.resolve!(current_user) - end - it "doesn't change resolved_at on the resolved note" do expect(first_note.resolved_at).not_to be_nil - expect { subject.resolve!(current_user) }.not_to change { first_note.resolved_at } + expect { subject.resolve!(current_user) }. + not_to change { first_note.reload.resolved_at } end it "doesn't change resolved_by on the resolved note" do expect(first_note.resolved_by).to eq(user) - expect { subject.resolve!(current_user) }.not_to change { first_note.resolved_by } + expect { subject.resolve!(current_user) }. + not_to change { first_note.reload && first_note.resolved_by } end it "doesn't change the resolved state on the resolved note" do expect(first_note.resolved?).to be true - expect { subject.resolve!(current_user) }.not_to change { first_note.resolved? } + expect { subject.resolve!(current_user) }. + not_to change { first_note.reload && first_note.resolved? } end it "sets resolved_at on the unresolved note" do subject.resolve!(current_user) + third_note.reload expect(third_note.resolved_at).not_to be_nil end it "sets resolved_by on the unresolved note" do subject.resolve!(current_user) + third_note.reload expect(third_note.resolved_by).to eq(current_user) end it "marks the unresolved note as resolved" do subject.resolve!(current_user) + third_note.reload expect(third_note.resolved?).to be true end @@ -373,16 +363,10 @@ describe Discussion, model: true do end context "when no resolvable notes are resolved" do - it "calls resolve! on every resolvable note" do - expect(first_note).to receive(:resolve!).with(current_user) - expect(second_note).not_to receive(:resolve!) - expect(third_note).to receive(:resolve!).with(current_user) - - subject.resolve!(current_user) - end - it "sets resolved_at on the unresolved notes" do subject.resolve!(current_user) + first_note.reload + third_note.reload expect(first_note.resolved_at).not_to be_nil expect(third_note.resolved_at).not_to be_nil @@ -390,6 +374,8 @@ describe Discussion, model: true do it "sets resolved_by on the unresolved notes" do subject.resolve!(current_user) + first_note.reload + third_note.reload expect(first_note.resolved_by).to eq(current_user) expect(third_note.resolved_by).to eq(current_user) @@ -397,6 +383,8 @@ describe Discussion, model: true do it "marks the unresolved notes as resolved" do subject.resolve!(current_user) + first_note.reload + third_note.reload expect(first_note.resolved?).to be true expect(third_note.resolved?).to be true @@ -404,18 +392,24 @@ describe Discussion, model: true do it "sets resolved_at" do subject.resolve!(current_user) + first_note.reload + third_note.reload expect(subject.resolved_at).not_to be_nil end it "sets resolved_by" do subject.resolve!(current_user) + first_note.reload + third_note.reload expect(subject.resolved_by).to eq(current_user) end it "marks as resolved" do subject.resolve!(current_user) + first_note.reload + third_note.reload expect(subject.resolved?).to be true end @@ -451,16 +445,10 @@ describe Discussion, model: true do third_note.resolve!(user) end - it "calls unresolve! on every resolvable note" do - expect(first_note).to receive(:unresolve!) - expect(second_note).not_to receive(:unresolve!) - expect(third_note).to receive(:unresolve!) - - subject.unresolve! - end - it "unsets resolved_at on the resolved notes" do subject.unresolve! + first_note.reload + third_note.reload expect(first_note.resolved_at).to be_nil expect(third_note.resolved_at).to be_nil @@ -468,6 +456,8 @@ describe Discussion, model: true do it "unsets resolved_by on the resolved notes" do subject.unresolve! + first_note.reload + third_note.reload expect(first_note.resolved_by).to be_nil expect(third_note.resolved_by).to be_nil @@ -475,6 +465,8 @@ describe Discussion, model: true do it "unmarks the resolved notes as resolved" do subject.unresolve! + first_note.reload + third_note.reload expect(first_note.resolved?).to be false expect(third_note.resolved?).to be false @@ -482,12 +474,16 @@ describe Discussion, model: true do it "unsets resolved_at" do subject.unresolve! + first_note.reload + third_note.reload expect(subject.resolved_at).to be_nil end it "unsets resolved_by" do subject.unresolve! + first_note.reload + third_note.reload expect(subject.resolved_by).to be_nil end @@ -504,40 +500,22 @@ describe Discussion, model: true do first_note.resolve!(user) end - it "calls unresolve! on every resolvable note" do - expect(first_note).to receive(:unresolve!) - expect(second_note).not_to receive(:unresolve!) - expect(third_note).to receive(:unresolve!) - - subject.unresolve! - end - it "unsets resolved_at on the resolved note" do subject.unresolve! - expect(first_note.resolved_at).to be_nil + expect(subject.first_note.resolved_at).to be_nil end it "unsets resolved_by on the resolved note" do subject.unresolve! - expect(first_note.resolved_by).to be_nil + expect(subject.first_note.resolved_by).to be_nil end it "unmarks the resolved note as resolved" do subject.unresolve! - expect(first_note.resolved?).to be false - end - end - - context "when no resolvable notes are resolved" do - it "calls unresolve! on every resolvable note" do - expect(first_note).to receive(:unresolve!) - expect(second_note).not_to receive(:unresolve!) - expect(third_note).to receive(:unresolve!) - - subject.unresolve! + expect(subject.first_note.resolved?).to be false end end end -- cgit v1.2.3 From 8e97323d490129b666d01f76412b57f0ccb0c909 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 6 Sep 2016 17:26:16 +0200 Subject: Constants in specs --- spec/lib/gitlab/workhorse_spec.rb | 2 +- spec/requests/ci/api/builds_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 63032d43027..395192149a9 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -63,7 +63,7 @@ describe Gitlab::Workhorse, lib: true do end describe '#verify_api_request!' do - let(:header_key) { described_class.const_get('INTERNAL_API_REQUEST_HEADER') } + let(:header_key) { described_class::INTERNAL_API_REQUEST_HEADER } let(:payload) { { 'iss' => 'gitlab-workhorse' } } it 'accepts a correct header' do diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 29a194b31f6..9e390bea50b 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -253,7 +253,7 @@ describe Ci::API::API do end it "reject requests that did not go through gitlab-workhorse" do - headers.delete('Gitlab-Workhorse-Api-Request') + headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) post authorize_url, { token: build.token }, headers expect(response).to have_http_status(500) end -- cgit v1.2.3 From 7da2dcc12dbc11ed9790d94861fc23f0962f331c Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Tue, 6 Sep 2016 10:54:42 -0700 Subject: fix margin for awards-control buttons --- app/assets/stylesheets/pages/awards.scss | 2 -- app/views/projects/merge_requests/_show.html.haml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 5faedfedd66..188a4e193a6 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -94,10 +94,8 @@ .award-control { margin-right: 5px; - margin-bottom: 5px; padding-left: 5px; padding-right: 5px; - line-height: 20px; outline: 0; &:hover, diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 9d8b4cc56be..4b4d418e8ec 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -83,7 +83,7 @@ .tab-content#diff-notes-app #notes.notes.tab-pane.voting_notes - .content-block.content-block-small.oneline-block + .content-block.content-block-small = render 'award_emoji/awards_block', awardable: @merge_request, inline: true .row -- cgit v1.2.3 From e485e601c244d45b1d23c5fa3da9332fa4cec50f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 6 Sep 2016 13:57:03 -0500 Subject: Ensure we update dropdown label after selecting an option --- app/assets/javascripts/gl_dropdown.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 77b2082cba0..e3f672d6794 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -322,7 +322,13 @@ if (self.options.clicked) { self.options.clicked(selected, $el, e); } - return $el.trigger('blur'); + + // Update label right after all modifications in dropdown has been done + if (self.options.toggleLabel) { + self.updateLabel(selected, $el, self); + } + + $el.trigger('blur'); }); } } @@ -594,11 +600,6 @@ } } - // Update label right after input has been added - if (this.options.toggleLabel) { - this.updateLabel(selectedObject, el, this); - } - return selectedObject; }; -- cgit v1.2.3 From c67d2f46dd24d6f2bfc0d64ed62a4a6b47f1fcc0 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 6 Sep 2016 13:59:20 -0500 Subject: =?UTF-8?q?fieldName=20can=E2=80=99t=20be=20a=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore changes introduced in https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/581 --- app/assets/javascripts/gl_dropdown.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e3f672d6794..8714ddb3136 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -488,7 +488,7 @@ } else { if (!selected) { value = this.options.id ? this.options.id(data) : data.id; - fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName; + fieldName = this.options.fieldName; field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); if (field.length) { @@ -546,6 +546,7 @@ GitLabDropdown.prototype.rowClicked = function(el) { var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; + fieldName = this.options.fieldName; isInput = $(this.el).is('input'); if (this.renderedData) { groupName = el.data('group'); @@ -557,7 +558,6 @@ selectedObject = this.renderedData[selectedIndex]; } } - fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { field = $(this.el); @@ -609,9 +609,6 @@ if (this.options.inputId != null) { $input.attr('id', this.options.inputId); } - if (selectedObject && selectedObject.type) { - $input.attr('data-type', selectedObject.type); - } return this.dropdown.before($input); }; -- cgit v1.2.3 From 54ee59154ff3905252a1f95d9f96ff06921cbf5e Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Tue, 6 Sep 2016 19:57:18 +0000 Subject: Gitlab -> GitLab --- doc/install/requirements.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 9799e0a3730..766a7119943 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -86,7 +86,7 @@ if your available memory changes. Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those. -## Gitlab Runner +## GitLab Runner We strongly advise against installing GitLab Runner on the same machine you plan to install GitLab on. Depending on how you decide to configure GitLab Runner and -- cgit v1.2.3 From aaaa781a2e042b15d0fef3f572184a420390fa3a Mon Sep 17 00:00:00 2001 From: Jonny McCullagh Date: Tue, 6 Sep 2016 21:34:33 +0100 Subject: Issue #21440: Fix to curl example for adding member to project --- doc/api/members.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/members.md b/doc/api/members.md index fd6d728dad2..6535e9a7801 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -110,8 +110,8 @@ POST /projects/:id/members | `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/:id/members/:user_id?access_level=30 -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/:id/members/:user_id?access_level=30 +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v3/groups/:id/members +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "user_id=1&access_level=30" https://gitlab.example.com/api/v3/projects/:id/members ``` Example response: -- cgit v1.2.3 From dfa286c1818eea9f05d3a76f99b6b2dd7b66e1d7 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 2 Sep 2016 12:30:54 -0300 Subject: Fix project settings field --- CHANGELOG | 1 + app/helpers/projects_helper.rb | 24 +++++++++++--------- spec/helpers/projects_helper_spec.rb | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 982b4dd5e5a..b28e3024ffc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.12.0 (unreleased) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - Remove suggested colors hover underline (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) + - Fix project visibility level fields on settings - Add hover color to emoji icon (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps) - Add white background for no readme container (ClemMakesApps) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 4c685b97c03..16a8e52a4ca 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -129,6 +129,19 @@ module ProjectsHelper current_user.recent_push(project_ids) end + def project_feature_access_select(field) + # Don't show option "everyone with access" if project is private + options = project_feature_options + + if @project.private? + options.delete('Everyone with access') + highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED + end + + options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field)) + content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe + end + private def get_project_nav_tabs(project, current_user) @@ -422,15 +435,4 @@ module ProjectsHelper 'Everyone with access' => ProjectFeature::ENABLED } end - - def project_feature_access_select(field) - # Don't show option "everyone with access" if project is private - options = project_feature_options - level = @project.project_feature.public_send(field) - - options.delete('Everyone with access') if @project.private? && level != ProjectFeature::ENABLED - - options = options_for_select(options, selected: @project.project_feature.public_send(field) || ProjectFeature::ENABLED) - content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control", data: { field: field }).html_safe - end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 284b58d8d5c..70032e7df94 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -174,4 +174,48 @@ describe ProjectsHelper do end end end + + describe "#project_feature_access_select" do + let(:project) { create(:empty_project, :public) } + let(:user) { create(:user) } + + context "when project is internal or public" do + it "shows all options" do + helper.instance_variable_set(:@project, project) + result = helper.project_feature_access_select(:issues_access_level) + expect(result).to include("Disabled") + expect(result).to include("Only team members") + expect(result).to include("Everyone with access") + end + end + + context "when project is private" do + before { project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + + it "shows only allowed options" do + helper.instance_variable_set(:@project, project) + result = helper.project_feature_access_select(:issues_access_level) + expect(result).to include("Disabled") + expect(result).to include("Only team members") + expect(result).not_to include("Everyone with access") + end + end + + context "when project moves from public to private" do + before do + project.project_feature.update_attributes(issues_access_level: ProjectFeature::ENABLED) + project.update_attributes(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it "shows the highest allowed level selected" do + helper.instance_variable_set(:@project, project) + result = helper.project_feature_access_select(:issues_access_level) + + expect(result).to include("Disabled") + expect(result).to include("Only team members") + expect(result).not_to include("Everyone with access") + expect(result).to have_selector('option[selected]', text: "Only team members") + end + end + end end -- cgit v1.2.3 From 9b5a83816ec96b4c3a896c3d7c5edf358e62622f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 31 Aug 2016 16:30:54 +0300 Subject: Add link to compare merge request versions It adds a dropdown to the right of merge request version dropdown and allow you to choose older version for compare. Once clicked it will take user to compare page with older and newer versions sha pre-filled Signed-off-by: Dmitriy Zaporozhets --- app/helpers/merge_requests_helper.rb | 4 ++++ .../merge_requests/show/_versions.html.haml | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index a9e175c3f5c..d5470d5d3c8 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -100,4 +100,8 @@ module MergeRequestsHelper def merge_request_button_visibility(merge_request, closed) return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? end + + def compare_versions_path(project, version, base_version) + namespace_project_compare_path(project.namespace, project, base_version.head_commit_sha, version.head_commit_sha) + end end diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 2da70ce7137..d2a70bcc716 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -22,10 +22,32 @@ #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, = time_ago_with_tooltip(merge_request_diff.created_at) + %span.prepend-left-default + Compared with: + %span.dropdown.inline + %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } + %strong.monospace base + %span.caret + %ul.dropdown-menu.dropdown-menu-selectable + - merge_request_diffs.each do |merge_request_diff| + - next if merge_request_diff.id >= @merge_request_diff.id + %li + = link_to compare_versions_path(@project, @merge_request_diff, merge_request_diff), class: ('is-active' if merge_request_diff == @base_version), target: '_blank' do + %strong.monospace + #{merge_request_diff.head_commit.short_id} + %br + %small + = time_ago_with_tooltip(merge_request_diff.created_at) + %li + = link_to '#', class: 'is-active' do + %strong.monospace + base + - unless @merge_request_diff.latest? %span.prepend-left-default = icon('info-circle') This version is not the latest one. Comments are disabled + .pull-right %span.monospace #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} -- cgit v1.2.3 From 8b2e065b7b878b27f0494248b3734381291a67bd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 31 Aug 2016 18:14:14 +0300 Subject: Compare merge request version against other version on changes page Signed-off-by: Dmitriy Zaporozhets --- .../projects/merge_requests_controller.rb | 17 ++++++++++++++- app/helpers/merge_requests_helper.rb | 6 ++++-- .../merge_requests/show/_versions.html.haml | 24 +++++++++++++--------- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 4f9ca0097a1..2fa55b83c0e 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -98,7 +98,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diff_notes_disabled = true end - @diffs = @merge_request_diff.diffs(diff_options) + if params[:start_sha].present? + compare_diff_version + else + @diffs = @merge_request_diff.diffs(diff_options) + end render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } end @@ -529,4 +533,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute end + + def compare_diff_version + @compare = CompareService.new.execute(@project, @merge_request_diff.head_commit_sha, @project, params[:start_sha]) + + if @compare + @commits = @compare.commits + @commit = @compare.commit + @diffs = @compare.diffs(diff_options) + @diff_notes_disabled = true + end + end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index d5470d5d3c8..1379180cb6e 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -101,7 +101,9 @@ module MergeRequestsHelper return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? end - def compare_versions_path(project, version, base_version) - namespace_project_compare_path(project.namespace, project, base_version.head_commit_sha, version.head_commit_sha) + def mr_version_path(project, merge_request, merge_request_diff, start_sha = nil) + diffs_namespace_project_merge_request_path( + project.namespace, project, merge_request, + diff_id: merge_request_diff.id, start_sha: start_sha) end end diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index d2a70bcc716..13bec019988 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -26,28 +26,32 @@ Compared with: %span.dropdown.inline %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } - %strong.monospace base + %strong.monospace< + - if params[:start_sha].present? + #{params[:start_sha][0...8]} + - else + #{"base"} %span.caret %ul.dropdown-menu.dropdown-menu-selectable - merge_request_diffs.each do |merge_request_diff| - next if merge_request_diff.id >= @merge_request_diff.id %li - = link_to compare_versions_path(@project, @merge_request_diff, merge_request_diff), class: ('is-active' if merge_request_diff == @base_version), target: '_blank' do + = link_to mr_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff.head_commit_sha == params[:start_sha]) do %strong.monospace #{merge_request_diff.head_commit.short_id} %br %small = time_ago_with_tooltip(merge_request_diff.created_at) %li - = link_to '#', class: 'is-active' do + = link_to mr_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless params[:start_sha].present?) do %strong.monospace base - - unless @merge_request_diff.latest? - %span.prepend-left-default - = icon('info-circle') - This version is not the latest one. Comments are disabled - .pull-right - %span.monospace - #{@merge_request_diff.base_commit.short_id}..#{@merge_request_diff.head_commit.short_id} + - unless @merge_request_diff.latest? && params[:start_sha].blank? + .pull-right + = icon('info-circle') + - if params[:start_sha].present? + Comments are disabled when compare with version different from base + - else + This version is not the latest one. Comments are disabled -- cgit v1.2.3 From 96b83a58cc7f9a8c5b3fdf2396f5188029a4f280 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 1 Sep 2016 15:23:39 +0300 Subject: Improve merge request version switch/compare dropdown Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 6 +- app/helpers/git_helper.rb | 4 ++ .../merge_requests/show/_versions.html.haml | 69 +++++++++++----------- .../merge_requests/merge_request_versions_spec.rb | 25 ++++++-- 4 files changed, 65 insertions(+), 39 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7fdd79fa8b9..dd3e79b37af 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -375,7 +375,7 @@ } } -.mr-version-switch { +.mr-version-controls { background: $background-color; padding: $gl-btn-padding; color: $gl-placeholder-color; @@ -383,6 +383,10 @@ a.btn-link { color: $gl-dark-link-color; } + + .compare-dots { + margin: 0 $btn-side-margin; + } } .merge-request-details { diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb index 09684955233..9d7982e169c 100644 --- a/app/helpers/git_helper.rb +++ b/app/helpers/git_helper.rb @@ -2,4 +2,8 @@ module GitHelper def strip_gpg_signature(text) text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "") end + + def short_sha(text) + text[0...8] + end end diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 13bec019988..bdedaa31e12 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -1,57 +1,58 @@ - merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff +- compareable_diffs = merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } - if merge_request_diffs.size > 1 - .mr-version-switch - Version: - %span.dropdown.inline + .mr-version-controls + Version + %span.dropdown.inline.mr-version-dropdown %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } %strong.monospace< - if @merge_request_diff.latest? - #{"latest"} + Latest: #{short_sha(@merge_request_diff.head_commit_sha)} - else - #{@merge_request_diff.head_commit.short_id} + #{short_sha(@merge_request_diff.head_commit_sha)} %span.caret %ul.dropdown-menu.dropdown-menu-selectable - - merge_request_diffs.each do |merge_request_diff| + - merge_request_diffs.each_with_index do |merge_request_diff, i| %li = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do %strong.monospace + - if i.zero? + Latest: + - else + #{merge_request_diffs.size - i}. #{merge_request_diff.head_commit.short_id} %br %small #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, = time_ago_with_tooltip(merge_request_diff.created_at) - %span.prepend-left-default - Compared with: - %span.dropdown.inline - %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } - %strong.monospace< - - if params[:start_sha].present? - #{params[:start_sha][0...8]} - - else - #{"base"} - %span.caret - %ul.dropdown-menu.dropdown-menu-selectable - - merge_request_diffs.each do |merge_request_diff| - - next if merge_request_diff.id >= @merge_request_diff.id - %li - = link_to mr_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff.head_commit_sha == params[:start_sha]) do - %strong.monospace - #{merge_request_diff.head_commit.short_id} - %br - %small - = time_ago_with_tooltip(merge_request_diff.created_at) + %span.compare-dots ... + + Compared with + %span.dropdown.inline.mr-version-compare-dropdown + %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } + %strong.monospace< + - if params[:start_sha].present? + #{short_sha(params[:start_sha])} + - else + Base: #{short_sha(@merge_request_diff.base_commit_sha)} + %span.caret + %ul.dropdown-menu.dropdown-menu-selectable + - compareable_diffs.each_with_index do |merge_request_diff, i| %li - = link_to mr_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless params[:start_sha].present?) do + = link_to mr_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff.head_commit_sha == params[:start_sha]) do %strong.monospace - base - + #{compareable_diffs.size - i}. #{short_sha(merge_request_diff.head_commit_sha)} + %br + %small + = time_ago_with_tooltip(merge_request_diff.created_at) + %li + = link_to mr_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless params[:start_sha].present?) do + %strong.monospace + Base: #{short_sha(@merge_request_diff.base_commit_sha)} - unless @merge_request_diff.latest? && params[:start_sha].blank? - .pull-right + .prepend-top-10 = icon('info-circle') - - if params[:start_sha].present? - Comments are disabled when compare with version different from base - - else - This version is not the latest one. Comments are disabled + Comments are disabled while viewing outdated merge versions or comparing to versions other than base. diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb index 577c910f11b..df66dd23448 100644 --- a/spec/features/merge_requests/merge_request_versions_spec.rb +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -11,7 +11,7 @@ feature 'Merge Request versions', js: true, feature: true do end it 'show the latest version of the diff' do - page.within '.mr-version-switch' do + page.within '.mr-version-dropdown' do expect(page).to have_content 'Version: latest' end @@ -20,15 +20,32 @@ feature 'Merge Request versions', js: true, feature: true do describe 'switch between versions' do before do - page.within '.mr-version-switch' do + page.within '.mr-version-dropdown' do find('.btn-link').click click_link '6f6d7e7e' end end it 'should show older version' do - page.within '.mr-version-switch' do - expect(page).to have_content 'Version: 6f6d7e7e' + page.within '.mr-version-dropdown' do + expect(page).to have_content '6f6d7e7e' + end + + expect(page).to have_content '5 changed files' + end + end + + describe 'compare with older version' do + before do + page.within '.mr-version-compare-dropdown' do + find('.btn-link').click + click_link '6f6d7e7e' + end + end + + it 'should show older version' do + page.within '.mr-version-compare-dropdown' do + expect(page).to have_content '6f6d7e7e' end expect(page).to have_content '5 changed files' -- cgit v1.2.3 From 45a984b80baab4b5330fcad22a2139d547116bd9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 1 Sep 2016 15:35:39 +0300 Subject: Add more tests for merge request versions feature Signed-off-by: Dmitriy Zaporozhets --- .../merge_requests/merge_request_versions_spec.rb | 20 +++++++++++++++++--- spec/helpers/git_helper_spec.rb | 9 +++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 spec/helpers/git_helper_spec.rb diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb index df66dd23448..7ccf4e8e8f2 100644 --- a/spec/features/merge_requests/merge_request_versions_spec.rb +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -12,7 +12,7 @@ feature 'Merge Request versions', js: true, feature: true do it 'show the latest version of the diff' do page.within '.mr-version-dropdown' do - expect(page).to have_content 'Version: latest' + expect(page).to have_content 'Latest: 5937ac0a' end expect(page).to have_content '8 changed files' @@ -33,6 +33,10 @@ feature 'Merge Request versions', js: true, feature: true do expect(page).to have_content '5 changed files' end + + it 'show the message about disabled comments' do + expect(page).to have_content 'Comments are disabled' + end end describe 'compare with older version' do @@ -43,12 +47,22 @@ feature 'Merge Request versions', js: true, feature: true do end end - it 'should show older version' do + it 'should has correct value in the compare dropdown' do page.within '.mr-version-compare-dropdown' do expect(page).to have_content '6f6d7e7e' end + end - expect(page).to have_content '5 changed files' + it 'show the message about disabled comments' do + expect(page).to have_content 'Comments are disabled' + end + + it 'show diff between new and old version' do + expect(page).to have_content '4 changed files with 15 additions and 6 deletions' + end + + it 'show diff between new and old version' do + expect(page).to have_content '4 changed files with 15 additions and 6 deletions' end end end diff --git a/spec/helpers/git_helper_spec.rb b/spec/helpers/git_helper_spec.rb new file mode 100644 index 00000000000..9b1ef1e05a2 --- /dev/null +++ b/spec/helpers/git_helper_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe GitHelper do + describe '#short_sha' do + let(:short_sha) { helper.short_sha('d4e043f6c20749a3ab3f4b8e23f2a8979f4b9100') } + + it { expect(short_sha).to eq('d4e043f6') } + end +end -- cgit v1.2.3 From aacb4caf7d60508256b7dabd751802d3ed83f3e0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 1 Sep 2016 17:02:53 +0300 Subject: Refactor code for merge request version compare feature Signed-off-by: Dmitriy Zaporozhets --- .../projects/merge_requests_controller.rb | 44 ++++++++++----- .../merge_requests/show/_versions.html.haml | 63 +++++++++++----------- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 2fa55b83c0e..e385cc20148 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -90,18 +90,21 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.merge_request_diff end + @merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff + @comparable_diffs = @merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } + + if params[:start_sha].present? + @start_sha = params[:start_sha] + validate_start_sha + end + respond_to do |format| format.html { define_discussion_vars } format.json do - unless @merge_request_diff.latest? - # Disable comments if browsing older version of the diff - @diff_notes_disabled = true - end - - if params[:start_sha].present? - compare_diff_version + if @start_sha + compared_diff_version else - @diffs = @merge_request_diff.diffs(diff_options) + original_diff_version end render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } @@ -534,14 +537,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute end - def compare_diff_version - @compare = CompareService.new.execute(@project, @merge_request_diff.head_commit_sha, @project, params[:start_sha]) + def compared_diff_version + compare = CompareService.new.execute(@project, @merge_request_diff.head_commit_sha, @project, @start_sha) - if @compare - @commits = @compare.commits - @commit = @compare.commit - @diffs = @compare.diffs(diff_options) + if compare + @diffs = compare.diffs(diff_options) @diff_notes_disabled = true end end + + def original_diff_version + unless @merge_request_diff.latest? + # Disable comments if browsing older version of the diff + @diff_notes_disabled = true + end + + @diffs = @merge_request_diff.diffs(diff_options) + end + + def validate_start_sha + unless @comparable_diffs.map(&:head_commit_sha).include?(@start_sha) + render_404 + end + end end diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index bdedaa31e12..aa45a6cb18b 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -1,58 +1,55 @@ -- merge_request_diffs = @merge_request.merge_request_diffs.select_without_diff -- compareable_diffs = merge_request_diffs.select { |diff| diff.id < @merge_request_diff.id } - -- if merge_request_diffs.size > 1 +- if @merge_request_diffs.size > 1 .mr-version-controls Version %span.dropdown.inline.mr-version-dropdown %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } %strong.monospace< - if @merge_request_diff.latest? - Latest: #{short_sha(@merge_request_diff.head_commit_sha)} - - else - #{short_sha(@merge_request_diff.head_commit_sha)} + Latest:  + #{short_sha(@merge_request_diff.head_commit_sha)} %span.caret %ul.dropdown-menu.dropdown-menu-selectable - - merge_request_diffs.each_with_index do |merge_request_diff, i| + - @merge_request_diffs.each_with_index do |merge_request_diff, i| %li - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request, diff_id: merge_request_diff.id), class: ('is-active' if merge_request_diff == @merge_request_diff) do + = link_to mr_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do %strong.monospace - if i.zero? Latest: - else - #{merge_request_diffs.size - i}. - #{merge_request_diff.head_commit.short_id} + #{@merge_request_diffs.size - i}. + #{short_sha(merge_request_diff.head_commit_sha)} %br %small #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, = time_ago_with_tooltip(merge_request_diff.created_at) - %span.compare-dots ... + - if @merge_request_diff.base_commit_sha + %span.compare-dots ... - Compared with - %span.dropdown.inline.mr-version-compare-dropdown - %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } - %strong.monospace< - - if params[:start_sha].present? - #{short_sha(params[:start_sha])} - - else - Base: #{short_sha(@merge_request_diff.base_commit_sha)} - %span.caret - %ul.dropdown-menu.dropdown-menu-selectable - - compareable_diffs.each_with_index do |merge_request_diff, i| + Compared with + %span.dropdown.inline.mr-version-compare-dropdown + %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } + %strong.monospace< + - if @start_sha + #{short_sha(@start_sha)} + - else + Base: #{short_sha(@merge_request_diff.base_commit_sha)} + %span.caret + %ul.dropdown-menu.dropdown-menu-selectable + - @comparable_diffs.each_with_index do |merge_request_diff, i| + %li + = link_to mr_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff.head_commit_sha == @start_sha) do + %strong.monospace + #{@comparable_diffs.size - i}. #{short_sha(merge_request_diff.head_commit_sha)} + %br + %small + = time_ago_with_tooltip(merge_request_diff.created_at) %li - = link_to mr_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff.head_commit_sha == params[:start_sha]) do + = link_to mr_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do %strong.monospace - #{compareable_diffs.size - i}. #{short_sha(merge_request_diff.head_commit_sha)} - %br - %small - = time_ago_with_tooltip(merge_request_diff.created_at) - %li - = link_to mr_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless params[:start_sha].present?) do - %strong.monospace - Base: #{short_sha(@merge_request_diff.base_commit_sha)} + Base: #{short_sha(@merge_request_diff.base_commit_sha)} - - unless @merge_request_diff.latest? && params[:start_sha].blank? + - unless @merge_request_diff.latest? && !@start_sha .prepend-top-10 = icon('info-circle') Comments are disabled while viewing outdated merge versions or comparing to versions other than base. -- cgit v1.2.3 From a449124a76c783f619c4b57e0c8380cdb3bb5c4c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Sep 2016 10:42:49 +0300 Subject: Add changelog entry and documentation for merge request versions compare Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + doc/user/project/merge_requests/versions.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 17388615908..8231d9e8413 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -118,6 +118,7 @@ v 8.11.4 - Fix issue boards leak private label names and descriptions - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Remove gitorious. !5866 + - Allow compare merge request versions v 8.11.3 - Allow system info page to handle case where info is unavailable diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md index 2b1c5ddd562..a6aa4b47835 100644 --- a/doc/user/project/merge_requests/versions.md +++ b/doc/user/project/merge_requests/versions.md @@ -12,6 +12,12 @@ can select an older one from version dropdown. ![Merge Request Versions](img/versions.png) +You can also compare the merge request version with older one to see what is +changed since then. + +Please note that comments are disabled while viewing outdated merge versions +or comparing to versions other than base. + --- >**Note:** -- cgit v1.2.3 From ffdfdc27e5f7d72f7ed48ece2dc06154e0c5b49e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 6 Sep 2016 18:35:27 +0300 Subject: Expand abbreviations in merge request version path helper Signed-off-by: Dmitriy Zaporozhets --- app/helpers/merge_requests_helper.rb | 2 +- app/views/projects/merge_requests/show/_versions.html.haml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 1379180cb6e..08ceaf9b4ca 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -101,7 +101,7 @@ module MergeRequestsHelper return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? end - def mr_version_path(project, merge_request, merge_request_diff, start_sha = nil) + def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil) diffs_namespace_project_merge_request_path( project.namespace, project, merge_request, diff_id: merge_request_diff.id, start_sha: start_sha) diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index aa45a6cb18b..894736247e7 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -11,7 +11,7 @@ %ul.dropdown-menu.dropdown-menu-selectable - @merge_request_diffs.each_with_index do |merge_request_diff, i| %li - = link_to mr_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do + = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do %strong.monospace - if i.zero? Latest: @@ -38,14 +38,14 @@ %ul.dropdown-menu.dropdown-menu-selectable - @comparable_diffs.each_with_index do |merge_request_diff, i| %li - = link_to mr_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff.head_commit_sha == @start_sha) do + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff.head_commit_sha == @start_sha) do %strong.monospace #{@comparable_diffs.size - i}. #{short_sha(merge_request_diff.head_commit_sha)} %br %small = time_ago_with_tooltip(merge_request_diff.created_at) %li - = link_to mr_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do %strong.monospace Base: #{short_sha(@merge_request_diff.base_commit_sha)} -- cgit v1.2.3 From 5f0535ac91cdf2a892fb697dc05f0930f2c6d434 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 6 Sep 2016 20:08:06 +0300 Subject: Improve merge request version feature * Use version numbers instead of sha as more user-friendly * Make it clear that we compare later version (left) with older one (right) * Improve wording of version switch panel Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 4 -- .../projects/merge_requests_controller.rb | 12 +++-- app/helpers/git_helper.rb | 2 +- app/helpers/merge_requests_helper.rb | 4 ++ .../merge_requests/show/_versions.html.haml | 51 ++++++++++++---------- .../merge_requests/merge_request_versions_spec.rb | 10 ++--- 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index dd3e79b37af..2a44b95de64 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -383,10 +383,6 @@ a.btn-link { color: $gl-dark-link-color; } - - .compare-dots { - margin: 0 $btn-side-margin; - } } .merge-request-details { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e385cc20148..b86915e8470 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -95,7 +95,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController if params[:start_sha].present? @start_sha = params[:start_sha] - validate_start_sha + @start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha } + + unless @start_version + render_404 + end end respond_to do |format| @@ -554,10 +558,4 @@ class Projects::MergeRequestsController < Projects::ApplicationController @diffs = @merge_request_diff.diffs(diff_options) end - - def validate_start_sha - unless @comparable_diffs.map(&:head_commit_sha).include?(@start_sha) - render_404 - end - end end diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb index 9d7982e169c..8ab394384f3 100644 --- a/app/helpers/git_helper.rb +++ b/app/helpers/git_helper.rb @@ -4,6 +4,6 @@ module GitHelper end def short_sha(text) - text[0...8] + Commit.truncate_sha(text) end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 08ceaf9b4ca..8abe7865fed 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -106,4 +106,8 @@ module MergeRequestsHelper project.namespace, project, merge_request, diff_id: merge_request_diff.id, start_sha: start_sha) end + + def version_index(merge_request_diff) + @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff) + end end diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 894736247e7..e8cb7f92c23 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -1,53 +1,56 @@ - if @merge_request_diffs.size > 1 .mr-version-controls - Version + Changes between %span.dropdown.inline.mr-version-dropdown %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } - %strong.monospace< + %strong - if @merge_request_diff.latest? - Latest:  - #{short_sha(@merge_request_diff.head_commit_sha)} + latest version + - else + version #{version_index(@merge_request_diff)} %span.caret %ul.dropdown-menu.dropdown-menu-selectable - - @merge_request_diffs.each_with_index do |merge_request_diff, i| + - @merge_request_diffs.each do |merge_request_diff| %li = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do - %strong.monospace - - if i.zero? - Latest: + %strong + - if merge_request_diff.latest? + latest version - else - #{@merge_request_diffs.size - i}. - #{short_sha(merge_request_diff.head_commit_sha)} - %br + version #{version_index(merge_request_diff)} + .monospace #{short_sha(merge_request_diff.head_commit_sha)} %small #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, = time_ago_with_tooltip(merge_request_diff.created_at) - if @merge_request_diff.base_commit_sha - %span.compare-dots ... - - Compared with +  and  %span.dropdown.inline.mr-version-compare-dropdown %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } - %strong.monospace< + %strong - if @start_sha - #{short_sha(@start_sha)} + version #{version_index(@start_version)} - else - Base: #{short_sha(@merge_request_diff.base_commit_sha)} + #{@merge_request.target_branch} %span.caret %ul.dropdown-menu.dropdown-menu-selectable - - @comparable_diffs.each_with_index do |merge_request_diff, i| + - @comparable_diffs.each do |merge_request_diff| %li - = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff.head_commit_sha == @start_sha) do - %strong.monospace - #{@comparable_diffs.size - i}. #{short_sha(merge_request_diff.head_commit_sha)} - %br + = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff, merge_request_diff.head_commit_sha), class: ('is-active' if merge_request_diff == @start_version) do + %strong + - if merge_request_diff.latest? + latest version + - else + version #{version_index(merge_request_diff)} + .monospace #{short_sha(merge_request_diff.head_commit_sha)} %small = time_ago_with_tooltip(merge_request_diff.created_at) %li = link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do - %strong.monospace - Base: #{short_sha(@merge_request_diff.base_commit_sha)} + %strong + #{@merge_request.target_branch} (base) + .monospace #{short_sha(@merge_request_diff.base_commit_sha)} + - unless @merge_request_diff.latest? && !@start_sha .prepend-top-10 diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb index 7ccf4e8e8f2..9e759de3752 100644 --- a/spec/features/merge_requests/merge_request_versions_spec.rb +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -12,7 +12,7 @@ feature 'Merge Request versions', js: true, feature: true do it 'show the latest version of the diff' do page.within '.mr-version-dropdown' do - expect(page).to have_content 'Latest: 5937ac0a' + expect(page).to have_content 'latest version' end expect(page).to have_content '8 changed files' @@ -22,13 +22,13 @@ feature 'Merge Request versions', js: true, feature: true do before do page.within '.mr-version-dropdown' do find('.btn-link').click - click_link '6f6d7e7e' + click_link 'version 1' end end it 'should show older version' do page.within '.mr-version-dropdown' do - expect(page).to have_content '6f6d7e7e' + expect(page).to have_content 'version 1' end expect(page).to have_content '5 changed files' @@ -43,13 +43,13 @@ feature 'Merge Request versions', js: true, feature: true do before do page.within '.mr-version-compare-dropdown' do find('.btn-link').click - click_link '6f6d7e7e' + click_link 'version 1' end end it 'should has correct value in the compare dropdown' do page.within '.mr-version-compare-dropdown' do - expect(page).to have_content '6f6d7e7e' + expect(page).to have_content 'version 1' end end -- cgit v1.2.3 From 6f7a3ee69457c89d8b774fd65bdc4da756cc27c3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 7 Sep 2016 10:16:21 +0300 Subject: Refactor merge request version compare feature Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/merge_requests_controller.rb | 8 ++------ app/models/merge_request_diff.rb | 4 ++++ app/views/projects/merge_requests/show/_versions.html.haml | 8 +++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index b86915e8470..39dec5223df 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -542,7 +542,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def compared_diff_version - compare = CompareService.new.execute(@project, @merge_request_diff.head_commit_sha, @project, @start_sha) + compare = @merge_request_diff.compare_with(@start_sha) if compare @diffs = compare.diffs(diff_options) @@ -551,11 +551,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def original_diff_version - unless @merge_request_diff.latest? - # Disable comments if browsing older version of the diff - @diff_notes_disabled = true - end - + @diff_notes_disabled = !@merge_request_diff.latest? @diffs = @merge_request_diff.diffs(diff_options) end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 445179a4487..18c583add88 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -152,6 +152,10 @@ class MergeRequestDiff < ActiveRecord::Base self == merge_request.merge_request_diff end + def compare_with(sha) + CompareService.new.execute(project, head_commit_sha, project, sha) + end + private def dump_commits(commits) diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index e8cb7f92c23..00287f2d245 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -24,7 +24,7 @@ = time_ago_with_tooltip(merge_request_diff.created_at) - if @merge_request_diff.base_commit_sha -  and  + and %span.dropdown.inline.mr-version-compare-dropdown %a.btn-link.dropdown-toggle{ data: {toggle: :dropdown} } %strong @@ -51,8 +51,10 @@ #{@merge_request.target_branch} (base) .monospace #{short_sha(@merge_request_diff.base_commit_sha)} - - unless @merge_request_diff.latest? && !@start_sha .prepend-top-10 = icon('info-circle') - Comments are disabled while viewing outdated merge versions or comparing to versions other than base. + - if @start_sha + Comments are disabled because you're comparing two versions of this merge request. + - else + Comments are disabled because you're viewing an old version of this merge request. -- cgit v1.2.3 From d41fc61fb45964810a98e38bd34c9a04f0a11ed8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 7 Sep 2016 10:22:07 +0300 Subject: Refactor compared_diff_version method in merge requests controller Signed-off-by: Dmitriy Zaporozhets --- app/controllers/projects/merge_requests_controller.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 39dec5223df..8895cb955bd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -542,12 +542,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def compared_diff_version - compare = @merge_request_diff.compare_with(@start_sha) - - if compare - @diffs = compare.diffs(diff_options) - @diff_notes_disabled = true - end + @diff_notes_disabled = true + @diffs = @merge_request_diff.compare_with(@start_sha).diffs(diff_options) end def original_diff_version -- cgit v1.2.3 From 7b75476610cd4ac9f1c8b83b721e6bc524b781a1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Sep 2016 17:14:35 +0800 Subject: Just use string interpolation, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_15026132 --- app/views/projects/pipelines/_info.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 03f5f55e336..5800ef7de48 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -11,8 +11,7 @@ in = time_interval_in_words(@pipeline.duration) - if @pipeline.queued_duration - (queued for - = "#{time_interval_in_words(@pipeline.queued_duration)})" + = "(queued for #{time_interval_in_words(@pipeline.queued_duration)})" .pull-right = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), class: "ci-status ci-#{@pipeline.status}" do -- cgit v1.2.3 From a329763d0555bfb9d7be6f2527e608fe36fe6e73 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 7 Sep 2016 11:44:52 +0200 Subject: Add failing reset all test. --- spec/features/issues/reset_filters_spec.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index 6ce3d919646..da8b2ba579b 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -6,7 +6,7 @@ feature 'Issues filter reset button', feature: true, js: true do let!(:project) { create(:project, :public) } let!(:user) { create(:user)} let!(:milestone) { create(:milestone, project: project) } - let!(:bug) { create(:label, project: project, name: 'bug')} # maybe switch back to title + let!(:bug) { create(:label, project: project, name: 'bug')} let!(:issue1) { create(:issue, project: project, milestone: milestone, author: user, assignee: user, title: 'Feature')} let!(:issue2) { create(:labeled_issue, project: project, labels: [bug], title: 'Bugfix1')} @@ -66,14 +66,19 @@ feature 'Issues filter reset button', feature: true, js: true do end end - context 'when label and text filters have been dually applied' do - it 'resets both filters' do - fill_in 'issue_search', with: 'Feat' - expect(page).to have_css('.issue', count: 1) + context 'when all filters have been applied' do + it 'resets all filters' do + + wait_for_ajax + + filter_by_milestone(milestone.title) + + wait_for_ajax + + filter_by_author(user.username) wait_for_ajax - filter_by_label(bug.title) expect(page).to have_css('.issue', count: 0) reset_filters -- cgit v1.2.3 From fb1c7254d5764fb495d73b7e5bb16d60fd09bf03 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 18 Aug 2016 08:50:01 +0200 Subject: API for CI Lint --- lib/ci/api/api.rb | 1 + lib/ci/api/entities.rb | 7 +++++++ lib/ci/api/lint.rb | 24 ++++++++++++++++++++++++ spec/requests/ci/api/lint_spec.rb | 23 +++++++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 lib/ci/api/lint.rb create mode 100644 spec/requests/ci/api/lint_spec.rb diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb index a6b9beecded..741a81ca8f0 100644 --- a/lib/ci/api/api.rb +++ b/lib/ci/api/api.rb @@ -24,6 +24,7 @@ module Ci mount ::Ci::API::Builds mount ::Ci::API::Runners mount ::Ci::API::Triggers + mount ::Ci::API::Lint end end end diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index 3f5bdaba3f5..8052908e78a 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -59,6 +59,13 @@ module Ci expose :id, :variables expose :pipeline, using: Commit, as: :commit end + + class Lint < Grape::Entity + expose :content + expose :status + expose :builds + expose :stages + end end end end diff --git a/lib/ci/api/lint.rb b/lib/ci/api/lint.rb new file mode 100644 index 00000000000..d781441d2b7 --- /dev/null +++ b/lib/ci/api/lint.rb @@ -0,0 +1,24 @@ +module Ci + module API + class Lint < Grape::API + before { authenticate! } + + resources :lint do + post do + content = params[:content] + + if content + config_processor = Ci::GitlabCiYamlProcessor.new(content) + stages = config_processor.stages + builds = config_processor.builds + status = true + + response = { status: status, stages: stages, builds: builds } + end + + response + end + end + end + end +end diff --git a/spec/requests/ci/api/lint_spec.rb b/spec/requests/ci/api/lint_spec.rb new file mode 100644 index 00000000000..2ffd88120ae --- /dev/null +++ b/spec/requests/ci/api/lint_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Ci::API::API do + include ApiHelpers + + let(:content) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end + + describe "Builds API for Lint" do + + describe 'POST /ci/lint' do + before { content } + + context "with valid .gitlab-ci.yaml file" do + it "has success status" do + # binding.pry + expect(response).to have_content(true) + end + end + end + end +end -- cgit v1.2.3 From 895ca3a74144d4f858b342d1dd4313faeae79229 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 23 Aug 2016 12:12:21 +0200 Subject: Add tests for API CI Lint --- lib/ci/api/entities.rb | 7 ------- lib/ci/api/lint.rb | 33 ++++++++++++++++++++++-------- spec/requests/ci/api/lint_spec.rb | 43 +++++++++++++++++++++++++++++++-------- 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index 8052908e78a..3f5bdaba3f5 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -59,13 +59,6 @@ module Ci expose :id, :variables expose :pipeline, using: Commit, as: :commit end - - class Lint < Grape::Entity - expose :content - expose :status - expose :builds - expose :stages - end end end end diff --git a/lib/ci/api/lint.rb b/lib/ci/api/lint.rb index d781441d2b7..53a80318195 100644 --- a/lib/ci/api/lint.rb +++ b/lib/ci/api/lint.rb @@ -4,19 +4,34 @@ module Ci before { authenticate! } resources :lint do + post do - content = params[:content] + begin + response = {} + @content = params[:content] - if content - config_processor = Ci::GitlabCiYamlProcessor.new(content) - stages = config_processor.stages - builds = config_processor.builds - status = true + if @content + @config_processor = Ci::GitlabCiYamlProcessor.new(@content) + @stages = @config_processor.stages + @builds = @config_processor.builds - response = { status: status, stages: stages, builds: builds } - end + response = { + content: @content, + status: "syntax is correct" + } + + stage_builds = @stages.each do |stage| + response["#{stage}"] = @builds.select { |build| build[:stage] == stage } + end + else + render_api_error!("Please provide content of .gitlab-ci.yml", 400) + end - response + response + + rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e + error!({ content: @content, status: "syntax is incorrect", message: e.message }) + end end end end diff --git a/spec/requests/ci/api/lint_spec.rb b/spec/requests/ci/api/lint_spec.rb index 2ffd88120ae..9bd8154ce6d 100644 --- a/spec/requests/ci/api/lint_spec.rb +++ b/spec/requests/ci/api/lint_spec.rb @@ -3,21 +3,48 @@ require 'spec_helper' describe Ci::API::API do include ApiHelpers - let(:content) do + let(:user) { create(:user) } + let(:yaml_content) do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) end - describe "Builds API for Lint" do + describe 'POST /ci/lint' do + context "with valid .gitlab-ci.yaml content" do + context "authorized user" do + it "validate content" do + post ci_api('/lint'), { private_token: user.private_token, content: yaml_content } - describe 'POST /ci/lint' do - before { content } + expect(response).to have_http_status(201) + expect(json_response).to be_an Hash + expect(json_response['status']).to eq('syntax is correct') + end + end + + context "unauthorized user" do + it "does not validate content" do + post ci_api('/lint'), { content: yaml_content } - context "with valid .gitlab-ci.yaml file" do - it "has success status" do - # binding.pry - expect(response).to have_content(true) + expect(response).to have_http_status(401) end end end + + context "with invalid .gitlab_ci.yml content" do + it "validate content" do + post ci_api('/lint'), { private_token: user.private_token, content: 'invalid content'} + + expect(response).to have_http_status(500) + expect(json_response['status']).to eq('syntax is incorrect') + end + end + + context "no content" do + it "shows error message" do + post ci_api('/lint'), { private_token: user.private_token } + + expect(response).to have_http_status(400) + expect(json_response['message']).to eq('Please provide content of .gitlab-ci.yml') + end + end end end -- cgit v1.2.3 From 0e4c0e7818e7ab19b6639bc4c44bc373ba8219d9 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 23 Aug 2016 12:56:24 +0200 Subject: Fix rubocop tests --- lib/ci/api/lint.rb | 3 +-- spec/requests/ci/api/lint_spec.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/ci/api/lint.rb b/lib/ci/api/lint.rb index 53a80318195..fab4f8d5925 100644 --- a/lib/ci/api/lint.rb +++ b/lib/ci/api/lint.rb @@ -4,7 +4,6 @@ module Ci before { authenticate! } resources :lint do - post do begin response = {} @@ -20,7 +19,7 @@ module Ci status: "syntax is correct" } - stage_builds = @stages.each do |stage| + @stages.each do |stage| response["#{stage}"] = @builds.select { |build| build[:stage] == stage } end else diff --git a/spec/requests/ci/api/lint_spec.rb b/spec/requests/ci/api/lint_spec.rb index 9bd8154ce6d..4a18fd0b6b3 100644 --- a/spec/requests/ci/api/lint_spec.rb +++ b/spec/requests/ci/api/lint_spec.rb @@ -31,7 +31,7 @@ describe Ci::API::API do context "with invalid .gitlab_ci.yml content" do it "validate content" do - post ci_api('/lint'), { private_token: user.private_token, content: 'invalid content'} + post ci_api('/lint'), { private_token: user.private_token, content: 'invalid content' } expect(response).to have_http_status(500) expect(json_response['status']).to eq('syntax is incorrect') -- cgit v1.2.3 From 257c2acde7a0be63d955df63ca29488236e5654f Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 24 Aug 2016 10:16:58 +0200 Subject: Add params requires to lint --- lib/ci/api/api.rb | 2 +- lib/ci/api/lint.rb | 40 +++++++++++++++++++-------------------- spec/requests/ci/api/lint_spec.rb | 37 ++++++++++++++---------------------- 3 files changed, 35 insertions(+), 44 deletions(-) diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb index 741a81ca8f0..00572e6efdb 100644 --- a/lib/ci/api/api.rb +++ b/lib/ci/api/api.rb @@ -22,9 +22,9 @@ module Ci helpers Gitlab::CurrentSettings mount ::Ci::API::Builds + mount ::Ci::API::Lint mount ::Ci::API::Runners mount ::Ci::API::Triggers - mount ::Ci::API::Lint end end end diff --git a/lib/ci/api/lint.rb b/lib/ci/api/lint.rb index fab4f8d5925..6ea91ac34dd 100644 --- a/lib/ci/api/lint.rb +++ b/lib/ci/api/lint.rb @@ -1,35 +1,35 @@ module Ci module API class Lint < Grape::API - before { authenticate! } - - resources :lint do + resource :lint do post do - begin - response = {} - @content = params[:content] + status 200 + params do + requires :content, type: String, desc: 'content of .gitlab-ci.yml' + end - if @content - @config_processor = Ci::GitlabCiYamlProcessor.new(@content) - @stages = @config_processor.stages - @builds = @config_processor.builds + begin + response = { + status: '', + errors: [], + jobs: [] + } - response = { - content: @content, - status: "syntax is correct" - } + config_processor = Ci::GitlabCiYamlProcessor.new(params[:content]) - @stages.each do |stage| - response["#{stage}"] = @builds.select { |build| build[:stage] == stage } - end - else - render_api_error!("Please provide content of .gitlab-ci.yml", 400) + config_processor.builds.each do |build| + response[:jobs].push("#{build[:name]}") + response[:status] = 'valid' end response rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e - error!({ content: @content, status: "syntax is incorrect", message: e.message }) + status 200 + response[:errors].push(e.message) + response[:status] = 'invalid' + + response end end end diff --git a/spec/requests/ci/api/lint_spec.rb b/spec/requests/ci/api/lint_spec.rb index 4a18fd0b6b3..cc3d77f3b38 100644 --- a/spec/requests/ci/api/lint_spec.rb +++ b/spec/requests/ci/api/lint_spec.rb @@ -9,41 +9,32 @@ describe Ci::API::API do end describe 'POST /ci/lint' do - context "with valid .gitlab-ci.yaml content" do - context "authorized user" do - it "validate content" do - post ci_api('/lint'), { private_token: user.private_token, content: yaml_content } - - expect(response).to have_http_status(201) - expect(json_response).to be_an Hash - expect(json_response['status']).to eq('syntax is correct') - end - end - - context "unauthorized user" do - it "does not validate content" do + context 'with valid .gitlab-ci.yaml content' do + context 'authorized user' do + it 'validate content' do post ci_api('/lint'), { content: yaml_content } - expect(response).to have_http_status(401) + expect(response).to have_http_status(200) + expect(json_response).to be_an Hash + expect(json_response['status']).to eq('valid') end end end - context "with invalid .gitlab_ci.yml content" do - it "validate content" do - post ci_api('/lint'), { private_token: user.private_token, content: 'invalid content' } + context 'with invalid .gitlab_ci.yml content' do + it 'validate content' do + post ci_api('/lint'), { content: 'invalid content' } - expect(response).to have_http_status(500) - expect(json_response['status']).to eq('syntax is incorrect') + expect(response).to have_http_status(200) + expect(json_response['status']).to eq('invalid') end end - context "no content" do - it "shows error message" do - post ci_api('/lint'), { private_token: user.private_token } + context 'no content parameters' do + it 'shows error message' do + post ci_api('/lint') expect(response).to have_http_status(400) - expect(json_response['message']).to eq('Please provide content of .gitlab-ci.yml') end end end -- cgit v1.2.3 From ca1f5ede8456b0c433699fc73931ad39b5571f97 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 24 Aug 2016 11:42:48 +0200 Subject: Move lint to api from ci/api --- config/routes.rb | 3 +++ lib/api/api.rb | 1 + lib/api/lint.rb | 38 ++++++++++++++++++++++++++++++++++++ lib/ci/api/api.rb | 1 - lib/ci/api/lint.rb | 38 ------------------------------------ spec/requests/api/lint_spec.rb | 41 +++++++++++++++++++++++++++++++++++++++ spec/requests/ci/api/lint_spec.rb | 41 --------------------------------------- 7 files changed, 83 insertions(+), 80 deletions(-) create mode 100644 lib/api/lint.rb delete mode 100644 lib/ci/api/lint.rb create mode 100644 spec/requests/api/lint_spec.rb delete mode 100644 spec/requests/ci/api/lint_spec.rb diff --git a/config/routes.rb b/config/routes.rb index 262a174437a..0b22923039e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,6 +81,9 @@ Rails.application.routes.draw do mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end + # Lint API + resources :lint, only: [:show, :create] + # Health check get 'health_check(/:checks)' => 'health_check#index', as: :health_check diff --git a/lib/api/api.rb b/lib/api/api.rb index e14464c1b0d..a08fb056049 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -45,6 +45,7 @@ module API mount ::API::Keys mount ::API::Labels mount ::API::LicenseTemplates + mount ::API::Lint mount ::API::Members mount ::API::MergeRequests mount ::API::Milestones diff --git a/lib/api/lint.rb b/lib/api/lint.rb new file mode 100644 index 00000000000..68eabb571d6 --- /dev/null +++ b/lib/api/lint.rb @@ -0,0 +1,38 @@ +module API + class Lint < Grape::API + resource :lint do + params do + requires :content, type: String, desc: 'content of .gitlab-ci.yml' + end + + desc 'Validation of .gitlab-ci.yml content' + post do + status 200 + + begin + response = { + status: '', + errors: [], + jobs: [] + } + + config_processor = Ci::GitlabCiYamlProcessor.new(params[:content]) + + config_processor.builds.each do |build| + response[:jobs].push("#{build[:name]}") + response[:status] = 'valid' + end + + response + + rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e + status 200 + response[:errors].push(e.message) + response[:status] = 'invalid' + + response + end + end + end + end +end diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb index 00572e6efdb..a6b9beecded 100644 --- a/lib/ci/api/api.rb +++ b/lib/ci/api/api.rb @@ -22,7 +22,6 @@ module Ci helpers Gitlab::CurrentSettings mount ::Ci::API::Builds - mount ::Ci::API::Lint mount ::Ci::API::Runners mount ::Ci::API::Triggers end diff --git a/lib/ci/api/lint.rb b/lib/ci/api/lint.rb deleted file mode 100644 index 6ea91ac34dd..00000000000 --- a/lib/ci/api/lint.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Ci - module API - class Lint < Grape::API - resource :lint do - post do - status 200 - params do - requires :content, type: String, desc: 'content of .gitlab-ci.yml' - end - - begin - response = { - status: '', - errors: [], - jobs: [] - } - - config_processor = Ci::GitlabCiYamlProcessor.new(params[:content]) - - config_processor.builds.each do |build| - response[:jobs].push("#{build[:name]}") - response[:status] = 'valid' - end - - response - - rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e - status 200 - response[:errors].push(e.message) - response[:status] = 'invalid' - - response - end - end - end - end - end -end diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb new file mode 100644 index 00000000000..9de9c90a4aa --- /dev/null +++ b/spec/requests/api/lint_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + + let(:user) { create(:user) } + let(:yaml_content) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end + + describe 'POST /lint' do + context 'with valid .gitlab-ci.yaml content' do + context 'authorized user' do + it 'validate content' do + post api('/lint'), { content: yaml_content } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Hash + expect(json_response['status']).to eq('valid') + end + end + end + + context 'with invalid .gitlab_ci.yml content' do + it 'validate content' do + post api('/lint'), { content: 'invalid content' } + + expect(response).to have_http_status(200) + expect(json_response['status']).to eq('invalid') + end + end + + context 'no content parameters' do + it 'shows error message' do + post api('/lint') + + expect(response).to have_http_status(400) + end + end + end +end diff --git a/spec/requests/ci/api/lint_spec.rb b/spec/requests/ci/api/lint_spec.rb deleted file mode 100644 index cc3d77f3b38..00000000000 --- a/spec/requests/ci/api/lint_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'spec_helper' - -describe Ci::API::API do - include ApiHelpers - - let(:user) { create(:user) } - let(:yaml_content) do - File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - end - - describe 'POST /ci/lint' do - context 'with valid .gitlab-ci.yaml content' do - context 'authorized user' do - it 'validate content' do - post ci_api('/lint'), { content: yaml_content } - - expect(response).to have_http_status(200) - expect(json_response).to be_an Hash - expect(json_response['status']).to eq('valid') - end - end - end - - context 'with invalid .gitlab_ci.yml content' do - it 'validate content' do - post ci_api('/lint'), { content: 'invalid content' } - - expect(response).to have_http_status(200) - expect(json_response['status']).to eq('invalid') - end - end - - context 'no content parameters' do - it 'shows error message' do - post ci_api('/lint') - - expect(response).to have_http_status(400) - end - end - end -end -- cgit v1.2.3 From de2e8d4a559bd99479f46e527d8e78f67ad37de6 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 24 Aug 2016 14:36:14 +0200 Subject: Add tests for yaml content with errors --- spec/requests/api/lint_spec.rb | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index 9de9c90a4aa..78a9b415405 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -3,30 +3,36 @@ require 'spec_helper' describe API::API do include ApiHelpers - let(:user) { create(:user) } let(:yaml_content) do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) end describe 'POST /lint' do context 'with valid .gitlab-ci.yaml content' do - context 'authorized user' do - it 'validate content' do - post api('/lint'), { content: yaml_content } - - expect(response).to have_http_status(200) - expect(json_response).to be_an Hash - expect(json_response['status']).to eq('valid') - end + it 'validates content' do + post api('/lint'), { content: yaml_content } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Hash + expect(json_response['status']).to eq('valid') end end - context 'with invalid .gitlab_ci.yml content' do - it 'validate content' do + context 'with invalid .gitlab_ci.yml' do + it 'validates content and shows correct errors' do post api('/lint'), { content: 'invalid content' } expect(response).to have_http_status(200) expect(json_response['status']).to eq('invalid') + expect(json_response['errors']).to eq(['Invalid configuration format']) + end + + it "validates content and shows configuration error" do + post api('/lint'), { content: '{ image: "ruby:2.1", services: ["postgres"] }' } + + expect(response).to have_http_status(200) + expect(json_response['status']).to eq('invalid') + expect(json_response['errors']).to eq(['jobs config should contain at least one visible job']) end end -- cgit v1.2.3 From 9e313c129418f847498e771abd6bea53884682b5 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 25 Aug 2016 13:40:35 +0200 Subject: Add class method to encapsulate exception --- app/controllers/ci/lints_controller.rb | 6 ++--- lib/api/lint.rb | 34 +++++++++++++--------------- lib/ci/gitlab_ci_yaml_processor.rb | 9 ++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 19 ++++++++++++++++ 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index a7af3cb8345..67028b66a66 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -11,15 +11,15 @@ module Ci if @content.blank? @status = false @error = "Please provide content of .gitlab-ci.yml" + elsif Ci::GitlabCiYamlProcessor.validate(@content) != "valid" + @status = false + @error = Ci::GitlabCiYamlProcessor.validate(@content) else @config_processor = Ci::GitlabCiYamlProcessor.new(@content) @stages = @config_processor.stages @builds = @config_processor.builds @status = true end - rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e - @error = e.message - @status = false rescue @error = 'Undefined error' @status = false diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 68eabb571d6..2d27bc65462 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -7,31 +7,29 @@ module API desc 'Validation of .gitlab-ci.yml content' post do - status 200 - - begin - response = { - status: '', - errors: [], - jobs: [] - } - - config_processor = Ci::GitlabCiYamlProcessor.new(params[:content]) - - config_processor.builds.each do |build| - response[:jobs].push("#{build[:name]}") - response[:status] = 'valid' - end - - response + response = { + status: '', + errors: [], + jobs: [] + } - rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e + if Ci::GitlabCiYamlProcessor.validate(@content) != "valid" status 200 response[:errors].push(e.message) response[:status] = 'invalid' response end + + config_processor = Ci::GitlabCiYamlProcessor.new(params[:content]) + + config_processor.builds.each do |build| + response[:jobs].push("#{build[:name]}") + response[:status] = 'valid' + end + + status 200 + response end end end diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 47efd5bd9f2..9799f494df6 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -78,6 +78,15 @@ module Ci } end + def self.validate(content) + begin + Ci::GitlabCiYamlProcessor.new(content) + "valid" + rescue ValidationError, Psych::SyntaxError => e + e.message + end + end + private def initial_parsing diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index be51d942af7..bd5ea2bb97d 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1250,5 +1250,24 @@ EOT end end end + + describe "#validate(config)" do + describe "Error handling" do + it "returns error to parse YAML" do + config = YAML.dump("invalid: yaml: test") + expect(GitlabCiYamlProcessor.validate(config)).to eq "Invalid configuration format" + end + + it "returns errors if tags parameter is invalid" do + config = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) + expect(GitlabCiYamlProcessor.validate(config)).to eq "jobs:rspec tags should be an array of strings" + end + + it "does not return errors" do + config = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + expect(GitlabCiYamlProcessor.validate(config)).to eq "valid" + end + end + end end end -- cgit v1.2.3 From cc06eab237a6fe5501fcfdb2d1f1e57f711b56f5 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 25 Aug 2016 14:57:57 +0200 Subject: Change class method name --- CHANGELOG | 1 + app/controllers/ci/lints_controller.rb | 4 ++-- lib/api/lint.rb | 6 +++--- lib/ci/gitlab_ci_yaml_processor.rb | 4 ++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 16 ++++++++-------- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2af2056979d..bf1136afd03 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -92,6 +92,7 @@ v 8.12.0 (unreleased) - Refactor the triggers page and documentation !6217 - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska) - Use default clone protocol on "check out, review, and merge locally" help page URL + - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index 67028b66a66..e03543a9267 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -11,9 +11,9 @@ module Ci if @content.blank? @status = false @error = "Please provide content of .gitlab-ci.yml" - elsif Ci::GitlabCiYamlProcessor.validate(@content) != "valid" + elsif Ci::GitlabCiYamlProcessor.errors(@content) != nil @status = false - @error = Ci::GitlabCiYamlProcessor.validate(@content) + @error = Ci::GitlabCiYamlProcessor.errors(@content) else @config_processor = Ci::GitlabCiYamlProcessor.new(@content) @stages = @config_processor.stages diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 2d27bc65462..379c266abe1 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -2,7 +2,7 @@ module API class Lint < Grape::API resource :lint do params do - requires :content, type: String, desc: 'content of .gitlab-ci.yml' + requires :content, type: String, desc: 'Content of .gitlab-ci.yml' end desc 'Validation of .gitlab-ci.yml content' @@ -13,9 +13,9 @@ module API jobs: [] } - if Ci::GitlabCiYamlProcessor.validate(@content) != "valid" + if Ci::GitlabCiYamlProcessor.errors(@content) != nil status 200 - response[:errors].push(e.message) + response[:errors].push(Ci::GitlabCiYamlProcessor.errors(@content)) response[:status] = 'invalid' response diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 9799f494df6..40c84b93799 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -78,10 +78,10 @@ module Ci } end - def self.validate(content) + def self.errors(content) begin Ci::GitlabCiYamlProcessor.new(content) - "valid" + nil rescue ValidationError, Psych::SyntaxError => e e.message end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index bd5ea2bb97d..0125f149581 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1251,21 +1251,21 @@ EOT end end - describe "#validate(config)" do + describe "#errors(content)" do describe "Error handling" do - it "returns error to parse YAML" do - config = YAML.dump("invalid: yaml: test") - expect(GitlabCiYamlProcessor.validate(config)).to eq "Invalid configuration format" + it "returns error if parse YAML failed" do + content = YAML.dump("invalid: yaml: test") + expect(GitlabCiYamlProcessor.errors(content)).to eq "Invalid configuration format" end it "returns errors if tags parameter is invalid" do - config = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) - expect(GitlabCiYamlProcessor.validate(config)).to eq "jobs:rspec tags should be an array of strings" + content = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) + expect(GitlabCiYamlProcessor.errors(content)).to eq "jobs:rspec tags should be an array of strings" end it "does not return errors" do - config = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - expect(GitlabCiYamlProcessor.validate(config)).to eq "valid" + content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + expect(GitlabCiYamlProcessor.errors(content)).to eq nil end end end -- cgit v1.2.3 From cfa18dab86d5408814f4c6083b843205c3d1599e Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 25 Aug 2016 15:45:23 +0200 Subject: Fix rubocop errors --- app/controllers/ci/lints_controller.rb | 2 +- lib/api/lint.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index e03543a9267..fa57e6c5e14 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -11,7 +11,7 @@ module Ci if @content.blank? @status = false @error = "Please provide content of .gitlab-ci.yml" - elsif Ci::GitlabCiYamlProcessor.errors(@content) != nil + elsif !Ci::GitlabCiYamlProcessor.errors(@content).nil? @status = false @error = Ci::GitlabCiYamlProcessor.errors(@content) else diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 379c266abe1..2757b800af0 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -13,7 +13,7 @@ module API jobs: [] } - if Ci::GitlabCiYamlProcessor.errors(@content) != nil + if !Ci::GitlabCiYamlProcessor.errors(@content).nil? status 200 response[:errors].push(Ci::GitlabCiYamlProcessor.errors(@content)) response[:status] = 'invalid' -- cgit v1.2.3 From bbba62fa51419b14be4f39873afdd45b5b248764 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 26 Aug 2016 12:49:59 +0200 Subject: Fix errors and grammar --- lib/api/lint.rb | 23 ++++++++++------------- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 8 ++++---- spec/requests/api/lint_spec.rb | 17 +++++++++-------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 2757b800af0..ff35e948e0c 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -9,23 +9,20 @@ module API post do response = { status: '', - errors: [], + error: [], jobs: [] } - if !Ci::GitlabCiYamlProcessor.errors(@content).nil? - status 200 - response[:errors].push(Ci::GitlabCiYamlProcessor.errors(@content)) - response[:status] = 'invalid' - - response - end + if Ci::GitlabCiYamlProcessor.errors(params[:content]).nil? + config_processor = Ci::GitlabCiYamlProcessor.new(params[:content]) - config_processor = Ci::GitlabCiYamlProcessor.new(params[:content]) - - config_processor.builds.each do |build| - response[:jobs].push("#{build[:name]}") - response[:status] = 'valid' + config_processor.builds.each do |build| + response[:jobs].push("#{build[:name]}") + response[:status] = 'valid' + end + else + response[:error].push(Ci::GitlabCiYamlProcessor.errors(params[:content])) + response[:status] = 'invalid' end status 200 diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 0125f149581..d3a37ba6eb5 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1251,19 +1251,19 @@ EOT end end - describe "#errors(content)" do + describe "#errors" do describe "Error handling" do - it "returns error if parse YAML failed" do + it "returns an error if the YAML could not be parsed" do content = YAML.dump("invalid: yaml: test") expect(GitlabCiYamlProcessor.errors(content)).to eq "Invalid configuration format" end - it "returns errors if tags parameter is invalid" do + it "returns an error if the tags parameter is invalid" do content = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) expect(GitlabCiYamlProcessor.errors(content)).to eq "jobs:rspec tags should be an array of strings" end - it "does not return errors" do + it "does not return any errors when the YAML is valid" do content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) expect(GitlabCiYamlProcessor.errors(content)).to eq nil end diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index 78a9b415405..8bad57819c8 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -9,7 +9,7 @@ describe API::API do describe 'POST /lint' do context 'with valid .gitlab-ci.yaml content' do - it 'validates content' do + it 'validates the content' do post api('/lint'), { content: yaml_content } expect(response).to have_http_status(200) @@ -18,29 +18,30 @@ describe API::API do end end - context 'with invalid .gitlab_ci.yml' do - it 'validates content and shows correct errors' do + context 'with an invalid .gitlab_ci.yml' do + it 'validates the content and shows an error message' do post api('/lint'), { content: 'invalid content' } expect(response).to have_http_status(200) expect(json_response['status']).to eq('invalid') - expect(json_response['errors']).to eq(['Invalid configuration format']) + expect(json_response['error']).to eq(['Invalid configuration format']) end - it "validates content and shows configuration error" do + it "validates the content and shows a configuration error" do post api('/lint'), { content: '{ image: "ruby:2.1", services: ["postgres"] }' } expect(response).to have_http_status(200) expect(json_response['status']).to eq('invalid') - expect(json_response['errors']).to eq(['jobs config should contain at least one visible job']) + expect(json_response['error']).to eq(['jobs config should contain at least one visible job']) end end - context 'no content parameters' do - it 'shows error message' do + context 'without the content parameter' do + it 'shows an error message' do post api('/lint') expect(response).to have_http_status(400) + expect(json_response['error']).to eq('content is missing') end end end -- cgit v1.2.3 From 2c8b830fdbf749e8bb7461d5c3ce4699b77ce3ca Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 29 Aug 2016 15:07:19 +0200 Subject: Code refactoring --- app/controllers/ci/lints_controller.rb | 9 ++---- config/routes.rb | 3 -- lib/api/lint.rb | 43 ++++++++++++---------------- lib/ci/gitlab_ci_yaml_processor.rb | 16 +++++++---- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 27 ++++++++++++----- spec/requests/api/lint_spec.rb | 31 ++++++++++---------- 6 files changed, 67 insertions(+), 62 deletions(-) diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index fa57e6c5e14..62f6b23faba 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -7,13 +7,10 @@ module Ci def create @content = params[:content] + @error = Ci::GitlabCiYamlProcessor.validation_message(@content) - if @content.blank? - @status = false - @error = "Please provide content of .gitlab-ci.yml" - elsif !Ci::GitlabCiYamlProcessor.errors(@content).nil? - @status = false - @error = Ci::GitlabCiYamlProcessor.errors(@content) + unless @error.blank? + @status = @error.blank? else @config_processor = Ci::GitlabCiYamlProcessor.new(@content) @stages = @config_processor.stages diff --git a/config/routes.rb b/config/routes.rb index 0b22923039e..262a174437a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,9 +81,6 @@ Rails.application.routes.draw do mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end - # Lint API - resources :lint, only: [:show, :create] - # Health check get 'health_check(/:checks)' => 'health_check#index', as: :health_check diff --git a/lib/api/lint.rb b/lib/api/lint.rb index ff35e948e0c..b1c6f52bccb 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -1,33 +1,26 @@ module API class Lint < Grape::API - resource :lint do - params do - requires :content, type: String, desc: 'Content of .gitlab-ci.yml' - end - - desc 'Validation of .gitlab-ci.yml content' - post do - response = { - status: '', - error: [], - jobs: [] - } - - if Ci::GitlabCiYamlProcessor.errors(params[:content]).nil? - config_processor = Ci::GitlabCiYamlProcessor.new(params[:content]) + desc 'Validation of .gitlab-ci.yml content' + params do + requires :content, type: String, desc: 'Content of .gitlab-ci.yml' + end - config_processor.builds.each do |build| - response[:jobs].push("#{build[:name]}") - response[:status] = 'valid' - end - else - response[:error].push(Ci::GitlabCiYamlProcessor.errors(params[:content])) - response[:status] = 'invalid' - end + post 'ci/lint' do + error = Ci::GitlabCiYamlProcessor.validation_message(params[:content]) + response = { + status: '', + error: '' + } - status 200 - response + if error.blank? + response[:status] = 'valid' + else + response[:error] = error + response[:status] = 'invalid' end + + status 200 + response end end end diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 40c84b93799..c547193ce4c 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -78,12 +78,16 @@ module Ci } end - def self.errors(content) - begin - Ci::GitlabCiYamlProcessor.new(content) - nil - rescue ValidationError, Psych::SyntaxError => e - e.message + def self.validation_message(content) + if content.blank? + 'Please provide content of .gitlab-ci.yml' + else + begin + Ci::GitlabCiYamlProcessor.new(content) + nil + rescue ValidationError, Psych::SyntaxError => e + e.message + end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index d3a37ba6eb5..21aca9ee39f 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1252,20 +1252,33 @@ EOT end describe "#errors" do - describe "Error handling" do - it "returns an error if the YAML could not be parsed" do + context "when the YAML could not be parsed" do + it "returns an error about invalid configutaion" do content = YAML.dump("invalid: yaml: test") - expect(GitlabCiYamlProcessor.errors(content)).to eq "Invalid configuration format" + + expect(GitlabCiYamlProcessor.validation_message(content)).to eq "Invalid configuration format" end + end - it "returns an error if the tags parameter is invalid" do + context "when the tags parameter is invalid" do + it "returns an error about invalid tags" do content = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) - expect(GitlabCiYamlProcessor.errors(content)).to eq "jobs:rspec tags should be an array of strings" + + expect(GitlabCiYamlProcessor.validation_message(content)).to eq "jobs:rspec tags should be an array of strings" + end + end + + context "when YMAL content is empty" do + it "returns an error about missing content" do + expect(GitlabCiYamlProcessor.validation_message('')).to eq "Please provide content of .gitlab-ci.yml" end + end - it "does not return any errors when the YAML is valid" do + context "when the YAML is valid" do + it "does not return any errors" do content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - expect(GitlabCiYamlProcessor.errors(content)).to eq nil + + expect(GitlabCiYamlProcessor.validation_message(content)).to be_nil end end end diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index 8bad57819c8..5f8c2829dfa 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -1,44 +1,45 @@ require 'spec_helper' -describe API::API do +describe API::Lint, api: true do include ApiHelpers - let(:yaml_content) do - File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - end + describe 'POST /ci/lint' do + let(:yaml_content) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end - describe 'POST /lint' do context 'with valid .gitlab-ci.yaml content' do - it 'validates the content' do - post api('/lint'), { content: yaml_content } + it 'passes validation' do + post api('/ci/lint'), { content: yaml_content } expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['status']).to eq('valid') + expect(json_response['error']).to eq('') end end context 'with an invalid .gitlab_ci.yml' do - it 'validates the content and shows an error message' do - post api('/lint'), { content: 'invalid content' } + it 'responds with errors about invalid syntax' do + post api('/ci/lint'), { content: 'invalid content' } expect(response).to have_http_status(200) expect(json_response['status']).to eq('invalid') - expect(json_response['error']).to eq(['Invalid configuration format']) + expect(json_response['error']).to eq('Invalid configuration format') end - it "validates the content and shows a configuration error" do - post api('/lint'), { content: '{ image: "ruby:2.1", services: ["postgres"] }' } + it "responds with errors about invalid configuration" do + post api('/ci/lint'), { content: '{ image: "ruby:2.1", services: ["postgres"] }' } expect(response).to have_http_status(200) expect(json_response['status']).to eq('invalid') - expect(json_response['error']).to eq(['jobs config should contain at least one visible job']) + expect(json_response['error']).to eq('jobs config should contain at least one visible job') end end context 'without the content parameter' do - it 'shows an error message' do - post api('/lint') + it 'responds with validation error about missing content' do + post api('/ci/lint') expect(response).to have_http_status(400) expect(json_response['error']).to eq('content is missing') -- cgit v1.2.3 From 1a7a900d8e4a4a3a65b33054168fdd86f252b757 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 30 Aug 2016 13:03:29 +0200 Subject: Improve code --- lib/api/lint.rb | 24 ++++++++++-------------- lib/ci/gitlab_ci_yaml_processor.rb | 15 ++++++--------- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 9 ++++++--- spec/requests/api/lint_spec.rb | 6 +++--- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/lib/api/lint.rb b/lib/api/lint.rb index b1c6f52bccb..98010cf3b68 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -5,22 +5,18 @@ module API requires :content, type: String, desc: 'Content of .gitlab-ci.yml' end - post 'ci/lint' do - error = Ci::GitlabCiYamlProcessor.validation_message(params[:content]) - response = { - status: '', - error: '' - } + namespace 'ci' do + post '/lint' do + errors = Ci::GitlabCiYamlProcessor.validation_message(params[:content]) - if error.blank? - response[:status] = 'valid' - else - response[:error] = error - response[:status] = 'invalid' - end + status 200 - status 200 - response + if errors.blank? + { status: 'valid', errors: [] } + else + { status: 'invalid', errors: [errors] } + end + end end end end diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index c547193ce4c..bdae3205511 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -79,15 +79,12 @@ module Ci end def self.validation_message(content) - if content.blank? - 'Please provide content of .gitlab-ci.yml' - else - begin - Ci::GitlabCiYamlProcessor.new(content) - nil - rescue ValidationError, Psych::SyntaxError => e - e.message - end + return 'Please provide content of .gitlab-ci.yml' if content.blank? + begin + Ci::GitlabCiYamlProcessor.new(content) + nil + rescue ValidationError, Psych::SyntaxError => e + e.message end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 21aca9ee39f..3b77dbdb817 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1256,7 +1256,8 @@ EOT it "returns an error about invalid configutaion" do content = YAML.dump("invalid: yaml: test") - expect(GitlabCiYamlProcessor.validation_message(content)).to eq "Invalid configuration format" + expect(GitlabCiYamlProcessor.validation_message(content)) + .to eq "Invalid configuration format" end end @@ -1264,13 +1265,15 @@ EOT it "returns an error about invalid tags" do content = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) - expect(GitlabCiYamlProcessor.validation_message(content)).to eq "jobs:rspec tags should be an array of strings" + expect(GitlabCiYamlProcessor.validation_message(content)) + .to eq "jobs:rspec tags should be an array of strings" end end context "when YMAL content is empty" do it "returns an error about missing content" do - expect(GitlabCiYamlProcessor.validation_message('')).to eq "Please provide content of .gitlab-ci.yml" + expect(GitlabCiYamlProcessor.validation_message('')) + .to eq "Please provide content of .gitlab-ci.yml" end end diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index 5f8c2829dfa..c791b4891b6 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -15,7 +15,7 @@ describe API::Lint, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Hash expect(json_response['status']).to eq('valid') - expect(json_response['error']).to eq('') + expect(json_response['errors']).to eq([]) end end @@ -25,7 +25,7 @@ describe API::Lint, api: true do expect(response).to have_http_status(200) expect(json_response['status']).to eq('invalid') - expect(json_response['error']).to eq('Invalid configuration format') + expect(json_response['errors']).to eq(['Invalid configuration format']) end it "responds with errors about invalid configuration" do @@ -33,7 +33,7 @@ describe API::Lint, api: true do expect(response).to have_http_status(200) expect(json_response['status']).to eq('invalid') - expect(json_response['error']).to eq('jobs config should contain at least one visible job') + expect(json_response['errors']).to eq(['jobs config should contain at least one visible job']) end end -- cgit v1.2.3 From 0d81fd05b905e5fe41fee6bb48214748ccf85e78 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 30 Aug 2016 15:38:36 +0200 Subject: Code refactor --- app/controllers/ci/lints_controller.rb | 6 ++---- lib/api/lint.rb | 2 +- lib/ci/gitlab_ci_yaml_processor.rb | 1 + spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/requests/api/lint_spec.rb | 8 ++++---- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index 62f6b23faba..e06d12cfce1 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -8,14 +8,12 @@ module Ci def create @content = params[:content] @error = Ci::GitlabCiYamlProcessor.validation_message(@content) + @status = @error.blank? - unless @error.blank? - @status = @error.blank? - else + if @error.blank? @config_processor = Ci::GitlabCiYamlProcessor.new(@content) @stages = @config_processor.stages @builds = @config_processor.builds - @status = true end rescue @error = 'Undefined error' diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 98010cf3b68..ec9c8c710e4 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -5,7 +5,7 @@ module API requires :content, type: String, desc: 'Content of .gitlab-ci.yml' end - namespace 'ci' do + namespace :ci do post '/lint' do errors = Ci::GitlabCiYamlProcessor.validation_message(params[:content]) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index bdae3205511..e8c86349339 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -80,6 +80,7 @@ module Ci def self.validation_message(content) return 'Please provide content of .gitlab-ci.yml' if content.blank? + begin Ci::GitlabCiYamlProcessor.new(content) nil diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 3b77dbdb817..43631070024 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1251,7 +1251,7 @@ EOT end end - describe "#errors" do + describe "#validation_message" do context "when the YAML could not be parsed" do it "returns an error about invalid configutaion" do content = YAML.dump("invalid: yaml: test") diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index c791b4891b6..391fc13a380 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -4,11 +4,11 @@ describe API::Lint, api: true do include ApiHelpers describe 'POST /ci/lint' do - let(:yaml_content) do - File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) - end - context 'with valid .gitlab-ci.yaml content' do + let(:yaml_content) do + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end + it 'passes validation' do post api('/ci/lint'), { content: yaml_content } -- cgit v1.2.3 From b63d20a1064c20204defd1d78af97893eb2b3fc8 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 31 Aug 2016 11:16:25 +0200 Subject: Add Documentation to Ci Lint API --- doc/api/README.md | 4 +++- doc/api/ci_lint.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ lib/api/lint.rb | 6 +++--- 3 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 doc/api/ci_lint.md diff --git a/doc/api/README.md b/doc/api/README.md index 96d94e08487..67ec502346e 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -41,8 +41,10 @@ following locations: - [Sidekiq metrics](sidekiq_metrics.md) - [System Hooks](system_hooks.md) - [Tags](tags.md) -- [Users](users.md) - [Todos](todos.md) +- [Users](users.md) +- [Validate the .gitlab-ci.yaml](ci_lint.md) + ### Internal CI API diff --git a/doc/api/ci_lint.md b/doc/api/ci_lint.md new file mode 100644 index 00000000000..2b169176dd6 --- /dev/null +++ b/doc/api/ci_lint.md @@ -0,0 +1,45 @@ +# Validate the .gitlab-ci.yaml + +Check whether your .gitlab-ci.yml file is valid. + +``` +POST ci/lint +``` + +| Attribute | Type | Required | Description | +| ---------- | ------- | -------- | -------- | +| `content` | hash | yes | the .gitlab-ci.yaml content| + +```bash +curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content=YAML+Content" +``` + +Example response: + +* valid content + +```json +{ + "status": "valid", + "errors": [] +} +``` + +* invalid content + +```json +{ + "status": "invalid", + "errors": [ + "variables config should be a hash of key value pairs" + ] +} +``` + +* without the content attribute + +```json +{ + "error": "content is missing" +} +``` diff --git a/lib/api/lint.rb b/lib/api/lint.rb index ec9c8c710e4..0af132803ba 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -7,14 +7,14 @@ module API namespace :ci do post '/lint' do - errors = Ci::GitlabCiYamlProcessor.validation_message(params[:content]) + error = Ci::GitlabCiYamlProcessor.validation_message(params[:content]) status 200 - if errors.blank? + if error.blank? { status: 'valid', errors: [] } else - { status: 'invalid', errors: [errors] } + { status: 'invalid', errors: [error] } end end end -- cgit v1.2.3 From ea714b625b68b801275adcc9294bc3ada2bcd8fb Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 31 Aug 2016 14:39:18 +0200 Subject: Move ci_lint to api/ci --- doc/api/README.md | 3 +-- doc/api/ci/README.md | 1 + doc/api/ci/ci_lint.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ doc/api/ci_lint.md | 45 --------------------------------------------- 4 files changed, 47 insertions(+), 47 deletions(-) create mode 100644 doc/api/ci/ci_lint.md delete mode 100644 doc/api/ci_lint.md diff --git a/doc/api/README.md b/doc/api/README.md index 67ec502346e..8c4f1b9c885 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -43,8 +43,6 @@ following locations: - [Tags](tags.md) - [Todos](todos.md) - [Users](users.md) -- [Validate the .gitlab-ci.yaml](ci_lint.md) - ### Internal CI API @@ -52,6 +50,7 @@ The following documentation is for the [internal CI API](ci/README.md): - [Builds](ci/builds.md) - [Runners](ci/runners.md) +- [Validate the .gitlab-ci.yaml](ci/ci_lint.md) ## Authentication diff --git a/doc/api/ci/README.md b/doc/api/ci/README.md index 96a281e27c8..8439598a5a8 100644 --- a/doc/api/ci/README.md +++ b/doc/api/ci/README.md @@ -22,3 +22,4 @@ GET /ci/api/v1/builds/:id/artifacts - [Builds](builds.md) - [Runners](runners.md) +- [Validate the .gitlab-ci.yaml](ci_lint.md) diff --git a/doc/api/ci/ci_lint.md b/doc/api/ci/ci_lint.md new file mode 100644 index 00000000000..2b169176dd6 --- /dev/null +++ b/doc/api/ci/ci_lint.md @@ -0,0 +1,45 @@ +# Validate the .gitlab-ci.yaml + +Check whether your .gitlab-ci.yml file is valid. + +``` +POST ci/lint +``` + +| Attribute | Type | Required | Description | +| ---------- | ------- | -------- | -------- | +| `content` | hash | yes | the .gitlab-ci.yaml content| + +```bash +curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content=YAML+Content" +``` + +Example response: + +* valid content + +```json +{ + "status": "valid", + "errors": [] +} +``` + +* invalid content + +```json +{ + "status": "invalid", + "errors": [ + "variables config should be a hash of key value pairs" + ] +} +``` + +* without the content attribute + +```json +{ + "error": "content is missing" +} +``` diff --git a/doc/api/ci_lint.md b/doc/api/ci_lint.md deleted file mode 100644 index 2b169176dd6..00000000000 --- a/doc/api/ci_lint.md +++ /dev/null @@ -1,45 +0,0 @@ -# Validate the .gitlab-ci.yaml - -Check whether your .gitlab-ci.yml file is valid. - -``` -POST ci/lint -``` - -| Attribute | Type | Required | Description | -| ---------- | ------- | -------- | -------- | -| `content` | hash | yes | the .gitlab-ci.yaml content| - -```bash -curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content=YAML+Content" -``` - -Example response: - -* valid content - -```json -{ - "status": "valid", - "errors": [] -} -``` - -* invalid content - -```json -{ - "status": "invalid", - "errors": [ - "variables config should be a hash of key value pairs" - ] -} -``` - -* without the content attribute - -```json -{ - "error": "content is missing" -} -``` -- cgit v1.2.3 From 25ece82dab6de88a4d202ffc94a517f8d3e1d205 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 1 Sep 2016 13:49:34 +0200 Subject: Rename file and update README --- doc/api/README.md | 2 +- doc/api/ci/README.md | 1 - doc/api/ci/ci_lint.md | 45 --------------------------------------------- doc/api/ci/lint.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 47 deletions(-) delete mode 100644 doc/api/ci/ci_lint.md create mode 100644 doc/api/ci/lint.md diff --git a/doc/api/README.md b/doc/api/README.md index 8c4f1b9c885..e12070dc1ce 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -43,6 +43,7 @@ following locations: - [Tags](tags.md) - [Todos](todos.md) - [Users](users.md) +- [Validate CI configuration](ci/lint.md) ### Internal CI API @@ -50,7 +51,6 @@ The following documentation is for the [internal CI API](ci/README.md): - [Builds](ci/builds.md) - [Runners](ci/runners.md) -- [Validate the .gitlab-ci.yaml](ci/ci_lint.md) ## Authentication diff --git a/doc/api/ci/README.md b/doc/api/ci/README.md index 8439598a5a8..96a281e27c8 100644 --- a/doc/api/ci/README.md +++ b/doc/api/ci/README.md @@ -22,4 +22,3 @@ GET /ci/api/v1/builds/:id/artifacts - [Builds](builds.md) - [Runners](runners.md) -- [Validate the .gitlab-ci.yaml](ci_lint.md) diff --git a/doc/api/ci/ci_lint.md b/doc/api/ci/ci_lint.md deleted file mode 100644 index 2b169176dd6..00000000000 --- a/doc/api/ci/ci_lint.md +++ /dev/null @@ -1,45 +0,0 @@ -# Validate the .gitlab-ci.yaml - -Check whether your .gitlab-ci.yml file is valid. - -``` -POST ci/lint -``` - -| Attribute | Type | Required | Description | -| ---------- | ------- | -------- | -------- | -| `content` | hash | yes | the .gitlab-ci.yaml content| - -```bash -curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content=YAML+Content" -``` - -Example response: - -* valid content - -```json -{ - "status": "valid", - "errors": [] -} -``` - -* invalid content - -```json -{ - "status": "invalid", - "errors": [ - "variables config should be a hash of key value pairs" - ] -} -``` - -* without the content attribute - -```json -{ - "error": "content is missing" -} -``` diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md new file mode 100644 index 00000000000..876ee508474 --- /dev/null +++ b/doc/api/ci/lint.md @@ -0,0 +1,45 @@ +# Validate the .gitlab-ci.yml + +Check whether your .gitlab-ci.yml file is valid. + +``` +POST ci/lint +``` + +| Attribute | Type | Required | Description | +| ---------- | ------- | -------- | -------- | +| `content` | hash | yes | the .gitlab-ci.yaml content| + +```bash +curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content=YAML+Content" +``` + +Example response: + +* valid content + +```json +{ + "status": "valid", + "errors": [] +} +``` + +* invalid content + +```json +{ + "status": "invalid", + "errors": [ + "variables config should be a hash of key value pairs" + ] +} +``` + +* without the content attribute + +```json +{ + "error": "content is missing" +} +``` -- cgit v1.2.3 From ced7cf7bff2589da7d5134495064c897bac68634 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 5 Sep 2016 11:06:16 +0200 Subject: Improve documentation --- doc/api/ci/lint.md | 89 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md index 876ee508474..74213f21de3 100644 --- a/doc/api/ci/lint.md +++ b/doc/api/ci/lint.md @@ -1,5 +1,7 @@ # Validate the .gitlab-ci.yml +> [Introduced][ce-5953] in GitLab 8.12. + Check whether your .gitlab-ci.yml file is valid. ``` @@ -11,35 +13,74 @@ POST ci/lint | `content` | hash | yes | the .gitlab-ci.yaml content| ```bash -curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content=YAML+Content" +curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content={ + image: "ruby:2.1", + services: ["postgres"], + before_script: ["gem install bundler", "bundle install", "bundle exec rake db:create"], + variables: {"DB_NAME": "postgres"}, + types: ["test", "deploy", "notify"], + rspec: { + script: "rake spec", + tags: ["ruby", "postgres"], + only: ["branches"] + }, + spinach: { + script: "rake spinach", + allow_failure: true, + tags: ["ruby", "mysql"], + except: ["tags"] + }, + staging: { + variables: {KEY1: "value1", KEY2: "value2"}, + script: "cap deploy stating", + type: "deploy", + tags: ["ruby", "mysql"], + except: ["stable"] + }, + production: { + variables: {DB_NAME: "mysql"}, + type: "deploy", + script: ["cap deploy production", "cap notify"], + tags: ["ruby", "mysql"], + only: ["master", "/^deploy-.*$/"] + }, + dockerhub: { + type: "notify", + script: "curl http://dockerhub/URL", + tags: ["ruby", "postgres"], + only: ["branches"] + } +}" ``` -Example response: +Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky with indentation and spaces. -* valid content +Example responses: -```json -{ - "status": "valid", - "errors": [] -} -``` +* Valid content: -* invalid content + ```json + { + "status": "valid", + "errors": [] + } + ``` -```json -{ - "status": "invalid", - "errors": [ - "variables config should be a hash of key value pairs" - ] -} -``` +* Invalid content: -* without the content attribute + ```json + { + "status": "invalid", + "errors": [ + "variables config should be a hash of key value pairs" + ] + } + ``` -```json -{ - "error": "content is missing" -} -``` +* Without the content attribute: + + ```json + { + "error": "content is missing" + } + ``` -- cgit v1.2.3 From 99d7b3ea0c7b97820ee25f12c4b77acc676a72b7 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 5 Sep 2016 13:51:33 +0200 Subject: Improve documentation grammar --- doc/api/ci/lint.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md index 74213f21de3..89eafad740c 100644 --- a/doc/api/ci/lint.md +++ b/doc/api/ci/lint.md @@ -2,7 +2,7 @@ > [Introduced][ce-5953] in GitLab 8.12. -Check whether your .gitlab-ci.yml file is valid. +Checks if your .gitlab-ci.yml file is valid. ``` POST ci/lint @@ -53,7 +53,7 @@ curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content={ }" ``` -Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky with indentation and spaces. +Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces. Example responses: -- cgit v1.2.3 From 1112b5a28323bdcee5b7e910575097d1ccb6256c Mon Sep 17 00:00:00 2001 From: Pascal Betz Date: Tue, 6 Sep 2016 21:40:49 +0200 Subject: Move parsing of sidekiq ps into helper --- app/helpers/sidekiq_helper.rb | 12 +++++++++ app/views/admin/background_jobs/show.html.haml | 11 +++----- spec/helpers/sidekiq_helper_spec.rb | 35 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 app/helpers/sidekiq_helper.rb create mode 100644 spec/helpers/sidekiq_helper_spec.rb diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb new file mode 100644 index 00000000000..3644039d38e --- /dev/null +++ b/app/helpers/sidekiq_helper.rb @@ -0,0 +1,12 @@ +module SidekiqHelper + SIDEKIQ_PS_REGEXP = /\A([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+(.+)\s+(sidekiq.*\])\s+\z/ + + def parse_sidekiq_ps(line) + match = line.match(SIDEKIQ_PS_REGEXP) + if match + match[1..6] + else + %w{? ? ? ? ? ?} + end + end +end diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 4f680b507c4..c2a312b2196 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -28,14 +28,9 @@ %th COMMAND %tbody - @sidekiq_processes.each do |process| - - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) - - data = process.strip.split(' ') - %tr - %td= gitlab_config.user - - 5.times do - %td= data.shift - %td= data.join(' ') - + %td= gitlab_config.user + -parse_sidekiq_ps(process).each do |value| + %td= value .clearfix %p %i.fa.fa-exclamation-circle diff --git a/spec/helpers/sidekiq_helper_spec.rb b/spec/helpers/sidekiq_helper_spec.rb new file mode 100644 index 00000000000..2eb1c816bc5 --- /dev/null +++ b/spec/helpers/sidekiq_helper_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe SidekiqHelper do + describe 'parse_sidekiq_ps' do + it 'parses line with time' do + line = '55137 10,0 2,1 S+ 2:30pm sidekiq 4.1.4 gitlab [0 of 25 busy] ' + parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['55137', '10,0', '2,1', 'S+', '2:30pm', 'sidekiq 4.1.4 gitlab [0 of 25 busy]']) + end + + it 'parses line with date' do + line = '55137 10,0 2,1 S+ Aug 4 sidekiq 4.1.4 gitlab [0 of 25 busy] ' + parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['55137', '10,0', '2,1', 'S+', 'Aug 4', 'sidekiq 4.1.4 gitlab [0 of 25 busy]']) + end + + it 'parses line with two digit date' do + line = '55137 10,0 2,1 S+ Aug 04 sidekiq 4.1.4 gitlab [0 of 25 busy] ' + parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['55137', '10,0', '2,1', 'S+', 'Aug 04', 'sidekiq 4.1.4 gitlab [0 of 25 busy]']) + end + + it 'parses line with dot as float separator' do + line = '55137 10.0 2.1 S+ 2:30pm sidekiq 4.1.4 gitlab [0 of 25 busy] ' + parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['55137', '10.0', '2.1', 'S+', '2:30pm', 'sidekiq 4.1.4 gitlab [0 of 25 busy]']) + end + + it 'does fail gracefully on line not matching the format' do + line = '55137 10.0 2.1 S+ 2:30pm something' + parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['?', '?', '?', '?', '?', '?']) + end + end +end -- cgit v1.2.3 From dac4c5bb5389b123a046d0e861443e124dc0e059 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 7 Sep 2016 12:19:36 +0200 Subject: Fix invalid curl command --- doc/api/ci/lint.md | 41 ++-------------------------- lib/api/lint.rb | 9 +++--- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- 3 files changed, 8 insertions(+), 44 deletions(-) diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md index 89eafad740c..080bc2148f3 100644 --- a/doc/api/ci/lint.md +++ b/doc/api/ci/lint.md @@ -10,47 +10,10 @@ POST ci/lint | Attribute | Type | Required | Description | | ---------- | ------- | -------- | -------- | -| `content` | hash | yes | the .gitlab-ci.yaml content| +| `content` | string | yes | the .gitlab-ci.yaml content| ```bash -curl --request POST "https://gitlab.example.com/api/v3/ci/lint?content={ - image: "ruby:2.1", - services: ["postgres"], - before_script: ["gem install bundler", "bundle install", "bundle exec rake db:create"], - variables: {"DB_NAME": "postgres"}, - types: ["test", "deploy", "notify"], - rspec: { - script: "rake spec", - tags: ["ruby", "postgres"], - only: ["branches"] - }, - spinach: { - script: "rake spinach", - allow_failure: true, - tags: ["ruby", "mysql"], - except: ["tags"] - }, - staging: { - variables: {KEY1: "value1", KEY2: "value2"}, - script: "cap deploy stating", - type: "deploy", - tags: ["ruby", "mysql"], - except: ["stable"] - }, - production: { - variables: {DB_NAME: "mysql"}, - type: "deploy", - script: ["cap deploy production", "cap notify"], - tags: ["ruby", "mysql"], - only: ["master", "/^deploy-.*$/"] - }, - dockerhub: { - type: "notify", - script: "curl http://dockerhub/URL", - tags: ["ruby", "postgres"], - only: ["branches"] - } -}" +curl -H "Content-Type: application/json" --request POST https://gitlab.example.com/api/v3/ci/lint -d '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' ``` Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces. diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 0af132803ba..8a7ced2d34d 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -1,11 +1,12 @@ module API class Lint < Grape::API - desc 'Validation of .gitlab-ci.yml content' - params do - requires :content, type: String, desc: 'Content of .gitlab-ci.yml' - end namespace :ci do + desc 'Validation of .gitlab-ci.yml content' + params do + requires :content, type: String, desc: 'Content of .gitlab-ci.yml' + end + post '/lint' do error = Ci::GitlabCiYamlProcessor.validation_message(params[:content]) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 43631070024..af192664b33 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1270,7 +1270,7 @@ EOT end end - context "when YMAL content is empty" do + context "when YAML content is empty" do it "returns an error about missing content" do expect(GitlabCiYamlProcessor.validation_message('')) .to eq "Please provide content of .gitlab-ci.yml" -- cgit v1.2.3 From ea48310579385c86cd6da9663e8150701707452d Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 7 Sep 2016 12:54:02 +0200 Subject: Improve curl commend, remove blank lines --- doc/api/ci/lint.md | 2 +- lib/api/lint.rb | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/api/ci/lint.md b/doc/api/ci/lint.md index 080bc2148f3..0c96b3ee335 100644 --- a/doc/api/ci/lint.md +++ b/doc/api/ci/lint.md @@ -13,7 +13,7 @@ POST ci/lint | `content` | string | yes | the .gitlab-ci.yaml content| ```bash -curl -H "Content-Type: application/json" --request POST https://gitlab.example.com/api/v3/ci/lint -d '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' +curl --header "Content-Type: application/json" https://gitlab.example.com/api/v3/ci/lint --data '{"content": "{ \"image\": \"ruby:2.1\", \"services\": [\"postgres\"], \"before_script\": [\"gem install bundler\", \"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}' ``` Be sure to copy paste the exact contents of `.gitlab-ci.yml` as YAML is very picky about indentation and spaces. diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 8a7ced2d34d..ae43a4a3237 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -1,12 +1,10 @@ module API class Lint < Grape::API - namespace :ci do desc 'Validation of .gitlab-ci.yml content' params do requires :content, type: String, desc: 'Content of .gitlab-ci.yml' end - post '/lint' do error = Ci::GitlabCiYamlProcessor.validation_message(params[:content]) -- cgit v1.2.3 From 6a29ac7d1d043474ec9352209243e0ea2118dc48 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 31 Aug 2016 15:32:05 +0200 Subject: Change update interval of runners when trying to preserve contacted_at --- CHANGELOG | 1 + app/models/ci/runner.rb | 2 +- lib/ci/api/builds.rb | 2 +- lib/ci/api/helpers.rb | 8 +++++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2af2056979d..69ff459238c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -63,6 +63,7 @@ v 8.12.0 (unreleased) - Fix badge count alignment (ClemMakesApps) - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Fix repo title alignment (ClemMakesApps) + - Change update interval of contacted_at - Fix branch title trailing space on hover (ClemMakesApps) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 49f05f881a2..ed5d4b13b7e 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -2,7 +2,7 @@ module Ci class Runner < ActiveRecord::Base extend Ci::Model - LAST_CONTACT_TIME = 5.minutes.ago + LAST_CONTACT_TIME = 2.hours.ago AVAILABLE_SCOPES = %w[specific shared active paused online] FORM_EDITABLE = %i[description tag_list active run_untagged locked] diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 9f3b582a263..eb4947cdbf1 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -12,7 +12,7 @@ module Ci # POST /builds/register post "register" do authenticate_runner! - update_runner_last_contact + update_runner_last_contact(save: false) update_runner_info required_attributes! [:token] not_found! unless current_runner.active? diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 199d62d9b8a..bcabf7a21b2 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -3,7 +3,7 @@ module Ci module Helpers BUILD_TOKEN_HEADER = "HTTP_BUILD_TOKEN" BUILD_TOKEN_PARAM = :token - UPDATE_RUNNER_EVERY = 60 + UPDATE_RUNNER_EVERY = 40 * 60 def authenticate_runners! forbidden! unless runner_registration_token_valid? @@ -22,11 +22,13 @@ module Ci params[:token] == current_application_settings.runners_registration_token end - def update_runner_last_contact + def update_runner_last_contact(save: true) # Use a random threshold to prevent beating DB updates + # it generates a distribution between: [40m, 80m] contacted_at_max_age = UPDATE_RUNNER_EVERY + Random.rand(UPDATE_RUNNER_EVERY) if current_runner.contacted_at.nil? || Time.now - current_runner.contacted_at >= contacted_at_max_age - current_runner.update_attributes(contacted_at: Time.now) + current_runner.contacted_at = Time.now + current_runner.save if current_runner.changed? && save end end -- cgit v1.2.3 From d392f147fc2b08cf3139e2cce2a264eaf0bc4a48 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 7 Sep 2016 14:52:13 +0200 Subject: Group similar builds --- app/models/commit_status.rb | 4 ++++ app/views/projects/commit/_pipeline.html.haml | 9 +++++++-- app/views/projects/commit/_pipeline_grouped_status.html.haml | 12 ++++++++++++ db/fixtures/development/14_pipelines.rb | 10 +++++++--- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 app/views/projects/commit/_pipeline_grouped_status.html.haml diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 4a628924499..af739342256 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -95,6 +95,10 @@ class CommitStatus < ActiveRecord::Base pipeline.before_sha || Gitlab::Git::BLANK_SHA end + def group_name + name.gsub(/\d+[\s:]+\d+\s*/, '') + end + def self.stages # We group by stage name, but order stages by theirs' index unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 20a85148ab5..a330c14061f 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -39,8 +39,13 @@ = stage.titleize .builds-container %ul - - statuses.each do |status| - = render "projects/#{status.to_partial_path}_pipeline", subject: status + - status_groups = statuses.group_by(&:group_name) + - status_groups.each do |group_name, grouped_statuses| + - if grouped_statuses.one? + - status = grouped_statuses.first + = render "projects/#{status.to_partial_path}_pipeline", subject: status + - else + = render "projects/commit/pipeline_grouped_status", name: group_name, subject: grouped_statuses - if pipeline.yaml_errors.present? diff --git a/app/views/projects/commit/_pipeline_grouped_status.html.haml b/app/views/projects/commit/_pipeline_grouped_status.html.haml new file mode 100644 index 00000000000..7e02703f0a6 --- /dev/null +++ b/app/views/projects/commit/_pipeline_grouped_status.html.haml @@ -0,0 +1,12 @@ +%li.build + .curve + .build-content + - group_status = CommitStatus.where(id: subject).status + = render_status_with_link('build', group_status) + %span.ci-status-text + = name + = subject.length + + // Access all other grouped statuses + //- subject.each do |status| + // = render "projects/#{status.to_partial_path}_pipeline", subject: status diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 49e6e2361b1..650b410595c 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines BUILDS = [ { name: 'build:linux', stage: 'build', status: :success }, { name: 'build:osx', stage: 'build', status: :success }, - { name: 'rspec:linux', stage: 'test', status: :success }, - { name: 'rspec:windows', stage: 'test', status: :success }, - { name: 'rspec:windows', stage: 'test', status: :success }, + { name: 'rspec:linux 0 3', stage: 'test', status: :success }, + { name: 'rspec:linux 1 3', stage: 'test', status: :success }, + { name: 'rspec:linux 2 3', stage: 'test', status: :success }, + { name: 'rspec:windows 0 3', stage: 'test', status: :success }, + { name: 'rspec:windows 1 3', stage: 'test', status: :success }, + { name: 'rspec:windows 2 3', stage: 'test', status: :success }, + { name: 'rspec:windows 2 3', stage: 'test', status: :success }, { name: 'rspec:osx', stage: 'test', status_event: :success }, { name: 'spinach:linux', stage: 'test', status: :success }, { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true}, -- cgit v1.2.3 From fd5c86aa2d1095eb47484b8dc3a29ceb43ad7d1d Mon Sep 17 00:00:00 2001 From: Patrick Thaele Date: Wed, 7 Sep 2016 12:56:50 +0000 Subject: Fix: Switch sample values for $CI_SERVER_REVISION and $CI_SERVER_VERSION. --- doc/ci/variables/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 4a7c21f811d..c32831d3aaa 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -76,8 +76,8 @@ export CI_RUNNER_DESCRIPTION="my runner" export CI_RUNNER_TAGS="docker, linux" export CI_SERVER="yes" export CI_SERVER_NAME="GitLab" -export CI_SERVER_REVISION="8.9.0" -export CI_SERVER_VERSION="70606bf" +export CI_SERVER_REVISION="70606bf" +export CI_SERVER_VERSION="8.9.0" ``` ### YAML-defined variables -- cgit v1.2.3 From 0e3f7927d5b970ec18f8ce7a3a08ee0f9001f1d7 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 7 Sep 2016 13:40:14 +0200 Subject: Add failing test for #14360 --- spec/helpers/search_helper_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index b0bb991539b..4b2ca3514f8 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -32,6 +32,10 @@ describe SearchHelper do expect(search_autocomplete_opts("adm").size).to eq(1) end + it "does not allow regular expression in search term" do + expect(search_autocomplete_opts("(webhooks|api)").size).to eq(0) + end + it "includes the user's groups" do create(:group).add_owner(user) expect(search_autocomplete_opts("gro").size).to eq(1) -- cgit v1.2.3 From e64e45db545242f033d6cb9636f61ab25e9a3ef2 Mon Sep 17 00:00:00 2001 From: winniehell Date: Wed, 7 Sep 2016 15:16:22 +0200 Subject: Escape search term before passing it to Regexp.new (!6241) --- CHANGELOG | 1 + app/helpers/search_helper.rb | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 07b6e1298e4..31f26af4e51 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.12.0 (unreleased) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 - Fix pagination on user snippets page + - Escape search term before passing it to Regexp.new !6241 (winniehell) - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 4549c2e5bb6..e523c46e879 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -7,8 +7,10 @@ module SearchHelper projects_autocomplete(term) ].flatten + search_pattern = Regexp.new(Regexp.escape(term), "i") + generic_results = project_autocomplete + default_autocomplete + help_autocomplete - generic_results.select! { |result| result[:label] =~ Regexp.new(term, "i") } + generic_results.select! { |result| result[:label] =~ search_pattern } [ resources_results, -- cgit v1.2.3 From c7f74256f7c84297199999145b8cc04a018f6a44 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 7 Sep 2016 15:36:35 +0200 Subject: Simulate filtering with url params, clean up accordingly. --- spec/features/issues/reset_filters_spec.rb | 51 ++++-------------------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb index da8b2ba579b..41f218eaa8b 100644 --- a/spec/features/issues/reset_filters_spec.rb +++ b/spec/features/issues/reset_filters_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' feature 'Issues filter reset button', feature: true, js: true do include WaitForAjax + include IssueHelpers let!(:project) { create(:project, :public) } let!(:user) { create(:user)} @@ -12,12 +13,11 @@ feature 'Issues filter reset button', feature: true, js: true do before do project.team << [user, :developer] - visit_issues(project) end context 'when a milestone filter has been applied' do it 'resets the milestone filter' do - filter_by_milestone(milestone.title) + visit_issues(project, milestone_title: milestone.title) expect(page).to have_css('.issue', count: 1) reset_filters @@ -27,7 +27,7 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when a label filter has been applied' do it 'resets the label filter' do - filter_by_label(bug.title) + visit_issues(project, label_name: bug.name) expect(page).to have_css('.issue', count: 1) reset_filters @@ -37,8 +37,7 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when a text search has been conducted' do it 'resets the text search filter' do - - fill_in 'issue_search', with: 'Bug' + visit_issues(project, issue_search: 'Bug') expect(page).to have_css('.issue', count: 1) reset_filters @@ -48,7 +47,7 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when author filter has been applied' do it 'resets the author filter' do - filter_by_author(user.name) + visit_issues(project, author_id: user.id) expect(page).to have_css('.issue', count: 1) reset_filters @@ -58,7 +57,7 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when assignee filter has been applied' do it 'resets the assignee filter' do - filter_by_assignee(user.name) + visit_issues(project, assignee_id: user.id) expect(page).to have_css('.issue', count: 1) reset_filters @@ -68,17 +67,7 @@ feature 'Issues filter reset button', feature: true, js: true do context 'when all filters have been applied' do it 'resets all filters' do - - wait_for_ajax - - filter_by_milestone(milestone.title) - - wait_for_ajax - - filter_by_author(user.username) - - wait_for_ajax - + visit_issues(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, issue_search: 'Bug') expect(page).to have_css('.issue', count: 0) reset_filters @@ -86,33 +75,7 @@ feature 'Issues filter reset button', feature: true, js: true do end end - def filter_by_milestone(title) - find('.js-milestone-select').click - find('.milestone-filter .dropdown-content a', text: title).click - end - - def filter_by_label(title) - find('.js-label-select').click - find('.labels-filter .dropdown-content a', text: title).click - find('.labels-filter .dropdown-title .dropdown-menu-close-icon').click - end - - def filter_by_author(name) - find('.js-author-search').click - find('.dropdown-menu-author .dropdown-content a', text: name).click - end - - def filter_by_assignee(name) - find('.js-assignee-search').click - find('.dropdown-menu-assignee .dropdown-content a', text: name).click - end - def reset_filters find('.reset-filters').click - wait_for_ajax - end - - def visit_issues(project) - visit namespace_project_issues_path(project.namespace, project) end end -- cgit v1.2.3 From 3152477114ed95d2ca5b5a27487c4f392f7486fa Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 29 Aug 2016 18:02:08 +0200 Subject: Use PipelinesFinder in Pipelines API --- CHANGELOG | 1 + app/controllers/projects/pipelines_controller.rb | 9 ++--- app/finders/pipelines_finder.rb | 32 ++++++++------- lib/api/pipelines.rb | 5 ++- spec/finders/pipelines_finder_spec.rb | 51 ++++++++++++++++++++++++ 5 files changed, 78 insertions(+), 20 deletions(-) create mode 100644 spec/finders/pipelines_finder_spec.rb diff --git a/CHANGELOG b/CHANGELOG index bf1136afd03..b03c5466d6c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.12.0 (unreleased) - Add Sentry logging to API calls - Add BroadcastMessage API - Use 'git update-ref' for safer web commits !6130 + - Sort pipelines requested through the API - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Remove unused mixins (ClemMakesApps) - Add search to all issue board lists diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index b0c72cfe4b4..371cc3787fb 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -7,11 +7,10 @@ class Projects::PipelinesController < Projects::ApplicationController def index @scope = params[:scope] - all_pipelines = project.pipelines - @pipelines_count = all_pipelines.count - @running_or_pending_count = all_pipelines.running_or_pending.count - @pipelines = PipelinesFinder.new(project).execute(all_pipelines, @scope) - @pipelines = @pipelines.order(id: :desc).page(params[:page]).per(30) + @pipelines = PipelinesFinder.new(project).execute(scope: @scope).page(params[:page]).per(30) + + @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count + @pipelines_count = PipelinesFinder.new(project).execute.count end def new diff --git a/app/finders/pipelines_finder.rb b/app/finders/pipelines_finder.rb index 641fbf838f1..32aea75486d 100644 --- a/app/finders/pipelines_finder.rb +++ b/app/finders/pipelines_finder.rb @@ -1,30 +1,34 @@ class PipelinesFinder - attr_reader :project + attr_reader :project, :pipelines def initialize(project) @project = project + @pipelines = project.pipelines end - def execute(pipelines, scope) - case scope - when 'running' - pipelines.running_or_pending - when 'branches' - from_ids(pipelines, ids_for_ref(pipelines, branches)) - when 'tags' - from_ids(pipelines, ids_for_ref(pipelines, tags)) - else - pipelines - end + def execute(scope: nil) + scoped_pipelines = + case scope + when 'running' + pipelines.running_or_pending + when 'branches' + from_ids(ids_for_ref(branches)) + when 'tags' + from_ids(ids_for_ref(tags)) + else + pipelines + end + + scoped_pipelines.order(id: :desc) end private - def ids_for_ref(pipelines, refs) + def ids_for_ref(refs) pipelines.where(ref: refs).group(:ref).select('max(id)') end - def from_ids(pipelines, ids) + def from_ids(ids) pipelines.unscoped.where(id: ids) end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index 2aae75c471d..2a0c8e1f2c0 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -13,11 +13,14 @@ module API params do optional :page, type: Integer, desc: 'Page number of the current request' optional :per_page, type: Integer, desc: 'Number of items per page' + optional :scope, type: String, values: ['running', 'branches', 'tags'], + desc: 'Either running, branches, or tags' end get ':id/pipelines' do authorize! :read_pipeline, user_project - present paginate(user_project.pipelines), with: Entities::Pipeline + pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope]) + present paginate(pipelines), with: Entities::Pipeline end desc 'Gets a specific pipeline for the project' do diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb new file mode 100644 index 00000000000..7100266ab55 --- /dev/null +++ b/spec/finders/pipelines_finder_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe PipelinesFinder do + let(:project) { create(:project) } + + let!(:tag_pipeline) { create(:ci_pipeline, project: project, ref: 'v1.0.0') } + let!(:branch_pipeline) { create(:ci_pipeline, project: project) } + + subject { described_class.new(project).execute(params) } + + describe "#execute" do + context 'when a scope is passed' do + context 'when scope is nil' do + let(:params) { { scope: nil } } + + it 'selects all pipelines' do + expect(subject.count).to be 2 + expect(subject).to include tag_pipeline + expect(subject).to include branch_pipeline + end + end + + context 'when selecting branches' do + let(:params) { { scope: 'branches' } } + + it 'excludes tags' do + expect(subject).not_to include tag_pipeline + expect(subject).to include branch_pipeline + end + end + + context 'when selecting tags' do + let(:params) { { scope: 'tags' } } + + it 'excludes branches' do + expect(subject).to include tag_pipeline + expect(subject).not_to include branch_pipeline + end + end + end + + # Scoping to running will speed up the test as it doesn't hit the FS + let(:params) { { scope: 'running' } } + + it 'orders in descending order on ID' do + create(:ci_pipeline, project: project, ref: 'feature') + + expect(subject.map(&:id)).to eq [3, 2, 1] + end + end +end -- cgit v1.2.3 From 56461e0c16a3558325a0319090faa420f15f6daa Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 5 Sep 2016 18:57:00 +0100 Subject: Reordered dropdown options Rearranged datetime created line Added `avatar_class` option to `link_to_member` so it can be hidden Made snippet title font heavier Added file title bar Hidden unneeded info from xs screens Added visibility icon Reveal file-actions on xs screens Added comments icon to snippets list items Refactored no-comments declaration into framework stylesheet Final touch ups Fixed snippets_spec --- app/assets/stylesheets/framework/lists.scss | 4 +++ app/assets/stylesheets/pages/issues.scss | 4 --- app/assets/stylesheets/pages/merge_requests.scss | 4 --- app/assets/stylesheets/pages/snippets.scss | 39 ++++------------------ app/helpers/projects_helper.rb | 2 +- app/helpers/snippets_helper.rb | 6 ++-- app/views/projects/issues/_issue.html.haml | 2 +- .../merge_requests/_merge_request.html.haml | 2 +- app/views/projects/snippets/_actions.html.haml | 14 ++++---- app/views/projects/snippets/show.html.haml | 21 ++++++------ app/views/shared/snippets/_header.html.haml | 8 ++--- app/views/shared/snippets/_snippet.html.haml | 21 +++++++++--- app/views/snippets/_actions.html.haml | 14 ++++---- app/views/snippets/show.html.haml | 19 +++++------ spec/features/issues_spec.rb | 2 +- spec/features/users/snippets_spec.rb | 5 ++- 16 files changed, 75 insertions(+), 92 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 965fcc06518..46af18580d5 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -162,6 +162,10 @@ ul.content-list { margin-right: 0; } } + + .no-comments { + opacity: 0.5; + } } // When dragging a list item diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 910700b0206..737eaa29a2f 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -21,10 +21,6 @@ .issue-labels { display: inline-block; } - - .issue-no-comments { - opacity: 0.5; - } } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7fdd79fa8b9..0b1dd57867e 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -231,10 +231,6 @@ .merge-request-labels { display: inline-block; } - - .merge-request-no-comments { - opacity: 0.5; - } } .merge-request-angle { diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index 2aa939b7dc3..5270aea4e79 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -2,20 +2,6 @@ padding: 2px; } -.snippet-holder { - margin-bottom: -$gl-padding; - - .file-holder { - border-top: 0; - } - - .file-actions { - .btn-clipboard { - @extend .btn; - } - } -} - .markdown-snippet-copy { position: fixed; top: -10px; @@ -24,29 +10,18 @@ max-width: 0; } -.file-holder.snippet-file-content { - padding-bottom: $gl-padding; - border-bottom: 1px solid $border-color; - - .file-title { - padding-top: $gl-padding; - padding-bottom: $gl-padding; - } - - .file-actions { - top: 12px; - } - - .file-content { - border-left: 1px solid $border-color; - border-right: 1px solid $border-color; - border-bottom: 1px solid $border-color; +.snippet-file-content { + border-radius: 3px; + .btn-clipboard { + @extend .btn; } } .snippet-title { font-size: 24px; - font-weight: normal; + font-weight: 600; + padding: $gl-padding; + padding-left: 0; } .snippet-actions { diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 4c685b97c03..3ac42daf26c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -27,7 +27,7 @@ module ProjectsHelper author_html = "" # Build avatar image tag - author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar] + author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar] # Build name span tag if opts[:by_username] diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 0a5a8eb5aee..7e33a562077 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -1,10 +1,10 @@ module SnippetsHelper - def reliable_snippet_path(snippet) + def reliable_snippet_path(snippet, opts = nil) if snippet.project_id? namespace_project_snippet_path(snippet.project.namespace, - snippet.project, snippet) + snippet.project, snippet, opts) else - snippet_path(snippet) + snippet_path(snippet, opts) end end diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 79b14819865..def9429db09 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -29,7 +29,7 @@ - note_count = issue.notes.user.count %li - = link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do + = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do = icon('comments') = note_count diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 5029b365f93..0381c5ed950 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -37,7 +37,7 @@ - note_count = merge_request.mr_and_commit_notes.user.count %li - = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do + = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do = icon('comments') = note_count diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index bdbf3e5f4d6..a5a5619fa12 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -2,12 +2,12 @@ - if can?(current_user, :create_project_snippet, @project) = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do New Snippet - - if can?(current_user, :update_project_snippet, @snippet) - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do - Edit - if can?(current_user, :update_project_snippet, @snippet) = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do Delete + - if can?(current_user, :update_project_snippet, @snippet) + = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do + Edit - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } @@ -19,11 +19,11 @@ %li = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do New Snippet - - if can?(current_user, :update_project_snippet, @snippet) - %li - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do - Edit - if can?(current_user, :update_project_snippet, @snippet) %li = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do Delete + - if can?(current_user, :update_project_snippet, @snippet) + %li + = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do + Edit diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index bae4d8f349f..b70fda88a79 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -1,15 +1,14 @@ - page_title @snippet.title, "Snippets" -.snippet-holder - = render 'shared/snippets/header' += render 'shared/snippets/header' - %article.file-holder.file-holder-no-border.snippet-file-content - .file-title.file-title-clear - = blob_icon 0, @snippet.file_name - = @snippet.file_name - .file-actions.hidden-xs - = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") - = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" - = render 'shared/snippets/blob' +%article.file-holder.snippet-file-content + .file-title + = blob_icon 0, @snippet.file_name + = @snippet.file_name + .file-actions + = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") + = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" + = render 'shared/snippets/blob' - %div#notes= render "projects/notes/notes_with_form" +%div#notes= render "projects/notes/notes_with_form" diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index af753496260..7ae4211ddfd 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -6,12 +6,13 @@ %strong.item-title Snippet #{@snippet.to_reference} %span.creator - created by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title")} + authored = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago') - if @snippet.updated_at != @snippet.created_at %span = icon('edit', title: 'edited') = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago') + by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")} .snippet-actions - if @snippet.project_id? @@ -19,6 +20,5 @@ - else = render "snippets/actions" -.content-block.second-block - %h2.snippet-title.prepend-top-0.append-bottom-0 - = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author +%h2.snippet-title.prepend-top-0.append-bottom-0 + = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index c96dfefe17f..ea17bec8677 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -3,19 +3,30 @@ .title = link_to reliable_snippet_path(snippet) do - = truncate(snippet.title, length: 60) + = snippet.title - if snippet.private? - %span.label.label-gray + %span.label.label-gray.hidden-xs = icon('lock') private - %span.monospace.pull-right + %span.monospace.pull-right.hidden-xs = snippet.file_name - %small.pull-right.cgray + %ul.controls.visible-xs + %li + - note_count = snippet.notes.user.count + = link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do + = icon('comments') + = note_count + %li + %span.sr-only + = visibility_level_label(snippet.visibility_level) + = visibility_level_icon(snippet.visibility_level, fw: false) + + %small.pull-right.cgray.hidden-xs - if snippet.project_id? = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) - .snippet-info + .snippet-info.hidden-xs = link_to user_snippets_path(snippet.author) do = snippet.author_name authored #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 160c6cd84da..fdaca199218 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -2,12 +2,12 @@ - if current_user = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do New Snippet - - if can?(current_user, :update_personal_snippet, @snippet) - = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do - Edit - if can?(current_user, :admin_personal_snippet, @snippet) = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do Delete + - if can?(current_user, :update_personal_snippet, @snippet) + = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do + Edit - if current_user .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } @@ -18,11 +18,11 @@ %li = link_to new_snippet_path, title: "New Snippet" do New Snippet - - if can?(current_user, :update_personal_snippet, @snippet) - %li - = link_to edit_snippet_path(@snippet) do - Edit - if can?(current_user, :admin_personal_snippet, @snippet) %li = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do Delete + - if can?(current_user, :update_personal_snippet, @snippet) + %li + = link_to edit_snippet_path(@snippet) do + Edit diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index ed3992650d4..fa403da8f79 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -1,13 +1,12 @@ - page_title @snippet.title, "Snippets" -.snippet-holder - = render 'shared/snippets/header' += render 'shared/snippets/header' - %article.file-holder.file-holder-no-border.snippet-file-content - .file-title.file-title-clear - = blob_icon 0, @snippet.file_name - = @snippet.file_name - .file-actions.hidden-xs - = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") - = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" - = render 'shared/snippets/blob' +%article.file-holder.snippet-file-content + .file-title + = blob_icon 0, @snippet.file_name + = @snippet.file_name + .file-actions + = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") + = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" + = render 'shared/snippets/blob' diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index d744d0eb6af..22359c8f938 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -144,7 +144,7 @@ describe 'Issues', feature: true do visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id) expect(page).to have_content 'foobar' - expect(page.all('.issue-no-comments').first.text).to eq "0" + expect(page.all('.no-comments').first.text).to eq "0" end end diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb index 356a8d668b0..f00abd82fea 100644 --- a/spec/features/users/snippets_spec.rb +++ b/spec/features/users/snippets_spec.rb @@ -19,7 +19,10 @@ describe 'Snippets tab on a user profile', feature: true, js: true do end context 'clicking on the link to the second page' do - before { click_link('2') } + before do + click_link('2') + wait_for_ajax + end it 'shows the remaining snippets' do expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5) -- cgit v1.2.3 From 6cdc3460da0e806f9cdef24b1fb293edb0a7a0cc Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 15:12:35 +0100 Subject: Fix styling of shortcuts modal --- app/views/help/_shortcuts.html.haml | 547 ++++++++++++++++++------------------ 1 file changed, 274 insertions(+), 273 deletions(-) diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index ce4536ebdc6..16c16cec137 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -7,277 +7,278 @@ Keyboard Shortcuts %small = link_to '(Show all)', '#', class: 'js-more-help-button' - .modal-body.shortcuts-cheatsheet - .col-lg-4 - %table.shortcut-mappings - %tbody - %tr - %th - %th Global Shortcuts - %tr - %td.shortcut - .key s - %td Focus Search - %tr - %td.shortcut - .key f - %td Focus Filter - %tr - %td.shortcut - .key ? - %td Show/hide this dialog - %tr - %td.shortcut - - if browser.platform.mac? - .key ⌘ shift p - - else - .key ctrl shift p - %td Toggle Markdown preview - %tr - %td.shortcut - .key - %i.fa.fa-arrow-up - %td Edit last comment (when focused on an empty textarea) - %tbody - %tr - %th - %th Project Files browsing - %tr - %td.shortcut - .key - %i.fa.fa-arrow-up - %td Move selection up - %tr - %td.shortcut - .key - %i.fa.fa-arrow-down - %td Move selection down - %tr - %td.shortcut - .key enter - %td Open Selection - %tbody - %tr - %th - %th Finding Project File - %tr - %td.shortcut - .key - %i.fa.fa-arrow-up - %td Move selection up - %tr - %td.shortcut - .key - %i.fa.fa-arrow-down - %td Move selection down - %tr - %td.shortcut - .key enter - %td Open Selection - %tr - %td.shortcut - .key esc - %td Go back + .modal-body + .row + .col-lg-4 + %table.shortcut-mappings + %tbody + %tr + %th + %th Global Shortcuts + %tr + %td.shortcut + .key s + %td Focus Search + %tr + %td.shortcut + .key f + %td Focus Filter + %tr + %td.shortcut + .key ? + %td Show/hide this dialog + %tr + %td.shortcut + - if browser.platform.mac? + .key ⌘ shift p + - else + .key ctrl shift p + %td Toggle Markdown preview + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Edit last comment (when focused on an empty textarea) + %tbody + %tr + %th + %th Project Files browsing + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Move selection up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + %td Move selection down + %tr + %td.shortcut + .key enter + %td Open Selection + %tbody + %tr + %th + %th Finding Project File + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Move selection up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + %td Move selection down + %tr + %td.shortcut + .key enter + %td Open Selection + %tr + %td.shortcut + .key esc + %td Go back - .col-lg-4 - %table.shortcut-mappings - %tbody{ class: 'hidden-shortcut project', style: 'display:none' } - %tr - %th - %th Global Dashboard - %tr - %td.shortcut - .key g - .key a - %td - Go to the activity feed - %tr - %td.shortcut - .key g - .key p - %td - Go to projects - %tr - %td.shortcut - .key g - .key i - %td - Go to issues - %tr - %td.shortcut - .key g - .key m - %td - Go to merge requests - %tbody - %tr - %th - %th Project - %tr - %td.shortcut - .key g - .key p - %td - Go to the project's home page - %tr - %td.shortcut - .key g - .key e - %td - Go to the project's activity feed - %tr - %td.shortcut - .key g - .key f - %td - Go to files - %tr - %td.shortcut - .key g - .key c - %td - Go to commits - %tr - %td.shortcut - .key g - .key b - %td - Go to builds - %tr - %td.shortcut - .key g - .key n - %td - Go to network graph - %tr - %td.shortcut - .key g - .key g - %td - Go to graphs - %tr - %td.shortcut - .key g - .key i - %td - Go to issues - %tr - %td.shortcut - .key g - .key m - %td - Go to merge requests - %tr - %td.shortcut - .key g - .key s - %td - Go to snippets - %tr - %td.shortcut - .key t - %td Go to finding file - %tr - %td.shortcut - .key i - %td New issue - .col-lg-4 - %table.shortcut-mappings - %tbody{ class: 'hidden-shortcut network', style: 'display:none' } - %tr - %th - %th Network Graph - %tr - %td.shortcut - .key - %i.fa.fa-arrow-left - \/ - .key h - %td Scroll left - %tr - %td.shortcut - .key - %i.fa.fa-arrow-right - \/ - .key l - %td Scroll right - %tr - %td.shortcut - .key - %i.fa.fa-arrow-up - \/ - .key k - %td Scroll up - %tr - %td.shortcut - .key - %i.fa.fa-arrow-down - \/ - .key j - %td Scroll down - %tr - %td.shortcut - .key - shift - %i.fa.fa-arrow-up - \/ - .key - shift k - %td Scroll to top - %tr - %td.shortcut - .key - shift - %i.fa.fa-arrow-down - \/ - .key - shift j - %td Scroll to bottom - %tbody{ class: 'hidden-shortcut issues', style: 'display:none' } - %tr - %th - %th Issues - %tr - %td.shortcut - .key a - %td Change assignee - %tr - %td.shortcut - .key m - %td Change milestone - %tr - %td.shortcut - .key r - %td Reply (quoting selected text) - %tr - %td.shortcut - .key e - %td Edit issue - %tr - %td.shortcut - .key l - %td Change Label - %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' } - %tr - %th - %th Merge Requests - %tr - %td.shortcut - .key a - %td Change assignee - %tr - %td.shortcut - .key m - %td Change milestone - %tr - %td.shortcut - .key r - %td Reply (quoting selected text) - %tr - %td.shortcut - .key e - %td Edit merge request - %tr - %td.shortcut - .key l - %td Change Label + .col-lg-4 + %table.shortcut-mappings + %tbody{ class: 'hidden-shortcut project', style: 'display:none' } + %tr + %th + %th Global Dashboard + %tr + %td.shortcut + .key g + .key a + %td + Go to the activity feed + %tr + %td.shortcut + .key g + .key p + %td + Go to projects + %tr + %td.shortcut + .key g + .key i + %td + Go to issues + %tr + %td.shortcut + .key g + .key m + %td + Go to merge requests + %tbody + %tr + %th + %th Project + %tr + %td.shortcut + .key g + .key p + %td + Go to the project's home page + %tr + %td.shortcut + .key g + .key e + %td + Go to the project's activity feed + %tr + %td.shortcut + .key g + .key f + %td + Go to files + %tr + %td.shortcut + .key g + .key c + %td + Go to commits + %tr + %td.shortcut + .key g + .key b + %td + Go to builds + %tr + %td.shortcut + .key g + .key n + %td + Go to network graph + %tr + %td.shortcut + .key g + .key g + %td + Go to graphs + %tr + %td.shortcut + .key g + .key i + %td + Go to issues + %tr + %td.shortcut + .key g + .key m + %td + Go to merge requests + %tr + %td.shortcut + .key g + .key s + %td + Go to snippets + %tr + %td.shortcut + .key t + %td Go to finding file + %tr + %td.shortcut + .key i + %td New issue + .col-lg-4 + %table.shortcut-mappings + %tbody{ class: 'hidden-shortcut network', style: 'display:none' } + %tr + %th + %th Network Graph + %tr + %td.shortcut + .key + %i.fa.fa-arrow-left + \/ + .key h + %td Scroll left + %tr + %td.shortcut + .key + %i.fa.fa-arrow-right + \/ + .key l + %td Scroll right + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + \/ + .key k + %td Scroll up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + \/ + .key j + %td Scroll down + %tr + %td.shortcut + .key + shift + %i.fa.fa-arrow-up + \/ + .key + shift k + %td Scroll to top + %tr + %td.shortcut + .key + shift + %i.fa.fa-arrow-down + \/ + .key + shift j + %td Scroll to bottom + %tbody{ class: 'hidden-shortcut issues', style: 'display:none' } + %tr + %th + %th Issues + %tr + %td.shortcut + .key a + %td Change assignee + %tr + %td.shortcut + .key m + %td Change milestone + %tr + %td.shortcut + .key r + %td Reply (quoting selected text) + %tr + %td.shortcut + .key e + %td Edit issue + %tr + %td.shortcut + .key l + %td Change Label + %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' } + %tr + %th + %th Merge Requests + %tr + %td.shortcut + .key a + %td Change assignee + %tr + %td.shortcut + .key m + %td Change milestone + %tr + %td.shortcut + .key r + %td Reply (quoting selected text) + %tr + %td.shortcut + .key e + %td Edit merge request + %tr + %td.shortcut + .key l + %td Change Label -- cgit v1.2.3 From a75026915863280c78fe73211e423bf6f4ce11c4 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 7 Sep 2016 16:26:06 +0200 Subject: Allow adding multiple commits in commit_with_hooks --- app/models/repository.rb | 6 +++++- spec/models/repository_spec.rb | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 414b82516bc..83605982933 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1040,7 +1040,11 @@ class Repository raise CommitError.new('Failed to create commit') end - oldrev = rugged.lookup(newrev).parent_ids.first || Gitlab::Git::BLANK_SHA + if rugged.lookup(newrev).parent_ids.empty? || target_branch.nil? + oldrev = Gitlab::Git::BLANK_SHA + else + oldrev = rugged.merge_base(newrev, target_branch.target.sha) + end GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do update_ref!(ref, newrev, oldrev) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index afc7dc5db81..17eecf6bbd6 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -473,6 +473,25 @@ describe Repository, models: true do end end + context 'when the update adds more than one commit' do + it 'runs without errors' do + old_rev = '33f3729a45c02fc67d00adb1b8bca394b0e761d9' # ancestor of new_rev by more than one commit + branch = 'feature-ff-target' + repository.add_branch(user, branch, old_rev) + + expect { repository.commit_with_hooks(user, branch) { new_rev } }.not_to raise_error + end + end + + context 'when the update would remove commits from the target branch' do + it 'raises an exception' do + # We use the fact that 'master' has diverged from 'feature' (new_rev): + # updating 'master' to new_rev would make us lose commits, which should + # not happen. + expect { repository.commit_with_hooks(user, 'master') { new_rev } }.to raise_error(Repository::CommitError) + end + end + context 'when pre hooks failed' do it 'gets an error' do allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) -- cgit v1.2.3 From 0f08bb86d8731edd40f9348335987f55a4ba9f11 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 7 Sep 2016 16:28:26 +0200 Subject: Rename {commit,update_branch}_with_hooks --- app/models/repository.rb | 18 +++++++++--------- spec/models/repository_spec.rb | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 83605982933..7b7090b8a73 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -757,7 +757,7 @@ class Repository end def commit_dir(user, path, message, branch) - commit_with_hooks(user, branch) do |ref| + update_branch_with_hooks(user, branch) do |ref| committer = user_to_committer(user) options = {} options[:committer] = committer @@ -774,7 +774,7 @@ class Repository end def commit_file(user, path, content, message, branch, update) - commit_with_hooks(user, branch) do |ref| + update_branch_with_hooks(user, branch) do |ref| committer = user_to_committer(user) options = {} options[:committer] = committer @@ -796,7 +796,7 @@ class Repository end def update_file(user, path, content, branch:, previous_path:, message:) - commit_with_hooks(user, branch) do |ref| + update_branch_with_hooks(user, branch) do |ref| committer = user_to_committer(user) options = {} options[:committer] = committer @@ -823,7 +823,7 @@ class Repository end def remove_file(user, path, message, branch) - commit_with_hooks(user, branch) do |ref| + update_branch_with_hooks(user, branch) do |ref| committer = user_to_committer(user) options = {} options[:committer] = committer @@ -871,7 +871,7 @@ class Repository merge_index = rugged.merge_commits(our_commit, their_commit) return false if merge_index.conflicts? - commit_with_hooks(user, merge_request.target_branch) do + update_branch_with_hooks(user, merge_request.target_branch) do actual_options = options.merge( parents: [our_commit, their_commit], tree: merge_index.write_tree(rugged), @@ -889,7 +889,7 @@ class Repository return false unless revert_tree_id - commit_with_hooks(user, base_branch) do + update_branch_with_hooks(user, base_branch) do committer = user_to_committer(user) source_sha = Rugged::Commit.create(rugged, message: commit.revert_message, @@ -906,7 +906,7 @@ class Repository return false unless cherry_pick_tree_id - commit_with_hooks(user, base_branch) do + update_branch_with_hooks(user, base_branch) do committer = user_to_committer(user) source_sha = Rugged::Commit.create(rugged, message: commit.message, @@ -922,7 +922,7 @@ class Repository end def resolve_conflicts(user, branch, params) - commit_with_hooks(user, branch) do + update_branch_with_hooks(user, branch) do committer = user_to_committer(user) Rugged::Commit.create(rugged, params.merge(author: committer, committer: committer)) @@ -1026,7 +1026,7 @@ class Repository Gitlab::Popen.popen(args, path_to_repo) end - def commit_with_hooks(current_user, branch) + def update_branch_with_hooks(current_user, branch) update_autocrlf_option ref = Gitlab::Git::BRANCH_REF_PREFIX + branch diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 17eecf6bbd6..bc899027d6b 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -441,7 +441,7 @@ describe Repository, models: true do end end - describe '#commit_with_hooks' do + describe '#update_branch_with_hooks' do let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature let(:new_rev) { 'a74ae73c1ccde9b974a70e82b901588071dc142a' } # commit whose parent is old_rev @@ -454,20 +454,20 @@ describe Repository, models: true do it 'runs without errors' do expect do - repository.commit_with_hooks(user, 'feature') { new_rev } + repository.update_branch_with_hooks(user, 'feature') { new_rev } end.not_to raise_error end it 'ensures the autocrlf Git option is set to :input' do expect(repository).to receive(:update_autocrlf_option) - repository.commit_with_hooks(user, 'feature') { new_rev } + repository.update_branch_with_hooks(user, 'feature') { new_rev } end context "when the branch wasn't empty" do it 'updates the head' do expect(repository.find_branch('feature').target.id).to eq(old_rev) - repository.commit_with_hooks(user, 'feature') { new_rev } + repository.update_branch_with_hooks(user, 'feature') { new_rev } expect(repository.find_branch('feature').target.id).to eq(new_rev) end end @@ -479,7 +479,7 @@ describe Repository, models: true do branch = 'feature-ff-target' repository.add_branch(user, branch, old_rev) - expect { repository.commit_with_hooks(user, branch) { new_rev } }.not_to raise_error + expect { repository.update_branch_with_hooks(user, branch) { new_rev } }.not_to raise_error end end @@ -488,7 +488,7 @@ describe Repository, models: true do # We use the fact that 'master' has diverged from 'feature' (new_rev): # updating 'master' to new_rev would make us lose commits, which should # not happen. - expect { repository.commit_with_hooks(user, 'master') { new_rev } }.to raise_error(Repository::CommitError) + expect { repository.update_branch_with_hooks(user, 'master') { new_rev } }.to raise_error(Repository::CommitError) end end @@ -497,7 +497,7 @@ describe Repository, models: true do allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([false, '']) expect do - repository.commit_with_hooks(user, 'feature') { new_rev } + repository.update_branch_with_hooks(user, 'feature') { new_rev } end.to raise_error(GitHooksService::PreReceiveError) end end @@ -516,7 +516,7 @@ describe Repository, models: true do expect(repository).to receive(:expire_has_visible_content_cache) expect(repository).to receive(:expire_branch_count_cache) - repository.commit_with_hooks(user, 'new-feature') { new_rev } + repository.update_branch_with_hooks(user, 'new-feature') { new_rev } end end -- cgit v1.2.3 From 084bac8935f6532d28db71fbf14e68d9060337e8 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 7 Sep 2016 16:56:11 +0200 Subject: Express intentions as expectations --- spec/models/repository_spec.rb | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index bc899027d6b..7624050878e 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -475,7 +475,14 @@ describe Repository, models: true do context 'when the update adds more than one commit' do it 'runs without errors' do - old_rev = '33f3729a45c02fc67d00adb1b8bca394b0e761d9' # ancestor of new_rev by more than one commit + old_rev = '33f3729a45c02fc67d00adb1b8bca394b0e761d9' + + # old_rev is an ancestor of new_rev + expect(repository.rugged.merge_base(old_rev, new_rev)).to eq(old_rev) + + # old_rev is not a direct ancestor (parent) of new_rev + expect(repository.rugged.lookup(new_rev).parent_ids).not_to include(old_rev) + branch = 'feature-ff-target' repository.add_branch(user, branch, old_rev) @@ -485,10 +492,17 @@ describe Repository, models: true do context 'when the update would remove commits from the target branch' do it 'raises an exception' do - # We use the fact that 'master' has diverged from 'feature' (new_rev): - # updating 'master' to new_rev would make us lose commits, which should - # not happen. - expect { repository.update_branch_with_hooks(user, 'master') { new_rev } }.to raise_error(Repository::CommitError) + branch = 'master' + old_rev = repository.find_branch(branch).target.sha + + # The 'master' branch is NOT an ancestor of new_rev. + expect(repository.rugged.merge_base(old_rev, new_rev)).not_to eq(old_rev) + + # Updating 'master' to new_rev would lose the commits on 'master' that + # are not contained in new_rev. This should not be allowed. + expect do + repository.update_branch_with_hooks(user, branch) { new_rev } + end.to raise_error(Repository::CommitError) end end -- cgit v1.2.3 From 24424d932f10c1a498a77d742d5ffd6ae18c506d Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 26 Aug 2016 10:54:19 +0100 Subject: Fix merge conflict size limit --- CHANGELOG | 1 + lib/gitlab/conflict/parser.rb | 2 +- spec/lib/gitlab/conflict/parser_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bf1136afd03..fbb304b677d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -97,6 +97,7 @@ v 8.12.0 (unreleased) v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch - Fix member expiration date picker after update + - Make merge conflict file size limit 200 KB, to match the docs - Fix suggested colors options for new labels in the admin area. !6138 - Fix GitLab import button - Remove gitorious from import_sources diff --git a/lib/gitlab/conflict/parser.rb b/lib/gitlab/conflict/parser.rb index 2d4d55daeeb..98e842cded3 100644 --- a/lib/gitlab/conflict/parser.rb +++ b/lib/gitlab/conflict/parser.rb @@ -18,7 +18,7 @@ module Gitlab def parse(text, our_path:, their_path:, parent_file: nil) raise UnmergeableFile if text.blank? # Typically a binary file - raise UnmergeableFile if text.length > 102400 + raise UnmergeableFile if text.length > 200.kilobytes begin text.to_json diff --git a/spec/lib/gitlab/conflict/parser_spec.rb b/spec/lib/gitlab/conflict/parser_spec.rb index a1d2ca1e272..16eb3766356 100644 --- a/spec/lib/gitlab/conflict/parser_spec.rb +++ b/spec/lib/gitlab/conflict/parser_spec.rb @@ -179,8 +179,8 @@ CONFLICT to raise_error(Gitlab::Conflict::Parser::UnmergeableFile) end - it 'raises UnmergeableFile when the file is over 100 KB' do - expect { parse_text('a' * 102401) }. + it 'raises UnmergeableFile when the file is over 200 KB' do + expect { parse_text('a' * 204801) }. to raise_error(Gitlab::Conflict::Parser::UnmergeableFile) end -- cgit v1.2.3 From 4ed7ffd385182bd54098d7066143c13a113d4454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 7 Sep 2016 17:15:37 +0200 Subject: Simplify the 'Implement design & UI elements' section in CONTRIBUTING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CONTRIBUTING.md | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c77dcd96a7d..549918284c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,19 +91,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs]. ## Implement design & UI elements -### Design reference - -The GitLab design reference can be found in the [gitlab-design] project. -The designs are made using Antetype (`.atype` files). You can use the -[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design -(the PNG is 1:1). - -The current designs can be found in the [`gitlab8.atype` file]. - -### UI development kit - -Implemented UI elements can also be found at https://gitlab.com/help/ui. Please -note that this page isn't comprehensive at this time. +Please see the [UI Guide for building GitLab]. ## Issue tracker @@ -489,7 +477,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" [scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide" [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide" -[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design -[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12 -[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/ +[UI Guide for building GitLab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/ui_guide.md [license-finder-doc]: doc/development/licensing.md -- cgit v1.2.3 From a958a046a8f6d273736f8270990cf13cdb12afab Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 11 Aug 2016 13:21:13 -0500 Subject: Fix contributions calendar month label truncation --- CHANGELOG | 1 + app/assets/javascripts/users/calendar.js | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index bf1136afd03..2f59da2e772 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -55,6 +55,7 @@ v 8.12.0 (unreleased) - Ability to manage project issues, snippets, wiki, merge requests and builds access level - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) - Align add button on repository view (ClemMakesApps) + - Fix contributions calendar month label truncation (ClemMakesApps) - Added tests for diff notes - Add a button to download latest successful artifacts for branches and tags !5142 - Remove redundant pipeline tooltips (ClemMakesApps) diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 74ecf4f4cf9..90cf551b32e 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -52,8 +52,22 @@ this.initTooltips(); } + // Add extra padding for the last month label if it is also the last column + Calendar.prototype.getExtraWidthPadding = function(group) { + var extraWidthPadding = 0; + var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth(); + var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth(); + + if (lastColMonth != secondLastColMonth) { + extraWidthPadding = 3; + } + + return extraWidthPadding; + } + Calendar.prototype.renderSvg = function(group) { - return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', (group + 1) * this.daySizeWithSpace).attr('height', 167).attr('class', 'contrib-calendar'); + var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group); + return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar'); }; Calendar.prototype.renderDays = function() { -- cgit v1.2.3 From 8a2b89f3ceedfba7d5b8ac196d2925d114b1cdeb Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Wed, 7 Sep 2016 10:04:59 -0700 Subject: fix margin for multi-line emojis --- app/assets/stylesheets/framework/blocks.scss | 4 ++++ app/assets/stylesheets/pages/awards.scss | 2 ++ app/views/projects/issues/_new_branch.html.haml | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 7ce203d2ec7..f5223207f3a 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -249,6 +249,10 @@ > .controls { float: right; } + + .new-branch { + margin-top: 3px; + } } .content-block-small { diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 188a4e193a6..447ba24b46b 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -93,6 +93,8 @@ } .award-control { + margin-top: 3px; + margin-bottom: 3px; margin-right: 5px; padding-left: 5px; padding-right: 5px; diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 33556a1a2b3..c56b6cc11f5 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -1,6 +1,6 @@ - if can?(current_user, :push_code, @project) .pull-right - #new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)} + #new-branch.new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)} = link_to '#', class: 'checking btn btn-grouped', disabled: 'disabled' do = icon('spinner spin') Checking branches -- cgit v1.2.3 From e25b48ffcf1f7ef31df8d6c3366674e7f5c29893 Mon Sep 17 00:00:00 2001 From: Olaf Tomalka Date: Mon, 5 Sep 2016 10:18:08 +0200 Subject: Added cron to prune events older than 12 months. Since contribution calendar shows only 12 months of activity, events older than that time are not visible anywhere and can be safely pruned saving big amount of database storage. Fixes #21164 --- CHANGELOG | 1 + app/workers/prune_old_events_worker.rb | 8 ++++++++ config/initializers/1_settings.rb | 3 +++ spec/workers/prune_old_events_worker_spec.rb | 24 ++++++++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 app/workers/prune_old_events_worker.rb create mode 100644 spec/workers/prune_old_events_worker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 16b813492a8..fea56a6ef21 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.12.0 (unreleased) - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) + - Prune events older than 12 months. - Filter tags by name !6121 - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb new file mode 100644 index 00000000000..a0182fc67d0 --- /dev/null +++ b/app/workers/prune_old_events_worker.rb @@ -0,0 +1,8 @@ +class PruneOldEventsWorker + include Sidekiq::Worker + + def perform + # Contribution calendar shows maximum 12 months of events + Event.where('created_at < ?', (12.months + 1.day).ago).destroy_all + end +end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 4a01b9e40fb..2fac3e34dc5 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -299,6 +299,9 @@ Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpire Settings.cron_jobs['remove_expired_group_links_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['remove_expired_group_links_worker']['cron'] ||= '10 0 * * *' Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker' +Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '0 0 * * *' +Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker' # # GitLab Shell diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb new file mode 100644 index 00000000000..35e1518a35e --- /dev/null +++ b/spec/workers/prune_old_events_worker_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe PruneOldEventsWorker do + describe '#perform' do + let!(:expired_event) { create(:event, author_id: 0, created_at: 13.months.ago) } + let!(:not_expired_event) { create(:event, author_id: 0, created_at: 1.day.ago) } + let!(:exactly_12_months_event) { create(:event, author_id: 0, created_at: 12.months.ago) } + + it 'prunes events older than 12 months' do + expect { subject.perform }.to change { Event.count }.by(-1) + expect(Event.find_by(id: expired_event.id)).to be_nil + end + + it 'leaves fresh events' do + subject.perform + expect(not_expired_event.reload).to be_present + end + + it 'leaves events from exactly 12 months ago' do + subject.perform + expect(exactly_12_months_event).to be_present + end + end +end -- cgit v1.2.3 From c0a92cb801528f00d1317b01fd8a94274552c166 Mon Sep 17 00:00:00 2001 From: Olaf Tomalka Date: Mon, 5 Sep 2016 22:57:56 +0200 Subject: Limited amount of pruned Event rows per run Old deployments of Gitlab might have a big number of old events to be deleted. Such numbers cause the worker to timeout. I've limited the amount of rows that should be destroyed at once to 10000, and increased how often pruning shall take place to 4 times a day. --- CHANGELOG | 1 + app/workers/prune_old_events_worker.rb | 2 +- config/initializers/1_settings.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fea56a6ef21..1d42acfe363 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 + - Prune events older than 12 months. @ritave - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Prune events older than 12 months. - Filter tags by name !6121 diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index a0182fc67d0..d75083f9ab1 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -3,6 +3,6 @@ class PruneOldEventsWorker def perform # Contribution calendar shows maximum 12 months of events - Event.where('created_at < ?', (12.months + 1.day).ago).destroy_all + Event.delete(Event.unscoped.where('created_at < ?', (12.months + 1.day).ago).limit(10_000).pluck(:id)) end end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 2fac3e34dc5..195108b921b 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -300,7 +300,7 @@ Settings.cron_jobs['remove_expired_group_links_worker'] ||= Settingslogic.new({} Settings.cron_jobs['remove_expired_group_links_worker']['cron'] ||= '10 0 * * *' Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker' Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '0 0 * * *' +Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *' Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker' # -- cgit v1.2.3 From 0794f7fe6244974ceedcc24d9dda9f77c48779ac Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 7 Sep 2016 19:07:58 +0100 Subject: Removed media query declaration relating to merge-request-info and issue-info --- app/assets/stylesheets/framework/mobile.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 367c7d01944..76b93b23b95 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -79,10 +79,6 @@ padding-left: 15px !important; } - .issue-info, .merge-request-info { - display: none; - } - .nav-links, .nav-links { li a { font-size: 14px; -- cgit v1.2.3 From b2f8ebbb4295e5f070187050e094db87ff9d6ad0 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 2 Sep 2016 01:33:43 -0500 Subject: refactor sidebar logic into singleton class --- CHANGELOG | 1 + app/assets/javascripts/application.js | 43 ++---------- app/assets/javascripts/sidebar.js | 41 ----------- app/assets/javascripts/sidebar.js.es6 | 98 +++++++++++++++++++++++++++ app/assets/stylesheets/framework/sidebar.scss | 4 +- 5 files changed, 104 insertions(+), 83 deletions(-) delete mode 100644 app/assets/javascripts/sidebar.js create mode 100644 app/assets/javascripts/sidebar.js.es6 diff --git a/CHANGELOG b/CHANGELOG index 16b813492a8..8f3c6fb8e76 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.12.0 (unreleased) - Instructions for enabling Git packfile bitmaps !6104 - Fix pagination on user snippets page - Escape search term before passing it to Regexp.new !6241 (winniehell) + - Fix pinned sidebar behavior in smaller viewports !6169 - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 43a679501a7..fea6f41d5e9 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -174,9 +174,7 @@ $body.tooltip({ selector: '.has-tooltip, [data-toggle="tooltip"]', placement: function(_, el) { - var $el; - $el = $(el); - return $el.data('placement') || 'bottom'; + return $(el).data('placement') || 'bottom'; } }); $('.trigger-submit').on('change', function() { @@ -286,42 +284,9 @@ gl.awardsHandler = new AwardsHandler(); checkInitialSidebarSize(); new Aside(); - if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') { - $.cookie('pin_nav', 'false', { - path: gon.relative_url_root || '/', - expires: 365 * 10 - }); - $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned'); - $('.navbar-fixed-top').removeClass('header-pinned-nav'); - } - $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) { - var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText; - e.preventDefault(); - $pinBtn = $(e.currentTarget); - $page = $('.page-with-sidebar'); - $topNav = $('.navbar-fixed-top'); - $tooltip = $("#" + ($pinBtn.attr('aria-describedby'))); - doPinNav = !$page.is('.page-sidebar-pinned'); - tooltipText = 'Pin navigation'; - $(this).toggleClass('is-active'); - if (doPinNav) { - $page.addClass('page-sidebar-pinned'); - $topNav.addClass('header-pinned-nav'); - } else { - $tooltip.remove(); - $page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded'); - $topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded'); - } - $.cookie('pin_nav', doPinNav, { - path: gon.relative_url_root || '/', - expires: 365 * 10 - }); - if ($.cookie('pin_nav') === 'true' || doPinNav) { - tooltipText = 'Unpin navigation'; - } - $tooltip.find('.tooltip-inner').text(tooltipText); - return $pinBtn.attr('title', tooltipText).tooltip('fixTitle'); - }); + + // bind sidebar events + new gl.Sidebar(); // Custom time ago gl.utils.shortTimeAgo($('.js-short-timeago')); diff --git a/app/assets/javascripts/sidebar.js b/app/assets/javascripts/sidebar.js deleted file mode 100644 index bd0c1194b36..00000000000 --- a/app/assets/javascripts/sidebar.js +++ /dev/null @@ -1,41 +0,0 @@ -(function() { - var collapsed, expanded, toggleSidebar; - - collapsed = 'page-sidebar-collapsed'; - - expanded = 'page-sidebar-expanded'; - - toggleSidebar = function() { - $('.page-with-sidebar').toggleClass(collapsed + " " + expanded); - $('.navbar-fixed-top').toggleClass("header-collapsed header-expanded"); - if ($.cookie('pin_nav') === 'true') { - $('.navbar-fixed-top').toggleClass('header-pinned-nav'); - $('.page-with-sidebar').toggleClass('page-sidebar-pinned'); - } - return setTimeout((function() { - var niceScrollBars; - niceScrollBars = $('.nav-sidebar').niceScroll(); - return niceScrollBars.updateScrollBar(); - }), 300); - }; - - $(document).off('click', 'body').on('click', 'body', function(e) { - var $nav, $target, $toggle, pageExpanded; - if ($.cookie('pin_nav') !== 'true') { - $target = $(e.target); - $nav = $target.closest('.sidebar-wrapper'); - pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded'); - $toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle'); - if ($nav.length === 0 && pageExpanded && $toggle.length === 0) { - $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded'); - return $('.navbar-fixed-top').toggleClass('header-collapsed header-expanded'); - } - } - }); - - $(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', function(e) { - e.preventDefault(); - return toggleSidebar(); - }); - -}).call(this); diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 new file mode 100644 index 00000000000..737f343ed0d --- /dev/null +++ b/app/assets/javascripts/sidebar.js.es6 @@ -0,0 +1,98 @@ +((global) => { + let singleton; + + const pinnedStateCookie = 'pin_nav'; + const sidebarBreakpoint = 1024; + + const pageSelector = '.page-with-sidebar'; + const navbarSelector = '.navbar-fixed-top'; + const sidebarWrapperSelector = '.sidebar-wrapper'; + const sidebarContentSelector = '.nav-sidebar'; + + const pinnedToggleSelector = '.js-nav-pin'; + const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle'; + + const pinnedPageClass = 'page-sidebar-pinned'; + const expandedPageClass = 'page-sidebar-expanded'; + const collapsedPageClass = 'page-sidebar-collapsed'; + + const pinnedNavbarClass = 'header-pinned-nav'; + const expandedNavbarClass = 'header-expanded'; + const collapsedNavbarClass = 'header-collapsed'; + + class Sidebar { + constructor() { + if (!singleton) { + singleton = this; + singleton.init(); + } else { + singleton.renderState(); + } + return singleton; + } + + init() { + this.isPinned = $.cookie(pinnedStateCookie) === 'true'; + this.isExpanded = ( + window.innerWidth >= sidebarBreakpoint && + $(pageSelector).hasClass(expandedPageClass) + ); + $(document) + .on('click', sidebarToggleSelector, () => this.toggleSidebar()) + .on('click', pinnedToggleSelector, () => this.togglePinnedState()) + .on('click', 'html, body', (e) => this.handleClickEvent(e)); + this.renderState(); + } + + handleClickEvent(e) { + if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) { + const $target = $(e.target); + const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0; + const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0; + if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) { + this.toggleSidebar(); + } + } + } + + toggleSidebar() { + this.isExpanded = !this.isExpanded; + this.renderState(); + } + + togglePinnedState() { + this.isPinned = !this.isPinned; + if (!this.isPinned) { + this.isExpanded = false; + } + $.cookie(pinnedStateCookie, this.isPinned ? 'true' : 'false', { + path: gon.relative_url_root || '/', + expires: 3650 + }); + this.renderState(); + } + + renderState() { + $(pageSelector) + .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded) + .toggleClass(expandedPageClass, this.isExpanded) + .toggleClass(collapsedPageClass, !this.isExpanded); + $(navbarSelector) + .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded) + .toggleClass(expandedNavbarClass, this.isExpanded) + .toggleClass(collapsedNavbarClass, !this.isExpanded); + + const $pinnedToggle = $(pinnedToggleSelector); + const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation'; + const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide'; + $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState); + + if (this.isExpanded) { + setTimeout(() => $(sidebarContentSelector).niceScroll().updateScrollBar(), 200); + } + } + } + + global.Sidebar = Sidebar; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 015fe3debf9..558a5fb5d25 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -128,10 +128,8 @@ .fa { transition: transform .15s; - } - &.is-active { - .fa { + .page-sidebar-pinned & { transform: rotate(90deg); } } -- cgit v1.2.3 From a510fe11026ddfd264b9cc4627541b6dac0c38c7 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 2 Sep 2016 10:34:04 -0500 Subject: remove superfluous "collapsed" class and rename header classes for clarity --- app/assets/javascripts/sidebar.js.es6 | 12 ++++-------- app/assets/stylesheets/framework/header.scss | 4 ---- app/assets/stylesheets/framework/sidebar.scss | 14 +++----------- app/assets/stylesheets/pages/builds.scss | 6 ------ app/helpers/nav_helper.rb | 6 +----- 5 files changed, 8 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 index 737f343ed0d..0cd6f946f61 100644 --- a/app/assets/javascripts/sidebar.js.es6 +++ b/app/assets/javascripts/sidebar.js.es6 @@ -14,11 +14,9 @@ const pinnedPageClass = 'page-sidebar-pinned'; const expandedPageClass = 'page-sidebar-expanded'; - const collapsedPageClass = 'page-sidebar-collapsed'; - const pinnedNavbarClass = 'header-pinned-nav'; - const expandedNavbarClass = 'header-expanded'; - const collapsedNavbarClass = 'header-collapsed'; + const pinnedNavbarClass = 'header-sidebar-pinned'; + const expandedNavbarClass = 'header-sidebar-expanded'; class Sidebar { constructor() { @@ -75,12 +73,10 @@ renderState() { $(pageSelector) .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded) - .toggleClass(expandedPageClass, this.isExpanded) - .toggleClass(collapsedPageClass, !this.isExpanded); + .toggleClass(expandedPageClass, this.isExpanded); $(navbarSelector) .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded) - .toggleClass(expandedNavbarClass, this.isExpanded) - .toggleClass(collapsedNavbarClass, !this.isExpanded); + .toggleClass(expandedNavbarClass, this.isExpanded); const $pinnedToggle = $(pinnedToggleSelector); const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation'; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 1036219172e..d4a030f7f7a 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -77,10 +77,6 @@ header { } } - &.header-collapsed { - padding: 0 16px; - } - .side-nav-toggle { position: absolute; left: -10px; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 558a5fb5d25..3b7de4b57bb 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -1,6 +1,5 @@ .page-with-sidebar { - padding-top: $header-height; - padding-bottom: 25px; + padding: $header-height 0 25px; transition: padding $sidebar-transition-duration; &.page-sidebar-pinned { @@ -15,6 +14,7 @@ bottom: 0; left: 0; height: 100%; + width: 0; overflow: hidden; transition: width $sidebar-transition-duration; @include box-shadow(2px 0 16px 0 $black-transparent); @@ -150,14 +150,6 @@ } } -.page-sidebar-collapsed { - padding-left: 0; - - .sidebar-wrapper { - width: 0; - } -} - .page-sidebar-expanded { .sidebar-wrapper { width: $sidebar_width; @@ -173,7 +165,7 @@ } } -header.header-pinned-nav { +header.header-sidebar-pinned { @media (min-width: $sidebar-breakpoint) { padding-left: ($sidebar_width + $gl-padding); diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 614405aa5c1..c879074c7fe 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -48,12 +48,6 @@ margin-bottom: 10px; } } - - .page-sidebar-collapsed { - .scroll-controls { - left: 70px; - } - } } .build-header { diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 2b0ff6c0d00..99e39eb442a 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -14,8 +14,6 @@ module NavHelper def page_sidebar_class if pinned_nav? "page-sidebar-expanded page-sidebar-pinned" - else - "page-sidebar-collapsed" end end @@ -43,9 +41,7 @@ module NavHelper class_name << " with-horizontal-nav" if defined?(nav) && nav if pinned_nav? - class_name << " header-expanded header-pinned-nav" - else - class_name << " header-collapsed" + class_name << " header-sidebar-expanded header-sidebar-pinned" end class_name -- cgit v1.2.3 From 32c53d6d1b71885acbcab7829e197744a357c9fc Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 2 Sep 2016 10:57:22 -0500 Subject: remove dead code - 'collapsed_nav' cookie no longer used since a1fbdbb6 (see MR !4579) --- app/helpers/nav_helper.rb | 13 ------------- app/views/layouts/_page.html.haml | 2 +- spec/helpers/nav_helper_spec.rb | 25 ------------------------- 3 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 spec/helpers/nav_helper_spec.rb diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 99e39eb442a..df87fac132d 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -1,16 +1,4 @@ module NavHelper - def nav_menu_collapsed? - cookies[:collapsed_nav] == 'true' - end - - def nav_sidebar_class - if nav_menu_collapsed? - "sidebar-collapsed" - else - "sidebar-expanded" - end - end - def page_sidebar_class if pinned_nav? "page-sidebar-expanded page-sidebar-pinned" @@ -24,7 +12,6 @@ module NavHelper current_path?('merge_requests#builds') || current_path?('merge_requests#conflicts') || current_path?('merge_requests#pipelines') || - current_path?('issues#show') if cookies[:collapsed_gutter] == 'true' "page-gutter right-sidebar-collapsed" diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index bf50633af24..4f7839a881f 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,5 +1,5 @@ .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } - .sidebar-wrapper.nicescroll{ class: nav_sidebar_class } + .sidebar-wrapper.nicescroll .sidebar-action-buttons = link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do %span.sr-only Toggle navigation diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb deleted file mode 100644 index e4d18d8bfc6..00000000000 --- a/spec/helpers/nav_helper_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'spec_helper' - -# Specs in this file have access to a helper object that includes -# the NavHelper. For example: -# -# describe NavHelper do -# describe "string concat" do -# it "concats two strings with spaces" do -# expect(helper.concat_strings("this","that")).to eq("this that") -# end -# end -# end -describe NavHelper do - describe '#nav_menu_collapsed?' do - it 'returns true when the nav is collapsed in the cookie' do - helper.request.cookies[:collapsed_nav] = 'true' - expect(helper.nav_menu_collapsed?).to eq true - end - - it 'returns false when the nav is not collapsed in the cookie' do - helper.request.cookies[:collapsed_nav] = 'false' - expect(helper.nav_menu_collapsed?).to eq false - end - end -end -- cgit v1.2.3 From 9b8ee45c87588c16337d2b1c3cff1fc52aec1571 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 2 Sep 2016 20:23:35 -0500 Subject: sync sidebar DOM state on page:change to minimize split-second appearance of the sidebar in certain situations --- app/assets/javascripts/sidebar.js.es6 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 index 0cd6f946f61..755fac8107b 100644 --- a/app/assets/javascripts/sidebar.js.es6 +++ b/app/assets/javascripts/sidebar.js.es6 @@ -23,8 +23,6 @@ if (!singleton) { singleton = this; singleton.init(); - } else { - singleton.renderState(); } return singleton; } @@ -38,7 +36,8 @@ $(document) .on('click', sidebarToggleSelector, () => this.toggleSidebar()) .on('click', pinnedToggleSelector, () => this.togglePinnedState()) - .on('click', 'html, body', (e) => this.handleClickEvent(e)); + .on('click', 'html, body', (e) => this.handleClickEvent(e)) + .on('page:change', () => this.renderState()); this.renderState(); } -- cgit v1.2.3 From ee26014dde1bd2c5b065cc692b716b43a63dee70 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Wed, 7 Sep 2016 12:32:59 -0700 Subject: update rouge to 2.0.6 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ab573b4d31e..c421713f6a1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -584,7 +584,7 @@ GEM railties (>= 4.2.0, < 5.1) rinku (2.0.0) rotp (2.1.2) - rouge (2.0.5) + rouge (2.0.6) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) -- cgit v1.2.3 From ffd5bd61ab0bc5faf90f2d96a28afde142f69281 Mon Sep 17 00:00:00 2001 From: "http://jneen.net/" Date: Wed, 7 Sep 2016 12:47:24 -0700 Subject: add a CHANGELOG note --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 16b813492a8..ec58f4b7b22 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) + - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) -- cgit v1.2.3 From 4b98e812b29e8f9bc11d2eaa4ad0ddd474caa9a6 Mon Sep 17 00:00:00 2001 From: Olaf Tomalka Date: Tue, 6 Sep 2016 11:53:34 +0200 Subject: Optimized event pruning query to avoid two queries. --- CHANGELOG | 3 +-- app/workers/prune_old_events_worker.rb | 13 +++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1d42acfe363..7eb535923a6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,9 +3,8 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 - - Prune events older than 12 months. @ritave + - Prune events older than 12 months. (ritave) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - - Prune events older than 12 months. - Filter tags by name !6121 - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index d75083f9ab1..5883cafe1d1 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -2,7 +2,16 @@ class PruneOldEventsWorker include Sidekiq::Worker def perform - # Contribution calendar shows maximum 12 months of events - Event.delete(Event.unscoped.where('created_at < ?', (12.months + 1.day).ago).limit(10_000).pluck(:id)) + # Contribution calendar shows maximum 12 months of events. + # Double nested query is used because MySQL doesn't allow DELETE subqueries + # on the same table. + Event.unscoped.where( + '(id IN (SELECT id FROM (?) ids_to_remove))', + Event.unscoped.where( + 'created_at < ?', + (12.months + 1.day).ago). + select(:id). + limit(10_000)). + delete_all end end -- cgit v1.2.3 From d1fda19fee6d0a53dee4dfb790af9a68f315d199 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Wed, 7 Sep 2016 13:28:02 -0700 Subject: minor refactor --- app/assets/stylesheets/pages/awards.scss | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 447ba24b46b..70f7bb611ff 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -93,11 +93,8 @@ } .award-control { - margin-top: 3px; - margin-bottom: 3px; - margin-right: 5px; - padding-left: 5px; - padding-right: 5px; + margin: 3px 5px 3px 0; + padding: 0 5px; outline: 0; &:hover, -- cgit v1.2.3 From 23157a3c4ac43e7ec26e5e0e612a0baf41ff7178 Mon Sep 17 00:00:00 2001 From: tauriedavis Date: Wed, 7 Sep 2016 13:31:20 -0700 Subject: fix padding top/bottom --- app/assets/stylesheets/pages/awards.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss index 70f7bb611ff..9282e0ae03b 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/pages/awards.scss @@ -94,7 +94,7 @@ .award-control { margin: 3px 5px 3px 0; - padding: 0 5px; + padding: 6px 5px; outline: 0; &:hover, -- cgit v1.2.3 From 1ef8be768df68b42d238ca9368aea65982ad0659 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 7 Sep 2016 10:31:32 -0500 Subject: Style grouped builds dropdown --- app/assets/stylesheets/pages/pipelines.scss | 81 ++++++++++++++++++++-- .../projects/ci/builds/_build_pipeline.html.haml | 4 +- .../commit/_pipeline_grouped_status.html.haml | 29 ++++++-- 3 files changed, 99 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 2d66ab25da6..cc71b8eb045 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -318,9 +318,17 @@ .build-content { width: 130px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + + .ci-status-text { + width: 110px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + display: inline-block; + position: relative; + top: -1px; + } a { color: $layout-link-gray; @@ -331,13 +339,74 @@ text-decoration: underline; } } + } + + .dropdown-menu-toggle { + border: none; + width: auto; + padding: 0; + color: $layout-link-gray; + + .ci-status-text { + width: 80px; + } + } + + .grouped-pipeline-dropdown { + padding: 8px 0; + width: 200px; + left: auto; + right: -214px; + top: -9px; + + a:hover { + .ci-status-text { + text-decoration: none; + } + } + + .ci-status-text { + width: 145px; + } + + .arrow { + &:before, + &:after { + content: ''; + display: inline-block; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 18px; + } + + &:before { + left: -5px; + margin-top: -6px; + border-width: 7px 5px 7px 0; + border-right-color: $border-color; + } + &:after { + left: -4px; + margin-top: -9px; + border-width: 10px 7px 10px 0; + border-right-color: $white-light; + } + } + } + + .badge { + background-color: $gray-dark; + color: $layout-link-gray; + font-weight: normal; } } svg { - position: relative; - top: 2px; + vertical-align: middle; margin-right: 5px; } @@ -442,7 +511,7 @@ width: 21px; height: 25px; position: absolute; - top: -28.5px; + top: -29px; border-top: 2px solid $border-color; } diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 36fb0300aeb..5289cd672f5 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -5,11 +5,11 @@ - if is_playable = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do = render_status_with_link('build', 'play') - %span.ci-status-text= subject.name + .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do = render_status_with_link('build', subject.status) - %span.ci-status-text= subject.name + .ci-status-text= subject.name - else = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) diff --git a/app/views/projects/commit/_pipeline_grouped_status.html.haml b/app/views/projects/commit/_pipeline_grouped_status.html.haml index 7e02703f0a6..dc8efc83d48 100644 --- a/app/views/projects/commit/_pipeline_grouped_status.html.haml +++ b/app/views/projects/commit/_pipeline_grouped_status.html.haml @@ -3,10 +3,25 @@ .build-content - group_status = CommitStatus.where(id: subject).status = render_status_with_link('build', group_status) - %span.ci-status-text - = name - = subject.length - - // Access all other grouped statuses - //- subject.each do |status| - // = render "projects/#{status.to_partial_path}_pipeline", subject: status + .dropdown.inline + %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %span.ci-status-text + = name + %span.badge= subject.length + %ul.dropdown-menu.grouped-pipeline-dropdown + .arrow + - subject.each do |status| + -# = render "projects/#{status.to_partial_path}_pipeline", subject: status + - is_playable = status.playable? && can?(current_user, :update_build, @project) + %li + - if is_playable + = link_to play_namespace_project_build_path(status.project.namespace, status.project, status, return_to: request.original_url), method: :post, title: 'Play' do + = render_status_with_link('build', 'play') + .ci-status-text= status.name + - elsif can?(current_user, :read_build, @project) + = link_to namespace_project_build_path(status.project.namespace, status.project, status) do + = render_status_with_link('build', status.status) + .ci-status-text= status.name + - else + = render_status_with_link('build', status.status) + = ci_icon_for_status(status.status) -- cgit v1.2.3 From 258ca063f0834f30883eec57905a851ae99cfd9e Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Wed, 7 Sep 2016 20:30:44 -0500 Subject: Update CHANGELOG entries for 8.11.5. I've also removed some duplicates from 8.11.4. --- CHANGELOG | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 025fb3d6fed..4fe64836e00 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -104,13 +104,16 @@ v 8.12.0 (unreleased) - Use default clone protocol on "check out, review, and merge locally" help page URL - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) -v 8.11.5 (unreleased) - - Optimize branch lookups and force a repository reload for Repository#find_branch - - Fix member expiration date picker after update - - Make merge conflict file size limit 200 KB, to match the docs +v 8.11.6 (unreleased) + +v 8.11.5 + - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 + - Fix member expiration date picker after update. !6184 - Fix suggested colors options for new labels in the admin area. !6138 - Fix GitLab import button - - Remove gitorious from import_sources + - Fix confidential issues being exposed as public using gitlab.com export + - Remove gitorious from import_sources. !6180 + - Scope webhooks/services that will run for confidential issues v 8.11.4 - Fix resolving conflicts on forks. !6082 @@ -124,10 +127,6 @@ v 8.11.4 - Creating an issue through our API now emails label subscribers !5720 - Block concurrent updates for Pipeline - Don't create groups for unallowed users when importing projects - - Fix resolving conflicts on forks - - Fix diff commenting on merge requests created prior to 8.10 - - Don't create groups for unallowed users when importing projects - - Scope webhooks/services that will run for confidential issues - Fix issue boards leak private label names and descriptions - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Remove gitorious. !5866 -- cgit v1.2.3 From 9a9e22a88cb670e5f3688a2ff8499e0e7bbe840e Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Thu, 8 Sep 2016 10:44:58 +1000 Subject: Rename `gl_user_id` to `gitlab_user_id` in importer classes --- lib/gitlab/bitbucket_import/importer.rb | 4 ++-- lib/gitlab/github_import/base_formatter.rb | 2 +- lib/gitlab/github_import/comment_formatter.rb | 2 +- lib/gitlab/github_import/issue_formatter.rb | 4 ++-- lib/gitlab/github_import/pull_request_formatter.rb | 4 ++-- lib/gitlab/gitlab_import/importer.rb | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 7beaecd1cf0..f4b5097adb1 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -21,7 +21,7 @@ module Gitlab private - def gl_user_id(project, bitbucket_id) + def gitlab_user_id(project, bitbucket_id) if bitbucket_id user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s) (user && user.id) || project.creator_id @@ -74,7 +74,7 @@ module Gitlab description: body, title: issue["title"], state: %w(resolved invalid duplicate wontfix closed).include?(issue["status"]) ? 'closed' : 'opened', - author_id: gl_user_id(project, reporter) + author_id: gitlab_user_id(project, reporter) ) end rescue ActiveRecord::RecordInvalid => e diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb index 72992baffd4..d546e102c63 100644 --- a/lib/gitlab/github_import/base_formatter.rb +++ b/lib/gitlab/github_import/base_formatter.rb @@ -15,7 +15,7 @@ module Gitlab private - def gl_user_id(github_id) + def gitlab_user_id(github_id) User.joins(:identities). find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s). try(:id) diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb index 2c1b94ef2cd..1c7c1a73c77 100644 --- a/lib/gitlab/github_import/comment_formatter.rb +++ b/lib/gitlab/github_import/comment_formatter.rb @@ -21,7 +21,7 @@ module Gitlab end def author_id - gl_user_id(raw_data.user.id) || project.creator_id + gitlab_user_id(raw_data.user.id) || project.creator_id end def body diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb index 07edbe37a13..ad4f1d8ae99 100644 --- a/lib/gitlab/github_import/issue_formatter.rb +++ b/lib/gitlab/github_import/issue_formatter.rb @@ -40,7 +40,7 @@ module Gitlab def assignee_id if assigned? - gl_user_id(raw_data.assignee.id) + gitlab_user_id(raw_data.assignee.id) end end @@ -49,7 +49,7 @@ module Gitlab end def author_id - gl_user_id(raw_data.user.id) || project.creator_id + gitlab_user_id(raw_data.user.id) || project.creator_id end def body diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index d9d436d7490..87e031b27f8 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -68,7 +68,7 @@ module Gitlab def assignee_id if assigned? - gl_user_id(raw_data.assignee.id) + gitlab_user_id(raw_data.assignee.id) end end @@ -77,7 +77,7 @@ module Gitlab end def author_id - gl_user_id(raw_data.user.id) || project.creator_id + gitlab_user_id(raw_data.user.id) || project.creator_id end def body diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb index 46d40f75be6..0ce7d854e62 100644 --- a/lib/gitlab/gitlab_import/importer.rb +++ b/lib/gitlab/gitlab_import/importer.rb @@ -41,7 +41,7 @@ module Gitlab title: issue["title"], state: issue["state"], updated_at: issue["updated_at"], - author_id: gl_user_id(project, issue["author"]["id"]) + author_id: gitlab_user_id(project, issue["author"]["id"]) ) end end @@ -51,7 +51,7 @@ module Gitlab private - def gl_user_id(project, gitlab_id) + def gitlab_user_id(project, gitlab_id) user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'gitlab'", gitlab_id.to_s) (user && user.id) || project.creator_id end -- cgit v1.2.3 From b7392549c4ad9e221c64cc17aef139470f7fb150 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 8 Sep 2016 00:05:12 -0400 Subject: Fix DB schema to match latest migration --- db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index c9023a02c77..5c283141084 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: 20160901141443) do +ActiveRecord::Schema.define(version: 20160902122721) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.3 From de6a44fae6273c44ef27ff55d13799efc9756acd Mon Sep 17 00:00:00 2001 From: Pascal Betz Date: Wed, 7 Sep 2016 21:59:29 +0200 Subject: feedback code review --- CHANGELOG | 1 + app/helpers/sidekiq_helper.rb | 2 +- app/views/admin/background_jobs/show.html.haml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d06b782f850..21d3cc1435f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.12.0 (unreleased) - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) + - Move parsing of sidekiq ps into helper !6245 (pascalbetz) - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb index 3644039d38e..82ab26408df 100644 --- a/app/helpers/sidekiq_helper.rb +++ b/app/helpers/sidekiq_helper.rb @@ -6,7 +6,7 @@ module SidekiqHelper if match match[1..6] else - %w{? ? ? ? ? ?} + %w[? ? ? ? ? ?] end end end diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index c2a312b2196..058919635da 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -29,7 +29,7 @@ %tbody - @sidekiq_processes.each do |process| %td= gitlab_config.user - -parse_sidekiq_ps(process).each do |value| + - parse_sidekiq_ps(process).each do |value| %td= value .clearfix %p -- cgit v1.2.3 From f28ca293b78405d798f1df78a39d549157a57c07 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 6 Sep 2016 15:49:49 +0300 Subject: Add bulk update support for merge requests list Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/dispatcher.js | 5 +- app/assets/javascripts/issuable.js.es6 | 2 +- app/assets/javascripts/issues-bulk-assignment.js | 4 +- app/assets/stylesheets/pages/issuable.scss | 15 ++ app/assets/stylesheets/pages/issues.scss | 11 - app/controllers/concerns/issuable_actions.rb | 31 +++ app/controllers/projects/issues_controller.rb | 26 -- app/services/issuable/bulk_update_service.rb | 26 ++ app/services/issues/bulk_update_service.rb | 25 -- app/views/projects/issues/_issue.html.haml | 4 +- app/views/projects/issues/index.html.haml | 4 +- .../merge_requests/_merge_request.html.haml | 4 + .../merge_requests/_merge_requests.html.haml | 2 +- app/views/projects/merge_requests/index.html.haml | 2 + app/views/shared/issuable/_filter.html.haml | 12 +- spec/services/issuable/bulk_update_service_spec.rb | 282 +++++++++++++++++++++ spec/services/issues/bulk_update_service_spec.rb | 282 --------------------- 17 files changed, 377 insertions(+), 360 deletions(-) create mode 100644 app/services/issuable/bulk_update_service.rb delete mode 100644 app/services/issues/bulk_update_service.rb create mode 100644 spec/services/issuable/bulk_update_service_spec.rb delete mode 100644 spec/services/issues/bulk_update_service_spec.rb diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 38cdc7b9fba..179d3bc38a5 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -23,6 +23,7 @@ case 'projects:boards:show': shortcut_handler = new ShortcutsNavigation(); break; + case 'projects:merge_requests:index': case 'projects:issues:index': Issuable.init(); new IssuableBulkActions(); @@ -93,10 +94,6 @@ break; case "projects:merge_requests:conflicts": window.mcui = new MergeConflictResolver() - case 'projects:merge_requests:index': - shortcut_handler = new ShortcutsNavigation(); - Issuable.init(); - break; case 'dashboard:activity': new Activities(); break; diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6 index 4006ac740b2..82c14ef0157 100644 --- a/app/assets/javascripts/issuable.js.es6 +++ b/app/assets/javascripts/issuable.js.es6 @@ -77,7 +77,7 @@ }, checkChanged: function() { const $checkedIssues = $('.selected_issue:checked'); - const $updateIssuesIds = $('#update_issues_ids'); + const $updateIssuesIds = $('#update_issuable_ids'); const $issuesOtherFilters = $('.issues-other-filters'); const $issuesBulkUpdate = $('.issues_bulk_update'); diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js index 98d3358ba92..8ca90490426 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js +++ b/app/assets/javascripts/issues-bulk-assignment.js @@ -5,7 +5,7 @@ if (opts == null) { opts = {}; } - this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issues-list .issue'); + this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); this.form.data('bulkActions', this); this.willUpdateLabels = false; this.bindEvents(); @@ -106,7 +106,7 @@ state_event: this.form.find('input[name="update[state_event]"]').val(), assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), - issues_ids: this.form.find('input[name="update[issues_ids]"]').val(), + issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), add_label_ids: [], remove_label_ids: [] diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 46c4a11aa2e..02d6d2082f9 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -404,3 +404,18 @@ margin-bottom: $gl-padding; } } + +.issuable-list { + li { + .issue-check { + float: left; + padding-right: 16px; + margin-bottom: 10px; + min-width: 15px; + + .selected_issue { + vertical-align: text-top; + } + } + } +} diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index d14224ed00f..7a26b7ad497 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -7,17 +7,6 @@ margin-bottom: 2px; } - .issue-check { - float: left; - padding-right: 16px; - margin-bottom: 10px; - min-width: 15px; - - .selected_issue { - vertical-align: text-top; - } - } - .issue-labels { display: inline-block; } diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index f40b62446e5..76f7f179008 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -3,6 +3,7 @@ module IssuableActions included do before_action :authorize_destroy_issuable!, only: :destroy + before_action :authorize_admin_issuable!, only: :bulk_update end def destroy @@ -13,6 +14,13 @@ module IssuableActions redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]) end + def bulk_update + result = Issuable::BulkUpdateService.new(project, current_user, bulk_update_params).execute(resource_name) + quantity = result[:count] + + render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" } + end + private def authorize_destroy_issuable! @@ -20,4 +28,27 @@ module IssuableActions return access_denied! end end + + def authorize_admin_issuable! + unless current_user.can?(:"admin_#{resource_name}", @project) + return access_denied! + end + end + + def bulk_update_params + params.require(:update).permit( + :issuable_ids, + :assignee_id, + :milestone_id, + :state_event, + :subscription_event, + label_ids: [], + add_label_ids: [], + remove_label_ids: [] + ) + end + + def resource_name + @resource_name ||= controller_name.singularize + end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 72d2d361878..de02e28e384 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -20,9 +20,6 @@ class Projects::IssuesController < Projects::ApplicationController # Allow modify issue before_action :authorize_update_issue!, only: [:edit, :update] - # Allow issues bulk update - before_action :authorize_admin_issues!, only: [:bulk_update] - respond_to :html def index @@ -168,16 +165,6 @@ class Projects::IssuesController < Projects::ApplicationController end end - def bulk_update - result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute - - respond_to do |format| - format.json do - render json: { notice: "#{result[:count]} issues updated" } - end - end - end - protected def issue @@ -237,17 +224,4 @@ class Projects::IssuesController < Projects::ApplicationController :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [] ) end - - def bulk_update_params - params.require(:update).permit( - :issues_ids, - :assignee_id, - :milestone_id, - :state_event, - :subscription_event, - label_ids: [], - add_label_ids: [], - remove_label_ids: [] - ) - end end diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb new file mode 100644 index 00000000000..60891cbb255 --- /dev/null +++ b/app/services/issuable/bulk_update_service.rb @@ -0,0 +1,26 @@ +module Issuable + class BulkUpdateService < IssuableBaseService + def execute(type) + model_class = type.classify.constantize + update_class = type.classify.pluralize.constantize::UpdateService + + ids = params.delete(:issuable_ids).split(",") + items = model_class.where(id: ids) + + %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key| + params.delete(key) unless params[key].present? + end + + items.each do |issuable| + next unless can?(current_user, :"update_#{type}", issuable) + + update_class.new(issuable.project, current_user, params).execute(issuable) + end + + { + count: items.count, + success: !items.count.zero? + } + end + end +end diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb deleted file mode 100644 index 7e19a73f71a..00000000000 --- a/app/services/issues/bulk_update_service.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Issues - class BulkUpdateService < BaseService - def execute - issues_ids = params.delete(:issues_ids).split(",") - issue_params = params - - %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key| - issue_params.delete(key) unless issue_params[key].present? - end - - issues = Issue.where(id: issues_ids) - - issues.each do |issue| - next unless can?(current_user, :update_issue, issue) - - Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue) - end - - { - count: issues.count, - success: !issues.count.zero? - } - end - end -end diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 79b14819865..851d4c06990 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,7 +1,7 @@ %li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id } } - - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) + - if @bulk_edit .issue-check - = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue" + = check_box_tag dom_id(issue, "selected"), nil, false, 'data-id' => issue.id, class: "selected_issue" .issue-title.title %span.issue-title-text diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 1a87045aa60..023ea5f17d7 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,4 +1,6 @@ - @no_container = true +- @bulk_edit = can?(current_user, :admin_issue, @project) + - page_title "Issues" - new_issue_email = @project.new_issue_address(current_user) = render "projects/issues/head" @@ -29,7 +31,7 @@ New Issue = render 'shared/issuable/filter', type: :issues - .issues-holder + .issues-holder.issuable-list = render 'issues' - if new_issue_email = render 'issue_by_email', email: new_issue_email diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 5029b365f93..31f8d0aeb5b 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -1,4 +1,8 @@ %li{ class: mr_css_classes(merge_request) } + - if @bulk_edit + .issue-check + = check_box_tag dom_id(merge_request, "selected"), nil, false, 'data-id' => merge_request.id, class: "selected_issue" + .merge-request-title.title %span.merge-request-title-text = link_to merge_request.title, merge_request_path(merge_request) diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml index 446887774a4..fe82f751f53 100644 --- a/app/views/projects/merge_requests/_merge_requests.html.haml +++ b/app/views/projects/merge_requests/_merge_requests.html.haml @@ -1,4 +1,4 @@ -%ul.content-list.mr-list +%ul.content-list.mr-list.issuable-list = render @merge_requests - if @merge_requests.blank? %li diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index ace275c689b..144b3a9c8c8 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,4 +1,6 @@ - @no_container = true +- @bulk_edit = can?(current_user, :admin_merge_request, @project) + - page_title "Merge Requests" = render "projects/issues/head" = render 'projects/last_push' diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index fabf6d74392..9b5d1236e1d 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -1,9 +1,11 @@ +- boards_page = controller.controller_name == 'boards' + .issues-filters .issues-details-filters.row-content-block.second-block = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do - if params[:issue_search].present? = hidden_field_tag :issue_search, params[:issue_search] - - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) + - if @bulk_edit .check-all-holder = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left" @@ -30,7 +32,7 @@ %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search])} Reset filters .pull-right - - if controller.controller_name == 'boards' + - if boards_page #js-boards-seach.issue-boards-search %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" } - if can?(current_user, :admin_list, @project) @@ -45,7 +47,7 @@ - else = render 'shared/sort_dropdown' - - if controller.controller_name == 'issues' + - if @bulk_edit .issues_bulk_update.hide = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post, class: 'bulk-update' do .filter-item.inline @@ -70,10 +72,10 @@ %li %a{href: "#", data: {id: "unsubscribe"}} Unsubscribe - = hidden_field_tag 'update[issues_ids]', [] + = hidden_field_tag 'update[issuable_ids]', [] = hidden_field_tag :state_event, params[:state_event] .filter-item.inline - = button_tag "Update issues", class: "btn update_selected_issues btn-save" + = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save" - if !@labels.nil? .row-content-block.second-block.filtered-labels{ class: ("hidden" if !@labels.any?) } diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb new file mode 100644 index 00000000000..6f7ce8ca992 --- /dev/null +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -0,0 +1,282 @@ +require 'spec_helper' + +describe Issuable::BulkUpdateService, services: true do + let(:user) { create(:user) } + let(:project) { create(:empty_project, namespace: user.namespace) } + + def bulk_update(issues, extra_params = {}) + bulk_update_params = extra_params + .reverse_merge(issuable_ids: Array(issues).map(&:id).join(',')) + + Issuable::BulkUpdateService.new(project, user, bulk_update_params).execute('issue') + end + + describe 'close issues' do + let(:issues) { create_list(:issue, 2, project: project) } + + it 'succeeds and returns the correct number of issues updated' do + result = bulk_update(issues, state_event: 'close') + + expect(result[:success]).to be_truthy + expect(result[:count]).to eq(issues.count) + end + + it 'closes all the issues passed' do + bulk_update(issues, state_event: 'close') + + expect(project.issues.opened).to be_empty + expect(project.issues.closed).not_to be_empty + end + end + + describe 'reopen issues' do + let(:issues) { create_list(:closed_issue, 2, project: project) } + + it 'succeeds and returns the correct number of issues updated' do + result = bulk_update(issues, state_event: 'reopen') + + expect(result[:success]).to be_truthy + expect(result[:count]).to eq(issues.count) + end + + it 'reopens all the issues passed' do + bulk_update(issues, state_event: 'reopen') + + expect(project.issues.closed).to be_empty + expect(project.issues.opened).not_to be_empty + end + end + + describe 'updating assignee' do + let(:issue) { create(:issue, project: project, assignee: user) } + + context 'when the new assignee ID is a valid user' do + it 'succeeds' do + result = bulk_update(issue, assignee_id: create(:user).id) + + expect(result[:success]).to be_truthy + expect(result[:count]).to eq(1) + end + + it 'updates the assignee to the use ID passed' do + assignee = create(:user) + + expect { bulk_update(issue, assignee_id: assignee.id) } + .to change { issue.reload.assignee }.from(user).to(assignee) + end + end + + context 'when the new assignee ID is -1' do + it 'unassigns the issues' do + expect { bulk_update(issue, assignee_id: -1) } + .to change { issue.reload.assignee }.to(nil) + end + end + + context 'when the new assignee ID is not present' do + it 'does not unassign' do + expect { bulk_update(issue, assignee_id: nil) } + .not_to change { issue.reload.assignee } + end + end + end + + describe 'updating milestones' do + let(:issue) { create(:issue, project: project) } + let(:milestone) { create(:milestone, project: project) } + + it 'succeeds' do + result = bulk_update(issue, milestone_id: milestone.id) + + expect(result[:success]).to be_truthy + expect(result[:count]).to eq(1) + end + + it 'updates the issue milestone' do + expect { bulk_update(issue, milestone_id: milestone.id) } + .to change { issue.reload.milestone }.from(nil).to(milestone) + end + end + + describe 'updating labels' do + def create_issue_with_labels(labels) + create(:labeled_issue, project: project, labels: labels) + end + + let(:bug) { create(:label, project: project) } + let(:regression) { create(:label, project: project) } + let(:merge_requests) { create(:label, project: project) } + + let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) } + let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) } + let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) } + let(:issue_no_labels) { create(:issue, project: project) } + let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] } + + let(:labels) { [] } + let(:add_labels) { [] } + let(:remove_labels) { [] } + + let(:bulk_update_params) do + { + label_ids: labels.map(&:id), + add_label_ids: add_labels.map(&:id), + remove_label_ids: remove_labels.map(&:id), + } + end + + before do + bulk_update(issues, bulk_update_params) + end + + context 'when label_ids are passed' do + let(:issues) { [issue_all_labels, issue_no_labels] } + let(:labels) { [bug, regression] } + + it 'updates the labels of all issues passed to the labels passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(eq(labels.map(&:id))) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + + context 'when those label IDs are empty' do + let(:labels) { [] } + + it 'updates the issues passed to have no labels' do + expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) + end + end + end + + context 'when add_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:add_labels) { [bug, regression, merge_requests] } + + it 'adds those label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id))) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when remove_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:remove_labels) { [bug, regression, merge_requests] } + + it 'removes those label IDs from all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when add_label_ids and remove_label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } + let(:add_labels) { [bug] } + let(:remove_labels) { [merge_requests] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + + context 'when add_label_ids and label_ids are passed' do + let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] } + let(:labels) { [merge_requests] } + let(:add_labels) { [regression] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id)) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'does not update issues not passed in' do + expect(issue_no_labels.label_ids).to be_empty + end + end + + context 'when remove_label_ids and label_ids are passed' do + let(:issues) { [issue_no_labels, issue_bug_and_regression] } + let(:labels) { [merge_requests] } + let(:remove_labels) { [regression] } + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) + end + + it 'does not update issues not passed in' do + expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id) + end + end + + context 'when add_label_ids, remove_label_ids, and label_ids are passed' do + let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] } + let(:labels) { [regression] } + let(:add_labels) { [bug] } + let(:remove_labels) { [merge_requests] } + + it 'adds the label IDs to all issues passed' do + expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) + end + + it 'removes the label IDs from all issues passed' do + expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) + end + + it 'ignores the label IDs parameter' do + expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id) + end + + it 'does not update issues not passed in' do + expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) + end + end + end + + describe 'subscribe to issues' do + let(:issues) { create_list(:issue, 2, project: project) } + + it 'subscribes the given user' do + bulk_update(issues, subscription_event: 'subscribe') + + expect(issues).to all(be_subscribed(user)) + end + end + + describe 'unsubscribe from issues' do + let(:issues) do + create_list(:closed_issue, 2, project: project) do |issue| + issue.subscriptions.create(user: user, subscribed: true) + end + end + + it 'unsubscribes the given user' do + bulk_update(issues, subscription_event: 'unsubscribe') + + issues.each do |issue| + expect(issue).not_to be_subscribed(user) + end + end + end +end diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb deleted file mode 100644 index ac08aa53b0b..00000000000 --- a/spec/services/issues/bulk_update_service_spec.rb +++ /dev/null @@ -1,282 +0,0 @@ -require 'spec_helper' - -describe Issues::BulkUpdateService, services: true do - let(:user) { create(:user) } - let(:project) { create(:empty_project, namespace: user.namespace) } - - def bulk_update(issues, extra_params = {}) - bulk_update_params = extra_params - .reverse_merge(issues_ids: Array(issues).map(&:id).join(',')) - - Issues::BulkUpdateService.new(project, user, bulk_update_params).execute - end - - describe 'close issues' do - let(:issues) { create_list(:issue, 2, project: project) } - - it 'succeeds and returns the correct number of issues updated' do - result = bulk_update(issues, state_event: 'close') - - expect(result[:success]).to be_truthy - expect(result[:count]).to eq(issues.count) - end - - it 'closes all the issues passed' do - bulk_update(issues, state_event: 'close') - - expect(project.issues.opened).to be_empty - expect(project.issues.closed).not_to be_empty - end - end - - describe 'reopen issues' do - let(:issues) { create_list(:closed_issue, 2, project: project) } - - it 'succeeds and returns the correct number of issues updated' do - result = bulk_update(issues, state_event: 'reopen') - - expect(result[:success]).to be_truthy - expect(result[:count]).to eq(issues.count) - end - - it 'reopens all the issues passed' do - bulk_update(issues, state_event: 'reopen') - - expect(project.issues.closed).to be_empty - expect(project.issues.opened).not_to be_empty - end - end - - describe 'updating assignee' do - let(:issue) { create(:issue, project: project, assignee: user) } - - context 'when the new assignee ID is a valid user' do - it 'succeeds' do - result = bulk_update(issue, assignee_id: create(:user).id) - - expect(result[:success]).to be_truthy - expect(result[:count]).to eq(1) - end - - it 'updates the assignee to the use ID passed' do - assignee = create(:user) - - expect { bulk_update(issue, assignee_id: assignee.id) } - .to change { issue.reload.assignee }.from(user).to(assignee) - end - end - - context 'when the new assignee ID is -1' do - it 'unassigns the issues' do - expect { bulk_update(issue, assignee_id: -1) } - .to change { issue.reload.assignee }.to(nil) - end - end - - context 'when the new assignee ID is not present' do - it 'does not unassign' do - expect { bulk_update(issue, assignee_id: nil) } - .not_to change { issue.reload.assignee } - end - end - end - - describe 'updating milestones' do - let(:issue) { create(:issue, project: project) } - let(:milestone) { create(:milestone, project: project) } - - it 'succeeds' do - result = bulk_update(issue, milestone_id: milestone.id) - - expect(result[:success]).to be_truthy - expect(result[:count]).to eq(1) - end - - it 'updates the issue milestone' do - expect { bulk_update(issue, milestone_id: milestone.id) } - .to change { issue.reload.milestone }.from(nil).to(milestone) - end - end - - describe 'updating labels' do - def create_issue_with_labels(labels) - create(:labeled_issue, project: project, labels: labels) - end - - let(:bug) { create(:label, project: project) } - let(:regression) { create(:label, project: project) } - let(:merge_requests) { create(:label, project: project) } - - let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) } - let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) } - let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) } - let(:issue_no_labels) { create(:issue, project: project) } - let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] } - - let(:labels) { [] } - let(:add_labels) { [] } - let(:remove_labels) { [] } - - let(:bulk_update_params) do - { - label_ids: labels.map(&:id), - add_label_ids: add_labels.map(&:id), - remove_label_ids: remove_labels.map(&:id), - } - end - - before do - bulk_update(issues, bulk_update_params) - end - - context 'when label_ids are passed' do - let(:issues) { [issue_all_labels, issue_no_labels] } - let(:labels) { [bug, regression] } - - it 'updates the labels of all issues passed to the labels passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(eq(labels.map(&:id))) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - - context 'when those label IDs are empty' do - let(:labels) { [] } - - it 'updates the issues passed to have no labels' do - expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) - end - end - end - - context 'when add_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:add_labels) { [bug, regression, merge_requests] } - - it 'adds those label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id))) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when remove_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:remove_labels) { [bug, regression, merge_requests] } - - it 'removes those label IDs from all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when add_label_ids and remove_label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] } - let(:add_labels) { [bug] } - let(:remove_labels) { [merge_requests] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - - context 'when add_label_ids and label_ids are passed' do - let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] } - let(:labels) { [merge_requests] } - let(:add_labels) { [regression] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id)) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'does not update issues not passed in' do - expect(issue_no_labels.label_ids).to be_empty - end - end - - context 'when remove_label_ids and label_ids are passed' do - let(:issues) { [issue_no_labels, issue_bug_and_regression] } - let(:labels) { [merge_requests] } - let(:remove_labels) { [regression] } - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) - end - - it 'does not update issues not passed in' do - expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id) - end - end - - context 'when add_label_ids, remove_label_ids, and label_ids are passed' do - let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] } - let(:labels) { [regression] } - let(:add_labels) { [bug] } - let(:remove_labels) { [merge_requests] } - - it 'adds the label IDs to all issues passed' do - expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id)) - end - - it 'removes the label IDs from all issues passed' do - expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id) - end - - it 'ignores the label IDs parameter' do - expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id) - end - - it 'does not update issues not passed in' do - expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id) - end - end - end - - describe 'subscribe to issues' do - let(:issues) { create_list(:issue, 2, project: project) } - - it 'subscribes the given user' do - bulk_update(issues, subscription_event: 'subscribe') - - expect(issues).to all(be_subscribed(user)) - end - end - - describe 'unsubscribe from issues' do - let(:issues) do - create_list(:closed_issue, 2, project: project) do |issue| - issue.subscriptions.create(user: user, subscribed: true) - end - end - - it 'unsubscribes the given user' do - bulk_update(issues, subscription_event: 'unsubscribe') - - issues.each do |issue| - expect(issue).not_to be_subscribed(user) - end - end - end -end -- cgit v1.2.3 From af9dc9736eadb3ab31c97219fb73a5e9136b0ad0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 7 Sep 2016 13:54:39 +0300 Subject: Make merge requests bulk update working Signed-off-by: Dmitriy Zaporozhets --- app/helpers/issuables_helper.rb | 8 ++ app/views/shared/issuable/_filter.html.haml | 2 +- config/routes.rb | 1 + .../merge_requests/update_merge_requests_spec.rb | 111 +++++++++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 spec/features/merge_requests/update_merge_requests_spec.rb diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 5c04bba323f..70ff7f9c1b2 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -111,4 +111,12 @@ module IssuablesHelper issuable.open? ? :opened : :closed end end + + def issuable_bulk_update_path(type) + if type == "issue" + bulk_update_namespace_project_issues_path(@project.namespace, @project) + else + bulk_update_namespace_project_merge_requests_path(@project.namespace, @project) + end + end end diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 9b5d1236e1d..45b8ec72fa4 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -49,7 +49,7 @@ - if @bulk_edit .issues_bulk_update.hide - = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post, class: 'bulk-update' do + = form_tag issuable_bulk_update_path(type), method: :post, class: 'bulk-update' do .filter-item.inline = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do %ul diff --git a/config/routes.rb b/config/routes.rb index 262a174437a..068c92d1400 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -747,6 +747,7 @@ Rails.application.routes.draw do get :branch_to get :update_branches get :diff_for_path + post :bulk_update end resources :discussions, only: [], constraints: { id: /\h{40}/ } do diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb new file mode 100644 index 00000000000..8b2636d07db --- /dev/null +++ b/spec/features/merge_requests/update_merge_requests_spec.rb @@ -0,0 +1,111 @@ +require 'rails_helper' + +feature 'Multiple merge requests updating from merge_requests#index', feature: true do + include WaitForAjax + + let!(:user) { create(:user)} + let!(:project) { create(:project) } + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + + before do + project.team << [user, :master] + login_as(user) + end + + context 'status', js: true do + it 'sets to closed' do + visit namespace_project_merge_requests_path(project.namespace, project) + + find('#check_all_issues').click + find('.js-issue-status').click + + find('.dropdown-menu-status a', text: 'Closed').click + click_update_merge_requests_button + expect(page).to have_selector('.merge-request', count: 0) + end + + it 'sets to open' do + merge_request.close + visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed') + + find('#check_all_issues').click + find('.js-issue-status').click + + find('.dropdown-menu-status a', text: 'Open').click + click_update_merge_requests_button + expect(page).to have_selector('.merge-request', count: 0) + end + end + + context 'assignee', js: true do + it 'updates to current user' do + visit namespace_project_merge_requests_path(project.namespace, project) + + find('#check_all_issues').click + click_update_assignee_button + + find('.dropdown-menu-user-link', text: user.username).click + click_update_merge_requests_button + + page.within('.merge-request .controls') do + expect(find('.author_link')["title"]).to have_content(user.name) + end + end + + it 'updates to unassigned' do + merge_request.assignee = user + merge_request.save + visit namespace_project_merge_requests_path(project.namespace, project) + + find('#check_all_issues').click + click_update_assignee_button + + click_link 'Unassigned' + click_update_merge_requests_button + expect(find('.merge-request:first-child .controls')).not_to have_css('.author_link') + end + end + + context 'milestone', js: true do + let(:milestone) { create(:milestone, project: project) } + + it 'updates milestone' do + visit namespace_project_merge_requests_path(project.namespace, project) + + find('#check_all_issues').click + find('.issues_bulk_update .js-milestone-select').click + + find('.dropdown-menu-milestone a', text: milestone.title).click + click_update_merge_requests_button + + expect(find('.merge-request')).to have_content milestone.title + end + + it 'sets to no milestone' do + merge_request.milestone = milestone + merge_request.save + + visit namespace_project_merge_requests_path(project.namespace, project) + + expect(first('.merge-request')).to have_content milestone.title + + find('#check_all_issues').click + find('.issues_bulk_update .js-milestone-select').click + + find('.dropdown-menu-milestone a', text: "No Milestone").click + click_update_merge_requests_button + + expect(find('.merge-request:first-child')).not_to have_content milestone.title + end + end + + def click_update_assignee_button + find('.js-update-assignee').click + wait_for_ajax + end + + def click_update_merge_requests_button + find('.update_selected_issues').click + wait_for_ajax + end +end -- cgit v1.2.3 From 035a38f18ea7bec1afd64218866a2fd986449b00 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 7 Sep 2016 16:18:15 +0300 Subject: Fix issuable_bulk_update_path and merge request spinach test Signed-off-by: Dmitriy Zaporozhets --- app/helpers/issuables_helper.rb | 6 +++--- features/steps/project/merge_requests.rb | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 70ff7f9c1b2..3f5af880646 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -113,10 +113,10 @@ module IssuablesHelper end def issuable_bulk_update_path(type) - if type == "issue" - bulk_update_namespace_project_issues_path(@project.namespace, @project) - else + if type == :merge_requests bulk_update_namespace_project_merge_requests_path(@project.namespace, @project) + else + bulk_update_namespace_project_issues_path(@project.namespace, @project) end end end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 56b28949585..df17b5626c6 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -31,7 +31,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I click link "Closed"' do - click_link "Closed" + page.within('.issues-state-filters') do + click_link "Closed" + end end step 'I should see merge request "Wiki Feature"' do -- cgit v1.2.3 From 274e6c491148f70a21f646fc1014da11db290460 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 7 Sep 2016 17:18:30 +0300 Subject: Add changelog item for merge requests bulk update Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 025fb3d6fed..d8c26564515 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -103,6 +103,7 @@ v 8.12.0 (unreleased) - Show values of CI trigger variables only when clicked (Katarzyna Kobierska Ula Budziszewska) - Use default clone protocol on "check out, review, and merge locally" help page URL - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) + - Allow bulk update merge requests from merge requests index page v 8.11.5 (unreleased) - Optimize branch lookups and force a repository reload for Repository#find_branch -- cgit v1.2.3 From 1a18eb59ab73670b143d0e688a9b715c75d1baa0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 7 Sep 2016 17:45:31 +0300 Subject: Fix issues bulk update from issues index page Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/issues/_issues.html.haml | 2 +- app/views/projects/issues/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index f34f3c05737..a2c31c0b4c5 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,4 +1,4 @@ -%ul.content-list.issues-list +%ul.content-list.issues-list.issuable-list = render @issues - if @issues.blank? %li diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 023ea5f17d7..8da9f2100e9 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -31,7 +31,7 @@ New Issue = render 'shared/issuable/filter', type: :issues - .issues-holder.issuable-list + .issues-holder = render 'issues' - if new_issue_email = render 'issue_by_email', email: new_issue_email -- cgit v1.2.3 From 7891aec0e59b00de5cd03a0915860ed40dbc9094 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 8 Sep 2016 11:46:04 +0300 Subject: Refactor merge requests bulk update spec Signed-off-by: Dmitriy Zaporozhets --- .../merge_requests/update_merge_requests_spec.rb | 112 +++++++++++---------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb index 8b2636d07db..43b31dce9b3 100644 --- a/spec/features/merge_requests/update_merge_requests_spec.rb +++ b/spec/features/merge_requests/update_merge_requests_spec.rb @@ -16,11 +16,7 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t it 'sets to closed' do visit namespace_project_merge_requests_path(project.namespace, project) - find('#check_all_issues').click - find('.js-issue-status').click - - find('.dropdown-menu-status a', text: 'Closed').click - click_update_merge_requests_button + change_status('Closed') expect(page).to have_selector('.merge-request', count: 0) end @@ -28,80 +24,92 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t merge_request.close visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed') - find('#check_all_issues').click - find('.js-issue-status').click - - find('.dropdown-menu-status a', text: 'Open').click - click_update_merge_requests_button + change_status('Open') expect(page).to have_selector('.merge-request', count: 0) end end context 'assignee', js: true do - it 'updates to current user' do - visit namespace_project_merge_requests_path(project.namespace, project) - - find('#check_all_issues').click - click_update_assignee_button + context 'set assignee' do + before do + visit namespace_project_merge_requests_path(project.namespace, project) + end - find('.dropdown-menu-user-link', text: user.username).click - click_update_merge_requests_button + it "should update merge request with assignee" do + change_assignee(user.name) - page.within('.merge-request .controls') do - expect(find('.author_link')["title"]).to have_content(user.name) + page.within('.merge-request .controls') do + expect(find('.author_link')["title"]).to have_content(user.name) + end end end - it 'updates to unassigned' do - merge_request.assignee = user - merge_request.save - visit namespace_project_merge_requests_path(project.namespace, project) - - find('#check_all_issues').click - click_update_assignee_button + context 'remove assignee' do + before do + merge_request.assignee = user + merge_request.save + visit namespace_project_merge_requests_path(project.namespace, project) + end - click_link 'Unassigned' - click_update_merge_requests_button - expect(find('.merge-request:first-child .controls')).not_to have_css('.author_link') + it "should remove assignee from the merge request" do + change_assignee('Unassigned') + expect(find('.merge-request .controls')).not_to have_css('.author_link') + end end end context 'milestone', js: true do let(:milestone) { create(:milestone, project: project) } - it 'updates milestone' do - visit namespace_project_merge_requests_path(project.namespace, project) - - find('#check_all_issues').click - find('.issues_bulk_update .js-milestone-select').click - - find('.dropdown-menu-milestone a', text: milestone.title).click - click_update_merge_requests_button + context 'set milestone' do + before do + visit namespace_project_merge_requests_path(project.namespace, project) + end - expect(find('.merge-request')).to have_content milestone.title + it "should update merge request with milestone" do + change_milestone(milestone.title) + expect(find('.merge-request')).to have_content milestone.title + end end - it 'sets to no milestone' do - merge_request.milestone = milestone - merge_request.save - - visit namespace_project_merge_requests_path(project.namespace, project) + context 'unset milestone' do + before do + merge_request.milestone = milestone + merge_request.save + visit namespace_project_merge_requests_path(project.namespace, project) + end - expect(first('.merge-request')).to have_content milestone.title + it "should remove milestone from the merge request" do + change_milestone("No Milestone") + expect(find('.merge-request')).not_to have_content milestone.title + end + end + end - find('#check_all_issues').click - find('.issues_bulk_update .js-milestone-select').click + def change_status(text) + find('#check_all_issues').click + find('.js-issue-status').click + find('.dropdown-menu-status a', text: text).click + click_update_merge_requests_button + end - find('.dropdown-menu-milestone a', text: "No Milestone").click - click_update_merge_requests_button + def change_assignee(text) + find('#check_all_issues').click + find('.js-update-assignee').click + wait_for_ajax - expect(find('.merge-request:first-child')).not_to have_content milestone.title + page.within '.dropdown-menu-user' do + click_link text end + + click_update_merge_requests_button end - def click_update_assignee_button - find('.js-update-assignee').click - wait_for_ajax + def change_milestone(text) + find('#check_all_issues').click + find('.issues_bulk_update .js-milestone-select').click + find('.dropdown-menu-milestone a', text: text).click + click_update_merge_requests_button end def click_update_merge_requests_button -- cgit v1.2.3 From 4a788f8a0c7a7b72d77afc3965f1656def2a0ac2 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Mon, 29 Aug 2016 19:48:36 +1000 Subject: Only add the original author if there isn't a linked GitLab account --- CHANGELOG | 1 + lib/gitlab/github_import/base_formatter.rb | 5 +++++ lib/gitlab/github_import/comment_formatter.rb | 8 ++++++-- lib/gitlab/github_import/issue_formatter.rb | 8 ++++++-- lib/gitlab/github_import/pull_request_formatter.rb | 8 ++++++-- spec/lib/gitlab/github_import/comment_formatter_spec.rb | 6 ++++++ spec/lib/gitlab/github_import/issue_formatter_spec.rb | 6 ++++++ spec/lib/gitlab/github_import/pull_request_formatter_spec.rb | 6 ++++++ 8 files changed, 42 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d8c57441b8b..0d26564fccf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -75,6 +75,7 @@ v 8.12.0 (unreleased) - Fix repo title alignment (ClemMakesApps) - Change update interval of contacted_at - Fix branch title trailing space on hover (ClemMakesApps) + - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) - Order award emoji tooltips in order they were added (EspadaV8) diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb index d546e102c63..8cacf4f4925 100644 --- a/lib/gitlab/github_import/base_formatter.rb +++ b/lib/gitlab/github_import/base_formatter.rb @@ -20,6 +20,11 @@ module Gitlab find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s). try(:id) end + + def gitlab_author_id + return @gitlab_author_id if defined?(@gitlab_author_id) + @gitlab_author_id = gitlab_user_id(raw_data.user.id) + end end end end diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb index 1c7c1a73c77..2bddcde2b7c 100644 --- a/lib/gitlab/github_import/comment_formatter.rb +++ b/lib/gitlab/github_import/comment_formatter.rb @@ -21,7 +21,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -52,7 +52,11 @@ module Gitlab end def note - formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def type diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb index ad4f1d8ae99..77621de9f4c 100644 --- a/lib/gitlab/github_import/issue_formatter.rb +++ b/lib/gitlab/github_import/issue_formatter.rb @@ -49,7 +49,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -57,7 +57,11 @@ module Gitlab end def description - @formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def milestone diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index 87e031b27f8..1408683100f 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -77,7 +77,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -85,7 +85,11 @@ module Gitlab end def description - formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def milestone diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb index 9ae02a6c45f..c520a9c53ad 100644 --- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb @@ -73,6 +73,12 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github') expect(comment.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns note without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(comment.attributes.fetch(:note)).to eq("I'm having a problem with this.") + end end end end diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb index d60c4111e99..c2f1f6b91a1 100644 --- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb @@ -109,6 +109,12 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do expect(issue.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns description without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(issue.attributes.fetch(:description)).to eq("I'm having a problem with this.") + end end end diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index edfc6ad81c6..302f0fc0623 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -140,6 +140,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns description without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(pull_request.attributes.fetch(:description)).to eq('Please pull these awesome changes') + end end context 'when it has a milestone' do -- cgit v1.2.3 From 9521edb4f44e0955990c4c534be022cae4ecd013 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 5 Sep 2016 16:37:26 +0100 Subject: Exclude some pending or inactivated rows in Member scopes An unapproved access request should not give access rights, and blocked users should not be considered members of anything. One visible outcome of this behaviour is that owners and masters of a group or project may be blocked, yet still receive notification emails for access requests. This commit prevents this from happening. --- app/models/member.rb | 33 +++++++++++++++++++++++++-------- spec/models/member_spec.rb | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/app/models/member.rb b/app/models/member.rb index 64e0d33fb20..69406379948 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -28,17 +28,34 @@ class Member < ActiveRecord::Base allow_nil: true } + # This scope encapsulates (most of) the conditions a row in the member table + # must satisfy if it is a valid permission. Of particular note: + # + # * Access requests must be excluded + # * Blocked users must be excluded + # * Invitations take effect immediately + # * expires_at is not implemented. A background worker purges expired rows + scope :active, -> do + is_external_invite = arel_table[:user_id].eq(nil).and(arel_table[:invite_token].not_eq(nil)) + user_is_active = User.arel_table[:state].eq(:active) + + includes(:user).references(:users) + .where(is_external_invite.or(user_is_active)) + .where(requested_at: nil) + end + scope :invite, -> { where.not(invite_token: nil) } scope :non_invite, -> { where(invite_token: nil) } scope :request, -> { where.not(requested_at: nil) } - scope :has_access, -> { where('access_level > 0') } - - scope :guests, -> { where(access_level: GUEST) } - scope :reporters, -> { where(access_level: REPORTER) } - scope :developers, -> { where(access_level: DEVELOPER) } - scope :masters, -> { where(access_level: MASTER) } - scope :owners, -> { where(access_level: OWNER) } - scope :owners_and_masters, -> { where(access_level: [OWNER, MASTER]) } + + scope :has_access, -> { active.where('access_level > 0') } + + scope :guests, -> { active.where(access_level: GUEST) } + scope :reporters, -> { active.where(access_level: REPORTER) } + scope :developers, -> { active.where(access_level: DEVELOPER) } + scope :masters, -> { active.where(access_level: MASTER) } + scope :owners, -> { active.where(access_level: OWNER) } + scope :owners_and_masters, -> { active.where(access_level: [OWNER, MASTER]) } before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index fef90d9b5cb..0b1634f654a 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -57,7 +57,7 @@ describe Member, models: true do describe 'Scopes & finders' do before do - project = create(:project) + project = create(:empty_project) group = create(:group) @owner_user = create(:user).tap { |u| group.add_owner(u) } @owner = group.members.find_by(user_id: @owner_user.id) @@ -65,6 +65,15 @@ describe Member, models: true do @master_user = create(:user).tap { |u| project.team << [u, :master] } @master = project.members.find_by(user_id: @master_user.id) + @blocked_user = create(:user).tap do |u| + project.team << [u, :master] + project.team << [u, :developer] + + u.block! + end + @blocked_master = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::MASTER) + @blocked_developer = project.members.find_by(user_id: @blocked_user.id, access_level: Gitlab::Access::DEVELOPER) + Member.add_user( project.members, 'toto1@example.com', @@ -73,7 +82,7 @@ describe Member, models: true do ) @invited_member = project.members.invite.find_by_invite_email('toto1@example.com') - accepted_invite_user = build(:user) + accepted_invite_user = build(:user, state: :active) Member.add_user( project.members, 'toto2@example.com', @@ -91,7 +100,7 @@ describe Member, models: true do describe '.access_for_user_ids' do it 'returns the right access levels' do - users = [@owner_user.id, @master_user.id] + users = [@owner_user.id, @master_user.id, @blocked_user.id] expected = { @owner_user.id => Gitlab::Access::OWNER, @master_user.id => Gitlab::Access::MASTER @@ -125,6 +134,19 @@ describe Member, models: true do it { expect(described_class.request).not_to include @accepted_request_member } end + describe '.developers' do + subject { described_class.developers.to_a } + + it { is_expected.not_to include @owner } + it { is_expected.not_to include @master } + it { is_expected.to include @invited_member } + it { is_expected.to include @accepted_invite_member } + it { is_expected.not_to include @requested_member } + it { is_expected.to include @accepted_request_member } + it { is_expected.not_to include @blocked_master } + it { is_expected.not_to include @blocked_developer } + end + describe '.owners_and_masters' do it { expect(described_class.owners_and_masters).to include @owner } it { expect(described_class.owners_and_masters).to include @master } @@ -132,6 +154,20 @@ describe Member, models: true do it { expect(described_class.owners_and_masters).not_to include @accepted_invite_member } it { expect(described_class.owners_and_masters).not_to include @requested_member } it { expect(described_class.owners_and_masters).not_to include @accepted_request_member } + it { expect(described_class.owners_and_masters).not_to include @blocked_master } + end + + describe '.has_access' do + subject { described_class.has_access.to_a } + + it { is_expected.to include @owner } + it { is_expected.to include @master } + it { is_expected.to include @invited_member } + it { is_expected.to include @accepted_invite_member } + it { is_expected.not_to include @requested_member } + it { is_expected.to include @accepted_request_member } + it { is_expected.not_to include @blocked_master } + it { is_expected.not_to include @blocked_developer } end end -- cgit v1.2.3 From 56311d2b1cca533bb97ae6a0b95987621b9ef041 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 8 Sep 2016 15:33:53 +0300 Subject: Refactor code for bulk update merge requests feature Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/issuable.scss | 2 +- app/controllers/concerns/issuable_actions.rb | 4 +- app/helpers/issuables_helper.rb | 8 ---- app/views/shared/issuable/_filter.html.haml | 2 +- .../merge_requests/update_merge_requests_spec.rb | 47 ++++++++++++++-------- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 02d6d2082f9..63845e6b9ba 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -409,7 +409,7 @@ li { .issue-check { float: left; - padding-right: 16px; + padding-right: $gl-padding; margin-bottom: 10px; min-width: 15px; diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 76f7f179008..77b4efffd7f 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -24,13 +24,13 @@ module IssuableActions private def authorize_destroy_issuable! - unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable) + unless can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable) return access_denied! end end def authorize_admin_issuable! - unless current_user.can?(:"admin_#{resource_name}", @project) + unless can?(current_user, :"admin_#{resource_name}", @project) return access_denied! end end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 3f5af880646..5c04bba323f 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -111,12 +111,4 @@ module IssuablesHelper issuable.open? ? :opened : :closed end end - - def issuable_bulk_update_path(type) - if type == :merge_requests - bulk_update_namespace_project_merge_requests_path(@project.namespace, @project) - else - bulk_update_namespace_project_issues_path(@project.namespace, @project) - end - end end diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 45b8ec72fa4..93c4d5c3d30 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -49,7 +49,7 @@ - if @bulk_edit .issues_bulk_update.hide - = form_tag issuable_bulk_update_path(type), method: :post, class: 'bulk-update' do + = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update' do .filter-item.inline = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do %ul diff --git a/spec/features/merge_requests/update_merge_requests_spec.rb b/spec/features/merge_requests/update_merge_requests_spec.rb index 43b31dce9b3..b56fdfe5611 100644 --- a/spec/features/merge_requests/update_merge_requests_spec.rb +++ b/spec/features/merge_requests/update_merge_requests_spec.rb @@ -13,29 +13,39 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t end context 'status', js: true do - it 'sets to closed' do - visit namespace_project_merge_requests_path(project.namespace, project) + describe 'close merge request' do + before do + visit namespace_project_merge_requests_path(project.namespace, project) + end - change_status('Closed') - expect(page).to have_selector('.merge-request', count: 0) + it 'closes merge request' do + change_status('Closed') + + expect(page).to have_selector('.merge-request', count: 0) + end end - it 'sets to open' do - merge_request.close - visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed') + describe 'reopen merge request' do + before do + merge_request.close + visit namespace_project_merge_requests_path(project.namespace, project, state: 'closed') + end + + it 'reopens merge request' do + change_status('Open') - change_status('Open') - expect(page).to have_selector('.merge-request', count: 0) + expect(page).to have_selector('.merge-request', count: 0) + end end end context 'assignee', js: true do - context 'set assignee' do + describe 'set assignee' do before do visit namespace_project_merge_requests_path(project.namespace, project) end - it "should update merge request with assignee" do + it "updates merge request with assignee" do change_assignee(user.name) page.within('.merge-request .controls') do @@ -44,15 +54,16 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t end end - context 'remove assignee' do + describe 'remove assignee' do before do merge_request.assignee = user merge_request.save visit namespace_project_merge_requests_path(project.namespace, project) end - it "should remove assignee from the merge request" do + it "removes assignee from the merge request" do change_assignee('Unassigned') + expect(find('.merge-request .controls')).not_to have_css('.author_link') end end @@ -61,26 +72,28 @@ feature 'Multiple merge requests updating from merge_requests#index', feature: t context 'milestone', js: true do let(:milestone) { create(:milestone, project: project) } - context 'set milestone' do + describe 'set milestone' do before do visit namespace_project_merge_requests_path(project.namespace, project) end - it "should update merge request with milestone" do + it "updates merge request with milestone" do change_milestone(milestone.title) + expect(find('.merge-request')).to have_content milestone.title end end - context 'unset milestone' do + describe 'unset milestone' do before do merge_request.milestone = milestone merge_request.save visit namespace_project_merge_requests_path(project.namespace, project) end - it "should remove milestone from the merge request" do + it "removes milestone from the merge request" do change_milestone("No Milestone") + expect(find('.merge-request')).not_to have_content milestone.title end end -- cgit v1.2.3 From 7ad0bfac2301e6d5be9d0621edcf695ce9f9c01a Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 8 Sep 2016 14:42:13 +0200 Subject: Use gitlab-workhorse 0.8.0 --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- doc/update/8.11-to-8.12.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index b4d6d12101f..a3df0a6959e 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.11 +0.8.0 diff --git a/doc/install/installation.md b/doc/install/installation.md index 9522c3e7170..c9562e071d9 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.7.11 + sudo -u git -H git checkout v0.8.0 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index c8ca42f97bc..0615729263f 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -82,7 +82,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.7.11 +sudo -u git -H git checkout v0.8.0 sudo -u git -H make ``` -- cgit v1.2.3 From d027ed719dfdfabb687985ec6d4c6301953102cc Mon Sep 17 00:00:00 2001 From: Pascal Betz Date: Thu, 8 Sep 2016 15:30:16 +0200 Subject: Reformat Regexp Fix show template (missing tr) Separate exercise/verify --- app/helpers/sidekiq_helper.rb | 9 +++++++++ app/views/admin/background_jobs/show.html.haml | 7 ++++--- spec/helpers/sidekiq_helper_spec.rb | 5 +++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb index 82ab26408df..37650ca642b 100644 --- a/app/helpers/sidekiq_helper.rb +++ b/app/helpers/sidekiq_helper.rb @@ -1,6 +1,15 @@ module SidekiqHelper SIDEKIQ_PS_REGEXP = /\A([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+(.+)\s+(sidekiq.*\])\s+\z/ + SIDEKIQ_PS_REGEXP = /\A + (?\d+)\s+ + (?[\d\.,]+)\s+ + (?[\d\.,]+)\s+ + (?[DRSTWXZNLsl\+<]+)\s+ + (?.+)\s+ + (?sidekiq.*\])\s+ + \z/x + def parse_sidekiq_ps(line) match = line.match(SIDEKIQ_PS_REGEXP) if match diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 058919635da..05855db963a 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -28,9 +28,10 @@ %th COMMAND %tbody - @sidekiq_processes.each do |process| - %td= gitlab_config.user - - parse_sidekiq_ps(process).each do |value| - %td= value + %tr + %td= gitlab_config.user + - parse_sidekiq_ps(process).each do |value| + %td= value .clearfix %p %i.fa.fa-exclamation-circle diff --git a/spec/helpers/sidekiq_helper_spec.rb b/spec/helpers/sidekiq_helper_spec.rb index 2eb1c816bc5..d60839b78ec 100644 --- a/spec/helpers/sidekiq_helper_spec.rb +++ b/spec/helpers/sidekiq_helper_spec.rb @@ -5,30 +5,35 @@ describe SidekiqHelper do it 'parses line with time' do line = '55137 10,0 2,1 S+ 2:30pm sidekiq 4.1.4 gitlab [0 of 25 busy] ' parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['55137', '10,0', '2,1', 'S+', '2:30pm', 'sidekiq 4.1.4 gitlab [0 of 25 busy]']) end it 'parses line with date' do line = '55137 10,0 2,1 S+ Aug 4 sidekiq 4.1.4 gitlab [0 of 25 busy] ' parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['55137', '10,0', '2,1', 'S+', 'Aug 4', 'sidekiq 4.1.4 gitlab [0 of 25 busy]']) end it 'parses line with two digit date' do line = '55137 10,0 2,1 S+ Aug 04 sidekiq 4.1.4 gitlab [0 of 25 busy] ' parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['55137', '10,0', '2,1', 'S+', 'Aug 04', 'sidekiq 4.1.4 gitlab [0 of 25 busy]']) end it 'parses line with dot as float separator' do line = '55137 10.0 2.1 S+ 2:30pm sidekiq 4.1.4 gitlab [0 of 25 busy] ' parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['55137', '10.0', '2.1', 'S+', '2:30pm', 'sidekiq 4.1.4 gitlab [0 of 25 busy]']) end it 'does fail gracefully on line not matching the format' do line = '55137 10.0 2.1 S+ 2:30pm something' parts = helper.parse_sidekiq_ps(line) + expect(parts).to eq(['?', '?', '?', '?', '?', '?']) end end -- cgit v1.2.3 From 27a3f1182ae2664f27150bd91f74a4f732cfd4af Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Sep 2016 21:47:19 +0800 Subject: Split try_merge_period into overlap? and merge: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_15083507 --- lib/gitlab/ci/pipeline_duration.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 10ad70f14fa..311f5a7f8d8 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -115,10 +115,10 @@ module Gitlab return periods if periods.empty? periods.drop(1).inject([periods.first]) do |result, current| - merged = try_merge_period(result.last, current) + previous = result.last - if merged - result[-1] = merged + if overlap?(previous, current) + result[-1] = merge(previous, current) result else result << current @@ -126,10 +126,12 @@ module Gitlab end end - def try_merge_period(previous, current) - if current.first <= previous.last - Period.new(previous.first, [previous.last, current.last].max) - end + def overlap?(previous, current) + current.first <= previous.last + end + + def merge(previous, current) + Period.new(previous.first, [previous.last, current.last].max) end def process_duration(periods) -- cgit v1.2.3 From 822efd5c3b08dbd51ae4b468863475fa9d0ebc43 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 8 Sep 2016 23:55:07 +0800 Subject: Struct.new could take a block for defining methods, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6084#note_15091858 --- lib/gitlab/ci/pipeline_duration.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gitlab/ci/pipeline_duration.rb b/lib/gitlab/ci/pipeline_duration.rb index 311f5a7f8d8..a210e76acaa 100644 --- a/lib/gitlab/ci/pipeline_duration.rb +++ b/lib/gitlab/ci/pipeline_duration.rb @@ -79,8 +79,7 @@ module Gitlab module PipelineDuration extend self - PeriodStruct = Struct.new(:first, :last) - class Period < PeriodStruct + Period = Struct.new(:first, :last) do def duration last - first end -- cgit v1.2.3 From 7f6474b269a5cfa454d28c0c0da969490c9eb33e Mon Sep 17 00:00:00 2001 From: Jared Deckard Date: Tue, 26 Jul 2016 22:32:10 -0500 Subject: Restore comments lost when converting CoffeeScript to JavaScript --- app/assets/javascripts/LabelManager.js | 5 + app/assets/javascripts/api.js | 6 + app/assets/javascripts/application.js | 21 +++ app/assets/javascripts/autosave.js | 6 +- app/assets/javascripts/awards_handler.js | 5 + app/assets/javascripts/behaviors/autosize.js | 2 - .../javascripts/behaviors/details_behavior.js | 6 + app/assets/javascripts/behaviors/quick_submit.js | 20 ++- app/assets/javascripts/behaviors/requires_input.js | 19 ++- .../javascripts/behaviors/toggler_behavior.js | 7 + app/assets/javascripts/blob/blob_file_dropzone.js | 3 + app/assets/javascripts/blob/template_selector.js | 3 + app/assets/javascripts/blob_edit/edit_blob.js | 2 + app/assets/javascripts/breakpoints.js | 2 + app/assets/javascripts/build.js | 8 + app/assets/javascripts/commit/image-file.js | 2 + app/assets/javascripts/commits.js | 1 + app/assets/javascripts/copy_to_clipboard.js | 7 +- app/assets/javascripts/diff.js | 3 + app/assets/javascripts/dispatcher.js | 4 + app/assets/javascripts/due_date_select.js | 3 + app/assets/javascripts/extensions/jquery.js | 2 + app/assets/javascripts/gfm_auto_complete.js.es6 | 24 +++ app/assets/javascripts/gl_dropdown.js | 64 ++++++++ app/assets/javascripts/gl_form.js | 6 + app/assets/javascripts/graphs/graphs_bundle.js | 8 +- .../graphs/stat_graph_contributors_graph.js | 1 + app/assets/javascripts/groups_select.js | 1 + app/assets/javascripts/issuable.js.es6 | 2 + app/assets/javascripts/issue.js | 9 +- app/assets/javascripts/issues-bulk-assignment.js | 6 + app/assets/javascripts/labels.js | 3 + app/assets/javascripts/labels_select.js | 9 ++ app/assets/javascripts/lib/chart.js | 1 - app/assets/javascripts/lib/cropper.js | 1 - app/assets/javascripts/lib/d3.js | 1 - app/assets/javascripts/lib/raphael.js | 5 - .../javascripts/lib/utils/datetime_utility.js | 1 + .../lib/utils/emoji_aliases.js.coffee.erb | 2 - .../javascripts/lib/utils/emoji_aliases.js.erb | 6 + app/assets/javascripts/lib/utils/notify.js | 5 + app/assets/javascripts/lib/utils/text_utility.js | 5 +- app/assets/javascripts/lib/utils/url_utility.js | 6 + app/assets/javascripts/line_highlighter.js | 69 ++++++++- app/assets/javascripts/merge_request.js | 15 +- app/assets/javascripts/merge_request_tabs.js | 87 ++++++++++- app/assets/javascripts/merge_request_widget.js | 8 + app/assets/javascripts/milestone.js | 1 + app/assets/javascripts/milestone_select.js | 1 + app/assets/javascripts/network/branch-graph.js | 13 ++ app/assets/javascripts/network/network_bundle.js | 7 +- app/assets/javascripts/notes.js | 74 ++++++++-- app/assets/javascripts/preview_markdown.js | 10 ++ app/assets/javascripts/profile/gl_crop.js | 12 +- app/assets/javascripts/profile/profile.js | 4 + app/assets/javascripts/profile/profile_bundle.js | 1 - app/assets/javascripts/project.js | 6 + app/assets/javascripts/project_find_file.js | 9 ++ app/assets/javascripts/project_show.js | 2 + app/assets/javascripts/projects_list.js | 1 + .../javascripts/protected_branch_dropdown.js.es6 | 1 + app/assets/javascripts/search_autocomplete.js | 22 +++ app/assets/javascripts/shortcuts.js | 1 + app/assets/javascripts/shortcuts_find_file.js | 2 + app/assets/javascripts/shortcuts_issuable.js | 6 +- app/assets/javascripts/syntax_highlight.js | 11 ++ app/assets/javascripts/todos.js | 6 + app/assets/javascripts/tree.js | 3 + app/assets/javascripts/u2f/authenticate.js | 18 +++ app/assets/javascripts/u2f/register.js | 7 + app/assets/javascripts/user_tabs.js | 69 +++++++++ app/assets/javascripts/users/calendar.js | 7 + app/assets/javascripts/users/users_bundle.js | 1 - app/assets/javascripts/users_select.js | 8 + app/assets/javascripts/zen_mode.js | 37 +++-- spec/javascripts/awards_handler_spec.js | 7 +- spec/javascripts/behaviors/quick_submit_spec.js | 3 + .../graphs/stat_graph_contributors_graph_spec.js | 8 +- spec/javascripts/issue_spec.js | 2 - spec/javascripts/new_branch_spec.js | 2 - spec/javascripts/project_title_spec.js | 12 -- spec/javascripts/right_sidebar_spec.js | 4 - spec/javascripts/search_autocomplete_spec.js | 14 +- spec/javascripts/shortcuts_issuable_spec.js | 1 + spec/javascripts/spec_helper.js | 44 ++++-- spec/javascripts/u2f/authenticate_spec.js | 8 - spec/javascripts/u2f/register_spec.js | 8 - spec/javascripts/zen_mode_spec.js | 4 +- vendor/assets/javascripts/task_list.js | 161 +++++++++++++++++++-- 89 files changed, 958 insertions(+), 142 deletions(-) delete mode 100644 app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb create mode 100644 app/assets/javascripts/lib/utils/emoji_aliases.js.erb diff --git a/app/assets/javascripts/LabelManager.js b/app/assets/javascripts/LabelManager.js index 151455ce4a3..d4a4c7abaa1 100644 --- a/app/assets/javascripts/LabelManager.js +++ b/app/assets/javascripts/LabelManager.js @@ -3,6 +3,7 @@ LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time'; function LabelManager(opts) { + // Defaults var ref, ref1, ref2; if (opts == null) { opts = {}; @@ -28,6 +29,7 @@ $btn = $(e.currentTarget); $label = $("#" + ($btn.data('domId'))); action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; + // Make sure tooltip will hide $tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby'))); $tooltip.tooltip('destroy'); return _this.toggleLabelPriority($label, action); @@ -42,6 +44,7 @@ url = $label.find('.js-toggle-priority').data('url'); $target = this.prioritizedLabels; $from = this.otherLabels; + // Optimistic update if (action === 'remove') { $target = this.otherLabels; $from = this.prioritizedLabels; @@ -53,6 +56,7 @@ $target.find('.empty-message').addClass('hidden'); } $label.detach().appendTo($target); + // Return if we are not persisting state if (!persistState) { return; } @@ -61,6 +65,7 @@ url: url, type: 'DELETE' }); + // Restore empty message if (!$from.find('li').length) { $from.find('.empty-message').removeClass('hidden'); } diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 84b292e59c6..6df2ecf57a2 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -24,6 +24,8 @@ return callback(group); }); }, + // Return groups list. Filtered by query + // Only active groups retrieved groups: function(query, skip_ldap, callback) { var url = Api.buildUrl(Api.groupsPath); return $.ajax({ @@ -38,6 +40,7 @@ return callback(groups); }); }, + // Return namespaces list. Filtered by query namespaces: function(query, callback) { var url = Api.buildUrl(Api.namespacesPath); return $.ajax({ @@ -52,6 +55,7 @@ return callback(namespaces); }); }, + // Return projects list. Filtered by query projects: function(query, order, callback) { var url = Api.buildUrl(Api.projectsPath); return $.ajax({ @@ -82,6 +86,7 @@ return callback(message.responseJSON); }); }, + // Return group projects list. Filtered by query groupProjects: function(group_id, query, callback) { var url = Api.buildUrl(Api.groupProjectsPath) .replace(':id', group_id); @@ -97,6 +102,7 @@ return callback(projects); }); }, + // Return text for a specific license licenseText: function(key, data, callback) { var url = Api.buildUrl(Api.licensePath) .replace(':key', key); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index fea6f41d5e9..31fa508d6c1 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -1,3 +1,9 @@ +// This is a manifest file that'll be compiled into including all the files listed below. +// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// be included in the compiled file accessible from http://example.com/assets/application.js +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// /*= require jquery2 */ /*= require jquery-ui/autocomplete */ /*= require jquery-ui/datepicker */ @@ -76,6 +82,7 @@ } }; + // Disable button if text field is empty window.disableButtonIfEmptyField = function(field_selector, button_selector) { var closest_submit, field; field = $(field_selector); @@ -92,6 +99,7 @@ }); }; + // Disable button if any input field with given selector is empty window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) { var closest_submit, updateButtons; closest_submit = form.find(button_selector); @@ -128,6 +136,8 @@ window.addEventListener("hashchange", shiftWindow); window.onload = function() { + // Scroll the window to avoid the topnav bar + // https://github.com/twitter/bootstrap/issues/1768 if (location.hash) { return setTimeout(shiftWindow, 100); } @@ -149,6 +159,8 @@ return $(this).select().one('mouseup', function(e) { return e.preventDefault(); }); + // Click a .js-select-on-focus field, select the contents + // Prevent a mouseup event from deselecting the input }); $('.remove-row').bind('ajax:success', function() { $(this).tooltip('destroy') @@ -163,6 +175,7 @@ }); $('select.select2').select2({ width: 'resolve', + // Initialize select2 selects dropdownAutoWidth: true }); $('.js-select2').bind('select2-close', function() { @@ -170,7 +183,9 @@ $('.select2-container-active').removeClass('select2-container-active'); return $(':focus').blur(); }), 1); + // Close select2 on escape }); + // Initialize tooltips $body.tooltip({ selector: '.has-tooltip, [data-toggle="tooltip"]', placement: function(_, el) { @@ -179,14 +194,17 @@ }); $('.trigger-submit').on('change', function() { return $(this).parents('form').submit(); + // Form submitter }); gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true); + // Flash if ((flash = $(".flash-container")).length > 0) { flash.click(function() { return $(this).fadeOut(); }); flash.show(); } + // Disable form buttons while a form is submitting $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) { var buttons; buttons = $('[type="submit"]', this); @@ -207,6 +225,7 @@ } }); $('.account-box').hover(function() { + // Show/Hide the profile menu when hovering the account box return $(this).toggleClass('hover'); }); $document.on('click', '.diff-content .js-show-suppressed-diff', function() { @@ -214,6 +233,7 @@ $container = $(this).parent(); $container.next('table').show(); return $container.remove(); + // Commit show suppressed diff }); $('.navbar-toggle').on('click', function() { $('.header-content .title').toggle(); @@ -221,6 +241,7 @@ $('.header-content .navbar-collapse').toggle(); return $('.navbar-toggle').toggleClass('active'); }); + // Show/hide comments on diff $body.on("click", ".js-toggle-diff-comments", function(e) { var $this = $(this); $this.toggleClass('active'); diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js index 7116512d6b7..a9aec6e8ea4 100644 --- a/app/assets/javascripts/autosave.js +++ b/app/assets/javascripts/autosave.js @@ -16,7 +16,7 @@ } Autosave.prototype.restore = function() { - var e, error, text; + var e, text; if (window.localStorage == null) { return; } @@ -41,7 +41,7 @@ if ((text != null ? text.length : void 0) > 0) { try { return window.localStorage.setItem(this.key, text); - } catch (undefined) {} + } catch (error) {} } else { return this.reset(); } @@ -53,7 +53,7 @@ } try { return window.localStorage.removeItem(this.key); - } catch (undefined) {} + } catch (error) {} }; return Autosave; diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 5ea18ea8b7a..0decc6d09e6 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -86,6 +86,8 @@ AwardsHandler.prototype.positionMenu = function($menu, $addBtn) { var css, position; position = $addBtn.data('position'); + // The menu could potentially be off-screen or in a hidden overflow element + // So we position the element absolute in the body css = { top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px" }; @@ -284,6 +286,7 @@ if (emojiIcon.length > 0) { unicodeName = emojiIcon.data('unicode-name'); } else { + // Find by alias unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name'); } return "emoji-" + unicodeName; @@ -350,8 +353,10 @@ return function(ev) { var found_emojis, h5, term, ul; term = $(ev.target).val(); + // Clean previous search results $('ul.emoji-menu-search, h5.emoji-search').remove(); if (term) { + // Generate a search result block h5 = $('
').text('Search results'); found_emojis = _this.searchEmojis(term).show(); ul = $('
    ').addClass('emoji-menu-list emoji-menu-search').append(found_emojis); diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js index f977a1e8a7b..dc8ae601961 100644 --- a/app/assets/javascripts/behaviors/autosize.js +++ b/app/assets/javascripts/behaviors/autosize.js @@ -1,7 +1,5 @@ /*= require jquery.ba-resize */ - - /*= require autosize */ (function() { diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js index 3631d1b74ac..1df681a4816 100644 --- a/app/assets/javascripts/behaviors/details_behavior.js +++ b/app/assets/javascripts/behaviors/details_behavior.js @@ -5,6 +5,12 @@ container = $(this).closest(".js-details-container"); return container.toggleClass("open"); }); + // Show details content. Hides link after click. + // + // %div + // %a.js-details-expand + // %div.js-details-content + // return $("body").on("click", ".js-details-expand", function(e) { $(this).next('.js-details-content').removeClass("hide"); $(this).hide(); diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index 3527d0a95fc..54b7360ab41 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -1,6 +1,20 @@ - +// Quick Submit behavior +// +// When a child field of a form with a `js-quick-submit` class receives a +// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form +// is submitted. +// /*= require extensions/jquery */ +// +// ### Example Markup +// +//
    +// +// +// +//
    +// (function() { var isMac, keyCodeIs; @@ -17,6 +31,7 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', function(e) { var $form, $submit_button; + // Enter if (!keyCodeIs(e, 13)) { return; } @@ -33,8 +48,11 @@ return $form.submit(); }); + // If the user tabs to a submit button on a `js-quick-submit` form, display a + // tooltip to let them know they could've used the hotkey $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) { var $this, title; + // Tab if (!keyCodeIs(e, 9)) { return; } diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index db0b36b24e9..894034bdd54 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -1,6 +1,18 @@ - +// Requires Input behavior +// +// When called on a form with input fields with the `required` attribute, the +// form's submit button will be disabled until all required fields have values. +// /*= require extensions/jquery */ +// +// ### Example Markup +// +//
    +// +// +//
    +// (function() { $.fn.requiresInput = function() { var $button, $form, fieldSelector, requireInput, required; @@ -11,14 +23,17 @@ requireInput = function() { var values; values = _.map($(fieldSelector, $form), function(field) { + // Collect the input values of *all* required fields return field.value; }); + // Disable the button if any required fields are empty if (values.length && _.any(values, _.isEmpty)) { return $button.disable(); } else { return $button.enable(); } }; + // Set initial button state requireInput(); return $form.on('change input', fieldSelector, requireInput); }; @@ -27,6 +42,8 @@ var $form, hideOrShowHelpBlock; $form = $('form.js-requires-input'); $form.requiresInput(); + // Hide or Show the help block when creating a new project + // based on the option selected hideOrShowHelpBlock = function(form) { var selected; selected = $('.js-select-namespace option:selected'); diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 5467e3edc69..a6ce378d67a 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -1,5 +1,12 @@ (function(w) { $(function() { + // Toggle button. Show/hide content inside parent container. + // Button does not change visibility. If button has icon - it changes chevron style. + // + // %div.js-toggle-container + // %a.js-toggle-button + // %div.js-toggle-content + // $('body').on('click', '.js-toggle-button', function(e) { e.preventDefault(); $(this) diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index f4044f22db2..8cca1aa9232 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -8,6 +8,8 @@ autoDiscover: false, autoProcessQueue: false, url: form.attr('action'), + // Rails uses a hidden input field for PUT + // http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails method: method, clickable: true, uploadMultiple: false, @@ -36,6 +38,7 @@ formData.append('commit_message', form.find('.js-commit-message').val()); }); }, + // Override behavior of adding error underneath preview error: function(file, errorMessage) { var stripped; stripped = $("
    ").html(errorMessage).text(); diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index b0a37ef0e0a..b18b6962382 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -66,6 +66,9 @@ // be added by all subclasses. }; + // To be implemented on the extending class + // e.g. + // Api.gitignoreText item.name, @requestFileSuccess.bind(@) TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) { this.editor.setValue(file.content, 1); if (!skipFocus) this.editor.focus(); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 649c79daee8..b846bab0424 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -18,6 +18,8 @@ return function() { return $("#file-content").val(_this.editor.getValue()); }; + // Before a form submission, move the content from the Ace editor into the + // submitted textarea })(this)); this.initModePanesAndLinks(); new BlobLicenseSelectors({ diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js index 1e0148e5798..5fef9725178 100644 --- a/app/assets/javascripts/breakpoints.js +++ b/app/assets/javascripts/breakpoints.js @@ -23,6 +23,7 @@ if ($(allDeviceSelector.join(",")).length) { return; } + // Create all the elements els = $.map(BREAKPOINTS, function(breakpoint) { return "
    "; }); @@ -40,6 +41,7 @@ BreakpointInstance.prototype.getBreakpointSize = function() { var $visibleDevice; $visibleDevice = this.visibleDevice; + // the page refreshed via turbolinks if (!$visibleDevice().length) { this.setup(); } diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 4d066f13646..10abeb50f4b 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -16,6 +16,7 @@ this.toggleSidebar = bind(this.toggleSidebar, this); this.updateDropdown = bind(this.updateDropdown, this); clearInterval(Build.interval); + // Init breakpoint checker this.bp = Breakpoints.get(); $('.js-build-sidebar').niceScroll(); @@ -42,6 +43,9 @@ $(this).data("state", "enabled"); return $(this).text("disable autoscroll"); } + // + // Bind autoscroll button to follow build output + // }); Build.interval = setInterval((function(_this) { return function() { @@ -49,6 +53,10 @@ return _this.getBuildTrace(); } }; + // + // Check for new build output if user still watching build page + // Only valid for runnig build when output changes during time + // })(this), 4000); } } diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image-file.js index c0d0b2d049f..e893491b19b 100644 --- a/app/assets/javascripts/commit/image-file.js +++ b/app/assets/javascripts/commit/image-file.js @@ -2,6 +2,7 @@ this.ImageFile = (function() { var prepareFrames; + // Width where images must fits in, for 2-up this gets divided by 2 ImageFile.availWidth = 900; ImageFile.viewModes = ['two-up', 'swipe']; @@ -9,6 +10,7 @@ function ImageFile(file) { this.file = file; this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) { + // Determine if old and new file has same dimensions, if not show 'two-up' view return function(deletedWidth, deletedHeight) { return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) { if (width === deletedWidth && height === deletedHeight) { diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 37f168c5190..9132089adcd 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -45,6 +45,7 @@ CommitsList.content.html(data.html); return history.replaceState({ page: commitsUrl + // Change url so if user reload a page - search results are saved }, document.title, commitsUrl); }, dataType: "json" diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index c43af17442b..3e20db7e308 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -6,14 +6,19 @@ genericSuccess = function(e) { showTooltip(e.trigger, 'Copied!'); + // Clear the selection and blur the trigger so it loses its border e.clearSelection(); return $(e.trigger).blur(); }; + // Safari doesn't support `execCommand`, so instead we inform the user to + // copy manually. + // + // See http://clipboardjs.com/#browser-support genericError = function(e) { var key; if (/Mac/i.test(navigator.userAgent)) { - key = '⌘'; + key = '⌘'; // Command } else { key = 'Ctrl'; } diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 3dd7ceba92f..c8634b78f2b 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -39,6 +39,9 @@ bottom: unfoldBottom, offset: offset, unfold: unfold, + // indent is used to compensate for single space indent to fit + // '+' and '-' prepended to diff lines, + // see https://gitlab.com/gitlab-org/gitlab-ce/issues/707 indent: 1, view: file.data('view') }; diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 179d3bc38a5..99b16f7d59b 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -164,6 +164,8 @@ } break; case 'projects:network:show': + // Ensure we don't create a particular shortcut handler here. This is + // already created, where the network graph is created. shortcut_handler = true; break; case 'projects:forks:new': @@ -260,12 +262,14 @@ shortcut_handler = new ShortcutsNavigation(); } } + // If we haven't installed a custom shortcut handler, install the default one if (!shortcut_handler) { return new Shortcuts(); } }; Dispatcher.prototype.initSearch = function() { + // Only when search form is present if ($('.search').length) { return new SearchAutocomplete(); } diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index 5a725a41fd1..bf68b7e3a9b 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -2,6 +2,7 @@ this.DueDateSelect = (function() { function DueDateSelect() { var $datePicker, $dueDate, $loading; + // Milestone edit/new form $datePicker = $('.datepicker'); if ($datePicker.length) { $dueDate = $('#milestone_due_date'); @@ -16,6 +17,7 @@ e.preventDefault(); return $.datepicker._clearDate($datePicker); }); + // Issuable sidebar $loading = $('.js-issuable-update .due_date').find('.block-loading').hide(); $('.js-due-date-select').each(function(i, dropdown) { var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL; @@ -38,6 +40,7 @@ }); addDueDate = function(isDropdown) { var data, date, mediumDate, value; + // Create the post date value = $("input[name='" + fieldName + "']").val(); if (value !== '') { date = new Date(value.replace(new RegExp('-', 'g'), ',')); diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js index ae3dde63da3..4978e24949c 100644 --- a/app/assets/javascripts/extensions/jquery.js +++ b/app/assets/javascripts/extensions/jquery.js @@ -1,3 +1,4 @@ +// Disable an element and add the 'disabled' Bootstrap class (function() { $.fn.extend({ disable: function() { @@ -5,6 +6,7 @@ } }); + // Enable an element and remove the 'disabled' Bootstrap class $.fn.extend({ enable: function() { return $(this).removeAttr('disabled').removeClass('disabled'); diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 3dca06d36b1..d0786bf0053 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -1,3 +1,4 @@ +// Creates the variables for setting up GFM auto-completion (function() { if (window.GitLab == null) { window.GitLab = {}; @@ -8,18 +9,22 @@ dataLoaded: false, cachedData: {}, dataSource: '', + // Emoji Emoji: { template: '
  • ${name} ${name}
  • ' }, + // Team Members Members: { template: '
  • ${username} ${title}
  • ' }, Labels: { template: '
  • ${title}
  • ' }, + // Issues and MergeRequests Issues: { template: '
  • ${id} ${title}
  • ' }, + // Milestones Milestones: { template: '
  • ${title}
  • ' }, @@ -48,8 +53,11 @@ } }, setup: function(input) { + // Add GFM auto-completion to all input fields, that accept GFM input. this.input = input || $('.js-gfm-input'); + // destroy previous instances this.destroyAtWho(); + // set up instances this.setupAtWho(); if (this.dataSource) { if (!this.dataLoading && !this.cachedData) { @@ -63,6 +71,11 @@ return _this.loadData(data); }); }; + // We should wait until initializations are done + // and only trigger the last .setup since + // The previous .dataSource belongs to the previous issuable + // and the last one will have the **proper** .dataSource property + // TODO: Make this a singleton and turn off events when moving to another page })(this), 1000); } if (this.cachedData != null) { @@ -71,6 +84,7 @@ } }, setupAtWho: function() { + // Emoji this.input.atwho({ at: ':', displayTpl: (function(_this) { @@ -90,6 +104,7 @@ beforeInsert: this.DefaultOptions.beforeInsert } }); + // Team Members this.input.atwho({ at: '@', displayTpl: (function(_this) { @@ -321,13 +336,22 @@ loadData: function(data) { this.cachedData = data; this.dataLoaded = true; + // load members this.input.atwho('load', '@', data.members); + // load issues this.input.atwho('load', 'issues', data.issues); + // load milestones this.input.atwho('load', 'milestones', data.milestones); + // load merge requests this.input.atwho('load', 'mergerequests', data.mergerequests); + // load emojis this.input.atwho('load', ':', data.emojis); + // load labels this.input.atwho('load', '~', data.labels); + // load commands this.input.atwho('load', '/', data.commands); + // This trigger at.js again + // otherwise we would be stuck with loading until the user types return $(':focus').trigger('keyup'); } }; diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 77b2082cba0..bea141bae51 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -21,12 +21,14 @@ $clearButton = $inputContainer.find('.js-dropdown-input-clear'); this.indeterminateIds = []; $clearButton.on('click', (function(_this) { + // Clear click return function(e) { e.preventDefault(); e.stopPropagation(); return _this.input.val('').trigger('keyup').focus(); }; })(this)); + // Key events timeout = ""; this.input .on('keydown', function (e) { @@ -49,6 +51,7 @@ if (keyCode === 13 && !options.elIsInput) { return false; } + // Only filter asynchronously only if option remote is set if (this.options.remote) { clearTimeout(timeout); return timeout = setTimeout(function() { @@ -79,11 +82,27 @@ if ((data != null) && !this.options.filterByText) { results = data; if (search_text !== '') { + // When data is an array of objects therefore [object Array] e.g. + // [ + // { prop: 'foo' }, + // { prop: 'baz' } + // ] if (_.isArray(data)) { results = fuzzaldrinPlus.filter(data, search_text, { key: this.options.keys }); } else { + // If data is grouped therefore an [object Object]. e.g. + // { + // groupName1: [ + // { prop: 'foo' }, + // { prop: 'baz' } + // ], + // groupName2: [ + // { prop: 'abc' }, + // { prop: 'def' } + // ] + // } if (gl.utils.isObject(data)) { results = {}; for (key in data) { @@ -140,6 +159,7 @@ this.options.beforeSend(); } return this.dataEndpoint("", (function(_this) { + // Fetch the data by calling the data funcfion return function(data) { if (_this.options.success) { _this.options.success(data); @@ -171,6 +191,7 @@ }; })(this) }); + // Fetch the data through ajax if the data is a string }; return GitLabDropdownRemote; @@ -209,13 +230,18 @@ self = this; selector = $(this.el).data("target"); this.dropdown = selector != null ? $(selector) : $(this.el).parent(); + // Set Defaults ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true; + // If no input is passed create a default one self = this; + // If selector was passed if (_.isString(this.filterInput)) { this.filterInput = this.getElement(this.filterInput); } searchFields = this.options.search ? this.options.search.fields : []; if (this.options.data) { + // If we provided data + // data could be an array of objects or a group of arrays if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) { this.fullData = this.options.data; currentIndex = -1; @@ -232,10 +258,12 @@ return _this.filter.input.trigger('keyup'); } }; + // Remote data })(this) }); } } + // Init filterable if (this.options.filterable) { this.filter = new GitLabDropdownFilter(this.filterInput, { elIsInput: $(this.el).is('input'), @@ -278,12 +306,14 @@ })(this) }); } + // Event listeners this.dropdown.on("shown.bs.dropdown", this.opened); this.dropdown.on("hidden.bs.dropdown", this.hidden); $(this.el).on("update.label", this.updateLabel); this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate); this.dropdown.on('keyup', (function(_this) { return function(e) { + // Escape key if (e.which === 27) { return $('.dropdown-menu-close', _this.dropdown).trigger('click'); } @@ -327,6 +357,7 @@ } } + // Finds an element inside wrapper element GitLabDropdown.prototype.getElement = function(selector) { return this.dropdown.find(selector); }; @@ -344,6 +375,7 @@ } } menu.toggleClass(PAGE_TWO_CLASS); + // Focus first visible input on active page return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus(); }; @@ -351,23 +383,28 @@ var full_html, groupData, html, name; this.renderedData = data; if (this.options.filterable && data.length === 0) { + // render no matching results html = [this.noResults()]; } else { + // Handle array groups if (gl.utils.isObject(data)) { html = []; for (name in data) { groupData = data[name]; html.push(this.renderItem({ header: name + // Add header for each group }, name)); this.renderData(groupData, name).map(function(item) { return html.push(item); }); } } else { + // Render each row html = this.renderData(data); } } + // Render the full menu full_html = this.renderMenu(html); return this.appendMenu(full_html); }; @@ -406,6 +443,7 @@ if (this.options.setActiveIds) { this.options.setActiveIds.call(this); } + // Makes indeterminate items effective if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) { this.parseData(this.fullData); } @@ -427,6 +465,8 @@ if (this.options.filterable) { $input.blur().val(""); } + // Triggering 'keyup' will re-render the dropdown which is not always required + // specially if we want to keep the state of the dropdown needed for bulk-assignment if (!this.options.persistWhenHide) { $input.trigger("keyup"); } @@ -439,6 +479,7 @@ return this.dropdown.trigger('hidden.gl.dropdown'); }; + // Render the full menu GitLabDropdown.prototype.renderMenu = function(html) { var menu_html; menu_html = ""; @@ -450,6 +491,7 @@ return menu_html; }; + // Append the menu into the dropdown GitLabDropdown.prototype.appendMenu = function(html) { var selector; selector = '.dropdown-content'; @@ -465,19 +507,24 @@ group = false; } if (index == null) { + // Render the row index = false; } html = ""; + // Divider if (data === "divider") { return "
  • "; } + // Separator is a full-width divider if (data === "separator") { return "
  • "; } + // Header if (data.header != null) { return _.template('')({ header: data.header }); } if (this.options.renderRow) { + // Call the render function html = this.options.renderRow.call(this.options, data, this); } else { if (!selected) { @@ -489,11 +536,13 @@ selected = true; } } + // Set URL if (this.options.url != null) { url = this.options.url(data); } else { url = data.url != null ? data.url : '#'; } + // Set Text if (this.options.text != null) { text = this.options.text(data); } else { @@ -584,6 +633,7 @@ if (value == null) { field.remove(); } + // Toggle active class for the tick mark el.addClass(ACTIVE_CLASS); if (value != null) { if (!field.length && fieldName) { @@ -604,6 +654,7 @@ GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) { var $input; + // Create hidden input for form $input = $('').attr('type', 'hidden').attr('name', fieldName).val(value); if (this.options.inputId != null) { $input.attr('id', this.options.inputId); @@ -625,6 +676,7 @@ if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one " + selector; } + // simulate a click on the first link $el = $(selector, this.dropdown); if ($el.length) { var href = $el.attr('href'); @@ -653,11 +705,15 @@ e.stopImmediatePropagation(); PREV_INDEX = currentIndex; $listItems = $(selector, _this.dropdown); + // if @options.filterable + // $input.blur() if (currentKeyCode === 40) { + // Move down if (currentIndex < ($listItems.length - 1)) { currentIndex += 1; } } else if (currentKeyCode === 38) { + // Move up if (currentIndex > 0) { currentIndex -= 1; } @@ -685,24 +741,32 @@ GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) { var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop; + // Remove the class for the previously focused row $('.is-focused', this.dropdown).removeClass('is-focused'); + // Update the class for the row at the specific index $listItem = $listItems.eq(index); $listItem.find('a:first-child').addClass("is-focused"); + // Dropdown content scroll area $dropdownContent = $listItem.closest('.dropdown-content'); dropdownScrollTop = $dropdownContent.scrollTop(); dropdownContentHeight = $dropdownContent.outerHeight(); dropdownContentTop = $dropdownContent.prop('offsetTop'); dropdownContentBottom = dropdownContentTop + dropdownContentHeight; + // Get the offset bottom of the list item listItemHeight = $listItem.outerHeight(); listItemTop = $listItem.prop('offsetTop'); listItemBottom = listItemTop + listItemHeight; if (!index) { + // Scroll the dropdown content to the top $dropdownContent.scrollTop(0) } else if (index === ($listItems.length - 1)) { + // Scroll the dropdown content to the bottom $dropdownContent.scrollTop($dropdownContent.prop('scrollHeight')); } else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) { + // Scroll the dropdown content down $dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING); } else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) { + // Scroll the dropdown content up return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING); } }; diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index 528a673eb15..2703adc0705 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -3,12 +3,15 @@ function GLForm(form) { this.form = form; this.textarea = this.form.find('textarea.js-gfm-input'); + // Before we start, we should clean up any previous data for this form this.destroy(); + // Setup the form this.setupForm(); this.form.data('gl-form', this); } GLForm.prototype.destroy = function() { + // Clean form listeners this.clearEventListeners(); return this.form.data('gl-form', null); }; @@ -21,12 +24,15 @@ this.form.find('.div-dropzone').remove(); this.form.addClass('gfm-form'); disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button')); + // remove notify commit author checkbox for non-commit notes GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); new DropzoneInput(this.form); autosize(this.textarea); + // form and textarea event listeners this.addEventListeners(); gl.text.init(this.form); } + // hide discard button this.form.find('.js-note-discard').hide(); return this.form.show(); }; diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js index b95faadc8e7..4886da9f21f 100644 --- a/app/assets/javascripts/graphs/graphs_bundle.js +++ b/app/assets/javascripts/graphs/graphs_bundle.js @@ -1,7 +1,11 @@ - +// This is a manifest file that'll be compiled into including all the files listed below. +// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// be included in the compiled file accessible from http://example.com/assets/application.js +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// /*= require_tree . */ (function() { - }).call(this); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js index a646ca1d84f..7d9d4d7c679 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js @@ -204,6 +204,7 @@ function ContributorsAuthorGraph(data1) { this.data = data1; + // Don't split graph size in half for mobile devices. if ($(window).width() < 768) { this.width = $('.content').width() - 80; } else { diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index fd5b6dc0ddd..7c2eebcdd44 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -38,6 +38,7 @@ return _this.formatSelection.apply(_this, args); }, dropdownCssClass: "ajax-groups-dropdown", + // we do not want to escape markup since we are displaying html in results escapeMarkup: function(m) { return m; } diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6 index 82c14ef0157..53faaa38a0c 100644 --- a/app/assets/javascripts/issuable.js.es6 +++ b/app/assets/javascripts/issuable.js.es6 @@ -38,9 +38,11 @@ return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) { var $button; $button = $(this); + // Remove the label input box $('input[name="label_name[]"]').filter(function() { return this.value === $button.data('label'); }).remove(); + // Submit the form to get new data Issuable.filterResults($('.filter-form')); return $('.js-label-select').trigger('update.label'); }); diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index e6422602ce8..261bf6137c2 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -1,10 +1,6 @@ /*= require flash */ - - /*= require jquery.waitforimages */ - - /*= require task_list */ (function() { @@ -13,6 +9,7 @@ this.Issue = (function() { function Issue() { this.submitNoteForm = bind(this.submitNoteForm, this); + // Prevent duplicate event bindings this.disableTaskList(); if ($('a.btn-close').length) { this.initTaskList(); @@ -99,6 +96,8 @@ url: $('form.js-issuable-update').attr('action'), data: patchData }); + // TODO (rspeicher): Make the issue description inline-editable like a note so + // that we can re-use its form here }; Issue.prototype.initMergeRequests = function() { @@ -128,6 +127,8 @@ Issue.prototype.initCanCreateBranch = function() { var $container; $container = $('#new-branch'); + // If the user doesn't have the required permissions the container isn't + // rendered at all. if ($container.length === 0) { return; } diff --git a/app/assets/javascripts/issues-bulk-assignment.js b/app/assets/javascripts/issues-bulk-assignment.js index 8ca90490426..62a7fc9a06c 100644 --- a/app/assets/javascripts/issues-bulk-assignment.js +++ b/app/assets/javascripts/issues-bulk-assignment.js @@ -1,14 +1,17 @@ (function() { this.IssuableBulkActions = (function() { function IssuableBulkActions(opts) { + // Set defaults var ref, ref1, ref2; if (opts == null) { opts = {}; } this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li'); + // Save instance this.form.data('bulkActions', this); this.willUpdateLabels = false; this.bindEvents(); + // Fixes bulk-assign not working when navigating through pages Issuable.initChecks(); } @@ -86,6 +89,7 @@ ref1 = this.getLabelsFromSelection(); for (j = 0, len1 = ref1.length; j < len1; j++) { id = ref1[j]; + // Only the ones that we are not going to keep if (labelsToKeep.indexOf(id) === -1) { result.push(id); } @@ -147,6 +151,8 @@ indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); labelsToApply = this.getLabelsToApply(); indeterminatedLabels.map(function(id) { + // We need to exclude label IDs that will be applied + // By not doing this will cause issues from selection to not add labels at all if (labelsToApply.indexOf(id) === -1) { return result.push(id); } diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js index fe071fca67c..cb16e2ba814 100644 --- a/app/assets/javascripts/labels.js +++ b/app/assets/javascripts/labels.js @@ -26,13 +26,16 @@ var previewColor; previewColor = $('input#label_color').val(); return $('div.label-color-preview').css('background-color', previewColor); + // Updates the the preview color with the hex-color input }; + // Updates the preview color with a click on a suggested color Labels.prototype.setSuggestedColor = function(e) { var color; color = $(e.currentTarget).data('color'); $('input#label_color').val(color); this.updateColorPreview(); + // Notify the form, that color has changed $('.label-form').trigger('keyup'); return e.preventDefault(); }; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index bab23ff5ac0..29a967a35a0 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -156,11 +156,13 @@ selectedClass.push('is-indeterminate'); } if (active.indexOf(label.id) !== -1) { + // Remove is-indeterminate class if the item will be marked as active i = selectedClass.indexOf('is-indeterminate'); if (i !== -1) { selectedClass.splice(i, 1); } selectedClass.push('is-active'); + // Add input manually instance.addInput(this.fieldName, label.id); } } @@ -172,6 +174,7 @@ } if (label.duplicate) { spacing = 100 / label.color.length; + // Reduce the colors to 4 label.color = label.color.filter(function(color, i) { return i < 4; }); @@ -192,11 +195,13 @@ } else { colorEl = ''; } + // We need to identify which items are actually labels if (label.id) { selectedClass.push('label-item'); $a.attr('data-label-id', label.id); } $a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title); + // Return generated html return $li.html($a).prop('outerHTML'); }, persistWhenHide: $dropdown.data('persistWhenHide'), @@ -238,6 +243,7 @@ isIssueIndex = page === 'projects:issues:index'; isMRIndex = page === 'projects:merge_requests:index'; $selectbox.hide(); + // display:block overrides the hide-collapse rule $value.removeAttr('style'); if (page === 'projects:boards:show') { return; @@ -255,6 +261,7 @@ } } if ($dropdown.hasClass('js-filter-bulk-update')) { + // If we are persisting state we need the classes if (!this.options.persistWhenHide) { return $dropdown.parent().find('.is-active, .is-indeterminate').removeClass(); } @@ -324,7 +331,9 @@ if ($('.selected_issue:checked').length) { return; } + // Remove inputs $('.issues_bulk_update .labels-filter input[type="hidden"]').remove(); + // Also restore button text return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label'); }; diff --git a/app/assets/javascripts/lib/chart.js b/app/assets/javascripts/lib/chart.js index 8d5e52286b7..d9b07c10a49 100644 --- a/app/assets/javascripts/lib/chart.js +++ b/app/assets/javascripts/lib/chart.js @@ -3,5 +3,4 @@ (function() { - }).call(this); diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js index 8ee81804513..a88e640f298 100644 --- a/app/assets/javascripts/lib/cropper.js +++ b/app/assets/javascripts/lib/cropper.js @@ -3,5 +3,4 @@ (function() { - }).call(this); diff --git a/app/assets/javascripts/lib/d3.js b/app/assets/javascripts/lib/d3.js index 31e6033e756..ee1baf54803 100644 --- a/app/assets/javascripts/lib/d3.js +++ b/app/assets/javascripts/lib/d3.js @@ -3,5 +3,4 @@ (function() { - }).call(this); diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js index 923c575dcfe..6df427bc2b1 100644 --- a/app/assets/javascripts/lib/raphael.js +++ b/app/assets/javascripts/lib/raphael.js @@ -1,13 +1,8 @@ /*= require raphael */ - - /*= require g.raphael */ - - /*= require g.bar */ (function() { - }).call(this); diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index d4d5927d3b0..8fdf4646cd8 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -29,6 +29,7 @@ if (setTimeago) { $timeagoEls.timeago(); $timeagoEls.tooltip('destroy'); + // Recreate with custom template return $timeagoEls.tooltip({ template: '' }); diff --git a/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb deleted file mode 100644 index 80f9936b9c2..00000000000 --- a/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb +++ /dev/null @@ -1,2 +0,0 @@ -gl.emojiAliases = -> - JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>') diff --git a/app/assets/javascripts/lib/utils/emoji_aliases.js.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.erb new file mode 100644 index 00000000000..aeb86c9fa5b --- /dev/null +++ b/app/assets/javascripts/lib/utils/emoji_aliases.js.erb @@ -0,0 +1,6 @@ +(function() { + gl.emojiAliases = function() { + return JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>'); + }; + +}).call(this); diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js index 42b6ac0589e..5b338b00d76 100644 --- a/app/assets/javascripts/lib/utils/notify.js +++ b/app/assets/javascripts/lib/utils/notify.js @@ -6,6 +6,7 @@ notification = new Notification(message, opts); setTimeout(function() { return notification.close(); + // Hide the notification after X amount of seconds }, 8000); if (onclick) { return notification.onclick = onclick; @@ -22,12 +23,16 @@ body: body, icon: icon }; + // Let's check if the browser supports notifications if (!('Notification' in window)) { + // do nothing } else if (Notification.permission === 'granted') { + // If it's okay let's create a notification return notificationGranted(message, opts, onclick); } else if (Notification.permission !== 'denied') { return Notification.requestPermission(function(permission) { + // If the user accepts, let's create a notification if (permission === 'granted') { return notificationGranted(message, opts, onclick); } diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index b6636de5767..d761a844be9 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -29,6 +29,7 @@ lineBefore = this.lineBefore(text, textArea); lineAfter = this.lineAfter(text, textArea); if (lineBefore === blockTag && lineAfter === blockTag) { + // To remove the block tag we have to select the line before & after if (blockTag != null) { textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1); textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1); @@ -63,11 +64,11 @@ if (!inserted) { try { document.execCommand("ms-beginUndoUnit"); - } catch (undefined) {} + } catch (error) {} textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText); try { document.execCommand("ms-endUndoUnit"); - } catch (undefined) {} + } catch (error) {} } return this.moveCursor(textArea, tag, wrap); }; diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 533310cc87c..f84a20cf0fe 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -7,6 +7,8 @@ if ((base = w.gl).utils == null) { base.utils = {}; } + // Returns an array containing the value(s) of the + // of the key passed as an argument w.gl.utils.getParameterValues = function(sParam) { var i, sPageURL, sParameterName, sURLVariables, values; sPageURL = decodeURIComponent(window.location.search.substring(1)); @@ -23,6 +25,8 @@ } return values; }; + // @param {Object} params - url keys and value to merge + // @param {String} url w.gl.utils.mergeUrlParams = function(params, url) { var lastChar, newUrl, paramName, paramValue, pattern; newUrl = decodeURIComponent(url); @@ -37,12 +41,14 @@ newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue; } } + // Remove a trailing ampersand lastChar = newUrl[newUrl.length - 1]; if (lastChar === '&') { newUrl = newUrl.slice(0, -1); } return newUrl; }; + // removes parameter query string from url. returns the modified url w.gl.utils.removeParamQueryString = function(url, param) { var urlVariables, variables; url = decodeURIComponent(url); diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index f145bd3ad74..93daea1dce7 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -1,17 +1,49 @@ - +// LineHighlighter +// +// Handles single- and multi-line selection and highlight for blob views. +// /*= require jquery.scrollTo */ +// +// ### Example Markup +// +//
    +//
    +//
    +// 1 +// 2 +// 3 +// 4 +// 5 +//
    +//
    +//         
    +//           ...
    +//           ...
    +//           ...
    +//           ...
    +//           ...
    +//         
    +//       
    +//
    +//
    +// (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; this.LineHighlighter = (function() { + // CSS class applied to highlighted lines LineHighlighter.prototype.highlightClass = 'hll'; + // Internal copy of location.hash so we're not dependent on `location` in tests LineHighlighter.prototype._hash = ''; function LineHighlighter(hash) { var range; if (hash == null) { + // Initialize a LineHighlighter object + // + // hash - String URL hash for dependency injection in tests hash = location.hash; } this.setHash = bind(this.setHash, this); @@ -24,6 +56,8 @@ if (range[0]) { this.highlightRange(range); $.scrollTo("#L" + range[0], { + // Scroll to the first highlighted line on initial load + // Offset -50 for the sticky top bar, and another -100 for some context offset: -150 }); } @@ -32,6 +66,12 @@ LineHighlighter.prototype.bindEvents = function() { $('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler); + // While it may seem odd to bind to the mousedown event and then throw away + // the click event, there is a method to our madness. + // + // If not done this way, the line number anchor will sometimes keep its + // active state even when the event is cancelled, resulting in an ugly border + // around the link and/or a persisted underline text decoration. return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) { return event.preventDefault(); }); @@ -44,6 +84,8 @@ lineNumber = $(event.target).closest('a').data('line-number'); current = this.hashToRange(this._hash); if (!(current[0] && event.shiftKey)) { + // If there's no current selection, or there is but Shift wasn't held, + // treat this like a single-line selection. this.setHash(lineNumber); return this.highlightLine(lineNumber); } else if (event.shiftKey) { @@ -59,10 +101,23 @@ LineHighlighter.prototype.clearHighlight = function() { return $("." + this.highlightClass).removeClass(this.highlightClass); + // Unhighlight previously highlighted lines }; + // Convert a URL hash String into line numbers + // + // hash - Hash String + // + // Examples: + // + // hashToRange('#L5') # => [5, null] + // hashToRange('#L5-15') # => [5, 15] + // hashToRange('#foo') # => [null, null] + // + // Returns an Array LineHighlighter.prototype.hashToRange = function(hash) { var first, last, matches; + //?L(\d+)(?:-(\d+))?$/) matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/); if (matches && matches.length) { first = parseInt(matches[1]); @@ -73,10 +128,16 @@ } }; + // Highlight a single line + // + // lineNumber - Line number to highlight LineHighlighter.prototype.highlightLine = function(lineNumber) { return $("#LC" + lineNumber).addClass(this.highlightClass); }; + // Highlight all lines within a range + // + // range - Array containing the starting and ending line numbers LineHighlighter.prototype.highlightRange = function(range) { var i, lineNumber, ref, ref1, results; if (range[1]) { @@ -90,6 +151,7 @@ } }; + // Set the URL hash string LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) { var hash; if (lastLineNumber) { @@ -101,10 +163,15 @@ return this.__setLocationHash__(hash); }; + // Make the actual hash change in the browser + // + // This method is stubbed in tests. LineHighlighter.prototype.__setLocationHash__ = function(value) { return history.pushState({ turbolinks: false, url: value + // We're using pushState instead of assigning location.hash directly to + // prevent the page from scrolling on the hashchange event }, document.title, value); }; diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index 56ebf84c4f6..05644b3d03c 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -1,10 +1,6 @@ /*= require jquery.waitforimages */ - - /*= require task_list */ - - /*= require merge_request_tabs */ (function() { @@ -12,6 +8,11 @@ this.MergeRequest = (function() { function MergeRequest(opts) { + // Initialize MergeRequest behavior + // + // Options: + // action - String, current controller action + // this.opts = opts != null ? opts : {}; this.submitNoteForm = bind(this.submitNoteForm, this); this.$el = $('.merge-request'); @@ -21,6 +22,7 @@ }; })(this)); this.initTabs(); + // Prevent duplicate event bindings this.disableTaskList(); this.initMRBtnListeners(); if ($("a.btn-close").length) { @@ -28,14 +30,17 @@ } } + // Local jQuery finder MergeRequest.prototype.$ = function(selector) { return this.$el.find(selector); }; MergeRequest.prototype.initTabs = function() { if (this.opts.action !== 'new') { + // `MergeRequests#new` has no tab-persisting or lazy-loading behavior window.mrTabs = new MergeRequestTabs(this.opts); } else { + // Show the first tab (Commits) return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show'); } }; @@ -96,6 +101,8 @@ url: $('form.js-issuable-update').attr('action'), data: patchData }); + // TODO (rspeicher): Make the merge request description inline-editable like a + // note so that we can re-use its form here }; return MergeRequest; diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index ad08209d61e..5316101457c 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -1,6 +1,49 @@ - +// MergeRequestTabs +// +// Handles persisting and restoring the current tab selection and lazily-loading +// content on the MergeRequests#show page. +// /*= require jquery.cookie */ +// +// ### Example Markup +// +// +// +//
    +//
    +// Notes Content +//
    +//
    +// Commits Content +//
    +//
    +// Diffs Content +//
    +//
    +// +//
    +//
    +// Loading Animation +//
    +//
    +// (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -19,6 +62,7 @@ this.setCurrentAction = bind(this.setCurrentAction, this); this.tabShown = bind(this.tabShown, this); this.showTab = bind(this.showTab, this); + // Store the `location` object, allowing for easier stubbing in tests this._location = location; this.bindEvents(); this.activateTab(this.opts.action); @@ -77,6 +121,7 @@ } }; + // Activate a tab based on the current action MergeRequestTabs.prototype.activateTab = function(action) { if (action === 'show') { action = 'notes'; @@ -84,20 +129,48 @@ return $(".merge-request-tabs a[data-action='" + action + "']").tab('show'); }; + // Replaces the current Merge Request-specific action in the URL with a new one + // + // If the action is "notes", the URL is reset to the standard + // `MergeRequests#show` route. + // + // Examples: + // + // location.pathname # => "/namespace/project/merge_requests/1" + // setCurrentAction('diffs') + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // setCurrentAction('notes') + // location.pathname # => "/namespace/project/merge_requests/1" + // + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // setCurrentAction('commits') + // location.pathname # => "/namespace/project/merge_requests/1/commits" + // + // Returns the new URL String MergeRequestTabs.prototype.setCurrentAction = function(action) { var new_state; + // Normalize action, just to be safe if (action === 'show') { action = 'notes'; } this.currentAction = action; + // Remove a trailing '/commits' or '/diffs' new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, ''); + // Append the new action if we're on a tab other than 'notes' if (action !== 'notes') { new_state += "/" + action; } + // Ensure parameters and hash come along for the ride new_state += this._location.search + this._location.hash; history.replaceState({ turbolinks: true, url: new_state + // Replace the current history state with the new one without breaking + // Turbolinks' history. + // + // See https://github.com/rails/turbolinks/issues/363 }, document.title, new_state); return new_state; }; @@ -191,6 +264,7 @@ }); }; +<<<<<<< a79ff9346b73079148cc4ecc81da82804bb51f3c MergeRequestTabs.prototype.loadPipelines = function(source) { if (this.pipelinesLoaded) { return; @@ -206,6 +280,11 @@ }); }; +======= + // Show or hide the loading spinner + // + // status - Boolean, true to show, false to hide +>>>>>>> Restore comments lost when converting CoffeeScript to JavaScript MergeRequestTabs.prototype.toggleLoading = function(status) { return $('.mr-loading-status .loading').toggle(status); }; @@ -232,6 +311,7 @@ MergeRequestTabs.prototype.diffViewType = function() { return $('.inline-parallel-buttons a.active').data('view-type'); + // Returns diff view type }; MergeRequestTabs.prototype.expandViewContainer = function() { @@ -245,6 +325,8 @@ if ($gutterIcon.is('.fa-angle-double-right')) { return $gutterIcon.closest('a').trigger('click', [true]); } + // Wait until listeners are set + // Only when sidebar is expanded }, 0); }; @@ -259,6 +341,9 @@ return $gutterIcon.closest('a').trigger('click', [true]); } }, 0); + // Expand the issuable sidebar unless the user explicitly collapsed it + // Wait until listeners are set + // Only when sidebar is collapsed }; return MergeRequestTabs; diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js index bd35b6f679d..7bbcdf59838 100644 --- a/app/assets/javascripts/merge_request_widget.js +++ b/app/assets/javascripts/merge_request_widget.js @@ -3,6 +3,12 @@ this.MergeRequestWidget = (function() { function MergeRequestWidget(opts) { + // Initialize MergeRequestWidget behavior + // + // check_enable - Boolean, whether to check automerge status + // merge_check_url - String, URL to use to check automerge status + // ci_status_url - String, URL to use to check CI status + // this.opts = opts; $('#modal_merge_info').modal({ show: false @@ -118,6 +124,8 @@ if (data.coverage) { _this.showCICoverage(data.coverage); } + // The first check should only update the UI, a notification + // should only be displayed on status changes if (showNotification && !_this.firstCICheck) { status = _this.ciLabelForStatus(data.status); if (status === "preparing") { diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index e8d51da7d58..bc1a99057d9 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -110,6 +110,7 @@ }, update: function(event, ui) { var data; + // Prevents sorting from container which element has been removed. if ($(this).find(ui.item).length > 0) { data = $(this).sortable("serialize"); return Milestone.sortIssues(data); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index e897ebdf630..c8031174dd2 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -92,6 +92,7 @@ }, hidden: function() { $selectbox.hide(); + // display:block overrides the hide-collapse rule return $value.css('display', ''); }, clicked: function(selected, $el, e) { diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch-graph.js index c0fec1f8607..91132af273a 100644 --- a/app/assets/javascripts/network/branch-graph.js +++ b/app/assets/javascripts/network/branch-graph.js @@ -90,6 +90,7 @@ results = []; while (k < this.mspace) { this.colors.push(Raphael.getColor(.8)); + // Skipping a few colors in the spectrum to get more contrast between colors Raphael.getColor(); Raphael.getColor(); results.push(k++); @@ -112,6 +113,7 @@ for (mm = j = 0, len = ref.length; j < len; mm = ++j) { day = ref[mm]; if (cuday !== day[0] || cumonth !== day[1]) { + // Dates r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({ font: "12px Monaco, monospace", fill: "#BBB" @@ -119,6 +121,7 @@ cuday = day[0]; } if (cumonth !== day[1]) { + // Months r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({ font: "12px Monaco, monospace", fill: "#EEE" @@ -207,6 +210,7 @@ } r = this.r; shortrefs = commit.refs; + // Truncate if longer than 15 chars if (shortrefs.length > 17) { shortrefs = shortrefs.substr(0, 15) + "…"; } @@ -217,6 +221,7 @@ title: commit.refs }); textbox = text.getBBox(); + // Create rectangle based on the size of the textbox rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({ fill: "#000", "fill-opacity": .5, @@ -229,6 +234,7 @@ }); label = r.set(rect, text); label.transform(["t", -rect.getBBox().width - 15, 0]); + // Set text to front return text.toFront(); }; @@ -283,11 +289,13 @@ parentY = this.offsetY + this.unitTime * parentCommit.time; parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space); parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]); + // Set line color if (parentCommit.space <= commit.space) { color = this.colors[commit.space]; } else { color = this.colors[parentCommit.space]; } + // Build line shape if (parent[1] === commit.space) { offset = [0, 5]; arrow = "l-2,5,4,0,-2,-5,0,5"; @@ -298,13 +306,17 @@ offset = [-3, 3]; arrow = "l-5,0,2,4,3,-4,-4,2"; } + // Start point route = ["M", x + offset[0], y + offset[1]]; + // Add arrow if not first parent if (i > 0) { route.push(arrow); } + // Circumvent if overlap if (commit.space !== parentCommit.space || commit.space !== parent[1]) { route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5); } + // End point route.push("L", parentX1, parentY); results.push(r.path(route).attr({ stroke: color, @@ -325,6 +337,7 @@ "fill-opacity": .5, stroke: "none" }); + // Displayed in the center return this.element.scrollTop(y - this.graphHeight / 2); } }; diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js index 6a7422a7755..67c3e645364 100644 --- a/app/assets/javascripts/network/network_bundle.js +++ b/app/assets/javascripts/network/network_bundle.js @@ -1,4 +1,9 @@ - +// This is a manifest file that'll be compiled into including all the files listed below. +// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically +// be included in the compiled file accessible from http://example.com/assets/application.js +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// /*= require_tree . */ (function() { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index ebe9ff2b8c4..c6854f703fb 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,22 +1,10 @@ /*= require autosave */ - - /*= require autosize */ - - /*= require dropzone */ - - /*= require dropzone_input */ - - /*= require gfm_auto_complete */ - - /*= require jquery.atwho */ - - /*= require task_list */ (function() { @@ -60,26 +48,43 @@ } Notes.prototype.addBinding = function() { + // add note to UI after creation $(document).on("ajax:success", ".js-main-target-form", this.addNote); $(document).on("ajax:success", ".js-discussion-note-form", this.addDiscussionNote); + // catch note ajax errors $(document).on("ajax:error", ".js-main-target-form", this.addNoteError); + // change note in UI after update $(document).on("ajax:success", "form.edit-note", this.updateNote); + // Edit note link $(document).on("click", ".js-note-edit", this.showEditForm); $(document).on("click", ".note-edit-cancel", this.cancelEdit); + // Reopen and close actions for Issue/MR combined with note form submit $(document).on("click", ".js-comment-button", this.updateCloseButton); $(document).on("keyup input", ".js-note-text", this.updateTargetButtons); + // resolve a discussion $(document).on('click', '.js-comment-resolve-button', this.resolveDiscussion); + // remove a note (in general) $(document).on("click", ".js-note-delete", this.removeNote); + // delete note attachment $(document).on("click", ".js-note-attachment-delete", this.removeAttachment); + // reset main target form after submit $(document).on("ajax:complete", ".js-main-target-form", this.reenableTargetFormSubmitButton); $(document).on("ajax:success", ".js-main-target-form", this.resetMainTargetForm); + // reset main target form when clicking discard $(document).on("click", ".js-note-discard", this.resetMainTargetForm); + // update the file name when an attachment is selected $(document).on("change", ".js-note-attachment-input", this.updateFormAttachment); + // reply to diff/discussion notes $(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote); + // add diff note $(document).on("click", ".js-add-diff-note-button", this.addDiffNote); + // hide diff note form $(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm); + // fetch notes when tab becomes visible $(document).on("visibilitychange", this.visibilityChange); + // when issue status changes, we need to refresh data $(document).on("issuable:change", this.refresh); + // when a key is clicked on the notes return $(document).on("keydown", ".js-note-text", this.keydownNoteText); }; @@ -112,6 +117,7 @@ return; } $textarea = $(e.target); + // Edit previous note when UP arrow is hit switch (e.which) { case 38: if ($textarea.val() !== '') { @@ -123,6 +129,7 @@ return myLastNoteEditBtn.trigger('click', [true, myLastNote]); } break; + // Cancel creating diff note or editing any note when ESCAPE is hit case 27: discussionNoteForm = $textarea.closest('.js-discussion-note-form'); if (discussionNoteForm.length) { @@ -247,10 +254,13 @@ votesBlock = $('.js-awards-block').eq(0); gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name); return gl.awardsHandler.scrollToAwards(); + // render note if it not present in loaded list + // or skip if rendered } else if (this.isNewNote(note)) { this.note_ids.push(note.id); $notesList = $('ul.main-notes-list'); $notesList.append(note.html).syntaxHighlight(); + // Update datetime format on the recent note gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false); this.initTaskList(); this.refresh(); @@ -291,19 +301,26 @@ row = form.closest("tr"); note_html = $(note.html); note_html.syntaxHighlight(); + // is this the first note of discussion? discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']"); if ((note.original_discussion_id != null) && discussionContainer.length === 0) { discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']"); } if (discussionContainer.length === 0) { + // insert the note and the reply button after the temp row row.after(note.diff_discussion_html); + // remove the note (will be added again below) row.next().find(".note").remove(); + // Before that, the container didn't exist discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']"); + // Add note to 'Changes' page discussions discussionContainer.append(note_html); + // Init discussion on 'Discussion' page if it is merge request page if ($('body').attr('data-page').indexOf('projects:merge_request') === 0) { $('ul.main-notes-list').append(note.discussion_html).syntaxHighlight(); } } else { + // append new note to all matching discussions discussionContainer.append(note_html); } @@ -327,7 +344,9 @@ Notes.prototype.resetMainTargetForm = function(e) { var form; form = $(".js-main-target-form"); + // remove validation errors form.find(".js-errors").remove(); + // reset text and preview form.find(".js-md-write-button").click(); form.find(".js-note-text").val("").trigger("input"); form.find(".js-note-text").data("autosave").reset(); @@ -354,9 +373,13 @@ Notes.prototype.setupMainTargetNoteForm = function() { var form; + // find the form form = $(".js-new-note-form"); + // Set a global clone of the form for later cloning this.formClone = form.clone(); + // show the form this.setupNoteForm(form); + // fix classes form.removeClass("js-new-note-form"); form.addClass("js-main-target-form"); form.find("#note_line_code").remove(); @@ -421,6 +444,7 @@ } this.renderDiscussionNote(note); + // cleanup after successfully creating a diff/discussion note this.removeDiscussionNoteForm($form); }; @@ -433,10 +457,12 @@ Notes.prototype.updateNote = function(_xhr, note, _status) { var $html, $note_li; + // Convert returned HTML to a jQuery object so we can modify it further $html = $(note.html); gl.utils.localTimeAgo($('.js-timeago', $html)); $html.syntaxHighlight(); $html.find('.js-task-list-container').taskList('enable'); + // Find the note's `li` element by ID and replace it with the updated HTML $note_li = $('.note-row-' + note.id); $note_li.replaceWith($html); @@ -461,15 +487,20 @@ note.addClass("is-editting"); form = note.find(".note-edit-form"); form.addClass('current-note-edit-form'); + // Show the attachment delete link note.find(".js-note-attachment-delete").show(); done = function($noteText) { var noteTextVal; + // Neat little trick to put the cursor at the end noteTextVal = $noteText.val(); + // Store the original note text in a data attribute to retrieve if a user cancels edit. form.find('form.edit-note').data('original-note', noteTextVal); return $noteText.val('').val(noteTextVal); }; new GLForm(form); if ((scrollTo != null) && (myLastNote != null)) { + // scroll to the bottom + // so the open of the last element doesn't make a jump $('html, body').scrollTop($(document).height()); return $('html, body').animate({ scrollTop: myLastNote.offset().top - 150 @@ -505,6 +536,7 @@ form = note.find(".current-note-edit-form"); note.removeClass("is-editting"); form.removeClass("current-note-edit-form"); + // Replace markdown textarea text with original note text. return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note')); }; @@ -520,6 +552,9 @@ var noteId; noteId = $(e.currentTarget).closest(".note").attr("id"); $(".note[id='" + noteId + "']").each((function(_this) { + // A same note appears in the "Discussion" and in the "Changes" tab, we have + // to remove all. Using $(".note[id='noteId']") ensure we get all the notes, + // where $("#noteId") would return only one. return function(i, el) { var note, notes; note = $(el); @@ -533,13 +568,17 @@ } } + // check if this is the last note for this line if (notes.find(".note").length === 1) { + // "Discussions" tab notes.closest(".timeline-entry").remove(); + // "Changes" tab / commit view notes.closest("tr").remove(); } return note.remove(); }; })(this)); + // Decrement the "Discussions" counter only once return this.updateNotesCount(-1); }; @@ -571,10 +610,12 @@ var form, replyLink; form = this.formClone.clone(); replyLink = $(e.target).closest(".js-discussion-reply-button"); + // insert the form after the button replyLink .closest('.discussion-reply-holder') .hide() .after(form); + // show the form return this.setupDiscussionNoteForm(replyLink, form); }; @@ -589,6 +630,7 @@ */ Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) { + // setup note target form.attr('id', "new-discussion-note-form-" + (dataHolder.data("discussionId"))); form.attr("data-line-code", dataHolder.data("lineCode")); form.find("#note_type").val(dataHolder.data("noteType")); @@ -636,6 +678,7 @@ addForm = false; notesContentSelector = ".notes_content"; rowCssToAdd = "
    "; + // In parallel view, look inside the correct left/right pane if (this.isParallelView()) { lineType = $link.data("lineType"); notesContentSelector += "." + lineType; @@ -652,6 +695,7 @@ e.target = replyButton[0]; $.proxy(this.replyToDiscussionNote, replyButton[0], e).call(); } else { + // In parallel view, the form may not be present in one of the panes noteForm = notesContent.find(".js-discussion-note-form"); if (noteForm.length === 0) { addForm = true; @@ -659,6 +703,7 @@ } } } else { + // add a notes row and insert the form row.after(rowCssToAdd); nextRow = row.next(); notesContent = nextRow.find(notesContentSelector); @@ -667,6 +712,7 @@ if (addForm) { newForm = this.formClone.clone(); newForm.appendTo(notesContent); + // show the form return this.setupDiscussionNoteForm($link, newForm); } }; @@ -685,12 +731,15 @@ glForm = form.data('gl-form'); glForm.destroy(); form.find(".js-note-text").data("autosave").reset(); + // show the reply button (will only work for replies) form .prev('.discussion-reply-holder') .show(); if (row.is(".js-temp-notes-holder")) { + // remove temporary row for diff lines return row.remove(); } else { + // only remove the form return form.remove(); } }; @@ -712,6 +761,7 @@ Notes.prototype.updateFormAttachment = function() { var filename, form; form = $(this).closest("form"); + // get only the basename filename = $(this).val().replace(/^.*[\\\/]/, ""); return form.find(".js-attachment-filename").text(filename); }; diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js index 5fd75799640..5200487814f 100644 --- a/app/assets/javascripts/preview_markdown.js +++ b/app/assets/javascripts/preview_markdown.js @@ -1,9 +1,15 @@ +// MarkdownPreview +// +// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview, +// and showing a warning when more than `x` users are referenced. +// (function() { var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector; this.MarkdownPreview = (function() { function MarkdownPreview() {} + // Minimum number of users referenced before triggering a warning MarkdownPreview.prototype.referenceThreshold = 10; MarkdownPreview.prototype.ajaxCache = {}; @@ -101,8 +107,10 @@ return; } lastTextareaPreviewed = $form.find('textarea.markdown-area'); + // toggle tabs $form.find(writeButtonSelector).parent().removeClass('active'); $form.find(previewButtonSelector).parent().addClass('active'); + // toggle content $form.find('.md-write-holder').hide(); $form.find('.md-preview-holder').show(); return markdownPreview.showPreview($form); @@ -113,8 +121,10 @@ return; } lastTextareaPreviewed = null; + // toggle tabs $form.find(writeButtonSelector).parent().addClass('active'); $form.find(previewButtonSelector).parent().removeClass('active'); + // toggle content $form.find('.md-write-holder').show(); $form.find('textarea.markdown-area').focus(); return $form.find('.md-preview-holder').hide(); diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js index a3eea316f67..30cd6f6e470 100644 --- a/app/assets/javascripts/profile/gl_crop.js +++ b/app/assets/javascripts/profile/gl_crop.js @@ -5,6 +5,7 @@ GitLabCrop = (function() { var FILENAMEREGEX; + // Matches everything but the file name FILENAMEREGEX = /^.*[\\\/]/; function GitLabCrop(input, opts) { @@ -17,11 +18,18 @@ this.onModalShow = bind(this.onModalShow, this); this.onPickImageClick = bind(this.onPickImageClick, this); this.fileInput = $(input); + // We should rename to avoid spec to fail + // Form will submit the proper input filed with a file using FormData this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger"); + // Set defaults this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg; + // Required params + // Ensure needed elements are jquery objects + // If selector is provided we will convert them to a jQuery Object this.filename = this.getElement(this.filename); this.previewImage = this.getElement(this.previewImage); this.pickImageEl = this.getElement(this.pickImageEl); + // Modal elements usually are outside the @form element this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop; this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn; this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; @@ -93,8 +101,8 @@ return this.modalCropImg.attr('src', '').cropper('destroy'); }; - GitLabCrop.prototype.onUploadImageBtnClick = function(e) { - e.preventDefault(); + GitLabCrop.prototype.onUploadImageBtnClick = function(e) { // Remove attached image + e.preventDefault(); // Destroy cropper instance this.setBlob(); this.setPreview(); this.modalCrop.modal('hide'); diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index ed1d87abafe..60f9fba5777 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -11,9 +11,11 @@ this.form = (ref = opts.form) != null ? ref : $('.edit-user'); $('.js-preferences-form').on('change.preference', 'input[type=radio]', function() { return $(this).parents('form').submit(); + // Automatically submit the Preferences form when any of its radio buttons change }); $('#user_notification_email').on('change', function() { return $(this).parents('form').submit(); + // Automatically submit email form when it changes }); $('.update-username').on('ajax:before', function() { $('.loading-username').show(); @@ -76,6 +78,7 @@ }, complete: function() { window.scrollTo(0, 0); + // Enable submit button after requests ends return self.form.find(':input[disabled]').enable(); } }); @@ -93,6 +96,7 @@ if (comment && comment.length > 1 && $title.val() === '') { return $title.val(comment[1]).change(); } + // Extract the SSH Key title from its comment }); if (gl.utils.getPagePath() === 'profiles') { return new Profile(); diff --git a/app/assets/javascripts/profile/profile_bundle.js b/app/assets/javascripts/profile/profile_bundle.js index b95faadc8e7..d6e4d9f7ad8 100644 --- a/app/assets/javascripts/profile/profile_bundle.js +++ b/app/assets/javascripts/profile/profile_bundle.js @@ -3,5 +3,4 @@ (function() { - }).call(this); diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 66e097c0a28..a6c015299a0 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -11,7 +11,13 @@ url = $("#project_clone").val(); $('#project_clone').val(url); return $('.clone').text(url); + // Git protocol switcher + // Remove the active class for all buttons (ssh, http, kerberos if shown) + // Add the active class for the clicked button + // Update the input field + // Update the command line instructions }); + // Ref switcher this.initRefSwitcher(); $('.project-refs-select').on('change', function() { return $(this).parents('form').submit(); diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 4925f0519f0..5bf900f3e1d 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -13,8 +13,11 @@ this.selectRowUp = bind(this.selectRowUp, this); this.filePaths = {}; this.inputElement = this.element.find(".file-finder-input"); + // init event this.initEvent(); + // focus text input box this.inputElement.focus(); + // load file list this.load(this.options.url); } @@ -42,6 +45,7 @@ } } }); + // init event }; ProjectFindFile.prototype.findFile = function() { @@ -49,8 +53,10 @@ searchText = this.inputElement.val(); result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths; return this.renderList(result, searchText); + // find file }; + // files pathes load ProjectFindFile.prototype.load = function(url) { return $.ajax({ url: url, @@ -67,6 +73,7 @@ }); }; + // render result ProjectFindFile.prototype.renderList = function(filePaths, searchText) { var blobItemUrl, filePath, html, i, j, len, matches, results; this.element.find(".tree-table > tbody").empty(); @@ -86,6 +93,7 @@ return results; }; + // highlight text(awefwbwgtc -> awefwbwgtc ) highlighter = function(element, text, matches) { var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched; lastIndex = 0; @@ -110,6 +118,7 @@ return element.append(document.createTextNode(text.substring(lastIndex))); }; + // make tbody row html ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) { var $tr; $tr = $(""); diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js index 8ca4c427912..c8cfc9a9ba8 100644 --- a/app/assets/javascripts/project_show.js +++ b/app/assets/javascripts/project_show.js @@ -7,3 +7,5 @@ })(); }).call(this); + +// I kept class for future diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js index 4f415b05dbc..04fb49552e8 100644 --- a/app/assets/javascripts/projects_list.js +++ b/app/assets/javascripts/projects_list.js @@ -33,6 +33,7 @@ $('.projects-list-holder').replaceWith(data.html); return history.replaceState({ page: project_filter_url + // Change url so if user reload a page - search results are saved }, document.title, project_filter_url); }, dataType: "json" diff --git a/app/assets/javascripts/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branch_dropdown.js.es6 index 6738dc8862d..983322cbecc 100644 --- a/app/assets/javascripts/protected_branch_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branch_dropdown.js.es6 @@ -45,6 +45,7 @@ class ProtectedBranchDropdown { } onClickCreateWildcard() { + // Refresh the dropdown's data, which ends up calling `getProtectedBranches` this.$dropdown.data('glDropdown').remote.execute(); this.$dropdown.data('glDropdown').selectRowAtIndex(0); } diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 227e8c696b4..8abb09c626f 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -24,6 +24,7 @@ this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this); this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this); this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || ''; + // Dropdown Element this.dropdown = this.wrap.find('.dropdown'); this.dropdownContent = this.dropdown.find('.dropdown-content'); this.locationBadgeEl = this.getElement('.location-badge'); @@ -35,6 +36,7 @@ this.repositoryInputEl = this.getElement('#repository_ref'); this.clearInput = this.getElement('.js-clear-input'); this.saveOriginalState(); + // Only when user is logged in if (gon.current_user_id) { this.createAutocomplete(); } @@ -43,6 +45,7 @@ this.bindEvents(); } + // Finds an element inside wrapper element SearchAutocomplete.prototype.getElement = function(selector) { return this.wrap.find(selector); }; @@ -82,6 +85,7 @@ } return; } + // Prevent multiple ajax calls if (this.loadingSuggestions) { return; } @@ -92,14 +96,17 @@ term: term }, function(response) { var data, firstCategory, i, lastCategory, len, suggestion; + // Hide dropdown menu if no suggestions returns if (!response.length) { _this.disableAutocomplete(); return; } data = []; + // List results firstCategory = true; for (i = 0, len = response.length; i < len; i++) { suggestion = response[i]; + // Add group header before list each group if (lastCategory !== suggestion.category) { if (!firstCategory) { data.push('separator'); @@ -119,6 +126,7 @@ url: suggestion.url }); } + // Add option to proceed with the search if (data.length) { data.push('separator'); data.push({ @@ -169,11 +177,13 @@ SearchAutocomplete.prototype.serializeState = function() { return { + // Search Criteria search_project_id: this.projectInputEl.val(), group_id: this.groupInputEl.val(), search_code: this.searchCodeInputEl.val(), repository_ref: this.repositoryInputEl.val(), scope: this.scopeInputEl.val(), + // Location badge _location: this.locationBadgeEl.text() }; }; @@ -194,6 +204,7 @@ SearchAutocomplete.prototype.enableAutocomplete = function() { var _this; + // No need to enable anything if user is not logged in if (!gon.current_user_id) { return; } @@ -206,18 +217,22 @@ }; SearchAutocomplete.prototype.onSearchInputKeyDown = function() { + // Saves last length of the entered text return this.saveTextLength(); }; SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) { switch (e.keyCode) { case KEYCODE.BACKSPACE: + // when trying to remove the location badge if (this.lastTextLength === 0 && this.badgePresent()) { this.removeLocationBadge(); } + // When removing the last character and no badge is present if (this.lastTextLength === 1) { this.disableAutocomplete(); } + // When removing any character from existin value if (this.lastTextLength > 1) { this.enableAutocomplete(); } @@ -232,9 +247,12 @@ case KEYCODE.DOWN: return; default: + // Handle the case when deleting the input value other than backspace + // e.g. Pressing ctrl + backspace or ctrl + x if (this.searchInput.val() === '') { this.disableAutocomplete(); } else { + // We should display the menu only when input is not empty if (e.keyCode !== KEYCODE.ENTER) { this.enableAutocomplete(); } @@ -243,7 +261,9 @@ this.wrap.toggleClass('has-value', !!e.target.value); }; + // Avoid falsy value to be returned SearchAutocomplete.prototype.onSearchInputClick = function(e) { + // Prevents closing the dropdown menu return e.stopImmediatePropagation(); }; @@ -267,6 +287,7 @@ SearchAutocomplete.prototype.onSearchInputBlur = function(e) { this.isFocused = false; this.wrap.removeClass('search-active'); + // If input is blank then restore state if (this.searchInput.val() === '') { return this.restoreOriginalState(); } @@ -311,6 +332,7 @@ results = []; for (i = 0, len = inputs.length; i < len; i++) { input = inputs[i]; + // _location isnt a input if (input === '_location') { break; } diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index 3b28332854a..3aa8536d40a 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -86,6 +86,7 @@ var defaultStopCallback; defaultStopCallback = Mousetrap.stopCallback; return function(e, element, combo) { + // allowed shortcuts if textarea, input, contenteditable are focused if (['ctrl+shift+p', 'command+shift+p'].indexOf(combo) !== -1) { return false; } else { diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js index 6c78914d338..92ce31969e3 100644 --- a/app/assets/javascripts/shortcuts_find_file.js +++ b/app/assets/javascripts/shortcuts_find_file.js @@ -14,8 +14,10 @@ ShortcutsFindFile.__super__.constructor.call(this); _oldStopCallback = Mousetrap.stopCallback; Mousetrap.stopCallback = (function(_this) { + // override to fire shortcuts action when focus in textbox return function(event, element, combo) { if (element === _this.projectFindFile.inputElement[0] && (combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter')) { + // when press up/down key in textbox, cusor prevent to move to home/end event.preventDefault(); return false; } diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js index 3f3a8a9dfd9..235bf4f95ec 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/shortcuts_issuable.js @@ -1,7 +1,5 @@ /*= require mousetrap */ - - /*= require shortcuts_navigation */ (function() { @@ -43,16 +41,20 @@ if (selected.trim() === "") { return; } + // Put a '>' character before each non-empty line in the selection quote = _.map(selected.split("\n"), function(val) { if (val.trim() !== '') { return "> " + val + "\n"; } }); + // If replyField already has some content, add a newline before our quote separator = replyField.val().trim() !== "" && "\n" || ''; replyField.val(function(_, current) { return current + separator + quote.join('') + "\n"; }); + // Trigger autosave for the added text replyField.trigger('input'); + // Focus the input field return replyField.focus(); } }; diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js index dba62638c78..2ae7bf5fc15 100644 --- a/app/assets/javascripts/syntax_highlight.js +++ b/app/assets/javascripts/syntax_highlight.js @@ -1,9 +1,20 @@ +// Syntax Highlighter +// +// Applies a syntax highlighting color scheme CSS class to any element with the +// `js-syntax-highlight` class +// +// ### Example Markup +// +//
    +// (function() { $.fn.syntaxHighlight = function() { var $children; if ($(this).hasClass('js-syntax-highlight')) { + // Given the element itself, apply highlighting return $(this).addClass(gon.user_color_scheme); } else { + // Given a parent element, recurse to any of its applicable children $children = $(this).find('.js-syntax-highlight'); if ($children.length) { return $children.syntaxHighlight(); diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index 23eda7d44ca..93421649ac7 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -129,16 +129,21 @@ var currPage, currPages, newPages, pageParams, url; currPages = this.getTotalPages(); currPage = this.getCurrentPage(); + // Refresh if no remaining Todos if (!total) { location.reload(); return; } + // Do nothing if no pagination if (!currPages) { return; } newPages = Math.ceil(total / this.getTodosPerPage()); + // Includes query strings url = location.href; + // If new total of pages is different than we have now if (newPages !== currPages) { + // Redirect to previous page if there's one available if (currPages > 1 && currPage === currPages) { pageParams = { page: currPages - 1 @@ -155,6 +160,7 @@ if (!todoLink) { return; } + // Allow Meta-Click or Mouse3-click to open in a new tab if (e.metaKey || e.which === 2) { e.preventDefault(); return window.open(todoLink, '_blank'); diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index 78e159a7ed9..9b7be17c4fe 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -2,6 +2,8 @@ this.TreeView = (function() { function TreeView() { this.initKeyNav(); + // Code browser tree slider + // Make the entire tree-item row clickable, but not if clicking another link (like a commit message) $(".tree-content-holder .tree-item").on('click', function(e) { var $clickedEl, path; $clickedEl = $(e.target); @@ -15,6 +17,7 @@ } } }); + // Show the "Loading commit data" for only the first element $('span.log_loading:first').removeClass('hide'); } diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index 9ba847fb0c2..ce2930c7fc7 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -1,3 +1,7 @@ +// Authenticate U2F (universal 2nd factor) devices for users to authenticate with. +// +// State Flow #1: setup -> in_progress -> authenticated -> POST to server +// State Flow #2: setup -> in_progress -> error -> setup (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -15,6 +19,17 @@ this.appId = u2fParams.app_id; this.challenge = u2fParams.challenge; this.signRequests = u2fParams.sign_requests.map(function(request) { + // The U2F Javascript API v1.1 requires a single challenge, with + // _no challenges per-request_. The U2F Javascript API v1.0 requires a + // challenge per-request, which is done by copying the single challenge + // into every request. + // + // In either case, we don't need the per-request challenges that the server + // has generated, so we can remove them. + // + // Note: The server library fixes this behaviour in (unreleased) version 1.0.0. + // This can be removed once we upgrade. + // https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4 return _(request).omit('challenge'); }); } @@ -41,6 +56,7 @@ })(this), 10); }; + // Rendering # U2FAuthenticate.prototype.templates = { "notSupported": "#js-authenticate-u2f-not-supported", "setup": '#js-authenticate-u2f-setup', @@ -75,6 +91,8 @@ U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) { this.renderTemplate('authenticated'); + // Prefer to do this instead of interpolating using Underscore templates + // because of JSON escaping issues. return this.container.find("#js-device-response").val(deviceResponse); }; diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index c87e0840df3..926912fa988 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -1,3 +1,7 @@ +// Register U2F (universal 2nd factor) devices for users to authenticate with. +// +// State Flow #1: setup -> in_progress -> registered -> POST to server +// State Flow #2: setup -> in_progress -> error -> setup (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -39,6 +43,7 @@ })(this), 10); }; + // Rendering # U2FRegister.prototype.templates = { "notSupported": "#js-register-u2f-not-supported", "setup": '#js-register-u2f-setup', @@ -73,6 +78,8 @@ U2FRegister.prototype.renderRegistered = function(deviceResponse) { this.renderTemplate('registered'); + // Prefer to do this instead of interpolating using Underscore templates + // because of JSON escaping issues. return this.container.find("#js-device-response").val(deviceResponse); }; diff --git a/app/assets/javascripts/user_tabs.js b/app/assets/javascripts/user_tabs.js index e5e75701fee..8a657780eb6 100644 --- a/app/assets/javascripts/user_tabs.js +++ b/app/assets/javascripts/user_tabs.js @@ -1,3 +1,61 @@ +// UserTabs +// +// Handles persisting and restoring the current tab selection and lazily-loading +// content on the Users#show page. +// +// ### Example Markup +// +// +// +//
    +//
    +// Activity Content +//
    +//
    +// Groups Content +//
    +//
    +// Contributed projects content +//
    +//
    +// Projects content +//
    +//
    +// Snippets content +//
    +//
    +// +//
    +//
    +// Loading Animation +//
    +//
    +// (function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; @@ -6,18 +64,23 @@ this.tabShown = bind(this.tabShown, this); var i, item, len, ref, ref1, ref2, ref3; this.action = (ref = opts.action) != null ? ref : 'activity', this.defaultAction = (ref1 = opts.defaultAction) != null ? ref1 : 'activity', this.parentEl = (ref2 = opts.parentEl) != null ? ref2 : $(document); + // Make jQuery object if selector is provided if (typeof this.parentEl === 'string') { this.parentEl = $(this.parentEl); } + // Store the `location` object, allowing for easier stubbing in tests this._location = location; + // Set tab states this.loaded = {}; ref3 = this.parentEl.find('.nav-links a'); for (i = 0, len = ref3.length; i < len; i++) { item = ref3[i]; this.loaded[$(item).attr('data-action')] = false; } + // Actions this.actions = Object.keys(this.loaded); this.bindEvents(); + // Set active tab if (this.action === 'show') { this.action = this.defaultAction; } @@ -25,6 +88,7 @@ } UserTabs.prototype.bindEvents = function() { + // Toggle event listeners return this.parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]').on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', this.tabShown); }; @@ -74,6 +138,7 @@ tabSelector = 'div#' + action; _this.parentEl.find(tabSelector).html(data.html); _this.loaded[action] = true; + // Fix tooltips return gl.utils.localTimeAgo($('.js-timeago', tabSelector)); }; })(this) @@ -97,13 +162,17 @@ UserTabs.prototype.setCurrentAction = function(action) { var new_state, regExp; + // Remove possible actions from URL regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$'); new_state = this._location.pathname; + // remove trailing slashes new_state = new_state.replace(/\/+$/, ""); new_state = new_state.replace(regExp, ''); + // Append the new action if we're on a tab other than 'activity' if (action !== this.defaultAction) { new_state += "/" + action; } + // Ensure parameters and hash come along for the ride new_state += this._location.search + this._location.hash; history.replaceState({ turbolinks: true, diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 90cf551b32e..b8da7c4f297 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -11,6 +11,8 @@ this.daySizeWithSpace = this.daySize + (this.daySpace * 2); this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; this.months = []; + // Loop through the timestamps to create a group of objects + // The group of objects will be grouped based on the day of the week they are this.timestampsTmp = []; var group = 0; @@ -29,12 +31,15 @@ var day = date.getDay(); var count = timestamps[date.getTime() * 0.001]; + // Create a new group array if this is the first day of the week + // or if is first object if ((day === 0 && i !== 0) || i === 0) { this.timestampsTmp.push([]); group++; } var innerArray = this.timestampsTmp[group - 1]; + // Push to the inner array the values that will be used to render map innerArray.push({ count: count || 0, date: date, @@ -42,8 +47,10 @@ }); } + // Init color functions this.colorKey = this.initColorKey(); this.color = this.initColor(); + // Init the svg element this.renderSvg(group); this.renderDays(); this.renderMonths(); diff --git a/app/assets/javascripts/users/users_bundle.js b/app/assets/javascripts/users/users_bundle.js index b95faadc8e7..d6e4d9f7ad8 100644 --- a/app/assets/javascripts/users/users_bundle.js +++ b/app/assets/javascripts/users/users_bundle.js @@ -3,5 +3,4 @@ (function() { - }).call(this); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index bad82868ab0..9c277998db4 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -81,6 +81,7 @@ if (term.length === 0) { showDivider = 0; if (firstUser) { + // Move current user to the front of the list for (index = j = 0, len = users.length; j < len; index = ++j) { obj = users[index]; if (obj.username === firstUser) { @@ -115,6 +116,7 @@ if (showDivider) { users.splice(showDivider, 0, "divider"); } + // Send the data back return callback(users); }); }, @@ -139,6 +141,7 @@ inputId: 'issue_assignee_id', hidden: function(e) { $selectbox.hide(); + // display:block overrides the hide-collapse rule return $value.css('display', ''); }, clicked: function(user, $el, e) { @@ -177,6 +180,7 @@ img = ""; } } + // split into three parts so we can remove the username section if nessesary listWithName = "
  • " + img + " " + user.name + " "; listWithUserName = " " + username + " "; listClosingTags = "
  • "; @@ -215,6 +219,7 @@ }; if (query.term.length === 0) { if (firstUser) { + // Move current user to the front of the list ref = data.results; for (index = j = 0, len = ref.length; j < len; index = ++j) { obj = ref[index]; @@ -271,6 +276,7 @@ return _this.formatSelection.apply(_this, args); }, dropdownCssClass: "ajax-users-dropdown", + // we do not want to escape markup since we are displaying html in results escapeMarkup: function(m) { return m; } @@ -318,6 +324,8 @@ }); }; + // Return users list. Filtered by query + // Only active users retrieved UsersSelect.prototype.users = function(query, options, callback) { var url; url = this.buildUrl(this.usersPath); diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index 71236c6a27d..777b32b41c9 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -1,21 +1,34 @@ - +// Zen Mode (full screen) textarea +// /*= provides zen_mode:enter */ - - /*= provides zen_mode:leave */ - - +// /*= require jquery.scrollTo */ - - /*= require dropzone */ - - /*= require mousetrap */ - - /*= require mousetrap/pause */ +// +// ### Events +// +// `zen_mode:enter` +// +// Fired when the "Edit in fullscreen" link is clicked. +// +// **Synchronicity** Sync +// **Bubbles** Yes +// **Cancelable** No +// **Target** a.js-zen-enter +// +// `zen_mode:leave` +// +// Fired when the "Leave Fullscreen" link is clicked. +// +// **Synchronicity** Sync +// **Bubbles** Yes +// **Cancelable** No +// **Target** a.js-zen-leave +// (function() { this.ZenMode = (function() { function ZenMode() { @@ -40,6 +53,7 @@ }; })(this)); $(document).on('keydown', function(e) { + // Esc if (e.keyCode === 27) { e.preventDefault(); return $(document).trigger('zen_mode:leave'); @@ -52,6 +66,7 @@ this.active_backdrop = $(backdrop); this.active_backdrop.addClass('fullscreen'); this.active_textarea = this.active_backdrop.find('textarea'); + // Prevent a user-resized textarea from persisting to fullscreen this.active_textarea.removeAttr('style'); return this.active_textarea.focus(); }; diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index c1c12b57b53..019ce3b0702 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -1,13 +1,7 @@ /*= require awards_handler */ - - /*= require jquery */ - - /*= require jquery.cookie */ - - /*= require ./fixtures/emoji_menu */ (function() { @@ -33,6 +27,7 @@ return setTimeout(function() { assertFn(); return done(); + // Maybe jasmine.clock here? }, 333); }; diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index 4c52ecd903d..13babb5bfdb 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -8,6 +8,7 @@ beforeEach(function() { fixture.load('behaviors/quick_submit.html'); $('form').submit(function(e) { + // Prevent a form submit from moving us off the testing page return e.preventDefault(); }); return this.spies = { @@ -38,6 +39,8 @@ expect($('input[type=submit]')).toBeDisabled(); return expect($('button[type=submit]')).toBeDisabled(); }); + // We cannot stub `navigator.userAgent` for CI's `rake teaspoon` task, so we'll + // only run the tests that apply to the current platform if (navigator.userAgent.match(/Macintosh/)) { it('responds to Meta+Enter', function() { $('input.quick-submit-input').trigger(keydownEvent()); diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js index 82ee1954a59..d5401fbb0d1 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js @@ -7,7 +7,7 @@ describe("ContributorsGraph", function () { expect(ContributorsGraph.prototype.x_domain).toEqual(20) }) }) - + describe("#set_y_domain", function () { it("sets the y_domain", function () { ContributorsGraph.set_y_domain([{commits: 30}]) @@ -89,7 +89,7 @@ describe("ContributorsGraph", function () { }) describe("ContributorsMasterGraph", function () { - + // TODO: fix or remove //describe("#process_dates", function () { //it("gets and parses dates", function () { @@ -103,7 +103,7 @@ describe("ContributorsMasterGraph", function () { //expect(graph.get_dates).toHaveBeenCalledWith(data) //expect(ContributorsGraph.set_dates).toHaveBeenCalledWith("get") //}) - //}) + //}) describe("#get_dates", function () { it("plucks the date field from data collection", function () { @@ -124,5 +124,5 @@ describe("ContributorsMasterGraph", function () { }) }) - + }) diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index dc6231ebb38..33690c7a5f3 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,7 +1,5 @@ /*= require lib/utils/text_utility */ - - /*= require issue */ (function() { diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 25d3f5b6c04..f09596bd36d 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -1,7 +1,5 @@ /*= require jquery-ui/autocomplete */ - - /*= require new_branch_form */ (function() { diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index ffe49828492..51eb12b41d4 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -1,22 +1,10 @@ /*= require bootstrap */ - - /*= require select2 */ - - /*= require lib/utils/type_utility */ - - /*= require gl_dropdown */ - - /*= require api */ - - /*= require project_select */ - - /*= require project */ (function() { diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 38b3b2653ec..c937a4706f7 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -1,10 +1,6 @@ /*= require right_sidebar */ - - /*= require jquery */ - - /*= require jquery.cookie */ (function() { diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 324f5152780..00d9fc1302a 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -1,19 +1,9 @@ /*= require gl_dropdown */ - - /*= require search_autocomplete */ - - /*= require jquery */ - - /*= require lib/utils/common_utils */ - - /*= require lib/utils/type_utility */ - - /*= require fuzzaldrin-plus */ (function() { @@ -43,6 +33,8 @@ groupName = 'Gitlab Org'; + // Add required attributes to body before starting the test. + // section would be dashboard|group|project addBodyAttributes = function(section) { var $body; if (section == null) { @@ -64,6 +56,7 @@ } }; + // Mock `gl` object in window for dashboard specific page. App code will need it. mockDashboardOptions = function() { window.gl || (window.gl = {}); return window.gl.dashboardOptions = { @@ -72,6 +65,7 @@ }; }; + // Mock `gl` object in window for project specific page. App code will need it. mockProjectOptions = function() { window.gl || (window.gl = {}); return window.gl.projectOptions = { diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index 7b6b55fe545..04ccf246052 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -10,6 +10,7 @@ }); return describe('#replyWithSelectedText', function() { var stubSelection; + // Stub window.getSelection to return the provided String. stubSelection = function(text) { return window.getSelection = function() { return text; diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js index 7d91ed0f855..9b41242354f 100644 --- a/spec/javascripts/spec_helper.js +++ b/spec/javascripts/spec_helper.js @@ -1,22 +1,42 @@ - -/*= require support/bind-poly */ +(function() { -/*= require jquery */ +}).call(this); +// PhantomJS (Teaspoons default driver) doesn't have support for +// Function.prototype.bind, which has caused confusion. Use this polyfill to +// avoid the confusion. +/*= require support/bind-poly */ +// You can require your own javascript files here. By default this will include +// everything in application, however you may get better load performance if you +// require the specific files that are being used in the spec that tests them. +/*= require jquery */ /*= require jquery.turbolinks */ - - /*= require bootstrap */ - - /*= require underscore */ - +// Teaspoon includes some support files, but you can use anything from your own +// support path too. +// require support/jasmine-jquery-1.7.0 +// require support/jasmine-jquery-2.0.0 /*= require support/jasmine-jquery-2.1.0 */ -(function() { - - -}).call(this); +// require support/sinon +// require support/your-support-file +// Deferring execution +// If you're using CommonJS, RequireJS or some other asynchronous library you can +// defer execution. Call Teaspoon.execute() after everything has been loaded. +// Simple example of a timeout: +// Teaspoon.defer = true +// setTimeout(Teaspoon.execute, 1000) +// Matching files +// By default Teaspoon will look for files that match +// _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path +// and it'll be included in the default suite automatically. If you want to +// customize suites, check out the configuration in teaspoon_env.rb +// Manifest +// If you'd rather require your spec files manually (to control order for +// instance) you can disable the suite matcher in the configuration and use this +// file as a manifest. +// For more information: http://github.com/modeset/teaspoon diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index e008ce956ad..7ce3884f844 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -1,16 +1,8 @@ /*= require u2f/authenticate */ - - /*= require u2f/util */ - - /*= require u2f/error */ - - /*= require u2f */ - - /*= require ./mock_u2f_device */ (function() { diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index 21c5266c60e..01d6b7a8961 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -1,16 +1,8 @@ /*= require u2f/register */ - - /*= require u2f/util */ - - /*= require u2f/error */ - - /*= require u2f */ - - /*= require ./mock_u2f_device */ (function() { diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index 3d680ec8ea3..0c1266800d7 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -14,8 +14,10 @@ return true; } }; + // Stub Dropzone.forElement(...).enable() }); this.zen = new ZenMode(); + // Set this manually because we can't actually scroll the window return this.zen.scroll_position = 456; }); describe('on enter', function() { @@ -60,7 +62,7 @@ return $('a.js-zen-enter').click(); }; - exitZen = function() { + exitZen = function() { // Ohmmmmmmm return $('a.js-zen-leave').click(); }; diff --git a/vendor/assets/javascripts/task_list.js b/vendor/assets/javascripts/task_list.js index bc451506b6a..9fbfef03f6d 100644 --- a/vendor/assets/javascripts/task_list.js +++ b/vendor/assets/javascripts/task_list.js @@ -1,15 +1,118 @@ - +// The MIT License (MIT) +// +// Copyright (c) 2014 GitHub, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// TaskList Behavior +// /*= provides tasklist:enabled */ - - /*= provides tasklist:disabled */ - - /*= provides tasklist:change */ - - /*= provides tasklist:changed */ - +// +// +// Enables Task List update behavior. +// +// ### Example Markup +// +//
    +//
      +//
    • +// +// text +//
    • +//
    +//
    +// +//
    +//
    +// +// ### Specification +// +// TaskLists MUST be contained in a `(div).js-task-list-container`. +// +// TaskList Items SHOULD be an a list (`UL`/`OL`) element. +// +// Task list items MUST match `(input).task-list-item-checkbox` and MUST be +// `disabled` by default. +// +// TaskLists MUST have a `(textarea).js-task-list-field` form element whose +// `value` attribute is the source (Markdown) to be udpated. The source MUST +// follow the syntax guidelines. +// +// TaskList updates trigger `tasklist:change` events. If the change is +// successful, `tasklist:changed` is fired. The change can be canceled. +// +// jQuery is required. +// +// ### Methods +// +// `.taskList('enable')` or `.taskList()` +// +// Enables TaskList updates for the container. +// +// `.taskList('disable')` +// +// Disables TaskList updates for the container. +// +//# ### Events +// +// `tasklist:enabled` +// +// Fired when the TaskList is enabled. +// +// * **Synchronicity** Sync +// * **Bubbles** Yes +// * **Cancelable** No +// * **Target** `.js-task-list-container` +// +// `tasklist:disabled` +// +// Fired when the TaskList is disabled. +// +// * **Synchronicity** Sync +// * **Bubbles** Yes +// * **Cancelable** No +// * **Target** `.js-task-list-container` +// +// `tasklist:change` +// +// Fired before the TaskList item change takes affect. +// +// * **Synchronicity** Sync +// * **Bubbles** Yes +// * **Cancelable** Yes +// * **Target** `.js-task-list-field` +// +// `tasklist:changed` +// +// Fired once the TaskList item change has taken affect. +// +// * **Synchronicity** Sync +// * **Bubbles** Yes +// * **Cancelable** No +// * **Target** `.js-task-list-field` +// +// ### NOTE +// +// Task list checkboxes are rendered as disabled by default because rendered +// user content is cached without regard for the viewer. (function() { var codeFencesPattern, complete, completePattern, disableTaskList, disableTaskLists, enableTaskList, enableTaskLists, escapePattern, incomplete, incompletePattern, itemPattern, itemsInParasPattern, updateTaskList, updateTaskListItem, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; @@ -18,20 +121,48 @@ complete = "[x]"; + // Escapes the String for regular expression matching. escapePattern = function(str) { return str.replace(/([\[\]])/g, "\\$1").replace(/\s/, "\\s").replace("x", "[xX]"); }; - incompletePattern = RegExp("" + (escapePattern(incomplete))); - - completePattern = RegExp("" + (escapePattern(complete))); + incompletePattern = RegExp("" + (escapePattern(incomplete))); // escape square brackets + // match all white space + completePattern = RegExp("" + (escapePattern(complete))); // match all cases + // Pattern used to identify all task list items. + // Useful when you need iterate over all items. itemPattern = RegExp("^(?:\\s*(?:>\\s*)*(?:[-+*]|(?:\\d+\\.)))\\s*(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ")\\s+(?!\\(.*?\\))(?=(?:\\[.*?\\]\\s*(?:\\[.*?\\]|\\(.*?\\))\\s*)*(?:[^\\[]|$))"); + // prefix, consisting of + // optional leading whitespace + // zero or more blockquotes + // list item indicator + // optional whitespace prefix + // checkbox + // is followed by whitespace + // is not part of a [foo](url) link + // and is followed by zero or more links + // and either a non-link or the end of the string + // Used to filter out code fences from the source for comparison only. + // http://rubular.com/r/x5EwZVrloI + // Modified slightly due to issues with JS codeFencesPattern = /^`{3}(?:\s*\w+)?[\S\s].*[\S\s]^`{3}$/mg; + // ``` + // followed by optional language + // whitespace + // code + // whitespace + // ``` + // Used to filter out potential mismatches (items not in lists). + // http://rubular.com/r/OInl6CiePy itemsInParasPattern = RegExp("^(" + (escapePattern(complete)) + "|" + (escapePattern(incomplete)) + ").+$", "g"); + // Given the source text, updates the appropriate task list item to match the + // given checked value. + // + // Returns the updated String text. updateTaskListItem = function(source, itemIndex, checked) { var clean, index, line, result; clean = source.replace(/\r/g, '').replace(codeFencesPattern, '').replace(itemsInParasPattern, '').split("\n"); @@ -55,6 +186,9 @@ return result.join("\n"); }; + // Updates the $field value to reflect the state of $item. + // Triggers the `tasklist:change` event before the value has changed, and fires + // a `tasklist:changed` event once the value has changed. updateTaskList = function($item) { var $container, $field, checked, event, index; $container = $item.closest('.js-task-list-container'); @@ -70,10 +204,12 @@ } }; + // When the task list item checkbox is updated, submit the change $(document).on('change', '.task-list-item-checkbox', function() { return updateTaskList($(this)); }); + // Enables TaskList item changes. enableTaskList = function($container) { if ($container.find('.js-task-list-field').length > 0) { $container.find('.task-list-item').addClass('enabled').find('.task-list-item-checkbox').attr('disabled', null); @@ -81,6 +217,7 @@ } }; + // Enables a collection of TaskList containers. enableTaskLists = function($containers) { var container, i, len, results; results = []; @@ -91,11 +228,13 @@ return results; }; + // Disable TaskList item changes. disableTaskList = function($container) { $container.find('.task-list-item').removeClass('enabled').find('.task-list-item-checkbox').attr('disabled', 'disabled'); return $container.removeClass('is-task-list-enabled').trigger('tasklist:disabled'); }; + // Disables a collection of TaskList containers. disableTaskLists = function($containers) { var container, i, len, results; results = []; -- cgit v1.2.3 From 76c7b83dd5e345583fbaa26d9462a1bec7107a60 Mon Sep 17 00:00:00 2001 From: Pascal Betz Date: Thu, 8 Sep 2016 19:41:25 +0200 Subject: Remove original regexp --- app/helpers/sidekiq_helper.rb | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb index 37650ca642b..d440edc55ba 100644 --- a/app/helpers/sidekiq_helper.rb +++ b/app/helpers/sidekiq_helper.rb @@ -1,14 +1,12 @@ module SidekiqHelper - SIDEKIQ_PS_REGEXP = /\A([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+(.+)\s+(sidekiq.*\])\s+\z/ - SIDEKIQ_PS_REGEXP = /\A - (?\d+)\s+ - (?[\d\.,]+)\s+ - (?[\d\.,]+)\s+ - (?[DRSTWXZNLsl\+<]+)\s+ - (?.+)\s+ - (?sidekiq.*\])\s+ - \z/x + (?\d+)\s+ + (?[\d\.,]+)\s+ + (?[\d\.,]+)\s+ + (?[DRSTWXZNLsl\+<]+)\s+ + (?.+)\s+ + (?sidekiq.*\])\s+ + \z/x def parse_sidekiq_ps(line) match = line.match(SIDEKIQ_PS_REGEXP) -- cgit v1.2.3 From d86c6666623a90d46fa4cfe624c67e86c6ad235f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 1 Sep 2016 19:12:05 -0300 Subject: Refresh todos count cache when an Issue/MR is deleted --- app/controllers/concerns/issuable_actions.rb | 2 ++ app/services/todo_service.rb | 20 ++++++++++++++++++++ spec/controllers/projects/issues_controller_spec.rb | 6 ++++++ .../projects/merge_requests_controller_spec.rb | 6 ++++++ spec/services/todo_service_spec.rb | 16 ++++++++++++++++ 5 files changed, 50 insertions(+) diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 77b4efffd7f..bb32bc502e6 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -8,6 +8,8 @@ module IssuableActions def destroy issuable.destroy + destroy_method = "destroy_#{issuable.class.name.underscore}".to_sym + TodoService.new.public_send(destroy_method, issuable, current_user) name = issuable.class.name.titleize.downcase flash[:notice] = "The #{name} was successfully deleted." diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 2aab8c736d6..776530ac0a5 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -31,6 +31,14 @@ class TodoService mark_pending_todos_as_done(issue, current_user) end + # When we destroy an issue we should: + # + # * refresh the todos count cache for the current user + # + def destroy_issue(issue, current_user) + destroy_issuable(issue, current_user) + end + # When we reassign an issue we should: # # * create a pending todo for new assignee if issue is assigned @@ -64,6 +72,14 @@ class TodoService mark_pending_todos_as_done(merge_request, current_user) end + # When we destroy a merge request we should: + # + # * refresh the todos count cache for the current user + # + def destroy_merge_request(merge_request, current_user) + destroy_issuable(merge_request, current_user) + end + # When we reassign a merge request we should: # # * creates a pending todo for new assignee if merge request is assigned @@ -187,6 +203,10 @@ class TodoService create_mention_todos(issuable.project, issuable, author) end + def destroy_issuable(issuable, user) + user.update_todos_count_cache + end + def toggling_tasks?(issuable) issuable.previous_changes.include?('description') && issuable.tasks? && issuable.updated_tasks.any? diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 16929767ddf..90419368f22 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -370,6 +370,12 @@ describe Projects::IssuesController do expect(response).to have_http_status(302) expect(controller).to set_flash[:notice].to(/The issue was successfully deleted\./).now end + + it 'delegates the update of the todos count cache to TodoService' do + expect_any_instance_of(TodoService).to receive(:destroy_issue).with(issue, owner).once + + delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: issue.iid + end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index a219400d75f..94c9edc91fe 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -320,6 +320,12 @@ describe Projects::MergeRequestsController do expect(response).to have_http_status(302) expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now end + + it 'delegates the update of the todos count cache to TodoService' do + expect_any_instance_of(TodoService).to receive(:destroy_merge_request).with(merge_request, owner).once + + delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid + end end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index cafcad3e3c0..b41f6f14fbd 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -145,6 +145,14 @@ describe TodoService, services: true do end end + describe '#destroy_issue' do + it 'refresh the todos count cache for the user' do + expect(john_doe).to receive(:update_todos_count_cache).and_call_original + + service.destroy_issue(issue, john_doe) + end + end + describe '#reassigned_issue' do it 'creates a pending todo for new assignee' do unassigned_issue.update_attribute(:assignee, john_doe) @@ -394,6 +402,14 @@ describe TodoService, services: true do end end + describe '#destroy_merge_request' do + it 'refresh the todos count cache for the user' do + expect(john_doe).to receive(:update_todos_count_cache).and_call_original + + service.destroy_merge_request(mr_assigned, john_doe) + end + end + describe '#reassigned_merge_request' do it 'creates a pending todo for new assignee' do mr_unassigned.update_attribute(:assignee, john_doe) -- cgit v1.2.3 From 28e6bb39de56376fc4672ffc41927d63ad2f2440 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 1 Sep 2016 19:12:11 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 21a2661515d..ba7246e115c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.12.0 (unreleased) - Fix project visibility level fields on settings - Add hover color to emoji icon (ClemMakesApps) - Add textarea autoresize after comment (ClemMakesApps) + - Refresh todos count cache when an Issue/MR is deleted - Fix branches page dropdown sort alignment (ClemMakesApps) - Add white background for no readme container (ClemMakesApps) - API: Expose issue confidentiality flag. (Robert Schilling) -- cgit v1.2.3 From 913857e57765f6911f171fb2fcd12532d6b6f1df Mon Sep 17 00:00:00 2001 From: Jared Deckard Date: Thu, 28 Jul 2016 12:40:17 -0500 Subject: Require comments must be included before code --- app/assets/javascripts/merge_request_tabs.js | 3 --- spec/javascripts/spec_helper.js | 10 +++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 5316101457c..dcba4a8d275 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -264,7 +264,6 @@ }); }; -<<<<<<< a79ff9346b73079148cc4ecc81da82804bb51f3c MergeRequestTabs.prototype.loadPipelines = function(source) { if (this.pipelinesLoaded) { return; @@ -280,11 +279,9 @@ }); }; -======= // Show or hide the loading spinner // // status - Boolean, true to show, false to hide ->>>>>>> Restore comments lost when converting CoffeeScript to JavaScript MergeRequestTabs.prototype.toggleLoading = function(status) { return $('.mr-loading-status .loading').toggle(status); }; diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js index 9b41242354f..8801c297887 100644 --- a/spec/javascripts/spec_helper.js +++ b/spec/javascripts/spec_helper.js @@ -1,8 +1,3 @@ -(function() { - - -}).call(this); - // PhantomJS (Teaspoons default driver) doesn't have support for // Function.prototype.bind, which has caused confusion. Use this polyfill to // avoid the confusion. @@ -40,3 +35,8 @@ // instance) you can disable the suite matcher in the configuration and use this // file as a manifest. // For more information: http://github.com/modeset/teaspoon + +(function() { + + +}).call(this); -- cgit v1.2.3 From 22ee3f5452c4193b1e3da3efce44f871e75287d6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 7 Sep 2016 23:54:08 -0400 Subject: Fix spec failures with spec/features/issues/user_uses_slash_commands_spec.rb Sidekiq async tasks were being run after project namespaces were deleted. Use Sidekiq in fake mode to prevent this from Sidekiq tasks from being enqueued in the first place. Closes #21723 --- .../issuable_slash_commands_shared_examples.rb | 69 +++++++--------------- 1 file changed, 21 insertions(+), 48 deletions(-) diff --git a/spec/support/issuable_slash_commands_shared_examples.rb b/spec/support/issuable_slash_commands_shared_examples.rb index d2a49ea5c5e..a51742089d0 100644 --- a/spec/support/issuable_slash_commands_shared_examples.rb +++ b/spec/support/issuable_slash_commands_shared_examples.rb @@ -18,6 +18,15 @@ shared_examples 'issuable record that supports slash commands in its description login_with(master) end + def write_note(text) + Sidekiq::Testing.fake! do + page.within('.js-main-target-form') do + fill_in 'note[note]', with: text + click_button 'Comment' + end + end + end + describe "new #{issuable_type}" do context 'with commands in the description' do it "creates the #{issuable_type} and interpret commands accordingly" do @@ -44,10 +53,7 @@ shared_examples 'issuable record that supports slash commands in its description context 'with a note containing commands' do it 'creates a note without the commands and interpret the commands accordingly' do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"" - click_button 'Comment' - end + write_note("Awesome!\n/assign @bob\n/label ~bug\n/milestone %\"ASAP\"") expect(page).to have_content 'Awesome!' expect(page).not_to have_content '/assign @bob' @@ -66,10 +72,7 @@ shared_examples 'issuable record that supports slash commands in its description context 'with a note containing only commands' do it 'does not create a note but interpret the commands accordingly' do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/assign @bob\n/label ~bug\n/milestone %\"ASAP\"" - click_button 'Comment' - end + write_note("/assign @bob\n/label ~bug\n/milestone %\"ASAP\"") expect(page).not_to have_content '/assign @bob' expect(page).not_to have_content '/label ~bug' @@ -92,10 +95,7 @@ shared_examples 'issuable record that supports slash commands in its description context "when current user can close #{issuable_type}" do it "closes the #{issuable_type}" do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/close" - click_button 'Comment' - end + write_note("/close") expect(page).not_to have_content '/close' expect(page).to have_content 'Your commands have been executed!' @@ -112,10 +112,7 @@ shared_examples 'issuable record that supports slash commands in its description end it "does not close the #{issuable_type}" do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/close" - click_button 'Comment' - end + write_note("/close") expect(page).not_to have_content '/close' expect(page).not_to have_content 'Your commands have been executed!' @@ -133,10 +130,7 @@ shared_examples 'issuable record that supports slash commands in its description context "when current user can reopen #{issuable_type}" do it "reopens the #{issuable_type}" do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/reopen" - click_button 'Comment' - end + write_note("/reopen") expect(page).not_to have_content '/reopen' expect(page).to have_content 'Your commands have been executed!' @@ -153,10 +147,7 @@ shared_examples 'issuable record that supports slash commands in its description end it "does not reopen the #{issuable_type}" do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/reopen" - click_button 'Comment' - end + write_note("/reopen") expect(page).not_to have_content '/reopen' expect(page).not_to have_content 'Your commands have been executed!' @@ -169,10 +160,7 @@ shared_examples 'issuable record that supports slash commands in its description context "with a note changing the #{issuable_type}'s title" do context "when current user can change title of #{issuable_type}" do it "reopens the #{issuable_type}" do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/title Awesome new title" - click_button 'Comment' - end + write_note("/title Awesome new title") expect(page).not_to have_content '/title' expect(page).to have_content 'Your commands have been executed!' @@ -189,10 +177,7 @@ shared_examples 'issuable record that supports slash commands in its description end it "does not reopen the #{issuable_type}" do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/title Awesome new title" - click_button 'Comment' - end + write_note("/title Awesome new title") expect(page).not_to have_content '/title' expect(page).not_to have_content 'Your commands have been executed!' @@ -204,10 +189,7 @@ shared_examples 'issuable record that supports slash commands in its description context "with a note marking the #{issuable_type} as todo" do it "creates a new todo for the #{issuable_type}" do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/todo" - click_button 'Comment' - end + write_note("/todo") expect(page).not_to have_content '/todo' expect(page).to have_content 'Your commands have been executed!' @@ -238,10 +220,7 @@ shared_examples 'issuable record that supports slash commands in its description expect(todo.author).to eq master expect(todo.user).to eq master - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/done" - click_button 'Comment' - end + write_note("/done") expect(page).not_to have_content '/done' expect(page).to have_content 'Your commands have been executed!' @@ -254,10 +233,7 @@ shared_examples 'issuable record that supports slash commands in its description it "creates a new todo for the #{issuable_type}" do expect(issuable.subscribed?(master)).to be_falsy - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/subscribe" - click_button 'Comment' - end + write_note("/subscribe") expect(page).not_to have_content '/subscribe' expect(page).to have_content 'Your commands have been executed!' @@ -274,10 +250,7 @@ shared_examples 'issuable record that supports slash commands in its description it "creates a new todo for the #{issuable_type}" do expect(issuable.subscribed?(master)).to be_truthy - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/unsubscribe" - click_button 'Comment' - end + write_note("/unsubscribe") expect(page).not_to have_content '/unsubscribe' expect(page).to have_content 'Your commands have been executed!' -- cgit v1.2.3 From 26e4d00eac301bb50c89aedd858dbfc27f4688ed Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 8 Sep 2016 07:08:44 -0400 Subject: Use wait_for_ajax to avoid database deadlocks after specs are cleaned --- spec/support/issuable_slash_commands_shared_examples.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/support/issuable_slash_commands_shared_examples.rb b/spec/support/issuable_slash_commands_shared_examples.rb index a51742089d0..a3fbba55e27 100644 --- a/spec/support/issuable_slash_commands_shared_examples.rb +++ b/spec/support/issuable_slash_commands_shared_examples.rb @@ -2,6 +2,8 @@ # It takes a `issuable_type`, and expect an `issuable`. shared_examples 'issuable record that supports slash commands in its description and notes' do |issuable_type| + include WaitForAjax + let(:master) { create(:user) } let(:assignee) { create(:user, username: 'bob') } let(:guest) { create(:user) } @@ -18,6 +20,11 @@ shared_examples 'issuable record that supports slash commands in its description login_with(master) end + after do + # Ensure all outstanding Ajax requests are complete to avoid database deadlocks + wait_for_ajax + end + def write_note(text) Sidekiq::Testing.fake! do page.within('.js-main-target-form') do -- cgit v1.2.3 From 65eadf98fe5b0dd3d5e860695981c4069ae20676 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 8 Sep 2016 13:45:33 -0400 Subject: Wait for logout message in login_helpers --- spec/support/login_helpers.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index e5f76afbfc0..c0b3e83244d 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -75,6 +75,7 @@ module LoginHelpers def logout find(".header-user-dropdown-toggle").click click_link "Sign out" + expect(page).to have_content('Signed out successfully') end # Logout without JavaScript driver -- cgit v1.2.3 From f12354916bf0531a1f2d7cffbc67d36fd425d088 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 8 Sep 2016 18:51:35 -0400 Subject: Move write_note into SlashCommandsHelper and update other dependent specs --- spec/features/issues/user_uses_slash_commands_spec.rb | 15 +++++++-------- .../merge_requests/user_uses_slash_commands_spec.rb | 10 ++++++---- spec/support/issuable_slash_commands_shared_examples.rb | 10 +--------- spec/support/slash_commands_helpers.rb | 10 ++++++++++ 4 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 spec/support/slash_commands_helpers.rb diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index 2883e392694..105629c485a 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -1,6 +1,7 @@ require 'rails_helper' feature 'Issues > User uses slash commands', feature: true, js: true do + include SlashCommandsHelpers include WaitForAjax it_behaves_like 'issuable record that supports slash commands in its description and notes', :issue do @@ -17,14 +18,15 @@ feature 'Issues > User uses slash commands', feature: true, js: true do visit namespace_project_issue_path(project.namespace, project, issue) end + after do + wait_for_ajax + end + describe 'adding a due date from note' do let(:issue) { create(:issue, project: project) } it 'does not create a note, and sets the due date accordingly' do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/due 2016-08-28" - click_button 'Comment' - end + write_note("/due 2016-08-28") expect(page).not_to have_content '/due 2016-08-28' expect(page).to have_content 'Your commands have been executed!' @@ -41,10 +43,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do it 'does not create a note, and removes the due date accordingly' do expect(issue.due_date).to eq Date.new(2016, 8, 28) - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/remove_due_date" - click_button 'Comment' - end + write_note("/remove_due_date") expect(page).not_to have_content '/remove_due_date' expect(page).to have_content 'Your commands have been executed!' diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index d9ef0d18074..22d9d1b9fd5 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -1,6 +1,7 @@ require 'rails_helper' feature 'Merge Requests > User uses slash commands', feature: true, js: true do + include SlashCommandsHelpers include WaitForAjax let(:user) { create(:user) } @@ -20,11 +21,12 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do visit namespace_project_merge_request_path(project.namespace, project, merge_request) end + after do + wait_for_ajax + end + it 'does not recognize the command nor create a note' do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: "/due 2016-08-28" - click_button 'Comment' - end + write_note("/due 2016-08-28") expect(page).not_to have_content '/due 2016-08-28' end diff --git a/spec/support/issuable_slash_commands_shared_examples.rb b/spec/support/issuable_slash_commands_shared_examples.rb index a3fbba55e27..5e3b8f2b23e 100644 --- a/spec/support/issuable_slash_commands_shared_examples.rb +++ b/spec/support/issuable_slash_commands_shared_examples.rb @@ -2,6 +2,7 @@ # It takes a `issuable_type`, and expect an `issuable`. shared_examples 'issuable record that supports slash commands in its description and notes' do |issuable_type| + include SlashCommandsHelpers include WaitForAjax let(:master) { create(:user) } @@ -25,15 +26,6 @@ shared_examples 'issuable record that supports slash commands in its description wait_for_ajax end - def write_note(text) - Sidekiq::Testing.fake! do - page.within('.js-main-target-form') do - fill_in 'note[note]', with: text - click_button 'Comment' - end - end - end - describe "new #{issuable_type}" do context 'with commands in the description' do it "creates the #{issuable_type} and interpret commands accordingly" do diff --git a/spec/support/slash_commands_helpers.rb b/spec/support/slash_commands_helpers.rb new file mode 100644 index 00000000000..df483afa0e3 --- /dev/null +++ b/spec/support/slash_commands_helpers.rb @@ -0,0 +1,10 @@ +module SlashCommandsHelpers + def write_note(text) + Sidekiq::Testing.fake! do + page.within('.js-main-target-form') do + fill_in 'note[note]', with: text + click_button 'Comment' + end + end + end +end -- cgit v1.2.3 From 0f73dd34fe4df166c2e724a80d798d452404b06f Mon Sep 17 00:00:00 2001 From: Sergey Gnuskov Date: Mon, 5 Sep 2016 12:42:59 +0300 Subject: Add information about user and manual build start to runner as variables --- CHANGELOG | 1 + app/models/ci/build.rb | 11 +++++++++++ doc/ci/variables/README.md | 6 ++++++ spec/models/build_spec.rb | 28 ++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index cc87ff3ecb7..f675df93c89 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -99,6 +99,7 @@ v 8.12.0 (unreleased) - Fix hover leading space bug in pipeline graph !5980 - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - Fix repository page ui issues + - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov) - Fixed invisible scroll controls on build page on iPhone - Fix error on raw build trace download for old builds stored in database !4822 - Refactor the triggers page and documentation !6217 diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 61052437318..fb16bc06d71 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -148,6 +148,7 @@ module Ci variables += runner.predefined_variables if runner variables += project.container_registry_variables variables += yaml_variables + variables += user_variables variables += project.secret_variables variables += trigger_request.user_variables if trigger_request variables @@ -434,6 +435,15 @@ module Ci read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || [] end + def user_variables + return [] if user.blank? + + [ + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } + ] + end + private def update_artifacts_size @@ -469,6 +479,7 @@ module Ci ] variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag? variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request + variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if manual? variables end diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index c32831d3aaa..6a971c3ae87 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -34,6 +34,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built | | **CI_BUILD_REPO** | all | all | The URL to clone the Git repository | | **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that build was [triggered] | +| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that build was manually started | | **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally | | **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally | @@ -47,6 +48,8 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | +| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | **Some of the variables are only available when using runner with at least defined version.** @@ -60,6 +63,7 @@ export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/git export CI_BUILD_TAG="1.0.0" export CI_BUILD_NAME="spec:other" export CI_BUILD_STAGE="test" +export CI_BUILD_MANUAL="true" export CI_BUILD_TRIGGERED="true" export CI_BUILD_TOKEN="abcde-1234ABCD5678ef" export CI_PIPELINE_ID="1000" @@ -78,6 +82,8 @@ export CI_SERVER="yes" export CI_SERVER_NAME="GitLab" export CI_SERVER_REVISION="70606bf" export CI_SERVER_VERSION="8.9.0" +export GITLAB_USER_ID="42" +export GITLAB_USER_EMAIL="alexzander@sporer.com" ``` ### YAML-defined variables diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index c45c2635cf4..8eab4281bc7 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -231,6 +231,34 @@ describe Ci::Build, models: true do it { is_expected.to eq(predefined_variables) } end + context 'when build has user' do + let(:user) { create(:user, username: 'starter') } + let(:user_variables) do + [ + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } + ] + end + + before do + build.update_attributes(user: user) + end + + it { user_variables.each { |v| is_expected.to include(v) } } + end + + context 'when build started manually' do + before do + build.update_attributes(when: :manual) + end + + let(:manual_variable) do + { key: 'CI_BUILD_MANUAL', value: 'true', public: true } + end + + it { is_expected.to include(manual_variable) } + end + context 'when build is for tag' do let(:tag_variable) do { key: 'CI_BUILD_TAG', value: 'master', public: true } -- cgit v1.2.3 From a9f5b8881c6f6621dee880744dc04b92c85a5938 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Sun, 14 Aug 2016 15:10:16 +0200 Subject: Minor update on CI docs examples Triggered by gitlab-org/gitlab-ci-yml#1 I've investigated how this is organized so far and did a minor clean up. In my opinion we should just keep a list of up to date blog posts in the docs and examples should be included in gitlab-org/gitlab-ci-yml. This reduces the number of projects and docs pages one has to look for information. [ci skip] --- doc/ci/examples/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index c134106bfd0..406396deaaa 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -1,17 +1,19 @@ # CI Examples +A collection of `.gitlab-ci.yml` files is maintained at the [GitLab CI Yml project][gitlab-ci-templates]. +If your favorite programming language or framework are missing we would love your help by sending a merge request +with a `.gitlab-ci.yml`. + +Apart from those, here is an collection of tutorials and guides on setting up your CI pipeline: + - [Testing a PHP application](php.md) - [Test and deploy a Ruby application to Heroku](test-and-deploy-ruby-application-to-heroku.md) - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test a Clojure application](test-clojure-application.md) - [Test a Scala application](test-scala-application.md) - [Using `dpl` as deployment tool](deployment/README.md) -- Help your favorite programming language and GitLab by sending a merge request - with a guide for that language. - -## Outside the documentation - - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) -- [A collection of useful .gitlab-ci.yml templates](https://gitlab.com/gitlab-org/gitlab-ci-yml) + +[gitlab-ci-templates][https://gitlab.com/gitlab-org/gitlab-ci-yml] -- cgit v1.2.3 From 6953d244f35f9e5ebd36e37a9968b8da0815ba40 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 9 Sep 2016 20:32:06 +0800 Subject: Enable pipeline events by default Not sure why I missed this before. I thought I added it. --- CHANGELOG | 1 + app/models/service.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f528ca074fa..294a921aca5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.12.0 (unreleased) - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) + - Enable pipeline events by default !6278 - Move parsing of sidekiq ps into helper !6245 (pascalbetz) - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) diff --git a/app/models/service.rb b/app/models/service.rb index 198e7247838..80de7175565 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -12,6 +12,7 @@ class Service < ActiveRecord::Base default_value_for :tag_push_events, true default_value_for :note_events, true default_value_for :build_events, true + default_value_for :pipeline_events, true default_value_for :wiki_page_events, true after_initialize :initialize_properties -- cgit v1.2.3 From 874ad681cd98616e560fb633e7c3867361faf46c Mon Sep 17 00:00:00 2001 From: Razzeee Date: Wed, 7 Sep 2016 20:44:06 +0200 Subject: Search should compare only the lowercase versions of the project names --- CHANGELOG | 1 + lib/api/projects.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f528ca074fa..5b2655bcb95 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -111,6 +111,7 @@ v 8.12.0 (unreleased) - Use default clone protocol on "check out, review, and merge locally" help page URL - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - Allow bulk update merge requests from merge requests index page + - Project search API is now agnostic to casing v 8.11.6 (unreleased) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 4033f597859..b76be145a14 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -430,7 +430,7 @@ module API get "/search/:query" do ids = current_user.authorized_projects.map(&:id) visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] - projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") + projects = Project.where("(id in (?) OR visibility_level in (?)) AND (LOWER(name) LIKE LOWER((?)))", ids, visibility_levels, "%#{params[:query]}%") sort = params[:sort] == 'desc' ? 'desc' : 'asc' projects = case params["order_by"] -- cgit v1.2.3 From 287663b2b11c052906aefe42354f75718f980568 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 9 Sep 2016 15:11:37 +0100 Subject: Trigger autosize update after template selection Closes #21982 --- CHANGELOG | 1 + app/assets/javascripts/blob/template_selector.js | 7 +++++++ spec/features/projects/issuable_templates_spec.rb | 13 +++++++++++++ 3 files changed, 21 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f528ca074fa..d45b1694ff9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.12.0 (unreleased) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Center build stage columns in pipeline overview (ClemMakesApps) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) + - Fix bug stopping issue description being scrollable after selecting issue template - Remove suggested colors hover underline (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) - Fix project visibility level fields on settings diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index b0a37ef0e0a..4866add779d 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -13,6 +13,9 @@ this.buildDropdown(); this.bindEvents(); this.onFilenameUpdate(); + + this.autosizeUpdateEvent = document.createEvent('Event'); + this.autosizeUpdateEvent.initEvent('autosize:update', true, false); } TemplateSelector.prototype.buildDropdown = function() { @@ -69,6 +72,10 @@ TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) { this.editor.setValue(file.content, 1); if (!skipFocus) this.editor.focus(); + + if (this.editor instanceof jQuery) { + this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent); + } }; TemplateSelector.prototype.startLoadingSpinner = function() { diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index 4a83740621a..d0f4e5469ed 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do context 'user creates an issue using templates' do let(:template_content) { 'this is a test "bug" template' } + let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) } let(:issue) { create(:issue, author: user, assignee: user, project: project) } background do project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) + project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false) visit edit_namespace_project_issue_path project.namespace, project, issue fill_in :'issue[title]', with: 'test issue title' end @@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do preview_template save_changes end + + it 'updates height of markdown textarea' do + start_height = page.evaluate_script('$(".markdown-area").outerHeight()') + + select_template 'test' + wait_for_ajax + + end_height = page.evaluate_script('$(".markdown-area").outerHeight()') + + expect(end_height).not_to eq(start_height) + end end context 'user creates a merge request using templates' do -- cgit v1.2.3 From 02761638f13a9c234794e9d6161e4bf766eab5f0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 1 Sep 2016 16:37:58 -0300 Subject: Avoid conflict with admin labels when importing GitHub labels --- lib/gitlab/github_import/importer.rb | 3 +-- lib/gitlab/github_import/label_formatter.rb | 6 +++++ spec/lib/gitlab/github_import/importer_spec.rb | 14 ++++++++--- .../gitlab/github_import/label_formatter_spec.rb | 28 +++++++++++++++++----- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 4fdc2f46be0..0388c58f811 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -133,8 +133,7 @@ module Gitlab if issue.labels.count > 0 label_ids = issue.labels - .map { |raw| LabelFormatter.new(project, raw).attributes } - .map { |attrs| Label.find_by(attrs).try(:id) } + .map { |attrs| project.labels.find_by(title: attrs.name).try(:id) } .compact issuable.update_attribute(:label_ids, label_ids) diff --git a/lib/gitlab/github_import/label_formatter.rb b/lib/gitlab/github_import/label_formatter.rb index 9f18244e7d7..2cad7fca88e 100644 --- a/lib/gitlab/github_import/label_formatter.rb +++ b/lib/gitlab/github_import/label_formatter.rb @@ -13,6 +13,12 @@ module Gitlab Label end + def create! + project.labels.find_or_create_by!(title: title) do |label| + label.color = color + end + end + private def color diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index 3fb8de81545..7df288f619f 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::GithubImport::Importer, lib: true do let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id } let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) } - let(:label) do + let(:label1) do double( name: 'Bug', color: 'ff0000', @@ -21,6 +21,14 @@ describe Gitlab::GithubImport::Importer, lib: true do ) end + let(:label2) do + double( + name: nil, + color: 'ff0000', + url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug' + ) + end + let(:milestone) do double( number: 1347, @@ -93,7 +101,7 @@ describe Gitlab::GithubImport::Importer, lib: true do before do allow(project).to receive(:import_data).and_return(double.as_null_object) allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound) - allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label, label]) + allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2]) allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone]) allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2]) allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request]) @@ -113,7 +121,7 @@ describe Gitlab::GithubImport::Importer, lib: true do error = { message: 'The remote data could not be fully imported.', errors: [ - { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title has already been taken" }, + { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" }, { type: :milestone, url: "https://api.github.com/repos/octocat/Hello-World/milestones/1", errors: "Validation failed: Title has already been taken" }, { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1347", errors: "Invalid Repository. Use user/repo format." }, { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" }, diff --git a/spec/lib/gitlab/github_import/label_formatter_spec.rb b/spec/lib/gitlab/github_import/label_formatter_spec.rb index 87593e32db0..8098754d735 100644 --- a/spec/lib/gitlab/github_import/label_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/label_formatter_spec.rb @@ -1,18 +1,34 @@ require 'spec_helper' describe Gitlab::GithubImport::LabelFormatter, lib: true do - describe '#attributes' do - it 'returns formatted attributes' do - project = create(:project) - raw = double(name: 'improvements', color: 'e6e6e6') + let(:project) { create(:project) } + let(:raw) { double(name: 'improvements', color: 'e6e6e6') } - formatter = described_class.new(project, raw) + subject { described_class.new(project, raw) } - expect(formatter.attributes).to eq({ + describe '#attributes' do + it 'returns formatted attributes' do + expect(subject.attributes).to eq({ project: project, title: 'improvements', color: '#e6e6e6' }) end end + + describe '#create!' do + context 'when label does not exist' do + it 'creates a new label' do + expect { subject.create! }.to change(Label, :count).by(1) + end + end + + context 'when label exists' do + it 'does not create a new label' do + project.labels.create(name: raw.name) + + expect { subject.create! }.not_to change(Label, :count) + end + end + end end -- cgit v1.2.3 From 599817a3ceacb00b4258f67df8d1000f1e6dff4a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 9 Sep 2016 11:18:29 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index f528ca074fa..9d1c8ea45b2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -102,6 +102,7 @@ v 8.12.0 (unreleased) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Adds response mime type to transaction metric action when it's not HTML - Fix hover leading space bug in pipeline graph !5980 + - Avoid conflict with admin labels when importing GitHub labels - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - Fix repository page ui issues - Fixed invisible scroll controls on build page on iPhone -- cgit v1.2.3 From 53615c30357327f45fd33407c4dc27b82ad7c73f Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 9 Sep 2016 16:56:59 +0200 Subject: Use gitlab-workhorse 0.8.1 This adds (basic) Sentry support. --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- doc/update/8.11-to-8.12.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index a3df0a6959e..6f4eebdf6f6 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.8.0 +0.8.1 diff --git a/doc/install/installation.md b/doc/install/installation.md index c9562e071d9..df98655c396 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.8.0 + sudo -u git -H git checkout v0.8.1 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 0615729263f..011c2b0e969 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -82,7 +82,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.8.0 +sudo -u git -H git checkout v0.8.1 sudo -u git -H make ``` -- cgit v1.2.3 From 45fc8b73ed1875cc9766e5735d4411fd5c8872be Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 9 Sep 2016 15:16:14 +0100 Subject: Fix API issues sorting --- CHANGELOG | 1 + lib/api/issues.rb | 12 +++- spec/requests/api/issues_spec.rb | 126 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 130 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f528ca074fa..c6981eb3b9f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.12.0 (unreleased) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 - Fix pagination on user snippets page + - Fix sorting of issues in API - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 - Change merge_error column from string to text type diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 556684187d8..c9689e6f8ef 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -41,7 +41,8 @@ module API issues = current_user.issues.inc_notes_with_associations issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? - issues.reorder(issuable_order_by => issuable_sort) + issues = issues.reorder(issuable_order_by => issuable_sort) + present paginate(issues), with: Entities::Issue, current_user: current_user end end @@ -73,7 +74,11 @@ module API params[:group_id] = group.id params[:milestone_title] = params.delete(:milestone) params[:label_name] = params.delete(:labels) - params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort] + + if params[:order_by] || params[:sort] + # The Sortable concern takes 'created_desc', not 'created_at_desc' (for example) + params[:sort] = "#{issuable_order_by.sub('_at', '')}_#{issuable_sort}" + end issues = IssuesFinder.new(current_user, params).execute @@ -113,7 +118,8 @@ module API issues = filter_issues_milestone(issues, params[:milestone]) end - issues.reorder(issuable_order_by => issuable_sort) + issues = issues.reorder(issuable_order_by => issuable_sort) + present paginate(issues), with: Entities::Issue, current_user: current_user end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 47344a13b5e..86d994be079 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -17,21 +17,24 @@ describe API::API, api: true do assignee: user, project: project, state: :closed, - milestone: milestone + milestone: milestone, + updated_at: 3.hours.ago end let!(:confidential_issue) do create :issue, :confidential, project: project, author: author, - assignee: assignee + assignee: assignee, + updated_at: 2.hours.ago end let!(:issue) do create :issue, author: user, assignee: user, project: project, - milestone: milestone + milestone: milestone, + updated_at: 1.hour.ago end let!(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) @@ -135,6 +138,42 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(0) end + + it 'sorts by created_at descending by default' do + get api('/issues', user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api('/issues?sort=asc', user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api('/issues?order_by=updated_at', user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api('/issues?order_by=updated_at&sort=asc', user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end end @@ -147,21 +186,24 @@ describe API::API, api: true do assignee: user, project: group_project, state: :closed, - milestone: group_milestone + milestone: group_milestone, + updated_at: 3.hours.ago end let!(:group_confidential_issue) do create :issue, :confidential, project: group_project, author: author, - assignee: assignee + assignee: assignee, + updated_at: 2.hours.ago end let!(:group_issue) do create :issue, author: user, assignee: user, project: group_project, - milestone: group_milestone + milestone: group_milestone, + updated_at: 1.hour.ago end let!(:group_label) do create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) @@ -278,6 +320,42 @@ describe API::API, api: true do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(group_closed_issue.id) end + + it 'sorts by created_at descending by default' do + get api(base_url, user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api("#{base_url}?sort=asc", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api("#{base_url}?order_by=updated_at", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api("#{base_url}?order_by=updated_at&sort=asc", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end describe "GET /projects/:id/issues" do @@ -386,6 +464,42 @@ describe API::API, api: true do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) end + + it 'sorts by created_at descending by default' do + get api("#{base_url}/issues", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api("#{base_url}/issues?sort=asc", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api("#{base_url}/issues?order_by=updated_at", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api("#{base_url}/issues?order_by=updated_at&sort=asc", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end describe "GET /projects/:id/issues/:issue_id" do -- cgit v1.2.3 From e672c0128a232cd25112de4e84528112ec2e59d9 Mon Sep 17 00:00:00 2001 From: Ojas Shirekar Date: Fri, 9 Sep 2016 21:24:20 +0530 Subject: Pull import sources from Gitlab::ImportSources --- app/models/application_setting.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 246477ffe88..55d2e07de08 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -146,7 +146,7 @@ class ApplicationSetting < ActiveRecord::Base default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], + import_sources: Gitlab::ImportSources.values, shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], max_artifacts_size: Settings.artifacts['max_size'], require_two_factor_authentication: false, -- cgit v1.2.3 From 48333b9da36d2d22feca28c89cfefed34bede9e0 Mon Sep 17 00:00:00 2001 From: Marc Siegfriedt Date: Tue, 2 Aug 2016 20:52:55 +0000 Subject: Add notification_settings API calls Use NotificationSetting::EMAIL_EVENTS for params --- CHANGELOG | 1 + doc/api/README.md | 1 + doc/api/notification_settings.md | 169 ++++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/entities.rb | 17 ++- lib/api/notification_settings.rb | 97 ++++++++++++++ spec/requests/api/notification_settings_spec.rb | 89 +++++++++++++ 7 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 doc/api/notification_settings.md create mode 100644 lib/api/notification_settings.rb create mode 100644 spec/requests/api/notification_settings_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 9d1c8ea45b2..86bc865ffd7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -112,6 +112,7 @@ v 8.12.0 (unreleased) - Use default clone protocol on "check out, review, and merge locally" help page URL - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - Allow bulk update merge requests from merge requests index page + - Add notification_settings API calls !5632 (mahcsig) v 8.11.6 (unreleased) diff --git a/doc/api/README.md b/doc/api/README.md index e12070dc1ce..7661e1eea02 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -27,6 +27,7 @@ following locations: - [Open source license templates](licenses.md) - [Namespaces](namespaces.md) - [Notes](notes.md) (comments) +- [Notification settings](notification_settings.md) - [Pipelines](pipelines.md) - [Projects](projects.md) including setting Webhooks - [Project Access Requests](access_requests.md) diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md new file mode 100644 index 00000000000..ff6c9e4931c --- /dev/null +++ b/doc/api/notification_settings.md @@ -0,0 +1,169 @@ +# Notification settings + +>**Note:** This feature was [introduced][ce-5632] in GitLab 8.12. + +**Valid notification levels** + +The notification levels are defined in the `NotificationSetting::level` model enumeration. Currently, these levels are recognized: + +``` +disabled +participating +watch +global +mention +custom +``` + +If the `custom` level is used, specific email events can be controlled. Notification email events are defined in the `NotificationSetting::EMAIL_EVENTS` model variable. Currently, these events are recognized: + +``` +new_note +new_issue +reopen_issue +close_issue +reassign_issue +new_merge_request +reopen_merge_request +close_merge_request +reassign_merge_request +merge_merge_request +``` + +## Global notification settings + +Get current notification settings and email address. + +``` +GET /notification_settings +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings +``` + +Example response: + +```json +{ + "level": "participating", + "notification_email": "admin@example.com" +} +``` + +## Update global notification settings + +Update current notification settings and email address. + +``` +PUT /notification_settings +``` + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings?level=watch +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `level` | string | no | The global notification level | +| `notification_email` | string | no | The email address to send notifications | +| `new_note` | boolean | no | Enable/disable this notification | +| `new_issue` | boolean | no | Enable/disable this notification | +| `reopen_issue` | boolean | no | Enable/disable this notification | +| `close_issue` | boolean | no | Enable/disable this notification | +| `reassign_issue` | boolean | no | Enable/disable this notification | +| `new_merge_request` | boolean | no | Enable/disable this notification | +| `reopen_merge_request` | boolean | no | Enable/disable this notification | +| `close_merge_request` | boolean | no | Enable/disable this notification | +| `reassign_merge_request` | boolean | no | Enable/disable this notification | +| `merge_merge_request` | boolean | no | Enable/disable this notification | + +Example response: + +```json +{ + "level": "watch", + "notification_email": "admin@example.com" +} +``` + +## Group / project level notification settings + +Get current group or project notification settings. + +``` +GET /groups/:id/notification_settings +GET /projects/:id/notification_settings +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The group/project ID or path | + +Example response: + +```json +{ + "level": "global" +} +``` + +## Update group/project level notification settings + +Update current group/project notification settings. + +``` +PUT /groups/:id/notification_settings +PUT /projects/:id/notification_settings +``` + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings?level=watch +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings?level=custom&new_note=true +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The group/project ID or path | +| `level` | string | no | The global notification level | +| `new_note` | boolean | no | Enable/disable this notification | +| `new_issue` | boolean | no | Enable/disable this notification | +| `reopen_issue` | boolean | no | Enable/disable this notification | +| `close_issue` | boolean | no | Enable/disable this notification | +| `reassign_issue` | boolean | no | Enable/disable this notification | +| `new_merge_request` | boolean | no | Enable/disable this notification | +| `reopen_merge_request` | boolean | no | Enable/disable this notification | +| `close_merge_request` | boolean | no | Enable/disable this notification | +| `reassign_merge_request` | boolean | no | Enable/disable this notification | +| `merge_merge_request` | boolean | no | Enable/disable this notification | + +Example responses: + +```json +{ + "level": "watch" +} + +{ + "level": "custom", + "events": { + "new_note": true, + "new_issue": false, + "reopen_issue": false, + "close_issue": false, + "reassign_issue": false, + "new_merge_request": false, + "reopen_merge_request": false, + "close_merge_request": false, + "reassign_merge_request": false, + "merge_merge_request": false + } +} +``` + +[ce-5632]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5632 diff --git a/lib/api/api.rb b/lib/api/api.rb index a08fb056049..74ca4728695 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -51,6 +51,7 @@ module API mount ::API::Milestones mount ::API::Namespaces mount ::API::Notes + mount ::API::NotificationSettings mount ::API::Pipelines mount ::API::ProjectHooks mount ::API::ProjectSnippets diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3faba79415b..4f736e4ec2b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -375,7 +375,7 @@ module API expose :access_level expose :notification_level do |member, options| if member.notification_setting - NotificationSetting.levels[member.notification_setting.level] + ::NotificationSetting.levels[member.notification_setting.level] end end end @@ -386,6 +386,21 @@ module API class GroupAccess < MemberAccess end + class NotificationSetting < Grape::Entity + expose :level + expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do + ::NotificationSetting::EMAIL_EVENTS.each do |event| + expose event + end + end + end + + class GlobalNotificationSetting < NotificationSetting + expose :notification_email do |notification_setting, options| + notification_setting.user.notification_email + end + end + class ProjectService < Grape::Entity expose :id, :title, :created_at, :updated_at, :active expose :push_events, :issues_events, :merge_requests_events diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb new file mode 100644 index 00000000000..a70a7e71073 --- /dev/null +++ b/lib/api/notification_settings.rb @@ -0,0 +1,97 @@ +module API + # notification_settings API + class NotificationSettings < Grape::API + before { authenticate! } + + helpers ::API::Helpers::MembersHelpers + + resource :notification_settings do + desc 'Get global notification level settings and email, defaults to Participate' do + detail 'This feature was introduced in GitLab 8.12' + success Entities::GlobalNotificationSetting + end + get do + notification_setting = current_user.global_notification_setting + + present notification_setting, with: Entities::GlobalNotificationSetting + end + + desc 'Update global notification level settings and email, defaults to Participate' do + detail 'This feature was introduced in GitLab 8.12' + success Entities::GlobalNotificationSetting + end + params do + optional :level, type: String, desc: 'The global notification level' + optional :notification_email, type: String, desc: 'The email address to send notifications' + NotificationSetting::EMAIL_EVENTS.each do |event| + optional event, type: Boolean, desc: 'Enable/disable this notification' + end + end + put do + notification_setting = current_user.global_notification_setting + + begin + notification_setting.transaction do + new_notification_email = params.delete(:notification_email) + declared_params = declared(params, include_missing: false).to_h + + current_user.update(notification_email: new_notification_email) if new_notification_email + notification_setting.update(declared_params) + end + rescue ArgumentError => e # catch level enum error + render_api_error! e.to_s, 400 + end + + render_validation_error! current_user + render_validation_error! notification_setting + present notification_setting, with: Entities::GlobalNotificationSetting + end + end + + %w[group project].each do |source_type| + resource source_type.pluralize do + desc "Get #{source_type} level notification level settings, defaults to Global" do + detail 'This feature was introduced in GitLab 8.12' + success Entities::NotificationSetting + end + params do + requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME' + end + get ":id/notification_settings" do + source = find_source(source_type, params[:id]) + + notification_setting = current_user.notification_settings_for(source) + + present notification_setting, with: Entities::NotificationSetting + end + + desc "Update #{source_type} level notification level settings, defaults to Global" do + detail 'This feature was introduced in GitLab 8.12' + success Entities::NotificationSetting + end + params do + requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME' + optional :level, type: String, desc: "The #{source_type} notification level" + NotificationSetting::EMAIL_EVENTS.each do |event| + optional event, type: Boolean, desc: 'Enable/disable this notification' + end + end + put ":id/notification_settings" do + source = find_source(source_type, params.delete(:id)) + notification_setting = current_user.notification_settings_for(source) + + begin + declared_params = declared(params, include_missing: false).to_h + + notification_setting.update(declared_params) + rescue ArgumentError => e # catch level enum error + render_api_error! e.to_s, 400 + end + + render_validation_error! notification_setting + present notification_setting, with: Entities::NotificationSetting + end + end + end + end +end diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb new file mode 100644 index 00000000000..e6d8a5ee954 --- /dev/null +++ b/spec/requests/api/notification_settings_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let!(:group) { create(:group) } + let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) } + + describe "GET /notification_settings" do + it "returns global notification settings for the current user" do + get api("/notification_settings", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_a Hash + expect(json_response['notification_email']).to eq(user.notification_email) + expect(json_response['level']).to eq(user.global_notification_setting.level) + end + end + + describe "PUT /notification_settings" do + let(:email) { create(:email, user: user) } + + it "updates global notification settings for the current user" do + put api("/notification_settings", user), { level: 'watch', notification_email: email.email } + + expect(response).to have_http_status(200) + expect(json_response['notification_email']).to eq(email.email) + expect(user.reload.notification_email).to eq(email.email) + expect(json_response['level']).to eq(user.reload.global_notification_setting.level) + end + end + + describe "PUT /notification_settings" do + it "fails on non-user email address" do + put api("/notification_settings", user), { notification_email: 'invalid@example.com' } + + expect(response).to have_http_status(400) + end + end + + describe "GET /groups/:id/notification_settings" do + it "returns group level notification settings for the current user" do + get api("/groups/#{group.id}/notification_settings", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_a Hash + expect(json_response['level']).to eq(user.notification_settings_for(group).level) + end + end + + describe "PUT /groups/:id/notification_settings" do + it "updates group level notification settings for the current user" do + put api("/groups/#{group.id}/notification_settings", user), { level: 'watch' } + + expect(response).to have_http_status(200) + expect(json_response['level']).to eq(user.reload.notification_settings_for(group).level) + end + end + + describe "GET /projects/:id/notification_settings" do + it "returns project level notification settings for the current user" do + get api("/projects/#{project.id}/notification_settings", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_a Hash + expect(json_response['level']).to eq(user.notification_settings_for(project).level) + end + end + + describe "PUT /projects/:id/notification_settings" do + it "updates project level notification settings for the current user" do + put api("/projects/#{project.id}/notification_settings", user), { level: 'custom', new_note: true } + + expect(response).to have_http_status(200) + expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level) + expect(json_response['events']['new_note']).to eq(true) + expect(json_response['events']['new_issue']).to eq(false) + end + end + + describe "PUT /projects/:id/notification_settings" do + it "fails on invalid level" do + put api("/projects/#{project.id}/notification_settings", user), { level: 'invalid' } + + expect(response).to have_http_status(400) + end + end +end -- cgit v1.2.3 From 0dcfc899d124bb618c84b6e25393a3c36939d633 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 9 Sep 2016 15:00:21 +0100 Subject: Used flexbox to force related merge request list items into columned items --- app/assets/stylesheets/pages/issues.scss | 9 +++++++++ app/views/projects/issues/_merge_requests.html.haml | 2 +- app/views/projects/issues/_related_branches.html.haml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index d14224ed00f..64ee1c308c0 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -48,6 +48,15 @@ form.edit-issue { margin: 0; } +ul.related-merge-requests > li { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + .merge-request-id { + flex-shrink: 0; + } +} + .merge-requests-title, .related-branches-title { font-size: 16px; font-weight: 600; diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index d8075371853..31d3ec23276 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -1,7 +1,7 @@ - if @merge_requests.any? %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') - %ul.unstyled-list + %ul.unstyled-list.related-merge-requests - has_any_ci = @merge_requests.any?(&:pipeline) - @merge_requests.each do |merge_request| %li diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index a8eeab3e55e..44683c8bcdb 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -1,7 +1,7 @@ - if @related_branches.any? %h2.related-branches-title = pluralize(@related_branches.count, 'Related Branch') - %ul.unstyled-list + %ul.unstyled-list.related-merge-requests - @related_branches.each do |branch| %li - target = @project.repository.find_branch(branch).target -- cgit v1.2.3 From 43d2cd511c3e068f78fb71387ae0ac31db4926b1 Mon Sep 17 00:00:00 2001 From: Diego Souza Date: Thu, 8 Sep 2016 22:01:49 -0300 Subject: Sort secret variables by key (fix #20870) --- app/models/ci/variable.rb | 6 ++++-- app/views/projects/variables/_table.html.haml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index c9c47ec7419..6959223aed9 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -1,7 +1,7 @@ module Ci class Variable < ActiveRecord::Base extend Ci::Model - + belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id validates_uniqueness_of :key, scope: :gl_project_id @@ -11,7 +11,9 @@ module Ci format: { with: /\A[a-zA-Z0-9_]+\z/, message: "can contain only letters, digits and '_'." } - attr_encrypted :value, + scope :order_key_asc, -> { reorder(key: :asc) } + + attr_encrypted :value, mode: :per_attribute_iv_and_salt, insecure_mode: true, key: Gitlab::Application.secrets.db_key_base, diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml index 6c43f822db4..07cee86ba4c 100644 --- a/app/views/projects/variables/_table.html.haml +++ b/app/views/projects/variables/_table.html.haml @@ -9,7 +9,7 @@ %th Value %th %tbody - - @project.variables.each do |variable| + - @project.variables.order_key_asc.each do |variable| - if variable.id? %tr %td= variable.key -- cgit v1.2.3 From bf8a48e179119830f83f3b358f66f8a95af17963 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Fri, 2 Sep 2016 05:52:13 -0500 Subject: Request only the LDAP attributes we need --- CHANGELOG | 1 + lib/gitlab/ldap/adapter.rb | 58 ++++++++++++++++------------- spec/lib/gitlab/ldap/adapter_spec.rb | 71 ++++++++++++++++++++++++++++++++++-- spec/support/ldap_helpers.rb | 47 ++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 spec/support/ldap_helpers.rb diff --git a/CHANGELOG b/CHANGELOG index eaa1e007ca9..82c2174a3ff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.12.0 (unreleased) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Fix blame table layout width - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) + - Request only the LDAP attributes we need !6187 - Center build stage columns in pipeline overview (ClemMakesApps) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - Remove suggested colors hover underline (ClemMakesApps) diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 9a5bcfb5c9b..9100719da87 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -23,31 +23,7 @@ module Gitlab end def users(field, value, limit = nil) - if field.to_sym == :dn - options = { - base: value, - scope: Net::LDAP::SearchScope_BaseObject - } - else - options = { - base: config.base, - filter: Net::LDAP::Filter.eq(field, value) - } - end - - if config.user_filter.present? - user_filter = Net::LDAP::Filter.construct(config.user_filter) - - options[:filter] = if options[:filter] - Net::LDAP::Filter.join(options[:filter], user_filter) - else - user_filter - end - end - - if limit.present? - options.merge!(size: limit) - end + options = user_options(field, value, limit) entries = ldap_search(options).select do |entry| entry.respond_to? config.uid @@ -90,6 +66,38 @@ module Gitlab Rails.logger.warn("LDAP search timed out after #{config.timeout} seconds") [] end + + private + + def user_options(field, value, limit) + options = { attributes: %W(#{config.uid} cn mail dn) } + options[:size] = limit if limit + + if field.to_sym == :dn + options[:base] = value + options[:scope] = Net::LDAP::SearchScope_BaseObject + options[:filter] = user_filter + else + options[:base] = config.base + options[:filter] = user_filter(Net::LDAP::Filter.eq(field, value)) + end + + options + end + + def user_filter(filter = nil) + if config.user_filter.present? + user_filter = Net::LDAP::Filter.construct(config.user_filter) + end + + if user_filter && filter + Net::LDAP::Filter.join(filter, user_filter) + elsif user_filter + user_filter + else + filter + end + end end end end diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb index 4847b5f3b0e..0600893f4cf 100644 --- a/spec/lib/gitlab/ldap/adapter_spec.rb +++ b/spec/lib/gitlab/ldap/adapter_spec.rb @@ -1,12 +1,77 @@ require 'spec_helper' describe Gitlab::LDAP::Adapter, lib: true do - let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' } + include LdapHelpers + + let(:ldap) { double(:ldap) } + let(:adapter) { ldap_adapter('ldapmain', ldap) } + + describe '#users' do + before do + stub_ldap_config(base: 'dc=example,dc=com') + end + + it 'searches with the proper options when searching by uid' do + # Requires this expectation style to match the filter + expect(adapter).to receive(:ldap_search) do |arg| + expect(arg[:filter].to_s).to eq('(uid=johndoe)') + expect(arg[:base]).to eq('dc=example,dc=com') + expect(arg[:attributes]).to match(%w{uid cn mail dn}) + end.and_return({}) + + adapter.users('uid', 'johndoe') + end + + it 'searches with the proper options when searching by dn' do + expect(adapter).to receive(:ldap_search).with( + base: 'uid=johndoe,ou=users,dc=example,dc=com', + scope: Net::LDAP::SearchScope_BaseObject, + attributes: %w{uid cn mail dn}, + filter: nil + ).and_return({}) + + adapter.users('dn', 'uid=johndoe,ou=users,dc=example,dc=com') + end + + it 'searches with the proper options when searching with a limit' do + expect(adapter) + .to receive(:ldap_search).with(hash_including(size: 100)).and_return({}) + + adapter.users('uid', 'johndoe', 100) + end + + it 'returns an LDAP::Person if search returns a result' do + entry = ldap_user_entry('johndoe') + allow(adapter).to receive(:ldap_search).and_return([entry]) + + results = adapter.users('uid', 'johndoe') + + expect(results.size).to eq(1) + expect(results.first.uid).to eq('johndoe') + end + + it 'returns empty array if search entry does not respond to uid' do + entry = Net::LDAP::Entry.new + entry['dn'] = user_dn('johndoe') + allow(adapter).to receive(:ldap_search).and_return([entry]) + + results = adapter.users('uid', 'johndoe') + + expect(results).to be_empty + end + + it 'uses the right uid attribute when non-default' do + stub_ldap_config(uid: 'sAMAccountName') + expect(adapter).to receive(:ldap_search).with( + hash_including(attributes: %w{sAMAccountName cn mail dn}) + ).and_return({}) + + adapter.users('sAMAccountName', 'johndoe') + end + end describe '#dn_matches_filter?' do - let(:ldap) { double(:ldap) } subject { adapter.dn_matches_filter?(:dn, :filter) } - before { allow(adapter).to receive(:ldap).and_return(ldap) } context "when the search is successful" do context "and the result is non-empty" do diff --git a/spec/support/ldap_helpers.rb b/spec/support/ldap_helpers.rb new file mode 100644 index 00000000000..079f244475c --- /dev/null +++ b/spec/support/ldap_helpers.rb @@ -0,0 +1,47 @@ +module LdapHelpers + def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap)) + ::Gitlab::LDAP::Adapter.new(provider, ldap) + end + + def user_dn(uid) + "uid=#{uid},ou=users,dc=example,dc=com" + end + + # Accepts a hash of Gitlab::LDAP::Config keys and values. + # + # Example: + # stub_ldap_config( + # group_base: 'ou=groups,dc=example,dc=com', + # admin_group: 'my-admin-group' + # ) + def stub_ldap_config(messages) + messages.each do |config, value| + allow_any_instance_of(::Gitlab::LDAP::Config) + .to receive(config.to_sym).and_return(value) + end + end + + # Stub an LDAP person search and provide the return entry. Specify `nil` for + # `entry` to simulate when an LDAP person is not found + # + # Example: + # adapter = ::Gitlab::LDAP::Adapter.new('ldapmain', double(:ldap)) + # ldap_user_entry = ldap_user_entry('john_doe') + # + # stub_ldap_person_find_by_uid('john_doe', ldap_user_entry, adapter) + def stub_ldap_person_find_by_uid(uid, entry, provider = 'ldapmain') + return_value = ::Gitlab::LDAP::Person.new(entry, provider) if entry.present? + + allow(::Gitlab::LDAP::Person) + .to receive(:find_by_uid).with(uid, any_args).and_return(return_value) + end + + # Create a simple LDAP user entry. + def ldap_user_entry(uid) + entry = Net::LDAP::Entry.new + entry['dn'] = user_dn(uid) + entry['uid'] = uid + + entry + end +end -- cgit v1.2.3 From 5c35e166bf54e262d12f3be257fa3b2df976c3ab Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 9 Sep 2016 14:35:13 -0400 Subject: Archive CHANGELOG entries prior to 8.0 in changelogs/archive.md [ci skip] --- CHANGELOG | 1691 +-------------------------------------------- changelogs/archive.md | 1810 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1812 insertions(+), 1689 deletions(-) create mode 100644 changelogs/archive.md diff --git a/CHANGELOG b/CHANGELOG index 17388615908..45ca3d5db87 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2003,1692 +2003,5 @@ v 8.0.0 - Redirect from incorrectly cased group or project path to correct one (Francesco Levorato) - Removed API calls from CE to CI -v 7.14.3 - - No changes - -v 7.14.2 - - Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu) - - Allow configuration of LDAP attributes GitLab will use for the new user account. - -v 7.14.1 - - Improve abuse reports management from admin area - - Fix "Reload with full diff" URL button in compare branch view (Stan Hu) - - Disabled DNS lookups for SSH in docker image (Rowan Wookey) - - Only include base URL in OmniAuth full_host parameter (Stan Hu) - - Fix Error 500 in API when accessing a group that has an avatar (Stan Hu) - - Ability to enable SSL verification for Webhooks - -v 7.14.0 - - Fix bug where non-project members of the target project could set labels on new merge requests. - - Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller) - - Fix redirection after sign in when using auto_sign_in_with_provider - - Upgrade gitlab_git to 7.2.14 to ignore CRLFs in .gitmodules (Stan Hu) - - Clear cache to prevent listing deleted branches after MR removes source branch (Stan Hu) - - Provide more feedback what went wrong if HipChat service failed test (Stan Hu) - - Fix bug where backslashes in inline diffs could be dropped (Stan Hu) - - Disable turbolinks when linking to Bitbucket import status (Stan Hu) - - Fix broken code import and display error messages if something went wrong with creating project (Stan Hu) - - Fix corrupted binary files when using API files endpoint (Stan Hu) - - Bump Haml to 4.0.7 to speed up textarea rendering (Stan Hu) - - Show incompatible projects in Bitbucket import status (Stan Hu) - - Fix coloring of diffs on MR Discussion-tab (Gert Goet) - - Fix "Network" and "Graphs" pages for branches with encoded slashes (Stan Hu) - - Fix errors deleting and creating branches with encoded slashes (Stan Hu) - - Always add current user to autocomplete controller to support filter by "Me" (Stan Hu) - - Fix multi-line syntax highlighting (Stan Hu) - - Fix network graph when branch name has single quotes (Stan Hu) - - Add "Confirm user" button in user admin page (Stan Hu) - - Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu) - - Add support for Unicode filenames in relative links (Hiroyuki Sato) - - Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki) - - Fix commit data retrieval when branch name has single quotes (Stan Hu) - - Check that project was actually created rather than just validated in import:repos task (Stan Hu) - - Fix full screen mode for snippet comments (Daniel Gerhardt) - - Fix 404 error in files view after deleting the last file in a repository (Stan Hu) - - Fix the "Reload with full diff" URL button (Stan Hu) - - Fix label read access for unauthenticated users (Daniel Gerhardt) - - Fix access to disabled features for unauthenticated users (Daniel Gerhardt) - - Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu) - - Fix file upload dialog for comment editing (Daniel Gerhardt) - - Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu) - - Return comments in created order in merge request API (Stan Hu) - - Disable internal issue tracker controller if external tracker is used (Stan Hu) - - Expire Rails cache entries after two weeks to prevent endless Redis growth - - Add support for destroying project milestones (Stan Hu) - - Allow custom backup archive permissions - - Add project star and fork count, group avatar URL and user/group web URL attributes to API - - Show who last edited a comment if it wasn't the original author - - Send notification to all participants when MR is merged. - - Add ability to manage user email addresses via the API. - - Show buttons to add license, changelog and contribution guide if they're missing. - - Tweak project page buttons. - - Disabled autocapitalize and autocorrect on login field (Daryl Chan) - - Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis) - - Update gravatar link on profile page to link to configured gravatar host (Ben Bodenmiller) - - Remove redis-store TTL monkey patch - - Add support for CI skipped status - - Fetch code from forks to refs/merge-requests/:id/head when merge request created - - Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg) - - Add "Check out branch" button to the MR page. - - Improve MR merge widget text and UI consistency. - - Improve text in MR "How To Merge" modal. - - Cache all events - - Order commits by date when comparing branches - - Fix bug causing error when the target branch of a symbolic ref was deleted - - Include branch/tag name in archive file and directory name - - Add dropzone upload progress - - Add a label for merged branches on branches page (Florent Baldino) - - Detect .mkd and .mkdn files as markdown (Ben Boeckel) - - Fix: User search feature in admin area does not respect filters - - Set max-width for README, issue and merge request description for easier read on big screens - - Update Flowdock integration to support new Flowdock API (Boyan Tabakov) - - Remove author from files view (Sven Strickroth) - - Fix infinite loop when SAML was incorrectly configured. - -v 7.13.5 - - Satellites reverted - -v 7.13.4 - - Allow users to send abuse reports - -v 7.13.3 - - Fix bug causing Bitbucket importer to crash when OAuth application had been removed. - - Allow users to send abuse reports - - Remove satellites - - Link username to profile on Group Members page (Tom Webster) - -v 7.13.2 - - Fix randomly failed spec - - Create project services on Project creation - - Add admin_merge_request ability to Developer level and up - - Fix Error 500 when browsing projects with no HEAD (Stan Hu) - - Fix labels / assignee / milestone for the merge requests when issues are disabled - - Show the first tab automatically on MergeRequests#new - - Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt) - - Fix Gmail Actions - -v 7.13.1 - - Fix: Label modifications are not reflected in existing notes and in the issue list - - Fix: Label not shown in the Issue list, although it's set through web interface - - Fix: Group/project references are linked incorrectly - - Improve documentation - - Fix of migration: Check if session_expire_delay column exists before adding the column - - Fix: ActionView::Template::Error - - Fix: "Create Merge Request" isn't always shown in event for newly pushed branch - - Fix bug causing "Remove source-branch" option not to work for merge requests from the same project. - - Render Note field hints consistently for "new" and "edit" forms - -v 7.13.0 - - Remove repository graph log to fix slow cache updates after push event (Stan Hu) - - Only enable HSTS header for HTTPS and port 443 (Stan Hu) - - Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu) - - Fix redirection to home page URL for unauthorized users (Daniel Gerhardt) - - Add branch switching support for graphs (Daniel Gerhardt) - - Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt) - - Remove link leading to a 404 error in Deploy Keys page (Stan Hu) - - Add support for unlocking users in admin settings (Stan Hu) - - Add Irker service configuration options (Stan Hu) - - Fix order of issues imported from GitHub (Hiroyuki Sato) - - Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart) - - Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI - - Add `two_factor_enabled` field to admin user API (Stan Hu) - - Fix invalid timestamps in RSS feeds (Rowan Wookey) - - Fix downloading of patches on public merge requests when user logged out (Stan Hu) - - Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu) - - Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu) - - Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu) - - Support commenting on diffs in side-by-side mode (Stan Hu) - - Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu) - - Return 40x error codes if branch could not be deleted in UI (Stan Hu) - - Remove project visibility icons from dashboard projects list - - Rename "Design" profile settings page to "Preferences". - - Allow users to customize their default Dashboard page. - - Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8 - - Admin can edit and remove user identities - - Convert CRLF newlines to LF when committing using the web editor. - - API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged. - - Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled. - - Show a user's Two-factor Authentication status in the administration area. - - Explicit error when commit not found in the CI - - Improve performance for issue and merge request pages - - Users with guest access level can not set assignee, labels or milestones for issue and merge request - - Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels - - Better performance for pages with events list, issues list and commits list - - Faster automerge check and merge itself when source and target branches are in same repository - - Correctly show anonymous authorized applications under Profile > Applications. - - Query Optimization in MySQL. - - Allow users to be blocked and unblocked via the API - - Use native Postgres database cleaning during backup restore - - Redesign project page. Show README as default instead of activity. Move project activity to separate page - - Make left menu more hierarchical and less contextual by adding back item at top - - A fork can’t have a visibility level that is greater than the original project. - - Faster code search in repository and wiki. Fixes search page timeout for big repositories - - Allow administrators to disable 2FA for a specific user - - Add error message for SSH key linebreaks - - Store commits count in database (will populate with valid values only after first push) - - Rebuild cache after push to repository in background job - - Fix transferring of project to another group using the API. - -v 7.12.2 - - Correctly show anonymous authorized applications under Profile > Applications. - - Faster automerge check and merge itself when source and target branches are in same repository - - Audit log for user authentication - - Allow custom label to be set for authentication providers. - -v 7.12.1 - - Fix error when deleting a user who has projects (Stan Hu) - - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu) - - Add SAML to list of social_provider (Matt Firtion) - - Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets) - - Fix closed merge request scope at milestone page (Dmitriy Zaporozhets) - - Revert merge request states renaming - - Fix hooks for web based events with external issue references (Daniel Gerhardt) - - Improve performance for issue and merge request pages - - Compress database dumps to reduce backup size - -v 7.12.0 - - Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu) - - Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu) - - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu) - - Update oauth button logos for Twitter and Google to recommended assets - - Update browser gem to version 0.8.0 for IE11 support (Stan Hu) - - Fix timeout when rendering file with thousands of lines. - - Add "Remember me" checkbox to LDAP signin form. - - Add session expiration delay configuration through UI application settings - - Don't notify users mentioned in code blocks or blockquotes. - - Omit link to generate labels if user does not have access to create them (Stan Hu) - - Show warning when a comment will add 10 or more people to the discussion. - - Disable changing of the source branch in merge request update API (Stan Hu) - - Shorten merge request WIP text. - - Add option to disallow users from registering any application to use GitLab as an OAuth provider - - Support editing target branch of merge request (Stan Hu) - - Refactor permission checks with issues and merge requests project settings (Stan Hu) - - Fix Markdown preview not working in Edit Milestone page (Stan Hu) - - Fix Zen Mode not closing with ESC key (Stan Hu) - - Allow HipChat API version to be blank and default to v2 (Stan Hu) - - Add file attachment support in Milestone description (Stan Hu) - - Fix milestone "Browse Issues" button. - - Set milestone on new issue when creating issue from index with milestone filter active. - - Make namespace API available to all users (Stan Hu) - - Add webhook support for note events (Stan Hu) - - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu) - - Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu) - - Fix git blame syntax highlighting when different commits break up lines (Stan Hu) - - Add "Resend confirmation e-mail" link in profile settings (Stan Hu) - - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) - - Disabled expansion of top/bottom blobs for new file diffs - - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - - Use the user list from the target project in a merge request (Stan Hu) - - Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen) - - Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen) - - Fix new/empty milestones showing 100% completion value (Jonah Bishop) - - Add a note when an Issue or Merge Request's title changes - - Consistently refer to MRs as either Merged or Closed. - - Add Merged tab to MR lists. - - Prefix EmailsOnPush email subject with `[Git]`. - - Group project contributions by both name and email. - - Clarify navigation labels for Project Settings and Group Settings. - - Move user avatar and logout button to sidebar - - You can not remove user if he/she is an only owner of group - - User should be able to leave group. If not - show him proper message - - User has ability to leave project - - Add SAML support as an omniauth provider - - Allow to configure a URL to show after sign out - - Add an option to automatically sign-in with an Omniauth provider - - GitLab CI service sends .gitlab-ci.yml in each push call - - When remove project - move repository and schedule it removal - - Improve group removing logic - - Trigger create-hooks on backup restore task - - Add option to automatically link omniauth and LDAP identities - - Allow special character in users bio. I.e.: I <3 GitLab - -v 7.11.4 - - Fix missing bullets when creating lists - - Set rel="nofollow" on external links - -v 7.11.3 - - no changes - - Fix upgrader script (Martins Polakovs) - -v 7.11.2 - - no changes - -v 7.11.1 - - no changes - -v 7.11.0 - - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) - - Get editing comments to work in Chrome 43 again. - - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) - - Don't show duplicate deploy keys - - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger) - - Make the first branch pushed to an empty repository the default HEAD (Stan Hu) - - Fix broken view when using a tag to display a tree that contains git submodules (Stan Hu) - - Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu) - - Add application setting to restrict user signups to e-mail domains (Stan Hu) - - Don't allow a merge request to be merged when its title starts with "WIP". - - Add a page title to every page. - - Allow primary email to be set to an email that you've already added. - - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky) - - Ignore invalid lines in .gitmodules - - Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu) - - Redirect to sign in page after signing out. - - Fix "Hello @username." references not working by no longer allowing usernames to end in period. - - Fix "Revspec not found" errors when viewing diffs in a forked project with submodules (Stan Hu) - - Improve project page UI - - Fix broken file browsing with relative submodule in personal projects (Stan Hu) - - Add "Reply quoting selected text" shortcut key (`r`) - - Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention. - - Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention. - - When use change branches link at MR form - save source branch selection instead of target one - - Improve handling of large diffs - - Added GitLab Event header for project hooks - - Add Two-factor authentication (2FA) for GitLab logins - - Show Atom feed buttons everywhere where applicable. - - Add project activity atom feed. - - Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits. - - Explain how to get a new password reset token in welcome emails - - Include commit comments in MR from a forked project. - - Group milestones by title in the dashboard and all other issue views. - - Query issues, merge requests and milestones with their IID through API (Julien Bianchi) - - Add default project and snippet visibility settings to the admin web UI. - - Show incompatible projects in Google Code import status (Stan Hu) - - Fix bug where commit data would not appear in some subdirectories (Stan Hu) - - Task lists are now usable in comments, and will show up in Markdown previews. - - Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu) - - Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu) - - Protect OmniAuth request phase against CSRF. - - Don't send notifications to mentioned users that don't have access to the project in question. - - Add search issues/MR by number - - Change plots to bar graphs in commit statistics screen - - Move snippets UI to fluid layout - - Improve UI for sidebar. Increase separation between navigation and content - - Improve new project command options (Ben Bodenmiller) - - Add common method to force UTF-8 and use it to properly handle non-ascii OAuth user properties (Onur Küçük) - - Prevent sending empty messages to HipChat (Chulki Lee) - - Improve UI for mobile phones on dashboard and project pages - - Add room notification and message color option for HipChat - - Allow to use non-ASCII letters and dashes in project and namespace name. (Jakub Jirutka) - - Add footnotes support to Markdown (Guillaume Delbergue) - - Add current_sign_in_at to UserFull REST api. - - Make Sidekiq MemoryKiller shutdown signal configurable - - Add "Create Merge Request" buttons to commits and branches pages and push event. - - Show user roles by comments. - - Fix automatic blocking of auto-created users from Active Directory. - - Call merge request webhook for each new commits (Arthur Gautier) - - Use SIGKILL by default in Sidekiq::MemoryKiller - - Fix mentioning of private groups. - - Add style for element in markdown - - Spin spinner icon next to "Checking for CI status..." on MR page. - - Fix reference links in dashboard activity and ATOM feeds. - - Ensure that the first added admin performs repository imports - -v 7.10.4 - - Fix migrations broken in 7.10.2 - - Make tags for GitLab installations running on MySQL case sensitive - - Get Gitorious importer to work again. - - Fix adding new group members from admin area - - Fix DB error when trying to tag a repository (Stan Hu) - - Fix Error 500 when searching Wiki pages (Stan Hu) - - Unescape branch names in compare commit (Stan Hu) - - Order commit comments chronologically in API. - -v 7.10.2 - - Fix CI links on MR page - -v 7.10.0 - - Ignore submodules that are defined in .gitmodules but are checked in as directories. - - Allow projects to be imported from Google Code. - - Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger) - - Allow users to be invited by email to join a group or project. - - Don't crash when project repository doesn't exist. - - Add config var to block auto-created LDAP users. - - Don't use HTML ellipsis in EmailsOnPush subject truncated commit message. - - Set EmailsOnPush reply-to address to committer email when enabled. - - Fix broken file browsing with a submodule that contains a relative link (Stan Hu) - - Fix persistent XSS vulnerability around profile website URLs. - - Fix project import URL regex to prevent arbitary local repos from being imported. - - Fix directory traversal vulnerability around uploads routes. - - Fix directory traversal vulnerability around help pages. - - Don't leak existence of project via search autocomplete. - - Don't leak existence of group or project via search. - - Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu) - - Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu) - - Add a rake task to check repository integrity with `git fsck` - - Add ability to configure Reply-To address in gitlab.yml (Stan Hu) - - Move current user to the top of the list in assignee/author filters (Stan Hu) - - Fix broken side-by-side diff view on merge request page (Stan Hu) - - Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu) - - Allow HTML tags in Markdown input - - Fix code unfold not working on Compare commits page (Stan Hu) - - Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik) - - Fix "Import projects from" button to show the correct instructions (Stan Hu) - - Fix dots in Wiki slugs causing errors (Stan Hu) - - Make maximum attachment size configurable via Application Settings (Stan Hu) - - Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg) - - Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu) - - Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu) - - Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu) - - enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger) - - Fix a link in the patch update guide - - Add a service to support external wikis (Hannes Rosenögger) - - Omit the "email patches" link and fix plain diff view for merge commits - - List new commits for newly pushed branch in activity view. - - Add sidetiq gem dependency to match EE - - Add changelog, license and contribution guide links to project tab bar. - - Improve diff UI - - Fix alignment of navbar toggle button (Cody Mize) - - Fix checkbox rendering for nested task lists - - Identical look of selectboxes in UI - - Upgrade the gitlab_git gem to version 7.1.3 - - Move "Import existing repository by URL" option to button. - - Improve error message when save profile has error. - - Passing the name of pushed ref to CI service (requires GitLab CI 7.9+) - - Add location field to user profile - - Fix print view for markdown files and wiki pages - - Fix errors when deleting old backups - - Improve GitLab performance when working with git repositories - - Add tag message and last commit to tag hook (Kamil Trzciński) - - Restrict permissions on backup files - - Improve oauth accounts UI in profile page - - Add ability to unlink connected accounts - - Replace commits calendar with faster contribution calendar that includes issues and merge requests - - Add inifinite scroll to user page activity - - Don't include system notes in issue/MR comment count. - - Don't mark merge request as updated when merge status relative to target branch changes. - - Link note avatar to user. - - Make Git-over-SSH errors more descriptive. - - Fix EmailsOnPush. - - Refactor issue filtering - - AJAX selectbox for issue assignee and author filters - - Fix issue with missing options in issue filtering dropdown if selected one - - Prevent holding Control-Enter or Command-Enter from posting comment multiple times. - - Prevent note form from being cleared when submitting failed. - - Improve file icons rendering on tree (Sullivan Sénéchal) - - API: Add pagination to project events - - Get issue links in notification mail to work again. - - Don't show commit comment button when user is not signed in. - - Fix admin user projects lists. - - Don't leak private group existence by redirecting from namespace controller to group controller. - - Ability to skip some items from backup (database, respositories or uploads) - - Archive repositories in background worker. - - Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace. - - Project labels are now available over the API under the "tag_list" field (Cristian Medina) - - Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz) - - Fix and improve help rendering (Sullivan Sénéchal) - - Fix final line in EmailsOnPush email diff being rendered as error. - - Prevent duplicate Buildkite service creation. - - Fix git over ssh errors 'fatal: protocol error: bad line length character' - - Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled - - Bust group page project list cache when namespace name or path changes. - - Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded - - Allow user to choose a public email to show on public profile - - Remove truncation from issue titles on milestone page (Jason Blanchard) - - Fix stuck Merge Request merging events from old installations (Ben Bodenmiller) - - Fix merge request comments on files with multiple commits - - Fix Resource Owner Password Authentication Flow - - Add icons to Add dropdown items. - - Allow admin to create public deploy keys that are accessible to any project. - - Warn when gitlab-shell version doesn't match requirement. - - Skip email confirmation when set by admin or via LDAP. - - Only allow users to reference groups, projects, issues, MRs, commits they have access to. - -v 7.9.4 - - Security: Fix project import URL regex to prevent arbitary local repos from being imported - - Fixed issue where only 25 commits would load in file listings - - Fix LDAP identities after config update - -v 7.9.3 - - Contains no changes - -v 7.9.2 - - Contains no changes - -v 7.9.1 - - Include missing events and fix save functionality in admin service template settings form (Stan Hu) - - Fix "Import projects from" button to show the correct instructions (Stan Hu) - - Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu) - - Fix for LDAP with commas in DN - - Fix missing events and in admin Slack service template settings form (Stan Hu) - - Don't show commit comment button when user is not signed in. - - Downgrade gemnasium-gitlab-service gem - -v 7.9.0 - - Add HipChat integration documentation (Stan Hu) - - Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu) - - Fix broken email images (Hannes Rosenögger) - - Automatically config git if user forgot, where possible (Zeger-Jan van de Weg) - - Fix mass SQL statements on initial push (Hannes Rosenögger) - - Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu) - - Add comment notification events to HipChat and Slack services (Stan Hu) - - Add issue and merge request events to HipChat and Slack services (Stan Hu) - - Fix merge request URL passed to Webhooks. (Stan Hu) - - Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu) - - Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu) - - Move labels/milestones tabs to sidebar - - Upgrade Rails gem to version 4.1.9. - - Improve error messages for file edit failures - - Improve UI for commits, issues and merge request lists - - Fix commit comments on first line of diff not rendering in Merge Request Discussion view. - - Allow admins to override restricted project visibility settings. - - Move restricted visibility settings from gitlab.yml into the web UI. - - Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev) - - Save web edit in new branch - - Fix ordering of imported but unchanged projects (Marco Wessel) - - Mobile UI improvements: make aside content expandable - - Expose avatar_url in projects API - - Fix checkbox alignment on the application settings page. - - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) - - Fix mass-unassignment of issues (Robert Speicher) - - Fix hidden diff comments in merge request discussion view - - Allow user confirmation to be skipped for new users via API - - Add a service to send updates to an Irker gateway (Romain Coltel) - - Add brakeman (security scanner for Ruby on Rails) - - Slack username and channel options - - Add grouped milestones from all projects to dashboard. - - Webhook sends pusher email as well as commiter - - Add Bitbucket omniauth provider. - - Add Bitbucket importer. - - Support referencing issues to a project whose name starts with a digit - - Condense commits already in target branch when updating merge request source branch. - - Send notifications and leave system comments when bulk updating issues. - - Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison) - - Move groups page from profile to dashboard - - Starred projects page at dashboard - - Blocking user does not remove him/her from project/groups but show blocked label - - Change subject of EmailsOnPush emails to include namespace, project and branch. - - Change subject of EmailsOnPush emails to include first commit message when multiple were pushed. - - Remove confusing footer from EmailsOnPush mail body. - - Add list of changed files to EmailsOnPush emails. - - Add option to send EmailsOnPush emails from committer email if domain matches. - - Add option to disable code diffs in EmailOnPush emails. - - Wrap commit message in EmailsOnPush email. - - Send EmailsOnPush emails when deleting commits using force push. - - Fix EmailsOnPush email comparison link to include first commit. - - Fix highliht of selected lines in file - - Reject access to group/project avatar if the user doesn't have access. - - Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update) - - Add GitLab active users count to rake gitlab:check - - Starred projects page at dashboard - - Make email display name configurable - - Improve json validation in hook data - - Use Emoji One - - Updated emoji help documentation to properly reference EmojiOne. - - Fix missing GitHub organisation repositories on import page. - - Added blue theme - - Remove annoying notice messages when create/update merge request - - Allow smb:// links in Markdown text. - - Filter merge request by title or description at Merge Requests page - - Block user if he/she was blocked in Active Directory - - Fix import pages not working after first load. - - Use custom LDAP label in LDAP signin form. - - Execute hooks and services when branch or tag is created or deleted through web interface. - - Block and unblock user if he/she was blocked/unblocked in Active Directory - - Raise recommended number of unicorn workers from 2 to 3 - - Use same layout and interactivity for project members as group members. - - Prevent gitlab-shell character encoding issues by receiving its changes as raw data. - - Ability to unsubscribe/subscribe to issue or merge request - - Delete deploy key when last connection to a project is destroyed. - - Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther) - - Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup) - - Add canceled status for CI - - Send EmailsOnPush email when branch or tag is created or deleted. - - Faster merge request processing for large repository - - Prevent doubling AJAX request with each commit visit via Turbolink - - Prevent unnecessary doubling of js events on import pages and user calendar - -v 7.8.4 - - Fix issue_tracker_id substitution in custom issue trackers - - Fix path and name duplication in namespaces - -v 7.8.3 - - Bump version of gitlab_git fixing annotated tags without message - -v 7.8.2 - - Fix service migration issue when upgrading from versions prior to 7.3 - - Fix setting of the default use project limit via admin UI - - Fix showing of already imported projects for GitLab and Gitorious importers - - Fix response of push to repository to return "Not found" if user doesn't have access - - Fix check if user is allowed to view the file attachment - - Fix import check for case sensetive namespaces - - Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time. - - Properly handle autosave local storage exceptions. - - Escape wildcards when searching LDAP by username. - -v 7.8.1 - - Fix run of custom post receive hooks - - Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3 - - Fix the warning for LDAP users about need to set password - - Fix avatars which were not shown for non logged in users - - Fix urls for the issues when relative url was enabled - -v 7.8.0 - - Fix access control and protection against XSS for note attachments and other uploads. - - Replace highlight.js with rouge-fork rugments (Stefan Tatschner) - - Make project search case insensitive (Hannes Rosenögger) - - Include issue/mr participants in list of recipients for reassign/close/reopen emails - - Expose description in groups API - - Better UI for project services page - - Cleaner UI for web editor - - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger) - - Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen) - - View note image attachments in new tab when clicked instead of downloading them - - Improve sorting logic in UI and API. Explicitly define what sorting method is used by default - - Fix overflow at sidebar when have several items - - Add notes for label changes in issue and merge requests - - Show tags in commit view (Hannes Rosenögger) - - Only count a user's vote once on a merge request or issue (Michael Clarke) - - Increase font size when browse source files and diffs - - Service Templates now let you set default values for all services - - Create new file in empty repository using GitLab UI - - Ability to clone project using oauth2 token - - Upgrade Sidekiq gem to version 3.3.0 - - Stop git zombie creation during force push check - - Show success/error messages for test setting button in services - - Added Rubocop for code style checks - - Fix commits pagination - - Async load a branch information at the commit page - - Disable blacklist validation for project names - - Allow configuring protection of the default branch upon first push (Marco Wessel) - - Add gitlab.com importer - - Add an ability to login with gitlab.com - - Add a commit calendar to the user profile (Hannes Rosenögger) - - Submit comment on command-enter - - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`. - - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger) - - Fix long broadcast message cut-off on left sidebar (Visay Keo) - - Add Project Avatars (Steven Thonus and Hannes Rosenögger) - - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation. - - Edit group members via API - - Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks) - - Add action property to merge request hook (Julien Bianchi) - - Remove duplicates from group milestone participants list. - - Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger) - - API: Access groups with their path (Julien Bianchi) - - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard) - - Allow notification email to be set separately from primary email. - - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) - - Don't have Markdown preview fail for long comments/wiki pages. - - When test webhook - show error message instead of 500 error page if connection to hook url was reset - - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) - - Added persistent collapse button for left side nav bar (Jason Blanchard) - - Prevent losing unsaved comments by automatically restoring them when comment page is loaded again. - - Don't allow page to be scaled on mobile. - - Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up. - - Show assignees in merge request index page (Kelvin Mutuma) - - Link head panel titles to relevant root page. - - Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S). - - Show users button to share their newly created public or internal projects on twitter - - Add quick help links to the GitLab pricing and feature comparison pages. - - Fix duplicate authorized applications in user profile and incorrect application client count in admin area. - - Make sure Markdown previews always use the same styling as the eventual destination. - - Remove deprecated Group#owner_id from API - - Show projects user contributed to on user page. Show stars near project on user page. - - Improve database performance for GitLab - - Add Asana service (Jeremy Benoist) - - Improve project webhooks with extra data - -v 7.7.2 - - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch - - Fix issue when LDAP user can't login with existing GitLab account - -v 7.7.1 - - Improve mention autocomplete performance - - Show setup instructions for GitHub import if disabled - - Allow use http for OAuth applications - -v 7.7.0 - - Import from GitHub.com feature - - Add Jetbrains Teamcity CI service (Jason Lippert) - - Mention notification level - - Markdown preview in wiki (Yuriy Glukhov) - - Raise group avatar filesize limit to 200kb - - OAuth applications feature - - Show user SSH keys in admin area - - Developer can push to protected branches option - - Set project path instead of project name in create form - - Block Git HTTP access after 10 failed authentication attempts - - Updates to the messages returned by API (sponsored by O'Reilly Media) - - New UI layout with side navigation - - Add alert message in case of outdated browser (IE < 10) - - Added API support for sorting projects - - Update gitlab_git to version 7.0.0.rc14 - - Add API project search filter option for authorized projects - - Fix File blame not respecting branch selection - - Change some of application settings on fly in admin area UI - - Redesign signin/signup pages - - Close standard input in Gitlab::Popen.popen - - Trigger GitLab CI when push tags - - When accept merge request - do merge using sidaekiq job - - Enable web signups by default - - Fixes for diff comments: drag-n-drop images, selecting images - - Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update - - Remove password strength indicator - -v 7.6.0 - - Fork repository to groups - - New rugged version - - Add CRON=1 backup setting for quiet backups - - Fix failing wiki restore - - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable) - - Monokai highlighting style now more faithful to original design (Mark Riedesel) - - Create project with repository in synchrony - - Added ability to create empty repo or import existing one if project does not have repository - - Reactivate highlight.js language autodetection - - Mobile UI improvements - - Change maximum avatar file size from 100KB to 200KB - - Strict validation for snippet file names - - Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada) - - In the docker directory is a container template based on the Omnibus packages. - - Update Sidekiq to version 2.17.8 - - Add author filter to project issues and merge requests pages - - Atom feed for user activity - - Support multiple omniauth providers for the same user - - Rendering cross reference in issue title and tooltip for merge request - - Show username in comments - - Possibility to create Milestones or Labels when Issues are disabled - - Fix bug with showing gpg signature in tag - -v 7.5.3 - - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2) - -v 7.5.2 - - Don't log Sidekiq arguments by default - - Fix restore of wiki repositories from backups - -v 7.5.1 - - Add missing timestamps to 'members' table - -v 7.5.0 - - API: Add support for Hipchat (Kevin Houdebert) - - Add time zone configuration in gitlab.yml (Sullivan Senechal) - - Fix LDAP authentication for Git HTTP access - - Run 'GC.start' after every EmailsOnPushWorker job - - Fix LDAP config lookup for provider 'ldap' - - Drop all sequences during Postgres database restore - - Project title links to project homepage (Ben Bodenmiller) - - Add Atlassian Bamboo CI service (Drew Blessing) - - Mentioned @user will receive email even if he is not participating in issue or commit - - Session API: Use case-insensitive authentication like in UI (Andrey Krivko) - - Tie up loose ends with annotated tags: API & UI (Sean Edge) - - Return valid json for deleting branch via API (sponsored by O'Reilly Media) - - Expose username in project events API (sponsored by O'Reilly Media) - - Adds comments to commits in the API - - Performance improvements - - Fix post-receive issue for projects with deleted forks - - New gitlab-shell version with custom hooks support - - Improve code - - GitLab CI 5.2+ support (does not support older versions) - - Fixed bug when you can not push commits starting with 000000 to protected branches - - Added a password strength indicator - - Change project name and path in one form - - Display renamed files in diff views (Vinnie Okada) - - Fix raw view for public snippets - - Use secret token with GitLab internal API. - - Add missing timestamps to 'members' table - -v 7.4.5 - - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2) - -v 7.4.4 - - No changes - -v 7.4.3 - - Fix raw snippets view - - Fix security issue for member api - - Fix buildbox integration - -v 7.4.2 - - Fix internal snippet exposing for unauthenticated users - -v 7.4.1 - - Fix LDAP authentication for Git HTTP access - - Fix LDAP config lookup for provider 'ldap' - - Fix public snippets - - Fix 500 error on projects with nested submodules - -v 7.4.0 - - Refactored membership logic - - Improve error reporting on users API (Julien Bianchi) - - Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally - - Default branch is protected by default - - Increase unicorn timeout to 60 seconds - - Sort search autocomplete projects by stars count so most popular go first - - Add README to tab on project show page - - Do not delete tmp/repositories itself during clean-up, only its contents - - Support for backup uploads to remote storage - - Prevent notes polling when there are not notes - - Internal ForkService: Prepare support for fork to a given namespace - - API: Add support for forking a project via the API (Bernhard Kaindl) - - API: filter project issues by milestone (Julien Bianchi) - - Fail harder in the backup script - - Changes to Slack service structure, only webhook url needed - - Zen mode for wiki and milestones (Robert Schilling) - - Move Emoji parsing to html-pipeline-gitlab (Robert Schilling) - - Font Awesome 4.2 integration (Sullivan Senechal) - - Add Pushover service integration (Sullivan Senechal) - - Add select field type for services options (Sullivan Senechal) - - Add cross-project references to the Markdown parser (Vinnie Okada) - - Add task lists to issue and merge request descriptions (Vinnie Okada) - - Snippets can be public, internal or private - - Improve danger zone: ask project path to confirm data-loss action - - Raise exception on forgery - - Show build coverage in Merge Requests (requires GitLab CI v5.1) - - New milestone and label links on issue edit form - - Improved repository graphs - - Improve event note display in dashboard and project activity views (Vinnie Okada) - - Add users sorting to admin area - - UI improvements - - Fix ambiguous sha problem with mentioned commit - - Fixed bug with apostrophe when at mentioning users - - Add active directory ldap option - - Developers can push to wiki repo. Protected branches does not affect wiki repo any more - - Faster rev list - - Fix branch removal - -v 7.3.2 - - Fix creating new file via web editor - - Use gitlab-shell v2.0.1 - -v 7.3.1 - - Fix ref parsing in Gitlab::GitAccess - - Fix error 500 when viewing diff on a file with changed permissions - - Fix adding comments to MR when source branch is master - - Fix error 500 when searching description contains relative link - -v 7.3.0 - - Always set the 'origin' remote in satellite actions - - Write authorized_keys in tmp/ during tests - - Use sockets to connect to Redis - - Add dormant New Relic gem (can be enabled via environment variables) - - Expire Rack sessions after 1 week - - Cleaner signin/signup pages - - Improved comments UI - - Better search with filtering, pagination etc - - Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov) - - Prevent project stars duplication when fork project - - Use the default Unicorn socket backlog value of 1024 - - Support Unix domain sockets for Redis - - Store session Redis keys in 'session:gitlab:' namespace - - Deprecate LDAP account takeover based on partial LDAP email / GitLab username match - - Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy) - - Keyboard shortcuts for productivity (Robert Schilling) - - API: filter issues by state (Julien Bianchi) - - API: filter issues by labels (Julien Bianchi) - - Add system hook for ssh key changes - - Add blob permalink link (Ciro Santilli) - - Create annotated tags through UI and API (Sean Edge) - - Snippets search (Charles Bushong) - - Comment new push to existing MR - - Add 'ci' to the blacklist of forbidden names - - Improve text filtering on issues page - - Comment & Close button - - Process git push --all much faster - - Don't allow edit of system notes - - Project wiki search (Ralf Seidler) - - Enabled Shibboleth authentication support (Matus Banas) - - Zen mode (fullscreen) for issues/MR/notes (Robert Schilling) - - Add ability to configure webhook timeout via gitlab.yml (Wes Gurney) - - Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media) - - Add Redis socket support to 'rake gitlab:shell:install' - -v 7.2.1 - - Delete orphaned labels during label migration (James Brooks) - - Security: prevent XSS with stricter MIME types for raw repo files - -v 7.2.0 - - Explore page - - Add project stars (Ciro Santilli) - - Log Sidekiq arguments - - Better labels: colors, ability to rename and remove - - Improve the way merge request collects diffs - - Improve compare page for large diffs - - Expose the full commit message via API - - Fix 500 error on repository rename - - Fix bug when MR download patch return invalid diff - - Test gitlab-shell integration - - Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported - - API for labels (Robert Schilling) - - API: ability to set an import url when creating project for specific user - -v 7.1.1 - - Fix cpu usage issue in Firefox - - Fix redirect loop when changing password by new user - - Fix 500 error on new merge request page - -v 7.1.0 - - Remove observers - - Improve MR discussions - - Filter by description on Issues#index page - - Fix bug with namespace select when create new project page - - Show README link after description for non-master members - - Add @all mention for comments - - Dont show reply button if user is not signed in - - Expose more information for issues with webhook - - Add a mention of the merge request into the default merge request commit message - - Improve code highlight, introduce support for more languages like Go, Clojure, Erlang etc - - Fix concurrency issue in repository download - - Dont allow repository name start with ? - - Improve email threading (Pierre de La Morinerie) - - Cleaner help page - - Group milestones - - Improved email notifications - - Contributors API (sponsored by Mobbr) - - Fix LDAP TLS authentication (Boris HUISGEN) - - Show VERSION information on project sidebar - - Improve branch removal logic when accept MR - - Fix bug where comment form is spawned inside the Reply button - - Remove Dir.chdir from Satellite#lock for thread-safety - - Increased default git max_size value from 5MB to 20MB in gitlab.yml. Please update your configs! - - Show error message in case of timeout in satellite when create MR - - Show first 100 files for huge diff instead of hiding all - - Change default admin email from admin@local.host to admin@example.com - -v 7.0.0 - - The CPU no longer overheats when you hold down the spacebar - - Improve edit file UI - - Add ability to upload group avatar when create - - Protected branch cannot be removed - - Developers can remove normal branches with UI - - Remove branch via API (sponsored by O'Reilly Media) - - Move protected branches page to Project settings area - - Redirect to Files view when create new branch via UI - - Drag and drop upload of image in every markdown-area (Earle Randolph Bunao and Neil Francis Calabroso) - - Refactor the markdown relative links processing - - Make it easier to implement other CI services for GitLab - - Group masters can create projects in group - - Deprecate ruby 1.9.3 support - - Only masters can rewrite/remove git tags - - Add X-Frame-Options SAMEORIGIN to Nginx config so Sidekiq admin is visible - - UI improvements - - Case-insensetive search for issues - - Update to rails 4.1 - - Improve performance of application for projects and groups with a lot of members - - Formally support Ruby 2.1 - - Include Nginx gitlab-ssl config - - Add manual language detection for highlight.js - - Added example.com/:username routing - - Show notice if your profile is public - - UI improvements for mobile devices - - Improve diff rendering performance - - Drag-n-drop for issues and merge requests between states at milestone page - - Fix '0 commits' message for huge repositories on project home page - - Prevent 500 error page when visit commit page from large repo - - Add notice about huge push over http to unicorn config - - File action in satellites uses default 30 seconds timeout instead of old 10 seconds one - - Overall performance improvements - - Skip init script check on omnibus-gitlab - - Be more selective when killing stray Sidekiqs - - Check LDAP user filter during sign-in - - Remove wall feature (no data loss - you can take it from database) - - Dont expose user emails via API unless you are admin - - Detect issues closed by Merge Request description - - Better email subject lines from email on push service (Alex Elman) - - Enable identicon for gravatar be default - -v 6.9.2 - - Revert the commit that broke the LDAP user filter - -v 6.9.1 - - Fix scroll to highlighted line - - Fix the pagination on load for commits page - -v 6.9.0 - - Store Rails cache data in the Redis `cache:gitlab` namespace - - Adjust MySQL limits for existing installations - - Add db index on project_id+iid column. This prevents duplicate on iid (During migration duplicates will be removed) - - Markdown preview or diff during editing via web editor (Evgeniy Sokovikov) - - Give the Rails cache its own Redis namespace - - Add ability to set different ssh host, if different from http/https - - Fix syntax highlighting for code comments blocks - - Improve comments loading logic - - Stop refreshing comments when the tab is hidden - - Improve issue and merge request mobile UI (Drew Blessing) - - Document how to convert a backup to PostgreSQL - - Fix locale bug in backup manager - - Fix can not automerge when MR description is too long - - Fix wiki backup skip bug - - Two Step MR creation process - - Remove unwanted files from satellite working directory with git clean -fdx - - Accept merge request via API (sponsored by O'Reilly Media) - - Add more access checks during API calls - - Block SSH access for 'disabled' Active Directory users - - Labels for merge requests (Drew Blessing) - - Threaded emails by setting a Message-ID (Philip Blatter) - -v 6.8.0 - - Ability to at mention users that are participating in issue and merge req. discussion - - Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu) - - Make user search case-insensitive (Christopher Arnold) - - Remove omniauth-ldap nickname bug workaround - - Drop all tables before restoring a Postgres backup - - Make the repository downloads path configurable - - Create branches via API (sponsored by O'Reilly Media) - - Changed permission of gitlab-satellites directory not to be world accessible - - Protected branch does not allow force push - - Fix popen bug in `rake gitlab:satellites:create` - - Disable connection reaping for MySQL - - Allow oauth signup without email for twitter and github - - Fix faulty namespace names that caused 500 on user creation - - Option to disable standard login - - Clean old created archives from repository downloads directory - - Fix download link for huge MR diffs - - Expose event and mergerequest timestamps in API - - Fix emails on push service when only one commit is pushed - -v 6.7.3 - - Fix the merge notification email not being sent (Pierre de La Morinerie) - - Drop all tables before restoring a Postgres backup - - Remove yanked modernizr gem - -v 6.7.2 - - Fix upgrader script - -v 6.7.1 - - Fix GitLab CI integration - -v 6.7.0 - - Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations - - Add support for Gemnasium as a Project Service (Olivier Gonzalez) - - Add edit file button to MergeRequest diff - - Public groups (Jason Hollingsworth) - - Cleaner headers in Notification Emails (Pierre de La Morinerie) - - Blob and tree gfm links to anchors work - - Piwik Integration (Sebastian Winkler) - - Show contribution guide link for new issue form (Jeroen van Baarsen) - - Fix CI status for merge requests from fork - - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard) - - New page load indicator that includes a spinner that scrolls with the page - - Converted all the help sections into markdown - - LDAP user filters - - Streamline the content of notification emails (Pierre de La Morinerie) - - Fixes a bug with group member administration (Matt DeTullio) - - Sort tag names using VersionSorter (Robert Speicher) - - Add GFM autocompletion for MergeRequests (Robert Speicher) - - Add webhook when a new tag is pushed (Jeroen van Baarsen) - - Add button for toggling inline comments in diff view - - Add retry feature for repository import - - Reuse the GitLab LDAP connection within each request - - Changed markdown new line behaviour to conform to markdown standards - - Fix global search - - Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5) - - Create and Update MR calls now support the description parameter (Greg Messner) - - Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository - - Added Slack service integration (Federico Ravasio) - - Better API responses for access_levels (sponsored by O'Reilly Media) - - Requires at least 2 unicorn workers - - Requires gitlab-shell v1.9+ - - Replaced gemoji(due to closed licencing problem) with Phantom Open Emoji library(combined SIL Open Font License, MIT License and the CC 3.0 License) - - Fix `/:username.keys` response content type (Dmitry Medvinsky) - -v 6.6.5 - - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard) - - Hide mr close button for comment form if merge request was closed or inline comment - - Adds ability to reopen closed merge request - -v 6.6.4 - - Add missing html escape for highlighted code blocks in comments, issues - -v 6.6.3 - - Fix 500 error when edit yourself from admin area - - Hide private groups for public profiles - -v 6.6.2 - - Fix 500 error on branch/tag create or remove via UI - -v 6.6.1 - - Fix 500 error on files tab if submodules presents - -v 6.6.0 - - Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys - - Permissions: Developer now can manage issue tracker (modify any issue) - - Improve Code Compare page performance - - Group avatar - - Pygments.rb replaced with highlight.js - - Improve Merge request diff store logic - - Improve render performnace for MR show page - - Fixed Assembla hardcoded project name - - Jira integration documentation - - Refactored app/services - - Remove snippet expiration - - Mobile UI improvements (Drew Blessing) - - Fix block/remove UI for admin::users#show page - - Show users' group membership on users' activity page (Robert Djurasaj) - - User pages are visible without login if user is authorized to a public project - - Markdown rendered headers have id derived from their name and link to their id - - Improve application to work faster with large groups (100+ members) - - Multiple emails per user - - Show last commit for file when view file source - - Restyle Issue#show page and MR#show page - - Ability to filter by multiple labels for Issues page - - Rails version to 4.0.3 - - Fixed attachment identifier displaying underneath note text (Jason Blanchard) - -v 6.5.1 - - Fix branch selectbox when create merge request from fork - -v 6.5.0 - - Dropdown menus on issue#show page for assignee and milestone (Jason Blanchard) - - Add color custimization and previewing to broadcast messages - - Fixed notes anchors - - Load new comments in issues dynamically - - Added sort options to Public page - - New filters (assigned/authored/all) for Dashboard#issues/merge_requests (sponsored by Say Media) - - Add project visibility icons to dashboard - - Enable secure cookies if https used - - Protect users/confirmation with rack_attack - - Default HTTP headers to protect against MIME-sniffing, force https if enabled - - Bootstrap 3 with responsive UI - - New repository download formats: tar.bz2, zip, tar (Jason Hollingsworth) - - Restyled accept widgets for MR - - SCSS refactored - - Use jquery timeago plugin - - Fix 500 error for rdoc files - - Ability to customize merge commit message (sponsored by Say Media) - - Search autocomplete via ajax - - Add website url to user profile - - Files API supports base64 encoded content (sponsored by O'Reilly Media) - - Added support for Go's repository retrieval (Bruno Albuquerque) - -v 6.4.3 - - Don't use unicorn worker killer if PhusionPassenger is defined - -v 6.4.2 - - Fixed wrong behaviour of script/upgrade.rb - -v 6.4.1 - - Fixed bug with repository rename - - Fixed bug with project transfer - -v 6.4.0 - - Added sorting to project issues page (Jason Blanchard) - - Assembla integration (Carlos Paramio) - - Fixed another 500 error with submodules - - UI: More compact issues page - - Minimal password length increased to 8 symbols - - Side-by-side diff view (Steven Thonus) - - Internal projects (Jason Hollingsworth) - - Allow removal of avatar (Drew Blessing) - - Project webhooks now support issues and merge request events - - Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth) - - Expire event cache on avatar creation/removal (Drew Blessing) - - Archiving old projects (Steven Thonus) - - Rails 4 - - Add time ago tooltips to show actual date/time - - UI: Fixed UI for admin system hooks - - Ruby script for easier GitLab upgrade - - Do not remove Merge requests if fork project was removed - - Improve sign-in/signup UX - - Add resend confirmation link to sign-in page - - Set noreply@HOSTNAME for reply_to field in all emails - - Show GitLab API version on Admin#dashboard - - API Cross-origin resource sharing - - Show READMe link at project home page - - Show repo size for projects in Admin area - -v 6.3.0 - - API for adding gitlab-ci service - - Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey) - - Restyle project home page - - Grammar fixes - - Show branches list (which branches contains commit) on commit page (Andrew Kumanyaev) - - Security improvements - - Added support for GitLab CI 4.0 - - Fixed issue with 500 error when group did not exist - - Ability to leave project - - You can create file in repo using UI - - You can remove file from repo using UI - - API: dropped default_branch attribute from project during creation - - Project default_branch is not stored in db any more. It takes from repo now. - - Admin broadcast messages - - UI improvements - - Dont show last push widget if user removed this branch - - Fix 500 error for repos with newline in file name - - Extended html titles - - API: create/update/delete repo files - - Admin can transfer project to any namespace - - API: projects/all for admin users - - Fix recent branches order - -v 6.2.4 - - Security: Cast API private_token to string (CVE-2013-4580) - - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583) - - Fix for Git SSH access for LDAP users - -v 6.2.3 - - Security: More protection against CVE-2013-4489 - - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546) - - Fix sidekiq rake tasks - -v 6.2.2 - - Security: Update gitlab_git (CVE-2013-4489) - -v 6.2.1 - - Security: Fix issue with generated passwords for new users - -v 6.2.0 - - Public project pages are now visible to everyone (files, issues, wik, etc.) - THIS MEANS YOUR ISSUES AND WIKI FOR PUBLIC PROJECTS ARE PUBLICLY VISIBLE AFTER THE UPGRADE - - Add group access to permissions page - - Require current password to change one - - Group owner or admin can remove other group owners - - Remove group transfer since we have multiple owners - - Respect authorization in Repository API - - Improve UI for Project#files page - - Add more security specs - - Added search for projects by name to api (Izaak Alpert) - - Make default user theme configurable (Izaak Alpert) - - Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev) - - Rake tasks for webhooks management (Jonhnny Weslley) - - Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov) - - API: Remove group - - API: Remove project - - Avatar upload on profile page with a maximum of 100KB (Steven Thonus) - - Store the sessions in Redis instead of the cookie store - - Fixed relative links in markdown - - User must confirm their email if signup enabled - - User must confirm changed email - -v 6.1.0 - - Project specific IDs for issues, mr, milestones - Above items will get a new id and for example all bookmarked issue urls will change. - Old issue urls are redirected to the new one if the issue id is too high for an internal id. - - Description field added to Merge Request - - API: Sudo api calls (Izaak Alpert) - - API: Group membership api (Izaak Alpert) - - Improved commit diff - - Improved large commit handling (Boyan Tabakov) - - Rewrite: Init script now less prone to errors and keeps better track of the service (Rovanion Luckey) - - Link issues, merge requests, and commits when they reference each other with GFM (Ash Wilson) - - Close issues automatically when pushing commits with a special message - - Improve user removal from admin area - - Invalidate events cache when project was moved - - Remove deprecated classes and rake tasks - - Add event filter for group and project show pages - - Add links to create branch/tag from project home page - - Add public-project? checkbox to new-project view - - Improved compare page. Added link to proceed into Merge Request - - Send an email to a user when they are added to group - - New landing page when you have 0 projects - -v 6.0.0 - - Feature: Replace teams with group membership - We introduce group membership in 6.0 as a replacement for teams. - The old combination of groups and teams was confusing for a lot of people. - And when the members of a team where changed this wasn't reflected in the project permissions. - In GitLab 6.0 you will be able to add members to a group with a permission level for each member. - These group members will have access to the projects in that group. - Any changes to group members will immediately be reflected in the project permissions. - You can even have multiple owners for a group, greatly simplifying administration. - - Feature: Ability to have multiple owners for group - - Feature: Merge Requests between fork and project (Izaak Alpert) - - Feature: Generate fingerprint for ssh keys - - Feature: Ability to create and remove branches with UI - - Feature: Ability to create and remove git tags with UI - - Feature: Groups page in profile. You can leave group there - - API: Allow login with LDAP credentials - - Redesign: project settings navigation - - Redesign: snippets area - - Redesign: ssh keys page - - Redesign: buttons, blocks and other ui elements - - Add comment title to rss feed - - You can use arrows to navigate at tree view - - Add project filter on dashboard - - Cache project graph - - Drop support of root namespaces - - Default theme is classic now - - Cache result of methods like authorize_projects, project.team.members etc - - Remove $.ready events - - Fix onclick events being double binded - - Add notification level to group membership - - Move all project controllers/views under Projects:: module - - Move all profile controllers/views under Profiles:: module - - Apply user project limit only for personal projects - - Unicorn is default web server again - - Store satellites lock files inside satellites dir - - Disabled threadsafety mode in rails - - Fixed bug with loosing MR comments - - Improved MR comments logic - - Render readme file for projects in public area - -v 5.4.2 - - Security: Cast API private_token to string (CVE-2013-4580) - - Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583) - -v 5.4.1 - - Security: Fixes for CVE-2013-4489 - - Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546) - -v 5.4.0 - - Ability to edit own comments - - Documentation improvements - - Improve dashboard projects page - - Fixed nav for empty repos - - GitLab Markdown help page - - Misspelling fixes - - Added support of unicorn and fog gems - - Added client list to API doc - - Fix PostgreSQL database restoration problem - - Increase snippet content column size - - allow project import via git:// url - - Show participants on issues, including mentions - - Notify mentioned users with email - -v 5.3.0 - - Refactored services - - Campfire service added - - HipChat service added - - Fixed bug with LDAP + git over http - - Fixed bug with google analytics code being ignored - - Improve sign-in page if ldap enabled - - Respect newlines in wall messages - - Generate the Rails secret token on first run - - Rename repo feature - - Init.d: remove gitlab.socket on service start - - Api: added teams api - - Api: Prevent blob content being escaped - - Api: Smart deploy key add behaviour - - Api: projects/owned.json return user owned project - - Fix bug with team assignation on project from #4109 - - Advanced snippets: public/private, project/personal (Andrew Kulakov) - - Repository Graphs (Karlo Nicholas T. Soriano) - - Fix dashboard lost if comment on commit - - Update gitlab-grack. Fixes issue with --depth option - - Fix project events duplicate on project page - - Fix postgres error when displaying network graph. - - Fix dashboard event filter when navigate via turbolinks - - init.d: Ensure socket is removed before starting service - - Admin area: Style teams:index, group:show pages - - Own page for failed forking - - Scrum view for milestone - -v 5.2.0 - - Turbolinks - - Git over http with ldap credentials - - Diff with better colors and some spacing on the corners - - Default values for project features - - Fixed huge_commit view - - Restyle project clone panel - - Move Gitlab::Git code to gitlab_git gem - - Move update docs in repo - - Requires gitlab-shell v1.4.0 - - Fixed submodules listing under file tab - - Fork feature (Angus MacArthur) - - git version check in gitlab:check - - Shared deploy keys feature - - Ability to generate default labels set for issues - - Improve gfm autocomplete (Harold Luo) - - Added support for Google Analytics - - Code search feature (Javier Castro) - -v 5.1.0 - - You can login with email or username now - - Corrected project transfer rollback when repository cannot be moved - - Move both repo and wiki when project transfer requested - - Admin area: project editing was removed from admin namespace - - Access: admin user has now access to any project. - - Notification settings - - Gitlab::Git set of objects to abstract from grit library - - Replace Unicorn web server with Puma - - Backup/Restore refactored. Backup dump project wiki too now - - Restyled Issues list. Show milestone version in issue row - - Restyled Merge Request list - - Backup now dump/restore uploads - - Improved performance of dashboard (Andrew Kumanyaev) - - File history now tracks renames (Akzhan Abdulin) - - Drop wiki migration tools - - Drop sqlite migration tools - - project tagging - - Paginate users in API - - Restyled network graph (Hiroyuki Sato) - -v 5.0.1 - - Fixed issue with gitlab-grit being overridden by grit - -v 5.0.0 - - Replaced gitolite with gitlab-shell - - Removed gitolite-related libraries - - State machine added - - Setup gitlab as git user - - Internal API - - Show team tab for empty projects - - Import repository feature - - Updated rails - - Use lambda for scopes - - Redesign admin area -> users - - Redesign admin area -> user - - Secure link to file attachments - - Add validations for Group and Team names - - Restyle team page for project - - Update capybara, rspec-rails, poltergeist to recent versions - - Wiki on git using Gollum - - Added Solarized Dark theme for code review - - Don't show user emails in autocomplete lists, profile pages - - Added settings tab for group, team, project - - Replace user popup with icons in header - - Handle project moving with gitlab-shell - - Added select2-rails for selectboxes with ajax data load - - Fixed search field on projects page - - Added teams to search autocomplete - - Move groups and teams on dashboard sidebar to sub-tabs - - API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell) - - Redesign wall to be more like chat - - Snippets, Wall features are disabled by default for new projects - -v 4.2.0 - - Teams - - User show page. Via /u/username - - Show help contents on pages for better navigation - - Async gitolite calls - - added satellites logs - - can_create_group, can_create_team booleans for User - - Process webhooks async - - GFM: Fix images escaped inside links - - Network graph improved - - Switchable branches for network graph - - API: Groups - - Fixed project download - -v 4.1.0 - - Optional Sign-Up - - Discussions - - Satellites outside of tmp - - Line numbers for blame - - Project public mode - - Public area with unauthorized access - - Load dashboard events with ajax - - remember dashboard filter in cookies - - replace resque with sidekiq - - fix routing issues - - cleanup rake tasks - - fix backup/restore - - scss cleanup - - show preview for note images - - improved network-graph - - get rid of app/roles/ - - added new classes Team, Repository - - Reduce amount of gitolite calls - - Ability to add user in all group projects - - remove deprecated configs - - replaced Korolev font with open font - - restyled admin/dashboard page - - restyled admin/projects page - -v 4.0.0 - - Remove project code and path from API. Use id instead - - Return valid cloneable url to repo for webhook - - Fixed backup issue - - Reorganized settings - - Fixed commits compare - - Refactored scss - - Improve status checks - - Validates presence of User#name - - Fixed postgres support - - Removed sqlite support - - Modified post-receive hook - - Milestones can be closed now - - Show comment events on dashboard - - Quick add team members via group#people page - - [API] expose created date for hooks and SSH keys - - [API] list, create issue notes - - [API] list, create snippet notes - - [API] list, create wall notes - - Remove project code - use path instead - - added username field to user - - rake task to fill usernames based on emails create namespaces for users - - STI Group < Namespace - - Project has namespace_id - - Projects with namespaces also namespaced in gitolite and stored in subdir - - Moving project to group will move it under group namespace - - Ability to move project from namespaces to another - - Fixes commit patches getting escaped (see #2036) - - Support diff and patch generation for commits and merge request - - MergeReqest doesn't generate a temporary file for the patch any more - - Update the UI to allow downloading Patch or Diff - -v 3.1.0 - - Updated gems - - Services: Gitlab CI integration - - Events filter on dashboard - - Own namespace for redis/resque - - Optimized commit diff views - - add alphabetical order for projects admin page - - Improved web editor - - Commit stats page - - Documentation split and cleanup - - Link to commit authors everywhere - - Restyled milestones list - - added Milestone to Merge Request - - Restyled Top panel - - Refactored Satellite Code - - Added file line links - - moved from capybara-webkit to poltergeist + phantomjs - -v 3.0.3 - - Fixed bug with issues list in Chrome - - New Feature: Import team from another project - -v 3.0.2 - - Fixed gitlab:app:setup - - Fixed application error on empty project in admin area - - Restyled last push widget - -v 3.0.1 - - Fixed git over http - -v 3.0.0 - - Projects groups - - Web Editor - - Fixed bug with gitolite keys - - UI improved - - Increased performance of application - - Show user avatar in last commit when browsing Files - - Refactored Gitlab::Merge - - Use Font Awesome for icons - - Separate observing of Note and MergeRequests - - Milestone "All Issues" filter - - Fix issue close and reopen button text and styles - - Fix forward/back while browsing Tree hierarchy - - Show number of notes for commits and merge requests - - Added support pg from box and update installation doc - - Reject ssh keys that break gitolite - - [API] list one project hook - - [API] edit project hook - - [API] list project snippets - - [API] allow to authorize using private token in HTTP header - - [API] add user creation - -v 2.9.1 - - Fixed resque custom config init - -v 2.9.0 - - fixed inline notes bugs - - refactored rspecs - - refactored gitolite backend - - added factory_girl - - restyled projects list on dashboard - - ssh keys validation to prevent gitolite crash - - send notifications if changed permission in project - - scss refactoring. gitlab_bootstrap/ dir - - fix git push http body bigger than 112k problem - - list of labels page under issues tab - - API for milestones, keys - - restyled buttons - - OAuth - - Comment order changed - -v 2.8.1 - - ability to disable gravatars - - improved MR diff logic - - ssh key help page - -v 2.8.0 - - Gitlab Flavored Markdown - - Bulk issues update - - Issues API - - Cucumber coverage increased - - Post-receive files fixed - - UI improved - - Application cleanup - - more cucumber - - capybara-webkit + headless - -v 2.7.0 - - Issue Labels - - Inline diff - - Git HTTP - - API - - UI improved - - System hooks - - UI improved - - Dashboard events endless scroll - - Source performance increased - -v 2.6.0 - - UI polished - - Improved network graph + keyboard nav - - Handle huge commits - - Last Push widget - - Bugfix - - Better performance - - Email in resque - - Increased test coverage - - Ability to remove branch with MR accept - - a lot of code refactored - -v 2.5.0 - - UI polished - - Git blame for file - - Bugfix - - Email in resque - - Better test coverage - -v 2.4.0 - - Admin area stats page - - Ability to block user - - Simplified dashboard area - - Improved admin area - - Bootstrap 2.0 - - Responsive layout - - Big commits handling - - Performance improved - - Milestones - -v 2.3.1 - - Issues pagination - - ssl fixes - - Merge Request pagination - -v 2.3.0 - - Dashboard r1 - - Search r1 - - Project page - - Close merge request on push - - Persist MR diff after merge - - mysql support - - Documentation - -v 2.2.0 - - We’ve added support of LDAP auth - - Improved permission logic (4 roles system) - - Protected branches (now only masters can push to protected branches) - - Usability improved - - twitter bootstrap integrated - - compare view between commits - - wiki feature - - now you can enable/disable issues, wiki, wall features per project - - security fixes - - improved code browsing (ajax branch switch etc) - - improved per-line commenting - - git submodules displayed - - moved to rails 3.2 - - help section improved - -v 2.1.0 - - Project tab r1 - - List branches/tags - - per line comments - - mass user import - -v 2.0.0 - - gitolite as main git host system - - merge requests - - project/repo access - - link to commit/issue feed - - design tab - - improved email notifications - - restyled dashboard - - bugfix - -v 1.2.2 - - common config file gitlab.yml - - issues restyle - - snippets restyle - - clickable news feed header on dashboard - - bugfix - -v 1.2.1 - - bugfix - -v 1.2.0 - - new design - - user dashboard - - network graph - - markdown support for comments - - encoding issues - - wall like twitter timeline - -v 1.1.0 - - project dashboard - - wall redesigned - - feature: code snippets - - fixed horizontal scroll on file preview - - fixed app crash if commit message has invalid chars - - bugfix & code cleaning - -v 1.0.2 - - fixed bug with empty project - - added adv validation for project path & code - - feature: issues can be sortable - - bugfix - - username displayed on top panel - -v 1.0.1 - - fixed: with invalid source code for commit - - fixed: lose branch/tag selection when use tree navigation - - when history clicked - display path - - bug fix & code cleaning - -v 1.0.0 - - bug fix - - projects preview mode - -v 0.9.6 - - css fix - - new repo empty tree until restart server - fixed - -v 0.9.4 - - security improved - - authorization improved - - html escaping - - bug fix - - increased test coverage - - design improvements - -v 0.9.1 - - increased test coverage - - design improvements - - new issue email notification - - updated app name - - issue redesigned - - issue can be edit - -v 0.8.0 - - syntax highlight for main file types - - redesign - - stability - - security fixes - - increased test coverage - - email notification +v 7.14.3 through 0.8.0 + - See changelogs/archive.md diff --git a/changelogs/archive.md b/changelogs/archive.md new file mode 100644 index 00000000000..c68ab694d39 --- /dev/null +++ b/changelogs/archive.md @@ -0,0 +1,1810 @@ +## 7.14.3 + +- No changes + +## 7.14.2 + +- Upgrade gitlab_git to 7.2.15 to fix `git blame` errors with ISO-encoded files (Stan Hu) +- Allow configuration of LDAP attributes GitLab will use for the new user account. + +## 7.14.1 + +- Improve abuse reports management from admin area +- Fix "Reload with full diff" URL button in compare branch view (Stan Hu) +- Disabled DNS lookups for SSH in docker image (Rowan Wookey) +- Only include base URL in OmniAuth full_host parameter (Stan Hu) +- Fix Error 500 in API when accessing a group that has an avatar (Stan Hu) +- Ability to enable SSL verification for Webhooks + +## 7.14.0 + +- Fix bug where non-project members of the target project could set labels on new merge requests. +- Update default robots.txt rules to disallow crawling of irrelevant pages (Ben Bodenmiller) +- Fix redirection after sign in when using auto_sign_in_with_provider +- Upgrade gitlab_git to 7.2.14 to ignore CRLFs in .gitmodules (Stan Hu) +- Clear cache to prevent listing deleted branches after MR removes source branch (Stan Hu) +- Provide more feedback what went wrong if HipChat service failed test (Stan Hu) +- Fix bug where backslashes in inline diffs could be dropped (Stan Hu) +- Disable turbolinks when linking to Bitbucket import status (Stan Hu) +- Fix broken code import and display error messages if something went wrong with creating project (Stan Hu) +- Fix corrupted binary files when using API files endpoint (Stan Hu) +- Bump Haml to 4.0.7 to speed up textarea rendering (Stan Hu) +- Show incompatible projects in Bitbucket import status (Stan Hu) +- Fix coloring of diffs on MR Discussion-tab (Gert Goet) +- Fix "Network" and "Graphs" pages for branches with encoded slashes (Stan Hu) +- Fix errors deleting and creating branches with encoded slashes (Stan Hu) +- Always add current user to autocomplete controller to support filter by "Me" (Stan Hu) +- Fix multi-line syntax highlighting (Stan Hu) +- Fix network graph when branch name has single quotes (Stan Hu) +- Add "Confirm user" button in user admin page (Stan Hu) +- Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu) +- Add support for Unicode filenames in relative links (Hiroyuki Sato) +- Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki) +- Fix commit data retrieval when branch name has single quotes (Stan Hu) +- Check that project was actually created rather than just validated in import:repos task (Stan Hu) +- Fix full screen mode for snippet comments (Daniel Gerhardt) +- Fix 404 error in files view after deleting the last file in a repository (Stan Hu) +- Fix the "Reload with full diff" URL button (Stan Hu) +- Fix label read access for unauthenticated users (Daniel Gerhardt) +- Fix access to disabled features for unauthenticated users (Daniel Gerhardt) +- Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu) +- Fix file upload dialog for comment editing (Daniel Gerhardt) +- Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu) +- Return comments in created order in merge request API (Stan Hu) +- Disable internal issue tracker controller if external tracker is used (Stan Hu) +- Expire Rails cache entries after two weeks to prevent endless Redis growth +- Add support for destroying project milestones (Stan Hu) +- Allow custom backup archive permissions +- Add project star and fork count, group avatar URL and user/group web URL attributes to API +- Show who last edited a comment if it wasn't the original author +- Send notification to all participants when MR is merged. +- Add ability to manage user email addresses via the API. +- Show buttons to add license, changelog and contribution guide if they're missing. +- Tweak project page buttons. +- Disabled autocapitalize and autocorrect on login field (Daryl Chan) +- Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis) +- Update gravatar link on profile page to link to configured gravatar host (Ben Bodenmiller) +- Remove redis-store TTL monkey patch +- Add support for CI skipped status +- Fetch code from forks to refs/merge-requests/:id/head when merge request created +- Remove comments and email addresses when publicly exposing ssh keys (Zeger-Jan van de Weg) +- Add "Check out branch" button to the MR page. +- Improve MR merge widget text and UI consistency. +- Improve text in MR "How To Merge" modal. +- Cache all events +- Order commits by date when comparing branches +- Fix bug causing error when the target branch of a symbolic ref was deleted +- Include branch/tag name in archive file and directory name +- Add dropzone upload progress +- Add a label for merged branches on branches page (Florent Baldino) +- Detect .mkd and .mkdn files as markdown (Ben Boeckel) +- Fix: User search feature in admin area does not respect filters +- Set max-width for README, issue and merge request description for easier read on big screens +- Update Flowdock integration to support new Flowdock API (Boyan Tabakov) +- Remove author from files view (Sven Strickroth) +- Fix infinite loop when SAML was incorrectly configured. + +## 7.13.5 + +- Satellites reverted + +## 7.13.4 + +- Allow users to send abuse reports + +## 7.13.3 + +- Fix bug causing Bitbucket importer to crash when OAuth application had been removed. +- Allow users to send abuse reports +- Remove satellites +- Link username to profile on Group Members page (Tom Webster) + +## 7.13.2 + +- Fix randomly failed spec +- Create project services on Project creation +- Add admin_merge_request ability to Developer level and up +- Fix Error 500 when browsing projects with no HEAD (Stan Hu) +- Fix labels / assignee / milestone for the merge requests when issues are disabled +- Show the first tab automatically on MergeRequests#new +- Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt) +- Fix Gmail Actions + +## 7.13.1 + +- Fix: Label modifications are not reflected in existing notes and in the issue list +- Fix: Label not shown in the Issue list, although it's set through web interface +- Fix: Group/project references are linked incorrectly +- Improve documentation +- Fix of migration: Check if session_expire_delay column exists before adding the column +- Fix: ActionView::Template::Error +- Fix: "Create Merge Request" isn't always shown in event for newly pushed branch +- Fix bug causing "Remove source-branch" option not to work for merge requests from the same project. +- Render Note field hints consistently for "new" and "edit" forms + +## 7.13.0 + +- Remove repository graph log to fix slow cache updates after push event (Stan Hu) +- Only enable HSTS header for HTTPS and port 443 (Stan Hu) +- Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu) +- Fix redirection to home page URL for unauthorized users (Daniel Gerhardt) +- Add branch switching support for graphs (Daniel Gerhardt) +- Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt) +- Remove link leading to a 404 error in Deploy Keys page (Stan Hu) +- Add support for unlocking users in admin settings (Stan Hu) +- Add Irker service configuration options (Stan Hu) +- Fix order of issues imported from GitHub (Hiroyuki Sato) +- Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart) +- Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI +- Add `two_factor_enabled` field to admin user API (Stan Hu) +- Fix invalid timestamps in RSS feeds (Rowan Wookey) +- Fix downloading of patches on public merge requests when user logged out (Stan Hu) +- Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu) +- Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu) +- Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu) +- Support commenting on diffs in side-by-side mode (Stan Hu) +- Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu) +- Return 40x error codes if branch could not be deleted in UI (Stan Hu) +- Remove project visibility icons from dashboard projects list +- Rename "Design" profile settings page to "Preferences". +- Allow users to customize their default Dashboard page. +- Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8 +- Admin can edit and remove user identities +- Convert CRLF newlines to LF when committing using the web editor. +- API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged. +- Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled. +- Show a user's Two-factor Authentication status in the administration area. +- Explicit error when commit not found in the CI +- Improve performance for issue and merge request pages +- Users with guest access level can not set assignee, labels or milestones for issue and merge request +- Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels +- Better performance for pages with events list, issues list and commits list +- Faster automerge check and merge itself when source and target branches are in same repository +- Correctly show anonymous authorized applications under Profile > Applications. +- Query Optimization in MySQL. +- Allow users to be blocked and unblocked via the API +- Use native Postgres database cleaning during backup restore +- Redesign project page. Show README as default instead of activity. Move project activity to separate page +- Make left menu more hierarchical and less contextual by adding back item at top +- A fork can’t have a visibility level that is greater than the original project. +- Faster code search in repository and wiki. Fixes search page timeout for big repositories +- Allow administrators to disable 2FA for a specific user +- Add error message for SSH key linebreaks +- Store commits count in database (will populate with valid values only after first push) +- Rebuild cache after push to repository in background job +- Fix transferring of project to another group using the API. + +## 7.12.2 + +- Correctly show anonymous authorized applications under Profile > Applications. +- Faster automerge check and merge itself when source and target branches are in same repository +- Audit log for user authentication +- Allow custom label to be set for authentication providers. + +## 7.12.1 + +- Fix error when deleting a user who has projects (Stan Hu) +- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu) +- Add SAML to list of social_provider (Matt Firtion) +- Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets) +- Fix closed merge request scope at milestone page (Dmitriy Zaporozhets) +- Revert merge request states renaming +- Fix hooks for web based events with external issue references (Daniel Gerhardt) +- Improve performance for issue and merge request pages +- Compress database dumps to reduce backup size + +## 7.12.0 + +- Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu) +- Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu) +- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu) +- Update oauth button logos for Twitter and Google to recommended assets +- Update browser gem to version 0.8.0 for IE11 support (Stan Hu) +- Fix timeout when rendering file with thousands of lines. +- Add "Remember me" checkbox to LDAP signin form. +- Add session expiration delay configuration through UI application settings +- Don't notify users mentioned in code blocks or blockquotes. +- Omit link to generate labels if user does not have access to create them (Stan Hu) +- Show warning when a comment will add 10 or more people to the discussion. +- Disable changing of the source branch in merge request update API (Stan Hu) +- Shorten merge request WIP text. +- Add option to disallow users from registering any application to use GitLab as an OAuth provider +- Support editing target branch of merge request (Stan Hu) +- Refactor permission checks with issues and merge requests project settings (Stan Hu) +- Fix Markdown preview not working in Edit Milestone page (Stan Hu) +- Fix Zen Mode not closing with ESC key (Stan Hu) +- Allow HipChat API version to be blank and default to v2 (Stan Hu) +- Add file attachment support in Milestone description (Stan Hu) +- Fix milestone "Browse Issues" button. +- Set milestone on new issue when creating issue from index with milestone filter active. +- Make namespace API available to all users (Stan Hu) +- Add webhook support for note events (Stan Hu) +- Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) +- Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu) +- Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu) +- Fix git blame syntax highlighting when different commits break up lines (Stan Hu) +- Add "Resend confirmation e-mail" link in profile settings (Stan Hu) +- Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) +- Disabled expansion of top/bottom blobs for new file diffs +- Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) +- Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) +- Use the user list from the target project in a merge request (Stan Hu) +- Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen) +- Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen) +- Fix new/empty milestones showing 100% completion value (Jonah Bishop) +- Add a note when an Issue or Merge Request's title changes +- Consistently refer to MRs as either Merged or Closed. +- Add Merged tab to MR lists. +- Prefix EmailsOnPush email subject with `[Git]`. +- Group project contributions by both name and email. +- Clarify navigation labels for Project Settings and Group Settings. +- Move user avatar and logout button to sidebar +- You can not remove user if he/she is an only owner of group +- User should be able to leave group. If not - show him proper message +- User has ability to leave project +- Add SAML support as an omniauth provider +- Allow to configure a URL to show after sign out +- Add an option to automatically sign-in with an Omniauth provider +- GitLab CI service sends .gitlab-ci.yml in each push call +- When remove project - move repository and schedule it removal +- Improve group removing logic +- Trigger create-hooks on backup restore task +- Add option to automatically link omniauth and LDAP identities +- Allow special character in users bio. I.e.: I <3 GitLab + +## 7.11.4 + +- Fix missing bullets when creating lists +- Set rel="nofollow" on external links + +## 7.11.3 + +- no changes +- Fix upgrader script (Martins Polakovs) + +## 7.11.2 + +- no changes + +## 7.11.1 + +- no changes + +## 7.11.0 + +- Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) +- Get editing comments to work in Chrome 43 again. +- Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) +- Don't show duplicate deploy keys +- Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger) +- Make the first branch pushed to an empty repository the default HEAD (Stan Hu) +- Fix broken view when using a tag to display a tree that contains git submodules (Stan Hu) +- Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu) +- Add application setting to restrict user signups to e-mail domains (Stan Hu) +- Don't allow a merge request to be merged when its title starts with "WIP". +- Add a page title to every page. +- Allow primary email to be set to an email that you've already added. +- Fix clone URL field and X11 Primary selection (Dmitry Medvinsky) +- Ignore invalid lines in .gitmodules +- Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu) +- Redirect to sign in page after signing out. +- Fix "Hello @username." references not working by no longer allowing usernames to end in period. +- Fix "Revspec not found" errors when viewing diffs in a forked project with submodules (Stan Hu) +- Improve project page UI +- Fix broken file browsing with relative submodule in personal projects (Stan Hu) +- Add "Reply quoting selected text" shortcut key (`r`) +- Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention. +- Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention. +- When use change branches link at MR form - save source branch selection instead of target one +- Improve handling of large diffs +- Added GitLab Event header for project hooks +- Add Two-factor authentication (2FA) for GitLab logins +- Show Atom feed buttons everywhere where applicable. +- Add project activity atom feed. +- Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits. +- Explain how to get a new password reset token in welcome emails +- Include commit comments in MR from a forked project. +- Group milestones by title in the dashboard and all other issue views. +- Query issues, merge requests and milestones with their IID through API (Julien Bianchi) +- Add default project and snippet visibility settings to the admin web UI. +- Show incompatible projects in Google Code import status (Stan Hu) +- Fix bug where commit data would not appear in some subdirectories (Stan Hu) +- Task lists are now usable in comments, and will show up in Markdown previews. +- Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu) +- Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu) +- Protect OmniAuth request phase against CSRF. +- Don't send notifications to mentioned users that don't have access to the project in question. +- Add search issues/MR by number +- Change plots to bar graphs in commit statistics screen +- Move snippets UI to fluid layout +- Improve UI for sidebar. Increase separation between navigation and content +- Improve new project command options (Ben Bodenmiller) +- Add common method to force UTF-8 and use it to properly handle non-ascii OAuth user properties (Onur Küçük) +- Prevent sending empty messages to HipChat (Chulki Lee) +- Improve UI for mobile phones on dashboard and project pages +- Add room notification and message color option for HipChat +- Allow to use non-ASCII letters and dashes in project and namespace name. (Jakub Jirutka) +- Add footnotes support to Markdown (Guillaume Delbergue) +- Add current_sign_in_at to UserFull REST api. +- Make Sidekiq MemoryKiller shutdown signal configurable +- Add "Create Merge Request" buttons to commits and branches pages and push event. +- Show user roles by comments. +- Fix automatic blocking of auto-created users from Active Directory. +- Call merge request webhook for each new commits (Arthur Gautier) +- Use SIGKILL by default in Sidekiq::MemoryKiller +- Fix mentioning of private groups. +- Add style for element in markdown +- Spin spinner icon next to "Checking for CI status..." on MR page. +- Fix reference links in dashboard activity and ATOM feeds. +- Ensure that the first added admin performs repository imports + +## 7.10.4 + +- Fix migrations broken in 7.10.2 +- Make tags for GitLab installations running on MySQL case sensitive +- Get Gitorious importer to work again. +- Fix adding new group members from admin area +- Fix DB error when trying to tag a repository (Stan Hu) +- Fix Error 500 when searching Wiki pages (Stan Hu) +- Unescape branch names in compare commit (Stan Hu) +- Order commit comments chronologically in API. + +## 7.10.2 + +- Fix CI links on MR page + +## 7.10.0 + +- Ignore submodules that are defined in .gitmodules but are checked in as directories. +- Allow projects to be imported from Google Code. +- Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger) +- Allow users to be invited by email to join a group or project. +- Don't crash when project repository doesn't exist. +- Add config var to block auto-created LDAP users. +- Don't use HTML ellipsis in EmailsOnPush subject truncated commit message. +- Set EmailsOnPush reply-to address to committer email when enabled. +- Fix broken file browsing with a submodule that contains a relative link (Stan Hu) +- Fix persistent XSS vulnerability around profile website URLs. +- Fix project import URL regex to prevent arbitary local repos from being imported. +- Fix directory traversal vulnerability around uploads routes. +- Fix directory traversal vulnerability around help pages. +- Don't leak existence of project via search autocomplete. +- Don't leak existence of group or project via search. +- Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu) +- Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu) +- Add a rake task to check repository integrity with `git fsck` +- Add ability to configure Reply-To address in gitlab.yml (Stan Hu) +- Move current user to the top of the list in assignee/author filters (Stan Hu) +- Fix broken side-by-side diff view on merge request page (Stan Hu) +- Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu) +- Allow HTML tags in Markdown input +- Fix code unfold not working on Compare commits page (Stan Hu) +- Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik) +- Fix "Import projects from" button to show the correct instructions (Stan Hu) +- Fix dots in Wiki slugs causing errors (Stan Hu) +- Make maximum attachment size configurable via Application Settings (Stan Hu) +- Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg) +- Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu) +- Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu) +- Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu) +- enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger) +- Fix a link in the patch update guide +- Add a service to support external wikis (Hannes Rosenögger) +- Omit the "email patches" link and fix plain diff view for merge commits +- List new commits for newly pushed branch in activity view. +- Add sidetiq gem dependency to match EE +- Add changelog, license and contribution guide links to project tab bar. +- Improve diff UI +- Fix alignment of navbar toggle button (Cody Mize) +- Fix checkbox rendering for nested task lists +- Identical look of selectboxes in UI +- Upgrade the gitlab_git gem to version 7.1.3 +- Move "Import existing repository by URL" option to button. +- Improve error message when save profile has error. +- Passing the name of pushed ref to CI service (requires GitLab CI 7.9+) +- Add location field to user profile +- Fix print view for markdown files and wiki pages +- Fix errors when deleting old backups +- Improve GitLab performance when working with git repositories +- Add tag message and last commit to tag hook (Kamil Trzciński) +- Restrict permissions on backup files +- Improve oauth accounts UI in profile page +- Add ability to unlink connected accounts +- Replace commits calendar with faster contribution calendar that includes issues and merge requests +- Add inifinite scroll to user page activity +- Don't include system notes in issue/MR comment count. +- Don't mark merge request as updated when merge status relative to target branch changes. +- Link note avatar to user. +- Make Git-over-SSH errors more descriptive. +- Fix EmailsOnPush. +- Refactor issue filtering +- AJAX selectbox for issue assignee and author filters +- Fix issue with missing options in issue filtering dropdown if selected one +- Prevent holding Control-Enter or Command-Enter from posting comment multiple times. +- Prevent note form from being cleared when submitting failed. +- Improve file icons rendering on tree (Sullivan Sénéchal) +- API: Add pagination to project events +- Get issue links in notification mail to work again. +- Don't show commit comment button when user is not signed in. +- Fix admin user projects lists. +- Don't leak private group existence by redirecting from namespace controller to group controller. +- Ability to skip some items from backup (database, respositories or uploads) +- Archive repositories in background worker. +- Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace. +- Project labels are now available over the API under the "tag_list" field (Cristian Medina) +- Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz) +- Fix and improve help rendering (Sullivan Sénéchal) +- Fix final line in EmailsOnPush email diff being rendered as error. +- Prevent duplicate Buildkite service creation. +- Fix git over ssh errors 'fatal: protocol error: bad line length character' +- Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled +- Bust group page project list cache when namespace name or path changes. +- Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded +- Allow user to choose a public email to show on public profile +- Remove truncation from issue titles on milestone page (Jason Blanchard) +- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller) +- Fix merge request comments on files with multiple commits +- Fix Resource Owner Password Authentication Flow +- Add icons to Add dropdown items. +- Allow admin to create public deploy keys that are accessible to any project. +- Warn when gitlab-shell version doesn't match requirement. +- Skip email confirmation when set by admin or via LDAP. +- Only allow users to reference groups, projects, issues, MRs, commits they have access to. + +## 7.9.4 + +- Security: Fix project import URL regex to prevent arbitary local repos from being imported +- Fixed issue where only 25 commits would load in file listings +- Fix LDAP identities after config update + +## 7.9.3 + +- Contains no changes + +## 7.9.2 + +- Contains no changes + +## 7.9.1 + +- Include missing events and fix save functionality in admin service template settings form (Stan Hu) +- Fix "Import projects from" button to show the correct instructions (Stan Hu) +- Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu) +- Fix for LDAP with commas in DN +- Fix missing events and in admin Slack service template settings form (Stan Hu) +- Don't show commit comment button when user is not signed in. +- Downgrade gemnasium-gitlab-service gem + +## 7.9.0 + +- Add HipChat integration documentation (Stan Hu) +- Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu) +- Fix broken email images (Hannes Rosenögger) +- Automatically config git if user forgot, where possible (Zeger-Jan van de Weg) +- Fix mass SQL statements on initial push (Hannes Rosenögger) +- Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu) +- Add comment notification events to HipChat and Slack services (Stan Hu) +- Add issue and merge request events to HipChat and Slack services (Stan Hu) +- Fix merge request URL passed to Webhooks. (Stan Hu) +- Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu) +- Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu) +- Move labels/milestones tabs to sidebar +- Upgrade Rails gem to version 4.1.9. +- Improve error messages for file edit failures +- Improve UI for commits, issues and merge request lists +- Fix commit comments on first line of diff not rendering in Merge Request Discussion view. +- Allow admins to override restricted project visibility settings. +- Move restricted visibility settings from gitlab.yml into the web UI. +- Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev) +- Save web edit in new branch +- Fix ordering of imported but unchanged projects (Marco Wessel) +- Mobile UI improvements: make aside content expandable +- Expose avatar_url in projects API +- Fix checkbox alignment on the application settings page. +- Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) +- Fix mass-unassignment of issues (Robert Speicher) +- Fix hidden diff comments in merge request discussion view +- Allow user confirmation to be skipped for new users via API +- Add a service to send updates to an Irker gateway (Romain Coltel) +- Add brakeman (security scanner for Ruby on Rails) +- Slack username and channel options +- Add grouped milestones from all projects to dashboard. +- Webhook sends pusher email as well as commiter +- Add Bitbucket omniauth provider. +- Add Bitbucket importer. +- Support referencing issues to a project whose name starts with a digit +- Condense commits already in target branch when updating merge request source branch. +- Send notifications and leave system comments when bulk updating issues. +- Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison) +- Move groups page from profile to dashboard +- Starred projects page at dashboard +- Blocking user does not remove him/her from project/groups but show blocked label +- Change subject of EmailsOnPush emails to include namespace, project and branch. +- Change subject of EmailsOnPush emails to include first commit message when multiple were pushed. +- Remove confusing footer from EmailsOnPush mail body. +- Add list of changed files to EmailsOnPush emails. +- Add option to send EmailsOnPush emails from committer email if domain matches. +- Add option to disable code diffs in EmailOnPush emails. +- Wrap commit message in EmailsOnPush email. +- Send EmailsOnPush emails when deleting commits using force push. +- Fix EmailsOnPush email comparison link to include first commit. +- Fix highliht of selected lines in file +- Reject access to group/project avatar if the user doesn't have access. +- Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update) +- Add GitLab active users count to rake gitlab:check +- Starred projects page at dashboard +- Make email display name configurable +- Improve json validation in hook data +- Use Emoji One +- Updated emoji help documentation to properly reference EmojiOne. +- Fix missing GitHub organisation repositories on import page. +- Added blue theme +- Remove annoying notice messages when create/update merge request +- Allow smb:// links in Markdown text. +- Filter merge request by title or description at Merge Requests page +- Block user if he/she was blocked in Active Directory +- Fix import pages not working after first load. +- Use custom LDAP label in LDAP signin form. +- Execute hooks and services when branch or tag is created or deleted through web interface. +- Block and unblock user if he/she was blocked/unblocked in Active Directory +- Raise recommended number of unicorn workers from 2 to 3 +- Use same layout and interactivity for project members as group members. +- Prevent gitlab-shell character encoding issues by receiving its changes as raw data. +- Ability to unsubscribe/subscribe to issue or merge request +- Delete deploy key when last connection to a project is destroyed. +- Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther) +- Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup) +- Add canceled status for CI +- Send EmailsOnPush email when branch or tag is created or deleted. +- Faster merge request processing for large repository +- Prevent doubling AJAX request with each commit visit via Turbolink +- Prevent unnecessary doubling of js events on import pages and user calendar + +## 7.8.4 + +- Fix issue_tracker_id substitution in custom issue trackers +- Fix path and name duplication in namespaces + +## 7.8.3 + +- Bump version of gitlab_git fixing annotated tags without message + +## 7.8.2 + +- Fix service migration issue when upgrading from versions prior to 7.3 +- Fix setting of the default use project limit via admin UI +- Fix showing of already imported projects for GitLab and Gitorious importers +- Fix response of push to repository to return "Not found" if user doesn't have access +- Fix check if user is allowed to view the file attachment +- Fix import check for case sensetive namespaces +- Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time. +- Properly handle autosave local storage exceptions. +- Escape wildcards when searching LDAP by username. + +## 7.8.1 + +- Fix run of custom post receive hooks +- Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3 +- Fix the warning for LDAP users about need to set password +- Fix avatars which were not shown for non logged in users +- Fix urls for the issues when relative url was enabled + +## 7.8.0 + +- Fix access control and protection against XSS for note attachments and other uploads. +- Replace highlight.js with rouge-fork rugments (Stefan Tatschner) +- Make project search case insensitive (Hannes Rosenögger) +- Include issue/mr participants in list of recipients for reassign/close/reopen emails +- Expose description in groups API +- Better UI for project services page +- Cleaner UI for web editor +- Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger) +- Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen) +- View note image attachments in new tab when clicked instead of downloading them +- Improve sorting logic in UI and API. Explicitly define what sorting method is used by default +- Fix overflow at sidebar when have several items +- Add notes for label changes in issue and merge requests +- Show tags in commit view (Hannes Rosenögger) +- Only count a user's vote once on a merge request or issue (Michael Clarke) +- Increase font size when browse source files and diffs +- Service Templates now let you set default values for all services +- Create new file in empty repository using GitLab UI +- Ability to clone project using oauth2 token +- Upgrade Sidekiq gem to version 3.3.0 +- Stop git zombie creation during force push check +- Show success/error messages for test setting button in services +- Added Rubocop for code style checks +- Fix commits pagination +- Async load a branch information at the commit page +- Disable blacklist validation for project names +- Allow configuring protection of the default branch upon first push (Marco Wessel) +- Add gitlab.com importer +- Add an ability to login with gitlab.com +- Add a commit calendar to the user profile (Hannes Rosenögger) +- Submit comment on command-enter +- Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`. +- Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger) +- Fix long broadcast message cut-off on left sidebar (Visay Keo) +- Add Project Avatars (Steven Thonus and Hannes Rosenögger) +- Password reset token validity increased from 2 hours to 2 days since it is also send on account creation. +- Edit group members via API +- Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks) +- Add action property to merge request hook (Julien Bianchi) +- Remove duplicates from group milestone participants list. +- Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger) +- API: Access groups with their path (Julien Bianchi) +- Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard) +- Allow notification email to be set separately from primary email. +- API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) +- Don't have Markdown preview fail for long comments/wiki pages. +- When test webhook - show error message instead of 500 error page if connection to hook url was reset +- Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) +- Added persistent collapse button for left side nav bar (Jason Blanchard) +- Prevent losing unsaved comments by automatically restoring them when comment page is loaded again. +- Don't allow page to be scaled on mobile. +- Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up. +- Show assignees in merge request index page (Kelvin Mutuma) +- Link head panel titles to relevant root page. +- Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S). +- Show users button to share their newly created public or internal projects on twitter +- Add quick help links to the GitLab pricing and feature comparison pages. +- Fix duplicate authorized applications in user profile and incorrect application client count in admin area. +- Make sure Markdown previews always use the same styling as the eventual destination. +- Remove deprecated Group#owner_id from API +- Show projects user contributed to on user page. Show stars near project on user page. +- Improve database performance for GitLab +- Add Asana service (Jeremy Benoist) +- Improve project webhooks with extra data + +## 7.7.2 + +- Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch +- Fix issue when LDAP user can't login with existing GitLab account + +## 7.7.1 + +- Improve mention autocomplete performance +- Show setup instructions for GitHub import if disabled +- Allow use http for OAuth applications + +## 7.7.0 + +- Import from GitHub.com feature +- Add Jetbrains Teamcity CI service (Jason Lippert) +- Mention notification level +- Markdown preview in wiki (Yuriy Glukhov) +- Raise group avatar filesize limit to 200kb +- OAuth applications feature +- Show user SSH keys in admin area +- Developer can push to protected branches option +- Set project path instead of project name in create form +- Block Git HTTP access after 10 failed authentication attempts +- Updates to the messages returned by API (sponsored by O'Reilly Media) +- New UI layout with side navigation +- Add alert message in case of outdated browser (IE < 10) +- Added API support for sorting projects +- Update gitlab_git to version 7.0.0.rc14 +- Add API project search filter option for authorized projects +- Fix File blame not respecting branch selection +- Change some of application settings on fly in admin area UI +- Redesign signin/signup pages +- Close standard input in Gitlab::Popen.popen +- Trigger GitLab CI when push tags +- When accept merge request - do merge using sidaekiq job +- Enable web signups by default +- Fixes for diff comments: drag-n-drop images, selecting images +- Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update +- Remove password strength indicator + +## 7.6.0 + +- Fork repository to groups +- New rugged version +- Add CRON=1 backup setting for quiet backups +- Fix failing wiki restore +- Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable) +- Monokai highlighting style now more faithful to original design (Mark Riedesel) +- Create project with repository in synchrony +- Added ability to create empty repo or import existing one if project does not have repository +- Reactivate highlight.js language autodetection +- Mobile UI improvements +- Change maximum avatar file size from 100KB to 200KB +- Strict validation for snippet file names +- Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada) +- In the docker directory is a container template based on the Omnibus packages. +- Update Sidekiq to version 2.17.8 +- Add author filter to project issues and merge requests pages +- Atom feed for user activity +- Support multiple omniauth providers for the same user +- Rendering cross reference in issue title and tooltip for merge request +- Show username in comments +- Possibility to create Milestones or Labels when Issues are disabled +- Fix bug with showing gpg signature in tag + +## 7.5.3 + +- Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2) + +## 7.5.2 + +- Don't log Sidekiq arguments by default +- Fix restore of wiki repositories from backups + +## 7.5.1 + +- Add missing timestamps to 'members' table + +## 7.5.0 + +- API: Add support for Hipchat (Kevin Houdebert) +- Add time zone configuration in gitlab.yml (Sullivan Senechal) +- Fix LDAP authentication for Git HTTP access +- Run 'GC.start' after every EmailsOnPushWorker job +- Fix LDAP config lookup for provider 'ldap' +- Drop all sequences during Postgres database restore +- Project title links to project homepage (Ben Bodenmiller) +- Add Atlassian Bamboo CI service (Drew Blessing) +- Mentioned @user will receive email even if he is not participating in issue or commit +- Session API: Use case-insensitive authentication like in UI (Andrey Krivko) +- Tie up loose ends with annotated tags: API & UI (Sean Edge) +- Return valid json for deleting branch via API (sponsored by O'Reilly Media) +- Expose username in project events API (sponsored by O'Reilly Media) +- Adds comments to commits in the API +- Performance improvements +- Fix post-receive issue for projects with deleted forks +- New gitlab-shell version with custom hooks support +- Improve code +- GitLab CI 5.2+ support (does not support older versions) +- Fixed bug when you can not push commits starting with 000000 to protected branches +- Added a password strength indicator +- Change project name and path in one form +- Display renamed files in diff views (Vinnie Okada) +- Fix raw view for public snippets +- Use secret token with GitLab internal API. +- Add missing timestamps to 'members' table + +## 7.4.5 + +- Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2) + +## 7.4.4 + +- No changes + +## 7.4.3 + +- Fix raw snippets view +- Fix security issue for member api +- Fix buildbox integration + +## 7.4.2 + +- Fix internal snippet exposing for unauthenticated users + +## 7.4.1 + +- Fix LDAP authentication for Git HTTP access +- Fix LDAP config lookup for provider 'ldap' +- Fix public snippets +- Fix 500 error on projects with nested submodules + +## 7.4.0 + +- Refactored membership logic +- Improve error reporting on users API (Julien Bianchi) +- Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally +- Default branch is protected by default +- Increase unicorn timeout to 60 seconds +- Sort search autocomplete projects by stars count so most popular go first +- Add README to tab on project show page +- Do not delete tmp/repositories itself during clean-up, only its contents +- Support for backup uploads to remote storage +- Prevent notes polling when there are not notes +- Internal ForkService: Prepare support for fork to a given namespace +- API: Add support for forking a project via the API (Bernhard Kaindl) +- API: filter project issues by milestone (Julien Bianchi) +- Fail harder in the backup script +- Changes to Slack service structure, only webhook url needed +- Zen mode for wiki and milestones (Robert Schilling) +- Move Emoji parsing to html-pipeline-gitlab (Robert Schilling) +- Font Awesome 4.2 integration (Sullivan Senechal) +- Add Pushover service integration (Sullivan Senechal) +- Add select field type for services options (Sullivan Senechal) +- Add cross-project references to the Markdown parser (Vinnie Okada) +- Add task lists to issue and merge request descriptions (Vinnie Okada) +- Snippets can be public, internal or private +- Improve danger zone: ask project path to confirm data-loss action +- Raise exception on forgery +- Show build coverage in Merge Requests (requires GitLab CI v5.1) +- New milestone and label links on issue edit form +- Improved repository graphs +- Improve event note display in dashboard and project activity views (Vinnie Okada) +- Add users sorting to admin area +- UI improvements +- Fix ambiguous sha problem with mentioned commit +- Fixed bug with apostrophe when at mentioning users +- Add active directory ldap option +- Developers can push to wiki repo. Protected branches does not affect wiki repo any more +- Faster rev list +- Fix branch removal + +## 7.3.2 + +- Fix creating new file via web editor +- Use gitlab-shell v2.0.1 + +## 7.3.1 + +- Fix ref parsing in Gitlab::GitAccess +- Fix error 500 when viewing diff on a file with changed permissions +- Fix adding comments to MR when source branch is master +- Fix error 500 when searching description contains relative link + +## 7.3.0 + +- Always set the 'origin' remote in satellite actions +- Write authorized_keys in tmp/ during tests +- Use sockets to connect to Redis +- Add dormant New Relic gem (can be enabled via environment variables) +- Expire Rack sessions after 1 week +- Cleaner signin/signup pages +- Improved comments UI +- Better search with filtering, pagination etc +- Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov) +- Prevent project stars duplication when fork project +- Use the default Unicorn socket backlog value of 1024 +- Support Unix domain sockets for Redis +- Store session Redis keys in 'session:gitlab:' namespace +- Deprecate LDAP account takeover based on partial LDAP email / GitLab username match +- Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy) +- Keyboard shortcuts for productivity (Robert Schilling) +- API: filter issues by state (Julien Bianchi) +- API: filter issues by labels (Julien Bianchi) +- Add system hook for ssh key changes +- Add blob permalink link (Ciro Santilli) +- Create annotated tags through UI and API (Sean Edge) +- Snippets search (Charles Bushong) +- Comment new push to existing MR +- Add 'ci' to the blacklist of forbidden names +- Improve text filtering on issues page +- Comment & Close button +- Process git push --all much faster +- Don't allow edit of system notes +- Project wiki search (Ralf Seidler) +- Enabled Shibboleth authentication support (Matus Banas) +- Zen mode (fullscreen) for issues/MR/notes (Robert Schilling) +- Add ability to configure webhook timeout via gitlab.yml (Wes Gurney) +- Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media) +- Add Redis socket support to 'rake gitlab:shell:install' + +## 7.2.1 + +- Delete orphaned labels during label migration (James Brooks) +- Security: prevent XSS with stricter MIME types for raw repo files + +## 7.2.0 + +- Explore page +- Add project stars (Ciro Santilli) +- Log Sidekiq arguments +- Better labels: colors, ability to rename and remove +- Improve the way merge request collects diffs +- Improve compare page for large diffs +- Expose the full commit message via API +- Fix 500 error on repository rename +- Fix bug when MR download patch return invalid diff +- Test gitlab-shell integration +- Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported +- API for labels (Robert Schilling) +- API: ability to set an import url when creating project for specific user + +## 7.1.1 + +- Fix cpu usage issue in Firefox +- Fix redirect loop when changing password by new user +- Fix 500 error on new merge request page + +## 7.1.0 + +- Remove observers +- Improve MR discussions +- Filter by description on Issues#index page +- Fix bug with namespace select when create new project page +- Show README link after description for non-master members +- Add @all mention for comments +- Dont show reply button if user is not signed in +- Expose more information for issues with webhook +- Add a mention of the merge request into the default merge request commit message +- Improve code highlight, introduce support for more languages like Go, Clojure, Erlang etc +- Fix concurrency issue in repository download +- Dont allow repository name start with ? +- Improve email threading (Pierre de La Morinerie) +- Cleaner help page +- Group milestones +- Improved email notifications +- Contributors API (sponsored by Mobbr) +- Fix LDAP TLS authentication (Boris HUISGEN) +- Show VERSION information on project sidebar +- Improve branch removal logic when accept MR +- Fix bug where comment form is spawned inside the Reply button +- Remove Dir.chdir from Satellite#lock for thread-safety +- Increased default git max_size value from 5MB to 20MB in gitlab.yml. Please update your configs! +- Show error message in case of timeout in satellite when create MR +- Show first 100 files for huge diff instead of hiding all +- Change default admin email from admin@local.host to admin@example.com + +## 7.0.0 + +- The CPU no longer overheats when you hold down the spacebar +- Improve edit file UI +- Add ability to upload group avatar when create +- Protected branch cannot be removed +- Developers can remove normal branches with UI +- Remove branch via API (sponsored by O'Reilly Media) +- Move protected branches page to Project settings area +- Redirect to Files view when create new branch via UI +- Drag and drop upload of image in every markdown-area (Earle Randolph Bunao and Neil Francis Calabroso) +- Refactor the markdown relative links processing +- Make it easier to implement other CI services for GitLab +- Group masters can create projects in group +- Deprecate ruby 1.9.3 support +- Only masters can rewrite/remove git tags +- Add X-Frame-Options SAMEORIGIN to Nginx config so Sidekiq admin is visible +- UI improvements +- Case-insensetive search for issues +- Update to rails 4.1 +- Improve performance of application for projects and groups with a lot of members +- Formally support Ruby 2.1 +- Include Nginx gitlab-ssl config +- Add manual language detection for highlight.js +- Added example.com/:username routing +- Show notice if your profile is public +- UI improvements for mobile devices +- Improve diff rendering performance +- Drag-n-drop for issues and merge requests between states at milestone page +- Fix '0 commits' message for huge repositories on project home page +- Prevent 500 error page when visit commit page from large repo +- Add notice about huge push over http to unicorn config +- File action in satellites uses default 30 seconds timeout instead of old 10 seconds one +- Overall performance improvements +- Skip init script check on omnibus-gitlab +- Be more selective when killing stray Sidekiqs +- Check LDAP user filter during sign-in +- Remove wall feature (no data loss - you can take it from database) +- Dont expose user emails via API unless you are admin +- Detect issues closed by Merge Request description +- Better email subject lines from email on push service (Alex Elman) +- Enable identicon for gravatar be default + +## 6.9.2 + +- Revert the commit that broke the LDAP user filter + +## 6.9.1 + +- Fix scroll to highlighted line +- Fix the pagination on load for commits page + +## 6.9.0 + +- Store Rails cache data in the Redis `cache:gitlab` namespace +- Adjust MySQL limits for existing installations +- Add db index on project_id+iid column. This prevents duplicate on iid (During migration duplicates will be removed) +- Markdown preview or diff during editing via web editor (Evgeniy Sokovikov) +- Give the Rails cache its own Redis namespace +- Add ability to set different ssh host, if different from http/https +- Fix syntax highlighting for code comments blocks +- Improve comments loading logic +- Stop refreshing comments when the tab is hidden +- Improve issue and merge request mobile UI (Drew Blessing) +- Document how to convert a backup to PostgreSQL +- Fix locale bug in backup manager +- Fix can not automerge when MR description is too long +- Fix wiki backup skip bug +- Two Step MR creation process +- Remove unwanted files from satellite working directory with git clean -fdx +- Accept merge request via API (sponsored by O'Reilly Media) +- Add more access checks during API calls +- Block SSH access for 'disabled' Active Directory users +- Labels for merge requests (Drew Blessing) +- Threaded emails by setting a Message-ID (Philip Blatter) + +## 6.8.0 + +- Ability to at mention users that are participating in issue and merge req. discussion +- Enabled GZip Compression for assets in example Nginx, make sure that Nginx is compiled with --with-http_gzip_static_module flag (this is default in Ubuntu) +- Make user search case-insensitive (Christopher Arnold) +- Remove omniauth-ldap nickname bug workaround +- Drop all tables before restoring a Postgres backup +- Make the repository downloads path configurable +- Create branches via API (sponsored by O'Reilly Media) +- Changed permission of gitlab-satellites directory not to be world accessible +- Protected branch does not allow force push +- Fix popen bug in `rake gitlab:satellites:create` +- Disable connection reaping for MySQL +- Allow oauth signup without email for twitter and github +- Fix faulty namespace names that caused 500 on user creation +- Option to disable standard login +- Clean old created archives from repository downloads directory +- Fix download link for huge MR diffs +- Expose event and mergerequest timestamps in API +- Fix emails on push service when only one commit is pushed + +## 6.7.3 + +- Fix the merge notification email not being sent (Pierre de La Morinerie) +- Drop all tables before restoring a Postgres backup +- Remove yanked modernizr gem + +## 6.7.2 + +- Fix upgrader script + +## 6.7.1 + +- Fix GitLab CI integration + +## 6.7.0 + +- Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations +- Add support for Gemnasium as a Project Service (Olivier Gonzalez) +- Add edit file button to MergeRequest diff +- Public groups (Jason Hollingsworth) +- Cleaner headers in Notification Emails (Pierre de La Morinerie) +- Blob and tree gfm links to anchors work +- Piwik Integration (Sebastian Winkler) +- Show contribution guide link for new issue form (Jeroen van Baarsen) +- Fix CI status for merge requests from fork +- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard) +- New page load indicator that includes a spinner that scrolls with the page +- Converted all the help sections into markdown +- LDAP user filters +- Streamline the content of notification emails (Pierre de La Morinerie) +- Fixes a bug with group member administration (Matt DeTullio) +- Sort tag names using VersionSorter (Robert Speicher) +- Add GFM autocompletion for MergeRequests (Robert Speicher) +- Add webhook when a new tag is pushed (Jeroen van Baarsen) +- Add button for toggling inline comments in diff view +- Add retry feature for repository import +- Reuse the GitLab LDAP connection within each request +- Changed markdown new line behaviour to conform to markdown standards +- Fix global search +- Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5) +- Create and Update MR calls now support the description parameter (Greg Messner) +- Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository +- Added Slack service integration (Federico Ravasio) +- Better API responses for access_levels (sponsored by O'Reilly Media) +- Requires at least 2 unicorn workers +- Requires gitlab-shell v1.9+ +- Replaced gemoji(due to closed licencing problem) with Phantom Open Emoji library(combined SIL Open Font License, MIT License and the CC 3.0 License) +- Fix `/:username.keys` response content type (Dmitry Medvinsky) + +## 6.6.5 + +- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard) +- Hide mr close button for comment form if merge request was closed or inline comment +- Adds ability to reopen closed merge request + +## 6.6.4 + +- Add missing html escape for highlighted code blocks in comments, issues + +## 6.6.3 + +- Fix 500 error when edit yourself from admin area +- Hide private groups for public profiles + +## 6.6.2 + +- Fix 500 error on branch/tag create or remove via UI + +## 6.6.1 + +- Fix 500 error on files tab if submodules presents + +## 6.6.0 + +- Retrieving user ssh keys publically(github style): http://__HOST__/__USERNAME__.keys +- Permissions: Developer now can manage issue tracker (modify any issue) +- Improve Code Compare page performance +- Group avatar +- Pygments.rb replaced with highlight.js +- Improve Merge request diff store logic +- Improve render performnace for MR show page +- Fixed Assembla hardcoded project name +- Jira integration documentation +- Refactored app/services +- Remove snippet expiration +- Mobile UI improvements (Drew Blessing) +- Fix block/remove UI for admin::users#show page +- Show users' group membership on users' activity page (Robert Djurasaj) +- User pages are visible without login if user is authorized to a public project +- Markdown rendered headers have id derived from their name and link to their id +- Improve application to work faster with large groups (100+ members) +- Multiple emails per user +- Show last commit for file when view file source +- Restyle Issue#show page and MR#show page +- Ability to filter by multiple labels for Issues page +- Rails version to 4.0.3 +- Fixed attachment identifier displaying underneath note text (Jason Blanchard) + +## 6.5.1 + +- Fix branch selectbox when create merge request from fork + +## 6.5.0 + +- Dropdown menus on issue#show page for assignee and milestone (Jason Blanchard) +- Add color custimization and previewing to broadcast messages +- Fixed notes anchors +- Load new comments in issues dynamically +- Added sort options to Public page +- New filters (assigned/authored/all) for Dashboard#issues/merge_requests (sponsored by Say Media) +- Add project visibility icons to dashboard +- Enable secure cookies if https used +- Protect users/confirmation with rack_attack +- Default HTTP headers to protect against MIME-sniffing, force https if enabled +- Bootstrap 3 with responsive UI +- New repository download formats: tar.bz2, zip, tar (Jason Hollingsworth) +- Restyled accept widgets for MR +- SCSS refactored +- Use jquery timeago plugin +- Fix 500 error for rdoc files +- Ability to customize merge commit message (sponsored by Say Media) +- Search autocomplete via ajax +- Add website url to user profile +- Files API supports base64 encoded content (sponsored by O'Reilly Media) +- Added support for Go's repository retrieval (Bruno Albuquerque) + +## 6.4.3 + +- Don't use unicorn worker killer if PhusionPassenger is defined + +## 6.4.2 + +- Fixed wrong behaviour of script/upgrade.rb + +## 6.4.1 + +- Fixed bug with repository rename +- Fixed bug with project transfer + +## 6.4.0 + +- Added sorting to project issues page (Jason Blanchard) +- Assembla integration (Carlos Paramio) +- Fixed another 500 error with submodules +- UI: More compact issues page +- Minimal password length increased to 8 symbols +- Side-by-side diff view (Steven Thonus) +- Internal projects (Jason Hollingsworth) +- Allow removal of avatar (Drew Blessing) +- Project webhooks now support issues and merge request events +- Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth) +- Expire event cache on avatar creation/removal (Drew Blessing) +- Archiving old projects (Steven Thonus) +- Rails 4 +- Add time ago tooltips to show actual date/time +- UI: Fixed UI for admin system hooks +- Ruby script for easier GitLab upgrade +- Do not remove Merge requests if fork project was removed +- Improve sign-in/signup UX +- Add resend confirmation link to sign-in page +- Set noreply@HOSTNAME for reply_to field in all emails +- Show GitLab API version on Admin#dashboard +- API Cross-origin resource sharing +- Show READMe link at project home page +- Show repo size for projects in Admin area + +## 6.3.0 + +- API for adding gitlab-ci service +- Init script now waits for pids to appear after (re)starting before reporting status (Rovanion Luckey) +- Restyle project home page +- Grammar fixes +- Show branches list (which branches contains commit) on commit page (Andrew Kumanyaev) +- Security improvements +- Added support for GitLab CI 4.0 +- Fixed issue with 500 error when group did not exist +- Ability to leave project +- You can create file in repo using UI +- You can remove file from repo using UI +- API: dropped default_branch attribute from project during creation +- Project default_branch is not stored in db any more. It takes from repo now. +- Admin broadcast messages +- UI improvements +- Dont show last push widget if user removed this branch +- Fix 500 error for repos with newline in file name +- Extended html titles +- API: create/update/delete repo files +- Admin can transfer project to any namespace +- API: projects/all for admin users +- Fix recent branches order + +## 6.2.4 + +- Security: Cast API private_token to string (CVE-2013-4580) +- Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583) +- Fix for Git SSH access for LDAP users + +## 6.2.3 + +- Security: More protection against CVE-2013-4489 +- Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546) +- Fix sidekiq rake tasks + +## 6.2.2 + +- Security: Update gitlab_git (CVE-2013-4489) + +## 6.2.1 + +- Security: Fix issue with generated passwords for new users + +## 6.2.0 + +- Public project pages are now visible to everyone (files, issues, wik, etc.) + THIS MEANS YOUR ISSUES AND WIKI FOR PUBLIC PROJECTS ARE PUBLICLY VISIBLE AFTER THE UPGRADE +- Add group access to permissions page +- Require current password to change one +- Group owner or admin can remove other group owners +- Remove group transfer since we have multiple owners +- Respect authorization in Repository API +- Improve UI for Project#files page +- Add more security specs +- Added search for projects by name to api (Izaak Alpert) +- Make default user theme configurable (Izaak Alpert) +- Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev) +- Rake tasks for webhooks management (Jonhnny Weslley) +- Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov) +- API: Remove group +- API: Remove project +- Avatar upload on profile page with a maximum of 100KB (Steven Thonus) +- Store the sessions in Redis instead of the cookie store +- Fixed relative links in markdown +- User must confirm their email if signup enabled +- User must confirm changed email + +## 6.1.0 + +- Project specific IDs for issues, mr, milestones + Above items will get a new id and for example all bookmarked issue urls will change. + Old issue urls are redirected to the new one if the issue id is too high for an internal id. +- Description field added to Merge Request +- API: Sudo api calls (Izaak Alpert) +- API: Group membership api (Izaak Alpert) +- Improved commit diff +- Improved large commit handling (Boyan Tabakov) +- Rewrite: Init script now less prone to errors and keeps better track of the service (Rovanion Luckey) +- Link issues, merge requests, and commits when they reference each other with GFM (Ash Wilson) +- Close issues automatically when pushing commits with a special message +- Improve user removal from admin area +- Invalidate events cache when project was moved +- Remove deprecated classes and rake tasks +- Add event filter for group and project show pages +- Add links to create branch/tag from project home page +- Add public-project? checkbox to new-project view +- Improved compare page. Added link to proceed into Merge Request +- Send an email to a user when they are added to group +- New landing page when you have 0 projects + +## 6.0.0 + +- Feature: Replace teams with group membership + We introduce group membership in 6.0 as a replacement for teams. + The old combination of groups and teams was confusing for a lot of people. + And when the members of a team where changed this wasn't reflected in the project permissions. + In GitLab 6.0 you will be able to add members to a group with a permission level for each member. + These group members will have access to the projects in that group. + Any changes to group members will immediately be reflected in the project permissions. + You can even have multiple owners for a group, greatly simplifying administration. +- Feature: Ability to have multiple owners for group +- Feature: Merge Requests between fork and project (Izaak Alpert) +- Feature: Generate fingerprint for ssh keys +- Feature: Ability to create and remove branches with UI +- Feature: Ability to create and remove git tags with UI +- Feature: Groups page in profile. You can leave group there +- API: Allow login with LDAP credentials +- Redesign: project settings navigation +- Redesign: snippets area +- Redesign: ssh keys page +- Redesign: buttons, blocks and other ui elements +- Add comment title to rss feed +- You can use arrows to navigate at tree view +- Add project filter on dashboard +- Cache project graph +- Drop support of root namespaces +- Default theme is classic now +- Cache result of methods like authorize_projects, project.team.members etc +- Remove $.ready events +- Fix onclick events being double binded +- Add notification level to group membership +- Move all project controllers/views under Projects:: module +- Move all profile controllers/views under Profiles:: module +- Apply user project limit only for personal projects +- Unicorn is default web server again +- Store satellites lock files inside satellites dir +- Disabled threadsafety mode in rails +- Fixed bug with loosing MR comments +- Improved MR comments logic +- Render readme file for projects in public area + +## 5.4.2 + +- Security: Cast API private_token to string (CVE-2013-4580) +- Security: Require gitlab-shell 1.7.8 (CVE-2013-4581, CVE-2013-4582, CVE-2013-4583) + +## 5.4.1 + +- Security: Fixes for CVE-2013-4489 +- Security: Require gitlab-shell 1.7.4 (CVE-2013-4490, CVE-2013-4546) + +## 5.4.0 + +- Ability to edit own comments +- Documentation improvements +- Improve dashboard projects page +- Fixed nav for empty repos +- GitLab Markdown help page +- Misspelling fixes +- Added support of unicorn and fog gems +- Added client list to API doc +- Fix PostgreSQL database restoration problem +- Increase snippet content column size +- allow project import via git:// url +- Show participants on issues, including mentions +- Notify mentioned users with email + +## 5.3.0 + +- Refactored services +- Campfire service added +- HipChat service added +- Fixed bug with LDAP + git over http +- Fixed bug with google analytics code being ignored +- Improve sign-in page if ldap enabled +- Respect newlines in wall messages +- Generate the Rails secret token on first run +- Rename repo feature +- Init.d: remove gitlab.socket on service start +- Api: added teams api +- Api: Prevent blob content being escaped +- Api: Smart deploy key add behaviour +- Api: projects/owned.json return user owned project +- Fix bug with team assignation on project from #4109 +- Advanced snippets: public/private, project/personal (Andrew Kulakov) +- Repository Graphs (Karlo Nicholas T. Soriano) +- Fix dashboard lost if comment on commit +- Update gitlab-grack. Fixes issue with --depth option +- Fix project events duplicate on project page +- Fix postgres error when displaying network graph. +- Fix dashboard event filter when navigate via turbolinks +- init.d: Ensure socket is removed before starting service +- Admin area: Style teams:index, group:show pages +- Own page for failed forking +- Scrum view for milestone + +## 5.2.0 + +- Turbolinks +- Git over http with ldap credentials +- Diff with better colors and some spacing on the corners +- Default values for project features +- Fixed huge_commit view +- Restyle project clone panel +- Move Gitlab::Git code to gitlab_git gem +- Move update docs in repo +- Requires gitlab-shell v1.4.0 +- Fixed submodules listing under file tab +- Fork feature (Angus MacArthur) +- git version check in gitlab:check +- Shared deploy keys feature +- Ability to generate default labels set for issues +- Improve gfm autocomplete (Harold Luo) +- Added support for Google Analytics +- Code search feature (Javier Castro) + +## 5.1.0 + +- You can login with email or username now +- Corrected project transfer rollback when repository cannot be moved +- Move both repo and wiki when project transfer requested +- Admin area: project editing was removed from admin namespace +- Access: admin user has now access to any project. +- Notification settings +- Gitlab::Git set of objects to abstract from grit library +- Replace Unicorn web server with Puma +- Backup/Restore refactored. Backup dump project wiki too now +- Restyled Issues list. Show milestone version in issue row +- Restyled Merge Request list +- Backup now dump/restore uploads +- Improved performance of dashboard (Andrew Kumanyaev) +- File history now tracks renames (Akzhan Abdulin) +- Drop wiki migration tools +- Drop sqlite migration tools +- project tagging +- Paginate users in API +- Restyled network graph (Hiroyuki Sato) + +## 5.0.1 + +- Fixed issue with gitlab-grit being overridden by grit + +## 5.0.0 + +- Replaced gitolite with gitlab-shell +- Removed gitolite-related libraries +- State machine added +- Setup gitlab as git user +- Internal API +- Show team tab for empty projects +- Import repository feature +- Updated rails +- Use lambda for scopes +- Redesign admin area -> users +- Redesign admin area -> user +- Secure link to file attachments +- Add validations for Group and Team names +- Restyle team page for project +- Update capybara, rspec-rails, poltergeist to recent versions +- Wiki on git using Gollum +- Added Solarized Dark theme for code review +- Don't show user emails in autocomplete lists, profile pages +- Added settings tab for group, team, project +- Replace user popup with icons in header +- Handle project moving with gitlab-shell +- Added select2-rails for selectboxes with ajax data load +- Fixed search field on projects page +- Added teams to search autocomplete +- Move groups and teams on dashboard sidebar to sub-tabs +- API: improved return codes and docs. (Felix Gilcher, Sebastian Ziebell) +- Redesign wall to be more like chat +- Snippets, Wall features are disabled by default for new projects + +## 4.2.0 + +- Teams +- User show page. Via /u/username +- Show help contents on pages for better navigation +- Async gitolite calls +- added satellites logs +- can_create_group, can_create_team booleans for User +- Process webhooks async +- GFM: Fix images escaped inside links +- Network graph improved +- Switchable branches for network graph +- API: Groups +- Fixed project download + +## 4.1.0 + +- Optional Sign-Up +- Discussions +- Satellites outside of tmp +- Line numbers for blame +- Project public mode +- Public area with unauthorized access +- Load dashboard events with ajax +- remember dashboard filter in cookies +- replace resque with sidekiq +- fix routing issues +- cleanup rake tasks +- fix backup/restore +- scss cleanup +- show preview for note images +- improved network-graph +- get rid of app/roles/ +- added new classes Team, Repository +- Reduce amount of gitolite calls +- Ability to add user in all group projects +- remove deprecated configs +- replaced Korolev font with open font +- restyled admin/dashboard page +- restyled admin/projects page + +## 4.0.0 + +- Remove project code and path from API. Use id instead +- Return valid cloneable url to repo for webhook +- Fixed backup issue +- Reorganized settings +- Fixed commits compare +- Refactored scss +- Improve status checks +- Validates presence of User#name +- Fixed postgres support +- Removed sqlite support +- Modified post-receive hook +- Milestones can be closed now +- Show comment events on dashboard +- Quick add team members via group#people page +- [API] expose created date for hooks and SSH keys +- [API] list, create issue notes +- [API] list, create snippet notes +- [API] list, create wall notes +- Remove project code - use path instead +- added username field to user +- rake task to fill usernames based on emails create namespaces for users +- STI Group < Namespace +- Project has namespace_id +- Projects with namespaces also namespaced in gitolite and stored in subdir +- Moving project to group will move it under group namespace +- Ability to move project from namespaces to another +- Fixes commit patches getting escaped (see #2036) +- Support diff and patch generation for commits and merge request +- MergeReqest doesn't generate a temporary file for the patch any more +- Update the UI to allow downloading Patch or Diff + +## 3.1.0 + +- Updated gems +- Services: Gitlab CI integration +- Events filter on dashboard +- Own namespace for redis/resque +- Optimized commit diff views +- add alphabetical order for projects admin page +- Improved web editor +- Commit stats page +- Documentation split and cleanup +- Link to commit authors everywhere +- Restyled milestones list +- added Milestone to Merge Request +- Restyled Top panel +- Refactored Satellite Code +- Added file line links +- moved from capybara-webkit to poltergeist + phantomjs + +## 3.0.3 + +- Fixed bug with issues list in Chrome +- New Feature: Import team from another project + +## 3.0.2 + +- Fixed gitlab:app:setup +- Fixed application error on empty project in admin area +- Restyled last push widget + +## 3.0.1 + +- Fixed git over http + +## 3.0.0 + +- Projects groups +- Web Editor +- Fixed bug with gitolite keys +- UI improved +- Increased performance of application +- Show user avatar in last commit when browsing Files +- Refactored Gitlab::Merge +- Use Font Awesome for icons +- Separate observing of Note and MergeRequests +- Milestone "All Issues" filter +- Fix issue close and reopen button text and styles +- Fix forward/back while browsing Tree hierarchy +- Show number of notes for commits and merge requests +- Added support pg from box and update installation doc +- Reject ssh keys that break gitolite +- [API] list one project hook +- [API] edit project hook +- [API] list project snippets +- [API] allow to authorize using private token in HTTP header +- [API] add user creation + +## 2.9.1 + +- Fixed resque custom config init + +## 2.9.0 + +- fixed inline notes bugs +- refactored rspecs +- refactored gitolite backend +- added factory_girl +- restyled projects list on dashboard +- ssh keys validation to prevent gitolite crash +- send notifications if changed permission in project +- scss refactoring. gitlab_bootstrap/ dir +- fix git push http body bigger than 112k problem +- list of labels page under issues tab +- API for milestones, keys +- restyled buttons +- OAuth +- Comment order changed + +## 2.8.1 + +- ability to disable gravatars +- improved MR diff logic +- ssh key help page + +## 2.8.0 + +- Gitlab Flavored Markdown +- Bulk issues update +- Issues API +- Cucumber coverage increased +- Post-receive files fixed +- UI improved +- Application cleanup +- more cucumber +- capybara-webkit + headless + +## 2.7.0 + +- Issue Labels +- Inline diff +- Git HTTP +- API +- UI improved +- System hooks +- UI improved +- Dashboard events endless scroll +- Source performance increased + +## 2.6.0 + +- UI polished +- Improved network graph + keyboard nav +- Handle huge commits +- Last Push widget +- Bugfix +- Better performance +- Email in resque +- Increased test coverage +- Ability to remove branch with MR accept +- a lot of code refactored + +## 2.5.0 + +- UI polished +- Git blame for file +- Bugfix +- Email in resque +- Better test coverage + +## 2.4.0 + +- Admin area stats page +- Ability to block user +- Simplified dashboard area +- Improved admin area +- Bootstrap 2.0 +- Responsive layout +- Big commits handling +- Performance improved +- Milestones + +## 2.3.1 + +- Issues pagination +- ssl fixes +- Merge Request pagination + +## 2.3.0 + +- Dashboard r1 +- Search r1 +- Project page +- Close merge request on push +- Persist MR diff after merge +- mysql support +- Documentation + +## 2.2.0 + +- We’ve added support of LDAP auth +- Improved permission logic (4 roles system) +- Protected branches (now only masters can push to protected branches) +- Usability improved +- twitter bootstrap integrated +- compare view between commits +- wiki feature +- now you can enable/disable issues, wiki, wall features per project +- security fixes +- improved code browsing (ajax branch switch etc) +- improved per-line commenting +- git submodules displayed +- moved to rails 3.2 +- help section improved + +## 2.1.0 + +- Project tab r1 +- List branches/tags +- per line comments +- mass user import + +## 2.0.0 + +- gitolite as main git host system +- merge requests +- project/repo access +- link to commit/issue feed +- design tab +- improved email notifications +- restyled dashboard +- bugfix + +## 1.2.2 + +- common config file gitlab.yml +- issues restyle +- snippets restyle +- clickable news feed header on dashboard +- bugfix + +## 1.2.1 + +- bugfix + +## 1.2.0 + +- new design +- user dashboard +- network graph +- markdown support for comments +- encoding issues +- wall like twitter timeline + +## 1.1.0 + +- project dashboard +- wall redesigned +- feature: code snippets +- fixed horizontal scroll on file preview +- fixed app crash if commit message has invalid chars +- bugfix & code cleaning + +## 1.0.2 + +- fixed bug with empty project +- added adv validation for project path & code +- feature: issues can be sortable +- bugfix +- username displayed on top panel + +## 1.0.1 + +- fixed: with invalid source code for commit +- fixed: lose branch/tag selection when use tree navigation +- when history clicked - display path +- bug fix & code cleaning + +## 1.0.0 + +- bug fix +- projects preview mode + +## 0.9.6 + +- css fix +- new repo empty tree until restart server - fixed + +## 0.9.4 + +- security improved +- authorization improved +- html escaping +- bug fix +- increased test coverage +- design improvements + +## 0.9.1 + +- increased test coverage +- design improvements +- new issue email notification +- updated app name +- issue redesigned +- issue can be edit + +## 0.8.0 + +- syntax highlight for main file types +- redesign +- stability +- security fixes +- increased test coverage +- email notification -- cgit v1.2.3 From 107953532bd1657aa57e3c35bfc385fa93a846d8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 9 Sep 2016 14:37:25 -0400 Subject: Add changelogs/unreleased/.gitkeep See gitlab-org/release-tools!29 [ci skip] --- changelogs/unreleased/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changelogs/unreleased/.gitkeep diff --git a/changelogs/unreleased/.gitkeep b/changelogs/unreleased/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d -- cgit v1.2.3 From e300e9d5ef032aa6543f471dcea37ba068c98a5e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 9 Sep 2016 16:21:46 -0400 Subject: Improve PipelinesFinder spec so that it does not depend on hard-coded database IDs Fixes failed builds such as https://gitlab.com/gitlab-org/gitlab-ce/builds/3919501 --- spec/finders/pipelines_finder_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index 7100266ab55..b0811d134fa 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -43,9 +43,10 @@ describe PipelinesFinder do let(:params) { { scope: 'running' } } it 'orders in descending order on ID' do - create(:ci_pipeline, project: project, ref: 'feature') + feature_pipeline = create(:ci_pipeline, project: project, ref: 'feature') - expect(subject.map(&:id)).to eq [3, 2, 1] + expected_ids = [feature_pipeline.id, branch_pipeline.id, tag_pipeline.id].sort.reverse + expect(subject.map(&:id)).to eq expected_ids end end end -- cgit v1.2.3 From b5d74bef8e3ccd18072267cc660ccdcbfd65eebf Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 9 Sep 2016 19:50:53 +0100 Subject: trace step buttons now scroll 50px up or down at a time --- app/assets/javascripts/build.js | 13 +++++++++++-- app/views/projects/builds/show.html.haml | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 10abeb50f4b..35c0c17d666 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -27,10 +27,11 @@ $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); + $('.step-up-trace, .step-down-trace').off('click').on('click', this.stepTrace); this.updateArtifactRemoveDate(); if ($('#build-trace').length) { this.getInitialBuildTrace(); - this.initScrollButtonAffix(); + this.initScrollButtons(); } if (this.build_status === "running" || this.build_status === "pending") { $('#autoscroll-button').on('click', function() { @@ -106,7 +107,7 @@ } }; - Build.prototype.initScrollButtonAffix = function() { + Build.prototype.initScrollButtons = function() { var $body, $buildScroll, $buildTrace; $buildScroll = $('#js-build-scroll'); $body = $('body'); @@ -165,6 +166,14 @@ this.populateJobs(stage); }; + Build.prototype.stepTrace = function(e) { + if ($(e.currentTarget).hasClass('step-up-trace')) { + $.scrollTo('-=50px'); + } else { + $.scrollTo('+=50px'); + } + }; + return Build; })(); diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index e4d41288aa6..e2361a26769 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -34,9 +34,9 @@ Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} - else #js-build-scroll.scroll-controls - = link_to '#build-trace', class: 'btn' do + %a.step-up-trace.btn %i.fa.fa-angle-up - = link_to '#down-build-trace', class: 'btn' do + %a.step-down-trace.btn %i.fa.fa-angle-down %pre.build-trace#build-trace %code.bash.js-build-output -- cgit v1.2.3 From f162dde69f339895d8cea44af3670fe197fe2414 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 9 Sep 2016 20:01:48 +0100 Subject: Now anchors to the top or bottom of the build trace with the correct offset --- app/assets/javascripts/build.js | 12 ++++++------ app/views/projects/builds/show.html.haml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 35c0c17d666..78d21c0552a 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -27,7 +27,7 @@ $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); - $('.step-up-trace, .step-down-trace').off('click').on('click', this.stepTrace); + $('#js-build-scroll > a').off('click').on('click', this.stepTrace); this.updateArtifactRemoveDate(); if ($('#build-trace').length) { this.getInitialBuildTrace(); @@ -167,11 +167,11 @@ }; Build.prototype.stepTrace = function(e) { - if ($(e.currentTarget).hasClass('step-up-trace')) { - $.scrollTo('-=50px'); - } else { - $.scrollTo('+=50px'); - } + e.preventDefault(); + $currentTarget = $(e.currentTarget); + $.scrollTo($currentTarget.attr('href'), { + offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()) + }); }; return Build; diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index e2361a26769..e4d41288aa6 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -34,9 +34,9 @@ Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)} - else #js-build-scroll.scroll-controls - %a.step-up-trace.btn + = link_to '#build-trace', class: 'btn' do %i.fa.fa-angle-up - %a.step-down-trace.btn + = link_to '#down-build-trace', class: 'btn' do %i.fa.fa-angle-down %pre.build-trace#build-trace %code.bash.js-build-output -- cgit v1.2.3 From 08dc8af7339828852ae8596aeea577f9551d399d Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Wed, 24 Aug 2016 14:30:39 +0100 Subject: fixes distinction between renaming a file and updating its content and refactors update file --- CHANGELOG | 1 + app/controllers/concerns/creates_commit.rb | 3 +-- app/controllers/projects/blob_controller.rb | 10 ++++------ app/models/repository.rb | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 94bccb729dd..41973a79057 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.12.0 (unreleased) - Fix pagination on user snippets page - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 + - Fix file permissions change when updating a file on the Gitlab UI !5979 - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index f2b8f297bc2..dacb5679dd3 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -7,8 +7,7 @@ module CreatesCommit commit_params = @commit_params.merge( source_project: @project, source_branch: @ref, - target_branch: @target_branch, - previous_path: @previous_path + target_branch: @target_branch ) result = service.new(@tree_edit_project, current_user, commit_params).execute diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index cdf9a04bacf..b78cc6585ba 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -38,12 +38,7 @@ class Projects::BlobController < Projects::ApplicationController end def update - if params[:file_path].present? - @previous_path = @path - @path = params[:file_path] - @commit_params[:file_path] = @path - end - + @path = params[:file_path] if params[:file_path].present? after_edit_path = if from_merge_request && @target_branch == @ref diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + @@ -143,6 +138,8 @@ class Projects::BlobController < Projects::ApplicationController params[:file_name] = params[:file].original_filename end File.join(@path, params[:file_name]) + elsif params[:file_path].present? + params[:file_path] else @path end @@ -155,6 +152,7 @@ class Projects::BlobController < Projects::ApplicationController @commit_params = { file_path: @file_path, commit_message: params[:commit_message], + previous_path: @path, file_content: params[:content], file_content_encoding: params[:encoding], last_commit_sha: params[:last_commit_sha] diff --git a/app/models/repository.rb b/app/models/repository.rb index 7b7090b8a73..37fbdcfe1c6 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -813,7 +813,7 @@ class Repository update: true } - if previous_path + unless previous_path == path || previous_path.blank? options[:file][:previous_path] = previous_path Gitlab::Git::Blob.rename(raw_repository, options) else -- cgit v1.2.3 From b12b7243787512b3099dcb6c264c5973eb397301 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sun, 11 Sep 2016 16:03:17 -0600 Subject: Remove x-json mime_type, rename to json_mime_types. --- config/initializers/mime_types.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 350388fedbe..cf3917033d8 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -13,11 +13,10 @@ Mime::Type.register "video/mp4", :mp4, [], [:m4v, :mov] Mime::Type.register "video/webm", :webm Mime::Type.register "video/ogg", :ogv -lfs_mime_types = %w( +json_mime_types = %w( application/vnd.git-lfs+json - text/x-json application/json ) Mime::Type.unregister :json -Mime::Type.register 'application/json', :json, lfs_mime_types +Mime::Type.register 'application/json', :json, json_mime_types -- cgit v1.2.3 From bc9700a6a848886ae5fde6475d4a68707e46df15 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 12 Sep 2016 07:48:32 +0000 Subject: doc/ci/quick_start/README: improve sentence about link to a Lint tool The link is now a button under **Pipelines > Pipelines** and **Pipelines > Builds**. There is no more **CI settings** menu. --- doc/ci/quick_start/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index c835ebc2d44..c40cdd55ea5 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -105,7 +105,8 @@ What is important is that each job is run independently from each other. If you want to check whether your `.gitlab-ci.yml` file is valid, there is a Lint tool under the page `/ci/lint` of your GitLab instance. You can also find -the link under **Settings > CI settings** in your project. +a "CI Lint" button to go to this page under **Pipelines > Pipelines** and +**Pipelines > Builds** in your project. For more information and a complete `.gitlab-ci.yml` syntax, please read [the documentation on .gitlab-ci.yml](../yaml/README.md). -- cgit v1.2.3 From ff423bbb290f3bcb8b6d2859c67d428ac80f681e Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 12 Sep 2016 10:14:11 +0200 Subject: doc/user/permissions: update option and menu names It looks like the option is now called **Public pipelines** and the menu item is now **Project Settings > CI/CD Pipelines**. --- doc/user/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 1498cb361c8..f1b75298180 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project. | Force push to protected branches [^2] | | | | | | | Remove protected branches [^2] | | | | | | -[^1]: If **Allow guest to access builds** is enabled in CI settings +[^1]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines** [^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner ## Group -- cgit v1.2.3 From 3a58dd2d8ffd0baf7a6a8f342d703cbdc34400bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 9 Sep 2016 15:16:26 +0200 Subject: Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also streamline the sorting part while we're at it. That being done, there's currently a duplication between `GET /projects/search/:query` and `GET /projects?search=:search` so we might want to keep only the latter for 9.0... Signed-off-by: Rémy Coutable --- CHANGELOG | 2 +- lib/api/projects.rb | 15 +++------------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5b2655bcb95..9d0cde95ebf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.12.0 (unreleased) - Add font color contrast to external label in admin area (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 + - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Fix pagination on user snippets page - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 @@ -111,7 +112,6 @@ v 8.12.0 (unreleased) - Use default clone protocol on "check out, review, and merge locally" help page URL - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - Allow bulk update merge requests from merge requests index page - - Project search API is now agnostic to casing v 8.11.6 (unreleased) diff --git a/lib/api/projects.rb b/lib/api/projects.rb index b76be145a14..644d836ed0b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -428,18 +428,9 @@ module API # Example Request: # GET /projects/search/:query get "/search/:query" do - ids = current_user.authorized_projects.map(&:id) - visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] - projects = Project.where("(id in (?) OR visibility_level in (?)) AND (LOWER(name) LIKE LOWER((?)))", ids, visibility_levels, "%#{params[:query]}%") - sort = params[:sort] == 'desc' ? 'desc' : 'asc' - - projects = case params["order_by"] - when 'id' then projects.order("id #{sort}") - when 'name' then projects.order("name #{sort}") - when 'created_at' then projects.order("created_at #{sort}") - when 'last_activity_at' then projects.order("last_activity_at #{sort}") - else projects - end + search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + projects = search_service.objects('projects', params[:page]) + projects = projects.reorder(project_order_by => project_sort) present paginate(projects), with: Entities::Project end -- cgit v1.2.3 From ea8107768d17752a12b834585a62553f96542738 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 12 Sep 2016 09:46:58 +0100 Subject: Hides tooltip on discussion toggle button when clicking Closes #20750 --- CHANGELOG | 1 + app/assets/javascripts/application.js | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 89c880467cd..de018ca07e4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.12.0 (unreleased) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Request only the LDAP attributes we need !6187 - Center build stage columns in pipeline overview (ClemMakesApps) + - Fix bug with tooltip not hiding on discussion toggle button - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - Remove suggested colors hover underline (ClemMakesApps) - Shorten task status phrase (ClemMakesApps) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 31fa508d6c1..c029bf3b5ca 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -251,6 +251,7 @@ } else { notesHolders.hide(); } + $this.trigger('blur'); return e.preventDefault(); }); $document.off("click", '.js-confirm-danger'); -- cgit v1.2.3 From 46b83f06055b5abda6aa63fe42b4efd890fed774 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 25 Aug 2016 17:52:09 +0200 Subject: Fix an error where we were unable to create a CommitStatus for running state --- CHANGELOG | 1 + lib/api/commit_statuses.rb | 54 ++++++++++++++++--------------- spec/requests/api/commit_statuses_spec.rb | 39 ++++++++++++++++------ 3 files changed, 58 insertions(+), 36 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 132122d460d..1d13c9872cc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -121,6 +121,7 @@ v 8.12.0 (unreleased) - Add notification_settings API calls !5632 (mahcsig) v 8.11.6 (unreleased) + - Fix an error where we were unable to create a CommitStatus for running state v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 5e3c9563703..dfbdd597d29 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -37,7 +37,7 @@ module API # id (required) - The ID of a project # sha (required) - The commit hash # ref (optional) - The ref - # state (required) - The state of the status. Can be: pending, running, success, error or failure + # state (required) - The state of the status. Can be: pending, running, success, failed or canceled # target_url (optional) - The target URL to associate with this status # description (optional) - A short description of the status # name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default" @@ -46,7 +46,7 @@ module API post ':id/statuses/:sha' do authorize! :create_commit_status, user_project required_attributes! [:state] - attrs = attributes_for_keys [:ref, :target_url, :description, :context, :name] + attrs = attributes_for_keys [:target_url, :description] commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit @@ -58,36 +58,38 @@ module API # 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 + ref ||= @project.repository.branch_names_contains(commit.sha).first + not_found! 'References for commit' unless ref + + name = params[:name] || params[:context] || 'default' pipeline = @project.ensure_pipeline(ref, commit.sha, current_user) - name = params[:name] || params[:context] - status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref]) - status ||= GenericCommitStatus.new(project: @project, pipeline: pipeline, user: current_user) - status.update(attrs) + status = GenericCommitStatus.running_or_pending.find_or_initialize_by( + project: @project, pipeline: pipeline, + user: current_user, name: name, ref: ref) + status.attributes = attrs - case params[:state].to_s - when 'running' - status.run - when 'success' - status.success - when 'failed' - status.drop - when 'canceled' - status.cancel - else - status.status = params[:state].to_s - end + begin + case params[:state].to_s + when 'pending' + status.enqueue! + when 'running' + status.enqueue + status.run! + when 'success' + status.success! + when 'failed' + status.drop! + when 'canceled' + status.cancel! + else + render_api_error!('invalid state', 400) + end - if status.save present status, with: Entities::CommitStatus - else - render_validation_error!(status) + rescue StateMachines::InvalidTransition => e + render_api_error!(e.message, 400) end end end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 2d6093fec7a..7aa7e85a9e2 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -117,17 +117,36 @@ describe API::CommitStatuses, api: true do let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" } context 'developer user' do - context 'only required parameters' do - before { post api(post_url, developer), state: 'success' } + %w[pending running success failed canceled].each do |status| + context "for #{status}" do + context 'uses only required parameters' do + it 'creates commit status' do + post api(post_url, developer), state: status + + expect(response).to have_http_status(201) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq(status) + expect(json_response['name']).to eq('default') + expect(json_response['ref']).not_to be_empty + expect(json_response['target_url']).to be_nil + expect(json_response['description']).to be_nil + end + end + end + end - it 'creates commit status' do - expect(response).to have_http_status(201) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq('success') - expect(json_response['name']).to eq('default') - expect(json_response['ref']).to be_nil - expect(json_response['target_url']).to be_nil - expect(json_response['description']).to be_nil + context 'transitions status from pending' do + before do + post api(post_url, developer), state: 'pending' + end + + %w[running success failed canceled].each do |status| + it "to #{status}" do + expect { post api(post_url, developer), state: status }.not_to change { CommitStatus.count } + + expect(response).to have_http_status(201) + expect(json_response['status']).to eq(status) + end end end -- cgit v1.2.3 From 3bb409d2eeaa36921149984065b10e6d9394e641 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 12 Sep 2016 18:49:58 +0800 Subject: Make the cases clear instead of having guards --- app/services/ci/process_pipeline_service.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index de48a50774e..36c93dddadb 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -31,13 +31,13 @@ module Ci current_status = status_for_prior_stages(index) created_builds_in_stage(index).select do |build| - process_build(build, current_status) + if HasStatus::COMPLETED_STATUSES.include?(current_status) + process_build(build, current_status) + end end end def process_build(build, current_status) - return false unless HasStatus::COMPLETED_STATUSES.include?(current_status) - if valid_statuses_for_when(build.when).include?(current_status) build.enqueue true -- cgit v1.2.3 From 683d8b7f007ad750d088b074e50339c66bf5dd82 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 12 Sep 2016 18:55:49 +0800 Subject: Fix the ordering of transition callbacks: Because pipeline status could be changed for the builds in the next stages, if we process next stages first, the current build would be out of synchronized, and would need a reload for that matter. Alternatively, like what I did in this commit, we could process the next stages later (by using `after_transition` rather than `around_transition`), and complete what're doing for the current build first. This way we don't have to reload because nothing is out synchronized. Note that since giving `false` in `after_transition` would halt the callbacks chain, according to: https://github.com/state-machines/state_machines-activemodel/blob/v0.4.0/lib/state_machines/integrations/active_model.rb#L426-L429 We'll need to make sure we're not returning false because we don't intend to interrupt the chain. This fixes #22010. After this fix, both pipeline events and build events would only show up once. --- app/models/commit_status.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 4a628924499..438f807b118 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - # We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed - around_transition any => [:success, :failed, :canceled] do |commit_status, block| - block.call - - commit_status.pipeline.try(:process!) - end - after_transition do |commit_status, transition| commit_status.pipeline.try(:build_updated) unless transition.loopback? end + after_transition any => [:success, :failed, :canceled] do |commit_status| + commit_status.pipeline.try(:process!) + true + end + after_transition [:created, :pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) end -- cgit v1.2.3 From d763aecd07ad8c76619658953c87a292949e55ba Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 11 Sep 2016 11:09:16 -0700 Subject: Only check :can_resolve permission if the note is resolvable Currently this permission check incurs a significant performance hit on loading issues where it does not make sense to check this permission in the first place. --- CHANGELOG | 1 + app/views/projects/notes/_note.html.haml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 94bccb729dd..054f54cb591 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 + - Only check :can_resolve permission if the note is resolvable - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 - Prune events older than 12 months. (ritave) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 7c82177f9ea..9ec17cf6e76 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -1,6 +1,5 @@ - return unless note.author - return if note.cross_reference_not_visible_for?(current_user) -- can_resolve = can?(current_user, :resolve_note, note) - note_editable = note_editable?(note) %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} } @@ -24,6 +23,8 @@ %span.note-role.hidden-xs= access - if note.resolvable? + - can_resolve = can?(current_user, :resolve_note, note) + %resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'", ":project-path" => "'#{note.project.path}'", ":discussion-id" => "'#{note.discussion_id}'", -- cgit v1.2.3 From 47dccfb32935426a4f1cf386466961f61225208a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 12 Sep 2016 20:22:59 +0800 Subject: Fix test (credits to Kamil) --- app/models/concerns/has_status.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index f7b8352405c..77fc66a7105 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -8,8 +8,9 @@ module HasStatus class_methods do def status_sql - scope = all.relevant + scope = all builds = scope.select('count(*)').to_sql + created = scope.created.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored) ignored ||= '0' @@ -22,9 +23,9 @@ module HasStatus WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' - WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending' + WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending' WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' - WHEN (#{running})+(#{pending})>0 THEN 'running' + WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running' ELSE 'failed' END)" -- cgit v1.2.3 From 575dc2b0d78b2680d6e5bb43f293c98d378b8733 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 12 Sep 2016 20:37:44 +0800 Subject: reload instead, so that we don't have to change order --- app/models/commit_status.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 438f807b118..ad4e1c25231 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -69,15 +69,22 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - after_transition do |commit_status, transition| - commit_status.pipeline.try(:build_updated) unless transition.loopback? - end + # We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed + around_transition any => [:success, :failed, :canceled] do |commit_status, block| + block.call - after_transition any => [:success, :failed, :canceled] do |commit_status| commit_status.pipeline.try(:process!) true end + after_transition do |commit_status, transition| + pipeline = commit_status.pipeline + if !transition.loopback? && pipeline + pipeline.reload + pipeline.build_updated + end + end + after_transition [:created, :pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) end -- cgit v1.2.3 From 6baf9971db231a2c9923bd4d73e1f59fe255aab8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 12 Sep 2016 21:27:46 +0800 Subject: Revert "reload instead, so that we don't have to change order" This reverts commit 575dc2b0d78b2680d6e5bb43f293c98d378b8733. --- app/models/commit_status.rb | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index ad4e1c25231..438f807b118 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -69,22 +69,15 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - # We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed - around_transition any => [:success, :failed, :canceled] do |commit_status, block| - block.call + after_transition do |commit_status, transition| + commit_status.pipeline.try(:build_updated) unless transition.loopback? + end + after_transition any => [:success, :failed, :canceled] do |commit_status| commit_status.pipeline.try(:process!) true end - after_transition do |commit_status, transition| - pipeline = commit_status.pipeline - if !transition.loopback? && pipeline - pipeline.reload - pipeline.build_updated - end - end - after_transition [:created, :pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) end -- cgit v1.2.3 From af7d383675269523ca4c6b7bddf8a34f6054f6af Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 12 Sep 2016 21:27:59 +0800 Subject: should show the status of the latest one --- app/models/commit.rb | 2 +- spec/requests/api/commits_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index e64fd1e0c1b..0e2ab76d347 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -231,7 +231,7 @@ class Commit def status return @status if defined?(@status) - @status ||= pipelines.status + @status ||= pipelines.order(:id).last.try(:status) end def revert_branch_name diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 5b3dc60aba2..e24e92e063c 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -110,7 +110,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response).to have_http_status(200) - expect(json_response['status']).to be_nil + expect(json_response['status']).to eq('created') end end -- cgit v1.2.3 From a027740fed115a2173d4de3fc40a32a1b2fd796e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 12 Sep 2016 14:44:16 +0100 Subject: Fixed large comments messing with diff table widths Closes #20694 --- CHANGELOG | 1 + app/assets/stylesheets/pages/diff.scss | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 132122d460d..d733bdb3858 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ v 8.12.0 (unreleased) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint + - Fix long comments in diffs messing with table width - Fix pagination on user snippets page - Fix sorting of issues in API - Escape search term before passing it to Regexp.new !6241 (winniehell) diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 21cee2e3a70..b8ef76cc74e 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -68,6 +68,11 @@ border-collapse: separate; margin: 0; padding: 0; + table-layout: fixed; + + .diff-line-num { + width: 50px; + } .line_holder td { line-height: $code_line_height; @@ -98,10 +103,6 @@ } tr.line_holder.parallel { - .old_line, .new_line { - min-width: 50px; - } - td.line_content.parallel { width: 46%; } -- cgit v1.2.3 From b7814205dc3aed8e1f405c43a63e376fec726112 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 12 Sep 2016 15:11:49 +0200 Subject: Ensure specs on sorting of issues in API are deterministic on MySQL MySQL could not have support for millisecond precision, depends on the MySQL version so we just create issues in different seconds in a deterministic way --- CHANGELOG | 1 + spec/factories/issues.rb | 4 ++++ spec/requests/api/issues_spec.rb | 3 +++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 132122d460d..e785484b70f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.12.0 (unreleased) - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Fix pagination on user snippets page - Fix sorting of issues in API + - Ensure specs on sorting of issues in API are deterministic on MySQL - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 - Change merge_error column from string to text type diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 2c0a2dd94ca..2b4670be468 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -1,4 +1,8 @@ FactoryGirl.define do + sequence :issue_created_at do |n| + 4.hours.ago + ( 2 * n ).seconds + end + factory :issue do title author diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 86d994be079..f840778ae9b 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -18,6 +18,7 @@ describe API::API, api: true do project: project, state: :closed, milestone: milestone, + created_at: generate(:issue_created_at), updated_at: 3.hours.ago end let!(:confidential_issue) do @@ -26,6 +27,7 @@ describe API::API, api: true do project: project, author: author, assignee: assignee, + created_at: generate(:issue_created_at), updated_at: 2.hours.ago end let!(:issue) do @@ -34,6 +36,7 @@ describe API::API, api: true do assignee: user, project: project, milestone: milestone, + created_at: generate(:issue_created_at), updated_at: 1.hour.ago end let!(:label) do -- cgit v1.2.3 From 265dbb12421c2fb2a67f32f3a3f5ea259b683065 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Sun, 11 Sep 2016 16:26:02 +0100 Subject: Removed horizontal overflow from collapsed issuable sidebar --- app/assets/stylesheets/pages/issuable.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 63845e6b9ba..41079b6eeb5 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -206,7 +206,7 @@ padding-top: 0; .block { - width: $sidebar_collapsed_width - 1px; + width: $sidebar_collapsed_width - 2px; margin-left: -19px; padding: 15px 0 0; border-bottom: none; -- cgit v1.2.3 From 0c046399d4361e2f3d8943efddd2ef4fd3b2152f Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 12 Sep 2016 17:41:32 +0200 Subject: Document how to track custom events Fixes gitlab-org/gitlab-ce#22070 [ci skip] --- doc/development/instrumentation.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index c2272ab0a2b..105e2f1242a 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -137,3 +137,18 @@ end ``` Here the final value of `sleep_real_time` will be `3`, _not_ `1`. + +## Tracking Custom Events + +Besides instrumenting code GitLab Performance Monitoring also supports tracking +of custom events. This is primarily intended to be used for tracking business +metrics such as the number of Git pushes, repository imports, and so on. + +To track a custom event simply call `Gitlab::Metrics.add_event` passing it an +event name and a custom set of (optional) tags. For example: + +```ruby +Gitlab::Metrics.add_event(:user_login, email: current_user.email) +``` + +Event names should be verbs such as `push_repository` and `remove_branch`. -- cgit v1.2.3 From 23612ada54381487692cca76523dfad8a201f618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 12 Sep 2016 18:10:08 +0200 Subject: Simplify a condition in Repository#update_file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 37fbdcfe1c6..3c354c25c6f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -813,7 +813,7 @@ class Repository update: true } - unless previous_path == path || previous_path.blank? + if previous_path && previous_path != path options[:file][:previous_path] = previous_path Gitlab::Git::Blob.rename(raw_repository, options) else -- cgit v1.2.3 From dbe37d2f6b588b399b8be95c696c770199df5cbd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 12 Sep 2016 12:35:08 -0700 Subject: Add link to article on how to write reliable, asynchronous tests with Capybara [ci skip] --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c77dcd96a7d..7a2ee38ad8f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -289,6 +289,8 @@ request is as follows: 1. For more complex migrations, write tests. 1. Merge requests **must** adhere to the [merge request performance guidelines](doc/development/merge_request_performance_guidelines.md). +1. For tests that use Capybara or PhantomJS, see this [article on how + to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara). The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. This is the best time to submit an MR and get -- cgit v1.2.3 From 8b6bbc866d78ec8d4d8a99c0862c9d4c30398d90 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 12 Sep 2016 14:23:32 -0600 Subject: No need for this variable. --- config/initializers/mime_types.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index cf3917033d8..5e3e4c966cb 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -13,10 +13,5 @@ Mime::Type.register "video/mp4", :mp4, [], [:m4v, :mov] Mime::Type.register "video/webm", :webm Mime::Type.register "video/ogg", :ogv -json_mime_types = %w( - application/vnd.git-lfs+json - application/json -) - Mime::Type.unregister :json -Mime::Type.register 'application/json', :json, json_mime_types +Mime::Type.register 'application/json', :json, %w(application/vnd.git-lfs+json application/json) -- cgit v1.2.3 From 05b52e0f5e4d06edb736849daaac5c33c68c1d47 Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Thu, 25 Aug 2016 07:45:31 +1000 Subject: Import GitHub release notes # Conflicts: # lib/gitlab/github_import/importer.rb --- CHANGELOG | 1 + .../importing/import_projects_from_github.md | 1 + lib/gitlab/github_import/importer.rb | 13 ++++++ lib/gitlab/github_import/release_formatter.rb | 23 +++++++++ spec/lib/gitlab/github_import/importer_spec.rb | 30 +++++++++++- .../gitlab/github_import/release_formatter_spec.rb | 54 ++++++++++++++++++++++ 6 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 lib/gitlab/github_import/release_formatter.rb create mode 100644 spec/lib/gitlab/github_import/release_formatter_spec.rb diff --git a/CHANGELOG b/CHANGELOG index d69168985cd..22ed94f648b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -79,6 +79,7 @@ v 8.12.0 (unreleased) - Remove inconsistent font weight for sidebar's labels (ClemMakesApps) - Align add button on repository view (ClemMakesApps) - Fix contributions calendar month label truncation (ClemMakesApps) + - Import release note descriptions from GitHub (EspadaV8) - Added tests for diff notes - Add pipeline events to Slack integration !5525 - Add a button to download latest successful artifacts for branches and tags !5142 diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index 306caabf6e6..370d885d366 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -15,6 +15,7 @@ At its current state, GitHub importer can import: - the wiki pages (introduced in GitLab 8.4) - the milestones (introduced in GitLab 8.7) - the labels (introduced in GitLab 8.7) +- the release note descriptions (introduced in GitLab 8.12) With GitLab 8.7+, references to pull requests and issues are preserved. diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index 0388c58f811..d35ee2a1c65 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -24,6 +24,7 @@ module Gitlab import_issues import_pull_requests import_wiki + import_releases handle_errors true @@ -177,6 +178,18 @@ module Gitlab errors << { type: :wiki, errors: e.message } end end + + def import_releases + releases = client.releases(repo, per_page: 100) + releases.each do |raw| + begin + gh_release = ReleaseFormatter.new(project, raw) + gh_release.create! if gh_release.valid? + rescue => e + errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message } + end + end + end end end end diff --git a/lib/gitlab/github_import/release_formatter.rb b/lib/gitlab/github_import/release_formatter.rb new file mode 100644 index 00000000000..73d643b00ad --- /dev/null +++ b/lib/gitlab/github_import/release_formatter.rb @@ -0,0 +1,23 @@ +module Gitlab + module GithubImport + class ReleaseFormatter < BaseFormatter + def attributes + { + project: project, + tag: raw_data.tag_name, + description: raw_data.body, + created_at: raw_data.created_at, + updated_at: raw_data.created_at + } + end + + def klass + Release + end + + def valid? + !raw_data.draft + end + end + end +end diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb index 7df288f619f..553c849c9b4 100644 --- a/spec/lib/gitlab/github_import/importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer_spec.rb @@ -98,6 +98,30 @@ describe Gitlab::GithubImport::Importer, lib: true do ) end + let(:release1) do + double( + tag_name: 'v1.0.0', + name: 'First release', + body: 'Release v1.0.0', + draft: false, + created_at: created_at, + updated_at: updated_at, + url: 'https://api.github.com/repos/octocat/Hello-World/releases/1' + ) + end + + let(:release2) do + double( + tag_name: 'v2.0.0', + name: 'Second release', + body: nil, + draft: false, + created_at: created_at, + updated_at: updated_at, + url: 'https://api.github.com/repos/octocat/Hello-World/releases/2' + ) + end + before do allow(project).to receive(:import_data).and_return(double.as_null_object) allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound) @@ -106,6 +130,7 @@ describe Gitlab::GithubImport::Importer, lib: true do allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2]) allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request]) allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil })) + allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2]) allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error) end @@ -127,8 +152,9 @@ describe Gitlab::GithubImport::Importer, lib: true do { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank, Title is too short (minimum is 0 characters)" }, { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Invalid Repository. Use user/repo format." }, { type: :pull_request, url: "https://api.github.com/repos/octocat/Hello-World/pulls/1347", errors: "Validation failed: Validate branches Cannot Create: This merge request already exists: [\"New feature\"]" }, - { type: :wiki, errors: "Gitlab::Shell::Error" } - ] + { type: :wiki, errors: "Gitlab::Shell::Error" }, + { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" } + ] } described_class.new(project).execute diff --git a/spec/lib/gitlab/github_import/release_formatter_spec.rb b/spec/lib/gitlab/github_import/release_formatter_spec.rb new file mode 100644 index 00000000000..793128c6ab9 --- /dev/null +++ b/spec/lib/gitlab/github_import/release_formatter_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::GithubImport::ReleaseFormatter, lib: true do + let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } + let(:octocat) { double(id: 123456, login: 'octocat') } + let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } + + let(:base_data) do + { + tag_name: 'v1.0.0', + name: 'First release', + draft: false, + created_at: created_at, + published_at: created_at, + body: 'Release v1.0.0' + } + end + + subject(:release) { described_class.new(project, raw_data) } + + describe '#attributes' do + let(:raw_data) { double(base_data) } + + it 'returns formatted attributes' do + expected = { + project: project, + tag: 'v1.0.0', + description: 'Release v1.0.0', + created_at: created_at, + updated_at: created_at + } + + expect(release.attributes).to eq(expected) + end + end + + describe '#valid' do + context 'when release is not a draft' do + let(:raw_data) { double(base_data) } + + it 'returns true' do + expect(release.valid?).to eq true + end + end + + context 'when release is draft' do + let(:raw_data) { double(base_data.merge(draft: true)) } + + it 'returns false' do + expect(release.valid?).to eq false + end + end + end +end -- cgit v1.2.3 From 978635f0830aca5da05b68b8e325ba50ad9ff3ee Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 13 Sep 2016 04:31:26 +0000 Subject: include region for source installation --- doc/administration/container_registry.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index 5eb5626cef5..c5611e2a121 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -429,6 +429,7 @@ storage: accesskey: 'AKIAKIAKI' secretkey: 'secret123' bucket: 'gitlab-registry-bucket-AKIAKIAKI' + region: 'your-s3-region' cache: blobdescriptor: inmemory delete: -- cgit v1.2.3 From ab43e9c1f9ad91f9ac2d8e648f747cd0873e4f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= Date: Sun, 17 Apr 2016 18:11:28 +0000 Subject: update gitlab shell secret file also when it is empty --- CHANGELOG | 1 + lib/gitlab/backend/shell.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d69168985cd..e281cdadc4a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.12.0 (unreleased) - Prune events older than 12 months. (ritave) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Filter tags by name !6121 + - Update gitlab shell secret file also when it is empty. !3774 (glensc) - Give project selection dropdowns responsive width, make non-wrapping. - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 839a4fa30d5..c412249a01e 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -195,7 +195,7 @@ module Gitlab # Create (if necessary) and link the secret token file def generate_and_link_secret_token secret_file = Gitlab.config.gitlab_shell.secret_file - unless File.exist? secret_file + unless File.size?(secret_file) # Generate a new token of 16 random hexadecimal characters and store it in secret_file. token = SecureRandom.hex(16) File.write(secret_file, token) -- cgit v1.2.3 From 516b2a12758aa1ddd947c32a14d0088df193402c Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 5 Aug 2016 11:34:57 +0200 Subject: Add partial to admin builds and project builds --- app/controllers/admin/builds_controller.rb | 2 ++ app/controllers/projects/builds_controller.rb | 2 ++ app/views/admin/builds/index.html.haml | 23 +++-------------------- app/views/projects/builds/_builds.html.haml | 24 ++++++++++++++++++++++++ app/views/projects/builds/index.html.haml | 27 +++------------------------ 5 files changed, 34 insertions(+), 44 deletions(-) create mode 100644 app/views/projects/builds/_builds.html.haml diff --git a/app/controllers/admin/builds_controller.rb b/app/controllers/admin/builds_controller.rb index 88f3c0e2fd4..c4e91d8f869 100644 --- a/app/controllers/admin/builds_controller.rb +++ b/app/controllers/admin/builds_controller.rb @@ -5,6 +5,8 @@ class Admin::BuildsController < Admin::ApplicationController @builds = @all_builds.order('created_at DESC') @builds = case @scope + when 'all' + @builds when 'pending' @builds.pending.reverse_order when 'running' diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 77934ff9962..c6a57ff8cb1 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -10,6 +10,8 @@ class Projects::BuildsController < Projects::ApplicationController @builds = @all_builds.order('created_at DESC') @builds = case @scope + when 'all' + @builds when 'pending' @builds.pending.reverse_order when 'running' diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 3d77634d8fa..f0599bd3004 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -4,26 +4,9 @@ %div{ class: container_class } .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to admin_builds_path do - All - %span.badge.js-totalbuilds-count= @all_builds.count(:id) - - %li{class: ('active' if @scope == 'pending')} - = link_to admin_builds_path(scope: :pending) do - Pending - %span.badge= number_with_delimiter(@all_builds.pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to admin_builds_path(scope: :running) do - Running - %span.badge= number_with_delimiter(@all_builds.running.count(:id)) - - %li{class: ('active' if @scope == 'finished')} - = link_to admin_builds_path(scope: :finished) do - Finished - %span.badge= number_with_delimiter(@all_builds.finished.count(:id)) + - scopes = [:all, :pending, :running, :finished] + - paths = scopes.zip(scopes.map { |scope| admin_builds_path(scope: scope) }).to_h + = render "projects/builds/builds", paths: paths .nav-controls - if @all_builds.running_or_pending.any? diff --git a/app/views/projects/builds/_builds.html.haml b/app/views/projects/builds/_builds.html.haml new file mode 100644 index 00000000000..61542f8a7cc --- /dev/null +++ b/app/views/projects/builds/_builds.html.haml @@ -0,0 +1,24 @@ +%ul.nav-links + %li{class: ('active' if @scope.nil? || @scope == 'all')} + = link_to paths[:all] do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_builds.count(:id)) + + %li{class: ('active' if @scope == 'pending')} + = link_to paths[:pending] do + Pending + %span.badge + = number_with_delimiter(@all_builds.pending.count(:id)) + + %li{class: ('active' if @scope == 'running')} + = link_to paths[:running] do + Running + %span.badge + = number_with_delimiter(@all_builds.running.count(:id)) + + %li{class: ('active' if @scope == 'finished')} + = link_to paths[:finished] do + Finished + %span.badge + = number_with_delimiter(@all_builds.finished.count(:id)) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 2af625f69cd..765e239d1de 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -4,30 +4,9 @@ %div{ class: container_class } .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to project_builds_path(@project) do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_builds.count(:id)) - - %li{class: ('active' if @scope == 'pending')} - = link_to project_builds_path(@project, scope: :pending) do - Pending - %span.badge - = number_with_delimiter(@all_builds.pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to project_builds_path(@project, scope: :running) do - Running - %span.badge - = number_with_delimiter(@all_builds.running.count(:id)) - - %li{class: ('active' if @scope == 'finished')} - = link_to project_builds_path(@project, scope: :finished) do - Finished - %span.badge - = number_with_delimiter(@all_builds.finished.count(:id)) + - scopes = [:all, :pending, :running, :finished] + - paths = scopes.zip(scopes.map { |scope| project_builds_path(@project, scope: scope) }).to_h + = render "builds", paths: paths .nav-controls - if can?(current_user, :update_build, @project) -- cgit v1.2.3 From e81ed371a10012c2d09f67b3523c3ba3fd17c7bd Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 10 Aug 2016 13:07:03 +0200 Subject: Change the way paths are generated --- app/controllers/admin/builds_controller.rb | 2 -- app/controllers/projects/builds_controller.rb | 2 -- app/views/admin/builds/index.html.haml | 5 ++--- app/views/projects/builds/_builds.html.haml | 24 ------------------------ app/views/projects/builds/_tabs.html.haml | 24 ++++++++++++++++++++++++ app/views/projects/builds/index.html.haml | 5 ++--- 6 files changed, 28 insertions(+), 34 deletions(-) delete mode 100644 app/views/projects/builds/_builds.html.haml create mode 100644 app/views/projects/builds/_tabs.html.haml diff --git a/app/controllers/admin/builds_controller.rb b/app/controllers/admin/builds_controller.rb index c4e91d8f869..88f3c0e2fd4 100644 --- a/app/controllers/admin/builds_controller.rb +++ b/app/controllers/admin/builds_controller.rb @@ -5,8 +5,6 @@ class Admin::BuildsController < Admin::ApplicationController @builds = @all_builds.order('created_at DESC') @builds = case @scope - when 'all' - @builds when 'pending' @builds.pending.reverse_order when 'running' diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index c6a57ff8cb1..77934ff9962 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -10,8 +10,6 @@ class Projects::BuildsController < Projects::ApplicationController @builds = @all_builds.order('created_at DESC') @builds = case @scope - when 'all' - @builds when 'pending' @builds.pending.reverse_order when 'running' diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index f0599bd3004..1bf4cb4d124 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -4,9 +4,8 @@ %div{ class: container_class } .top-area - - scopes = [:all, :pending, :running, :finished] - - paths = scopes.zip(scopes.map { |scope| admin_builds_path(scope: scope) }).to_h - = render "projects/builds/builds", paths: paths + - build_path = ->(scope) { admin_builds_path(scope: scope) } + = render "projects/builds/tabs", build_path: build_path .nav-controls - if @all_builds.running_or_pending.any? diff --git a/app/views/projects/builds/_builds.html.haml b/app/views/projects/builds/_builds.html.haml deleted file mode 100644 index 61542f8a7cc..00000000000 --- a/app/views/projects/builds/_builds.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -%ul.nav-links - %li{class: ('active' if @scope.nil? || @scope == 'all')} - = link_to paths[:all] do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_builds.count(:id)) - - %li{class: ('active' if @scope == 'pending')} - = link_to paths[:pending] do - Pending - %span.badge - = number_with_delimiter(@all_builds.pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to paths[:running] do - Running - %span.badge - = number_with_delimiter(@all_builds.running.count(:id)) - - %li{class: ('active' if @scope == 'finished')} - = link_to paths[:finished] do - Finished - %span.badge - = number_with_delimiter(@all_builds.finished.count(:id)) diff --git a/app/views/projects/builds/_tabs.html.haml b/app/views/projects/builds/_tabs.html.haml new file mode 100644 index 00000000000..50894975a88 --- /dev/null +++ b/app/views/projects/builds/_tabs.html.haml @@ -0,0 +1,24 @@ +%ul.nav-links + %li{class: ('active' if @scope.nil?)} + = link_to build_path[nil] do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(@all_builds.count(:id)) + + %li{class: ('active' if @scope == 'pending')} + = link_to build_path[:pending] do + Pending + %span.badge + = number_with_delimiter(@all_builds.pending.count(:id)) + + %li{class: ('active' if @scope == 'running')} + = link_to build_path[:running] do + Running + %span.badge + = number_with_delimiter(@all_builds.running.count(:id)) + + %li{class: ('active' if @scope == 'finished')} + = link_to build_path[:finished] do + Finished + %span.badge + = number_with_delimiter(@all_builds.finished.count(:id)) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 765e239d1de..ec5e33366d1 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -4,9 +4,8 @@ %div{ class: container_class } .top-area - - scopes = [:all, :pending, :running, :finished] - - paths = scopes.zip(scopes.map { |scope| project_builds_path(@project, scope: scope) }).to_h - = render "builds", paths: paths + - build_path = ->(scope) { project_builds_path(@project, scope: scope) } + = render "tabs", build_path: build_path .nav-controls - if can?(current_user, :update_build, @project) -- cgit v1.2.3 From e9dbcd8abef32884e1fa95a52e4a33d2046ef29a Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 10 Aug 2016 16:12:22 +0200 Subject: Use local variables in partial --- app/views/admin/builds/index.html.haml | 2 +- app/views/projects/builds/_tabs.html.haml | 16 ++++++++-------- app/views/projects/builds/index.html.haml | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 1bf4cb4d124..eb189328ab8 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -5,7 +5,7 @@ .top-area - build_path = ->(scope) { admin_builds_path(scope: scope) } - = render "projects/builds/tabs", build_path: build_path + = render "projects/builds/tabs", build_path: build_path, all_builds: @all_builds, scope: @scope .nav-controls - if @all_builds.running_or_pending.any? diff --git a/app/views/projects/builds/_tabs.html.haml b/app/views/projects/builds/_tabs.html.haml index 50894975a88..9c3173f7a1a 100644 --- a/app/views/projects/builds/_tabs.html.haml +++ b/app/views/projects/builds/_tabs.html.haml @@ -1,24 +1,24 @@ %ul.nav-links - %li{class: ('active' if @scope.nil?)} + %li{class: ('active' if scope.nil?)} = link_to build_path[nil] do All %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_builds.count(:id)) + = number_with_delimiter(all_builds.count(:id)) - %li{class: ('active' if @scope == 'pending')} + %li{class: ('active' if scope == 'pending')} = link_to build_path[:pending] do Pending %span.badge - = number_with_delimiter(@all_builds.pending.count(:id)) + = number_with_delimiter(all_builds.pending.count(:id)) - %li{class: ('active' if @scope == 'running')} + %li{class: ('active' if scope == 'running')} = link_to build_path[:running] do Running %span.badge - = number_with_delimiter(@all_builds.running.count(:id)) + = number_with_delimiter(all_builds.running.count(:id)) - %li{class: ('active' if @scope == 'finished')} + %li{class: ('active' if scope == 'finished')} = link_to build_path[:finished] do Finished %span.badge - = number_with_delimiter(@all_builds.finished.count(:id)) + = number_with_delimiter(all_builds.finished.count(:id)) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index ec5e33366d1..3f3895e19f0 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -5,7 +5,7 @@ %div{ class: container_class } .top-area - build_path = ->(scope) { project_builds_path(@project, scope: scope) } - = render "tabs", build_path: build_path + = render "tabs", build_path: build_path, all_builds: @all_builds, scope: @scope .nav-controls - if can?(current_user, :update_build, @project) -- cgit v1.2.3 From 21976091175598b590f4f9306d812280ba9f4c28 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 11 Aug 2016 11:02:09 +0200 Subject: Move builds content list to partial --- app/views/admin/builds/index.html.haml | 21 +---------- app/views/projects/builds/_content_list.html.haml | 43 +++++++++++++++++++++++ app/views/projects/builds/index.html.haml | 21 +---------- 3 files changed, 45 insertions(+), 40 deletions(-) create mode 100644 app/views/projects/builds/_content_list.html.haml diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index eb189328ab8..3715d368095 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -15,23 +15,4 @@ #{(@scope || 'all').capitalize} builds %ul.content-list.builds-content-list - - if @builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit - %th Project - %th Runner - %th Name - %th - %th - - - @builds.each do |build| - = render "admin/builds/build", build: build - - = paginate @builds, theme: 'gitlab' + = render "projects/builds/content_list", builds: @builds, project: nil diff --git a/app/views/projects/builds/_content_list.html.haml b/app/views/projects/builds/_content_list.html.haml new file mode 100644 index 00000000000..93283edc2e1 --- /dev/null +++ b/app/views/projects/builds/_content_list.html.haml @@ -0,0 +1,43 @@ +- if project.nil? + - if builds.blank? + %li + .nothing-here-block No builds to show + - else + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Commit + %th Project + %th Runner + %th Name + %th + %th + + - builds.each do |build| + = render "admin/builds/build", build: build + + = paginate builds, theme: 'gitlab' + +- if project.present? + - if builds.blank? + %li + .nothing-here-block No builds to show + - else + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Commit + %th Stage + %th Name + %th + - if project.build_coverage_enabled? + %th Coverage + %th + + = 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/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 3f3895e19f0..1f1330b6b26 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -20,23 +20,4 @@ %span CI Lint %ul.content-list.builds-content-list - - if @builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit - %th Stage - %th Name - %th - - if @project.build_coverage_enabled? - %th Coverage - %th - - = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? - - = paginate @builds, theme: 'gitlab' + = render "content_list", builds: @builds, project: @project -- cgit v1.2.3 From 0c14eaa6de4ce1c44d98581714b9c99f4e3045be Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 1 Sep 2016 10:02:01 +0200 Subject: Liken admin and project builds views to each other --- app/views/admin/builds/_build.html.haml | 9 ++-- app/views/projects/builds/_content_list.html.haml | 58 +++++++++-------------- app/views/projects/ci/builds/_build.html.haml | 7 ++- 3 files changed, 31 insertions(+), 43 deletions(-) diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index f29d9c94441..3e868256366 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -45,7 +45,10 @@ .light none %td - #{build.stage} / #{build.name} + #{build.stage} + + %td + #{build.name} %td - if build.duration @@ -58,8 +61,8 @@ = icon("calendar") %span #{time_ago_with_tooltip(build.finished_at)} - - if defined?(coverage) && coverage - %td.coverage + %td.coverage + - if defined?(coverage) && coverage - if build.try(:coverage) #{build.coverage}% diff --git a/app/views/projects/builds/_content_list.html.haml b/app/views/projects/builds/_content_list.html.haml index 93283edc2e1..b59599595dd 100644 --- a/app/views/projects/builds/_content_list.html.haml +++ b/app/views/projects/builds/_content_list.html.haml @@ -1,43 +1,29 @@ -- if project.nil? - - if builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit +- if builds.blank? + %li + .nothing-here-block No builds to show +- else + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Commit + - if project.nil? %th Project %th Runner - %th Name - %th - %th + %th Stage + %th Name + %th + %th + - if project.present? + - if @project.build_coverage_enabled? + %th Coverage + + - if project.nil? - builds.each do |build| = render "admin/builds/build", build: build - - = paginate builds, theme: 'gitlab' - -- if project.present? - - if builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit - %th Stage - %th Name - %th - - if project.build_coverage_enabled? - %th Coverage - %th - + - else = render builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: project.build_coverage_enabled? - = paginate builds, theme: 'gitlab' + = 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 73de8abe55b..68c938d67d5 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -52,12 +52,11 @@ - else .light none - - if defined?(stage) && stage - %td - = build.stage + %td + #{build.stage} %td - = build.name + #{build.name} %td - if build.duration -- cgit v1.2.3 From 4fa6941020d414bcd6ef5b469340de6c940a2055 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 1 Sep 2016 14:09:58 +0200 Subject: Improve code --- app/views/admin/builds/_build.html.haml | 4 ++-- app/views/admin/builds/index.html.haml | 2 +- app/views/projects/builds/_content_list.html.haml | 29 ----------------------- app/views/projects/builds/_table.html.haml | 29 +++++++++++++++++++++++ app/views/projects/builds/index.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 5 ++-- 6 files changed, 36 insertions(+), 35 deletions(-) delete mode 100644 app/views/projects/builds/_content_list.html.haml create mode 100644 app/views/projects/builds/_table.html.haml diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 3e868256366..2b6ad068c57 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -45,10 +45,10 @@ .light none %td - #{build.stage} + = build.stage %td - #{build.name} + = build.name %td - if build.duration diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 3715d368095..a86e054c8d4 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -15,4 +15,4 @@ #{(@scope || 'all').capitalize} builds %ul.content-list.builds-content-list - = render "projects/builds/content_list", builds: @builds, project: nil + = render "projects/builds/table", builds: @builds, project: nil, admin: true diff --git a/app/views/projects/builds/_content_list.html.haml b/app/views/projects/builds/_content_list.html.haml deleted file mode 100644 index b59599595dd..00000000000 --- a/app/views/projects/builds/_content_list.html.haml +++ /dev/null @@ -1,29 +0,0 @@ -- if builds.blank? - %li - .nothing-here-block No builds to show -- else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit - - if project.nil? - %th Project - %th Runner - %th Stage - %th Name - %th - %th - - if project.present? - - if @project.build_coverage_enabled? - %th Coverage - - - - if project.nil? - - builds.each do |build| - = render "admin/builds/build", build: build - - else - = 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/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml new file mode 100644 index 00000000000..eecd1a1ee44 --- /dev/null +++ b/app/views/projects/builds/_table.html.haml @@ -0,0 +1,29 @@ +- if builds.blank? + %li + .nothing-here-block No builds to show +- else + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Commit + - if admin + %th Project + %th Runner + %th Stage + %th Name + %th + %th + - unless admin + - if project.build_coverage_enabled? + %th Coverage + + + - if admin + - builds.each do |build| + = render "admin/builds/build", build: build + - else + = 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/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 1f1330b6b26..cbcaad9e6b7 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -20,4 +20,4 @@ %span CI Lint %ul.content-list.builds-content-list - = render "content_list", builds: @builds, project: @project + = render "table", builds: @builds, project: @project, admin: false diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 68c938d67d5..3ab64aff282 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -53,10 +53,11 @@ .light none %td - #{build.stage} + - if defined?(stage) && stage + = build.stage %td - #{build.name} + = build.name %td - if build.duration -- cgit v1.2.3 From 9e9829d76b2fa8143f45fc53fde766139c738150 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 1 Sep 2016 14:27:23 +0200 Subject: Improve code in table partial --- app/views/projects/builds/_table.html.haml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index eecd1a1ee44..b6602f8b083 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -15,9 +15,10 @@ %th Name %th %th - - unless admin - - if project.build_coverage_enabled? - %th Coverage + - unless admin + - if project.build_coverage_enabled? + Coverage + %th - if admin -- cgit v1.2.3 From b1016a355eded528c9f9ecb15861d084a6673823 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 2 Sep 2016 15:45:03 +0200 Subject: Improve table code and add CHANGELOG --- CHANGELOG | 1 + app/views/projects/builds/_table.html.haml | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d69168985cd..273c29ca6d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -124,6 +124,7 @@ v 8.12.0 (unreleased) - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - Allow bulk update merge requests from merge requests index page - Add notification_settings API calls !5632 (mahcsig) + - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) v 8.11.6 (unreleased) diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index b6602f8b083..9e4fd9b767b 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -15,12 +15,10 @@ %th Name %th %th - - unless admin - - if project.build_coverage_enabled? - Coverage + - if !admin && project.build_coverage_enabled? + Coverage %th - - if admin - builds.each do |build| = render "admin/builds/build", build: build -- cgit v1.2.3 From 27c3a87194a3a29c06cf8a2dd489d28ca0b552ab Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 7 Sep 2016 12:46:29 +0200 Subject: Moved build tabs partial to shared --- app/views/admin/builds/index.html.haml | 2 +- app/views/projects/builds/_tabs.html.haml | 24 ------------------------ app/views/projects/builds/index.html.haml | 2 +- app/views/shared/_builds_tabs.html.haml | 24 ++++++++++++++++++++++++ 4 files changed, 26 insertions(+), 26 deletions(-) delete mode 100644 app/views/projects/builds/_tabs.html.haml create mode 100644 app/views/shared/_builds_tabs.html.haml diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index a86e054c8d4..7dba77d2dee 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -5,7 +5,7 @@ .top-area - build_path = ->(scope) { admin_builds_path(scope: scope) } - = render "projects/builds/tabs", build_path: build_path, all_builds: @all_builds, scope: @scope + = render "shared/builds_tabs", build_path: build_path, all_builds: @all_builds, scope: @scope .nav-controls - if @all_builds.running_or_pending.any? diff --git a/app/views/projects/builds/_tabs.html.haml b/app/views/projects/builds/_tabs.html.haml deleted file mode 100644 index 9c3173f7a1a..00000000000 --- a/app/views/projects/builds/_tabs.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -%ul.nav-links - %li{class: ('active' if scope.nil?)} - = link_to build_path[nil] do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(all_builds.count(:id)) - - %li{class: ('active' if scope == 'pending')} - = link_to build_path[:pending] do - Pending - %span.badge - = number_with_delimiter(all_builds.pending.count(:id)) - - %li{class: ('active' if scope == 'running')} - = link_to build_path[:running] do - Running - %span.badge - = number_with_delimiter(all_builds.running.count(:id)) - - %li{class: ('active' if scope == 'finished')} - = link_to build_path[:finished] do - Finished - %span.badge - = number_with_delimiter(all_builds.finished.count(:id)) diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index cbcaad9e6b7..f1b3fd1bcea 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -5,7 +5,7 @@ %div{ class: container_class } .top-area - build_path = ->(scope) { project_builds_path(@project, scope: scope) } - = render "tabs", build_path: build_path, all_builds: @all_builds, scope: @scope + = render "shared/builds_tabs", build_path: build_path, all_builds: @all_builds, scope: @scope .nav-controls - if can?(current_user, :update_build, @project) diff --git a/app/views/shared/_builds_tabs.html.haml b/app/views/shared/_builds_tabs.html.haml new file mode 100644 index 00000000000..9c3173f7a1a --- /dev/null +++ b/app/views/shared/_builds_tabs.html.haml @@ -0,0 +1,24 @@ +%ul.nav-links + %li{class: ('active' if scope.nil?)} + = link_to build_path[nil] do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(all_builds.count(:id)) + + %li{class: ('active' if scope == 'pending')} + = link_to build_path[:pending] do + Pending + %span.badge + = number_with_delimiter(all_builds.pending.count(:id)) + + %li{class: ('active' if scope == 'running')} + = link_to build_path[:running] do + Running + %span.badge + = number_with_delimiter(all_builds.running.count(:id)) + + %li{class: ('active' if scope == 'finished')} + = link_to build_path[:finished] do + Finished + %span.badge + = number_with_delimiter(all_builds.finished.count(:id)) -- cgit v1.2.3 From 80c86bcc5b778306825e7650ccaa3c01bfd75ac0 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Wed, 7 Sep 2016 15:27:48 +0200 Subject: Create shared partial for project and admin builds --- app/views/admin/builds/_build.html.haml | 80 -------------------- app/views/admin/builds/index.html.haml | 2 +- app/views/projects/builds/_table.html.haml | 6 +- app/views/projects/builds/index.html.haml | 4 +- app/views/projects/ci/builds/_build.html.haml | 92 ----------------------- app/views/shared/builds/_build.html.haml | 101 ++++++++++++++++++++++++++ app/views/shared/builds/_tabs.html.haml | 24 ++++++ 7 files changed, 131 insertions(+), 178 deletions(-) delete mode 100644 app/views/admin/builds/_build.html.haml delete mode 100644 app/views/projects/ci/builds/_build.html.haml create mode 100644 app/views/shared/builds/_build.html.haml create mode 100644 app/views/shared/builds/_tabs.html.haml diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml deleted file mode 100644 index 2b6ad068c57..00000000000 --- a/app/views/admin/builds/_build.html.haml +++ /dev/null @@ -1,80 +0,0 @@ -- project = build.project -%tr.build.commit - %td.status - = ci_status_with_icon(build.status) - - %td - .branch-commit - - if can?(current_user, :read_build, build.project) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span.build-link ##{build.id} - - else - %span.build-link ##{build.id} - - - if build.ref - .icon-container - = build.tag? ? icon('tag') : icon('code-fork') - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - - else - .light none - .icon-container - = custom_icon("icon_commit") - - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id" - - if build.stuck? - %i.fa.fa-warning.text-warning - - .label-container - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %span.label.label-info triggered - - if build.try(:allow_failure) - %span.label.label-danger allowed to fail - - %td - - if project - = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) - - %td - - if build.try(:runner) - = runner_link(build.runner) - - else - .light none - - %td - = build.stage - - %td - = build.name - - %td - - if build.duration - %p.duration - = custom_icon("icon_timer") - = duration_in_numbers(build.duration) - - - if build.finished_at - %p.finished-at - = icon("calendar") - %span #{time_ago_with_tooltip(build.finished_at)} - - %td.coverage - - if defined?(coverage) && coverage - - if build.try(:coverage) - #{build.coverage}% - - %td - .pull-right - - if can?(current_user, :read_build, project) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do - %i.fa.fa-download - - if can?(current_user, :update_build, build.project) - - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do - %i.fa.fa-remove.cred - - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - %i.fa.fa-refresh diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 7dba77d2dee..7d84172bd2e 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -5,7 +5,7 @@ .top-area - build_path = ->(scope) { admin_builds_path(scope: scope) } - = render "shared/builds_tabs", build_path: build_path, all_builds: @all_builds, scope: @scope + = render "shared/builds/tabs", build_path: build_path, all_builds: @all_builds, scope: @scope .nav-controls - if @all_builds.running_or_pending.any? diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index 9e4fd9b767b..a4cd9596d68 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -1,3 +1,4 @@ +- admin = false unless admin - if builds.blank? %li .nothing-here-block No builds to show @@ -20,9 +21,8 @@ %th - if admin - - builds.each do |build| - = render "admin/builds/build", build: build + = render partial: "shared/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, runner: true, admin: true } - else - = render builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: project.build_coverage_enabled? + = render partial: "shared/builds/build", collection: builds, as: :build, locals: {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/builds/index.html.haml b/app/views/projects/builds/index.html.haml index f1b3fd1bcea..d2fc2ae34fe 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -5,7 +5,7 @@ %div{ class: container_class } .top-area - build_path = ->(scope) { project_builds_path(@project, scope: scope) } - = render "shared/builds_tabs", build_path: build_path, all_builds: @all_builds, scope: @scope + = render "shared/builds/tabs", build_path: build_path, all_builds: @all_builds, scope: @scope .nav-controls - if can?(current_user, :update_build, @project) @@ -20,4 +20,4 @@ %span CI Lint %ul.content-list.builds-content-list - = render "table", builds: @builds, project: @project, admin: false + = render "table", builds: @builds, project: @project diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml deleted file mode 100644 index 3ab64aff282..00000000000 --- a/app/views/projects/ci/builds/_build.html.haml +++ /dev/null @@ -1,92 +0,0 @@ -%tr.build.commit - %td.status - - if can?(current_user, :read_build, build) - = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) - - else - = ci_status_with_icon(build.status) - - %td - .branch-commit - - if can?(current_user, :read_build, build) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span ##{build.id} - - else - %span ##{build.id} - - - if defined?(ref) && ref - - if build.ref - .icon-container - = build.tag? ? icon('tag') : icon('code-fork') - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - - else - .light none - .icon-container - = custom_icon("icon_commit") - - - if defined?(commit_sha) && commit_sha - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" - - - if build.stuck? - = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if defined?(retried) && retried - = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') - - .label-container - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %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 - - if build.manual? - %span.label.label-info manual - - - if defined?(runner) && runner - %td - - if build.try(:runner) - = runner_link(build.runner) - - else - .light none - - %td - - if defined?(stage) && stage - = build.stage - - %td - = build.name - - %td - - if build.duration - %p.duration - = custom_icon("icon_timer") - = duration_in_numbers(build.duration) - - if build.finished_at - %p.finished-at - = icon("calendar") - %span #{time_ago_with_tooltip(build.finished_at)} - - - if defined?(coverage) && coverage - %td.coverage - - if build.try(:coverage) - #{build.coverage}% - - %td - .pull-right - - if can?(current_user, :read_build, build) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do - = icon('download') - - if can?(current_user, :update_build, build) - - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do - = icon('remove', class: 'cred') - - elsif defined?(allow_retry) && allow_retry - - if build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - = icon('repeat') - - elsif build.playable? - = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do - = custom_icon('icon_play') diff --git a/app/views/shared/builds/_build.html.haml b/app/views/shared/builds/_build.html.haml new file mode 100644 index 00000000000..a5f38d0ce95 --- /dev/null +++ b/app/views/shared/builds/_build.html.haml @@ -0,0 +1,101 @@ +- admin = false unless admin +- if admin + - project = build.project + +%tr.build.commit + %td.status + - if can?(current_user, :read_build, build) + = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) + - else + = ci_status_with_icon(build.status) + + %td + .branch-commit + - if can?(current_user, :read_build, build) + = link_to namespace_project_build_url(build.project.namespace, build.project, build) do + %span.build-link ##{build.id} + - else + %span.build-link ##{build.id} + + - if defined?(ref) && ref + - if build.ref + .icon-container + = build.tag? ? icon('tag') : icon('code-fork') + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" + - else + .light none + .icon-container + = custom_icon("icon_commit") + + - if defined?(commit_sha) && commit_sha + = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" + + - if build.stuck? + = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') + - if defined?(retried) && retried + = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') + + .label-container + - if build.tags.any? + - build.tags.each do |tag| + %span.label.label-primary + = tag + - if build.try(:trigger_request) + %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 + - if build.manual? + %span.label.label-info manual + + - if admin + %td + - if project + = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) + + - if defined?(runner) && runner + %td + - if build.try(:runner) + = runner_link(build.runner) + - else + .light none + + %td + = build.stage + + %td + = build.name + + %td + - if build.duration + %p.duration + = custom_icon("icon_timer") + = duration_in_numbers(build.duration) + + - if build.finished_at + %p.finished-at + = icon("calendar") + %span #{time_ago_with_tooltip(build.finished_at)} + + %td.coverage + - if defined?(coverage) && coverage + - if build.try(:coverage) + #{build.coverage}% + + %td + .pull-right + - if can?(current_user, :read_build, build) && build.artifacts? + = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do + = icon('download') + - if can?(current_user, :update_build, build) + - if build.active? + = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do + = icon('remove', class: 'cred') + - elsif defined?(allow_retry) && allow_retry + - if build.retryable? + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + = icon('repeat') + - elsif build.playable? && !admin + = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do + = custom_icon('icon_play') diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml new file mode 100644 index 00000000000..9c3173f7a1a --- /dev/null +++ b/app/views/shared/builds/_tabs.html.haml @@ -0,0 +1,24 @@ +%ul.nav-links + %li{class: ('active' if scope.nil?)} + = link_to build_path[nil] do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(all_builds.count(:id)) + + %li{class: ('active' if scope == 'pending')} + = link_to build_path[:pending] do + Pending + %span.badge + = number_with_delimiter(all_builds.pending.count(:id)) + + %li{class: ('active' if scope == 'running')} + = link_to build_path[:running] do + Running + %span.badge + = number_with_delimiter(all_builds.running.count(:id)) + + %li{class: ('active' if scope == 'finished')} + = link_to build_path[:finished] do + Finished + %span.badge + = number_with_delimiter(all_builds.finished.count(:id)) -- cgit v1.2.3 From f341d95703f4268e176f02aa9d923e7f2069d5c4 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 8 Sep 2016 10:06:29 +0200 Subject: Build path with call --- app/views/projects/builds/_table.html.haml | 4 +- app/views/projects/ci/builds/_build.html.haml | 101 ++++++++++++++++++++++++++ app/views/shared/_builds_tabs.html.haml | 24 ------ app/views/shared/builds/_build.html.haml | 101 -------------------------- app/views/shared/builds/_tabs.html.haml | 8 +- 5 files changed, 107 insertions(+), 131 deletions(-) create mode 100644 app/views/projects/ci/builds/_build.html.haml delete mode 100644 app/views/shared/_builds_tabs.html.haml delete mode 100644 app/views/shared/builds/_build.html.haml diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index a4cd9596d68..da43dbb8d81 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -21,8 +21,8 @@ %th - if admin - = render partial: "shared/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, runner: true, admin: true } + = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, runner: true, admin: true } - else - = render partial: "shared/builds/build", collection: builds, as: :build, locals: {commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: project.build_coverage_enabled? } + = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: {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 new file mode 100644 index 00000000000..a5f38d0ce95 --- /dev/null +++ b/app/views/projects/ci/builds/_build.html.haml @@ -0,0 +1,101 @@ +- admin = false unless admin +- if admin + - project = build.project + +%tr.build.commit + %td.status + - if can?(current_user, :read_build, build) + = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) + - else + = ci_status_with_icon(build.status) + + %td + .branch-commit + - if can?(current_user, :read_build, build) + = link_to namespace_project_build_url(build.project.namespace, build.project, build) do + %span.build-link ##{build.id} + - else + %span.build-link ##{build.id} + + - if defined?(ref) && ref + - if build.ref + .icon-container + = build.tag? ? icon('tag') : icon('code-fork') + = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" + - else + .light none + .icon-container + = custom_icon("icon_commit") + + - if defined?(commit_sha) && commit_sha + = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" + + - if build.stuck? + = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') + - if defined?(retried) && retried + = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') + + .label-container + - if build.tags.any? + - build.tags.each do |tag| + %span.label.label-primary + = tag + - if build.try(:trigger_request) + %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 + - if build.manual? + %span.label.label-info manual + + - if admin + %td + - if project + = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) + + - if defined?(runner) && runner + %td + - if build.try(:runner) + = runner_link(build.runner) + - else + .light none + + %td + = build.stage + + %td + = build.name + + %td + - if build.duration + %p.duration + = custom_icon("icon_timer") + = duration_in_numbers(build.duration) + + - if build.finished_at + %p.finished-at + = icon("calendar") + %span #{time_ago_with_tooltip(build.finished_at)} + + %td.coverage + - if defined?(coverage) && coverage + - if build.try(:coverage) + #{build.coverage}% + + %td + .pull-right + - if can?(current_user, :read_build, build) && build.artifacts? + = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do + = icon('download') + - if can?(current_user, :update_build, build) + - if build.active? + = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do + = icon('remove', class: 'cred') + - elsif defined?(allow_retry) && allow_retry + - if build.retryable? + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + = icon('repeat') + - elsif build.playable? && !admin + = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do + = custom_icon('icon_play') diff --git a/app/views/shared/_builds_tabs.html.haml b/app/views/shared/_builds_tabs.html.haml deleted file mode 100644 index 9c3173f7a1a..00000000000 --- a/app/views/shared/_builds_tabs.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -%ul.nav-links - %li{class: ('active' if scope.nil?)} - = link_to build_path[nil] do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(all_builds.count(:id)) - - %li{class: ('active' if scope == 'pending')} - = link_to build_path[:pending] do - Pending - %span.badge - = number_with_delimiter(all_builds.pending.count(:id)) - - %li{class: ('active' if scope == 'running')} - = link_to build_path[:running] do - Running - %span.badge - = number_with_delimiter(all_builds.running.count(:id)) - - %li{class: ('active' if scope == 'finished')} - = link_to build_path[:finished] do - Finished - %span.badge - = number_with_delimiter(all_builds.finished.count(:id)) diff --git a/app/views/shared/builds/_build.html.haml b/app/views/shared/builds/_build.html.haml deleted file mode 100644 index a5f38d0ce95..00000000000 --- a/app/views/shared/builds/_build.html.haml +++ /dev/null @@ -1,101 +0,0 @@ -- admin = false unless admin -- if admin - - project = build.project - -%tr.build.commit - %td.status - - if can?(current_user, :read_build, build) - = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) - - else - = ci_status_with_icon(build.status) - - %td - .branch-commit - - if can?(current_user, :read_build, build) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span.build-link ##{build.id} - - else - %span.build-link ##{build.id} - - - if defined?(ref) && ref - - if build.ref - .icon-container - = build.tag? ? icon('tag') : icon('code-fork') - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - - else - .light none - .icon-container - = custom_icon("icon_commit") - - - if defined?(commit_sha) && commit_sha - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" - - - if build.stuck? - = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if defined?(retried) && retried - = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') - - .label-container - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %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 - - if build.manual? - %span.label.label-info manual - - - if admin - %td - - if project - = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) - - - if defined?(runner) && runner - %td - - if build.try(:runner) - = runner_link(build.runner) - - else - .light none - - %td - = build.stage - - %td - = build.name - - %td - - if build.duration - %p.duration - = custom_icon("icon_timer") - = duration_in_numbers(build.duration) - - - if build.finished_at - %p.finished-at - = icon("calendar") - %span #{time_ago_with_tooltip(build.finished_at)} - - %td.coverage - - if defined?(coverage) && coverage - - if build.try(:coverage) - #{build.coverage}% - - %td - .pull-right - - if can?(current_user, :read_build, build) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do - = icon('download') - - if can?(current_user, :update_build, build) - - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do - = icon('remove', class: 'cred') - - elsif defined?(allow_retry) && allow_retry - - if build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - = icon('repeat') - - elsif build.playable? && !admin - = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do - = custom_icon('icon_play') diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml index 9c3173f7a1a..398ccf480d9 100644 --- a/app/views/shared/builds/_tabs.html.haml +++ b/app/views/shared/builds/_tabs.html.haml @@ -1,24 +1,24 @@ %ul.nav-links %li{class: ('active' if scope.nil?)} - = link_to build_path[nil] do + = link_to build_path.call(nil) do All %span.badge.js-totalbuilds-count = number_with_delimiter(all_builds.count(:id)) %li{class: ('active' if scope == 'pending')} - = link_to build_path[:pending] do + = link_to build_path.call('pending') do Pending %span.badge = number_with_delimiter(all_builds.pending.count(:id)) %li{class: ('active' if scope == 'running')} - = link_to build_path[:running] do + = link_to build_path.call('running') do Running %span.badge = number_with_delimiter(all_builds.running.count(:id)) %li{class: ('active' if scope == 'finished')} - = link_to build_path[:finished] do + = link_to build_path.call('finished') do Finished %span.badge = number_with_delimiter(all_builds.finished.count(:id)) -- cgit v1.2.3 From b902a9632435ec7e4d4def609c7f0eaaf9d98535 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 8 Sep 2016 10:26:16 +0200 Subject: Fix error in paths --- app/views/projects/ci/builds/_build.html.haml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index a5f38d0ce95..ecf15c77ad1 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,6 +1,4 @@ - admin = false unless admin -- if admin - - project = build.project %tr.build.commit %td.status @@ -51,8 +49,8 @@ - if admin %td - - if project - = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) + - if build.project + = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) - if defined?(runner) && runner %td @@ -61,8 +59,9 @@ - else .light none - %td - = build.stage + - if defined?(stage) && stage + %td + = build.stage %td = build.name -- cgit v1.2.3 From d39e314cf5896ac06219446dc1ab8707f222d0d8 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 12 Sep 2016 09:52:56 +0200 Subject: Improve build_path name and admin variable --- app/views/admin/builds/index.html.haml | 4 ++-- app/views/projects/builds/_table.html.haml | 9 ++++----- app/views/projects/builds/index.html.haml | 4 ++-- app/views/projects/ci/builds/_build.html.haml | 2 +- app/views/shared/builds/_tabs.html.haml | 8 ++++---- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 7d84172bd2e..e44e58880fa 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -4,8 +4,8 @@ %div{ class: container_class } .top-area - - build_path = ->(scope) { admin_builds_path(scope: scope) } - = render "shared/builds/tabs", build_path: build_path, all_builds: @all_builds, scope: @scope + - build_path_proc = ->(scope) { admin_builds_path(scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope .nav-controls - if @all_builds.running_or_pending.any? diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index da43dbb8d81..d77405b2b1f 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -1,4 +1,5 @@ -- admin = false unless admin +- admin = false unless defined?(admin) + - if builds.blank? %li .nothing-here-block No builds to show @@ -15,13 +16,11 @@ %th Stage %th Name %th - %th - - if !admin && project.build_coverage_enabled? - Coverage + %th Coverage %th - if admin - = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, runner: true, admin: true } + = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, runner: true, coverage: true, admin: true } - else = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: {commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: project.build_coverage_enabled? } diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index d2fc2ae34fe..5c60b7a7364 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -4,8 +4,8 @@ %div{ class: container_class } .top-area - - build_path = ->(scope) { project_builds_path(@project, scope: scope) } - = render "shared/builds/tabs", build_path: build_path, all_builds: @all_builds, scope: @scope + - build_path_proc = ->(scope) { project_builds_path(@project, scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope .nav-controls - if can?(current_user, :update_build, @project) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index ecf15c77ad1..37e507ea00a 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,4 +1,4 @@ -- admin = false unless admin +- admin = false unless defined?(admin) %tr.build.commit %td.status diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml index 398ccf480d9..49ab28bd47b 100644 --- a/app/views/shared/builds/_tabs.html.haml +++ b/app/views/shared/builds/_tabs.html.haml @@ -1,24 +1,24 @@ %ul.nav-links %li{class: ('active' if scope.nil?)} - = link_to build_path.call(nil) do + = link_to build_path_proc.call(nil) do All %span.badge.js-totalbuilds-count = number_with_delimiter(all_builds.count(:id)) %li{class: ('active' if scope == 'pending')} - = link_to build_path.call('pending') do + = link_to build_path_proc.call('pending') do Pending %span.badge = number_with_delimiter(all_builds.pending.count(:id)) %li{class: ('active' if scope == 'running')} - = link_to build_path.call('running') do + = link_to build_path_proc.call('running') do Running %span.badge = number_with_delimiter(all_builds.running.count(:id)) %li{class: ('active' if scope == 'finished')} - = link_to build_path.call('finished') do + = link_to build_path_proc.call('finished') do Finished %span.badge = number_with_delimiter(all_builds.finished.count(:id)) -- cgit v1.2.3 From 93742a439ab4492ef7a25cb9ddfd6ae004a1fc81 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 12 Sep 2016 13:58:38 +0200 Subject: Code refactoring --- app/views/projects/builds/_table.html.haml | 7 ++----- app/views/projects/ci/builds/_build.html.haml | 10 ++++++++-- app/views/shared/builds/_tabs.html.haml | 8 ++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml index d77405b2b1f..61eff73da26 100644 --- a/app/views/projects/builds/_table.html.haml +++ b/app/views/projects/builds/_table.html.haml @@ -1,4 +1,4 @@ -- admin = false unless defined?(admin) +- admin = local_assigns.fetch(:admin, false) - if builds.blank? %li @@ -19,9 +19,6 @@ %th Coverage %th - - if admin - = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, runner: true, coverage: true, admin: true } - - else - = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: {commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: project.build_coverage_enabled? } + = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: admin || project.build_coverage_enabled?, admin: admin } = 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 37e507ea00a..fd3d94bee9b 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,4 +1,10 @@ -- admin = false unless defined?(admin) +- admin = local_assigns.fetch(:admin, false) +- ref = local_assigns.fetch(:ref, nil) +- commit_sha = local_assigns.fetch(:commit_sha, nil) +- retried = local_assigns.fetch(:retried, false) +- stage = local_assigns.fetch(:stage, false) +- coverage = local_assigns.fetch(:coverage, false) +- allow_retry = local_assigns.fetch(:allow_retry, false) %tr.build.commit %td.status @@ -52,7 +58,7 @@ - if build.project = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) - - if defined?(runner) && runner + - if admin %td - if build.try(:runner) = runner_link(build.runner) diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml index 49ab28bd47b..60353aee7f1 100644 --- a/app/views/shared/builds/_tabs.html.haml +++ b/app/views/shared/builds/_tabs.html.haml @@ -1,23 +1,23 @@ %ul.nav-links - %li{class: ('active' if scope.nil?)} + %li{ class: ('active' if scope.nil?) } = link_to build_path_proc.call(nil) do All %span.badge.js-totalbuilds-count = number_with_delimiter(all_builds.count(:id)) - %li{class: ('active' if scope == 'pending')} + %li{ class: ('active' if scope == 'pending') } = link_to build_path_proc.call('pending') do Pending %span.badge = number_with_delimiter(all_builds.pending.count(:id)) - %li{class: ('active' if scope == 'running')} + %li{ class: ('active' if scope == 'running') } = link_to build_path_proc.call('running') do Running %span.badge = number_with_delimiter(all_builds.running.count(:id)) - %li{class: ('active' if scope == 'finished')} + %li{ class: ('active' if scope == 'finished') } = link_to build_path_proc.call('finished') do Finished %span.badge -- cgit v1.2.3 From 41d5708078cf45f2f2026e27c5cae4f1a096420a Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 12 Sep 2016 18:16:40 +0200 Subject: Remove unnecessary variables --- app/views/admin/builds/index.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index e44e58880fa..26a8846b609 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -15,4 +15,4 @@ #{(@scope || 'all').capitalize} builds %ul.content-list.builds-content-list - = render "projects/builds/table", builds: @builds, project: nil, admin: true + = render "projects/builds/table", builds: @builds, admin: true diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index fd3d94bee9b..75192c48188 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -21,7 +21,7 @@ - else %span.build-link ##{build.id} - - if defined?(ref) && ref + - if ref - if build.ref .icon-container = build.tag? ? icon('tag') : icon('code-fork') @@ -31,12 +31,12 @@ .icon-container = custom_icon("icon_commit") - - if defined?(commit_sha) && commit_sha + - if commit_sha = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" - if build.stuck? = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if defined?(retried) && retried + - if retried = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') .label-container @@ -48,7 +48,7 @@ %span.label.label-info triggered - if build.try(:allow_failure) %span.label.label-danger allowed to fail - - if defined?(retried) && retried + - if retried %span.label.label-warning retried - if build.manual? %span.label.label-info manual @@ -65,7 +65,7 @@ - else .light none - - if defined?(stage) && stage + - if stage %td = build.stage @@ -84,7 +84,7 @@ %span #{time_ago_with_tooltip(build.finished_at)} %td.coverage - - if defined?(coverage) && coverage + - if coverage - if build.try(:coverage) #{build.coverage}% @@ -97,7 +97,7 @@ - if build.active? = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - - elsif defined?(allow_retry) && allow_retry + - elsif allow_retry - if build.retryable? = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do = icon('repeat') -- cgit v1.2.3 From ab818b248e1d502d064ad071bc475ce8854c43c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 12 Sep 2016 15:21:58 +0200 Subject: Use haml_lint for views linting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 1 + CHANGELOG | 1 + Gemfile | 1 + Gemfile.lock | 9 +++++++++ 4 files changed, 12 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f51d506f64a..1b777f5016f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -206,6 +206,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21 - bundle exec $CI_BUILD_NAME rubocop: *exec +rake haml-lint: *exec rake scss_lint: *exec rake brakeman: *exec rake flog: *exec diff --git a/CHANGELOG b/CHANGELOG index d69168985cd..2a39ac99d49 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.12.0 (unreleased) - Fix pagination on user snippets page - Fix sorting of issues in API - Ensure specs on sorting of issues in API are deterministic on MySQL + - Use haml_lint for views linting - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 - Fix file permissions change when updating a file on the Gitlab UI !5979 diff --git a/Gemfile b/Gemfile index 81b7002027a..df546849fad 100644 --- a/Gemfile +++ b/Gemfile @@ -298,6 +298,7 @@ group :development, :test do gem 'rubocop', '~> 0.41.2', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'scss_lint', '~> 0.47.0', require: false + gem 'haml_lint', '~> 0.18.2', require: false gem 'simplecov', '0.12.0', require: false gem 'flog', '~> 4.3.2', require: false gem 'flay', '~> 2.6.1', require: false diff --git a/Gemfile.lock b/Gemfile.lock index c421713f6a1..b6307f72fa8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -322,6 +322,13 @@ GEM grape-entity (0.4.8) activesupport multi_json (>= 1.3.2) + haml (4.0.7) + tilt + haml_lint (0.18.2) + haml (~> 4.0) + rake (>= 10, < 12) + rubocop (>= 0.36.0) + sysexits (~> 1.1) hamlit (2.6.1) temple (~> 0.7.6) thor @@ -723,6 +730,7 @@ GEM stringex (2.5.2) sys-filesystem (1.1.6) ffi + sysexits (1.2.0) systemu (2.6.5) task_list (1.0.2) html-pipeline @@ -866,6 +874,7 @@ DEPENDENCIES gon (~> 6.1.0) grape (~> 0.15.0) grape-entity (~> 0.4.2) + haml_lint (~> 0.18.2) hamlit (~> 2.6.1) health_check (~> 2.1.0) hipchat (~> 1.5.0) -- cgit v1.2.3 From 427fc10dd99cd1784f1cf2c840ddff8562884b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 12 Sep 2016 19:23:06 +0200 Subject: Add .haml-lint.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .haml-lint.yml | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 .haml-lint.yml diff --git a/.haml-lint.yml b/.haml-lint.yml new file mode 100644 index 00000000000..6e3b35607b1 --- /dev/null +++ b/.haml-lint.yml @@ -0,0 +1,107 @@ +# Default application configuration that all configurations inherit from. +# +# This is an opinionated list of which hooks are valuable to run and what their +# out of the box settings should be. + +# Whether to ignore frontmatter at the beginning of HAML documents for +# frameworks such as Jekyll/Middleman +skip_frontmatter: false +exclude: + - 'vendor/**/*' + +linters: + AltText: + enabled: false + + ClassAttributeWithStaticValue: + enabled: false + + ClassesBeforeIds: + enabled: false + + ConsecutiveComments: + enabled: false + + ConsecutiveSilentScripts: + enabled: false + max_consecutive: 2 + + EmptyObjectReference: + enabled: true + + EmptyScript: + enabled: true + + FinalNewline: + enabled: false + present: true + + HtmlAttributes: + enabled: false + + ImplicitDiv: + enabled: false + + LeadingCommentSpace: + enabled: false + + LineLength: + enabled: false + max: 80 + + MultilinePipe: + enabled: false + + MultilineScript: + enabled: true + + ObjectReferenceAttributes: + enabled: true + + RuboCop: + enabled: false + # These cops are incredibly noisy when it comes to HAML templates, so we + # ignore them. + ignored_cops: + - Lint/BlockAlignment + - Lint/EndAlignment + - Lint/Void + - Metrics/LineLength + - Style/AlignParameters + - Style/BlockNesting + - Style/ElseAlignment + - Style/FileName + - Style/FinalNewline + - Style/FrozenStringLiteralComment + - Style/IfUnlessModifier + - Style/IndentationWidth + - Style/Next + - Style/TrailingBlankLines + - Style/TrailingWhitespace + - Style/WhileUntilModifier + + RubyComments: + enabled: false + + SpaceBeforeScript: + enabled: false + + SpaceInsideHashAttributes: + enabled: false + style: space + + Indentation: + enabled: true + character: space # or tab + + TagName: + enabled: true + + TrailingWhitespace: + enabled: false + + UnnecessaryInterpolation: + enabled: false + + UnnecessaryStringOutput: + enabled: false -- cgit v1.2.3 From 9e34c57d5e698b7ef88c51051f17ea943d6b0bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 12 Sep 2016 19:36:24 +0200 Subject: Add haml_lint rake task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 2 +- lib/tasks/haml-lint.rake | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 lib/tasks/haml-lint.rake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1b777f5016f..b5eef681d0e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -206,7 +206,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21 - bundle exec $CI_BUILD_NAME rubocop: *exec -rake haml-lint: *exec +rake haml_lint: *exec rake scss_lint: *exec rake brakeman: *exec rake flog: *exec diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake new file mode 100644 index 00000000000..80f70820853 --- /dev/null +++ b/lib/tasks/haml-lint.rake @@ -0,0 +1,8 @@ +unless Rails.env.production? + require 'haml_lint/rake_task' + + HamlLint::RakeTask.new do |t| + t.config = '.haml-lint.yml' + t.files = ['app/views'] + end +end -- cgit v1.2.3 From ddcb8c880cbeb27a10e36fe1acb1e46963c3a377 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 13 Sep 2016 15:53:10 +0800 Subject: Fix styling --- spec/models/commit_status_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index fcfa3138ce5..6355f636bb9 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -40,7 +40,7 @@ describe CommitStatus, models: true do it { is_expected.to be_falsey } end - %w(running success failed).each do |status| + %w[running success failed].each do |status| context "if commit status is #{status}" do before { commit_status.status = status } @@ -48,7 +48,7 @@ describe CommitStatus, models: true do end end - %w(pending canceled).each do |status| + %w[pending canceled].each do |status| context "if commit status is #{status}" do before { commit_status.status = status } @@ -60,7 +60,7 @@ describe CommitStatus, models: true do describe '#active?' do subject { commit_status.active? } - %w(pending running).each do |state| + %w[pending running].each do |state| context "if commit_status.status is #{state}" do before { commit_status.status = state } @@ -68,7 +68,7 @@ describe CommitStatus, models: true do end end - %w(success failed canceled).each do |state| + %w[success failed canceled].each do |state| context "if commit_status.status is #{state}" do before { commit_status.status = state } @@ -80,7 +80,7 @@ describe CommitStatus, models: true do describe '#complete?' do subject { commit_status.complete? } - %w(success failed canceled).each do |state| + %w[success failed canceled].each do |state| context "if commit_status.status is #{state}" do before { commit_status.status = state } @@ -88,7 +88,7 @@ describe CommitStatus, models: true do end end - %w(pending running).each do |state| + %w[pending running].each do |state| context "if commit_status.status is #{state}" do before { commit_status.status = state } @@ -187,7 +187,7 @@ describe CommitStatus, models: true do subject { CommitStatus.where(pipeline: pipeline).stages } it 'returns ordered list of stages' do - is_expected.to eq(%w(build test deploy)) + is_expected.to eq(%w[build test deploy]) end end -- cgit v1.2.3 From 18d7ae43099040d21dddbb114761c34c833ec766 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 13 Sep 2016 16:14:20 +0800 Subject: Add a test for #22010 The observed faulty state transition is probably hard to test, because we need to hook into internal states to observe them. Namely this: 07:30:16 | Build#ruby-2.2 enqueue: created -> pending 07:30:16 | Pipeline#32 enqueue: created -> pending 07:30:16 | Build#ruby-2.3 enqueue: created -> pending 07:30:16 | Build#ruby-2.2 run: pending -> running 07:30:16 | Pipeline#32 run: pending -> running 07:30:29 | Build#ruby-2.2 drop: running -> failed 07:30:29 | Pipeline#32 run: running -> running 07:30:29 | Build#ruby-2.3 run: pending -> running 07:30:30 | Pipeline#32 run: running -> running 07:30:57 | Build#gem:build skip: created -> skipped 07:30:57 | Pipeline#32 drop: running -> failed 07:30:57 | Build#gem:release skip: created -> skipped 07:30:57 | Pipeline#32 drop: failed -> failed 07:30:57 | Build#ruby-2.3 drop: running -> failed 07:30:57 | Pipeline#32 drop: running -> failed ^^^ Should be failed -> failed However, the consequence of this, executing hooks twice would be easy enough to observe. So we could at least test against this. Keep in mind that if we ever changed how we execute the hooks this won't be testing against faulty state transition. --- spec/models/ci/pipeline_spec.rb | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index fbf945c757c..5d9063c0bc5 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -373,8 +373,8 @@ describe Ci::Pipeline, models: true do end describe '#execute_hooks' do - let!(:build_a) { create_build('a') } - let!(:build_b) { create_build('b') } + let!(:build_a) { create_build('a', 0) } + let!(:build_b) { create_build('b', 1) } let!(:hook) do create(:project_hook, project: project, pipeline_events: enabled) @@ -427,6 +427,16 @@ describe Ci::Pipeline, models: true do end end + context 'when stage one failed' do + before do + build_a.drop + end + + it 'receive a failed event once' do + expect(WebMock).to have_requested_pipeline_hook('failed').once + end + end + def have_requested_pipeline_hook(status) have_requested(:post, hook.url).with do |req| json_body = JSON.parse(req.body) @@ -450,8 +460,12 @@ describe Ci::Pipeline, models: true do end end - def create_build(name) - create(:ci_build, :created, pipeline: pipeline, name: name) + def create_build(name, stage_idx) + create(:ci_build, + :created, + pipeline: pipeline, + name: name, + stage_idx: stage_idx) end end end -- cgit v1.2.3 From 05faeec708824bf97c3114b23235859663631b27 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 13 Sep 2016 16:22:02 +0800 Subject: Fix English --- spec/models/ci/pipeline_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 5d9063c0bc5..f1857f846dc 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -398,7 +398,7 @@ describe Ci::Pipeline, models: true do build_b.enqueue end - it 'receive a pending event once' do + it 'receives a pending event once' do expect(WebMock).to have_requested_pipeline_hook('pending').once end end @@ -411,7 +411,7 @@ describe Ci::Pipeline, models: true do build_b.run end - it 'receive a running event once' do + it 'receives a running event once' do expect(WebMock).to have_requested_pipeline_hook('running').once end end @@ -422,7 +422,7 @@ describe Ci::Pipeline, models: true do build_b.success end - it 'receive a success event once' do + it 'receives a success event once' do expect(WebMock).to have_requested_pipeline_hook('success').once end end @@ -432,7 +432,7 @@ describe Ci::Pipeline, models: true do build_a.drop end - it 'receive a failed event once' do + it 'receives a failed event once' do expect(WebMock).to have_requested_pipeline_hook('failed').once end end -- cgit v1.2.3 From 08871cc36ac5ffea541ea5b5e7666327e65c6b9d Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Tue, 13 Sep 2016 11:43:41 +0200 Subject: Avoid protected branches checks when verifying access without branch name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitlabShell verify access sending ‘_any’ as the changes made on the git command, in those cases Gitlab::Checks::ChangeAccess won’t receive a branch_name so we don’t need to check for access to the protected branches on that repository. So we avoid some git operations in case the are not cached (empty_repo?) and some database lookups to get protected branches. These request is happening in every push. --- CHANGELOG | 1 + lib/gitlab/checks/change_access.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index dc8dbb6e166..8ccb586f6e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -115,6 +115,7 @@ v 8.12.0 (unreleased) - Avoid conflict with admin labels when importing GitHub labels - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - Fix repository page ui issues + - Avoid protected branches checks when verifying access without branch name - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov) - Fixed invisible scroll controls on build page on iPhone - Fix error on raw build trace download for old builds stored in database !4822 diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 4b32eb966aa..cb1065223d4 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -23,6 +23,7 @@ module Gitlab protected def protected_branch_checks + return unless @branch_name return unless project.protected_branch?(@branch_name) if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches) -- cgit v1.2.3 From 41c08c93bca20c4e25797d61d697fdac71881600 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Tue, 13 Sep 2016 11:58:08 +0200 Subject: Gitlab::Checks is now instrumented So we have a detailed view of what checks perform bad --- CHANGELOG | 1 + config/initializers/metrics.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index dc8dbb6e166..ffd4932250d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -97,6 +97,7 @@ v 8.12.0 (unreleased) - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) - Update merge_requests.md with a simpler way to check out a merge request. !5944 - Fix button missing type (ClemMakesApps) + - Gitlab::Checks is now instrumented - Move to project dropdown with infinite scroll for better performance - Fix leaking of submit buttons outside the width of a main container !18731 (originally by @pavelloz) - Load branches asynchronously in Cherry Pick and Revert dialogs. diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index 52522e099e7..be22085b0df 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -68,7 +68,8 @@ if Gitlab::Metrics.enabled? ['app', 'mailers', 'emails'] => ['app', 'mailers'], ['app', 'services', '**'] => ['app', 'services'], ['lib', 'gitlab', 'diff'] => ['lib'], - ['lib', 'gitlab', 'email', 'message'] => ['lib'] + ['lib', 'gitlab', 'email', 'message'] => ['lib'], + ['lib', 'gitlab', 'checks'] => ['lib'] } paths_to_instrument.each do |(path, prefix)| -- cgit v1.2.3 From 3e855b247f7a1df63130d6ac9a005a8cf6d5c135 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 13 Sep 2016 11:27:16 +0100 Subject: Fixed issue boards loading issues on large screens Closes #22092 --- CHANGELOG | 1 + app/assets/javascripts/boards/components/board_list.js.es6 | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index dc8dbb6e166..7f6d6dba1c0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -58,6 +58,7 @@ v 8.12.0 (unreleased) - Use 'git update-ref' for safer web commits !6130 - Sort pipelines requested through the API - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) + - Fix issue boards loading on large screens - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 - Show queued time when showing a pipeline !6084 - Remove unused mixins (ClemMakesApps) diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 50fc11d7737..474805c1437 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -34,6 +34,11 @@ }, issues () { this.$nextTick(() => { + if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) { + this.list.page++; + this.list.getIssues(false); + } + if (this.scrollHeight() > this.listHeight()) { this.showCount = true; } else { -- cgit v1.2.3 From 2eb2cbe6ae4aa8c66966f47f84551c76d114af75 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 13 Sep 2016 10:26:04 +0100 Subject: Fix line diff side-by-side line highlighting --- app/assets/javascripts/merge_request_tabs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index dcba4a8d275..18bbfa7a459 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -232,10 +232,10 @@ $('.hll').removeClass('hll'); locationHash = window.location.hash; if (locationHash !== '') { - hashClassString = "." + (locationHash.replace('#', '')); + dataLineString = '[data-line-code="' + locationHash.replace('#', '') + '"]'; $diffLine = $(locationHash + ":not(.match)", $('#diffs')); if (!$diffLine.is('tr')) { - $diffLine = $('#diffs').find("td" + locationHash + ", td" + hashClassString); + $diffLine = $('#diffs').find("td" + locationHash + ", td" + dataLineString); } else { $diffLine = $diffLine.find('td'); } -- cgit v1.2.3 From d8b7bb981294995166dd27e8fbd5a8dd1e8a8858 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 13 Sep 2016 13:09:04 +0200 Subject: Update omniauth-facebook to '~>4.0.0' --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 81b7002027a..1bfac82ffc6 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,7 @@ gem 'omniauth-auth0', '~> 1.4.1' gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-cas3', '~> 1.1.2' -gem 'omniauth-facebook', '~> 3.0.0' +gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-google-oauth2', '~> 0.4.1' diff --git a/Gemfile.lock b/Gemfile.lock index c421713f6a1..5b1e1395e41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -326,7 +326,7 @@ GEM temple (~> 0.7.6) thor tilt - hashie (3.4.3) + hashie (3.4.4) health_check (2.1.0) rails (>= 4.0) hipchat (1.5.2) @@ -437,7 +437,7 @@ GEM addressable (~> 2.3) nokogiri (~> 1.6.6) omniauth (~> 1.2) - omniauth-facebook (3.0.0) + omniauth-facebook (4.0.0) omniauth-oauth2 (~> 1.2) omniauth-github (1.1.2) omniauth (~> 1.0) @@ -900,7 +900,7 @@ DEPENDENCIES omniauth-azure-oauth2 (~> 0.0.6) omniauth-bitbucket (~> 0.0.2) omniauth-cas3 (~> 1.1.2) - omniauth-facebook (~> 3.0.0) + omniauth-facebook (~> 4.0.0) omniauth-github (~> 1.1.1) omniauth-gitlab (~> 1.0.0) omniauth-google-oauth2 (~> 0.4.1) -- cgit v1.2.3 From 505dc808b3c0dc98413506446d368b91b56ff682 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 8 Aug 2016 12:01:25 +0200 Subject: Use a permissions of user to access all dependent projects from CI jobs (this also includes a container images, and in future LFS files) --- app/controllers/jwt_controller.rb | 18 +++++----- .../projects/git_http_client_controller.rb | 12 ++++++- app/controllers/projects/git_http_controller.rb | 2 +- app/helpers/lfs_helper.rb | 16 +++++++-- app/models/ci/build.rb | 13 +++---- app/models/project.rb | 6 ---- app/policies/project_policy.rb | 15 +++++--- .../container_registry_authentication_service.rb | 40 ++++++++++++++++++++-- db/migrate/20160808085531_add_token_to_build.rb | 10 ++++++ .../20160808085602_add_index_for_build_token.rb | 12 +++++++ lib/ci/api/helpers.rb | 14 ++++++-- lib/gitlab/auth.rb | 31 ++++++++++++----- lib/gitlab/git_access.rb | 19 ++++++++-- 13 files changed, 163 insertions(+), 45 deletions(-) create mode 100644 db/migrate/20160808085531_add_token_to_build.rb create mode 100644 db/migrate/20160808085602_add_index_for_build_token.rb diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 66ebdcc37a7..ca02df28b91 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -11,7 +11,7 @@ class JwtController < ApplicationController service = SERVICES[params[:service]] return head :not_found unless service - result = service.new(@project, @user, auth_params).execute + result = service.new(@project, @user, auth_params).execute(access_type: @access_type) render json: result, status: result[:http_status] end @@ -21,10 +21,10 @@ class JwtController < ApplicationController def authenticate_project_or_user authenticate_with_http_basic do |login, password| # if it's possible we first try to authenticate project with login and password - @project = authenticate_project(login, password) + @project, @user, @access_type = authenticate_build(login, password) return if @project - @user = authenticate_user(login, password) + @user, @access_type = authenticate_user(login, password) return if @user render_403 @@ -35,15 +35,17 @@ class JwtController < ApplicationController params.permit(:service, :scope, :account, :client_id) end - def authenticate_project(login, password) - if login == 'gitlab-ci-token' - Project.with_builds_enabled.find_by(runners_token: password) - end + def authenticate_build(login, password) + return unless login == 'gitlab-ci-token' + return unless password + + build = Ci::Build.running.find_by(token: password) + return build.project, build.user, :restricted if build end def authenticate_user(login, password) user = Gitlab::Auth.find_with_user_password(login, password) Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login) - user + return user, :full end end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index f5ce63fdfed..0f72dc8437c 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :user + attr_reader :user, :access_type # Git clients will not know what authenticity token to send along skip_before_action :verify_authenticity_token @@ -34,6 +34,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController @user = auth_result.user end + @access_type = auth_result.access_type + if ci? || user return # Allow access end @@ -118,6 +120,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController @ci.present? end + def full? + @access_type == :full + end + + def restricted? + @access_type == :restricted + end + def verify_workhorse_api! Gitlab::Workhorse.verify_api_request!(request.headers) end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 9805705c4e3..d59a47417f4 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def access - @access ||= Gitlab::GitAccess.new(user, project, 'http') + @access ||= Gitlab::GitAccess.new(user, project, 'http', access_type: access_type) end def access_check diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 5d82abfca79..625dfddcf8d 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -25,13 +25,25 @@ module LfsHelper def lfs_download_access? return false unless project.lfs_enabled? - project.public? || ci? || (user && user.can?(:download_code, project)) + project.public? || ci? || privileged_user_can_download_code? || restricted_user_can_download_code? + end + + def privileged_user_can_download_code? + full? && user && user.can?(:download_code, project) + end + + def restricted_user_can_download_code? + restricted? && user && user.can?(:restricted_download_code, project) end def lfs_upload_access? return false unless project.lfs_enabled? - user && user.can?(:push_code, project) + privileged_user_can_push_code? + end + + def privileged_user_can_push_code? + full? && user && user.can?(:push_code, project) end def render_lfs_forbidden diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 61052437318..1c2e0f1edea 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1,5 +1,7 @@ module Ci class Build < CommitStatus + include TokenAuthenticatable + belongs_to :runner, class_name: 'Ci::Runner' belongs_to :trigger_request, class_name: 'Ci::TriggerRequest' belongs_to :erased_by, class_name: 'User' @@ -23,7 +25,10 @@ module Ci acts_as_taggable + add_authentication_token_field :token + before_save :update_artifacts_size, if: :artifacts_file_changed? + before_save :ensure_token before_destroy { project } after_create :execute_hooks @@ -172,7 +177,7 @@ module Ci end def repo_url - auth = "gitlab-ci-token:#{token}@" + auth = "gitlab-ci-token:#{ensure_token}@" project.http_url_to_repo.sub(/^https?:\/\//) do |prefix| prefix + auth end @@ -340,12 +345,8 @@ module Ci ) end - def token - project.runners_token - end - def valid_token?(token) - project.valid_runners_token?(token) + self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) end def has_tags? diff --git a/app/models/project.rb b/app/models/project.rb index a6de2c48071..d7cdf8775b3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1138,12 +1138,6 @@ class Project < ActiveRecord::Base self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) end - # TODO (ayufan): For now we use runners_token (backward compatibility) - # In 8.4 every build will have its own individual token valid for time of build - def valid_build_token?(token) - self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) - end - def build_coverage_enabled? build_coverage_regex.present? end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index acf36d422d1..cda83bcc74a 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -64,6 +64,12 @@ class ProjectPolicy < BasePolicy can! :read_deployment end + # Permissions given when an user is direct member of a group + def restricted_reporter_access! + can! :restricted_download_code + can! :restricted_read_container_image + end + def developer_access! can! :admin_merge_request can! :update_merge_request @@ -130,10 +136,11 @@ class ProjectPolicy < BasePolicy def team_access!(user) access = project.team.max_member_access(user.id) - guest_access! if access >= Gitlab::Access::GUEST - reporter_access! if access >= Gitlab::Access::REPORTER - developer_access! if access >= Gitlab::Access::DEVELOPER - master_access! if access >= Gitlab::Access::MASTER + guest_access! if access >= Gitlab::Access::GUEST + reporter_access! if access >= Gitlab::Access::REPORTER + restricted_reporter_access! if access >= Gitlab::Access::REPORTER + developer_access! if access >= Gitlab::Access::DEVELOPER + master_access! if access >= Gitlab::Access::MASTER end def archived_access! diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 6072123b851..270d5a11d9e 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -4,7 +4,9 @@ module Auth AUDIENCE = 'container_registry' - def execute + def execute(access_type: access_type) + @access_type = access_type + return error('not found', 404) unless registry.enabled unless current_user || project @@ -74,9 +76,9 @@ module Auth case requested_action when 'pull' - requested_project == project || can?(current_user, :read_container_image, requested_project) + restricted_user_can_pull?(requested_project) || privileged_user_can_pull?(requested_project) when 'push' - requested_project == project || can?(current_user, :create_container_image, requested_project) + restricted_user_can_push?(requested_project) || privileged_user_can_push?(requested_project) else false end @@ -85,5 +87,37 @@ module Auth def registry Gitlab.config.registry end + + private + + def restricted_user_can_pull?(requested_project) + return false unless restricted? + + # Restricted can: + # 1. pull from it's own project (for ex. a build) + # 2. read images from dependent projects if he is a team member + requested_project == project || can?(current_user, :restricted_read_container_image, requested_project) + end + + def privileged_user_can_pull?(requested_project) + full? && can?(current_user, :read_container_image, requested_project) + end + + def restricted_user_can_push?(requested_project) + # Restricted can push only to project to from which he originates + restricted? && requested_project == project + end + + def privileged_user_can_push?(requested_project) + full? && can?(current_user, :create_container_image, requested_project) + end + + def full? + @access_type == :full + end + + def restricted? + @access_type == :restricted + end end end diff --git a/db/migrate/20160808085531_add_token_to_build.rb b/db/migrate/20160808085531_add_token_to_build.rb new file mode 100644 index 00000000000..3ed2a103ae3 --- /dev/null +++ b/db/migrate/20160808085531_add_token_to_build.rb @@ -0,0 +1,10 @@ +class AddTokenToBuild < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + add_column :ci_builds, :token, :string + end +end diff --git a/db/migrate/20160808085602_add_index_for_build_token.rb b/db/migrate/20160808085602_add_index_for_build_token.rb new file mode 100644 index 00000000000..10ef42afce1 --- /dev/null +++ b/db/migrate/20160808085602_add_index_for_build_token.rb @@ -0,0 +1,12 @@ +class AddIndexForBuildToken < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def change + add_concurrent_index :ci_builds, :token, unique: true + end +end diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index bcabf7a21b2..411e0dea15e 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -14,12 +14,20 @@ module Ci end def authenticate_build_token!(build) - token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s - forbidden! unless token && build.valid_token?(token) + forbidden! unless build_token_valid? end def runner_registration_token_valid? - params[:token] == current_application_settings.runners_registration_token + ActiveSupport::SecurityUtils.variable_size_secure_compare( + params[:token], + current_application_settings.runners_registration_token) + end + + def build_token_valid? + token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s + + # We require to also check `runners_token` to maintain compatibility with old version of runners + token && (build.valid_token?(token) || build.project.valid_runners_token?(token)) end def update_runner_last_contact(save: true) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 91f0270818a..e7bf8ee6166 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,6 @@ module Gitlab module Auth - Result = Struct.new(:user, :type) + Result = Struct.new(:user, :type, :access_type) class << self def find_for_git_client(login, password, project:, ip:) @@ -64,9 +64,7 @@ module Gitlab underscored_service = matched_login['service'].underscore - if underscored_service == 'gitlab_ci' - project && project.valid_build_token?(password) - elsif Service.available_services_names.include?(underscored_service) + if Service.available_services_names.include?(underscored_service) # We treat underscored_service as a trusted input because it is included # in the Service.available_services_names whitelist. service = project.public_send("#{underscored_service}_service") @@ -77,12 +75,13 @@ module Gitlab def populate_result(login, password) result = + build_access_token_check(login, password) || user_with_password_for_git(login, password) || oauth_access_token_check(login, password) || personal_access_token_check(login, password) if result - result.type = nil unless result.user + result.type = nil unless result.user && result.type != :ci if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap result.type = :missing_personal_token @@ -94,7 +93,7 @@ module Gitlab def user_with_password_for_git(login, password) user = find_with_user_password(login, password) - Result.new(user, :gitlab_or_ldap) if user + Result.new(user, :gitlab_or_ldap, :full) if user end def oauth_access_token_check(login, password) @@ -102,7 +101,7 @@ module Gitlab token = Doorkeeper::AccessToken.by_token(password) if token && token.accessible? user = User.find_by(id: token.resource_owner_id) - Result.new(user, :oauth) + Result.new(user, :oauth, :full) end end end @@ -111,7 +110,23 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, :personal_token) if user == validation + Result.new(user, :personal_token, :full) if user == validation + end + end + + def build_access_token_check(login, password) + return unless login == 'gitlab-ci-token' + return unless password + + build = Ci::Build.running.find_by_token(password) + return unless build + + if build.user + # If user is assigned to build, use restricted credentials of user + Result.new(build.user, :build, :restricted) + else + # Otherwise use generic CI credentials (backward compatibility) + Result.new(nil, :ci, :restricted) end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 1882eb8d050..5bd0134ed45 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,12 +5,13 @@ module Gitlab DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } PUSH_COMMANDS = %w{ git-receive-pack } - attr_reader :actor, :project, :protocol, :user_access + attr_reader :actor, :project, :protocol, :user_access, :access_type - def initialize(actor, project, protocol) + def initialize(actor, project, protocol, access_type: access_type) @actor = actor @project = project @protocol = protocol + @access_type = access_type @user_access = UserAccess.new(user, project: project) end @@ -60,14 +61,26 @@ module Gitlab end def user_download_access_check - unless user_access.can_do_action?(:download_code) + unless privileged_user_can_download_code? || restricted_user_can_download_code? return build_status_object(false, "You are not allowed to download code from this project.") end build_status_object(true) end + def privileged_user_can_download_code? + access_type == :full && user_access.can_do_action?(:download_code) + end + + def restricted_user_can_download_code? + access_type == :restricted && user_access.can_do_action?(:restricted_download_code) + end + def user_push_access_check(changes) + unless access_type == :full + return build_status_object(false, "You are not allowed to upload code for this project.") + end + if changes.blank? return build_status_object(true) end -- cgit v1.2.3 From 8c77a1fb250cf6d6ca06bedc3b52fc62f1cc4819 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 1 Sep 2016 13:42:17 +0200 Subject: Before deleting project if forked unlink fork --- app/services/projects/destroy_service.rb | 2 ++ spec/controllers/projects_controller_spec.rb | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 8a53f65aec1..a08c6fcd94b 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -27,6 +27,8 @@ module Projects # Git data (e.g. a list of branch names). flush_caches(project, wiki_path) + Projects::UnlinkForkService.new(project, current_user).execute + Project.transaction do project.destroy! diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index ffe0641ddd7..95315d86c4d 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -181,6 +181,23 @@ describe ProjectsController do expect(response).to have_http_status(302) expect(response).to redirect_to(dashboard_projects_path) end + + context "when project is forked" do + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } + let(:merge_request) do + create(:merge_request, + source_project: fork_project, + target_project: project) + end + + it "closes all related merge requests" do + fork_project.destroy + + expect(fork_project.destroyed?).to be_truthy + expect(merge_request.state).to eq('closed') + end + end end describe "POST #toggle_star" do -- cgit v1.2.3 From 09cded29d8b86cafbcfaed57b1d915588195f69f Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 2 Sep 2016 13:36:25 +0200 Subject: Checks if deleting forked project closed all open merge requests --- app/controllers/projects/merge_requests_controller.rb | 4 ++-- app/models/merge_request.rb | 2 +- spec/controllers/projects_controller_spec.rb | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8895cb955bd..479b2d1d900 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -429,7 +429,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def validates_merge_request # If source project was removed (Ex. mr from fork to origin) - return invalid_mr unless @merge_request.source_project + # return invalid_mr unless @merge_request.source_project # Show git not found page # if there is no saved commits between source & target branch @@ -438,7 +438,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController return invalid_mr unless @merge_request.target_branch_exists? # or if source branch doesn't exist - return invalid_mr unless @merge_request.source_branch_exists? + # return invalid_mr unless @merge_request.source_branch_exists? end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b0b1313f94a..7d3c8c5078a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -313,7 +313,7 @@ class MergeRequest < ActiveRecord::Base end def closed_without_fork? - closed? && forked_source_project_missing? + closed? && (forked_source_project_missing? || !source_project) end def forked_source_project_missing? diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 95315d86c4d..4f5741f1647 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -192,10 +192,12 @@ describe ProjectsController do end it "closes all related merge requests" do - fork_project.destroy + project.merge_requests << merge_request + sign_in(admin) - expect(fork_project.destroyed?).to be_truthy - expect(merge_request.state).to eq('closed') + delete :destroy, namespace_id: fork_project.namespace.path, id: fork_project.path + + expect(merge_request.reload.state).to eq('closed') end end end -- cgit v1.2.3 From 31c37c6c38258684fc92e0d91119c33872e39034 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 2 Sep 2016 15:36:59 +0200 Subject: Add #closed_without_source_project? --- app/models/merge_request.rb | 6 ++++- .../projects/merge_requests/_discussion.html.haml | 2 +- app/views/projects/merge_requests/_show.html.haml | 31 ++++++++++++---------- spec/controllers/projects_controller_spec.rb | 6 ++--- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 7d3c8c5078a..f752a4fd1cc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -313,7 +313,11 @@ class MergeRequest < ActiveRecord::Base end def closed_without_fork? - closed? && (forked_source_project_missing? || !source_project) + closed? && forked_source_project_missing? + end + + def closed_without_source_project? + closed? && !source_project end def forked_source_project_missing? diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index d070979bcfe..0aec7b2fbbb 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -2,7 +2,7 @@ - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"} - - if @merge_request.closed? + - unless @merge_request.open? || @merge_request.closed_without_fork? = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" } %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } } diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 4b4d418e8ec..d03ff9ec7e8 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -29,17 +29,19 @@ %ul.dropdown-menu.dropdown-menu-align-right %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) - .normal - %span Request to merge - %span.label-branch= source_branch_with_namespace(@merge_request) - %span into - %span.label-branch - = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) - - if @merge_request.open? && @merge_request.diverged_from_target_branch? - %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) + - unless @merge_request.closed_without_fork? + .normal + %span Request to merge + %span.label-branch= source_branch_with_namespace(@merge_request) + %span into + %span.label-branch + = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) + - if @merge_request.open? && @merge_request.diverged_from_target_branch? + %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) - = render "projects/merge_requests/show/how_to_merge" - = render "projects/merge_requests/widget/show.html.haml" + - unless @merge_request.closed_without_source_project? + = render "projects/merge_requests/show/how_to_merge" + = render "projects/merge_requests/widget/show.html.haml" - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user) .light.prepend-top-default.append-bottom-default @@ -53,10 +55,11 @@ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do Discussion %span.badge= @merge_request.mr_and_commit_notes.user.count - %li.commits-tab - = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do - Commits - %span.badge= @commits_count + - unless @merge_request.closed_without_source_project? + %li.commits-tab + = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do + Commits + %span.badge= @commits_count - if @pipeline %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 4f5741f1647..da75f4ccd2c 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -186,9 +186,9 @@ describe ProjectsController do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } let(:merge_request) do - create(:merge_request, - source_project: fork_project, - target_project: project) + create(:merge_request, + source_project: fork_project, + target_project: project) end it "closes all related merge requests" do -- cgit v1.2.3 From 81da7f137807a9f8c35b6c96b6034975558ddf97 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Mon, 5 Sep 2016 13:42:35 +0200 Subject: Add test checking method closed_without_source_project --- .../projects/merge_requests_controller.rb | 6 ---- spec/models/merge_request_spec.rb | 38 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 479b2d1d900..aa8645ba8cc 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -428,17 +428,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def validates_merge_request - # If source project was removed (Ex. mr from fork to origin) - # return invalid_mr unless @merge_request.source_project - # Show git not found page # if there is no saved commits between source & target branch if @merge_request.commits.blank? # and if target branch doesn't exist return invalid_mr unless @merge_request.target_branch_exists? - - # or if source branch doesn't exist - # return invalid_mr unless @merge_request.source_branch_exists? end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5bf3b8e609e..e223d51aa6a 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1038,4 +1038,42 @@ describe MergeRequest, models: true do end end end + + describe "#closed_without_source_project?" do + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } + let(:user) { create(:user) } + let(:destroy_project) { Projects::DestroyService.new(fork_project, user, {}) } + + context "when the merge request is closed" do + let(:closed_merge_request) do + create(:closed_merge_request, + source_project: fork_project, + target_project: project) + end + + it "returns false if the source project exist" do + expect(closed_merge_request.closed_without_source_project?).to be_falsey + end + + it "returns true if the source project does not exist" do + destroy_project.async_execute + closed_merge_request.reload + + expect(closed_merge_request.closed_without_source_project?).to be_truthy + end + end + + context "when the merge request is open" do + let(:open_merge_request) do + create(:merge_request, + source_project: fork_project, + target_project: project) + end + + it "returns false" do + expect(open_merge_request.closed_without_source_project?).to be_falsey + end + end + end end -- cgit v1.2.3 From 554baec9e8d2d077afff5502fdfa4ab78036499c Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 6 Sep 2016 16:48:59 +0200 Subject: Add method --- app/models/merge_request.rb | 7 +++++++ spec/models/merge_request_spec.rb | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index f752a4fd1cc..5986a464bbc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -327,6 +327,13 @@ class MergeRequest < ActiveRecord::Base !source_project.forked_from?(target_project) end + def can_reopen? + return false if closed_without_fork? || closed_without_source_project? + return true if closed? + + # false + end + def ensure_merge_request_diff merge_request_diff || create_merge_request_diff end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index e223d51aa6a..4780d9fb3fe 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1076,4 +1076,17 @@ describe MergeRequest, models: true do end end end + + describe '#can_reopen?' do + it "returns true" do + subject.close + binding.pry + + expect(subject.can_reopen?).to be_truthy + end + + it "returns false" do + expect(subject.can_reopen?).to be_falsey + end + end end -- cgit v1.2.3 From 34c146a17dbb66322bc57b0755c979c05e7d4340 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 8 Sep 2016 13:25:16 +0200 Subject: Add #can_reopen? and tests --- app/helpers/merge_requests_helper.rb | 2 +- app/models/merge_request.rb | 4 +- .../projects/merge_requests/_discussion.html.haml | 2 +- spec/models/merge_request_spec.rb | 64 +++++++++++++++------- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 8abe7865fed..e9a61e2c2a6 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -98,7 +98,7 @@ module MergeRequestsHelper end def merge_request_button_visibility(merge_request, closed) - return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? + return 'hidden' if merge_request.can_reopen? end def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5986a464bbc..014a6ce023b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -328,10 +328,8 @@ class MergeRequest < ActiveRecord::Base end def can_reopen? - return false if closed_without_fork? || closed_without_source_project? + return false if closed_without_fork? || closed_without_source_project? || merged? return true if closed? - - # false end def ensure_merge_request_diff diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 0aec7b2fbbb..b8013c4ee82 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -2,7 +2,7 @@ - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"} - - unless @merge_request.open? || @merge_request.closed_without_fork? + - if @merge_request.can_reopen? = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" } %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 4780d9fb3fe..d58156ba045 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1039,24 +1039,24 @@ describe MergeRequest, models: true do end end - describe "#closed_without_source_project?" do + describe '#closed_without_source_project?' do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } let(:user) { create(:user) } let(:destroy_project) { Projects::DestroyService.new(fork_project, user, {}) } - context "when the merge request is closed" do + context 'when the merge request is closed' do let(:closed_merge_request) do create(:closed_merge_request, source_project: fork_project, target_project: project) end - it "returns false if the source project exist" do + it 'returns false if the source project exists' do expect(closed_merge_request.closed_without_source_project?).to be_falsey end - it "returns true if the source project does not exist" do + it 'returns true if the source project does not exist' do destroy_project.async_execute closed_merge_request.reload @@ -1064,29 +1064,55 @@ describe MergeRequest, models: true do end end - context "when the merge request is open" do - let(:open_merge_request) do - create(:merge_request, - source_project: fork_project, - target_project: project) - end - - it "returns false" do - expect(open_merge_request.closed_without_source_project?).to be_falsey + context 'when the merge request is open' do + it 'returns false' do + expect(subject.closed_without_source_project?).to be_falsey end end end describe '#can_reopen?' do - it "returns true" do - subject.close - binding.pry + context 'when the merge request is closed' do + it 'returns true' do + subject.close - expect(subject.can_reopen?).to be_truthy + expect(subject.can_reopen?).to be_truthy + end + + context 'forked project' do + let(:project) { create(:project) } + let(:fork_project) { create(:project, forked_from_project: project) } + let(:user) { create(:user) } + let(:merge_request) do + create(:closed_merge_request, + source_project: fork_project, + target_project: project) + end + + it 'returns false if unforked' do + Projects::UnlinkForkService.new(fork_project, user).execute + + expect(merge_request.reload.can_reopen?).to be_falsey + end + + it 'returns false if the source project is deleted' do + Projects::DestroyService.new(fork_project, user, {}).async_execute + + expect(merge_request.reload.can_reopen?).to be_falsey + end + + it 'returnes false if the merge request is merged' do + merge_request.update_attributes(state: 'merged') + + expect(merge_request.reload.can_reopen?).to be_falsey + end + end end - it "returns false" do - expect(subject.can_reopen?).to be_falsey + context 'when merge request is opened' do + it 'returns false' do + expect(subject.can_reopen?).to be_falsey + end end end end -- cgit v1.2.3 From d88f708b02720404af4b7647a65f831dae59f764 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 8 Sep 2016 14:20:11 +0200 Subject: Improve grammar --- spec/controllers/projects_controller_spec.rb | 2 +- spec/models/merge_request_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index da75f4ccd2c..b0f740f48f7 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -182,7 +182,7 @@ describe ProjectsController do expect(response).to redirect_to(dashboard_projects_path) end - context "when project is forked" do + context "when the project is forked" do let(:project) { create(:project) } let(:fork_project) { create(:project, forked_from_project: project) } let(:merge_request) do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d58156ba045..9ec26a65ba1 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1109,7 +1109,7 @@ describe MergeRequest, models: true do end end - context 'when merge request is opened' do + context 'when the merge request is opened' do it 'returns false' do expect(subject.can_reopen?).to be_falsey end -- cgit v1.2.3 From 0437842d84a04953609b52e04bf970f4e9e050db Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Thu, 8 Sep 2016 14:22:05 +0200 Subject: Add CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 195362046dc..4cad8707015 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -127,6 +127,7 @@ v 8.12.0 (unreleased) - Allow bulk update merge requests from merge requests index page - Add notification_settings API calls !5632 (mahcsig) - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) + - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) v 8.11.6 (unreleased) - Fix an error where we were unable to create a CommitStatus for running state -- cgit v1.2.3 From bef1292cbd0588bbd9daa9484a00381d95ffbf94 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 13 Sep 2016 08:27:37 +0200 Subject: Fix not working test with execute --- spec/models/merge_request_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 9ec26a65ba1..031e95b411e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1041,8 +1041,8 @@ describe MergeRequest, models: true do describe '#closed_without_source_project?' do let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } let(:user) { create(:user) } + let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } let(:destroy_project) { Projects::DestroyService.new(fork_project, user, {}) } context 'when the merge request is closed' do @@ -1057,7 +1057,7 @@ describe MergeRequest, models: true do end it 'returns true if the source project does not exist' do - destroy_project.async_execute + destroy_project.execute closed_merge_request.reload expect(closed_merge_request.closed_without_source_project?).to be_truthy @@ -1081,8 +1081,8 @@ describe MergeRequest, models: true do context 'forked project' do let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } let(:user) { create(:user) } + let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } let(:merge_request) do create(:closed_merge_request, source_project: fork_project, @@ -1096,7 +1096,7 @@ describe MergeRequest, models: true do end it 'returns false if the source project is deleted' do - Projects::DestroyService.new(fork_project, user, {}).async_execute + Projects::DestroyService.new(fork_project, user, {}).execute expect(merge_request.reload.can_reopen?).to be_falsey end -- cgit v1.2.3 From 34586c1894882ce1ec2a0978d16cbd23d8603986 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 13 Sep 2016 12:16:40 +0200 Subject: Improve grammar --- app/helpers/merge_requests_helper.rb | 2 +- spec/models/merge_request_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index e9a61e2c2a6..8abe7865fed 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -98,7 +98,7 @@ module MergeRequestsHelper end def merge_request_button_visibility(merge_request, closed) - return 'hidden' if merge_request.can_reopen? + return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?) || merge_request.closed_without_fork? end def merge_request_version_path(project, merge_request, merge_request_diff, start_sha = nil) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 031e95b411e..4b8daacc7e0 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1101,7 +1101,7 @@ describe MergeRequest, models: true do expect(merge_request.reload.can_reopen?).to be_falsey end - it 'returnes false if the merge request is merged' do + it 'returns false if the merge request is merged' do merge_request.update_attributes(state: 'merged') expect(merge_request.reload.can_reopen?).to be_falsey -- cgit v1.2.3 From 66e92895e3c62a53c10ac93148d5f4fd963c93b2 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Tue, 13 Sep 2016 14:40:00 +0200 Subject: Change method name to #reopenable? --- app/models/merge_request.rb | 5 +++-- .../projects/merge_requests/_discussion.html.haml | 2 +- spec/models/merge_request_spec.rb | 18 +++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 014a6ce023b..f7d1253d957 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -327,9 +327,10 @@ class MergeRequest < ActiveRecord::Base !source_project.forked_from?(target_project) end - def can_reopen? + def reopenable? return false if closed_without_fork? || closed_without_source_project? || merged? - return true if closed? + + closed? end def ensure_merge_request_diff diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index b8013c4ee82..3900b4f6f17 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -2,7 +2,7 @@ - if can?(current_user, :update_merge_request, @merge_request) - if @merge_request.open? = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"} - - if @merge_request.can_reopen? + - if @merge_request.reopenable? = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"} %comment-and-resolve-btn{ "inline-template" => true, ":discussion-id" => "" } %button.btn.btn-nr.btn-default.append-right-10.js-comment-resolve-button{ "v-if" => "showButton", type: "submit", data: { namespace_path: "#{@merge_request.project.namespace.path}", project_path: "#{@merge_request.project.path}" } } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 4b8daacc7e0..3b815ded2d3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1043,7 +1043,7 @@ describe MergeRequest, models: true do let(:project) { create(:project) } let(:user) { create(:user) } let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } - let(:destroy_project) { Projects::DestroyService.new(fork_project, user, {}) } + let(:destroy_service) { Projects::DestroyService.new(fork_project, user) } context 'when the merge request is closed' do let(:closed_merge_request) do @@ -1057,7 +1057,7 @@ describe MergeRequest, models: true do end it 'returns true if the source project does not exist' do - destroy_project.execute + destroy_service.execute closed_merge_request.reload expect(closed_merge_request.closed_without_source_project?).to be_truthy @@ -1071,12 +1071,12 @@ describe MergeRequest, models: true do end end - describe '#can_reopen?' do + describe '#reopenable?' do context 'when the merge request is closed' do it 'returns true' do subject.close - expect(subject.can_reopen?).to be_truthy + expect(subject.reopenable?).to be_truthy end context 'forked project' do @@ -1092,26 +1092,26 @@ describe MergeRequest, models: true do it 'returns false if unforked' do Projects::UnlinkForkService.new(fork_project, user).execute - expect(merge_request.reload.can_reopen?).to be_falsey + expect(merge_request.reload.reopenable?).to be_falsey end it 'returns false if the source project is deleted' do - Projects::DestroyService.new(fork_project, user, {}).execute + Projects::DestroyService.new(fork_project, user).execute - expect(merge_request.reload.can_reopen?).to be_falsey + expect(merge_request.reload.reopenable?).to be_falsey end it 'returns false if the merge request is merged' do merge_request.update_attributes(state: 'merged') - expect(merge_request.reload.can_reopen?).to be_falsey + expect(merge_request.reload.reopenable?).to be_falsey end end end context 'when the merge request is opened' do it 'returns false' do - expect(subject.can_reopen?).to be_falsey + expect(subject.reopenable?).to be_falsey end end end -- cgit v1.2.3 From 571226f166f638f821ce84b90bce9cec1e5d5d06 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 13 Sep 2016 15:27:05 +0200 Subject: Make result to return project and capabilities granted --- app/controllers/jwt_controller.rb | 30 +++++++------------ .../projects/git_http_client_controller.rb | 12 +++----- app/controllers/projects/git_http_controller.rb | 2 +- app/helpers/lfs_helper.rb | 6 ++-- .../container_registry_authentication_service.rb | 23 ++++++-------- lib/gitlab/auth.rb | 35 +++++++++++++++++----- lib/gitlab/git_access.rb | 12 ++++---- 7 files changed, 60 insertions(+), 60 deletions(-) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index ca02df28b91..1b075cc5e2d 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -11,7 +11,7 @@ class JwtController < ApplicationController service = SERVICES[params[:service]] return head :not_found unless service - result = service.new(@project, @user, auth_params).execute(access_type: @access_type) + result = service.new(@project, @user, auth_params).execute(capabilities: @capabilities) render json: result, status: result[:http_status] end @@ -20,12 +20,16 @@ class JwtController < ApplicationController def authenticate_project_or_user authenticate_with_http_basic do |login, password| - # if it's possible we first try to authenticate project with login and password - @project, @user, @access_type = authenticate_build(login, password) - return if @project + @auth_result = Gitlab::Auth.find_for_git_client(login, password, ip: request.ip) - @user, @access_type = authenticate_user(login, password) - return if @user + @user = auth_result.user + @project = auth_result.project + @type = auth_result.type + @capabilities = auth_result.capabilities || [] + + if @user || @project + return # Allow access + end render_403 end @@ -34,18 +38,4 @@ class JwtController < ApplicationController def auth_params params.permit(:service, :scope, :account, :client_id) end - - def authenticate_build(login, password) - return unless login == 'gitlab-ci-token' - return unless password - - build = Ci::Build.running.find_by(token: password) - return build.project, build.user, :restricted if build - end - - def authenticate_user(login, password) - user = Gitlab::Auth.find_with_user_password(login, password) - Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login) - return user, :full - end end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 0f72dc8437c..6870102c296 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :user, :access_type + attr_reader :user, :capabilities # Git clients will not know what authenticity token to send along skip_before_action :verify_authenticity_token @@ -34,7 +34,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController @user = auth_result.user end - @access_type = auth_result.access_type + @capabilities = auth_result.capabilities || [] if ci? || user return # Allow access @@ -120,12 +120,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController @ci.present? end - def full? - @access_type == :full - end - - def restricted? - @access_type == :restricted + def has_capability?(capability) + @capabilities.include?(capability) end def verify_workhorse_api! diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index d59a47417f4..89afaaed510 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def access - @access ||= Gitlab::GitAccess.new(user, project, 'http', access_type: access_type) + @access ||= Gitlab::GitAccess.new(user, project, 'http', capabilities: capabilities) end def access_check diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 625dfddcf8d..bee03ffb446 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -29,11 +29,11 @@ module LfsHelper end def privileged_user_can_download_code? - full? && user && user.can?(:download_code, project) + has_capability?(:download_code) && user && user.can?(:download_code, project) end def restricted_user_can_download_code? - restricted? && user && user.can?(:restricted_download_code, project) + has_capability?(:restricted_download_code) && user && user.can?(:restricted_download_code, project) end def lfs_upload_access? @@ -43,7 +43,7 @@ module LfsHelper end def privileged_user_can_push_code? - full? && user && user.can?(:push_code, project) + has_capability?(:push_code) && user && user.can?(:push_code, project) end def render_lfs_forbidden diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 270d5a11d9e..cba0e2297a8 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -4,8 +4,8 @@ module Auth AUDIENCE = 'container_registry' - def execute(access_type: access_type) - @access_type = access_type + def execute(capabilities: capabilities) + @capabilities = capabilities return error('not found', 404) unless registry.enabled @@ -91,33 +91,28 @@ module Auth private def restricted_user_can_pull?(requested_project) - return false unless restricted? - # Restricted can: # 1. pull from it's own project (for ex. a build) # 2. read images from dependent projects if he is a team member - requested_project == project || can?(current_user, :restricted_read_container_image, requested_project) + requested_project == project || + has_ability?(:restricted_read_container_image, requested_project) end def privileged_user_can_pull?(requested_project) - full? && can?(current_user, :read_container_image, requested_project) + has_ability?(:read_container_image, requested_project) end def restricted_user_can_push?(requested_project) # Restricted can push only to project to from which he originates - restricted? && requested_project == project + requested_project == project end def privileged_user_can_push?(requested_project) - full? && can?(current_user, :create_container_image, requested_project) - end - - def full? - @access_type == :full + has_ability?(:create_container_image, requested_project) end - def restricted? - @access_type == :restricted + def has_ability?(ability, requested_project) + @capabilities.include?(ability) && can?(current_user, ability, requested_project) end end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index e7bf8ee6166..001917211a1 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,6 @@ module Gitlab module Auth - Result = Struct.new(:user, :type, :access_type) + Result = Struct.new(:user, :type, :project, :capabilities) class << self def find_for_git_client(login, password, project:, ip:) @@ -9,7 +9,7 @@ module Gitlab result = Result.new if valid_ci_request?(login, password, project) - result.type = :ci + result = Result.new(nil, project, :ci, restricted_capabilities) else result = populate_result(login, password) end @@ -81,7 +81,7 @@ module Gitlab personal_access_token_check(login, password) if result - result.type = nil unless result.user && result.type != :ci + result.type = nil unless result.capabilities if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap result.type = :missing_personal_token @@ -93,7 +93,7 @@ module Gitlab def user_with_password_for_git(login, password) user = find_with_user_password(login, password) - Result.new(user, :gitlab_or_ldap, :full) if user + Result.new(user, :gitlab_or_ldap, nil, full_capabilities) if user end def oauth_access_token_check(login, password) @@ -101,7 +101,7 @@ module Gitlab token = Doorkeeper::AccessToken.by_token(password) if token && token.accessible? user = User.find_by(id: token.resource_owner_id) - Result.new(user, :oauth, :full) + Result.new(user, nil, :oauth, full_capabilities) end end end @@ -110,7 +110,7 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, :personal_token, :full) if user == validation + Result.new(user, nil, :personal_token, full_capabilities) if user == validation end end @@ -123,12 +123,31 @@ module Gitlab if build.user # If user is assigned to build, use restricted credentials of user - Result.new(build.user, :build, :restricted) + Result.new(build.user, build.project, :build, restricted_capabilities) else # Otherwise use generic CI credentials (backward compatibility) - Result.new(nil, :ci, :restricted) + Result.new(nil, build.project, :ci, restricted_capabilities) end end + + private + + def restricted_capabilities + [ + :read_project, + :restricted_download_code, + :restricted_read_container_image + ] + end + + def full_capabilities + restricted_capabilities + [ + :download_code, + :push_code, + :read_container_image, + :update_container_image + ] + end end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 5bd0134ed45..10ef4a1e3cf 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,13 +5,13 @@ module Gitlab DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } PUSH_COMMANDS = %w{ git-receive-pack } - attr_reader :actor, :project, :protocol, :user_access, :access_type + attr_reader :actor, :project, :protocol, :user_access, :capabilities - def initialize(actor, project, protocol, access_type: access_type) + def initialize(actor, project, protocol, capabilities: capabilities) @actor = actor @project = project @protocol = protocol - @access_type = access_type + @capabilities = capabilities @user_access = UserAccess.new(user, project: project) end @@ -69,15 +69,15 @@ module Gitlab end def privileged_user_can_download_code? - access_type == :full && user_access.can_do_action?(:download_code) + capabilities.include?(:download_code) && user_access.can_do_action?(:download_code) end def restricted_user_can_download_code? - access_type == :restricted && user_access.can_do_action?(:restricted_download_code) + capabilities.include?(:restricted_download_code) && user_access.can_do_action?(:restricted_download_code) end def user_push_access_check(changes) - unless access_type == :full + unless capabilities.include?(:push_code) return build_status_object(false, "You are not allowed to upload code for this project.") end -- cgit v1.2.3 From ca8ed65efc8a56aafdb2011da06dd48ec55f1e07 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 13 Sep 2016 15:28:42 +0200 Subject: Fix result --- lib/gitlab/auth.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 001917211a1..0e8559022c6 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,6 @@ module Gitlab module Auth - Result = Struct.new(:user, :type, :project, :capabilities) + Result = Struct.new(:user, :project, :type, :capabilities) class << self def find_for_git_client(login, password, project:, ip:) -- cgit v1.2.3 From 07b127ce6064e962d7e8d469aa2148dc3504df8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 13 Sep 2016 14:21:03 +0000 Subject: Document the fact that merge requests from private forks can be checked out locally --- doc/user/project/merge_requests.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/user/project/merge_requests.md b/doc/user/project/merge_requests.md index f79535d1542..5af9a5d049c 100644 --- a/doc/user/project/merge_requests.md +++ b/doc/user/project/merge_requests.md @@ -93,6 +93,9 @@ A merge request contains all the history from a repository, plus the additional commits added to the branch associated with the merge request. Here's a few tricks to checkout a merge request locally. +Please note that you can checkout a merge request locally even if the source +project is a fork (even a private fork) of the target project. + #### Checkout locally by adding a git alias Add the following alias to your `~/.gitconfig`: -- cgit v1.2.3 From 113372173c757abcbebe26563a4301a3027f5037 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 13 Sep 2016 16:55:57 +0200 Subject: Update db/schema.rb --- db/schema.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 5c283141084..bf913e278fe 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: 20160902122721) do +ActiveRecord::Schema.define(version: 20160907131111) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -181,6 +181,7 @@ ActiveRecord::Schema.define(version: 20160902122721) do t.string "when" t.text "yaml_variables" t.datetime "queued_at" + t.string "token" 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 @@ -192,6 +193,7 @@ ActiveRecord::Schema.define(version: 20160902122721) 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", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree create_table "ci_commits", force: :cascade do |t| t.integer "project_id" -- cgit v1.2.3 From 940f900f1cc5343c6ba753b6570ca56caa93f8fd Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 6 Sep 2016 14:30:54 +0200 Subject: Change response for /ci/api/v1/builds/register.json from 404 to 204 --- lib/api/helpers.rb | 4 +++ lib/ci/api/builds.rb | 2 +- lib/ci/api/helpers.rb | 8 ++++++ spec/requests/ci/api/builds_spec.rb | 50 +++++++++++++++++++++++-------------- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6a20ba95a79..150875ed4f0 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -269,6 +269,10 @@ module API render_api_error!('304 Not Modified', 304) end + def no_content! + render_api_error!('204 No Content', 204) + end + def render_validation_error!(model) if model.errors.any? render_api_error!(model.errors.messages || '400 Bad Request', 400) diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 54db63d4628..59f85416ee5 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -27,7 +27,7 @@ module Ci else Gitlab::Metrics.add_event(:build_not_found) - not_found! + build_not_found! end end diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index bcabf7a21b2..ba80c89df78 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -32,6 +32,14 @@ module Ci end end + def build_not_found! + if headers['User-Agent'].match(/gitlab-ci-multi-runner \d+\.\d+\.\d+(~beta\.\d+\.g[0-9a-f]+)? /) + no_content! + else + not_found! + end + end + def current_runner @runner ||= Runner.find_by_token(params[:token].to_s) end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 9e390bea50b..80cf9b92423 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -15,6 +15,25 @@ describe Ci::API::API do describe "POST /builds/register" do let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } + let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' } + + shared_examples 'no builds available' do + context 'when runner sends version in User-Agent' do + context 'for stable version' do + it { expect(response).to have_http_status(204) } + end + + context 'for beta version' do + let(:user_agent) { 'gitlab-ci-multi-runner 1.6.0~beta.167.g2b2bacc (1-5-stable; go1.6.3; linux/amd64)' } + it { expect(response).to have_http_status(204) } + end + end + + context "when runner doesn't send version in User-Agent" do + let(:user_agent) { 'Go-http-client/1.1' } + it { expect(response).to have_http_status(404) } + end + end it "starts a build" do register_builds info: { platform: :darwin } @@ -33,36 +52,30 @@ describe Ci::API::API do context 'when builds are finished' do before do build.success - end - - it "returns 404 error if no builds for specific runner" do register_builds - - expect(response).to have_http_status(404) end + + it_behaves_like 'no builds available' end context 'for other project with builds' do before do build.success create(:ci_build, :pending) - end - - it "returns 404 error if no builds for shared runner" do register_builds - - expect(response).to have_http_status(404) end + + it_behaves_like 'no builds available' end context 'for shared runner' do let(:shared_runner) { create(:ci_runner, token: "SharedRunner") } - it "should return 404 error if no builds for shared runner" do + before do register_builds shared_runner.token - - expect(response).to have_http_status(404) end + + it_behaves_like 'no builds available' end context 'for triggered build' do @@ -136,18 +149,17 @@ describe Ci::API::API do end context 'when runner is not allowed to pick untagged builds' do - before { runner.update_column(:run_untagged, false) } - - it 'does not pick build' do + before do + runner.update_column(:run_untagged, false) register_builds - - expect(response).to have_http_status 404 end + + it_behaves_like 'no builds available' end end def register_builds(token = runner.token, **params) - post ci_api("/builds/register"), params.merge(token: token) + post ci_api("/builds/register"), params.merge(token: token), {'User-Agent' => user_agent} end end -- cgit v1.2.3 From d24b6adfb75c5b8eab4639e684ee45ce2340ce34 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 6 Sep 2016 14:44:18 +0200 Subject: Update CHANGELOG [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 4cad8707015..45c9685348a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -128,6 +128,7 @@ v 8.12.0 (unreleased) - Add notification_settings API calls !5632 (mahcsig) - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) + - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 v 8.11.6 (unreleased) - Fix an error where we were unable to create a CommitStatus for running state -- cgit v1.2.3 From 1c634131fdc2ca46d27166a66ba6072f9f5e2a35 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 6 Sep 2016 14:51:13 +0200 Subject: Update API documentation [ci skip] --- doc/api/ci/builds.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md index 2a71b087f19..e8d6b750082 100644 --- a/doc/api/ci/builds.md +++ b/doc/api/ci/builds.md @@ -38,6 +38,15 @@ POST /ci/api/v1/builds/register curl --request POST "https://gitlab.example.com/ci/api/v1/builds/register" --form "token=t0k3n" ``` +**Responses:** + +| Status | Data |Description | +|--------|------|---------------------------------------------------------------------------| +| `201` | yes | When a build is scheduled for a runner | +| `204` | no | When no builds are scheduled for a runner (for GitLab Runner >= `v1.3.0`) | +| `403` | no | When invalid token is used or no token is send | +| `404` | no | When no builds are scheduled for a runner (for GitLab Runner < `v1.3.0`) **or** when the runner is set to `paused` in GitLab runner's configuration page | + ### Update details of an existing build ``` -- cgit v1.2.3 From f369e62a16d5f7c529f277c1125ee99c85dfc56d Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 6 Sep 2016 14:52:57 +0200 Subject: Fix rubocop offences --- spec/requests/ci/api/builds_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 80cf9b92423..9b808457dc0 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -159,7 +159,7 @@ describe Ci::API::API do end def register_builds(token = runner.token, **params) - post ci_api("/builds/register"), params.merge(token: token), {'User-Agent' => user_agent} + post ci_api("/builds/register"), params.merge(token: token), { 'User-Agent' => user_agent } end end -- cgit v1.2.3 From 5b8613b8e657051a6a9199d417dd300f2fd9a281 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 13 Sep 2016 00:36:16 +0200 Subject: Add test with inactive error use case for 'POST /ci/api/v1/builds/register.json' --- spec/factories/ci/runners.rb | 4 ++++ spec/requests/ci/api/builds_spec.rb | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index 5b645fab32e..45eaebb2576 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -30,5 +30,9 @@ FactoryGirl.define do trait :shared do is_shared true end + + trait :inactive do + active false + end end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 9b808457dc0..780bd7f2859 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -158,6 +158,16 @@ describe Ci::API::API do end end + context 'when runner is paused' do + let(:inactive_runner) { create(:ci_runner, :inactive, token: "InactiveRunner") } + + before do + register_builds inactive_runner.token + end + + it { expect(response).to have_http_status 404 } + end + def register_builds(token = runner.token, **params) post ci_api("/builds/register"), params.merge(token: token), { 'User-Agent' => user_agent } end -- cgit v1.2.3 From 0663d72d306e17c2f116cdf9becfb06b01d83842 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 2 Sep 2016 12:55:16 -0300 Subject: Restore SSH Key title auto-population behavior Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/21823 --- CHANGELOG | 1 + app/views/profiles/keys/index.html.haml | 1 + spec/features/profiles/keys_spec.rb | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 spec/features/profiles/keys_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 4cad8707015..38ce9e5450b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -131,6 +131,7 @@ v 8.12.0 (unreleased) v 8.11.6 (unreleased) - Fix an error where we were unable to create a CommitStatus for running state + - Restore SSH Key title auto-population behavior. !6186 v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index a42b3b8eb38..93187873501 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,4 +1,5 @@ - page_title "SSH Keys" += render 'profiles/head' .row.prepend-top-default .col-lg-3.profile-settings-sidebar diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb new file mode 100644 index 00000000000..3b20d38c520 --- /dev/null +++ b/spec/features/profiles/keys_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +describe 'Profile > SSH Keys', feature: true do + let(:user) { create(:user) } + + before do + login_as(user) + visit profile_keys_path + end + + describe 'User adds an SSH key' do + it 'auto-populates the title', js: true do + fill_in('Key', with: attributes_for(:key).fetch(:key)) + + expect(find_field('Title').value).to eq 'dummy@gitlab.com' + end + end +end -- cgit v1.2.3 From e940d97dc03e7876ecf07948736da4477d13ca80 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 13 Sep 2016 17:43:32 +0200 Subject: Fix a typo in documentation --- doc/api/ci/builds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/ci/builds.md b/doc/api/ci/builds.md index e8d6b750082..b6d79706a84 100644 --- a/doc/api/ci/builds.md +++ b/doc/api/ci/builds.md @@ -44,7 +44,7 @@ curl --request POST "https://gitlab.example.com/ci/api/v1/builds/register" --for |--------|------|---------------------------------------------------------------------------| | `201` | yes | When a build is scheduled for a runner | | `204` | no | When no builds are scheduled for a runner (for GitLab Runner >= `v1.3.0`) | -| `403` | no | When invalid token is used or no token is send | +| `403` | no | When invalid token is used or no token is sent | | `404` | no | When no builds are scheduled for a runner (for GitLab Runner < `v1.3.0`) **or** when the runner is set to `paused` in GitLab runner's configuration page | ### Update details of an existing build -- cgit v1.2.3 From bb406cadfeeee3f56ff046ec3013ce4b277d90d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 13 Sep 2016 18:56:00 +0200 Subject: Improve .haml-lint.yml, simplify the haml_lint task and remove CHANGELOG entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .haml-lint.yml | 6 +----- CHANGELOG | 1 - lib/tasks/haml-lint.rake | 5 +---- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.haml-lint.yml b/.haml-lint.yml index 6e3b35607b1..da9a43d9c6d 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -1,13 +1,9 @@ -# Default application configuration that all configurations inherit from. -# -# This is an opinionated list of which hooks are valuable to run and what their -# out of the box settings should be. - # Whether to ignore frontmatter at the beginning of HAML documents for # frameworks such as Jekyll/Middleman skip_frontmatter: false exclude: - 'vendor/**/*' + - 'spec/**/*' linters: AltText: diff --git a/CHANGELOG b/CHANGELOG index 2a39ac99d49..d69168985cd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,7 +20,6 @@ v 8.12.0 (unreleased) - Fix pagination on user snippets page - Fix sorting of issues in API - Ensure specs on sorting of issues in API are deterministic on MySQL - - Use haml_lint for views linting - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 - Fix file permissions change when updating a file on the Gitlab UI !5979 diff --git a/lib/tasks/haml-lint.rake b/lib/tasks/haml-lint.rake index 80f70820853..609dfaa48e3 100644 --- a/lib/tasks/haml-lint.rake +++ b/lib/tasks/haml-lint.rake @@ -1,8 +1,5 @@ unless Rails.env.production? require 'haml_lint/rake_task' - HamlLint::RakeTask.new do |t| - t.config = '.haml-lint.yml' - t.files = ['app/views'] - end + HamlLint::RakeTask.new end -- cgit v1.2.3 From 50e62b3eb8ab030e123e990d1335f68478cc4a4b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Sep 2016 01:25:26 +0800 Subject: Fix Commit#status, feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6305#note_15230024 --- app/models/commit.rb | 2 +- app/models/concerns/has_status.rb | 2 +- spec/requests/api/commits_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 0e2ab76d347..e64fd1e0c1b 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -231,7 +231,7 @@ class Commit def status return @status if defined?(@status) - @status ||= pipelines.order(:id).last.try(:status) + @status ||= pipelines.status end def revert_branch_name diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 77fc66a7105..d658552f695 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -20,7 +20,7 @@ module HasStatus skipped = scope.skipped.select('count(*)').to_sql deduce_status = "(CASE - WHEN (#{builds})=0 THEN NULL + WHEN (#{builds})=(#{created}) THEN NULL WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending' diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index e24e92e063c..5b3dc60aba2 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -110,7 +110,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response).to have_http_status(200) - expect(json_response['status']).to eq('created') + expect(json_response['status']).to be_nil end end -- cgit v1.2.3 From 11f54caada4baaf8fb179213b1a93aa1a047f9b3 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 13 Sep 2016 19:45:02 +0200 Subject: Allow trailing newline in secret base64 data --- lib/gitlab/workhorse.rb | 2 +- spec/lib/gitlab/workhorse_spec.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index efe4aeb399d..60aae541d46 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -102,7 +102,7 @@ module Gitlab def secret @secret ||= begin - bytes = Base64.strict_decode64(File.read(secret_path)) + bytes = Base64.strict_decode64(File.read(secret_path).chomp) raise "#{secret_path} does not contain #{SECRET_LENGTH} bytes" if bytes.length != SECRET_LENGTH bytes end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 395192149a9..6c7fa7e7c15 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -30,6 +30,11 @@ describe Gitlab::Workhorse, lib: true do expect(subject.encoding).to eq(Encoding::ASCII_8BIT) end + it 'accepts a trailing newline' do + open(described_class.secret_path, 'a') { |f| f.write "\n" } + expect(subject.length).to eq(32) + end + it 'raises an exception if the secret file cannot be read' do File.delete(described_class.secret_path) expect { subject }.to raise_exception(Errno::ENOENT) -- cgit v1.2.3 From c90174afcd586b7652e527f4506b07ab833c7a87 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Sun, 4 Sep 2016 01:36:07 +0200 Subject: Fix Gitlab::Popen.popen thread-safety issue Fixes #21842 --- CHANGELOG | 1 + lib/gitlab/popen.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dc763269268..88b182c08fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -131,6 +131,7 @@ v 8.12.0 (unreleased) - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 + - Fix Gitlab::Popen.popen thread-safety issue v 8.11.6 (unreleased) - Fix an error where we were unable to create a CommitStatus for running state diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index a0fd41161a5..cc74bb29087 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -18,18 +18,18 @@ module Gitlab FileUtils.mkdir_p(path) end - @cmd_output = "" - @cmd_status = 0 + cmd_output = "" + cmd_status = 0 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| yield(stdin) if block_given? stdin.close - @cmd_output << stdout.read - @cmd_output << stderr.read - @cmd_status = wait_thr.value.exitstatus + cmd_output << stdout.read + cmd_output << stderr.read + cmd_status = wait_thr.value.exitstatus end - [@cmd_output, @cmd_status] + [cmd_output, cmd_status] end end end -- cgit v1.2.3 From 4e87c02313241b1e0d36560a11014cb4b7992403 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 13 Sep 2016 18:28:45 +0200 Subject: Move pushes_since_gc to Redis This moves tracking of the pushes since the last Git GC from PostgreSQL to Redis. This reduces the number of writes on the "projects" table. This in turn reduces the vacuuming overhead. The lease used for incrementing the counter has been removed. This lease was mostly put in place to prevent high database load but this isn't needed anymore due to the counter now being stored in Redis. Fixes gitlab-org/gitlab-ce#22125 --- CHANGELOG | 1 + app/models/project.rb | 16 +++++++ app/services/projects/housekeeping_service.rb | 12 ++--- ...160913162434_remove_projects_pushes_since_gc.rb | 19 ++++++++ db/schema.rb | 3 +- spec/models/project_spec.rb | 52 ++++++++++++++++++++++ .../services/projects/housekeeping_service_spec.rb | 29 +++--------- 7 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 db/migrate/20160913162434_remove_projects_pushes_since_gc.rb diff --git a/CHANGELOG b/CHANGELOG index 195362046dc..d1ba16c7ed3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.12.0 (unreleased) - Add two-factor recovery endpoint to internal API !5510 - Pass the "Remember me" value to the U2F authentication form - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) + - Move pushes_since_gc from the database to Redis - Add font color contrast to external label in admin area (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 diff --git a/app/models/project.rb b/app/models/project.rb index 4017cabe9f0..f3f3ffbbd28 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1288,8 +1288,24 @@ class Project < ActiveRecord::Base end end + def pushes_since_gc + Gitlab::Redis.with { |redis| redis.get(pushes_since_gc_redis_key).to_i } + end + + def increment_pushes_since_gc + Gitlab::Redis.with { |redis| redis.incr(pushes_since_gc_redis_key) } + end + + def reset_pushes_since_gc + Gitlab::Redis.with { |redis| redis.del(pushes_since_gc_redis_key) } + end + private + def pushes_since_gc_redis_key + "projects/#{id}/pushes_since_gc" + end + # Prevents the creation of project_feature record for every project def setup_project_feature build_project_feature unless project_feature diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb index 29b3981f49f..c3dfc8cfbe8 100644 --- a/app/services/projects/housekeeping_service.rb +++ b/app/services/projects/housekeeping_service.rb @@ -30,10 +30,8 @@ module Projects end def increment! - if Gitlab::ExclusiveLease.new("project_housekeeping:increment!:#{@project.id}", timeout: 60).try_obtain - Gitlab::Metrics.measure(:increment_pushes_since_gc) do - update_pushes_since_gc(@project.pushes_since_gc + 1) - end + Gitlab::Metrics.measure(:increment_pushes_since_gc) do + @project.increment_pushes_since_gc end end @@ -43,14 +41,10 @@ module Projects GitGarbageCollectWorker.perform_async(@project.id) ensure Gitlab::Metrics.measure(:reset_pushes_since_gc) do - update_pushes_since_gc(0) + @project.reset_pushes_since_gc end end - def update_pushes_since_gc(new_value) - @project.update_column(:pushes_since_gc, new_value) - end - def try_obtain_lease Gitlab::Metrics.measure(:obtain_housekeeping_lease) do lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT) diff --git a/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb new file mode 100644 index 00000000000..c5b8c35e961 --- /dev/null +++ b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb @@ -0,0 +1,19 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveProjectsPushesSinceGc < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'This migration removes an existing column' + + disable_ddl_transaction! + + def up + remove_column :projects, :pushes_since_gc + end + + def down + add_column_with_default! :projects, :pushes_since_gc, :integer, default: 0 + end +end diff --git a/db/schema.rb b/db/schema.rb index 5c283141084..61873e38113 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: 20160902122721) do +ActiveRecord::Schema.define(version: 20160913162434) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -824,7 +824,6 @@ ActiveRecord::Schema.define(version: 20160902122721) do t.integer "build_timeout", default: 3600, null: false t.boolean "pending_delete", default: false t.boolean "public_builds", default: true, null: false - t.integer "pushes_since_gc", default: 0 t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" t.boolean "container_registry_enabled" diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 4a41fafb84d..74c1d93a460 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1497,4 +1497,56 @@ describe Project, models: true do project.change_head(project.default_branch) end end + + describe '#pushes_since_gc' do + let(:project) { create(:project) } + + after do + project.reset_pushes_since_gc + end + + context 'without any pushes' do + it 'returns 0' do + expect(project.pushes_since_gc).to eq(0) + end + end + + context 'with a number of pushes' do + it 'returns the number of pushes' do + 3.times { project.increment_pushes_since_gc } + + expect(project.pushes_since_gc).to eq(3) + end + end + end + + describe '#increment_pushes_since_gc' do + let(:project) { create(:project) } + + after do + project.reset_pushes_since_gc + end + + it 'increments the number of pushes since the last GC' do + 3.times { project.increment_pushes_since_gc } + + expect(project.pushes_since_gc).to eq(3) + end + end + + describe '#reset_pushes_since_gc' do + let(:project) { create(:project) } + + after do + project.reset_pushes_since_gc + end + + it 'resets the number of pushes since the last GC' do + 3.times { project.increment_pushes_since_gc } + + project.reset_pushes_since_gc + + expect(project.pushes_since_gc).to eq(0) + end + end end diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index ad0d58672b3..c6160f4fa57 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -4,12 +4,11 @@ describe Projects::HousekeepingService do subject { Projects::HousekeepingService.new(project) } let(:project) { create :project } - describe 'execute' do - before do - project.pushes_since_gc = 3 - project.save! - end + after do + project.reset_pushes_since_gc + end + describe '#execute' do it 'enqueues a sidekiq job' do expect(subject).to receive(:try_obtain_lease).and_return(true) expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id) @@ -32,12 +31,12 @@ describe Projects::HousekeepingService do it 'does not reset pushes_since_gc' do expect do expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) - end.not_to change { project.pushes_since_gc }.from(3) + end.not_to change { project.pushes_since_gc } end end end - describe 'needed?' do + describe '#needed?' do it 'when the count is low enough' do expect(subject.needed?).to eq(false) end @@ -48,25 +47,11 @@ describe Projects::HousekeepingService do end end - describe 'increment!' do - let(:lease_key) { "project_housekeeping:increment!:#{project.id}" } - + describe '#increment!' do it 'increments the pushes_since_gc counter' do - lease = double(:lease, try_obtain: true) - expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease) - expect do subject.increment! end.to change { project.pushes_since_gc }.from(0).to(1) end - - it 'does not increment when no lease can be obtained' do - lease = double(:lease, try_obtain: false) - expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease) - - expect do - subject.increment! - end.not_to change { project.pushes_since_gc } - end end end -- cgit v1.2.3 From 5c9376f90dc977205df374c7504d477d05bc988c Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 11 Sep 2016 16:29:39 -0700 Subject: Fix URLs with anchors in wiki --- CHANGELOG | 1 + lib/banzai/filter/wiki_link_filter/rewriter.rb | 1 + spec/lib/banzai/pipeline/wiki_pipeline_spec.rb | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f1e2c5060f8..b706461ce5a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -129,6 +129,7 @@ v 8.12.0 (unreleased) - Allow bulk update merge requests from merge requests index page - Add notification_settings API calls !5632 (mahcsig) - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) + - Fix URLs with anchors in wiki !6300 (houqp) - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) v 8.11.6 (unreleased) diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb index 2e2c8da311e..e7a1ec8457d 100644 --- a/lib/banzai/filter/wiki_link_filter/rewriter.rb +++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb @@ -31,6 +31,7 @@ module Banzai def apply_relative_link_rules! if @uri.relative? && @uri.path.present? link = ::File.join(@wiki_base_path, @uri.path) + link = "#{link}##{@uri.fragment}" if @uri.fragment @uri = Addressable::URI.parse(link) end end diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index 51c89ac4889..ac9bde6baf1 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -127,6 +127,13 @@ describe Banzai::Pipeline::WikiPipeline do expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/nested/twice/page.md\"") end + + it 'rewrites links with anchor' do + markdown = '[Link to Header](start-page#title)' + output = described_class.to_html(markdown, project: project, project_wiki: project_wiki, page_slug: page.slug) + + expect(output).to include("href=\"#{relative_url_root}/wiki_link_ns/wiki_link_project/wikis/start-page#title\"") + end end describe "when creating root links" do -- cgit v1.2.3 From a310030005225348822a3cba2474a5756fa8b3ae Mon Sep 17 00:00:00 2001 From: ubudzisz Date: Fri, 2 Sep 2016 14:15:18 +0200 Subject: add test about flash alert add tests with dependent destroy add tests with dependent destroy add tests add tests to projects spec update it title and remove let update it title and remove let remove changes after rebase remove changes after rebase update changelog --- CHANGELOG | 1 + spec/features/projects_spec.rb | 8 ++++++-- spec/models/project_spec.rb | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ae5470964ff..9d813c9e35f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -133,6 +133,7 @@ v 8.12.0 (unreleased) - Deleting source project with existing fork link will close all related merge requests !6177 (Katarzyna Kobierska Ula Budziszeska) - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 - Fix Gitlab::Popen.popen thread-safety issue + - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) v 8.11.6 (unreleased) - Fix an error where we were unable to create a CommitStatus for running state diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index e00d85904d5..2242cb6236a 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -57,7 +57,7 @@ feature 'Project', feature: true do describe 'removal', js: true do let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, namespace: user.namespace, name: 'project1') } before do login_with(user) @@ -65,8 +65,12 @@ feature 'Project', feature: true do visit edit_namespace_project_path(project.namespace, project) end - it 'removes project' do + it 'removes a project' do expect { remove_with_confirm('Remove project', project.path) }.to change {Project.count}.by(-1) + expect(page).to have_content "Project 'project1' will be deleted." + expect(Project.all.count).to be_zero + expect(project.issues).to be_empty + expect(project.merge_requests).to be_empty end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 74c1d93a460..f6e811828fc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -6,6 +6,7 @@ describe Project, models: true do it { is_expected.to belong_to(:namespace) } it { is_expected.to belong_to(:creator).class_name('User') } it { is_expected.to have_many(:users) } + it { is_expected.to have_many(:services) } it { is_expected.to have_many(:events).dependent(:destroy) } it { is_expected.to have_many(:merge_requests).dependent(:destroy) } it { is_expected.to have_many(:issues).dependent(:destroy) } @@ -24,6 +25,30 @@ describe Project, models: true do it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } it { is_expected.to have_one(:board).dependent(:destroy) } + it { is_expected.to have_one(:campfire_service).dependent(:destroy) } + it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) } + it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } + it { is_expected.to have_one(:builds_email_service).dependent(:destroy) } + it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } + it { is_expected.to have_one(:irker_service).dependent(:destroy) } + it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) } + it { is_expected.to have_one(:hipchat_service).dependent(:destroy) } + it { is_expected.to have_one(:flowdock_service).dependent(:destroy) } + it { is_expected.to have_one(:assembla_service).dependent(:destroy) } + it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) } + it { is_expected.to have_one(:buildkite_service).dependent(:destroy) } + it { is_expected.to have_one(:bamboo_service).dependent(:destroy) } + it { is_expected.to have_one(:teamcity_service).dependent(:destroy) } + it { is_expected.to have_one(:jira_service).dependent(:destroy) } + it { is_expected.to have_one(:redmine_service).dependent(:destroy) } + it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) } + it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) } + it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) } + it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) } + it { is_expected.to have_one(:project_feature).dependent(:destroy) } + it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) } + it { is_expected.to have_one(:last_event).class_name('Event') } + it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) } it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:pipelines) } it { is_expected.to have_many(:builds) } @@ -31,9 +56,16 @@ describe Project, models: true do it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } it { is_expected.to have_many(:triggers) } + it { is_expected.to have_many(:labels).dependent(:destroy) } + it { is_expected.to have_many(:users_star_projects).dependent(:destroy) } it { is_expected.to have_many(:environments).dependent(:destroy) } it { is_expected.to have_many(:deployments).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) } + it { is_expected.to have_many(:releases).dependent(:destroy) } + it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) } + it { is_expected.to have_many(:project_group_links).dependent(:destroy) } + it { is_expected.to have_many(:notification_settings).dependent(:destroy) } + it { is_expected.to have_many(:forks).through(:forked_project_links) } describe '#members & #requesters' do let(:project) { create(:project) } @@ -178,7 +210,7 @@ describe Project, models: true do expect(project.runners_token).not_to eq('') end - it 'does not set an random toke if one provided' do + it 'does not set an random token if one provided' do project = FactoryGirl.create :empty_project, runners_token: 'my-token' expect(project.runners_token).to eq('my-token') end -- cgit v1.2.3 From e05692f44d0effe916d66cb8e4da569da8ec0af1 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 6 Sep 2016 13:31:49 +0000 Subject: Update GITLAB_SHELL_VERSION --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 18091983f59..1545d966571 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.4.0 +3.5.0 -- cgit v1.2.3 From 178b2cdcec7b9a23d7e2fd4376878d62fcba7f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 14 Sep 2016 10:23:37 +0200 Subject: Update the 8.11 to 8.12 update documentation to use gitlab-shell 3.5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/update/8.11-to-8.12.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 011c2b0e969..8017c36587e 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -70,7 +70,7 @@ sudo -u git -H git checkout 8-12-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v3.4.0 +sudo -u git -H git checkout v3.5.0 ``` ### 6. Update gitlab-workhorse -- cgit v1.2.3 From 3411212e414208dfdc7abd03c3fee4c4ec214476 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 14 Sep 2016 10:51:42 +0100 Subject: Fixed add to tree button on mobile Closes #22128 --- app/assets/stylesheets/pages/projects.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 3e6e50375f6..db46d8072ce 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -334,6 +334,10 @@ a.deploy-project-label { a { color: $gl-dark-link-color; } + + .dropdown-menu { + width: 240px; + } } .last-push-widget { -- cgit v1.2.3 From 32979803f232c43c7b87e30bec275b79182b06ad Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 12 Sep 2016 21:02:09 +0100 Subject: Replace single quotes with escaped single quotes and write spec to test multiple lable selection over a page load Added spec for removing label with space and single quote Fixed removing label with single quote --- app/assets/javascripts/gl_dropdown.js | 13 ++++----- app/assets/javascripts/labels_select.js | 2 +- .../shared/issuable/_label_dropdown.html.haml | 2 +- spec/features/issues/filter_issues_spec.rb | 31 ++++++++++++++++++++-- 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index bea141bae51..b95b7f29d1e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -600,14 +600,15 @@ selectedObject = this.renderedData[selectedIndex]; } } + field = []; fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { field = $(this.el); - } else { - field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']"); + } else if(value) { + field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); } - if (el.hasClass(ACTIVE_CLASS)) { + if (field.length && el.hasClass(ACTIVE_CLASS)) { el.removeClass(ACTIVE_CLASS); if (isInput) { field.val(''); @@ -617,7 +618,7 @@ } else if (el.hasClass(INDETERMINATE_CLASS)) { el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); - if (value == null) { + if (field.length && value == null) { field.remove(); } if (!field.length && fieldName) { @@ -630,7 +631,7 @@ this.dropdown.parent().find("input[name='" + fieldName + "']").remove(); } } - if (value == null) { + if (field.length && value == null) { field.remove(); } // Toggle active class for the tick mark @@ -638,7 +639,7 @@ if (value != null) { if (!field.length && fieldName) { this.addInput(fieldName, value, selectedObject); - } else { + } else if (field.length) { field.val(value).trigger('change'); } } diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 29a967a35a0..3f15a117ca8 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -166,7 +166,7 @@ instance.addInput(this.fieldName, label.id); } } - if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) { + if (this.id(label) && $form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + this.id(label).toString().replace(/'/g, '\\\'') + "']").length) { selectedClass.push('is-active'); } if ($dropdown.hasClass('js-multiselect') && removesAll) { diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 24a1a616919..d34d28f6736 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -12,7 +12,7 @@ - if params[:label_name].present? - if params[:label_name].respond_to?('any?') - params[:label_name].each do |label| - = hidden_field_tag "label_name[]", u(label), id: nil + = hidden_field_tag "label_name[]", label, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 0e9f814044e..69fda27cc61 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -101,7 +101,7 @@ describe 'Filter issues', feature: true do expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label') end - it 'filters by no label' do + it 'filters by a label' do find('.dropdown-menu-labels a', text: label.title).click page.within '.labels-filter' do expect(page).to have_content label.title @@ -109,7 +109,7 @@ describe 'Filter issues', feature: true do expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) end - it 'filters by wont fix labels' do + it "filters by `won't fix` and another label" do find('.dropdown-menu-labels a', text: label.title).click page.within '.labels-filter' do expect(page).to have_content wontfix.title @@ -117,6 +117,33 @@ describe 'Filter issues', feature: true do end expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title) end + + it "filters by `won't fix` label followed by another label after page load" do + find('.dropdown-menu-labels a', text: wontfix.title).click + # Close label dropdown to load + find('body').click + expect(find('.filtered-labels')).to have_content(wontfix.title) + + find('.js-label-select').click + wait_for_ajax + find('.dropdown-menu-labels a', text: label.title).click + # Close label dropdown to load + find('body').click + expect(find('.filtered-labels')).to have_content(label.title) + + find('.js-label-select').click + wait_for_ajax + expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active') + expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active') + end + + it "selects and unselects `won't fix`" do + find('.dropdown-menu-labels a', text: wontfix.title).click + find('.dropdown-menu-labels a', text: wontfix.title).click + # Close label dropdown to load + find('body').click + expect(page).not_to have_css('.filtered-labels') + end end describe 'Filter issues for assignee and label from issues#index' do -- cgit v1.2.3 From 6355041affb9b0b493d041d8b11772b8423bb32e Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 14 Sep 2016 13:29:23 +0300 Subject: Backport search_helper changes from EE --- app/helpers/search_helper.rb | 31 +++++++++++++++++++++++++ app/models/repository.rb | 33 +-------------------------- app/views/search/results/_blob.html.haml | 2 +- app/views/search/results/_wiki_blob.html.haml | 2 +- spec/helpers/search_helper_spec.rb | 32 ++++++++++++++++++++++++++ spec/models/repository_spec.rb | 26 --------------------- 6 files changed, 66 insertions(+), 60 deletions(-) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index e523c46e879..8a7446b7cc7 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -30,6 +30,37 @@ module SearchHelper "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\"" end + def parse_search_result(result) + ref = nil + filename = nil + basename = nil + startline = 0 + + result.each_line.each_with_index do |line, index| + if line =~ /^.*:.*:\d+:/ + ref, filename, startline = line.split(':') + startline = startline.to_i - index + extname = Regexp.escape(File.extname(filename)) + basename = filename.sub(/#{extname}$/, '') + break + end + end + + data = "" + + result.each_line do |line| + data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') + end + + OpenStruct.new( + filename: filename, + basename: basename, + ref: ref, + startline: startline, + data: data + ) + end + private # Autocomplete results for various settings pages diff --git a/app/models/repository.rb b/app/models/repository.rb index 3c354c25c6f..c69e5a22a69 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -990,37 +990,6 @@ class Repository Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/) end - def parse_search_result(result) - ref = nil - filename = nil - basename = nil - startline = 0 - - result.each_line.each_with_index do |line, index| - if line =~ /^.*:.*:\d+:/ - ref, filename, startline = line.split(':') - startline = startline.to_i - index - extname = Regexp.escape(File.extname(filename)) - basename = filename.sub(/#{extname}$/, '') - break - end - end - - data = "" - - result.each_line do |line| - data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') - end - - OpenStruct.new( - filename: filename, - basename: basename, - ref: ref, - startline: startline, - data: data - ) - end - def fetch_ref(source_path, source_ref, target_ref) args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) Gitlab::Popen.popen(args, path_to_repo) @@ -1048,7 +1017,7 @@ class Repository GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do update_ref!(ref, newrev, oldrev) - + if was_empty || !target_branch # If repo was empty expire cache after_create if was_empty diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index 290743feb4a..6f0a0ea36ec 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,4 +1,4 @@ -- blob = @project.repository.parse_search_result(blob) +- blob = parse_search_result(blob) .blob-result .file-holder .file-title diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index 235106c4f74..648d0bd76cb 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -1,4 +1,4 @@ -- wiki_blob = @project.repository.parse_search_result(wiki_blob) +- wiki_blob = parse_search_result(wiki_blob) .blob-result .file-holder .file-title diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 4b2ca3514f8..c5b5aa8c445 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -6,6 +6,38 @@ describe SearchHelper do str end + describe 'parsing result' do + let(:project) { create(:project) } + let(:repository) { project.repository } + let(:results) { repository.search_files('feature', 'master') } + let(:search_result) { results.first } + + subject { helper.parse_search_result(search_result) } + + it "returns a valid OpenStruct object" do + is_expected.to be_an OpenStruct + expect(subject.filename).to eq('CHANGELOG') + expect(subject.basename).to eq('CHANGELOG') + expect(subject.ref).to eq('master') + expect(subject.startline).to eq(186) + expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") + end + + context "when filename has extension" do + let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" } + + it { expect(subject.filename).to eq('CONTRIBUTE.md') } + it { expect(subject.basename).to eq('CONTRIBUTE') } + end + + context "when file under directory" do + let(:search_result) { "master:a/b/c.md:5:a b c\n" } + + it { expect(subject.filename).to eq('a/b/c.md') } + it { expect(subject.basename).to eq('a/b/c') } + end + end + describe 'search_autocomplete_source' do context "with no current user" do before do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 7624050878e..94681004c96 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -186,32 +186,6 @@ describe Repository, models: true do it { is_expected.to be_an String } it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") } end - - describe 'parsing result' do - subject { repository.parse_search_result(search_result) } - let(:search_result) { results.first } - - it { is_expected.to be_an OpenStruct } - it { expect(subject.filename).to eq('CHANGELOG') } - it { expect(subject.basename).to eq('CHANGELOG') } - it { expect(subject.ref).to eq('master') } - it { expect(subject.startline).to eq(186) } - it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") } - - context "when filename has extension" do - let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" } - - it { expect(subject.filename).to eq('CONTRIBUTE.md') } - it { expect(subject.basename).to eq('CONTRIBUTE') } - end - - context "when file under directory" do - let(:search_result) { "master:a/b/c.md:5:a b c\n" } - - it { expect(subject.filename).to eq('a/b/c.md') } - it { expect(subject.basename).to eq('a/b/c') } - end - end end describe "#changelog" do -- cgit v1.2.3 From 9980f52cb4ee32fbf1b132d605add5678e5ec067 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 9 Sep 2016 15:19:48 +0200 Subject: Update gitlab_git to 10.6.6 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 81b7002027a..481e94ae569 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.6.3' +gem 'gitlab_git', '~> 10.6.6' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index c421713f6a1..a1346bcb5ce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -279,7 +279,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.6.3) + gitlab_git (10.6.6) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -394,7 +394,7 @@ GEM mime-types (>= 1.16, < 4) mail_room (0.8.0) method_source (0.8.2) - mime-types (2.99.2) + mime-types (2.99.3) mimemagic (0.3.0) mini_portile2 (2.1.0) minitest (5.7.0) @@ -858,7 +858,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.6.3) + gitlab_git (~> 10.6.6) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) -- cgit v1.2.3 From 0bc443e3b442b49cb6989282601d477c673c4412 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 12 Sep 2016 17:25:35 +0200 Subject: Handle encoding in non-binary Blob instances gitlab_git 10.6.4 relies on Rugged marking blobs as binary or not, instead of relying on Linguist. Linguist in turn would mark text blobs as binary whenever they would contain byte sequences that could not be encoded using UTF-8. However, marking such blobs as binary is not correct. If one pushes a Markdown document with invalid character sequences it's still a text based Markdown document and not some random binary blob. This commit overwrites Blob#data so it automatically converts text-based content to UTF-8 (the encoding we use everywhere else) while taking care of replacing any invalid sequences with the UTF-8 replacement character. The data of binary blobs is left as-is. --- app/models/blob.rb | 12 ++++++++++++ spec/models/blob_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/app/models/blob.rb b/app/models/blob.rb index 12cc5aaafba..ab92e820335 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -22,6 +22,18 @@ class Blob < SimpleDelegator new(blob) end + # Returns the data of the blob. + # + # If the blob is a text based blob the content is converted to UTF-8 and any + # invalid byte sequences are replaced. + def data + if binary? + super + else + @data ||= super.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) + end + end + def no_highlighting? size && size > 1.megabyte end diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index cee20234e1f..03d02b4d382 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require 'rails_helper' describe Blob do @@ -7,6 +8,25 @@ describe Blob do end end + describe '#data' do + context 'using a binary blob' do + it 'returns the data as-is' do + data = "\n\xFF\xB9\xC3" + blob = described_class.new(double(binary?: true, data: data)) + + expect(blob.data).to eq(data) + end + end + + context 'using a text blob' do + it 'converts the data to UTF-8' do + blob = described_class.new(double(binary?: false, data: "\n\xFF\xB9\xC3")) + + expect(blob.data).to eq("\n���") + end + end + end + describe '#svg?' do it 'is falsey when not text' do git_blob = double(text?: false) -- cgit v1.2.3 From 8759770c962f7b6fe509a2d650dd420f75864de6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 15:03:45 +0200 Subject: Re-use as much of views as possible --- .../projects/ci/builds/_build_pipeline.html.haml | 27 +++++++++++----------- app/views/projects/commit/_pipeline.html.haml | 8 +------ .../commit/_pipeline_grouped_status.html.haml | 27 ---------------------- .../projects/commit/_pipeline_stage.html.haml | 14 +++++++++++ .../commit/_pipeline_status_group.html.haml | 11 +++++++++ .../_generic_commit_status_pipeline.html.haml | 17 ++++++-------- 6 files changed, 46 insertions(+), 58 deletions(-) delete mode 100644 app/views/projects/commit/_pipeline_grouped_status.html.haml create mode 100644 app/views/projects/commit/_pipeline_stage.html.haml create mode 100644 app/views/projects/commit/_pipeline_status_group.html.haml diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 5289cd672f5..d19e193c432 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,15 +1,14 @@ - is_playable = subject.playable? && can?(current_user, :update_build, @project) -%li.build{class: ("playable" if is_playable)} - .curve - .build-content - - if is_playable - = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do - = render_status_with_link('build', 'play') - .ci-status-text= subject.name - - elsif can?(current_user, :read_build, @project) - = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = render_status_with_link('build', subject.status) - .ci-status-text= subject.name - - else - = render_status_with_link('build', subject.status) - = ci_icon_for_status(subject.status) +- if is_playable + = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do + = render_status_with_link('build', 'play') + .ci-status-text= subject.name +- elsif can?(current_user, :read_build, @project) + = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do + = render_status_with_link('build', subject.status) + .ci-status-text= subject.name +- else + = render_status_with_link('build', subject.status) + = ci_icon_for_status(subject.status) + + diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index a330c14061f..9258f4b3c25 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -39,13 +39,7 @@ = stage.titleize .builds-container %ul - - status_groups = statuses.group_by(&:group_name) - - status_groups.each do |group_name, grouped_statuses| - - if grouped_statuses.one? - - status = grouped_statuses.first - = render "projects/#{status.to_partial_path}_pipeline", subject: status - - else - = render "projects/commit/pipeline_grouped_status", name: group_name, subject: grouped_statuses + = render "projects/commit/pipeline_stage", statuses: statuses - if pipeline.yaml_errors.present? diff --git a/app/views/projects/commit/_pipeline_grouped_status.html.haml b/app/views/projects/commit/_pipeline_grouped_status.html.haml deleted file mode 100644 index dc8efc83d48..00000000000 --- a/app/views/projects/commit/_pipeline_grouped_status.html.haml +++ /dev/null @@ -1,27 +0,0 @@ -%li.build - .curve - .build-content - - group_status = CommitStatus.where(id: subject).status - = render_status_with_link('build', group_status) - .dropdown.inline - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} - %span.ci-status-text - = name - %span.badge= subject.length - %ul.dropdown-menu.grouped-pipeline-dropdown - .arrow - - subject.each do |status| - -# = render "projects/#{status.to_partial_path}_pipeline", subject: status - - is_playable = status.playable? && can?(current_user, :update_build, @project) - %li - - if is_playable - = link_to play_namespace_project_build_path(status.project.namespace, status.project, status, return_to: request.original_url), method: :post, title: 'Play' do - = render_status_with_link('build', 'play') - .ci-status-text= status.name - - elsif can?(current_user, :read_build, @project) - = link_to namespace_project_build_path(status.project.namespace, status.project, status) do - = render_status_with_link('build', status.status) - .ci-status-text= status.name - - else - = render_status_with_link('build', status.status) - = ci_icon_for_status(status.status) diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml new file mode 100644 index 00000000000..368564481c4 --- /dev/null +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -0,0 +1,14 @@ +- status_groups = statuses.group_by(&:group_name) +- status_groups.each do |group_name, grouped_statuses| + - if grouped_statuses.one? + - status = grouped_statuses.first + - is_playable = status.playable? && can?(current_user, :update_build, @project) + %li.build{class: ("playable" if is_playable)} + .curve + .build-content + = render "projects/#{status.to_partial_path}_pipeline", subject: status + - else + %li.build + .curve + .build-content + = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml new file mode 100644 index 00000000000..03b6249963b --- /dev/null +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -0,0 +1,11 @@ +- group_status = CommitStatus.where(id: subject).status += render_status_with_link('build', group_status) +.dropdown.inline + %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %span.ci-status-text + = name + %span.badge= subject.length + %ul.dropdown-menu.grouped-pipeline-dropdown + .arrow + - subject.each do |status| + = render "projects/#{status.to_partial_path}_pipeline", subject: status diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 576d0bec51b..9b54c2bc3af 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,10 +1,7 @@ -%li.build - .curve - .build-content - - if subject.target_url - - link_to subject.target_url do - = render_status_with_link('commit status', subject.status) - %span.ci-status-text= subject.name - - else - = render_status_with_link('commit status', subject.status) - %span.ci-status-text= subject.name +- if subject.target_url + - link_to subject.target_url do + = render_status_with_link('commit status', subject.status) + %span.ci-status-text= subject.name +- else + = render_status_with_link('commit status', subject.status) + %span.ci-status-text= subject.name -- cgit v1.2.3 From b964c6c579f57f1ff83eb27caf8ff7f7be6d7671 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 15:04:12 +0200 Subject: Add grouping tests --- app/models/commit_status.rb | 6 +++++- spec/models/commit_status_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index af739342256..1ae1a24c168 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -96,7 +96,7 @@ class CommitStatus < ActiveRecord::Base end def group_name - name.gsub(/\d+[\s:]+\d+\s*/, '') + name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip end def self.stages @@ -117,6 +117,10 @@ class CommitStatus < ActiveRecord::Base allow_failure? && (failed? || canceled?) end + def playable? + false + end + def duration calculate_duration end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index fcfa3138ce5..ea3b8295364 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -223,4 +223,31 @@ describe CommitStatus, models: true do expect(commit_status.commit).to eq project.commit end end + + describe '#group_name' do + subject { commit_status.group_name } + + tests = { + 'rspec:windows' => 'rspec:windows', + 'rspec:windows 0' => 'rspec:windows 0', + 'rspec:windows 0 test' => 'rspec:windows 0 test', + 'rspec:windows 0 1' => 'rspec:windows', + 'rspec:windows 0 1 name' => 'rspec:windows name', + 'rspec:windows 0/1' => 'rspec:windows', + 'rspec:windows 0/1 name' => 'rspec:windows name', + 'rspec:windows 0:1' => 'rspec:windows', + 'rspec:windows 0:1 name' => 'rspec:windows name', + 'rspec:windows 10000 20000' => 'rspec:windows', + 'rspec:windows 0 : / 1' => 'rspec:windows', + 'rspec:windows 0 : / 1 name' => 'rspec:windows name', + } + + tests.each do |name, group_name| + it "'#{name}' puts in '#{group_name}'" do + commit_status.name = name + + is_expected.to eq(group_name) + end + end + end end -- cgit v1.2.3 From d8ba09fca9ac9e0ad55c30686decb1bee7584468 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 15:23:07 +0200 Subject: Add view specs for pipelines graph --- .../projects/pipelines/show.html.haml_spec.rb | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 spec/views/projects/pipelines/show.html.haml_spec.rb diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb new file mode 100644 index 00000000000..920d7528892 --- /dev/null +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe 'projects/pipelines/show' do + include Devise::TestHelpers + + let(:project) { create(:project) } + let(:pipeline) do + create(:ci_empty_pipeline, project: project, + sha: project.commit.id) + end + + before do + create_build('build', 0, 'build') + create_build('test', 1, 'rspec 0 2') + create_build('test', 1, 'rspec 1 2') + create_build('test', 1, 'audit') + create_build('deploy', 2, 'production') + + create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3) + + assign(:project, project) + assign(:pipeline, pipeline) + + allow(view).to receive(:can?).and_return(true) + end + + it 'shows a graph with grouped stages' do + render + + expect(rendered).to have_css('.pipeline-graph') + expect(rendered).to have_css('.grouped-pipeline-dropdown') + + # stages + expect(rendered).to have_text('Build') + expect(rendered).to have_text('Test') + expect(rendered).to have_text('Deploy') + expect(rendered).to have_text('External') + + # builds + expect(rendered).to have_text('rspec') + expect(rendered).to have_text('rspec 0:1') + expect(rendered).to have_text('production') + expect(rendered).to have_text('jenkins') + end + + private + + def create_build(stage, stage_idx, name) + create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name) + end +end -- cgit v1.2.3 From 4e60f79e4027961ed3ec33fc16e2260c660d545a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 15:25:04 +0200 Subject: Add more regexp tests --- spec/models/commit_status_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index ea3b8295364..49984fbc5aa 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -240,6 +240,8 @@ describe CommitStatus, models: true do 'rspec:windows 10000 20000' => 'rspec:windows', 'rspec:windows 0 : / 1' => 'rspec:windows', 'rspec:windows 0 : / 1 name' => 'rspec:windows name', + '0 1 name ruby' => 'name ruby', + '0 :/ 1 name ruby' => 'name ruby' } tests.each do |name, group_name| -- cgit v1.2.3 From fd4ad6546278aef904f02dffe106b41872a9068c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 14 Sep 2016 16:18:49 +0200 Subject: Fix migration for removing MR diff indexes For whatever reason on some PostgreSQL installations there would be a separate UNIQUE constraint on the merge_request_id column. Rails' index_exists?() returns false for this constraint (even when using the full name), yet the indexes() method returns it. This commit changes the migration so that the constraint is dropped as well. MySQL installations don't appear to be affected. Fixes gitlab-org/gitlab-ce#22136 --- .../20160725104020_merge_request_diff_remove_uniq.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb index c8cbd2718ff..75a3eb15124 100644 --- a/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb +++ b/db/migrate/20160725104020_merge_request_diff_remove_uniq.rb @@ -8,14 +8,28 @@ class MergeRequestDiffRemoveUniq < ActiveRecord::Migration DOWNTIME = false def up - if index_exists?(:merge_request_diffs, :merge_request_id) - remove_index :merge_request_diffs, :merge_request_id + constraint_name = 'merge_request_diffs_merge_request_id_key' + + transaction do + if index_exists?(:merge_request_diffs, :merge_request_id) + remove_index(:merge_request_diffs, :merge_request_id) + end + + # In some bizarre cases PostgreSQL might have a separate unique constraint + # that we'll need to drop. + if constraint_exists?(constraint_name) && Gitlab::Database.postgresql? + execute("ALTER TABLE merge_request_diffs DROP CONSTRAINT IF EXISTS #{constraint_name};") + end end end def down unless index_exists?(:merge_request_diffs, :merge_request_id) - add_concurrent_index :merge_request_diffs, :merge_request_id, unique: true + add_concurrent_index(:merge_request_diffs, :merge_request_id, unique: true) end end + + def constraint_exists?(name) + indexes(:merge_request_diffs).map(&:name).include?(name) + end end -- cgit v1.2.3 From 79e4bb8d0b3b74ddd185677e4828d737788c3b1a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 17:28:24 +0200 Subject: Refactor Gitlab::Auth to simplify the data flow --- lib/gitlab/auth.rb | 74 +++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 0e8559022c6..a792db027ff 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,21 +1,29 @@ module Gitlab module Auth - Result = Struct.new(:user, :project, :type, :capabilities) + class Result + attr_reader :user, :project, :type, :capabilities + + def initialize?(user = nil, project = nil, type = nil, capabilities = nil) + @user, @project, @type, @capabilities = user, project, type, capabilities + end + + def success? + user.present? || [:ci, :missing_personal_token].include?(type) + end + end class << self def find_for_git_client(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? - result = Result.new - - if valid_ci_request?(login, password, project) - result = Result.new(nil, project, :ci, restricted_capabilities) - else - result = populate_result(login, password) - end + result = service_access_token_check(login, password, project) || + build_access_token_check(login, password) || + user_with_password_for_git(login, password) || + oauth_access_token_check(login, password) || + personal_access_token_check(login, password) || + Result.new - success = result.user.present? || [:ci, :missing_personal_token].include?(result.type) - rate_limit!(ip, success: success, login: login) + rate_limit!(ip, success: result.success?, login: login) result end @@ -57,10 +65,10 @@ module Gitlab private - def valid_ci_request?(login, password, project) + def service_access_token_check(login, password, project) matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) - return false unless project && matched_login.present? + return unless project && matched_login.present? underscored_service = matched_login['service'].underscore @@ -69,31 +77,24 @@ module Gitlab # in the Service.available_services_names whitelist. service = project.public_send("#{underscored_service}_service") - service && service.activated? && service.valid_token?(password) - end - end - - def populate_result(login, password) - result = - build_access_token_check(login, password) || - user_with_password_for_git(login, password) || - oauth_access_token_check(login, password) || - personal_access_token_check(login, password) - - if result - result.type = nil unless result.capabilities - - if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap - result.type = :missing_personal_token + if service && service.activated? && service.valid_token?(password) + Result.new(nil, project, :ci, restricted_capabilities) end end - - result || Result.new end def user_with_password_for_git(login, password) user = find_with_user_password(login, password) - Result.new(user, :gitlab_or_ldap, nil, full_capabilities) if user + return unless user + + type = + if user.two_factor_enabled? + :missing_personal_token + else + :gitlab_or_ldap + end + + Result.new(user, type, nil, full_capabilities) end def oauth_access_token_check(login, password) @@ -101,7 +102,7 @@ module Gitlab token = Doorkeeper::AccessToken.by_token(password) if token && token.accessible? user = User.find_by(id: token.resource_owner_id) - Result.new(user, nil, :oauth, full_capabilities) + Result.new(user, nil, :oauth, read_capabilities) end end end @@ -140,11 +141,16 @@ module Gitlab ] end - def full_capabilities + def read_capabilities restricted_capabilities + [ :download_code, + :read_container_image + ] + end + + def full_capabilities + read_capabilities + [ :push_code, - :read_container_image, :update_container_image ] end -- cgit v1.2.3 From 2f3dc314f42dbd79813e6251792853bc231e69dd Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 16:57:36 +0200 Subject: Fix spec failures --- app/views/projects/ci/builds/_build_pipeline.html.haml | 2 -- app/views/projects/commit/_pipeline_stage.html.haml | 2 +- app/views/projects/commit/_pipeline_status_group.html.haml | 4 ++-- .../_generic_commit_status_pipeline.html.haml | 2 +- spec/views/projects/pipelines/show.html.haml_spec.rb | 13 ++++++------- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index d19e193c432..547bc0c9c19 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -10,5 +10,3 @@ - else = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) - - diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 368564481c4..23c5c51fbc2 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -3,7 +3,7 @@ - if grouped_statuses.one? - status = grouped_statuses.first - is_playable = status.playable? && can?(current_user, :update_build, @project) - %li.build{class: ("playable" if is_playable)} + %li.build{ class: ("playable" if is_playable) } .curve .build-content = render "projects/#{status.to_partial_path}_pipeline", subject: status diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 03b6249963b..4e7a6f1af08 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,10 +1,10 @@ - group_status = CommitStatus.where(id: subject).status = render_status_with_link('build', group_status) .dropdown.inline - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %span.ci-status-text = name - %span.badge= subject.length + %span.badge= subject.size %ul.dropdown-menu.grouped-pipeline-dropdown .arrow - subject.each do |status| diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 9b54c2bc3af..409f4701e4b 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,5 +1,5 @@ - if subject.target_url - - link_to subject.target_url do + = link_to subject.target_url do = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name - else diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb index 920d7528892..c5b16c1c304 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -4,15 +4,14 @@ describe 'projects/pipelines/show' do include Devise::TestHelpers let(:project) { create(:project) } - let(:pipeline) do - create(:ci_empty_pipeline, project: project, - sha: project.commit.id) - end + let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) } before do + controller.prepend_view_path('app/views/projects') + create_build('build', 0, 'build') - create_build('test', 1, 'rspec 0 2') - create_build('test', 1, 'rspec 1 2') + create_build('test', 1, 'rspec 0:2') + create_build('test', 1, 'rspec 1:2') create_build('test', 1, 'audit') create_build('deploy', 2, 'production') @@ -38,7 +37,7 @@ describe 'projects/pipelines/show' do # builds expect(rendered).to have_text('rspec') - expect(rendered).to have_text('rspec 0:1') + expect(rendered).to have_text('rspec 0:2') expect(rendered).to have_text('production') expect(rendered).to have_text('jenkins') end -- cgit v1.2.3 From cbf478856e0858e9155c2d657a3fc1281a009294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 14 Sep 2016 18:41:27 +0200 Subject: Update CHANGELOG for 8.9.9 and 8.11.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- CHANGELOG | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9d813c9e35f..fef22f1fe9c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -135,9 +135,15 @@ v 8.12.0 (unreleased) - Fix Gitlab::Popen.popen thread-safety issue - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) -v 8.11.6 (unreleased) - - Fix an error where we were unable to create a CommitStatus for running state +v 8.11.6 + - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 + - Make merge conflict file size limit 200 KB, to match the docs. !6052 + - Fix an error where we were unable to create a CommitStatus for running state. !6107 + - Optimize discussion notes resolving and unresolving. !6141 + - Fix GitLab import button. !6167 - Restore SSH Key title auto-population behavior. !6186 + - Fix DB schema to match latest migration. !6256 + - Exclude some pending or inactivated rows in Member scopes. v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 @@ -567,6 +573,9 @@ v 8.10.0 - Fix migration corrupting import data for old version upgrades - Show tooltip on GitLab export link in new project page +v 8.9.9 + - Exclude some pending or inactivated rows in Member scopes + v 8.9.8 - Upgrade Doorkeeper to 4.2.0. !5881 -- cgit v1.2.3 From bf6f9f34e9052eca8bce238ae91e7d7172b216eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 14 Sep 2016 18:50:51 +0200 Subject: Add missing 8.10.9 CHANGELOG entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index fef22f1fe9c..9af2528a2bc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -348,6 +348,13 @@ v 8.11.0 - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done +v 8.10.9 + - Exclude some pending or inactivated rows in Member scopes + +v 8.10.8 + - Fix information disclosure in issue boards. + - Fix privilege escalation in project import. + v 8.10.7 - Upgrade Hamlit to 2.6.1. !5873 - Upgrade Doorkeeper to 4.2.0. !5881 -- cgit v1.2.3 From c1a990082f9f2e0c612deb6fd481021f72065cae Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Sep 2016 15:49:32 -0400 Subject: Bump rubocop to 0.42.0 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 620338e5997..bc6ee6eb1e7 100644 --- a/Gemfile +++ b/Gemfile @@ -295,7 +295,7 @@ group :development, :test do gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-teaspoon', '~> 0.0.2' - gem 'rubocop', '~> 0.41.2', require: false + gem 'rubocop', '~> 0.42.0', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'scss_lint', '~> 0.47.0', require: false gem 'simplecov', '0.12.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 28ede86b3ba..3a1ce0740d7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -612,7 +612,7 @@ GEM rspec-retry (0.4.5) rspec-core rspec-support (3.5.0) - rubocop (0.41.2) + rubocop (0.42.0) parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) @@ -754,7 +754,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.2) - unicode-display_width (1.1.0) + unicode-display_width (1.1.1) unicorn (4.9.0) kgio (~> 2.6) rack @@ -935,7 +935,7 @@ DEPENDENCIES rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.5.0) rspec-retry (~> 0.4.5) - rubocop (~> 0.41.2) + rubocop (~> 0.42.0) rubocop-rspec (~> 1.5.0) ruby-fogbugz (~> 0.2.1) ruby-prof (~> 0.15.9) -- cgit v1.2.3 From 287293bec1a1e6fc4ffbe84e05ddddf5f48bd8ab Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Sep 2016 15:50:11 -0400 Subject: Regenerate .rubocop_todo.yml based on rubocop 0.42.0 --- .rubocop_todo.yml | 125 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 72 insertions(+), 53 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 20daf1619a7..87520c67dd5 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,21 +1,21 @@ # This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 0` -# on 2016-07-13 12:36:08 -0600 using RuboCop version 0.41.2. +# on 2016-09-14 15:44:53 -0400 using RuboCop version 0.42.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 154 +# Offense count: 158 Lint/AmbiguousRegexpLiteral: Enabled: false -# Offense count: 43 +# Offense count: 41 # Configuration parameters: AllowSafeAssignment. Lint/AssignmentInCondition: Enabled: false -# Offense count: 14 +# Offense count: 16 Lint/HandleExceptions: Enabled: false @@ -23,28 +23,28 @@ Lint/HandleExceptions: Lint/Loop: Enabled: false -# Offense count: 15 +# Offense count: 16 Lint/ShadowingOuterLocalVariable: Enabled: false -# Offense count: 3 +# Offense count: 6 # Cop supports --auto-correct. Lint/StringConversionInInterpolation: Enabled: false -# Offense count: 44 +# Offense count: 49 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Enabled: false -# Offense count: 129 +# Offense count: 144 # Cop supports --auto-correct. # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. Lint/UnusedMethodArgument: Enabled: false -# Offense count: 12 +# Offense count: 9 # Cop supports --auto-correct. Performance/PushSplat: Enabled: false @@ -59,51 +59,51 @@ Performance/RedundantBlockCall: Performance/RedundantMatch: Enabled: false -# Offense count: 24 +# Offense count: 27 # Cop supports --auto-correct. # Configuration parameters: MaxKeyValuePairs. Performance/RedundantMerge: Enabled: false -# Offense count: 60 +# Offense count: 61 Rails/OutputSafety: Enabled: false -# Offense count: 128 +# Offense count: 129 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: strict, flexible Rails/TimeZone: Enabled: false -# Offense count: 12 +# Offense count: 15 # Cop supports --auto-correct. # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/Validation: Enabled: false -# Offense count: 217 +# Offense count: 273 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: with_first_parameter, with_fixed_indentation Style/AlignParameters: Enabled: false -# Offense count: 32 +# Offense count: 30 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: always, conditionals Style/AndOr: Enabled: false -# Offense count: 47 +# Offense count: 50 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: percent_q, bare_percent Style/BarePercentLiterals: Enabled: false -# Offense count: 258 +# Offense count: 289 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: braces, no_braces, context_dependent @@ -126,14 +126,14 @@ Style/ColonMethodCall: Style/CommentAnnotation: Enabled: false -# Offense count: 34 +# Offense count: 33 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Enabled: false -# Offense count: 789 +# Offense count: 881 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: leading, trailing @@ -144,11 +144,12 @@ Style/DotPosition: Style/DoubleNegation: Enabled: false -# Offense count: 3 +# Offense count: 4 +# Cop supports --auto-correct. Style/EachWithObject: Enabled: false -# Offense count: 30 +# Offense count: 25 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: empty, nil, both @@ -160,7 +161,7 @@ Style/EmptyElse: Style/EmptyLiteral: Enabled: false -# Offense count: 123 +# Offense count: 135 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. Style/ExtraSpacing: @@ -172,16 +173,16 @@ Style/ExtraSpacing: Style/FormatString: Enabled: false -# Offense count: 48 +# Offense count: 51 # Configuration parameters: MinBodyLength. Style/GuardClause: Enabled: false -# Offense count: 11 +# Offense count: 9 Style/IfInsideElse: Enabled: false -# Offense count: 177 +# Offense count: 174 # Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: @@ -194,7 +195,7 @@ Style/IfUnlessModifier: Style/IndentArray: Enabled: false -# Offense count: 89 +# Offense count: 97 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces @@ -208,7 +209,7 @@ Style/IndentHash: Style/Lambda: Enabled: false -# Offense count: 6 +# Offense count: 5 # Cop supports --auto-correct. Style/LineEndConcatenation: Enabled: false @@ -218,17 +219,21 @@ Style/LineEndConcatenation: Style/MethodCallParentheses: Enabled: false -# Offense count: 62 +# Offense count: 8 +Style/MethodMissing: + Enabled: false + +# Offense count: 85 # Cop supports --auto-correct. Style/MutableConstant: Enabled: false -# Offense count: 10 +# Offense count: 8 # Cop supports --auto-correct. Style/NestedParenthesizedCalls: Enabled: false -# Offense count: 12 +# Offense count: 13 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. # SupportedStyles: skip_modifier_ifs, always @@ -242,12 +247,19 @@ Style/Next: Style/NumericLiteralPrefix: Enabled: false +# Offense count: 64 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Enabled: false + # Offense count: 29 # Cop supports --auto-correct. Style/ParallelAssignment: Enabled: false -# Offense count: 208 +# Offense count: 264 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: @@ -265,7 +277,7 @@ Style/PercentQLiterals: Style/PerlBackrefs: Enabled: false -# Offense count: 32 +# Offense count: 35 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. # NamePrefix: is_, has_, have_ # NamePrefixBlacklist: is_, has_, have_ @@ -273,7 +285,7 @@ Style/PerlBackrefs: Style/PredicateName: Enabled: false -# Offense count: 28 +# Offense count: 27 # Cop supports --auto-correct. Style/PreferredHashMethods: Enabled: false @@ -283,14 +295,14 @@ Style/PreferredHashMethods: Style/Proc: Enabled: false -# Offense count: 20 +# Offense count: 22 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: compact, exploded Style/RaiseArgs: Enabled: false -# Offense count: 3 +# Offense count: 4 # Cop supports --auto-correct. Style/RedundantBegin: Enabled: false @@ -300,29 +312,29 @@ Style/RedundantBegin: Style/RedundantException: Enabled: false -# Offense count: 23 +# Offense count: 24 # Cop supports --auto-correct. Style/RedundantFreeze: Enabled: false -# Offense count: 377 +# Offense count: 408 # Cop supports --auto-correct. Style/RedundantSelf: Enabled: false -# Offense count: 94 +# Offense count: 93 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed Style/RegexpLiteral: Enabled: false -# Offense count: 17 +# Offense count: 18 # Cop supports --auto-correct. Style/RescueModifier: Enabled: false -# Offense count: 2 +# Offense count: 5 # Cop supports --auto-correct. Style/SelfAssignment: Enabled: false @@ -339,42 +351,42 @@ Style/SingleLineBlockParams: Style/SingleLineMethods: Enabled: false -# Offense count: 119 +# Offense count: 124 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: space, no_space Style/SpaceBeforeBlockBraces: Enabled: false -# Offense count: 11 +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment. Style/SpaceBeforeFirstArg: Enabled: false -# Offense count: 130 +# Offense count: 141 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space Style/SpaceInsideBlockBraces: Enabled: false -# Offense count: 98 +# Offense count: 96 # Cop supports --auto-correct. Style/SpaceInsideBrackets: Enabled: false -# Offense count: 60 +# Offense count: 62 # Cop supports --auto-correct. Style/SpaceInsideParens: Enabled: false -# Offense count: 5 +# Offense count: 7 # Cop supports --auto-correct. Style/SpaceInsidePercentLiteralDelimiters: Enabled: false -# Offense count: 36 +# Offense count: 40 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles. # SupportedStyles: use_perl_names, use_english_names @@ -388,21 +400,28 @@ Style/SpecialGlobalVars: Style/StringLiteralsInInterpolation: Enabled: false -# Offense count: 24 +# Offense count: 32 # Cop supports --auto-correct. # Configuration parameters: IgnoredMethods. # IgnoredMethods: respond_to, define_method Style/SymbolProc: Enabled: false -# Offense count: 23 +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment. +# SupportedStyles: require_parentheses, require_no_parentheses +Style/TernaryParentheses: + Enabled: false + +# Offense count: 24 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. # SupportedStyles: comma, consistent_comma, no_comma Style/TrailingCommaInArguments: Enabled: false -# Offense count: 113 +# Offense count: 102 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. # SupportedStyles: comma, consistent_comma, no_comma @@ -415,7 +434,7 @@ Style/TrailingCommaInLiteral: Style/TrailingUnderscoreVariable: Enabled: false -# Offense count: 90 +# Offense count: 76 # Cop supports --auto-correct. Style/TrailingWhitespace: Enabled: false @@ -427,12 +446,12 @@ Style/TrailingWhitespace: Style/TrivialAccessors: Enabled: false -# Offense count: 3 +# Offense count: 2 # Cop supports --auto-correct. Style/UnlessElse: Enabled: false -# Offense count: 13 +# Offense count: 14 # Cop supports --auto-correct. Style/UnneededInterpolation: Enabled: false -- cgit v1.2.3 From e7e628bf959b77d5a58471d224ebd18c4e97032d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Sep 2016 16:50:15 -0400 Subject: Improve validity of spec/features/todos/todos_filtering_spec.rb Previously, we were checking that a CSS selector string didn't have some page-related content, which, of course it didn't. Now we check against the actual content of the selector, we use a more semantic selector, and we add an additional expectation for the text that _should_ be there, as an additional sanity check. --- spec/features/todos/todos_filtering_spec.rb | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb index 83cf306437d..b9e66243d84 100644 --- a/spec/features/todos/todos_filtering_spec.rb +++ b/spec/features/todos/todos_filtering_spec.rb @@ -29,8 +29,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do fill_in 'Search projects', with: project_1.name_with_namespace click_link project_1.name_with_namespace end + wait_for_ajax - expect('.prepend-top-default').not_to have_content project_2.name_with_namespace + + expect(page).to have_content project_1.name_with_namespace + expect(page).not_to have_content project_2.name_with_namespace end it 'filters by author' do @@ -39,8 +42,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do fill_in 'Search authors', with: user_1.name click_link user_1.name end + wait_for_ajax - expect('.prepend-top-default').not_to have_content user_2.name + + expect(find('.todos-list')).to have_content user_1.name + expect(find('.todos-list')).not_to have_content user_2.name end it 'filters by type' do @@ -48,8 +54,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do within '.dropdown-menu-type' do click_link 'Issue' end + wait_for_ajax - expect('.prepend-top-default').not_to have_content ' merge request !' + + expect(find('.todos-list')).to have_content issue.to_reference + expect(find('.todos-list')).not_to have_content merge_request.to_reference end it 'filters by action' do @@ -57,7 +66,10 @@ describe 'Dashboard > User filters todos', feature: true, js: true do within '.dropdown-menu-action' do click_link 'Assigned' end + wait_for_ajax - expect('.prepend-top-default').not_to have_content ' mentioned ' + + expect(find('.todos-list')).to have_content ' assigned you ' + expect(find('.todos-list')).not_to have_content ' mentioned ' end end -- cgit v1.2.3 From a3a83e4f071a735dd89c93d4d7e4c0622e391897 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 14 Sep 2016 17:11:33 -0700 Subject: Ignore SVG whitelist code from flay --- .flayignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.flayignore b/.flayignore index 9c9875d4f9e..f120de527bd 100644 --- a/.flayignore +++ b/.flayignore @@ -1 +1,2 @@ *.erb +lib/gitlab/sanitizers/svg/whitelist.rb -- cgit v1.2.3 From 02bc717f3423b2eb84328f4d908862db10ec76ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 14 Sep 2016 15:15:28 -0300 Subject: Update references to deprecated `repos_path` configuration key to avoid errors on updates from older versions --- db/migrate/20140502125220_migrate_repo_size.rb | 5 ++++- lib/gitlab/import_export/repo_restorer.rb | 4 ---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb index 84463727b3b..e8de7ccf3db 100644 --- a/db/migrate/20140502125220_migrate_repo_size.rb +++ b/db/migrate/20140502125220_migrate_repo_size.rb @@ -1,12 +1,15 @@ # rubocop:disable all class MigrateRepoSize < ActiveRecord::Migration + DOWNTIME = false + def up project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id') project_data.each do |project| id = project['id'] namespace_path = project['namespace_path'] || '' - path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git') + repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default + path = File.join(repos_path, namespace_path, project['project_path'] + '.git') begin repo = Gitlab::Git::Repository.new(path) diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb index 6d9379acf25..d1e33ea8678 100644 --- a/lib/gitlab/import_export/repo_restorer.rb +++ b/lib/gitlab/import_export/repo_restorer.rb @@ -22,10 +22,6 @@ module Gitlab private - def repos_path - Gitlab.config.gitlab_shell.repos_path - end - def path_to_repo @project.repository.path_to_repo end -- cgit v1.2.3 From d22918a1f017d6aa4af6a682c7266c95d909d9f9 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 14 Sep 2016 17:02:22 -0700 Subject: Allow flay and flog to fail for now --- .gitlab-ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b5eef681d0e..b167fc74996 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -209,8 +209,12 @@ rubocop: *exec rake haml_lint: *exec rake scss_lint: *exec rake brakeman: *exec -rake flog: *exec -rake flay: *exec +rake flog: + <<: *exec + allow_failure: yes +rake flay: + <<: *exec + allow_failure: yes license_finder: *exec rake downtime_check: *exec -- cgit v1.2.3 From 718572245724b20126a87bfb9653a566393548ec Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 14 Sep 2016 16:15:26 +0300 Subject: Clean environment variables when running git hooks --- CHANGELOG | 1 + lib/gitlab/git/hook.rb | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 35ef11bb30c..e410d73d1f6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -135,6 +135,7 @@ v 8.12.0 (unreleased) - Return 204 instead of 404 for /ci/api/v1/builds/register.json if no builds are scheduled for a runner !6225 - Fix Gitlab::Popen.popen thread-safety issue - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) + - Clean environment variables when running git hooks v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index 9b681e636c7..bd90d24a2ec 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -17,11 +17,13 @@ module Gitlab def trigger(gl_id, oldrev, newrev, ref) return [true, nil] unless exists? - case name - when "pre-receive", "post-receive" - call_receive_hook(gl_id, oldrev, newrev, ref) - when "update" - call_update_hook(gl_id, oldrev, newrev, ref) + Bundler.with_clean_env do + case name + when "pre-receive", "post-receive" + call_receive_hook(gl_id, oldrev, newrev, ref) + when "update" + call_update_hook(gl_id, oldrev, newrev, ref) + end end end -- cgit v1.2.3 From d9e86e796a42efb5f60eff61907ce4b7834295a4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 14 Sep 2016 19:57:31 +0100 Subject: Simplified the CSS for issue boards It gained a lot of extra cruft through the different improvements etc. This removes a lot of the stuff which would hopefully also speed up the browser rendering --- app/assets/stylesheets/pages/boards.scss | 90 +++++++------------------------- 1 file changed, 19 insertions(+), 71 deletions(-) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 037278bb083..f8659489ddf 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -18,6 +18,10 @@ } } +.is-ghost { + opacity: 0.3; +} + .dropdown-menu-issues-board-new { width: 320px; @@ -34,47 +38,13 @@ > p { margin: 0; font-size: 14px; - color: #9c9c9c; } } .issue-boards-page { - .content-wrapper { - display: -webkit-flex; - display: flex; - -webkit-flex-direction: column; - flex-direction: column; - } - - .sub-nav, - .issues-filters { - -webkit-flex: none; - flex: none; - } - .page-with-sidebar { - display: -webkit-flex; - display: flex; - min-height: 100vh; - max-height: 100vh; padding-bottom: 0; } - - .issue-boards-content { - display: -webkit-flex; - display: flex; - -webkit-flex: 1; - flex: 1; - width: 100%; - - .content { - display: -webkit-flex; - display: flex; - -webkit-flex-direction: column; - flex-direction: column; - width: 100%; - } - } } .boards-app-loading { @@ -83,37 +53,29 @@ } .boards-list { - display: -webkit-flex; - display: flex; - -webkit-flex: 1; - flex: 1; - -webkit-flex-basis: 0; - flex-basis: 0; - min-height: calc(100vh - 152px); - max-height: calc(100vh - 152px); + height: calc(100vh - 152px); padding-top: 25px; + padding-bottom: 25px; padding-right: ($gl-padding / 2); padding-left: ($gl-padding / 2); overflow-x: scroll; + white-space: nowrap; @media (min-width: $screen-sm-min) { - min-height: 475px; - max-height: none; + height: calc(100vh - 220px); } } .board { - display: -webkit-flex; - display: flex; - min-width: calc(85vw - 15px); - max-width: calc(85vw - 15px); - margin-bottom: 25px; + display: inline-block; + width: calc(85vw - 15px); + height: 100%; padding-right: ($gl-padding / 2); padding-left: ($gl-padding / 2); + white-space: normal; @media (min-width: $screen-sm-min) { - min-width: 400px; - max-width: 400px; + width: 400px; } } @@ -123,6 +85,7 @@ -webkit-flex-direction: column; flex-direction: column; width: 100%; + height: 100%; font-size: $issue-boards-font-size; background: $background-color; border: 1px solid $border-color; @@ -193,45 +156,31 @@ } .board-list { - -webkit-flex: 1; - flex: 1; - height: 400px; + height: 100%; margin-bottom: 0; padding: 5px; + list-style: none; overflow-y: scroll; overflow-x: hidden; } .board-list-loading { margin-top: 10px; - font-size: 26px; -} - -.is-ghost { - opacity: 0.3; + font-size: (26px / $issue-boards-font-size) * 1em; } .card { position: relative; - width: 100%; padding: 10px $gl-padding; background: #fff; border-radius: $border-radius-default; box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5); list-style: none; - &.user-can-drag { - padding-left: $gl-padding; - } - &:not(:last-child) { margin-bottom: 5px; } - a { - cursor: pointer; - } - .label { border: 0; outline: 0; @@ -256,14 +205,13 @@ line-height: 25px; .label { - margin-right: 4px; + margin-right: 5px; font-size: (14px / $issue-boards-font-size) * 1em; } } .card-number { - margin-right: 8px; - font-weight: 500; + margin-right: 5px; } .issue-boards-search { -- cgit v1.2.3 From c65e903a9e7d90be3db072fb9d9fd2e4d6c76a74 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 14 Sep 2016 20:05:52 +0100 Subject: Fixed height issue on smaller screens Fixed IE width issue --- app/assets/stylesheets/pages/boards.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index f8659489ddf..3df7976dda4 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -54,6 +54,7 @@ .boards-list { height: calc(100vh - 152px); + width: 100%; padding-top: 25px; padding-bottom: 25px; padding-right: ($gl-padding / 2); @@ -63,6 +64,7 @@ @media (min-width: $screen-sm-min) { height: calc(100vh - 220px); + min-height: 475px; } } -- cgit v1.2.3 From 09d949ce89c208165697c5af8e4d1d9cfe397f86 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 15 Sep 2016 09:07:29 +0100 Subject: Fixed styling issue with PhantomJS causing builds to fail --- app/assets/stylesheets/pages/boards.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 3df7976dda4..ff6b8636b97 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -63,6 +63,7 @@ white-space: nowrap; @media (min-width: $screen-sm-min) { + height: 475px; // Needed for PhantomJS height: calc(100vh - 220px); min-height: 475px; } @@ -86,7 +87,6 @@ display: flex; -webkit-flex-direction: column; flex-direction: column; - width: 100%; height: 100%; font-size: $issue-boards-font-size; background: $background-color; -- cgit v1.2.3 From 6b381f3fdf00c7eeb971f365bde2a41f0cecf944 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 10:34:53 +0200 Subject: Use `build_read_container_image` and use `build_download_code` --- app/controllers/jwt_controller.rb | 18 ++++------- app/helpers/lfs_helper.rb | 8 ++--- app/policies/project_policy.rb | 18 ++++++----- .../container_registry_authentication_service.rb | 35 +++++++++++----------- lib/gitlab/auth.rb | 16 +++++----- lib/gitlab/git_access.rb | 8 ++--- 6 files changed, 50 insertions(+), 53 deletions(-) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 1b075cc5e2d..7bf534d8732 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -11,7 +11,10 @@ class JwtController < ApplicationController service = SERVICES[params[:service]] return head :not_found unless service - result = service.new(@project, @user, auth_params).execute(capabilities: @capabilities) + @@authentication_result ||= Gitlab::Auth.Result.new + + result = service.new(@authentication_result.project, @authentication_result.user, auth_params). + execute(capabilities: @authentication_result.capabilities || []) render json: result, status: result[:http_status] end @@ -20,18 +23,9 @@ class JwtController < ApplicationController def authenticate_project_or_user authenticate_with_http_basic do |login, password| - @auth_result = Gitlab::Auth.find_for_git_client(login, password, ip: request.ip) - - @user = auth_result.user - @project = auth_result.project - @type = auth_result.type - @capabilities = auth_result.capabilities || [] - - if @user || @project - return # Allow access - end + @authentication_result = Gitlab::Auth.find_for_git_client(login, password, ip: request.ip) - render_403 + render_403 unless @authentication_result.success? end end diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index bee03ffb446..a2359d94443 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -25,15 +25,15 @@ module LfsHelper def lfs_download_access? return false unless project.lfs_enabled? - project.public? || ci? || privileged_user_can_download_code? || restricted_user_can_download_code? + project.public? || ci? || user_can_download_code? || build_can_download_code? end - def privileged_user_can_download_code? + def user_can_download_code? has_capability?(:download_code) && user && user.can?(:download_code, project) end - def restricted_user_can_download_code? - has_capability?(:restricted_download_code) && user && user.can?(:restricted_download_code, project) + def build_can_download_code? + has_capability?(:build_download_code) && user && user.can?(:build_download_code, project) end def lfs_upload_access? diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index cda83bcc74a..ce686af2ade 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -65,9 +65,9 @@ class ProjectPolicy < BasePolicy end # Permissions given when an user is direct member of a group - def restricted_reporter_access! - can! :restricted_download_code - can! :restricted_read_container_image + def team_member_reporter_access! + can! :build_download_code + can! :build_read_container_image end def developer_access! @@ -115,6 +115,8 @@ class ProjectPolicy < BasePolicy can! :read_commit_status can! :read_pipeline can! :read_container_image + can! :build_download_code + can! :build_read_container_image end def owner_access! @@ -136,11 +138,11 @@ class ProjectPolicy < BasePolicy def team_access!(user) access = project.team.max_member_access(user.id) - guest_access! if access >= Gitlab::Access::GUEST - reporter_access! if access >= Gitlab::Access::REPORTER - restricted_reporter_access! if access >= Gitlab::Access::REPORTER - developer_access! if access >= Gitlab::Access::DEVELOPER - master_access! if access >= Gitlab::Access::MASTER + guest_access! if access >= Gitlab::Access::GUEST + reporter_access! if access >= Gitlab::Access::REPORTER + team_member_reporter_access! if access >= Gitlab::Access::REPORTER + developer_access! if access >= Gitlab::Access::DEVELOPER + master_access! if access >= Gitlab::Access::MASTER end def archived_access! diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index cba0e2297a8..ba0b60abfe4 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -76,9 +76,9 @@ module Auth case requested_action when 'pull' - restricted_user_can_pull?(requested_project) || privileged_user_can_pull?(requested_project) + build_can_pull?(requested_project) || user_can_pull?(requested_project) when 'push' - restricted_user_can_push?(requested_project) || privileged_user_can_push?(requested_project) + build_can_push?(requested_project) || user_can_push?(requested_project) else false end @@ -90,29 +90,28 @@ module Auth private - def restricted_user_can_pull?(requested_project) - # Restricted can: + def build_can_pull?(requested_project) + # Build can: # 1. pull from it's own project (for ex. a build) - # 2. read images from dependent projects if he is a team member - requested_project == project || - has_ability?(:restricted_read_container_image, requested_project) + # 2. read images from dependent projects if creator of build is a team member + @capabilities.include?(:build_read_container_image) && + (requested_project == project || can?(current_user, :build_read_container_image, requested_project)) end - def privileged_user_can_pull?(requested_project) - has_ability?(:read_container_image, requested_project) + def user_can_pull?(requested_project) + @capabilities.include?(:read_container_image) && + can?(current_user, :read_container_image, requested_project) end - def restricted_user_can_push?(requested_project) - # Restricted can push only to project to from which he originates - requested_project == project + def build_can_push?(requested_project) + # Build can push only to project to from which he originates + @capabilities.include?(:build_create_container_image) && + requested_project == project end - def privileged_user_can_push?(requested_project) - has_ability?(:create_container_image, requested_project) - end - - def has_ability?(ability, requested_project) - @capabilities.include?(ability) && can?(current_user, ability, requested_project) + def user_can_push?(requested_project) + @capabilities.include?(:create_container_image) && + can?(current_user, :create_container_image, requested_project) end end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index a792db027ff..6a55c50c3f3 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -78,7 +78,7 @@ module Gitlab service = project.public_send("#{underscored_service}_service") if service && service.activated? && service.valid_token?(password) - Result.new(nil, project, :ci, restricted_capabilities) + Result.new(nil, project, :ci, build_capabilities) end end end @@ -124,25 +124,27 @@ module Gitlab if build.user # If user is assigned to build, use restricted credentials of user - Result.new(build.user, build.project, :build, restricted_capabilities) + Result.new(build.user, build.project, :build, build_capabilities) else # Otherwise use generic CI credentials (backward compatibility) - Result.new(nil, build.project, :ci, restricted_capabilities) + Result.new(nil, build.project, :ci, build_capabilities) end end private - def restricted_capabilities + def build_capabilities [ :read_project, - :restricted_download_code, - :restricted_read_container_image + :build_download_code, + :build_read_container_image, + :build_create_container_image ] end def read_capabilities - restricted_capabilities + [ + [ + :read_project, :download_code, :read_container_image ] diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 10ef4a1e3cf..63b707db814 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -61,19 +61,19 @@ module Gitlab end def user_download_access_check - unless privileged_user_can_download_code? || restricted_user_can_download_code? + unless user_can_download_code? || build_can_download_code? return build_status_object(false, "You are not allowed to download code from this project.") end build_status_object(true) end - def privileged_user_can_download_code? + def user_can_download_code? capabilities.include?(:download_code) && user_access.can_do_action?(:download_code) end - def restricted_user_can_download_code? - capabilities.include?(:restricted_download_code) && user_access.can_do_action?(:restricted_download_code) + def build_can_download_code? + capabilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code) end def user_push_access_check(changes) -- cgit v1.2.3 From 11f87700e8bceeec96440809682406ae24334ed8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 11:57:09 +0200 Subject: Add access specs --- app/controllers/jwt_controller.rb | 2 +- lib/api/internal.rb | 14 ++++- lib/gitlab/auth.rb | 20 +++----- spec/lib/gitlab/auth_spec.rb | 76 +++++++++++++++++++++++++--- spec/lib/gitlab/git_access_spec.rb | 101 ++++++++++++++++++++++++++++--------- 5 files changed, 168 insertions(+), 45 deletions(-) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 7bf534d8732..ed5d28e0d2c 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -25,7 +25,7 @@ class JwtController < ApplicationController authenticate_with_http_basic do |login, password| @authentication_result = Gitlab::Auth.find_for_git_client(login, password, ip: request.ip) - render_403 unless @authentication_result.success? + render_403 unless @authentication_result.succeeded? end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 6e6efece7c4..2ec94570506 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -51,9 +51,9 @@ module API access = if wiki? - Gitlab::GitAccessWiki.new(actor, project, protocol) + Gitlab::GitAccessWiki.new(actor, project, protocol, capabilities: ssh_capabilities) else - Gitlab::GitAccess.new(actor, project, protocol) + Gitlab::GitAccess.new(actor, project, protocol, capabilities: ssh_capabilities) end access_status = access.check(params[:action], params[:changes]) @@ -130,6 +130,16 @@ module API { success: true, recovery_codes: codes } end + + private + + def ssh_capabilities + [ + :read_project, + :download_code, + :push_code + ] + end end end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 6a55c50c3f3..7af9bb9a326 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,14 +1,8 @@ module Gitlab module Auth - class Result - attr_reader :user, :project, :type, :capabilities - - def initialize?(user = nil, project = nil, type = nil, capabilities = nil) - @user, @project, @type, @capabilities = user, project, type, capabilities - end - - def success? - user.present? || [:ci, :missing_personal_token].include?(type) + Result = Struct.new(:user, :project, :type, :capabilities) do + def succeeded? + user.present? || [:ci].include?(type) end end @@ -23,7 +17,7 @@ module Gitlab personal_access_token_check(login, password) || Result.new - rate_limit!(ip, success: result.success?, login: login) + rate_limit!(ip, success: result.succeeded?, login: login) result end @@ -94,7 +88,7 @@ module Gitlab :gitlab_or_ldap end - Result.new(user, type, nil, full_capabilities) + Result.new(user, nil, type, full_capabilities) end def oauth_access_token_check(login, password) @@ -111,7 +105,9 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, nil, :personal_token, full_capabilities) if user == validation + if user && user == validation + Result.new(user, nil, :personal_token, full_capabilities) + end end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 7c23e02d05a..b665517bbb0 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -4,15 +4,51 @@ describe Gitlab::Auth, lib: true do let(:gl_auth) { described_class } describe 'find_for_git_client' do - it 'recognizes CI' do - token = '123' + context 'build token' do + subject { gl_auth.find_for_git_client('gitlab-ci-token', build.token, project: project, ip: 'ip') } + + context 'for running build' do + let!(:build) { create(:ci_build, :running) } + let(:project) { build.project } + + before do + expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token') + end + + it 'recognises user-less build' do + expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_capabilities)) + end + + it 'recognises user token' do + build.update(user: create(:user)) + + expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_capabilities)) + end + end + + context 'for non-running build' do + let!(:build) { create(:ci_build, :pending) } + let(:project) { build.project } + + before do + expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token') + end + + it 'denies authentication' do + expect(subject).to eq(Gitlab::Auth::Result.new) + end + end + end + + it 'recognizes other ci services' do project = create(:empty_project) - project.update_attributes(runners_token: token) + project.create_drone_ci_service(active: true) + project.drone_ci_service.update(token: 'token') ip = 'ip' - expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token') - expect(gl_auth.find_for_git_client('gitlab-ci-token', token, project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, :ci)) + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'drone-ci-token') + expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_capabilities)) end it 'recognizes master passwords' do @@ -20,7 +56,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap)) + expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_capabilities)) end it 'recognizes OAuth tokens' do @@ -30,7 +66,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2') - expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :oauth)) + expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_capabilities)) end it 'returns double nil for invalid credentials' do @@ -92,4 +128,30 @@ describe Gitlab::Auth, lib: true do end end end + + private + + def build_capabilities + [ + :read_project, + :build_download_code, + :build_read_container_image, + :build_create_container_image + ] + end + + def read_capabilities + [ + :read_project, + :download_code, + :read_container_image + ] + end + + def full_capabilities + read_capabilities + [ + :push_code, + :update_container_image + ] + end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index f12c9a370f7..77dce676cdb 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,10 +1,17 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do - let(:access) { Gitlab::GitAccess.new(actor, project, 'web') } + let(:access) { Gitlab::GitAccess.new(actor, project, 'web', capabilities: capabilities) } let(:project) { create(:project) } let(:user) { create(:user) } let(:actor) { user } + let(:capabilities) do + [ + :read_project, + :download_code, + :push_code + ] + end describe '#check with single protocols allowed' do def disable_protocol(protocol) @@ -111,6 +118,36 @@ describe Gitlab::GitAccess, lib: true do end end end + + describe 'build capabilities permissions' do + let(:capabilities) { build_capabilities } + + describe 'reporter user' do + before { project.team << [user, :reporter] } + + context 'pull code' do + it { expect(subject).to be_allowed } + end + end + + describe 'admin user' do + let(:user) { create(:admin) } + + context 'when member of the project' do + before { project.team << [user, :reporter] } + + context 'pull code' do + it { expect(subject).to be_allowed } + end + end + + context 'when is not member of the project' do + context 'pull code' do + it { expect(subject).not_to be_allowed } + end + end + end + end end describe 'push_access_check' do @@ -281,40 +318,58 @@ describe Gitlab::GitAccess, lib: true do admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) end end + end - describe 'deploy key permissions' do - let(:key) { create(:deploy_key) } - let(:actor) { key } + shared_examples 'can not push code' do + subject { access.check('git-receive-pack', '_any') } - context 'push code' do - subject { access.check('git-receive-pack', '_any') } + context 'when project is authorized' do + before { key.projects << project } - context 'when project is authorized' do - before { key.projects << project } + it { expect(subject).not_to be_allowed } + end + + context 'when unauthorized' do + context 'to public project' do + let(:project) { create(:project, :public) } it { expect(subject).not_to be_allowed } end - context 'when unauthorized' do - context 'to public project' do - let(:project) { create(:project, :public) } - - it { expect(subject).not_to be_allowed } - end - - context 'to internal project' do - let(:project) { create(:project, :internal) } + context 'to internal project' do + let(:project) { create(:project, :internal) } - it { expect(subject).not_to be_allowed } - end + it { expect(subject).not_to be_allowed } + end - context 'to private project' do - let(:project) { create(:project, :internal) } + context 'to private project' do + let(:project) { create(:project, :internal) } - it { expect(subject).not_to be_allowed } - end + it { expect(subject).not_to be_allowed } end end end + + describe 'build capabilities permissions' do + let(:capabilities) { build_capabilities } + + it_behaves_like 'cannot push code' + end + + describe 'deploy key permissions' do + let(:key) { create(:deploy_key) } + let(:actor) { key } + + it_behaves_like 'cannot push code' + end + + private + + def build_capabilities + [ + :read_project, + :build_download_code + ] + end end -- cgit v1.2.3 From 9d1ccd2ad3af37139649100476b568d219343a57 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 13:49:11 +0200 Subject: Fix existing authorization specs --- app/controllers/jwt_controller.rb | 6 +++--- app/controllers/projects/git_http_client_controller.rb | 2 +- app/models/ci/build.rb | 1 + .../auth/container_registry_authentication_service.rb | 8 +++----- lib/api/internal.rb | 18 ++++++++---------- lib/gitlab/auth.rb | 2 +- lib/gitlab/git_access.rb | 2 +- spec/lib/gitlab/git_access_spec.rb | 17 ++++++++++++----- spec/requests/git_http_spec.rb | 9 ++++----- spec/requests/jwt_controller_spec.rb | 6 ++++-- .../container_registry_authentication_service_spec.rb | 14 +++++++++++++- 11 files changed, 51 insertions(+), 34 deletions(-) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index ed5d28e0d2c..0870a2a8f50 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -11,10 +11,10 @@ class JwtController < ApplicationController service = SERVICES[params[:service]] return head :not_found unless service - @@authentication_result ||= Gitlab::Auth.Result.new + @authentication_result ||= Gitlab::Auth::Result.new result = service.new(@authentication_result.project, @authentication_result.user, auth_params). - execute(capabilities: @authentication_result.capabilities || []) + execute(capabilities: @authentication_result.capabilities) render json: result, status: result[:http_status] end @@ -23,7 +23,7 @@ class JwtController < ApplicationController def authenticate_project_or_user authenticate_with_http_basic do |login, password| - @authentication_result = Gitlab::Auth.find_for_git_client(login, password, ip: request.ip) + @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) render_403 unless @authentication_result.succeeded? end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 6870102c296..aabb5b0fe01 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -36,7 +36,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController @capabilities = auth_result.capabilities || [] - if ci? || user + if auth_result.succeeded? return # Allow access end elsif allow_kerberos_spnego_auth? && spnego_provided? diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 29788b8285d..0b017c98916 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -43,6 +43,7 @@ module Ci new_build.status = 'pending' new_build.runner_id = nil new_build.trigger_request_id = nil + new_build.token = nil new_build.save end diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index ba0b60abfe4..df1c9b2851c 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -4,8 +4,8 @@ module Auth AUDIENCE = 'container_registry' - def execute(capabilities: capabilities) - @capabilities = capabilities + def execute(capabilities:) + @capabilities = capabilities || [] return error('not found', 404) unless registry.enabled @@ -76,7 +76,7 @@ module Auth case requested_action when 'pull' - build_can_pull?(requested_project) || user_can_pull?(requested_project) + requested_project.public? || build_can_pull?(requested_project) || user_can_pull?(requested_project) when 'push' build_can_push?(requested_project) || user_can_push?(requested_project) else @@ -88,8 +88,6 @@ module Auth Gitlab.config.registry end - private - def build_can_pull?(requested_project) # Build can: # 1. pull from it's own project (for ex. a build) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 2ec94570506..2610fd329d6 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -35,6 +35,14 @@ module API Project.find_with_namespace(project_path) end end + + def ssh_capabilities + [ + :read_project, + :download_code, + :push_code + ] + end end post "/allowed" do @@ -130,16 +138,6 @@ module API { success: true, recovery_codes: codes } end - - private - - def ssh_capabilities - [ - :read_project, - :download_code, - :push_code - ] - end end end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 7af9bb9a326..666cf1b3e26 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -115,7 +115,7 @@ module Gitlab return unless login == 'gitlab-ci-token' return unless password - build = Ci::Build.running.find_by_token(password) + build = ::Ci::Build.running.find_by_token(password) return unless build if build.user diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 63b707db814..21286e77dc6 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -7,7 +7,7 @@ module Gitlab attr_reader :actor, :project, :protocol, :user_access, :capabilities - def initialize(actor, project, protocol, capabilities: capabilities) + def initialize(actor, project, protocol, capabilities:) @actor = actor @project = project @protocol = protocol diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 77dce676cdb..d418b0be0ed 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::GitAccess, lib: true do context 'ssh disabled' do before do disable_protocol('ssh') - @acc = Gitlab::GitAccess.new(actor, project, 'ssh') + @acc = Gitlab::GitAccess.new(actor, project, 'ssh', capabilities: capabilities) end it 'blocks ssh git push' do @@ -37,7 +37,7 @@ describe Gitlab::GitAccess, lib: true do context 'http disabled' do before do disable_protocol('http') - @acc = Gitlab::GitAccess.new(actor, project, 'http') + @acc = Gitlab::GitAccess.new(actor, project, 'http', capabilities: capabilities) end it 'blocks http push' do @@ -318,7 +318,6 @@ describe Gitlab::GitAccess, lib: true do admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) end end - end shared_examples 'can not push code' do @@ -354,14 +353,14 @@ describe Gitlab::GitAccess, lib: true do describe 'build capabilities permissions' do let(:capabilities) { build_capabilities } - it_behaves_like 'cannot push code' + it_behaves_like 'can not push code' end describe 'deploy key permissions' do let(:key) { create(:deploy_key) } let(:actor) { key } - it_behaves_like 'cannot push code' + it_behaves_like 'can not push code' end private @@ -372,4 +371,12 @@ describe Gitlab::GitAccess, lib: true do :build_download_code ] end + + def full_capabilities + [ + :read_project, + :download_code, + :push_code + ] + end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index b7001fede40..5977ee04524 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -300,23 +300,22 @@ describe 'Git HTTP requests', lib: true do end context "when a gitlab ci token is provided" do - let(:token) { 123 } - let(:project) { FactoryGirl.create :empty_project } + let(:build) { create(:ci_build, :running) } + let(:project) { build.project } before do - project.update_attributes(runners_token: token) project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED) end it "downloads get status 200" do - clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token + clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token expect(response).to have_http_status(200) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end it "uploads get status 401 (no project existence information leak)" do - push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token + push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token expect(response).to have_http_status(401) end diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index fc42b534dca..93b9cfaf33d 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -22,11 +22,13 @@ describe JwtController do context 'when using authorized request' do context 'using CI token' do - let(:project) { create(:empty_project, runners_token: 'token') } - let(:headers) { { authorization: credentials('gitlab-ci-token', project.runners_token) } } + let(:build) { create(:ci_build, :running) } + let(:project) { build.project } + let(:headers) { { authorization: credentials('gitlab-ci-token', build.token) } } context 'project with enabled CI' do subject! { get '/jwt/auth', parameters, headers } + it { expect(service_class).to have_received(:new).with(project, nil, parameters) } end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 7cc71f706ce..c82deb7d423 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -6,8 +6,14 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do let(:current_params) { {} } let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) } let(:payload) { JWT.decode(subject[:token], rsa_key).first } + let(:capabilities) do + [ + :read_container_image, + :create_container_image + ] + end - subject { described_class.new(current_project, current_user, current_params).execute } + subject { described_class.new(current_project, current_user, current_params).execute(capabilities: capabilities) } before do allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil) @@ -42,6 +48,12 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do 'actions' => actions, }] end + let(:capabilities) do + [ + :build_read_container_image, + :build_create_container_image + ] + end it_behaves_like 'a valid token' it { expect(payload).to include('access' => access) } -- cgit v1.2.3 From 551787ac5c12a502b46c819939b2fa11684a799c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 14:06:10 +0200 Subject: Simplify LFS helper --- app/helpers/lfs_helper.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index a2359d94443..d27b87ed5e4 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -39,10 +39,6 @@ module LfsHelper def lfs_upload_access? return false unless project.lfs_enabled? - privileged_user_can_push_code? - end - - def privileged_user_can_push_code? has_capability?(:push_code) && user && user.can?(:push_code, project) end -- cgit v1.2.3 From 548169cfb57b27cca911d947e2aa6f4f7e6df004 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 15:40:53 +0200 Subject: Fix most of specs --- app/controllers/projects/git_http_client_controller.rb | 7 +++++-- lib/ci/api/helpers.rb | 4 ++-- lib/gitlab/auth.rb | 3 +-- spec/lib/gitlab/git_access_spec.rb | 14 +++++++++++--- spec/lib/gitlab/git_access_wiki_spec.rb | 9 ++++++++- spec/requests/lfs_http_spec.rb | 10 ++++++---- .../auth/container_registry_authentication_service_spec.rb | 12 ++++++------ 7 files changed, 39 insertions(+), 20 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index aabb5b0fe01..c2a298fe37f 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -23,10 +23,12 @@ class Projects::GitHttpClientController < Projects::ApplicationController login, password = user_name_and_password(request) auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) - if auth_result.type == :ci && download_request? - @ci = true + if auth_result.type == :ci && !download_request? + # Not allowed + auth_result = Gitlab::Auth::Result.new elsif auth_result.type == :oauth && !download_request? # Not allowed + auth_result = Gitlab::Auth::Result.new elsif auth_result.type == :missing_personal_token render_missing_personal_token return # Render above denied access, nothing left to do @@ -35,6 +37,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController end @capabilities = auth_result.capabilities || [] + @ci = auth_result.type == :ci if auth_result.succeeded? return # Allow access diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 3dfebc0cac1..23353c62885 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -14,7 +14,7 @@ module Ci end def authenticate_build_token!(build) - forbidden! unless build_token_valid? + forbidden! unless build_token_valid?(build) end def runner_registration_token_valid? @@ -23,7 +23,7 @@ module Ci current_application_settings.runners_registration_token) end - def build_token_valid? + def build_token_valid?(build) token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s # We require to also check `runners_token` to maintain compatibility with old version of runners diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 666cf1b3e26..b1427f412b0 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -117,6 +117,7 @@ module Gitlab build = ::Ci::Build.running.find_by_token(password) return unless build + return unless build.project.builds_enabled? if build.user # If user is assigned to build, use restricted credentials of user @@ -127,8 +128,6 @@ module Gitlab end end - private - def build_capabilities [ :read_project, diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index d418b0be0ed..c6fe56aac1c 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -324,7 +324,7 @@ describe Gitlab::GitAccess, lib: true do subject { access.check('git-receive-pack', '_any') } context 'when project is authorized' do - before { key.projects << project } + before { authorize } it { expect(subject).not_to be_allowed } end @@ -353,14 +353,22 @@ describe Gitlab::GitAccess, lib: true do describe 'build capabilities permissions' do let(:capabilities) { build_capabilities } - it_behaves_like 'can not push code' + it_behaves_like 'can not push code' do + def authorize + project.team << [user, :reporter] + end + end end describe 'deploy key permissions' do let(:key) { create(:deploy_key) } let(:actor) { key } - it_behaves_like 'can not push code' + it_behaves_like 'can not push code' do + def authorize + key.projects << project + end + end end private diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 4244b807d41..860e701c1a1 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,9 +1,16 @@ require 'spec_helper' describe Gitlab::GitAccessWiki, lib: true do - let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web') } + let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', capabilities: capabilities) } let(:project) { create(:project) } let(:user) { create(:user) } + let(:capabilities) do + [ + :read_project, + :download_code, + :push_code + ] + end describe 'push_allowed?' do before do diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 6e551bb65fa..85290ec05c2 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -586,8 +586,8 @@ describe 'Git LFS API and storage' do context 'when CI is authorized' do let(:authorization) { authorize_ci_project } - it 'responds with 401' do - expect(response).to have_http_status(401) + it 'responds with 403' do + expect(response).to have_http_status(403) end end end @@ -614,7 +614,7 @@ describe 'Git LFS API and storage' do let(:authorization) { authorize_ci_project } it 'responds with status 403' do - expect(response).to have_http_status(401) + expect(response).to have_http_status(403) end end end @@ -897,7 +897,9 @@ describe 'Git LFS API and storage' do end def authorize_ci_project - ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token) + pipeline = create(:ci_empty_pipeline, project: project) + build = create(:ci_build, :running, pipeline: pipeline) + ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', build.token) end def authorize_user diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index c82deb7d423..5f82fee43c6 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -48,12 +48,6 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do 'actions' => actions, }] end - let(:capabilities) do - [ - :build_read_container_image, - :build_create_container_image - ] - end it_behaves_like 'a valid token' it { expect(payload).to include('access' => access) } @@ -203,6 +197,12 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do context 'project authorization' do let(:current_project) { create(:empty_project) } + let(:capabilities) do + [ + :build_read_container_image, + :build_create_container_image + ] + end context 'allow to use scope-less authentication' do it_behaves_like 'a valid token' -- cgit v1.2.3 From 634e8e30de54b467de81758430df8bcf89ba0e3e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 15 Sep 2016 14:47:53 +0100 Subject: Removed more flexbox --- app/assets/stylesheets/pages/boards.scss | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index ff6b8636b97..9c84dceed05 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -1,3 +1,4 @@ +lex [v-cloak] { display: none; } @@ -76,6 +77,7 @@ padding-right: ($gl-padding / 2); padding-left: ($gl-padding / 2); white-space: normal; + vertical-align: top; @media (min-width: $screen-sm-min) { width: 400px; @@ -83,10 +85,6 @@ } .board-inner { - display: -webkit-flex; - display: flex; - -webkit-flex-direction: column; - flex-direction: column; height: 100%; font-size: $issue-boards-font-size; background: $background-color; @@ -158,7 +156,7 @@ } .board-list { - height: 100%; + height: calc(100% - 49px); margin-bottom: 0; padding: 5px; list-style: none; -- cgit v1.2.3 From e3a422c2672096a819291c395623619c8c669e74 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 16:17:57 +0200 Subject: Fix LFS specs --- spec/requests/lfs_http_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 85290ec05c2..1ee3881b839 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -586,8 +586,8 @@ describe 'Git LFS API and storage' do context 'when CI is authorized' do let(:authorization) { authorize_ci_project } - it 'responds with 403' do - expect(response).to have_http_status(403) + it 'responds with 401' do + expect(response).to have_http_status(401) end end end @@ -613,8 +613,8 @@ describe 'Git LFS API and storage' do context 'when CI is authorized' do let(:authorization) { authorize_ci_project } - it 'responds with status 403' do - expect(response).to have_http_status(403) + it 'responds with status 401' do + expect(response).to have_http_status(401) end end end -- cgit v1.2.3 From 0950d92df5d413f43ac6db53751589b597d4283c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 15 Sep 2016 16:30:27 +0200 Subject: Reduce duplication in Commits::{CherryPickService,RevertService} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/services/commits/change_service.rb | 20 +++++++++++++++++++- app/services/commits/cherry_pick_service.rb | 14 +------------- app/services/commits/revert_service.rb | 14 +------------- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index ed73d8cb8c2..1c82599c579 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -16,11 +16,29 @@ module Commits error(ex.message) end + private + def commit raise NotImplementedError end - private + def commit_change(action) + raise NotImplementedError unless repository.respond_to?(action) + + into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch + tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch) + + if tree_id + create_target_branch(into) if @create_merge_request + + repository.public_send(action, current_user, @commit, into, tree_id) + success + else + error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically. + It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content." + raise ChangeError, error_msg + end + end def check_push_permissions allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch) diff --git a/app/services/commits/cherry_pick_service.rb b/app/services/commits/cherry_pick_service.rb index f9a4efa7182..605cca36f9c 100644 --- a/app/services/commits/cherry_pick_service.rb +++ b/app/services/commits/cherry_pick_service.rb @@ -1,19 +1,7 @@ module Commits class CherryPickService < ChangeService def commit - cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch - cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch) - - if cherry_pick_tree_id - create_target_branch(cherry_pick_into) if @create_merge_request - - repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id) - success - else - error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically. - It may have already been cherry-picked, or a more recent commit may have updated some of its content." - raise ChangeError, error_msg - end + commit_change(:cherry_pick) end end end diff --git a/app/services/commits/revert_service.rb b/app/services/commits/revert_service.rb index c7de9f6f35e..addd55cb32f 100644 --- a/app/services/commits/revert_service.rb +++ b/app/services/commits/revert_service.rb @@ -1,19 +1,7 @@ module Commits class RevertService < ChangeService def commit - revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch - revert_tree_id = repository.check_revert_content(@commit, @target_branch) - - if revert_tree_id - create_target_branch(revert_into) if @create_merge_request - - repository.revert(current_user, @commit, revert_into, revert_tree_id) - success - else - error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically. - It may have already been reverted, or a more recent commit may have updated some of its content." - raise ChangeError, error_msg - end + commit_change(:revert) end end end -- cgit v1.2.3 From eed5c58d8542cef8cc4012a303c9bb963b7f5f20 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 16:36:39 +0200 Subject: Verify permission of build in context of dependent project --- spec/requests/lfs_http_spec.rb | 6 ++-- ...ntainer_registry_authentication_service_spec.rb | 40 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 1ee3881b839..7bf43a03f23 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -14,6 +14,8 @@ describe 'Git LFS API and storage' do end let(:authorization) { } let(:sendfile) { } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let(:build) { create(:ci_build, :running, pipeline: pipeline) } let(:sample_oid) { lfs_object.oid } let(:sample_size) { lfs_object.size } @@ -244,7 +246,7 @@ describe 'Git LFS API and storage' do end end - context 'when CI is authorized' do + context 'when build is authorized' do let(:authorization) { authorize_ci_project } let(:update_permissions) do @@ -897,8 +899,6 @@ describe 'Git LFS API and storage' do end def authorize_ci_project - pipeline = create(:ci_empty_pipeline, project: project) - build = create(:ci_build, :running, pipeline: pipeline) ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', build.token) end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 5f82fee43c6..2d39bd61b8f 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -195,8 +195,9 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do end end - context 'project authorization' do + context 'build authorized as user' do let(:current_project) { create(:empty_project) } + let(:current_user) { create(:user) } let(:capabilities) do [ :build_read_container_image, @@ -204,10 +205,12 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do ] end - context 'allow to use scope-less authentication' do - it_behaves_like 'a valid token' + before do + current_project.team << [current_user, :developer] end + it_behaves_like 'a valid token' + context 'allow to pull and push images' do let(:current_params) do { scope: "repository:#{current_project.path_with_namespace}:pull,push" } @@ -226,12 +229,34 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do context 'allow for public' do let(:project) { create(:empty_project, :public) } + it_behaves_like 'a pullable' end - context 'disallow for private' do + shared_examples 'pullable for being team member' do + context 'when you are not member' do + it_behaves_like 'an inaccessible' + end + + context 'when you are member' do + before do + project.team << [current_user, :developer] + end + + it_behaves_like 'a pullable' + end + end + + context 'for private' do let(:project) { create(:empty_project, :private) } - it_behaves_like 'an inaccessible' + + it_behaves_like 'pullable for being team member' + + context 'when you are admin' do + let(:current_user) { create(:admin) } + + it_behaves_like 'pullable for being team member' + end end end @@ -242,6 +267,11 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do context 'disallow for all' do let(:project) { create(:empty_project, :public) } + + before do + project.team << [current_user, :developer] + end + it_behaves_like 'an inaccessible' end end -- cgit v1.2.3 From e40e3fdc8271d1becf7952c7e30546c5abecb79b Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 25 Aug 2016 17:26:20 -0500 Subject: Added LFS support to SSH - Required on the GitLab Rails side is mostly authentication and API related. --- .../projects/git_http_client_controller.rb | 42 +++++++++++++++------- app/helpers/lfs_helper.rb | 2 +- app/models/deploy_key.rb | 5 +++ app/models/user.rb | 3 +- .../20160825173042_add_lfs_token_to_users.rb | 16 +++++++++ db/migrate/20160825182924_add_lfs_token_to_keys.rb | 16 +++++++++ lib/api/entities.rb | 2 +- lib/api/internal.rb | 13 ++++++- lib/gitlab/auth.rb | 13 ++++++- spec/lib/gitlab/auth_spec.rb | 16 +++++++++ spec/models/concerns/token_authenticatable_spec.rb | 20 +++++++++++ spec/requests/api/internal_spec.rb | 26 +++++++++++--- spec/requests/lfs_http_spec.rb | 16 +++++++++ 13 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 db/migrate/20160825173042_add_lfs_token_to_users.rb create mode 100644 db/migrate/20160825182924_add_lfs_token_to_keys.rb diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index f5ce63fdfed..1e6709dc8eb 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,6 +4,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper + class MissingPersonalTokenError < StandardError; end + attr_reader :user # Git clients will not know what authenticity token to send along @@ -21,18 +23,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController if allow_basic_auth? && basic_auth_provided? login, password = user_name_and_password(request) - auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) - - if auth_result.type == :ci && download_request? - @ci = true - elsif auth_result.type == :oauth && !download_request? - # Not allowed - elsif auth_result.type == :missing_personal_token - render_missing_personal_token - return # Render above denied access, nothing left to do - else - @user = auth_result.user - end + + handle_authentication(login, password) if ci? || user return # Allow access @@ -48,6 +40,10 @@ class Projects::GitHttpClientController < Projects::ApplicationController send_challenges render plain: "HTTP Basic: Access denied\n", status: 401 + + rescue MissingPersonalTokenError + render_missing_personal_token + return end def basic_auth_provided? @@ -118,6 +114,28 @@ class Projects::GitHttpClientController < Projects::ApplicationController @ci.present? end + def handle_authentication(login, password) + auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) + + if auth_result.type == :ci && download_request? + @ci = true + elsif auth_result.type == :oauth && !download_request? + # Not allowed + elsif auth_result.type == :missing_personal_token + raise MissingPersonalTokenError + elsif auth_result.type == :lfs_deploy_token && download_request? + @lfs_deploy_key = true + @user = auth_result.user + else + @user = auth_result.user + end + end + + def lfs_deploy_key? + key = user + @lfs_deploy_key.present? && (key && key.projects.include?(project)) + end + def verify_workhorse_api! Gitlab::Workhorse.verify_api_request!(request.headers) end diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 5d82abfca79..0c867fc8741 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -25,7 +25,7 @@ module LfsHelper def lfs_download_access? return false unless project.lfs_enabled? - project.public? || ci? || (user && user.can?(:download_code, project)) + project.public? || ci? || lfs_deploy_key? || (user && user.can?(:download_code, project)) end def lfs_upload_access? diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 2c525d4cd7a..de51b63c120 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -1,7 +1,12 @@ class DeployKey < Key + include TokenAuthenticatable + add_authentication_token_field :lfs_token + has_many :deploy_keys_projects, dependent: :destroy has_many :projects, through: :deploy_keys_projects + before_save :ensure_lfs_token + scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } scope :are_public, -> { where(public: true) } diff --git a/app/models/user.rb b/app/models/user.rb index 6996740eebd..94ae3911859 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -13,6 +13,7 @@ class User < ActiveRecord::Base DEFAULT_NOTIFICATION_LEVEL = :participating add_authentication_token_field :authentication_token + add_authentication_token_field :lfs_token default_value_for :admin, false default_value_for(:external) { current_application_settings.user_default_external } @@ -117,7 +118,7 @@ class User < ActiveRecord::Base before_validation :set_public_email, if: ->(user) { user.public_email_changed? } after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? } - before_save :ensure_authentication_token + before_save :ensure_authentication_token, :ensure_lfs_token before_save :ensure_external_user_rights after_save :ensure_namespace_correct after_initialize :set_projects_limit diff --git a/db/migrate/20160825173042_add_lfs_token_to_users.rb b/db/migrate/20160825173042_add_lfs_token_to_users.rb new file mode 100644 index 00000000000..f7f210bcd67 --- /dev/null +++ b/db/migrate/20160825173042_add_lfs_token_to_users.rb @@ -0,0 +1,16 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddLfsTokenToUsers < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def change + add_column :users, :lfs_token, :string + add_concurrent_index :users, :lfs_token + end +end diff --git a/db/migrate/20160825182924_add_lfs_token_to_keys.rb b/db/migrate/20160825182924_add_lfs_token_to_keys.rb new file mode 100644 index 00000000000..3ff010ef328 --- /dev/null +++ b/db/migrate/20160825182924_add_lfs_token_to_keys.rb @@ -0,0 +1,16 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddLfsTokenToKeys < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def change + add_column :keys, :lfs_token, :string + add_concurrent_index :keys, :lfs_token + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4f736e4ec2b..b4fcacca896 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,7 +1,7 @@ module API module Entities class UserSafe < Grape::Entity - expose :name, :username + expose :name, :username, :lfs_token end class UserBasic < UserSafe diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 6e6efece7c4..7c0a6eaa652 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -69,6 +69,10 @@ module API else project.repository.path_to_repo end + + # Return HTTP full path, so that gitlab-shell has this information + # ready for git-lfs-authenticate + response[:repository_http_path] = project.http_url_to_repo end response @@ -83,7 +87,14 @@ module API # get "/discover" do key = Key.find(params[:key_id]) - present key.user, with: Entities::UserSafe + user = key.user + if user + user.ensure_lfs_token! + present user, with: Entities::UserSafe + else + key.ensure_lfs_token! + { username: 'lfs-deploy-key', lfs_token: key.lfs_token } + end end get "/check" do diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 91f0270818a..5446093de4d 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -79,12 +79,13 @@ module Gitlab result = user_with_password_for_git(login, password) || oauth_access_token_check(login, password) || + lfs_token_check(login, password) || personal_access_token_check(login, password) if result result.type = nil unless result.user - if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap + if result.user && result.type == :gitlab_or_ldap && result.user.two_factor_enabled? result.type = :missing_personal_token end end @@ -114,6 +115,16 @@ module Gitlab Result.new(user, :personal_token) if user == validation end end + + def lfs_token_check(login, password) + if login == 'lfs-deploy-key' + key = DeployKey.find_by_lfs_token(password) + Result.new(key, :lfs_deploy_token) if key + else + user = User.find_by_lfs_token(password) + Result.new(user, :lfs_token) if user && user.username == login + end + end end end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 7c23e02d05a..cd00a15be3b 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -23,6 +23,22 @@ describe Gitlab::Auth, lib: true do expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :gitlab_or_ldap)) end + it 'recognizes user lfs tokens' do + user = create(:user) + ip = 'ip' + + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) + expect(gl_auth.find_for_git_client(user.username, user.lfs_token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :lfs_token)) + end + + it 'recognizes deploy key lfs tokens' do + key = create(:deploy_key) + ip = 'ip' + + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'lfs-deploy-key') + expect(gl_auth.find_for_git_client('lfs-deploy-key', key.lfs_token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, :lfs_deploy_token)) + end + it 'recognizes OAuth tokens' do user = create(:user) application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index eb64f3d0c83..82076600f3b 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -18,6 +18,26 @@ describe User, 'TokenAuthenticatable' do subject { create(:user).send(token_field) } it { is_expected.to be_a String } end + + describe 'lfs token' do + let(:token_field) { :lfs_token } + it_behaves_like 'TokenAuthenticatable' + + describe 'ensure it' do + subject { create(:user).send(token_field) } + it { is_expected.to be_a String } + end + end +end + +describe DeployKey, 'TokenAuthenticatable' do + let(:token_field) { :lfs_token } + it_behaves_like 'TokenAuthenticatable' + + describe 'ensures authentication token' do + subject { create(:deploy_key).send(token_field) } + it { is_expected.to be_a String } + end end describe ApplicationSetting, 'TokenAuthenticatable' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 46d1b868782..95fc5f790e8 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -101,12 +101,28 @@ describe API::API, api: true do end describe "GET /internal/discover" do - it do - get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) + context 'user key' do + it 'returns the correct information about the key' do + get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) - expect(response).to have_http_status(200) + expect(response).to have_http_status(200) + + expect(json_response['name']).to eq(user.name) + expect(json_response['lfs_token']).to eq(user.lfs_token) + end + end - expect(json_response['name']).to eq(user.name) + context 'deploy key' do + let(:key) { create(:deploy_key) } + + it 'returns the correct information about the key' do + get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) + + expect(response).to have_http_status(200) + + expect(json_response['username']).to eq('lfs-deploy-key') + expect(json_response['lfs_token']).to eq(key.lfs_token) + end end end @@ -143,6 +159,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(json_response["repository_http_path"]).to eq(project.http_url_to_repo) end end @@ -153,6 +170,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) + expect(json_response["repository_http_path"]).to eq(project.http_url_to_repo) end end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 6e551bb65fa..58f8515c0e2 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -244,6 +244,18 @@ describe 'Git LFS API and storage' do end end + context 'when deploy key is authorized' do + let(:key) { create(:deploy_key) } + let(:authorization) { authorize_deploy_key } + + let(:update_permissions) do + project.deploy_keys << key + project.lfs_objects << lfs_object + end + + it_behaves_like 'responds with a file' + end + context 'when CI is authorized' do let(:authorization) { authorize_ci_project } @@ -904,6 +916,10 @@ describe 'Git LFS API and storage' do ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) end + def authorize_deploy_key + ActionController::HttpAuthentication::Basic.encode_credentials('lfs-deploy-key', key.lfs_token) + end + def fork_project(project, user, object = nil) allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) Projects::ForkService.new(project, user, {}).execute -- cgit v1.2.3 From 372be2d2e8fe8d607011aa7e2b2fca99eeea007d Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 25 Aug 2016 18:43:14 -0500 Subject: Added CHANGELOG item and documentation. --- CHANGELOG | 1 + doc/workflow/lfs/lfs_administration.md | 4 ++-- doc/workflow/lfs/manage_large_binaries_with_git_lfs.md | 8 ++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0056c6cc649..9458413669d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -93,6 +93,7 @@ v 8.12.0 (unreleased) - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Fix repo title alignment (ClemMakesApps) - Change update interval of contacted_at + - Add LFS support to SSH !6043 - Fix branch title trailing space on hover (ClemMakesApps) - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md index 9dc1e9b47e3..b3c73e947f0 100644 --- a/doc/workflow/lfs/lfs_administration.md +++ b/doc/workflow/lfs/lfs_administration.md @@ -45,5 +45,5 @@ In `config/gitlab.yml`: * Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported * Currently, removing LFS objects from GitLab Git LFS storage is not supported -* LFS authentications via SSH is not supported for the time being -* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2. +* LFS authentications via SSH was added with GitLab 8.12 +* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2. diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index 9fe065fa680..1a4f213a792 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -35,6 +35,10 @@ Documentation for GitLab instance administrators is under [LFS administration do credentials store is recommended * Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting) + +>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication + still goes over HTTP, but now the SSH client passes the correct credentials + to the Git LFS client, so no action is required by the user. ## Using Git LFS @@ -132,6 +136,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs" ### Credentials are always required when pushing an object +>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication + still goes over HTTP, but now the SSH client passes the correct credentials + to the Git LFS client, so no action is required by the user. + Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required. -- cgit v1.2.3 From cb85cf1f0a7047c485d7b29b2792b8965e270898 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 29 Aug 2016 13:05:07 -0500 Subject: Refactor LFS token logic to use a Redis key instead of a DB field, making it a 1 use only token. --- .../projects/git_http_client_controller.rb | 3 +- app/helpers/lfs_helper.rb | 6 +++- app/models/deploy_key.rb | 5 ---- app/models/user.rb | 3 +- .../20160825173042_add_lfs_token_to_users.rb | 16 ---------- db/migrate/20160825182924_add_lfs_token_to_keys.rb | 16 ---------- lib/api/entities.rb | 2 +- lib/api/internal.rb | 9 +++--- lib/gitlab/auth.rb | 12 ++++---- lib/gitlab/lfs_token.rb | 29 ++++++++++++++++++ spec/lib/gitlab/auth_spec.rb | 8 +++-- spec/lib/gitlab/lfs_token_spec.rb | 35 ++++++++++++++++++++++ spec/models/concerns/token_authenticatable_spec.rb | 20 ------------- spec/requests/api/internal_spec.rb | 6 ++-- spec/requests/lfs_http_spec.rb | 2 +- 15 files changed, 93 insertions(+), 79 deletions(-) delete mode 100644 db/migrate/20160825173042_add_lfs_token_to_users.rb delete mode 100644 db/migrate/20160825182924_add_lfs_token_to_keys.rb create mode 100644 lib/gitlab/lfs_token.rb create mode 100644 spec/lib/gitlab/lfs_token_spec.rb diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 1e6709dc8eb..4dff1ce6568 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -132,8 +132,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def lfs_deploy_key? - key = user - @lfs_deploy_key.present? && (key && key.projects.include?(project)) + @lfs_deploy_key.present? && (user && user.projects.include?(project)) end def verify_workhorse_api! diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 0c867fc8741..2f5709a7395 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -25,7 +25,11 @@ module LfsHelper def lfs_download_access? return false unless project.lfs_enabled? - project.public? || ci? || lfs_deploy_key? || (user && user.can?(:download_code, project)) + return true if project.public? + return true if ci? + return true if lfs_deploy_key? + + (user && user.can?(:download_code, project)) end def lfs_upload_access? diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index de51b63c120..2c525d4cd7a 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -1,12 +1,7 @@ class DeployKey < Key - include TokenAuthenticatable - add_authentication_token_field :lfs_token - has_many :deploy_keys_projects, dependent: :destroy has_many :projects, through: :deploy_keys_projects - before_save :ensure_lfs_token - scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } scope :are_public, -> { where(public: true) } diff --git a/app/models/user.rb b/app/models/user.rb index 94ae3911859..6996740eebd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -13,7 +13,6 @@ class User < ActiveRecord::Base DEFAULT_NOTIFICATION_LEVEL = :participating add_authentication_token_field :authentication_token - add_authentication_token_field :lfs_token default_value_for :admin, false default_value_for(:external) { current_application_settings.user_default_external } @@ -118,7 +117,7 @@ class User < ActiveRecord::Base before_validation :set_public_email, if: ->(user) { user.public_email_changed? } after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? } - before_save :ensure_authentication_token, :ensure_lfs_token + before_save :ensure_authentication_token before_save :ensure_external_user_rights after_save :ensure_namespace_correct after_initialize :set_projects_limit diff --git a/db/migrate/20160825173042_add_lfs_token_to_users.rb b/db/migrate/20160825173042_add_lfs_token_to_users.rb deleted file mode 100644 index f7f210bcd67..00000000000 --- a/db/migrate/20160825173042_add_lfs_token_to_users.rb +++ /dev/null @@ -1,16 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddLfsTokenToUsers < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - disable_ddl_transaction! - - def change - add_column :users, :lfs_token, :string - add_concurrent_index :users, :lfs_token - end -end diff --git a/db/migrate/20160825182924_add_lfs_token_to_keys.rb b/db/migrate/20160825182924_add_lfs_token_to_keys.rb deleted file mode 100644 index 3ff010ef328..00000000000 --- a/db/migrate/20160825182924_add_lfs_token_to_keys.rb +++ /dev/null @@ -1,16 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddLfsTokenToKeys < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - disable_ddl_transaction! - - def change - add_column :keys, :lfs_token, :string - add_concurrent_index :keys, :lfs_token - end -end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index b4fcacca896..4f736e4ec2b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,7 +1,7 @@ module API module Entities class UserSafe < Grape::Entity - expose :name, :username, :lfs_token + expose :name, :username end class UserBasic < UserSafe diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 7c0a6eaa652..760f69663ab 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -88,12 +88,13 @@ module API get "/discover" do key = Key.find(params[:key_id]) user = key.user + if user - user.ensure_lfs_token! - present user, with: Entities::UserSafe + token = Gitlab::LfsToken.new(user).set_token + { name: user.name, username: user.username, lfs_token: token } else - key.ensure_lfs_token! - { username: 'lfs-deploy-key', lfs_token: key.lfs_token } + token = Gitlab::LfsToken.new(key).set_token + { username: "lfs-deploy-key-#{key.id}", lfs_token: token } end end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 5446093de4d..e43f8119658 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -117,12 +117,14 @@ module Gitlab end def lfs_token_check(login, password) - if login == 'lfs-deploy-key' - key = DeployKey.find_by_lfs_token(password) - Result.new(key, :lfs_deploy_token) if key + if login.include?('lfs-deploy-key') + key = DeployKey.find(login.gsub('lfs-deploy-key-', '')) + token = Gitlab::LfsToken.new(key).get_value + Result.new(key, :lfs_deploy_token) if key && token == password else - user = User.find_by_lfs_token(password) - Result.new(user, :lfs_token) if user && user.username == login + user = User.by_login(login) + token = Gitlab::LfsToken.new(user).get_value + Result.new(user, :lfs_token) if user && token == password end end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb new file mode 100644 index 00000000000..0685eb775ef --- /dev/null +++ b/lib/gitlab/lfs_token.rb @@ -0,0 +1,29 @@ +module Gitlab + class LfsToken + attr_accessor :actor + + def initialize(actor) + @actor = actor + end + + def set_token + token = Devise.friendly_token(50) + Gitlab::Redis.with do |redis| + redis.set(redis_key, token, ex: 3600) + end + token + end + + def get_value + Gitlab::Redis.with do |redis| + redis.get(redis_key) + end + end + + private + + def redis_key + "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor + end + end +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index cd00a15be3b..6ce680e3c26 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -26,17 +26,19 @@ describe Gitlab::Auth, lib: true do it 'recognizes user lfs tokens' do user = create(:user) ip = 'ip' + token = Gitlab::LfsToken.new(user).set_token expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, user.lfs_token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :lfs_token)) + expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :lfs_token)) end it 'recognizes deploy key lfs tokens' do key = create(:deploy_key) ip = 'ip' + token = Gitlab::LfsToken.new(key).set_token - expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'lfs-deploy-key') - expect(gl_auth.find_for_git_client('lfs-deploy-key', key.lfs_token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, :lfs_deploy_token)) + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs-deploy-key-#{key.id}") + expect(gl_auth.find_for_git_client("lfs-deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, :lfs_deploy_token)) end it 'recognizes OAuth tokens' do diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb new file mode 100644 index 00000000000..76b348637c7 --- /dev/null +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Gitlab::LfsToken, lib: true do + describe '#set_token and #get_value' do + shared_examples 'an LFS token generator' do + it 'returns a randomly generated token' do + token = handler.set_token + + expect(token).not_to be_nil + expect(token).to be_a String + expect(token.length).to eq 50 + end + + it 'returns the correct token based on the key' do + token = handler.set_token + + expect(handler.get_value).to eq(token) + end + end + + context 'when the actor is a user' do + let(:actor) { create(:user) } + let(:handler) { described_class.new(actor) } + + it_behaves_like 'an LFS token generator' + end + + context 'when the actor is a deploy key' do + let(:actor) { create(:deploy_key) } + let(:handler) { described_class.new(actor) } + + it_behaves_like 'an LFS token generator' + end + end +end diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index 82076600f3b..eb64f3d0c83 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -18,26 +18,6 @@ describe User, 'TokenAuthenticatable' do subject { create(:user).send(token_field) } it { is_expected.to be_a String } end - - describe 'lfs token' do - let(:token_field) { :lfs_token } - it_behaves_like 'TokenAuthenticatable' - - describe 'ensure it' do - subject { create(:user).send(token_field) } - it { is_expected.to be_a String } - end - end -end - -describe DeployKey, 'TokenAuthenticatable' do - let(:token_field) { :lfs_token } - it_behaves_like 'TokenAuthenticatable' - - describe 'ensures authentication token' do - subject { create(:deploy_key).send(token_field) } - it { is_expected.to be_a String } - end end describe ApplicationSetting, 'TokenAuthenticatable' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 95fc5f790e8..59df5af770b 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -108,7 +108,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response['name']).to eq(user.name) - expect(json_response['lfs_token']).to eq(user.lfs_token) + expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(user).get_value) end end @@ -120,8 +120,8 @@ describe API::API, api: true do expect(response).to have_http_status(200) - expect(json_response['username']).to eq('lfs-deploy-key') - expect(json_response['lfs_token']).to eq(key.lfs_token) + expect(json_response['username']).to eq("lfs-deploy-key-#{key.id}") + expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).get_value) end end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 58f8515c0e2..d15e72b2570 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -917,7 +917,7 @@ describe 'Git LFS API and storage' do end def authorize_deploy_key - ActionController::HttpAuthentication::Basic.encode_credentials('lfs-deploy-key', key.lfs_token) + ActionController::HttpAuthentication::Basic.encode_credentials("lfs-deploy-key-#{key.id}", Gitlab::LfsToken.new(key).set_token) end def fork_project(project, user, object = nil) -- cgit v1.2.3 From 48f1a61fd5c6aac395be0ce5d59aee61bbb69fe9 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 30 Aug 2016 13:38:22 -0500 Subject: Refactored LFS auth logic when using SSH to use its own API endpoint `/lfs_authenticate` and added tests. --- lib/api/internal.rb | 30 ++++++++++++++++------------ lib/gitlab/auth.rb | 4 ++-- lib/gitlab/lfs_token.rb | 8 +++++--- spec/lib/gitlab/auth_spec.rb | 4 ++-- spec/lib/gitlab/lfs_token_spec.rb | 6 +++--- spec/requests/api/internal_spec.rb | 40 +++++++++++++++++++++++++++++--------- spec/requests/lfs_http_spec.rb | 2 +- 7 files changed, 62 insertions(+), 32 deletions(-) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 760f69663ab..1b3388347a8 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -69,12 +69,26 @@ module API else project.repository.path_to_repo end + end + + response + end + + post "/lfs_authenticate" do + status 200 + + key = Key.find(params[:key_id]) + user = key.user - # Return HTTP full path, so that gitlab-shell has this information - # ready for git-lfs-authenticate - response[:repository_http_path] = project.http_url_to_repo + if user + token = Gitlab::LfsToken.new(user).generate + response = { username: user.username, lfs_token: token } + else + token = Gitlab::LfsToken.new(key).generate + response = { username: "lfs-deploy-key-#{key.id}", lfs_token: token } end + response[:repository_http_path] = project.http_url_to_repo response end @@ -87,15 +101,7 @@ module API # get "/discover" do key = Key.find(params[:key_id]) - user = key.user - - if user - token = Gitlab::LfsToken.new(user).set_token - { name: user.name, username: user.username, lfs_token: token } - else - token = Gitlab::LfsToken.new(key).set_token - { username: "lfs-deploy-key-#{key.id}", lfs_token: token } - end + present key.user, with: Entities::UserSafe end get "/check" do diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index e43f8119658..1b0398d18ee 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -119,11 +119,11 @@ module Gitlab def lfs_token_check(login, password) if login.include?('lfs-deploy-key') key = DeployKey.find(login.gsub('lfs-deploy-key-', '')) - token = Gitlab::LfsToken.new(key).get_value + token = Gitlab::LfsToken.new(key).value Result.new(key, :lfs_deploy_token) if key && token == password else user = User.by_login(login) - token = Gitlab::LfsToken.new(user).get_value + token = Gitlab::LfsToken.new(user).value Result.new(user, :lfs_token) if user && token == password end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 0685eb775ef..63656f0b4f1 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -6,15 +6,17 @@ module Gitlab @actor = actor end - def set_token + def generate token = Devise.friendly_token(50) + Gitlab::Redis.with do |redis| - redis.set(redis_key, token, ex: 3600) + redis.set(redis_key, token, ex: 600) end + token end - def get_value + def value Gitlab::Redis.with do |redis| redis.get(redis_key) end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 6ce680e3c26..4c8e09cd904 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Auth, lib: true do it 'recognizes user lfs tokens' do user = create(:user) ip = 'ip' - token = Gitlab::LfsToken.new(user).set_token + token = Gitlab::LfsToken.new(user).generate expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :lfs_token)) @@ -35,7 +35,7 @@ describe Gitlab::Auth, lib: true do it 'recognizes deploy key lfs tokens' do key = create(:deploy_key) ip = 'ip' - token = Gitlab::LfsToken.new(key).set_token + token = Gitlab::LfsToken.new(key).generate expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs-deploy-key-#{key.id}") expect(gl_auth.find_for_git_client("lfs-deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, :lfs_deploy_token)) diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb index 76b348637c7..1d2e4fd9566 100644 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::LfsToken, lib: true do describe '#set_token and #get_value' do shared_examples 'an LFS token generator' do it 'returns a randomly generated token' do - token = handler.set_token + token = handler.generate expect(token).not_to be_nil expect(token).to be_a String @@ -12,9 +12,9 @@ describe Gitlab::LfsToken, lib: true do end it 'returns the correct token based on the key' do - token = handler.set_token + token = handler.generate - expect(handler.get_value).to eq(token) + expect(handler.value).to eq(token) end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 59df5af770b..ff697286927 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -100,15 +100,20 @@ describe API::API, api: true do end end - describe "GET /internal/discover" do + describe "POST /internal/lfs_authenticate" do + before do + project.team << [user, :developer] + end + context 'user key' do it 'returns the correct information about the key' do - get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) + lfs_auth(key, project) expect(response).to have_http_status(200) + expect(json_response['username']).to eq(user.username) + expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(user).value) - expect(json_response['name']).to eq(user.name) - expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(user).get_value) + expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) end end @@ -116,16 +121,26 @@ describe API::API, api: true do let(:key) { create(:deploy_key) } it 'returns the correct information about the key' do - get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) + lfs_auth(key, project) expect(response).to have_http_status(200) - expect(json_response['username']).to eq("lfs-deploy-key-#{key.id}") - expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).get_value) + expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value) + expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) end end end + describe "GET /internal/discover" do + it do + get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) + + expect(response).to have_http_status(200) + + expect(json_response['name']).to eq(user.name) + end + end + describe "POST /internal/allowed" do context "access granted" do before do @@ -159,7 +174,6 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(json_response["repository_http_path"]).to eq(project.http_url_to_repo) end end @@ -170,7 +184,6 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response["status"]).to be_truthy expect(json_response["repository_path"]).to eq(project.repository.path_to_repo) - expect(json_response["repository_http_path"]).to eq(project.http_url_to_repo) end end end @@ -407,4 +420,13 @@ describe API::API, api: true do protocol: 'ssh' ) end + + def lfs_auth(key, project) + post( + api("/internal/lfs_authenticate"), + key_id: key.id, + secret_token: secret_token, + project: project.path_with_namespace + ) + end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index d15e72b2570..e61502400ff 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -917,7 +917,7 @@ describe 'Git LFS API and storage' do end def authorize_deploy_key - ActionController::HttpAuthentication::Basic.encode_credentials("lfs-deploy-key-#{key.id}", Gitlab::LfsToken.new(key).set_token) + ActionController::HttpAuthentication::Basic.encode_credentials("lfs-deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate) end def fork_project(project, user, object = nil) -- cgit v1.2.3 From c25630ee2c2804e351a2c3ae4fd9224434e4698a Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 30 Aug 2016 18:43:24 -0500 Subject: Refactored handling of the `LfsToken` and added functionality to it to simplify external code. --- app/helpers/lfs_helper.rb | 4 +--- lib/api/internal.rb | 20 +++++++++++--------- lib/gitlab/auth.rb | 19 ++++++++++--------- lib/gitlab/lfs_token.rb | 8 ++++++++ spec/lib/gitlab/lfs_token_spec.rb | 16 ++++++++++++++++ 5 files changed, 46 insertions(+), 21 deletions(-) diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 2f5709a7395..031e7e72909 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -25,9 +25,7 @@ module LfsHelper def lfs_download_access? return false unless project.lfs_enabled? - return true if project.public? - return true if ci? - return true if lfs_deploy_key? + return true if project.public? || ci? || lfs_deploy_key? (user && user.can?(:download_code, project)) end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 1b3388347a8..1f189d81d16 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -80,16 +80,18 @@ module API key = Key.find(params[:key_id]) user = key.user - if user - token = Gitlab::LfsToken.new(user).generate - response = { username: user.username, lfs_token: token } - else - token = Gitlab::LfsToken.new(key).generate - response = { username: "lfs-deploy-key-#{key.id}", lfs_token: token } - end + token_handler = + if user + Gitlab::LfsToken.new(user) + else + Gitlab::LfsToken.new(key) + end - response[:repository_http_path] = project.http_url_to_repo - response + { + username: token_handler.actor_name, + lfs_token: token_handler.generate, + repository_http_path: project.http_url_to_repo + } end get "/merge_request_urls" do diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 1b0398d18ee..821c0ef87e9 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -117,15 +117,16 @@ module Gitlab end def lfs_token_check(login, password) - if login.include?('lfs-deploy-key') - key = DeployKey.find(login.gsub('lfs-deploy-key-', '')) - token = Gitlab::LfsToken.new(key).value - Result.new(key, :lfs_deploy_token) if key && token == password - else - user = User.by_login(login) - token = Gitlab::LfsToken.new(user).value - Result.new(user, :lfs_token) if user && token == password - end + actor = + if login.include?('lfs-deploy-key') + DeployKey.find(login.gsub('lfs-deploy-key-', '')) + else + User.by_login(login) + end + + token_handler = Gitlab::LfsToken.new(actor) + + Result.new(actor, token_handler.type) if actor && token_handler.value == password end end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 63656f0b4f1..8f49deb4d03 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -22,6 +22,14 @@ module Gitlab end end + def type + actor.is_a?(User) ? :lfs_token : :lfs_deploy_token + end + + def actor_name + actor.is_a?(User) ? actor.username : "lfs-deploy-key-#{actor.id}" + end + private def redis_key diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb index 1d2e4fd9566..f9812664e3b 100644 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -23,6 +23,14 @@ describe Gitlab::LfsToken, lib: true do let(:handler) { described_class.new(actor) } it_behaves_like 'an LFS token generator' + + it 'returns the correct username' do + expect(handler.actor_name).to eq(actor.username) + end + + it 'returns the correct token type' do + expect(handler.type).to eq(:lfs_token) + end end context 'when the actor is a deploy key' do @@ -30,6 +38,14 @@ describe Gitlab::LfsToken, lib: true do let(:handler) { described_class.new(actor) } it_behaves_like 'an LFS token generator' + + it 'returns the correct username' do + expect(handler.actor_name).to eq("lfs-deploy-key-#{actor.id}") + end + + it 'returns the correct token type' do + expect(handler.type).to eq(:lfs_deploy_token) + end end end end -- cgit v1.2.3 From 85152f0291b7e6dd4a92a068e7d5c4334df54e80 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 31 Aug 2016 11:03:46 -0500 Subject: Improve string handling. --- lib/gitlab/auth.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 821c0ef87e9..02b33c8c683 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -118,8 +118,8 @@ module Gitlab def lfs_token_check(login, password) actor = - if login.include?('lfs-deploy-key') - DeployKey.find(login.gsub('lfs-deploy-key-', '')) + if login.start_with?('lfs-deploy-key') + DeployKey.find(login.sub('lfs-deploy-key-', '')) else User.by_login(login) end -- cgit v1.2.3 From c144db2935f0f71c7f282a3015d126526bc16b57 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 6 Sep 2016 16:32:39 -0500 Subject: Better authentication handling, syntax fixes and better actor handling for LFS Tokens --- .../projects/git_http_client_controller.rb | 27 ++++++++--------- app/helpers/lfs_helper.rb | 2 +- lib/api/internal.rb | 9 +----- lib/gitlab/auth.rb | 35 +++++++++++----------- lib/gitlab/lfs_token.rb | 21 +++++++++++-- spec/requests/api/internal_spec.rb | 2 +- 6 files changed, 51 insertions(+), 45 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 4dff1ce6568..b4ec5b3fae1 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,8 +4,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - class MissingPersonalTokenError < StandardError; end - attr_reader :user # Git clients will not know what authenticity token to send along @@ -40,10 +38,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController send_challenges render plain: "HTTP Basic: Access denied\n", status: 401 - - rescue MissingPersonalTokenError + rescue Gitlab::Auth::MissingPersonalTokenError render_missing_personal_token - return end def basic_auth_provided? @@ -117,17 +113,20 @@ class Projects::GitHttpClientController < Projects::ApplicationController def handle_authentication(login, password) auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) - if auth_result.type == :ci && download_request? - @ci = true - elsif auth_result.type == :oauth && !download_request? - # Not allowed - elsif auth_result.type == :missing_personal_token - raise MissingPersonalTokenError - elsif auth_result.type == :lfs_deploy_token && download_request? - @lfs_deploy_key = true + case auth_result.type + when :ci + @ci = true if download_request? + when :oauth + @user = auth_result.user if download_request? + when :lfs_deploy_token + if download_request? + @lfs_deploy_key = true + @user = auth_result.user + end + when :lfs_token, :personal_token, :gitlab_or_ldap @user = auth_result.user else - @user = auth_result.user + # Not allowed end end diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 031e7e72909..de7c9f253b2 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -27,7 +27,7 @@ module LfsHelper return true if project.public? || ci? || lfs_deploy_key? - (user && user.can?(:download_code, project)) + user && user.can?(:download_code, project) end def lfs_upload_access? diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 1f189d81d16..f8211bdd8af 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -78,14 +78,7 @@ module API status 200 key = Key.find(params[:key_id]) - user = key.user - - token_handler = - if user - Gitlab::LfsToken.new(user) - else - Gitlab::LfsToken.new(key) - end + token_handler = Gitlab::LfsToken.new(key) { username: token_handler.actor_name, diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 02b33c8c683..14e29124aac 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -2,21 +2,13 @@ module Gitlab module Auth Result = Struct.new(:user, :type) + class MissingPersonalTokenError < StandardError; end + class << self def find_for_git_client(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? - result = Result.new - - if valid_ci_request?(login, password, project) - result.type = :ci - else - result = populate_result(login, password) - end - - success = result.user.present? || [:ci, :missing_personal_token].include?(result.type) - rate_limit!(ip, success: success, login: login) - result + populate_result(login, password, project, ip) end def find_with_user_password(login, password) @@ -75,21 +67,26 @@ module Gitlab end end - def populate_result(login, password) - result = + def populate_result(login, password, project, ip) + result = Result.new(nil, :ci) if valid_ci_request?(login, password, project) + + result ||= user_with_password_for_git(login, password) || oauth_access_token_check(login, password) || lfs_token_check(login, password) || personal_access_token_check(login, password) - if result + if result && result.type != :ci result.type = nil unless result.user if result.user && result.type == :gitlab_or_ldap && result.user.two_factor_enabled? - result.type = :missing_personal_token + raise Gitlab::Auth::MissingPersonalTokenError end end + success = result ? result.user.present? || [:ci].include?(result.type) : false + rate_limit!(ip, success: success, login: login) + result || Result.new end @@ -118,15 +115,17 @@ module Gitlab def lfs_token_check(login, password) actor = - if login.start_with?('lfs-deploy-key') - DeployKey.find(login.sub('lfs-deploy-key-', '')) + if login =~ /\Alfs-deploy-key-\d+\Z/ + /\d+\Z/.match(login) do |id| + DeployKey.find(id[0]) + end else User.by_login(login) end token_handler = Gitlab::LfsToken.new(actor) - Result.new(actor, token_handler.type) if actor && token_handler.value == password + Result.new(actor, token_handler.type) if actor && Devise.secure_compare(token_handler.value, password) end end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 8f49deb4d03..d7db8017475 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -2,15 +2,18 @@ module Gitlab class LfsToken attr_accessor :actor + TOKEN_LENGTH = 50 + EXPIRY_TIME = 1800 + def initialize(actor) - @actor = actor + set_actor(actor) end def generate - token = Devise.friendly_token(50) + token = Devise.friendly_token(TOKEN_LENGTH) Gitlab::Redis.with do |redis| - redis.set(redis_key, token, ex: 600) + redis.set(redis_key, token, ex: EXPIRY_TIME) end token @@ -35,5 +38,17 @@ module Gitlab def redis_key "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor end + + def set_actor(actor) + @actor = + case actor + when DeployKey, User + actor + when Key + actor.user + else + # + end + end end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index ff697286927..1ee390e0a19 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -111,7 +111,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) - expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(user).value) + expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value) expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) end -- cgit v1.2.3 From 71aff7f6a3ab63f1395bfab6ea49f0175fe08167 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 7 Sep 2016 11:55:54 -0500 Subject: Use special characters for `lfs+deploy-key` to prevent a someone from creating a user with this username, and method name refactoring. --- app/controllers/projects/git_http_client_controller.rb | 4 ++-- lib/gitlab/auth.rb | 2 +- lib/gitlab/lfs_token.rb | 2 +- spec/lib/gitlab/auth_spec.rb | 4 ++-- spec/lib/gitlab/lfs_token_spec.rb | 2 +- spec/requests/api/internal_spec.rb | 2 +- spec/requests/lfs_http_spec.rb | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index b4ec5b3fae1..4be580e759e 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -22,7 +22,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController if allow_basic_auth? && basic_auth_provided? login, password = user_name_and_password(request) - handle_authentication(login, password) + handle_basic_authentication(login, password) if ci? || user return # Allow access @@ -110,7 +110,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController @ci.present? end - def handle_authentication(login, password) + def handle_basic_authentication(login, password) auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) case auth_result.type diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 14e29124aac..f4e6ebb7bc7 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -115,7 +115,7 @@ module Gitlab def lfs_token_check(login, password) actor = - if login =~ /\Alfs-deploy-key-\d+\Z/ + if login =~ /\Alfs\+deploy-key-\d+\Z/ /\d+\Z/.match(login) do |id| DeployKey.find(id[0]) end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index d7db8017475..edf4dffc4c0 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -30,7 +30,7 @@ module Gitlab end def actor_name - actor.is_a?(User) ? actor.username : "lfs-deploy-key-#{actor.id}" + actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}" end private diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 4c8e09cd904..56f349f5d92 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -37,8 +37,8 @@ describe Gitlab::Auth, lib: true do ip = 'ip' token = Gitlab::LfsToken.new(key).generate - expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs-deploy-key-#{key.id}") - expect(gl_auth.find_for_git_client("lfs-deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, :lfs_deploy_token)) + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}") + expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, :lfs_deploy_token)) end it 'recognizes OAuth tokens' do diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb index f9812664e3b..184f235c1b2 100644 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -40,7 +40,7 @@ describe Gitlab::LfsToken, lib: true do it_behaves_like 'an LFS token generator' it 'returns the correct username' do - expect(handler.actor_name).to eq("lfs-deploy-key-#{actor.id}") + expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}") end it 'returns the correct token type' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 1ee390e0a19..2e1e6a11b53 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -124,7 +124,7 @@ describe API::API, api: true do lfs_auth(key, project) expect(response).to have_http_status(200) - expect(json_response['username']).to eq("lfs-deploy-key-#{key.id}") + expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}") expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value) expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index e61502400ff..54ecb793729 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -917,7 +917,7 @@ describe 'Git LFS API and storage' do end def authorize_deploy_key - ActionController::HttpAuthentication::Basic.encode_credentials("lfs-deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate) + ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate) end def fork_project(project, user, object = nil) -- cgit v1.2.3 From be09bcf074e6048aa9ba5f8dfb99754e6afbe156 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 15 Sep 2016 11:54:24 -0500 Subject: Refactored authentication code to make it a bit clearer, added test for wrong SSH key. --- .../projects/git_http_client_controller.rb | 25 +++++++++---- lib/gitlab/auth.rb | 43 ++++++++++------------ lib/gitlab/lfs_token.rb | 2 +- spec/lib/gitlab/auth_spec.rb | 2 +- spec/lib/gitlab/lfs_token_spec.rb | 2 +- spec/requests/api/internal_spec.rb | 14 +++++-- 6 files changed, 50 insertions(+), 38 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index f5a07608bf8..4dae953b69f 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :user, :actor + attr_reader :actor # Git clients will not know what authenticity token to send along skip_before_action :verify_authenticity_token @@ -22,9 +22,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController if allow_basic_auth? && basic_auth_provided? login, password = user_name_and_password(request) - handle_basic_authentication(login, password) - - if ci? || actor + if handle_basic_authentication(login, password) return # Allow access end elsif allow_kerberos_spnego_auth? && spnego_provided? @@ -107,7 +105,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def ci? - @ci.present? + @ci end def user @@ -119,9 +117,17 @@ class Projects::GitHttpClientController < Projects::ApplicationController case auth_result.type when :ci - @ci = true if download_request? + if download_request? + @ci = true + else + return false + end when :oauth - @actor = auth_result.actor if download_request? + if download_request? + @actor = auth_result.actor + else + return false + end when :lfs_deploy_token if download_request? @lfs_deploy_key = true @@ -131,11 +137,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController @actor = auth_result.actor else # Not allowed + return false end + + true end def lfs_deploy_key? - @lfs_deploy_key.present? && actor && actor.projects.include?(project) + @lfs_deploy_key && actor && actor.projects.include?(project) end def verify_workhorse_api! diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 391b8f2f5de..6be9bf7de44 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,10 @@ module Gitlab module Auth - Result = Struct.new(:actor, :type) + Result = Struct.new(:actor, :type) do + def success? + actor.present? || type == :ci + end + end class MissingPersonalTokenError < StandardError; end @@ -8,7 +12,16 @@ module Gitlab def find_for_git_client(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? - populate_result(login, password, project, ip) + result = + ci_request_check(login, password, project) || + user_with_password_for_git(login, password) || + oauth_access_token_check(login, password) || + lfs_token_check(login, password) || + personal_access_token_check(login, password) + + rate_limit!(ip, success: result && result.success?, login: login) + + result || Result.new end def find_with_user_password(login, password) @@ -49,24 +62,6 @@ module Gitlab private - def populate_result(login, password, project, ip) - result = - ci_request_check(login, password, project) || - user_with_password_for_git(login, password) || - oauth_access_token_check(login, password) || - lfs_token_check(login, password) || - personal_access_token_check(login, password) - - if result && result.type != :ci - result.type = nil unless result.actor - end - - success = result ? result.actor.present? || result.type == :ci : false - rate_limit!(ip, success: success, login: login) - - result || Result.new - end - def valid_ci_request?(login, password, project) matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) @@ -110,7 +105,7 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, :personal_token) if user == validation + Result.new(user, :personal_token) if user.present? && user == validation end end @@ -124,9 +119,11 @@ module Gitlab User.by_login(login) end - token_handler = Gitlab::LfsToken.new(actor) + if actor + token_handler = Gitlab::LfsToken.new(actor) - Result.new(actor, token_handler.type) if actor && Devise.secure_compare(token_handler.value, password) + Result.new(actor, token_handler.type) if Devise.secure_compare(token_handler.value, password) + end end end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 224e4516074..f492754b1c8 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -13,7 +13,7 @@ module Gitlab when Key actor.user else - # + raise 'Bad Actor' end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 56f349f5d92..13c5a7156f5 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -55,7 +55,7 @@ describe Gitlab::Auth, lib: true do login = 'foo' ip = 'ip' - expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login) + expect(gl_auth).to receive(:rate_limit!).with(ip, success: nil, login: login) expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new) end end diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb index 184f235c1b2..9f04f67e0a8 100644 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::LfsToken, lib: true do - describe '#set_token and #get_value' do + describe '#generate and #value' do shared_examples 'an LFS token generator' do it 'returns a randomly generated token' do token = handler.generate diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 2e1e6a11b53..46e8e6f1169 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -107,7 +107,7 @@ describe API::API, api: true do context 'user key' do it 'returns the correct information about the key' do - lfs_auth(key, project) + lfs_auth(key.id, project) expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) @@ -115,13 +115,19 @@ describe API::API, api: true do expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) end + + it 'returns a 404 when the wrong key is provided' do + lfs_auth(nil, project) + + expect(response).to have_http_status(404) + end end context 'deploy key' do let(:key) { create(:deploy_key) } it 'returns the correct information about the key' do - lfs_auth(key, project) + lfs_auth(key.id, project) expect(response).to have_http_status(200) expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}") @@ -421,10 +427,10 @@ describe API::API, api: true do ) end - def lfs_auth(key, project) + def lfs_auth(key_id, project) post( api("/internal/lfs_authenticate"), - key_id: key.id, + key_id: key_id, secret_token: secret_token, project: project.path_with_namespace ) -- cgit v1.2.3 From de24075ea5960bd7c6290c05496915e8f0ca23f2 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 8 Sep 2016 12:32:20 -0500 Subject: Further refactoring of authentication code, and code style fixes. --- .../projects/git_http_client_controller.rb | 20 ++++---- lib/gitlab/auth.rb | 53 +++++++++++----------- lib/gitlab/lfs_token.rb | 22 ++++----- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 4be580e759e..f5a07608bf8 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :user + attr_reader :user, :actor # Git clients will not know what authenticity token to send along skip_before_action :verify_authenticity_token @@ -24,13 +24,13 @@ class Projects::GitHttpClientController < Projects::ApplicationController handle_basic_authentication(login, password) - if ci? || user + if ci? || actor return # Allow access end elsif allow_kerberos_spnego_auth? && spnego_provided? - @user = find_kerberos_user + @actor = find_kerberos_user - if user + if actor send_final_spnego_response return # Allow access end @@ -110,6 +110,10 @@ class Projects::GitHttpClientController < Projects::ApplicationController @ci.present? end + def user + @actor + end + def handle_basic_authentication(login, password) auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) @@ -117,21 +121,21 @@ class Projects::GitHttpClientController < Projects::ApplicationController when :ci @ci = true if download_request? when :oauth - @user = auth_result.user if download_request? + @actor = auth_result.actor if download_request? when :lfs_deploy_token if download_request? @lfs_deploy_key = true - @user = auth_result.user + @actor = auth_result.actor end when :lfs_token, :personal_token, :gitlab_or_ldap - @user = auth_result.user + @actor = auth_result.actor else # Not allowed end end def lfs_deploy_key? - @lfs_deploy_key.present? && (user && user.projects.include?(project)) + @lfs_deploy_key.present? && actor && actor.projects.include?(project) end def verify_workhorse_api! diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index f4e6ebb7bc7..391b8f2f5de 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,6 @@ module Gitlab module Auth - Result = Struct.new(:user, :type) + Result = Struct.new(:actor, :type) class MissingPersonalTokenError < StandardError; end @@ -49,6 +49,24 @@ module Gitlab private + def populate_result(login, password, project, ip) + result = + ci_request_check(login, password, project) || + user_with_password_for_git(login, password) || + oauth_access_token_check(login, password) || + lfs_token_check(login, password) || + personal_access_token_check(login, password) + + if result && result.type != :ci + result.type = nil unless result.actor + end + + success = result ? result.actor.present? || result.type == :ci : false + rate_limit!(ip, success: success, login: login) + + result || Result.new + end + def valid_ci_request?(login, password, project) matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) @@ -67,31 +85,14 @@ module Gitlab end end - def populate_result(login, password, project, ip) - result = Result.new(nil, :ci) if valid_ci_request?(login, password, project) - - result ||= - user_with_password_for_git(login, password) || - oauth_access_token_check(login, password) || - lfs_token_check(login, password) || - personal_access_token_check(login, password) - - if result && result.type != :ci - result.type = nil unless result.user - - if result.user && result.type == :gitlab_or_ldap && result.user.two_factor_enabled? - raise Gitlab::Auth::MissingPersonalTokenError - end - end - - success = result ? result.user.present? || [:ci].include?(result.type) : false - rate_limit!(ip, success: success, login: login) - - result || Result.new + def ci_request_check(login, password, project) + Result.new(nil, :ci) if valid_ci_request?(login, password, project) end def user_with_password_for_git(login, password) user = find_with_user_password(login, password) + raise Gitlab::Auth::MissingPersonalTokenError if user && user.two_factor_enabled? + Result.new(user, :gitlab_or_ldap) if user end @@ -114,11 +115,11 @@ module Gitlab end def lfs_token_check(login, password) + deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/) + actor = - if login =~ /\Alfs\+deploy-key-\d+\Z/ - /\d+\Z/.match(login) do |id| - DeployKey.find(id[0]) - end + if deploy_key_matches + DeployKey.find(deploy_key_matches[1]) else User.by_login(login) end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index edf4dffc4c0..224e4516074 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -6,7 +6,15 @@ module Gitlab EXPIRY_TIME = 1800 def initialize(actor) - set_actor(actor) + @actor = + case actor + when DeployKey, User + actor + when Key + actor.user + else + # + end end def generate @@ -38,17 +46,5 @@ module Gitlab def redis_key "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor end - - def set_actor(actor) - @actor = - case actor - when DeployKey, User - actor - when Key - actor.user - else - # - end - end end end -- cgit v1.2.3 From fd621429508ba3877e27a8187f0d491576b65ad0 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 1 Sep 2016 18:49:48 -0500 Subject: Added group-specific setting for LFS. Groups can enable/disable LFS, but this setting can be overridden at the project level. Admin only --- app/controllers/admin/groups_controller.rb | 10 +++- app/controllers/groups_controller.rb | 12 +++- app/helpers/groups_helper.rb | 5 ++ app/helpers/projects_helper.rb | 4 +- app/models/group.rb | 7 +++ app/models/namespace.rb | 5 ++ app/models/project.rb | 9 ++- app/views/admin/groups/_form.html.haml | 2 + app/views/admin/groups/show.html.haml | 7 +++ app/views/admin/projects/show.html.haml | 2 +- app/views/groups/edit.html.haml | 2 + app/views/shared/_visibility_level.html.haml | 2 +- .../shared/groups/_group_lfs_settings.html.haml | 11 ++++ ...20160901213340_add_lfs_enabled_to_namespaces.rb | 29 ++++++++++ db/schema.rb | 1 + doc/api/groups.md | 2 + lib/api/entities.rb | 2 +- lib/api/groups.rb | 6 +- spec/models/project_spec.rb | 66 ++++++++++++++++++++++ 19 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 app/views/shared/groups/_group_lfs_settings.html.haml create mode 100644 db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index cdfa8d91a28..adb8a891c32 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -60,6 +60,14 @@ class Admin::GroupsController < Admin::ApplicationController end def group_params - params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled) + params.require(:group).permit( + :name, + :description, + :path, + :avatar, + :visibility_level, + :request_access_enabled, + :lfs_enabled + ) end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index cb82d62616c..2f7113aa709 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -121,7 +121,17 @@ class GroupsController < Groups::ApplicationController end def group_params - params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled) + params.require(:group).permit( + :name, + :description, + :path, + :avatar, + :public, + :visibility_level, + :share_with_group_lock, + :request_access_enabled, + :lfs_enabled + ) end def load_events diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index b9211e88473..e87197d2056 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -23,4 +23,9 @@ module GroupsHelper full_title end end + + def projects_with_lfs_enabled(group) + total = group.projects.size + "#{total - group.projects.select{ |p| !p.lfs_enabled? }.size}/#{total} projects have it enabled" + end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 16a8e52a4ca..b4be679c72d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -202,8 +202,8 @@ module ProjectsHelper nav_tabs.flatten end - def project_lfs_status(project) - if project.lfs_enabled? + def lfs_status_helper(subject) + if subject.lfs_enabled? content_tag(:span, class: 'lfs-enabled') do 'Enabled' end diff --git a/app/models/group.rb b/app/models/group.rb index c48869ae465..aefb94b2ada 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -95,6 +95,13 @@ class Group < Namespace end end + def lfs_enabled? + return false unless Gitlab.config.lfs.enabled + return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil? + + self[:lfs_enabled] + end + def add_users(user_ids, access_level, current_user: nil, expires_at: nil) user_ids.each do |user_id| Member.add_user( diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 7c29d27ce97..919b3b1f095 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -141,6 +141,11 @@ class Namespace < ActiveRecord::Base projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id) end + def lfs_enabled? + # User namespace will always default to the global setting + Gitlab.config.lfs.enabled + end + private def repository_storage_paths diff --git a/app/models/project.rb b/app/models/project.rb index f3f3ffbbd28..8e3bedffe1f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -393,10 +393,13 @@ class Project < ActiveRecord::Base end def lfs_enabled? - return false unless Gitlab.config.lfs.enabled - return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil? + # Specifically check is lfs_enabled is false + return false if self[:lfs_enabled] == false - self[:lfs_enabled] + # Should only fallback to the namespace value if no value is set for the project + return namespace.lfs_enabled? if self[:lfs_enabled].nil? + + self[:lfs_enabled] && Gitlab.config.lfs.enabled end def repository_storage_path diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 5f7fdfdb011..cb631cbfb8d 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -13,6 +13,8 @@ .col-sm-offset-2.col-sm-10 = render 'shared/allow_request_access', form: f + = render 'shared/groups/group_lfs_settings', f: f + - if @group.new_record? .form-group .col-sm-offset-2.col-sm-10 diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index bb374694400..b8e83075a72 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -37,6 +37,13 @@ %strong = @group.created_at.to_s(:medium) + %li + %span.light Group Git LFS status: + %strong + = lfs_status_helper(@group) + = projects_with_lfs_enabled(@group) + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + .panel.panel-default .panel-heading %h3.panel-title diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 6c7c3c48604..eecc69b125c 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -77,7 +77,7 @@ %li %span.light Git LFS status: %strong - = project_lfs_status(@project) + = lfs_status_helper(@project) = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - else %li diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index decb89b2fd6..849d0f360e7 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -25,6 +25,8 @@ .col-sm-offset-2.col-sm-10 = render 'shared/allow_request_access', form: f + = render 'shared/groups/group_lfs_settings', f: f + .form-group %hr = f.label :share_with_group_lock, class: 'control-label' do diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml index 107ad19177c..add4536a0a2 100644 --- a/app/views/shared/_visibility_level.html.haml +++ b/app/views/shared/_visibility_level.html.haml @@ -1,7 +1,7 @@ .form-group.project-visibility-level-holder = f.label :visibility_level, class: 'control-label' do Visibility Level - = link_to "(?)", help_page_path("public_access/public_access") + = link_to icon('question-circle'), help_page_path("public_access/public_access") .col-sm-10 - if can_change_visibility_level = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) diff --git a/app/views/shared/groups/_group_lfs_settings.html.haml b/app/views/shared/groups/_group_lfs_settings.html.haml new file mode 100644 index 00000000000..af57065f0fc --- /dev/null +++ b/app/views/shared/groups/_group_lfs_settings.html.haml @@ -0,0 +1,11 @@ +- if current_user.admin? + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :lfs_enabled do + = f.check_box :lfs_enabled, checked: @group.lfs_enabled? + %strong + Allow projects within this group to use Git LFS + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + %br/ + %span.descr This setting can be overridden in each project. \ No newline at end of file diff --git a/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb new file mode 100644 index 00000000000..9b3ee915e21 --- /dev/null +++ b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddLfsEnabledToNamespaces < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :namespaces, :lfs_enabled, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 61873e38113..70279f372c9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -650,6 +650,7 @@ ActiveRecord::Schema.define(version: 20160913162434) do t.integer "visibility_level", default: 20, null: false t.boolean "request_access_enabled", default: true, null: false t.datetime "deleted_at" + t.boolean "lfs_enabled" end add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree diff --git a/doc/api/groups.md b/doc/api/groups.md index a898387eaa2..cb49648c36f 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -288,6 +288,7 @@ Parameters: - `path` (required) - The path of the group - `description` (optional) - The group's description - `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public. +- `lfs_enabled` (optional) - Enable/disable LFS for the projects in this group ## Transfer project to group @@ -317,6 +318,7 @@ PUT /groups/:id | `path` | string | no | The path of the group | | `description` | string | no | The description of the group | | `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. | +| `lfs_enabled` (optional) | boolean | no | Enable/disable LFS for the projects in this group | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4f736e4ec2b..1529a44f58d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -120,7 +120,7 @@ module API end class Group < Grape::Entity - expose :id, :name, :path, :description, :visibility_level + expose :id, :name, :path, :description, :visibility_level, :lfs_enabled expose :avatar_url expose :web_url end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index d2df77238d5..60ac9bdfa33 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -27,13 +27,14 @@ module API # path (required) - The path of the group # description (optional) - The description of the group # visibility_level (optional) - The visibility level of the group + # lfs_enabled (optional) - Enable/disable LFS for the projects in this group # Example Request: # POST /groups post do authorize! :create_group required_attributes! [:name, :path] - attrs = attributes_for_keys [:name, :path, :description, :visibility_level] + attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled] @group = Group.new(attrs) if @group.save @@ -51,13 +52,14 @@ module API # path (optional) - The path of the group # description (optional) - The description of the group # visibility_level (optional) - The visibility level of the group + # lfs_enabled (optional) - Enable/disable LFS for the projects in this group # Example Request: # PUT /groups/:id put ':id' do group = find_group(params[:id]) authorize! :admin_group, group - attrs = attributes_for_keys [:name, :path, :description, :visibility_level] + attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled] if ::Groups::UpdateService.new(group, current_user, attrs).execute present group, with: Entities::GroupDetail diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f6e811828fc..7ca1bd1e5c9 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1417,6 +1417,68 @@ describe Project, models: true do end end + describe '#lfs_enabled?' do + let(:project) { create(:project) } + + shared_examples 'project overrides group' do + it 'returns true when enabled in project' do + project.update_attribute(:lfs_enabled, true) + + expect(project.lfs_enabled?).to be_truthy + end + + it 'returns false when disabled in project' do + project.update_attribute(:lfs_enabled, false) + + expect(project.lfs_enabled?).to be_falsey + end + + it 'returns the value from the namespace, when no value is set in project' do + expect(project.lfs_enabled?).to eq(project.namespace.lfs_enabled?) + end + end + + context 'LFS disabled in group' do + before do + project.namespace.update_attribute(:lfs_enabled, false) + enable_lfs + end + + it_behaves_like 'project overrides group' + end + + context 'LFS enabled in group' do + before do + project.namespace.update_attribute(:lfs_enabled, true) + enable_lfs + end + + it_behaves_like 'project overrides group' + end + + describe 'LFS disabled globally' do + shared_examples 'it always returns false' do + it do + expect(project.lfs_enabled?).to be_falsey + expect(project.namespace.lfs_enabled?).to be_falsey + end + end + + context 'when no values are set' do + it_behaves_like 'it always returns false' + end + + context 'when all values are set to true' do + before do + project.namespace.update_attribute(:lfs_enabled, true) + project.update_attribute(:lfs_enabled, true) + end + + it_behaves_like 'it always returns false' + end + end + end + describe '.where_paths_in' do context 'without any paths' do it 'returns an empty relation' do @@ -1581,4 +1643,8 @@ describe Project, models: true do expect(project.pushes_since_gc).to eq(0) end end + + def enable_lfs + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end end -- cgit v1.2.3 From a8b0d501017ed8ab8656e8cabe5c29ed7e3cbe89 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 1 Sep 2016 18:57:38 -0500 Subject: Added CHANGELOG item --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0056c6cc649..304839c7e77 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -76,6 +76,8 @@ v 8.12.0 (unreleased) - Fix accessibility and visibility of project list dropdown button !6140 - Fix missing flash messages on service edit page (airatshigapov) - Added project specific enable/disable setting for LFS !5997 + - Added project-specific enable/disable setting for LFS !5997 + - Added group-specific enable/disable setting for LFS !6164 - Don't expose a user's token in the `/api/v3/user` API (!6047) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Ability to manage project issues, snippets, wiki, merge requests and builds access level -- cgit v1.2.3 From c788c66a85ba4682f5e27a12b5fa73489af23b5f Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 5 Sep 2016 13:14:49 -0500 Subject: Improved helper methods, better flow for `project.lfs_enabled?`, and UI fixes. --- app/helpers/groups_helper.rb | 23 ++++++++++++++++++++-- app/helpers/projects_helper.rb | 4 ++-- app/models/project.rb | 4 ---- app/views/admin/groups/show.html.haml | 3 +-- app/views/admin/projects/show.html.haml | 2 +- app/views/projects/edit.html.haml | 17 ++++++++-------- ...20160901213340_add_lfs_enabled_to_namespaces.rb | 17 ---------------- 7 files changed, 33 insertions(+), 37 deletions(-) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index e87197d2056..0352a48e050 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -25,7 +25,26 @@ module GroupsHelper end def projects_with_lfs_enabled(group) - total = group.projects.size - "#{total - group.projects.select{ |p| !p.lfs_enabled? }.size}/#{total} projects have it enabled" + lfs_enabled = group.projects.select(&:lfs_enabled?).size + size = group.projects.size + + if lfs_enabled == size || lfs_enabled == 0 + ' on all projects' + else + " on #{lfs_enabled}/#{size} projects" + end + end + + def group_lfs_status(group) + if group.lfs_enabled? + output = content_tag(:span, class: 'lfs-enabled') do + 'Enabled' + end + else + output = content_tag(:span, class: 'lfs-disabled') do + 'Disabled' + end + end + output << projects_with_lfs_enabled(group) end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index b4be679c72d..16a8e52a4ca 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -202,8 +202,8 @@ module ProjectsHelper nav_tabs.flatten end - def lfs_status_helper(subject) - if subject.lfs_enabled? + def project_lfs_status(project) + if project.lfs_enabled? content_tag(:span, class: 'lfs-enabled') do 'Enabled' end diff --git a/app/models/project.rb b/app/models/project.rb index 8e3bedffe1f..8b5a6f167bd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -393,10 +393,6 @@ class Project < ActiveRecord::Base end def lfs_enabled? - # Specifically check is lfs_enabled is false - return false if self[:lfs_enabled] == false - - # Should only fallback to the namespace value if no value is set for the project return namespace.lfs_enabled? if self[:lfs_enabled].nil? self[:lfs_enabled] && Gitlab.config.lfs.enabled diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index b8e83075a72..0188ed448ce 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -40,8 +40,7 @@ %li %span.light Group Git LFS status: %strong - = lfs_status_helper(@group) - = projects_with_lfs_enabled(@group) + = group_lfs_status(@group) = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') .panel.panel-default diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index eecc69b125c..6c7c3c48604 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -77,7 +77,7 @@ %li %span.light Git LFS status: %strong - = lfs_status_helper(@project) + = project_lfs_status(@project) = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - else %li diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index f6d751a343e..a04d53e02bf 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -84,15 +84,14 @@ = project_feature_access_select(:snippets_access_level) - if Gitlab.config.lfs.enabled && current_user.admin? - .form-group - .checkbox - = f.label :lfs_enabled do - = f.check_box :lfs_enabled, checked: @project.lfs_enabled? - %strong LFS - %br - %span.descr - Git Large File Storage - = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + .row + .col-md-9 + = f.label :lfs_enabled, 'LFS', class: 'label-light' + %span.help-block + Git Large File Storage + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + .col-md-3 + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control' - if Gitlab.config.registry.enabled .form-group diff --git a/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb index 9b3ee915e21..fd413d1ca8c 100644 --- a/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb +++ b/db/migrate/20160901213340_add_lfs_enabled_to_namespaces.rb @@ -4,25 +4,8 @@ class AddLfsEnabledToNamespaces < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - # Set this constant to true if this migration requires downtime. DOWNTIME = false - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def change add_column :namespaces, :lfs_enabled, :boolean end -- cgit v1.2.3 From d0279ccba5c4a2cd8611ddec04eeff67e0e9f9c6 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 5 Sep 2016 15:40:49 -0500 Subject: Correct helper for group LFS status. --- app/helpers/groups_helper.rb | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 0352a48e050..76911efe354 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -24,27 +24,27 @@ module GroupsHelper end end - def projects_with_lfs_enabled(group) - lfs_enabled = group.projects.select(&:lfs_enabled?).size + def projects_with_lfs_enabled(group, status) + if status + lfs_status = group.projects.select(&:lfs_enabled?).size + else + lfs_status = group.projects.select{ |p| !p.lfs_enabled? }.size + end + size = group.projects.size - if lfs_enabled == size || lfs_enabled == 0 - ' on all projects' + if lfs_status == size || lfs_status == 0 + 'on all projects' else - " on #{lfs_enabled}/#{size} projects" + "on #{lfs_status} out of #{size} projects" end end def group_lfs_status(group) - if group.lfs_enabled? - output = content_tag(:span, class: 'lfs-enabled') do - 'Enabled' - end - else - output = content_tag(:span, class: 'lfs-disabled') do - 'Disabled' - end + status = group.lfs_enabled? ? 'enabled' : 'disabled' + + content_tag(:span, class: "lfs-#{status}") do + "#{status.humanize} #{projects_with_lfs_enabled(group, group.lfs_enabled?)}" end - output << projects_with_lfs_enabled(group) end end -- cgit v1.2.3 From dfc7f50ce5569bacdb4167e39b1c37fc02430313 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Sep 2016 17:26:04 -0400 Subject: Bump rubocop-rspec to 1.7.0 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 70d8495325f..1f83f8c83f2 100644 --- a/Gemfile +++ b/Gemfile @@ -296,7 +296,7 @@ group :development, :test do gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'rubocop', '~> 0.42.0', require: false - gem 'rubocop-rspec', '~> 1.5.0', require: false + gem 'rubocop-rspec', '~> 1.7.0', require: false gem 'scss_lint', '~> 0.47.0', require: false gem 'haml_lint', '~> 0.18.2', require: false gem 'simplecov', '0.12.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index e2b5a58d973..1edb218373d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -625,8 +625,8 @@ GEM rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - rubocop-rspec (1.5.0) - rubocop (>= 0.40.0) + rubocop-rspec (1.7.0) + rubocop (>= 0.42.0) ruby-fogbugz (0.2.1) crack (~> 0.4) ruby-prof (0.15.9) @@ -945,7 +945,7 @@ DEPENDENCIES rspec-rails (~> 3.5.0) rspec-retry (~> 0.4.5) rubocop (~> 0.42.0) - rubocop-rspec (~> 1.5.0) + rubocop-rspec (~> 1.7.0) ruby-fogbugz (~> 0.2.1) ruby-prof (~> 0.15.9) sanitize (~> 2.0) -- cgit v1.2.3 From 2dbcd0eb97562a60480d8dd86b10af580cc52ef5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 14 Sep 2016 17:26:48 -0400 Subject: Update `.rubocop.yml` for rubocop-rspec 1.7.0 --- .rubocop.yml | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5bd31ccf329..b054675d677 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -767,26 +767,33 @@ Rails/ScopeArgs: RSpec/AnyInstance: Enabled: false -# Check that the first argument to the top level describe is the tested class or -# module. +# Check for expectations where `be(...)` can replace `eql(...)`. +RSpec/BeEql: + Enabled: false + +# Check that the first argument to the top level describe is a constant. RSpec/DescribeClass: Enabled: false -# Use `described_class` for tested class / module. +# Checks that tests use `described_class`. +RSpec/DescribedClass: + Enabled: false + +# Checks that the second argument to `describe` specifies a method. RSpec/DescribeMethod: Enabled: false -# Checks that the second argument to top level describe is the tested method -# name. -RSpec/DescribedClass: +# Checks if an example group does not include any tests. +RSpec/EmptyExampleGroup: Enabled: false + CustomIncludeMethods: [] -# Checks for long example. +# Checks for long examples. RSpec/ExampleLength: Enabled: false Max: 5 -# Do not use should when describing your tests. +# Checks that example descriptions do not start with "should". RSpec/ExampleWording: Enabled: false CustomTransform: @@ -795,6 +802,10 @@ RSpec/ExampleWording: not: does not IgnoredWords: [] +# Checks for `expect(...)` calls containing literal values. +RSpec/ExpectActual: + Enabled: false + # Checks the file and folder naming of the spec file. RSpec/FilePath: Enabled: false @@ -806,19 +817,65 @@ RSpec/FilePath: RSpec/Focus: Enabled: true +# Checks the arguments passed to `before`, `around`, and `after`. +RSpec/HookArgument: + Enabled: false + EnforcedStyle: implicit + +# Check that a consistent implict expectation style is used. +# TODO (rspeicher): Available in rubocop-rspec 1.8.0 +# RSpec/ImplicitExpect: +# Enabled: true +# EnforcedStyle: is_expected + # Checks for the usage of instance variables. RSpec/InstanceVariable: Enabled: false -# Checks for multiple top-level describes. +# Checks for `subject` definitions that come after `let` definitions. +RSpec/LeadingSubject: + Enabled: false + +# Checks unreferenced `let!` calls being used for test setup. +RSpec/LetSetup: + Enabled: false + +# Check that chains of messages are not being stubbed. +RSpec/MessageChain: + Enabled: false + +# Checks for consistent message expectation style. +RSpec/MessageExpectation: + Enabled: false + EnforcedStyle: allow + +# Checks for multiple top level describes. RSpec/MultipleDescribes: Enabled: false -# Enforces the usage of the same method on all negative message expectations. +# Checks if examples contain too many `expect` calls. +RSpec/MultipleExpectations: + Enabled: false + Max: 1 + +# Checks for explicitly referenced test subjects. +RSpec/NamedSubject: + Enabled: false + +# Checks for nested example groups. +RSpec/NestedGroups: + Enabled: false + MaxNesting: 2 + +# Checks for consistent method usage for negating expectations. RSpec/NotToNot: EnforcedStyle: not_to Enabled: true +# Checks for stubbed test subjects. +RSpec/SubjectStub: + Enabled: false + # Prefer using verifying doubles over normal doubles. RSpec/VerifiedDoubles: Enabled: false -- cgit v1.2.3 From eed7960298d4dfc7f97c913fe31b7df2e0352d34 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 13 Sep 2016 15:36:13 -0300 Subject: Fix issuable templates dropdown for forked projects --- CHANGELOG | 1 + app/views/shared/issuable/_form.html.haml | 2 +- spec/features/projects/issuable_templates_spec.rb | 20 ++++++++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0056c6cc649..f0a637507fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 8.12.0 (unreleased) - Cleanup misalignments in Issue list view !6206 - Prune events older than 12 months. (ritave) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) + - Fix issues/merge-request templates dropdown for forked projects - Filter tags by name !6121 - Update gitlab shell secret file also when it is empty. !3774 (glensc) - Give project selection dropdowns responsive width, make non-wrapping. diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 3856a4917b4..04373684ee9 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -19,7 +19,7 @@ = dropdown_tag(title, options: { toggle_class: 'js-issuable-selector', title: title, filter: true, placeholder: 'Filter', footer_content: true, - data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: @project.path, namespace_path: @project.namespace.path } } ) do + data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do %ul.dropdown-footer-list %li %a.reset-template diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb index d0f4e5469ed..f76c4fe8b57 100644 --- a/spec/features/projects/issuable_templates_spec.rb +++ b/spec/features/projects/issuable_templates_spec.rb @@ -64,7 +64,7 @@ feature 'issuable templates', feature: true, js: true do let(:template_content) { 'this is a test "feature-proposal" template' } let(:fork_user) { create(:user) } let(:fork_project) { create(:project, :public) } - let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project) } + let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) } background do logout @@ -72,16 +72,20 @@ feature 'issuable templates', feature: true, js: true do fork_project.team << [fork_user, :master] create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project) login_as fork_user - fork_project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false) - visit edit_namespace_project_merge_request_path fork_project.namespace, fork_project, merge_request + project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false) + visit edit_namespace_project_merge_request_path project.namespace, project, merge_request fill_in :'merge_request[title]', with: 'test merge request title' end - scenario 'user selects "feature-proposal" template' do - select_template 'feature-proposal' - wait_for_ajax - preview_template - save_changes + context 'feature proposal template' do + context 'template exists in target project' do + scenario 'user selects template' do + select_template 'feature-proposal' + wait_for_ajax + preview_template + save_changes + end + end end end -- cgit v1.2.3 From 02ddb9dff4084f615f744614cf81dc4166d61668 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 6 Sep 2016 11:48:00 -0500 Subject: Syntax fixes and better tests for helper methods. Updated docs. --- CHANGELOG | 1 - app/controllers/admin/groups_controller.rb | 8 +-- app/controllers/groups_controller.rb | 10 ++-- app/helpers/groups_helper.rb | 21 ++++---- app/views/admin/groups/_form.html.haml | 2 +- app/views/groups/_group_lfs_settings.html.haml | 11 ++++ app/views/groups/edit.html.haml | 2 +- .../shared/groups/_group_lfs_settings.html.haml | 11 ---- doc/api/groups.md | 4 +- lib/api/entities.rb | 6 ++- spec/helpers/groups_helper_spec.rb | 63 ++++++++++++++++++++++ spec/models/group_spec.rb | 46 ++++++++++++++++ 12 files changed, 148 insertions(+), 37 deletions(-) create mode 100644 app/views/groups/_group_lfs_settings.html.haml delete mode 100644 app/views/shared/groups/_group_lfs_settings.html.haml diff --git a/CHANGELOG b/CHANGELOG index 304839c7e77..868d4af439a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -75,7 +75,6 @@ v 8.12.0 (unreleased) - Add last commit time to repo view (ClemMakesApps) - Fix accessibility and visibility of project list dropdown button !6140 - Fix missing flash messages on service edit page (airatshigapov) - - Added project specific enable/disable setting for LFS !5997 - Added project-specific enable/disable setting for LFS !5997 - Added group-specific enable/disable setting for LFS !6164 - Don't expose a user's token in the `/api/v3/user` API (!6047) diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index adb8a891c32..aed77d0358a 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -61,13 +61,13 @@ class Admin::GroupsController < Admin::ApplicationController def group_params params.require(:group).permit( - :name, + :avatar, :description, + :lfs_enabled, + :name, :path, - :avatar, - :visibility_level, :request_access_enabled, - :lfs_enabled + :visibility_level ) end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 2f7113aa709..b83c3a872cf 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -122,15 +122,15 @@ class GroupsController < Groups::ApplicationController def group_params params.require(:group).permit( - :name, + :avatar, :description, + :lfs_enabled, + :name, :path, - :avatar, :public, - :visibility_level, - :share_with_group_lock, :request_access_enabled, - :lfs_enabled + :share_with_group_lock, + :visibility_level ) end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 76911efe354..ab880ed6de0 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -24,19 +24,20 @@ module GroupsHelper end end - def projects_with_lfs_enabled(group, status) - if status - lfs_status = group.projects.select(&:lfs_enabled?).size - else - lfs_status = group.projects.select{ |p| !p.lfs_enabled? }.size - end + def projects_lfs_status(group) + lfs_status = + if group.lfs_enabled? + group.projects.select(&:lfs_enabled?).size + else + group.projects.reject(&:lfs_enabled?).size + end size = group.projects.size - if lfs_status == size || lfs_status == 0 - 'on all projects' + if lfs_status == size + 'for all projects' else - "on #{lfs_status} out of #{size} projects" + "for #{lfs_status} out of #{pluralize(size, 'project')}" end end @@ -44,7 +45,7 @@ module GroupsHelper status = group.lfs_enabled? ? 'enabled' : 'disabled' content_tag(:span, class: "lfs-#{status}") do - "#{status.humanize} #{projects_with_lfs_enabled(group, group.lfs_enabled?)}" + "#{status.humanize} #{projects_lfs_status(group)}" end end end diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index cb631cbfb8d..817910f7ddf 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -13,7 +13,7 @@ .col-sm-offset-2.col-sm-10 = render 'shared/allow_request_access', form: f - = render 'shared/groups/group_lfs_settings', f: f + = render 'groups/group_lfs_settings', f: f - if @group.new_record? .form-group diff --git a/app/views/groups/_group_lfs_settings.html.haml b/app/views/groups/_group_lfs_settings.html.haml new file mode 100644 index 00000000000..af57065f0fc --- /dev/null +++ b/app/views/groups/_group_lfs_settings.html.haml @@ -0,0 +1,11 @@ +- if current_user.admin? + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :lfs_enabled do + = f.check_box :lfs_enabled, checked: @group.lfs_enabled? + %strong + Allow projects within this group to use Git LFS + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + %br/ + %span.descr This setting can be overridden in each project. \ No newline at end of file diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 849d0f360e7..c766370d5a0 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -25,7 +25,7 @@ .col-sm-offset-2.col-sm-10 = render 'shared/allow_request_access', form: f - = render 'shared/groups/group_lfs_settings', f: f + = render 'group_lfs_settings', f: f .form-group %hr diff --git a/app/views/shared/groups/_group_lfs_settings.html.haml b/app/views/shared/groups/_group_lfs_settings.html.haml deleted file mode 100644 index af57065f0fc..00000000000 --- a/app/views/shared/groups/_group_lfs_settings.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -- if current_user.admin? - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox - = f.label :lfs_enabled do - = f.check_box :lfs_enabled, checked: @group.lfs_enabled? - %strong - Allow projects within this group to use Git LFS - = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - %br/ - %span.descr This setting can be overridden in each project. \ No newline at end of file diff --git a/doc/api/groups.md b/doc/api/groups.md index cb49648c36f..3e94e1e4efe 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -288,7 +288,7 @@ Parameters: - `path` (required) - The path of the group - `description` (optional) - The group's description - `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public. -- `lfs_enabled` (optional) - Enable/disable LFS for the projects in this group +- `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group ## Transfer project to group @@ -318,7 +318,7 @@ PUT /groups/:id | `path` | string | no | The path of the group | | `description` | string | no | The description of the group | | `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. | -| `lfs_enabled` (optional) | boolean | no | Enable/disable LFS for the projects in this group | +| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1529a44f58d..bfee4b6c752 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -86,7 +86,8 @@ module API expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) } expose :created_at, :last_activity_at - expose :shared_runners_enabled, :lfs_enabled + expose :shared_runners_enabled + expose :lfs_enabled?, as: :lfs_enabled expose :creator_id expose :namespace expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } @@ -120,7 +121,8 @@ module API end class Group < Grape::Entity - expose :id, :name, :path, :description, :visibility_level, :lfs_enabled + expose :id, :name, :path, :description, :visibility_level + expose :lfs_enabled?, as: :lfs_enabled expose :avatar_url expose :web_url end diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 0807534720a..233d00534e5 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -18,4 +18,67 @@ describe GroupsHelper do expect(group_icon(group.path)).to match('group_avatar.png') end end + + describe 'group_lfs_status' do + let(:group) { create(:group) } + let!(:project) { create(:empty_project, namespace_id: group.id) } + + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + context 'only one project in group' do + before do + group.update_attribute(:lfs_enabled, true) + end + + it 'returns all projects as enabled' do + expect(group_lfs_status(group)).to include('Enabled for all projects') + end + + it 'returns all projects as disabled' do + project.update_attribute(:lfs_enabled, false) + + expect(group_lfs_status(group)).to include('Enabled for 0 out of 1 project') + end + end + + context 'more than one project in group' do + before do + create(:empty_project, namespace_id: group.id) + end + + context 'LFS enabled in group' do + before do + group.update_attribute(:lfs_enabled, true) + end + + it 'returns both projects as enabled' do + expect(group_lfs_status(group)).to include('Enabled for all projects') + end + + it 'returns only one as enabled' do + project.update_attribute(:lfs_enabled, false) + + expect(group_lfs_status(group)).to include('Enabled for 1 out of 2 projects') + end + end + + context 'LFS disabled in group' do + before do + group.update_attribute(:lfs_enabled, false) + end + + it 'returns both projects as disabled' do + expect(group_lfs_status(group)).to include('Disabled for all projects') + end + + it 'returns only one as disabled' do + project.update_attribute(:lfs_enabled, true) + + expect(group_lfs_status(group)).to include('Disabled for 1 out of 2 projects') + end + end + end + end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index ea4b59c26b1..0b3ef9b98fd 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -187,6 +187,52 @@ describe Group, models: true do it { expect(group.has_master?(@members[:requester])).to be_falsey } end + describe '#lfs_enabled?' do + context 'LFS enabled globally' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + it 'returns true when nothing is set' do + expect(group.lfs_enabled?).to be_truthy + end + + it 'returns false when set to false' do + group.update_attribute(:lfs_enabled, false) + + expect(group.lfs_enabled?).to be_falsey + end + + it 'returns true when set to true' do + group.update_attribute(:lfs_enabled, true) + + expect(group.lfs_enabled?).to be_truthy + end + end + + context 'LFS disabled globally' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + end + + it 'returns false when nothing is set' do + expect(group.lfs_enabled?).to be_falsey + end + + it 'returns false when set to false' do + group.update_attribute(:lfs_enabled, false) + + expect(group.lfs_enabled?).to be_falsey + end + + it 'returns false when set to true' do + group.update_attribute(:lfs_enabled, true) + + expect(group.lfs_enabled?).to be_falsey + end + end + end + describe '#owners' do let(:owner) { create(:user) } let(:developer) { create(:user) } -- cgit v1.2.3 From 740ca302070607472dff1c1d7c898e22841e9a6c Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 14 Sep 2016 16:35:46 -0500 Subject: fix anchor icon regression introduced in 2fd6472 (!5577) --- app/assets/stylesheets/framework/typography.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 3f8433a0e7f..2582cde5a71 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -164,7 +164,7 @@ text-decoration: none; &:after { - content: url('icon_anchor.svg'); + content: image-url('icon_anchor.svg'); visibility: hidden; } } -- cgit v1.2.3 From 5f45ddc54577fb65db00636a05408b00636544f5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 22:17:12 +0200 Subject: Fix specs after merging LFS changes --- app/controllers/jwt_controller.rb | 14 ++++++++++++-- spec/lib/gitlab/auth_spec.rb | 6 +++--- spec/requests/jwt_controller_spec.rb | 24 +++++++++++++++++++++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 0870a2a8f50..a69534c2258 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -13,7 +13,7 @@ class JwtController < ApplicationController @authentication_result ||= Gitlab::Auth::Result.new - result = service.new(@authentication_result.project, @authentication_result.user, auth_params). + result = service.new(@authentication_result.project, @authentication_result.actor, auth_params). execute(capabilities: @authentication_result.capabilities) render json: result, status: result[:http_status] @@ -25,8 +25,18 @@ class JwtController < ApplicationController authenticate_with_http_basic do |login, password| @authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip) - render_403 unless @authentication_result.succeeded? + render_403 unless @authentication_result.success? && + (@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User)) end + rescue Gitlab::Auth::MissingPersonalTokenError + render_missing_personal_token + end + + def render_missing_personal_token + render plain: "HTTP Basic: Access denied\n" \ + "You have 2FA enabled, please use a personal access token for Git over HTTP.\n" \ + "You can generate one at #{profile_personal_access_tokens_url}", + status: 401 end def auth_params diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index c09ab1dbd57..e24ad530904 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -65,7 +65,7 @@ describe Gitlab::Auth, lib: true do token = Gitlab::LfsToken.new(user).generate expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :lfs_token)) + expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, read_capabilities)) end it 'recognizes deploy key lfs tokens' do @@ -74,7 +74,7 @@ describe Gitlab::Auth, lib: true do token = Gitlab::LfsToken.new(key).generate expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}") - expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, :lfs_deploy_token)) + expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_capabilities)) end it 'recognizes OAuth tokens' do @@ -91,7 +91,7 @@ describe Gitlab::Auth, lib: true do login = 'foo' ip = 'ip' - expect(gl_auth).to receive(:rate_limit!).with(ip, success: nil, login: login) + expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login) expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new) end end diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 93b9cfaf33d..1ca4541dbde 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -45,13 +45,31 @@ describe JwtController do context 'using User login' do let(:user) { create(:user) } - let(:headers) { { authorization: credentials('user', 'password') } } - - before { expect(Gitlab::Auth).to receive(:find_with_user_password).with('user', 'password').and_return(user) } + let(:headers) { { authorization: credentials(user.username , user.password) } } subject! { get '/jwt/auth', parameters, headers } it { expect(service_class).to have_received(:new).with(nil, user, parameters) } + + context 'when user has 2FA enabled' do + let(:user) { create(:user, :two_factor) } + + context 'without personal token' do + it 'rejects the authorization attempt' do + expect(response).to have_http_status(401) + expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP') + end + end + + context 'with personal token' do + let(:access_token) { create(:personal_access_token, user: user) } + let(:headers) { { authorization: credentials(user.username, access_token.token) } } + + it 'rejects the authorization attempt' do + expect(response).to have_http_status(200) + end + end + end end context 'using invalid login' do -- cgit v1.2.3 From ac6412d0766fbc090a3aa8272cfd4cc2d9a26c16 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 23:27:01 +0200 Subject: Added builds_spec and git_http_specs --- .../projects/git_http_client_controller.rb | 2 +- spec/requests/ci/api/builds_spec.rb | 77 ++++++++++++++++++---- spec/requests/git_http_spec.rb | 69 +++++++++++++++++-- spec/requests/jwt_controller_spec.rb | 2 +- 4 files changed, 130 insertions(+), 20 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 14e83ddda04..d92d28b7e02 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -117,7 +117,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController case auth_result.type when :ci - if download_request? + if auth_result.project == project && download_request? @ci = true else return false diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 780bd7f2859..09d72fe0a0e 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -254,7 +254,8 @@ describe Ci::API::API do let(:get_url) { ci_api("/builds/#{build.id}/artifacts") } let(:jwt_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } let(:headers) { { "GitLab-Workhorse" => "1.0", Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => jwt_token } } - let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) } + let(:token) { build.token } + let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => token) } before { build.run! } @@ -274,6 +275,13 @@ describe Ci::API::API do expect(json_response["TempPath"]).not_to be_nil end + it "using runners token" do + post authorize_url, { token: build.project.runners_token }, headers + expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + expect(json_response["TempPath"]).not_to be_nil + end + it "reject requests that did not go through gitlab-workhorse" do headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) post authorize_url, { token: build.token }, headers @@ -358,6 +366,16 @@ describe Ci::API::API do it_behaves_like 'successful artifacts upload' end + + context 'when using runners token' do + let(:token) { build.project.runners_token } + + before do + upload_artifacts(file_upload, headers_with_token) + end + + it_behaves_like 'successful artifacts upload' + end end context 'posts artifacts file and metadata file' do @@ -497,19 +515,40 @@ describe Ci::API::API do before do delete delete_url, token: build.token - build.reload end - it 'removes build artifacts' do - expect(response).to have_http_status(200) - expect(build.artifacts_file.exists?).to be_falsy - expect(build.artifacts_metadata.exists?).to be_falsy - expect(build.artifacts_size).to be_nil + shared_examples 'having removable artifacts' do + it 'removes build artifacts' do + build.reload + + expect(response).to have_http_status(200) + expect(build.artifacts_file.exists?).to be_falsy + expect(build.artifacts_metadata.exists?).to be_falsy + expect(build.artifacts_size).to be_nil + end + end + + context 'when using build token' do + before do + delete delete_url, token: build.token + end + + it_behaves_like 'having removable artifacts' + end + + context 'when using runnners token' do + before do + delete delete_url, token: build.project.runners_token + end + + it_behaves_like 'having removable artifacts' end end describe 'GET /builds/:id/artifacts' do - before { get get_url, token: build.token } + before do + get get_url, token: token + end context 'build has artifacts' do let(:build) { create(:ci_build, :artifacts) } @@ -518,13 +557,29 @@ describe Ci::API::API do 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } end - it 'downloads artifact' do - expect(response).to have_http_status(200) - expect(response.headers).to include download_headers + shared_examples 'having downloadable artifacts' do + it 'download artifacts' do + expect(response).to have_http_status(200) + expect(response.headers).to include download_headers + end + end + + context 'when using build token' do + let(:token) { build.token } + + it_behaves_like 'having downloadable artifacts' + end + + context 'when using runnners token' do + let(:token) { build.project.runners_token } + + it_behaves_like 'having downloadable artifacts' end end context 'build does not has artifacts' do + let(:token) { build.token } + it 'responds with not found' do expect(response).to have_http_status(404) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5977ee04524..0311755dd06 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -302,22 +302,77 @@ describe 'Git HTTP requests', lib: true do context "when a gitlab ci token is provided" do let(:build) { create(:ci_build, :running) } let(:project) { build.project } + let(:other_project) { create(:empty_project) } before do project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED) end - it "downloads get status 200" do - clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + context 'when build created by system is authenticated' do + it "downloads get status 200" do + clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(200) - expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + end + + it "uploads get status 401 (no project existence information leak)" do + push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + + expect(response).to have_http_status(401) + end + + it "downloads from other project get status 401" do + clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + + expect(response).to have_http_status(401) + end end - it "uploads get status 401 (no project existence information leak)" do - push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + context 'and build created by' do + before do + build.update(user: user) + project.team << [user, :reporter] + end - expect(response).to have_http_status(401) + shared_examples 'can download code only from own projects' do + it 'downloads get status 200' do + clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + + expect(response).to have_http_status(200) + expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) + end + + it 'uploads get status 403' do + push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + + expect(response).to have_http_status(403) + end + end + + context 'administrator' do + let(:user) { create(:admin) } + + it_behaves_like 'can download code only from own projects' + + it 'downloads from other project get status 403' do + clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + + expect(response).to have_http_status(403) + end + end + + context 'regular user' do + let(:user) { create(:user) } + + it_behaves_like 'can download code only from own projects' + + it 'downloads from other project get status 404' do + clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + + expect(response).to have_http_status(404) + end + end end end end diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb index 1ca4541dbde..6b956e63004 100644 --- a/spec/requests/jwt_controller_spec.rb +++ b/spec/requests/jwt_controller_spec.rb @@ -45,7 +45,7 @@ describe JwtController do context 'using User login' do let(:user) { create(:user) } - let(:headers) { { authorization: credentials(user.username , user.password) } } + let(:headers) { { authorization: credentials(user.username, user.password) } } subject! { get '/jwt/auth', parameters, headers } -- cgit v1.2.3 From 23303990ee7acaeab64c4445c00216021e33e5a3 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 16:13:38 +0100 Subject: Added keyboard shortcut to navigate to issue boards Closes #21218 --- CHANGELOG | 1 + app/assets/javascripts/shortcuts_navigation.js | 3 +++ app/views/help/_shortcuts.html.haml | 6 ++++++ app/views/layouts/nav/_project.html.haml | 4 ++++ spec/features/boards/keyboard_shortcut_spec.rb | 20 ++++++++++++++++++++ 5 files changed, 34 insertions(+) create mode 100644 spec/features/boards/keyboard_shortcut_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 0056c6cc649..f01c38b90a1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.12.0 (unreleased) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Enable pipeline events by default !6278 - Move parsing of sidekiq ps into helper !6245 (pascalbetz) + - Added go to issue boards keyboard shortcut - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Fix blame table layout width diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index 469e25482bb..b04159420d1 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -34,6 +34,9 @@ Mousetrap.bind('g i', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'); }); + Mousetrap.bind('g l', function() { + ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards'); + }); Mousetrap.bind('g m', function() { return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'); }); diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 16c16cec137..65842a0479b 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -162,6 +162,12 @@ .key i %td Go to issues + %tr + %td.shortcut + .key g + .key l + %td + Go to issue boards %tr %td.shortcut .key g diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index f7012595a5a..8e4937b7aa0 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -113,3 +113,7 @@ %li.hidden = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do Commits + + -# Shortcut to issue boards + %li.hidden + = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb new file mode 100644 index 00000000000..261a9836b79 --- /dev/null +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +describe 'Issue Boards shortcut', feature: true, js: true do + let(:project) { create(:empty_project) } + + before do + project.create_board + project.board.lists.create(list_type: :backlog) + project.board.lists.create(list_type: :done) + + login_as :admin + + visit namespace_project_path(project.namespace, project) + end + + it 'takes user to issue board index' do + find('body').native.send_keys('gl') + expect(page).to have_selector('.boards-list') + end +end -- cgit v1.2.3 From 07d39ac053942b8c656024f41796fbaf6e35e9ea Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 7 Sep 2016 17:14:41 +0100 Subject: Fixed tests Added wait for vue helper --- spec/features/boards/boards_spec.rb | 35 ++++++++++---------------- spec/features/boards/keyboard_shortcut_spec.rb | 4 +++ spec/support/wait_for_vue_resource.rb | 7 ++++++ 3 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 spec/support/wait_for_vue_resource.rb diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index c6c2e2095df..e51586d32ec 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' describe 'Issue Boards', feature: true, js: true do include WaitForAjax + include WaitForVueResource let(:project) { create(:empty_project, :public) } let(:user) { create(:user) } @@ -187,13 +188,13 @@ describe 'Issue Boards', feature: true, js: true do expect(page).to have_content('Showing 20 of 56 issues') evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(page).to have_selector('.card', count: 40) expect(page).to have_content('Showing 40 of 56 issues') evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(page).to have_selector('.card', count: 56) expect(page).to have_content('Showing all issues') @@ -372,7 +373,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-author' do click_link(user2.name) end - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(find('.js-author-search')).to have_content(user2.name) end @@ -398,7 +399,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-assignee' do click_link(user.name) end - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(find('.js-assignee-search')).to have_content(user.name) end @@ -424,7 +425,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.milestone-filter' do click_link(milestone.title) end - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(find('.js-milestone-select')).to have_content(milestone.title) end @@ -449,7 +450,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-labels' do click_link(testing.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource find('.dropdown-menu-close').click end end @@ -478,7 +479,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-labels' do click_link(testing.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource find('.dropdown-menu-close').click end end @@ -509,9 +510,9 @@ describe 'Issue Boards', feature: true, js: true do page.within(find('.dropdown-menu-labels')) do click_link(testing.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource click_link(bug.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource find('.dropdown-menu-close').click end end @@ -536,7 +537,7 @@ describe 'Issue Boards', feature: true, js: true do page.within '.dropdown-menu-labels' do click_link("No Label") - wait_for_vue_resource(spinner: false) + wait_for_vue_resource find('.dropdown-menu-close').click end end @@ -559,7 +560,7 @@ describe 'Issue Boards', feature: true, js: true do expect(page).to have_selector('.card', count: 6) expect(find('.card', match: :first)).to have_content(bug.title) click_button(bug.title) - wait_for_vue_resource(spinner: false) + wait_for_vue_resource end wait_for_vue_resource @@ -584,7 +585,7 @@ describe 'Issue Boards', feature: true, js: true do page.within(find('.card', match: :first)) do click_button(bug.title) end - wait_for_vue_resource(spinner: false) + wait_for_vue_resource expect(page).to have_selector('.card', count: 1) end @@ -647,14 +648,4 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource end - - def wait_for_vue_resource(spinner: true) - Timeout.timeout(Capybara.default_max_wait_time) do - loop until page.evaluate_script('Vue.activeResources').zero? - end - - if spinner - expect(find('.boards-list')).not_to have_selector('.fa-spinner') - end - end end diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index 261a9836b79..7ef68e9eb8d 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe 'Issue Boards shortcut', feature: true, js: true do + include WaitForVueResource + let(:project) { create(:empty_project) } before do @@ -16,5 +18,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do it 'takes user to issue board index' do find('body').native.send_keys('gl') expect(page).to have_selector('.boards-list') + + wait_for_vue_resource end end diff --git a/spec/support/wait_for_vue_resource.rb b/spec/support/wait_for_vue_resource.rb new file mode 100644 index 00000000000..1029f84716f --- /dev/null +++ b/spec/support/wait_for_vue_resource.rb @@ -0,0 +1,7 @@ +module WaitForVueResource + def wait_for_vue_resource(spinner: true) + Timeout.timeout(Capybara.default_max_wait_time) do + loop until page.evaluate_script('Vue.activeResources').zero? + end + end +end -- cgit v1.2.3 From 3d933082c36aad49829403d9d0c970d58860edb2 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 15 Sep 2016 11:54:24 -0500 Subject: Refactored authentication code to make it a bit clearer, added test for wrong SSH key. --- .../projects/git_http_client_controller.rb | 27 ++++++++++---- lib/gitlab/auth.rb | 43 ++++++++++------------ lib/gitlab/lfs_token.rb | 2 +- spec/lib/gitlab/auth_spec.rb | 2 +- spec/lib/gitlab/lfs_token_spec.rb | 2 +- spec/requests/api/internal_spec.rb | 14 +++++-- 6 files changed, 52 insertions(+), 38 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index f5a07608bf8..f74a7000373 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :user, :actor + attr_reader :actor # Git clients will not know what authenticity token to send along skip_before_action :verify_authenticity_token @@ -22,9 +22,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController if allow_basic_auth? && basic_auth_provided? login, password = user_name_and_password(request) - handle_basic_authentication(login, password) - - if ci? || actor + if handle_basic_authentication(login, password) return # Allow access end elsif allow_kerberos_spnego_auth? && spnego_provided? @@ -107,7 +105,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def ci? - @ci.present? + @ci end def user @@ -119,23 +117,36 @@ class Projects::GitHttpClientController < Projects::ApplicationController case auth_result.type when :ci - @ci = true if download_request? + if download_request? + @ci = true + else + return false + end when :oauth - @actor = auth_result.actor if download_request? + if download_request? + @actor = auth_result.actor + else + return false + end when :lfs_deploy_token if download_request? @lfs_deploy_key = true @actor = auth_result.actor + else + return false end when :lfs_token, :personal_token, :gitlab_or_ldap @actor = auth_result.actor else # Not allowed + return false end + + true end def lfs_deploy_key? - @lfs_deploy_key.present? && actor && actor.projects.include?(project) + @lfs_deploy_key && actor && actor.projects.include?(project) end def verify_workhorse_api! diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 391b8f2f5de..6be9bf7de44 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,10 @@ module Gitlab module Auth - Result = Struct.new(:actor, :type) + Result = Struct.new(:actor, :type) do + def success? + actor.present? || type == :ci + end + end class MissingPersonalTokenError < StandardError; end @@ -8,7 +12,16 @@ module Gitlab def find_for_git_client(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? - populate_result(login, password, project, ip) + result = + ci_request_check(login, password, project) || + user_with_password_for_git(login, password) || + oauth_access_token_check(login, password) || + lfs_token_check(login, password) || + personal_access_token_check(login, password) + + rate_limit!(ip, success: result && result.success?, login: login) + + result || Result.new end def find_with_user_password(login, password) @@ -49,24 +62,6 @@ module Gitlab private - def populate_result(login, password, project, ip) - result = - ci_request_check(login, password, project) || - user_with_password_for_git(login, password) || - oauth_access_token_check(login, password) || - lfs_token_check(login, password) || - personal_access_token_check(login, password) - - if result && result.type != :ci - result.type = nil unless result.actor - end - - success = result ? result.actor.present? || result.type == :ci : false - rate_limit!(ip, success: success, login: login) - - result || Result.new - end - def valid_ci_request?(login, password, project) matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) @@ -110,7 +105,7 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, :personal_token) if user == validation + Result.new(user, :personal_token) if user.present? && user == validation end end @@ -124,9 +119,11 @@ module Gitlab User.by_login(login) end - token_handler = Gitlab::LfsToken.new(actor) + if actor + token_handler = Gitlab::LfsToken.new(actor) - Result.new(actor, token_handler.type) if actor && Devise.secure_compare(token_handler.value, password) + Result.new(actor, token_handler.type) if Devise.secure_compare(token_handler.value, password) + end end end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index 224e4516074..f492754b1c8 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -13,7 +13,7 @@ module Gitlab when Key actor.user else - # + raise 'Bad Actor' end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 56f349f5d92..13c5a7156f5 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -55,7 +55,7 @@ describe Gitlab::Auth, lib: true do login = 'foo' ip = 'ip' - expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login) + expect(gl_auth).to receive(:rate_limit!).with(ip, success: nil, login: login) expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new) end end diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb index 184f235c1b2..9f04f67e0a8 100644 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::LfsToken, lib: true do - describe '#set_token and #get_value' do + describe '#generate and #value' do shared_examples 'an LFS token generator' do it 'returns a randomly generated token' do token = handler.generate diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 2e1e6a11b53..46e8e6f1169 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -107,7 +107,7 @@ describe API::API, api: true do context 'user key' do it 'returns the correct information about the key' do - lfs_auth(key, project) + lfs_auth(key.id, project) expect(response).to have_http_status(200) expect(json_response['username']).to eq(user.username) @@ -115,13 +115,19 @@ describe API::API, api: true do expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) end + + it 'returns a 404 when the wrong key is provided' do + lfs_auth(nil, project) + + expect(response).to have_http_status(404) + end end context 'deploy key' do let(:key) { create(:deploy_key) } it 'returns the correct information about the key' do - lfs_auth(key, project) + lfs_auth(key.id, project) expect(response).to have_http_status(200) expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}") @@ -421,10 +427,10 @@ describe API::API, api: true do ) end - def lfs_auth(key, project) + def lfs_auth(key_id, project) post( api("/internal/lfs_authenticate"), - key_id: key.id, + key_id: key_id, secret_token: secret_token, project: project.path_with_namespace ) -- cgit v1.2.3 From a655c5f2e967a86985498a99d1029adff4e8b27a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 15 Sep 2016 22:43:23 -0300 Subject: Fix undefined method when reverting migration RemoveProjectsPushesSinceGc --- db/migrate/20160913162434_remove_projects_pushes_since_gc.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb index c5b8c35e961..18ea9d43a43 100644 --- a/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb +++ b/db/migrate/20160913162434_remove_projects_pushes_since_gc.rb @@ -14,6 +14,6 @@ class RemoveProjectsPushesSinceGc < ActiveRecord::Migration end def down - add_column_with_default! :projects, :pushes_since_gc, :integer, default: 0 + add_column_with_default :projects, :pushes_since_gc, :integer, default: 0 end end -- cgit v1.2.3 From 5a30a1eae6ea7a9d0bdb44ed92885656f8a0b0f0 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 15 Sep 2016 19:27:49 -0700 Subject: Fix spelling: sucessfully -> successfully Originally from gitlab-org/gitlab-ee!682 --- app/controllers/projects/builds_controller.rb | 2 +- app/views/profiles/update_username.js.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 77934ff9962..f13fb1df373 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -74,7 +74,7 @@ class Projects::BuildsController < Projects::ApplicationController def erase @build.erase(erased_by: current_user) redirect_to namespace_project_build_path(project.namespace, project, @build), - notice: "Build has been sucessfully erased!" + notice: "Build has been successfully erased!" end def raw diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml index 249680bcab6..de1337a2a24 100644 --- a/app/views/profiles/update_username.js.haml +++ b/app/views/profiles/update_username.js.haml @@ -1,6 +1,6 @@ - if @user.valid? :plain - new Flash("Username sucessfully changed", "notice") + new Flash("Username successfully changed", "notice") - else :plain new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") -- cgit v1.2.3 From b7e6357e54470f3536c9904d6742f85c7738e2d2 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Thu, 15 Sep 2016 21:52:17 -0500 Subject: Increase ci_builds artifacts_size column to 8-byte integer to allow larger files --- CHANGELOG | 1 + db/migrate/20160913212128_change_artifacts_size_column.rb | 15 +++++++++++++++ db/schema.rb | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20160913212128_change_artifacts_size_column.rb diff --git a/CHANGELOG b/CHANGELOG index f01c38b90a1..71bea1054f8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.12.0 (unreleased) - Shorten task status phrase (ClemMakesApps) - Fix project visibility level fields on settings - Add hover color to emoji icon (ClemMakesApps) + - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files - Add textarea autoresize after comment (ClemMakesApps) - Refresh todos count cache when an Issue/MR is deleted - Fix branches page dropdown sort alignment (ClemMakesApps) diff --git a/db/migrate/20160913212128_change_artifacts_size_column.rb b/db/migrate/20160913212128_change_artifacts_size_column.rb new file mode 100644 index 00000000000..063bbca537c --- /dev/null +++ b/db/migrate/20160913212128_change_artifacts_size_column.rb @@ -0,0 +1,15 @@ +class ChangeArtifactsSizeColumn < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + + DOWNTIME_REASON = 'Changing an integer column size requires a full table rewrite.' + + def up + change_column :ci_builds, :artifacts_size, :integer, limit: 8 + end + + def down + # do nothing + end +end diff --git a/db/schema.rb b/db/schema.rb index 61873e38113..98d5a07dc47 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: 20160913162434) do +ActiveRecord::Schema.define(version: 20160913212128) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -177,7 +177,7 @@ ActiveRecord::Schema.define(version: 20160913162434) do t.datetime "erased_at" t.datetime "artifacts_expire_at" t.string "environment" - t.integer "artifacts_size" + t.integer "artifacts_size", limit: 8 t.string "when" t.text "yaml_variables" t.datetime "queued_at" -- cgit v1.2.3 From e000f02bd3b7c13f7f3a95b855fea6b3dd277417 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 13 Sep 2016 17:15:14 -0500 Subject: Add support for column limits in add_column_with_default --- doc/development/migration_style_guide.md | 22 +++++ lib/gitlab/database/migration_helpers.rb | 10 +- spec/lib/gitlab/database/migration_helpers_spec.rb | 106 +++++++++++++-------- 3 files changed, 94 insertions(+), 44 deletions(-) diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index b8fab3aaff7..295eae0a88e 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -111,6 +111,28 @@ class MyMigration < ActiveRecord::Migration end ``` + +## Integer column type + +By default, an integer column can hold up to a 4-byte (32-bit) number. That is +a max value of 2,147,483,647. Be aware of this when creating a column that will +hold file sizes in byte units. If you are tracking file size in bytes this +restricts the maximum file size to just over 2GB. + +To allow an integer column to hold up to an 8-byte (64-bit) number, explicitly +set the limit to 8-bytes. This will allow the column to hold a value up to +9,223,372,036,854,775,807. + +Rails migration example: + +``` +add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8) + +# or + +add_column(:projects, :foo, :integer, default: 10, limit: 8) +``` + ## Testing Make sure that your migration works with MySQL and PostgreSQL with data. An empty database does not guarantee that your migration is correct. diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 927f9dad20b..0bd6e148ba8 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -129,12 +129,14 @@ module Gitlab # column - The name of the column to add. # type - The column type (e.g. `:integer`). # default - The default value for the column. + # limit - Sets a column limit. For example, for :integer, the default is + # 4-bytes. Set `limit: 8` to allow 8-byte integers. # allow_null - When set to `true` the column will allow NULL values, the # default is to not allow NULL values. # # This method can also take a block which is passed directly to the # `update_column_in_batches` method. - def add_column_with_default(table, column, type, default:, allow_null: false, &block) + def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, &block) if transaction_open? raise 'add_column_with_default can not be run inside a transaction, ' \ 'you can disable transactions by calling disable_ddl_transaction! ' \ @@ -144,7 +146,11 @@ module Gitlab disable_statement_timeout transaction do - add_column(table, column, type, default: nil) + if limit + add_column(table, column, type, default: nil, limit: limit) + else + add_column(table, column, type, default: nil) + end # Changing the default before the update ensures any newly inserted # rows already use the proper default value. diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 4ec3f19e03f..21d90e29cdb 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -91,63 +91,85 @@ describe Gitlab::Database::MigrationHelpers, lib: true do describe '#add_column_with_default' do context 'outside of a transaction' do - before do - expect(model).to receive(:transaction_open?).and_return(false) + context 'when a column limit is not set' do + before do + expect(model).to receive(:transaction_open?).and_return(false) - expect(model).to receive(:transaction).and_yield + expect(model).to receive(:transaction).and_yield - expect(model).to receive(:add_column). - with(:projects, :foo, :integer, default: nil) + expect(model).to receive(:add_column). + with(:projects, :foo, :integer, default: nil) - expect(model).to receive(:change_column_default). - with(:projects, :foo, 10) - end + expect(model).to receive(:change_column_default). + with(:projects, :foo, 10) + end - it 'adds the column while allowing NULL values' do - expect(model).to receive(:update_column_in_batches). - with(:projects, :foo, 10) + it 'adds the column while allowing NULL values' do + expect(model).to receive(:update_column_in_batches). + with(:projects, :foo, 10) - expect(model).not_to receive(:change_column_null) + expect(model).not_to receive(:change_column_null) - model.add_column_with_default(:projects, :foo, :integer, - default: 10, - allow_null: true) - end + model.add_column_with_default(:projects, :foo, :integer, + default: 10, + allow_null: true) + end - it 'adds the column while not allowing NULL values' do - expect(model).to receive(:update_column_in_batches). - with(:projects, :foo, 10) + it 'adds the column while not allowing NULL values' do + expect(model).to receive(:update_column_in_batches). + with(:projects, :foo, 10) - expect(model).to receive(:change_column_null). - with(:projects, :foo, false) + expect(model).to receive(:change_column_null). + with(:projects, :foo, false) - model.add_column_with_default(:projects, :foo, :integer, default: 10) - end + model.add_column_with_default(:projects, :foo, :integer, default: 10) + end - it 'removes the added column whenever updating the rows fails' do - expect(model).to receive(:update_column_in_batches). - with(:projects, :foo, 10). - and_raise(RuntimeError) + it 'removes the added column whenever updating the rows fails' do + expect(model).to receive(:update_column_in_batches). + with(:projects, :foo, 10). + and_raise(RuntimeError) - expect(model).to receive(:remove_column). - with(:projects, :foo) + expect(model).to receive(:remove_column). + with(:projects, :foo) - expect do - model.add_column_with_default(:projects, :foo, :integer, default: 10) - end.to raise_error(RuntimeError) - end + expect do + model.add_column_with_default(:projects, :foo, :integer, default: 10) + end.to raise_error(RuntimeError) + end - it 'removes the added column whenever changing a column NULL constraint fails' do - expect(model).to receive(:change_column_null). - with(:projects, :foo, false). - and_raise(RuntimeError) + it 'removes the added column whenever changing a column NULL constraint fails' do + expect(model).to receive(:change_column_null). + with(:projects, :foo, false). + and_raise(RuntimeError) - expect(model).to receive(:remove_column). - with(:projects, :foo) + expect(model).to receive(:remove_column). + with(:projects, :foo) - expect do - model.add_column_with_default(:projects, :foo, :integer, default: 10) - end.to raise_error(RuntimeError) + expect do + model.add_column_with_default(:projects, :foo, :integer, default: 10) + end.to raise_error(RuntimeError) + end + end + + context 'when a column limit is set' do + it 'adds the column with a limit' do + allow(model).to receive(:transaction_open?).and_return(false) + allow(model).to receive(:transaction).and_yield + expect(model).to receive(:add_column). + with(:projects, :foo, :integer, default: nil, limit: 8) + allow(model).to receive(:update_column_in_batches). + with(:projects, :foo, 10) + allow(model).to receive(:change_column_null). + with(:projects, :foo, false) + allow(model).to receive(:change_column_default). + with(:projects, :foo, 10) + + expect(model).to receive(:add_column). + with(:projects, :foo, :integer, default: nil, limit: 8) + + model.add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8) + end end end -- cgit v1.2.3 From 1f399fe437abc67e3f6d4812bab17f0ef0aab77b Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Thu, 15 Sep 2016 21:59:55 -0500 Subject: fix --- spec/lib/gitlab/database/migration_helpers_spec.rb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 21d90e29cdb..7fd25b9e5bf 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -156,14 +156,9 @@ describe Gitlab::Database::MigrationHelpers, lib: true do it 'adds the column with a limit' do allow(model).to receive(:transaction_open?).and_return(false) allow(model).to receive(:transaction).and_yield - expect(model).to receive(:add_column). - with(:projects, :foo, :integer, default: nil, limit: 8) - allow(model).to receive(:update_column_in_batches). - with(:projects, :foo, 10) - allow(model).to receive(:change_column_null). - with(:projects, :foo, false) - allow(model).to receive(:change_column_default). - with(:projects, :foo, 10) + allow(model).to receive(:update_column_in_batches).with(:projects, :foo, 10) + allow(model).to receive(:change_column_null).with(:projects, :foo, false) + allow(model).to receive(:change_column_default).with(:projects, :foo, 10) expect(model).to receive(:add_column). with(:projects, :foo, :integer, default: nil, limit: 8) -- cgit v1.2.3 From 08714d2bcd6ccf18c202504ea03a8c81c193adc6 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Thu, 15 Sep 2016 22:46:22 -0500 Subject: Move LDAP user attributes to a method --- lib/gitlab/ldap/adapter.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 9100719da87..82cb8cef754 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -70,7 +70,7 @@ module Gitlab private def user_options(field, value, limit) - options = { attributes: %W(#{config.uid} cn mail dn) } + options = { attributes: user_attributes } options[:size] = limit if limit if field.to_sym == :dn @@ -98,6 +98,10 @@ module Gitlab filter end end + + def user_attributes + %W(#{config.uid} cn mail dn) + end end end end -- cgit v1.2.3 From 0983d01bc10d118962864cb32a53ae0ce68a3e5f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 9 Sep 2016 19:38:42 -0500 Subject: Remove stage names; connect stages in column --- app/assets/stylesheets/pages/pipelines.scss | 24 ++++++++++++++++++++++ .../projects/ci/pipelines/_pipeline.html.haml | 17 +++++++-------- app/views/projects/pipelines/index.html.haml | 15 +++++++------- app/views/shared/icons/_icon_stage_empty.svg | 7 +++++++ 4 files changed, 48 insertions(+), 15 deletions(-) create mode 100644 app/views/shared/icons/_icon_stage_empty.svg diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index cc71b8eb045..81c2c7314c9 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -155,6 +155,30 @@ vertical-align: middle; overflow: visible; } + + .stage-container { + display: inline-block; + position: relative; + margin-right: 6px; + + &:not(:last-child) { + &::after { + content: ''; + height: 20px; + width: 9px; + position: absolute;; + right: -9px; + bottom: 36%; + border-bottom: 2px solid $white-dark; + } + } + } + + .stage-empty { + g { + fill: $white-normal; + } + } } .duration, diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index bb9493f5158..9b8aa57a9dc 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -36,16 +36,17 @@ - stages_status = pipeline.statuses.relevant.latest.stages_status - - stages.each do |stage| - %td.stage-cell + %td.stage-cell + - stages.each do |stage| - status = stages_status[stage] - tooltip = "#{stage.titleize}: #{status || 'not found'}" - - if status - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do - = ci_icon_for_status(status) - - else - .light.has-tooltip{ title: tooltip } - \- + .stage-container + - if status + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do + = ci_icon_for_status(status) + - else + .light.has-tooltip{ title: tooltip } + = custom_icon('icon_stage_empty') %td - if pipeline.duration diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4d957e0d890..0c784f626a9 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -47,13 +47,14 @@ %tbody %th Status %th Commit - - stages.each do |stage| - %th.stage - - if stage.titleize.length > 12 - %span.has-tooltip{ title: "#{stage.titleize}" } - = stage.titleize - - else - = stage.titleize + %th Stages + -# - stages.each do |stage| + -# %th.stage + -# - if stage.titleize.length > 12 + -# %span.has-tooltip{ title: "#{stage.titleize}" } + -# = stage.titleize + -# - else + -# = stage.titleize %th %th = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages diff --git a/app/views/shared/icons/_icon_stage_empty.svg b/app/views/shared/icons/_icon_stage_empty.svg new file mode 100644 index 00000000000..c317033b625 --- /dev/null +++ b/app/views/shared/icons/_icon_stage_empty.svg @@ -0,0 +1,7 @@ + + + + + + + -- cgit v1.2.3 From 7ec9c20df19a63b6111e59549218ec19132d0a61 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 9 Sep 2016 19:40:29 -0500 Subject: Remove centering of stages --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 81c2c7314c9..a792b60f89a 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -147,7 +147,6 @@ } .stage-cell { - text-align: center; svg { height: 18px; -- cgit v1.2.3 From 39bce58bd6168b37252350f13e64a54f2d645d5e Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 14 Sep 2016 10:54:01 +0200 Subject: Remove empty stage state; fit tooltip on one line --- app/assets/stylesheets/pages/pipelines.scss | 12 +++++------- app/views/projects/ci/pipelines/_pipeline.html.haml | 7 ++----- app/views/projects/pipelines/index.html.haml | 7 ------- app/views/shared/icons/_icon_stage_empty.svg | 7 ------- 4 files changed, 7 insertions(+), 26 deletions(-) delete mode 100644 app/views/shared/icons/_icon_stage_empty.svg diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a792b60f89a..bf801a8845f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -160,6 +160,10 @@ position: relative; margin-right: 6px; + .tooltip { + white-space: nowrap; + } + &:not(:last-child) { &::after { content: ''; @@ -168,16 +172,10 @@ position: absolute;; right: -9px; bottom: 36%; - border-bottom: 2px solid $white-dark; + border-bottom: 3px solid $table-text-gray; } } } - - .stage-empty { - g { - fill: $white-normal; - } - } } .duration, diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 9b8aa57a9dc..6391c67021b 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -40,13 +40,10 @@ - stages.each do |stage| - status = stages_status[stage] - tooltip = "#{stage.titleize}: #{status || 'not found'}" - .stage-container - - if status + - if status + .stage-container = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do = ci_icon_for_status(status) - - else - .light.has-tooltip{ title: tooltip } - = custom_icon('icon_stage_empty') %td - if pipeline.duration diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 0c784f626a9..faf28db68d1 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -48,13 +48,6 @@ %th Status %th Commit %th Stages - -# - stages.each do |stage| - -# %th.stage - -# - if stage.titleize.length > 12 - -# %span.has-tooltip{ title: "#{stage.titleize}" } - -# = stage.titleize - -# - else - -# = stage.titleize %th %th = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages diff --git a/app/views/shared/icons/_icon_stage_empty.svg b/app/views/shared/icons/_icon_stage_empty.svg deleted file mode 100644 index c317033b625..00000000000 --- a/app/views/shared/icons/_icon_stage_empty.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - -- cgit v1.2.3 From 7d6f8cdd0e63fc355ab5149efab85d15c7494797 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 14 Sep 2016 12:32:19 +0200 Subject: Update stages to match sketch file --- app/assets/stylesheets/pages/pipelines.scss | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index bf801a8845f..e05f52a08c2 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -147,10 +147,13 @@ } .stage-cell { + font-size: 0; svg { height: 18px; width: 18px; + position: relative; + z-index: 2; vertical-align: middle; overflow: visible; } @@ -167,12 +170,11 @@ &:not(:last-child) { &::after { content: ''; - height: 20px; - width: 9px; + width: 8px; position: absolute;; - right: -9px; - bottom: 36%; - border-bottom: 3px solid $table-text-gray; + right: -7px; + bottom: 44%; + border-bottom: 2px solid $border-color; } } } -- cgit v1.2.3 From 019afb605342aa55b292809efd6df62390372a5a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 16 Sep 2016 09:48:26 +0200 Subject: Change percentage to px --- app/assets/stylesheets/pages/pipelines.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e05f52a08c2..1b4d12d3053 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -173,7 +173,7 @@ width: 8px; position: absolute;; right: -7px; - bottom: 44%; + bottom: 8px; border-bottom: 2px solid $border-color; } } -- cgit v1.2.3 From 73269b587cb3542a99a356984b3803d232d4b161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 16 Sep 2016 10:16:21 +0200 Subject: MergeRequest#environments now returns an empty array when diff_head_commit is nil MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/merge_request.rb | 2 +- spec/models/merge_request_spec.rb | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index f7d1253d957..75f48fd4ba5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -652,7 +652,7 @@ class MergeRequest < ActiveRecord::Base end def environments - return unless diff_head_commit + return [] unless diff_head_commit target_project.environments.select do |environment| environment.includes_commit?(diff_head_commit) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 3b815ded2d3..06feeb1bbba 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -703,16 +703,24 @@ describe MergeRequest, models: true do describe "#environments" do let(:project) { create(:project) } - let!(:environment) { create(:environment, project: project) } - let!(:environment1) { create(:environment, project: project) } - let!(:environment2) { create(:environment, project: project) } let(:merge_request) { create(:merge_request, source_project: project) } it 'selects deployed environments' do - create(:deployment, environment: environment, sha: project.commit('master').id) - create(:deployment, environment: environment1, sha: project.commit('feature').id) + environments = create_list(:environment, 3, project: project) + create(:deployment, environment: environments.first, sha: project.commit('master').id) + create(:deployment, environment: environments.second, sha: project.commit('feature').id) - expect(merge_request.environments).to eq [environment] + expect(merge_request.environments).to eq [environments.first] + end + + context 'without a diff_head_commit' do + before do + expect(merge_request).to receive(:diff_head_commit).and_return(nil) + end + + it 'returns an empty array' do + expect(merge_request.environments).to be_empty + end end end -- cgit v1.2.3 From e941365f3be88cebd57e9b08ba8702c1b688cb94 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Sep 2016 09:59:10 +0200 Subject: Rename capabilities to authentication_abilities --- app/controllers/jwt_controller.rb | 2 +- .../projects/git_http_client_controller.rb | 14 +++++++------ app/controllers/projects/git_http_controller.rb | 2 +- .../container_registry_authentication_service.rb | 12 +++++------ lib/api/internal.rb | 6 +++--- lib/gitlab/auth.rb | 24 +++++++++++----------- lib/gitlab/git_access.rb | 12 +++++------ spec/lib/gitlab/auth_spec.rb | 22 ++++++++++---------- spec/lib/gitlab/git_access_spec.rb | 20 +++++++++--------- spec/lib/gitlab/git_access_wiki_spec.rb | 4 ++-- ...ntainer_registry_authentication_service_spec.rb | 18 ++++++++++++---- 11 files changed, 74 insertions(+), 62 deletions(-) diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index a69534c2258..06d96774754 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -14,7 +14,7 @@ class JwtController < ApplicationController @authentication_result ||= Gitlab::Auth::Result.new result = service.new(@authentication_result.project, @authentication_result.actor, auth_params). - execute(capabilities: @authentication_result.capabilities) + execute(authentication_abilities: @authentication_result.authentication_abilities) render json: result, status: result[:http_status] end diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index d92d28b7e02..3cc915ecc2a 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :actor, :capabilities + attr_reader :actor, :authentication_abilities # Git clients will not know what authenticity token to send along skip_before_action :verify_authenticity_token @@ -125,7 +125,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController when :oauth if download_request? @actor = auth_result.actor - @capabilities = auth_result.capabilities + @authentication_abilities = auth_result.authentication_abilities else return false end @@ -133,11 +133,13 @@ class Projects::GitHttpClientController < Projects::ApplicationController if download_request? @lfs_deploy_key = true @actor = auth_result.actor - @capabilities = auth_result.capabilities + @authentication_abilities = auth_result.authentication_abilities + else + return false end when :lfs_token, :personal_token, :gitlab_or_ldap, :build @actor = auth_result.actor - @capabilities = auth_result.capabilities + @authentication_abilities = auth_result.authentication_abilities else # Not allowed return false @@ -150,8 +152,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController @lfs_deploy_key && actor && actor.projects.include?(project) end - def has_capability?(capability) - @capabilities.include?(capability) + def has_authentication_ability?(capability) + @authentication_abilities.include?(capability) end def verify_workhorse_api! diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 89afaaed510..662d38b10a5 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def access - @access ||= Gitlab::GitAccess.new(user, project, 'http', capabilities: capabilities) + @access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities) end def access_check diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index df1c9b2851c..36120a5bc99 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -4,8 +4,8 @@ module Auth AUDIENCE = 'container_registry' - def execute(capabilities:) - @capabilities = capabilities || [] + def execute(authentication_abilities:) + @authentication_abilities = authentication_abilities || [] return error('not found', 404) unless registry.enabled @@ -92,23 +92,23 @@ module Auth # Build can: # 1. pull from it's own project (for ex. a build) # 2. read images from dependent projects if creator of build is a team member - @capabilities.include?(:build_read_container_image) && + @authentication_abilities.include?(:build_read_container_image) && (requested_project == project || can?(current_user, :build_read_container_image, requested_project)) end def user_can_pull?(requested_project) - @capabilities.include?(:read_container_image) && + @authentication_abilities.include?(:read_container_image) && can?(current_user, :read_container_image, requested_project) end def build_can_push?(requested_project) # Build can push only to project to from which he originates - @capabilities.include?(:build_create_container_image) && + @authentication_abilities.include?(:build_create_container_image) && requested_project == project end def user_can_push?(requested_project) - @capabilities.include?(:create_container_image) && + @authentication_abilities.include?(:create_container_image) && can?(current_user, :create_container_image, requested_project) end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 865379c51c4..090d04544da 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -36,7 +36,7 @@ module API end end - def ssh_capabilities + def ssh_authentication_abilities [ :read_project, :download_code, @@ -59,9 +59,9 @@ module API access = if wiki? - Gitlab::GitAccessWiki.new(actor, project, protocol, capabilities: ssh_capabilities) + Gitlab::GitAccessWiki.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities) else - Gitlab::GitAccess.new(actor, project, protocol, capabilities: ssh_capabilities) + Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities) end access_status = access.check(params[:action], params[:changes]) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index b14c4e565d5..3d7cc176e07 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,6 @@ module Gitlab module Auth - Result = Struct.new(:actor, :project, :type, :capabilities) do + Result = Struct.new(:actor, :project, :type, :authentication_abilities) do def success? actor.present? || type == :ci end @@ -77,7 +77,7 @@ module Gitlab service = project.public_send("#{underscored_service}_service") if service && service.activated? && service.valid_token?(password) - Result.new(nil, project, :ci, build_capabilities) + Result.new(nil, project, :ci, build_authentication_abilities) end end end @@ -88,7 +88,7 @@ module Gitlab raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled? - Result.new(user, nil, :gitlab_or_ldap, full_capabilities) + Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities) end def oauth_access_token_check(login, password) @@ -96,7 +96,7 @@ module Gitlab token = Doorkeeper::AccessToken.by_token(password) if token && token.accessible? user = User.find_by(id: token.resource_owner_id) - Result.new(user, nil, :oauth, read_capabilities) + Result.new(user, nil, :oauth, read_authentication_abilities) end end end @@ -105,7 +105,7 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, nil, :personal_token, full_capabilities) if user.present? && user == validation + Result.new(user, nil, :personal_token, full_authentication_abilities) if user.present? && user == validation end end @@ -122,7 +122,7 @@ module Gitlab if actor token_handler = Gitlab::LfsToken.new(actor) - Result.new(actor, nil, token_handler.type, read_capabilities) if Devise.secure_compare(token_handler.value, password) + Result.new(actor, nil, token_handler.type, read_authentication_abilities) if Devise.secure_compare(token_handler.value, password) end end @@ -136,14 +136,14 @@ module Gitlab if build.user # If user is assigned to build, use restricted credentials of user - Result.new(build.user, build.project, :build, build_capabilities) + Result.new(build.user, build.project, :build, build_authentication_abilities) else # Otherwise use generic CI credentials (backward compatibility) - Result.new(nil, build.project, :ci, build_capabilities) + Result.new(nil, build.project, :ci, build_authentication_abilities) end end - def build_capabilities + def build_authentication_abilities [ :read_project, :build_download_code, @@ -152,7 +152,7 @@ module Gitlab ] end - def read_capabilities + def read_authentication_abilities [ :read_project, :download_code, @@ -160,8 +160,8 @@ module Gitlab ] end - def full_capabilities - read_capabilities + [ + def full_authentication_abilities + read_authentication_abilities + [ :push_code, :update_container_image ] diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 21286e77dc6..799794c0171 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,13 +5,13 @@ module Gitlab DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } PUSH_COMMANDS = %w{ git-receive-pack } - attr_reader :actor, :project, :protocol, :user_access, :capabilities + attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities - def initialize(actor, project, protocol, capabilities:) + def initialize(actor, project, protocol, authentication_abilities:) @actor = actor @project = project @protocol = protocol - @capabilities = capabilities + @authentication_abilities = authentication_abilities @user_access = UserAccess.new(user, project: project) end @@ -69,15 +69,15 @@ module Gitlab end def user_can_download_code? - capabilities.include?(:download_code) && user_access.can_do_action?(:download_code) + authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code) end def build_can_download_code? - capabilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code) + authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code) end def user_push_access_check(changes) - unless capabilities.include?(:push_code) + unless authentication_abilities.include?(:push_code) return build_status_object(false, "You are not allowed to upload code for this project.") end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index e24ad530904..744282b2afa 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -16,13 +16,13 @@ describe Gitlab::Auth, lib: true do end it 'recognises user-less build' do - expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_capabilities)) + expect(subject).to eq(Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities)) end it 'recognises user token' do build.update(user: create(:user)) - expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_capabilities)) + expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)) end end @@ -48,7 +48,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'drone-ci-token') - expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_capabilities)) + expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: ip)).to eq(Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)) end it 'recognizes master passwords' do @@ -56,7 +56,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_capabilities)) + expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) end it 'recognizes user lfs tokens' do @@ -65,7 +65,7 @@ describe Gitlab::Auth, lib: true do token = Gitlab::LfsToken.new(user).generate expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, read_capabilities)) + expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, read_authentication_abilities)) end it 'recognizes deploy key lfs tokens' do @@ -74,7 +74,7 @@ describe Gitlab::Auth, lib: true do token = Gitlab::LfsToken.new(key).generate expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}") - expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_capabilities)) + expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities)) end it 'recognizes OAuth tokens' do @@ -84,7 +84,7 @@ describe Gitlab::Auth, lib: true do ip = 'ip' expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'oauth2') - expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_capabilities)) + expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities)) end it 'returns double nil for invalid credentials' do @@ -149,7 +149,7 @@ describe Gitlab::Auth, lib: true do private - def build_capabilities + def build_authentication_abilities [ :read_project, :build_download_code, @@ -158,7 +158,7 @@ describe Gitlab::Auth, lib: true do ] end - def read_capabilities + def read_authentication_abilities [ :read_project, :download_code, @@ -166,8 +166,8 @@ describe Gitlab::Auth, lib: true do ] end - def full_capabilities - read_capabilities + [ + def full_authentication_abilities + read_authentication_abilities + [ :push_code, :update_container_image ] diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index c6fe56aac1c..ed43646330f 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' describe Gitlab::GitAccess, lib: true do - let(:access) { Gitlab::GitAccess.new(actor, project, 'web', capabilities: capabilities) } + let(:access) { Gitlab::GitAccess.new(actor, project, 'web', authentication_abilities: authentication_abilities) } let(:project) { create(:project) } let(:user) { create(:user) } let(:actor) { user } - let(:capabilities) do + let(:authentication_abilities) do [ :read_project, :download_code, @@ -22,7 +22,7 @@ describe Gitlab::GitAccess, lib: true do context 'ssh disabled' do before do disable_protocol('ssh') - @acc = Gitlab::GitAccess.new(actor, project, 'ssh', capabilities: capabilities) + @acc = Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities) end it 'blocks ssh git push' do @@ -37,7 +37,7 @@ describe Gitlab::GitAccess, lib: true do context 'http disabled' do before do disable_protocol('http') - @acc = Gitlab::GitAccess.new(actor, project, 'http', capabilities: capabilities) + @acc = Gitlab::GitAccess.new(actor, project, 'http', authentication_abilities: authentication_abilities) end it 'blocks http push' do @@ -119,8 +119,8 @@ describe Gitlab::GitAccess, lib: true do end end - describe 'build capabilities permissions' do - let(:capabilities) { build_capabilities } + describe 'build authentication_abilities permissions' do + let(:authentication_abilities) { build_authentication_abilities } describe 'reporter user' do before { project.team << [user, :reporter] } @@ -350,8 +350,8 @@ describe Gitlab::GitAccess, lib: true do end end - describe 'build capabilities permissions' do - let(:capabilities) { build_capabilities } + describe 'build authentication abilities' do + let(:authentication_abilities) { build_authentication_abilities } it_behaves_like 'can not push code' do def authorize @@ -373,14 +373,14 @@ describe Gitlab::GitAccess, lib: true do private - def build_capabilities + def build_authentication_abilities [ :read_project, :build_download_code ] end - def full_capabilities + def full_authentication_abilities [ :read_project, :download_code, diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 860e701c1a1..d05f0beb080 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' describe Gitlab::GitAccessWiki, lib: true do - let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', capabilities: capabilities) } + let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities) } let(:project) { create(:project) } let(:user) { create(:user) } - let(:capabilities) do + let(:authentication_abilities) do [ :read_project, :download_code, diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 2d39bd61b8f..c64df4979b0 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -6,14 +6,14 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do let(:current_params) { {} } let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) } let(:payload) { JWT.decode(subject[:token], rsa_key).first } - let(:capabilities) do + let(:authentication_abilities) do [ :read_container_image, :create_container_image ] end - subject { described_class.new(current_project, current_user, current_params).execute(capabilities: capabilities) } + subject { described_class.new(current_project, current_user, current_params).execute(authentication_abilities: authentication_abilities) } before do allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil) @@ -198,7 +198,7 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do context 'build authorized as user' do let(:current_project) { create(:empty_project) } let(:current_user) { create(:user) } - let(:capabilities) do + let(:authentication_abilities) do [ :build_read_container_image, :build_create_container_image @@ -255,7 +255,17 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do context 'when you are admin' do let(:current_user) { create(:admin) } - it_behaves_like 'pullable for being team member' + context 'when you are not member' do + it_behaves_like 'an inaccessible' + end + + context 'when you are member' do + before do + project.team << [current_user, :developer] + end + + it_behaves_like 'a pullable' + end end end end -- cgit v1.2.3 From a387ff7ba85dc75608ae5347aa405ea30b4e8c8c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Sep 2016 11:06:31 +0200 Subject: Fix specs after renaming authentication_capabilities --- app/helpers/lfs_helper.rb | 6 +++--- spec/lib/gitlab/auth_spec.rb | 22 ++++++++++++---------- spec/lib/gitlab/git_access_wiki_spec.rb | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 0d6b72ff746..018ca7d7bba 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -29,17 +29,17 @@ module LfsHelper end def user_can_download_code? - has_capability?(:download_code) && user && user.can?(:download_code, project) + has_authentication_ability?(:download_code) && can?(user, :download_code, project) end def build_can_download_code? - has_capability?(:build_download_code) && user && user.can?(:build_download_code, project) + has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project) end def lfs_upload_access? return false unless project.lfs_enabled? - has_capability?(:push_code) && user && user.can?(:push_code, project) + has_authentication_ability?(:push_code) && can?(user, :push_code, project) end def render_lfs_forbidden diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 744282b2afa..d3707005a0e 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -26,16 +26,18 @@ describe Gitlab::Auth, lib: true do end end - context 'for non-running build' do - let!(:build) { create(:ci_build, :pending) } - let(:project) { build.project } - - before do - expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token') - end - - it 'denies authentication' do - expect(subject).to eq(Gitlab::Auth::Result.new) + (HasStatus::AVAILABLE_STATUSES - [:running]).each do |build_status| + context "for #{build_status} build" do + let!(:build) { create(:ci_build, status: build_status) } + let(:project) { build.project } + + before do + expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token') + end + + it 'denies authentication' do + expect(subject).not_to eq(Gitlab::Auth::Result.new) + end end end end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index d05f0beb080..576cda595bb 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::GitAccessWiki, lib: true do - let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities) } + let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) } let(:project) { create(:project) } let(:user) { create(:user) } let(:authentication_abilities) do -- cgit v1.2.3 From 1954cb80fd168b7d7cd3a446782fdaf30d0b3f08 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Sep 2016 11:06:57 +0200 Subject: Added missing LFS specs --- spec/requests/lfs_http_spec.rb | 235 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 213 insertions(+), 22 deletions(-) diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 8ead97efb01..cad8b914668 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -15,7 +15,6 @@ describe 'Git LFS API and storage' do let(:authorization) { } let(:sendfile) { } let(:pipeline) { create(:ci_empty_pipeline, project: project) } - let(:build) { create(:ci_build, :running, pipeline: pipeline) } let(:sample_oid) { lfs_object.oid } let(:sample_size) { lfs_object.size } @@ -258,14 +257,63 @@ describe 'Git LFS API and storage' do it_behaves_like 'responds with a file' end - context 'when build is authorized' do + context 'when build is authorized as' do let(:authorization) { authorize_ci_project } - let(:update_permissions) do - project.lfs_objects << lfs_object + shared_examples 'can download LFS only from own projects' do + context 'for own project' do + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + + let(:update_permissions) do + project.team << [user, :reporter] + project.lfs_objects << lfs_object + end + + it_behaves_like 'responds with a file' + end + + context 'for other project' do + let(:other_project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + + let(:update_permissions) do + project.lfs_objects << lfs_object + end + + it 'rejects downloading code' do + expect(response).to have_http_status(other_project_status) + end + end end - it_behaves_like 'responds with a file' + context 'administrator' do + let(:user) { create(:admin) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + it_behaves_like 'can download LFS only from own projects' do + # We render 403, because administrator does have normally access + let(:other_project_status) { 403 } + end + end + + context 'regular user' do + let(:user) { create(:user) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + it_behaves_like 'can download LFS only from own projects' do + # We render 404, to prevent data leakage about existence of the project + let(:other_project_status) { 404 } + end + end + + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } + + it_behaves_like 'can download LFS only from own projects' do + # We render 401, to prevent data leakage about existence of the project + let(:other_project_status) { 401 } + end + end end end @@ -445,10 +493,62 @@ describe 'Git LFS API and storage' do end end - context 'when CI is authorized' do + context 'when build is authorized as' do let(:authorization) { authorize_ci_project } - it_behaves_like 'an authorized requests' + let(:update_lfs_permissions) do + project.lfs_objects << lfs_object + end + + shared_examples 'can download LFS only from own projects' do + context 'for own project' do + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + + let(:update_user_permissions) do + project.team << [user, :reporter] + end + + it_behaves_like 'an authorized requests' + end + + context 'for other project' do + let(:other_project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + + it 'rejects downloading code' do + expect(response).to have_http_status(other_project_status) + end + end + end + + context 'administrator' do + let(:user) { create(:admin) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + it_behaves_like 'can download LFS only from own projects' do + # We render 403, because administrator does have normally access + let(:other_project_status) { 403 } + end + end + + context 'regular user' do + let(:user) { create(:user) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + it_behaves_like 'can download LFS only from own projects' do + # We render 404, to prevent data leakage about existence of the project + let(:other_project_status) { 404 } + end + end + + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } + + it_behaves_like 'can download LFS only from own projects' do + # We render 401, to prevent data leakage about existence of the project + let(:other_project_status) { 401 } + end + end end context 'when user is not authenticated' do @@ -597,11 +697,37 @@ describe 'Git LFS API and storage' do end end - context 'when CI is authorized' do + context 'when build is authorized' do let(:authorization) { authorize_ci_project } - it 'responds with 401' do - expect(response).to have_http_status(401) + context 'build has an user' do + let(:user) { create(:user) } + + context 'tries to push to own project' do + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + + context 'tries to push to other project' do + let(:other_project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + end + + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } + + it 'responds with 401' do + expect(response).to have_http_status(401) + end end end end @@ -623,14 +749,6 @@ describe 'Git LFS API and storage' do end end end - - context 'when CI is authorized' do - let(:authorization) { authorize_ci_project } - - it 'responds with status 401' do - expect(response).to have_http_status(401) - end - end end describe 'unsupported' do @@ -793,10 +911,51 @@ describe 'Git LFS API and storage' do end end - context 'when CI is authenticated' do + context 'when build is authorized' do let(:authorization) { authorize_ci_project } - it_behaves_like 'unauthorized' + context 'build has an user' do + let(:user) { create(:user) } + + context 'tries to push to own project' do + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + before do + project.team << [user, :developer] + put_authorize + end + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + + context 'tries to push to other project' do + let(:other_project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + before do + put_authorize + end + + it 'responds with 404' do + expect(response).to have_http_status(404) + end + end + end + + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } + + before do + put_authorize + end + + it 'responds with 401' do + expect(response).to have_http_status(401) + end + end end context 'for unauthenticated' do @@ -853,10 +1012,42 @@ describe 'Git LFS API and storage' do end end - context 'when CI is authenticated' do + context 'when build is authorized' do let(:authorization) { authorize_ci_project } - it_behaves_like 'unauthorized' + before do + put_authorize + end + + context 'build has an user' do + let(:user) { create(:user) } + + context 'tries to push to own project' do + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + + context 'tries to push to other project' do + let(:other_project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + + it 'responds with 403' do + expect(response).to have_http_status(403) + end + end + end + + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } + + it 'responds with 401' do + expect(response).to have_http_status(401) + end + end end context 'for unauthenticated' do -- cgit v1.2.3 From c04ef86c644cab2cd620df9dd7ab3ddafab1f80f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 15 Sep 2016 09:38:47 +0100 Subject: Scrolls active tab into middle of nav on mobile --- CHANGELOG | 1 + app/assets/javascripts/layout_nav.js | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7f144628d61..82da272b0e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -69,6 +69,7 @@ v 8.12.0 (unreleased) - Show queued time when showing a pipeline !6084 - Remove unused mixins (ClemMakesApps) - Add search to all issue board lists + - Scroll active tab into view on mobile - Fix groups sort dropdown alignment (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Use JavaScript tooltips for mentions !5301 (winniehell) diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index ce472f3bcd0..ab95009b9b8 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -10,11 +10,13 @@ }; $(function() { - hideEndFade($('.scrolling-tabs')); + var $scrollingTabs = $('.scrolling-tabs'); + + hideEndFade($scrollingTabs); $(window).off('resize.nav').on('resize.nav', function() { - return hideEndFade($('.scrolling-tabs')); + return hideEndFade($scrollingTabs); }); - return $('.scrolling-tabs').on('scroll', function(event) { + $scrollingTabs.off('scroll').on('scroll', function(event) { var $this, currentPosition, maxPosition; $this = $(this); currentPosition = $this.scrollLeft(); @@ -22,6 +24,20 @@ $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0); return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1); }); + + $scrollingTabs.each(function () { + var $this = $(this), + scrollingTabWidth = $this.width(), + $active = $this.find('.active'), + activeWidth = $active.width(), + offset = $active.offset().left + activeWidth; + + if (offset > scrollingTabWidth - 30) { + var scrollLeft = scrollingTabWidth / 2; + scrollLeft = (offset - scrollLeft) - (activeWidth / 2); + $this.scrollLeft(scrollLeft); + } + }); }); }).call(this); -- cgit v1.2.3 From 67ec96e3e0ea9d25356ed3de0be12c0d92ed6d16 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 16 Sep 2016 11:43:05 +0200 Subject: Strip comments before sending keys to gitlab-shell Avoid issues with text encoding by not sending out non-7-bit ASCII text. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/22167 --- CHANGELOG | 1 + lib/gitlab/backend/shell.rb | 13 +++++++++++-- spec/lib/gitlab/backend/shell_spec.rb | 32 ++++++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f1e2c5060f8..fc4f9517a1c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.12.0 (unreleased) - Fix project visibility level fields on settings - Add hover color to emoji icon (ClemMakesApps) - Add textarea autoresize after comment (ClemMakesApps) + - Do not write SSH public key 'comments' to authorized_keys !6381 - Refresh todos count cache when an Issue/MR is deleted - Fix branches page dropdown sort alignment (ClemMakesApps) - Hides merge request button on branches page is user doesn't have permissions diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index c412249a01e..79eac66b364 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -6,7 +6,12 @@ module Gitlab KeyAdder = Struct.new(:io) do def add_key(id, key) - key.gsub!(/[[:space:]]+/, ' ').strip! + key = Gitlab::Shell.strip_key(key) + # Newline and tab are part of the 'protocol' used to transmit id+key to the other end + if key.include?("\t") || key.include?("\n") + raise Error.new("Invalid key: #{key.inspect}") + end + io.puts("#{id}\t#{key}") end end @@ -16,6 +21,10 @@ module Gitlab @version_required ||= File.read(Rails.root. join('GITLAB_SHELL_VERSION')).strip end + + def strip_key(key) + key.split(/ /)[0, 2].join(' ') + end end # Init new repository @@ -107,7 +116,7 @@ module Gitlab # def add_key(key_id, key_content) Gitlab::Utils.system_silent([gitlab_shell_keys_path, - 'add-key', key_id, key_content]) + 'add-key', key_id, self.class.strip_key(key_content)]) end # Batch-add keys to authorized_keys diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index 6e5ba211382..07407f212aa 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'stringio' describe Gitlab::Shell, lib: true do let(:project) { double('Project', id: 7, path: 'diaspora') } @@ -44,15 +45,38 @@ describe Gitlab::Shell, lib: true do end end + describe '#add_key' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with( + [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end + describe Gitlab::Shell::KeyAdder, lib: true do describe '#add_key' do - it 'normalizes space characters in the key' do - io = spy + it 'removes trailing garbage' do + io = spy(:io) adder = described_class.new(io) - adder.add_key('key-42', "sha-rsa foo\tbar\tbaz") + adder.add_key('key-42', "ssh-rsa foo bar\tbaz") + + expect(io).to have_received(:puts).with("key-42\tssh-rsa foo") + end + + it 'raises an exception if the key contains a tab' do + expect do + described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar") + end.to raise_error(Gitlab::Shell::Error) + end - expect(io).to have_received(:puts).with("key-42\tsha-rsa foo bar baz") + it 'raises an exception if the key contains a newline' do + expect do + described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned") + end.to raise_error(Gitlab::Shell::Error) end end end -- cgit v1.2.3 From 9d8afa222c678a2222f5219458759897089d7dad Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Sep 2016 12:46:33 +0200 Subject: Improve code comments --- app/models/ci/build.rb | 2 +- app/policies/project_policy.rb | 2 +- app/services/auth/container_registry_authentication_service.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 0b017c98916..57ef4646d24 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -179,7 +179,7 @@ module Ci end def repo_url - auth = "gitlab-ci-token:#{ensure_token}@" + auth = "gitlab-ci-token:#{ensure_token!}@" project.http_url_to_repo.sub(/^https?:\/\//) do |prefix| prefix + auth end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index ce686af2ade..00c4c7b1440 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -64,7 +64,7 @@ class ProjectPolicy < BasePolicy can! :read_deployment end - # Permissions given when an user is direct member of a group + # Permissions given when an user is team member of a project def team_member_reporter_access! can! :build_download_code can! :build_read_container_image diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 36120a5bc99..98da6563947 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -90,7 +90,7 @@ module Auth def build_can_pull?(requested_project) # Build can: - # 1. pull from it's own project (for ex. a build) + # 1. pull from its own project (for ex. a build) # 2. read images from dependent projects if creator of build is a team member @authentication_abilities.include?(:build_read_container_image) && (requested_project == project || can?(current_user, :build_read_container_image, requested_project)) @@ -102,7 +102,7 @@ module Auth end def build_can_push?(requested_project) - # Build can push only to project to from which he originates + # Build can push only to the project from which it originates @authentication_abilities.include?(:build_create_container_image) && requested_project == project end -- cgit v1.2.3 From 1c353e497efa73b18e57c6ebf6c36e146b1a8c6f Mon Sep 17 00:00:00 2001 From: Jason Aquino Date: Thu, 15 Sep 2016 12:12:15 -0400 Subject: fixed incorrect reference to @repository.root_ref in _readme.html.haml. #22083 --- CHANGELOG | 1 + app/views/projects/tree/_readme.html.haml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index b6a2aa1fe95..8e06b23a3e4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -143,6 +143,7 @@ v 8.12.0 (unreleased) - Fix Gitlab::Popen.popen thread-safety issue - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) - Clean environment variables when running git hooks + - Fix non-master branch readme display in tree view v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index baaa2caa6de..a1f4e3e8ed6 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -1,7 +1,7 @@ %article.file-holder.readme-holder .file-title = blob_icon readme.mode, readme.name - = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, @path, readme.name)) do + = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, @path, readme.name)) do %strong = readme.name .file-content.wiki -- cgit v1.2.3 From f7ae37c1d092f89cd9b9dc24be95670abed16ffc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Sep 2016 13:34:05 +0200 Subject: Simplify checking of allowed abilities in git_http_client_controller --- .../projects/git_http_client_controller.rb | 75 ++++++++++------------ lib/gitlab/auth.rb | 10 +++ spec/requests/git_http_spec.rb | 2 +- spec/requests/lfs_http_spec.rb | 32 ++++----- 4 files changed, 60 insertions(+), 59 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 3cc915ecc2a..632dac6aac9 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -4,7 +4,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController include ActionController::HttpAuthentication::Basic include KerberosSpnegoHelper - attr_reader :actor, :authentication_abilities + attr_reader :authentication_result + + delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true + + alias_method :user, :actor # Git clients will not know what authenticity token to send along skip_before_action :verify_authenticity_token @@ -26,9 +30,12 @@ class Projects::GitHttpClientController < Projects::ApplicationController return # Allow access end elsif allow_kerberos_spnego_auth? && spnego_provided? - @actor = find_kerberos_user + user = find_kerberos_user + + if user + @authentication_result = Gitlab::Auth::Result.new( + user, nil, :kerberos, Gitlab::Auth.full_authentication_abilities) - if actor send_final_spnego_response return # Allow access end @@ -104,56 +111,40 @@ class Projects::GitHttpClientController < Projects::ApplicationController render plain: 'Not Found', status: :not_found end - def ci? - @ci - end + def handle_basic_authentication(login, password) + @authentication_result = Gitlab::Auth.find_for_git_client( + login, password, project: project, ip: request.ip) - def user - @actor - end + return false unless @authentication_result.success? - def handle_basic_authentication(login, password) - auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip) - - case auth_result.type - when :ci - if auth_result.project == project && download_request? - @ci = true - else - return false - end - when :oauth - if download_request? - @actor = auth_result.actor - @authentication_abilities = auth_result.authentication_abilities - else - return false - end - when :lfs_deploy_token - if download_request? - @lfs_deploy_key = true - @actor = auth_result.actor - @authentication_abilities = auth_result.authentication_abilities - else - return false - end - when :lfs_token, :personal_token, :gitlab_or_ldap, :build - @actor = auth_result.actor - @authentication_abilities = auth_result.authentication_abilities + if download_request? + authentication_has_download_access? else - # Not allowed - return false + authentication_has_upload_access? end + end + + def authentication_has_download_access? + has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) + end + + def authentication_has_upload_access? + has_authentication_ability?(:push_code) + end - true + def ci? + authentication_result && authentication_result.ci? && + authentication_result.project && authentication_result.project == project end def lfs_deploy_key? - @lfs_deploy_key && actor && actor.projects.include?(project) + authentication_result && authentication_result.lfs_deploy_token? && + actor && actor.projects.include?(project) end def has_authentication_ability?(capability) - @authentication_abilities.include?(capability) + authentication_abilities && + authentication_abilities.include?(capability) end def verify_workhorse_api! diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 3d7cc176e07..f9ae5e4543f 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,6 +1,14 @@ module Gitlab module Auth Result = Struct.new(:actor, :project, :type, :authentication_abilities) do + def ci? + type == :ci + end + + def lfs_deploy_token? + type == :lfs_deploy_token + end + def success? actor.present? || type == :ci end @@ -143,6 +151,8 @@ module Gitlab end end + public + def build_authentication_abilities [ :read_project, diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 0311755dd06..f828e898740 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -346,7 +346,7 @@ describe 'Git HTTP requests', lib: true do it 'uploads get status 403' do push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(403) + expect(response).to have_http_status(401) end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index cad8b914668..09e4e265dd1 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -310,8 +310,8 @@ describe 'Git LFS API and storage' do let(:build) { create(:ci_build, :running, pipeline: pipeline) } it_behaves_like 'can download LFS only from own projects' do - # We render 401, to prevent data leakage about existence of the project - let(:other_project_status) { 401 } + # We render 404, to prevent data leakage about existence of the project + let(:other_project_status) { 404 } end end end @@ -545,8 +545,8 @@ describe 'Git LFS API and storage' do let(:build) { create(:ci_build, :running, pipeline: pipeline) } it_behaves_like 'can download LFS only from own projects' do - # We render 401, to prevent data leakage about existence of the project - let(:other_project_status) { 401 } + # We render 404, to prevent data leakage about existence of the project + let(:other_project_status) { 404 } end end end @@ -706,8 +706,8 @@ describe 'Git LFS API and storage' do context 'tries to push to own project' do let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'responds with 403' do - expect(response).to have_http_status(403) + it 'responds with 401' do + expect(response).to have_http_status(401) end end @@ -716,8 +716,8 @@ describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'responds with 403' do - expect(response).to have_http_status(403) + it 'responds with 401' do + expect(response).to have_http_status(401) end end end @@ -925,8 +925,8 @@ describe 'Git LFS API and storage' do put_authorize end - it 'responds with 403' do - expect(response).to have_http_status(403) + it 'responds with 401' do + expect(response).to have_http_status(401) end end @@ -939,8 +939,8 @@ describe 'Git LFS API and storage' do put_authorize end - it 'responds with 404' do - expect(response).to have_http_status(404) + it 'responds with 401' do + expect(response).to have_http_status(401) end end end @@ -1025,8 +1025,8 @@ describe 'Git LFS API and storage' do context 'tries to push to own project' do let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'responds with 403' do - expect(response).to have_http_status(403) + it 'responds with 401' do + expect(response).to have_http_status(401) end end @@ -1035,8 +1035,8 @@ describe 'Git LFS API and storage' do let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'responds with 403' do - expect(response).to have_http_status(403) + it 'responds with 401' do + expect(response).to have_http_status(401) end end end -- cgit v1.2.3 From 8b2dbe8997636a57ed6cf02a3be7f113d7a29f04 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 16 Sep 2016 14:37:48 +0300 Subject: Remove schema annotations completely --- spec/factories/ci/runner_projects.rb | 11 ----------- spec/factories/ci/runners.rb | 19 ------------------- spec/factories/ci/variables.rb | 14 -------------- spec/factories/group_members.rb | 13 ------------- spec/models/hooks/project_hook_spec.rb | 18 ------------------ spec/models/hooks/service_hook_spec.rb | 18 ------------------ spec/models/hooks/system_hook_spec.rb | 20 +------------------- spec/models/hooks/web_hook_spec.rb | 18 ------------------ spec/models/members/group_member_spec.rb | 19 ------------------- spec/models/members/project_member_spec.rb | 19 ------------------- spec/models/project_services/asana_service_spec.rb | 20 -------------------- .../project_services/assembla_service_spec.rb | 20 -------------------- spec/models/project_services/bamboo_service_spec.rb | 20 -------------------- .../project_services/bugzilla_service_spec.rb | 20 -------------------- .../project_services/buildkite_service_spec.rb | 20 -------------------- .../project_services/campfire_service_spec.rb | 20 -------------------- .../custom_issue_tracker_service_spec.rb | 20 -------------------- .../project_services/drone_ci_service_spec.rb | 20 -------------------- .../project_services/external_wiki_service_spec.rb | 21 --------------------- .../project_services/flowdock_service_spec.rb | 20 -------------------- .../project_services/gemnasium_service_spec.rb | 20 -------------------- .../gitlab_issue_tracker_service_spec.rb | 20 -------------------- .../models/project_services/hipchat_service_spec.rb | 20 -------------------- spec/models/project_services/irker_service_spec.rb | 20 -------------------- spec/models/project_services/jira_service_spec.rb | 20 -------------------- .../project_services/pivotaltracker_service_spec.rb | 20 -------------------- .../project_services/pushover_service_spec.rb | 20 -------------------- .../models/project_services/redmine_service_spec.rb | 20 -------------------- spec/models/project_services/slack_service_spec.rb | 20 -------------------- .../project_services/teamcity_service_spec.rb | 20 -------------------- 30 files changed, 1 insertion(+), 569 deletions(-) diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb index 83fccad679f..3372e5ab685 100644 --- a/spec/factories/ci/runner_projects.rb +++ b/spec/factories/ci/runner_projects.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: runner_projects -# -# id :integer not null, primary key -# runner_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - FactoryGirl.define do factory :ci_runner_project, class: Ci::RunnerProject do runner_id 1 diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index 45eaebb2576..e3b73e29987 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: runners -# -# id :integer not null, primary key -# token :string(255) -# created_at :datetime -# updated_at :datetime -# description :string(255) -# contacted_at :datetime -# active :boolean default(TRUE), not null -# is_shared :boolean default(FALSE) -# name :string(255) -# version :string(255) -# revision :string(255) -# platform :string(255) -# architecture :string(255) -# - FactoryGirl.define do factory :ci_runner, class: Ci::Runner do sequence :description do |n| diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb index 856a8e725eb..6653f0bb5c3 100644 --- a/spec/factories/ci/variables.rb +++ b/spec/factories/ci/variables.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: ci_variables -# -# id :integer not null, primary key -# project_id :integer not null -# key :string(255) -# value :text -# encrypted_value :text -# encrypted_value_salt :string(255) -# encrypted_value_iv :string(255) -# gl_project_id :integer -# - FactoryGirl.define do factory :ci_variable, class: Ci::Variable do sequence(:key) { |n| "VARIABLE_#{n}" } diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb index debb86d997f..2044ebec09a 100644 --- a/spec/factories/group_members.rb +++ b/spec/factories/group_members.rb @@ -1,16 +1,3 @@ -# == Schema Information -# -# Table name: group_members -# -# id :integer not null, primary key -# group_access :integer not null -# group_id :integer not null -# user_id :integer not null -# created_at :datetime -# updated_at :datetime -# notification_level :integer default(3), not null -# - FactoryGirl.define do factory :group_member do access_level { GroupMember::OWNER } diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 4a457997a4f..474ae62ccec 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null -# - require 'spec_helper' describe ProjectHook, models: true do diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index 534e1b4f128..1a83c836652 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null -# - require "spec_helper" describe ServiceHook, models: true do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index cbdf7eec082..ad2b710041a 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null -# - require "spec_helper" describe SystemHook, models: true do @@ -48,7 +30,7 @@ describe SystemHook, models: true do it "user_create hook" do create(:user) - + expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_create/, headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index f9bab487b96..e52b9d75cef 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null -# - require 'spec_helper' describe WebHook, models: true do diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb index 4f875fd257a..56fa7fa6134 100644 --- a/spec/models/members/group_member_spec.rb +++ b/spec/models/members/group_member_spec.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string(255) not null -# user_id :integer -# notification_level :integer not null -# type :string(255) -# created_at :datetime -# updated_at :datetime -# created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) -# invite_accepted_at :datetime -# - require 'spec_helper' describe GroupMember, models: true do diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index be57957b569..805c15a4e5e 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string(255) not null -# user_id :integer -# notification_level :integer not null -# type :string(255) -# created_at :datetime -# updated_at :datetime -# created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) -# invite_accepted_at :datetime -# - require 'spec_helper' describe ProjectMember, models: true do diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index dc702cfc42c..8e5145e824b 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe AsanaService, models: true do diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index d672d80156c..4c5acb7990b 100644 --- a/spec/models/project_services/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe AssemblaService, models: true do diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb index 9ae461f8c2d..d7e1a4e3b6c 100644 --- a/spec/models/project_services/bamboo_service_spec.rb +++ b/spec/models/project_services/bamboo_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe BambooService, models: true do diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb index a6d9717ccb5..739cc72b2ff 100644 --- a/spec/models/project_services/bugzilla_service_spec.rb +++ b/spec/models/project_services/bugzilla_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe BugzillaService, models: true do diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb index 0866e1532dd..6f65beb79d0 100644 --- a/spec/models/project_services/buildkite_service_spec.rb +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe BuildkiteService, models: true do diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index c76ae21421b..a3b9d084a75 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe CampfireService, models: true do diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb index ff976f8ec59..7020667ea58 100644 --- a/spec/models/project_services/custom_issue_tracker_service_spec.rb +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe CustomIssueTrackerService, models: true do diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 8ef892259f2..f13bb1e8adf 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe DroneCiService, models: true do diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index d7c5ea95d71..342d86aeca9 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -1,24 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# - require 'spec_helper' describe ExternalWikiService, models: true do diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index d2557019756..d6db02d6e76 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe FlowdockService, models: true do diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 3d0b6c9816b..529044d1d8b 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe GemnasiumService, models: true do diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 8ef79a17d50..652804fb444 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe GitlabIssueTrackerService, models: true do diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 34eafbe555d..cf713684463 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe HipchatService, models: true do diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index ffb17fd3259..f8c45b37561 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' require 'socket' require 'json' diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 9037ca5cc20..b48a3176007 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe JiraService, models: true do diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb index d098d988521..45b2f1068bf 100644 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe PivotaltrackerService, models: true do diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index 5959c81577d..8fc92a9ab51 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe PushoverService, models: true do diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index 7d14f6e8280..b8679cd2563 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe RedmineService, models: true do diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb index 5afdc4b2f7b..c07a70a8069 100644 --- a/spec/models/project_services/slack_service_spec.rb +++ b/spec/models/project_services/slack_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe SlackService, models: true do diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 474715d24c3..f7e878844dc 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# - require 'spec_helper' describe TeamcityService, models: true do -- cgit v1.2.3 From b0195d5c55d913dd62cb01b553b045f2681e7eb7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Sep 2016 13:47:54 +0200 Subject: Fix specs for available statuses --- spec/lib/gitlab/auth_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index d3707005a0e..1af5abe9f15 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Auth, lib: true do end end - (HasStatus::AVAILABLE_STATUSES - [:running]).each do |build_status| + (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status| context "for #{build_status} build" do let!(:build) { create(:ci_build, status: build_status) } let(:project) { build.project } @@ -36,7 +36,7 @@ describe Gitlab::Auth, lib: true do end it 'denies authentication' do - expect(subject).not_to eq(Gitlab::Auth::Result.new) + expect(subject).to eq(Gitlab::Auth::Result.new) end end end -- cgit v1.2.3 From 187dd50f881ffced6484ba1b7ffd419b92cda151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 16 Sep 2016 14:14:09 +0200 Subject: Add missing CHANGELOG entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index b6a2aa1fe95..8cbc970c4cf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.12.0 (unreleased) - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Fix pagination on user snippets page - Fix sorting of issues in API + - Sort project variables by key. !6275 (Diego Souza) - Ensure specs on sorting of issues in API are deterministic on MySQL - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 -- cgit v1.2.3 From c88caf6150fe3f99f17f814a265b352dade8d7b8 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 16 Sep 2016 14:30:36 +0200 Subject: Use gitlab-workhorse 0.8.2 Fixes rejected API git archive extensions; improves Sentry errors. --- GITLAB_WORKHORSE_VERSION | 2 +- doc/install/installation.md | 2 +- doc/update/8.11-to-8.12.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 6f4eebdf6f6..100435be135 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.8.1 +0.8.2 diff --git a/doc/install/installation.md b/doc/install/installation.md index df98655c396..3ac813aa914 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.8.1 + sudo -u git -H git checkout v0.8.2 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 8017c36587e..686c7e8e7b5 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -82,7 +82,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.8.1 +sudo -u git -H git checkout v0.8.2 sudo -u git -H make ``` -- cgit v1.2.3 From 473684847ec79b870f182003a6dbed98cb6cd254 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 16 Sep 2016 13:52:06 +0100 Subject: Doesnt run JS if active element doesnt exist --- app/assets/javascripts/layout_nav.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index ab95009b9b8..8e2fc0d1479 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -29,13 +29,16 @@ var $this = $(this), scrollingTabWidth = $this.width(), $active = $this.find('.active'), - activeWidth = $active.width(), - offset = $active.offset().left + activeWidth; + activeWidth = $active.width(); - if (offset > scrollingTabWidth - 30) { - var scrollLeft = scrollingTabWidth / 2; - scrollLeft = (offset - scrollLeft) - (activeWidth / 2); - $this.scrollLeft(scrollLeft); + if ($active.length) { + var offset = $active.offset().left + activeWidth; + + if (offset > scrollingTabWidth - 30) { + var scrollLeft = scrollingTabWidth / 2; + scrollLeft = (offset - scrollLeft) - (activeWidth / 2); + $this.scrollLeft(scrollLeft); + } } }); }); -- cgit v1.2.3 From 2742f9fb98babc0009e446d291757ae43c54c101 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Sep 2016 16:07:21 +0200 Subject: Improve authentication_result usage --- .../projects/git_http_client_controller.rb | 29 ++++++++++++++-------- spec/requests/git_http_spec.rb | 4 +-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 632dac6aac9..ee9ea4bc8b2 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -19,6 +19,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController private def authenticate_user + @authentication_result = Gitlab::Auth::Result.new + if project && project.public? && download_request? return # Allow access end @@ -124,6 +126,18 @@ class Projects::GitHttpClientController < Projects::ApplicationController end end + def ci? + authentication_result.ci? && + authentication_project && + authentication_project == project + end + + def lfs_deploy_key? + authentication_result.lfs_deploy_token? && + actor && + actor.projects.include?(project) + end + def authentication_has_download_access? has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) end @@ -132,19 +146,12 @@ class Projects::GitHttpClientController < Projects::ApplicationController has_authentication_ability?(:push_code) end - def ci? - authentication_result && authentication_result.ci? && - authentication_result.project && authentication_result.project == project - end - - def lfs_deploy_key? - authentication_result && authentication_result.lfs_deploy_token? && - actor && actor.projects.include?(project) + def has_authentication_ability?(capability) + (authentication_abilities || []).include?(capability) end - def has_authentication_ability?(capability) - authentication_abilities && - authentication_abilities.include?(capability) + def authentication_project + authentication_result.project end def verify_workhorse_api! diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index f828e898740..e3922bec689 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -322,10 +322,10 @@ describe 'Git HTTP requests', lib: true do expect(response).to have_http_status(401) end - it "downloads from other project get status 401" do + it "downloads from other project get status 404" do clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token - expect(response).to have_http_status(401) + expect(response).to have_http_status(404) end end -- cgit v1.2.3 From cf00fbecc544dfd2d597ae89c5ecbae1b6842932 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 16 Sep 2016 14:30:28 +0200 Subject: Fix API notes endpoint when posting only emoji --- doc/api/notes.md | 6 +++++- lib/api/notes.rb | 8 ++++---- spec/requests/api/notes_spec.rb | 9 +++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/doc/api/notes.md b/doc/api/notes.md index 85d140d06ac..572844b8b3f 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -78,7 +78,8 @@ Parameters: ### Create new issue note -Creates a new note to a single project issue. +Creates a new note to a single project issue. If you create a note where the body +only contains an Award Emoji, you'll receive this object back. ``` POST /projects/:id/issues/:issue_id/notes @@ -204,6 +205,7 @@ Parameters: ### Create new snippet note Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet. +If you create a note where the body only contains an Award Emoji, you'll receive this object back. ``` POST /projects/:id/snippets/:snippet_id/notes @@ -332,6 +334,8 @@ Parameters: ### Create new merge request note Creates a new note for a single merge request. +If you create a note where the body only contains an Award Emoji, you'll receive +this object back. ``` POST /projects/:id/merge_requests/:merge_request_id/notes diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 8bfa998dc53..c5c214d4d13 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -83,12 +83,12 @@ module API opts[:created_at] = params[:created_at] end - @note = ::Notes::CreateService.new(user_project, current_user, opts).execute + note = ::Notes::CreateService.new(user_project, current_user, opts).execute - if @note.valid? - present @note, with: Entities::Note + if note.valid? + present note, with: Entities::const_get(note.class.name) else - not_found!("Note #{@note.errors.messages}") + not_found!("Note #{note.errors.messages}") end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 223444ea39f..063a8706e76 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -220,6 +220,15 @@ describe API::API, api: true do expect(Time.parse(json_response['created_at'])).to be_within(1.second).of(creation_time) end end + + context 'when the user is posting an award emoji' do + it 'returns an award emoji' do + post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: ':+1:' + + expect(response).to have_http_status(201) + expect(json_response['awardable_id']).to eq issue.id + end + end end context "when noteable is a Snippet" do -- cgit v1.2.3 From 0f3afca309f16049e951b7044a88eb0366641e96 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 16 Sep 2016 07:57:19 -0700 Subject: Bump fog-aws and other dependent modules Closes #22260 --- CHANGELOG | 1 + Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 950a44acc47..20948a8db5e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable + - Bump fog-aws to v0.11.0 to support ap-south-1 region - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 - Prune events older than 12 months. (ritave) diff --git a/Gemfile.lock b/Gemfile.lock index 1edb218373d..6fe761fe0b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -189,7 +189,7 @@ GEM erubis (2.7.0) escape_utils (1.1.1) eventmachine (1.0.8) - excon (0.49.0) + excon (0.52.0) execjs (2.6.0) expression_parser (0.9.0) factory_girl (4.5.0) @@ -215,8 +215,8 @@ GEM flowdock (0.7.1) httparty (~> 0.7) multi_json - fog-aws (0.9.2) - fog-core (~> 1.27) + fog-aws (0.11.0) + fog-core (~> 1.38) fog-json (~> 1.0) fog-xml (~> 0.1) ipaddress (~> 0.8) @@ -225,7 +225,7 @@ GEM fog-core (~> 1.27) fog-json (~> 1.0) fog-xml (~> 0.1) - fog-core (1.40.0) + fog-core (1.42.0) builder excon (~> 0.49) formatador (~> 0.2) -- cgit v1.2.3 From d8dd1c1940c929eab324951e3c302d197c5f0dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 15 Sep 2016 18:34:57 +0200 Subject: Ensure invitees are not returned in Members API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + lib/api/access_requests.rb | 2 +- lib/api/entities.rb | 6 +++--- lib/api/members.rb | 8 ++++---- spec/requests/api/members_spec.rb | 25 ++++++++++++++++--------- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 950a44acc47..5ce60e719d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.12.0 (unreleased) - Update gitlab shell secret file also when it is empty. !3774 (glensc) - Give project selection dropdowns responsive width, make non-wrapping. - Make push events have equal vertical spacing. + - API: Ensure invitees are not returned in Members API. - Add two-factor recovery endpoint to internal API !5510 - Pass the "Remember me" value to the U2F authentication form - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index d02b469dac8..29a97ccbd75 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -20,7 +20,7 @@ module API access_requesters = paginate(source.requesters.includes(:user)) - present access_requesters.map(&:user), with: Entities::AccessRequester, access_requesters: access_requesters + present access_requesters.map(&:user), with: Entities::AccessRequester, source: source end # Request access to the group/project diff --git a/lib/api/entities.rb b/lib/api/entities.rb index bfee4b6c752..cbc5aa0f75d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -104,18 +104,18 @@ module API class Member < UserBasic expose :access_level do |user, options| - member = options[:member] || options[:members].find { |m| m.user_id == user.id } + member = options[:member] || options[:source].members.find_by(user_id: user.id) member.access_level end expose :expires_at do |user, options| - member = options[:member] || options[:members].find { |m| m.user_id == user.id } + member = options[:member] || options[:source].members.find_by(user_id: user.id) member.expires_at end end class AccessRequester < UserBasic expose :requested_at do |user, options| - access_requester = options[:access_requester] || options[:access_requesters].find { |m| m.user_id == user.id } + access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id) access_requester.requested_at end end diff --git a/lib/api/members.rb b/lib/api/members.rb index 94c16710d9a..37f0a6512f4 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -18,11 +18,11 @@ module API get ":id/members" do source = find_source(source_type, params[:id]) - members = source.members.includes(:user) - members = members.joins(:user).merge(User.search(params[:query])) if params[:query] - members = paginate(members) + users = source.users + users = users.merge(User.search(params[:query])) if params[:query] + users = paginate(users) - present members.map(&:user), with: Entities::Member, members: members + present users, with: Entities::Member, source: source end # Get a group/project member diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 1e365bf353a..b9cf348bbf0 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -30,20 +30,27 @@ describe API::Members, api: true do let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) } end - context 'when authenticated as a non-member' do - %i[access_requester stranger].each do |type| - context "as a #{type}" do - it 'returns 200' do - user = public_send(type) - get api("/#{source_type.pluralize}/#{source.id}/members", user) + %i[master developer access_requester stranger].each do |type| + context "when authenticated as a #{type}" do + it 'returns 200' do + user = public_send(type) + get api("/#{source_type.pluralize}/#{source.id}/members", user) - expect(response).to have_http_status(200) - expect(json_response.size).to eq(2) - end + expect(response).to have_http_status(200) + expect(json_response.size).to eq(2) end end end + it 'does not return invitees' do + invitee = create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil) + + get api("/#{source_type.pluralize}/#{source.id}/members", developer) + + expect(response).to have_http_status(200) + expect(json_response.size).to eq(2) + end + it 'finds members with query string' do get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username -- cgit v1.2.3 From c054254f1e5038f85e407317e63a7bcb45368125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 16 Sep 2016 10:30:16 +0200 Subject: Better assertion in API members specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/requests/api/members_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index b9cf348bbf0..92032f09b17 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -38,17 +38,19 @@ describe API::Members, api: true do expect(response).to have_http_status(200) expect(json_response.size).to eq(2) + expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] end end end it 'does not return invitees' do - invitee = create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil) + create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil) get api("/#{source_type.pluralize}/#{source.id}/members", developer) expect(response).to have_http_status(200) expect(json_response.size).to eq(2) + expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] end it 'finds members with query string' do -- cgit v1.2.3 From 63493944b829883e9601ff0a0f1ca3679c936674 Mon Sep 17 00:00:00 2001 From: Katarzyna Kobierska Date: Fri, 16 Sep 2016 16:08:19 +0200 Subject: Render invalid template for merge requests without source project and open --- app/controllers/projects/merge_requests_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index aa8645ba8cc..0288ee87717 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -428,6 +428,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def validates_merge_request + # If source project was removed and merge request for some reason + # wasn't close (Ex. mr from fork to origin) + return invalid_mr if !@merge_request.source_project && @merge_request.open? + # Show git not found page # if there is no saved commits between source & target branch if @merge_request.commits.blank? -- cgit v1.2.3 From f8cc5483af89550879fc3c800c2523ad8259130b Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Fri, 16 Sep 2016 19:55:00 +0200 Subject: Use oj gem for faster JSON processing It's mainly intended to improve Elasticsearch indexing performance (through multi_json gem), but other gems could benefit from it too, like grape. --- CHANGELOG | 1 + Gemfile | 3 +++ Gemfile.lock | 2 ++ 3 files changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 20948a8db5e..44ea3d6e01d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -168,6 +168,7 @@ v 8.11.5 - Scope webhooks/services that will run for confidential issues - Remove gitorious from import_sources - Fix confidential issues being exposed as public using gitlab.com export + - Use oj gem for faster JSON processing v 8.11.4 - Fix resolving conflicts on forks. !6082 diff --git a/Gemfile b/Gemfile index 1f83f8c83f2..cb1c619cc64 100644 --- a/Gemfile +++ b/Gemfile @@ -206,6 +206,9 @@ gem 'mousetrap-rails', '~> 1.4.6' # Detect and convert string character encoding gem 'charlock_holmes', '~> 0.7.3' +# Faster JSON +gem 'oj', '~> 2.17.4' + # Parse time & duration gem 'chronic', '~> 0.10.2' gem 'chronic_duration', '~> 0.10.6' diff --git a/Gemfile.lock b/Gemfile.lock index 6fe761fe0b9..8e26429df14 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -427,6 +427,7 @@ GEM rack (>= 1.2, < 3) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) + oj (2.17.4) omniauth (1.3.1) hashie (>= 1.2, < 4) rack (>= 1.0, < 3) @@ -904,6 +905,7 @@ DEPENDENCIES nokogiri (~> 1.6.7, >= 1.6.7.2) oauth2 (~> 1.2.0) octokit (~> 4.3.0) + oj (~> 2.17.4) omniauth (~> 1.3.1) omniauth-auth0 (~> 1.4.1) omniauth-azure-oauth2 (~> 0.0.6) -- cgit v1.2.3 From a147b43dcce7da64512efed392041c37ef45851c Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 13 Sep 2016 21:43:25 -0500 Subject: Replace contributions calendar timezone payload with dates --- CHANGELOG | 1 + app/assets/javascripts/users/calendar.js | 2 +- app/controllers/users_controller.rb | 2 +- app/views/users/calendar.html.haml | 4 ++-- lib/gitlab/contributions_calendar.rb | 17 +++++++------- spec/features/calendar_spec.rb | 39 ++++++++++++++++++++++++++++++++ 6 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 spec/features/calendar_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 44ea3d6e01d..43b3b33155d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.12.0 (unreleased) - Fix file permissions change when updating a file on the Gitlab UI !5979 - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) + - Replace contributions calendar timezone payload with dates (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Enable pipeline events by default !6278 - Move parsing of sidekiq ps into helper !6245 (pascalbetz) diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index b8da7c4f297..3bd4c3c066f 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -29,7 +29,7 @@ date.setDate(date.getDate() + i); var day = date.getDay(); - var count = timestamps[date.getTime() * 0.001]; + var count = timestamps[dateFormat(date, 'yyyy-mm-dd')]; // Create a new group array if this is the first day of the week // or if is first object diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index a99632454d9..a4bedb3bfe6 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -73,7 +73,7 @@ class UsersController < ApplicationController def calendar calendar = contributions_calendar - @timestamps = calendar.timestamps + @activity_dates = calendar.activity_dates render 'calendar', layout: false end diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml index 77f2ddefb1e..09ff8a76d27 100644 --- a/app/views/users/calendar.html.haml +++ b/app/views/users/calendar.html.haml @@ -4,6 +4,6 @@ Summary of issues, merge requests, and push events :javascript new Calendar( - #{@timestamps.to_json}, + #{@activity_dates.to_json}, '#{user_calendar_activities_path}' - ); + ); \ No newline at end of file diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index bd681f03173..b164f5a2eea 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -1,16 +1,16 @@ module Gitlab class ContributionsCalendar - attr_reader :timestamps, :projects, :user + attr_reader :activity_dates, :projects, :user def initialize(projects, user) @projects = projects @user = user end - def timestamps - return @timestamps if @timestamps.present? + def activity_dates + return @activity_dates if @activity_dates.present? - @timestamps = {} + @activity_dates = {} date_from = 1.year.ago events = Event.reorder(nil).contributions.where(author_id: user.id). @@ -19,18 +19,17 @@ module Gitlab select('date(created_at) as date, count(id) as total_amount'). map(&:attributes) - dates = (1.year.ago.to_date..Date.today).to_a + activity_dates = (1.year.ago.to_date..Date.today).to_a - dates.each do |date| - date_id = date.to_time.to_i.to_s + activity_dates.each do |date| day_events = events.find { |day_events| day_events["date"] == date } if day_events - @timestamps[date_id] = day_events["total_amount"] + @activity_dates[date] = day_events["total_amount"] end end - @timestamps + @activity_dates end def events_by_date(date) diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb new file mode 100644 index 00000000000..fd5fbaf2af4 --- /dev/null +++ b/spec/features/calendar_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +feature 'Contributions Calendar', js: true, feature: true do + include WaitForAjax + + let(:contributed_project) { create(:project, :public) } + + before do + login_as :user + + issue_params = { title: 'Bug in old browser' } + Issues::CreateService.new(contributed_project, @user, issue_params).execute + + # Push code contribution + push_params = { + project: contributed_project, + action: Event::PUSHED, + author_id: @user.id, + data: { commit_count: 3 } + } + + Event.create(push_params) + + visit @user.username + wait_for_ajax + end + + it 'displays calendar', js: true do + expect(page).to have_css('.js-contrib-calendar') + end + + it 'displays calendar activity log', js: true do + expect(find('.content_list .event-note')).to have_content "Bug in old browser" + end + + it 'displays calendar activity square color', js: true do + expect(page).to have_selector('.user-contrib-cell[fill=\'#acd5f2\']', count: 1) + end +end -- cgit v1.2.3 From c0a7eb3809ba8031ee8afc84fd1b66f8ed68a5af Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 4 Sep 2016 11:05:26 -0700 Subject: Refactor boards_spec.rb to avoid code duplication Originally created to address #21197, but !6224 solved the intermittent spec failures. --- spec/features/boards/boards_spec.rb | 177 +++++++++++++++--------------------- 1 file changed, 72 insertions(+), 105 deletions(-) diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index e51586d32ec..19941978c5f 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -94,15 +94,8 @@ describe 'Issue Boards', feature: true, js: true do end it 'shows issues in lists' do - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('2') - expect(page).to have_selector('.card', count: 2) - end - - page.within(find('.board:nth-child(3)')) do - expect(page.find('.board-header')).to have_content('2') - expect(page).to have_selector('.card', count: 2) - end + wait_for_board_cards(2, 2) + wait_for_board_cards(3, 2) end it 'shows confidential issues with icon' do @@ -203,37 +196,33 @@ describe 'Issue Boards', feature: true, js: true do context 'backlog' do it 'shows issues in backlog with no labels' do - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('6') - expect(page).to have_selector('.card', count: 6) - end + wait_for_board_cards(1, 6) end it 'moves issue from backlog into list' do drag_to(list_to_index: 1) - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('5') - expect(page).to have_selector('.card', count: 5) - end - wait_for_vue_resource - - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('3') - expect(page).to have_selector('.card', count: 3) - end + wait_for_board_cards(1, 5) + wait_for_board_cards(2, 3) end end context 'done' do it 'shows list of done issues' do - expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1) + wait_for_board_cards(4, 1) + wait_for_ajax end it 'moves issue to done' do drag_to(list_from_index: 0, list_to_index: 3) + wait_for_board_cards(1, 5) + wait_for_board_cards(2, 2) + wait_for_board_cards(3, 2) + wait_for_board_cards(4, 2) + + expect(find('.board:nth-child(1)')).not_to have_content(issue9.title) expect(find('.board:nth-child(4)')).to have_selector('.card', count: 2) expect(find('.board:nth-child(4)')).to have_content(issue9.title) expect(find('.board:nth-child(4)')).not_to have_content(planning.title) @@ -242,8 +231,12 @@ describe 'Issue Boards', feature: true, js: true do it 'removes all of the same issue to done' do drag_to(list_from_index: 1, list_to_index: 3) - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1) - expect(find('.board:nth-child(3)')).to have_selector('.card', count: 1) + wait_for_board_cards(1, 6) + wait_for_board_cards(2, 1) + wait_for_board_cards(3, 1) + wait_for_board_cards(4, 2) + + expect(find('.board:nth-child(2)')).not_to have_content(issue6.title) expect(find('.board:nth-child(4)')).to have_content(issue6.title) expect(find('.board:nth-child(4)')).not_to have_content(planning.title) end @@ -253,6 +246,11 @@ describe 'Issue Boards', feature: true, js: true do it 'changes position of list' do drag_to(list_from_index: 1, list_to_index: 2, selector: '.board-header') + wait_for_board_cards(1, 6) + wait_for_board_cards(2, 2) + wait_for_board_cards(3, 2) + wait_for_board_cards(4, 1) + expect(find('.board:nth-child(2)')).to have_content(development.title) expect(find('.board:nth-child(2)')).to have_content(planning.title) end @@ -260,8 +258,11 @@ describe 'Issue Boards', feature: true, js: true do it 'issue moves between lists' do drag_to(list_from_index: 1, card_index: 1, list_to_index: 2) - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1) - expect(find('.board:nth-child(3)')).to have_selector('.card', count: 3) + wait_for_board_cards(1, 6) + wait_for_board_cards(2, 1) + wait_for_board_cards(3, 3) + wait_for_board_cards(4, 1) + expect(find('.board:nth-child(3)')).to have_content(issue6.title) expect(find('.board:nth-child(3)').all('.card').last).not_to have_content(development.title) end @@ -269,8 +270,11 @@ describe 'Issue Boards', feature: true, js: true do it 'issue moves between lists' do drag_to(list_from_index: 2, list_to_index: 1) - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 3) - expect(find('.board:nth-child(3)')).to have_selector('.card', count: 1) + wait_for_board_cards(1, 6) + wait_for_board_cards(2, 3) + wait_for_board_cards(3, 1) + wait_for_board_cards(4, 1) + expect(find('.board:nth-child(2)')).to have_content(issue7.title) expect(find('.board:nth-child(2)').all('.card').first).not_to have_content(planning.title) end @@ -278,8 +282,12 @@ describe 'Issue Boards', feature: true, js: true do it 'issue moves from done' do drag_to(list_from_index: 3, list_to_index: 1) - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 3) expect(find('.board:nth-child(2)')).to have_content(issue8.title) + + wait_for_board_cards(1, 6) + wait_for_board_cards(2, 3) + wait_for_board_cards(3, 2) + wait_for_board_cards(4, 0) end context 'issue card' do @@ -342,10 +350,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'moves issues from backlog into new list' do - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('6') - expect(page).to have_selector('.card', count: 6) - end + wait_for_board_cards(1, 6) click_button 'Create new list' wait_for_ajax @@ -356,10 +361,7 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('5') - expect(page).to have_selector('.card', count: 5) - end + wait_for_board_cards(1, 5) end end end @@ -379,16 +381,8 @@ describe 'Issue Boards', feature: true, js: true do end wait_for_vue_resource - - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('1') - expect(page).to have_selector('.card', count: 1) - end - - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) - end + wait_for_board_cards(1, 1) + wait_for_empty_boards((2..4)) end it 'filters by assignee' do @@ -406,15 +400,8 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('1') - expect(page).to have_selector('.card', count: 1) - end - - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) - end + wait_for_board_cards(1, 1) + wait_for_empty_boards((2..4)) end it 'filters by milestone' do @@ -431,16 +418,10 @@ describe 'Issue Boards', feature: true, js: true do end wait_for_vue_resource - - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) - end - - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('1') - expect(page).to have_selector('.card', count: 1) - end + wait_for_board_cards(1, 0) + wait_for_board_cards(2, 1) + wait_for_board_cards(3, 0) + wait_for_board_cards(4, 0) end it 'filters by label' do @@ -456,16 +437,8 @@ describe 'Issue Boards', feature: true, js: true do end wait_for_vue_resource - - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('1') - expect(page).to have_selector('.card', count: 1) - end - - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) - end + wait_for_board_cards(1, 1) + wait_for_empty_boards((2..4)) end it 'infinite scrolls list with label filter' do @@ -519,15 +492,8 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('1') - expect(page).to have_selector('.card', count: 1) - end - - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) - end + wait_for_board_cards(1, 1) + wait_for_empty_boards((2..4)) end it 'filters by no label' do @@ -544,15 +510,10 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('5') - expect(page).to have_selector('.card', count: 5) - end - - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) - end + wait_for_board_cards(1, 5) + wait_for_board_cards(2, 0) + wait_for_board_cards(3, 0) + wait_for_board_cards(4, 1) end it 'filters by clicking label button on issue' do @@ -565,15 +526,8 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource - page.within(find('.board', match: :first)) do - expect(page.find('.board-header')).to have_content('1') - expect(page).to have_selector('.card', count: 1) - end - - page.within(find('.board:nth-child(2)')) do - expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) - end + wait_for_board_cards(1, 1) + wait_for_empty_boards((2..4)) page.within('.labels-filter') do expect(find('.dropdown-toggle-text')).to have_content(bug.title) @@ -648,4 +602,17 @@ describe 'Issue Boards', feature: true, js: true do wait_for_vue_resource end + + def wait_for_board_cards(board_number, expected_cards) + page.within(find(".board:nth-child(#{board_number})")) do + expect(page.find('.board-header')).to have_content(expected_cards.to_s) + expect(page).to have_selector('.card', count: expected_cards) + end + end + + def wait_for_empty_boards(board_numbers) + board_numbers.each do |board| + wait_for_board_cards(board, 0) + end + end end -- cgit v1.2.3 From 43e05fe99eedc58379aac170e8c6c7be6ced543c Mon Sep 17 00:00:00 2001 From: sknebel Date: Sun, 18 Sep 2016 02:29:45 +0000 Subject: Formatting fix in doc/ci/examples/README.md --- doc/ci/examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 406396deaaa..71670e6247c 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -16,4 +16,4 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) -[gitlab-ci-templates][https://gitlab.com/gitlab-org/gitlab-ci-yml] +[gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml -- cgit v1.2.3 From b1a4d94091b87ca0a8d36a7b75095f7ae5d7ccef Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 17 Sep 2016 20:33:41 -0700 Subject: Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist A customer ran into an issue where a Sidekiq task retried over and over, leading to duplicate master branches in their protected branch list. Closes #22177 --- CHANGELOG | 1 + app/services/git_push_service.rb | 2 +- spec/services/git_push_service_spec.rb | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e9445a18a18..b4921fedb52 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.12.0 (unreleased) - Bump fog-aws to v0.11.0 to support ap-south-1 region - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 + - Only create a protected branch upon a push to a new branch if a rule for that branch doesn't exist - Prune events older than 12 months. (ritave) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Fix issues/merge-request templates dropdown for forked projects diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 78feb37aa2a..948041063c0 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -87,7 +87,7 @@ class GitPushService < BaseService project.change_head(branch_name) # Set protection on the default branch if configured - if current_application_settings.default_branch_protection != PROTECTION_NONE + if current_application_settings.default_branch_protection != PROTECTION_NONE && !@project.protected_branch?(@project.default_branch) params = { name: @project.default_branch, diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 6ac1fa8f182..22724434a7f 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -253,6 +253,21 @@ describe GitPushService, services: true do expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER]) end + it "when pushing a branch for the first time with an existing branch permission configured" do + stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH) + + create(:protected_branch, :no_one_can_push, :developers_can_merge, project: project, name: 'master') + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + expect_any_instance_of(ProtectedBranches::CreateService).not_to receive(:execute) + + execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) + + expect(project.protected_branches).not_to be_empty + expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::NO_ACCESS]) + expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER]) + end + it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE) -- cgit v1.2.3 From 260d1e6af5dbe6f66270c7844e54ec20f4007dbf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 24 Aug 2016 12:57:04 -0300 Subject: Update GitHub importer documentation to reflect current importer state --- doc/workflow/importing/import_projects_from_github.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index 370d885d366..dd33513d9e3 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -49,6 +49,16 @@ The importer will create any new namespaces if they don't exist or in the case the namespace is taken, the project will be imported on the user's namespace. +### Note + +When we are importing Issues/Pull Requests we try to find the original +author or assignee using the Github Id, but for this to work the original +author or assignee should had signed in before on the GitLab instance and +associated their GitHub account. If we don’t find the user in our database +we set the project creator (most of the times the current user that started +the import process) as the author but we keep a reference on the issue +about the original author. + [gh-import]: ../../integration/github.md "GitHub integration" [ee-gh]: http://docs.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE" [new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab" -- cgit v1.2.3 From 9c2f778cc45ac77926561584c592dd09d620f9ea Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 31 Aug 2016 13:16:44 +0200 Subject: Refactor GitHub importing documentation --- .../img/import_projects_from_github_importer.png | Bin 22711 -> 28989 bytes ...mport_projects_from_github_new_project_page.png | Bin 13668 -> 24911 bytes ...ort_projects_from_github_select_auth_method.png | Bin 0 -> 42043 bytes .../importing/import_projects_from_github.md | 136 ++++++++++++++------- 4 files changed, 92 insertions(+), 44 deletions(-) create mode 100644 doc/workflow/importing/img/import_projects_from_github_select_auth_method.png diff --git a/doc/workflow/importing/img/import_projects_from_github_importer.png b/doc/workflow/importing/img/import_projects_from_github_importer.png index b6ed8dd692a..2082de06f47 100644 Binary files a/doc/workflow/importing/img/import_projects_from_github_importer.png and b/doc/workflow/importing/img/import_projects_from_github_importer.png differ diff --git a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png index c8f35a50f48..6e91c430a33 100644 Binary files a/doc/workflow/importing/img/import_projects_from_github_new_project_page.png and b/doc/workflow/importing/img/import_projects_from_github_new_project_page.png differ diff --git a/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png b/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png new file mode 100644 index 00000000000..c11863ab10c Binary files /dev/null and b/doc/workflow/importing/img/import_projects_from_github_select_auth_method.png differ diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index dd33513d9e3..7e5dd628c94 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -1,64 +1,112 @@ # Import your project from GitHub to GitLab +Import your projects from GitHub to GitLab with minimal effort. + +## Overview + >**Note:** -In order to enable the GitHub import setting, you may also want to -enable the [GitHub integration][gh-import] in your GitLab instance. This -configuration is optional, you will be able import your GitHub repositories -with a Personal Access Token. +If you are an administrator you can enable the [GitHub integration][gh-import] +in your GitLab instance sitewide. This configuration is optional, users will be +able import their GitHub repositories with a [personal access token][gh-token]. + +- At its current state, GitHub importer can import: + - the repository description (GitLab 7.7+) + - the Git repository data (GitLab 7.7+) + - the issues (GitLab 7.7+) + - the pull requests (GitLab 8.4+) + - the wiki pages (GitLab 8.4+) + - the milestones (GitLab 8.7+) + - the labels (GitLab 8.7+) + - the release note descriptions (GitLab 8.12+) +- References to pull requests and issues are preserved (GitLab 8.7+) +- Repository public access is retained. If a repository is private in GitHub + it will be created as private in GitLab as well. + +## How it works + +When issues/pull requests are being imported, the GitHub importer tries to find +the GitHub author/assignee in GitLab's database using the GitHub ID. For this +to work, the GitHub author/assignee should have signed in beforehand in GitLab +and [**associated their GitHub account**][social sign-in]. If the user is not +found in GitLab's database, the project creator (most of the times the current +user that started the import process) is set as the author, but a reference on +the issue about the original GitHub author is kept. + +The importer will create any new namespaces (groups) if they don't exist or in +the case the namespace is taken, the repository will be imported under the user's +namespace that started the import process. + +## Importing your GitHub repositories + +The importer page is visible when you create a new project. -At its current state, GitHub importer can import: +![New project page on GitLab](img/import_projects_from_github_new_project_page.png) -- the repository description (introduced in GitLab 7.7) -- the git repository data (introduced in GitLab 7.7) -- the issues (introduced in GitLab 7.7) -- the pull requests (introduced in GitLab 8.4) -- the wiki pages (introduced in GitLab 8.4) -- the milestones (introduced in GitLab 8.7) -- the labels (introduced in GitLab 8.7) -- the release note descriptions (introduced in GitLab 8.12) +Click on the **GitHub** link and the import authorization process will start. +There are two ways to authorize access to your GitHub repositories: -With GitLab 8.7+, references to pull requests and issues are preserved. +1. [Using the GitHub integration][gh-integration] (if it's enabled by your + GitLab administrator). This is the preferred way as it's possible to + preserve the GitHub authors/assignees. Read more in the [How it works](#how-it-works) + section. +1. [Using a personal access token][gh-token] provided by GitHub. -The importer page is visible when you [create a new project][new-project]. -Click on the **GitHub** link and, if you are logged in via the GitHub -integration, you will be redirected to GitHub for permission to access your -projects. After accepting, you'll be automatically redirected to the importer. +![Select authentication method](img/import_projects_from_github_select_auth_method.png) -If you are not using the GitHub integration, you can still perform a one-off -authorization with GitHub to access your projects. +### Authorize access to your repositories using the GitHub integration -Alternatively, you can also enter a GitHub Personal Access Token. Once you enter -your token, you'll be taken to the importer. +If the [GitHub integration][gh-import] is enabled by your GitLab administrator, +you can use it instead of the personal access token. -![New project page on GitLab](img/import_projects_from_github_new_project_page.png) +1. First you may want to connect your GitHub account to GitLab in order for + the username mapping to be correct. Follow the [social sign-in] documentation + on how to do so. +1. Once you connect GitHub, click the **List your GitHub repositories** button + and you will be redirected to GitHub for permission to access your projects. +1. After accepting, you'll be automatically redirected to the importer. ---- +You can now go on and [select which repositories to import](#select-which-repositories-to-import). -While at the GitHub importer page, you can see the import statuses of your -GitHub projects. Those that are being imported will show a _started_ status, -those already imported will be green, whereas those that are not yet imported -have an **Import** button on the right side of the table. If you want, you can -import all your GitHub projects in one go by hitting **Import all projects** -in the upper left corner. +### Authorize access to your repositories using a personal access token -![GitHub importer page](img/import_projects_from_github_importer.png) +>**Note:** +For a proper author/assignee mapping, the [GitHub integration][gh-integration] +should be used instead of using a [personal access token][gh-token]. If the +GitHub integration is enabled by your GitLab administrator, it should be the +preferred method to import your repositories. ---- +If you are not using the GitHub integration, you can still perform a one-off +authorization with GitHub to grant GitLab access your repositories: + +1. Go to . +1. Enter a token description. +1. Check the `repo` scope. +1. Click **Generate token**. +1. Copy the token hash. +1. Go back to GitLab and provide the token to the GitHub importer. +1. Hit the **List your GitHub repositories** button and wait while GitLab reads + your repositories' information. Once done, you'll be taken to the importer + page to select the repositories to import. + +### Select which repositories to import -The importer will create any new namespaces if they don't exist or in the -case the namespace is taken, the project will be imported on the user's -namespace. +After you've authorized access to your GitHub repositories, you will be +redirected to the GitHub importer page. -### Note +From there, you can see the import statuses of your GitHub repositories. -When we are importing Issues/Pull Requests we try to find the original -author or assignee using the Github Id, but for this to work the original -author or assignee should had signed in before on the GitLab instance and -associated their GitHub account. If we don’t find the user in our database -we set the project creator (most of the times the current user that started -the import process) as the author but we keep a reference on the issue -about the original author. +- Those that are being imported will show a _started_ status, +- those already successfully imported will be green with a _done_ status, +- whereas those that are not yet imported will have an **Import** button on the + right side of the table. + +If you want, you can import all your GitHub projects in one go by hitting +**Import all projects** in the upper left corner. + +![GitHub importer page](img/import_projects_from_github_importer.png) [gh-import]: ../../integration/github.md "GitHub integration" -[ee-gh]: http://docs.gitlab.com/ee/integration/github.html "GitHub integration for GitLab EE" [new-project]: ../../gitlab-basics/create-project.md "How to create a new project in GitLab" +[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration +[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token +[social sign-in]: ../../profile/account/social_sign_in.md -- cgit v1.2.3 From c2ad17754e189c23dcf8e9495d2b8b52c20eca2c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 18 Sep 2016 11:32:01 +0200 Subject: Clarify why GH integration is the preferred importing method --- doc/workflow/importing/import_projects_from_github.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md index 7e5dd628c94..dd38fe0bc01 100644 --- a/doc/workflow/importing/import_projects_from_github.md +++ b/doc/workflow/importing/import_projects_from_github.md @@ -70,10 +70,11 @@ You can now go on and [select which repositories to import](#select-which-reposi ### Authorize access to your repositories using a personal access token >**Note:** -For a proper author/assignee mapping, the [GitHub integration][gh-integration] -should be used instead of using a [personal access token][gh-token]. If the -GitHub integration is enabled by your GitLab administrator, it should be the -preferred method to import your repositories. +For a proper author/assignee mapping for issues and pull requests, the +[GitHub integration][gh-integration] should be used instead of the +[personal access token][gh-token]. If the GitHub integration is enabled by your +GitLab administrator, it should be the preferred method to import your repositories. +Read more in the [How it works](#how-it-works) section. If you are not using the GitHub integration, you can still perform a one-off authorization with GitHub to grant GitLab access your repositories: -- cgit v1.2.3 From 505d9cf9b2229ede14004e5ee7d754e67b1fbf75 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 18 Sep 2016 16:39:44 +0200 Subject: Document how to download the latest build artifacts [ci skip] --- doc/user/project/builds/artifacts.md | 32 +++++++++++++++++++++ .../builds/img/build_latest_artifacts_browser.png | Bin 0 -> 26617 bytes 2 files changed, 32 insertions(+) create mode 100644 doc/user/project/builds/img/build_latest_artifacts_browser.png diff --git a/doc/user/project/builds/artifacts.md b/doc/user/project/builds/artifacts.md index c93ae1c369c..88f1863dddb 100644 --- a/doc/user/project/builds/artifacts.md +++ b/doc/user/project/builds/artifacts.md @@ -101,4 +101,36 @@ inside GitLab that make that possible. ![Build artifacts browser](img/build_artifacts_browser.png) +## Downloading the latest build artifacts + +It is possible to download the latest artifacts of a build via a well known URL +so you can use it for scripting purposes. + +The structure of the URL is the following: + +``` +https://example.com///builds/artifacts//download?job= +``` + +For example, to download the latest artifacts of the job named `rspec 6 20` of +the `master` branch of the `gitlab-ce` project that belongs to the `gitlab-org` +namespace, the URL would be: + +``` +https://gitlab.com/gitlab-org/gitlab-ce/builds/artifacts/master/download?job=rspec+6+20 +``` + +The latest builds are also exposed in the UI in various places. Specifically, +look for the download button in: + +- the main project's page +- the branches page +- the tags page + +If the latest build has failed to upload the artifacts, you can see that +information in the UI. + +![Latest artifacts button](img/build_latest_artifacts_browser.png) + + [gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository" diff --git a/doc/user/project/builds/img/build_latest_artifacts_browser.png b/doc/user/project/builds/img/build_latest_artifacts_browser.png new file mode 100644 index 00000000000..d8e9071958c Binary files /dev/null and b/doc/user/project/builds/img/build_latest_artifacts_browser.png differ -- cgit v1.2.3 From e0f90fb2dee39a6d7f2ecea7de43466bf9ac25ad Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 18 Sep 2016 20:39:44 +0200 Subject: Return created status --- app/models/concerns/has_status.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index d658552f695..0fa4df0fb56 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -20,7 +20,7 @@ module HasStatus skipped = scope.skipped.select('count(*)').to_sql deduce_status = "(CASE - WHEN (#{builds})=(#{created}) THEN NULL + WHEN (#{builds})=(#{created}) THEN 'created' WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending' -- cgit v1.2.3 From bf41ab2640d215dccd890cc9ba3dd4f3f72cf84a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 18 Sep 2016 21:41:45 +0200 Subject: Add specs that target status failure --- spec/requests/api/commits_spec.rb | 2 +- spec/views/projects/pipelines/show.html.haml_spec.rb | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 5b3dc60aba2..10f772c5b1a 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -110,7 +110,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) expect(response).to have_http_status(200) - expect(json_response['status']).to be_nil + expect(json_response['status']).to eq("created") end end diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb index c5b16c1c304..ac7f3ffb157 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -9,11 +9,13 @@ describe 'projects/pipelines/show' do before do controller.prepend_view_path('app/views/projects') - create_build('build', 0, 'build') - create_build('test', 1, 'rspec 0:2') - create_build('test', 1, 'rspec 1:2') - create_build('test', 1, 'audit') - create_build('deploy', 2, 'production') + create_build('build', 0, 'build', :success) + create_build('test', 1, 'rspec 0:2', :pending) + create_build('test', 1, 'rspec 1:2', :running) + create_build('test', 1, 'spinach 0:2', :created) + create_build('test', 1, 'spinach 1:2', :created) + create_build('test', 1, 'audit', :created) + create_build('deploy', 2, 'production', :created) create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3) @@ -37,6 +39,7 @@ describe 'projects/pipelines/show' do # builds expect(rendered).to have_text('rspec') + expect(rendered).to have_text('spinach') expect(rendered).to have_text('rspec 0:2') expect(rendered).to have_text('production') expect(rendered).to have_text('jenkins') @@ -44,7 +47,7 @@ describe 'projects/pipelines/show' do private - def create_build(stage, stage_idx, name) - create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name) + def create_build(stage, stage_idx, name, status) + create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name, status: status) end end -- cgit v1.2.3 From 73f5a8eb88383563802952f53e2bc4f074016744 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 18 Sep 2016 13:44:34 -0700 Subject: Add missing spec for ProtectedBranches::CreateService --- .../protected_branches/create_service_spec.rb | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 spec/services/protected_branches/create_service_spec.rb diff --git a/spec/services/protected_branches/create_service_spec.rb b/spec/services/protected_branches/create_service_spec.rb new file mode 100644 index 00000000000..7d4eff3b6ef --- /dev/null +++ b/spec/services/protected_branches/create_service_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe ProtectedBranches::CreateService, services: true do + let(:project) { create(:empty_project) } + let(:user) { project.owner } + let(:params) do + { + name: 'master', + merge_access_levels_attributes: [ { access_level: Gitlab::Access::MASTER } ], + push_access_levels_attributes: [ { access_level: Gitlab::Access::MASTER } ] + } + end + + describe '#execute' do + subject(:service) { described_class.new(project, user, params) } + + it 'creates a new protected branch' do + expect { service.execute }.to change(ProtectedBranch, :count).by(1) + expect(project.protected_branches.last.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER]) + expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MASTER]) + end + end +end -- cgit v1.2.3 From 282c325d024e115b52c2ebfe394096b7e4cc68ed Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 18 Sep 2016 21:21:43 -0700 Subject: Reset pushes_since_gc counter before specs run to ensure starting point is 0 --- spec/services/projects/housekeeping_service_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index c6160f4fa57..cf90b33dfb4 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -4,6 +4,10 @@ describe Projects::HousekeepingService do subject { Projects::HousekeepingService.new(project) } let(:project) { create :project } + before do + project.reset_pushes_since_gc + end + after do project.reset_pushes_since_gc end -- cgit v1.2.3 From 31e592647aaab8923922a316e442c404ef70b8df Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Sep 2016 13:12:45 +0800 Subject: Fix download artifacts button link: Rails ignored unused arguments and ref_name_and_path is the last path argument, therefore we need to concatenate that parts ourselves. Otherwise, 'download' won't be shown at all. --- app/views/projects/buttons/_download.html.haml | 2 +- spec/features/projects/branches/download_buttons_spec.rb | 6 +++++- spec/features/projects/files/download_buttons_spec.rb | 6 +++++- spec/features/projects/main/download_buttons_spec.rb | 6 +++++- spec/features/projects/tags/download_buttons_spec.rb | 6 +++++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 5f5e071eb40..24de020917a 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -37,6 +37,6 @@ %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li - = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do + = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do %i.fa.fa-download %span Download '#{job.name}' diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index 04058300570..92028c19361 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -33,7 +33,11 @@ feature 'Download buttons in branches page', feature: true do end scenario 'shows download artifacts button' do - expect(page).to have_link "Download '#{build.name}'" + href = latest_succeeded_namespace_project_artifacts_path( + project.namespace, project, 'binary-encoding/download', + job: 'build') + + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb index be5cebcd7c9..d7c29a7e074 100644 --- a/spec/features/projects/files/download_buttons_spec.rb +++ b/spec/features/projects/files/download_buttons_spec.rb @@ -34,7 +34,11 @@ feature 'Download buttons in files tree', feature: true do end scenario 'shows download artifacts button' do - expect(page).to have_link "Download '#{build.name}'" + href = latest_succeeded_namespace_project_artifacts_path( + project.namespace, project, "#{project.default_branch}/download", + job: 'build') + + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb index b26c0ea7a14..227ccf9459c 100644 --- a/spec/features/projects/main/download_buttons_spec.rb +++ b/spec/features/projects/main/download_buttons_spec.rb @@ -33,7 +33,11 @@ feature 'Download buttons in project main page', feature: true do end scenario 'shows download artifacts button' do - expect(page).to have_link "Download '#{build.name}'" + href = latest_succeeded_namespace_project_artifacts_path( + project.namespace, project, "#{project.default_branch}/download", + job: 'build') + + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index 6e0022c179f..dd93d25c2c6 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -34,7 +34,11 @@ feature 'Download buttons in tags page', feature: true do end scenario 'shows download artifacts button' do - expect(page).to have_link "Download '#{build.name}'" + href = latest_succeeded_namespace_project_artifacts_path( + project.namespace, project, "#{tag}/download", + job: 'build') + + expect(page).to have_link "Download '#{build.name}'", href: href end end end -- cgit v1.2.3 From cc51e76e9ab95f5b3146d5551ad43fc5e495347a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Sep 2016 13:24:43 +0800 Subject: Add an entry to CHANGELOG [ci skip] --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index e9445a18a18..fb591168dac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.12.0 (unreleased) - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Fix pagination on user snippets page - Fix sorting of issues in API + - Fix download artifacts button links !6407 - Sort project variables by key. !6275 (Diego Souza) - Ensure specs on sorting of issues in API are deterministic on MySQL - Escape search term before passing it to Regexp.new !6241 (winniehell) -- cgit v1.2.3 From e74b7d665b80525c894bd3743ed7c4dc8810b4af Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 30 Aug 2016 10:09:41 +0200 Subject: squashed - Fix DB exceptions raised importing some specific projects. Better import of labels, milestones and protected branches. Updated relevant specs. Loose pipeline validation on importing, so it does not fail when there are missing fields, which are not validated at DB level. Also, updated spec with relevant test. --- CHANGELOG | 3 + app/models/ci/pipeline.rb | 11 +-- lib/gitlab/import_export.rb | 2 +- lib/gitlab/import_export/import_export.yml | 4 +- lib/gitlab/import_export/relation_factory.rb | 28 ++++++-- lib/gitlab/import_export/version_checker.rb | 4 +- .../import_export/test_project_export.tar.gz | Bin 676870 -> 1363770 bytes spec/lib/gitlab/import_export/project.json | 80 +++++++++++++++++++-- .../import_export/project_tree_restorer_spec.rb | 36 ++++++++-- .../gitlab/import_export/version_checker_spec.rb | 2 +- 10 files changed, 145 insertions(+), 25 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e9445a18a18..8380a02b14b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -158,6 +158,9 @@ v 8.11.6 - Restore SSH Key title auto-population behavior. !6186 - Fix DB schema to match latest migration. !6256 - Exclude some pending or inactivated rows in Member scopes. + - Fix Import/Export issues importing protected branches and some specific models + +v 8.11.6 (unreleased) v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 0b1df9f4294..70647b8532b 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -2,6 +2,7 @@ module Ci class Pipeline < ActiveRecord::Base extend Ci::Model include HasStatus + include Importable self.table_name = 'ci_commits' @@ -12,12 +13,12 @@ module Ci has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id - validates_presence_of :sha - validates_presence_of :ref - validates_presence_of :status - validate :valid_commit_sha + validates_presence_of :sha, unless: :importing? + validates_presence_of :ref, unless: :importing? + validates_presence_of :status, unless: :importing? + validate :valid_commit_sha, unless: :importing? - after_save :keep_around_commits + after_save :keep_around_commits, unless: :importing? delegate :stages, to: :statuses diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index bb562bdcd2c..b90651cbc2d 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -2,7 +2,7 @@ module Gitlab module ImportExport extend self - VERSION = '0.1.3' + VERSION = '0.1.4' FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index c2e8a1ca5dd..925a952156f 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -35,7 +35,9 @@ project_tree: - :deploy_keys - :services - :hooks - - :protected_branches + - protected_branches: + - :merge_access_levels + - :push_access_levels - :labels - milestones: - :events diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index b0726268ca6..7b18d11ce0f 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -7,7 +7,9 @@ module Gitlab variables: 'Ci::Variable', triggers: 'Ci::Trigger', builds: 'Ci::Build', - hooks: 'ProjectHook' }.freeze + hooks: 'ProjectHook', + merge_access_levels: 'ProtectedBranch::MergeAccessLevel', + push_access_levels: 'ProtectedBranch::PushAccessLevel' }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze @@ -17,6 +19,8 @@ module Gitlab EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze + FINDER_ATTRIBUTES = %w[title color project_id].freeze + def self.create(*args) new(*args).create end @@ -149,7 +153,7 @@ module Gitlab end def parsed_relation_hash - @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } + @parsed_relation_hash ||= @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } end def set_st_diffs @@ -161,14 +165,30 @@ module Gitlab # Otherwise always create the record, skipping the extra SELECT clause. @existing_or_new_object ||= begin if EXISTING_OBJECT_CHECK.include?(@relation_name) - existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id')) - existing_object.assign_attributes(parsed_relation_hash) + events = parsed_relation_hash.delete('events') + + unless events.blank? + existing_object.assign_attributes(events: events) + end + existing_object else relation_class.new(parsed_relation_hash) end end end + + def existing_object + @existing_object ||= + begin + finder_hash = parsed_relation_hash.slice(*FINDER_ATTRIBUTES) + existing_object = relation_class.find_or_create_by(finder_hash) + # Done in two steps, as MySQL behaves differently than PostgreSQL using + # the +find_or_create_by+ method and does not return the ID the second time. + existing_object.update(parsed_relation_hash) + existing_object + end + end end end end diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb index de3fe6d822e..fc08082fc86 100644 --- a/lib/gitlab/import_export/version_checker.rb +++ b/lib/gitlab/import_export/version_checker.rb @@ -24,8 +24,8 @@ module Gitlab end def verify_version!(version) - if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version) - raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") + if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version) + raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}") else true end diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz index e14b2705704..d04bdea0fe4 100644 Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 5114f9c55e1..281f6cf1177 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -24,7 +24,7 @@ "test_ee_field": "test", "milestone": { "id": 1, - "title": "v0.0", + "title": "test milestone", "project_id": 8, "description": "test milestone", "due_date": null, @@ -51,7 +51,7 @@ { "id": 2, "label_id": 2, - "target_id": 3, + "target_id": 40, "target_type": "Issue", "created_at": "2016-07-22T08:57:02.840Z", "updated_at": "2016-07-22T08:57:02.840Z", @@ -281,6 +281,31 @@ "deleted_at": null, "due_date": null, "moved_to_id": null, + "milestone": { + "id": 1, + "title": "test milestone", + "project_id": 8, + "description": "test milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "events": [ + { + "id": 487, + "target_type": "Milestone", + "target_id": 1, + "title": null, + "data": null, + "project_id": 46, + "created_at": "2016-06-14T15:02:04.418Z", + "updated_at": "2016-06-14T15:02:04.418Z", + "action": 1, + "author_id": 18 + } + ] + }, "notes": [ { "id": 359, @@ -494,6 +519,27 @@ "deleted_at": null, "due_date": null, "moved_to_id": null, + "label_links": [ + { + "id": 99, + "label_id": 2, + "target_id": 38, + "target_type": "Issue", + "created_at": "2016-07-22T08:57:02.840Z", + "updated_at": "2016-07-22T08:57:02.840Z", + "label": { + "id": 2, + "title": "test2", + "color": "#428bca", + "project_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "priority": null + } + } + ], "notes": [ { "id": 367, @@ -6478,7 +6524,7 @@ { "id": 37, "project_id": 5, - "ref": "master", + "ref": null, "sha": "048721d90c449b244b7b4c53a9186b04330174ec", "before_sha": null, "push_data": null, @@ -7301,6 +7347,30 @@ ], "protected_branches": [ - + { + "id": 1, + "project_id": 9, + "name": "master", + "created_at": "2016-08-30T07:32:52.426Z", + "updated_at": "2016-08-30T07:32:52.426Z", + "merge_access_levels": [ + { + "id": 1, + "protected_branch_id": 1, + "access_level": 40, + "created_at": "2016-08-30T07:32:52.458Z", + "updated_at": "2016-08-30T07:32:52.458Z" + } + ], + "push_access_levels": [ + { + "id": 1, + "protected_branch_id": 1, + "access_level": 40, + "created_at": "2016-08-30T07:32:52.490Z", + "updated_at": "2016-08-30T07:32:52.490Z" + } + ] + } ] -} +} \ No newline at end of file diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index a07ef279e68..feacb295231 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -29,12 +29,30 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED) end + it 'has the same label associated to two issues' do + restored_project_json + + expect(Label.first.issues.count).to eq(2) + end + + it 'has milestones associated to two separate issues' do + restored_project_json + + expect(Milestone.find_by_description('test milestone').issues.count).to eq(2) + end + it 'creates a valid pipeline note' do restored_project_json expect(Ci::Pipeline.first.notes).not_to be_empty end + it 'restores pipelines with missing ref' do + restored_project_json + + expect(Ci::Pipeline.where(ref: nil)).not_to be_empty + end + it 'restores the correct event with symbolised data' do restored_project_json @@ -49,6 +67,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC') end + it 'contains the merge access levels on a protected branch' do + restored_project_json + + expect(ProtectedBranch.first.merge_access_levels).not_to be_empty + end + + it 'contains the push access levels on a protected branch' do + restored_project_json + + expect(ProtectedBranch.first.push_access_levels).not_to be_empty + end + context 'event at forth level of the tree' do let(:event) { Event.where(title: 'test levels').first } @@ -77,12 +107,6 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(Label.first.label_links.first.target).not_to be_nil end - it 'has milestones associated to issues' do - restored_project_json - - expect(Milestone.find_by_description('test milestone').issues).not_to be_empty - end - context 'Merge requests' do before do restored_project_json diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb index 90c6d1c67f6..c680e712b59 100644 --- a/spec/lib/gitlab/import_export/version_checker_spec.rb +++ b/spec/lib/gitlab/import_export/version_checker_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::ImportExport::VersionChecker, services: true do it 'shows the correct error message' do described_class.check!(shared: shared) - expect(shared.errors.first).to eq("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") + expect(shared.errors.first).to eq("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}") end end end -- cgit v1.2.3 From 3c7feaf3b12e3c8319f8ccc0f45a87ae0e2cfe8c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 7 Sep 2016 17:08:19 +0200 Subject: Fixed label color issue and added Import/Export versioning table --- doc/user/project/settings/import_export.md | 18 ++++++++++++++++-- lib/gitlab/import_export.rb | 1 + lib/gitlab/import_export/relation_factory.rb | 2 +- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index 08ff89ce6ae..445c0ee8333 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -3,8 +3,8 @@ >**Notes:** > > - [Introduced][ce-3050] in GitLab 8.9. -> - Importing will not be possible if the import instance version is lower -> than that of the exporter. +> - Importing will not be possible if the import instance version differs from +> that of the exporter. > - For existing installations, the project import option has to be enabled in > application settings (`/admin/application_settings`) under 'Import sources'. > You will have to be an administrator to enable and use the import functionality. @@ -17,6 +17,20 @@ Existing projects running on any GitLab instance or GitLab.com can be exported with all their related data and be moved into a new GitLab instance. +## Version history + +| GitLab version | Import/Export version | +| -------- | -------- | +| 8.12.0 to current | 0.1.4 | +| 8.10.3 | 0.1.3 | +| 8.10.0 | 0.1.2 | +| 8.9.5 | 0.1.1 | +| 8.9.0 | 0.1.0 | + + > The table reflects what GitLab version we updated the Import/Export version at. + > For instance, 8.10.3 and 8.11 will have the same Import/Export version (0.1.3) + > and the exports between them will be compatible. + ## Exported contents The following items will be exported: diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index b90651cbc2d..181e288a014 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -2,6 +2,7 @@ module Gitlab module ImportExport extend self + # For every version update, the version history in import_export.md has to be kept up to date. VERSION = '0.1.4' FILENAME_LIMIT = 50 diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 7b18d11ce0f..354ccd64696 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -19,7 +19,7 @@ module Gitlab EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze - FINDER_ATTRIBUTES = %w[title color project_id].freeze + FINDER_ATTRIBUTES = %w[title project_id].freeze def self.create(*args) new(*args).create -- cgit v1.2.3 From 0a3d2d7c2b97b228860aa5618bda5978a30084eb Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 19 Sep 2016 09:33:03 +0200 Subject: fix changelog --- CHANGELOG | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8380a02b14b..5bbb636a2eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -147,6 +147,7 @@ v 8.12.0 (unreleased) - Fix Gitlab::Popen.popen thread-safety issue - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) - Clean environment variables when running git hooks + - Fix Import/Export issues importing protected branches and some specific models - Fix non-master branch readme display in tree view v 8.11.6 @@ -158,9 +159,6 @@ v 8.11.6 - Restore SSH Key title auto-population behavior. !6186 - Fix DB schema to match latest migration. !6256 - Exclude some pending or inactivated rows in Member scopes. - - Fix Import/Export issues importing protected branches and some specific models - -v 8.11.6 (unreleased) v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 -- cgit v1.2.3 From a4638dddf22797f46d72ea7b73c8453ba68645ab Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 13 Sep 2016 14:14:55 +0200 Subject: Add support for dynamic environments Environments that can have a URL with predefined CI variables. --- app/models/ci/build.rb | 4 ++- app/models/environment.rb | 12 ++++++++ app/models/merge_request.rb | 13 ++++++++- app/services/create_deployment_service.rb | 34 +++++++++++++++++++++- ...7131111_add_environment_type_to_environments.rb | 29 ++++++++++++++++++ lib/expand_variables.rb | 15 ++++++++++ lib/gitlab/ci/config/node/environment.rb | 33 +++++++++++++++++++++ lib/gitlab/regex.rb | 4 +-- 8 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20160907131111_add_environment_type_to_environments.rb create mode 100644 lib/expand_variables.rb create mode 100644 lib/gitlab/ci/config/node/environment.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index fb16bc06d71..abdf8c76447 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -83,7 +83,9 @@ module Ci environment: build.environment, sha: build.sha, ref: build.ref, - tag: build.tag) + tag: build.tag, + options: build.options[:environment], + variables: variables) service.execute(build) end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 75e6f869786..33c9abf382a 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -4,6 +4,7 @@ class Environment < ActiveRecord::Base has_many :deployments before_validation :nullify_external_url + before_save :set_environment_type validates :name, presence: true, @@ -26,6 +27,17 @@ class Environment < ActiveRecord::Base self.external_url = nil if self.external_url.blank? end + def set_environment_type + names = name.split('/') + + self.environment_type = + if names.many? + names.first + else + nil + end + end + def includes_commit?(commit) return false unless last_deployment diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 75f48fd4ba5..b215f02e4b7 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -744,10 +744,21 @@ class MergeRequest < ActiveRecord::Base @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end + def all_commits_sha + merge_request_diffs.map(&:commits).flatten.map(&:sha).sort.uniq + end + + def latest_pipelines + @latest_pipelines ||= + if diff_head_sha && source_project + source_project.pipelines.order(id: :desc).where(sha: commits_sha, ref: source_branch) + end + end + def all_pipelines @all_pipelines ||= if diff_head_sha && source_project - source_project.pipelines.order(id: :desc).where(sha: commits_sha, ref: source_branch) + source_project.pipelines.order(id: :desc).where(sha: all_commits_sha, ref: source_branch) end end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index efeb9df9527..efa0798f47d 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -3,9 +3,13 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) environment = project.environments.find_or_create_by( - name: params[:environment] + name: expanded_name ) + if expanded_url + environment.external_url = expanded_url + end + project.deployments.create( environment: environment, ref: params[:ref], @@ -15,4 +19,32 @@ class CreateDeploymentService < BaseService deployable: deployable ) end + + private + + def expanded_name + name.expand_variables(variables) + end + + def expanded_url + return unless url + + @expanded_url ||= url.expand_variables(variables) + end + + def name + params[:environment] + end + + def url + options[:url] + end + + def options + params[:environment] || {} + end + + def variables + params[:variables] || [] + end end diff --git a/db/migrate/20160907131111_add_environment_type_to_environments.rb b/db/migrate/20160907131111_add_environment_type_to_environments.rb new file mode 100644 index 00000000000..d22b3f4d2d1 --- /dev/null +++ b/db/migrate/20160907131111_add_environment_type_to_environments.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddEnvironmentTypeToEnvironments < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :environments, :environment_type, :string + end +end diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb new file mode 100644 index 00000000000..40bf9483eb5 --- /dev/null +++ b/lib/expand_variables.rb @@ -0,0 +1,15 @@ +String.instance_eval + def expand_variables(variables) + # Convert hash array to variables + if variables.is_a?(Array) + variables = variables.reduce({}) do |hash, variable| + hash[variable[:key]] = variable[:value] + hash + end + end + + self.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do + variables[$1 || $2] + end + end +end diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb new file mode 100644 index 00000000000..6f04180039e --- /dev/null +++ b/lib/gitlab/ci/config/node/environment.rb @@ -0,0 +1,33 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents environment variables. + # + class Environment < Entry + include Validatable + + validations do + include LegacyValidationHelpers + + validate do + unless string_or_array_of_strings?(config) + errors.add(:config, + 'should be a string or an array of strings') + end + end + + def string_or_array_of_strings?(field) + validate_string(field) || validate_array_of_strings(field) + end + end + + def value + Array(@config) + end + end + end + end + end +end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index ffad5e17c78..d1a3e54ccd7 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -96,11 +96,11 @@ module Gitlab end def environment_name_regex - @environment_name_regex ||= /\A[a-zA-Z0-9_-]+\z/.freeze + git_reference_regex end def environment_name_regex_message - "can contain only letters, digits, '-' and '_'." + "be a valid git reference name" end end end -- cgit v1.2.3 From ba5bd3d1d64b1f56c39e4ddd03270de6820b2f7b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 14 Sep 2016 19:52:28 +0200 Subject: Add new CI configuration entry for the environment --- lib/ci/gitlab_ci_yaml_processor.rb | 3 +- lib/gitlab/ci/config/node/environment.rb | 33 ++++-- lib/gitlab/ci/config/node/job.rb | 6 +- spec/lib/gitlab/ci/config/node/environment_spec.rb | 125 +++++++++++++++++++++ 4 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 spec/lib/gitlab/ci/config/node/environment_spec.rb diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index caa815f720f..94a63508f79 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -60,7 +60,7 @@ module Ci name: job[:name].to_s, allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', - environment: job[:environment], + environment: job.fetch(:environment, {})[:name], yaml_variables: yaml_variables(name), options: { image: job[:image], @@ -69,6 +69,7 @@ module Ci cache: job[:cache], dependencies: job[:dependencies], after_script: job[:after_script], + environment: job[:environment], }.compact } end diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index 6f04180039e..e2fb1ab131e 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -3,28 +3,45 @@ module Gitlab class Config module Node ## - # Entry that represents environment variables. + # Entry that represents an environment. # class Environment < Entry include Validatable validations do - include LegacyValidationHelpers + validates :name, presence: true validate do - unless string_or_array_of_strings?(config) - errors.add(:config, - 'should be a string or an array of strings') + unless hash? || string? + errors.add(:config, 'should be a hash or a string') end end + end + + def hash? + @config.is_a?(Hash) + end + + def string? + @config.is_a?(String) + end - def string_or_array_of_strings?(field) - validate_string(field) || validate_array_of_strings(field) + def name + case + when string? then @config + when hash? then @config[:name] end end + def url + @config[:url] if hash? + end + def value - Array(@config) + case + when string? then { name: @config } + when hash? then @config + end end end end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 0cbdf7619c0..e90e80171a4 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -13,7 +13,7 @@ module Gitlab type stage when artifacts cache dependencies before_script after_script variables environment] - attributes :tags, :allow_failure, :when, :environment, :dependencies + attributes :tags, :allow_failure, :when, :dependencies validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -78,6 +78,9 @@ module Gitlab node :artifacts, Artifacts, description: 'Artifacts configuration for this job.' + node :environment, Environment, + description: 'Environment configuration for this job.' + helpers :before_script, :script, :stage, :type, :after_script, :cache, :image, :services, :only, :except, :variables, :artifacts, :commands @@ -133,6 +136,7 @@ module Gitlab only: only, except: except, variables: variables_defined? ? variables : nil, + environment: environment_defined? ? environment : nil, artifacts: artifacts, after_script: after_script } end diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb new file mode 100644 index 00000000000..27238f9c887 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb @@ -0,0 +1,125 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Environment do + let(:entry) { described_class.new(config) } + + before { entry.compose! } + + context 'when configuration is a string' do + let(:config) { 'production' } + + describe '#string?' do + it 'is string configuration' do + expect(entry).to be_string + end + end + + describe '#hash?' do + it 'is not hash configuration' do + expect(entry).not_to be_hash + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid hash' do + expect(entry.value).to eq(name: 'production') + end + end + + describe '#name' do + it 'returns environment name' do + expect(entry.name).to eq 'production' + end + end + + describe '#url' do + it 'returns environment url' do + expect(entry.url).to be_nil + end + end + end + + context 'when configuration is a hash' do + let(:config) do + { name: 'development', url: 'https://example.gitlab.com' } + end + + describe '#string?' do + it 'is not string configuration' do + expect(entry).not_to be_string + end + end + + describe '#hash?' do + it 'is hash configuration' do + expect(entry).to be_hash + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns valid hash' do + expect(entry.value).to eq config + end + end + + describe '#name' do + it 'returns environment name' do + expect(entry.name).to eq 'development' + end + end + + describe '#url' do + it 'returns environment url' do + expect(entry.url).to eq 'https://example.gitlab.com' + end + end + end + + context 'when configuration is invalid' do + context 'when configuration is an array' do + let(:config) { ['env'] } + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors' do + it 'contains error about invalid type' do + expect(entry.errors) + .to include 'environment config should be a hash or a string' + end + end + end + + context 'when environment name is not present' do + let(:config) { { url: 'https://example.gitlab.com' } } + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors?' do + it 'contains error about missing environment name' do + expect(entry.errors) + .to include "environment name can't be blank" + end + end + end + end +end -- cgit v1.2.3 From 08272ec1513cbd565e5db5995a681c25e1f4544f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 22:14:26 +0200 Subject: Add validation of URL and validation of name --- lib/gitlab/ci/config/node/environment.rb | 5 ++++ spec/lib/gitlab/ci/config/node/environment_spec.rb | 30 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index e2fb1ab131e..629c17e6250 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -11,6 +11,11 @@ module Gitlab validations do validates :name, presence: true + validates :url, + length: { maximum: 255 }, + allow_nil: true, + addressable_url: true + validate do unless hash? || string? errors.add(:config, 'should be a hash or a string') diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb index 27238f9c887..df453223da7 100644 --- a/spec/lib/gitlab/ci/config/node/environment_spec.rb +++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb @@ -87,6 +87,19 @@ describe Gitlab::Ci::Config::Node::Environment do end end + context 'when variables are used for environment' do + let(:config) do + { name: 'review/$CI_BUILD_REF_NAME', + url: 'https://$CI_BUILD_REF_NAME.review.gitlab.com' } + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + context 'when configuration is invalid' do context 'when configuration is an array' do let(:config) { ['env'] } @@ -121,5 +134,22 @@ describe Gitlab::Ci::Config::Node::Environment do end end end + + context 'when invalid URL is used' do + let(:config) { { name: 'test', url: 'invalid-example.gitlab.com' } } + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + + describe '#errors?' do + it 'contains error about invalid URL' do + expect(entry.errors) + .to include "environment url must be a valid url" + end + end + end end end -- cgit v1.2.3 From 2cc9a785dfdada5e2976b8341d3c9e6eae8fa66f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 22:33:56 +0200 Subject: Properly create deployment using all possible options --- app/models/ci/build.rb | 15 ++++++++------- lib/ci/api/entities.rb | 9 +++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index abdf8c76447..47dedef38d0 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -79,13 +79,14 @@ module Ci after_transition any => [:success] do |build| if build.environment.present? - service = CreateDeploymentService.new(build.project, build.user, - environment: build.environment, - sha: build.sha, - ref: build.ref, - tag: build.tag, - options: build.options[:environment], - variables: variables) + service = CreateDeploymentService.new( + build.project, build.user, + environment: build.environment, + sha: build.sha, + ref: build.ref, + tag: build.tag, + options: build.options[:environment], + variables: variables) service.execute(build) end end diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index 3f5bdaba3f5..66c05773b68 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -15,6 +15,15 @@ module Ci expose :filename, :size end + class BuildOptions < Grape::Entity + expose :image + expose :services + expose :artifacts + expose :cache + expose :dependencies + expose :after_script + end + class Build < Grape::Entity expose :id, :ref, :tag, :sha, :status expose :name, :token, :stage -- cgit v1.2.3 From e1b3ab5af290f6d2eeb56c4b72e341324414a6d2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 22:32:11 +0200 Subject: Verify expandability of variables defined as part of environment --- app/services/create_deployment_service.rb | 4 +- lib/expand_variables.rb | 22 ++++---- spec/lib/expand_variables_spec.rb | 73 +++++++++++++++++++++++++ spec/models/environment_spec.rb | 16 ++++++ spec/services/create_deployment_service_spec.rb | 32 ++++++++++- 5 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 spec/lib/expand_variables_spec.rb diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index efa0798f47d..577ba731583 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -23,13 +23,13 @@ class CreateDeploymentService < BaseService private def expanded_name - name.expand_variables(variables) + ExpandVariables.expand(name, variables) end def expanded_url return unless url - @expanded_url ||= url.expand_variables(variables) + @expanded_url ||= ExpandVariables.expand(url, variables) end def name diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb index 40bf9483eb5..669735cc56c 100644 --- a/lib/expand_variables.rb +++ b/lib/expand_variables.rb @@ -1,15 +1,17 @@ -String.instance_eval - def expand_variables(variables) - # Convert hash array to variables - if variables.is_a?(Array) - variables = variables.reduce({}) do |hash, variable| - hash[variable[:key]] = variable[:value] - hash +module ExpandVariables + class << self + def expand_variables(value, variables) + # Convert hash array to variables + if variables.is_a?(Array) + variables = variables.reduce({}) do |hash, variable| + hash[variable[:key]] = variable[:value] + hash + end end - end - self.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do - variables[$1 || $2] + value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do + variables[$1 || $2] + end end end end diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb new file mode 100644 index 00000000000..90bc7dad379 --- /dev/null +++ b/spec/lib/expand_variables_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe ExpandVariables do + describe '#expand' do + subject { described_class.expand(value, variables) } + + tests = [ + { value: 'key', + result: 'key', + variables: [] + }, + { value: 'key$variable', + result: 'key', + variables: [] + }, + { value: 'key$variable', + result: 'keyvalue', + variables: [ + { key: 'variable', value: 'value' } + ] + }, + { value: 'key${variable}', + result: 'keyvalue', + variables: [ + { key: 'variable', value: 'value' } + ] + }, + { value: 'key$variable$variable2', + result: 'keyvalueresult', + variables: [ + { key: 'variable', value: 'value' }, + { key: 'variable2', value: 'result' }, + ] + }, + { value: 'key${variable}${variable2}', + result: 'keyvalueresult', + variables: [ + { key: 'variable', value: 'value' }, + { key: 'variable2', value: 'result' } + ] + }, + { value: 'key$variable2$variable', + result: 'keyresultvalue', + variables: [ + { key: 'variable', value: 'value' }, + { key: 'variable2', value: 'result' }, + ] + }, + { value: 'key${variable2}${variable}', + result: 'keyresultvalue', + variables: [ + { key: 'variable', value: 'value' }, + { key: 'variable2', value: 'result' } + ] + }, + { value: 'review/$CI_BUILD_REF_NAME', + result: 'review/feature/add-review-apps', + variables: [ + { key: 'CI_BUILD_REF_NAME', value: 'feature/add-review-apps' } + ] + }, + ] + + tests.each do |test| + context "#{test[:value]} resolves to #{test[:result]}" do + let(:value) { test[:value] } + let(:variables) { test[:variables] } + + it { is_expected.to eq(test[:result]) } + end + end + end +end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index c881897926e..7afc7ec5ca1 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -63,4 +63,20 @@ describe Environment, models: true do end end end + + describe '#environment_type' do + subject { environment.environment_type } + + it 'sets a environment type if name has multiple segments' do + environment.update(name: 'production/worker.gitlab.com') + + is_expected.to eq('production') + end + + it 'nullifies a type if it\'s a simple name' do + environment.update(name: 'production') + + is_expected.to be_nil + end + end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 8da2a2b3c1b..c8c741c0e88 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -56,8 +56,38 @@ describe CreateDeploymentService, services: true do expect(subject).not_to be_persisted end end + + context 'when variables are used' do + let(:params) do + { environment: 'review-apps/$CI_BUILD_REF_NAME', + ref: 'master', + tag: false, + sha: '97de212e80737a608d939f648d959671fb0a0142', + options: { + environment: { + name: 'review-apps/$CI_BUILD_REF_NAME', + url: 'http://$CI_BUILD_REF_NAME.review-apps.gitlab.com' + } + }, + variables: [ + { key: 'CI_BUILD_REF_NAME', value: 'feature-review-apps' } + ] + } + end + + it 'does create a new environment' do + expect { subject }.to change { Environment.count }.by(1) + + expect(subject.environment.name).to eq('review-apps/feature-review-apps') + expect(subject.environment.external_url).to eq('http://feature-review-apps.review-apps.gitlab.com') + end + + it 'does create a new deployment' do + expect(subject).not_to be_persisted + end + end end - + describe 'processing of builds' do let(:environment) { nil } -- cgit v1.2.3 From 6b979687459ad1ab5f1953bf451ee80fdb899b96 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 23:00:15 +0200 Subject: Update support for dynamic environments --- app/models/ci/build.rb | 2 +- app/services/create_deployment_service.rb | 16 ++++++++-------- lib/expand_variables.rb | 2 +- lib/gitlab/ci/config/node/environment.rb | 16 ++++++++++------ lib/gitlab/ci/config/node/job.rb | 10 +--------- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 14 ++++++++++++++ spec/services/create_deployment_service_spec.rb | 21 ++++++++++++++------- 7 files changed, 49 insertions(+), 32 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 47dedef38d0..d5724af4cce 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -86,7 +86,7 @@ module Ci ref: build.ref, tag: build.tag, options: build.options[:environment], - variables: variables) + variables: build.variables) service.execute(build) end end diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb index 577ba731583..e6667132e27 100644 --- a/app/services/create_deployment_service.rb +++ b/app/services/create_deployment_service.rb @@ -2,13 +2,7 @@ require_relative 'base_service' class CreateDeploymentService < BaseService def execute(deployable = nil) - environment = project.environments.find_or_create_by( - name: expanded_name - ) - - if expanded_url - environment.external_url = expanded_url - end + environment = find_or_create_environment project.deployments.create( environment: environment, @@ -22,6 +16,12 @@ class CreateDeploymentService < BaseService private + def find_or_create_environment + project.environments.find_or_create_by(name: expanded_name) do |environment| + environment.external_url = expanded_url + end + end + def expanded_name ExpandVariables.expand(name, variables) end @@ -41,7 +41,7 @@ class CreateDeploymentService < BaseService end def options - params[:environment] || {} + params[:options] || {} end def variables diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb index 669735cc56c..7b1533d0d32 100644 --- a/lib/expand_variables.rb +++ b/lib/expand_variables.rb @@ -1,6 +1,6 @@ module ExpandVariables class << self - def expand_variables(value, variables) + def expand(value, variables) # Convert hash array to variables if variables.is_a?(Array) variables = variables.reduce({}) do |hash, variable| diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index 629c17e6250..85302589ce6 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -8,7 +8,11 @@ module Gitlab class Environment < Entry include Validatable + ALLOWED_KEYS = %i[name url] + validations do + validates :config, allowed_keys: ALLOWED_KEYS, if: :hash? + validates :name, presence: true validates :url, @@ -32,9 +36,9 @@ module Gitlab end def name - case - when string? then @config - when hash? then @config[:name] + case @config.type + when String then @config + when Hash then @config[:name] end end @@ -43,9 +47,9 @@ module Gitlab end def value - case - when string? then { name: @config } - when hash? then @config + case @config.type + when String then { name: @config } + when Hash then @config end end end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index e90e80171a4..3ab34d23d37 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -29,14 +29,6 @@ module Gitlab inclusion: { in: %w[on_success on_failure always manual], message: 'should be on_success, on_failure, ' \ 'always or manual' } - validates :environment, - type: { - with: String, - message: Gitlab::Regex.environment_name_regex_message } - validates :environment, - format: { - with: Gitlab::Regex.environment_name_regex, - message: Gitlab::Regex.environment_name_regex_message } validates :dependencies, array_of_strings: true end @@ -83,7 +75,7 @@ module Gitlab helpers :before_script, :script, :stage, :type, :after_script, :cache, :image, :services, :only, :except, :variables, - :artifacts, :commands + :artifacts, :commands, :environment def compose!(deps = nil) super do diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index af192664b33..2ad33007b8a 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -754,6 +754,20 @@ module Ci it 'does return production' do expect(builds.size).to eq(1) expect(builds.first[:environment]).to eq(environment) + expect(builds.first[:options]).to include(environment: { name: environment }) + end + end + + context 'when hash is specified' do + let(:environment) do + { name: 'production', + url: 'http://production.gitlab.com' } + end + + it 'does return production and URL' do + expect(builds.size).to eq(1) + expect(builds.first[:environment]).to eq(environment[:name]) + expect(builds.first[:options]).to include(environment) end end diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index c8c741c0e88..7ebfddc45d5 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -41,7 +41,7 @@ describe CreateDeploymentService, services: true do context 'for environment with invalid name' do let(:params) do - { environment: 'name with spaces', + { environment: '..', ref: 'master', tag: false, sha: '97de212e80737a608d939f648d959671fb0a0142', @@ -64,10 +64,8 @@ describe CreateDeploymentService, services: true do tag: false, sha: '97de212e80737a608d939f648d959671fb0a0142', options: { - environment: { - name: 'review-apps/$CI_BUILD_REF_NAME', - url: 'http://$CI_BUILD_REF_NAME.review-apps.gitlab.com' - } + name: 'review-apps/$CI_BUILD_REF_NAME', + url: 'http://$CI_BUILD_REF_NAME.review-apps.gitlab.com' }, variables: [ { key: 'CI_BUILD_REF_NAME', value: 'feature-review-apps' } @@ -83,7 +81,7 @@ describe CreateDeploymentService, services: true do end it 'does create a new deployment' do - expect(subject).not_to be_persisted + expect(subject).to be_persisted end end end @@ -125,6 +123,12 @@ describe CreateDeploymentService, services: true do expect(Deployment.last.deployable).to eq(deployable) end + + it 'create environment has URL set' do + subject + + expect(Deployment.last.environment.external_url).not_to be_nil + end end context 'without environment specified' do @@ -137,7 +141,10 @@ describe CreateDeploymentService, services: true do context 'when environment is specified' do let(:pipeline) { create(:ci_pipeline, project: project) } - let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production') } + let(:build) { create(:ci_build, pipeline: pipeline, environment: 'production', options: options) } + let(:options) do + { environment: { name: 'production', url: 'http://gitlab.com' } } + end context 'when build succeeds' do it_behaves_like 'does create environment and deployment' do -- cgit v1.2.3 From 274d3d50e5d24bef18098ee16464873e17fa010a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 23:27:54 +0200 Subject: Added missing db/schema changes --- db/schema.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index 974f5855cf9..930d41d8bdd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -390,10 +390,11 @@ ActiveRecord::Schema.define(version: 20160913212128) do create_table "environments", force: :cascade do |t| t.integer "project_id" - t.string "name", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" t.string "external_url" + t.string "environment_type" end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree -- cgit v1.2.3 From 4a5c21728ee4e6c3ef8e1c410ee0f0c9a47634cc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 14 Sep 2016 23:33:10 +0200 Subject: Added documentation about dynamic environments --- CHANGELOG | 2 ++ doc/ci/yaml/README.md | 29 ++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e9445a18a18..3da548ef2af 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,8 @@ v 8.12.0 (unreleased) - Fix sorting of issues in API - Sort project variables by key. !6275 (Diego Souza) - Ensure specs on sorting of issues in API are deterministic on MySQL + - Added ability to use predefined CI variables for environment name + - Added ability to specify URL in environment configuration in gitlab-ci.yml - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 - Fix file permissions change when updating a file on the Gitlab UI !5979 diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index ff4c8ddc54b..4772565fac9 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -553,7 +553,7 @@ GitLab. If `environment` is specified and no environment under that name exists, a new one will be created automatically. -The `environment` name must contain only letters, digits, '-' and '_'. Common +The `environment` name must be a valid git reference name. Common names are `qa`, `staging`, and `production`, but you can use whatever name works with your workflow. @@ -571,6 +571,33 @@ deploy to production: The `deploy to production` job will be marked as doing deployment to `production` environment. +#### dynamic environments + +>**Note:** +Introduced in GitLab 8.12. + +`environment` can also represent a configuration hash with `name` and `url`. +These parameters can use any of defined CI variables (including predefined, secure variables and .gitlab-ci.yml variables). + +The common use case is to create a dynamic environments for branches and use them as review apps. + +--- + +**Example configurations** + +``` +deploy as review app: + stage: deploy + script: ... + environment: + name: review-apps/$CI_BUILD_REF_NAME + url: https://$CI_BUILD_REF_NAME.review.example.com/ +``` + +The `deploy to production` job will be marked as doing deployment to +`production` environment. + + ### artifacts >**Notes:** -- cgit v1.2.3 From abfceb1e565490bb75e9a4fba0571cb2390fa5d8 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Sep 2016 21:59:01 +0200 Subject: Cleanup changes --- app/models/merge_request.rb | 13 +------------ ...907131111_add_environment_type_to_environments.rb | 20 -------------------- doc/ci/yaml/README.md | 9 +++++---- lib/gitlab/ci/config/node/environment.rb | 7 ++----- 4 files changed, 8 insertions(+), 41 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b215f02e4b7..75f48fd4ba5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -744,21 +744,10 @@ class MergeRequest < ActiveRecord::Base @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end - def all_commits_sha - merge_request_diffs.map(&:commits).flatten.map(&:sha).sort.uniq - end - - def latest_pipelines - @latest_pipelines ||= - if diff_head_sha && source_project - source_project.pipelines.order(id: :desc).where(sha: commits_sha, ref: source_branch) - end - end - def all_pipelines @all_pipelines ||= if diff_head_sha && source_project - source_project.pipelines.order(id: :desc).where(sha: all_commits_sha, ref: source_branch) + source_project.pipelines.order(id: :desc).where(sha: commits_sha, ref: source_branch) end end diff --git a/db/migrate/20160907131111_add_environment_type_to_environments.rb b/db/migrate/20160907131111_add_environment_type_to_environments.rb index d22b3f4d2d1..fac73753d5b 100644 --- a/db/migrate/20160907131111_add_environment_type_to_environments.rb +++ b/db/migrate/20160907131111_add_environment_type_to_environments.rb @@ -1,28 +1,8 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - class AddEnvironmentTypeToEnvironments < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - # Set this constant to true if this migration requires downtime. DOWNTIME = false - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def change add_column :environments, :environment_type, :string end diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 4772565fac9..a4cf0ec468a 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -577,9 +577,9 @@ The `deploy to production` job will be marked as doing deployment to Introduced in GitLab 8.12. `environment` can also represent a configuration hash with `name` and `url`. -These parameters can use any of defined CI variables (including predefined, secure variables and .gitlab-ci.yml variables). +These parameters can use any of the defined CI variables (including predefined, secure variables and `.gitlab-ci.yml` variables). -The common use case is to create a dynamic environments for branches and use them as review apps. +The common use case is to create dynamic environments for branches and use them as review apps. --- @@ -594,9 +594,10 @@ deploy as review app: url: https://$CI_BUILD_REF_NAME.review.example.com/ ``` -The `deploy to production` job will be marked as doing deployment to -`production` environment. +The `deploy as review app` job will be marked as deployment to +dynamically created `review-apps/branch-name` environment. +This environment should be accessible under `https://branch-name.review.example.com/`. ### artifacts diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index 85302589ce6..580fcda7549 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -36,14 +36,11 @@ module Gitlab end def name - case @config.type - when String then @config - when Hash then @config[:name] - end + value[:name] end def url - @config[:url] if hash? + value[:url] end def value -- cgit v1.2.3 From 04b56955906f3fa4a5a41912d1b1234e45c392c6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 16 Sep 2016 09:53:08 +0200 Subject: Small refactor of review apps docs --- doc/ci/yaml/README.md | 66 ++++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index a4cf0ec468a..f65340f190e 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -90,8 +90,7 @@ builds, including deploy builds. This can be an array or a multi-line string. ### after_script ->**Note:** -Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 +> Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 `after_script` is used to define the command that will be run after for all builds. This has to be an array or a multi-line string. @@ -135,8 +134,7 @@ Alias for [stages](#stages). ### variables ->**Note:** -Introduced in GitLab Runner v0.5.0. +> Introduced in GitLab Runner v0.5.0. GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the build environment. The variables are stored in the Git repository and are meant @@ -158,8 +156,7 @@ Variables can be also defined on [job level](#job-variables). ### cache ->**Note:** -Introduced in GitLab Runner v0.7.0. +> Introduced in GitLab Runner v0.7.0. `cache` is used to specify a list of files and directories which should be cached between builds. @@ -220,8 +217,7 @@ will be always present. For implementation details, please check GitLab Runner. #### cache:key ->**Note:** -Introduced in GitLab Runner v1.0.0. +> Introduced in GitLab Runner v1.0.0. The `key` directive allows you to define the affinity of caching between jobs, allowing to have a single cache for all jobs, @@ -531,8 +527,7 @@ The above script will: #### Manual actions ->**Note:** -Introduced in GitLab 8.10. +> Introduced in GitLab 8.10. Manual actions are a special type of job that are not executed automatically; they need to be explicitly started by a user. Manual actions can be started @@ -543,17 +538,16 @@ An example usage of manual actions is deployment to production. ### environment ->**Note:** -Introduced in GitLab 8.9. +> Introduced in GitLab 8.9. -`environment` is used to define that a job deploys to a specific environment. +`environment` is used to define that a job deploys to a specific [environment]. This allows easy tracking of all deployments to your environments straight from GitLab. If `environment` is specified and no environment under that name exists, a new one will be created automatically. -The `environment` name must be a valid git reference name. Common +The `environment` name must be a valid [Git reference name][gitref]. Common names are `qa`, `staging`, and `production`, but you can use whatever name works with your workflow. @@ -573,13 +567,14 @@ The `deploy to production` job will be marked as doing deployment to #### dynamic environments ->**Note:** -Introduced in GitLab 8.12. +> [Introduced][ce-6323] in GitLab 8.12 and GitLab Runner 1.6. `environment` can also represent a configuration hash with `name` and `url`. -These parameters can use any of the defined CI variables (including predefined, secure variables and `.gitlab-ci.yml` variables). +These parameters can use any of the defined CI [variables](#variables) +(including predefined, secure variables and `.gitlab-ci.yml` variables). -The common use case is to create dynamic environments for branches and use them as review apps. +The common use case is to create dynamic environments for branches and use them +as review apps. --- @@ -589,13 +584,13 @@ The common use case is to create dynamic environments for branches and use them deploy as review app: stage: deploy script: ... - environment: + environment: name: review-apps/$CI_BUILD_REF_NAME url: https://$CI_BUILD_REF_NAME.review.example.com/ ``` -The `deploy as review app` job will be marked as deployment to -dynamically created `review-apps/branch-name` environment. +The `deploy as review app` job will be marked as deployment to dynamically +create the `review-apps/branch-name` environment. This environment should be accessible under `https://branch-name.review.example.com/`. @@ -666,8 +661,7 @@ be available for download in the GitLab UI. #### artifacts:name ->**Note:** -Introduced in GitLab 8.6 and GitLab Runner v1.1.0. +> Introduced in GitLab 8.6 and GitLab Runner v1.1.0. The `name` directive allows you to define the name of the created artifacts archive. That way, you can have a unique name for every archive which could be @@ -730,8 +724,7 @@ job: #### artifacts:when ->**Note:** -Introduced in GitLab 8.9 and GitLab Runner v1.3.0. +> Introduced in GitLab 8.9 and GitLab Runner v1.3.0. `artifacts:when` is used to upload artifacts on build failure or despite the failure. @@ -756,8 +749,7 @@ job: #### artifacts:expire_in ->**Note:** -Introduced in GitLab 8.9 and GitLab Runner v1.3.0. +> Introduced in GitLab 8.9 and GitLab Runner v1.3.0. `artifacts:expire_in` is used to delete uploaded artifacts after the specified time. By default, artifacts are stored on GitLab forever. `expire_in` allows you @@ -792,8 +784,7 @@ job: ### dependencies ->**Note:** -Introduced in GitLab 8.6 and GitLab Runner v1.1.1. +> Introduced in GitLab 8.6 and GitLab Runner v1.1.1. This feature should be used in conjunction with [`artifacts`](#artifacts) and allows you to define the artifacts to pass between different builds. @@ -867,9 +858,8 @@ job: ## Git Strategy ->**Note:** -Introduced in GitLab 8.9 as an experimental feature. May change in future -releases or be removed completely. +> Introduced in GitLab 8.9 as an experimental feature. May change in future + releases or be removed completely. You can set the `GIT_STRATEGY` used for getting recent application code. `clone` is slower, but makes sure you have a clean directory before every build. `fetch` @@ -891,8 +881,7 @@ variables: ## Shallow cloning ->**Note:** -Introduced in GitLab 8.9 as an experimental feature. May change in future +> Introduced in GitLab 8.9 as an experimental feature. May change in future releases or be removed completely. You can specify the depth of fetching and cloning using `GIT_DEPTH`. This allows @@ -922,8 +911,7 @@ variables: ## Hidden keys ->**Note:** -Introduced in GitLab 8.6 and GitLab Runner v1.1.1. +> Introduced in GitLab 8.6 and GitLab Runner v1.1.1. Keys that start with a dot (`.`) will be not processed by GitLab CI. You can use this feature to ignore jobs, or use the @@ -951,8 +939,7 @@ Read more about the various [YAML features](https://learnxinyminutes.com/docs/ya ### Anchors ->**Note:** -Introduced in GitLab 8.6 and GitLab Runner v1.1.1. +> Introduced in GitLab 8.6 and GitLab Runner v1.1.1. YAML also has a handy feature called 'anchors', which let you easily duplicate content across your document. Anchors can be used to duplicate/inherit @@ -1095,3 +1082,6 @@ Visit the [examples README][examples] to see a list of examples using GitLab CI with various languages. [examples]: ../examples/README.md +[ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323 +[gitref]: https://git-scm.com/docs/git-check-ref-format +[environment]: ../environments.md -- cgit v1.2.3 From 3fbfc30f5e131e950564712d6f0d1837cd7605e3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 16 Sep 2016 11:00:13 +0200 Subject: Fix CI job environment configuration entry class --- lib/gitlab/ci/config/node/environment.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index 580fcda7549..d2d00f0ad81 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -11,20 +11,22 @@ module Gitlab ALLOWED_KEYS = %i[name url] validations do - validates :config, allowed_keys: ALLOWED_KEYS, if: :hash? - validates :name, presence: true - validates :url, - length: { maximum: 255 }, - allow_nil: true, - addressable_url: true - validate do unless hash? || string? errors.add(:config, 'should be a hash or a string') end end + + with_options if: :hash? do + validates :config, allowed_keys: ALLOWED_KEYS + + validates :url, + length: { maximum: 255 }, + addressable_url: true, + allow_nil: true + end end def hash? @@ -44,9 +46,10 @@ module Gitlab end def value - case @config.type + case @config when String then { name: @config } when Hash then @config + else {} end end end -- cgit v1.2.3 From 2ad7753d34a963abfcadfa42e5d5b4cd59afc221 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 16 Sep 2016 11:25:37 +0200 Subject: Fix CI job environment configuration attributes --- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- lib/gitlab/ci/config/node/environment.rb | 4 ++-- lib/gitlab/ci/config/node/job.rb | 1 + spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 5 +++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 94a63508f79..0369e80312a 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -60,7 +60,7 @@ module Ci name: job[:name].to_s, allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', - environment: job.fetch(:environment, {})[:name], + environment: job[:environment_name], yaml_variables: yaml_variables(name), options: { image: job[:image], diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index d2d00f0ad81..bc153854a8d 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -11,14 +11,14 @@ module Gitlab ALLOWED_KEYS = %i[name url] validations do - validates :name, presence: true - validate do unless hash? || string? errors.add(:config, 'should be a hash or a string') end end + validates :name, presence: true + with_options if: :hash? do validates :config, allowed_keys: ALLOWED_KEYS diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 3ab34d23d37..6ecc46200c0 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -129,6 +129,7 @@ module Gitlab except: except, variables: variables_defined? ? variables : nil, environment: environment_defined? ? environment : nil, + environment_name: environment_defined? ? environment[:name] : nil, artifacts: artifacts, after_script: after_script } end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 2ad33007b8a..c139ef36a9a 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -767,7 +767,7 @@ module Ci it 'does return production and URL' do expect(builds.size).to eq(1) expect(builds.first[:environment]).to eq(environment[:name]) - expect(builds.first[:options]).to include(environment) + expect(builds.first[:options]).to include(environment: environment) end end @@ -784,7 +784,8 @@ module Ci let(:environment) { 1 } it 'raises error' do - expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}") + expect { builds }.to raise_error( + 'jobs:deploy_to_production:environment config should be a hash or a string') end end -- cgit v1.2.3 From 99f1385ee02a368e8fa7cc0bcaad78b904d1a81d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 16 Sep 2016 11:38:56 +0200 Subject: Restore validation of CI job environment name --- lib/gitlab/ci/config/node/environment.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb index bc153854a8d..d388ab6b879 100644 --- a/lib/gitlab/ci/config/node/environment.rb +++ b/lib/gitlab/ci/config/node/environment.rb @@ -18,6 +18,15 @@ module Gitlab end validates :name, presence: true + validates :name, + type: { + with: String, + message: Gitlab::Regex.environment_name_regex_message } + + validates :name, + format: { + with: Gitlab::Regex.environment_name_regex, + message: Gitlab::Regex.environment_name_regex_message } with_options if: :hash? do validates :config, allowed_keys: ALLOWED_KEYS -- cgit v1.2.3 From 223041fa1bba534d613489f41d6143f1785fd0b4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 18 Sep 2016 20:31:00 +0200 Subject: Fix environments handling --- doc/ci/yaml/README.md | 3 +-- lib/gitlab/regex.rb | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index f65340f190e..16868554c1f 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -547,7 +547,7 @@ GitLab. If `environment` is specified and no environment under that name exists, a new one will be created automatically. -The `environment` name must be a valid [Git reference name][gitref]. Common +The `environment` name must contain only letters, digits, '-', '_', '/', '$', '{', '}' and spaces. Common names are `qa`, `staging`, and `production`, but you can use whatever name works with your workflow. @@ -1083,5 +1083,4 @@ CI with various languages. [examples]: ../examples/README.md [ce-6323]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6323 -[gitref]: https://git-scm.com/docs/git-check-ref-format [environment]: ../environments.md diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index d1a3e54ccd7..4efd9ae2c1e 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -96,11 +96,11 @@ module Gitlab end def environment_name_regex - git_reference_regex + @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${} -]+\z/.freeze end def environment_name_regex_message - "be a valid git reference name" + "can contain only letters, digits, '-', '_', '/', '$', '{', '}' and spaces" end end end -- cgit v1.2.3 From 31e8721a44ceddc4d3578f3af2b6c7a1797be35b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 19 Sep 2016 08:49:48 +0200 Subject: Fix scope of the CI config key nodes in jobs entry --- lib/gitlab/ci/config/node/job.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 6ecc46200c0..603334d6793 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -34,43 +34,43 @@ module Gitlab end end - node :before_script, Script, + node :before_script, Node::Script, description: 'Global before script overridden in this job.' - node :script, Commands, + node :script, Node::Commands, description: 'Commands that will be executed in this job.' - node :stage, Stage, + node :stage, Node::Stage, description: 'Pipeline stage this job will be executed into.' - node :type, Stage, + node :type, Node::Stage, description: 'Deprecated: stage this job will be executed into.' - node :after_script, Script, + node :after_script, Node::Script, description: 'Commands that will be executed when finishing job.' - node :cache, Cache, + node :cache, Node::Cache, description: 'Cache definition for this job.' - node :image, Image, + node :image, Node::Image, description: 'Image that will be used to execute this job.' - node :services, Services, + node :services, Node::Services, description: 'Services that will be used to execute this job.' - node :only, Trigger, + node :only, Node::Trigger, description: 'Refs policy this job will be executed for.' - node :except, Trigger, + node :except, Node::Trigger, description: 'Refs policy this job will be executed for.' - node :variables, Variables, + node :variables, Node::Variables, description: 'Environment variables available for this job.' - node :artifacts, Artifacts, + node :artifacts, Node::Artifacts, description: 'Artifacts configuration for this job.' - node :environment, Environment, + node :environment, Node::Environment, description: 'Environment configuration for this job.' helpers :before_script, :script, :stage, :type, :after_script, -- cgit v1.2.3 From 8fe05d83ac259bf8a0fa4ca344d330a1c0cea8bb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 09:01:04 +0200 Subject: Fix validation regexs (+1 squashed commit) Squashed commits: [f9a9315] Use : to test invalid environment name --- lib/gitlab/regex.rb | 4 ++-- spec/features/environments_spec.rb | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 ++-- spec/models/environment_spec.rb | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 4efd9ae2c1e..bc8bbf337f3 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -96,11 +96,11 @@ module Gitlab end def environment_name_regex - @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${} -]+\z/.freeze + @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${}. -]+\z/.freeze end def environment_name_regex_message - "can contain only letters, digits, '-', '_', '/', '$', '{', '}' and spaces" + "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.' and spaces" end end end diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index fcd41b38413..4309a726917 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -150,7 +150,7 @@ feature 'Environments', feature: true do context 'for invalid name' do before do - fill_in('Name', with: 'name with spaces') + fill_in('Name', with: 'name,with,commas') click_on 'Save' end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index c139ef36a9a..6dedd25e9d3 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -790,10 +790,10 @@ module Ci end context 'is not a valid string' do - let(:environment) { 'production staging' } + let(:environment) { 'production:staging' } it 'raises error' do - expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}") + expect { builds }.to raise_error("jobs:deploy_to_production:environment name #{Gitlab::Regex.environment_name_regex_message}") end end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 7afc7ec5ca1..6b1867a44e1 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -68,13 +68,13 @@ describe Environment, models: true do subject { environment.environment_type } it 'sets a environment type if name has multiple segments' do - environment.update(name: 'production/worker.gitlab.com') + environment.update!(name: 'production/worker.gitlab.com') is_expected.to eq('production') end it 'nullifies a type if it\'s a simple name' do - environment.update(name: 'production') + environment.update!(name: 'production') is_expected.to be_nil end -- cgit v1.2.3 From 58d02520b037255f9948de4386ab6cd970586445 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Mon, 12 Sep 2016 20:14:26 +0100 Subject: Ensure validation messages are shown within the milestone form * Remove call to Milestone#save! instead just Milestone#save * Add safety specs to stop a regression Fixes #22033 --- CHANGELOG | 1 + app/services/milestones/create_service.rb | 2 +- spec/features/milestone_spec.rb | 21 ++++++++++++++++++--- spec/requests/api/milestones_spec.rb | 23 +++++++++++++++++++++-- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e9445a18a18..2394864467e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -139,6 +139,7 @@ v 8.12.0 (unreleased) - Use default clone protocol on "check out, review, and merge locally" help page URL - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - Allow bulk update merge requests from merge requests index page + - Ensure validation messages are shown within the milestone form - Add notification_settings API calls !5632 (mahcsig) - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) - Fix URLs with anchors in wiki !6300 (houqp) diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb index 3b90399af64..b8e08c9f1eb 100644 --- a/app/services/milestones/create_service.rb +++ b/app/services/milestones/create_service.rb @@ -3,7 +3,7 @@ module Milestones def execute milestone = project.milestones.new(params) - if milestone.save! + if milestone.save event_service.open_milestone(milestone, current_user) end diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index c43661e5681..b8c838bf7ab 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -3,9 +3,8 @@ require 'rails_helper' feature 'Milestone', feature: true do include WaitForAjax - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:user) { create(:user) } - let(:milestone) { create(:milestone, project: project, title: 8.7) } before do project.team << [user, :master] @@ -13,7 +12,7 @@ feature 'Milestone', feature: true do end feature 'Create a milestone' do - scenario 'shows an informative message for a new issue' do + scenario 'shows an informative message for a new milestone' do visit new_namespace_project_milestone_path(project.namespace, project) page.within '.milestone-form' do fill_in "milestone_title", with: '8.7' @@ -26,10 +25,26 @@ feature 'Milestone', feature: true do feature 'Open a milestone with closed issues' do scenario 'shows an informative message' do + milestone = create(:milestone, project: project, title: 8.7) + create(:issue, title: "Bugfix1", project: project, milestone: milestone, state: "closed") visit namespace_project_milestone_path(project.namespace, project, milestone) expect(find('.alert-success')).to have_content('All issues for this milestone are closed. You may close this milestone now.') end end + + feature 'Open a milestone with an existing title' do + scenario 'displays validation message' do + milestone = create(:milestone, project: project, title: 8.7) + + visit new_namespace_project_milestone_path(project.namespace, project) + page.within '.milestone-form' do + fill_in "milestone_title", with: milestone.title + end + find('input[name="commit"]').click + + expect(find('.alert-danger')).to have_content('Title has already been taken') + end + end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index d6a0c656e74..b89dac01040 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace ) } + let!(:project) { create(:empty_project, namespace: user.namespace ) } let!(:closed_milestone) { create(:closed_milestone, project: project) } let!(:milestone) { create(:milestone, project: project) } @@ -12,6 +12,7 @@ describe API::API, api: true do describe 'GET /projects/:id/milestones' do it 'returns project milestones' do get api("/projects/#{project.id}/milestones", user) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['title']).to eq(milestone.title) @@ -19,6 +20,7 @@ describe API::API, api: true do it 'returns a 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones") + expect(response).to have_http_status(401) end @@ -44,6 +46,7 @@ describe API::API, api: true do describe 'GET /projects/:id/milestones/:milestone_id' do it 'returns a project milestone by id' do get api("/projects/#{project.id}/milestones/#{milestone.id}", user) + expect(response).to have_http_status(200) expect(json_response['title']).to eq(milestone.title) expect(json_response['iid']).to eq(milestone.iid) @@ -60,11 +63,13 @@ describe API::API, api: true do it 'returns 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}") + expect(response).to have_http_status(401) end it 'returns a 404 error if milestone id not found' do get api("/projects/#{project.id}/milestones/1234", user) + expect(response).to have_http_status(404) end end @@ -72,6 +77,7 @@ describe API::API, api: true do describe 'POST /projects/:id/milestones' do it 'creates a new project milestone' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone' + expect(response).to have_http_status(201) expect(json_response['title']).to eq('new milestone') expect(json_response['description']).to be_nil @@ -80,6 +86,7 @@ describe API::API, api: true do it 'creates a new project milestone with description and due date' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone', description: 'release', due_date: '2013-03-02' + expect(response).to have_http_status(201) expect(json_response['description']).to eq('release') expect(json_response['due_date']).to eq('2013-03-02') @@ -87,6 +94,14 @@ describe API::API, api: true do it 'returns a 400 error if title is missing' do post api("/projects/#{project.id}/milestones", user) + + expect(response).to have_http_status(400) + end + + it 'returns a 400 error if params are invalid (duplicate title)' do + post api("/projects/#{project.id}/milestones", user), + title: milestone.title, description: 'release', due_date: '2013-03-02' + expect(response).to have_http_status(400) end end @@ -95,6 +110,7 @@ describe API::API, api: true do it 'updates a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), title: 'updated title' + expect(response).to have_http_status(200) expect(json_response['title']).to eq('updated title') end @@ -102,6 +118,7 @@ describe API::API, api: true do it 'returns a 404 error if milestone id not found' do put api("/projects/#{project.id}/milestones/1234", user), title: 'updated title' + expect(response).to have_http_status(404) end end @@ -131,6 +148,7 @@ describe API::API, api: true do end it 'returns project issues for a particular milestone' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) + expect(response).to have_http_status(200) expect(json_response).to be_an Array expect(json_response.first['milestone']['title']).to eq(milestone.title) @@ -138,11 +156,12 @@ describe API::API, api: true do it 'returns a 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") + expect(response).to have_http_status(401) end describe 'confidential issues' do - let(:public_project) { create(:project, :public) } + let(:public_project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: public_project) } let(:issue) { create(:issue, project: public_project) } let(:confidential_issue) { create(:issue, confidential: true, project: public_project) } -- cgit v1.2.3 From 2c3b75a28c90c4bf51a3e5d4db02b44cf2480791 Mon Sep 17 00:00:00 2001 From: winniehell Date: Thu, 25 Aug 2016 22:48:17 +0200 Subject: Add linting for duplicate CHANGELOG versions (!6039) --- scripts/lint-doc.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index bc6e4d94061..fb4d8463981 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -10,6 +10,15 @@ then exit 1 fi +# Ensure that the CHANGELOG does not contain duplicate versions +DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^v [0-9.]+' CHANGELOG | sed 's| (unreleased)||' | sort | uniq -d) +if [ "${DUPLICATE_CHANGELOG_VERSIONS}" != "" ] +then + echo '✖ ERROR: Duplicate versions in CHANGELOG:' >&2 + echo "${DUPLICATE_CHANGELOG_VERSIONS}" >&2 + exit 1 +fi + echo "✔ Linting passed" exit 0 -- cgit v1.2.3 From 8d53271dd03f26be30c7ebf3ea2e11bb27265185 Mon Sep 17 00:00:00 2001 From: winniehell Date: Thu, 25 Aug 2016 23:20:08 +0200 Subject: Fix old CHANGELOG entries --- CHANGELOG | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e9445a18a18..39f0a9a04be 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -594,6 +594,7 @@ v 8.10.0 - Export and import avatar as part of project import/export - Fix migration corrupting import data for old version upgrades - Show tooltip on GitLab export link in new project page + - Fix import_data wrongly saved as a result of an invalid import_url !5206 v 8.9.9 - Exclude some pending or inactivated rows in Member scopes @@ -614,12 +615,6 @@ v 8.9.6 - Keeps issue number when importing from Gitlab.com - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska) -v 8.9.7 (unreleased) - - Fix import_data wrongly saved as a result of an invalid import_url - -v 8.9.6 - - Fix importing of events under notes for GitLab projects - v 8.9.5 - Add more debug info to import/export and memory killer. !5108 - Fixed avatar alignment in new MR view. !5095 @@ -1885,7 +1880,7 @@ v 8.1.3 - Use issue editor as cross reference comment author when issue is edited with a new mention - Add Facebook authentication -v 8.1.1 +v 8.1.2 - Fix cloning Wiki repositories via HTTP (Stan Hu) - Add migration to remove satellites directory - Fix specific runners visibility -- cgit v1.2.3 From 0ca43b1b86edea69656582b2a8febb0d41f7ef01 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 12:37:46 +0200 Subject: Fix permissions for creating container images --- lib/gitlab/auth.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index f9ae5e4543f..150a4ead45d 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -173,7 +173,7 @@ module Gitlab def full_authentication_abilities read_authentication_abilities + [ :push_code, - :update_container_image + :create_container_image ] end end -- cgit v1.2.3 From b51ededc5fef05f94a632aa7651b5a1f7395bd4e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 12:38:03 +0200 Subject: Don't leak build tokens in build logs --- app/controllers/projects/builds_controller.rb | 6 ++- app/models/ci/build.rb | 16 +++--- lib/ci/mask_secret.rb | 9 ++++ spec/lib/ci/mask_secret_spec.rb | 19 +++++++ spec/models/build_spec.rb | 78 ++++++++++++++++++++++++--- 5 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 lib/ci/mask_secret.rb create mode 100644 spec/lib/ci/mask_secret_spec.rb diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 77934ff9962..9ce5b4de42f 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -35,7 +35,11 @@ class Projects::BuildsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @build.to_json(methods: :trace_html) + render json: { + id: @build.id, + status: @build.status, + trace_html: @build.trace_html + } end end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 57ef4646d24..8a9d7555393 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -241,12 +241,7 @@ module Ci end def trace - trace = raw_trace - if project && trace.present? && project.runners_token.present? - trace.gsub(project.runners_token, 'xxxxxx') - else - trace - end + hide_secrets(raw_trace) end def trace_length @@ -259,6 +254,7 @@ module Ci def trace=(trace) recreate_trace_dir + trace = hide_secrets(trace) File.write(path_to_trace, trace) end @@ -272,6 +268,8 @@ module Ci def append_trace(trace_part, offset) recreate_trace_dir + trace_part = hide_secrets(trace_part) + File.truncate(path_to_trace, offset) if File.exist?(path_to_trace) File.open(path_to_trace, 'ab') do |f| f.write(trace_part) @@ -490,5 +488,11 @@ module Ci pipeline.config_processor.build_attributes(name) end + + def hide_secrets(trace) + trace = Ci::MaskSecret.mask(trace, project.runners_token) if project + trace = Ci::MaskSecret.mask(trace, token) + trace + end end end diff --git a/lib/ci/mask_secret.rb b/lib/ci/mask_secret.rb new file mode 100644 index 00000000000..3da04edde70 --- /dev/null +++ b/lib/ci/mask_secret.rb @@ -0,0 +1,9 @@ +module Ci::MaskSecret + class << self + def mask(value, token) + return value unless value.present? && token.present? + + value.gsub(token, 'x' * token.length) + end + end +end diff --git a/spec/lib/ci/mask_secret_spec.rb b/spec/lib/ci/mask_secret_spec.rb new file mode 100644 index 00000000000..518de76911c --- /dev/null +++ b/spec/lib/ci/mask_secret_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Ci::MaskSecret, lib: true do + subject { described_class } + + describe '#mask' do + it 'masks exact number of characters' do + expect(subject.mask('token', 'oke')).to eq('txxxn') + end + + it 'masks multiple occurrences' do + expect(subject.mask('token token token', 'oke')).to eq('txxxn txxxn txxxn') + end + + it 'does not mask if not found' do + expect(subject.mask('token', 'not')).to eq('token') + end + end +end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 8eab4281bc7..e7864b7ad33 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -88,9 +88,7 @@ describe Ci::Build, models: true do end describe '#trace' do - subject { build.trace_html } - - it { is_expected.to be_empty } + it { expect(build.trace).to be_nil } context 'when build.trace contains text' do let(:text) { 'example output' } @@ -98,16 +96,80 @@ describe Ci::Build, models: true do build.trace = text end - it { is_expected.to include(text) } - it { expect(subject.length).to be >= text.length } + it { expect(build.trace).to eq(text) } + end + + context 'when build.trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.update(trace: token) + build.project.update(runners_token: token) + end + + it { expect(build.trace).not_to include(token) } + it { expect(build.raw_trace).to include(token) } + end + + context 'when build.trace hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(trace: token) + build.update(token: token) + end + + it { expect(build.trace).not_to include(token) } + it { expect(build.raw_trace).to include(token) } + end + end + + describe '#raw_trace' do + subject { build.raw_trace } + + context 'when build.trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.project.update(runners_token: token) + build.update(trace: token) + end + + it { is_expected.not_to include(token) } + end + + context 'when build.trace hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(token: token) + build.update(trace: token) + end + + it { is_expected.not_to include(token) } + end + end + + context '#append_trace' do + subject { build.trace_html } + + context 'when build.trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.project.update(runners_token: token) + build.append_trace(token, 0) + end + + it { is_expected.not_to include(token) } end - context 'when build.trace hides token' do + context 'when build.trace hides build token' do let(:token) { 'my_secret_token' } before do - build.project.update_attributes(runners_token: token) - build.update_attributes(trace: token) + build.update(token: token) + build.append_trace(token, 0) end it { is_expected.not_to include(token) } -- cgit v1.2.3 From 4939911e96297aa6ed9fb60f635d7d16a360876f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 12:44:10 +0200 Subject: Fix specs failures --- spec/services/create_deployment_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index 7ebfddc45d5..41b897f36cd 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -41,7 +41,7 @@ describe CreateDeploymentService, services: true do context 'for environment with invalid name' do let(:params) do - { environment: '..', + { environment: 'name,with,commas', ref: 'master', tag: false, sha: '97de212e80737a608d939f648d959671fb0a0142', -- cgit v1.2.3 From 5790684d1f81ca9fa63a10f3fec6339ef0092627 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 13:11:11 +0200 Subject: Support pushing via SSH --- lib/gitlab/auth.rb | 9 ++++++++- lib/gitlab/lfs_token.rb | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 150a4ead45d..1e0a7ec253a 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -130,7 +130,14 @@ module Gitlab if actor token_handler = Gitlab::LfsToken.new(actor) - Result.new(actor, nil, token_handler.type, read_authentication_abilities) if Devise.secure_compare(token_handler.value, password) + authentication_abilities = + if token_handler.user? + full_authentication_abilities + else + read_authentication_abilities + end + + Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.value, password) end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb index f492754b1c8..d089a2f9b0b 100644 --- a/lib/gitlab/lfs_token.rb +++ b/lib/gitlab/lfs_token.rb @@ -33,6 +33,10 @@ module Gitlab end end + def user? + actor.is_a?(User) + end + def type actor.is_a?(User) ? :lfs_token : :lfs_deploy_token end -- cgit v1.2.3 From 748dd35c65b0a7f3fbb0832fd18933ff8c19ef7d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 13:29:48 +0200 Subject: Fix spec failures --- spec/lib/gitlab/auth_spec.rb | 2 +- spec/models/ci/build_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 1af5abe9f15..21f0d46100e 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -171,7 +171,7 @@ describe Gitlab::Auth, lib: true do def full_authentication_abilities read_authentication_abilities + [ :push_code, - :update_container_image + :create_container_image ] end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index bce18b4e99e..a37a00f461a 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -8,7 +8,7 @@ describe Ci::Build, models: true do it 'obfuscates project runners token' do allow(build).to receive(:raw_trace).and_return("Test: #{build.project.runners_token}") - expect(build.trace).to eq("Test: xxxxxx") + expect(build.trace).to eq("Test: xxxxxxxxxxxxxxxxxxxx") end it 'empty project runners token' do -- cgit v1.2.3 From abcc0ba57027026cb5bb074b8691510a24724958 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 13:40:46 +0200 Subject: Added CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 8eaeae0cb9c..cc54378f9f1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.12.0 (unreleased) - Instructions for enabling Git packfile bitmaps !6104 - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Fix pagination on user snippets page + - Run CI builds with the permissions of users !5735 - Fix sorting of issues in API - Sort project variables by key. !6275 (Diego Souza) - Ensure specs on sorting of issues in API are deterministic on MySQL -- cgit v1.2.3 From 79f60e2b5cf388416cdc5948e19ae0401f97d353 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 13:42:10 +0200 Subject: Move Gitlab::Auth.Result to separate file --- lib/gitlab/auth.rb | 14 -------------- lib/gitlab/auth/result.rb | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 lib/gitlab/auth/result.rb diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 1e0a7ec253a..ca2a0920c00 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,19 +1,5 @@ module Gitlab module Auth - Result = Struct.new(:actor, :project, :type, :authentication_abilities) do - def ci? - type == :ci - end - - def lfs_deploy_token? - type == :lfs_deploy_token - end - - def success? - actor.present? || type == :ci - end - end - class MissingPersonalTokenError < StandardError; end class << self diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb new file mode 100644 index 00000000000..e4786b12676 --- /dev/null +++ b/lib/gitlab/auth/result.rb @@ -0,0 +1,17 @@ +module Gitlab + module Auth + Result = Struct.new(:actor, :project, :type, :authentication_abilities) do + def ci? + type == :ci + end + + def lfs_deploy_token? + type == :lfs_deploy_token + end + + def success? + actor.present? || type == :ci + end + end + end +end -- cgit v1.2.3 From 6d43c95b7011ec7ec4600e00bdc8df76bb39813c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 13:38:58 +0200 Subject: Revert all changes introduced by https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6043 --- CHANGELOG | 1 - .../projects/git_http_client_controller.rb | 6 --- app/helpers/lfs_helper.rb | 2 +- doc/workflow/lfs/lfs_administration.md | 4 +- .../lfs/manage_large_binaries_with_git_lfs.md | 8 ---- lib/api/internal.rb | 13 ------ lib/gitlab/auth.rb | 25 ---------- lib/gitlab/auth/result.rb | 4 -- lib/gitlab/lfs_token.rb | 54 ---------------------- spec/lib/gitlab/auth_spec.rb | 18 -------- spec/lib/gitlab/lfs_token_spec.rb | 51 -------------------- spec/requests/api/internal_spec.rb | 46 ------------------ spec/requests/lfs_http_spec.rb | 16 ------- 13 files changed, 3 insertions(+), 245 deletions(-) delete mode 100644 lib/gitlab/lfs_token.rb delete mode 100644 spec/lib/gitlab/lfs_token_spec.rb diff --git a/CHANGELOG b/CHANGELOG index cc54378f9f1..057c4bffda1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -106,7 +106,6 @@ v 8.12.0 (unreleased) - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Fix repo title alignment (ClemMakesApps) - Change update interval of contacted_at - - Add LFS support to SSH !6043 - Fix branch title trailing space on hover (ClemMakesApps) - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index ee9ea4bc8b2..d1a2c52d80a 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -132,12 +132,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController authentication_project == project end - def lfs_deploy_key? - authentication_result.lfs_deploy_token? && - actor && - actor.projects.include?(project) - end - def authentication_has_download_access? has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) end diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 018ca7d7bba..8e827664681 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -25,7 +25,7 @@ module LfsHelper def lfs_download_access? return false unless project.lfs_enabled? - project.public? || ci? || lfs_deploy_key? || user_can_download_code? || build_can_download_code? + project.public? || ci? || user_can_download_code? || build_can_download_code? end def user_can_download_code? diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md index b3c73e947f0..9dc1e9b47e3 100644 --- a/doc/workflow/lfs/lfs_administration.md +++ b/doc/workflow/lfs/lfs_administration.md @@ -45,5 +45,5 @@ In `config/gitlab.yml`: * Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported * Currently, removing LFS objects from GitLab Git LFS storage is not supported -* LFS authentications via SSH was added with GitLab 8.12 -* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2. +* LFS authentications via SSH is not supported for the time being +* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2. diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index 1a4f213a792..9fe065fa680 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -35,10 +35,6 @@ Documentation for GitLab instance administrators is under [LFS administration do credentials store is recommended * Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting) - ->**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication - still goes over HTTP, but now the SSH client passes the correct credentials - to the Git LFS client, so no action is required by the user. ## Using Git LFS @@ -136,10 +132,6 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs" ### Credentials are always required when pushing an object ->**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication - still goes over HTTP, but now the SSH client passes the correct credentials - to the Git LFS client, so no action is required by the user. - Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required. diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 090d04544da..1114fd21784 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -82,19 +82,6 @@ module API response end - post "/lfs_authenticate" do - status 200 - - key = Key.find(params[:key_id]) - token_handler = Gitlab::LfsToken.new(key) - - { - username: token_handler.actor_name, - lfs_token: token_handler.generate, - repository_http_path: project.http_url_to_repo - } - end - get "/merge_request_urls" do ::MergeRequests::GetUrlsService.new(project).execute(params[:changes]) end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index ca2a0920c00..7464d6082cb 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -11,7 +11,6 @@ module Gitlab build_access_token_check(login, password) || user_with_password_for_git(login, password) || oauth_access_token_check(login, password) || - lfs_token_check(login, password) || personal_access_token_check(login, password) || Result.new @@ -103,30 +102,6 @@ module Gitlab end end - def lfs_token_check(login, password) - deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/) - - actor = - if deploy_key_matches - DeployKey.find(deploy_key_matches[1]) - else - User.by_login(login) - end - - if actor - token_handler = Gitlab::LfsToken.new(actor) - - authentication_abilities = - if token_handler.user? - full_authentication_abilities - else - read_authentication_abilities - end - - Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.value, password) - end - end - def build_access_token_check(login, password) return unless login == 'gitlab-ci-token' return unless password diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb index e4786b12676..bf625649cbf 100644 --- a/lib/gitlab/auth/result.rb +++ b/lib/gitlab/auth/result.rb @@ -5,10 +5,6 @@ module Gitlab type == :ci end - def lfs_deploy_token? - type == :lfs_deploy_token - end - def success? actor.present? || type == :ci end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb deleted file mode 100644 index d089a2f9b0b..00000000000 --- a/lib/gitlab/lfs_token.rb +++ /dev/null @@ -1,54 +0,0 @@ -module Gitlab - class LfsToken - attr_accessor :actor - - TOKEN_LENGTH = 50 - EXPIRY_TIME = 1800 - - def initialize(actor) - @actor = - case actor - when DeployKey, User - actor - when Key - actor.user - else - raise 'Bad Actor' - end - end - - def generate - token = Devise.friendly_token(TOKEN_LENGTH) - - Gitlab::Redis.with do |redis| - redis.set(redis_key, token, ex: EXPIRY_TIME) - end - - token - end - - def value - Gitlab::Redis.with do |redis| - redis.get(redis_key) - end - end - - def user? - actor.is_a?(User) - end - - def type - actor.is_a?(User) ? :lfs_token : :lfs_deploy_token - end - - def actor_name - actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}" - end - - private - - def redis_key - "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor - end - end -end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 21f0d46100e..8807a68a0a2 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -61,24 +61,6 @@ describe Gitlab::Auth, lib: true do expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) end - it 'recognizes user lfs tokens' do - user = create(:user) - ip = 'ip' - token = Gitlab::LfsToken.new(user).generate - - expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) - expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, read_authentication_abilities)) - end - - it 'recognizes deploy key lfs tokens' do - key = create(:deploy_key) - ip = 'ip' - token = Gitlab::LfsToken.new(key).generate - - expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}") - expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities)) - end - it 'recognizes OAuth tokens' do user = create(:user) application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb deleted file mode 100644 index 9f04f67e0a8..00000000000 --- a/spec/lib/gitlab/lfs_token_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LfsToken, lib: true do - describe '#generate and #value' do - shared_examples 'an LFS token generator' do - it 'returns a randomly generated token' do - token = handler.generate - - expect(token).not_to be_nil - expect(token).to be_a String - expect(token.length).to eq 50 - end - - it 'returns the correct token based on the key' do - token = handler.generate - - expect(handler.value).to eq(token) - end - end - - context 'when the actor is a user' do - let(:actor) { create(:user) } - let(:handler) { described_class.new(actor) } - - it_behaves_like 'an LFS token generator' - - it 'returns the correct username' do - expect(handler.actor_name).to eq(actor.username) - end - - it 'returns the correct token type' do - expect(handler.type).to eq(:lfs_token) - end - end - - context 'when the actor is a deploy key' do - let(:actor) { create(:deploy_key) } - let(:handler) { described_class.new(actor) } - - it_behaves_like 'an LFS token generator' - - it 'returns the correct username' do - expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}") - end - - it 'returns the correct token type' do - expect(handler.type).to eq(:lfs_deploy_token) - end - end - end -end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 46e8e6f1169..46d1b868782 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -100,43 +100,6 @@ describe API::API, api: true do end end - describe "POST /internal/lfs_authenticate" do - before do - project.team << [user, :developer] - end - - context 'user key' do - it 'returns the correct information about the key' do - lfs_auth(key.id, project) - - expect(response).to have_http_status(200) - expect(json_response['username']).to eq(user.username) - expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value) - - expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) - end - - it 'returns a 404 when the wrong key is provided' do - lfs_auth(nil, project) - - expect(response).to have_http_status(404) - end - end - - context 'deploy key' do - let(:key) { create(:deploy_key) } - - it 'returns the correct information about the key' do - lfs_auth(key.id, project) - - expect(response).to have_http_status(200) - expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}") - expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value) - expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) - end - end - end - describe "GET /internal/discover" do it do get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) @@ -426,13 +389,4 @@ describe API::API, api: true do protocol: 'ssh' ) end - - def lfs_auth(key_id, project) - post( - api("/internal/lfs_authenticate"), - key_id: key_id, - secret_token: secret_token, - project: project.path_with_namespace - ) - end end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 09e4e265dd1..b58d410b7a3 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -245,18 +245,6 @@ describe 'Git LFS API and storage' do end end - context 'when deploy key is authorized' do - let(:key) { create(:deploy_key) } - let(:authorization) { authorize_deploy_key } - - let(:update_permissions) do - project.deploy_keys << key - project.lfs_objects << lfs_object - end - - it_behaves_like 'responds with a file' - end - context 'when build is authorized as' do let(:authorization) { authorize_ci_project } @@ -1109,10 +1097,6 @@ describe 'Git LFS API and storage' do ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) end - def authorize_deploy_key - ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate) - end - def fork_project(project, user, object = nil) allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) Projects::ForkService.new(project, user, {}).execute -- cgit v1.2.3 From dc2968546500af4ea17dd23f851f00e002290bcc Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 13:50:28 +0200 Subject: Properly support Gitlab::Auth::Result --- lib/gitlab/auth.rb | 14 +++++++------- lib/gitlab/auth/result.rb | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 7464d6082cb..0a0f1c3b17b 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -12,7 +12,7 @@ module Gitlab user_with_password_for_git(login, password) || oauth_access_token_check(login, password) || personal_access_token_check(login, password) || - Result.new + Gitlab::Auth::Result.new rate_limit!(ip, success: result.success?, login: login) @@ -70,7 +70,7 @@ module Gitlab service = project.public_send("#{underscored_service}_service") if service && service.activated? && service.valid_token?(password) - Result.new(nil, project, :ci, build_authentication_abilities) + Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities) end end end @@ -81,7 +81,7 @@ module Gitlab raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled? - Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities) + Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities) end def oauth_access_token_check(login, password) @@ -89,7 +89,7 @@ module Gitlab token = Doorkeeper::AccessToken.by_token(password) if token && token.accessible? user = User.find_by(id: token.resource_owner_id) - Result.new(user, nil, :oauth, read_authentication_abilities) + Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities) end end end @@ -98,7 +98,7 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, nil, :personal_token, full_authentication_abilities) if user.present? && user == validation + Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities) if user.present? && user == validation end end @@ -112,10 +112,10 @@ module Gitlab if build.user # If user is assigned to build, use restricted credentials of user - Result.new(build.user, build.project, :build, build_authentication_abilities) + Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities) else # Otherwise use generic CI credentials (backward compatibility) - Result.new(nil, build.project, :ci, build_authentication_abilities) + Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities) end end diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb index bf625649cbf..3ec5765b6b0 100644 --- a/lib/gitlab/auth/result.rb +++ b/lib/gitlab/auth/result.rb @@ -1,6 +1,6 @@ module Gitlab module Auth - Result = Struct.new(:actor, :project, :type, :authentication_abilities) do + class Result < Struct.new(:actor, :project, :type, :authentication_abilities) def ci? type == :ci end -- cgit v1.2.3 From 135be3cabb01ca3c825829f18ede4e8720383d7b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Sep 2016 14:23:18 +0200 Subject: Solve code review comments --- lib/gitlab/auth/result.rb | 2 +- spec/requests/ci/api/builds_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb index 3ec5765b6b0..bf625649cbf 100644 --- a/lib/gitlab/auth/result.rb +++ b/lib/gitlab/auth/result.rb @@ -1,6 +1,6 @@ module Gitlab module Auth - class Result < Struct.new(:actor, :project, :type, :authentication_abilities) + Result = Struct.new(:actor, :project, :type, :authentication_abilities) do def ci? type == :ci end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 09d72fe0a0e..df97f1bf7b6 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -263,6 +263,7 @@ describe Ci::API::API do context "should authorize posting artifact to running build" do it "using token as parameter" do post authorize_url, { token: build.token }, headers + expect(response).to have_http_status(200) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(json_response["TempPath"]).not_to be_nil @@ -270,6 +271,7 @@ describe Ci::API::API do it "using token as header" do post authorize_url, {}, headers_with_token + expect(response).to have_http_status(200) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(json_response["TempPath"]).not_to be_nil @@ -277,6 +279,7 @@ describe Ci::API::API do it "using runners token" do post authorize_url, { token: build.project.runners_token }, headers + expect(response).to have_http_status(200) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(json_response["TempPath"]).not_to be_nil @@ -284,7 +287,9 @@ describe Ci::API::API do it "reject requests that did not go through gitlab-workhorse" do headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) + post authorize_url, { token: build.token }, headers + expect(response).to have_http_status(500) end end @@ -292,13 +297,17 @@ describe Ci::API::API do context "should fail to post too large artifact" do it "using token as parameter" do stub_application_setting(max_artifacts_size: 0) + post authorize_url, { token: build.token, filesize: 100 }, headers + expect(response).to have_http_status(413) end it "using token as header" do stub_application_setting(max_artifacts_size: 0) + post authorize_url, { filesize: 100 }, headers_with_token + expect(response).to have_http_status(413) end end -- cgit v1.2.3 From 028c086f902f56e26db6382caa6131404ce74dcd Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 16 Sep 2016 16:05:12 +0200 Subject: Restrict last_activity_at updates to one per hour The lock in turn is only obtained when actually needed, reducing some load on Redis. Fixes gitlab-org/gitlab-ce#22213 --- CHANGELOG | 1 + app/models/event.rb | 17 ++++++++++++++--- spec/models/event_spec.rb | 43 +++++++++++++++++++++++++++++++++---------- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6093cb09880..0e9c5df09eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.12.0 (unreleased) - Make push events have equal vertical spacing. - Add two-factor recovery endpoint to internal API !5510 - Pass the "Remember me" value to the U2F authentication form + - Only update projects.last_activity_at once per hour when creating a new event - Remove vendor prefixes for linear-gradient CSS (ClemMakesApps) - Move pushes_since_gc from the database to Redis - Add font color contrast to external label in admin area (ClemMakesApps) diff --git a/app/models/event.rb b/app/models/event.rb index a0b7b0dc2b5..b6e8bef3f67 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -13,6 +13,8 @@ class Event < ActiveRecord::Base LEFT = 9 # User left project DESTROYED = 10 + RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour + delegate :name, :email, to: :author, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true delegate :title, to: :merge_request, prefix: true, allow_nil: true @@ -324,8 +326,17 @@ class Event < ActiveRecord::Base end def reset_project_activity - if project && Gitlab::ExclusiveLease.new("project:update_last_activity_at:#{project.id}", timeout: 60).try_obtain - project.update_column(:last_activity_at, self.created_at) - end + return unless project + + # Don't even bother obtaining a lock if the last update happened less than + # 60 minutes ago. + return if project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago + + return unless Gitlab::ExclusiveLease. + new("project:update_last_activity_at:#{project.id}", + timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i). + try_obtain + + project.update_column(:last_activity_at, created_at) end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index b5d0d79e14e..8600eb4d2c4 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -16,18 +16,12 @@ describe Event, models: true do describe 'Callbacks' do describe 'after_create :reset_project_activity' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } - context "project's last activity was less than 5 minutes ago" do - it 'does not update project.last_activity_at if it has been touched less than 5 minutes ago' do - create_event(project, project.owner) - project.update_column(:last_activity_at, 5.minutes.ago) - project_last_activity_at = project.last_activity_at + it 'calls the reset_project_activity method' do + expect_any_instance_of(Event).to receive(:reset_project_activity) - create_event(project, project.owner) - - expect(project.last_activity_at).to eq(project_last_activity_at) - end + create_event(project, project.owner) end end end @@ -161,6 +155,35 @@ describe Event, models: true do end end + describe '#reset_project_activity' do + let(:project) { create(:empty_project) } + + context 'when a project was updated less than 1 hour ago' do + it 'does not update the project' do + project.update(last_activity_at: Time.now) + + expect(project).not_to receive(:update_column). + with(:last_activity_at, a_kind_of(Time)) + + create_event(project, project.owner) + end + end + + context 'when a project was updated more than 1 hour ago' do + it 'updates the project' do + project.update(last_activity_at: 1.year.ago) + + expect_any_instance_of(Gitlab::ExclusiveLease). + to receive(:try_obtain).and_return(true) + + expect(project).to receive(:update_column). + with(:last_activity_at, a_kind_of(Time)) + + create_event(project, project.owner) + end + end + end + def create_event(project, user, attrs = {}) data = { before: Gitlab::Git::BLANK_SHA, -- cgit v1.2.3 From e0067d185005f563b0f8dad9e150d922419c0ea2 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 14 Sep 2016 19:04:27 -0300 Subject: Allow to set request_access_enabled for groups and projects using API --- CHANGELOG | 1 + doc/api/groups.md | 16 +++++-- doc/api/projects.md | 24 +++++++--- lib/api/entities.rb | 2 + lib/api/groups.rb | 26 ++++++----- lib/api/projects.rb | 89 ++++++++++++++++++++------------------ spec/requests/api/groups_spec.rb | 11 ++++- spec/requests/api/projects_spec.rb | 18 ++++++-- 8 files changed, 117 insertions(+), 70 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 20948a8db5e..398329b9f10 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.12.0 (unreleased) - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region - Add ability to fork to a specific namespace using API. (ritave) + - Allow to set request_access_enabled for groups and projects - Cleanup misalignments in Issue list view !6206 - Prune events older than 12 months. (ritave) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) diff --git a/doc/api/groups.md b/doc/api/groups.md index 3e94e1e4efe..e81d6f9de4b 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -84,7 +84,8 @@ Parameters: "forks_count": 0, "open_issues_count": 3, "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "request_access_enabled": false } ] ``` @@ -118,6 +119,7 @@ Example response: "visibility_level": 20, "avatar_url": null, "web_url": "https://gitlab.example.com/groups/twitter", + "request_access_enabled": false, "projects": [ { "id": 7, @@ -163,7 +165,8 @@ Example response: "forks_count": 0, "open_issues_count": 3, "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "request_access_enabled": false }, { "id": 6, @@ -209,7 +212,8 @@ Example response: "forks_count": 0, "open_issues_count": 8, "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "request_access_enabled": false } ], "shared_projects": [ @@ -289,6 +293,7 @@ Parameters: - `description` (optional) - The group's description - `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public. - `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group +- `request_access_enabled` (optional) - Allow users to request member access. ## Transfer project to group @@ -319,6 +324,7 @@ PUT /groups/:id | `description` | string | no | The description of the group | | `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. | | `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group | +| `request_access_enabled` | boolean | no | Allow users to request member access. | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" @@ -336,6 +342,7 @@ Example response: "visibility_level": 10, "avatar_url": null, "web_url": "http://gitlab.example.com/groups/h5bp", + "request_access_enabled": false, "projects": [ { "id": 9, @@ -380,7 +387,8 @@ Example response: "forks_count": 0, "open_issues_count": 3, "public_builds": true, - "shared_with_groups": [] + "shared_with_groups": [], + "request_access_enabled": false } ] } diff --git a/doc/api/projects.md b/doc/api/projects.md index fe3c8709d13..750ce1508df 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -85,7 +85,8 @@ Parameters: "runners_token": "b8547b1dc37721d05889db52fa2f02", "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false + "only_allow_merge_if_build_succeeds": false, + "request_access_enabled": false }, { "id": 6, @@ -146,7 +147,8 @@ Parameters: "runners_token": "b8547b1dc37721d05889db52fa2f02", "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false + "only_allow_merge_if_build_succeeds": false, + "request_access_enabled": false } ] ``` @@ -283,7 +285,8 @@ Parameters: "group_access_level": 10 } ], - "only_allow_merge_if_build_succeeds": false + "only_allow_merge_if_build_succeeds": false, + "request_access_enabled": false } ``` @@ -453,6 +456,7 @@ Parameters: - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) - `lfs_enabled` (optional) +- `request_access_enabled` (optional) - Allow users to request member access. ### Create project for user @@ -480,6 +484,7 @@ Parameters: - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) - `lfs_enabled` (optional) +- `request_access_enabled` (optional) - Allow users to request member access. ### Edit project @@ -508,6 +513,7 @@ Parameters: - `public_builds` (optional) - `only_allow_merge_if_build_succeeds` (optional) - `lfs_enabled` (optional) +- `request_access_enabled` (optional) - Allow users to request member access. On success, method returns 200 with the updated project. If parameters are invalid, 400 is returned. @@ -588,7 +594,8 @@ Example response: "star_count": 1, "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false + "only_allow_merge_if_build_succeeds": false, + "request_access_enabled": false } ``` @@ -655,7 +662,8 @@ Example response: "star_count": 0, "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false + "only_allow_merge_if_build_succeeds": false, + "request_access_enabled": false } ``` @@ -742,7 +750,8 @@ Example response: "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false + "only_allow_merge_if_build_succeeds": false, + "request_access_enabled": false } ``` @@ -829,7 +838,8 @@ Example response: "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b", "public_builds": true, "shared_with_groups": [], - "only_allow_merge_if_build_succeeds": false + "only_allow_merge_if_build_succeeds": false, + "request_access_enabled": false } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index bfee4b6c752..0235ba3d580 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -100,6 +100,7 @@ module API SharedGroup.represent(project.project_group_links.all, options) end expose :only_allow_merge_if_build_succeeds + expose :request_access_enabled end class Member < UserBasic @@ -125,6 +126,7 @@ module API expose :lfs_enabled?, as: :lfs_enabled expose :avatar_url expose :web_url + expose :request_access_enabled end class GroupDetail < Group diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 60ac9bdfa33..953fa474e88 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -23,18 +23,19 @@ module API # Create group. Available only for users who can create groups. # # Parameters: - # name (required) - The name of the group - # path (required) - The path of the group - # description (optional) - The description of the group - # visibility_level (optional) - The visibility level of the group - # lfs_enabled (optional) - Enable/disable LFS for the projects in this group + # name (required) - The name of the group + # path (required) - The path of the group + # description (optional) - The description of the group + # visibility_level (optional) - The visibility level of the group + # lfs_enabled (optional) - Enable/disable LFS for the projects in this group + # request_access_enabled (optional) - Allow users to request member access # Example Request: # POST /groups post do authorize! :create_group required_attributes! [:name, :path] - attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled] + attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled] @group = Group.new(attrs) if @group.save @@ -48,18 +49,19 @@ module API # Update group. Available only for users who can administrate groups. # # Parameters: - # id (required) - The ID of a group - # path (optional) - The path of the group - # description (optional) - The description of the group - # visibility_level (optional) - The visibility level of the group - # lfs_enabled (optional) - Enable/disable LFS for the projects in this group + # id (required) - The ID of a group + # path (optional) - The path of the group + # description (optional) - The description of the group + # visibility_level (optional) - The visibility level of the group + # lfs_enabled (optional) - Enable/disable LFS for the projects in this group + # request_access_enabled (optional) - Allow users to request member access # Example Request: # PUT /groups/:id put ':id' do group = find_group(params[:id]) authorize! :admin_group, group - attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled] + attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled, :request_access_enabled] if ::Groups::UpdateService.new(group, current_user, attrs).execute present group, with: Entities::GroupDetail diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 644d836ed0b..5eb83c2c8f8 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -91,8 +91,8 @@ module API # Create new project # # Parameters: - # name (required) - name for new project - # description (optional) - short project description + # name (required) - name for new project + # description (optional) - short project description # issues_enabled (optional) # merge_requests_enabled (optional) # builds_enabled (optional) @@ -100,33 +100,35 @@ module API # snippets_enabled (optional) # container_registry_enabled (optional) # shared_runners_enabled (optional) - # namespace_id (optional) - defaults to user namespace - # public (optional) - if true same as setting visibility_level = 20 - # visibility_level (optional) - 0 by default + # namespace_id (optional) - defaults to user namespace + # public (optional) - if true same as setting visibility_level = 20 + # visibility_level (optional) - 0 by default # import_url (optional) # public_builds (optional) # lfs_enabled (optional) + # request_access_enabled (optional) - Allow users to request member access # Example Request # POST /projects post do required_attributes! [:name] - attrs = attributes_for_keys [:name, - :path, + attrs = attributes_for_keys [:builds_enabled, + :container_registry_enabled, :description, + :import_url, :issues_enabled, + :lfs_enabled, :merge_requests_enabled, - :builds_enabled, - :wiki_enabled, - :snippets_enabled, - :container_registry_enabled, - :shared_runners_enabled, + :name, :namespace_id, + :only_allow_merge_if_build_succeeds, + :path, :public, - :visibility_level, - :import_url, :public_builds, - :only_allow_merge_if_build_succeeds, - :lfs_enabled] + :request_access_enabled, + :shared_runners_enabled, + :snippets_enabled, + :visibility_level, + :wiki_enabled] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(current_user, attrs).execute if @project.saved? @@ -143,10 +145,10 @@ module API # Create new project for a specified user. Only available to admin users. # # Parameters: - # user_id (required) - The ID of a user - # name (required) - name for new project - # description (optional) - short project description - # default_branch (optional) - 'master' by default + # user_id (required) - The ID of a user + # name (required) - name for new project + # description (optional) - short project description + # default_branch (optional) - 'master' by default # issues_enabled (optional) # merge_requests_enabled (optional) # builds_enabled (optional) @@ -154,31 +156,33 @@ module API # snippets_enabled (optional) # container_registry_enabled (optional) # shared_runners_enabled (optional) - # public (optional) - if true same as setting visibility_level = 20 + # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) # import_url (optional) # public_builds (optional) # lfs_enabled (optional) + # request_access_enabled (optional) - Allow users to request member access # Example Request # POST /projects/user/:user_id post "user/:user_id" do authenticated_as_admin! user = User.find(params[:user_id]) - attrs = attributes_for_keys [:name, - :description, + attrs = attributes_for_keys [:builds_enabled, :default_branch, + :description, + :import_url, :issues_enabled, + :lfs_enabled, :merge_requests_enabled, - :builds_enabled, - :wiki_enabled, - :snippets_enabled, - :shared_runners_enabled, + :name, + :only_allow_merge_if_build_succeeds, :public, - :visibility_level, - :import_url, :public_builds, - :only_allow_merge_if_build_succeeds, - :lfs_enabled] + :request_access_enabled, + :shared_runners_enabled, + :snippets_enabled, + :visibility_level, + :wiki_enabled] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(user, attrs).execute if @project.saved? @@ -242,22 +246,23 @@ module API # Example Request # PUT /projects/:id put ':id' do - attrs = attributes_for_keys [:name, - :path, - :description, + attrs = attributes_for_keys [:builds_enabled, + :container_registry_enabled, :default_branch, + :description, :issues_enabled, + :lfs_enabled, :merge_requests_enabled, - :builds_enabled, - :wiki_enabled, - :snippets_enabled, - :container_registry_enabled, - :shared_runners_enabled, + :name, + :only_allow_merge_if_build_succeeds, + :path, :public, - :visibility_level, :public_builds, - :only_allow_merge_if_build_succeeds, - :lfs_enabled] + :request_access_enabled, + :shared_runners_enabled, + :snippets_enabled, + :visibility_level, + :wiki_enabled] attrs = map_public_to_visibility_level(attrs) authorize_admin_project authorize! :rename_project, user_project if attrs[:name].present? diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 4860b23c2ed..1f68ef1af8f 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -120,10 +120,11 @@ describe API::API, api: true do context 'when authenticated as the group owner' do it 'updates the group' do - put api("/groups/#{group1.id}", user1), name: new_group_name + put api("/groups/#{group1.id}", user1), name: new_group_name, request_access_enabled: true expect(response).to have_http_status(200) expect(json_response['name']).to eq(new_group_name) + expect(json_response['request_access_enabled']).to eq(true) end it 'returns 404 for a non existing group' do @@ -238,8 +239,14 @@ describe API::API, api: true do context "when authenticated as user with group permissions" do it "creates group" do - post api("/groups", user3), attributes_for(:group) + group = attributes_for(:group, { request_access_enabled: false }) + + post api("/groups", user3), group expect(response).to have_http_status(201) + + expect(json_response["name"]).to eq(group[:name]) + expect(json_response["path"]).to eq(group[:path]) + expect(json_response["request_access_enabled"]).to eq(group[:request_access_enabled]) end it "does not create group, duplicate" do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 28aa56e8644..192c7d14c13 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -225,7 +225,8 @@ describe API::API, api: true do issues_enabled: false, merge_requests_enabled: false, wiki_enabled: false, - only_allow_merge_if_build_succeeds: false + only_allow_merge_if_build_succeeds: false, + request_access_enabled: true }) post api('/projects', user), project @@ -352,7 +353,8 @@ describe API::API, api: true do description: FFaker::Lorem.sentence, issues_enabled: false, merge_requests_enabled: false, - wiki_enabled: false + wiki_enabled: false, + request_access_enabled: true }) post api("/projects/user/#{user.id}", admin), project @@ -887,6 +889,15 @@ describe API::API, api: true do expect(json_response['message']['name']).to eq(['has already been taken']) end + it 'updates request_access_enabled' do + project_param = { request_access_enabled: false } + + put api("/projects/#{project.id}", user), project_param + + expect(response).to have_http_status(200) + expect(json_response['request_access_enabled']).to eq(false) + end + it 'updates path & name to existing path & name in different namespace' do project_param = { path: project4.path, name: project4.name } put api("/projects/#{project3.id}", user), project_param @@ -948,7 +959,8 @@ describe API::API, api: true do wiki_enabled: true, snippets_enabled: true, merge_requests_enabled: true, - description: 'new description' } + description: 'new description', + request_access_enabled: true } put api("/projects/#{project.id}", user3), project_param expect(response).to have_http_status(403) end -- cgit v1.2.3 From 376ccc3835efbb04fd37f6daaccd3c7847edd04f Mon Sep 17 00:00:00 2001 From: Sid Sijbrandij Date: Mon, 19 Sep 2016 15:41:36 +0000 Subject: Update README.md to fix icon --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d70653eb91e..cedff9f16d8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) -![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) +[!Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) ## Canonical source -- cgit v1.2.3 From 7509e5a71f98b76ce2c2d5624dee99abfd25a3a2 Mon Sep 17 00:00:00 2001 From: Sid Sijbrandij Date: Mon, 19 Sep 2016 15:42:37 +0000 Subject: Update README.md to really fix icon --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cedff9f16d8..9672044da94 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) -[!Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) +[![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) ## Canonical source -- cgit v1.2.3 From bb6b1659adb74cec3991320f7dcb4b537c57b13a Mon Sep 17 00:00:00 2001 From: Sid Sijbrandij Date: Mon, 19 Sep 2016 15:43:44 +0000 Subject: Contributing via GH no longer encouraged. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9672044da94..9661a554b9f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Canonical source -The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. +The cannonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). ## Open source software to collaborate on code -- cgit v1.2.3 From 313f3181cc75b8d12468d03e6aa7ed2bf5bba871 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 20 Sep 2016 07:20:48 +0300 Subject: Remove trailing spaces from messages in Gitlab::Regex --- lib/gitlab/regex.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index bc8bbf337f3..776bbcbb5d0 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -44,7 +44,7 @@ module Gitlab end def file_name_regex_message - "can contain only letters, digits, '_', '-', '@' and '.'. " + "can contain only letters, digits, '_', '-', '@' and '.'." end def file_path_regex @@ -52,7 +52,7 @@ module Gitlab end def file_path_regex_message - "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. " + "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'." end def directory_traversal_regex @@ -60,7 +60,7 @@ module Gitlab end def directory_traversal_regex_message - "cannot include directory traversal. " + "cannot include directory traversal." end def archive_formats_regex -- cgit v1.2.3 From cf9ee8fda7dbb42f388f0044b22bd3ff2b30048c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 19 Sep 2016 19:59:29 -0700 Subject: Fix broken spec due to last_activity_at updates being throttled In https://gitlab.com/gitlab-org/gitlab-ce/builds/4218398, the build failed because the last_activity_at column was only being updated once per hour. We can fix this spec by stubbing out the throttling and adjusting the spec to test the right event timestamp. --- app/models/event.rb | 18 ++++++++++++++---- spec/factories/events.rb | 5 +++-- spec/models/project_spec.rb | 13 ++++++++----- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index b6e8bef3f67..55a76e26f3c 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -330,13 +330,23 @@ class Event < ActiveRecord::Base # Don't even bother obtaining a lock if the last update happened less than # 60 minutes ago. - return if project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago + return if recent_update? - return unless Gitlab::ExclusiveLease. + return unless try_obtain_lease + + project.update_column(:last_activity_at, created_at) + end + + private + + def recent_update? + project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago + end + + def try_obtain_lease + Gitlab::ExclusiveLease. new("project:update_last_activity_at:#{project.id}", timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i). try_obtain - - project.update_column(:last_activity_at, created_at) end end diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 90788f30ac9..8820d527c61 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -1,10 +1,11 @@ FactoryGirl.define do factory :event do + project + author factory: :user + factory :closed_issue_event do - project action { Event::CLOSED } target factory: :closed_issue - author factory: :user end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 7ca1bd1e5c9..a388ff703a6 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -308,20 +308,23 @@ describe Project, models: true do end describe 'last_activity methods' do - let(:project) { create(:project) } - let(:last_event) { double(created_at: Time.now) } + let(:timestamp) { Time.now - 2.hours } + let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) } describe 'last_activity' do it 'alias last_activity to last_event' do - allow(project).to receive(:last_event).and_return(last_event) + last_event = create(:event, project: project) + expect(project.last_activity).to eq(last_event) end end describe 'last_activity_date' do it 'returns the creation date of the project\'s last event if present' do - create(:event, project: project) - expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i) + expect_any_instance_of(Event).to receive(:try_obtain_lease).and_return(true) + new_event = create(:event, project: project, created_at: Time.now) + + expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i) end it 'returns the project\'s last update date if it has no events' do -- cgit v1.2.3